@sanity/workflow-cli 0.7.1 → 0.8.1
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 +20 -20
- package/dist/commands/abort.js +1 -1
- package/dist/commands/definition/delete.d.ts +8 -4
- package/dist/commands/definition/delete.js +2 -2
- package/dist/commands/definition/diff.js +9 -4
- package/dist/commands/definition/list.d.ts +2 -2
- package/dist/commands/definition/list.js +3 -3
- package/dist/commands/definition/show.d.ts +5 -3
- package/dist/commands/definition/show.js +17 -13
- package/dist/commands/deploy.js +5 -6
- package/dist/commands/diagnose.d.ts +6 -2
- package/dist/commands/diagnose.js +30 -27
- package/dist/commands/fire-action.d.ts +13 -5
- package/dist/commands/fire-action.js +32 -28
- package/dist/commands/list.d.ts +2 -2
- package/dist/commands/list.js +8 -8
- package/dist/commands/move-stage.d.ts +6 -1
- package/dist/commands/move-stage.js +13 -4
- package/dist/commands/{retry-task.d.ts → retry-activity.d.ts} +2 -2
- package/dist/commands/{retry-task.js → retry-activity.js} +3 -3
- package/dist/commands/set-stage.js +1 -1
- package/dist/commands/show.d.ts +1 -1
- package/dist/commands/show.js +10 -10
- package/dist/commands/tail.js +2 -2
- package/dist/lib/definitions.js +1 -1
- package/dist/lib/diff.d.ts +2 -1
- package/dist/lib/diff.js +2 -2
- package/dist/lib/operation-args.d.ts +5 -1
- package/dist/lib/operation-args.js +1 -1
- package/dist/lib/ops-report.d.ts +5 -1
- package/dist/lib/ops-report.js +1 -1
- package/dist/lib/ui.d.ts +6 -4
- package/dist/lib/ui.js +3 -5
- package/oclif.manifest.json +22 -22
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Command-line tool for deploying, inspecting, and administering Sanity workflow
|
|
|
4
4
|
definitions and instances.
|
|
5
5
|
|
|
6
6
|
> [!WARNING]
|
|
7
|
-
> Early access, restricted. `retry-
|
|
7
|
+
> Early access, restricted. `retry-activity` and `set-stage` are still stubs —
|
|
8
8
|
> they print their parsed input and exit non-zero. The "wired" commands below
|
|
9
9
|
> talk to a real Sanity dataset using `SANITY_*` env vars.
|
|
10
10
|
|
|
@@ -75,25 +75,25 @@ built or standalone CLI instead resolves the package's compiled `dist`.
|
|
|
75
75
|
|
|
76
76
|
## Command status
|
|
77
77
|
|
|
78
|
-
| Command
|
|
79
|
-
|
|
|
80
|
-
| `deploy`
|
|
81
|
-
| `deploy --check`
|
|
82
|
-
| `deploy --dry-run`
|
|
83
|
-
| `deploy --only <id>`
|
|
84
|
-
| `list`
|
|
85
|
-
| `show <instance-id>`
|
|
86
|
-
| `diagnose <instance-id>`
|
|
87
|
-
| `tail <instance-id>`
|
|
88
|
-
| `abort <instance-id>`
|
|
89
|
-
| `retry-
|
|
90
|
-
| `move-stage <instance-id> --to <stage-id>`
|
|
91
|
-
| `fire-action <instance-id>`
|
|
92
|
-
| `set-stage <instance-id> --to <stage-id>`
|
|
93
|
-
| `definition list`
|
|
94
|
-
| `definition show <id>`
|
|
95
|
-
| `definition diff <id>`
|
|
96
|
-
| `definition delete <name>`
|
|
78
|
+
| Command | Status |
|
|
79
|
+
| -------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
80
|
+
| `deploy` | wired — calls `workflow.deployDefinitions` over the `WORKFLOW_DEFS` module |
|
|
81
|
+
| `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-id check |
|
|
82
|
+
| `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change |
|
|
83
|
+
| `deploy --only <id>` | wired — filters deploy/check/dry-run to one `definition` |
|
|
84
|
+
| `list` | wired — `client.fetch` over `workflow.instance` documents |
|
|
85
|
+
| `show <instance-id>` | wired — `client.getDocument` |
|
|
86
|
+
| `diagnose <instance-id>` | wired — one `workflow.evaluate` call, classifies why the instance is/isn't progressing |
|
|
87
|
+
| `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
|
|
88
|
+
| `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards) |
|
|
89
|
+
| `retry-activity <instance-id> <activity-id>` | stub |
|
|
90
|
+
| `move-stage <instance-id> --to <stage-id>` | wired — calls `workflow.setStage` (runs guards + onEnter effects) |
|
|
91
|
+
| `fire-action <instance-id>` | wired — `workflow.evaluate` lists actions; `workflow.fireAction` fires one |
|
|
92
|
+
| `set-stage <instance-id> --to <stage-id>` | stub (engine has no guard-bypass path yet) |
|
|
93
|
+
| `definition list` | wired — `client.fetch` over `workflow.definition` documents |
|
|
94
|
+
| `definition show <id>` | wired — `client.fetch`, latest version unless `--version` |
|
|
95
|
+
| `definition diff <id>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin) |
|
|
96
|
+
| `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`) |
|
|
97
97
|
|
|
98
98
|
## Known gaps
|
|
99
99
|
|
package/dist/commands/abort.js
CHANGED
|
@@ -28,7 +28,7 @@ export default class Abort extends Command {
|
|
|
28
28
|
try {
|
|
29
29
|
const result = await workflow.abortInstance({
|
|
30
30
|
client,
|
|
31
|
-
...buildOperationArgs(config, args.instanceId, flags.reason),
|
|
31
|
+
...buildOperationArgs({ config, instanceId: args.instanceId, reason: flags.reason }),
|
|
32
32
|
});
|
|
33
33
|
const report = abortReport(result);
|
|
34
34
|
if (!report.fired) {
|
|
@@ -21,10 +21,14 @@ export default class DefinitionDelete extends Command {
|
|
|
21
21
|
* engine's optionals reject an explicit `undefined` under
|
|
22
22
|
* `exactOptionalPropertyTypes`.
|
|
23
23
|
*/
|
|
24
|
-
export declare function buildDeleteArgs(config
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
export declare function buildDeleteArgs({ config, name, flags, }: {
|
|
25
|
+
config: WorkflowConfig;
|
|
26
|
+
name: string;
|
|
27
|
+
flags: {
|
|
28
|
+
version?: number | undefined;
|
|
29
|
+
cascade: boolean;
|
|
30
|
+
reason?: string | undefined;
|
|
31
|
+
};
|
|
28
32
|
}): Omit<DeleteDefinitionArgs, 'client'>;
|
|
29
33
|
/** What the spinner names: the workflow, or one pinned version of it. */
|
|
30
34
|
export declare function deleteTargetLabel(args: Pick<DeleteDefinitionArgs, 'definition' | 'version'>): string;
|
|
@@ -36,7 +36,7 @@ export default class DefinitionDelete extends Command {
|
|
|
36
36
|
const { args, flags } = await this.parse(DefinitionDelete);
|
|
37
37
|
const config = loadWorkflowConfig(flags.tag);
|
|
38
38
|
const client = loadClient();
|
|
39
|
-
const deleteArgs = buildDeleteArgs(config, args.name, flags);
|
|
39
|
+
const deleteArgs = buildDeleteArgs({ config, name: args.name, flags });
|
|
40
40
|
const spinner = ora(`Deleting definition ${deleteTargetLabel(deleteArgs)}…`).start();
|
|
41
41
|
try {
|
|
42
42
|
const result = await workflow.deleteDefinition({ client, ...deleteArgs });
|
|
@@ -55,7 +55,7 @@ export default class DefinitionDelete extends Command {
|
|
|
55
55
|
* engine's optionals reject an explicit `undefined` under
|
|
56
56
|
* `exactOptionalPropertyTypes`.
|
|
57
57
|
*/
|
|
58
|
-
export function buildDeleteArgs(config, name, flags) {
|
|
58
|
+
export function buildDeleteArgs({ config, name, flags, }) {
|
|
59
59
|
return {
|
|
60
60
|
...baseEngineArgs(config),
|
|
61
61
|
definition: name,
|
|
@@ -29,14 +29,19 @@ export default class DefinitionDiff extends Command {
|
|
|
29
29
|
const spinner = ora(`Diffing ${args.definition} against deployed…`).start();
|
|
30
30
|
let deployed;
|
|
31
31
|
try {
|
|
32
|
-
deployed = await fetchDeployedDefinition(
|
|
33
|
-
|
|
32
|
+
deployed = await fetchDeployedDefinition({
|
|
33
|
+
client,
|
|
34
|
+
name: args.definition,
|
|
35
|
+
config,
|
|
36
|
+
version: flags.version,
|
|
37
|
+
});
|
|
38
|
+
spinner.succeed(`Diffed ${args.definition}`);
|
|
34
39
|
}
|
|
35
40
|
catch (error) {
|
|
36
41
|
spinner.fail('Diff failed');
|
|
37
42
|
throw error;
|
|
38
43
|
}
|
|
39
|
-
for (const line of diffReport([diffEntry(def, deployed, config)]))
|
|
44
|
+
for (const line of diffReport([diffEntry({ def, latestRaw: deployed, target: config })]))
|
|
40
45
|
this.log(line);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
@@ -47,7 +52,7 @@ export default class DefinitionDiff extends Command {
|
|
|
47
52
|
* superseded — latest unless pinned via `version`. An absent latest is a
|
|
48
53
|
* legitimate "create" diff, but an absent *pinned* version is an error.
|
|
49
54
|
*/
|
|
50
|
-
async function fetchDeployedDefinition(client, name, config, version) {
|
|
55
|
+
async function fetchDeployedDefinition({ client, name, config, version, }) {
|
|
51
56
|
const { groq, params } = buildDefinitionShowQuery({ name, tag: config.tag, version });
|
|
52
57
|
const deployed = await client.fetch(groq, params);
|
|
53
58
|
if (deployed === null && version !== undefined) {
|
|
@@ -2,7 +2,7 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { type WorkflowRole } from '@sanity/workflow-engine';
|
|
3
3
|
interface DefinitionListFlags {
|
|
4
4
|
tag?: string | undefined;
|
|
5
|
-
'workflow-
|
|
5
|
+
'workflow-name'?: string | undefined;
|
|
6
6
|
limit: number;
|
|
7
7
|
}
|
|
8
8
|
export interface DefinitionListRow {
|
|
@@ -24,7 +24,7 @@ export default class DefinitionList extends Command {
|
|
|
24
24
|
static examples: string[];
|
|
25
25
|
static flags: {
|
|
26
26
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
-
'workflow-
|
|
27
|
+
'workflow-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
28
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
29
|
};
|
|
30
30
|
run(): Promise<void>;
|
|
@@ -21,8 +21,8 @@ export function buildDefinitionListQuery(flags) {
|
|
|
21
21
|
params.tag = flags.tag;
|
|
22
22
|
filters.push(tagScopeFilter());
|
|
23
23
|
}
|
|
24
|
-
if (flags['workflow-
|
|
25
|
-
params.name = flags['workflow-
|
|
24
|
+
if (flags['workflow-name']) {
|
|
25
|
+
params.name = flags['workflow-name'];
|
|
26
26
|
filters.push('name == $name');
|
|
27
27
|
}
|
|
28
28
|
const instanceMatch = instanceFilters.join(' && ');
|
|
@@ -50,7 +50,7 @@ export default class DefinitionList extends Command {
|
|
|
50
50
|
description: 'The maximum number of definitions to return.',
|
|
51
51
|
default: 100,
|
|
52
52
|
}),
|
|
53
|
-
'workflow-
|
|
53
|
+
'workflow-name': Flags.string({
|
|
54
54
|
description: 'Filter to a single workflow definition name (e.g. product-launch)',
|
|
55
55
|
}),
|
|
56
56
|
};
|
|
@@ -24,10 +24,12 @@ export default class DefinitionShow extends Command {
|
|
|
24
24
|
* title, then the human title, description, and deploy `tag` as aligned
|
|
25
25
|
* detail rows. `tag` is deployment metadata (the partition), not part of the
|
|
26
26
|
* authored definition, so it's passed separately and omitted when unknown. */
|
|
27
|
-
export declare function definitionHeader(def: WorkflowDefinition
|
|
27
|
+
export declare function definitionHeader(def: WorkflowDefinition & {
|
|
28
|
+
version: number;
|
|
29
|
+
}, tag?: string): string;
|
|
28
30
|
/**
|
|
29
|
-
* The body lines for `definition show`: every stage with its
|
|
30
|
-
*
|
|
31
|
+
* The body lines for `definition show`: every stage with its activities and each
|
|
32
|
+
* activity's actions. Pure so it can be asserted without driving the oclif command.
|
|
31
33
|
*/
|
|
32
34
|
export declare function describeDefinition(def: WorkflowDefinition): string[];
|
|
33
35
|
export {};
|
|
@@ -36,8 +36,8 @@ export default class DefinitionShow extends Command {
|
|
|
36
36
|
version: flags.version,
|
|
37
37
|
});
|
|
38
38
|
const client = loadClient();
|
|
39
|
-
//
|
|
40
|
-
//
|
|
39
|
+
// A deployed document: the authored {@link WorkflowDefinition} plus the
|
|
40
|
+
// deploy-stamped envelope (`version`, `contentHash`, `tag`).
|
|
41
41
|
const def = await client.fetch(groq, params);
|
|
42
42
|
if (!def) {
|
|
43
43
|
const at = flags.version ? `v${flags.version}` : 'latest';
|
|
@@ -57,11 +57,15 @@ const HEADER_PAD = 11; // "Description" — the longest label in the block
|
|
|
57
57
|
* authored definition, so it's passed separately and omitted when unknown. */
|
|
58
58
|
export function definitionHeader(def, tag) {
|
|
59
59
|
const rows = [
|
|
60
|
-
formatKeyValue('Title', def.title,
|
|
61
|
-
formatKeyValue(
|
|
60
|
+
formatKeyValue({ key: 'Title', value: def.title, padTo: HEADER_PAD }),
|
|
61
|
+
formatKeyValue({
|
|
62
|
+
key: 'Description',
|
|
63
|
+
value: def.description ?? styleText('dim', '—'),
|
|
64
|
+
padTo: HEADER_PAD,
|
|
65
|
+
}),
|
|
62
66
|
];
|
|
63
67
|
if (tag !== undefined) {
|
|
64
|
-
rows.push(formatKeyValue('Tag', tag,
|
|
68
|
+
rows.push(formatKeyValue({ key: 'Tag', value: tag, padTo: HEADER_PAD }));
|
|
65
69
|
}
|
|
66
70
|
return [styleText('bold', `${def.name} v${def.version}`), ...rows].join('\n');
|
|
67
71
|
}
|
|
@@ -71,24 +75,24 @@ function actionLine(action) {
|
|
|
71
75
|
const status = statusOp ? styleText('dim', ` → ${statusOp.status}`) : '';
|
|
72
76
|
return ` · ${action.name}${status}`;
|
|
73
77
|
}
|
|
74
|
-
/** A `-
|
|
75
|
-
function
|
|
76
|
-
const title =
|
|
77
|
-
return [` - ${
|
|
78
|
+
/** A `- activity` line followed by one line per action it exposes. */
|
|
79
|
+
function activityBlock(activity) {
|
|
80
|
+
const title = activity.title ? styleText('dim', ` — ${activity.title}`) : '';
|
|
81
|
+
return [` - ${activity.name}${title}`, ...(activity.actions ?? []).map(actionLine)];
|
|
78
82
|
}
|
|
79
|
-
/** A `• stage` line (with initial/terminal markers) followed by its
|
|
83
|
+
/** A `• stage` line (with initial/terminal markers) followed by its activity blocks. */
|
|
80
84
|
function stageBlock(stage, initialStage) {
|
|
81
85
|
const title = stage.title ? styleText('dim', ` — ${stage.title}`) : '';
|
|
82
86
|
const terminal = isTerminalStage(stage) ? styleText('dim', ' (terminal)') : '';
|
|
83
87
|
const initial = stage.name === initialStage ? styleText('cyan', ' [initial]') : '';
|
|
84
88
|
return [
|
|
85
89
|
` • ${stage.name}${title}${terminal}${initial}`,
|
|
86
|
-
...(stage.
|
|
90
|
+
...(stage.activities ?? []).flatMap(activityBlock),
|
|
87
91
|
];
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
* The body lines for `definition show`: every stage with its
|
|
91
|
-
*
|
|
94
|
+
* The body lines for `definition show`: every stage with its activities and each
|
|
95
|
+
* activity's actions. Pure so it can be asserted without driving the oclif command.
|
|
92
96
|
*/
|
|
93
97
|
export function describeDefinition(def) {
|
|
94
98
|
return [
|
package/dist/commands/deploy.js
CHANGED
|
@@ -41,12 +41,12 @@ export default class Deploy extends Command {
|
|
|
41
41
|
}
|
|
42
42
|
const client = loadClient();
|
|
43
43
|
if (flags['dry-run']) {
|
|
44
|
-
await this.runDryRun(client, selected, config);
|
|
44
|
+
await this.runDryRun({ client, defs: selected, config });
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
await this.executeDeploy(client, selected, config);
|
|
47
|
+
await this.executeDeploy({ client, defs: selected, config });
|
|
48
48
|
}
|
|
49
|
-
async executeDeploy(client, defs, config) {
|
|
49
|
+
async executeDeploy({ client, defs, config, }) {
|
|
50
50
|
const spinner = ora(`Deploying ${defs.length} definition(s)…`).start();
|
|
51
51
|
try {
|
|
52
52
|
const { results } = await workflow.deployDefinitions({
|
|
@@ -64,10 +64,10 @@ export default class Deploy extends Command {
|
|
|
64
64
|
throw error;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
async runDryRun(client, defs, config) {
|
|
67
|
+
async runDryRun({ client, defs, config, }) {
|
|
68
68
|
const spinner = ora(`Diffing ${defs.length} definition(s) against dataset…`).start();
|
|
69
69
|
try {
|
|
70
|
-
const entries = await computeDiffEntries(client, defs, config);
|
|
70
|
+
const entries = await computeDiffEntries({ client, defs, target: config });
|
|
71
71
|
spinner.succeed(`Diffed ${defs.length} definition(s)`);
|
|
72
72
|
for (const line of diffReport(entries))
|
|
73
73
|
this.log(line);
|
|
@@ -88,7 +88,6 @@ export function validateModeFlags(check, dryRun) {
|
|
|
88
88
|
export function deployResultLines(results) {
|
|
89
89
|
const symbols = {
|
|
90
90
|
created: logSymbols.success,
|
|
91
|
-
updated: logSymbols.info,
|
|
92
91
|
};
|
|
93
92
|
return results.map((r) => {
|
|
94
93
|
const symbol = symbols[r.status] ?? logSymbols.warning;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { type Diagnosis, type DiagnoseInput, type SuggestedRemediation } from '@sanity/workflow-engine';
|
|
3
3
|
/**
|
|
4
|
-
* The diagnosis body: the verdict line, the current stage's
|
|
4
|
+
* The diagnosis body: the verdict line, the current stage's activities +
|
|
5
5
|
* transitions as evidence (for any in-flight state), and the cause block with
|
|
6
6
|
* the engine's suggested fix when stuck. Pure so every verdict permutation is
|
|
7
7
|
* assertable without driving a live evaluation. Takes the engine's
|
|
8
8
|
* {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
|
|
9
9
|
* fix block and the `--json` output stay one computation.
|
|
10
10
|
*/
|
|
11
|
-
export declare function renderDiagnosis(diagnosis
|
|
11
|
+
export declare function renderDiagnosis({ diagnosis, input, remediations, }: {
|
|
12
|
+
diagnosis: Diagnosis;
|
|
13
|
+
input: DiagnoseInput;
|
|
14
|
+
remediations: SuggestedRemediation[];
|
|
15
|
+
}): string[];
|
|
12
16
|
export default class Diagnose extends Command {
|
|
13
17
|
static description: string;
|
|
14
18
|
static examples: string[];
|
|
@@ -6,25 +6,28 @@ import { loadClient } from "../lib/client.js";
|
|
|
6
6
|
import { loadWorkflowConfig } from "../lib/config.js";
|
|
7
7
|
import { fail } from "../lib/fail.js";
|
|
8
8
|
import { tagFlags } from "../lib/flags.js";
|
|
9
|
-
import { formatTimestamp, sectionHeader,
|
|
9
|
+
import { formatTimestamp, sectionHeader, activityIcon } from "../lib/ui.js";
|
|
10
10
|
import { instanceHeader } from "./show.js";
|
|
11
11
|
function formatAssignee(a) {
|
|
12
12
|
return a.type === 'user' ? `user:${a.id}` : `role:${a.role}`;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function activityLine(t, assignees) {
|
|
15
15
|
const who = assignees.length > 0
|
|
16
16
|
? styleText('dim', ` assigned → ${assignees.map(formatAssignee).join(', ')}`)
|
|
17
17
|
: '';
|
|
18
|
-
return ` ${
|
|
18
|
+
return ` ${activityIcon[t.status]} ${t.activity.name} ${styleText('dim', `[${t.status}]`)}${who}`;
|
|
19
19
|
}
|
|
20
20
|
function transitionLine(tr) {
|
|
21
21
|
const mark = tr.filterSatisfied ? styleText('green', 'ready') : styleText('red', 'false');
|
|
22
22
|
return ` → ${tr.transition.to} ${styleText('dim', tr.transition.filter ?? 'true')} ${mark}`;
|
|
23
23
|
}
|
|
24
24
|
function currentStageLines(input) {
|
|
25
|
-
const lines = [
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const lines = [
|
|
26
|
+
`${sectionHeader('Stage')} ${input.instance.currentStage}`,
|
|
27
|
+
sectionHeader('Activities'),
|
|
28
|
+
];
|
|
29
|
+
for (const t of input.activities) {
|
|
30
|
+
lines.push(activityLine(t, input.assignees[t.activity.name] ?? []));
|
|
28
31
|
}
|
|
29
32
|
if (input.transitions.length > 0) {
|
|
30
33
|
lines.push('', sectionHeader('Exit transitions'));
|
|
@@ -37,14 +40,14 @@ function currentStageLines(input) {
|
|
|
37
40
|
function failedEffectDetail(effect) {
|
|
38
41
|
const ran = effect.durationMs !== undefined ? ` (after ${effect.durationMs}ms)` : '';
|
|
39
42
|
return {
|
|
40
|
-
headline: `a failed effect is blocking
|
|
43
|
+
headline: `a failed effect is blocking activity '${effect.origin.name}'`,
|
|
41
44
|
why: [
|
|
42
45
|
styleText('red', `${logSymbols.error} failed effect: ${effect.name}`),
|
|
43
|
-
` queued by
|
|
46
|
+
` queued by activity '${effect.origin.name}', failed ${formatTimestamp(effect.ranAt)}${ran}`,
|
|
44
47
|
...(effect.error !== undefined ? [` error: ${effect.error.message}`] : []),
|
|
45
48
|
'',
|
|
46
|
-
`
|
|
47
|
-
`external system, so the
|
|
49
|
+
`Activity '${effect.origin.name}' is waiting on this effect. It failed against an`,
|
|
50
|
+
`external system, so the activity never completes and the stage can't advance.`,
|
|
48
51
|
],
|
|
49
52
|
};
|
|
50
53
|
}
|
|
@@ -58,12 +61,12 @@ function hungEffectDetail(effect) {
|
|
|
58
61
|
],
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
|
-
function
|
|
64
|
+
function failedActivityDetail(activity) {
|
|
62
65
|
return {
|
|
63
|
-
headline: `
|
|
66
|
+
headline: `activity '${activity}' failed`,
|
|
64
67
|
why: [
|
|
65
|
-
styleText('red', `${logSymbols.error}
|
|
66
|
-
`Any exit transition gated on '${
|
|
68
|
+
styleText('red', `${logSymbols.error} activity '${activity}' is in a terminal failed state.`),
|
|
69
|
+
`Any exit transition gated on '${activity}' being done can never fire.`,
|
|
67
70
|
],
|
|
68
71
|
};
|
|
69
72
|
}
|
|
@@ -76,31 +79,31 @@ function waitingLines(w) {
|
|
|
76
79
|
? `assigned to ${w.assignees.map(formatAssignee).join(', ')}`
|
|
77
80
|
: 'unassigned — anyone with permission can act';
|
|
78
81
|
return [
|
|
79
|
-
`${logSymbols.info}
|
|
82
|
+
`${logSymbols.info} activity '${w.activity}' is active (${who}),`,
|
|
80
83
|
`waiting for action: ${w.actions.join(' or ')}. This is the normal in-flight`,
|
|
81
84
|
`state — it advances when someone acts.`,
|
|
82
85
|
];
|
|
83
86
|
}
|
|
84
87
|
function blockedHeadline(b) {
|
|
85
88
|
const who = b.assignees.length > 0 ? ` for ${b.assignees.map(formatAssignee).join(', ')}` : '';
|
|
86
|
-
return `
|
|
89
|
+
return `activity '${b.activity}'${who} is not yet ready — unmet requirement(s): ${b.requirements.join(', ')}`;
|
|
87
90
|
}
|
|
88
91
|
function blockedLines(b) {
|
|
89
92
|
const who = b.assignees.length > 0
|
|
90
93
|
? `assigned to ${b.assignees.map(formatAssignee).join(', ')}`
|
|
91
94
|
: 'unassigned';
|
|
92
95
|
return [
|
|
93
|
-
`${logSymbols.info}
|
|
96
|
+
`${logSymbols.info} activity '${b.activity}' is visible but not yet executable (${who}).`,
|
|
94
97
|
`Unmet requirement(s): ${b.requirements.join(', ')}. It can't be acted on until`,
|
|
95
98
|
`those preconditions are met — check what would satisfy them (a prerequisite`,
|
|
96
|
-
`
|
|
99
|
+
`activity completing, required content landing). It will not advance on its own.`,
|
|
97
100
|
];
|
|
98
101
|
}
|
|
99
102
|
function noTransitionDetail() {
|
|
100
103
|
return {
|
|
101
104
|
headline: `no exit transition's filter is satisfied`,
|
|
102
105
|
why: [
|
|
103
|
-
`${logSymbols.info} every
|
|
106
|
+
`${logSymbols.info} every activity is resolved, but no exit transition's filter is true.`,
|
|
104
107
|
`Likely a routing state value a filter reads never got written.`,
|
|
105
108
|
],
|
|
106
109
|
};
|
|
@@ -114,7 +117,7 @@ function transitionUnevaluableDetail(transitions) {
|
|
|
114
117
|
return {
|
|
115
118
|
headline: `an exit transition's filter could not be evaluated`,
|
|
116
119
|
why: [
|
|
117
|
-
`${logSymbols.info} every
|
|
120
|
+
`${logSymbols.info} every activity is resolved, but ${transitions.join(', ')} reads an operand`,
|
|
118
121
|
`that is missing or unreadable (GROQ null), so routing is held rather than`,
|
|
119
122
|
`falling through. Make the data the filter reads readable — publish the`,
|
|
120
123
|
`subject (or fill the field) — and the instance advances on its own; no`,
|
|
@@ -125,7 +128,7 @@ function transitionUnevaluableDetail(transitions) {
|
|
|
125
128
|
const REMEDIATION_LABEL = {
|
|
126
129
|
'retry-effect': 'retry-effect',
|
|
127
130
|
'drain-effects': 're-run the effect drainer',
|
|
128
|
-
'reset-
|
|
131
|
+
'reset-activity': 'reset-activity',
|
|
129
132
|
'set-stage': 'move-stage',
|
|
130
133
|
abort: 'abort',
|
|
131
134
|
};
|
|
@@ -142,8 +145,8 @@ function causeDetail(cause) {
|
|
|
142
145
|
return failedEffectDetail(cause.effect);
|
|
143
146
|
case 'hung-effect':
|
|
144
147
|
return hungEffectDetail(cause.effect);
|
|
145
|
-
case 'failed-
|
|
146
|
-
return
|
|
148
|
+
case 'failed-activity':
|
|
149
|
+
return failedActivityDetail(cause.activity);
|
|
147
150
|
case 'no-transition-fires':
|
|
148
151
|
return noTransitionDetail();
|
|
149
152
|
case 'transition-unevaluable':
|
|
@@ -169,14 +172,14 @@ function statusLine(diagnosis) {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
/**
|
|
172
|
-
* The diagnosis body: the verdict line, the current stage's
|
|
175
|
+
* The diagnosis body: the verdict line, the current stage's activities +
|
|
173
176
|
* transitions as evidence (for any in-flight state), and the cause block with
|
|
174
177
|
* the engine's suggested fix when stuck. Pure so every verdict permutation is
|
|
175
178
|
* assertable without driving a live evaluation. Takes the engine's
|
|
176
179
|
* {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
|
|
177
180
|
* fix block and the `--json` output stay one computation.
|
|
178
181
|
*/
|
|
179
|
-
export function renderDiagnosis(diagnosis, input, remediations) {
|
|
182
|
+
export function renderDiagnosis({ diagnosis, input, remediations, }) {
|
|
180
183
|
const lines = [statusLine(diagnosis)];
|
|
181
184
|
const inFlight = diagnosis.state === 'progressing' ||
|
|
182
185
|
diagnosis.state === 'waiting' ||
|
|
@@ -232,7 +235,7 @@ export default class Diagnose extends Command {
|
|
|
232
235
|
workflowResource: config.workflowResource,
|
|
233
236
|
instanceId: args.instanceId,
|
|
234
237
|
// Token-auth CLI can't hit /users/me; pin the same system actor the
|
|
235
|
-
// write verbs use. Transition filters and
|
|
238
|
+
// write verbs use. Transition filters and activity status are actor-
|
|
236
239
|
// independent. A requirement that reads $actor/$assigned/$can is the
|
|
237
240
|
// exception — it's evaluated against this system actor, so a `blocked`
|
|
238
241
|
// verdict on such a requirement reflects the engine's view, not a
|
|
@@ -252,7 +255,7 @@ export default class Diagnose extends Command {
|
|
|
252
255
|
}
|
|
253
256
|
this.log(instanceHeader(evaluation.instance));
|
|
254
257
|
this.log('');
|
|
255
|
-
for (const line of renderDiagnosis(diagnosis, input, remediations)) {
|
|
258
|
+
for (const line of renderDiagnosis({ diagnosis, input, remediations })) {
|
|
256
259
|
this.log(line);
|
|
257
260
|
}
|
|
258
261
|
}
|
|
@@ -9,12 +9,16 @@ import { type RanOp } from '../lib/ops-report.ts';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseParams(pairs: string[]): Record<string, unknown>;
|
|
11
11
|
/**
|
|
12
|
-
* The list-mode body: every action on the current stage's
|
|
12
|
+
* The list-mode body: every action on the current stage's activities, marked
|
|
13
13
|
* fireable or not (with the disabling reason), plus a hint showing the
|
|
14
14
|
* flags to fire one. Pure so the populated/empty permutations assert
|
|
15
15
|
* without a live evaluation.
|
|
16
16
|
*/
|
|
17
|
-
export declare function renderAvailableActions(actions
|
|
17
|
+
export declare function renderAvailableActions({ actions, stage, instanceId, }: {
|
|
18
|
+
actions: AvailableAction[];
|
|
19
|
+
stage: string;
|
|
20
|
+
instanceId: string;
|
|
21
|
+
}): string[];
|
|
18
22
|
interface FireOutcome {
|
|
19
23
|
fired: boolean;
|
|
20
24
|
cascaded: number;
|
|
@@ -27,7 +31,7 @@ interface FireOutcome {
|
|
|
27
31
|
* {@link fireActionReport}. Pure so its shape is asserted directly. */
|
|
28
32
|
export declare function fireResultJson(result: FireOutcome, ids: {
|
|
29
33
|
instanceId: string;
|
|
30
|
-
|
|
34
|
+
activity: string;
|
|
31
35
|
action: string;
|
|
32
36
|
}): Record<string, unknown>;
|
|
33
37
|
export interface FireActionReport {
|
|
@@ -40,7 +44,11 @@ export interface FireActionReport {
|
|
|
40
44
|
* the fired / no-op / cascaded / ops permutations assert without a live
|
|
41
45
|
* fire.
|
|
42
46
|
*/
|
|
43
|
-
export declare function fireActionReport(result
|
|
47
|
+
export declare function fireActionReport({ result, activity, action, }: {
|
|
48
|
+
result: FireOutcome;
|
|
49
|
+
activity: string;
|
|
50
|
+
action: string;
|
|
51
|
+
}): FireActionReport;
|
|
44
52
|
export default class FireAction extends Command {
|
|
45
53
|
static description: string;
|
|
46
54
|
static examples: string[];
|
|
@@ -48,7 +56,7 @@ export default class FireAction extends Command {
|
|
|
48
56
|
instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
49
57
|
};
|
|
50
58
|
static flags: {
|
|
51
|
-
|
|
59
|
+
activity: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
52
60
|
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
53
61
|
param: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
54
62
|
as: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|