@toloka-platform/sdk 0.1.0-preview.6 → 0.1.0-preview.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -15
- package/dist/cli/toloka.js +209 -121
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/authoring.md +20 -9
- package/docs/cli-v2.md +3 -3
- package/docs/cli.md +44 -16
- package/docs/getting-started.md +7 -12
- package/docs/limitations.md +6 -6
- package/docs/testing-examples.md +248 -0
- package/docs/testing.md +115 -0
- package/examples/face-image-collection/README.md +7 -1
- package/examples/face-image-collection/fields.ts +20 -8
- package/examples/face-image-collection/nodes/annotate-faces/node.ts +1 -9
- package/examples/face-image-collection/nodes/order-faces/node.ts +3 -5
- package/examples/face-image-collection/pipeline.browser.integration.spec.ts +78 -0
- package/examples/video-pair-rating/README.md +14 -6
- package/examples/video-pair-rating/nodes/adjudicate/node.ts +1 -3
- package/examples/video-pair-rating/nodes/adjudicate_op/node.ts +1 -3
- package/examples/video-pair-rating/nodes/adjudication_match/node.ts +15 -17
- package/examples/video-pair-rating/nodes/assemble/node.ts +25 -27
- package/examples/video-pair-rating/nodes/cascade/node.ts +24 -26
- package/examples/video-pair-rating/nodes/finalize_overall/node.ts +17 -19
- package/examples/video-pair-rating/nodes/merge_adjudication/node.ts +13 -15
- package/examples/video-pair-rating/nodes/merge_op_adjudication/node.ts +12 -14
- package/examples/video-pair-rating/nodes/op_quorum_resolve/node.ts +13 -15
- package/examples/video-pair-rating/nodes/overall_preference_pick/node.ts +1 -1
- package/examples/video-pair-rating/nodes/rate_pair/node.ts +1 -1
- package/examples/video-pair-rating/nodes/route_adjudication/node.ts +1 -3
- package/examples/video-pair-rating/nodes/route_op_adjudication/node.ts +5 -7
- package/examples/video-pair-rating/nodes/route_overall/node.ts +1 -3
- package/examples/video-pair-rating/nodes/shuffle_ab/node.ts +11 -13
- package/examples/video-pair-rating/pipeline.browser.integration.spec.ts +267 -0
- package/openapi/gen/api.ts +48 -11
- package/openapi/gen/models/createDatasetExportBody.ts +3 -3
- package/openapi/gen/models/createDatasetExportBodyOneOf.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThree.ts +10 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeContract.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilter.ts +9 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/datasetExportReport.ts +9 -0
- package/openapi/gen/models/datasetExportReportErrorsItem.ts +5 -0
- package/openapi/gen/models/datasetExportReportTokens.ts +7 -0
- package/openapi/gen/models/datasetItem.ts +0 -2
- package/openapi/gen/models/exportJob.ts +2 -0
- package/openapi/gen/models/exportJobReport.ts +3 -0
- package/openapi/gen/models/index.ts +48 -1
- package/openapi/gen/models/sampleDatasetItemsBody.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOf.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethree.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleJob.ts +13 -0
- package/openapi/gen/models/sampleJobStatus.ts +2 -0
- package/openapi/gen/models/sampleResult.ts +5 -0
- package/package.json +23 -2
- package/skills/catalog.json +14 -6
- package/skills/toloka-build/SKILL.md +35 -12
- package/skills/toloka-build/references/node-semantics.md +32 -29
- package/skills/toloka-build/references/workflow-and-cli.md +61 -27
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +27 -26
- package/src/checks/ast.ts +32 -0
- package/src/checks/index.ts +14 -0
- package/src/checks/postprocess-collisions.ts +145 -0
- package/src/checks/task-ui-declarations.generated.ts +19 -0
- package/src/checks/task-ui-typecheck.ts +345 -0
- package/src/checks/typescript-vfs.d.ts +26 -0
- package/src/checks/typescript-vfs.js +5 -0
- package/src/cli/analytics.ts +9 -19
- package/src/cli/auto-update.ts +367 -0
- package/src/cli/boolean-flag.ts +13 -0
- package/src/cli/check.ts +977 -0
- package/src/cli/credentials.ts +3 -0
- package/src/cli/program.ts +184 -20
- package/src/cli/project-context.ts +14 -4
- package/src/cli/runs.ts +21 -11
- package/src/cli/settings.ts +93 -0
- package/src/cli/skill.ts +17 -6
- package/src/importer.ts +12 -16
- package/src/testing/code-node.ts +160 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/task-ui-browser.ts +120 -0
- package/src/testing/task-ui-validation.ts +135 -0
- package/src/testing/task-ui.ts +557 -0
- package/src/testing/test-evidence.ts +170 -0
- package/src/testing/types.ts +141 -0
- package/openapi/gen/models/datasetItemDuplicateOfSubmissionId.ts +0 -2
package/src/cli/credentials.ts
CHANGED
|
@@ -52,6 +52,9 @@ export const credentialsPath = (environment: CliEnvironment = process.env): stri
|
|
|
52
52
|
export const legacyCredentialsPath = (environment: CliEnvironment = process.env): string =>
|
|
53
53
|
join(home(environment), ".toloka", "credentials.json");
|
|
54
54
|
|
|
55
|
+
export const configDirectory = (environment: CliEnvironment = process.env): string =>
|
|
56
|
+
dirname(credentialsPath(environment));
|
|
57
|
+
|
|
55
58
|
const removeTrailingSlashes = (value: string): string => {
|
|
56
59
|
let end = value.length;
|
|
57
60
|
while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
|
package/src/cli/program.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
2
2
|
import { assert } from "es-toolkit";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
3
4
|
import { resolve } from "node:path";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { createClient } from "../client";
|
|
6
7
|
import type { PotatoClient } from "../client";
|
|
7
8
|
import { readTolokaState } from "../state";
|
|
8
9
|
import type { TolokaState } from "../state";
|
|
10
|
+
import {
|
|
11
|
+
CHECK_RESULT_FILE_ENV,
|
|
12
|
+
checkProject,
|
|
13
|
+
watchProjectChecks,
|
|
14
|
+
runFreshCheck,
|
|
15
|
+
type CheckProjectOptions,
|
|
16
|
+
type CheckProjectResult,
|
|
17
|
+
} from "./check";
|
|
9
18
|
import { authStatus, login, logout } from "./auth";
|
|
10
19
|
import type { AuthPrompt } from "./auth";
|
|
11
20
|
import { commandNameFromArgv, createCliAnalytics } from "./analytics";
|
|
12
21
|
import type { CliAnalytics, CliAnalyticsCommandOutcome } from "./analytics";
|
|
22
|
+
import type { AutoUpdateHooks } from "./auto-update";
|
|
23
|
+
import { maybeAutoUpdate } from "./auto-update";
|
|
13
24
|
import { PRODUCTION_BASE_URL, resolveInternalBaseUrl, resolveTolokaConfig } from "./config";
|
|
14
25
|
import type { TolokaEnvironment } from "./config";
|
|
15
26
|
import {
|
|
@@ -18,6 +29,7 @@ import {
|
|
|
18
29
|
requireStoredCredentials,
|
|
19
30
|
} from "./credentials";
|
|
20
31
|
import type { StoredCredentials } from "./credentials";
|
|
32
|
+
import { getCliSetting, readCliSettings, setCliSetting } from "./settings";
|
|
21
33
|
import {
|
|
22
34
|
generateDatasetFieldDeclarations,
|
|
23
35
|
listProjectDatasets,
|
|
@@ -83,6 +95,7 @@ export type ProgramContext = {
|
|
|
83
95
|
readonly environment: TolokaEnvironment;
|
|
84
96
|
readonly io: CliIo;
|
|
85
97
|
readonly injectedClient?: PotatoClient;
|
|
98
|
+
readonly runChecks?: typeof checkProject;
|
|
86
99
|
readonly analytics?: CliAnalytics;
|
|
87
100
|
};
|
|
88
101
|
|
|
@@ -202,6 +215,29 @@ export type CreatedProgram = {
|
|
|
202
215
|
export function createProgram(context: ProgramContext): CreatedProgram {
|
|
203
216
|
const program = new Command("toloka");
|
|
204
217
|
const exitCode = { value: 0 };
|
|
218
|
+
const runChecks = context.runChecks ?? checkProject;
|
|
219
|
+
const runChecksInterruptibly = async (
|
|
220
|
+
options: CheckProjectOptions,
|
|
221
|
+
runner: (options: CheckProjectOptions) => Promise<CheckProjectResult> = runChecks,
|
|
222
|
+
): Promise<{ result: CheckProjectResult; interruptedExitCode?: number }> => {
|
|
223
|
+
const abortController = new AbortController();
|
|
224
|
+
let interruptedExitCode: number | undefined;
|
|
225
|
+
const interrupt = (exitCode: number) => {
|
|
226
|
+
interruptedExitCode = exitCode;
|
|
227
|
+
abortController.abort();
|
|
228
|
+
};
|
|
229
|
+
const interruptSigint = () => interrupt(130);
|
|
230
|
+
const interruptSigterm = () => interrupt(143);
|
|
231
|
+
process.on("SIGINT", interruptSigint);
|
|
232
|
+
process.on("SIGTERM", interruptSigterm);
|
|
233
|
+
try {
|
|
234
|
+
const result = await runner({ ...options, signal: abortController.signal });
|
|
235
|
+
return { result, ...(interruptedExitCode === undefined ? {} : { interruptedExitCode }) };
|
|
236
|
+
} finally {
|
|
237
|
+
process.off("SIGINT", interruptSigint);
|
|
238
|
+
process.off("SIGTERM", interruptSigterm);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
205
241
|
let warnedAboutStaleSkills = false;
|
|
206
242
|
|
|
207
243
|
program
|
|
@@ -236,7 +272,8 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
236
272
|
commandNames.includes("help") ||
|
|
237
273
|
commandNames.includes("report") ||
|
|
238
274
|
commandNames.includes("url") ||
|
|
239
|
-
commandNames.includes("version")
|
|
275
|
+
commandNames.includes("version") ||
|
|
276
|
+
commandNames.includes("config")
|
|
240
277
|
)
|
|
241
278
|
return;
|
|
242
279
|
const sourceRoot = await resolveSourceRoot(globals(), context.io);
|
|
@@ -250,6 +287,29 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
250
287
|
.description("print the current CLI version")
|
|
251
288
|
.action(() => context.io.stdout(SDK_VERSION));
|
|
252
289
|
|
|
290
|
+
const config = program.command("config").description("read and write machine CLI settings");
|
|
291
|
+
config
|
|
292
|
+
.command("get")
|
|
293
|
+
.argument("[name]", "setting name; omit to print every setting")
|
|
294
|
+
.description("print a CLI setting, or every setting")
|
|
295
|
+
.action(async (name?: string) => {
|
|
296
|
+
const settings = await readCliSettings(context.environment);
|
|
297
|
+
context.io.stdout(getCliSetting(settings, name).output);
|
|
298
|
+
});
|
|
299
|
+
config
|
|
300
|
+
.command("set")
|
|
301
|
+
.argument("<name>", "setting name")
|
|
302
|
+
.argument("<value>", "setting value")
|
|
303
|
+
.description("write a CLI setting for this machine")
|
|
304
|
+
.action(async (name: string, value: string) => {
|
|
305
|
+
const result = await setCliSetting(context.environment, name, value);
|
|
306
|
+
context.io.stdout(result.output);
|
|
307
|
+
});
|
|
308
|
+
config.action(async () => {
|
|
309
|
+
const settings = await readCliSettings(context.environment);
|
|
310
|
+
context.io.stdout(getCliSetting(settings, undefined).output);
|
|
311
|
+
});
|
|
312
|
+
|
|
253
313
|
program
|
|
254
314
|
.command("login")
|
|
255
315
|
.description("store an API key for this machine after verifying it")
|
|
@@ -324,8 +384,9 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
324
384
|
.description("select and manage the current platform project");
|
|
325
385
|
project
|
|
326
386
|
.command("list")
|
|
387
|
+
.option("--limit <n>", "maximum projects to list", positiveInteger)
|
|
327
388
|
.description("list remote projects this key can see")
|
|
328
|
-
.action(async () => {
|
|
389
|
+
.action(async (options: { limit?: number }) => {
|
|
329
390
|
const flags = globals();
|
|
330
391
|
const { client, stored, state } = await resolveContext(flags, context);
|
|
331
392
|
const selected = resolveSelectedProject(flags, context, stored, state);
|
|
@@ -333,6 +394,7 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
333
394
|
client,
|
|
334
395
|
io: context.io,
|
|
335
396
|
...(selected === undefined ? {} : { context: selected }),
|
|
397
|
+
...(options.limit === undefined ? {} : { limit: options.limit }),
|
|
336
398
|
});
|
|
337
399
|
});
|
|
338
400
|
project
|
|
@@ -440,9 +502,12 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
440
502
|
.action(async () => {
|
|
441
503
|
const flags = globals();
|
|
442
504
|
const sourceRoot = resolveInitRoot(flags);
|
|
443
|
-
const stored = await readCommandCredentials(flags, context);
|
|
444
505
|
const state = await readTolokaState(sourceRoot);
|
|
445
|
-
const selected =
|
|
506
|
+
const selected = resolveProjectContext({
|
|
507
|
+
flags,
|
|
508
|
+
environment: context.environment,
|
|
509
|
+
...(state === undefined ? {} : { state }),
|
|
510
|
+
});
|
|
446
511
|
const result = await initProject(
|
|
447
512
|
sourceRoot,
|
|
448
513
|
selected === undefined ? {} : { projectId: selected.id },
|
|
@@ -500,13 +565,89 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
500
565
|
if (!result.valid) exitCode.value = 1;
|
|
501
566
|
});
|
|
502
567
|
|
|
568
|
+
program
|
|
569
|
+
.command("check")
|
|
570
|
+
.argument("[pipeline]", "pipeline source directory or name")
|
|
571
|
+
.option("--node <node>", "check one code or human-work node by name or id")
|
|
572
|
+
.option("--json", "print the structured check report", false)
|
|
573
|
+
.option("--watch", "rerun checks when project files change", false)
|
|
574
|
+
.description("run the complete local source, type, and test gate")
|
|
575
|
+
.action(
|
|
576
|
+
async (
|
|
577
|
+
pipeline: string | undefined,
|
|
578
|
+
options: { node?: string; json: boolean; watch: boolean },
|
|
579
|
+
) => {
|
|
580
|
+
const sourceRoot = await resolveSourceRoot(globals(), context.io);
|
|
581
|
+
const checkOptions = {
|
|
582
|
+
environment: context.environment,
|
|
583
|
+
sourceRoot,
|
|
584
|
+
...(pipeline === undefined ? {} : { pipeline }),
|
|
585
|
+
...(options.node === undefined ? {} : { node: options.node }),
|
|
586
|
+
...(options.json ? { json: true } : {}),
|
|
587
|
+
};
|
|
588
|
+
const writeCheckOutput = async (output: string): Promise<void> => {
|
|
589
|
+
const resultPath = context.environment[CHECK_RESULT_FILE_ENV];
|
|
590
|
+
if (resultPath === undefined) context.io.stdout(output);
|
|
591
|
+
else await writeFile(resultPath, output, "utf8");
|
|
592
|
+
};
|
|
593
|
+
const checkRunner =
|
|
594
|
+
context.runChecks === undefined &&
|
|
595
|
+
context.environment[CHECK_RESULT_FILE_ENV] === undefined
|
|
596
|
+
? runFreshCheck
|
|
597
|
+
: runChecks;
|
|
598
|
+
if (options.watch) {
|
|
599
|
+
const abortController = new AbortController();
|
|
600
|
+
const stop = () => abortController.abort();
|
|
601
|
+
process.on("SIGINT", stop);
|
|
602
|
+
process.on("SIGTERM", stop);
|
|
603
|
+
try {
|
|
604
|
+
await watchProjectChecks(
|
|
605
|
+
checkOptions,
|
|
606
|
+
async (result) => {
|
|
607
|
+
await writeCheckOutput(result.output);
|
|
608
|
+
exitCode.value = result.exitCode;
|
|
609
|
+
},
|
|
610
|
+
undefined,
|
|
611
|
+
abortController.signal,
|
|
612
|
+
);
|
|
613
|
+
} finally {
|
|
614
|
+
process.off("SIGINT", stop);
|
|
615
|
+
process.off("SIGTERM", stop);
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const { result, interruptedExitCode } = await runChecksInterruptibly(
|
|
620
|
+
checkOptions,
|
|
621
|
+
checkRunner,
|
|
622
|
+
);
|
|
623
|
+
await writeCheckOutput(result.output);
|
|
624
|
+
exitCode.value = interruptedExitCode ?? result.exitCode;
|
|
625
|
+
},
|
|
626
|
+
);
|
|
627
|
+
|
|
503
628
|
program
|
|
504
629
|
.command("apply")
|
|
505
|
-
.description("make the platform match
|
|
630
|
+
.description("check source, then make the platform match it")
|
|
506
631
|
.action(async () => {
|
|
507
|
-
const
|
|
632
|
+
const sourceRoot = await resolveSourceRoot(globals(), context.io);
|
|
633
|
+
const { result: checked, interruptedExitCode } = await runChecksInterruptibly({
|
|
634
|
+
sourceRoot,
|
|
635
|
+
environment: context.environment,
|
|
636
|
+
});
|
|
637
|
+
context.io.stdout(checked.output);
|
|
638
|
+
if (interruptedExitCode !== undefined) {
|
|
639
|
+
exitCode.value = interruptedExitCode;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (checked.exitCode !== 0) {
|
|
643
|
+
exitCode.value = checked.exitCode;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
assert(checked.source, "Passing checks must return project source");
|
|
647
|
+
const { client, projectId } = await resolveContext(globals(), context);
|
|
508
648
|
const result = await applyProject(client, {
|
|
509
649
|
sourceRoot,
|
|
650
|
+
source: checked.source,
|
|
510
651
|
...(projectId === undefined ? {} : { projectId }),
|
|
511
652
|
});
|
|
512
653
|
context.io.stdout(result.output);
|
|
@@ -699,11 +840,17 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
699
840
|
.description("operate on a live pipeline in the selected project");
|
|
700
841
|
pipeline
|
|
701
842
|
.command("list")
|
|
843
|
+
.option("--limit <n>", "maximum pipelines to list", positiveInteger)
|
|
702
844
|
.description("list live pipelines in the selected project")
|
|
703
|
-
.action(async () => {
|
|
845
|
+
.action(async (options: { limit?: number }) => {
|
|
704
846
|
const { client, projectId, state } = await resolveContext(globals(), context);
|
|
705
847
|
assert(projectId, MISSING_PROJECT_ID_MESSAGE);
|
|
706
|
-
const result = await listLivePipelines({
|
|
848
|
+
const result = await listLivePipelines({
|
|
849
|
+
client,
|
|
850
|
+
projectId,
|
|
851
|
+
state,
|
|
852
|
+
...(options.limit === undefined ? {} : { limit: options.limit }),
|
|
853
|
+
});
|
|
707
854
|
context.io.stdout(result.output);
|
|
708
855
|
});
|
|
709
856
|
pipeline
|
|
@@ -1005,27 +1152,44 @@ export async function runTolokaCli(
|
|
|
1005
1152
|
environment: TolokaEnvironment = process.env,
|
|
1006
1153
|
io: CliIo = terminalIo(),
|
|
1007
1154
|
injectedClient?: PotatoClient,
|
|
1008
|
-
analytics
|
|
1155
|
+
analytics?: CliAnalytics,
|
|
1156
|
+
runChecks?: typeof checkProject,
|
|
1157
|
+
autoUpdate?: AutoUpdateHooks,
|
|
1009
1158
|
): Promise<number> {
|
|
1159
|
+
const autoUpdateResult = await maybeAutoUpdate({
|
|
1160
|
+
argv,
|
|
1161
|
+
environment,
|
|
1162
|
+
io,
|
|
1163
|
+
...(autoUpdate === undefined ? {} : { hooks: autoUpdate }),
|
|
1164
|
+
});
|
|
1165
|
+
if (autoUpdateResult.status === "reexec") return autoUpdateResult.exitCode;
|
|
1166
|
+
|
|
1167
|
+
const activeAnalytics =
|
|
1168
|
+
environment[CHECK_RESULT_FILE_ENV] === undefined
|
|
1169
|
+
? (analytics ?? createCliAnalytics({ environment }))
|
|
1170
|
+
: undefined;
|
|
1010
1171
|
const { program, exitCode } = createProgram({
|
|
1011
1172
|
environment,
|
|
1012
1173
|
io,
|
|
1013
|
-
analytics,
|
|
1174
|
+
...(activeAnalytics === undefined ? {} : { analytics: activeAnalytics }),
|
|
1014
1175
|
...(injectedClient === undefined ? {} : { injectedClient }),
|
|
1176
|
+
...(runChecks === undefined ? {} : { runChecks }),
|
|
1015
1177
|
});
|
|
1016
1178
|
|
|
1017
1179
|
const startedAt = Date.now();
|
|
1018
1180
|
const result = await runProgram(program, exitCode, argv, io);
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1181
|
+
if (activeAnalytics !== undefined) {
|
|
1182
|
+
try {
|
|
1183
|
+
activeAnalytics.captureCommand({
|
|
1184
|
+
command: commandNameFromArgv(argv),
|
|
1185
|
+
durationMs: Date.now() - startedAt,
|
|
1186
|
+
exitCode: result.code,
|
|
1187
|
+
outcome: result.outcome,
|
|
1188
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
1189
|
+
});
|
|
1190
|
+
await activeAnalytics.shutdown();
|
|
1191
|
+
} catch {}
|
|
1192
|
+
}
|
|
1029
1193
|
|
|
1030
1194
|
return result.code;
|
|
1031
1195
|
}
|
|
@@ -11,7 +11,10 @@ type ProjectRecord = { readonly id: string; readonly name: string; readonly team
|
|
|
11
11
|
|
|
12
12
|
export type ProjectListApi = {
|
|
13
13
|
readonly projects: {
|
|
14
|
-
readonly
|
|
14
|
+
readonly listPage: (params: { readonly limit: number }) => Promise<{
|
|
15
|
+
readonly projects: readonly ProjectRecord[];
|
|
16
|
+
readonly pagination: { readonly total: number; readonly hasMore: boolean };
|
|
17
|
+
}>;
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
|
|
@@ -225,15 +228,22 @@ export async function listRemoteProjects(options: {
|
|
|
225
228
|
readonly client: ProjectListApi;
|
|
226
229
|
readonly context?: ProjectContext;
|
|
227
230
|
readonly io: ProjectIo;
|
|
231
|
+
readonly limit?: number;
|
|
228
232
|
}): Promise<{ readonly output: string }> {
|
|
229
|
-
const
|
|
230
|
-
|
|
233
|
+
const limit = options.limit ?? 20;
|
|
234
|
+
const page = await options.client.projects.listPage({ limit });
|
|
235
|
+
const lines = page.projects.map((project) => {
|
|
231
236
|
const mark = options.context?.id === project.id ? "*" : " ";
|
|
232
237
|
const alias =
|
|
233
238
|
options.context?.id === project.id && options.context.alias !== undefined
|
|
234
239
|
? `\t${options.context.alias}`
|
|
235
240
|
: "";
|
|
236
|
-
|
|
241
|
+
return `${mark}\t${project.id}\t${project.name}${alias}`;
|
|
242
|
+
});
|
|
243
|
+
if (page.pagination.hasMore) {
|
|
244
|
+
lines.push(
|
|
245
|
+
`Showing ${page.projects.length} of ${page.pagination.total} projects. Raise --limit to see more.`,
|
|
246
|
+
);
|
|
237
247
|
}
|
|
238
248
|
const output = lines.length === 0 ? "No projects visible." : lines.join("\n");
|
|
239
249
|
options.io.stdout(output);
|
package/src/cli/runs.ts
CHANGED
|
@@ -84,21 +84,31 @@ export async function listLivePipelines(options: {
|
|
|
84
84
|
readonly client: PotatoClient;
|
|
85
85
|
readonly projectId: string;
|
|
86
86
|
readonly state?: TolokaState;
|
|
87
|
+
readonly limit?: number;
|
|
87
88
|
}): Promise<RunCommandResult> {
|
|
88
|
-
const
|
|
89
|
+
const limit = options.limit ?? 20;
|
|
90
|
+
const page = await options.client.pipelines.listPage({ projectId: options.projectId, limit });
|
|
91
|
+
const sourceDirectoryById = bindingByLiveId(options.projectId, options.state);
|
|
92
|
+
const live = page.pipelines
|
|
93
|
+
.map((pipeline) => toRef(pipeline, sourceDirectoryById))
|
|
94
|
+
.toSorted((left, right) => {
|
|
95
|
+
const byName = left.name.localeCompare(right.name);
|
|
96
|
+
return byName === 0 ? left.pipelineId.localeCompare(right.pipelineId) : byName;
|
|
97
|
+
});
|
|
89
98
|
if (live.length === 0) {
|
|
90
99
|
return { outcome: "reported", output: "This project has no pipelines." };
|
|
91
100
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
101
|
+
const lines = live.map((ref) =>
|
|
102
|
+
ref.sourceDirectory === undefined
|
|
103
|
+
? `${ref.name}\t${ref.pipelineId}`
|
|
104
|
+
: `${ref.name}\t${ref.pipelineId}\t${ref.sourceDirectory}`,
|
|
105
|
+
);
|
|
106
|
+
if (page.pagination.hasMore) {
|
|
107
|
+
lines.push(
|
|
108
|
+
`Showing ${live.length} of ${page.pagination.total} pipelines. Raise --limit to see more.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return { outcome: "reported", output: lines.join("\n") };
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
const collectLivePipelineRefs = async (
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isFalseyFlag, isTruthyFlag } from "./boolean-flag";
|
|
4
|
+
import type { CliEnvironment } from "./credentials";
|
|
5
|
+
import { configDirectory, preparePrivateFilePath } from "./credentials";
|
|
6
|
+
|
|
7
|
+
export const AUTO_UPDATE_SETTING = "auto-update";
|
|
8
|
+
|
|
9
|
+
export type CliSettings = {
|
|
10
|
+
readonly autoUpdate: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_CLI_SETTINGS: CliSettings = {
|
|
14
|
+
autoUpdate: true,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class SettingsError extends Error {
|
|
18
|
+
constructor(message: string, options?: { readonly cause?: unknown }) {
|
|
19
|
+
super(message, options);
|
|
20
|
+
this.name = "SettingsError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const settingsPath = (environment: CliEnvironment = process.env): string =>
|
|
25
|
+
join(configDirectory(environment), "settings.json");
|
|
26
|
+
|
|
27
|
+
const asSettings = (payload: unknown): CliSettings => {
|
|
28
|
+
const autoUpdate =
|
|
29
|
+
typeof payload === "object" && payload !== null
|
|
30
|
+
? Reflect.get(payload, "autoUpdate")
|
|
31
|
+
: undefined;
|
|
32
|
+
if (typeof autoUpdate !== "boolean") return DEFAULT_CLI_SETTINGS;
|
|
33
|
+
return { autoUpdate };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function readCliSettings(
|
|
37
|
+
environment: CliEnvironment = process.env,
|
|
38
|
+
): Promise<CliSettings> {
|
|
39
|
+
try {
|
|
40
|
+
return asSettings(JSON.parse(await readFile(settingsPath(environment), "utf8")));
|
|
41
|
+
} catch {
|
|
42
|
+
return DEFAULT_CLI_SETTINGS;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function writeCliSettings(
|
|
47
|
+
settings: CliSettings,
|
|
48
|
+
environment: CliEnvironment = process.env,
|
|
49
|
+
): Promise<string> {
|
|
50
|
+
const path = settingsPath(environment);
|
|
51
|
+
await preparePrivateFilePath(path);
|
|
52
|
+
await writeFile(path, `${JSON.stringify({ autoUpdate: settings.autoUpdate }, null, 2)}\n`, {
|
|
53
|
+
mode: 0o600,
|
|
54
|
+
});
|
|
55
|
+
await chmod(path, 0o600);
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const parseBooleanSetting = (value: string): boolean => {
|
|
60
|
+
if (isTruthyFlag(value)) return true;
|
|
61
|
+
if (isFalseyFlag(value)) return false;
|
|
62
|
+
throw new SettingsError(`Expected a boolean (true or false), got ${JSON.stringify(value)}.`);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const formatCliSettings = (settings: CliSettings): string =>
|
|
66
|
+
`${AUTO_UPDATE_SETTING}=${settings.autoUpdate}`;
|
|
67
|
+
|
|
68
|
+
export const getCliSetting = (
|
|
69
|
+
settings: CliSettings,
|
|
70
|
+
name: string | undefined,
|
|
71
|
+
): { readonly output: string } => {
|
|
72
|
+
if (name === undefined || name.length === 0) return { output: formatCliSettings(settings) };
|
|
73
|
+
if (name !== AUTO_UPDATE_SETTING) {
|
|
74
|
+
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`);
|
|
75
|
+
}
|
|
76
|
+
return { output: String(settings.autoUpdate) };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export async function setCliSetting(
|
|
80
|
+
environment: CliEnvironment,
|
|
81
|
+
name: string,
|
|
82
|
+
value: string,
|
|
83
|
+
): Promise<{ readonly output: string; readonly settings: CliSettings }> {
|
|
84
|
+
if (name !== AUTO_UPDATE_SETTING) {
|
|
85
|
+
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`);
|
|
86
|
+
}
|
|
87
|
+
const settings = { autoUpdate: parseBooleanSetting(value) };
|
|
88
|
+
await writeCliSettings(settings, environment);
|
|
89
|
+
return {
|
|
90
|
+
settings,
|
|
91
|
+
output: `Set ${AUTO_UPDATE_SETTING} to ${settings.autoUpdate}.`,
|
|
92
|
+
};
|
|
93
|
+
}
|
package/src/cli/skill.ts
CHANGED
|
@@ -191,12 +191,23 @@ export const searchSkills = (
|
|
|
191
191
|
skills: readonly SkillDescriptor[],
|
|
192
192
|
query: string,
|
|
193
193
|
): SkillDescriptor[] => {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)
|
|
199
|
-
|
|
194
|
+
const needles = query.trim().toLowerCase().split(/\s+/u).filter(Boolean);
|
|
195
|
+
if (needles.length === 0) return [];
|
|
196
|
+
|
|
197
|
+
return skills
|
|
198
|
+
.map((skill, index) => {
|
|
199
|
+
const searchable = [skill.id, skill.name, skill.description, ...skill.tags]
|
|
200
|
+
.join("\n")
|
|
201
|
+
.toLowerCase();
|
|
202
|
+
return {
|
|
203
|
+
skill,
|
|
204
|
+
index,
|
|
205
|
+
score: needles.filter((needle) => searchable.includes(needle)).length,
|
|
206
|
+
};
|
|
207
|
+
})
|
|
208
|
+
.filter(({ score }) => score > 0)
|
|
209
|
+
.toSorted((left, right) => right.score - left.score || left.index - right.index)
|
|
210
|
+
.map(({ skill }) => skill);
|
|
200
211
|
};
|
|
201
212
|
|
|
202
213
|
export async function skillStatus(
|
package/src/importer.ts
CHANGED
|
@@ -220,7 +220,7 @@ function formatSource(value: unknown, depth = 0): string {
|
|
|
220
220
|
return `{\n${properties}\n${indentation(depth)}}`;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function
|
|
223
|
+
function transformFieldReference(
|
|
224
224
|
reference: { id: string; optional: boolean },
|
|
225
225
|
fieldReference: FieldReference,
|
|
226
226
|
): unknown {
|
|
@@ -276,10 +276,10 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
|
|
|
276
276
|
return {
|
|
277
277
|
...settings,
|
|
278
278
|
started: startedFieldProxies.output.map((reference) =>
|
|
279
|
-
|
|
279
|
+
transformFieldReference(reference, fieldReference),
|
|
280
280
|
),
|
|
281
281
|
completed: completedFieldProxies.input.map((reference) =>
|
|
282
|
-
|
|
282
|
+
transformFieldReference(reference, fieldReference),
|
|
283
283
|
),
|
|
284
284
|
};
|
|
285
285
|
}
|
|
@@ -287,14 +287,12 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
|
|
|
287
287
|
const { fieldProxies, ...settings } = node.settings;
|
|
288
288
|
return {
|
|
289
289
|
...settings,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
),
|
|
297
|
-
},
|
|
290
|
+
input: fieldProxies.input.map((reference) =>
|
|
291
|
+
transformFieldReference(reference, fieldReference),
|
|
292
|
+
),
|
|
293
|
+
output: fieldProxies.output.map((reference) =>
|
|
294
|
+
transformFieldReference(reference, fieldReference),
|
|
295
|
+
),
|
|
298
296
|
};
|
|
299
297
|
}
|
|
300
298
|
case "generation": {
|
|
@@ -322,11 +320,9 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
|
|
|
322
320
|
field: fieldReference(fieldProxyId),
|
|
323
321
|
})),
|
|
324
322
|
})),
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
),
|
|
329
|
-
},
|
|
323
|
+
input: fieldProxies.input.map((reference) =>
|
|
324
|
+
transformFieldReference(reference, fieldReference),
|
|
325
|
+
),
|
|
330
326
|
};
|
|
331
327
|
}
|
|
332
328
|
case "quorum": {
|