@toloka-platform/sdk 0.1.0-preview.8 → 0.1.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/README.md +5 -4
- package/dist/cli/toloka.js +138 -136
- 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 +9 -4
- package/docs/cli-v2.md +2 -2
- package/docs/cli.md +13 -2
- package/docs/getting-started.md +3 -3
- package/docs/limitations.md +2 -2
- package/package.json +3 -3
- package/skills/catalog.json +4 -4
- package/skills/toloka-build/SKILL.md +1 -1
- package/skills/toloka-build/references/node-semantics.md +2 -2
- package/skills/toloka-build/references/workflow-and-cli.md +2 -1
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +10 -0
- package/src/checks/task-ui-typecheck.ts +1 -2
- package/src/cli/auto-update.ts +3 -7
- package/src/cli/check.ts +94 -34
- package/src/cli/cli-entry.ts +41 -0
- package/src/cli/cost.ts +7 -1
- package/src/cli/program.ts +28 -2
- package/src/cli/project-context.ts +47 -2
- package/src/cli/project.ts +53 -10
- package/src/cli/skill.ts +4 -5
- package/src/cli/version.ts +68 -0
- package/src/index.ts +2 -0
package/dist/cli/toloka.js.jsc
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b3ff8750d4254e81d4dccbba6db162057e702b7b823d76d6adc2e6b122212251 toloka.js.jsc
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
122b2c1f1afe960176a8f8f5557f847da14ed454809f44d8ed4a5212be6ace2a toloka.js
|
package/docs/authoring.md
CHANGED
|
@@ -37,19 +37,24 @@ The name is a bare identifier. When a field is a pipeline **input**, its name mu
|
|
|
37
37
|
|
|
38
38
|
## Node constructors
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Four work-node constructors take a name, settings, and optional `{ id }`: `code`, `generation`, `quorum`, and `match`. `root` is the single pipeline boundary and takes only settings plus optional `{ id }`.
|
|
41
41
|
|
|
42
42
|
### root
|
|
43
43
|
|
|
44
44
|
Defines the pipeline's input and output contract. `started` is what each item carries in from the dataset; `completed` is what gets written back.
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
|
-
import { root } from "@toloka-platform/sdk";
|
|
47
|
+
import { optional, root } from "@toloka-platform/sdk";
|
|
48
48
|
|
|
49
|
-
export const collection = root({
|
|
49
|
+
export const collection = root({
|
|
50
|
+
started: [imageUrl],
|
|
51
|
+
completed: [optional(annotations)],
|
|
52
|
+
});
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
Pass `{ field, optional
|
|
55
|
+
Pass a bare field for a required binding. Use `optional(field)` for new optional bindings. The explicit `{ field, optional }` form remains supported and may appear in imported source.
|
|
56
|
+
|
|
57
|
+
`root()` deliberately has no name argument: a pipeline has exactly one boundary, and its default wire id is `root`.
|
|
53
58
|
|
|
54
59
|
### code
|
|
55
60
|
|
package/docs/cli-v2.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI v2: project as the root
|
|
2
2
|
|
|
3
|
-
How `toloka` should work in the next version.
|
|
3
|
+
How `toloka` should work in the next version. Beta remains a clean cutover rather than a compatibility layer.
|
|
4
4
|
|
|
5
5
|
The platform object is a **project** (`pp.…`, today's pipeline project). Windmill calls the same role a workspace. PostHog calls it a project. We keep **project** so the CLI matches the API and the console.
|
|
6
6
|
|
|
@@ -212,7 +212,7 @@ Do not add those groups until the resource exists. The rule is already fixed: th
|
|
|
212
212
|
| `toloka runs` / `cost` / `cancel` / `results` | `toloka pipeline …` |
|
|
213
213
|
| root = `toloka.json` or `pipelines/` | root = `toloka.json` only |
|
|
214
214
|
|
|
215
|
-
No shims.
|
|
215
|
+
No shims. Beta callers update.
|
|
216
216
|
|
|
217
217
|
## Out of scope
|
|
218
218
|
|
package/docs/cli.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `toloka` CLI reads TypeScript source as the desired state, compares it with the live platform state, and reconciles the two.
|
|
4
4
|
|
|
5
|
-
This CLI is
|
|
5
|
+
This CLI is Beta software. Commands, flags, and output can change before general availability.
|
|
6
6
|
|
|
7
7
|
`toloka` requires Bun 1.4.0. The published package ships matching CLI bytecode; the launcher falls back to TypeScript source in a development checkout when the bytecode is absent.
|
|
8
8
|
|
|
@@ -61,6 +61,14 @@ The CLI is resource-first: `toloka <resource> <action>`. A Base Action has one m
|
|
|
61
61
|
|
|
62
62
|
Resources may also expose specialized actions beyond the Base Actions, such as `pipeline cost`, `skill search`, and `skill install`.
|
|
63
63
|
|
|
64
|
+
## version
|
|
65
|
+
|
|
66
|
+
Print the SDK/CLI version. `toloka version --json` also prints build provenance: the build commit when the bytecode bundle recorded one, the install channel (`global-registry`, `linked`, or `project-local`), and the local package path for linked installs. `toloka -v` and `toloka --version` remain semver-only for scripts.
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
toloka version [--json]
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
## project
|
|
65
73
|
|
|
66
74
|
Register and select the platform project later commands use.
|
|
@@ -73,9 +81,12 @@ toloka project current
|
|
|
73
81
|
toloka project url
|
|
74
82
|
toloka project remove <alias>
|
|
75
83
|
toloka project create [alias] [--name <name>] [--team-id <id>]
|
|
84
|
+
toloka project untrack <pipeline|dataset> <name> [--confirm]
|
|
76
85
|
```
|
|
77
86
|
|
|
78
|
-
`add`, `switch`, `remove`, and `create` write the machine registry in the credentials file. They refuse `--api-key` / `--base-url` / `POTATO_*` when those would select a different key or platform than the stored file. `create` reuses the only visible team when every listed project shares one; otherwise pass `--team-id`.
|
|
87
|
+
`add`, `switch`, `remove`, and `create` write the machine registry in the credentials file. They refuse `--api-key` / `--base-url` / `POTATO_*` when those would select a different key or platform than the stored file. `project list` prints project id, name, and team id. `create` reuses the only visible team when every listed project shares one; otherwise pass `--team-id`.
|
|
88
|
+
|
|
89
|
+
`project untrack` removes one pipeline or dataset binding from `toloka.json` without deleting the live resource. It previews the name and id in a terminal; outside a terminal, pass `--confirm` after reviewing the state file.
|
|
79
90
|
|
|
80
91
|
`url` fetches the selected project to resolve its team and prints only its platform UI URL to stdout, making the output safe to capture in scripts and agent workflows.
|
|
81
92
|
|
package/docs/getting-started.md
CHANGED
|
@@ -4,11 +4,11 @@ Install the SDK, sign in, and get a pipeline project reconciling from source in
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Install the bytecode-cached CLI globally and declare the
|
|
7
|
+
Install the bytecode-cached CLI globally and declare the Beta SDK in the project:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
curl -fsSL https://platform.toloka.ai/cli/install.sh | sh
|
|
11
|
-
bun add @toloka-platform/sdk@
|
|
11
|
+
bun add @toloka-platform/sdk@latest
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The installer requires Bun 1.4.0, installs `@toloka-platform/sdk` globally, and exposes `toloka` through Bun's global binary directory. The published CLI includes matching precompiled bytecode. The project install makes SDK imports resolvable.
|
|
@@ -140,4 +140,4 @@ Exit code `0` means the run reached a successful terminal state, `1` a failed on
|
|
|
140
140
|
- [Nodes](./nodes.md) — which node to reach for, and the pricing floors you must clear
|
|
141
141
|
- [CLI](./cli.md) — every command and flag
|
|
142
142
|
- [Troubleshooting](./troubleshooting.md) — the failures you will hit
|
|
143
|
-
- [Limitations](./limitations.md) —
|
|
143
|
+
- [Limitations](./limitations.md) — Beta status and what this SDK does not do yet
|
package/docs/limitations.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Limitations
|
|
2
2
|
|
|
3
|
-
What this
|
|
3
|
+
What this Beta SDK does not do yet, stated plainly so you can decide what to depend on.
|
|
4
4
|
|
|
5
|
-
`@toloka-platform/sdk` is `0.1.0
|
|
5
|
+
`@toloka-platform/sdk` is `0.1.0`. This is a Beta: the authoring API, the CLI surface, and the generated source layout can change before general availability. Pin an exact version. Do not depend on this in production yet.
|
|
6
6
|
|
|
7
7
|
## Local checks do not certify deployability
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toloka-platform/sdk",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Beta. Build, inspect, and operate Toloka pipeline projects as TypeScript source. The public API may change.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"bin": {
|
|
8
8
|
"toloka": "bin/toloka.js"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public",
|
|
30
30
|
"registry": "https://registry.npmjs.org/",
|
|
31
|
-
"tag": "
|
|
31
|
+
"tag": "latest"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "bun ./scripts/build-cli.ts ./dist/cli",
|
package/skills/catalog.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"testing",
|
|
14
14
|
"checks"
|
|
15
15
|
],
|
|
16
|
-
"version": "0.1.0
|
|
17
|
-
"digest": "
|
|
16
|
+
"version": "0.1.0",
|
|
17
|
+
"digest": "bcdb60e89bc4b8e345c1793c7a7d5275e8b196e171bd09adba242ed89f117a50",
|
|
18
18
|
"files": ["SKILL.md", "references/node-semantics.md", "references/workflow-and-cli.md"]
|
|
19
19
|
},
|
|
20
20
|
{
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"name": "Report a Toloka problem",
|
|
23
23
|
"description": "Report a problem with the Toloka platform, CLI, or SDK as a redacted Markdown issue report sent through the toloka CLI only after explicit approval. Use when the user hits unexpected behavior, an error, or a bug and wants Toloka engineers to investigate.",
|
|
24
24
|
"tags": ["support", "issue", "bug", "report"],
|
|
25
|
-
"version": "0.1.0
|
|
26
|
-
"digest": "
|
|
25
|
+
"version": "0.1.0",
|
|
26
|
+
"digest": "969aedef68e56243395e0a9115d90041c7c1e27f759762b07e62a7d521a8a251",
|
|
27
27
|
"files": ["SKILL.md", "references/report-template.md"]
|
|
28
28
|
}
|
|
29
29
|
]
|
|
@@ -5,7 +5,7 @@ description: Onboard users and build, test, inspect, or change Toloka platform p
|
|
|
5
5
|
|
|
6
6
|
# Build Toloka pipelines as code
|
|
7
7
|
|
|
8
|
-
This SDK is
|
|
8
|
+
This SDK is Beta software. The authoring API, the CLI, and generated source can change before general availability. Do not invent helpers or flags that are not in the installed package.
|
|
9
9
|
|
|
10
10
|
Use this skill when the user wants to get started or to create, inspect, test, or change a Toloka pipeline project as code. Work in the project source tree; do not translate the request into direct platform API calls.
|
|
11
11
|
|
|
@@ -43,13 +43,13 @@ connect(annotate.output, collection.completed);
|
|
|
43
43
|
|
|
44
44
|
### Pick and configure it
|
|
45
45
|
|
|
46
|
-
Create exactly one `root` node. Use `started` for dataset fields the pipeline reads and `completed` for fields it writes back to the same dataset. A completed field is not a separate output destination: the platform creates or uses that dataset column per item.
|
|
46
|
+
Create exactly one `root` node with `root(settings)`. It has no name argument because it represents the pipeline boundary, not a named unit of work. Use `started` for dataset fields the pipeline reads and `completed` for fields it writes back to the same dataset. A completed field is not a separate output destination: the platform creates or uses that dataset column per item.
|
|
47
47
|
|
|
48
48
|
The public authoring shape is `RootAuthoringSettings`:
|
|
49
49
|
|
|
50
50
|
- `started`: fields exposed through the `started` output port.
|
|
51
51
|
- `completed`: fields accepted through the `completed` input port and written to the dataset.
|
|
52
|
-
- Each entry can be a `Field` or `{ field, optional }
|
|
52
|
+
- Each entry can be a required `Field`, `optional(field)`, or an explicit `{ field, optional }` binding. Prefer `optional(field)` for new optional bindings and preserve the explicit object form in imported source.
|
|
53
53
|
- `itemLimit` exists in both `RootNodeSettings` and `RootAuthoringSettings`. Do not invent a limit; author one only when the user explicitly requests a bounded test or batch. Apply preserves a live `itemLimit` unless source declares its own.
|
|
54
54
|
|
|
55
55
|
The dataset name belongs in `definePipeline({ dataset: 'name', ... })`, not in the root settings. `toloka apply` resolves or creates the named dataset and supplies its ID.
|
|
@@ -83,7 +83,7 @@ All commands accept `--source-root <directory>`. Prefer running from the source
|
|
|
83
83
|
The real command surface is:
|
|
84
84
|
|
|
85
85
|
```text
|
|
86
|
-
toloka version
|
|
86
|
+
toloka version [--json]
|
|
87
87
|
toloka -v
|
|
88
88
|
toloka --version
|
|
89
89
|
toloka login
|
|
@@ -99,6 +99,7 @@ toloka project current
|
|
|
99
99
|
toloka project url
|
|
100
100
|
toloka project remove <alias>
|
|
101
101
|
toloka project create [alias] [--name <name>] [--team-id <id>]
|
|
102
|
+
toloka project untrack <pipeline|dataset> <name> [--confirm]
|
|
102
103
|
toloka init
|
|
103
104
|
toloka import [projectId]
|
|
104
105
|
toloka check [pipeline] [--node <name-or-id>] [--json] [--watch]
|
|
@@ -26,7 +26,7 @@ The CLI exits 1 with `Potato API request failed with status 500`. The same comma
|
|
|
26
26
|
|
|
27
27
|
## Environment
|
|
28
28
|
|
|
29
|
-
- toloka CLI 0.1.0
|
|
29
|
+
- toloka CLI 0.1.0 (`toloka --version`), Bun 1.4.0, macOS 15
|
|
30
30
|
- Platform: https://platform.toloka.ai
|
|
31
31
|
|
|
32
32
|
## Redactions
|
package/src/authoring/index.ts
CHANGED
|
@@ -56,6 +56,8 @@ type FieldReference = {
|
|
|
56
56
|
readonly optional: boolean;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
export type OptionalFieldBinding = { readonly field: Field; readonly optional: true };
|
|
60
|
+
|
|
59
61
|
type RootFieldReference = Field | { readonly field: Field; readonly optional?: boolean };
|
|
60
62
|
|
|
61
63
|
type FieldRelation<Relation> = Relation extends { id: string }
|
|
@@ -445,10 +447,18 @@ export function field<const Name extends string>(
|
|
|
445
447
|
};
|
|
446
448
|
}
|
|
447
449
|
|
|
450
|
+
export function optional(referencedField: Field): OptionalFieldBinding {
|
|
451
|
+
return { field: referencedField, optional: true };
|
|
452
|
+
}
|
|
453
|
+
|
|
448
454
|
function createRoot(
|
|
449
455
|
settings: RootAuthoringSettings,
|
|
450
456
|
options: AuthoringIdOptions = {},
|
|
451
457
|
): AuthoredRootNode {
|
|
458
|
+
assert(
|
|
459
|
+
typeof settings === "object" && settings !== null && !Array.isArray(settings),
|
|
460
|
+
"root() defines the single pipeline boundary; call root({ started, completed }) without a name",
|
|
461
|
+
);
|
|
452
462
|
const id = options.id ?? "root";
|
|
453
463
|
const { started, completed, ...settingsWithoutFieldProxies } = settings;
|
|
454
464
|
return {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
|
|
4
3
|
import { assert } from "es-toolkit";
|
|
@@ -11,7 +10,7 @@ import {
|
|
|
11
10
|
createVirtualTypeScriptEnvironment,
|
|
12
11
|
} from "./typescript-vfs.js";
|
|
13
12
|
|
|
14
|
-
const tsLibDirectory = path.dirname(
|
|
13
|
+
const tsLibDirectory = path.dirname(ts.getDefaultLibFilePath({ target: ts.ScriptTarget.Latest }));
|
|
15
14
|
|
|
16
15
|
const BLOCK_MARKERS = {
|
|
17
16
|
data: "DATA_INJECTION",
|
package/src/cli/auto-update.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { once } from "node:events";
|
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { join, sep } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { findCliEntry } from "./cli-entry";
|
|
7
7
|
import type { CliEnvironment } from "./credentials";
|
|
8
8
|
import { configDirectory, preparePrivateFilePath } from "./credentials";
|
|
9
9
|
import { SDK_VERSION } from "./init";
|
|
@@ -123,11 +123,7 @@ export const asNpmPackageView = (payload: unknown): NpmPackageView | undefined =
|
|
|
123
123
|
};
|
|
124
124
|
|
|
125
125
|
export const latestPublishedVersion = (view: NpmPackageView): string | undefined => {
|
|
126
|
-
const
|
|
127
|
-
if (tags === undefined) return undefined;
|
|
128
|
-
const preview = tags.preview;
|
|
129
|
-
if (typeof preview === "string" && preview.length > 0) return preview;
|
|
130
|
-
const latest = tags.latest;
|
|
126
|
+
const latest = view["dist-tags"]?.latest;
|
|
131
127
|
return typeof latest === "string" && latest.length > 0 ? latest : undefined;
|
|
132
128
|
};
|
|
133
129
|
|
|
@@ -172,7 +168,7 @@ const BUN_GLOBAL_MARKER = `${sep}install${sep}global${sep}`;
|
|
|
172
168
|
|
|
173
169
|
export const isGlobalCliInstall = (
|
|
174
170
|
entry = process.argv[1],
|
|
175
|
-
runtimePath =
|
|
171
|
+
runtimePath = findCliEntry(),
|
|
176
172
|
): boolean =>
|
|
177
173
|
[entry, runtimePath].some((path) => {
|
|
178
174
|
if (typeof path !== "string") return false;
|
package/src/cli/check.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { once } from "node:events";
|
|
5
5
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
8
|
|
|
10
9
|
import { assert } from "es-toolkit";
|
|
11
10
|
import {
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
type TestEvidence,
|
|
26
25
|
type TestEvidenceKind,
|
|
27
26
|
} from "../testing/test-evidence";
|
|
27
|
+
import { resolveCliEntry } from "./cli-entry";
|
|
28
28
|
import { loadProjectSource, type ProjectSource } from "./source";
|
|
29
29
|
|
|
30
30
|
export type CheckStatus = "passed" | "failed" | "missing" | "partial" | "infrastructure_error";
|
|
@@ -114,6 +114,18 @@ export type CheckProjectResult = {
|
|
|
114
114
|
readonly source?: ProjectSource;
|
|
115
115
|
readonly output: string;
|
|
116
116
|
};
|
|
117
|
+
|
|
118
|
+
export type FreshCheckFailurePhase = "spawn" | "execute" | "read_result" | "parse_result";
|
|
119
|
+
|
|
120
|
+
export class FreshCheckProcessError extends Error {
|
|
121
|
+
readonly phase: FreshCheckFailurePhase;
|
|
122
|
+
|
|
123
|
+
constructor(phase: FreshCheckFailurePhase, message: string, options?: ErrorOptions) {
|
|
124
|
+
super(message, options);
|
|
125
|
+
this.name = "FreshCheckProcessError";
|
|
126
|
+
this.phase = phase;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
117
129
|
const MAX_DIAGNOSTIC_CHARS = 5_000;
|
|
118
130
|
const MAX_TOOL_OUTPUT_BYTES = 64 * 1024;
|
|
119
131
|
export const CHECK_RESULT_FILE_ENV = "TOLOKA_INTERNAL_CHECK_RESULT_FILE";
|
|
@@ -865,45 +877,93 @@ const parseFreshCheckResult = (value: unknown, json: boolean): CheckProjectResul
|
|
|
865
877
|
output: json ? JSON.stringify({ exitCode, results }) : formatCheckResults(results),
|
|
866
878
|
};
|
|
867
879
|
};
|
|
880
|
+
|
|
881
|
+
const checkProcessOutput = (run: ToolRunResult): string =>
|
|
882
|
+
[run.stderr, run.stdout].filter(Boolean).join("\n");
|
|
883
|
+
|
|
884
|
+
export const readFreshCheckResult = async (
|
|
885
|
+
run: ToolRunResult,
|
|
886
|
+
readResult: () => Promise<string>,
|
|
887
|
+
json = false,
|
|
888
|
+
): Promise<CheckProjectResult> => {
|
|
889
|
+
let raw: string;
|
|
890
|
+
try {
|
|
891
|
+
raw = await readResult();
|
|
892
|
+
} catch (error) {
|
|
893
|
+
if (run.exitCode !== 0) {
|
|
894
|
+
const output = checkProcessOutput(run);
|
|
895
|
+
throw new FreshCheckProcessError(
|
|
896
|
+
"execute",
|
|
897
|
+
output.length === 0
|
|
898
|
+
? `Local check process exited ${run.exitCode} before producing results`
|
|
899
|
+
: `Local check process failed before producing results:\n${output}`,
|
|
900
|
+
{ cause: error },
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
throw new FreshCheckProcessError(
|
|
904
|
+
"read_result",
|
|
905
|
+
"Local check process completed without producing a result file",
|
|
906
|
+
{ cause: error },
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
let parsed: unknown;
|
|
911
|
+
try {
|
|
912
|
+
parsed = JSON.parse(raw);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
throw new FreshCheckProcessError("parse_result", "Local check process returned invalid JSON", {
|
|
915
|
+
cause: error,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
try {
|
|
920
|
+
return parseFreshCheckResult(parsed, json);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
throw new FreshCheckProcessError(
|
|
923
|
+
"parse_result",
|
|
924
|
+
`Local check process returned an invalid result: ${error instanceof Error ? error.message : String(error)}`,
|
|
925
|
+
{ cause: error },
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
};
|
|
868
929
|
export const runFreshCheck = async (options: CheckProjectOptions): Promise<CheckProjectResult> => {
|
|
869
|
-
const
|
|
870
|
-
const entry = existsSync(sourceEntry) ? sourceEntry : fileURLToPath(import.meta.url);
|
|
930
|
+
const entry = resolveCliEntry();
|
|
871
931
|
const resultDirectory = await mkdtemp(join(tmpdir(), "toloka-check-watch-result-"));
|
|
872
932
|
const resultPath = join(resultDirectory, "result.json");
|
|
873
|
-
let run: ToolRunResult | undefined;
|
|
874
933
|
try {
|
|
875
|
-
run
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
[
|
|
879
|
-
"--source-root",
|
|
934
|
+
let run: ToolRunResult;
|
|
935
|
+
try {
|
|
936
|
+
run = await runModule(
|
|
880
937
|
options.sourceRoot,
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
938
|
+
entry,
|
|
939
|
+
[
|
|
940
|
+
"--source-root",
|
|
941
|
+
options.sourceRoot,
|
|
942
|
+
"check",
|
|
943
|
+
...(options.pipeline === undefined ? [] : [options.pipeline]),
|
|
944
|
+
...(options.node === undefined ? [] : ["--node", options.node]),
|
|
945
|
+
"--json",
|
|
946
|
+
],
|
|
947
|
+
{
|
|
948
|
+
...(process.env.PATH === undefined ? {} : { PATH: process.env.PATH }),
|
|
949
|
+
...(options.environment ?? process.env),
|
|
950
|
+
[CHECK_RESULT_FILE_ENV]: resultPath,
|
|
951
|
+
},
|
|
952
|
+
options.signal,
|
|
953
|
+
"bun",
|
|
954
|
+
);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
throw new FreshCheckProcessError(
|
|
957
|
+
"spawn",
|
|
958
|
+
`Unable to start the local check process: ${error instanceof Error ? error.message : String(error)}`,
|
|
959
|
+
{ cause: error },
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
return await readFreshCheckResult(
|
|
963
|
+
run,
|
|
964
|
+
async () => readFile(resultPath, "utf8"),
|
|
896
965
|
options.json === true,
|
|
897
966
|
);
|
|
898
|
-
} catch (error) {
|
|
899
|
-
const detail = [
|
|
900
|
-
run?.stderr,
|
|
901
|
-
run?.stdout,
|
|
902
|
-
error instanceof Error ? error.message : String(error),
|
|
903
|
-
]
|
|
904
|
-
.filter(Boolean)
|
|
905
|
-
.join("\n");
|
|
906
|
-
throw new Error(`Fresh check process returned unreadable output: ${detail}`, { cause: error });
|
|
907
967
|
} finally {
|
|
908
968
|
await rm(resultDirectory, { recursive: true, force: true });
|
|
909
969
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { assert } from "es-toolkit";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const CLI_ENTRY = join("bin", "toloka.js");
|
|
6
|
+
|
|
7
|
+
const runtimePath = (entry: string): string => {
|
|
8
|
+
try {
|
|
9
|
+
return realpathSync(entry);
|
|
10
|
+
} catch {
|
|
11
|
+
return entry;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const findCliEntryAbove = (directory: string): string | undefined => {
|
|
16
|
+
const candidate = join(directory, CLI_ENTRY);
|
|
17
|
+
if (existsSync(candidate)) return candidate;
|
|
18
|
+
const parent = dirname(directory);
|
|
19
|
+
return parent === directory ? undefined : findCliEntryAbove(parent);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const findCliEntry = (
|
|
23
|
+
runtimeEntry: string | undefined = process.argv[1],
|
|
24
|
+
): string | undefined => {
|
|
25
|
+
const fromRuntimeEntry =
|
|
26
|
+
runtimeEntry === undefined ? undefined : findCliEntryAbove(dirname(runtimePath(runtimeEntry)));
|
|
27
|
+
return fromRuntimeEntry ?? findCliEntryAbove(process.cwd());
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const resolveCliEntry = (runtimeEntry: string | undefined = process.argv[1]): string => {
|
|
31
|
+
const entry = runtimeEntry === undefined ? undefined : runtimePath(runtimeEntry);
|
|
32
|
+
if (entry !== undefined && existsSync(entry) && findCliEntryAbove(dirname(entry)) !== undefined) {
|
|
33
|
+
return entry;
|
|
34
|
+
}
|
|
35
|
+
const workingDirectoryEntry = findCliEntryAbove(process.cwd());
|
|
36
|
+
assert(
|
|
37
|
+
workingDirectoryEntry !== undefined,
|
|
38
|
+
`Cannot locate the toloka CLI entry point. An installed @toloka-platform/sdk ships ${CLI_ENTRY} in a directory above its runtime entry, but none exists above ${runtimeEntry ?? "the unset process.argv[1]"} or above ${process.cwd()}. Reinstall @toloka-platform/sdk.`,
|
|
39
|
+
);
|
|
40
|
+
return workingDirectoryEntry;
|
|
41
|
+
};
|
package/src/cli/cost.ts
CHANGED
|
@@ -80,7 +80,13 @@ const collectNodeRows = (
|
|
|
80
80
|
};
|
|
81
81
|
|
|
82
82
|
const collectTotalRows = (total: PipelineCostEstimate["total"]): TableRow[] => {
|
|
83
|
-
const totals: TableRow[] =
|
|
83
|
+
const totals: TableRow[] =
|
|
84
|
+
total.expected === total.worstCase
|
|
85
|
+
? [["Run cost", total.expected, ""]]
|
|
86
|
+
: [
|
|
87
|
+
["Expected run cost", total.expected, ""],
|
|
88
|
+
["Maximum run cost", "", total.worstCase],
|
|
89
|
+
];
|
|
84
90
|
if (total.bestCase != null) {
|
|
85
91
|
totals.push(["Best case", total.bestCase, ""]);
|
|
86
92
|
}
|
package/src/cli/program.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
removeProject,
|
|
65
65
|
resolveProjectContext,
|
|
66
66
|
switchProject,
|
|
67
|
+
untrackProjectResource,
|
|
67
68
|
} from "./project-context";
|
|
68
69
|
import { estimatePipelineCost } from "./cost";
|
|
69
70
|
import { submitIssueReport } from "./report";
|
|
@@ -81,6 +82,7 @@ import {
|
|
|
81
82
|
|
|
82
83
|
import type { RunOutcome } from "./runs";
|
|
83
84
|
import { skillInstallCommand, skillListCommand, skillSearchCommand } from "./skill";
|
|
85
|
+
import { cliVersionInfo } from "./version";
|
|
84
86
|
import { findSourceRoot } from "./source";
|
|
85
87
|
|
|
86
88
|
export type CliIo = {
|
|
@@ -242,7 +244,7 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
242
244
|
|
|
243
245
|
program
|
|
244
246
|
.description(
|
|
245
|
-
"
|
|
247
|
+
"Beta. Build and operate Toloka pipeline projects from TypeScript source. The public API may change before general availability.",
|
|
246
248
|
)
|
|
247
249
|
.version(SDK_VERSION, "-v, --version", "output the current version")
|
|
248
250
|
.option(
|
|
@@ -282,10 +284,14 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
282
284
|
warnedAboutStaleSkills = true;
|
|
283
285
|
context.io.stderr(warning);
|
|
284
286
|
});
|
|
287
|
+
|
|
285
288
|
program
|
|
286
289
|
.command("version")
|
|
290
|
+
.option("--json", "print version and build provenance as JSON", false)
|
|
287
291
|
.description("print the current CLI version")
|
|
288
|
-
.action(() =>
|
|
292
|
+
.action((options: { json: boolean }) =>
|
|
293
|
+
context.io.stdout(options.json ? JSON.stringify(cliVersionInfo()) : SDK_VERSION),
|
|
294
|
+
);
|
|
289
295
|
|
|
290
296
|
const config = program.command("config").description("read and write machine CLI settings");
|
|
291
297
|
config
|
|
@@ -472,6 +478,26 @@ export function createProgram(context: ProgramContext): CreatedProgram {
|
|
|
472
478
|
io: context.io,
|
|
473
479
|
});
|
|
474
480
|
});
|
|
481
|
+
project
|
|
482
|
+
.command("untrack")
|
|
483
|
+
.argument("<type>", "pipeline or dataset")
|
|
484
|
+
.argument("<name>", "tracked resource name")
|
|
485
|
+
.option("--confirm", "confirm untracking in a non-interactive environment", false)
|
|
486
|
+
.description("remove a pipeline or dataset from local tracking without deleting it")
|
|
487
|
+
.action(async (type: string, name: string, options: { confirm: boolean }) => {
|
|
488
|
+
assert(
|
|
489
|
+
type === "pipeline" || type === "dataset",
|
|
490
|
+
"Resource type must be pipeline or dataset.",
|
|
491
|
+
);
|
|
492
|
+
const sourceRoot = await resolveSourceRoot(globals(), context.io);
|
|
493
|
+
await untrackProjectResource({
|
|
494
|
+
sourceRoot,
|
|
495
|
+
type,
|
|
496
|
+
name,
|
|
497
|
+
confirm: options.confirm,
|
|
498
|
+
io: context.io,
|
|
499
|
+
});
|
|
500
|
+
});
|
|
475
501
|
project
|
|
476
502
|
.command("create")
|
|
477
503
|
.argument("[alias]", "local name for the new project")
|