From 21605c3e2c3ef9d2279d7ebe87110d1a09ebd350 Mon Sep 17 00:00:00 2001 From: cha0s Date: Thu, 1 Feb 2024 14:02:57 -0600 Subject: [PATCH] refactor: cli --- build/flecks.yml | 3 +- ...dd-fleck-to-yml.js => add-paths-to-yml.js} | 10 +- packages/build/build/commands.js | 74 ++++++------- packages/build/build/flecks.bootstrap.js | 4 - packages/core/src/server/index.js | 33 +----- packages/core/src/server/package-manager.js | 100 ++++++++++++++++++ packages/core/src/server/process.js | 28 +++++ packages/create-app/build/build.js | 9 -- packages/create-app/build/cli.js | 17 +-- packages/create-fleck/build/cli.js | 27 +++-- packages/create-fleck/package.json | 3 +- 11 files changed, 194 insertions(+), 114 deletions(-) rename packages/build/build/{add-fleck-to-yml.js => add-paths-to-yml.js} (59%) create mode 100644 packages/core/src/server/package-manager.js create mode 100644 packages/core/src/server/process.js delete mode 100644 packages/create-app/build/build.js diff --git a/build/flecks.yml b/build/flecks.yml index fbb821c..f3fa2bc 100644 --- a/build/flecks.yml +++ b/build/flecks.yml @@ -1,5 +1,4 @@ -'@flecks/build': - packageManager: yarn +'@flecks/build': {} '@flecks/core:./packages/core': {} '@flecks/create:./packages/create-app': {} '@flecks/create:./packages/create-fleck': {} diff --git a/packages/build/build/add-fleck-to-yml.js b/packages/build/build/add-paths-to-yml.js similarity index 59% rename from packages/build/build/add-fleck-to-yml.js rename to packages/build/build/add-paths-to-yml.js index fae10df..c530ef3 100644 --- a/packages/build/build/add-fleck-to-yml.js +++ b/packages/build/build/add-paths-to-yml.js @@ -1,8 +1,5 @@ const {readFile, writeFile} = require('fs/promises'); -const { - join, - sep, -} = require('path'); +const {join} = require('path'); const {dump: dumpYml, load: loadYml} = require('js-yaml'); @@ -10,10 +7,9 @@ const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; -module.exports = async (fleck, path) => { - const key = [fleck].concat(path ? `.${sep}${join('packages', path)}` : []).join(':'); +module.exports = async (paths) => { const ymlPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml'); let yml = loadYml(await readFile(ymlPath)); - yml = Object.fromEntries(Object.entries(yml).concat([[key, {}]])); + yml = Object.fromEntries(Object.entries(yml).concat(paths.map((path) => [path, {}]))); await writeFile(ymlPath, dumpYml(yml, {sortKeys: true})); }; diff --git a/packages/build/build/commands.js b/packages/build/build/commands.js index 96a73a9..c847f85 100644 --- a/packages/build/build/commands.js +++ b/packages/build/build/commands.js @@ -17,12 +17,15 @@ const { stringLiteral, } = require('@babel/types'); const D = require('@flecks/core/build/debug'); -const {processCode, spawnWith} = require('@flecks/core/server'); -const {Argument, Option, program} = require('commander'); +const { + add, + lockFile, + spawnWith, +} = require('@flecks/core/server'); const {glob} = require('glob'); const rimraf = require('rimraf'); -const addFleckToYml = require('./add-fleck-to-yml'); +const addPathsToYml = require('./add-paths-to-yml'); const { FLECKS_CORE_ROOT = process.cwd(), @@ -55,26 +58,23 @@ function stringLiteralSinglequote(value) { } exports.commands = (program, flecks) => { - const {packageManager} = flecks.get('@flecks/build'); const commands = { add: { args: [ - new Argument('', 'fleck'), + program.createArgument('', 'packages to add'), ], options: [ program.createOption('-d, --dev-dependency', 'add to dev dependencies'), + program.createOption('-pm,--package-manager ', 'package manager binary') + .choices(['npm', 'bun', 'pnpm', 'yarn']), ], - description: 'Add a fleck to your application.', - action: async (fleck, {devDependency}) => { - const args = []; - if (['bun', 'yarn'].includes(packageManager)) { - args.push(packageManager, ['add', ...(devDependency ? ['--dev'] : []), fleck]); - } - else { - args.push(packageManager, ['install', ...(devDependency ? ['--save-dev'] : []), fleck]); - } - args.push({stdio: 'inherit'}); - await processCode(spawn(...args)); + description: 'Add flecks to your application.', + action: async (packages, {devDependency, packageManager}) => { + await add({ + dev: devDependency, + packageManager, + packages, + }); // If it seems like we're in a fleck path, update the bootstrap dependencies if possible. const bootstrapPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.bootstrap.js'); let code; @@ -107,7 +107,7 @@ exports.commands = (program, flecks) => { const seen = {}; // Add the fleck to dependencies. dependencies.elements = elements - .concat(stringLiteralSinglequote(fleck)) + .concat(packages.map((fleck) => stringLiteralSinglequote(fleck))) // Filter duplicate literal strings. .filter((node) => { if (isStringLiteral(node)) { @@ -129,35 +129,29 @@ exports.commands = (program, flecks) => { else { code = [ code, - `exports.dependencies = ['${fleck}'];\n`, + `exports.dependencies = ['${packages.join("', '")}'];\n`, ].join('\n'); } await writeFile(bootstrapPath, code); } // Otherwise, assume we're in an application root. else { - await addFleckToYml(fleck); + await addPathsToYml(packages); } }, }, clean: { description: 'Remove node_modules, lock file, and build artifacts.', - action: () => { - rimraf.sync(join(FLECKS_CORE_ROOT, 'dist')); - rimraf.sync(join(FLECKS_CORE_ROOT, 'node_modules')); - switch (packageManager) { - case 'yarn': - rimraf.sync(join(FLECKS_CORE_ROOT, 'yarn.lock')); - break; - case 'bun': - rimraf.sync(join(FLECKS_CORE_ROOT, 'bun.lockb')); - break; - case 'npm': - rimraf.sync(join(FLECKS_CORE_ROOT, 'package-lock.json')); - break; - default: - break; - } + options: [ + program.createOption('-pm,--package-manager ', 'package manager binary') + .choices(['npm', 'bun', 'pnpm', 'yarn']), + ], + action: async ({packageManager}) => { + await Promise.all([ + rimraf(join(FLECKS_CORE_ROOT, 'dist')), + rimraf(join(FLECKS_CORE_ROOT, 'node_modules')), + rimraf(join(FLECKS_CORE_ROOT, lockFile(packageManager))), + ]); }, }, }; @@ -171,7 +165,7 @@ exports.commands = (program, flecks) => { options: [ program.createOption('-d, --no-production', 'dev build'), program.createOption('-h, --hot', 'build with hot module reloading') - .implies({production: false}), + .implies({production: false, watch: true}), program.createOption('-w, --watch', 'watch for changes') .implies({production: false}), ], @@ -185,7 +179,7 @@ exports.commands = (program, flecks) => { debug('Building...', opts); const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js'); const cmd = [ - 'npx', 'webpack', + join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'webpack'), '--config', webpackConfig, '--mode', (production && !hot) ? 'production' : 'development', ...((watch || hot) ? ['--watch'] : []), @@ -217,7 +211,7 @@ exports.commands = (program, flecks) => { .map((pkg) => join(process.cwd(), pkg)) .map(async (cwd) => { const cmd = [ - 'npx', 'eslint', + join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'eslint'), '--config', await flecks.resolveBuildConfig('eslint.config.js'), '.', ]; @@ -260,7 +254,3 @@ exports.commands = (program, flecks) => { }; return commands; }; - -exports.Argument = Argument; -exports.Option = Option; -exports.program = program; diff --git a/packages/build/build/flecks.bootstrap.js b/packages/build/build/flecks.bootstrap.js index b548d1e..dc3d743 100644 --- a/packages/build/build/flecks.bootstrap.js +++ b/packages/build/build/flecks.bootstrap.js @@ -49,10 +49,6 @@ exports.hooks = { 'fleck.webpack.config.js', ], '@flecks/core.config': () => ({ - /** - * The package manager used for tasks. - */ - packageManager: 'npm', /** * Build targets to profile with `webpack.debug.ProfilingPlugin`. */ diff --git a/packages/core/src/server/index.js b/packages/core/src/server/index.js index 4132f0b..b05027d 100644 --- a/packages/core/src/server/index.js +++ b/packages/core/src/server/index.js @@ -1,32 +1,5 @@ -const {spawn} = require('child_process'); - export {glob} from 'glob'; -const D = require('../../build/debug'); - -const debug = D('@flecks/core/server'); -const debugSilly = debug.extend('silly'); - -export {JsonStream, transform} from '../../build/stream'; - -export const processCode = (child) => new Promise((resolve, reject) => { - child.on('error', reject); - child.on('exit', (code) => { - child.off('error', reject); - resolve(code); - }); -}); - -export const spawnWith = (cmd, opts = {}) => { - debug("spawning: '%s'", cmd.join(' ')); - debugSilly('with options: %O', opts); - const child = spawn(cmd[0], cmd.slice(1), { - stdio: 'inherit', - ...opts, - env: { - ...process.env, - ...opts.env, - }, - }); - return child; -}; +export * from '../../build/stream'; +export * from './package-manager'; +export * from './process'; diff --git a/packages/core/src/server/package-manager.js b/packages/core/src/server/package-manager.js new file mode 100644 index 0000000..5d32652 --- /dev/null +++ b/packages/core/src/server/package-manager.js @@ -0,0 +1,100 @@ +import {processCode, spawnWith} from './process'; + +/* eslint-disable camelcase */ +const { + npm_config_user_agent = 'npm', +} = process.env; + +export const inferPackageManager = () => npm_config_user_agent.split('/')[0]; +/* eslint-enable camelcase */ + +export const build = async ({cwd, packageManager = inferPackageManager()}) => { + let args; + switch (packageManager) { + case 'bun': + args = ['bun', 'run', 'build']; + break; + case 'npm': + args = ['npm', 'run', 'build']; + break; + case 'pnpm': + args = ['pnpm', 'run', 'build']; + break; + case 'yarn': + args = ['yarn', 'run', 'build']; + break; + default: + } + return args && processCode(spawnWith(args, {cwd})); +}; + +export const add = async ({dev, packageManager = inferPackageManager(), packages}) => { + let args; + switch (packageManager) { + case 'bun': + args = [ + 'bun', 'add', + ...(dev ? ['--dev'] : []), + ...packages, + ]; + break; + case 'npm': + args = [ + 'npm', 'install', + ...(dev ? ['--save-dev'] : []), + ...packages, + ]; + break; + case 'pnpm': + args = [ + 'pnpm', 'add', + ...(dev ? ['--save-dev'] : []), + ...packages, + ]; + break; + case 'yarn': + args = [ + 'yarn', 'add', + ...(dev ? ['--dev'] : []), + ...packages, + ]; + break; + default: + } + return args && processCode(spawnWith(args)); +}; + +export const install = async ({cwd, packageManager = inferPackageManager()}) => { + let args; + switch (packageManager) { + case 'bun': + args = ['bun', 'install']; + break; + case 'npm': + args = ['npm', 'install']; + break; + case 'pnpm': + args = ['pnpm', 'install']; + break; + case 'yarn': + args = ['yarn', 'install']; + break; + default: + } + return args && processCode(spawnWith(args, {cwd})); +}; + +export const lockFile = (packageManager = inferPackageManager()) => { + switch (packageManager) { + case 'bun': + return 'bun.lockb'; + case 'npm': + return 'package-lock.json'; + case 'pnpm': + return 'pnpm-lock.yaml'; + case 'yarn': + return 'yarn.lock'; + default: + return ''; + } +}; diff --git a/packages/core/src/server/process.js b/packages/core/src/server/process.js new file mode 100644 index 0000000..5bfb0b7 --- /dev/null +++ b/packages/core/src/server/process.js @@ -0,0 +1,28 @@ +import {spawn} from 'child_process'; + +import D from '../../build/debug'; + +const debug = D('@flecks/core/server'); +const debugSilly = debug.extend('silly'); + +export const processCode = (child) => new Promise((resolve, reject) => { + child.on('error', reject); + child.on('exit', (code) => { + child.off('error', reject); + resolve(code); + }); +}); + +export const spawnWith = (cmd, opts = {}) => { + debug("spawning: '%s'", cmd.join(' ')); + debugSilly('with options: %O', opts); + const child = spawn(cmd[0], cmd.slice(1), { + stdio: 'inherit', + ...opts, + env: { + ...process.env, + ...opts.env, + }, + }); + return child; +}; diff --git a/packages/create-app/build/build.js b/packages/create-app/build/build.js deleted file mode 100644 index 33a95af..0000000 --- a/packages/create-app/build/build.js +++ /dev/null @@ -1,9 +0,0 @@ -const {processCode, spawnWith} = require('@flecks/core/server'); - -module.exports = async (packageManager, cwd) => { - const code = await processCode(spawnWith([packageManager, 'install'], {cwd})); - if (0 !== code) { - return code; - } - return processCode(spawnWith([packageManager, 'run', 'build'], {cwd})); -}; diff --git a/packages/create-app/build/cli.js b/packages/create-app/build/cli.js index ad06aed..ecda8cd 100644 --- a/packages/create-app/build/cli.js +++ b/packages/create-app/build/cli.js @@ -2,12 +2,16 @@ const {join} = require('path'); -const {transform} = require('@flecks/core/server'); +const { + build, + install, + transform, +} = require('@flecks/core/server'); const {program} = require('commander'); const {dump: dumpYml, load: loadYml} = require('js-yaml'); const validate = require('validate-npm-package-name'); -const build = require('./build'); +// const build = require('./build'); const {move, testDestination} = require('./move'); const { @@ -18,8 +22,7 @@ const { program.argument('', 'name of the app to create'); program.addOption( program.createOption('-pm,--package-manager ', 'package manager binary') - .choices(['npm', 'bun', 'yarn']) - .default('npm'), + .choices(['npm', 'bun', 'pnpm', 'yarn']), ); program.action(async (app, {packageManager}) => { try { @@ -42,16 +45,14 @@ const { transform((chunk, encoding, done, stream) => { const yml = loadYml(chunk); yml['@flecks/core'].id = app; - if ('npm' !== packageManager) { - yml['@flecks/build'].packageManager = packageManager; - } stream.push(dumpYml(yml, {forceQuotes: true, sortKeys: true})); done(); }), ); // Write the tree. await fileTree.writeTo(destination); - await build(packageManager, destination); + await install({cwd: destination, packageManager}); + await build({cwd: destination, packageManager}); } catch (error) { // eslint-disable-next-line no-console diff --git a/packages/create-fleck/build/cli.js b/packages/create-fleck/build/cli.js index 63d7406..ad5a60a 100644 --- a/packages/create-fleck/build/cli.js +++ b/packages/create-fleck/build/cli.js @@ -3,11 +3,13 @@ const {stat} = require('fs/promises'); const {join} = require('path'); -const Build = require('@flecks/build/build/build'); -const addFleckToYml = require('@flecks/build/build/add-fleck-to-yml'); -const {program} = require('@flecks/build/build/commands'); -const {transform} = require('@flecks/core/server'); -const build = require('@flecks/create-app/build/build'); +const addPathsToYml = require('@flecks/build/build/add-paths-to-yml'); +const {program} = require('commander'); +const { + build, + install, + transform, +} = require('@flecks/core/server'); const {move, testDestination} = require('@flecks/create-app/build/move'); const {validate} = require('@flecks/create-app/server'); @@ -64,11 +66,12 @@ const target = async (fleck) => { program.argument('', 'name of the fleck to create'); program.option('--no-add', 'do not add an entry to `build/flecks.yml`'); program.option('--no-alias', 'do not alias the fleck in `build/flecks.yml`'); - program.action(async (fleck, o) => { - const {alias, add} = o; + program.addOption( + program.createOption('-pm,--package-manager ', 'package manager binary') + .choices(['npm', 'bun', 'pnpm', 'yarn']), + ); + program.action(async (fleck, {alias, add, packageManager}) => { try { - const flecks = await Build.from(); - const {packageManager} = flecks.get('@flecks/build'); const isMonorepo = await checkIsMonorepo(); const [scope, pkg] = await target(fleck); const name = [scope, pkg].filter((e) => !!e).join('/'); @@ -97,9 +100,11 @@ const target = async (fleck) => { } // Write the tree. await fileTree.writeTo(destination); - await build(packageManager, destination); + // Install and build. + await install({cwd: destination, packageManager}); + await build({cwd: destination, packageManager}); if (isMonorepo && add) { - await addFleckToYml(...[name].concat(alias ? pkg : [])); + await addPathsToYml([[name].concat(alias ? `./packages/${pkg}` : []).join(':')]); } } catch (error) { diff --git a/packages/create-fleck/package.json b/packages/create-fleck/package.json index bba27b1..986bc00 100644 --- a/packages/create-fleck/package.json +++ b/packages/create-fleck/package.json @@ -27,7 +27,8 @@ "dependencies": { "@flecks/build": "^3.2.0", "@flecks/core": "^3.2.0", - "@flecks/create-app": "^3.2.0" + "@flecks/create-app": "^3.2.0", + "commander": "11.1.0" }, "devDependencies": { "@flecks/fleck": "^3.2.0"