From e3341e8d099e0b5b3d16fa0a441a2d231ad95ea8 Mon Sep 17 00:00:00 2001 From: lgandecki Date: Tue, 22 Aug 2023 15:25:44 +0200 Subject: [PATCH] feat: working eject command straight from chimp --- oclif.manifest.json | 150 +++++++++++++++++++++++++++- src/commands/eject.ts | 149 +++++++++++++++++++++++++++ src/generate/helpers/execQuietly.ts | 4 +- 3 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 src/commands/eject.ts diff --git a/oclif.manifest.json b/oclif.manifest.json index 078a9f3c..09bca488 100644 --- a/oclif.manifest.json +++ b/oclif.manifest.json @@ -1 +1,149 @@ -{"version":"0.0.0-development","commands":{"create":{"id":"create","description":"describe the command here","pluginName":"chimp","pluginType":"core","aliases":[],"examples":["$ chimp create my-new-app","$ chimp create my-new-app -a ~src -g ~chimp-helpers"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"appPrefix":{"name":"appPrefix","type":"option","char":"a","description":"prefix that points to the sourcecode of your app","default":"~app"},"generatedPrefix":{"name":"generatedPrefix","type":"option","char":"g","description":"prefix that points to the generated by chimp helper code","default":"~generated"}},"args":[{"name":"name","description":"name of the new app, also used as the directory"}]},"generate":{"id":"generate","description":"generate GraphQL code","pluginName":"chimp","pluginType":"core","aliases":[],"examples":["$ chimp generate","$ chimp generate -a ~src -g ~chimp-helpers"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"appPrefix":{"name":"appPrefix","type":"option","char":"a","description":"prefix that points to the sourcecode of your app","default":"~app"},"generatedPrefix":{"name":"generatedPrefix","type":"option","char":"g","description":"prefix that points to the generated by chimp helper code","default":"~generated"},"modulesPath":{"name":"modulesPath","type":"option","char":"p","description":"path to the graphQL modules, only use if you are migrating an existing Apollo App and you want to use chimp only for a part of it"}},"args":[]},"init":{"id":"init","description":"init Chimp","pluginName":"chimp","pluginType":"core","aliases":[],"examples":["$ chimp init","$ chimp init -p ./src/chimp-modules"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"modulesPath":{"name":"modulesPath","type":"option","char":"p","description":"path to the GraphQL modules.","default":"./src/modules"}},"args":[]}}} \ No newline at end of file +{ + "version": "0.0.0-development", + "commands": { + "create": { + "id": "create", + "description": "create (scaffold) a new app", + "strict": true, + "pluginName": "chimp", + "pluginAlias": "chimp", + "pluginType": "core", + "aliases": [], + "examples": [ + "$ chimp create my-new-app", + "$ chimp create my-new-app -a ~src -g ~chimp-helpers" + ], + "flags": { + "help": { + "name": "help", + "type": "boolean", + "char": "h", + "description": "Show CLI help.", + "allowNo": false + }, + "appPrefix": { + "name": "appPrefix", + "type": "option", + "char": "a", + "description": "prefix that points to the sourcecode of your app", + "multiple": false, + "default": "~app" + }, + "generatedPrefix": { + "name": "generatedPrefix", + "type": "option", + "char": "g", + "description": "prefix that points to the generated by chimp helper code", + "multiple": false, + "default": "~generated" + } + }, + "args": { + "name": { + "name": "name", + "description": "name of the new app, also used as the directory", + "required": true + } + } + }, + "eject": { + "id": "eject", + "description": "eject from chimp", + "strict": true, + "pluginName": "chimp", + "pluginAlias": "chimp", + "pluginType": "core", + "aliases": [], + "examples": [ + "$ chimp eject" + ], + "flags": { + "help": { + "name": "help", + "type": "boolean", + "char": "h", + "description": "Show CLI help.", + "allowNo": false + } + }, + "args": {} + }, + "generate": { + "id": "generate", + "description": "generate GraphQL code", + "strict": true, + "pluginName": "chimp", + "pluginAlias": "chimp", + "pluginType": "core", + "aliases": [], + "examples": [ + "$ chimp generate", + "$ chimp generate -a ~src -g ~chimp-helpers" + ], + "flags": { + "help": { + "name": "help", + "type": "boolean", + "char": "h", + "description": "Show CLI help.", + "allowNo": false + }, + "appPrefix": { + "name": "appPrefix", + "type": "option", + "char": "a", + "description": "prefix that points to the sourcecode of your app", + "multiple": false, + "default": "~app" + }, + "generatedPrefix": { + "name": "generatedPrefix", + "type": "option", + "char": "g", + "description": "prefix that points to the generated by chimp helper code", + "multiple": false, + "default": "~generated" + }, + "modulesPath": { + "name": "modulesPath", + "type": "option", + "char": "p", + "description": "path to the graphQL modules, only use if you are migrating an existing Apollo App and you want to use chimp only for a part of it", + "multiple": false + } + }, + "args": {} + }, + "init": { + "id": "init", + "description": "init Chimp", + "strict": true, + "pluginName": "chimp", + "pluginAlias": "chimp", + "pluginType": "core", + "aliases": [], + "examples": [ + "$ chimp init", + "$ chimp init -p ./src/chimp-modules" + ], + "flags": { + "help": { + "name": "help", + "type": "boolean", + "char": "h", + "description": "Show CLI help.", + "allowNo": false + }, + "modulesPath": { + "name": "modulesPath", + "type": "option", + "char": "p", + "description": "path to the GraphQL modules.", + "multiple": false, + "default": "./src/modules" + } + }, + "args": {} + } + } +} \ No newline at end of file diff --git a/src/commands/eject.ts b/src/commands/eject.ts new file mode 100644 index 00000000..39da2d65 --- /dev/null +++ b/src/commands/eject.ts @@ -0,0 +1,149 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { Command, Flags } from '@oclif/core'; +import shell from 'shelljs'; +import { execQuietly } from '../generate/helpers/execQuietly'; +import { findProjectMainPath } from '../generate/helpers/findProjectMainPath'; +import { newTask, setupListr } from '../generate/helpers/ListrHelper'; +import { assertGitCleanState } from '../init/assert-git-clean-state'; + +function installingTooling(projectMainPath: string): Promise { + function determinePackageManager() { + if (fs.existsSync('./yarn.lock')) { + return 'yarn'; + } + + if (fs.existsSync('./pnpm-lock.yaml')) { + return 'pnpm'; + } + + return 'npm'; + } + + function installPackage(packageManager: 'yarn' | 'pnpm' | 'npm', packageName: string) { + let command; + + switch (packageManager) { + case 'yarn': + command = `yarn add --dev ${packageName}`; + break; + case 'pnpm': + command = `pnpm add --save-dev ${packageName}`; + break; + case 'npm': + default: + command = `npm install --save-dev ${packageName}`; + break; + } + + console.log('installing packages'); + return execQuietly(command, { cwd: projectMainPath }); + } + + const packageManager = determinePackageManager(); + return installPackage(packageManager, '@graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/merge'); +} + +function moveCode(projectMainPath: string, chimpMainPath: string) { + shell.cp( + path.join(chimpMainPath, 'src/generate/templates/ejectedSchema.ts'), + path.join(projectMainPath, 'src/schema.ts'), + ); + + // Move genericDataModelSchema.graphql + shell.mv( + path.join(projectMainPath, 'generated/graphql/genericDataModelSchema.graphql'), + path.join(projectMainPath, 'src/genericDataModelSchema.graphql'), + ); + + // Move types.ts + shell.mv(path.join(projectMainPath, 'generated/graphql/types.ts'), path.join(projectMainPath, 'src/graphqlTypes.ts')); + + for (const resolverFile of shell.ls(path.join(projectMainPath, 'generated/graphql/*Resolvers.ts'))) { + const fileName = resolverFile.split('/').pop(); + const moduleName = fileName!.replace('Resolvers.ts', ''); + const targetDir = path.join(projectMainPath, `src/modules/${moduleName}/graphql/`); + + shell.mv(resolverFile, targetDir); + } + + // Move resolvers.ts + shell.mv( + path.join(projectMainPath, 'generated/graphql/resolvers.ts'), + path.join(projectMainPath, 'src/resolvers.ts'), + ); + + // Move schema.ts + shell.rm(path.join(projectMainPath, 'generated/graphql/schema.ts')); + + // Move helpers + for (const helperFile of shell.ls(path.join(projectMainPath, 'generated/graphql/helpers/*.ts'))) { + const fileName = helperFile.split('/').pop()!.replace('SpecWrapper.ts', ''); + const importPath = shell.grep(`import { ${fileName} }`, helperFile).replace(/.*from\s+["']([^"']+)["'].*/, '$1'); + const resolvedPath = importPath.replace('~app/modules', path.join(projectMainPath, 'src/modules')); + const targetDir = `${resolvedPath.replace(fileName, '').replace('\n', '')}test-helpers`; + + shell.mkdir('-p', targetDir); + shell.mv(helperFile, `${targetDir}/${fileName}SpecWrapper.ts`); + } + + // Update imports in resolvers.ts + shell.sed( + '-i', + /import { (.+?)Resolvers } from "\.\/(.+?)Resolvers";/g, + 'import { $1Resolvers } from "~app/modules/$1/graphql/$1Resolvers";', + path.join(projectMainPath, 'src/resolvers.ts'), + ); + + // Update imports in ./src/ that use path: "~generated/graphql/types" + shell + .find(path.join(projectMainPath, 'src/')) + .filter((file) => file.match(/\.ts$/)) + // eslint-disable-next-line unicorn/no-array-for-each + .forEach((file) => { + shell.sed( + '-i', + `import { schema } from "~generated/graphql/schema"`, + `import { schema } from "~app/schema"`, + file, + ); + shell.sed( + '-i', + `import { resolvers } from "~generated/graphql/resolvers"`, + `import { resolvers } from "~app/resolvers"`, + file, + ); + shell.sed('-i', `import { Resolvers } from "./types";`, `import { Resolvers } from "~app/graphqlTypes";`, file); + shell.sed('-i', '~generated/graphql/helpers/', './test-helpers/', file); + shell.sed('-i', /~generated\/graphql\/types/g, '~app/graphqlTypes', file); + }); +} + +export default class Eject extends Command { + static description = 'eject from chimp'; + + static examples = ['$ chimp eject']; + + static flags = { + help: Flags.help({ char: 'h' }), + }; + + async run() { + await this.parse(Eject); + + assertGitCleanState(); + const chimpMainPath = path.join(__dirname, '../../'); + const projectMainPath = findProjectMainPath(); + + const tasks = setupListr([ + newTask('Moving code', async () => moveCode(projectMainPath, chimpMainPath)), + newTask('Installing GraphQL schema tooling', async () => installingTooling(projectMainPath)), + ]); + + try { + await tasks.run(); + } catch (error) { + console.error(error); + } + } +} diff --git a/src/generate/helpers/execQuietly.ts b/src/generate/helpers/execQuietly.ts index b55f9ebf..7f51dd9b 100644 --- a/src/generate/helpers/execQuietly.ts +++ b/src/generate/helpers/execQuietly.ts @@ -3,7 +3,7 @@ import debugConfigurator from 'debug'; const debug = debugConfigurator('execQuietly'); -export async function execQuietly(command: string, options: Record, errorMessage = '') { +export async function execQuietly(command: string, options: Record, errorMessage = ''): Promise { return new Promise((resolve, reject) => { const child = shelljs.exec(command, { ...options, @@ -25,7 +25,7 @@ export async function execQuietly(command: string, options: Record { if (code === 0) { - resolve(stdoutData); + resolve(); } else { reject(new Error(`${stdoutData} ${errorMessage}: ${stderrData} ${command}`)); }