@rigkit/cli 0.2.10 → 0.2.11
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 +8 -5
- package/package.json +5 -4
- package/src/cli.test.ts +50 -1
- package/src/cli.ts +92 -15
- package/src/completion.test.ts +74 -1
- package/src/completion.ts +83 -9
- package/src/project.test.ts +3 -3
- package/src/project.ts +3 -3
- package/src/version.ts +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rigkit contributors
|
|
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
|
@@ -7,7 +7,7 @@ npm i -g @rigkit/cli
|
|
|
7
7
|
rig init
|
|
8
8
|
rig plan
|
|
9
9
|
rig ls
|
|
10
|
-
rig
|
|
10
|
+
rig --chdir=examples/smoke plan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
`rig init` asks for a project name, Freestyle API key, and package manager. It creates a project folder containing a workflow-based `rig.config.ts`, `.env`, `.env.example`, `package.json`, and local ignore rules.
|
|
@@ -23,12 +23,15 @@ defined by the project run as `rig run <workspace> <operation>`, for example
|
|
|
23
23
|
|
|
24
24
|
`rig ls` lists workspaces for the selected project. `rig ls snapshots` lists cached snapshot runs, and `rig ls config` shows the resolved project paths.
|
|
25
25
|
|
|
26
|
-
Use
|
|
27
|
-
|
|
26
|
+
Use global context options before the command to select another project or
|
|
27
|
+
config:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
rig
|
|
31
|
-
rig
|
|
30
|
+
rig --chdir=examples/smoke plan
|
|
31
|
+
rig --chdir=examples/global-fragments --config=api.rig.config.ts apply
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
Legacy Terraform-style aliases such as `-chdir=...` and `-config=...` are still
|
|
35
|
+
accepted.
|
|
36
|
+
|
|
34
37
|
Projects should install matching `@rigkit/sdk` versions locally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigkit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
"chalk": "^5.6.2",
|
|
23
24
|
"commander": "^14.0.3",
|
|
24
25
|
"inquirer": "^13.4.3",
|
|
25
|
-
"@rigkit/
|
|
26
|
-
"@rigkit/engine": "0.2.
|
|
27
|
-
"@rigkit/
|
|
26
|
+
"@rigkit/runtime-client": "0.2.11",
|
|
27
|
+
"@rigkit/engine": "0.2.11",
|
|
28
|
+
"@rigkit/provider-cmux": "0.2.11"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/bun": "latest",
|
package/src/cli.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { createFileProviderHostStorage } from "@rigkit/engine";
|
|
5
6
|
import { projectIdFor, runtimeFingerprintFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
6
7
|
import { RIGKIT_CLI_VERSION } from "./version.ts";
|
|
7
8
|
|
|
@@ -27,6 +28,7 @@ describe("CLI entrypoint", () => {
|
|
|
27
28
|
expect(rootHelp.stdout).toContain("rm Remove a workspace");
|
|
28
29
|
expect(rootHelp.stdout).toContain("run Run a workspace operation");
|
|
29
30
|
expect(rootHelp.stdout).toContain("cache Inspect and clear Rigkit cache");
|
|
31
|
+
expect(rootHelp.stdout).toContain("providers Manage provider-owned local state");
|
|
30
32
|
|
|
31
33
|
const version = await runCli(["version"]);
|
|
32
34
|
expect(version.exitCode).toBe(0);
|
|
@@ -41,6 +43,7 @@ describe("CLI entrypoint", () => {
|
|
|
41
43
|
expect(help.stdout).toContain("rm Remove a workspace");
|
|
42
44
|
expect(help.stdout).toContain("run Run a workspace operation");
|
|
43
45
|
expect(help.stdout).toContain("cache Inspect and clear Rigkit cache");
|
|
46
|
+
expect(help.stdout).toContain("providers Manage provider-owned local state");
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
test("prints an update notice when latest metadata is newer", async () => {
|
|
@@ -165,7 +168,28 @@ describe("CLI entrypoint", () => {
|
|
|
165
168
|
expect(result.stderr).toContain("Found named Rigkit configs");
|
|
166
169
|
expect(result.stderr).toContain("api.rig.config.ts");
|
|
167
170
|
expect(result.stderr).toContain("web.rig.config.ts");
|
|
168
|
-
expect(result.stderr).toContain("rig
|
|
171
|
+
expect(result.stderr).toContain("rig --chdir=. --config=api.rig.config.ts <command>");
|
|
172
|
+
} finally {
|
|
173
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("accepts conventional double-dash global options", async () => {
|
|
178
|
+
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-global-options-"));
|
|
179
|
+
mkdirSync(join(cwd, "api"));
|
|
180
|
+
writeFileSync(join(cwd, "api", "rig.config.ts"), "export default {}\n");
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const result = await runCli(["--chdir=api", "projects", "--json"], { cwd });
|
|
184
|
+
|
|
185
|
+
expect(result.exitCode).toBe(0);
|
|
186
|
+
expect(result.stderr).toBe("");
|
|
187
|
+
expect(JSON.parse(result.stdout)).toEqual({
|
|
188
|
+
projects: [{
|
|
189
|
+
projectDir: join(realpathSync(cwd), "api"),
|
|
190
|
+
configPath: join(realpathSync(cwd), "api", "rig.config.ts"),
|
|
191
|
+
}],
|
|
192
|
+
});
|
|
169
193
|
} finally {
|
|
170
194
|
rmSync(cwd, { recursive: true, force: true });
|
|
171
195
|
}
|
|
@@ -194,6 +218,31 @@ describe("CLI entrypoint", () => {
|
|
|
194
218
|
}
|
|
195
219
|
});
|
|
196
220
|
|
|
221
|
+
test("clears Freestyle provider host storage without loading a project", async () => {
|
|
222
|
+
const storageRoot = mkdtempSync(join(tmpdir(), "rigkit-provider-storage-"));
|
|
223
|
+
const storage = createFileProviderHostStorage({ providerId: "freestyle", rootDir: storageRoot });
|
|
224
|
+
storage.set("stack-auth:test", { refreshToken: "refresh-token", updatedAt: 1 });
|
|
225
|
+
storage.set("identity:test", { token: "ssh-token" });
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const result = await runCli(["providers", "freestyle", "clear", "--json"], {
|
|
229
|
+
env: { RIGKIT_HOST_STORAGE_DIR: storageRoot },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(result.exitCode).toBe(0);
|
|
233
|
+
expect(result.stderr).toBe("");
|
|
234
|
+
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
235
|
+
ok: true,
|
|
236
|
+
providerId: "freestyle",
|
|
237
|
+
deleted: 2,
|
|
238
|
+
storageRoot,
|
|
239
|
+
});
|
|
240
|
+
expect(storage.entries()).toEqual([]);
|
|
241
|
+
} finally {
|
|
242
|
+
rmSync(storageRoot, { recursive: true, force: true });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
197
246
|
test("does not render a success marker when cache invalidation is a no-op", async () => {
|
|
198
247
|
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-cache-invalidate-"));
|
|
199
248
|
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
type RuntimeClient,
|
|
11
11
|
} from "@rigkit/runtime-client";
|
|
12
12
|
import {
|
|
13
|
+
createFileProviderHostStorage,
|
|
14
|
+
defaultProviderHostStorageDir,
|
|
13
15
|
type DevMachineEvent,
|
|
14
16
|
type WorkflowPlan,
|
|
15
17
|
type SnapshotRecord,
|
|
@@ -162,6 +164,7 @@ const STATIC_COMMANDS = new Set([
|
|
|
162
164
|
"run",
|
|
163
165
|
"ls",
|
|
164
166
|
"cache",
|
|
167
|
+
"providers",
|
|
165
168
|
"projects",
|
|
166
169
|
"doctor",
|
|
167
170
|
"version",
|
|
@@ -205,16 +208,18 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
205
208
|
.exitOverride()
|
|
206
209
|
.argument("[command]")
|
|
207
210
|
.addOption(new Option("--chdir <dir>", `Switch to a directory containing ${DEFAULT_CONFIG_FILE} before running the command`).hideHelp())
|
|
208
|
-
.addOption(new Option("--config <file>", "Config file to load, relative to
|
|
211
|
+
.addOption(new Option("--config <file>", "Config file to load, relative to --chdir when set").hideHelp())
|
|
209
212
|
.addOption(new Option("--state <file>", "Local runtime state database path").hideHelp())
|
|
210
213
|
.addOption(new Option("--json", "Print machine-readable JSON where supported").hideHelp())
|
|
211
214
|
.addHelpText("after", [
|
|
212
215
|
"",
|
|
213
216
|
"Global Options:",
|
|
214
|
-
"
|
|
215
|
-
"
|
|
216
|
-
"
|
|
217
|
-
"
|
|
217
|
+
" --chdir <dir> Switch to a directory containing rig.config.ts before running the command",
|
|
218
|
+
" --config <file> Config file to load, relative to --chdir when set",
|
|
219
|
+
" --state <file> Local runtime state database path",
|
|
220
|
+
" --json Print machine-readable JSON where supported",
|
|
221
|
+
"",
|
|
222
|
+
"Legacy single-dash global aliases such as -chdir=DIR are still accepted.",
|
|
218
223
|
].join("\n"))
|
|
219
224
|
.action(async (command?: string) => {
|
|
220
225
|
if (command) program.error(`unknown command '${command}'`);
|
|
@@ -360,6 +365,22 @@ async function runCli(argv: string[]): Promise<void> {
|
|
|
360
365
|
});
|
|
361
366
|
});
|
|
362
367
|
|
|
368
|
+
const providers = program
|
|
369
|
+
.command("providers")
|
|
370
|
+
.description("Manage provider-owned local state");
|
|
371
|
+
|
|
372
|
+
const freestyleProvider = providers
|
|
373
|
+
.command("freestyle")
|
|
374
|
+
.description("Manage Freestyle provider local state");
|
|
375
|
+
|
|
376
|
+
freestyleProvider
|
|
377
|
+
.command("clear")
|
|
378
|
+
.description("Clear Freestyle provider local auth and identity state")
|
|
379
|
+
.option("--json", "Print machine-readable JSON")
|
|
380
|
+
.action(async (options: { json?: boolean }) => {
|
|
381
|
+
await runProvidersFreestyleClear(makeInvocation(rootOptions(program), options.json));
|
|
382
|
+
});
|
|
383
|
+
|
|
363
384
|
program
|
|
364
385
|
.command("projects")
|
|
365
386
|
.description("Discover Rigkit projects below the current directory")
|
|
@@ -574,7 +595,7 @@ function canPrompt(): boolean {
|
|
|
574
595
|
function resolveInitProjectPaths(invocation: CliInvocation, name: string): { projectDir: string; configPath: string } {
|
|
575
596
|
const options = invocation.global;
|
|
576
597
|
if (options.config) {
|
|
577
|
-
throw new Error(`rig init does not support
|
|
598
|
+
throw new Error(`rig init does not support --config. Use --chdir to choose the parent directory.`);
|
|
578
599
|
}
|
|
579
600
|
|
|
580
601
|
const parentDir = resolve(process.cwd(), options.chdir ?? ".");
|
|
@@ -964,7 +985,7 @@ async function runDiscoveredProjectOperation(
|
|
|
964
985
|
if (!options.all && projects.length > 1) {
|
|
965
986
|
throw new Error([
|
|
966
987
|
"Multiple Rigkit projects found.",
|
|
967
|
-
"Use `rig projects` to list candidates, pass
|
|
988
|
+
"Use `rig projects` to list candidates, pass --chdir or --config to select one, or pass --all to run every discovered project.",
|
|
968
989
|
...projects.map((project) => `- ${project.configPath}`),
|
|
969
990
|
].join("\n"));
|
|
970
991
|
}
|
|
@@ -1082,7 +1103,7 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
|
|
|
1082
1103
|
|
|
1083
1104
|
if (options.global && options.all) {
|
|
1084
1105
|
if (invocation.global.chdir || invocation.global.config || invocation.global.state) {
|
|
1085
|
-
throw new Error(`rig cache clear --global --all cannot be combined with
|
|
1106
|
+
throw new Error(`rig cache clear --global --all cannot be combined with --chdir, --config, or --state`);
|
|
1086
1107
|
}
|
|
1087
1108
|
const fragmentRoot = join(defaultRigkitHome(), "fragments");
|
|
1088
1109
|
rmSync(fragmentRoot, { recursive: true, force: true });
|
|
@@ -1104,6 +1125,32 @@ async function runCacheClear(invocation: CliInvocation, options: CacheClearOptio
|
|
|
1104
1125
|
console.log(`Cleared ${result.deleted} cache ${result.deleted === 1 ? "entry" : "entries"}.`);
|
|
1105
1126
|
}
|
|
1106
1127
|
|
|
1128
|
+
async function runProvidersFreestyleClear(invocation: CliInvocation): Promise<void> {
|
|
1129
|
+
const providerId = "freestyle";
|
|
1130
|
+
const storageRoot = defaultProviderHostStorageDir();
|
|
1131
|
+
const storage = createFileProviderHostStorage({ providerId, rootDir: storageRoot });
|
|
1132
|
+
const keys = storage.entries().map((entry) => entry.key);
|
|
1133
|
+
for (const key of keys) storage.delete(key);
|
|
1134
|
+
|
|
1135
|
+
const result = {
|
|
1136
|
+
ok: true,
|
|
1137
|
+
providerId,
|
|
1138
|
+
deleted: keys.length,
|
|
1139
|
+
storageRoot,
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
if (wantsJson(invocation)) {
|
|
1143
|
+
printJson(result);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (keys.length === 0) {
|
|
1148
|
+
console.log("No Freestyle provider local state to clear.");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
console.log(`Cleared ${keys.length} Freestyle provider ${keys.length === 1 ? "entry" : "entries"}.`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1107
1154
|
type CacheInvalidateOptions = {
|
|
1108
1155
|
step?: string;
|
|
1109
1156
|
all: boolean;
|
|
@@ -1524,6 +1571,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
|
|
|
1524
1571
|
{ name: "run", description: "Run a workspace operation" },
|
|
1525
1572
|
{ name: "ls", description: "List project workspaces" },
|
|
1526
1573
|
{ name: "cache", description: "Inspect and clear Rigkit cache" },
|
|
1574
|
+
{ name: "providers", description: "Manage provider-owned local state" },
|
|
1527
1575
|
{ name: "projects", description: "Discover Rigkit projects below the current directory" },
|
|
1528
1576
|
{ name: "doctor", description: "Show Rigkit runtime diagnostics" },
|
|
1529
1577
|
{ name: "version", description: "Show Rigkit CLI version" },
|
|
@@ -1535,7 +1583,7 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
|
|
|
1535
1583
|
const cmd = (name: string, description: string): string =>
|
|
1536
1584
|
` ${ui.bold(name.padEnd(10))} ${description}`;
|
|
1537
1585
|
const opt = (flag: string, description: string): string =>
|
|
1538
|
-
` ${ui.bold(flag.padEnd(
|
|
1586
|
+
` ${ui.bold(flag.padEnd(17))} ${description}`;
|
|
1539
1587
|
|
|
1540
1588
|
console.log([
|
|
1541
1589
|
`${ui.bold("rig")} ${ui.dim(RIGKIT_CLI_VERSION)}`,
|
|
@@ -1553,16 +1601,19 @@ async function runHelp(invocation: CliInvocation): Promise<void> {
|
|
|
1553
1601
|
cmd("run", "Run a workspace operation"),
|
|
1554
1602
|
cmd("ls", "List project workspaces"),
|
|
1555
1603
|
cmd("cache", "Inspect and clear Rigkit cache"),
|
|
1604
|
+
cmd("providers", "Manage provider-owned local state"),
|
|
1556
1605
|
cmd("projects", "Discover Rigkit projects below the current directory"),
|
|
1557
1606
|
cmd("doctor", "Show Rigkit runtime diagnostics"),
|
|
1558
1607
|
cmd("version", "Show Rigkit CLI version"),
|
|
1559
1608
|
cmd("completion", "Generate shell completion script"),
|
|
1560
1609
|
"",
|
|
1561
1610
|
ui.dim("Options:"),
|
|
1562
|
-
opt("
|
|
1563
|
-
opt("
|
|
1564
|
-
opt("
|
|
1565
|
-
opt("
|
|
1611
|
+
opt("--chdir <dir>", "Switch to a directory containing rig.config.ts before running the command"),
|
|
1612
|
+
opt("--config <file>", "Config file to load, relative to --chdir when set"),
|
|
1613
|
+
opt("--state <file>", "Local runtime state database path"),
|
|
1614
|
+
opt("--json", "Print machine-readable JSON where supported"),
|
|
1615
|
+
"",
|
|
1616
|
+
ui.dim("Legacy single-dash global aliases such as -chdir=DIR are still accepted."),
|
|
1566
1617
|
].join("\n"));
|
|
1567
1618
|
}
|
|
1568
1619
|
|
|
@@ -2116,8 +2167,12 @@ async function promptHostSelect(params: unknown): Promise<string> {
|
|
|
2116
2167
|
.filter((item) => item.value)
|
|
2117
2168
|
: [];
|
|
2118
2169
|
if (options.length === 0) throw new Error(`Host select prompt has no options`);
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2170
|
+
const configuredDefaultValue = stringField(params, "defaultValue");
|
|
2171
|
+
const defaultValue = configuredDefaultValue ?? options[0]!.value;
|
|
2172
|
+
if (!canPrompt()) {
|
|
2173
|
+
if (configuredDefaultValue !== undefined) return configuredDefaultValue;
|
|
2174
|
+
throw new Error(`Host select prompt requires an interactive terminal: ${message}`);
|
|
2175
|
+
}
|
|
2121
2176
|
const answers = await inquirer.prompt<{ value: string }>([{
|
|
2122
2177
|
type: "select",
|
|
2123
2178
|
name: "value",
|
|
@@ -2301,6 +2356,16 @@ function printPlan(plan: WorkflowPlan): void {
|
|
|
2301
2356
|
console.log(`${ui.bold(plan.workflow)} ${ui.dim(`${plan.cachedNodeCount}/${plan.nodeCount} cached`)}`);
|
|
2302
2357
|
console.log("");
|
|
2303
2358
|
|
|
2359
|
+
if (plan.providerChecks?.length) {
|
|
2360
|
+
const rows = plan.providerChecks.map((check) => [
|
|
2361
|
+
{ text: check.label || check.providerName, style: ui.dim },
|
|
2362
|
+
{ text: check.status, style: providerCheckStatusStyle(check.status) },
|
|
2363
|
+
{ text: providerCheckValue(check), style: check.status === "ok" ? ((value: string) => value) : ui.warn },
|
|
2364
|
+
]);
|
|
2365
|
+
console.log(ui.columns(["provider check", "status", "current"], rows));
|
|
2366
|
+
console.log("");
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2304
2369
|
const rows = plan.nodes.map((node) => [
|
|
2305
2370
|
{ text: String(node.index + 1), style: ui.dim },
|
|
2306
2371
|
{ text: node.status, style: planStatusStyle(node.status) },
|
|
@@ -2310,6 +2375,18 @@ function printPlan(plan: WorkflowPlan): void {
|
|
|
2310
2375
|
console.log(ui.columns(["#", "status", "node", "reason"], rows));
|
|
2311
2376
|
}
|
|
2312
2377
|
|
|
2378
|
+
function providerCheckValue(check: NonNullable<WorkflowPlan["providerChecks"]>[number]): string {
|
|
2379
|
+
if (check.status === "required" && check.message) return check.message;
|
|
2380
|
+
if (check.detail && check.detail !== check.value && !check.value.includes(check.detail)) {
|
|
2381
|
+
return `${check.value} ${ui.dim(check.detail)}`;
|
|
2382
|
+
}
|
|
2383
|
+
return check.value;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
function providerCheckStatusStyle(status: string): (text: string) => string {
|
|
2387
|
+
return status === "ok" ? ui.ok : ui.warn;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2313
2390
|
function planStatusStyle(status: string): (text: string) => string {
|
|
2314
2391
|
switch (status) {
|
|
2315
2392
|
case "cached":
|
package/src/completion.test.ts
CHANGED
|
@@ -82,6 +82,45 @@ describe("CLI completion", () => {
|
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
test("suggests conventional double-dash global flags by default", async () => {
|
|
86
|
+
const items = await completeRig({
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
words: ["rig", "--"],
|
|
89
|
+
currentIndex: 1,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(items.map((item) => item.value)).toEqual([
|
|
93
|
+
"--chdir=",
|
|
94
|
+
"--config=",
|
|
95
|
+
"--state=",
|
|
96
|
+
"--json",
|
|
97
|
+
"--help",
|
|
98
|
+
"--version",
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("completes project directories for --chdir", async () => {
|
|
103
|
+
const cwd = mkdtempSync(join(tmpdir(), "rigkit-completion-dirs-"));
|
|
104
|
+
mkdirSync(join(cwd, "examples", "global-fragments"), { recursive: true });
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const roots = await completeRig({
|
|
108
|
+
cwd,
|
|
109
|
+
words: ["rig", "--chdir="],
|
|
110
|
+
currentIndex: 1,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(roots).toContainEqual({
|
|
114
|
+
value: "--chdir=examples/",
|
|
115
|
+
description: "directory",
|
|
116
|
+
noSpace: true,
|
|
117
|
+
group: "Paths",
|
|
118
|
+
});
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
85
124
|
test("completes named config files", async () => {
|
|
86
125
|
const cwd = mkdtempSync(join(tmpdir(), "rigkit-completion-configs-"));
|
|
87
126
|
writeFileSync(join(cwd, "api.rig.config.ts"), "export default {}\n");
|
|
@@ -183,7 +222,7 @@ describe("CLI completion", () => {
|
|
|
183
222
|
currentIndex: 1,
|
|
184
223
|
});
|
|
185
224
|
|
|
186
|
-
expect(items.map((item) => item.value)).toEqual(["plan", "projects"]);
|
|
225
|
+
expect(items.map((item) => item.value)).toEqual(["plan", "providers", "projects"]);
|
|
187
226
|
});
|
|
188
227
|
});
|
|
189
228
|
|
|
@@ -221,6 +260,40 @@ describe("CLI completion", () => {
|
|
|
221
260
|
]);
|
|
222
261
|
});
|
|
223
262
|
|
|
263
|
+
test("completes provider targets, subcommands, and flags", async () => {
|
|
264
|
+
const targets = await completeRig({
|
|
265
|
+
cwd: process.cwd(),
|
|
266
|
+
words: ["rig", "providers", ""],
|
|
267
|
+
currentIndex: 2,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(targets.map((item) => item.value)).toEqual(["freestyle"]);
|
|
271
|
+
|
|
272
|
+
const targetFlags = await completeRig({
|
|
273
|
+
cwd: process.cwd(),
|
|
274
|
+
words: ["rig", "providers", "--"],
|
|
275
|
+
currentIndex: 2,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(targetFlags.map((item) => item.value)).toEqual(["--help"]);
|
|
279
|
+
|
|
280
|
+
const subcommands = await completeRig({
|
|
281
|
+
cwd: process.cwd(),
|
|
282
|
+
words: ["rig", "providers", "freestyle", ""],
|
|
283
|
+
currentIndex: 3,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(subcommands.map((item) => item.value)).toEqual(["clear"]);
|
|
287
|
+
|
|
288
|
+
const clearFlags = await completeRig({
|
|
289
|
+
cwd: process.cwd(),
|
|
290
|
+
words: ["rig", "providers", "freestyle", "clear", "--"],
|
|
291
|
+
currentIndex: 4,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(clearFlags.map((item) => item.value)).toEqual(["--json", "--help"]);
|
|
295
|
+
});
|
|
296
|
+
|
|
224
297
|
test("completes project operation flags and workflow values", async () => {
|
|
225
298
|
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-completion-"));
|
|
226
299
|
await withWorkspaceRuntime({ projectDir }, async () => {
|
package/src/completion.ts
CHANGED
|
@@ -28,6 +28,7 @@ type CommandName =
|
|
|
28
28
|
| "run"
|
|
29
29
|
| "ls"
|
|
30
30
|
| "cache"
|
|
31
|
+
| "providers"
|
|
31
32
|
| "projects"
|
|
32
33
|
| "doctor"
|
|
33
34
|
| "version"
|
|
@@ -126,6 +127,7 @@ const GROUP_VALUES = "Values";
|
|
|
126
127
|
const GROUP_PATHS = "Paths";
|
|
127
128
|
const GROUP_SHELLS = "Shells";
|
|
128
129
|
const GROUP_CACHE = "Cache entries";
|
|
130
|
+
const GROUP_PROVIDERS = "Providers";
|
|
129
131
|
|
|
130
132
|
const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
131
133
|
{ value: "help", description: "show CLI help" },
|
|
@@ -137,6 +139,7 @@ const COMMANDS: CompletionItem[] = withGroup(GROUP_COMMANDS, [
|
|
|
137
139
|
{ value: "run", description: "run a workspace operation" },
|
|
138
140
|
{ value: "ls", description: "list project workspaces" },
|
|
139
141
|
{ value: "cache", description: "inspect and clear Rigkit cache" },
|
|
142
|
+
{ value: "providers", description: "manage provider-owned local state" },
|
|
140
143
|
{ value: "projects", description: "discover Rigkit projects" },
|
|
141
144
|
{ value: "doctor", description: "show runtime diagnostics" },
|
|
142
145
|
{ value: "version", description: "show CLI version" },
|
|
@@ -149,36 +152,42 @@ const JSON_OPTION = option(["--json"], "print JSON");
|
|
|
149
152
|
const HELP_OPTION = option(["--help"], "show help");
|
|
150
153
|
|
|
151
154
|
const GLOBAL_OPTIONS: OptionDefinition[] = [
|
|
152
|
-
option(["
|
|
155
|
+
option(["--chdir", "-chdir"], "working directory", {
|
|
153
156
|
group: GROUP_GLOBAL,
|
|
154
157
|
takesValue: true,
|
|
155
158
|
valueKind: "directories",
|
|
156
159
|
completions: [
|
|
157
|
-
{ value: "-chdir=", noSpace: true },
|
|
158
160
|
{ value: "--chdir=", noSpace: true },
|
|
159
161
|
],
|
|
160
162
|
}),
|
|
161
|
-
option(["
|
|
163
|
+
option(["--config", "-config"], "config file", {
|
|
162
164
|
group: GROUP_GLOBAL,
|
|
163
165
|
takesValue: true,
|
|
164
166
|
valueKind: "config-files",
|
|
165
167
|
completions: [
|
|
166
|
-
{ value: "-config=", noSpace: true },
|
|
167
168
|
{ value: "--config=", noSpace: true },
|
|
168
169
|
],
|
|
169
170
|
}),
|
|
170
|
-
option(["
|
|
171
|
+
option(["--state", "-state"], "state database path", {
|
|
171
172
|
group: GROUP_GLOBAL,
|
|
172
173
|
takesValue: true,
|
|
173
174
|
valueKind: "filesystem",
|
|
174
175
|
completions: [
|
|
175
|
-
{ value: "-state=", noSpace: true },
|
|
176
176
|
{ value: "--state=", noSpace: true },
|
|
177
177
|
],
|
|
178
178
|
}),
|
|
179
|
-
option(["
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
option(["--json", "-json"], "print JSON", {
|
|
180
|
+
group: GROUP_GLOBAL,
|
|
181
|
+
completions: [{ value: "--json" }],
|
|
182
|
+
}),
|
|
183
|
+
option(["--help", "-help"], "show help", {
|
|
184
|
+
group: GROUP_GLOBAL,
|
|
185
|
+
completions: [{ value: "--help" }],
|
|
186
|
+
}),
|
|
187
|
+
option(["--version", "-version", "-v"], "show version", {
|
|
188
|
+
group: GROUP_GLOBAL,
|
|
189
|
+
completions: [{ value: "--version" }, { value: "-v" }],
|
|
190
|
+
}),
|
|
182
191
|
];
|
|
183
192
|
|
|
184
193
|
const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
|
|
@@ -226,6 +235,9 @@ const COMMAND_OPTIONS: Record<CommandName, OptionDefinition[]> = {
|
|
|
226
235
|
cache: [
|
|
227
236
|
HELP_OPTION,
|
|
228
237
|
],
|
|
238
|
+
providers: [
|
|
239
|
+
HELP_OPTION,
|
|
240
|
+
],
|
|
229
241
|
projects: [
|
|
230
242
|
JSON_OPTION,
|
|
231
243
|
HELP_OPTION,
|
|
@@ -294,6 +306,31 @@ const CACHE_SUBCOMMAND_OPTIONS: Record<string, OptionDefinition[]> = {
|
|
|
294
306
|
],
|
|
295
307
|
};
|
|
296
308
|
|
|
309
|
+
const PROVIDER_TARGETS: CompletionItem[] = withGroup(GROUP_PROVIDERS, [
|
|
310
|
+
{ value: "freestyle", description: "Freestyle provider state" },
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
const PROVIDER_SUBCOMMANDS: Record<string, CompletionItem[]> = {
|
|
314
|
+
freestyle: withGroup(GROUP_SUBCOMMANDS, [
|
|
315
|
+
{ value: "clear", description: "clear Freestyle provider local auth and identity state" },
|
|
316
|
+
]),
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const PROVIDER_TARGET_OPTIONS: Record<string, OptionDefinition[]> = {
|
|
320
|
+
freestyle: [
|
|
321
|
+
HELP_OPTION,
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const PROVIDER_SUBCOMMAND_OPTIONS: Record<string, Record<string, OptionDefinition[]>> = {
|
|
326
|
+
freestyle: {
|
|
327
|
+
clear: [
|
|
328
|
+
JSON_OPTION,
|
|
329
|
+
HELP_OPTION,
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
297
334
|
const COMPLETION_SHELLS: CompletionItem[] = withGroup(GROUP_SHELLS, [
|
|
298
335
|
{ value: "bash", description: "Bash completion" },
|
|
299
336
|
{ value: "fish", description: "fish completion" },
|
|
@@ -471,6 +508,14 @@ async function optionsForCommandContext(context: CompletionContext): Promise<Opt
|
|
|
471
508
|
if (cache.subcommand) return CACHE_SUBCOMMAND_OPTIONS[cache.subcommand] ?? [HELP_OPTION];
|
|
472
509
|
}
|
|
473
510
|
|
|
511
|
+
if (context.command === "providers") {
|
|
512
|
+
const providers = parseProvidersArgs(context);
|
|
513
|
+
if (providers.provider && providers.subcommand) {
|
|
514
|
+
return PROVIDER_SUBCOMMAND_OPTIONS[providers.provider]?.[providers.subcommand] ?? [HELP_OPTION];
|
|
515
|
+
}
|
|
516
|
+
if (providers.provider) return PROVIDER_TARGET_OPTIONS[providers.provider] ?? [HELP_OPTION];
|
|
517
|
+
}
|
|
518
|
+
|
|
474
519
|
return COMMAND_OPTIONS[context.command] ?? [];
|
|
475
520
|
}
|
|
476
521
|
|
|
@@ -553,6 +598,8 @@ async function completeCommand(context: CompletionContext): Promise<CompletionIt
|
|
|
553
598
|
return completeLsCommand(context);
|
|
554
599
|
case "cache":
|
|
555
600
|
return await completeCacheCommand(context);
|
|
601
|
+
case "providers":
|
|
602
|
+
return completeProvidersCommand(context);
|
|
556
603
|
case "completion":
|
|
557
604
|
return completeCompletionCommand(context);
|
|
558
605
|
case "init":
|
|
@@ -690,6 +737,24 @@ async function completeCacheCommand(context: CompletionContext): Promise<Complet
|
|
|
690
737
|
return [];
|
|
691
738
|
}
|
|
692
739
|
|
|
740
|
+
function completeProvidersCommand(context: CompletionContext): CompletionItem[] {
|
|
741
|
+
const providers = parseProvidersArgs(context);
|
|
742
|
+
if (!providers.provider) {
|
|
743
|
+
if (context.current.startsWith("-")) return filterItems(optionItems(COMMAND_OPTIONS.providers), context.current);
|
|
744
|
+
return filterItems(PROVIDER_TARGETS, context.current);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (!providers.subcommand) {
|
|
748
|
+
const options = PROVIDER_TARGET_OPTIONS[providers.provider] ?? [HELP_OPTION];
|
|
749
|
+
const subcommands = PROVIDER_SUBCOMMANDS[providers.provider] ?? [];
|
|
750
|
+
if (context.current.startsWith("-")) return filterItems(optionItems(options), context.current);
|
|
751
|
+
return filterItems(subcommands, context.current);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const options = PROVIDER_SUBCOMMAND_OPTIONS[providers.provider]?.[providers.subcommand] ?? [HELP_OPTION];
|
|
755
|
+
return completeOptionsOnlyCommand(context, options);
|
|
756
|
+
}
|
|
757
|
+
|
|
693
758
|
function completeCompletionCommand(context: CompletionContext): CompletionItem[] {
|
|
694
759
|
const shells = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.completion);
|
|
695
760
|
if (shells.length === 0) {
|
|
@@ -861,6 +926,15 @@ function parseCacheArgs(context: CompletionContext): { subcommand?: string; args
|
|
|
861
926
|
};
|
|
862
927
|
}
|
|
863
928
|
|
|
929
|
+
function parseProvidersArgs(context: CompletionContext): { provider?: string; subcommand?: string; args: string[] } {
|
|
930
|
+
const positionals = positionalsFrom(context.argsBefore, COMMAND_OPTIONS.providers);
|
|
931
|
+
return {
|
|
932
|
+
provider: positionals[0],
|
|
933
|
+
subcommand: positionals[1],
|
|
934
|
+
args: positionals.slice(2),
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
864
938
|
function positionalsFrom(tokens: string[], options: OptionDefinition[]): string[] {
|
|
865
939
|
return positionalTokensFrom(tokens, options).map((token) => token.value);
|
|
866
940
|
}
|
package/src/project.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { discoverProjectConfigs, resolveConfigPaths } from "./project.ts";
|
|
6
6
|
|
|
7
7
|
describe("CLI project resolution", () => {
|
|
8
|
-
test("resolves
|
|
8
|
+
test("resolves --chdir to that directory's rig.config.ts", () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-"));
|
|
10
10
|
mkdirSync(join(cwd, "example"));
|
|
11
11
|
writeFileSync(join(cwd, "example", "rig.config.ts"), "export default {}\n");
|
|
@@ -15,7 +15,7 @@ describe("CLI project resolution", () => {
|
|
|
15
15
|
expect(paths.configPath).toBe(join(cwd, "example", "rig.config.ts"));
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
test("resolves
|
|
18
|
+
test("resolves --config project root from the config dirname", () => {
|
|
19
19
|
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-"));
|
|
20
20
|
const paths = resolveConfigPaths({ cwd, config: "machines/platform.ts" });
|
|
21
21
|
|
|
@@ -40,7 +40,7 @@ describe("CLI project resolution", () => {
|
|
|
40
40
|
writeFileSync(join(cwd, "web.rig.config.ts"), "export default {}\n");
|
|
41
41
|
|
|
42
42
|
expect(() => resolveConfigPaths({ cwd })).toThrow(
|
|
43
|
-
/Found named Rigkit configs[\s\S]*api\.rig\.config\.ts[\s\S]*web\.rig\.config\.ts[\s\S]*rig
|
|
43
|
+
/Found named Rigkit configs[\s\S]*api\.rig\.config\.ts[\s\S]*web\.rig\.config\.ts[\s\S]*rig --chdir=\. --config=api\.rig\.config\.ts <command>/,
|
|
44
44
|
);
|
|
45
45
|
});
|
|
46
46
|
|
package/src/project.ts
CHANGED
|
@@ -159,7 +159,7 @@ function appendConfigFilesHint(
|
|
|
159
159
|
options: { commandCwd: string; hint?: ConfigFilesHint },
|
|
160
160
|
): string {
|
|
161
161
|
const hint = options.hint;
|
|
162
|
-
if (!hint) return `${message} Run "rig init" or pass
|
|
162
|
+
if (!hint) return `${message} Run "rig init" or pass --config=<file>.`;
|
|
163
163
|
|
|
164
164
|
const configFile = hint.files[0]!;
|
|
165
165
|
const configPath = displayPath(options.commandCwd, join(hint.dir, configFile));
|
|
@@ -171,8 +171,8 @@ function appendConfigFilesHint(
|
|
|
171
171
|
...hint.files.map((file) => `- ${file}`),
|
|
172
172
|
"",
|
|
173
173
|
"Choose one explicitly:",
|
|
174
|
-
` rig
|
|
175
|
-
` rig
|
|
174
|
+
` rig --config=${configPath} <command>`,
|
|
175
|
+
` rig --chdir=${projectDir} --config=${configFile} <command>`,
|
|
176
176
|
].join("\n");
|
|
177
177
|
}
|
|
178
178
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RIGKIT_CLI_VERSION = "0.2.
|
|
1
|
+
export const RIGKIT_CLI_VERSION = "0.2.11";
|