From 21f636fc2166478deca911d17928a6d0df19f3e1 Mon Sep 17 00:00:00 2001 From: Flavian DESVERNE Date: Wed, 27 Nov 2019 19:25:13 +0100 Subject: [PATCH] feat: scaffold tsconfig is none is present --- package.json | 1 + src/cli/commands/build.ts | 12 ++-- src/cli/commands/dev.tsx | 9 ++- src/cli/commands/doctor.ts | 23 ++++++- src/framework/nexus.ts | 2 - src/utils/path.ts | 14 ++-- src/utils/tsc.ts | 64 ++++++++++++++++- test/__helpers/run.ts | 9 ++- test/__helpers/utils.ts | 10 +++ .../__snapshots__/build.spec.ts.snap | 26 +++---- test/integration/doctor.spec.ts | 68 ++++++++++++++++++- .../prisma/__snapshots__/build.spec.ts.snap | 33 +++++++++ yarn.lock | 12 ++++ 13 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 test/integration/prisma/__snapshots__/build.spec.ts.snap diff --git a/package.json b/package.json index 2e9f70553..fa010d9d2 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "globby": "^10", "jest": "^24.9.0", "prettier": "^1.18.2", + "strip-ansi": "^6.0.0", "ts-jest": "^24.1.0" }, "engines": { diff --git a/src/cli/commands/build.ts b/src/cli/commands/build.ts index c6c9d0e68..d41cdf1db 100644 --- a/src/cli/commands/build.ts +++ b/src/cli/commands/build.ts @@ -1,16 +1,17 @@ import * as fs from 'fs-jetpack' +import { BUILD_FOLDER_NAME } from '../../constants' import { scan } from '../../framework/layout' import { runPrismaGenerators } from '../../framework/plugins' +import { createStartModuleContent } from '../../framework/start' import { compile, + findOrScaffoldTsConfig, generateArtifacts, + pog, readTsConfig, transpileModule, - pog, } from '../../utils' -import { createStartModuleContent } from '../../framework/start' import { Command } from '../helpers' -import { BUILD_FOLDER_NAME } from '../../constants' const log = pog.sub('cli:build') @@ -22,10 +23,11 @@ export class Build implements Command { async parse(argv: string[]) { // Handle Prisma integration // TODO pluggable CLI - await runPrismaGenerators() - const layout = await scan() + await findOrScaffoldTsConfig(layout) + await runPrismaGenerators() + log('running typegen') console.log('🎃 Generating Nexus artifacts ...') await generateArtifacts( diff --git a/src/cli/commands/dev.tsx b/src/cli/commands/dev.tsx index eb837620f..f74c75b26 100644 --- a/src/cli/commands/dev.tsx +++ b/src/cli/commands/dev.tsx @@ -2,11 +2,11 @@ import { Box, Instance, render } from 'ink' import React from 'react' import * as readline from 'readline' import { Layout, scan } from '../../framework/layout' +import { runPrismaGenerators } from '../../framework/plugins' import { createStartModuleContent } from '../../framework/start' -import { pog } from '../../utils' +import { findOrScaffoldTsConfig, pog } from '../../utils' import { createWatcher } from '../../watcher' import { Command } from '../helpers' -import { runPrismaGenerators } from '../../framework/plugins' const log = pog.sub('cli:dev') @@ -23,7 +23,10 @@ export class Dev implements Command { process.exit(0) } - const [layout] = await Promise.all([scan(), runPrismaGenerators()]) + const layout = await scan() + + await findOrScaffoldTsConfig(layout) + await runPrismaGenerators() // Setup ui/log toggling system let state: diff --git a/src/cli/commands/doctor.ts b/src/cli/commands/doctor.ts index 15b0fe45b..ba911c93d 100644 --- a/src/cli/commands/doctor.ts +++ b/src/cli/commands/doctor.ts @@ -1,5 +1,8 @@ import Git from 'simple-git/promise' +import { scan } from '../../framework/layout' +import { findOrScaffoldTsConfig } from '../../utils' import { Command } from '../helpers' +import chalk from 'chalk' export class Doctor implements Command { public static new(): Doctor { @@ -10,12 +13,28 @@ export class Doctor implements Command { const frameworkDotFolder = '.pumpkins' const git = Git(process.cwd()) + console.log(chalk.bold('-- .gitignore --')) if (await git.checkIsRepo()) { if ((await git.checkIgnore([frameworkDotFolder])).length === 0) { - console.log(`please add ${frameworkDotFolder} to your gitignore file`) + console.log( + chalk`{yellow Warning:} Please add ${frameworkDotFolder} to your gitignore file` + ) } else { - console.log(`ok ${frameworkDotFolder} is git-ignored correctly`) + console.log( + chalk`{green OK:} ${frameworkDotFolder} is git-ignored correctly` + ) } } + + console.log(chalk.bold('-- tsconfig.json --')) + const layout = await scan() + const result = await findOrScaffoldTsConfig(layout, { + exitAfterError: false, + }) + if (result === 'success') { + console.log( + chalk`{green OK:} "tsconfig.json" is present and in the right directory` + ) + } } } diff --git a/src/framework/nexus.ts b/src/framework/nexus.ts index 782db7ced..53ac29665 100644 --- a/src/framework/nexus.ts +++ b/src/framework/nexus.ts @@ -2,8 +2,6 @@ import * as nexus from 'nexus' import { generateSchema } from 'nexus/dist/core' import * as fs from 'fs-jetpack' import * as Nexus from 'nexus' -import * as path from 'path' -import { findProjectDir } from '../utils' export function createNexusSingleton() { const __types: any[] = [] diff --git a/src/utils/path.ts b/src/utils/path.ts index 7bd718915..1a24ee54e 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -49,19 +49,13 @@ export const writeCachedFile = async ( } export function findProjectDir() { - let filePath = findConfigFile('package.json', { required: false }) + let packageJsonPath = findConfigFile('package.json', { required: false }) - if (!filePath) { - filePath = findConfigFile('tsconfig.json', { required: false }) + if (packageJsonPath) { + return path.dirname(packageJsonPath) } - if (!filePath) { - throw new Error( - 'Could not find the project directory. A "package.json" or "tsconfig.json" file is required.' - ) - } - - return path.dirname(filePath) + return process.cwd() } // build/index.js => index.ts diff --git a/src/utils/tsc.ts b/src/utils/tsc.ts index d9a9c2a94..8b8b6570f 100644 --- a/src/utils/tsc.ts +++ b/src/utils/tsc.ts @@ -1,6 +1,10 @@ -import * as ts from 'typescript' +import * as fs from 'fs-jetpack' import * as path from 'path' +import * as ts from 'typescript' import { BUILD_FOLDER_NAME } from '../constants' +import { Layout } from '../framework/layout' +import { findProjectDir } from './path' +import chalk = require('chalk') const diagnosticHost: ts.FormatDiagnosticsHost = { getNewLine: () => ts.sys.newLine, @@ -112,3 +116,61 @@ export function transpileModule( ): string { return ts.transpileModule(input, { compilerOptions }).outputText } + +/** + * Find or scaffold a tsconfig.json file + * Process will exit if package.json is not in the projectDir** + */ +export async function findOrScaffoldTsConfig( + layout: Layout, + options: { exitAfterError: boolean } = { exitAfterError: true } +): Promise<'success' | 'warning' | 'error'> { + const tsConfigPath = findConfigFile('tsconfig.json', { required: false }) + const projectDir = findProjectDir() + + if (tsConfigPath) { + if (path.dirname(tsConfigPath) !== projectDir) { + console.error( + chalk`{red ERROR:} Your tsconfig.json file needs to be in your project root directory` + ) + console.error( + chalk`{red ERROR:} Found ${tsConfigPath}, expected ${path.join( + projectDir, + 'tsconfig.json' + )}` + ) + if (options.exitAfterError) { + process.exit(1) + } else { + return 'error' + } + } + } + + if (!tsConfigPath) { + console.log(` +${chalk.yellow('Warning:')} We could not find a "tsconfig.json" file. +${chalk.yellow('Warning:')} We scaffolded one for you at ${path.join( + projectDir, + 'tsconfig.json' + )}. + `) + + const tsConfigContent = `\ +{ + "target": "es2016", + "module": "commonjs", + "lib": ["esnext"], + "rootDir": "${path.relative(projectDir, layout.sourceRoot)}", + "outDir": "${BUILD_FOLDER_NAME}", + "skipLibCheck": true, + "strict": true, +} +` + const tsConfigPath = path.join(projectDir, 'tsconfig.json') + await fs.writeAsync(tsConfigPath, tsConfigContent) + return 'warning' + } + + return 'success' +} diff --git a/test/__helpers/run.ts b/test/__helpers/run.ts index 3ec89b81d..3e3e67639 100644 --- a/test/__helpers/run.ts +++ b/test/__helpers/run.ts @@ -1,7 +1,12 @@ import { spawnSync, SpawnSyncOptions } from 'child_process' import * as path from 'path' +import { withoutColors } from './utils' -type RunResult = { stderr: string; stdout: string; status: null | number } +export type RunResult = { + stderr: string + stdout: string + status: null | number +} type RunOptions = Omit & { require?: boolean } @@ -26,7 +31,7 @@ export const run = (command: string, options?: RunOptions): RunResult => { `) } - return { stderr, stdout, status } + return withoutColors({ stderr, stdout, status }) } export const createRunner = (cwd: string): typeof run => { diff --git a/test/__helpers/utils.ts b/test/__helpers/utils.ts index 9cf214944..c19c850b9 100644 --- a/test/__helpers/utils.ts +++ b/test/__helpers/utils.ts @@ -1,4 +1,6 @@ import { SimpleGit } from 'simple-git/promise' +import stripAnsi from 'strip-ansi' +import { RunResult } from './run' export async function gitReset(git: SimpleGit) { await Promise.all([ @@ -12,3 +14,11 @@ export async function gitRepo(git: SimpleGit) { await git.raw(['add', '-A']) await git.raw(['commit', '--allow-empty', '--message', 'initial commit']) } + +export function withoutColors(result: RunResult): RunResult { + return { + status: result.status, + stdout: stripAnsi(result.stdout), + stderr: stripAnsi(result.stderr), + } +} diff --git a/test/integration/__snapshots__/build.spec.ts.snap b/test/integration/__snapshots__/build.spec.ts.snap index 5733f56c4..25a26e43c 100644 --- a/test/integration/__snapshots__/build.spec.ts.snap +++ b/test/integration/__snapshots__/build.spec.ts.snap @@ -16,7 +16,7 @@ Object { "children": Array [ Object { "name": "app.js", - "size": 319, + "size": 370, "type": "file", }, Object { @@ -26,17 +26,17 @@ Object { }, Object { "name": "schema.js", - "size": 366, + "size": 499, "type": "file", }, Object { "name": "start.js", - "size": 625, + "size": 480, "type": "file", }, ], - "name": "dist", - "size": 1578, + "name": ".build", + "size": 1617, "type": "dir", } `; @@ -57,17 +57,17 @@ Object { "children": Array [ Object { "name": "a.js", - "size": 95, + "size": 213, "type": "file", }, Object { "name": "start.js", - "size": 472, + "size": 509, "type": "file", }, ], - "name": "build", - "size": 567, + "name": ".build", + "size": 722, "type": "dir", } `; @@ -88,17 +88,17 @@ Object { "children": Array [ Object { "name": "schema.js", - "size": 95, + "size": 213, "type": "file", }, Object { "name": "start.js", - "size": 470, + "size": 507, "type": "file", }, ], - "name": "build", - "size": 565, + "name": ".build", + "size": 720, "type": "dir", } `; diff --git a/test/integration/doctor.spec.ts b/test/integration/doctor.spec.ts index 2c9849011..9b9f3f88b 100644 --- a/test/integration/doctor.spec.ts +++ b/test/integration/doctor.spec.ts @@ -11,7 +11,10 @@ it('warns if .pumpkins is not git-ignored', () => { Object { "status": 0, "stderr": "", - "stdout": "please add .pumpkins to your gitignore file + "stdout": "-- .gitignore -- + Warning: Please add .pumpkins to your gitignore file + -- tsconfig.json -- + OK: \\"tsconfig.json\\" is present and in the right directory ", } `) @@ -23,7 +26,68 @@ it('validates if .pumpkins is git-ignored', () => { Object { "status": 0, "stderr": "", - "stdout": "ok .pumpkins is git-ignored correctly + "stdout": "-- .gitignore -- + OK: .pumpkins is git-ignored correctly + -- tsconfig.json -- + OK: \\"tsconfig.json\\" is present and in the right directory + ", + } + `) +}) + +it('warns and scaffold if there is no tsconfig', () => { + ws.fs.write('.gitignore', '.pumpkins') + ws.fs.remove('tsconfig.json') + + expect(ws.run('yarn -s pumpkins doctor')).toMatchInlineSnapshot(` + Object { + "status": 0, + "stderr": "", + "stdout": "-- .gitignore -- + OK: .pumpkins is git-ignored correctly + -- tsconfig.json -- + + Warning: We could not find a \\"tsconfig.json\\" file. + Warning: We scaffolded one for you at /private/tmp/pumpkins-integration-test-project-bases/doctor-v5-yarnlock-319895dfc0c76e09f06a149233eb6be6-gitbranch-master-testv1/tsconfig.json. + + ", + } + `) +}) + +it('errors if tsconfig is not in the project dir', () => { + ws.fs.write('.gitignore', '.pumpkins') + ws.fs.remove('tsconfig.json') + ws.fs.write('../tsconfig.json', '') + + expect(ws.run('yarn -s pumpkins doctor', { require: false })) + .toMatchInlineSnapshot(` + Object { + "status": 0, + "stderr": "ERROR: Your tsconfig.json file needs to be in your project root directory + ERROR: Found /private/tmp/pumpkins-integration-test-project-bases/tsconfig.json, expected /private/tmp/pumpkins-integration-test-project-bases/doctor-v5-yarnlock-319895dfc0c76e09f06a149233eb6be6-gitbranch-master-testv1/tsconfig.json + ", + "stdout": "-- .gitignore -- + OK: .pumpkins is git-ignored correctly + -- tsconfig.json -- + ", + } + `) + + ws.fs.remove('../tsconfig.json') +}) + +it('validates that there is a tsconfig.json file', () => { + ws.fs.write('.gitignore', '.pumpkins') + + expect(ws.run('yarn -s pumpkins doctor')).toMatchInlineSnapshot(` + Object { + "status": 0, + "stderr": "", + "stdout": "-- .gitignore -- + OK: .pumpkins is git-ignored correctly + -- tsconfig.json -- + OK: \\"tsconfig.json\\" is present and in the right directory ", } `) diff --git a/test/integration/prisma/__snapshots__/build.spec.ts.snap b/test/integration/prisma/__snapshots__/build.spec.ts.snap new file mode 100644 index 000000000..c0fc61d77 --- /dev/null +++ b/test/integration/prisma/__snapshots__/build.spec.ts.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can build a prisma framework project 1`] = ` +Object { + "status": 0, + "stderr": "", + "stdout": "🎃 Running Prisma generators ... +🎃 Generating Nexus artifacts ... +🎃 Compiling ... +🎃 Pumpkins server successfully compiled! +", +} +`; + +exports[`can build a prisma framework project 2`] = ` +Object { + "children": Array [ + Object { + "name": "schema.js", + "size": 239, + "type": "file", + }, + Object { + "name": "start.js", + "size": 507, + "type": "file", + }, + ], + "name": ".build", + "size": 746, + "type": "dir", +} +`; diff --git a/yarn.lock b/yarn.lock index 24ee43e59..8b4a3b253 100644 --- a/yarn.lock +++ b/yarn.lock @@ -917,6 +917,11 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -5480,6 +5485,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"