@ultimat3/cli 21.0.0 → 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +84 -1650
- package/README.md +40 -2
- package/package.json +30 -30
- package/src/api-registration.ts +124 -0
- package/src/app-artifacts.ts +39 -0
- package/src/app-boundaries.ts +5 -10
- package/src/app-load.ts +37 -5
- package/src/app-openapi.ts +27 -0
- package/src/app-permissions.ts +0 -0
- package/src/app-root.ts +1 -1
- package/src/async-pages.ts +32 -0
- package/src/bin.ts +7 -1
- package/src/boundary-findings.ts +36 -0
- package/src/browser-launcher-fake-html.ts +80 -0
- package/src/browser-launcher-fake.ts +165 -0
- package/src/browser-launcher-port.ts +159 -0
- package/src/browser-launcher.ts +26 -133
- package/src/budgets.ts +11 -1
- package/src/cdp-shot-a11y.ts +77 -0
- package/src/cdp-shot-clock.ts +14 -0
- package/src/cdp-shot-driver.ts +150 -0
- package/src/cdp-shot-element.ts +147 -0
- package/src/cdp-shot-errors.ts +62 -0
- package/src/cdp-shot-keys.ts +152 -0
- package/src/cdp-shot-page.ts +230 -0
- package/src/cdp-shot-watch.ts +241 -0
- package/src/cmd-affected-spec.ts +24 -0
- package/src/cmd-affected.ts +3 -19
- package/src/cmd-build-spec.ts +16 -0
- package/src/cmd-build.ts +46 -20
- package/src/cmd-ci-spec.ts +24 -0
- package/src/cmd-ci.ts +17 -21
- package/src/cmd-db-branch.ts +2 -2
- package/src/cmd-db-spec.ts +99 -0
- package/src/cmd-db.ts +15 -94
- package/src/cmd-deploy-helm.ts +136 -0
- package/src/cmd-deploy-spec.ts +40 -0
- package/src/cmd-deploy.ts +111 -43
- package/src/cmd-dev-spec.ts +25 -0
- package/src/cmd-dev.ts +55 -36
- package/src/cmd-docs-spec.ts +16 -0
- package/src/cmd-docs.ts +2 -11
- package/src/cmd-doctor-spec.ts +21 -0
- package/src/cmd-doctor.ts +4 -18
- package/src/cmd-env-spec.ts +18 -0
- package/src/cmd-env.ts +4 -13
- package/src/cmd-errors-spec.ts +23 -0
- package/src/cmd-errors.ts +3 -17
- package/src/cmd-fix-spec.ts +18 -0
- package/src/cmd-fix.ts +4 -13
- package/src/cmd-generate-spec.ts +31 -0
- package/src/cmd-generate.ts +46 -42
- package/src/cmd-i18n-spec.ts +16 -0
- package/src/cmd-i18n.ts +7 -14
- package/src/cmd-jobs-spec.ts +56 -0
- package/src/cmd-jobs.ts +3 -50
- package/src/cmd-manifest-spec.ts +15 -0
- package/src/cmd-manifest.ts +19 -20
- package/src/cmd-mcp-spec.ts +21 -0
- package/src/cmd-mcp.ts +2 -15
- package/src/cmd-new-spec.ts +33 -0
- package/src/cmd-new.ts +38 -33
- package/src/cmd-policy-spec.ts +13 -0
- package/src/cmd-policy.ts +2 -9
- package/src/cmd-pr-spec.ts +39 -0
- package/src/cmd-pr.ts +5 -65
- package/src/cmd-registries-spec.ts +32 -0
- package/src/cmd-registries.ts +4 -25
- package/src/cmd-routes-spec.ts +12 -0
- package/src/cmd-routes.ts +2 -7
- package/src/cmd-secrets-spec.ts +19 -0
- package/src/cmd-secrets.ts +22 -34
- package/src/cmd-shot-island.ts +12 -13
- package/src/cmd-shot-spec.ts +53 -0
- package/src/cmd-shot.ts +11 -60
- package/src/cmd-tasks-spec.ts +21 -0
- package/src/cmd-tasks.ts +2 -16
- package/src/cmd-test-spec.ts +54 -0
- package/src/cmd-test.ts +4 -49
- package/src/cmd-verify-spec.ts +28 -0
- package/src/cmd-verify.ts +3 -23
- package/src/db-seed.ts +4 -2
- package/src/dev-dashboard.ts +4 -4
- package/src/dev-lock.ts +18 -2
- package/src/dev-port.ts +21 -0
- package/src/dev-route-table.ts +3 -3
- package/src/dispatch.ts +12 -5
- package/src/document-styles.ts +1 -1
- package/src/drift.ts +4 -18
- package/src/error-catalog.ts +16 -9
- package/src/error-codes.ts +28 -30
- package/src/error-pages.ts +19 -7
- package/src/errors.ts +14 -0
- package/src/favicon.ts +2 -2
- package/src/fix-imports.ts +1 -1
- package/src/fix-scan.ts +2 -9
- package/src/flag-reads.ts +1 -2
- package/src/foreign-text.ts +36 -0
- package/src/framework-schema.ts +5 -6
- package/src/generate-feature.ts +42 -0
- package/src/generate-files.ts +7 -3
- package/src/generate-grants.ts +83 -0
- package/src/generate-kinds.ts +56 -4
- package/src/i18n-index.ts +59 -10
- package/src/icon-assets.ts +1 -1
- package/src/image-prepare.ts +14 -0
- package/src/import-scan.ts +63 -0
- package/src/index.ts +67 -348
- package/src/invocation-flags.ts +26 -0
- package/src/island-bundle.ts +6 -1
- package/src/island-capture.ts +6 -6
- package/src/island-harness-script.ts +6 -2
- package/src/island-realtime.ts +15 -8
- package/src/island-shot.ts +5 -0
- package/src/island-store.ts +131 -0
- package/src/island-verdict.ts +1 -1
- package/src/job-registration.ts +42 -0
- package/src/jobs-driver.ts +2 -2
- package/src/load-findings.ts +51 -0
- package/src/mcp-db-target.ts +1 -1
- package/src/mcp-errors.ts +28 -27
- package/src/mcp-host.ts +27 -12
- package/src/mcp-ui-diff.ts +27 -0
- package/src/mcp-ui-inspect.ts +4 -4
- package/src/mcp-ui-interact.ts +21 -15
- package/src/mcp-ui.ts +19 -15
- package/src/measure-database.ts +73 -0
- package/src/measure-paths.ts +74 -0
- package/src/measure-scope.ts +74 -0
- package/src/messages.ts +1 -3
- package/src/metrics-endpoint.ts +1 -1
- package/src/otlp-export.ts +1 -1
- package/src/output.ts +6 -0
- package/src/permission-grants.ts +86 -0
- package/src/prerender-out.ts +25 -0
- package/src/prerender.ts +121 -102
- package/src/pwa-artifacts.ts +3 -3
- package/src/reexport-manifest.ts +2 -1
- package/src/registry.ts +80 -56
- package/src/role-realtime.ts +36 -0
- package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
- package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
- package/src/role-start-types.ts +112 -0
- package/src/{dev-roles.ts → role-start.ts} +42 -115
- package/src/{dev-sync.ts → role-sync.ts} +18 -8
- package/src/root-env.ts +67 -0
- package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
- package/src/{dev-services.ts → runtime-bindings.ts} +39 -16
- package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
- package/src/runtime-jobs.ts +87 -0
- package/src/{dev-live-feed.ts → runtime-live-feed.ts} +18 -5
- package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
- package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
- package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
- package/src/runtime-realtime.ts +55 -0
- package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
- package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
- package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
- package/src/scaffold-fixture.ts +28 -6
- package/src/scaffold-typecheck.ts +6 -3
- package/src/schema-drift.ts +7 -1
- package/src/script-csp.ts +5 -2
- package/src/secrets-rotation.ts +59 -0
- package/src/serve-boot.ts +192 -0
- package/src/serve-drain.ts +24 -0
- package/src/serve-entry.ts +6 -0
- package/src/serve-env.ts +116 -0
- package/src/serve-types.ts +55 -0
- package/src/serve.ts +44 -347
- package/src/shot-server.ts +2 -2
- package/src/shot-settle.ts +10 -1
- package/src/shot-theme.ts +3 -3
- package/src/shot-verdict.ts +16 -7
- package/src/signal-shred.ts +27 -0
- package/src/solid-loader.ts +26 -2
- package/src/static-report.ts +8 -1
- package/src/templates/action.ts +30 -16
- package/src/templates/entity.ts +12 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/job.ts +10 -7
- package/src/templates/policy.ts +20 -2
- package/src/templates/resource-create.ts +127 -0
- package/src/templates/resource-form-island.ts +49 -12
- package/src/templates/resource.ts +10 -4
- package/src/templates/scaffold-app.ts +4 -1
- package/src/templates/scaffold-auth.ts +3 -1
- package/src/templates/scaffold-container-compose.ts +184 -0
- package/src/templates/scaffold-container.ts +24 -159
- package/src/templates/scaffold-dashboard-example.ts +2 -2
- package/src/templates/scaffold-db-package.ts +16 -3
- package/src/templates/scaffold-demo-org.ts +41 -0
- package/src/templates/scaffold-entries.ts +1 -1
- package/src/templates/scaffold-helm-templates.ts +66 -7
- package/src/templates/scaffold-helm.ts +27 -5
- package/src/templates/scaffold-i18n.ts +15 -10
- package/src/templates/scaffold-repo.ts +13 -10
- package/src/templates/scaffold-roles.ts +38 -10
- package/src/templates/slice-foundation.ts +1 -1
- package/src/templates/wrap.ts +4 -1
- package/src/test-passes.ts +2 -1
- package/src/test-workers.ts +26 -0
- package/src/ts-scan.ts +3 -6
- package/src/tsconfig-references.ts +1 -2
- package/src/verify-checks.ts +41 -39
- package/src/verify-e2e.ts +10 -7
- package/src/verify-run.ts +11 -1
- package/src/verify-step.ts +3 -3
- package/src/verify-tests.ts +1 -26
- package/src/verify-typecheck.ts +28 -0
- package/src/web-binding.ts +2 -2
- package/src/workspace-graph.ts +10 -33
- package/src/cdp-browser.ts +0 -94
- package/src/cdp-connection.ts +0 -247
- package/src/cdp-e2e-page.ts +0 -180
- package/src/cdp-e2e-session.ts +0 -199
- package/src/cdp-errors.ts +0 -56
- package/src/cdp-launch.ts +0 -193
- package/src/cdp-offline-script.ts +0 -73
- package/src/cdp-pipe.ts +0 -77
- package/src/e2e-app.ts +0 -103
- package/src/e2e-browser-handle.ts +0 -55
- package/src/e2e-dom-fixture.ts +0 -117
- package/src/e2e-driver.ts +0 -117
- package/src/e2e-errors.ts +0 -117
- package/src/e2e-evaluate.ts +0 -156
- package/src/e2e-locator.ts +0 -86
- package/src/e2e-page.ts +0 -153
- package/src/e2e-preload.ts +0 -64
- package/src/e2e-probe.ts +0 -23
- package/src/e2e-selection.ts +0 -182
- package/src/e2e-spawn.ts +0 -169
- package/src/measurement-actor.ts +0 -26
- /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
- /package/src/{dev-render.ts → runtime-render.ts} +0 -0
package/src/cmd-new.ts
CHANGED
|
@@ -7,13 +7,16 @@ import { chmod } from 'node:fs/promises';
|
|
|
7
7
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { ERROR_DOCS_URL, renderThrowable } from '@ultimat3/core';
|
|
9
9
|
import { dedupe } from './cmd-generate';
|
|
10
|
+
import { newSpec } from './cmd-new-spec';
|
|
10
11
|
import type { CliCommand, CommandContext } from './command';
|
|
11
12
|
import { invocationOf } from './command';
|
|
12
|
-
import { AppNameIsPathError, MissingPositionalError } from './errors';
|
|
13
|
+
import { AppNameEmptyError, AppNameIsPathError, MissingPositionalError } from './errors';
|
|
13
14
|
import type { Runner } from './exec';
|
|
15
|
+
import { reproducedFlags } from './invocation-flags';
|
|
14
16
|
import { msg } from './messages';
|
|
15
17
|
import type { CommandResult } from './output';
|
|
16
18
|
import { flagBool, flagString } from './parse';
|
|
19
|
+
import { quoteArg } from './shell-quote';
|
|
17
20
|
import type { GeneratedFile } from './templates';
|
|
18
21
|
import { appFiles, EXECUTABLE_FILES, names, repoFiles, resourceFiles } from './templates';
|
|
19
22
|
import { loadVersion } from './version-loader';
|
|
@@ -59,6 +62,17 @@ const GIT_STEPS: readonly (readonly string[])[] = [
|
|
|
59
62
|
/** What `--no-git` records, so `data.git` has the same shape whichever way the flag went. */
|
|
60
63
|
const NO_GIT: RepositoryInit = { initialized: false, committed: false, problem: SKIPPED };
|
|
61
64
|
|
|
65
|
+
/** `--force` into a directory that already existed: its files are not ours to commit. */
|
|
66
|
+
const PRE_EXISTING: RepositoryInit = {
|
|
67
|
+
initialized: false,
|
|
68
|
+
committed: false,
|
|
69
|
+
problem: 'the directory already existed, so nothing was committed — its files are yours',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Every flag the caller set, in the spelling that reproduces it (`invocation-flags.ts`). */
|
|
73
|
+
const callerFlags = (ctx: CommandContext): readonly string[] =>
|
|
74
|
+
reproducedFlags(newCommand.spec, ctx.args);
|
|
75
|
+
|
|
62
76
|
/**
|
|
63
77
|
* A scaffold is a REPOSITORY, because three surfaces of this CLI already assume one and answered
|
|
64
78
|
* `not a git repository` in a fresh app: `x affected`, `x ci` and `x pr`. It was four —
|
|
@@ -151,34 +165,7 @@ export function appNamePath(raw: string): { parent: string; base: string } | und
|
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
export const newCommand: CliCommand = {
|
|
154
|
-
spec:
|
|
155
|
-
name: 'new',
|
|
156
|
-
summary: 'scaffold a new Ultimate monorepo that already runs',
|
|
157
|
-
// Every flag the table below declares, in the spelling that turns it off where the default is
|
|
158
|
-
// on: the usage line offered `--no-example` while the table listed `--example`, and a reader
|
|
159
|
-
// had to reconcile the two to answer "which one do I get if I type neither".
|
|
160
|
-
usage: 'x new <name> [--dir path] [--no-example] [--no-git] [--dry-run] [--force] [--json]',
|
|
161
|
-
flags: [
|
|
162
|
-
{ name: 'dir', type: 'string', summary: 'parent directory (default: cwd)' },
|
|
163
|
-
{
|
|
164
|
-
// The summary carries the default and the negation because the page has to answer "which
|
|
165
|
-
// one do I get if I type neither": the usage line offered `--no-example`, this table said
|
|
166
|
-
// `--example`, and `default: true` is a field only `--json` renders. 136 files against 109.
|
|
167
|
-
name: 'example',
|
|
168
|
-
type: 'boolean',
|
|
169
|
-
summary: 'include the example feature slice (default: on; --no-example for an empty app/)',
|
|
170
|
-
default: true,
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: 'git',
|
|
174
|
-
type: 'boolean',
|
|
175
|
-
summary: 'git init and commit the scaffold (default: on; --no-git for a bare directory)',
|
|
176
|
-
default: true,
|
|
177
|
-
},
|
|
178
|
-
{ name: 'dry-run', type: 'boolean', summary: 'print the file list, write nothing' },
|
|
179
|
-
{ name: 'force', type: 'boolean', summary: 'write into a directory that already exists' },
|
|
180
|
-
],
|
|
181
|
-
},
|
|
168
|
+
spec: newSpec,
|
|
182
169
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
183
170
|
const raw = ctx.args.positionals[0];
|
|
184
171
|
// The class, not a hand-built finding with the same code: `MissingPositionalError` is what
|
|
@@ -202,6 +189,13 @@ export const newCommand: CliCommand = {
|
|
|
202
189
|
throw new AppNameIsPathError({ name: raw, invocation: invocationOf(ctx, 'new'), ...path });
|
|
203
190
|
}
|
|
204
191
|
const app = names(raw);
|
|
192
|
+
if (app.kebab === '') {
|
|
193
|
+
throw new AppNameEmptyError({
|
|
194
|
+
name: raw,
|
|
195
|
+
invocation: invocationOf(ctx, 'new'),
|
|
196
|
+
flags: callerFlags(ctx),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
205
199
|
const target = resolve(parentDir(ctx.cwd, flagString(ctx.args, 'dir')), app.kebab);
|
|
206
200
|
const options: NewAppOptions = { name: raw, example: ctx.args.flags.get('example') !== false };
|
|
207
201
|
|
|
@@ -224,22 +218,33 @@ export const newCommand: CliCommand = {
|
|
|
224
218
|
{
|
|
225
219
|
code: 'X_GENERATE_CONFLICT',
|
|
226
220
|
cause: `${target} already exists`,
|
|
227
|
-
|
|
221
|
+
// The invocation the caller ran, with every flag it set: dropping `--dir` wrote a
|
|
222
|
+
// second app into the cwd, and the literal `x` is not installed under create-ultimate.
|
|
223
|
+
fix: `${[invocationOf(ctx, 'new'), quoteArg(app.kebab), ...callerFlags(ctx), '--force'].join(' ')} # or choose another name`,
|
|
228
224
|
docs: ERROR_DOCS_URL,
|
|
229
225
|
at: target,
|
|
230
226
|
},
|
|
231
227
|
],
|
|
232
228
|
};
|
|
233
229
|
}
|
|
230
|
+
// Read BEFORE writing: `--force` into a directory that already held files must never
|
|
231
|
+
// `git add -A && git commit` them — they are the user's, and `x new` did not write them.
|
|
232
|
+
const existed = existsSync(target);
|
|
234
233
|
const written = await writeNewApp(target, options);
|
|
235
234
|
const git =
|
|
236
|
-
ctx.args.flags.get('git') === false
|
|
235
|
+
ctx.args.flags.get('git') === false
|
|
236
|
+
? NO_GIT
|
|
237
|
+
: existed
|
|
238
|
+
? PRE_EXISTING
|
|
239
|
+
: await initRepository(ctx.runner, target);
|
|
237
240
|
const lines = [msg('cli.new.wrote', { count: written.files.length, dir: target })];
|
|
238
|
-
if (git.problem !== null && git.problem !== SKIPPED) {
|
|
241
|
+
if (git.problem !== null && git.problem !== SKIPPED && git !== PRE_EXISTING) {
|
|
239
242
|
lines.push(msg('cli.new.noRepository', { problem: git.problem }));
|
|
240
243
|
// Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
|
|
241
244
|
// a translated command is a broken one (`packages/cli/CLAUDE.md`).
|
|
242
|
-
lines.push(
|
|
245
|
+
lines.push(
|
|
246
|
+
` run: cd ${quoteArg(target)} && git init && git add -A && git commit -m 'x new'`,
|
|
247
|
+
);
|
|
243
248
|
}
|
|
244
249
|
return {
|
|
245
250
|
ok: true,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `x policy`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-policy.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const policySpec: CommandSpec = {
|
|
7
|
+
name: 'policy',
|
|
8
|
+
summary: 'which clause decided a permission, and why',
|
|
9
|
+
usage: 'x policy [list|explain <subject>] [--json]',
|
|
10
|
+
requiresApp: true,
|
|
11
|
+
subcommands: ['list', 'explain'],
|
|
12
|
+
defaultSubcommand: 'list',
|
|
13
|
+
};
|
package/src/cmd-policy.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { nearestName } from '@ultimat3/core';
|
|
6
6
|
import { loadApp } from './app-load';
|
|
7
7
|
import { requireAppRoot } from './app-root';
|
|
8
|
+
import { policySpec } from './cmd-policy-spec';
|
|
8
9
|
import type { CliCommand, CommandContext } from './command';
|
|
9
10
|
import { DeclarationUnknownError, MissingPositionalError } from './errors';
|
|
10
11
|
import { msg } from './messages';
|
|
11
12
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
12
|
-
|
|
13
13
|
import type { DeclarationExplanation } from './policy-facts';
|
|
14
14
|
import { explainPolicy, knownPolicySubjects, listPolicy } from './policy-facts';
|
|
15
15
|
import { renderTable } from './table';
|
|
@@ -123,14 +123,7 @@ function runExplain(ctx: CommandContext, findings: readonly Finding[]): CommandR
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export const policyCommand: CliCommand = {
|
|
126
|
-
spec:
|
|
127
|
-
name: 'policy',
|
|
128
|
-
summary: 'which clause decided a permission, and why',
|
|
129
|
-
usage: 'x policy [list|explain <subject>] [--json]',
|
|
130
|
-
requiresApp: true,
|
|
131
|
-
subcommands: ['list', 'explain'],
|
|
132
|
-
defaultSubcommand: 'list',
|
|
133
|
-
},
|
|
126
|
+
spec: policySpec,
|
|
134
127
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
135
128
|
const root = requireAppRoot('policy', ctx.cwd).dir;
|
|
136
129
|
const { findings } = await loadApp(root);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// `x pr`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-pr.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const PR_SUBCOMMANDS = ['review', 'resolve', 'reply'] as const;
|
|
7
|
+
|
|
8
|
+
export const prSpec: CommandSpec = {
|
|
9
|
+
name: 'pr',
|
|
10
|
+
summary: 'inline review threads: list them with their ids, resolve one, reply in one',
|
|
11
|
+
usage:
|
|
12
|
+
'x pr review [--pr <n>] [--repo owner/name] [--all] [--full] | x pr resolve <thread-id> | x pr reply <thread-id> --body "…"',
|
|
13
|
+
subcommands: PR_SUBCOMMANDS,
|
|
14
|
+
flags: [
|
|
15
|
+
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
16
|
+
{ name: 'pr', type: 'string', summary: 'pull request number; this branch own by default' },
|
|
17
|
+
// Scoped to the subcommand each summary already names: `resolve` and `reply` WRITE to
|
|
18
|
+
// somebody else's pull request, and a flag they silently ignore is a flag whose caller
|
|
19
|
+
// believed it did something to a request that cannot be re-run.
|
|
20
|
+
{
|
|
21
|
+
name: 'all',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
summary: 'review: resolved threads too, not just open ones',
|
|
24
|
+
subcommands: ['review'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'full',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
summary: 'review: whole comment bodies, never truncated',
|
|
30
|
+
subcommands: ['review'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'body',
|
|
34
|
+
type: 'string',
|
|
35
|
+
summary: 'reply: the comment text to post in the thread',
|
|
36
|
+
subcommands: ['reply'],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
package/src/cmd-pr.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// `reviewDecision` outlives the push that answered it, and a RESOLVED thread is a closed
|
|
6
6
|
// conversation rather than a fixed finding.
|
|
7
7
|
|
|
8
|
+
import { PR_SUBCOMMANDS, prSpec } from './cmd-pr-spec';
|
|
8
9
|
import type { CliCommand, CommandContext } from './command';
|
|
9
10
|
import {
|
|
10
11
|
BadFlagError,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
UnknownCommandError,
|
|
14
15
|
} from './errors';
|
|
15
16
|
import { parseIntFlag } from './flag-number';
|
|
17
|
+
import { commentBlock } from './foreign-text';
|
|
16
18
|
import { PrNotFoundError, resolvePrNumber, resolveRepo } from './gh-target';
|
|
17
19
|
import { msg } from './messages';
|
|
18
20
|
import type { CommandResult, JsonValue } from './output';
|
|
@@ -20,7 +22,7 @@ import { flagBool, flagString } from './parse';
|
|
|
20
22
|
import type { PrReviewReport, PrThread } from './pr-threads';
|
|
21
23
|
import { fetchReviewReport, replyToThread, resolveThread, THREAD_PAGE } from './pr-threads';
|
|
22
24
|
|
|
23
|
-
export
|
|
25
|
+
export { PR_SUBCOMMANDS } from './cmd-pr-spec';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Every catalog key this command renders, declared. `msg()` answers `⟦key⟧` for a key nobody
|
|
@@ -51,38 +53,7 @@ export const PR_MESSAGE_KEYS = [
|
|
|
51
53
|
export const BODY_LINES = 20;
|
|
52
54
|
|
|
53
55
|
export const prCommand: CliCommand = {
|
|
54
|
-
spec:
|
|
55
|
-
name: 'pr',
|
|
56
|
-
summary: 'inline review threads: list them with their ids, resolve one, reply in one',
|
|
57
|
-
usage:
|
|
58
|
-
'x pr review [--pr <n>] [--repo owner/name] [--all] [--full] | x pr resolve <thread-id> | x pr reply <thread-id> --body "…"',
|
|
59
|
-
subcommands: PR_SUBCOMMANDS,
|
|
60
|
-
flags: [
|
|
61
|
-
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
62
|
-
{ name: 'pr', type: 'string', summary: 'pull request number; this branch own by default' },
|
|
63
|
-
// Scoped to the subcommand each summary already names: `resolve` and `reply` WRITE to
|
|
64
|
-
// somebody else's pull request, and a flag they silently ignore is a flag whose caller
|
|
65
|
-
// believed it did something to a request that cannot be re-run.
|
|
66
|
-
{
|
|
67
|
-
name: 'all',
|
|
68
|
-
type: 'boolean',
|
|
69
|
-
summary: 'review: resolved threads too, not just open ones',
|
|
70
|
-
subcommands: ['review'],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'full',
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
summary: 'review: whole comment bodies, never truncated',
|
|
76
|
-
subcommands: ['review'],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: 'body',
|
|
80
|
-
type: 'string',
|
|
81
|
-
summary: 'reply: the comment text to post in the thread',
|
|
82
|
-
subcommands: ['reply'],
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
56
|
+
spec: prSpec,
|
|
86
57
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
87
58
|
// No `defaultSubcommand`: `resolve` and `reply` both WRITE to a pull request, so "whatever the
|
|
88
59
|
// caller left out" is not a safe guess for any of the three.
|
|
@@ -201,38 +172,7 @@ function threadLines(thread: PrThread, bodyLines: number): readonly string[] {
|
|
|
201
172
|
return out;
|
|
202
173
|
}
|
|
203
174
|
|
|
204
|
-
|
|
205
|
-
const BLOCK_CLOSE = '</comment>';
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* The fence, as a READER would parse it rather than as this file spells it.
|
|
209
|
-
*
|
|
210
|
-
* Two literal `replaceAll`s were the whole neutralisation, and markup is not spelled one way:
|
|
211
|
-
* `</comment >`, `</COMMENT>` and `< comment id=` all end or open a block for anything reading
|
|
212
|
-
* tags, and none of the three matched. One pattern over `<`, an optional `/`, and whitespace
|
|
213
|
-
* around a case-insensitive `comment` covers every spelling of the delimiter; the escape goes on
|
|
214
|
-
* the `<`, so what the reviewer wrote after it survives byte for byte.
|
|
215
|
-
*/
|
|
216
|
-
const BLOCK_DELIMITER = /<(\s*\/?\s*comment\b)/gi;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* One comment body, fenced and labelled with the thread id it came from — `@ultimat3/ai`'s
|
|
220
|
-
* `documentBlock` (`rag.ts`), applied to the other place foreign text enters an agent's context.
|
|
221
|
-
* `x pr review` exists because an agent cannot read the GitHub web UI, and a review body is
|
|
222
|
-
* written by anyone who can comment on the pull request: rendered as bare indented text it arrived
|
|
223
|
-
* in that agent's context indistinguishable from the command's own output, which is prompt
|
|
224
|
-
* injection with a shell attached.
|
|
225
|
-
*
|
|
226
|
-
* The fence is neutralised INSIDE the payload rather than deleted, so every word the reviewer
|
|
227
|
-
* wrote still reads, and the label is stripped of the three characters that would end the
|
|
228
|
-
* attribute. Influence only, and deliberately not sold as more: a fence tells a reader this text
|
|
229
|
-
* is data, and it can never stop one that decides otherwise.
|
|
230
|
-
*/
|
|
231
|
-
export function commentBlock(id: string, lines: readonly string[]): readonly string[] {
|
|
232
|
-
const label = id.replaceAll('"', "'").replaceAll('>', ')').replaceAll('<', '(');
|
|
233
|
-
const body = lines.map((line) => line.replace(BLOCK_DELIMITER, '<\\$1'));
|
|
234
|
-
return [`${BLOCK_OPEN}"${label}">`, ...body, BLOCK_CLOSE];
|
|
235
|
-
}
|
|
175
|
+
export { commentBlock };
|
|
236
176
|
|
|
237
177
|
/**
|
|
238
178
|
* A review body is prose written for a browser: the ones in this repo run to six thousand
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// `x actions`, `x queries` and `x entities`' declarations, apart from their body: the parser, `x help`
|
|
2
|
+
// and the `errors` step read them without loading `cmd-registries.ts` and the three registries it
|
|
3
|
+
// projects, which `registry.ts` imports only when one of the commands runs.
|
|
4
|
+
|
|
5
|
+
import type { CommandSpec } from './parse';
|
|
6
|
+
|
|
7
|
+
export const actionsSpec: CommandSpec = {
|
|
8
|
+
name: 'actions',
|
|
9
|
+
summary: 'the action registry: input/output schema, policy, tags, MCP exposure',
|
|
10
|
+
usage: 'x actions [list|describe <name>] [--json]',
|
|
11
|
+
subcommands: ['list', 'describe'],
|
|
12
|
+
defaultSubcommand: 'list',
|
|
13
|
+
requiresApp: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const queriesSpec: CommandSpec = {
|
|
17
|
+
name: 'queries',
|
|
18
|
+
summary: 'the query registry: schema, policy, live, cache tags',
|
|
19
|
+
usage: 'x queries [list|describe <name>] [--json]',
|
|
20
|
+
subcommands: ['list', 'describe'],
|
|
21
|
+
defaultSubcommand: 'list',
|
|
22
|
+
requiresApp: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const entitiesSpec: CommandSpec = {
|
|
26
|
+
name: 'entities',
|
|
27
|
+
summary: 'the entity registry: columns, invariants, indexes, tenancy',
|
|
28
|
+
usage: 'x entities [list|describe <name>] [--json]',
|
|
29
|
+
subcommands: ['list', 'describe'],
|
|
30
|
+
defaultSubcommand: 'list',
|
|
31
|
+
requiresApp: true,
|
|
32
|
+
};
|
package/src/cmd-registries.ts
CHANGED
|
@@ -13,12 +13,12 @@ import type { AnyQuery, QueryDescriptor } from '@ultimat3/query';
|
|
|
13
13
|
import { describeQueries, getQuery } from '@ultimat3/query';
|
|
14
14
|
import { loadApp } from './app-load';
|
|
15
15
|
import { requireAppRoot } from './app-root';
|
|
16
|
+
import { actionsSpec, entitiesSpec, queriesSpec } from './cmd-registries-spec';
|
|
16
17
|
import type { CliCommand, CommandContext } from './command';
|
|
17
18
|
import { DeclarationUnknownError, MissingPositionalError } from './errors';
|
|
18
19
|
import { msg } from './messages';
|
|
19
20
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
20
21
|
import type { CommandSpec } from './parse';
|
|
21
|
-
|
|
22
22
|
import { renderTable } from './table';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -53,14 +53,7 @@ interface RegistryKind<D extends { readonly name: string }, Raw extends { descri
|
|
|
53
53
|
const ACTIONS: RegistryKind<ActionDescriptor, AnyAction> = {
|
|
54
54
|
kind: 'actions',
|
|
55
55
|
singular: 'action',
|
|
56
|
-
spec:
|
|
57
|
-
name: 'actions',
|
|
58
|
-
summary: 'the action registry: input/output schema, policy, tags, MCP exposure',
|
|
59
|
-
usage: 'x actions [list|describe <name>] [--json]',
|
|
60
|
-
subcommands: ['list', 'describe'],
|
|
61
|
-
defaultSubcommand: 'list',
|
|
62
|
-
requiresApp: true,
|
|
63
|
-
},
|
|
56
|
+
spec: actionsSpec,
|
|
64
57
|
header: ['name', 'verb', 'resource', 'path', 'capability', 'mcp'],
|
|
65
58
|
list: describeActions,
|
|
66
59
|
find: getAction,
|
|
@@ -71,14 +64,7 @@ const ACTIONS: RegistryKind<ActionDescriptor, AnyAction> = {
|
|
|
71
64
|
const QUERIES: RegistryKind<QueryDescriptor, AnyQuery> = {
|
|
72
65
|
kind: 'queries',
|
|
73
66
|
singular: 'query',
|
|
74
|
-
spec:
|
|
75
|
-
name: 'queries',
|
|
76
|
-
summary: 'the query registry: schema, policy, live, cache tags',
|
|
77
|
-
usage: 'x queries [list|describe <name>] [--json]',
|
|
78
|
-
subcommands: ['list', 'describe'],
|
|
79
|
-
defaultSubcommand: 'list',
|
|
80
|
-
requiresApp: true,
|
|
81
|
-
},
|
|
67
|
+
spec: queriesSpec,
|
|
82
68
|
header: ['name', 'live', 'capability', 'tags', 'ttlMs'],
|
|
83
69
|
list: describeQueries,
|
|
84
70
|
find: getQuery,
|
|
@@ -97,14 +83,7 @@ const QUERIES: RegistryKind<QueryDescriptor, AnyQuery> = {
|
|
|
97
83
|
const ENTITIES: RegistryKind<EntityDescription, RegistryEntry> = {
|
|
98
84
|
kind: 'entities',
|
|
99
85
|
singular: 'entity',
|
|
100
|
-
spec:
|
|
101
|
-
name: 'entities',
|
|
102
|
-
summary: 'the entity registry: columns, invariants, indexes, tenancy',
|
|
103
|
-
usage: 'x entities [list|describe <name>] [--json]',
|
|
104
|
-
subcommands: ['list', 'describe'],
|
|
105
|
-
defaultSubcommand: 'list',
|
|
106
|
-
requiresApp: true,
|
|
107
|
-
},
|
|
86
|
+
spec: entitiesSpec,
|
|
108
87
|
header: ['name', 'table', 'columns', 'invariants', 'indexes', 'orgScoped'],
|
|
109
88
|
list: describeEntities,
|
|
110
89
|
find: getEntity,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// `x routes`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-routes.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const routesSpec: CommandSpec = {
|
|
7
|
+
name: 'routes',
|
|
8
|
+
summary: 'the route table: path, surface, render mode, hydrate, offline',
|
|
9
|
+
usage: 'x routes [--surface site|app|api|shared] [--json]',
|
|
10
|
+
requiresApp: true,
|
|
11
|
+
flags: [{ name: 'surface', type: 'string', summary: 'filter by surface' }],
|
|
12
|
+
};
|
package/src/cmd-routes.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { RouteDescriptor, Surface } from '@ultimat3/render';
|
|
|
8
8
|
import { describeRoutes, SURFACES } from '@ultimat3/render';
|
|
9
9
|
import { loadApp } from './app-load';
|
|
10
10
|
import { requireAppRoot } from './app-root';
|
|
11
|
+
import { routesSpec } from './cmd-routes-spec';
|
|
11
12
|
import type { CliCommand, CommandContext } from './command';
|
|
12
13
|
import { BadFlagError } from './errors';
|
|
13
14
|
import { msg } from './messages';
|
|
@@ -65,13 +66,7 @@ export function readSurfaceFilter(raw: string | undefined): Surface | undefined
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export const routesCommand: CliCommand = {
|
|
68
|
-
spec:
|
|
69
|
-
name: 'routes',
|
|
70
|
-
summary: 'the route table: path, surface, render mode, hydrate, offline',
|
|
71
|
-
usage: 'x routes [--surface site|app|api|shared] [--json]',
|
|
72
|
-
requiresApp: true,
|
|
73
|
-
flags: [{ name: 'surface', type: 'string', summary: 'filter by surface' }],
|
|
74
|
-
},
|
|
69
|
+
spec: routesSpec,
|
|
75
70
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
76
71
|
const root = requireAppRoot('routes', ctx.cwd).dir;
|
|
77
72
|
// Read before the app is loaded: a typo must not cost a boot to report, the rule `x mcp`'s
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// `x secrets`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-secrets.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import { ENV_SCHEMA_EXPORT } from './app-env';
|
|
5
|
+
import type { CommandSpec } from './parse';
|
|
6
|
+
|
|
7
|
+
export const SECRETS_SUBCOMMANDS = ['show', 'init', 'edit', 'set', 'rotate'] as const;
|
|
8
|
+
|
|
9
|
+
export const secretsSpec: CommandSpec = {
|
|
10
|
+
name: 'secrets',
|
|
11
|
+
summary: `the committed encrypted secrets, decrypted into the ${ENV_SCHEMA_EXPORT} variables of the same names`,
|
|
12
|
+
usage: 'x secrets [show|init|edit|set <NAME>|rotate] [--json]',
|
|
13
|
+
requiresApp: true,
|
|
14
|
+
subcommands: [...SECRETS_SUBCOMMANDS],
|
|
15
|
+
// The bare `x secrets` answers without a key ever leaving the file. Declared, not inherited
|
|
16
|
+
// from the array's order — `init`, `edit`, `set` and `rotate` all write.
|
|
17
|
+
defaultSubcommand: 'show',
|
|
18
|
+
flags: [],
|
|
19
|
+
};
|
package/src/cmd-secrets.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '@ultimat3/core';
|
|
31
31
|
import { ENV_SCHEMA_EXPORT, loadEnvSchema } from './app-env';
|
|
32
32
|
import { requireAppRoot } from './app-root';
|
|
33
|
+
import { secretsSpec } from './cmd-secrets-spec';
|
|
33
34
|
import type { CliCommand, CommandContext } from './command';
|
|
34
35
|
import {
|
|
35
36
|
MissingPositionalError,
|
|
@@ -39,9 +40,11 @@ import {
|
|
|
39
40
|
} from './errors';
|
|
40
41
|
import { msg } from './messages';
|
|
41
42
|
import type { CommandResult, JsonValue } from './output';
|
|
43
|
+
import { recoverRotation, rotateMasterKey } from './secrets-rotation';
|
|
44
|
+
import { shredOnSignal } from './signal-shred';
|
|
42
45
|
import { renderTable } from './table';
|
|
43
46
|
|
|
44
|
-
export
|
|
47
|
+
export { SECRETS_SUBCOMMANDS } from './cmd-secrets-spec';
|
|
45
48
|
|
|
46
49
|
/** In order of precedence. `VISUAL` outranks `EDITOR` by POSIX convention on an interactive tty. */
|
|
47
50
|
export const EDITOR_VARS = ['VISUAL', 'EDITOR'] as const;
|
|
@@ -74,9 +77,10 @@ interface Session {
|
|
|
74
77
|
readonly key: MasterKeyRef;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
/** The root and its key — after finishing or abandoning a rotation a crash interrupted. */
|
|
81
|
+
const open = async (ctx: CommandContext, subcommand: string): Promise<Session> => {
|
|
78
82
|
const root = requireAppRoot(`secrets ${subcommand}`, ctx.cwd).dir;
|
|
79
|
-
return { root, key: requireMasterKey(root, ctx.env) };
|
|
83
|
+
return { root, key: await recoverRotation(root, requireMasterKey(root, ctx.env)) };
|
|
80
84
|
};
|
|
81
85
|
|
|
82
86
|
const names = (values: SecretValues): readonly string[] => Object.keys(values).sort();
|
|
@@ -148,7 +152,7 @@ async function init(ctx: CommandContext): Promise<CommandResult> {
|
|
|
148
152
|
* of the two harms. Leaving decrypted values on disk so they can be recovered is the larger one.
|
|
149
153
|
*/
|
|
150
154
|
async function edit(ctx: CommandContext, io: SecretsIo): Promise<CommandResult> {
|
|
151
|
-
const { root, key } = open(ctx, 'edit');
|
|
155
|
+
const { root, key } = await open(ctx, 'edit');
|
|
152
156
|
const editor = EDITOR_VARS.map((name) => ctx.env[name]).find(
|
|
153
157
|
(value): value is string => value !== undefined && value.trim().length > 0,
|
|
154
158
|
);
|
|
@@ -157,9 +161,9 @@ async function edit(ctx: CommandContext, io: SecretsIo): Promise<CommandResult>
|
|
|
157
161
|
const dir = await mkdtemp(join(tmpdir(), 'ultimate-secrets-'));
|
|
158
162
|
const buffer = join(dir, 'secrets.json');
|
|
159
163
|
const shred = (): void => rmSync(dir, { recursive: true, force: true });
|
|
160
|
-
// A `finally` does not run for a signal, and Ctrl-C inside an editor kills this process too
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
// A `finally` does not run for a signal, and Ctrl-C inside an editor kills this process too —
|
|
165
|
+
// after the shred, which `shredOnSignal` runs and then re-raises the signal for.
|
|
166
|
+
const unlisten = shredOnSignal(shred);
|
|
163
167
|
try {
|
|
164
168
|
await Bun.write(buffer, serializeSecretValues(before));
|
|
165
169
|
// `$EDITOR` may carry flags (`code --wait`), and there is no shell here to split them.
|
|
@@ -195,8 +199,7 @@ async function edit(ctx: CommandContext, io: SecretsIo): Promise<CommandResult>
|
|
|
195
199
|
};
|
|
196
200
|
} finally {
|
|
197
201
|
shred();
|
|
198
|
-
|
|
199
|
-
process.off('SIGTERM', shred);
|
|
202
|
+
unlisten();
|
|
200
203
|
}
|
|
201
204
|
}
|
|
202
205
|
|
|
@@ -217,7 +220,7 @@ async function set(ctx: CommandContext, io: SecretsIo): Promise<CommandResult> {
|
|
|
217
220
|
example: 'printf %s "$TOKEN" | x secrets set STRIPE_KEY --json',
|
|
218
221
|
});
|
|
219
222
|
}
|
|
220
|
-
const { root, key } = open(ctx, 'set');
|
|
223
|
+
const { root, key } = await open(ctx, 'set');
|
|
221
224
|
const before = await readSecretsFile(root, key);
|
|
222
225
|
// Exactly one trailing newline, because `echo` adds one and a secret with a stray `\n` fails
|
|
223
226
|
// against the service it authenticates to with an error that names nothing.
|
|
@@ -242,23 +245,18 @@ async function set(ctx: CommandContext, io: SecretsIo): Promise<CommandResult> {
|
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
/**
|
|
245
|
-
* A new master key over the same values
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* a file it cannot open — `X_SECRETS_KEY_MISMATCH`, whose fix restores the file from git.
|
|
248
|
+
* A new master key over the same values: staged beside the old one, the file sealed with it, then
|
|
249
|
+
* made live (`rotateMasterKey`). Sealed first with the key file written last, a crash between the
|
|
250
|
+
* two left a committed file no key on disk could open.
|
|
249
251
|
*/
|
|
250
252
|
async function rotate(ctx: CommandContext): Promise<CommandResult> {
|
|
251
|
-
const { root, key } = open(ctx, 'rotate');
|
|
253
|
+
const { root, key } = await open(ctx, 'rotate');
|
|
252
254
|
const values = await readSecretsFile(root, key);
|
|
253
255
|
const previous = await masterKeyIdOf(key);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
source: 'file',
|
|
257
|
-
at: masterKeyPath(root),
|
|
258
|
-
};
|
|
259
|
-
await writeSecretsFile(root, values, next);
|
|
256
|
+
// Ignored BEFORE any key file is written, staged one included; the order inside is
|
|
257
|
+
// `secrets-rotation.ts`'s: stage the key, seal, then rename it live.
|
|
260
258
|
await ensureIgnored(root);
|
|
261
|
-
|
|
259
|
+
const next = await rotateMasterKey(root, values);
|
|
262
260
|
const keyId = await masterKeyIdOf(next);
|
|
263
261
|
return {
|
|
264
262
|
ok: true,
|
|
@@ -286,7 +284,7 @@ async function rotate(ctx: CommandContext): Promise<CommandResult> {
|
|
|
286
284
|
* `x secrets edit`. A `--reveal` flag would be a second path that `--json` could not honour.
|
|
287
285
|
*/
|
|
288
286
|
async function show(ctx: CommandContext): Promise<CommandResult> {
|
|
289
|
-
const { root, key } = open(ctx, 'show');
|
|
287
|
+
const { root, key } = await open(ctx, 'show');
|
|
290
288
|
const values = await readSecretsFile(root, key);
|
|
291
289
|
const schema = await loadEnvSchema(root);
|
|
292
290
|
const summaries = describeSecrets(values);
|
|
@@ -337,17 +335,7 @@ async function show(ctx: CommandContext): Promise<CommandResult> {
|
|
|
337
335
|
|
|
338
336
|
export function createSecretsCommand(io: SecretsIo): CliCommand {
|
|
339
337
|
return {
|
|
340
|
-
spec:
|
|
341
|
-
name: 'secrets',
|
|
342
|
-
summary: `the committed encrypted secrets, decrypted into the ${ENV_SCHEMA_EXPORT} variables of the same names`,
|
|
343
|
-
usage: 'x secrets [show|init|edit|set <NAME>|rotate] [--json]',
|
|
344
|
-
requiresApp: true,
|
|
345
|
-
subcommands: [...SECRETS_SUBCOMMANDS],
|
|
346
|
-
// The bare `x secrets` answers without a key ever leaving the file. Declared, not inherited
|
|
347
|
-
// from the array's order — `init`, `edit`, `set` and `rotate` all write.
|
|
348
|
-
defaultSubcommand: 'show',
|
|
349
|
-
flags: [],
|
|
350
|
-
},
|
|
338
|
+
spec: secretsSpec,
|
|
351
339
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
352
340
|
switch (ctx.args.subcommand ?? 'show') {
|
|
353
341
|
case 'init':
|
package/src/cmd-shot-island.ts
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
// why: no Bun native joins or resolves a path; `--out` is resolved against the app root.
|
|
11
11
|
import { join, resolve } from 'node:path';
|
|
12
|
-
import type { ScrapeDriver } from '@ultimat3/scraping';
|
|
13
12
|
import type { IslandStatesManifest, IslandViewport } from '@ultimat3/testing';
|
|
14
13
|
import { findIslandStates } from '@ultimat3/testing';
|
|
15
14
|
import { appBrowser } from './browser-launcher';
|
|
15
|
+
import type { ShotDriver } from './browser-launcher-port';
|
|
16
16
|
import { BadFlagError } from './errors';
|
|
17
17
|
import type { IslandBrowser } from './island-shot';
|
|
18
18
|
import { ISLAND_SHOT_DIR, runIslandShot, runIslandSweep } from './island-shot';
|
|
@@ -39,19 +39,18 @@ export function islandBrowser(input: {
|
|
|
39
39
|
readonly executablePath?: string | undefined;
|
|
40
40
|
readonly cdpUrl?: string | undefined;
|
|
41
41
|
}): IslandBrowser {
|
|
42
|
-
const byViewport = new Map<string, Promise<
|
|
43
|
-
return (viewport: IslandViewport): Promise<
|
|
42
|
+
const byViewport = new Map<string, Promise<ShotDriver>>();
|
|
43
|
+
return (viewport: IslandViewport): Promise<ShotDriver> => {
|
|
44
44
|
const key = `${viewport.width}x${viewport.height}`;
|
|
45
|
-
const held =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return started;
|
|
45
|
+
const held =
|
|
46
|
+
byViewport.get(key) ??
|
|
47
|
+
appBrowser({
|
|
48
|
+
...(input.executablePath === undefined ? {} : { executablePath: input.executablePath }),
|
|
49
|
+
...(input.cdpUrl === undefined ? {} : { cdpUrl: input.cdpUrl }),
|
|
50
|
+
viewport: { width: viewport.width, height: viewport.height },
|
|
51
|
+
});
|
|
52
|
+
byViewport.set(key, held);
|
|
53
|
+
return held;
|
|
55
54
|
};
|
|
56
55
|
}
|
|
57
56
|
|