refactor: cli

This commit is contained in:
cha0s 2024-02-01 14:02:57 -06:00
parent 78dc275d85
commit 21605c3e2c
11 changed files with 194 additions and 114 deletions

View File

@ -1,5 +1,4 @@
'@flecks/build': '@flecks/build': {}
packageManager: yarn
'@flecks/core:./packages/core': {} '@flecks/core:./packages/core': {}
'@flecks/create:./packages/create-app': {} '@flecks/create:./packages/create-app': {}
'@flecks/create:./packages/create-fleck': {} '@flecks/create:./packages/create-fleck': {}

View File

@ -1,8 +1,5 @@
const {readFile, writeFile} = require('fs/promises'); const {readFile, writeFile} = require('fs/promises');
const { const {join} = require('path');
join,
sep,
} = require('path');
const {dump: dumpYml, load: loadYml} = require('js-yaml'); const {dump: dumpYml, load: loadYml} = require('js-yaml');
@ -10,10 +7,9 @@ const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
} = process.env; } = process.env;
module.exports = async (fleck, path) => { module.exports = async (paths) => {
const key = [fleck].concat(path ? `.${sep}${join('packages', path)}` : []).join(':');
const ymlPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml'); const ymlPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml');
let yml = loadYml(await readFile(ymlPath)); 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})); await writeFile(ymlPath, dumpYml(yml, {sortKeys: true}));
}; };

View File

@ -17,12 +17,15 @@ const {
stringLiteral, stringLiteral,
} = require('@babel/types'); } = require('@babel/types');
const D = require('@flecks/core/build/debug'); const D = require('@flecks/core/build/debug');
const {processCode, spawnWith} = require('@flecks/core/server'); const {
const {Argument, Option, program} = require('commander'); add,
lockFile,
spawnWith,
} = require('@flecks/core/server');
const {glob} = require('glob'); const {glob} = require('glob');
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const addFleckToYml = require('./add-fleck-to-yml'); const addPathsToYml = require('./add-paths-to-yml');
const { const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
@ -55,26 +58,23 @@ function stringLiteralSinglequote(value) {
} }
exports.commands = (program, flecks) => { exports.commands = (program, flecks) => {
const {packageManager} = flecks.get('@flecks/build');
const commands = { const commands = {
add: { add: {
args: [ args: [
new Argument('<fleck>', 'fleck'), program.createArgument('<packages...>', 'packages to add'),
], ],
options: [ options: [
program.createOption('-d, --dev-dependency', 'add to dev dependencies'), program.createOption('-d, --dev-dependency', 'add to dev dependencies'),
program.createOption('-pm,--package-manager <binary>', 'package manager binary')
.choices(['npm', 'bun', 'pnpm', 'yarn']),
], ],
description: 'Add a fleck to your application.', description: 'Add flecks to your application.',
action: async (fleck, {devDependency}) => { action: async (packages, {devDependency, packageManager}) => {
const args = []; await add({
if (['bun', 'yarn'].includes(packageManager)) { dev: devDependency,
args.push(packageManager, ['add', ...(devDependency ? ['--dev'] : []), fleck]); packageManager,
} packages,
else { });
args.push(packageManager, ['install', ...(devDependency ? ['--save-dev'] : []), fleck]);
}
args.push({stdio: 'inherit'});
await processCode(spawn(...args));
// If it seems like we're in a fleck path, update the bootstrap dependencies if possible. // 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'); const bootstrapPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.bootstrap.js');
let code; let code;
@ -107,7 +107,7 @@ exports.commands = (program, flecks) => {
const seen = {}; const seen = {};
// Add the fleck to dependencies. // Add the fleck to dependencies.
dependencies.elements = elements dependencies.elements = elements
.concat(stringLiteralSinglequote(fleck)) .concat(packages.map((fleck) => stringLiteralSinglequote(fleck)))
// Filter duplicate literal strings. // Filter duplicate literal strings.
.filter((node) => { .filter((node) => {
if (isStringLiteral(node)) { if (isStringLiteral(node)) {
@ -129,35 +129,29 @@ exports.commands = (program, flecks) => {
else { else {
code = [ code = [
code, code,
`exports.dependencies = ['${fleck}'];\n`, `exports.dependencies = ['${packages.join("', '")}'];\n`,
].join('\n'); ].join('\n');
} }
await writeFile(bootstrapPath, code); await writeFile(bootstrapPath, code);
} }
// Otherwise, assume we're in an application root. // Otherwise, assume we're in an application root.
else { else {
await addFleckToYml(fleck); await addPathsToYml(packages);
} }
}, },
}, },
clean: { clean: {
description: 'Remove node_modules, lock file, and build artifacts.', description: 'Remove node_modules, lock file, and build artifacts.',
action: () => { options: [
rimraf.sync(join(FLECKS_CORE_ROOT, 'dist')); program.createOption('-pm,--package-manager <binary>', 'package manager binary')
rimraf.sync(join(FLECKS_CORE_ROOT, 'node_modules')); .choices(['npm', 'bun', 'pnpm', 'yarn']),
switch (packageManager) { ],
case 'yarn': action: async ({packageManager}) => {
rimraf.sync(join(FLECKS_CORE_ROOT, 'yarn.lock')); await Promise.all([
break; rimraf(join(FLECKS_CORE_ROOT, 'dist')),
case 'bun': rimraf(join(FLECKS_CORE_ROOT, 'node_modules')),
rimraf.sync(join(FLECKS_CORE_ROOT, 'bun.lockb')); rimraf(join(FLECKS_CORE_ROOT, lockFile(packageManager))),
break; ]);
case 'npm':
rimraf.sync(join(FLECKS_CORE_ROOT, 'package-lock.json'));
break;
default:
break;
}
}, },
}, },
}; };
@ -171,7 +165,7 @@ exports.commands = (program, flecks) => {
options: [ options: [
program.createOption('-d, --no-production', 'dev build'), program.createOption('-d, --no-production', 'dev build'),
program.createOption('-h, --hot', 'build with hot module reloading') program.createOption('-h, --hot', 'build with hot module reloading')
.implies({production: false}), .implies({production: false, watch: true}),
program.createOption('-w, --watch', 'watch for changes') program.createOption('-w, --watch', 'watch for changes')
.implies({production: false}), .implies({production: false}),
], ],
@ -185,7 +179,7 @@ exports.commands = (program, flecks) => {
debug('Building...', opts); debug('Building...', opts);
const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js'); const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js');
const cmd = [ const cmd = [
'npx', 'webpack', join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'webpack'),
'--config', webpackConfig, '--config', webpackConfig,
'--mode', (production && !hot) ? 'production' : 'development', '--mode', (production && !hot) ? 'production' : 'development',
...((watch || hot) ? ['--watch'] : []), ...((watch || hot) ? ['--watch'] : []),
@ -217,7 +211,7 @@ exports.commands = (program, flecks) => {
.map((pkg) => join(process.cwd(), pkg)) .map((pkg) => join(process.cwd(), pkg))
.map(async (cwd) => { .map(async (cwd) => {
const cmd = [ const cmd = [
'npx', 'eslint', join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'eslint'),
'--config', await flecks.resolveBuildConfig('eslint.config.js'), '--config', await flecks.resolveBuildConfig('eslint.config.js'),
'.', '.',
]; ];
@ -260,7 +254,3 @@ exports.commands = (program, flecks) => {
}; };
return commands; return commands;
}; };
exports.Argument = Argument;
exports.Option = Option;
exports.program = program;

View File

@ -49,10 +49,6 @@ exports.hooks = {
'fleck.webpack.config.js', 'fleck.webpack.config.js',
], ],
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/**
* The package manager used for tasks.
*/
packageManager: 'npm',
/** /**
* Build targets to profile with `webpack.debug.ProfilingPlugin`. * Build targets to profile with `webpack.debug.ProfilingPlugin`.
*/ */

View File

@ -1,32 +1,5 @@
const {spawn} = require('child_process');
export {glob} from 'glob'; export {glob} from 'glob';
const D = require('../../build/debug'); export * from '../../build/stream';
export * from './package-manager';
const debug = D('@flecks/core/server'); export * from './process';
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;
};

View File

@ -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 '';
}
};

View File

@ -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;
};

View File

@ -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}));
};

View File

@ -2,12 +2,16 @@
const {join} = require('path'); const {join} = require('path');
const {transform} = require('@flecks/core/server'); const {
build,
install,
transform,
} = require('@flecks/core/server');
const {program} = require('commander'); const {program} = require('commander');
const {dump: dumpYml, load: loadYml} = require('js-yaml'); const {dump: dumpYml, load: loadYml} = require('js-yaml');
const validate = require('validate-npm-package-name'); const validate = require('validate-npm-package-name');
const build = require('./build'); // const build = require('./build');
const {move, testDestination} = require('./move'); const {move, testDestination} = require('./move');
const { const {
@ -18,8 +22,7 @@ const {
program.argument('<app>', 'name of the app to create'); program.argument('<app>', 'name of the app to create');
program.addOption( program.addOption(
program.createOption('-pm,--package-manager <binary>', 'package manager binary') program.createOption('-pm,--package-manager <binary>', 'package manager binary')
.choices(['npm', 'bun', 'yarn']) .choices(['npm', 'bun', 'pnpm', 'yarn']),
.default('npm'),
); );
program.action(async (app, {packageManager}) => { program.action(async (app, {packageManager}) => {
try { try {
@ -42,16 +45,14 @@ const {
transform((chunk, encoding, done, stream) => { transform((chunk, encoding, done, stream) => {
const yml = loadYml(chunk); const yml = loadYml(chunk);
yml['@flecks/core'].id = app; yml['@flecks/core'].id = app;
if ('npm' !== packageManager) {
yml['@flecks/build'].packageManager = packageManager;
}
stream.push(dumpYml(yml, {forceQuotes: true, sortKeys: true})); stream.push(dumpYml(yml, {forceQuotes: true, sortKeys: true}));
done(); done();
}), }),
); );
// Write the tree. // Write the tree.
await fileTree.writeTo(destination); await fileTree.writeTo(destination);
await build(packageManager, destination); await install({cwd: destination, packageManager});
await build({cwd: destination, packageManager});
} }
catch (error) { catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -3,11 +3,13 @@
const {stat} = require('fs/promises'); const {stat} = require('fs/promises');
const {join} = require('path'); const {join} = require('path');
const Build = require('@flecks/build/build/build'); const addPathsToYml = require('@flecks/build/build/add-paths-to-yml');
const addFleckToYml = require('@flecks/build/build/add-fleck-to-yml'); const {program} = require('commander');
const {program} = require('@flecks/build/build/commands'); const {
const {transform} = require('@flecks/core/server'); build,
const build = require('@flecks/create-app/build/build'); install,
transform,
} = require('@flecks/core/server');
const {move, testDestination} = require('@flecks/create-app/build/move'); const {move, testDestination} = require('@flecks/create-app/build/move');
const {validate} = require('@flecks/create-app/server'); const {validate} = require('@flecks/create-app/server');
@ -64,11 +66,12 @@ const target = async (fleck) => {
program.argument('<fleck>', 'name of the fleck to create'); program.argument('<fleck>', 'name of the fleck to create');
program.option('--no-add', 'do not add an entry to `build/flecks.yml`'); 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.option('--no-alias', 'do not alias the fleck in `build/flecks.yml`');
program.action(async (fleck, o) => { program.addOption(
const {alias, add} = o; program.createOption('-pm,--package-manager <binary>', 'package manager binary')
.choices(['npm', 'bun', 'pnpm', 'yarn']),
);
program.action(async (fleck, {alias, add, packageManager}) => {
try { try {
const flecks = await Build.from();
const {packageManager} = flecks.get('@flecks/build');
const isMonorepo = await checkIsMonorepo(); const isMonorepo = await checkIsMonorepo();
const [scope, pkg] = await target(fleck); const [scope, pkg] = await target(fleck);
const name = [scope, pkg].filter((e) => !!e).join('/'); const name = [scope, pkg].filter((e) => !!e).join('/');
@ -97,9 +100,11 @@ const target = async (fleck) => {
} }
// Write the tree. // Write the tree.
await fileTree.writeTo(destination); await fileTree.writeTo(destination);
await build(packageManager, destination); // Install and build.
await install({cwd: destination, packageManager});
await build({cwd: destination, packageManager});
if (isMonorepo && add) { if (isMonorepo && add) {
await addFleckToYml(...[name].concat(alias ? pkg : [])); await addPathsToYml([[name].concat(alias ? `./packages/${pkg}` : []).join(':')]);
} }
} }
catch (error) { catch (error) {

View File

@ -27,7 +27,8 @@
"dependencies": { "dependencies": {
"@flecks/build": "^3.2.0", "@flecks/build": "^3.2.0",
"@flecks/core": "^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": { "devDependencies": {
"@flecks/fleck": "^3.2.0" "@flecks/fleck": "^3.2.0"