@xsynaptic/astro-scripts 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +36 -2
- package/dist/index.d.mts +171 -0
- package/dist/index.mjs +865 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Synaptic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @xsynaptic/astro-scripts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plumbing for an Astro project's `scripts` package: a command runner, output through interchangeable reporters, workspace root discovery, and a bridge for reading `astro:content` outside the Astro runtime. Early prototype; the API will move.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @xsynaptic/astro-scripts
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
createCommandRunner,
|
|
12
|
+
getCollectionEntries,
|
|
13
|
+
log,
|
|
14
|
+
withAstroContent,
|
|
15
|
+
} from '@xsynaptic/astro-scripts';
|
|
16
|
+
|
|
17
|
+
const { runCommand } = createCommandRunner();
|
|
18
|
+
|
|
19
|
+
await runCommand({
|
|
20
|
+
name: 'count-posts',
|
|
21
|
+
run: async () => {
|
|
22
|
+
const posts = await withAstroContent((content) => getCollectionEntries(content, ['posts']));
|
|
23
|
+
|
|
24
|
+
log.data(posts.length);
|
|
25
|
+
},
|
|
26
|
+
summary: 'Count published posts',
|
|
27
|
+
title: 'Posts',
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Output renders `pretty` on a TTY, `plain` elsewhere, and NDJSON with `--json`; `SCRIPTS_OUTPUT` overrides the choice. Run a `--json` command through `pnpm -s`, or pnpm's own banner lines land in the stream.
|
|
32
|
+
|
|
33
|
+
`withAstroModules` and `withAstroContent` load `astro` and `vite` only when called, and need a content store written by `astro sync`.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ParseArgsOptionDescriptor, parseArgs } from "node:util";
|
|
2
|
+
import * as AstroContentModule from "astro:content";
|
|
3
|
+
import { CollectionEntry, CollectionKey } from "astro:content";
|
|
4
|
+
import { ModuleRunner } from "vite/module-runner";
|
|
5
|
+
//#region src/astro.d.ts
|
|
6
|
+
type AstroContent = typeof AstroContentModule;
|
|
7
|
+
interface ContentEntry {
|
|
8
|
+
body?: string | undefined;
|
|
9
|
+
collection: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
digest?: number | string | undefined;
|
|
12
|
+
filePath?: string | undefined;
|
|
13
|
+
id: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function findAstroRoot(startDir?: string): string;
|
|
16
|
+
export declare function getCollectionEntries<C extends CollectionKey>({ getCollection }: AstroContent, collections: Array<C>): Promise<Array<CollectionEntry<C>>>;
|
|
17
|
+
export declare function withAstroModules<T>(callback: (runner: ModuleRunner) => Promise<T>): Promise<T>;
|
|
18
|
+
export declare function withAstroContent<T>(callback: (content: AstroContent) => Promise<T>): Promise<T>;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/command.d.ts
|
|
21
|
+
type CommandOption = ParseArgsOptionDescriptor & {
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
type CommandOptions = Record<string, CommandOption>;
|
|
25
|
+
interface CommandPositional {
|
|
26
|
+
description: string;
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
type ParsedValues<Options extends CommandOptions> = ReturnType<typeof parseArgs<{
|
|
30
|
+
allowPositionals: true;
|
|
31
|
+
options: Options;
|
|
32
|
+
strict: true;
|
|
33
|
+
}>>['values'];
|
|
34
|
+
interface CommandDescription {
|
|
35
|
+
name: string;
|
|
36
|
+
options?: CommandOptions;
|
|
37
|
+
positional?: CommandPositional;
|
|
38
|
+
summary: string;
|
|
39
|
+
title: string;
|
|
40
|
+
}
|
|
41
|
+
interface CommandContext<Options extends CommandOptions> {
|
|
42
|
+
positionals: Array<string>;
|
|
43
|
+
rootPath: string;
|
|
44
|
+
signal: AbortSignal;
|
|
45
|
+
values: ParsedValues<Options>;
|
|
46
|
+
}
|
|
47
|
+
interface Command<Options extends CommandOptions, Context> extends CommandDescription {
|
|
48
|
+
interrupted?: string;
|
|
49
|
+
options?: Options;
|
|
50
|
+
run: (context: CommandContext<Options> & Context) => Promise<void> | void;
|
|
51
|
+
}
|
|
52
|
+
interface CommandRunnerOptions<Context> {
|
|
53
|
+
context?: (base: {
|
|
54
|
+
rootPath: string;
|
|
55
|
+
}) => Context | Promise<Context>;
|
|
56
|
+
usagePrefix?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function parseNumberOption(value: string, name: string): number;
|
|
59
|
+
export declare function createCommandRunner<Context extends object = object>(runnerOptions?: CommandRunnerOptions<Context>): {
|
|
60
|
+
defineCommand: <const Options extends CommandOptions>(command: Command<Options, Context>) => Command<Options, Context>;
|
|
61
|
+
runCommand: <const Options extends CommandOptions>(command: Command<Options, Context>) => Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/output/format.d.ts
|
|
65
|
+
export declare function formatCount(count: number): string;
|
|
66
|
+
export declare function formatBytes(bytes: number): string;
|
|
67
|
+
export declare function formatError(error: unknown): string;
|
|
68
|
+
export declare function formatDuration(milliseconds: number): string;
|
|
69
|
+
interface ProgressSnapshot {
|
|
70
|
+
done: number;
|
|
71
|
+
label: string;
|
|
72
|
+
note?: string;
|
|
73
|
+
perSecond?: number;
|
|
74
|
+
total: number;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/output/events.d.ts
|
|
78
|
+
type MessageLevel = 'detail' | 'error' | 'info' | 'step' | 'success' | 'title' | 'warn';
|
|
79
|
+
type StatusState = 'fail' | 'start' | 'stop' | 'succeed' | 'update' | 'warn';
|
|
80
|
+
type EndStatus = 'failed' | 'interrupted' | 'ok';
|
|
81
|
+
type SummaryRow = [label: string, value: number | string];
|
|
82
|
+
interface StartEvent {
|
|
83
|
+
title: string;
|
|
84
|
+
type: 'start';
|
|
85
|
+
}
|
|
86
|
+
interface MessageEvent {
|
|
87
|
+
level: MessageLevel;
|
|
88
|
+
text: string;
|
|
89
|
+
type: 'message';
|
|
90
|
+
}
|
|
91
|
+
interface StatusEvent {
|
|
92
|
+
id: number;
|
|
93
|
+
state: StatusState;
|
|
94
|
+
text: string;
|
|
95
|
+
type: 'status';
|
|
96
|
+
}
|
|
97
|
+
interface ProgressEvent extends ProgressSnapshot {
|
|
98
|
+
id: number;
|
|
99
|
+
type: 'progress';
|
|
100
|
+
}
|
|
101
|
+
interface SummaryEvent {
|
|
102
|
+
rows: Array<SummaryRow>;
|
|
103
|
+
type: 'summary';
|
|
104
|
+
}
|
|
105
|
+
interface DataEvent {
|
|
106
|
+
text?: string;
|
|
107
|
+
type: 'data';
|
|
108
|
+
value: unknown;
|
|
109
|
+
}
|
|
110
|
+
interface EndEvent {
|
|
111
|
+
detail?: string;
|
|
112
|
+
durationMs: number;
|
|
113
|
+
exitCode: number;
|
|
114
|
+
forced?: true;
|
|
115
|
+
message?: string;
|
|
116
|
+
status: EndStatus;
|
|
117
|
+
type: 'end';
|
|
118
|
+
}
|
|
119
|
+
type OutputEvent = DataEvent | EndEvent | MessageEvent | ProgressEvent | StartEvent | StatusEvent | SummaryEvent;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/output/output.d.ts
|
|
122
|
+
type OutputMode = 'json' | 'plain' | 'pretty';
|
|
123
|
+
export declare function getOutputMode(): OutputMode;
|
|
124
|
+
export declare function captureOutput<Result>(task: () => Promise<Result> | Result): Promise<{
|
|
125
|
+
events: OutputEvent[];
|
|
126
|
+
result: Awaited<Result>;
|
|
127
|
+
}>;
|
|
128
|
+
export declare const log: {
|
|
129
|
+
data<Value>(value: Value, format?: (value: Value) => string): void;
|
|
130
|
+
detail(text: string): void;
|
|
131
|
+
error(text: string): void;
|
|
132
|
+
info(text: string): void;
|
|
133
|
+
step(text: string): void;
|
|
134
|
+
success(text: string): void;
|
|
135
|
+
summary(rows: Array<SummaryRow>): void;
|
|
136
|
+
title(text: string): void;
|
|
137
|
+
warn(text: string): void;
|
|
138
|
+
};
|
|
139
|
+
export declare function createStatus(text: string): {
|
|
140
|
+
fail(finalText?: string): void;
|
|
141
|
+
stop(): void;
|
|
142
|
+
succeed(finalText?: string): void;
|
|
143
|
+
update(nextText: string): void;
|
|
144
|
+
warn(finalText?: string): void;
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/output/progress.d.ts
|
|
148
|
+
interface ProgressOptions {
|
|
149
|
+
label: string;
|
|
150
|
+
note?: () => string;
|
|
151
|
+
total: number;
|
|
152
|
+
}
|
|
153
|
+
export declare function createProgress({ label, note, total }: ProgressOptions): {
|
|
154
|
+
fail(text?: string): void;
|
|
155
|
+
succeed(text?: string): void;
|
|
156
|
+
tick(count?: number): void;
|
|
157
|
+
warn(text?: string): void;
|
|
158
|
+
};
|
|
159
|
+
export declare function withStatus<Result>(text: string, task: () => Promise<Result>): Promise<Result>;
|
|
160
|
+
export declare function withSpinner<Result>(text: string, task: () => Promise<Result>, describe?: (result: Result) => string): Promise<Result>;
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/user-error.d.ts
|
|
163
|
+
export declare class UserError extends Error {
|
|
164
|
+
name: string;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/workspace.d.ts
|
|
168
|
+
export declare function findWorkspaceRoot(startDir?: string): string;
|
|
169
|
+
//#endregion
|
|
170
|
+
export type { Command, CommandContext, ContentEntry, OutputEvent, OutputMode };
|
|
171
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { parseArgs, stripVTControlCharacters } from "node:util";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
//#region src/user-error.ts
|
|
7
|
+
var UserError = class extends Error {
|
|
8
|
+
name = "UserError";
|
|
9
|
+
};
|
|
10
|
+
function isUserError(error) {
|
|
11
|
+
return error instanceof Error && error.name === "UserError";
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/astro.ts
|
|
15
|
+
const configFilenames = [
|
|
16
|
+
"astro.config.mjs",
|
|
17
|
+
"astro.config.js",
|
|
18
|
+
"astro.config.ts",
|
|
19
|
+
"astro.config.mts"
|
|
20
|
+
];
|
|
21
|
+
function findAstroRoot(startDir = process.cwd()) {
|
|
22
|
+
let current = path.resolve(startDir);
|
|
23
|
+
while (current !== path.dirname(current)) {
|
|
24
|
+
if (configFilenames.some((filename) => existsSync(path.join(current, filename)))) return current;
|
|
25
|
+
current = path.dirname(current);
|
|
26
|
+
}
|
|
27
|
+
throw new UserError(`Could not locate an Astro config above ${startDir}`);
|
|
28
|
+
}
|
|
29
|
+
async function getCollectionEntries({ getCollection }, collections) {
|
|
30
|
+
const flattened = (await Promise.all(collections.map((collection) => getCollection(collection)))).flat();
|
|
31
|
+
if (flattened.length === 0) throw new UserError(`No entries found in: ${collections.join(", ")}. Run \`astro sync\` to write the content store.`);
|
|
32
|
+
return flattened;
|
|
33
|
+
}
|
|
34
|
+
async function withAstroModules(callback) {
|
|
35
|
+
const root = findAstroRoot();
|
|
36
|
+
const [{ getViteConfig }, { createLogger, createServer, createServerModuleRunner }] = await Promise.all([import("astro/config"), import("vite")]);
|
|
37
|
+
const server = await createServer({
|
|
38
|
+
...await getViteConfig({}, {
|
|
39
|
+
logLevel: "silent",
|
|
40
|
+
root
|
|
41
|
+
})({
|
|
42
|
+
command: "serve",
|
|
43
|
+
mode: "development"
|
|
44
|
+
}),
|
|
45
|
+
customLogger: createLogger("silent"),
|
|
46
|
+
logLevel: "silent",
|
|
47
|
+
server: {
|
|
48
|
+
hmr: false,
|
|
49
|
+
middlewareMode: true,
|
|
50
|
+
ws: false
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
return await callback(createServerModuleRunner(server.environments.ssr));
|
|
55
|
+
} finally {
|
|
56
|
+
await server.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function withAstroContent(callback) {
|
|
60
|
+
return withAstroModules(async (runner) => callback(await runner.import("astro:content")));
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/output/reporter-json.ts
|
|
64
|
+
const throttleMs = 1e3;
|
|
65
|
+
function strip(text) {
|
|
66
|
+
return text === void 0 ? void 0 : stripVTControlCharacters(text);
|
|
67
|
+
}
|
|
68
|
+
function stripEvent(event) {
|
|
69
|
+
switch (event.type) {
|
|
70
|
+
case "data": return event;
|
|
71
|
+
case "end": {
|
|
72
|
+
const { detail, message, ...rest } = event;
|
|
73
|
+
return {
|
|
74
|
+
...rest,
|
|
75
|
+
...detail === void 0 ? {} : { detail: strip(detail) },
|
|
76
|
+
...message === void 0 ? {} : { message: strip(message) }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case "message":
|
|
80
|
+
case "status": return {
|
|
81
|
+
...event,
|
|
82
|
+
text: strip(event.text)
|
|
83
|
+
};
|
|
84
|
+
case "progress": {
|
|
85
|
+
const { note, ...rest } = event;
|
|
86
|
+
return {
|
|
87
|
+
...rest,
|
|
88
|
+
label: strip(event.label),
|
|
89
|
+
...note ? { note: strip(note) } : {}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
case "start": return {
|
|
93
|
+
...event,
|
|
94
|
+
title: strip(event.title)
|
|
95
|
+
};
|
|
96
|
+
case "summary": return {
|
|
97
|
+
...event,
|
|
98
|
+
rows: event.rows.map(([label, value]) => [strip(label), typeof value === "string" ? strip(value) : value])
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function createJsonReporter({ stderr, stdout }, { command, now = Date.now } = {}) {
|
|
103
|
+
const progressThrottles = /* @__PURE__ */ new Map();
|
|
104
|
+
const updateThrottles = /* @__PURE__ */ new Map();
|
|
105
|
+
function write(event) {
|
|
106
|
+
const { type, ...payload } = stripEvent(event);
|
|
107
|
+
const envelope = {
|
|
108
|
+
command,
|
|
109
|
+
time: new Date(now()).toISOString()
|
|
110
|
+
};
|
|
111
|
+
stderr.write(`${JSON.stringify({
|
|
112
|
+
type,
|
|
113
|
+
...envelope,
|
|
114
|
+
...payload
|
|
115
|
+
})}\n`);
|
|
116
|
+
}
|
|
117
|
+
function isDue(throttles, id) {
|
|
118
|
+
const time = now();
|
|
119
|
+
const throttle = throttles.get(id);
|
|
120
|
+
if (throttle && time - throttle.lastSent < throttleMs) return false;
|
|
121
|
+
throttles.set(id, { lastSent: time });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return { emit(event) {
|
|
125
|
+
if (event.type === "data") {
|
|
126
|
+
stdout.write(`${JSON.stringify(event.value)}\n`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (event.type === "progress") {
|
|
130
|
+
if (isDue(progressThrottles, event.id)) {
|
|
131
|
+
write(event);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const throttle = progressThrottles.get(event.id);
|
|
135
|
+
if (throttle) throttle.pending = event;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (event.type === "status" && event.state === "update") {
|
|
139
|
+
if (isDue(updateThrottles, event.id)) write(event);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (event.type === "status" && event.state !== "start") {
|
|
143
|
+
const pending = progressThrottles.get(event.id)?.pending;
|
|
144
|
+
if (pending) write(pending);
|
|
145
|
+
progressThrottles.delete(event.id);
|
|
146
|
+
updateThrottles.delete(event.id);
|
|
147
|
+
}
|
|
148
|
+
write(event);
|
|
149
|
+
} };
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/output/format.ts
|
|
153
|
+
const countFormat = new Intl.NumberFormat("en-CA");
|
|
154
|
+
const byteUnits = [
|
|
155
|
+
"B",
|
|
156
|
+
"kB",
|
|
157
|
+
"MB",
|
|
158
|
+
"GB",
|
|
159
|
+
"TB"
|
|
160
|
+
];
|
|
161
|
+
function formatCount(count) {
|
|
162
|
+
return countFormat.format(count);
|
|
163
|
+
}
|
|
164
|
+
function formatBytes(bytes) {
|
|
165
|
+
let value = bytes;
|
|
166
|
+
let unitIndex = 0;
|
|
167
|
+
while (value >= 1e3 && unitIndex < byteUnits.length - 1) {
|
|
168
|
+
value /= 1e3;
|
|
169
|
+
unitIndex += 1;
|
|
170
|
+
}
|
|
171
|
+
const unit = byteUnits[unitIndex] ?? "B";
|
|
172
|
+
return unitIndex === 0 ? `${String(value)} ${unit}` : `${value.toFixed(1)} ${unit}`;
|
|
173
|
+
}
|
|
174
|
+
function formatError(error) {
|
|
175
|
+
return error instanceof Error ? error.message : String(error);
|
|
176
|
+
}
|
|
177
|
+
function formatDuration(milliseconds) {
|
|
178
|
+
const seconds = milliseconds / 1e3;
|
|
179
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
180
|
+
const totalSeconds = Math.round(seconds);
|
|
181
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
182
|
+
const remainder = totalSeconds % 60;
|
|
183
|
+
return `${String(minutes)}m ${String(remainder).padStart(2, "0")}s`;
|
|
184
|
+
}
|
|
185
|
+
function formatRate(perSecond) {
|
|
186
|
+
return perSecond >= 10 ? formatCount(Math.round(perSecond)) : perSecond.toFixed(1);
|
|
187
|
+
}
|
|
188
|
+
function formatProgressText({ done, label, note, perSecond, total }) {
|
|
189
|
+
const percent = total === 0 ? 100 : Math.floor(done / total * 100);
|
|
190
|
+
const parts = [`${label} ${formatCount(done)} / ${formatCount(total)} (${String(percent)}%)`];
|
|
191
|
+
const remaining = total - done;
|
|
192
|
+
if (perSecond !== void 0 && perSecond > 0) {
|
|
193
|
+
parts.push(`${formatRate(perSecond)}/s`);
|
|
194
|
+
if (remaining > 0) parts.push(`~${formatDuration(remaining / perSecond * 1e3)} left`);
|
|
195
|
+
}
|
|
196
|
+
if (note) parts.push(note);
|
|
197
|
+
return parts.join(" · ");
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/output/human-format.ts
|
|
201
|
+
function unpainted(text) {
|
|
202
|
+
return text;
|
|
203
|
+
}
|
|
204
|
+
const plainPalette = {
|
|
205
|
+
blue: unpainted,
|
|
206
|
+
gray: unpainted,
|
|
207
|
+
green: unpainted,
|
|
208
|
+
magenta: unpainted,
|
|
209
|
+
red: unpainted,
|
|
210
|
+
yellow: unpainted
|
|
211
|
+
};
|
|
212
|
+
function getStateSymbol(state, palette) {
|
|
213
|
+
if (state === "succeed") return palette.green("✓");
|
|
214
|
+
if (state === "fail") return palette.red("✗");
|
|
215
|
+
return palette.yellow("⚠");
|
|
216
|
+
}
|
|
217
|
+
function formatMessage(level, text, palette) {
|
|
218
|
+
switch (level) {
|
|
219
|
+
case "detail": return palette.gray(` ${text}`);
|
|
220
|
+
case "error": return `${getStateSymbol("fail", palette)} ${palette.red(text)}`;
|
|
221
|
+
case "info": return text;
|
|
222
|
+
case "step": return palette.blue(text);
|
|
223
|
+
case "success": return `${getStateSymbol("succeed", palette)} ${text}`;
|
|
224
|
+
case "title": return palette.magenta(`=== ${text} ===`);
|
|
225
|
+
case "warn": return `${getStateSymbol("warn", palette)} ${palette.yellow(text)}`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function formatSummaryRows(rows, palette) {
|
|
229
|
+
const width = Math.max(...rows.map(([label]) => label.length)) + 2;
|
|
230
|
+
return rows.map(([label, value]) => {
|
|
231
|
+
const formatted = typeof value === "number" ? formatCount(value) : value;
|
|
232
|
+
return ` ${palette.gray(`${label}:`.padEnd(width))}${formatted}`;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function formatData(value) {
|
|
236
|
+
return typeof value === "string" ? value : JSON.stringify(value, void 0, 2);
|
|
237
|
+
}
|
|
238
|
+
function formatEnd(event, palette) {
|
|
239
|
+
if (event.status === "ok") return [formatMessage("detail", `Finished in ${formatDuration(event.durationMs)}`, palette)];
|
|
240
|
+
const level = event.status === "failed" || event.forced ? "error" : "warn";
|
|
241
|
+
const lines = event.message === void 0 ? [] : [formatMessage(level, event.message, palette)];
|
|
242
|
+
if (event.detail) lines.push(formatMessage("detail", event.detail, palette));
|
|
243
|
+
return lines;
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/output/reporter-plain.ts
|
|
247
|
+
const progressIntervalMs = 1e4;
|
|
248
|
+
function createPlainReporter({ stderr, stdout }, now = Date.now) {
|
|
249
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
250
|
+
const openIds = [];
|
|
251
|
+
function writeLine(line) {
|
|
252
|
+
stderr.write(`${stripVTControlCharacters(line)}\n`);
|
|
253
|
+
}
|
|
254
|
+
function closeStatus(id) {
|
|
255
|
+
statuses.delete(id);
|
|
256
|
+
openIds.splice(openIds.indexOf(id), 1);
|
|
257
|
+
}
|
|
258
|
+
return { emit(event) {
|
|
259
|
+
switch (event.type) {
|
|
260
|
+
case "data": {
|
|
261
|
+
const line = event.text === void 0 ? formatData(event.value) : stripVTControlCharacters(event.text);
|
|
262
|
+
stdout.write(`${line}\n`);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "end": {
|
|
266
|
+
const activeId = openIds.at(-1);
|
|
267
|
+
const active = activeId === void 0 ? void 0 : statuses.get(activeId);
|
|
268
|
+
if (active && event.status !== "ok") writeLine(`${getStateSymbol("fail", plainPalette)} ${active.text}`);
|
|
269
|
+
statuses.clear();
|
|
270
|
+
openIds.length = 0;
|
|
271
|
+
for (const line of formatEnd(event, plainPalette)) writeLine(line);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "message":
|
|
275
|
+
writeLine(formatMessage(event.level, event.text, plainPalette));
|
|
276
|
+
break;
|
|
277
|
+
case "progress": {
|
|
278
|
+
const status = statuses.get(event.id);
|
|
279
|
+
const time = now();
|
|
280
|
+
if (!status || time - status.lastPrint < progressIntervalMs) break;
|
|
281
|
+
status.lastPrint = time;
|
|
282
|
+
writeLine(formatProgressText(event));
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "start":
|
|
286
|
+
writeLine(formatMessage("title", event.title, plainPalette));
|
|
287
|
+
break;
|
|
288
|
+
case "status": {
|
|
289
|
+
const { id, state, text } = event;
|
|
290
|
+
if (state === "start") {
|
|
291
|
+
statuses.set(id, {
|
|
292
|
+
lastPrint: now(),
|
|
293
|
+
text
|
|
294
|
+
});
|
|
295
|
+
openIds.push(id);
|
|
296
|
+
writeLine(text);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
const status = statuses.get(id);
|
|
300
|
+
if (!status) break;
|
|
301
|
+
if (state === "update") {
|
|
302
|
+
status.text = text;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (state !== "stop") writeLine(`${getStateSymbol(state, plainPalette)} ${text}`);
|
|
306
|
+
closeStatus(id);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "summary": for (const line of formatSummaryRows(event.rows, plainPalette)) writeLine(line);
|
|
310
|
+
}
|
|
311
|
+
} };
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/output/reporter-pretty.ts
|
|
315
|
+
function isTerminal(stream) {
|
|
316
|
+
return "isTTY" in stream && stream.isTTY === true;
|
|
317
|
+
}
|
|
318
|
+
function createPrettyReporter({ stderr, stdout }) {
|
|
319
|
+
const spinners = /* @__PURE__ */ new Map();
|
|
320
|
+
const openIds = [];
|
|
321
|
+
function getActiveSpinner() {
|
|
322
|
+
const activeId = openIds.at(-1);
|
|
323
|
+
return activeId === void 0 ? void 0 : spinners.get(activeId);
|
|
324
|
+
}
|
|
325
|
+
function writeAround(stream, line) {
|
|
326
|
+
const active = getActiveSpinner();
|
|
327
|
+
if (!active?.isSpinning) {
|
|
328
|
+
stream.write(`${line}\n`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
active.clear();
|
|
332
|
+
stream.write(`${line}\n`);
|
|
333
|
+
active.render();
|
|
334
|
+
}
|
|
335
|
+
function writeLine(line) {
|
|
336
|
+
writeAround(stderr, line);
|
|
337
|
+
}
|
|
338
|
+
function endSpinner(id, state, text) {
|
|
339
|
+
const spinner = spinners.get(id);
|
|
340
|
+
if (!spinner) return;
|
|
341
|
+
const wasActive = openIds.at(-1) === id;
|
|
342
|
+
if (state === "stop") spinner.stop();
|
|
343
|
+
else spinner.stopAndPersist({
|
|
344
|
+
symbol: getStateSymbol(state, chalk),
|
|
345
|
+
text
|
|
346
|
+
});
|
|
347
|
+
spinners.delete(id);
|
|
348
|
+
openIds.splice(openIds.indexOf(id), 1);
|
|
349
|
+
const outer = getActiveSpinner();
|
|
350
|
+
if (wasActive && outer?.isEnabled) outer.start();
|
|
351
|
+
}
|
|
352
|
+
function onStatus({ id, state, text }) {
|
|
353
|
+
if (state === "start") {
|
|
354
|
+
getActiveSpinner()?.stop();
|
|
355
|
+
spinners.set(id, ora({
|
|
356
|
+
discardStdin: false,
|
|
357
|
+
stream: stderr,
|
|
358
|
+
text
|
|
359
|
+
}).start());
|
|
360
|
+
openIds.push(id);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (state === "update") {
|
|
364
|
+
const spinner = spinners.get(id);
|
|
365
|
+
if (spinner) spinner.text = text;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
endSpinner(id, state, text);
|
|
369
|
+
}
|
|
370
|
+
return { emit(event) {
|
|
371
|
+
switch (event.type) {
|
|
372
|
+
case "data": {
|
|
373
|
+
const line = event.text ?? formatData(event.value);
|
|
374
|
+
if (isTerminal(stdout)) writeAround(stdout, line);
|
|
375
|
+
else stdout.write(`${line}\n`);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case "end": {
|
|
379
|
+
const activeId = openIds.at(-1);
|
|
380
|
+
const active = getActiveSpinner();
|
|
381
|
+
if (active && activeId !== void 0 && event.status !== "ok") {
|
|
382
|
+
spinners.delete(activeId);
|
|
383
|
+
active.stopAndPersist({
|
|
384
|
+
symbol: getStateSymbol("fail", chalk),
|
|
385
|
+
text: active.text
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
for (const spinner of spinners.values()) spinner.stop();
|
|
389
|
+
spinners.clear();
|
|
390
|
+
openIds.length = 0;
|
|
391
|
+
for (const line of formatEnd(event, chalk)) writeLine(line);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case "message":
|
|
395
|
+
writeLine(formatMessage(event.level, event.text, chalk));
|
|
396
|
+
break;
|
|
397
|
+
case "progress": {
|
|
398
|
+
const spinner = spinners.get(event.id);
|
|
399
|
+
if (spinner) spinner.text = formatProgressText(event);
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case "start":
|
|
403
|
+
writeLine(formatMessage("title", event.title, chalk));
|
|
404
|
+
break;
|
|
405
|
+
case "status":
|
|
406
|
+
onStatus(event);
|
|
407
|
+
break;
|
|
408
|
+
case "summary": for (const line of formatSummaryRows(event.rows, chalk)) writeLine(line);
|
|
409
|
+
}
|
|
410
|
+
} };
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/output/output.ts
|
|
414
|
+
const outputModes = /* @__PURE__ */ new Set([
|
|
415
|
+
"json",
|
|
416
|
+
"plain",
|
|
417
|
+
"pretty"
|
|
418
|
+
]);
|
|
419
|
+
let currentMode;
|
|
420
|
+
let currentReporter;
|
|
421
|
+
let capturingReporter;
|
|
422
|
+
let nextStatusId = 1;
|
|
423
|
+
function isOutputMode(value) {
|
|
424
|
+
return outputModes.has(value);
|
|
425
|
+
}
|
|
426
|
+
function detectOutputMode() {
|
|
427
|
+
const requested = process.env.SCRIPTS_OUTPUT;
|
|
428
|
+
if (isOutputMode(requested)) return requested;
|
|
429
|
+
return process.stderr.isTTY ? "pretty" : "plain";
|
|
430
|
+
}
|
|
431
|
+
function createReporter(mode, command) {
|
|
432
|
+
const streams = {
|
|
433
|
+
stderr: process.stderr,
|
|
434
|
+
stdout: process.stdout
|
|
435
|
+
};
|
|
436
|
+
if (mode === "json") return createJsonReporter(streams, { command });
|
|
437
|
+
if (mode === "plain") return createPlainReporter(streams);
|
|
438
|
+
return createPrettyReporter(streams);
|
|
439
|
+
}
|
|
440
|
+
function configureOutput(mode = detectOutputMode(), command) {
|
|
441
|
+
currentMode = mode;
|
|
442
|
+
currentReporter = createReporter(mode, command);
|
|
443
|
+
}
|
|
444
|
+
function getOutputMode() {
|
|
445
|
+
if (currentMode === void 0) currentMode = detectOutputMode();
|
|
446
|
+
return currentMode;
|
|
447
|
+
}
|
|
448
|
+
function emit(event) {
|
|
449
|
+
if (capturingReporter) {
|
|
450
|
+
capturingReporter.emit(event);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (currentReporter === void 0) currentReporter = createReporter(getOutputMode());
|
|
454
|
+
currentReporter.emit(event);
|
|
455
|
+
}
|
|
456
|
+
async function captureOutput(task) {
|
|
457
|
+
const events = [];
|
|
458
|
+
const previous = capturingReporter;
|
|
459
|
+
capturingReporter = { emit(event) {
|
|
460
|
+
events.push(event);
|
|
461
|
+
} };
|
|
462
|
+
try {
|
|
463
|
+
return {
|
|
464
|
+
events,
|
|
465
|
+
result: await task()
|
|
466
|
+
};
|
|
467
|
+
} finally {
|
|
468
|
+
capturingReporter = previous;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function emitMessage(level, text) {
|
|
472
|
+
emit({
|
|
473
|
+
level,
|
|
474
|
+
text,
|
|
475
|
+
type: "message"
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const log = {
|
|
479
|
+
data(value, format) {
|
|
480
|
+
emit(format ? {
|
|
481
|
+
text: format(value),
|
|
482
|
+
type: "data",
|
|
483
|
+
value
|
|
484
|
+
} : {
|
|
485
|
+
type: "data",
|
|
486
|
+
value
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
detail(text) {
|
|
490
|
+
emitMessage("detail", text);
|
|
491
|
+
},
|
|
492
|
+
error(text) {
|
|
493
|
+
emitMessage("error", text);
|
|
494
|
+
},
|
|
495
|
+
info(text) {
|
|
496
|
+
emitMessage("info", text);
|
|
497
|
+
},
|
|
498
|
+
step(text) {
|
|
499
|
+
emitMessage("step", text);
|
|
500
|
+
},
|
|
501
|
+
success(text) {
|
|
502
|
+
emitMessage("success", text);
|
|
503
|
+
},
|
|
504
|
+
summary(rows) {
|
|
505
|
+
emit({
|
|
506
|
+
rows,
|
|
507
|
+
type: "summary"
|
|
508
|
+
});
|
|
509
|
+
},
|
|
510
|
+
title(text) {
|
|
511
|
+
emitMessage("title", text);
|
|
512
|
+
},
|
|
513
|
+
warn(text) {
|
|
514
|
+
emitMessage("warn", text);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
function startStatus(initialText) {
|
|
518
|
+
const id = nextStatusId;
|
|
519
|
+
let text = initialText;
|
|
520
|
+
nextStatusId += 1;
|
|
521
|
+
emit({
|
|
522
|
+
id,
|
|
523
|
+
state: "start",
|
|
524
|
+
text,
|
|
525
|
+
type: "status"
|
|
526
|
+
});
|
|
527
|
+
function finish(state, finalText) {
|
|
528
|
+
text = finalText ?? text;
|
|
529
|
+
emit({
|
|
530
|
+
id,
|
|
531
|
+
state,
|
|
532
|
+
text,
|
|
533
|
+
type: "status"
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
id,
|
|
538
|
+
status: {
|
|
539
|
+
fail(finalText) {
|
|
540
|
+
finish("fail", finalText);
|
|
541
|
+
},
|
|
542
|
+
stop() {
|
|
543
|
+
finish("stop");
|
|
544
|
+
},
|
|
545
|
+
succeed(finalText) {
|
|
546
|
+
finish("succeed", finalText);
|
|
547
|
+
},
|
|
548
|
+
update(nextText) {
|
|
549
|
+
text = nextText;
|
|
550
|
+
emit({
|
|
551
|
+
id,
|
|
552
|
+
state: "update",
|
|
553
|
+
text,
|
|
554
|
+
type: "status"
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
warn(finalText) {
|
|
558
|
+
finish("warn", finalText);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function createStatus(text) {
|
|
564
|
+
return startStatus(text).status;
|
|
565
|
+
}
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/workspace.ts
|
|
568
|
+
function findWorkspaceRoot(startDir = process.cwd()) {
|
|
569
|
+
let current = path.resolve(startDir);
|
|
570
|
+
while (current !== path.dirname(current)) {
|
|
571
|
+
if (existsSync(path.join(current, "pnpm-workspace.yaml"))) return current;
|
|
572
|
+
current = path.dirname(current);
|
|
573
|
+
}
|
|
574
|
+
throw new UserError(`Could not locate pnpm-workspace.yaml above ${startDir}`);
|
|
575
|
+
}
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/command.ts
|
|
578
|
+
const interruptedExitCode = 130;
|
|
579
|
+
const interruptBurstMs = 1e3;
|
|
580
|
+
const builtInOptions = [["json", {
|
|
581
|
+
description: "Print output as JSON lines",
|
|
582
|
+
type: "boolean"
|
|
583
|
+
}], ["help", {
|
|
584
|
+
description: "Show this help",
|
|
585
|
+
short: "h",
|
|
586
|
+
type: "boolean"
|
|
587
|
+
}]];
|
|
588
|
+
function parseNumberOption(value, name) {
|
|
589
|
+
const parsed = Number(value);
|
|
590
|
+
if (value.trim() === "" || !Number.isFinite(parsed)) throw new UserError(`--${name} expects a number, got "${value}"`);
|
|
591
|
+
return parsed;
|
|
592
|
+
}
|
|
593
|
+
function getUsage(command, usagePrefix) {
|
|
594
|
+
const positional = command.positional ? ` [${command.positional.name}]` : "";
|
|
595
|
+
return `${usagePrefix} ${command.name}${positional} [options]`;
|
|
596
|
+
}
|
|
597
|
+
function getOptionLabel(name, option) {
|
|
598
|
+
const flag = option.short ? `-${option.short}, --${name}` : ` --${name}`;
|
|
599
|
+
return option.type === "string" ? `${flag} <value>` : flag;
|
|
600
|
+
}
|
|
601
|
+
function getOptionDescription(option) {
|
|
602
|
+
const { default: defaultValue, description } = option;
|
|
603
|
+
if (defaultValue === void 0 || defaultValue === false) return description;
|
|
604
|
+
if (Array.isArray(defaultValue) && defaultValue.length === 0) return description;
|
|
605
|
+
return `${description} (default: ${String(defaultValue)})`;
|
|
606
|
+
}
|
|
607
|
+
function formatHelp(command, usagePrefix = "pnpm") {
|
|
608
|
+
const rows = [...Object.entries(command.options ?? {}), ...builtInOptions].map(([name, option]) => [getOptionLabel(name, option), getOptionDescription(option)]);
|
|
609
|
+
if (command.positional) rows.unshift([command.positional.name, command.positional.description]);
|
|
610
|
+
const width = Math.max(...rows.map(([label]) => label.length)) + 2;
|
|
611
|
+
return [
|
|
612
|
+
chalk.magenta(command.title),
|
|
613
|
+
command.summary,
|
|
614
|
+
"",
|
|
615
|
+
`Usage: ${getUsage(command, usagePrefix)}`,
|
|
616
|
+
"",
|
|
617
|
+
...rows.map(([label, description]) => ` ${label.padEnd(width)}${chalk.gray(description)}`)
|
|
618
|
+
].join("\n");
|
|
619
|
+
}
|
|
620
|
+
function isParseError(error) {
|
|
621
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" && error.code.startsWith("ERR_PARSE_ARGS_");
|
|
622
|
+
}
|
|
623
|
+
function isChildProcessFailure(error) {
|
|
624
|
+
return error instanceof Error && "exitCode" in error && "signal" in error;
|
|
625
|
+
}
|
|
626
|
+
function isInterruption(error, isAborted) {
|
|
627
|
+
if (isAborted) return true;
|
|
628
|
+
return isChildProcessFailure(error) && (error.exitCode === interruptedExitCode || error.signal === "SIGINT");
|
|
629
|
+
}
|
|
630
|
+
function describeError(error) {
|
|
631
|
+
if (isParseError(error)) return {
|
|
632
|
+
detail: "See --help",
|
|
633
|
+
message: error.message
|
|
634
|
+
};
|
|
635
|
+
if (isUserError(error)) return { message: error.message };
|
|
636
|
+
if (isChildProcessFailure(error)) {
|
|
637
|
+
const message = `Child process ${error.signal ? `was killed by ${error.signal}` : `exited with code ${String(error.exitCode)}`}`;
|
|
638
|
+
const stderr = typeof error.stderr === "string" ? error.stderr.trimEnd() : "";
|
|
639
|
+
if (stderr === "") return { message };
|
|
640
|
+
return {
|
|
641
|
+
detail: stderr.split("\n").slice(-20).join("\n"),
|
|
642
|
+
message
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (!(error instanceof Error)) return { message: String(error) };
|
|
646
|
+
const stack = error.stack?.split("\n").slice(1).join("\n");
|
|
647
|
+
return stack ? {
|
|
648
|
+
detail: stack,
|
|
649
|
+
message: error.message
|
|
650
|
+
} : { message: error.message };
|
|
651
|
+
}
|
|
652
|
+
function getFailureReport(error, { interrupted, isAborted }) {
|
|
653
|
+
if (isInterruption(error, isAborted)) return {
|
|
654
|
+
exitCode: interruptedExitCode,
|
|
655
|
+
isInterrupted: true,
|
|
656
|
+
message: interrupted ? `Interrupted; ${interrupted}` : "Interrupted"
|
|
657
|
+
};
|
|
658
|
+
return {
|
|
659
|
+
exitCode: 1,
|
|
660
|
+
isInterrupted: false,
|
|
661
|
+
...describeError(error)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function parseCommandArgs(command) {
|
|
665
|
+
const options = [...Object.entries(command.options ?? {}), ...builtInOptions];
|
|
666
|
+
return parseArgs({
|
|
667
|
+
allowPositionals: command.positional !== void 0,
|
|
668
|
+
args: process.argv.slice(2),
|
|
669
|
+
options: Object.fromEntries(options.map(([name, { description: _description, ...descriptor }]) => [name, descriptor])),
|
|
670
|
+
strict: true
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
function selectOutput(isJson) {
|
|
674
|
+
if (isJson) {
|
|
675
|
+
process.env.SCRIPTS_OUTPUT = "json";
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const requested = process.env.SCRIPTS_OUTPUT;
|
|
679
|
+
if (requested && !isOutputMode(requested)) throw new UserError(`SCRIPTS_OUTPUT must be pretty, plain or json, got "${requested}"`);
|
|
680
|
+
}
|
|
681
|
+
function createCommandRunner(runnerOptions = {}) {
|
|
682
|
+
const { context: createContext, usagePrefix = "pnpm" } = runnerOptions;
|
|
683
|
+
function defineCommand(command) {
|
|
684
|
+
return command;
|
|
685
|
+
}
|
|
686
|
+
async function runCommand(command) {
|
|
687
|
+
const controller = new AbortController();
|
|
688
|
+
const startTime = Date.now();
|
|
689
|
+
let interruptTime;
|
|
690
|
+
const isJson = process.argv.slice(2).includes("--json");
|
|
691
|
+
configureOutput(isJson ? "json" : void 0, command.name);
|
|
692
|
+
function onInterrupt() {
|
|
693
|
+
if (interruptTime === void 0) {
|
|
694
|
+
interruptTime = Date.now();
|
|
695
|
+
controller.abort();
|
|
696
|
+
log.warn("Stopping; press Ctrl-C again to force quit");
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (Date.now() - interruptTime < interruptBurstMs) return;
|
|
700
|
+
emit({
|
|
701
|
+
durationMs: Date.now() - startTime,
|
|
702
|
+
exitCode: interruptedExitCode,
|
|
703
|
+
forced: true,
|
|
704
|
+
message: "Force quit",
|
|
705
|
+
status: "interrupted",
|
|
706
|
+
type: "end"
|
|
707
|
+
});
|
|
708
|
+
process.exit(interruptedExitCode);
|
|
709
|
+
}
|
|
710
|
+
process.on("SIGINT", onInterrupt);
|
|
711
|
+
process.on("SIGTERM", onInterrupt);
|
|
712
|
+
let hasStarted = false;
|
|
713
|
+
try {
|
|
714
|
+
const { positionals, values } = parseCommandArgs(command);
|
|
715
|
+
if (values.help) {
|
|
716
|
+
process.stdout.write(`${formatHelp(command, usagePrefix)}\n`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
selectOutput(isJson);
|
|
720
|
+
emit({
|
|
721
|
+
title: command.title,
|
|
722
|
+
type: "start"
|
|
723
|
+
});
|
|
724
|
+
hasStarted = true;
|
|
725
|
+
const rootPath = findWorkspaceRoot();
|
|
726
|
+
const context = createContext ? await createContext({ rootPath }) : {};
|
|
727
|
+
await command.run({
|
|
728
|
+
...context,
|
|
729
|
+
positionals,
|
|
730
|
+
rootPath,
|
|
731
|
+
signal: controller.signal,
|
|
732
|
+
values
|
|
733
|
+
});
|
|
734
|
+
controller.signal.throwIfAborted();
|
|
735
|
+
emit({
|
|
736
|
+
durationMs: Date.now() - startTime,
|
|
737
|
+
exitCode: 0,
|
|
738
|
+
status: "ok",
|
|
739
|
+
type: "end"
|
|
740
|
+
});
|
|
741
|
+
} catch (error) {
|
|
742
|
+
if (!hasStarted && getOutputMode() === "json") emit({
|
|
743
|
+
title: command.title,
|
|
744
|
+
type: "start"
|
|
745
|
+
});
|
|
746
|
+
const report = getFailureReport(error, {
|
|
747
|
+
interrupted: command.interrupted,
|
|
748
|
+
isAborted: controller.signal.aborted
|
|
749
|
+
});
|
|
750
|
+
emit({
|
|
751
|
+
...report.detail === void 0 ? {} : { detail: report.detail },
|
|
752
|
+
durationMs: Date.now() - startTime,
|
|
753
|
+
exitCode: report.exitCode,
|
|
754
|
+
message: report.message,
|
|
755
|
+
status: report.isInterrupted ? "interrupted" : "failed",
|
|
756
|
+
type: "end"
|
|
757
|
+
});
|
|
758
|
+
process.exitCode = report.exitCode;
|
|
759
|
+
} finally {
|
|
760
|
+
process.off("SIGINT", onInterrupt);
|
|
761
|
+
process.off("SIGTERM", onInterrupt);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
defineCommand,
|
|
766
|
+
runCommand
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region src/output/progress.ts
|
|
771
|
+
const renderIntervalMs = 100;
|
|
772
|
+
const rateWindowMs = 1e4;
|
|
773
|
+
function createRateWindow(windowMs = rateWindowMs) {
|
|
774
|
+
const samples = [];
|
|
775
|
+
return {
|
|
776
|
+
add(count, time) {
|
|
777
|
+
samples.push({
|
|
778
|
+
count,
|
|
779
|
+
time
|
|
780
|
+
});
|
|
781
|
+
while (samples.length > 2 && time - (samples[0]?.time ?? time) > windowMs) samples.shift();
|
|
782
|
+
},
|
|
783
|
+
perSecond() {
|
|
784
|
+
const first = samples.at(0);
|
|
785
|
+
const last = samples.at(-1);
|
|
786
|
+
if (!first || !last || last.time <= first.time) return;
|
|
787
|
+
return (last.count - first.count) * 1e3 / (last.time - first.time);
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function createProgress({ label, note, total }) {
|
|
792
|
+
const rateWindow = createRateWindow();
|
|
793
|
+
const startTime = Date.now();
|
|
794
|
+
let done = 0;
|
|
795
|
+
let lastRender = 0;
|
|
796
|
+
rateWindow.add(0, startTime);
|
|
797
|
+
function getSnapshot() {
|
|
798
|
+
const noteText = note?.();
|
|
799
|
+
const perSecond = rateWindow.perSecond();
|
|
800
|
+
return {
|
|
801
|
+
done,
|
|
802
|
+
label,
|
|
803
|
+
total,
|
|
804
|
+
...noteText ? { note: noteText } : {},
|
|
805
|
+
...perSecond === void 0 ? {} : { perSecond }
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const { id, status } = startStatus(formatProgressText(getSnapshot()));
|
|
809
|
+
function render() {
|
|
810
|
+
const now = Date.now();
|
|
811
|
+
if (now - lastRender < renderIntervalMs) return;
|
|
812
|
+
lastRender = now;
|
|
813
|
+
rateWindow.add(done, now);
|
|
814
|
+
emit({
|
|
815
|
+
id,
|
|
816
|
+
type: "progress",
|
|
817
|
+
...getSnapshot()
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function finish(state, text) {
|
|
821
|
+
const snapshot = getSnapshot();
|
|
822
|
+
emit({
|
|
823
|
+
id,
|
|
824
|
+
type: "progress",
|
|
825
|
+
...snapshot
|
|
826
|
+
});
|
|
827
|
+
status[state](text ?? formatProgressText(snapshot));
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
fail(text) {
|
|
831
|
+
finish("fail", text);
|
|
832
|
+
},
|
|
833
|
+
succeed(text) {
|
|
834
|
+
finish("succeed", text);
|
|
835
|
+
},
|
|
836
|
+
tick(count = 1) {
|
|
837
|
+
done += count;
|
|
838
|
+
render();
|
|
839
|
+
},
|
|
840
|
+
warn(text) {
|
|
841
|
+
finish("warn", text);
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
async function withStatus(text, task) {
|
|
846
|
+
const status = createStatus(text);
|
|
847
|
+
const result = await task();
|
|
848
|
+
status.stop();
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
async function withSpinner(text, task, describe) {
|
|
852
|
+
const status = createStatus(text);
|
|
853
|
+
try {
|
|
854
|
+
const result = await task();
|
|
855
|
+
status.succeed(describe?.(result) ?? text);
|
|
856
|
+
return result;
|
|
857
|
+
} catch (error) {
|
|
858
|
+
status.fail(text);
|
|
859
|
+
throw error;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
//#endregion
|
|
863
|
+
export { UserError, captureOutput, createCommandRunner, createProgress, createStatus, findAstroRoot, findWorkspaceRoot, formatBytes, formatCount, formatDuration, formatError, getCollectionEntries, getOutputMode, log, parseNumberOption, withAstroContent, withAstroModules, withSpinner, withStatus };
|
|
864
|
+
|
|
865
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/user-error.ts","../src/astro.ts","../src/output/reporter-json.ts","../src/output/format.ts","../src/output/human-format.ts","../src/output/reporter-plain.ts","../src/output/reporter-pretty.ts","../src/output/output.ts","../src/workspace.ts","../src/command.ts","../src/output/progress.ts"],"sourcesContent":["// Matched by `name`, so a library's own `UserError` class reports the same way\nexport class UserError extends Error {\n\toverride name = 'UserError';\n}\n\nexport function isUserError(error: unknown): error is Error {\n\treturn error instanceof Error && error.name === 'UserError';\n}\n","import type * as AstroContentModule from 'astro:content';\nimport type { CollectionEntry, CollectionKey } from 'astro:content';\nimport type { ModuleRunner } from 'vite/module-runner';\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\n\nimport { UserError } from './user-error.js';\n\ntype AstroContent = typeof AstroContentModule;\n\n// Mirrors Astro's own `configPaths`; it does not accept the CommonJS variants\nconst configFilenames = [\n\t'astro.config.mjs',\n\t'astro.config.js',\n\t'astro.config.ts',\n\t'astro.config.mts',\n];\n\n// Every `CollectionEntry` must satisfy this\nexport interface ContentEntry {\n\tbody?: string | undefined;\n\tcollection: string;\n\tdata: Record<string, unknown>;\n\t// Astro widens this to `string | number`; file-based collections only ever write a string\n\tdigest?: number | string | undefined;\n\tfilePath?: string | undefined;\n\tid: string;\n}\n\nexport function findAstroRoot(startDir = process.cwd()) {\n\tlet current = path.resolve(startDir);\n\n\twhile (current !== path.dirname(current)) {\n\t\tconst hasConfig = configFilenames.some((filename) => existsSync(path.join(current, filename)));\n\n\t\tif (hasConfig) return current;\n\n\t\tcurrent = path.dirname(current);\n\t}\n\n\tthrow new UserError(`Could not locate an Astro config above ${startDir}`);\n}\n\n// Annotated so the declaration keeps the consumer's `CollectionEntry`; inferred, it bakes in the unsynced `any`\nexport async function getCollectionEntries<C extends CollectionKey>(\n\t{ getCollection }: AstroContent,\n\tcollections: Array<C>,\n): Promise<Array<CollectionEntry<C>>> {\n\tconst entries = await Promise.all(\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- unsynced here, `getCollection` is the stub's `any`; a consumer's is typed\n\t\tcollections.map((collection) => getCollection(collection)),\n\t);\n\tconst flattened = entries.flat();\n\n\tif (flattened.length === 0) {\n\t\tthrow new UserError(\n\t\t\t`No entries found in: ${collections.join(', ')}. Run \\`astro sync\\` to write the content store.`,\n\t\t);\n\t}\n\n\treturn flattened;\n}\n\nexport async function withAstroModules<T>(callback: (runner: ModuleRunner) => Promise<T>) {\n\t// An unrooted config resolves against `process.cwd()`, finds none, and reads every collection back empty\n\tconst root = findAstroRoot();\n\tconst [{ getViteConfig }, { createLogger, createServer, createServerModuleRunner }] =\n\t\tawait Promise.all([import('astro/config'), import('vite')]);\n\n\tconst config = await getViteConfig(\n\t\t{},\n\t\t{ logLevel: 'silent', root },\n\t)({\n\t\tcommand: 'serve',\n\t\tmode: 'development',\n\t});\n\n\tconst server = await createServer({\n\t\t...config,\n\t\t// Astro's config sets a `customLogger` that writes to stdout and ignores `logLevel`\n\t\tcustomLogger: createLogger('silent'),\n\t\tlogLevel: 'silent',\n\t\tserver: { hmr: false, middlewareMode: true, ws: false },\n\t});\n\n\ttry {\n\t\treturn await callback(createServerModuleRunner(server.environments.ssr));\n\t} finally {\n\t\t// The process hangs on the open server otherwise\n\t\tawait server.close();\n\t}\n}\n\nexport async function withAstroContent<T>(callback: (content: AstroContent) => Promise<T>) {\n\treturn withAstroModules(async (runner) =>\n\t\tcallback(await runner.import<AstroContent>('astro:content')),\n\t);\n}\n","import { stripVTControlCharacters } from 'node:util';\n\nimport type { OutputEvent, Reporter, ReporterStreams } from './events.js';\n\ntype ProgressEvent = Extract<OutputEvent, { type: 'progress' }>;\n\ninterface JsonReporterOptions {\n\tcommand?: string | undefined;\n\tnow?: () => number;\n}\n\ninterface Throttle {\n\tlastSent: number;\n\tpending?: ProgressEvent;\n}\n\nconst throttleMs = 1000;\n\nfunction strip(text: string): string;\nfunction strip(text: string | undefined): string | undefined;\nfunction strip(text: string | undefined) {\n\treturn text === undefined ? undefined : stripVTControlCharacters(text);\n}\n\nfunction stripEvent(event: OutputEvent): OutputEvent {\n\tswitch (event.type) {\n\t\tcase 'data': {\n\t\t\treturn event;\n\t\t}\n\t\tcase 'end': {\n\t\t\tconst { detail, message, ...rest } = event;\n\n\t\t\treturn {\n\t\t\t\t...rest,\n\t\t\t\t...(detail === undefined ? {} : { detail: strip(detail) }),\n\t\t\t\t...(message === undefined ? {} : { message: strip(message) }),\n\t\t\t};\n\t\t}\n\t\tcase 'message':\n\t\tcase 'status': {\n\t\t\treturn { ...event, text: strip(event.text) };\n\t\t}\n\t\tcase 'progress': {\n\t\t\tconst { note, ...rest } = event;\n\n\t\t\treturn { ...rest, label: strip(event.label), ...(note ? { note: strip(note) } : {}) };\n\t\t}\n\t\tcase 'start': {\n\t\t\treturn { ...event, title: strip(event.title) };\n\t\t}\n\t\tcase 'summary': {\n\t\t\treturn {\n\t\t\t\t...event,\n\t\t\t\trows: event.rows.map(([label, value]) => [\n\t\t\t\t\tstrip(label),\n\t\t\t\t\ttypeof value === 'string' ? strip(value) : value,\n\t\t\t\t]),\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport function createJsonReporter(\n\t{ stderr, stdout }: ReporterStreams,\n\t{ command, now = Date.now }: JsonReporterOptions = {},\n): Reporter {\n\tconst progressThrottles = new Map<number, Throttle>();\n\tconst updateThrottles = new Map<number, Throttle>();\n\n\tfunction write(event: OutputEvent) {\n\t\tconst { type, ...payload } = stripEvent(event);\n\t\tconst envelope = { command, time: new Date(now()).toISOString() };\n\n\t\tstderr.write(`${JSON.stringify({ type, ...envelope, ...payload })}\\n`);\n\t}\n\n\tfunction isDue(throttles: Map<number, Throttle>, id: number) {\n\t\tconst time = now();\n\t\tconst throttle = throttles.get(id);\n\n\t\tif (throttle && time - throttle.lastSent < throttleMs) return false;\n\n\t\tthrottles.set(id, { lastSent: time });\n\n\t\treturn true;\n\t}\n\n\treturn {\n\t\temit(event) {\n\t\t\tif (event.type === 'data') {\n\t\t\t\tstdout.write(`${JSON.stringify(event.value)}\\n`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'progress') {\n\t\t\t\tif (isDue(progressThrottles, event.id)) {\n\t\t\t\t\twrite(event);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst throttle = progressThrottles.get(event.id);\n\n\t\t\t\tif (throttle) throttle.pending = event;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'status' && event.state === 'update') {\n\t\t\t\tif (isDue(updateThrottles, event.id)) write(event);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'status' && event.state !== 'start') {\n\t\t\t\tconst pending = progressThrottles.get(event.id)?.pending;\n\n\t\t\t\tif (pending) write(pending);\n\n\t\t\t\tprogressThrottles.delete(event.id);\n\t\t\t\tupdateThrottles.delete(event.id);\n\t\t\t}\n\n\t\t\twrite(event);\n\t\t},\n\t};\n}\n","const countFormat = new Intl.NumberFormat('en-CA');\n\nconst byteUnits = ['B', 'kB', 'MB', 'GB', 'TB'] as const;\n\nexport function formatCount(count: number) {\n\treturn countFormat.format(count);\n}\n\nexport function formatBytes(bytes: number) {\n\tlet value = bytes;\n\tlet unitIndex = 0;\n\n\twhile (value >= 1000 && unitIndex < byteUnits.length - 1) {\n\t\tvalue /= 1000;\n\t\tunitIndex += 1;\n\t}\n\n\tconst unit = byteUnits[unitIndex] ?? 'B';\n\n\treturn unitIndex === 0 ? `${String(value)} ${unit}` : `${value.toFixed(1)} ${unit}`;\n}\n\nexport function formatError(error: unknown) {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function formatDuration(milliseconds: number) {\n\tconst seconds = milliseconds / 1000;\n\n\tif (seconds < 60) return `${seconds.toFixed(1)}s`;\n\n\tconst totalSeconds = Math.round(seconds);\n\tconst minutes = Math.floor(totalSeconds / 60);\n\tconst remainder = totalSeconds % 60;\n\n\treturn `${String(minutes)}m ${String(remainder).padStart(2, '0')}s`;\n}\n\nfunction formatRate(perSecond: number) {\n\treturn perSecond >= 10 ? formatCount(Math.round(perSecond)) : perSecond.toFixed(1);\n}\n\nexport interface ProgressSnapshot {\n\tdone: number;\n\tlabel: string;\n\tnote?: string;\n\tperSecond?: number;\n\ttotal: number;\n}\n\nexport function formatProgressText({ done, label, note, perSecond, total }: ProgressSnapshot) {\n\tconst percent = total === 0 ? 100 : Math.floor((done / total) * 100);\n\tconst parts = [`${label} ${formatCount(done)} / ${formatCount(total)} (${String(percent)}%)`];\n\tconst remaining = total - done;\n\n\tif (perSecond !== undefined && perSecond > 0) {\n\t\tparts.push(`${formatRate(perSecond)}/s`);\n\n\t\tif (remaining > 0) parts.push(`~${formatDuration((remaining / perSecond) * 1000)} left`);\n\t}\n\n\tif (note) parts.push(note);\n\n\treturn parts.join(' · ');\n}\n","import type { MessageLevel, OutputEvent, StatusState, SummaryRow } from './events.js';\n\nimport { formatCount, formatDuration } from './format.js';\n\ntype Paint = (text: string) => string;\n\nexport interface Palette {\n\tblue: Paint;\n\tgray: Paint;\n\tgreen: Paint;\n\tmagenta: Paint;\n\tred: Paint;\n\tyellow: Paint;\n}\n\ntype EndEvent = Extract<OutputEvent, { type: 'end' }>;\n\ntype TerminalState = Exclude<StatusState, 'start' | 'stop' | 'update'>;\n\nfunction unpainted(text: string) {\n\treturn text;\n}\n\nexport const plainPalette: Palette = {\n\tblue: unpainted,\n\tgray: unpainted,\n\tgreen: unpainted,\n\tmagenta: unpainted,\n\tred: unpainted,\n\tyellow: unpainted,\n};\n\nexport function getStateSymbol(state: TerminalState, palette: Palette) {\n\tif (state === 'succeed') return palette.green('✓');\n\tif (state === 'fail') return palette.red('✗');\n\n\treturn palette.yellow('⚠');\n}\n\nexport function formatMessage(level: MessageLevel, text: string, palette: Palette) {\n\tswitch (level) {\n\t\tcase 'detail': {\n\t\t\treturn palette.gray(` ${text}`);\n\t\t}\n\t\tcase 'error': {\n\t\t\treturn `${getStateSymbol('fail', palette)} ${palette.red(text)}`;\n\t\t}\n\t\tcase 'info': {\n\t\t\treturn text;\n\t\t}\n\t\tcase 'step': {\n\t\t\treturn palette.blue(text);\n\t\t}\n\t\tcase 'success': {\n\t\t\treturn `${getStateSymbol('succeed', palette)} ${text}`;\n\t\t}\n\t\tcase 'title': {\n\t\t\treturn palette.magenta(`=== ${text} ===`);\n\t\t}\n\t\tcase 'warn': {\n\t\t\treturn `${getStateSymbol('warn', palette)} ${palette.yellow(text)}`;\n\t\t}\n\t}\n}\n\nexport function formatSummaryRows(rows: Array<SummaryRow>, palette: Palette) {\n\tconst width = Math.max(...rows.map(([label]) => label.length)) + 2;\n\n\treturn rows.map(([label, value]) => {\n\t\tconst formatted = typeof value === 'number' ? formatCount(value) : value;\n\n\t\treturn ` ${palette.gray(`${label}:`.padEnd(width))}${formatted}`;\n\t});\n}\n\nexport function formatData(value: unknown) {\n\treturn typeof value === 'string' ? value : JSON.stringify(value, undefined, 2);\n}\n\nexport function formatEnd(event: EndEvent, palette: Palette) {\n\tif (event.status === 'ok') {\n\t\treturn [formatMessage('detail', `Finished in ${formatDuration(event.durationMs)}`, palette)];\n\t}\n\n\tconst level = event.status === 'failed' || event.forced ? 'error' : 'warn';\n\tconst lines = event.message === undefined ? [] : [formatMessage(level, event.message, palette)];\n\n\tif (event.detail) lines.push(formatMessage('detail', event.detail, palette));\n\n\treturn lines;\n}\n","import { stripVTControlCharacters } from 'node:util';\n\nimport type { Reporter, ReporterStreams } from './events.js';\n\nimport { formatProgressText } from './format.js';\nimport {\n\tformatData,\n\tformatEnd,\n\tformatMessage,\n\tformatSummaryRows,\n\tgetStateSymbol,\n\tplainPalette,\n} from './human-format.js';\n\ninterface OpenStatus {\n\tlastPrint: number;\n\ttext: string;\n}\n\nconst progressIntervalMs = 10_000;\n\nexport function createPlainReporter(\n\t{ stderr, stdout }: ReporterStreams,\n\tnow: () => number = Date.now,\n): Reporter {\n\tconst statuses = new Map<number, OpenStatus>();\n\tconst openIds: Array<number> = [];\n\n\tfunction writeLine(line: string) {\n\t\tstderr.write(`${stripVTControlCharacters(line)}\\n`);\n\t}\n\n\tfunction closeStatus(id: number) {\n\t\tstatuses.delete(id);\n\t\topenIds.splice(openIds.indexOf(id), 1);\n\t}\n\n\treturn {\n\t\temit(event) {\n\t\t\tswitch (event.type) {\n\t\t\t\tcase 'data': {\n\t\t\t\t\tconst line =\n\t\t\t\t\t\tevent.text === undefined\n\t\t\t\t\t\t\t? formatData(event.value)\n\t\t\t\t\t\t\t: stripVTControlCharacters(event.text);\n\n\t\t\t\t\tstdout.write(`${line}\\n`);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'end': {\n\t\t\t\t\tconst activeId = openIds.at(-1);\n\t\t\t\t\tconst active = activeId === undefined ? undefined : statuses.get(activeId);\n\n\t\t\t\t\tif (active && event.status !== 'ok') {\n\t\t\t\t\t\twriteLine(`${getStateSymbol('fail', plainPalette)} ${active.text}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tstatuses.clear();\n\t\t\t\t\topenIds.length = 0;\n\n\t\t\t\t\tfor (const line of formatEnd(event, plainPalette)) writeLine(line);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'message': {\n\t\t\t\t\twriteLine(formatMessage(event.level, event.text, plainPalette));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'progress': {\n\t\t\t\t\tconst status = statuses.get(event.id);\n\t\t\t\t\tconst time = now();\n\n\t\t\t\t\tif (!status || time - status.lastPrint < progressIntervalMs) break;\n\n\t\t\t\t\tstatus.lastPrint = time;\n\t\t\t\t\twriteLine(formatProgressText(event));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'start': {\n\t\t\t\t\twriteLine(formatMessage('title', event.title, plainPalette));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'status': {\n\t\t\t\t\tconst { id, state, text } = event;\n\n\t\t\t\t\tif (state === 'start') {\n\t\t\t\t\t\tstatuses.set(id, { lastPrint: now(), text });\n\t\t\t\t\t\topenIds.push(id);\n\t\t\t\t\t\twriteLine(text);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst status = statuses.get(id);\n\n\t\t\t\t\tif (!status) break;\n\n\t\t\t\t\tif (state === 'update') {\n\t\t\t\t\t\tstatus.text = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (state !== 'stop') writeLine(`${getStateSymbol(state, plainPalette)} ${text}`);\n\n\t\t\t\t\tcloseStatus(id);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'summary': {\n\t\t\t\t\tfor (const line of formatSummaryRows(event.rows, plainPalette)) writeLine(line);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n}\n","import type { Ora } from 'ora';\n\nimport chalk from 'chalk';\nimport ora from 'ora';\n\nimport type { OutputEvent, Reporter, ReporterStreams, StatusState } from './events.js';\n\nimport { formatProgressText } from './format.js';\nimport {\n\tformatData,\n\tformatEnd,\n\tformatMessage,\n\tformatSummaryRows,\n\tgetStateSymbol,\n} from './human-format.js';\n\ntype StatusEvent = Extract<OutputEvent, { type: 'status' }>;\n\nfunction isTerminal(stream: NodeJS.WritableStream) {\n\treturn 'isTTY' in stream && stream.isTTY === true;\n}\n\nexport function createPrettyReporter({ stderr, stdout }: ReporterStreams): Reporter {\n\tconst spinners = new Map<number, Ora>();\n\tconst openIds: Array<number> = [];\n\n\tfunction getActiveSpinner() {\n\t\tconst activeId = openIds.at(-1);\n\n\t\treturn activeId === undefined ? undefined : spinners.get(activeId);\n\t}\n\n\tfunction writeAround(stream: NodeJS.WritableStream, line: string) {\n\t\tconst active = getActiveSpinner();\n\n\t\tif (!active?.isSpinning) {\n\t\t\tstream.write(`${line}\\n`);\n\t\t\treturn;\n\t\t}\n\n\t\tactive.clear();\n\t\tstream.write(`${line}\\n`);\n\t\tactive.render();\n\t}\n\n\tfunction writeLine(line: string) {\n\t\twriteAround(stderr, line);\n\t}\n\n\tfunction endSpinner(id: number, state: Exclude<StatusState, 'start' | 'update'>, text: string) {\n\t\tconst spinner = spinners.get(id);\n\n\t\tif (!spinner) return;\n\n\t\tconst wasActive = openIds.at(-1) === id;\n\n\t\tif (state === 'stop') {\n\t\t\tspinner.stop();\n\t\t} else {\n\t\t\tspinner.stopAndPersist({ symbol: getStateSymbol(state, chalk), text });\n\t\t}\n\n\t\tspinners.delete(id);\n\t\topenIds.splice(openIds.indexOf(id), 1);\n\n\t\tconst outer = getActiveSpinner();\n\n\t\tif (wasActive && outer?.isEnabled) outer.start();\n\t}\n\n\tfunction onStatus({ id, state, text }: StatusEvent) {\n\t\tif (state === 'start') {\n\t\t\tgetActiveSpinner()?.stop();\n\t\t\t// Stdin stays cooked so the terminal still signals the whole process group on Ctrl-C, child processes included\n\t\t\tspinners.set(id, ora({ discardStdin: false, stream: stderr, text }).start());\n\t\t\topenIds.push(id);\n\t\t\treturn;\n\t\t}\n\n\t\tif (state === 'update') {\n\t\t\tconst spinner = spinners.get(id);\n\n\t\t\tif (spinner) spinner.text = text;\n\t\t\treturn;\n\t\t}\n\n\t\tendSpinner(id, state, text);\n\t}\n\n\treturn {\n\t\temit(event) {\n\t\t\tswitch (event.type) {\n\t\t\t\tcase 'data': {\n\t\t\t\t\tconst line = event.text ?? formatData(event.value);\n\n\t\t\t\t\tif (isTerminal(stdout)) {\n\t\t\t\t\t\twriteAround(stdout, line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstdout.write(`${line}\\n`);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'end': {\n\t\t\t\t\tconst activeId = openIds.at(-1);\n\t\t\t\t\tconst active = getActiveSpinner();\n\n\t\t\t\t\tif (active && activeId !== undefined && event.status !== 'ok') {\n\t\t\t\t\t\tspinners.delete(activeId);\n\t\t\t\t\t\tactive.stopAndPersist({ symbol: getStateSymbol('fail', chalk), text: active.text });\n\t\t\t\t\t}\n\n\t\t\t\t\t// An open spinner's render interval keeps the process alive\n\t\t\t\t\tfor (const spinner of spinners.values()) spinner.stop();\n\n\t\t\t\t\tspinners.clear();\n\t\t\t\t\topenIds.length = 0;\n\n\t\t\t\t\tfor (const line of formatEnd(event, chalk)) writeLine(line);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'message': {\n\t\t\t\t\twriteLine(formatMessage(event.level, event.text, chalk));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'progress': {\n\t\t\t\t\tconst spinner = spinners.get(event.id);\n\n\t\t\t\t\tif (spinner) spinner.text = formatProgressText(event);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'start': {\n\t\t\t\t\twriteLine(formatMessage('title', event.title, chalk));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'status': {\n\t\t\t\t\tonStatus(event);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'summary': {\n\t\t\t\t\tfor (const line of formatSummaryRows(event.rows, chalk)) writeLine(line);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n}\n","import type { MessageLevel, OutputEvent, Reporter, SummaryRow } from './events.js';\n\nimport { createJsonReporter } from './reporter-json.js';\nimport { createPlainReporter } from './reporter-plain.js';\nimport { createPrettyReporter } from './reporter-pretty.js';\n\nexport type OutputMode = 'json' | 'plain' | 'pretty';\n\nconst outputModes = new Set<unknown>(['json', 'plain', 'pretty'] satisfies Array<OutputMode>);\n\nlet currentMode: OutputMode | undefined;\nlet currentReporter: Reporter | undefined;\nlet capturingReporter: Reporter | undefined;\nlet nextStatusId = 1;\n\nexport function isOutputMode(value: unknown): value is OutputMode {\n\treturn outputModes.has(value);\n}\n\nfunction detectOutputMode(): OutputMode {\n\tconst requested = process.env.SCRIPTS_OUTPUT;\n\n\tif (isOutputMode(requested)) return requested;\n\n\treturn process.stderr.isTTY ? 'pretty' : 'plain';\n}\n\nfunction createReporter(mode: OutputMode, command?: string) {\n\tconst streams = { stderr: process.stderr, stdout: process.stdout };\n\n\tif (mode === 'json') return createJsonReporter(streams, { command });\n\tif (mode === 'plain') return createPlainReporter(streams);\n\n\treturn createPrettyReporter(streams);\n}\n\nexport function configureOutput(mode: OutputMode = detectOutputMode(), command?: string) {\n\tcurrentMode = mode;\n\tcurrentReporter = createReporter(mode, command);\n}\n\nexport function getOutputMode() {\n\tif (currentMode === undefined) currentMode = detectOutputMode();\n\n\treturn currentMode;\n}\n\nexport function emit(event: OutputEvent) {\n\tif (capturingReporter) {\n\t\tcapturingReporter.emit(event);\n\t\treturn;\n\t}\n\n\tif (currentReporter === undefined) currentReporter = createReporter(getOutputMode());\n\n\tcurrentReporter.emit(event);\n}\n\nexport async function captureOutput<Result>(task: () => Promise<Result> | Result) {\n\tconst events: Array<OutputEvent> = [];\n\tconst previous = capturingReporter;\n\n\tcapturingReporter = {\n\t\temit(event) {\n\t\t\tevents.push(event);\n\t\t},\n\t};\n\n\ttry {\n\t\tconst result = await task();\n\n\t\treturn { events, result };\n\t} finally {\n\t\tcapturingReporter = previous;\n\t}\n}\n\nfunction emitMessage(level: MessageLevel, text: string) {\n\temit({ level, text, type: 'message' });\n}\n\nexport const log = {\n\tdata<Value>(value: Value, format?: (value: Value) => string) {\n\t\temit(format ? { text: format(value), type: 'data', value } : { type: 'data', value });\n\t},\n\tdetail(text: string) {\n\t\temitMessage('detail', text);\n\t},\n\terror(text: string) {\n\t\temitMessage('error', text);\n\t},\n\tinfo(text: string) {\n\t\temitMessage('info', text);\n\t},\n\tstep(text: string) {\n\t\temitMessage('step', text);\n\t},\n\tsuccess(text: string) {\n\t\temitMessage('success', text);\n\t},\n\tsummary(rows: Array<SummaryRow>) {\n\t\temit({ rows, type: 'summary' });\n\t},\n\ttitle(text: string) {\n\t\temitMessage('title', text);\n\t},\n\twarn(text: string) {\n\t\temitMessage('warn', text);\n\t},\n};\n\nexport function startStatus(initialText: string) {\n\tconst id = nextStatusId;\n\tlet text = initialText;\n\n\tnextStatusId += 1;\n\temit({ id, state: 'start', text, type: 'status' });\n\n\tfunction finish(state: 'fail' | 'stop' | 'succeed' | 'warn', finalText?: string) {\n\t\ttext = finalText ?? text;\n\t\temit({ id, state, text, type: 'status' });\n\t}\n\n\tconst status = {\n\t\tfail(finalText?: string) {\n\t\t\tfinish('fail', finalText);\n\t\t},\n\t\tstop() {\n\t\t\tfinish('stop');\n\t\t},\n\t\tsucceed(finalText?: string) {\n\t\t\tfinish('succeed', finalText);\n\t\t},\n\t\tupdate(nextText: string) {\n\t\t\ttext = nextText;\n\t\t\temit({ id, state: 'update', text, type: 'status' });\n\t\t},\n\t\twarn(finalText?: string) {\n\t\t\tfinish('warn', finalText);\n\t\t},\n\t};\n\n\treturn { id, status };\n}\n\nexport function createStatus(text: string) {\n\treturn startStatus(text).status;\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\n\nimport { UserError } from './user-error.js';\n\nexport function findWorkspaceRoot(startDir = process.cwd()) {\n\tlet current = path.resolve(startDir);\n\n\twhile (current !== path.dirname(current)) {\n\t\tif (existsSync(path.join(current, 'pnpm-workspace.yaml'))) return current;\n\n\t\tcurrent = path.dirname(current);\n\t}\n\n\tthrow new UserError(`Could not locate pnpm-workspace.yaml above ${startDir}`);\n}\n","import type { ParseArgsOptionDescriptor } from 'node:util';\n\nimport chalk from 'chalk';\nimport { parseArgs } from 'node:util';\n\nimport { configureOutput, emit, getOutputMode, isOutputMode, log } from './output/output.js';\nimport { isUserError, UserError } from './user-error.js';\nimport { findWorkspaceRoot } from './workspace.js';\n\ntype CommandOption = ParseArgsOptionDescriptor & { description: string };\n\ntype CommandOptions = Record<string, CommandOption>;\n\ninterface CommandPositional {\n\tdescription: string;\n\tname: string;\n}\n\ntype ParsedValues<Options extends CommandOptions> = ReturnType<\n\ttypeof parseArgs<{ allowPositionals: true; options: Options; strict: true }>\n>['values'];\n\ninterface CommandDescription {\n\tname: string;\n\toptions?: CommandOptions;\n\tpositional?: CommandPositional;\n\tsummary: string;\n\ttitle: string;\n}\n\nexport interface CommandContext<Options extends CommandOptions> {\n\tpositionals: Array<string>;\n\trootPath: string;\n\tsignal: AbortSignal;\n\tvalues: ParsedValues<Options>;\n}\n\nexport interface Command<Options extends CommandOptions, Context> extends CommandDescription {\n\tinterrupted?: string;\n\toptions?: Options;\n\trun: (context: CommandContext<Options> & Context) => Promise<void> | void;\n}\n\ninterface CommandRunnerOptions<Context> {\n\tcontext?: (base: { rootPath: string }) => Context | Promise<Context>;\n\tusagePrefix?: string;\n}\n\ninterface ChildProcessFailure extends Error {\n\texitCode: null | number | undefined;\n\tsignal: null | string | undefined;\n\tstderr?: unknown;\n}\n\ninterface FailureReport {\n\tdetail?: string;\n\texitCode: number;\n\tisInterrupted: boolean;\n\tmessage: string;\n}\n\nconst interruptedExitCode = 130;\nconst interruptBurstMs = 1000;\nconst stderrTailLines = 20;\n\nconst jsonOption = {\n\tdescription: 'Print output as JSON lines',\n\ttype: 'boolean',\n} as const satisfies CommandOption;\n\nconst helpOption = {\n\tdescription: 'Show this help',\n\tshort: 'h',\n\ttype: 'boolean',\n} as const satisfies CommandOption;\n\nconst builtInOptions: Array<[string, CommandOption]> = [\n\t['json', jsonOption],\n\t['help', helpOption],\n];\n\nexport function parseNumberOption(value: string, name: string) {\n\tconst parsed = Number(value);\n\n\tif (value.trim() === '' || !Number.isFinite(parsed))\n\t\tthrow new UserError(`--${name} expects a number, got \"${value}\"`);\n\n\treturn parsed;\n}\n\nfunction getUsage(command: CommandDescription, usagePrefix: string) {\n\tconst positional = command.positional ? ` [${command.positional.name}]` : '';\n\n\treturn `${usagePrefix} ${command.name}${positional} [options]`;\n}\n\nfunction getOptionLabel(name: string, option: CommandOption) {\n\tconst flag = option.short ? `-${option.short}, --${name}` : ` --${name}`;\n\n\treturn option.type === 'string' ? `${flag} <value>` : flag;\n}\n\nfunction getOptionDescription(option: CommandOption) {\n\tconst { default: defaultValue, description } = option;\n\n\tif (defaultValue === undefined || defaultValue === false) return description;\n\tif (Array.isArray(defaultValue) && defaultValue.length === 0) return description;\n\n\treturn `${description} (default: ${String(defaultValue)})`;\n}\n\nexport function formatHelp(command: CommandDescription, usagePrefix = 'pnpm') {\n\tconst options = [...Object.entries(command.options ?? {}), ...builtInOptions];\n\tconst rows: Array<[string, string]> = options.map(([name, option]) => [\n\t\tgetOptionLabel(name, option),\n\t\tgetOptionDescription(option),\n\t]);\n\n\tif (command.positional) {\n\t\trows.unshift([command.positional.name, command.positional.description]);\n\t}\n\n\tconst width = Math.max(...rows.map(([label]) => label.length)) + 2;\n\n\treturn [\n\t\tchalk.magenta(command.title),\n\t\tcommand.summary,\n\t\t'',\n\t\t`Usage: ${getUsage(command, usagePrefix)}`,\n\t\t'',\n\t\t...rows.map(([label, description]) => ` ${label.padEnd(width)}${chalk.gray(description)}`),\n\t].join('\\n');\n}\n\nfunction isParseError(error: unknown): error is Error {\n\treturn (\n\t\terror instanceof Error &&\n\t\t'code' in error &&\n\t\ttypeof error.code === 'string' &&\n\t\terror.code.startsWith('ERR_PARSE_ARGS_')\n\t);\n}\n\n// Matched by shape, which covers zx's `ProcessOutput` and execa's `ExecaError` alike\nfunction isChildProcessFailure(error: unknown): error is ChildProcessFailure {\n\treturn error instanceof Error && 'exitCode' in error && 'signal' in error;\n}\n\nfunction isInterruption(error: unknown, isAborted: boolean) {\n\tif (isAborted) return true;\n\n\treturn (\n\t\tisChildProcessFailure(error) &&\n\t\t(error.exitCode === interruptedExitCode || error.signal === 'SIGINT')\n\t);\n}\n\nfunction describeError(error: unknown): Pick<FailureReport, 'detail' | 'message'> {\n\tif (isParseError(error)) return { detail: 'See --help', message: error.message };\n\n\tif (isUserError(error)) return { message: error.message };\n\n\tif (isChildProcessFailure(error)) {\n\t\tconst reason = error.signal\n\t\t\t? `was killed by ${error.signal}`\n\t\t\t: `exited with code ${String(error.exitCode)}`;\n\n\t\tconst message = `Child process ${reason}`;\n\t\tconst stderr = typeof error.stderr === 'string' ? error.stderr.trimEnd() : '';\n\n\t\tif (stderr === '') return { message };\n\n\t\treturn { detail: stderr.split('\\n').slice(-stderrTailLines).join('\\n'), message };\n\t}\n\n\tif (!(error instanceof Error)) return { message: String(error) };\n\n\tconst stack = error.stack?.split('\\n').slice(1).join('\\n');\n\n\treturn stack ? { detail: stack, message: error.message } : { message: error.message };\n}\n\nexport function getFailureReport(\n\terror: unknown,\n\t{ interrupted, isAborted }: { interrupted?: string | undefined; isAborted: boolean },\n): FailureReport {\n\tif (isInterruption(error, isAborted)) {\n\t\treturn {\n\t\t\texitCode: interruptedExitCode,\n\t\t\tisInterrupted: true,\n\t\t\tmessage: interrupted ? `Interrupted; ${interrupted}` : 'Interrupted',\n\t\t};\n\t}\n\n\treturn { exitCode: 1, isInterrupted: false, ...describeError(error) };\n}\n\nfunction parseCommandArgs(command: CommandDescription) {\n\tconst options = [...Object.entries(command.options ?? {}), ...builtInOptions];\n\n\treturn parseArgs({\n\t\tallowPositionals: command.positional !== undefined,\n\t\targs: process.argv.slice(2),\n\t\toptions: Object.fromEntries(\n\t\t\toptions.map(([name, { description: _description, ...descriptor }]) => [name, descriptor]),\n\t\t),\n\t\tstrict: true,\n\t});\n}\n\nfunction selectOutput(isJson: boolean) {\n\tif (isJson) {\n\t\t// Nested commands inherit this and join one stream\n\t\tprocess.env.SCRIPTS_OUTPUT = 'json';\n\t\treturn;\n\t}\n\n\tconst requested = process.env.SCRIPTS_OUTPUT;\n\n\tif (requested && !isOutputMode(requested)) {\n\t\tthrow new UserError(`SCRIPTS_OUTPUT must be pretty, plain or json, got \"${requested}\"`);\n\t}\n}\n\nexport function createCommandRunner<Context extends object = object>(\n\trunnerOptions: CommandRunnerOptions<Context> = {},\n) {\n\tconst { context: createContext, usagePrefix = 'pnpm' } = runnerOptions;\n\n\tfunction defineCommand<const Options extends CommandOptions>(command: Command<Options, Context>) {\n\t\treturn command;\n\t}\n\n\tasync function runCommand<const Options extends CommandOptions>(\n\t\tcommand: Command<Options, Context>,\n\t) {\n\t\tconst controller = new AbortController();\n\t\tconst startTime = Date.now();\n\t\tlet interruptTime: number | undefined;\n\t\t// Read before parsing, so a parse error in a `--json` run still reports as JSON\n\t\tconst isJson = process.argv.slice(2).includes('--json');\n\n\t\tconfigureOutput(isJson ? 'json' : undefined, command.name);\n\n\t\t// One Ctrl-C arrives as a burst: the terminal's SIGINT, pnpm's forwarded copy, and a SIGTERM per nested pnpm\n\t\tfunction onInterrupt() {\n\t\t\tif (interruptTime === undefined) {\n\t\t\t\tinterruptTime = Date.now();\n\t\t\t\tcontroller.abort();\n\t\t\t\tlog.warn('Stopping; press Ctrl-C again to force quit');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (Date.now() - interruptTime < interruptBurstMs) return;\n\n\t\t\temit({\n\t\t\t\tdurationMs: Date.now() - startTime,\n\t\t\t\texitCode: interruptedExitCode,\n\t\t\t\tforced: true,\n\t\t\t\tmessage: 'Force quit',\n\t\t\t\tstatus: 'interrupted',\n\t\t\t\ttype: 'end',\n\t\t\t});\n\t\t\t// eslint-disable-next-line unicorn/no-process-exit -- a second Ctrl-C must quit even while the command still holds the event loop\n\t\t\tprocess.exit(interruptedExitCode);\n\t\t}\n\n\t\tprocess.on('SIGINT', onInterrupt);\n\t\tprocess.on('SIGTERM', onInterrupt);\n\n\t\tlet hasStarted = false;\n\n\t\ttry {\n\t\t\tconst { positionals, values } = parseCommandArgs(command);\n\n\t\t\tif (values.help) {\n\t\t\t\tprocess.stdout.write(`${formatHelp(command, usagePrefix)}\\n`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tselectOutput(isJson);\n\t\t\temit({ title: command.title, type: 'start' });\n\t\t\thasStarted = true;\n\n\t\t\tconst rootPath = findWorkspaceRoot();\n\t\t\t// `Context` is only left at its empty default when no factory is given\n\t\t\tconst context = createContext ? await createContext({ rootPath }) : ({} as Context);\n\n\t\t\tawait command.run({\n\t\t\t\t...context,\n\t\t\t\tpositionals,\n\t\t\t\trootPath,\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tvalues: values as ParsedValues<Options>,\n\t\t\t});\n\n\t\t\t// A command that honours the signal by returning early was still interrupted\n\t\t\tcontroller.signal.throwIfAborted();\n\n\t\t\temit({ durationMs: Date.now() - startTime, exitCode: 0, status: 'ok', type: 'end' });\n\t\t} catch (error) {\n\t\t\tif (!hasStarted && getOutputMode() === 'json') emit({ title: command.title, type: 'start' });\n\n\t\t\tconst report = getFailureReport(error, {\n\t\t\t\tinterrupted: command.interrupted,\n\t\t\t\tisAborted: controller.signal.aborted,\n\t\t\t});\n\n\t\t\temit({\n\t\t\t\t...(report.detail === undefined ? {} : { detail: report.detail }),\n\t\t\t\tdurationMs: Date.now() - startTime,\n\t\t\t\texitCode: report.exitCode,\n\t\t\t\tmessage: report.message,\n\t\t\t\tstatus: report.isInterrupted ? 'interrupted' : 'failed',\n\t\t\t\ttype: 'end',\n\t\t\t});\n\n\t\t\tprocess.exitCode = report.exitCode;\n\t\t} finally {\n\t\t\tprocess.off('SIGINT', onInterrupt);\n\t\t\tprocess.off('SIGTERM', onInterrupt);\n\t\t}\n\t}\n\n\treturn { defineCommand, runCommand };\n}\n","import type { ProgressSnapshot } from './format.js';\n\nimport { formatProgressText } from './format.js';\nimport { createStatus, emit, startStatus } from './output.js';\n\ninterface ProgressOptions {\n\tlabel: string;\n\tnote?: () => string;\n\ttotal: number;\n}\n\ninterface RateSample {\n\tcount: number;\n\ttime: number;\n}\n\nconst renderIntervalMs = 100;\nconst rateWindowMs = 10_000;\n\nexport function createRateWindow(windowMs = rateWindowMs) {\n\tconst samples: Array<RateSample> = [];\n\n\treturn {\n\t\tadd(count: number, time: number) {\n\t\t\tsamples.push({ count, time });\n\n\t\t\twhile (samples.length > 2 && time - (samples[0]?.time ?? time) > windowMs) samples.shift();\n\t\t},\n\t\tperSecond() {\n\t\t\tconst first = samples.at(0);\n\t\t\tconst last = samples.at(-1);\n\n\t\t\tif (!first || !last || last.time <= first.time) return;\n\n\t\t\treturn ((last.count - first.count) * 1000) / (last.time - first.time);\n\t\t},\n\t};\n}\n\nexport function createProgress({ label, note, total }: ProgressOptions) {\n\tconst rateWindow = createRateWindow();\n\tconst startTime = Date.now();\n\tlet done = 0;\n\tlet lastRender = 0;\n\n\trateWindow.add(0, startTime);\n\n\tfunction getSnapshot(): ProgressSnapshot {\n\t\tconst noteText = note?.();\n\t\tconst perSecond = rateWindow.perSecond();\n\n\t\treturn {\n\t\t\tdone,\n\t\t\tlabel,\n\t\t\ttotal,\n\t\t\t...(noteText ? { note: noteText } : {}),\n\t\t\t...(perSecond === undefined ? {} : { perSecond }),\n\t\t};\n\t}\n\n\tconst { id, status } = startStatus(formatProgressText(getSnapshot()));\n\n\tfunction render() {\n\t\tconst now = Date.now();\n\n\t\tif (now - lastRender < renderIntervalMs) return;\n\n\t\tlastRender = now;\n\t\trateWindow.add(done, now);\n\t\temit({ id, type: 'progress', ...getSnapshot() });\n\t}\n\n\tfunction finish(state: 'fail' | 'succeed' | 'warn', text?: string) {\n\t\tconst snapshot = getSnapshot();\n\n\t\temit({ id, type: 'progress', ...snapshot });\n\t\tstatus[state](text ?? formatProgressText(snapshot));\n\t}\n\n\treturn {\n\t\tfail(text?: string) {\n\t\t\tfinish('fail', text);\n\t\t},\n\t\tsucceed(text?: string) {\n\t\t\tfinish('succeed', text);\n\t\t},\n\t\ttick(count = 1) {\n\t\t\tdone += count;\n\t\t\trender();\n\t\t},\n\t\twarn(text?: string) {\n\t\t\tfinish('warn', text);\n\t\t},\n\t};\n}\n\n// A failure leaves the status open for `runCommand` to mark as failed\nexport async function withStatus<Result>(text: string, task: () => Promise<Result>) {\n\tconst status = createStatus(text);\n\tconst result = await task();\n\n\tstatus.stop();\n\n\treturn result;\n}\n\nexport async function withSpinner<Result>(\n\ttext: string,\n\ttask: () => Promise<Result>,\n\tdescribe?: (result: Result) => string,\n) {\n\tconst status = createStatus(text);\n\n\ttry {\n\t\tconst result = await task();\n\n\t\tstatus.succeed(describe?.(result) ?? text);\n\n\t\treturn result;\n\t} catch (error) {\n\t\tstatus.fail(text);\n\t\tthrow error;\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xsynaptic/astro-scripts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Plumbing for an Astro project's scripts package: a command runner, output with interchangeable reporters, and a bridge into Astro's content layer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Alexander Synaptic <x@synapticism.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/xsynaptic/astro-lab.git",
|
|
11
|
+
"directory": "packages/astro-scripts"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/xsynaptic/astro-lab/tree/main/packages/astro-scripts#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/xsynaptic/astro-lab/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"astro",
|
|
19
|
+
"withastro",
|
|
20
|
+
"cli",
|
|
21
|
+
"scripts",
|
|
22
|
+
"content-collections",
|
|
23
|
+
"vite"
|
|
24
|
+
],
|
|
25
|
+
"main": "./dist/index.mjs",
|
|
26
|
+
"types": "./dist/index.d.mts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"import": "./dist/index.mjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chalk": "^6.0.1",
|
|
39
|
+
"ora": "^9.4.1"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"astro": "^7.0.0",
|
|
43
|
+
"vite": "^8.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"astro": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"vite": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^24.19.2",
|
|
55
|
+
"astro": "^7.4.2",
|
|
56
|
+
"vite": "^8.3.5",
|
|
57
|
+
"zx": "^8.8.5"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsdown",
|
|
61
|
+
"dev": "tsdown --watch",
|
|
62
|
+
"check-types": "tsc --noEmit"
|
|
63
|
+
}
|
|
6
64
|
}
|