@kici-dev/compiler 0.1.27 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/headless-detect.d.ts +14 -5
- package/dist/auth/headless-detect.js +50 -9
- package/dist/cli.js +120 -11
- package/dist/commands/compile.js +28 -4
- package/dist/commands/diagnostics.js +14 -2
- package/dist/commands/doctor.d.ts +64 -0
- package/dist/commands/doctor.js +413 -0
- package/dist/commands/endpoints.js +6 -0
- package/dist/commands/fixture.js +13 -0
- package/dist/commands/hook.js +3 -2
- package/dist/commands/index.d.ts +8 -0
- package/dist/commands/index.js +5 -1
- package/dist/commands/init-host-os.d.ts +34 -0
- package/dist/commands/init-host-os.js +44 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +159 -36
- package/dist/commands/local.d.ts +28 -2
- package/dist/commands/local.js +74 -5
- package/dist/commands/login.js +1 -1
- package/dist/commands/notifications.d.ts +61 -0
- package/dist/commands/notifications.js +274 -0
- package/dist/commands/preview.js +8 -1
- package/dist/commands/run-routed.js +10 -1
- package/dist/commands/run.js +41 -12
- package/dist/commands/runs/artifacts/download.d.ts +24 -0
- package/dist/commands/runs/artifacts/download.js +154 -0
- package/dist/commands/runs/artifacts/list.d.ts +5 -0
- package/dist/commands/runs/artifacts/list.js +47 -0
- package/dist/commands/runs/cancel.js +2 -2
- package/dist/commands/runs/list.js +8 -2
- package/dist/commands/verify-attestation.d.ts +3 -1
- package/dist/commands/verify-attestation.js +18 -7
- package/dist/errors/formatter.d.ts +14 -1
- package/dist/errors/formatter.js +13 -3
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +3 -2
- package/dist/errors/source-location.d.ts +20 -0
- package/dist/errors/source-location.js +45 -0
- package/dist/execution/executor.js +25 -5
- package/dist/fixtures/defaults/index.js +2 -1
- package/dist/llm-context/llms-architecture.txt +48 -19
- package/dist/llm-context/llms-cli.txt +2461 -1582
- package/dist/llm-context/llms-features.txt +178 -106
- package/dist/llm-context/llms-full.txt +6124 -4423
- package/dist/llm-context/llms-getting-started.txt +306 -42
- package/dist/llm-context/llms-patterns.txt +13 -7
- package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
- package/dist/llm-context/llms-sdk.txt +591 -2152
- package/dist/llm-context/llms.txt +30 -16
- package/dist/local-plane/orchestrator-process.d.ts +12 -7
- package/dist/local-plane/orchestrator-process.js +28 -14
- package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
- package/dist/local-plane/os-mismatch-hint.js +34 -0
- package/dist/local-plane/plane-liveness.d.ts +68 -0
- package/dist/local-plane/plane-liveness.js +145 -0
- package/dist/local-plane/plane-manager.d.ts +60 -18
- package/dist/local-plane/plane-manager.js +210 -70
- package/dist/local-plane/plane-trigger.d.ts +13 -4
- package/dist/local-plane/plane-trigger.js +27 -14
- package/dist/local-plane/platform-attach.js +1 -5
- package/dist/local-plane/port-holder.d.ts +78 -0
- package/dist/local-plane/port-holder.js +227 -0
- package/dist/local-plane/postgres.d.ts +15 -3
- package/dist/local-plane/postgres.js +34 -12
- package/dist/local-plane/run-follow.d.ts +20 -0
- package/dist/local-plane/run-follow.js +51 -4
- package/dist/local-plane/scaler-config.d.ts +28 -14
- package/dist/local-plane/scaler-config.js +59 -41
- package/dist/local-plane/source-provider.js +4 -10
- package/dist/lockfile/generator.d.ts +14 -1
- package/dist/lockfile/generator.js +99 -33
- package/dist/lockfile/index.d.ts +3 -1
- package/dist/lockfile/index.js +3 -2
- package/dist/lockfile/purity-diagnostics.d.ts +31 -0
- package/dist/lockfile/purity-diagnostics.js +52 -0
- package/dist/postinstall.js +2 -1
- package/dist/remote/artifact-extract.d.ts +8 -0
- package/dist/remote/artifact-extract.js +58 -0
- package/dist/remote/config.d.ts +21 -3
- package/dist/remote/config.js +24 -5
- package/dist/remote/dashboard-client.d.ts +39 -2
- package/dist/remote/dashboard-client.js +47 -4
- package/dist/remote/fs-case.d.ts +21 -0
- package/dist/remote/fs-case.js +45 -0
- package/dist/remote/history.d.ts +9 -3
- package/dist/remote/history.js +79 -10
- package/dist/remote/local-repo-identity.d.ts +1 -1
- package/dist/remote/local-repo-identity.js +2 -1
- package/dist/remote/notifications-client.d.ts +149 -0
- package/dist/remote/notifications-client.js +103 -0
- package/dist/remote/oauth.d.ts +13 -0
- package/dist/remote/oauth.js +31 -5
- package/dist/remote/output/summary.js +76 -14
- package/dist/remote/render.js +30 -9
- package/dist/remote/uploader.js +5 -4
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +6 -6
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +2 -2
- package/dist/templates/package-json.d.ts +15 -0
- package/dist/templates/package-json.js +26 -5
- package/dist/test-runner/dry-run.d.ts +2 -1
- package/dist/test-runner/dry-run.js +18 -1
- package/dist/test-runner/event-types.d.ts +2 -0
- package/dist/test-runner/event-types.js +3 -0
- package/dist/test-runner/job-executor.d.ts +1 -0
- package/dist/test-runner/job-executor.js +81 -43
- package/dist/test-runner/output-formatter.d.ts +5 -2
- package/dist/test-runner/output-formatter.js +6 -4
- package/dist/test-runner/payload-builder.d.ts +7 -1
- package/dist/test-runner/payload-builder.js +11 -2
- package/dist/test-runner/rule-evaluator.d.ts +7 -3
- package/dist/test-runner/rule-evaluator.js +13 -12
- package/dist/test-runner/step-context.d.ts +23 -5
- package/dist/test-runner/step-context.js +28 -118
- package/dist/types.d.ts +21 -2
- package/dist/types.js +4 -2
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -1
- package/dist/validation/typecheck.d.ts +28 -0
- package/dist/validation/typecheck.js +110 -0
- package/dist/validation/validator.d.ts +9 -4
- package/dist/validation/validator.js +38 -44
- package/package.json +12 -15
- package/sbom.spdx.json +1322 -1964
- package/dist/assets/api-TJJVHrjC.json +0 -118
- package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
- package/dist/assets/package-BpQF9kR8.json +0 -74
- package/dist/assets/package-Ceo2h27X.json +0 -89
- package/dist/assets/source_context-D0atuL28.json +0 -20
- package/dist/assets/type-BFqO8SCZ.json +0 -202
- package/dist/commands/cancel.d.ts +0 -22
- package/dist/commands/detect-package-manager.d.ts +0 -42
- package/dist/commands/held-run-resolve.d.ts +0 -50
- package/dist/commands/status.d.ts +0 -34
- package/dist/commands/test.d.ts +0 -88
- package/dist/errors/codes.d.ts +0 -25
- package/dist/local-executor/dag-scheduler.d.ts +0 -44
- package/dist/local-executor/index.d.ts +0 -23
- package/dist/local-executor/job-runner.d.ts +0 -47
- package/dist/local-executor/materializer.d.ts +0 -44
- package/dist/local-executor/output-streamer.d.ts +0 -31
- package/dist/local-executor/payload-generator.d.ts +0 -16
- package/dist/local-executor/picker.d.ts +0 -33
- package/dist/local-executor/runs-on-display.d.ts +0 -9
- package/dist/local-executor/secret-loader.d.ts +0 -18
- package/dist/local-executor/to-event-payload.d.ts +0 -16
- package/dist/local-executor/types.d.ts +0 -93
- package/dist/local-executor/workflow-lock.d.ts +0 -82
- package/dist/package-F7UXSDHW.json +0 -74
- package/dist/remote/client.d.ts +0 -210
- package/dist/remote/observer.d.ts +0 -81
- package/dist/test-runner/summary.d.ts +0 -7
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { DashboardClientError } from "../remote/dashboard-client.js";
|
|
3
|
+
import { NotificationChannelKind, NotificationScopeKind, NotificationSubLevel, NotificationsClient, RosterInputForm, RosterSubjectKind } from "../remote/notifications-client.js";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { toErrorMessage } from "@kici-dev/core";
|
|
6
|
+
//#region src/commands/notifications.ts
|
|
7
|
+
/**
|
|
8
|
+
* kici notifications commands
|
|
9
|
+
*
|
|
10
|
+
* Customer self-service management of an org's notification configuration —
|
|
11
|
+
* channels, subscriptions, and the Layer 1 Slack-identity roster — under the
|
|
12
|
+
* logged-in user's PAT against the hosted Platform's org-scoped HTTP API. This
|
|
13
|
+
* is a tenant-plane developer operation, so it lives in the public `kici` CLI
|
|
14
|
+
* (like `kici org` / `kici verify-attestation`), never the DB-direct
|
|
15
|
+
* `kici-platform-admin`.
|
|
16
|
+
*/
|
|
17
|
+
/** Split a comma-separated flag value into a trimmed, non-empty list. */
|
|
18
|
+
function parseCsv(value) {
|
|
19
|
+
if (!value) return [];
|
|
20
|
+
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21
|
+
}
|
|
22
|
+
/** Print a simple padded table. */
|
|
23
|
+
function printTable(headers, rows) {
|
|
24
|
+
if (rows.length === 0) {
|
|
25
|
+
console.log(pc.gray("None."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
29
|
+
console.log(pc.bold(headers.map((h, i) => h.padEnd(widths[i])).join(" ")));
|
|
30
|
+
for (const row of rows) console.log(row.map((c, i) => c.padEnd(widths[i])).join(" "));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load the client and run `fn`, turning any {@link DashboardClientError} into a
|
|
34
|
+
* red console message and a `false` return (the CLI exit-code convention).
|
|
35
|
+
*/
|
|
36
|
+
async function withClient(opts, fn) {
|
|
37
|
+
try {
|
|
38
|
+
return await fn(await NotificationsClient.load({ org: opts.org }));
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof DashboardClientError) {
|
|
41
|
+
console.error(pc.red(err.message));
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
console.error(pc.red(`Notifications command failed: ${toErrorMessage(err)}`));
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Validate a value against a Zod enum, printing an actionable error on miss. */
|
|
49
|
+
function requireEnum(label, value, options) {
|
|
50
|
+
if (value === void 0) {
|
|
51
|
+
console.error(pc.red(`--${label} is required (one of: ${options.join(", ")})`));
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (!options.includes(value)) {
|
|
55
|
+
console.error(pc.red(`Invalid --${label} "${value}" (one of: ${options.join(", ")})`));
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
async function notificationsChannelsListCommand(opts) {
|
|
61
|
+
return withClient(opts, async (client) => {
|
|
62
|
+
const channels = await client.listChannels();
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
console.log(JSON.stringify(channels, null, 2));
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
printTable([
|
|
68
|
+
"ID",
|
|
69
|
+
"TYPE",
|
|
70
|
+
"NAME",
|
|
71
|
+
"MANAGED"
|
|
72
|
+
], channels.map((c) => [
|
|
73
|
+
c.id,
|
|
74
|
+
c.type,
|
|
75
|
+
c.name,
|
|
76
|
+
c.managed ? "yes" : "no"
|
|
77
|
+
]));
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function notificationsChannelsAddCommand(opts) {
|
|
82
|
+
const type = requireEnum("type", opts.type, NotificationChannelKind.options);
|
|
83
|
+
if (!type) return false;
|
|
84
|
+
if (!opts.name) {
|
|
85
|
+
console.error(pc.red("--name is required"));
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
let body;
|
|
89
|
+
if (type === NotificationChannelKind.enum.slack) {
|
|
90
|
+
if (!opts.connection || !opts.slackChannel) {
|
|
91
|
+
console.error(pc.red("slack channels require --connection <id> and --slack-channel <id>"));
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
body = {
|
|
95
|
+
type: "slack",
|
|
96
|
+
connection_id: opts.connection,
|
|
97
|
+
slack_channel_id: opts.slackChannel,
|
|
98
|
+
name: opts.name
|
|
99
|
+
};
|
|
100
|
+
} else body = {
|
|
101
|
+
type: "email",
|
|
102
|
+
name: opts.name,
|
|
103
|
+
...opts.fromName ? { from_name: opts.fromName } : {},
|
|
104
|
+
...opts.replyTo ? { reply_to: opts.replyTo } : {}
|
|
105
|
+
};
|
|
106
|
+
return withClient(opts, async (client) => {
|
|
107
|
+
const channel = await client.createChannel(body);
|
|
108
|
+
console.log(pc.green(`Channel created: ${channel.id}`));
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function notificationsChannelsRemoveCommand(id, opts) {
|
|
113
|
+
return withClient(opts, async (client) => {
|
|
114
|
+
await client.deleteChannel(id);
|
|
115
|
+
console.log(pc.green(`Channel removed: ${id}`));
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function notificationsSubscriptionsListCommand(opts) {
|
|
120
|
+
return withClient(opts, async (client) => {
|
|
121
|
+
const subs = await client.listSubscriptions();
|
|
122
|
+
if (opts.json) {
|
|
123
|
+
console.log(JSON.stringify(subs, null, 2));
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
printTable([
|
|
127
|
+
"ID",
|
|
128
|
+
"LEVEL",
|
|
129
|
+
"SCOPE",
|
|
130
|
+
"CHANNEL",
|
|
131
|
+
"ON STATUS",
|
|
132
|
+
"ENABLED"
|
|
133
|
+
], subs.map((s) => [
|
|
134
|
+
s.id,
|
|
135
|
+
s.level,
|
|
136
|
+
s.scope_id ? `${s.scope_type}:${s.scope_id}` : s.scope_type,
|
|
137
|
+
s.channel_id,
|
|
138
|
+
(s.on_status ?? []).join(","),
|
|
139
|
+
s.enabled ? "yes" : "no"
|
|
140
|
+
]));
|
|
141
|
+
return true;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** Build a create-subscription body from parsed flags (null on validation error). */
|
|
145
|
+
function buildSubscriptionBody(opts) {
|
|
146
|
+
if (!opts.channel) {
|
|
147
|
+
console.error(pc.red("--channel <id> is required"));
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const level = requireEnum("level", opts.level ?? "run", NotificationSubLevel.options);
|
|
151
|
+
if (!level) return null;
|
|
152
|
+
const scope = requireEnum("scope", opts.scope ?? "org", NotificationScopeKind.options);
|
|
153
|
+
if (!scope) return null;
|
|
154
|
+
const onStatus = parseCsv(opts.onStatus);
|
|
155
|
+
if (onStatus.length === 0) {
|
|
156
|
+
console.error(pc.red("--on-status <csv> is required (e.g. failed,success)"));
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const needsScopeId = scope === NotificationScopeKind.enum.team || scope === NotificationScopeKind.enum.user;
|
|
160
|
+
if (needsScopeId && !opts.scopeId) {
|
|
161
|
+
console.error(pc.red(`--scope-id is required for ${scope} scope`));
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
let accumulateFor = null;
|
|
165
|
+
if (opts.accumulateFor !== void 0) {
|
|
166
|
+
accumulateFor = Number(opts.accumulateFor);
|
|
167
|
+
if (!Number.isFinite(accumulateFor)) {
|
|
168
|
+
console.error(pc.red("--accumulate-for must be a number of milliseconds"));
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const mentions = parseCsv(opts.mentions);
|
|
173
|
+
const recipientOverride = parseCsv(opts.recipientOverride);
|
|
174
|
+
const onFailureClass = parseCsv(opts.onFailureClass);
|
|
175
|
+
return {
|
|
176
|
+
level,
|
|
177
|
+
channel_id: opts.channel,
|
|
178
|
+
scope_type: scope,
|
|
179
|
+
scope_id: needsScopeId ? opts.scopeId : null,
|
|
180
|
+
on_status: onStatus,
|
|
181
|
+
repo_glob: opts.repoGlob ?? null,
|
|
182
|
+
workflow_glob: opts.workflowGlob ?? null,
|
|
183
|
+
job_glob: opts.jobGlob ?? null,
|
|
184
|
+
mentions: mentions.length > 0 ? mentions : null,
|
|
185
|
+
recipient_override: recipientOverride.length > 0 ? recipientOverride : null,
|
|
186
|
+
on_failure_class: onFailureClass.length > 0 ? onFailureClass : null,
|
|
187
|
+
accumulate_for: accumulateFor
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async function notificationsSubscriptionsAddCommand(opts) {
|
|
191
|
+
const body = buildSubscriptionBody(opts);
|
|
192
|
+
if (!body) return false;
|
|
193
|
+
return withClient(opts, async (client) => {
|
|
194
|
+
const sub = await client.createSubscription(body);
|
|
195
|
+
console.log(pc.green(`Subscription created: ${sub.id}`));
|
|
196
|
+
return true;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async function notificationsSubscriptionsRemoveCommand(id, opts) {
|
|
200
|
+
return withClient(opts, async (client) => {
|
|
201
|
+
await client.deleteSubscription(id);
|
|
202
|
+
console.log(pc.green(`Subscription removed: ${id}`));
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async function notificationsRosterListCommand(opts) {
|
|
207
|
+
return withClient(opts, async (client) => {
|
|
208
|
+
const identities = await client.listIdentities();
|
|
209
|
+
if (opts.json) {
|
|
210
|
+
console.log(JSON.stringify(identities, null, 2));
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
printTable([
|
|
214
|
+
"ID",
|
|
215
|
+
"SUBJECT KIND",
|
|
216
|
+
"SUBJECT",
|
|
217
|
+
"SLACK MEMBER",
|
|
218
|
+
"STATUS"
|
|
219
|
+
], identities.map((r) => [
|
|
220
|
+
r.id,
|
|
221
|
+
r.subject_kind,
|
|
222
|
+
r.subject_value,
|
|
223
|
+
r.slack_member_id,
|
|
224
|
+
r.status
|
|
225
|
+
]));
|
|
226
|
+
return true;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/** Build an add-identity body from parsed flags (null on validation error). */
|
|
230
|
+
function buildIdentityBody(opts) {
|
|
231
|
+
if (!opts.connection) {
|
|
232
|
+
console.error(pc.red("--connection <id> is required"));
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const subjectKind = requireEnum("subject-kind", opts.subjectKind, RosterSubjectKind.options);
|
|
236
|
+
if (!subjectKind) return null;
|
|
237
|
+
if (!opts.subject) {
|
|
238
|
+
console.error(pc.red("--subject <value> is required (the KiCI user sub, git login, or email)"));
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const inputForm = requireEnum("input-form", opts.inputForm ?? "id", RosterInputForm.options);
|
|
242
|
+
if (!inputForm) return null;
|
|
243
|
+
if (!opts.value) {
|
|
244
|
+
console.error(pc.red("--value <slack id | email | @handle> is required"));
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
connection_id: opts.connection,
|
|
249
|
+
subject_kind: subjectKind,
|
|
250
|
+
subject_value: opts.subject,
|
|
251
|
+
input_form: inputForm,
|
|
252
|
+
value: opts.value
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async function notificationsRosterAddCommand(opts) {
|
|
256
|
+
const body = buildIdentityBody(opts);
|
|
257
|
+
if (!body) return false;
|
|
258
|
+
return withClient(opts, async (client) => {
|
|
259
|
+
const row = await client.addIdentity(body);
|
|
260
|
+
console.log(pc.green(`Roster identity added: ${row.id} → ${row.slack_member_id}`));
|
|
261
|
+
return true;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async function notificationsRosterRemoveCommand(id, opts) {
|
|
265
|
+
return withClient(opts, async (client) => {
|
|
266
|
+
await client.deleteIdentity(id);
|
|
267
|
+
console.log(pc.green(`Roster identity removed: ${id}`));
|
|
268
|
+
return true;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand };
|
|
273
|
+
|
|
274
|
+
//# sourceMappingURL=notifications.js.map
|
package/dist/commands/preview.js
CHANGED
|
@@ -2,6 +2,8 @@ import "../rolldown-runtime-ClRpJifh.js";
|
|
|
2
2
|
import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
|
|
3
3
|
import "../execution/index.js";
|
|
4
4
|
import { transformTriggers } from "../lockfile/generator.js";
|
|
5
|
+
import { analyzeJobPurity } from "../lockfile/purity-diagnostics.js";
|
|
6
|
+
import "../lockfile/index.js";
|
|
5
7
|
import { displayDryRun } from "../test-runner/dry-run.js";
|
|
6
8
|
import { parseEventArg } from "../test-runner/event-types.js";
|
|
7
9
|
import { buildEventPayload } from "../test-runner/payload-builder.js";
|
|
@@ -88,10 +90,15 @@ async function previewEvent(event, options) {
|
|
|
88
90
|
return false;
|
|
89
91
|
}
|
|
90
92
|
}
|
|
93
|
+
const purityWarnings = [];
|
|
94
|
+
for (const w of workflows) for (const j of w.jobs) {
|
|
95
|
+
if (typeof j === "function") continue;
|
|
96
|
+
purityWarnings.push(...analyzeJobPurity(j, w.name));
|
|
97
|
+
}
|
|
91
98
|
displayDryRun(lockWorkflows, decisions, {
|
|
92
99
|
workflow: options.workflow,
|
|
93
100
|
job: options.job
|
|
94
|
-
});
|
|
101
|
+
}, purityWarnings);
|
|
95
102
|
return true;
|
|
96
103
|
} catch (error) {
|
|
97
104
|
const message = toErrorMessage(error);
|
|
@@ -2,7 +2,9 @@ import "../rolldown-runtime-ClRpJifh.js";
|
|
|
2
2
|
import { resolveKiciDir } from "../execution/executor.js";
|
|
3
3
|
import "../execution/index.js";
|
|
4
4
|
import { formatSummary } from "../remote/output/summary.js";
|
|
5
|
+
import { detectOsMismatchHints } from "../local-plane/os-mismatch-hint.js";
|
|
5
6
|
import { IN_PLACE_ROUTING_LABEL, TRUSTED_ROUTING_LABEL } from "../local-plane/scaler-config.js";
|
|
7
|
+
import { planeLogPath } from "../local-plane/plane-manager.js";
|
|
6
8
|
import { ensureLocalSource } from "../local-plane/plane-seed.js";
|
|
7
9
|
import { triggerRun } from "../local-plane/plane-trigger.js";
|
|
8
10
|
import { resolvePlaneForRun } from "../local-plane/resolve-plane.js";
|
|
@@ -76,6 +78,12 @@ async function runRouted(options) {
|
|
|
76
78
|
check: false,
|
|
77
79
|
verbose: options.debug ?? false
|
|
78
80
|
}))) return false;
|
|
81
|
+
if (!quiet) try {
|
|
82
|
+
const os = await import("node:os");
|
|
83
|
+
const lockRaw = readFileSync(path.join(kiciDir, "kici.lock.json"), "utf-8");
|
|
84
|
+
const hints = detectOsMismatchHints(JSON.parse(lockRaw), os.platform(), os.arch());
|
|
85
|
+
for (const hint of hints) logger.warn(pc.yellow(hint));
|
|
86
|
+
} catch {}
|
|
79
87
|
let cleanup;
|
|
80
88
|
const restoreLocks = [];
|
|
81
89
|
try {
|
|
@@ -146,7 +154,8 @@ async function runRouted(options) {
|
|
|
146
154
|
if (!quiet) logger.info(pc.green(`Run started: ${runId}`));
|
|
147
155
|
const outcome = await followRun(plane.url, plane.adminToken, runId, {
|
|
148
156
|
quiet,
|
|
149
|
-
onLine: (line) => process.stdout.write(line + "\n")
|
|
157
|
+
onLine: (line) => process.stdout.write(line + "\n"),
|
|
158
|
+
hintLogPath: planeLogPath()
|
|
150
159
|
});
|
|
151
160
|
if (!quiet) {
|
|
152
161
|
const result = {
|
package/dist/commands/run.js
CHANGED
|
@@ -4,7 +4,7 @@ import "../execution/index.js";
|
|
|
4
4
|
import { loadGlobalConfig } from "../remote/config.js";
|
|
5
5
|
import { RunHistory } from "../remote/history.js";
|
|
6
6
|
import { buildLocalRepoIdentity } from "../remote/local-repo-identity.js";
|
|
7
|
-
import { AccessDeniedError, AmbiguousClusterError, AuthenticationError, ConnectionError, NoClusterError, PlatformRunClient } from "../remote/platform-client.js";
|
|
7
|
+
import { AccessDeniedError, AmbiguousClusterError, AuthenticationError, ConnectionError, NoClusterError, NotFoundError, PlatformRunClient } from "../remote/platform-client.js";
|
|
8
8
|
import { buildEncryptedSecrets } from "../remote/secret-upload.js";
|
|
9
9
|
import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/uploader.js";
|
|
10
10
|
import { formatJsonResult } from "../remote/output/json.js";
|
|
@@ -20,17 +20,22 @@ import path from "node:path";
|
|
|
20
20
|
import pc from "picocolors";
|
|
21
21
|
import { readFile, writeFile } from "node:fs/promises";
|
|
22
22
|
import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
|
|
23
|
-
import { coerceDispatchInputs, parseInputPairs } from "@kici-dev/engine";
|
|
23
|
+
import { TERMINAL_RUN_STATES, coerceDispatchInputs, parseInputPairs, toCanonicalStatus } from "@kici-dev/engine";
|
|
24
24
|
import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
25
25
|
import { confirm } from "@inquirer/prompts";
|
|
26
26
|
//#region src/commands/run.ts
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
/**
|
|
28
|
+
* True when the Platform run-status snapshot reports a terminal run status.
|
|
29
|
+
*
|
|
30
|
+
* Resolves through the engine's canonical vocabulary rather than matching a
|
|
31
|
+
* hand-written literal set: that is what makes the legacy `error` spelling
|
|
32
|
+
* terminal without a second copy of the alias, and what stops a newly-added
|
|
33
|
+
* terminal status leaving `kici run` polling a finished run forever.
|
|
34
|
+
*/
|
|
35
|
+
function isTerminalRunStatus(status) {
|
|
36
|
+
const canonical = toCanonicalStatus(status.toLowerCase());
|
|
37
|
+
return canonical !== void 0 && TERMINAL_RUN_STATES.has(canonical);
|
|
38
|
+
}
|
|
34
39
|
/**
|
|
35
40
|
* Compile `--target` selector strings into a {@link HostTargetSelector}. Each
|
|
36
41
|
* string becomes one AND value (its own include set); repeated values
|
|
@@ -91,6 +96,19 @@ function buildDispatchInputs(pairs, descriptor) {
|
|
|
91
96
|
/** Interval between status/log polls while a run is active. */
|
|
92
97
|
const POLL_INTERVAL_MS = 750;
|
|
93
98
|
/**
|
|
99
|
+
* How long a freshly triggered run may still read as missing before a 404 is
|
|
100
|
+
* reported as a real error.
|
|
101
|
+
*
|
|
102
|
+
* The trigger call answers with the run id as soon as the orchestrator has
|
|
103
|
+
* created the run, while the Platform's view of that run arrives over the
|
|
104
|
+
* relay a moment later. The first poll therefore fires while the run is not
|
|
105
|
+
* yet readable and legitimately 404s. Within this budget — and only until the
|
|
106
|
+
* first successful read — a 404 means "not visible yet" and the poll retries;
|
|
107
|
+
* after it, or once the run has been read at least once, a 404 is surfaced as
|
|
108
|
+
* a genuinely missing run.
|
|
109
|
+
*/
|
|
110
|
+
const RUN_VISIBILITY_GRACE_MS = 3e4;
|
|
111
|
+
/**
|
|
94
112
|
* Run `fn` with everything written to `process.stdout` redirected to
|
|
95
113
|
* `process.stderr`, then restore the original writer. Compiling a repo
|
|
96
114
|
* evaluates every user workflow / fixture module, and a `console.log` at a
|
|
@@ -466,16 +484,27 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
|
|
|
466
484
|
let lastStatus = null;
|
|
467
485
|
const seenHolds = /* @__PURE__ */ new Set();
|
|
468
486
|
let heldCtx;
|
|
487
|
+
let runSeen = false;
|
|
469
488
|
while (!cancelled) {
|
|
470
|
-
|
|
489
|
+
let logs;
|
|
490
|
+
try {
|
|
491
|
+
logs = await ctx.client.runLogs(ctx.orgId, runId, cursor, ctx.target);
|
|
492
|
+
lastStatus = await ctx.client.runStatus(ctx.orgId, runId, ctx.target);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
if (err instanceof NotFoundError && !runSeen && Date.now() - startTime < RUN_VISIBILITY_GRACE_MS) {
|
|
495
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
throw err;
|
|
499
|
+
}
|
|
500
|
+
runSeen = true;
|
|
471
501
|
for (const line of logs.lines) {
|
|
472
502
|
if (!options.quiet) process.stdout.write(line + "\n");
|
|
473
503
|
tailLines.push(line);
|
|
474
504
|
while (tailLines.length > MAX_TAIL) tailLines.shift();
|
|
475
505
|
}
|
|
476
506
|
cursor = logs.nextCursor;
|
|
477
|
-
|
|
478
|
-
const terminal = lastStatus.done || TERMINAL_STATUSES.has(lastStatus.status);
|
|
507
|
+
const terminal = lastStatus.done || isTerminalRunStatus(lastStatus.status);
|
|
479
508
|
if (terminal && logs.done) return finishRun(fixtureId, runId, lastStatus, startTime, tailLines, options);
|
|
480
509
|
if (!terminal) await watchRunHolds(runId, seenHolds, () => heldCtx, (c) => heldCtx = c, options);
|
|
481
510
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface RunsArtifactsDownloadOptions {
|
|
2
|
+
archive?: boolean;
|
|
3
|
+
output?: string;
|
|
4
|
+
}
|
|
5
|
+
/** Basename an artifact takes under the output directory in the given mode. */
|
|
6
|
+
export declare function destBasename(name: string, archive: boolean): string;
|
|
7
|
+
/**
|
|
8
|
+
* Reduce a basename to the form a case-insensitive filesystem looks it up by.
|
|
9
|
+
*
|
|
10
|
+
* The NFC pass is deliberate defence for a widened artifact-name charset:
|
|
11
|
+
* `ArtifactNameSchema` accepts ASCII only today, so no live name can differ by
|
|
12
|
+
* normalisation — but HFS+ stores NFD and APFS folds case while preserving
|
|
13
|
+
* normalisation, so a non-ASCII name would collide on those volumes without it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function foldBasename(basename: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Group basenames that a case-insensitive filesystem resolves to one path.
|
|
18
|
+
*
|
|
19
|
+
* Groups appear in first-appearance order and each group is sorted; a basename
|
|
20
|
+
* with no partner is not reported.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findCaseCollisions(basenames: string[]): string[][];
|
|
23
|
+
export declare function runsArtifactsDownloadCommand(runId: string, name: string | undefined, options?: RunsArtifactsDownloadOptions): Promise<boolean>;
|
|
24
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import "../../../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { extractArtifactTarball } from "../../../remote/artifact-extract.js";
|
|
3
|
+
import { DashboardClient, DashboardClientError } from "../../../remote/dashboard-client.js";
|
|
4
|
+
import { isCaseInsensitiveDir } from "../../../remote/fs-case.js";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { logger, sha256, toErrorMessage } from "@kici-dev/core";
|
|
9
|
+
import { ArtifactNameSchema } from "@kici-dev/engine";
|
|
10
|
+
//#region src/commands/runs/artifacts/download.ts
|
|
11
|
+
/** kici runs artifacts download — fetch one artifact (by name) or every artifact of a run. */
|
|
12
|
+
/** Extension of the raw artifact archive written by `--archive`. */
|
|
13
|
+
const ARCHIVE_EXTENSION = ".tar.gz";
|
|
14
|
+
/** Basename an artifact takes under the output directory in the given mode. */
|
|
15
|
+
function destBasename(name, archive) {
|
|
16
|
+
return archive ? `${name}${ARCHIVE_EXTENSION}` : name;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reduce a basename to the form a case-insensitive filesystem looks it up by.
|
|
20
|
+
*
|
|
21
|
+
* The NFC pass is deliberate defence for a widened artifact-name charset:
|
|
22
|
+
* `ArtifactNameSchema` accepts ASCII only today, so no live name can differ by
|
|
23
|
+
* normalisation — but HFS+ stores NFD and APFS folds case while preserving
|
|
24
|
+
* normalisation, so a non-ASCII name would collide on those volumes without it.
|
|
25
|
+
*/
|
|
26
|
+
function foldBasename(basename) {
|
|
27
|
+
return basename.normalize("NFC").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Group basenames that a case-insensitive filesystem resolves to one path.
|
|
31
|
+
*
|
|
32
|
+
* Groups appear in first-appearance order and each group is sorted; a basename
|
|
33
|
+
* with no partner is not reported.
|
|
34
|
+
*/
|
|
35
|
+
function findCaseCollisions(basenames) {
|
|
36
|
+
const byFold = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const basename of basenames) {
|
|
38
|
+
const fold = foldBasename(basename);
|
|
39
|
+
const group = byFold.get(fold);
|
|
40
|
+
if (group) group.add(basename);
|
|
41
|
+
else byFold.set(fold, /* @__PURE__ */ new Set([basename]));
|
|
42
|
+
}
|
|
43
|
+
return [...byFold.values()].filter((g) => g.size > 1).map((g) => [...g].sort());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Fetch a presigned artifact URL into a Buffer and verify its sha-256.
|
|
47
|
+
*
|
|
48
|
+
* NO `Authorization` header: `downloadUrl` is a presigned object-storage GET,
|
|
49
|
+
* and the PAT authenticates to the Platform, not to object storage — attaching
|
|
50
|
+
* it would leak the credential to the storage host. The bytes therefore flow
|
|
51
|
+
* CLI <-> object storage directly and never transit the Platform.
|
|
52
|
+
*/
|
|
53
|
+
async function fetchArtifactBytes(entry) {
|
|
54
|
+
if (!entry.downloadUrl) throw new Error(`artifact "${entry.name}" has no download URL (the stored object is missing or expired)`);
|
|
55
|
+
const res = await fetch(entry.downloadUrl);
|
|
56
|
+
if (!res.ok) throw new Error(`download of "${entry.name}" failed (HTTP ${res.status})`);
|
|
57
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
58
|
+
const actual = sha256(buf);
|
|
59
|
+
if (actual !== entry.sha256) throw new Error(`artifact "${entry.name}" checksum mismatch: expected ${entry.sha256}, got ${actual}`);
|
|
60
|
+
return buf;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Refuse a listed name that is not a valid artifact name.
|
|
64
|
+
*
|
|
65
|
+
* Every name is validated against {@link ArtifactNameSchema} at upload, so a
|
|
66
|
+
* response carrying one outside that contract comes from a broken or hostile
|
|
67
|
+
* peer. The name becomes a path component under the output directory, so it is
|
|
68
|
+
* checked before it is joined — an unchecked `../…` would let a list response
|
|
69
|
+
* write anywhere on the caller's filesystem.
|
|
70
|
+
*/
|
|
71
|
+
function assertPathSafeName(name) {
|
|
72
|
+
if (!ArtifactNameSchema.safeParse(name).success) throw new Error(`listed artifact "${name}" is not a valid artifact name — refusing to write it under the output directory`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Refuse the whole invocation when two targets would land on the same path.
|
|
76
|
+
*
|
|
77
|
+
* Artifact names are case-sensitive, so a run can legitimately hold both
|
|
78
|
+
* `bundle` and `Bundle`. On a filesystem that ignores case in path lookups the
|
|
79
|
+
* two resolve to one path — the second extraction merges into the first's
|
|
80
|
+
* directory, or overwrites its archive under `--archive`. The filesystem is
|
|
81
|
+
* probed only once a fold collision is known to exist, so a case-sensitive
|
|
82
|
+
* output directory downloads both exactly as before. A probe that cannot answer
|
|
83
|
+
* is treated as case-insensitive: the collision is already certain, so the
|
|
84
|
+
* unknown half is resolved towards refusing rather than towards losing data.
|
|
85
|
+
*/
|
|
86
|
+
async function assertNoCaseCollisions(targets, outDir, archive) {
|
|
87
|
+
const groups = findCaseCollisions(targets.map((t) => destBasename(t.name, archive)));
|
|
88
|
+
if (groups.length === 0) return;
|
|
89
|
+
let probeError;
|
|
90
|
+
let caseInsensitive = true;
|
|
91
|
+
try {
|
|
92
|
+
caseInsensitive = await isCaseInsensitiveDir(outDir);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
probeError = err;
|
|
95
|
+
}
|
|
96
|
+
if (!caseInsensitive) return;
|
|
97
|
+
const pairs = groups.map((g) => g.map((basename) => `"${basename}"`).join(" and ")).join("; ");
|
|
98
|
+
const consequence = archive ? "writing both would overwrite one file with the other" : "extracting both would merge them into one directory";
|
|
99
|
+
const reason = probeError !== void 0 ? `the output directory ${outDir} could not be checked for case sensitivity (${toErrorMessage(probeError)})` : `the output directory ${outDir} is on a case-insensitive filesystem`;
|
|
100
|
+
throw new Error(`this run holds artifacts whose names differ only in case: ${pairs} — ${consequence}, and ${reason}. Download them one at a time into separate directories: kici runs artifacts download <run-id> <name> -o <dir>`);
|
|
101
|
+
}
|
|
102
|
+
/** Fetch one entry and write it to `outDir`, extracted or as the raw archive. */
|
|
103
|
+
async function writeOne(entry, outDir, archive) {
|
|
104
|
+
assertPathSafeName(entry.name);
|
|
105
|
+
const buf = await fetchArtifactBytes(entry);
|
|
106
|
+
await mkdir(outDir, { recursive: true });
|
|
107
|
+
const dest = join(outDir, destBasename(entry.name, archive));
|
|
108
|
+
if (archive) {
|
|
109
|
+
await writeFile(dest, buf);
|
|
110
|
+
console.log(pc.green(`✓ ${entry.name} → ${dest}`));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await extractArtifactTarball(buf, dest);
|
|
114
|
+
console.log(pc.green(`✓ ${entry.name} → ${dest}/`));
|
|
115
|
+
}
|
|
116
|
+
/** Resolve which listed artifacts the invocation targets. */
|
|
117
|
+
function resolveTargets(artifacts, name, runId) {
|
|
118
|
+
if (!name) return artifacts;
|
|
119
|
+
const match = artifacts.find((a) => a.name === name);
|
|
120
|
+
if (match) return [match];
|
|
121
|
+
const names = artifacts.map((a) => a.name).join(", ") || "(none)";
|
|
122
|
+
throw new Error(`artifact "${name}" not found in run ${runId}. Available: ${names}`);
|
|
123
|
+
}
|
|
124
|
+
async function runsArtifactsDownloadCommand(runId, name, options = {}) {
|
|
125
|
+
try {
|
|
126
|
+
const { artifacts } = await (await DashboardClient.load()).listArtifacts(runId);
|
|
127
|
+
const outDir = options.output ?? process.cwd();
|
|
128
|
+
const downloadingAll = !name;
|
|
129
|
+
const targets = resolveTargets(artifacts, name, runId);
|
|
130
|
+
if (targets.length === 0) {
|
|
131
|
+
console.log(pc.gray("No artifacts for this run."));
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
for (const entry of targets) assertPathSafeName(entry.name);
|
|
135
|
+
if (downloadingAll) await assertNoCaseCollisions(targets.filter((t) => t.downloadUrl), outDir, options.archive ?? false);
|
|
136
|
+
let failures = 0;
|
|
137
|
+
for (const entry of targets) {
|
|
138
|
+
if (downloadingAll && !entry.downloadUrl) {
|
|
139
|
+
logger.warn(pc.yellow(`! ${entry.name} skipped — no download URL (object missing or expired)`));
|
|
140
|
+
failures++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await writeOne(entry, outDir, options.archive ?? false);
|
|
144
|
+
}
|
|
145
|
+
return failures < targets.length;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { destBasename, findCaseCollisions, foldBasename, runsArtifactsDownloadCommand };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "../../../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { DashboardClient, DashboardClientError } from "../../../remote/dashboard-client.js";
|
|
3
|
+
import { relativeTime, renderTable } from "../../../remote/render.js";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
|
|
6
|
+
//#region src/commands/runs/artifacts/list.ts
|
|
7
|
+
/** kici runs artifacts list — the artifacts a run uploaded (the rows the dashboard Artifacts tab shows). */
|
|
8
|
+
/** Number of leading sha-256 hex chars shown in the table (full digest via --json). */
|
|
9
|
+
const SHA_PREFIX_LENGTH = 12;
|
|
10
|
+
async function runsArtifactsListCommand(runId, options = {}) {
|
|
11
|
+
try {
|
|
12
|
+
const result = await (await DashboardClient.load()).listArtifacts(runId);
|
|
13
|
+
if (options.json) {
|
|
14
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (result.artifacts.length === 0) {
|
|
18
|
+
console.log(pc.gray("No artifacts for this run."));
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const rows = result.artifacts.map((a) => [
|
|
22
|
+
a.name,
|
|
23
|
+
a.jobId,
|
|
24
|
+
formatBytes(a.sizeBytes),
|
|
25
|
+
a.sha256.slice(0, SHA_PREFIX_LENGTH),
|
|
26
|
+
relativeTime(a.createdAt),
|
|
27
|
+
a.downloadUrl ? "" : pc.yellow("unavailable")
|
|
28
|
+
]);
|
|
29
|
+
console.log(renderTable([
|
|
30
|
+
"name",
|
|
31
|
+
"job",
|
|
32
|
+
"size",
|
|
33
|
+
"sha-256",
|
|
34
|
+
"created",
|
|
35
|
+
""
|
|
36
|
+
], rows));
|
|
37
|
+
console.log(pc.gray(`\n${result.artifacts.length} artifact(s). Download with kici runs artifacts download ${runId} <name>`));
|
|
38
|
+
return true;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { runsArtifactsListCommand };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -18,8 +18,8 @@ async function runsCancelCommand(runId, options = {}) {
|
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
logger.info(`${options.force ? "Force-cancelling" : "Cancelling"} run ${pc.cyan(runId)}...`);
|
|
21
|
-
const { cancelledJobs = 0 } = await client.cancelRun(runId, options.force ?? false);
|
|
22
|
-
console.log(pc.green(`Run ${runId} cancelled (${cancelledJobs} job${cancelledJobs !== 1 ? "s" : ""} affected).`));
|
|
21
|
+
const { cancelledJobs = 0, alreadyTerminal } = await client.cancelRun(runId, options.force ?? false);
|
|
22
|
+
console.log(alreadyTerminal ? pc.yellow(`Run ${runId} had already finished; nothing was cancelled.`) : pc.green(`Run ${runId} cancelled (${cancelledJobs} job${cancelledJobs !== 1 ? "s" : ""} affected).`));
|
|
23
23
|
return true;
|
|
24
24
|
} catch (err) {
|
|
25
25
|
logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
|