@basaltkit/cli 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/launcher.d.ts +15 -0
- package/dist/launcher.js +40 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,10 @@ app's, at its pinned versions. Arguments and the exit code pass through
|
|
|
41
41
|
unchanged. Outside an app it says so and exits 1. The launcher loads nothing of
|
|
42
42
|
the framework, only Node built-ins.
|
|
43
43
|
|
|
44
|
+
`basalt new <name> [flags]` is the launcher's own command: it scaffolds an app
|
|
45
|
+
with `create-basalt@latest` (through `pnpm dlx`, else `npx`; `--pm=` picks the
|
|
46
|
+
manager), so it always gets the latest scaffolder.
|
|
47
|
+
|
|
44
48
|
## Get started in 5 minutes
|
|
45
49
|
|
|
46
50
|
1. Create a `bin/basalt.ts` file at the root of the project — this will be the entry point for the `basalt` command:
|
package/dist/launcher.d.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* app's own package manager — so the code that runs is always the app's, at
|
|
8
8
|
* the versions its lockfile pins, never the launcher's. Node built-ins only:
|
|
9
9
|
* nothing of the framework is loaded.
|
|
10
|
+
*
|
|
11
|
+
* One command is the launcher's own: `basalt new <name> [flags]` scaffolds an
|
|
12
|
+
* app through `create-basalt@latest` (`pnpm dlx`, else `npx`) — the latest
|
|
13
|
+
* scaffolder, whatever version the launcher is.
|
|
10
14
|
*/
|
|
11
15
|
export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
|
|
12
16
|
export interface LauncherProject {
|
|
@@ -41,6 +45,17 @@ export interface RunLauncherOptions {
|
|
|
41
45
|
/** Where the launcher's own lines go (never the app's output). */
|
|
42
46
|
error?: (line: string) => void;
|
|
43
47
|
log?: (line: string) => void;
|
|
48
|
+
/** Whether pnpm is installed — what `basalt new` runs create-basalt with. Default: `pnpm --version` answers. */
|
|
49
|
+
hasPnpm?: () => boolean;
|
|
44
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* How `basalt new` runs `create-basalt@latest`: through the manager `--pm=`
|
|
53
|
+
* names, else pnpm when it is installed, else npx. create-basalt reads the
|
|
54
|
+
* manager it was started with, so the new app uses the same one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function scaffoldCommand(args: readonly string[], hasPnpm: () => boolean): {
|
|
57
|
+
command: string;
|
|
58
|
+
args: string[];
|
|
59
|
+
};
|
|
45
60
|
/** Runs `basalt <argv>`: the app's `basalt` script, through the app's package manager. Resolves to the exit code. */
|
|
46
61
|
export declare function runLauncher(argv: readonly string[], options?: RunLauncherOptions): Promise<number>;
|
package/dist/launcher.js
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
* app's own package manager — so the code that runs is always the app's, at
|
|
8
8
|
* the versions its lockfile pins, never the launcher's. Node built-ins only:
|
|
9
9
|
* nothing of the framework is loaded.
|
|
10
|
+
*
|
|
11
|
+
* One command is the launcher's own: `basalt new <name> [flags]` scaffolds an
|
|
12
|
+
* app through `create-basalt@latest` (`pnpm dlx`, else `npx`) — the latest
|
|
13
|
+
* scaffolder, whatever version the launcher is.
|
|
10
14
|
*/
|
|
11
|
-
import { spawn } from 'node:child_process';
|
|
15
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
12
16
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
17
|
import { dirname, join, resolve } from 'node:path';
|
|
14
18
|
/** Set on the script's environment: a `basalt` script that calls `basalt` again would loop. */
|
|
@@ -93,9 +97,24 @@ function launcherVersion() {
|
|
|
93
97
|
return 'unknown';
|
|
94
98
|
}
|
|
95
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* How `basalt new` runs `create-basalt@latest`: through the manager `--pm=`
|
|
102
|
+
* names, else pnpm when it is installed, else npx. create-basalt reads the
|
|
103
|
+
* manager it was started with, so the new app uses the same one.
|
|
104
|
+
*/
|
|
105
|
+
export function scaffoldCommand(args, hasPnpm) {
|
|
106
|
+
const pm = args.find((arg) => arg.startsWith('--pm='))?.slice('--pm='.length);
|
|
107
|
+
const pkg = 'create-basalt@latest';
|
|
108
|
+
if (pm === 'pnpm' || (pm === undefined && hasPnpm()))
|
|
109
|
+
return { command: 'pnpm', args: ['--silent', 'dlx', pkg, ...args] };
|
|
110
|
+
if (pm === 'bun')
|
|
111
|
+
return { command: 'bunx', args: [pkg, ...args] };
|
|
112
|
+
return { command: 'npx', args: ['--yes', pkg, ...args] }; // npm, and yarn (create-basalt honours --pm=yarn)
|
|
113
|
+
}
|
|
114
|
+
const pnpmInstalled = () => spawnSync('pnpm', ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' }).status === 0;
|
|
96
115
|
const NOT_AN_APP = [
|
|
97
116
|
'basalt: no Basalt app here — no package.json with a "basalt" script in this directory or above it.',
|
|
98
|
-
' Start one:
|
|
117
|
+
' Start one: basalt new my-app',
|
|
99
118
|
' Or cd into an app first; `basalt <command>` then runs `<pm> basalt <command>` there.',
|
|
100
119
|
];
|
|
101
120
|
/** Runs `basalt <argv>`: the app's `basalt` script, through the app's package manager. Resolves to the exit code. */
|
|
@@ -104,6 +123,11 @@ export async function runLauncher(argv, options = {}) {
|
|
|
104
123
|
const env = options.env ?? process.env;
|
|
105
124
|
const error = options.error ?? ((line) => process.stderr.write(`${line}\n`));
|
|
106
125
|
const log = options.log ?? ((line) => process.stdout.write(`${line}\n`));
|
|
126
|
+
// The launcher's own command, inside an app too: a new app is never the current one's business.
|
|
127
|
+
if (argv[0] === 'new') {
|
|
128
|
+
const scaffold = scaffoldCommand(argv.slice(1), options.hasPnpm ?? pnpmInstalled);
|
|
129
|
+
return spawnAndWait(scaffold.command, scaffold.args, { cwd, env, error, what: 'create a new app' });
|
|
130
|
+
}
|
|
107
131
|
const project = findProject(cwd);
|
|
108
132
|
if (!project) {
|
|
109
133
|
if (argv[0] === '--version' || argv[0] === '-v') {
|
|
@@ -120,14 +144,24 @@ export async function runLauncher(argv, options = {}) {
|
|
|
120
144
|
return 1;
|
|
121
145
|
}
|
|
122
146
|
const { command, args } = launchCommand(project.pm, argv);
|
|
147
|
+
return spawnAndWait(command, args, {
|
|
148
|
+
cwd: project.root,
|
|
149
|
+
env: { ...env, [LAUNCHER_ENV]: project.root },
|
|
150
|
+
error,
|
|
151
|
+
what: `run this app's basalt script (${project.root})`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Runs one command with the terminal attached; resolves to its exit code. */
|
|
155
|
+
function spawnAndWait(command, args, options) {
|
|
156
|
+
const { error } = options;
|
|
123
157
|
return new Promise((done) => {
|
|
124
158
|
const child = spawn(command, args, {
|
|
125
|
-
cwd:
|
|
126
|
-
env:
|
|
159
|
+
cwd: options.cwd,
|
|
160
|
+
env: options.env,
|
|
127
161
|
stdio: 'inherit',
|
|
128
162
|
shell: process.platform === 'win32',
|
|
129
163
|
});
|
|
130
|
-
// Ctrl-C reaches the whole foreground group: the
|
|
164
|
+
// Ctrl-C reaches the whole foreground group: the child handles it; the
|
|
131
165
|
// launcher only waits for it to finish and reports its code.
|
|
132
166
|
const ignore = () => { };
|
|
133
167
|
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
@@ -140,7 +174,7 @@ export async function runLauncher(argv, options = {}) {
|
|
|
140
174
|
};
|
|
141
175
|
child.on('error', (cause) => {
|
|
142
176
|
error(cause.code === 'ENOENT'
|
|
143
|
-
? `basalt: ${command} is not installed, and
|
|
177
|
+
? `basalt: ${command} is not installed, and it is needed to ${options.what}.`
|
|
144
178
|
: `basalt: could not start ${command}: ${cause.message}`);
|
|
145
179
|
settle(1);
|
|
146
180
|
});
|