feat: package manager (and bun)

This commit is contained in:
cha0s 2024-01-04 03:20:55 -06:00
parent 7198ad998e
commit 16934d55c9
10 changed files with 159 additions and 109 deletions

2
.gitignore vendored
View File

@ -117,6 +117,8 @@ dist
# local
/yarn.lock
/bun.lockb
# package-locals
/packages/*/yarn.lock
/packages/*/bun.lockb

View File

@ -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"

View File

@ -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>>', 'fleck'),
new Argument('<fleck>', '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'));

View File

@ -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`.
*/

View File

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

View File

@ -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('<app>', 'name of the app to create');
program.addOption(
new Option('--package-manager <binary>', '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);
})();

View File

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

View File

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

View File

@ -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"

View File

@ -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('<fleck>', '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);
})();