diff --git a/.gitignore b/.gitignore index 49a9f90..82c903c 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,8 @@ dist # local /yarn.lock +/bun.lockb # package-locals /packages/*/yarn.lock +/packages/*/bun.lockb diff --git a/packages/core/package.json b/packages/core/package.json index 2137f3d..ebf5b66 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -17,7 +17,7 @@ }, "scripts": { "build": "NODE_PATH=./node_modules webpack --config ./build/webpack.config.js --mode production", - "clean": "rm -rf dist node_modules yarn.lock && yarn", + "clean": "rm -rf dist bun.lockb && bun install", "lint": "NODE_PATH=./node_modules eslint --config ./build/eslint.config.js .", "postversion": "cp package.json dist", "test": "npm run build && mocha -t 10000 --colors ./dist/test.js" diff --git a/packages/core/src/server/commands.js b/packages/core/src/server/commands.js index e427ef7..48ab564 100644 --- a/packages/core/src/server/commands.js +++ b/packages/core/src/server/commands.js @@ -17,7 +17,7 @@ const debug = D('@flecks/core/commands'); const debugSilly = debug.extend('silly'); const flecksRoot = normalize(FLECKS_CORE_ROOT); -export {Argument}; +export {Argument, Option, program} from 'commander'; export const processCode = (child) => new Promise((resolve, reject) => { child.on('error', reject); @@ -45,45 +45,46 @@ export const spawnWith = (cmd, opts = {}) => { }; export default (program, flecks) => { + const {packageManager} = flecks.get('@flecks/core/server'); const commands = { add: { args: [ - new Argument('>', 'fleck'), + new Argument('', 'fleck'), ], description: 'add a fleck to your application', action: async (fleck, opts) => { - const { - noYarn, - } = opts; - await processCode( - noYarn - ? spawn('npm', ['install', fleck], {stdio: 'inherit'}) - : spawn('yarn', ['add', fleck], {stdio: 'inherit'}), - ); + const args = []; + if ('yarn' === packageManager) { + args.push('yarn', ['add', fleck]); + } + else { + args.push(packageManager, ['install', fleck]); + } + args.push({stdio: 'inherit'}); + await processCode(spawn(...args)); await Flecks.addFleckToYml(fleck); }, - options: [ - ['--no-yarn', 'use npm instead of yarn'], - ], }, clean: { description: 'remove node_modules, lock file, build artifacts, then reinstall', action: (opts) => { - const { - noYarn, - } = opts; rimraf.sync(join(flecksRoot, 'dist')); rimraf.sync(join(flecksRoot, 'node_modules')); - if (noYarn) { - rimraf.sync(join(flecksRoot, 'package-lock.json')); - return spawn('npm', ['install'], {stdio: 'inherit'}); + switch (packageManager) { + case 'yarn': + rimraf.sync(join(flecksRoot, 'yarn.lock')); + break; + case 'bun': + rimraf.sync(join(flecksRoot, 'bun.lockb')); + break; + case 'npm': + rimraf.sync(join(flecksRoot, 'package-lock.json')); + break; + default: + break; } - rimraf.sync(join(flecksRoot, 'yarn.lock')); - return spawn('yarn', [], {stdio: 'inherit'}); + return spawn(packageManager, ['install'], {stdio: 'inherit'}); }, - options: [ - ['--no-yarn', 'use npm instead of yarn'], - ], }, }; const targets = flatten(flecks.invokeFlat('@flecks/core.targets')); diff --git a/packages/core/src/server/index.js b/packages/core/src/server/index.js index d1907b3..201bcc9 100644 --- a/packages/core/src/server/index.js +++ b/packages/core/src/server/index.js @@ -21,7 +21,9 @@ export {dump as dumpYml, load as loadYml} from 'js-yaml'; export { Argument, default as commands, + Option, processCode, + program, spawnWith, } from './commands'; export {default as Flecks} from './flecks'; @@ -101,6 +103,10 @@ export const hooks = { * Build targets to exclude from ESLint. */ 'eslint.exclude': [], + /** + * The package manager used for tasks. + */ + packageManager: 'npm', /** * Build targets to profile with `webpack.debug.ProfilingPlugin`. */ diff --git a/packages/create-app/src/build.js b/packages/create-app/src/build.js index 8e7e07e..294aa51 100644 --- a/packages/create-app/src/build.js +++ b/packages/create-app/src/build.js @@ -1,9 +1,9 @@ import {processCode, spawnWith} from '@flecks/core/server'; -export default async (cwd) => { - const code = await processCode(spawnWith(['yarn'], {cwd})); +export default async (packageManager, cwd) => { + const code = await processCode(spawnWith([packageManager, 'install'], {cwd})); if (0 !== code) { return code; } - return processCode(spawnWith(['yarn', 'build'], {cwd})); + return processCode(spawnWith([packageManager, 'run', 'build'], {cwd})); }; diff --git a/packages/create-app/src/cli.js b/packages/create-app/src/cli.js index 0ca7d9a..be3323b 100644 --- a/packages/create-app/src/cli.js +++ b/packages/create-app/src/cli.js @@ -1,39 +1,72 @@ -import {join, normalize} from 'path'; +import {join} from 'path'; -import {Flecks} from '@flecks/core/server'; +import { + dumpYml, + Flecks, + loadYml, + Option, + program, + transform, +} from '@flecks/core/server'; import validate from 'validate-npm-package-name'; import build from './build'; -import move from './move'; +import move, {testDestination} from './move'; const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; -const cwd = normalize(FLECKS_CORE_ROOT); - -const create = async (flecks) => { - let name = process.argv[2]; - const {errors} = validate(name); - if (errors) { - throw new Error(`@flecks/create-app: invalid app name: ${errors.join(', ')}`); - } - const destination = join(cwd, name); - if (!name.startsWith('@')) { - name = `@${name}/monorepo`; - } - await move(name, join(__dirname, 'template'), destination, 'app', flecks); - await build(destination); -}; - (async () => { - const flecks = await Flecks.bootstrap(); - try { - await create(flecks); - } - catch (error) { - // eslint-disable-next-line no-console - console.error(error.message); - process.exitCode = 1; - } + program.argument('', 'name of the app to create'); + program.addOption( + new Option('--package-manager ', 'package manager binary') + .choices(['npm', 'bun', 'yarn']) + .default('npm'), + ); + program.action(async (app, {packageManager}) => { + const flecks = await Flecks.bootstrap({ + config: { + '@flecks/core': {}, + '@flecks/core/server': {packageManager}, + '@flecks/create-app': {}, + '@flecks/fleck': {}, + }, + }); + try { + const {errors} = validate(app); + if (errors) { + throw new Error(`@flecks/create-app: invalid app name: ${errors.join(', ')}`); + } + const destination = join(FLECKS_CORE_ROOT, app); + if (!app.startsWith('@')) { + app = `@${app}/monorepo`; + } + if (!await testDestination(destination)) { + const error = new Error( + `@flecks/create-app: destination '${destination} already exists: aborting`, + ); + error.code = 129; + throw error; + } + const fileTree = await move(app, join(__dirname, 'template'), 'app', flecks); + fileTree.pipe( + 'build/flecks.yml', + transform((chunk, encoding, done, stream) => { + const yml = loadYml(chunk); + yml['@flecks/core/server'] = {packageManager}; + stream.push(dumpYml(yml, {sortKeys: true})); + done(); + }), + ); + // Write the tree. + await fileTree.writeTo(destination); + await build(packageManager, destination); + } + catch (error) { + // eslint-disable-next-line no-console + console.error(error); + } + }); + await program.parseAsync(process.argv); })(); diff --git a/packages/create-app/src/move.js b/packages/create-app/src/move.js index 7a3bedc..9646eff 100644 --- a/packages/create-app/src/move.js +++ b/packages/create-app/src/move.js @@ -3,11 +3,14 @@ import { } from 'fs/promises'; import {basename, dirname, join} from 'path'; -import {JsonStream, transform} from '@flecks/core/server'; +import { + JsonStream, + transform, +} from '@flecks/core/server'; import FileTree from './tree'; -const testDestination = async (destination) => { +export const testDestination = async (destination) => { try { await stat(destination); return false; @@ -20,14 +23,7 @@ const testDestination = async (destination) => { } }; -export default async (name, source, destination, type, flecks) => { - if (!await testDestination(destination)) { - const error = new Error( - `@flecks/create-fleck: destination '${destination} already exists: aborting`, - ); - error.code = 129; - throw error; - } +export default async (name, source, type, flecks) => { const fileTree = await FileTree.loadFrom(source); // Renamed to avoid conflicts. const {files} = fileTree; @@ -56,6 +52,5 @@ export default async (name, source, destination, type, flecks) => { .forEach((path) => { fileTree.pipe(path, new JsonStream.PrettyPrint()); }); - // Write the tree. - await fileTree.writeTo(destination); + return fileTree; }; diff --git a/packages/create-app/src/server.js b/packages/create-app/src/server.js index 133ecec..19ec933 100644 --- a/packages/create-app/src/server.js +++ b/packages/create-app/src/server.js @@ -1,5 +1,5 @@ export {default as validate} from 'validate-npm-package-name'; export {default as build} from './build'; -export {default as move} from './move'; +export {default as move, testDestination} from './move'; export {default as FileTree} from './tree'; diff --git a/packages/create-fleck/package.json b/packages/create-fleck/package.json index ade2f04..f72394a 100644 --- a/packages/create-fleck/package.json +++ b/packages/create-fleck/package.json @@ -27,8 +27,7 @@ ], "dependencies": { "@flecks/core": "^2.0.3", - "@flecks/create-app": "^2.0.3", - "@inquirer/prompts": "^3.3.0" + "@flecks/create-app": "^2.0.3" }, "devDependencies": { "@flecks/fleck": "^2.0.3" diff --git a/packages/create-fleck/src/cli.js b/packages/create-fleck/src/cli.js index 3d1eb5d..0c0d7dd 100644 --- a/packages/create-fleck/src/cli.js +++ b/packages/create-fleck/src/cli.js @@ -1,19 +1,21 @@ import {stat} from 'fs/promises'; -import {join, normalize} from 'path'; +import {join} from 'path'; -import {build, move, validate} from '@flecks/create-app/server'; -import {Flecks} from '@flecks/core/server'; -import {confirm} from '@inquirer/prompts'; +import { + build, + move, + testDestination, + validate, +} from '@flecks/create-app/server'; +import {Flecks, program} from '@flecks/core/server'; const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; -const cwd = normalize(FLECKS_CORE_ROOT); - -const checkIsMonorepo = async (cwd) => { +const checkIsMonorepo = async () => { try { - await stat(join(cwd, 'packages')); + await stat(join(FLECKS_CORE_ROOT, 'packages')); return true; } catch (error) { @@ -24,9 +26,9 @@ const checkIsMonorepo = async (cwd) => { } }; -const monorepoScope = async (cwd) => { +const monorepoScope = async () => { try { - const {name} = __non_webpack_require__(join(cwd, 'package.json')); + const {name} = __non_webpack_require__(join(FLECKS_CORE_ROOT, 'package.json')); const [scope] = name.split('/'); return scope; } @@ -38,45 +40,57 @@ const monorepoScope = async (cwd) => { } }; -const target = async (name) => { - const {errors} = validate(name); +const target = async (fleck) => { + const {errors} = validate(fleck); if (errors) { throw new Error(`@flecks/create-fleck: invalid fleck name: ${errors.join(', ')}`); } - const parts = name.split('/'); + const parts = fleck.split('/'); let pkg; let scope; if (1 === parts.length) { - pkg = name; - if (await checkIsMonorepo(cwd)) { - scope = await monorepoScope(cwd); + pkg = fleck; + if (await checkIsMonorepo()) { + scope = await monorepoScope(); } return [scope, pkg]; } return parts; }; -const create = async (flecks) => { - const isMonorepo = await checkIsMonorepo(cwd); - const [scope, pkg] = await target(process.argv[2]); - const path = scope && isMonorepo ? join(cwd, 'packages') : cwd; - const name = [scope, pkg].filter((e) => !!e).join('/'); - const destination = join(path, pkg); - await move(name, join(__dirname, 'template'), destination, 'fleck', flecks); - await build(destination); - if (isMonorepo && await confirm({message: 'Add fleck to `build/flecks.yml`?'})) { - await Flecks.addFleckToYml(name, pkg); - } -}; - (async () => { - const flecks = await Flecks.bootstrap(); - try { - await create(flecks); - } - catch (error) { - // eslint-disable-next-line no-console - console.error(error.message); - process.exitCode = 1; - } + program.argument('', 'name of the fleck to create'); + program.option('--no-add', 'do not add an entry to `build/flecks.yml`'); + program.action(async (fleck, {add}) => { + try { + const flecks = await Flecks.bootstrap(); + const {packageManager} = flecks.get('@flecks/core/server'); + const isMonorepo = await checkIsMonorepo(); + const [scope, pkg] = await target(fleck); + const name = [scope, pkg].filter((e) => !!e).join('/'); + const destination = join( + join(...[FLECKS_CORE_ROOT].concat(isMonorepo ? ['packages'] : [])), + pkg, + ); + if (!await testDestination(destination)) { + const error = new Error( + `@flecks/create-fleck: destination '${destination} already exists: aborting`, + ); + error.code = 129; + throw error; + } + const fileTree = await move(name, join(__dirname, 'template'), 'fleck', flecks); + // Write the tree. + await fileTree.writeTo(destination); + await build(packageManager, destination); + if (isMonorepo && add) { + await Flecks.addFleckToYml(name, pkg); + } + } + catch (error) { + // eslint-disable-next-line no-console + console.error('Creation failed:', error); + } + }); + await program.parseAsync(process.argv); })();