@rigkit/cli 0.2.7 → 0.2.8
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 +12 -3
- package/package.json +4 -7
- package/src/cli.test.ts +139 -7
- package/src/cli.ts +699 -235
- package/src/completion.test.ts +139 -10
- package/src/completion.ts +427 -72
- package/src/init.ts +6 -4
- package/src/project.test.ts +33 -3
- package/src/project.ts +99 -11
- package/src/run-logger.test.ts +92 -0
- package/src/run-logger.ts +203 -0
- package/src/run-presenter.ts +176 -299
- package/src/ui.ts +159 -0
- package/src/version.ts +1 -1
- package/src/remote-project.test.ts +0 -55
- package/src/remote-project.ts +0 -225
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, rmSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import
|
|
5
|
-
import { Command, CommanderError } from "commander";
|
|
4
|
+
import { Command, CommanderError, Option } from "commander";
|
|
6
5
|
import inquirer from "inquirer";
|
|
7
|
-
import
|
|
6
|
+
import * as ui from "./ui.ts";
|
|
8
7
|
import {
|
|
9
8
|
getOrStartRuntime,
|
|
9
|
+
defaultRigkitHome,
|
|
10
10
|
type RuntimeClient,
|
|
11
11
|
} from "@rigkit/runtime-client";
|
|
12
12
|
import {
|
|
@@ -20,15 +20,11 @@ import {
|
|
|
20
20
|
type CmuxHostCapabilityHandler,
|
|
21
21
|
} from "@rigkit/provider-cmux/host";
|
|
22
22
|
import { DEFAULT_CONFIG_FILE, discoverProjectConfigs, resolveConfigPaths, PROJECT_PACKAGE_NAME } from "./project.ts";
|
|
23
|
-
import {
|
|
24
|
-
materializeGithubProject,
|
|
25
|
-
splitGithubProjectTarget,
|
|
26
|
-
type GithubProjectTarget,
|
|
27
|
-
} from "./remote-project.ts";
|
|
28
23
|
import { RIGKIT_CLI_VERSION } from "./version.ts";
|
|
29
24
|
import { initProject, normalizeMachineName, type InitProjectResult } from "./init.ts";
|
|
30
25
|
import { openExternalTarget } from "./interaction.ts";
|
|
31
26
|
import { createRunPresenter, type RunPresenter } from "./run-presenter.ts";
|
|
27
|
+
import { createRunLogger, type RunLogger } from "./run-logger.ts";
|
|
32
28
|
import {
|
|
33
29
|
completeRig,
|
|
34
30
|
formatCompletionItems,
|
|
@@ -39,7 +35,7 @@ import {
|
|
|
39
35
|
import { generateWorkspaceName } from "./workspace-name.ts";
|
|
40
36
|
|
|
41
37
|
type GlobalOptions = {
|
|
42
|
-
|
|
38
|
+
chdir?: string;
|
|
43
39
|
config?: string;
|
|
44
40
|
state?: string;
|
|
45
41
|
json: boolean;
|
|
@@ -75,6 +71,12 @@ type ListOptions = {
|
|
|
75
71
|
target?: string;
|
|
76
72
|
};
|
|
77
73
|
|
|
74
|
+
type CacheClearOptions = {
|
|
75
|
+
local: boolean;
|
|
76
|
+
global: boolean;
|
|
77
|
+
all: boolean;
|
|
78
|
+
};
|
|
79
|
+
|
|
78
80
|
type PackageManager = "npm" | "bun" | "pnpm" | "skip";
|
|
79
81
|
|
|
80
82
|
type InitInstallResult = {
|
|
@@ -149,10 +151,46 @@ const CLI_HOST_CAPABILITIES: Array<{ id: string; schemaHash?: string }> = [
|
|
|
149
151
|
...(capability.schemaHash ? { schemaHash: capability.schemaHash } : {}),
|
|
150
152
|
}));
|
|
151
153
|
|
|
154
|
+
const TERRAFORM_STYLE_GLOBAL_OPTIONS = new Set(["chdir", "config", "state", "json", "help", "version"]);
|
|
155
|
+
const STATIC_COMMANDS = new Set([
|
|
156
|
+
"init",
|
|
157
|
+
"plan",
|
|
158
|
+
"apply",
|
|
159
|
+
"create",
|
|
160
|
+
"rm",
|
|
161
|
+
"run",
|
|
162
|
+
"ls",
|
|
163
|
+
"cache",
|
|
164
|
+
"projects",
|
|
165
|
+
"doctor",
|
|
166
|
+
"version",
|
|
167
|
+
"help",
|
|
168
|
+
"completion",
|
|
169
|
+
]);
|
|
170
|
+
|
|
152
171
|
if (process.argv[2] === "__complete") {
|
|
153
172
|
runCompletionEndpoint(process.argv.slice(3)).catch(handleCliError);
|
|
154
173
|
} else {
|
|
155
|
-
runCli(process.argv).catch(handleCliError);
|
|
174
|
+
runCli(normalizeCliArgv(process.argv)).catch(handleCliError);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function normalizeCliArgv(argv: string[]): string[] {
|
|
178
|
+
const normalized = argv.slice();
|
|
179
|
+
for (let index = 2; index < normalized.length; index += 1) {
|
|
180
|
+
const arg = normalized[index]!;
|
|
181
|
+
if (arg === "--") break;
|
|
182
|
+
if (!arg.startsWith("-")) {
|
|
183
|
+
if (STATIC_COMMANDS.has(arg)) break;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const match = /^-([A-Za-z][A-Za-z0-9-]*)(=.*)?$/.exec(arg);
|
|
188
|
+
if (!match) continue;
|
|
189
|
+
const name = match[1]!;
|
|
190
|
+
if (!TERRAFORM_STYLE_GLOBAL_OPTIONS.has(name)) continue;
|
|
191
|
+
normalized[index] = `--${name}${match[2] ?? ""}`;
|
|
192
|
+
}
|
|
193
|
+
return normalized;
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
async function runCli(argv: string[]): Promise<void> {
|
|
@@ -160,15 +198,23 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
160
198
|
program
|
|
161
199
|
.name("rig")
|
|
162
200
|
.description("Rigkit workflow CLI")
|
|
163
|
-
.usage("[options] <command>")
|
|
201
|
+
.usage("[global options] <command> [args]")
|
|
164
202
|
.version(RIGKIT_CLI_VERSION, "-v, --version", "Show Rigkit CLI version")
|
|
165
203
|
.showHelpAfterError()
|
|
166
204
|
.exitOverride()
|
|
167
205
|
.argument("[command]")
|
|
168
|
-
.
|
|
169
|
-
.
|
|
170
|
-
.
|
|
171
|
-
.
|
|
206
|
+
.addOption(new Option("--chdir <dir>", `Switch to a directory containing ${DEFAULT_CONFIG_FILE} before running the command`).hideHelp())
|
|
207
|
+
.addOption(new Option("--config <file>", "Config file to load, relative to -chdir when set").hideHelp())
|
|
208
|
+
.addOption(new Option("--state <file>", "Local runtime state database path").hideHelp())
|
|
209
|
+
.addOption(new Option("--json", "Print machine-readable JSON where supported").hideHelp())
|
|
210
|
+
.addHelpText("after", [
|
|
211
|
+
"",
|
|
212
|
+
"Global Options:",
|
|
213
|
+
" -chdir=DIR Switch to a directory containing rig.config.ts before running the command",
|
|
214
|
+
" -config=FILE Config file to load, relative to -chdir when set",
|
|
215
|
+
" -state=FILE Local runtime state database path",
|
|
216
|
+
" -json Print machine-readable JSON where supported",
|
|
217
|
+
].join("\n"))
|
|
172
218
|
.action(async (command?: string) => {
|
|
173
219
|
if (command) program.error(`unknown command '${command}'`);
|
|
174
220
|
await runHelp(makeInvocation(rootOptions(program)));
|
|
@@ -225,6 +271,23 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
225
271
|
});
|
|
226
272
|
});
|
|
227
273
|
|
|
274
|
+
program
|
|
275
|
+
.command("rm [workspace]")
|
|
276
|
+
.description("Remove one workspace, several via multi-select, or every workspace")
|
|
277
|
+
.option("-y, --yes", "Remove without confirmation")
|
|
278
|
+
.option("--all", "Remove every workspace in this project")
|
|
279
|
+
.option("--json", "Print machine-readable JSON")
|
|
280
|
+
.action(async (workspace: string | undefined, options: { yes?: boolean; all?: boolean; json?: boolean }) => {
|
|
281
|
+
await runRemove(
|
|
282
|
+
makeInvocation(rootOptions(program), options.json),
|
|
283
|
+
{
|
|
284
|
+
workspace,
|
|
285
|
+
yes: Boolean(options.yes),
|
|
286
|
+
all: Boolean(options.all),
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
228
291
|
program
|
|
229
292
|
.command("run <workspace> <operation> [args...]")
|
|
230
293
|
.description("Run a workspace operation")
|
|
@@ -247,6 +310,47 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
247
310
|
await runList(makeInvocation(rootOptions(program), options.json), { target });
|
|
248
311
|
});
|
|
249
312
|
|
|
313
|
+
const cache = program
|
|
314
|
+
.command("cache")
|
|
315
|
+
.description("Inspect and clear Rigkit cache");
|
|
316
|
+
|
|
317
|
+
cache
|
|
318
|
+
.command("ls")
|
|
319
|
+
.description("List cache entries for the selected project config")
|
|
320
|
+
.option("--json", "Print machine-readable JSON")
|
|
321
|
+
.action(async (options: { json?: boolean }) => {
|
|
322
|
+
await runCacheList(makeInvocation(rootOptions(program), options.json));
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
cache
|
|
326
|
+
.command("clear")
|
|
327
|
+
.description("Clear cache entries")
|
|
328
|
+
.option("--local", "Clear local cache entries for the selected config")
|
|
329
|
+
.option("--global", "Clear global cache fragments")
|
|
330
|
+
.option("--all", "With --global, clear every global fragment without loading a config")
|
|
331
|
+
.option("--json", "Print machine-readable JSON")
|
|
332
|
+
.action(async (options: { local?: boolean; global?: boolean; all?: boolean; json?: boolean }) => {
|
|
333
|
+
await runCacheClear(makeInvocation(rootOptions(program), options.json), {
|
|
334
|
+
local: Boolean(options.local),
|
|
335
|
+
global: Boolean(options.global),
|
|
336
|
+
all: Boolean(options.all),
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
cache
|
|
341
|
+
.command("invalidate [step]")
|
|
342
|
+
.description("Invalidate cached task outputs so they re-run on next plan/apply")
|
|
343
|
+
.option("--all", "Invalidate every cached task in this project's workflow")
|
|
344
|
+
.option("-y, --yes", "Skip confirmation when invalidating --all")
|
|
345
|
+
.option("--json", "Print machine-readable JSON")
|
|
346
|
+
.action(async (step: string | undefined, options: { all?: boolean; yes?: boolean; json?: boolean }) => {
|
|
347
|
+
await runCacheInvalidate(makeInvocation(rootOptions(program), options.json), {
|
|
348
|
+
step,
|
|
349
|
+
all: Boolean(options.all),
|
|
350
|
+
yes: Boolean(options.yes),
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
250
354
|
program
|
|
251
355
|
.command("projects")
|
|
252
356
|
.description("Discover Rigkit projects below the current directory")
|
|
@@ -291,13 +395,13 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
291
395
|
|
|
292
396
|
function rootOptions(program: Command): GlobalOptions {
|
|
293
397
|
const options = program.opts<{
|
|
294
|
-
|
|
398
|
+
chdir?: string;
|
|
295
399
|
config?: string;
|
|
296
400
|
state?: string;
|
|
297
401
|
json?: boolean;
|
|
298
402
|
}>();
|
|
299
403
|
return {
|
|
300
|
-
|
|
404
|
+
chdir: options.chdir,
|
|
301
405
|
config: options.config,
|
|
302
406
|
state: options.state,
|
|
303
407
|
json: Boolean(options.json),
|
|
@@ -362,11 +466,25 @@ function parseCompletionEndpointArgs(args: string[]): CompletionOptions & { word
|
|
|
362
466
|
return { ...options, words };
|
|
363
467
|
}
|
|
364
468
|
|
|
469
|
+
// Errors already rendered to the user (via printRunFailure or similar) carry
|
|
470
|
+
// this sentinel so handleCliError doesn't re-print the message a second time.
|
|
471
|
+
class DisplayedCliError extends Error {
|
|
472
|
+
readonly displayed = true as const;
|
|
473
|
+
constructor(message: string) {
|
|
474
|
+
super(message);
|
|
475
|
+
this.name = "DisplayedCliError";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
365
479
|
function handleCliError(error: unknown): void {
|
|
366
480
|
if (error instanceof CommanderError) {
|
|
367
481
|
process.exitCode = error.exitCode;
|
|
368
482
|
return;
|
|
369
483
|
}
|
|
484
|
+
if (error instanceof DisplayedCliError) {
|
|
485
|
+
process.exitCode = 1;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
370
488
|
console.error(error instanceof Error ? error.message : String(error));
|
|
371
489
|
process.exitCode = 1;
|
|
372
490
|
}
|
|
@@ -413,8 +531,7 @@ async function resolveInitAnswers(
|
|
|
413
531
|
}
|
|
414
532
|
|
|
415
533
|
if (!jsonMode) {
|
|
416
|
-
console.log(
|
|
417
|
-
console.log(chalk.dim("This creates a project folder with rig.config.ts, package.json, and local ignore rules."));
|
|
534
|
+
console.log(`${ui.bold("rig")} ${ui.dim("· initialize")}`);
|
|
418
535
|
console.log("");
|
|
419
536
|
}
|
|
420
537
|
|
|
@@ -443,10 +560,10 @@ function canPrompt(): boolean {
|
|
|
443
560
|
function resolveInitProjectPaths(invocation: CliInvocation, name: string): { projectDir: string; configPath: string } {
|
|
444
561
|
const options = invocation.global;
|
|
445
562
|
if (options.config) {
|
|
446
|
-
throw new Error(`rig init does not support
|
|
563
|
+
throw new Error(`rig init does not support -config. Use -chdir to choose the parent directory.`);
|
|
447
564
|
}
|
|
448
565
|
|
|
449
|
-
const parentDir = resolve(process.cwd(), options.
|
|
566
|
+
const parentDir = resolve(process.cwd(), options.chdir ?? ".");
|
|
450
567
|
const projectDir = resolve(parentDir, name);
|
|
451
568
|
return {
|
|
452
569
|
projectDir,
|
|
@@ -540,35 +657,8 @@ async function runPackageManagerInstall(
|
|
|
540
657
|
}
|
|
541
658
|
|
|
542
659
|
const command = packageManagerInstallCommand(packageManager);
|
|
543
|
-
if (!jsonMode && process.stderr.isTTY) {
|
|
544
|
-
const spinner = ora({
|
|
545
|
-
text: `installing ${command.join(" ")}`,
|
|
546
|
-
stream: process.stderr,
|
|
547
|
-
}).start();
|
|
548
|
-
const proc = Bun.spawn(command, {
|
|
549
|
-
cwd: projectDir,
|
|
550
|
-
stdin: "inherit",
|
|
551
|
-
stdout: "pipe",
|
|
552
|
-
stderr: "pipe",
|
|
553
|
-
});
|
|
554
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
555
|
-
proc.exited,
|
|
556
|
-
new Response(proc.stdout).text(),
|
|
557
|
-
new Response(proc.stderr).text(),
|
|
558
|
-
]);
|
|
559
|
-
if (exitCode !== 0) {
|
|
560
|
-
spinner.fail(`${command.join(" ")} failed`);
|
|
561
|
-
if (stdout) process.stdout.write(stdout);
|
|
562
|
-
if (stderr) process.stderr.write(stderr);
|
|
563
|
-
throw new Error(`${command.join(" ")} failed with exit code ${exitCode}`);
|
|
564
|
-
}
|
|
565
|
-
spinner.succeed(`installed ${command.join(" ")}`);
|
|
566
|
-
return { packageManager, command: command.join(" "), skipped: false, reported: true };
|
|
567
|
-
}
|
|
568
|
-
|
|
569
660
|
if (!jsonMode) {
|
|
570
|
-
|
|
571
|
-
console.log(`${chalk.cyan("installing")} ${command.join(" ")}`);
|
|
661
|
+
process.stderr.write(`${ui.accent(ui.sym.active)} ${ui.dim(`$ ${command.join(" ")}`)}\n`);
|
|
572
662
|
}
|
|
573
663
|
|
|
574
664
|
const proc = Bun.spawn(command, {
|
|
@@ -587,31 +677,43 @@ async function runPackageManagerInstall(
|
|
|
587
677
|
}
|
|
588
678
|
|
|
589
679
|
function printInitResult(result: InitProjectResult, install: InitInstallResult): void {
|
|
680
|
+
console.log(`${ui.ok(ui.sym.ok)} ${ui.bold(result.name)} ${ui.dim("ready")}`);
|
|
590
681
|
console.log("");
|
|
591
|
-
console.log(`${chalk.green("Rigkit initialized")} ${chalk.bold(result.name)}`);
|
|
592
|
-
printInitLine(result.created.config ? "created" : "updated", result.configPath);
|
|
593
|
-
printInitLine(result.created.env ? "created" : result.updated.envApiKey ? "updated" : "kept", result.envPath);
|
|
594
|
-
printInitLine(result.created.envExample ? "created" : "kept", result.envExamplePath);
|
|
595
|
-
printInitLine(result.created.packageJson ? "created" : result.updated.packageJson ? "updated" : "kept", result.packageJsonPath);
|
|
596
|
-
printInitLine(result.created.gitignore ? "created" : result.updated.gitignore ? "updated" : "kept", result.gitignorePath);
|
|
597
682
|
|
|
683
|
+
const fileLines = [
|
|
684
|
+
ui.fileStatus(initStatus(result.created.config, false), shortPath(result.configPath)),
|
|
685
|
+
ui.fileStatus(initStatus(result.created.env, result.updated.envApiKey), shortPath(result.envPath)),
|
|
686
|
+
ui.fileStatus(initStatus(result.created.envExample, false), shortPath(result.envExamplePath)),
|
|
687
|
+
ui.fileStatus(initStatus(result.created.packageJson, result.updated.packageJson), shortPath(result.packageJsonPath)),
|
|
688
|
+
ui.fileStatus(initStatus(result.created.gitignore, result.updated.gitignore), shortPath(result.gitignorePath)),
|
|
689
|
+
];
|
|
598
690
|
if (result.updated.sdkDependency) {
|
|
599
|
-
|
|
691
|
+
fileLines.push(ui.fileStatus("pinned", `${PROJECT_PACKAGE_NAME}@${RIGKIT_CLI_VERSION}`));
|
|
600
692
|
}
|
|
693
|
+
for (const line of fileLines) console.log(line);
|
|
601
694
|
|
|
602
|
-
if (install.skipped) {
|
|
603
|
-
console.log(
|
|
604
|
-
} else if (install.command && !install.reported) {
|
|
605
|
-
console.log(`${chalk.green("installed")} ${install.command}`);
|
|
695
|
+
if (!install.skipped && install.command && !install.reported) {
|
|
696
|
+
console.log(ui.fileStatus("created", install.command));
|
|
606
697
|
}
|
|
607
698
|
|
|
608
699
|
console.log("");
|
|
609
|
-
console.log(
|
|
610
|
-
console.log(`
|
|
700
|
+
console.log(ui.bold("Next"));
|
|
701
|
+
console.log(ui.hint(`cd ${displayProjectDir(result.projectDir)}`));
|
|
611
702
|
if (install.skipped) {
|
|
612
|
-
console.log(
|
|
703
|
+
console.log(ui.hint(detectInstallCommand(result.packageJsonPath)));
|
|
613
704
|
}
|
|
614
|
-
console.log("
|
|
705
|
+
console.log(ui.hint("rig plan"));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function initStatus(created: boolean, updated: boolean): ui.FileStatus {
|
|
709
|
+
if (created) return "created";
|
|
710
|
+
if (updated) return "updated";
|
|
711
|
+
return "kept";
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function shortPath(path: string): string {
|
|
715
|
+
const rel = relative(process.cwd(), path);
|
|
716
|
+
return rel && !rel.startsWith("..") ? rel : path;
|
|
615
717
|
}
|
|
616
718
|
|
|
617
719
|
function displayProjectDir(projectDir: string): string {
|
|
@@ -619,11 +721,6 @@ function displayProjectDir(projectDir: string): string {
|
|
|
619
721
|
return path && !path.startsWith("..") ? path : projectDir;
|
|
620
722
|
}
|
|
621
723
|
|
|
622
|
-
function printInitLine(status: "created" | "updated" | "kept", path: string): void {
|
|
623
|
-
const color = status === "kept" ? chalk.dim : status === "updated" ? chalk.yellow : chalk.green;
|
|
624
|
-
console.log(`${color(status.padEnd(7))} ${path}`);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
724
|
function detectInstallCommand(packageJsonPath: string): string {
|
|
628
725
|
const projectDir = dirname(packageJsonPath);
|
|
629
726
|
if (existsSync(join(projectDir, "bun.lock")) || existsSync(join(projectDir, "bun.lockb"))) return "bun install";
|
|
@@ -654,24 +751,17 @@ async function runProjectOperation(
|
|
|
654
751
|
args: string[],
|
|
655
752
|
options: RunOptions,
|
|
656
753
|
): Promise<void> {
|
|
657
|
-
const remote = splitGithubProjectTarget(args);
|
|
658
|
-
if ((options.all || options.discover) && remote.target) {
|
|
659
|
-
throw new Error(`Remote GitHub project targets cannot be combined with --all or --discover`);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
754
|
if (options.all || options.discover) {
|
|
663
|
-
await runDiscoveredProjectOperation(invocation, requestedOperation,
|
|
755
|
+
await runDiscoveredProjectOperation(invocation, requestedOperation, args, { all: options.all });
|
|
664
756
|
return;
|
|
665
757
|
}
|
|
666
758
|
|
|
667
|
-
const runtime =
|
|
668
|
-
? await loadGithubRuntime(invocation, remote.target)
|
|
669
|
-
: await loadRuntime(invocation);
|
|
759
|
+
const runtime = await loadRuntime(invocation);
|
|
670
760
|
const { operation, parsed, result } = await executeRuntimeOperation(
|
|
671
761
|
invocation,
|
|
672
762
|
runtime,
|
|
673
763
|
requestedOperation,
|
|
674
|
-
|
|
764
|
+
args,
|
|
675
765
|
);
|
|
676
766
|
|
|
677
767
|
if (wantsJson(invocation)) {
|
|
@@ -680,6 +770,9 @@ async function runProjectOperation(
|
|
|
680
770
|
}
|
|
681
771
|
|
|
682
772
|
await renderOperationResult(operation, result, parsed.hostOptions);
|
|
773
|
+
if (operation.createsWorkspace && isWorkspaceRecord(result)) {
|
|
774
|
+
await printWorkspaceNextSteps(runtime, result.name);
|
|
775
|
+
}
|
|
683
776
|
printInteractiveOutputGap(invocation);
|
|
684
777
|
}
|
|
685
778
|
|
|
@@ -724,6 +817,123 @@ async function runWorkspaceOperation(
|
|
|
724
817
|
printInteractiveOutputGap(invocation);
|
|
725
818
|
}
|
|
726
819
|
|
|
820
|
+
async function runRemove(
|
|
821
|
+
invocation: CliInvocation,
|
|
822
|
+
options: { workspace?: string; yes: boolean; all: boolean },
|
|
823
|
+
): Promise<void> {
|
|
824
|
+
if (options.workspace && options.all) {
|
|
825
|
+
throw new Error(`rig rm accepts either a workspace name or --all, not both`);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (options.workspace) {
|
|
829
|
+
await runRemoveWorkspaceOperation(invocation, options.workspace, { yes: options.yes });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const runtime = await loadRuntime(invocation);
|
|
834
|
+
const workspaces = await runtime.control.workspaces()
|
|
835
|
+
.then((response) => response.workspaces as WorkspaceRecord[])
|
|
836
|
+
.catch(() => []);
|
|
837
|
+
if (workspaces.length === 0) {
|
|
838
|
+
if (wantsJson(invocation)) {
|
|
839
|
+
printJson({ removed: [] });
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
console.log(ui.dim("no workspaces"));
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
let targets: string[];
|
|
847
|
+
if (options.all) {
|
|
848
|
+
if (!options.yes && !wantsJson(invocation) && canPrompt()) {
|
|
849
|
+
const confirmed = await promptHostConfirm({
|
|
850
|
+
message: `Remove all ${workspaces.length} workspaces?`,
|
|
851
|
+
defaultValue: false,
|
|
852
|
+
});
|
|
853
|
+
if (!confirmed) throw new Error("Remove cancelled");
|
|
854
|
+
} else if (!options.yes && !canPrompt()) {
|
|
855
|
+
throw new Error(`rig rm --all needs --yes when not running in an interactive terminal`);
|
|
856
|
+
}
|
|
857
|
+
targets = workspaces.map((workspace) => workspace.name);
|
|
858
|
+
} else {
|
|
859
|
+
if (wantsJson(invocation) || !canPrompt()) {
|
|
860
|
+
throw new Error(`rig rm needs a workspace name or --all when not running in an interactive terminal`);
|
|
861
|
+
}
|
|
862
|
+
targets = await promptWorkspaceRemoveSelection(workspaces);
|
|
863
|
+
if (targets.length === 0) throw new Error("Nothing selected");
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const removed: string[] = [];
|
|
867
|
+
for (const name of targets) {
|
|
868
|
+
await runRemoveWorkspaceOperation(invocation, name, { yes: true });
|
|
869
|
+
removed.push(name);
|
|
870
|
+
}
|
|
871
|
+
if (wantsJson(invocation)) printJson({ removed });
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async function promptWorkspaceRemoveSelection(
|
|
875
|
+
workspaces: ReadonlyArray<Pick<WorkspaceRecord, "name" | "workflow">>,
|
|
876
|
+
): Promise<string[]> {
|
|
877
|
+
const answers = await inquirer.prompt<{ names: string[] }>([{
|
|
878
|
+
type: "checkbox",
|
|
879
|
+
name: "names",
|
|
880
|
+
message: "Select workspaces to remove",
|
|
881
|
+
choices: workspaces.map((workspace) => ({
|
|
882
|
+
name: workspace.name,
|
|
883
|
+
value: workspace.name,
|
|
884
|
+
description: workspace.workflow ? `workflow ${workspace.workflow}` : undefined,
|
|
885
|
+
})),
|
|
886
|
+
}]);
|
|
887
|
+
return answers.names;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async function runRemoveWorkspaceOperation(
|
|
891
|
+
invocation: CliInvocation,
|
|
892
|
+
workspaceName: string,
|
|
893
|
+
options: { yes: boolean },
|
|
894
|
+
): Promise<void> {
|
|
895
|
+
const runtime = await loadRuntime(invocation);
|
|
896
|
+
const manifest = await readRuntimeOperations(runtime);
|
|
897
|
+
const operation = (manifest.workspaceOperations ?? []).find((item) => item.id === "remove");
|
|
898
|
+
if (!operation) {
|
|
899
|
+
throw new Error(`This project does not define a removable workspace.`);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const workspaces = await runtime.control.workspaces()
|
|
903
|
+
.then((response) => response.workspaces as WorkspaceRecord[])
|
|
904
|
+
.catch(() => []);
|
|
905
|
+
if (!workspaces.some((workspace) => workspace.name === workspaceName)) {
|
|
906
|
+
throw new Error(`This project does not have a workspace named "${workspaceName}".`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (!options.yes && !wantsJson(invocation) && canPrompt()) {
|
|
910
|
+
const confirmed = await promptHostConfirm({
|
|
911
|
+
message: `Remove workspace ${workspaceName}?`,
|
|
912
|
+
defaultValue: false,
|
|
913
|
+
});
|
|
914
|
+
if (!confirmed) throw new Error("Remove cancelled");
|
|
915
|
+
options = { yes: true };
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const parsed = parseOperationArgs(operation, options.yes ? ["--yes"] : []);
|
|
919
|
+
enforceHostOnlyBooleanGuards(operation, parsed);
|
|
920
|
+
|
|
921
|
+
const result = await runRuntimeOperation<unknown>(
|
|
922
|
+
runtime,
|
|
923
|
+
`${workspaceName}/remove`,
|
|
924
|
+
parsed.input,
|
|
925
|
+
{ renderEvents: !wantsJson(invocation) },
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
if (wantsJson(invocation)) {
|
|
929
|
+
printJson(result);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
await renderOperationResult(operation, result, parsed.hostOptions);
|
|
934
|
+
printInteractiveOutputGap(invocation);
|
|
935
|
+
}
|
|
936
|
+
|
|
727
937
|
async function runDiscoveredProjectOperation(
|
|
728
938
|
invocation: CliInvocation,
|
|
729
939
|
requestedOperation: string,
|
|
@@ -731,7 +941,7 @@ async function runDiscoveredProjectOperation(
|
|
|
731
941
|
options: { all: boolean },
|
|
732
942
|
): Promise<void> {
|
|
733
943
|
const projects = discoverProjectConfigs({
|
|
734
|
-
|
|
944
|
+
chdir: invocation.global.chdir,
|
|
735
945
|
config: invocation.global.config,
|
|
736
946
|
});
|
|
737
947
|
if (projects.length === 0) {
|
|
@@ -740,7 +950,7 @@ async function runDiscoveredProjectOperation(
|
|
|
740
950
|
if (!options.all && projects.length > 1) {
|
|
741
951
|
throw new Error([
|
|
742
952
|
"Multiple Rigkit projects found.",
|
|
743
|
-
"Use `rig projects` to list candidates, pass -
|
|
953
|
+
"Use `rig projects` to list candidates, pass -chdir or -config to select one, or pass --all to run every discovered project.",
|
|
744
954
|
...projects.map((project) => `- ${project.configPath}`),
|
|
745
955
|
].join("\n"));
|
|
746
956
|
}
|
|
@@ -758,7 +968,7 @@ async function runDiscoveredProjectOperation(
|
|
|
758
968
|
const runtime = await getOrStartRuntime({
|
|
759
969
|
projectDir: project.projectDir,
|
|
760
970
|
configPath: project.configPath,
|
|
761
|
-
statePath: invocation.global.state ?
|
|
971
|
+
statePath: invocation.global.state ? resolveGlobalPath(invocation, invocation.global.state) : undefined,
|
|
762
972
|
});
|
|
763
973
|
const { operation, parsed, result } = await executeRuntimeOperation(
|
|
764
974
|
invocation,
|
|
@@ -774,7 +984,7 @@ async function runDiscoveredProjectOperation(
|
|
|
774
984
|
|
|
775
985
|
if (!wantsJson(invocation)) {
|
|
776
986
|
if (projects.length > 1) {
|
|
777
|
-
console.log(
|
|
987
|
+
console.log(ui.bold(displayProjectDir(project.projectDir)));
|
|
778
988
|
}
|
|
779
989
|
await renderOperationResult(operation, result, parsed.hostOptions);
|
|
780
990
|
printInteractiveOutputGap(invocation);
|
|
@@ -788,7 +998,7 @@ async function runDiscoveredProjectOperation(
|
|
|
788
998
|
|
|
789
999
|
async function runProjects(invocation: CliInvocation): Promise<void> {
|
|
790
1000
|
const projects = discoverProjectConfigs({
|
|
791
|
-
|
|
1001
|
+
chdir: invocation.global.chdir,
|
|
792
1002
|
config: invocation.global.config,
|
|
793
1003
|
});
|
|
794
1004
|
if (wantsJson(invocation)) {
|
|
@@ -796,13 +1006,14 @@ async function runProjects(invocation: CliInvocation): Promise<void> {
|
|
|
796
1006
|
return;
|
|
797
1007
|
}
|
|
798
1008
|
if (projects.length === 0) {
|
|
799
|
-
console.log("
|
|
1009
|
+
console.log(ui.dim("no Rigkit projects found"));
|
|
800
1010
|
return;
|
|
801
1011
|
}
|
|
802
|
-
|
|
803
|
-
project.projectDir,
|
|
804
|
-
project.configPath,
|
|
805
|
-
])
|
|
1012
|
+
const rows = projects.map((project) => [
|
|
1013
|
+
{ text: project.projectDir, style: ui.bold },
|
|
1014
|
+
{ text: project.configPath, style: ui.dim },
|
|
1015
|
+
]);
|
|
1016
|
+
console.log(ui.columns(["project", "config"], rows));
|
|
806
1017
|
}
|
|
807
1018
|
|
|
808
1019
|
async function runList(invocation: CliInvocation, options: ListOptions): Promise<void> {
|
|
@@ -837,6 +1048,123 @@ async function runList(invocation: CliInvocation, options: ListOptions): Promise
|
|
|
837
1048
|
printConfig(project);
|
|
838
1049
|
}
|
|
839
1050
|
|
|
1051
|
+
async function runCacheList(invocation: CliInvocation): Promise<void> {
|
|
1052
|
+
const runtime = await loadRuntime(invocation);
|
|
1053
|
+
const cache = await runtime.control.cache();
|
|
1054
|
+
if (wantsJson(invocation)) {
|
|
1055
|
+
printJson(cache);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
printCacheEntries(cache.entries);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
async function runCacheClear(invocation: CliInvocation, options: CacheClearOptions): Promise<void> {
|
|
1062
|
+
if (options.all && !options.global) {
|
|
1063
|
+
throw new Error(`rig cache clear --all must be combined with --global`);
|
|
1064
|
+
}
|
|
1065
|
+
if (options.local && options.global && !options.all) {
|
|
1066
|
+
throw new Error(`Choose --local or --global, not both`);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (options.global && options.all) {
|
|
1070
|
+
if (invocation.global.chdir || invocation.global.config || invocation.global.state) {
|
|
1071
|
+
throw new Error(`rig cache clear --global --all cannot be combined with -chdir, -config, or -state`);
|
|
1072
|
+
}
|
|
1073
|
+
const fragmentRoot = join(defaultRigkitHome(), "fragments");
|
|
1074
|
+
rmSync(fragmentRoot, { recursive: true, force: true });
|
|
1075
|
+
if (wantsJson(invocation)) {
|
|
1076
|
+
printJson({ ok: true, deleted: null, scope: "global-all", fragmentRoot });
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
console.log(`Cleared global fragment cache at ${fragmentRoot}`);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const scope = options.local ? "local" : options.global ? "global" : "all";
|
|
1084
|
+
const runtime = await loadRuntime(invocation);
|
|
1085
|
+
const result = await runtime.control.clearCache({ scope });
|
|
1086
|
+
if (wantsJson(invocation)) {
|
|
1087
|
+
printJson(result);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.log(`Cleared ${result.deleted} cache ${result.deleted === 1 ? "entry" : "entries"}.`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
type CacheInvalidateOptions = {
|
|
1094
|
+
step?: string;
|
|
1095
|
+
all: boolean;
|
|
1096
|
+
yes: boolean;
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
async function runCacheInvalidate(invocation: CliInvocation, options: CacheInvalidateOptions): Promise<void> {
|
|
1100
|
+
if (options.step && options.all) {
|
|
1101
|
+
throw new Error(`rig cache invalidate accepts either a step or --all, not both`);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const runtime = await loadRuntime(invocation);
|
|
1105
|
+
let targets: string[] = [];
|
|
1106
|
+
|
|
1107
|
+
if (options.step) {
|
|
1108
|
+
targets = [options.step];
|
|
1109
|
+
} else if (options.all) {
|
|
1110
|
+
if (!options.yes && !wantsJson(invocation) && canPrompt()) {
|
|
1111
|
+
const confirmed = await promptHostConfirm({
|
|
1112
|
+
message: "Invalidate every cached task in this project?",
|
|
1113
|
+
defaultValue: false,
|
|
1114
|
+
});
|
|
1115
|
+
if (!confirmed) throw new Error("Invalidate cancelled");
|
|
1116
|
+
} else if (!options.yes && !canPrompt()) {
|
|
1117
|
+
throw new Error(`rig cache invalidate --all needs --yes when not running in an interactive terminal`);
|
|
1118
|
+
}
|
|
1119
|
+
targets = []; // empty = engine invalidates everything for the workflow
|
|
1120
|
+
} else {
|
|
1121
|
+
// Interactive: multi-select among currently-valid cache entries.
|
|
1122
|
+
const cache = await runtime.control.cache();
|
|
1123
|
+
const candidates = cache.entries
|
|
1124
|
+
.filter((entry) => !entry.invalidated && entry.scope === "local")
|
|
1125
|
+
.map((entry) => ({ path: entry.nodePath || entry.nodeName, workflow: entry.workflow }));
|
|
1126
|
+
if (candidates.length === 0) {
|
|
1127
|
+
if (wantsJson(invocation)) {
|
|
1128
|
+
printJson({ ok: true, invalidated: 0 });
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
console.log(ui.dim("no valid cache entries to invalidate"));
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (wantsJson(invocation) || !canPrompt()) {
|
|
1135
|
+
throw new Error(`rig cache invalidate needs a step name or --all when not running in an interactive terminal`);
|
|
1136
|
+
}
|
|
1137
|
+
const picked = await promptCacheInvalidateSelection(candidates);
|
|
1138
|
+
if (picked.length === 0) throw new Error("Nothing selected");
|
|
1139
|
+
targets = picked;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const result = await runtime.control.invalidateCache({ nodePaths: targets });
|
|
1143
|
+
if (wantsJson(invocation)) {
|
|
1144
|
+
printJson(result);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
console.log(
|
|
1148
|
+
`${ui.ok(ui.sym.ok)} invalidated ${result.invalidated} cache ${result.invalidated === 1 ? "entry" : "entries"}`,
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
async function promptCacheInvalidateSelection(
|
|
1153
|
+
candidates: Array<{ path: string; workflow: string }>,
|
|
1154
|
+
): Promise<string[]> {
|
|
1155
|
+
const answers = await inquirer.prompt<{ paths: string[] }>([{
|
|
1156
|
+
type: "checkbox",
|
|
1157
|
+
name: "paths",
|
|
1158
|
+
message: "Select tasks to invalidate",
|
|
1159
|
+
choices: candidates.map((c) => ({
|
|
1160
|
+
name: c.path,
|
|
1161
|
+
value: c.path,
|
|
1162
|
+
description: c.workflow ? `workflow ${c.workflow}` : undefined,
|
|
1163
|
+
})),
|
|
1164
|
+
}]);
|
|
1165
|
+
return answers.paths;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
840
1168
|
async function executeRuntimeOperation(
|
|
841
1169
|
invocation: CliInvocation,
|
|
842
1170
|
runtime: RuntimeClient,
|
|
@@ -1069,7 +1397,9 @@ async function renderOperationResult(
|
|
|
1069
1397
|
}
|
|
1070
1398
|
|
|
1071
1399
|
if (operation.createsWorkspace && isWorkspaceRecord(result)) {
|
|
1072
|
-
|
|
1400
|
+
// Only emit the bareword name when stdout is piped, so scripts can do
|
|
1401
|
+
// `name=$(rig create)` while TTY users aren't shown a redundant line.
|
|
1402
|
+
if (!process.stdout.isTTY) console.log(result.name);
|
|
1073
1403
|
return;
|
|
1074
1404
|
}
|
|
1075
1405
|
|
|
@@ -1085,7 +1415,7 @@ async function renderOperationResult(
|
|
|
1085
1415
|
}
|
|
1086
1416
|
|
|
1087
1417
|
if (isWorkspaceRecord(result)) {
|
|
1088
|
-
console.log(result.name);
|
|
1418
|
+
if (!process.stdout.isTTY) console.log(result.name);
|
|
1089
1419
|
return;
|
|
1090
1420
|
}
|
|
1091
1421
|
|
|
@@ -1104,12 +1434,12 @@ async function runDoctor(invocation: CliInvocation, options: DoctorOptions): Pro
|
|
|
1104
1434
|
printJson(diagnostics);
|
|
1105
1435
|
return;
|
|
1106
1436
|
}
|
|
1107
|
-
|
|
1437
|
+
console.log(ui.kvList([
|
|
1108
1438
|
["cli", diagnostics.cliVersion],
|
|
1109
1439
|
["binary", diagnostics.binary ?? ""],
|
|
1110
1440
|
["node", diagnostics.node],
|
|
1111
1441
|
["bun", diagnostics.bun ?? ""],
|
|
1112
|
-
]);
|
|
1442
|
+
]));
|
|
1113
1443
|
return;
|
|
1114
1444
|
}
|
|
1115
1445
|
|
|
@@ -1137,7 +1467,7 @@ async function runDoctor(invocation: CliInvocation, options: DoctorOptions): Pro
|
|
|
1137
1467
|
return;
|
|
1138
1468
|
}
|
|
1139
1469
|
|
|
1140
|
-
|
|
1470
|
+
console.log(ui.kvList([
|
|
1141
1471
|
["cli", RIGKIT_CLI_VERSION],
|
|
1142
1472
|
["project", project.projectDir],
|
|
1143
1473
|
["config", project.configPath],
|
|
@@ -1150,7 +1480,7 @@ async function runDoctor(invocation: CliInvocation, options: DoctorOptions): Pro
|
|
|
1150
1480
|
["protocol", runtimeInfo.protocolHash],
|
|
1151
1481
|
["state", project.statePath ?? ""],
|
|
1152
1482
|
["expires", health.expiresAt ?? runtime.handle.expiresAt ?? ""],
|
|
1153
|
-
]);
|
|
1483
|
+
]));
|
|
1154
1484
|
}
|
|
1155
1485
|
|
|
1156
1486
|
async function runVersion(invocation: CliInvocation): Promise<void> {
|
|
@@ -1172,8 +1502,10 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
|
|
|
1172
1502
|
{ name: "plan", description: "Plan project workflow changes" },
|
|
1173
1503
|
{ name: "apply", description: "Apply project workflow changes" },
|
|
1174
1504
|
{ name: "create", description: "Create a workspace" },
|
|
1505
|
+
{ name: "rm", description: "Remove a workspace" },
|
|
1175
1506
|
{ name: "run", description: "Run a workspace operation" },
|
|
1176
1507
|
{ name: "ls", description: "List project workspaces" },
|
|
1508
|
+
{ name: "cache", description: "Inspect and clear Rigkit cache" },
|
|
1177
1509
|
{ name: "projects", description: "Discover Rigkit projects below the current directory" },
|
|
1178
1510
|
{ name: "doctor", description: "Show Rigkit runtime diagnostics" },
|
|
1179
1511
|
{ name: "version", description: "Show Rigkit CLI version" },
|
|
@@ -1182,30 +1514,37 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
|
|
|
1182
1514
|
});
|
|
1183
1515
|
return;
|
|
1184
1516
|
}
|
|
1517
|
+
const cmd = (name: string, description: string): string =>
|
|
1518
|
+
` ${ui.bold(name.padEnd(10))} ${description}`;
|
|
1519
|
+
const opt = (flag: string, description: string): string =>
|
|
1520
|
+
` ${ui.bold(flag.padEnd(12))} ${description}`;
|
|
1521
|
+
|
|
1185
1522
|
console.log([
|
|
1186
|
-
|
|
1523
|
+
`${ui.bold("rig")} ${ui.dim(RIGKIT_CLI_VERSION)}`,
|
|
1187
1524
|
"",
|
|
1188
|
-
"Usage:",
|
|
1189
|
-
|
|
1525
|
+
ui.dim("Usage:"),
|
|
1526
|
+
` ${ui.accent(ui.sym.prompt)} rig [global options] <command> [args]`,
|
|
1190
1527
|
"",
|
|
1191
|
-
"Commands:",
|
|
1192
|
-
"
|
|
1193
|
-
"
|
|
1194
|
-
"
|
|
1195
|
-
"
|
|
1196
|
-
"
|
|
1197
|
-
"
|
|
1198
|
-
"
|
|
1199
|
-
"
|
|
1200
|
-
"
|
|
1201
|
-
"
|
|
1202
|
-
"
|
|
1528
|
+
ui.dim("Commands:"),
|
|
1529
|
+
cmd("help", "Show Rigkit CLI help"),
|
|
1530
|
+
cmd("init", "Initialize a Rigkit project"),
|
|
1531
|
+
cmd("plan", "Plan project workflow changes"),
|
|
1532
|
+
cmd("apply", "Apply project workflow changes"),
|
|
1533
|
+
cmd("create", "Create a workspace"),
|
|
1534
|
+
cmd("rm", "Remove a workspace"),
|
|
1535
|
+
cmd("run", "Run a workspace operation"),
|
|
1536
|
+
cmd("ls", "List project workspaces"),
|
|
1537
|
+
cmd("cache", "Inspect and clear Rigkit cache"),
|
|
1538
|
+
cmd("projects", "Discover Rigkit projects below the current directory"),
|
|
1539
|
+
cmd("doctor", "Show Rigkit runtime diagnostics"),
|
|
1540
|
+
cmd("version", "Show Rigkit CLI version"),
|
|
1541
|
+
cmd("completion", "Generate shell completion script"),
|
|
1203
1542
|
"",
|
|
1204
|
-
"Options:",
|
|
1205
|
-
"
|
|
1206
|
-
"
|
|
1207
|
-
"
|
|
1208
|
-
"
|
|
1543
|
+
ui.dim("Options:"),
|
|
1544
|
+
opt("-chdir=DIR", "Switch to a directory containing rig.config.ts before running the command"),
|
|
1545
|
+
opt("-config=FILE", "Config file to load, relative to -chdir when set"),
|
|
1546
|
+
opt("-state=FILE", "Local runtime state database path"),
|
|
1547
|
+
opt("-json", "Print machine-readable JSON where supported"),
|
|
1209
1548
|
].join("\n"));
|
|
1210
1549
|
}
|
|
1211
1550
|
|
|
@@ -1214,50 +1553,6 @@ async function loadRuntime(invocation: CliInvocation): Promise<RuntimeClient> {
|
|
|
1214
1553
|
return await getOrStartRuntime(engineOptions);
|
|
1215
1554
|
}
|
|
1216
1555
|
|
|
1217
|
-
async function loadGithubRuntime(invocation: CliInvocation, target: GithubProjectTarget): Promise<RuntimeClient> {
|
|
1218
|
-
if (invocation.global.project || invocation.global.config || invocation.global.state) {
|
|
1219
|
-
throw new Error(`Remote GitHub project targets cannot be combined with -C/--project, --config, or --state`);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
await confirmGithubProjectTarget(target);
|
|
1223
|
-
const project = await materializeGithubProject(target);
|
|
1224
|
-
if (!wantsJson(invocation)) {
|
|
1225
|
-
console.error(`github ${target.owner}/${target.repo}@${project.commitSha.slice(0, 12)}`);
|
|
1226
|
-
}
|
|
1227
|
-
return await getOrStartRuntime({
|
|
1228
|
-
projectDir: project.projectDir,
|
|
1229
|
-
configPath: project.configPath,
|
|
1230
|
-
statePath: project.statePath,
|
|
1231
|
-
source: {
|
|
1232
|
-
kind: "github",
|
|
1233
|
-
target: target.raw,
|
|
1234
|
-
repoUrl: project.repoUrl,
|
|
1235
|
-
ref: project.ref,
|
|
1236
|
-
commitSha: project.commitSha,
|
|
1237
|
-
},
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
async function confirmGithubProjectTarget(target: GithubProjectTarget): Promise<void> {
|
|
1242
|
-
if (process.env.RIGKIT_TRUST_REMOTE_CONFIGS === "1") return;
|
|
1243
|
-
const repo = `https://github.com/${target.owner}/${target.repo}`;
|
|
1244
|
-
const ref = target.ref ? ` at ${target.ref}` : "";
|
|
1245
|
-
const message = `Run and install dependencies for ${repo}${ref}?`;
|
|
1246
|
-
|
|
1247
|
-
if (!canPrompt()) {
|
|
1248
|
-
throw new Error(
|
|
1249
|
-
`Remote GitHub project ${target.raw} executes code on this machine. ` +
|
|
1250
|
-
`Run from an interactive terminal or set RIGKIT_TRUST_REMOTE_CONFIGS=1 to allow it explicitly.`,
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
console.error("");
|
|
1255
|
-
console.error(chalk.yellow("Remote Rigkit configs execute code on this machine."));
|
|
1256
|
-
console.error(`Project: ${repo}${ref}`);
|
|
1257
|
-
const allowed = await promptHostConfirm({ message, defaultValue: false });
|
|
1258
|
-
if (!allowed) throw new Error(`Remote GitHub project denied`);
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1556
|
async function readRuntimeProject(runtime: RuntimeClient): Promise<EngineProjectInfo> {
|
|
1262
1557
|
return await runtime.control.project() as EngineProjectInfo;
|
|
1263
1558
|
}
|
|
@@ -1276,14 +1571,33 @@ async function runRuntimeOperation<T>(
|
|
|
1276
1571
|
let presenter: RunPresenter | undefined = options.renderEvents
|
|
1277
1572
|
? createRunPresenter(operation)
|
|
1278
1573
|
: undefined;
|
|
1574
|
+
const logger: RunLogger | undefined = createRunLogger({
|
|
1575
|
+
projectDir: runtime.handle.projectDir,
|
|
1576
|
+
operation,
|
|
1577
|
+
daemonStderrPath: runtime.paths.runtimeLogPath,
|
|
1578
|
+
});
|
|
1579
|
+
if (logger) {
|
|
1580
|
+
logger.append({ type: "run.started", runId: started.runId, operation, input });
|
|
1581
|
+
}
|
|
1279
1582
|
let result: T | undefined;
|
|
1280
1583
|
let failure: Error | undefined;
|
|
1584
|
+
let failureCode: string | undefined;
|
|
1585
|
+
let activeNodePath: string | undefined;
|
|
1281
1586
|
|
|
1282
1587
|
const handleEvent = async (
|
|
1283
1588
|
event: unknown,
|
|
1284
1589
|
respond?: (id: string, response: unknown) => void | Promise<void>,
|
|
1285
1590
|
sendSession?: (message: unknown) => void | Promise<void>,
|
|
1286
1591
|
) => {
|
|
1592
|
+
logger?.append(event);
|
|
1593
|
+
if (isRecord(event)) {
|
|
1594
|
+
if (event.type === "node.started" && typeof event.nodePath === "string") {
|
|
1595
|
+
activeNodePath = event.nodePath;
|
|
1596
|
+
}
|
|
1597
|
+
if (event.type === "node.completed" && event.nodePath === activeNodePath) {
|
|
1598
|
+
activeNodePath = undefined;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1287
1601
|
if (isHostRequestEvent(event)) {
|
|
1288
1602
|
const suspendPresenter = hostRequestNeedsTerminal(event);
|
|
1289
1603
|
if (suspendPresenter) presenter?.pause();
|
|
@@ -1331,6 +1645,9 @@ async function runRuntimeOperation<T>(
|
|
|
1331
1645
|
? event.error.message
|
|
1332
1646
|
: "Runtime operation failed";
|
|
1333
1647
|
failure = new Error(message);
|
|
1648
|
+
failureCode = isRecord(event.error) && typeof event.error.code === "string"
|
|
1649
|
+
? event.error.code
|
|
1650
|
+
: undefined;
|
|
1334
1651
|
presenter?.render({ ...event, type: "run.failed" });
|
|
1335
1652
|
return;
|
|
1336
1653
|
}
|
|
@@ -1379,14 +1696,85 @@ async function runRuntimeOperation<T>(
|
|
|
1379
1696
|
}
|
|
1380
1697
|
} finally {
|
|
1381
1698
|
presenter?.close();
|
|
1699
|
+
if (logger) {
|
|
1700
|
+
logger.finish({
|
|
1701
|
+
status: failure ? "failed" : "completed",
|
|
1702
|
+
error: failure,
|
|
1703
|
+
result,
|
|
1704
|
+
});
|
|
1705
|
+
logger.close();
|
|
1706
|
+
}
|
|
1382
1707
|
uninstallRunCancelHandler();
|
|
1383
1708
|
}
|
|
1384
1709
|
|
|
1385
|
-
if (failure)
|
|
1710
|
+
if (failure) {
|
|
1711
|
+
printRunFailure({
|
|
1712
|
+
operation,
|
|
1713
|
+
node: activeNodePath,
|
|
1714
|
+
code: failureCode,
|
|
1715
|
+
message: failure.message,
|
|
1716
|
+
logPath: logger?.path,
|
|
1717
|
+
});
|
|
1718
|
+
throw new DisplayedCliError(failure.message);
|
|
1719
|
+
}
|
|
1386
1720
|
if (result === undefined) throw new Error(`Runtime operation ${operation} finished without a result`);
|
|
1387
1721
|
return result;
|
|
1388
1722
|
}
|
|
1389
1723
|
|
|
1724
|
+
function printRunFailure(input: {
|
|
1725
|
+
operation: string;
|
|
1726
|
+
node: string | undefined;
|
|
1727
|
+
code: string | undefined;
|
|
1728
|
+
message: string;
|
|
1729
|
+
logPath: string | undefined;
|
|
1730
|
+
}): void {
|
|
1731
|
+
const pairs: Array<[string, string]> = [];
|
|
1732
|
+
if (input.node) pairs.push(["node", input.node]);
|
|
1733
|
+
if (input.code) pairs.push(["code", ui.bold(input.code)]);
|
|
1734
|
+
pairs.push(["reason", input.message]);
|
|
1735
|
+
|
|
1736
|
+
process.stderr.write("\n");
|
|
1737
|
+
process.stderr.write(`${ui.err(ui.sym.err)} ${ui.bold(`${input.operation} failed`)}\n`);
|
|
1738
|
+
process.stderr.write(`${ui.kvList(pairs)}\n`);
|
|
1739
|
+
if (input.logPath) {
|
|
1740
|
+
process.stderr.write("\n");
|
|
1741
|
+
process.stderr.write(`${ui.dim("full log")} ${shortPath(input.logPath)}\n`);
|
|
1742
|
+
process.stderr.write(`${ui.dim(" ")} ${ui.dim("daemon stderr appended on failure")}\n`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// After a successful `rig create`, list the workspace's available operations so
|
|
1747
|
+
// the user doesn't have to guess what to do next. TTY-only — JSON consumers and
|
|
1748
|
+
// pipes get clean output.
|
|
1749
|
+
async function printWorkspaceNextSteps(runtime: RuntimeClient, workspaceName: string): Promise<void> {
|
|
1750
|
+
if (!process.stderr.isTTY) return;
|
|
1751
|
+
|
|
1752
|
+
let manifest: RuntimeOperationManifest;
|
|
1753
|
+
try {
|
|
1754
|
+
manifest = await readRuntimeOperations(runtime);
|
|
1755
|
+
} catch {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const ops = (manifest.workspaceOperations ?? []).filter((op) => op.id !== "remove");
|
|
1760
|
+
const invocations: Array<{ command: string; description: string }> = ops.map((op) => ({
|
|
1761
|
+
command: `rig run ${workspaceName} ${op.id}`,
|
|
1762
|
+
description: op.description ?? op.title ?? "",
|
|
1763
|
+
}));
|
|
1764
|
+
invocations.push({ command: `rig rm ${workspaceName}`, description: "Remove this workspace" });
|
|
1765
|
+
|
|
1766
|
+
const commandWidth = invocations.reduce((max, item) => Math.max(max, item.command.length), 0);
|
|
1767
|
+
|
|
1768
|
+
process.stderr.write("\n");
|
|
1769
|
+
process.stderr.write(`${ui.bold("Next")}\n`);
|
|
1770
|
+
for (const item of invocations) {
|
|
1771
|
+
const command = `${ui.bold("rig")}${item.command.slice("rig".length)}`;
|
|
1772
|
+
const padding = " ".repeat(Math.max(0, commandWidth - item.command.length));
|
|
1773
|
+
const tail = item.description ? ` ${ui.dim(item.description)}` : "";
|
|
1774
|
+
process.stderr.write(`${ui.dim(ui.sym.arrow)} ${command}${padding}${tail}\n`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1390
1778
|
let uninstallActiveRunCancelHandler: (() => void) | undefined;
|
|
1391
1779
|
|
|
1392
1780
|
function installRunCancelHandler(session: { send(message: unknown): void; close(code?: number, reason?: string): void }): void {
|
|
@@ -1418,13 +1806,17 @@ function resolveEngineOptions(invocation: CliInvocation): { projectDir: string;
|
|
|
1418
1806
|
return {
|
|
1419
1807
|
projectDir: paths.projectDir,
|
|
1420
1808
|
configPath: paths.configPath,
|
|
1421
|
-
statePath: options.state ?
|
|
1809
|
+
statePath: options.state ? resolveGlobalPath(invocation, options.state) : undefined,
|
|
1422
1810
|
};
|
|
1423
1811
|
}
|
|
1424
1812
|
|
|
1425
1813
|
function resolveCommandConfigPaths(invocation: CliInvocation): { projectDir: string; configPath: string } {
|
|
1426
1814
|
const options = invocation.global;
|
|
1427
|
-
return resolveConfigPaths({
|
|
1815
|
+
return resolveConfigPaths({ chdir: options.chdir, config: options.config });
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function resolveGlobalPath(invocation: CliInvocation, path: string): string {
|
|
1819
|
+
return resolve(process.cwd(), invocation.global.chdir ?? ".", path);
|
|
1428
1820
|
}
|
|
1429
1821
|
|
|
1430
1822
|
type HostRequestEvent = {
|
|
@@ -1798,15 +2190,21 @@ async function confirmHostCommand(input: {
|
|
|
1798
2190
|
throw new Error(`Host command requires confirmation in an interactive terminal`);
|
|
1799
2191
|
}
|
|
1800
2192
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
if (input.cwd)
|
|
2193
|
+
const pairs: Array<[string, string]> = [
|
|
2194
|
+
["command", input.argv.map(shellDisplay).join(" ")],
|
|
2195
|
+
["mode", input.mode],
|
|
2196
|
+
];
|
|
2197
|
+
if (input.cwd) pairs.push(["cwd", input.cwd]);
|
|
1806
2198
|
if (input.env && Object.keys(input.env).length > 0) {
|
|
1807
|
-
|
|
2199
|
+
pairs.push(["env", Object.keys(input.env).join(", ")]);
|
|
1808
2200
|
}
|
|
1809
|
-
if (input.reason)
|
|
2201
|
+
if (input.reason) pairs.push(["reason", input.reason]);
|
|
2202
|
+
|
|
2203
|
+
console.error("");
|
|
2204
|
+
console.error(`${ui.warn("!")} ${ui.bold("this config wants to run a command on your machine")}`);
|
|
2205
|
+
console.error("");
|
|
2206
|
+
console.error(ui.kvList(pairs));
|
|
2207
|
+
console.error("");
|
|
1810
2208
|
return await promptHostConfirm({ message: "Allow?", defaultValue: false });
|
|
1811
2209
|
}
|
|
1812
2210
|
|
|
@@ -1882,82 +2280,125 @@ function printInteractiveOutputGap(invocation: CliInvocation): void {
|
|
|
1882
2280
|
}
|
|
1883
2281
|
|
|
1884
2282
|
function printPlan(plan: WorkflowPlan): void {
|
|
1885
|
-
console.log(`${plan.workflow}
|
|
2283
|
+
console.log(`${ui.bold(plan.workflow)} ${ui.dim(`${plan.cachedNodeCount}/${plan.nodeCount} cached`)}`);
|
|
2284
|
+
console.log("");
|
|
1886
2285
|
|
|
1887
2286
|
const rows = plan.nodes.map((node) => [
|
|
1888
|
-
String(node.index + 1),
|
|
1889
|
-
node.status,
|
|
1890
|
-
node.path,
|
|
1891
|
-
node.reason ?? "",
|
|
2287
|
+
{ text: String(node.index + 1), style: ui.dim },
|
|
2288
|
+
{ text: node.status, style: planStatusStyle(node.status) },
|
|
2289
|
+
{ text: node.path },
|
|
2290
|
+
{ text: node.reason ?? "", style: ui.dim },
|
|
1892
2291
|
]);
|
|
1893
|
-
|
|
2292
|
+
console.log(ui.columns(["#", "status", "node", "reason"], rows));
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function planStatusStyle(status: string): (text: string) => string {
|
|
2296
|
+
switch (status) {
|
|
2297
|
+
case "cached":
|
|
2298
|
+
case "skipped":
|
|
2299
|
+
return ui.dim;
|
|
2300
|
+
case "pending":
|
|
2301
|
+
return ui.warn;
|
|
2302
|
+
case "completed":
|
|
2303
|
+
case "ready":
|
|
2304
|
+
case "applied":
|
|
2305
|
+
return ui.ok;
|
|
2306
|
+
case "failed":
|
|
2307
|
+
case "error":
|
|
2308
|
+
return ui.err;
|
|
2309
|
+
default:
|
|
2310
|
+
return ui.accent;
|
|
2311
|
+
}
|
|
1894
2312
|
}
|
|
1895
2313
|
|
|
1896
2314
|
function printWorkspaces(
|
|
1897
2315
|
workspaces: ReadonlyArray<Pick<WorkspaceRecord, "name" | "workflow" | "createdAt">>,
|
|
1898
2316
|
): void {
|
|
1899
2317
|
if (workspaces.length === 0) {
|
|
1900
|
-
console.log("
|
|
2318
|
+
console.log(ui.dim("no workspaces"));
|
|
1901
2319
|
return;
|
|
1902
2320
|
}
|
|
1903
2321
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
]),
|
|
1912
|
-
);
|
|
2322
|
+
const rows = workspaces.map((workspace) => [
|
|
2323
|
+
{ text: workspace.name, style: ui.bold },
|
|
2324
|
+
{ text: workspace.workflow },
|
|
2325
|
+
{ text: workspace.createdAt, style: ui.dim },
|
|
2326
|
+
formatWorkspaceAge(workspace.createdAt),
|
|
2327
|
+
]);
|
|
2328
|
+
console.log(ui.columns(["name", "workflow", "created", "age"], rows));
|
|
1913
2329
|
}
|
|
1914
2330
|
|
|
1915
|
-
function formatWorkspaceAge(createdAt: string): string {
|
|
2331
|
+
function formatWorkspaceAge(createdAt: string): { text: string; style: (text: string) => string } {
|
|
1916
2332
|
const createdTime = Date.parse(createdAt);
|
|
1917
|
-
if (Number.isNaN(createdTime)) return
|
|
2333
|
+
if (Number.isNaN(createdTime)) return { text: "unknown", style: ui.dim };
|
|
1918
2334
|
|
|
1919
2335
|
const ageMs = Math.max(0, Date.now() - createdTime);
|
|
1920
2336
|
const minute = 60 * 1000;
|
|
1921
2337
|
const hour = 60 * minute;
|
|
1922
2338
|
const day = 24 * hour;
|
|
1923
|
-
const
|
|
2339
|
+
const text = ageMs < hour
|
|
1924
2340
|
? `${Math.max(1, Math.floor(ageMs / minute))}m`
|
|
1925
2341
|
: ageMs < day
|
|
1926
2342
|
? `${Math.floor(ageMs / hour)}h`
|
|
1927
2343
|
: `${Math.floor(ageMs / day)}d`;
|
|
1928
2344
|
|
|
1929
|
-
if (ageMs < day) return
|
|
1930
|
-
if (ageMs <= 3 * day) return
|
|
1931
|
-
return
|
|
2345
|
+
if (ageMs < day) return { text, style: ui.ok };
|
|
2346
|
+
if (ageMs <= 3 * day) return { text, style: ui.warn };
|
|
2347
|
+
return { text, style: ui.dim };
|
|
1932
2348
|
}
|
|
1933
2349
|
|
|
1934
2350
|
function printSnapshots(snapshots: SnapshotRecord[]): void {
|
|
1935
2351
|
if (snapshots.length === 0) {
|
|
1936
|
-
console.log("
|
|
2352
|
+
console.log(ui.dim("no snapshots"));
|
|
1937
2353
|
return;
|
|
1938
2354
|
}
|
|
1939
2355
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2356
|
+
const rows = snapshots.map((snapshot) => [
|
|
2357
|
+
{ text: snapshot.id, style: ui.dim },
|
|
2358
|
+
{ text: snapshot.workflow },
|
|
2359
|
+
{ text: snapshot.nodePath, style: ui.bold },
|
|
2360
|
+
{ text: typeof snapshot.metadata.snapshotId === "string" ? snapshot.metadata.snapshotId : "" },
|
|
2361
|
+
{ text: snapshot.createdAt, style: ui.dim },
|
|
2362
|
+
]);
|
|
2363
|
+
console.log(ui.columns(["run", "workflow", "node", "snapshot", "created"], rows));
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
function printCacheEntries(entries: ReadonlyArray<{
|
|
2367
|
+
scope: "local" | "global";
|
|
2368
|
+
workflow: string;
|
|
2369
|
+
nodePath: string;
|
|
2370
|
+
nodeName: string;
|
|
2371
|
+
createdAt: string;
|
|
2372
|
+
invalidated: boolean;
|
|
2373
|
+
fragmentHash?: string;
|
|
2374
|
+
}>): void {
|
|
2375
|
+
if (entries.length === 0) {
|
|
2376
|
+
console.log(ui.dim("no cache entries"));
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
const rows = entries.map((entry) => [
|
|
2381
|
+
{ text: entry.scope, style: ui.dim },
|
|
2382
|
+
{ text: entry.workflow },
|
|
2383
|
+
{ text: entry.nodePath || entry.nodeName, style: ui.bold },
|
|
2384
|
+
{
|
|
2385
|
+
text: entry.invalidated ? "invalidated" : "valid",
|
|
2386
|
+
style: entry.invalidated ? ui.warn : ui.ok,
|
|
2387
|
+
},
|
|
2388
|
+
{ text: entry.fragmentHash ? entry.fragmentHash.slice(0, 19) : "", style: ui.dim },
|
|
2389
|
+
{ text: entry.createdAt, style: ui.dim },
|
|
2390
|
+
]);
|
|
2391
|
+
console.log(ui.columns(["scope", "workflow", "node", "status", "fragment", "created"], rows));
|
|
1950
2392
|
}
|
|
1951
2393
|
|
|
1952
2394
|
function printConfig(info: EngineProjectInfo): void {
|
|
1953
|
-
|
|
2395
|
+
console.log(ui.kvList([
|
|
1954
2396
|
["config", info.configPath],
|
|
1955
2397
|
["project", info.projectDir],
|
|
1956
|
-
["state", info.statePath],
|
|
1957
|
-
["workflow", info.workflow?.name ?? "(not loaded)"],
|
|
2398
|
+
["state", info.statePath ?? ""],
|
|
2399
|
+
["workflow", info.workflow?.name ?? ui.dim("(not loaded)")],
|
|
1958
2400
|
["providers", info.workflow?.providers.join(", ") ?? ""],
|
|
1959
|
-
];
|
|
1960
|
-
printTable(["key", "value"], rows);
|
|
2401
|
+
]));
|
|
1961
2402
|
}
|
|
1962
2403
|
|
|
1963
2404
|
function normalizeListTarget(target: string | undefined): "workspaces" | "snapshots" | "config" {
|
|
@@ -1969,62 +2410,85 @@ function normalizeListTarget(target: string | undefined): "workspaces" | "snapsh
|
|
|
1969
2410
|
throw new Error(`Unknown ls target ${target}. Expected workspaces, snapshots, or config.`);
|
|
1970
2411
|
}
|
|
1971
2412
|
|
|
1972
|
-
function printTable(headers: string[], rows: string[][]): void {
|
|
1973
|
-
const widths = headers.map((header, index) =>
|
|
1974
|
-
Math.max(header.length, ...rows.map((row) => String(row[index] ?? "").length)),
|
|
1975
|
-
);
|
|
1976
|
-
const format = (row: string[]) =>
|
|
1977
|
-
row.map((value, index) => String(value ?? "").padEnd(widths[index] ?? 0)).join(" ").trimEnd();
|
|
1978
|
-
|
|
1979
|
-
console.log(format(headers));
|
|
1980
|
-
console.log(format(widths.map((width) => "-".repeat(width))));
|
|
1981
|
-
for (const row of rows) console.log(format(row));
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
2413
|
function renderEvent(event: DevMachineEvent): void {
|
|
2414
|
+
const write = (line: string) => process.stderr.write(`${line}\n`);
|
|
1985
2415
|
switch (event.type) {
|
|
1986
2416
|
case "definition.loaded":
|
|
1987
|
-
|
|
2417
|
+
write(`${ui.dim(ui.sym.dot)} ${ui.dim(`loaded ${event.workflow}`)}`);
|
|
1988
2418
|
return;
|
|
1989
2419
|
case "plan.created":
|
|
1990
|
-
|
|
2420
|
+
write(`${ui.accent(ui.sym.active)} ${ui.bold(event.workflow)} ${ui.dim(`${event.cachedNodeCount}/${event.nodeCount} cached`)}`);
|
|
2421
|
+
return;
|
|
2422
|
+
case "workflow.apply.started":
|
|
2423
|
+
write(`${ui.accent(ui.sym.active)} workflow ${ui.bold(event.workflow)}`);
|
|
1991
2424
|
return;
|
|
2425
|
+
case "workflow.apply.completed": {
|
|
2426
|
+
const summary = event.nodeCount > 0
|
|
2427
|
+
? ` ${ui.dim(`${event.cachedNodeCount}/${event.nodeCount} cached`)}`
|
|
2428
|
+
: "";
|
|
2429
|
+
write(`${ui.ok(ui.sym.ok)} ${ui.bold(event.workflow)} ${ui.dim("prepared")}${summary}`);
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
1992
2432
|
case "node.cached":
|
|
1993
|
-
|
|
2433
|
+
write(` ${ui.dim(ui.sym.ok)} ${ui.dim(`${event.nodePath} cached`)}`);
|
|
1994
2434
|
return;
|
|
1995
2435
|
case "vm.created":
|
|
1996
|
-
|
|
2436
|
+
write(` ${ui.dim(event.fromSnapshotId ? `vm ${event.vmId} from ${event.fromSnapshotId}` : `vm ${event.vmId} created`)}`);
|
|
1997
2437
|
return;
|
|
1998
2438
|
case "node.started":
|
|
1999
|
-
|
|
2439
|
+
write(` ${ui.accent(ui.sym.active)} ${ui.bold(String(event.nodePath))}`);
|
|
2000
2440
|
return;
|
|
2001
2441
|
case "node.completed":
|
|
2002
|
-
|
|
2442
|
+
write(` ${ui.ok(ui.sym.ok)} ${event.nodePath}`);
|
|
2003
2443
|
return;
|
|
2004
2444
|
case "command.started":
|
|
2005
|
-
|
|
2445
|
+
write(` ${ui.dim(`$ ${event.commandName}`)}`);
|
|
2006
2446
|
return;
|
|
2007
2447
|
case "command.output":
|
|
2008
2448
|
process.stderr.write(event.data);
|
|
2009
2449
|
return;
|
|
2010
2450
|
case "command.completed":
|
|
2011
|
-
|
|
2451
|
+
if (event.exitCode !== 0) {
|
|
2452
|
+
write(` ${ui.err(`${event.commandName} exited ${event.exitCode}`)}`);
|
|
2453
|
+
}
|
|
2012
2454
|
return;
|
|
2013
|
-
case "log.output":
|
|
2014
|
-
|
|
2455
|
+
case "log.output": {
|
|
2456
|
+
const prefix = event.stream && event.stream !== "info" && event.stream !== "stdout"
|
|
2457
|
+
? `[${event.stream}] `
|
|
2458
|
+
: "";
|
|
2459
|
+
for (const line of event.data.replace(/\r/g, "").split("\n")) {
|
|
2460
|
+
if (!line) continue;
|
|
2461
|
+
process.stderr.write(`${prefix}${line}\n`);
|
|
2462
|
+
}
|
|
2015
2463
|
return;
|
|
2464
|
+
}
|
|
2016
2465
|
case "interaction.awaiting_user":
|
|
2017
|
-
|
|
2018
|
-
|
|
2466
|
+
write(` ${ui.accent(ui.sym.arrow)} waiting on ${ui.bold(event.label)}`);
|
|
2467
|
+
write(` ${ui.dim(event.url)}`);
|
|
2019
2468
|
return;
|
|
2020
2469
|
case "interaction.completed":
|
|
2021
|
-
|
|
2470
|
+
write(` ${ui.ok(ui.sym.ok)} ${ui.dim(`${event.label} completed`)}`);
|
|
2022
2471
|
return;
|
|
2023
2472
|
case "artifact.created":
|
|
2024
|
-
|
|
2473
|
+
write(` ${ui.dim(`+ ${event.providerId}:${event.kind}`)}`);
|
|
2474
|
+
return;
|
|
2475
|
+
case "workspace.create.started":
|
|
2476
|
+
write(`${ui.accent(ui.sym.active)} creating workspace ${ui.bold(String(event.workspaceName))}`);
|
|
2025
2477
|
return;
|
|
2026
2478
|
case "workspace.ready":
|
|
2027
|
-
|
|
2479
|
+
write(`${ui.ok(ui.sym.ok)} ${ui.bold(String(event.workspaceId))} ${ui.dim("ready")}`);
|
|
2480
|
+
return;
|
|
2481
|
+
case "workspace.remove.started":
|
|
2482
|
+
write(`${ui.accent(ui.sym.active)} removing workspace ${ui.bold(String(event.workspaceName))}`);
|
|
2483
|
+
return;
|
|
2484
|
+
case "workspace.remove.completed":
|
|
2485
|
+
write(`${ui.ok(ui.sym.ok)} removed ${ui.bold(String(event.workspaceName))}`);
|
|
2486
|
+
return;
|
|
2487
|
+
case "workspace.operation.started":
|
|
2488
|
+
write(`${ui.accent(ui.sym.active)} running ${ui.bold(String(event.operationId))} on ${ui.bold(String(event.workspaceName))}`);
|
|
2489
|
+
return;
|
|
2490
|
+
case "workspace.operation.completed":
|
|
2491
|
+
write(`${ui.ok(ui.sym.ok)} ran ${ui.bold(String(event.operationId))} on ${ui.bold(String(event.workspaceName))}`);
|
|
2028
2492
|
return;
|
|
2029
2493
|
default:
|
|
2030
2494
|
return;
|