@toloka-platform/sdk 0.1.0-preview.9 → 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.
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import type {
5
5
  PipelineGraphSnapshot,
6
6
  PipelineGraphV1,
7
+ PipelineValidationIssue,
7
8
  PipelineValidationReport,
8
9
  } from "../../openapi/gen/models";
9
10
  import { resolvePipelineDefinition, validatePipelineDefinition } from "../authoring";
@@ -83,10 +84,13 @@ export type ApplyProjectResult = {
83
84
 
84
85
  export type ValidateProjectOptions = PlanProjectOptions;
85
86
 
87
+ export type PipelineValidationStatus = "valid" | "invalid" | "checks_pending";
88
+
86
89
  export type PipelineValidationResult = {
87
90
  readonly sourceDirectory: string;
88
91
  readonly pipelineId?: string;
89
92
  readonly report?: PipelineValidationReport;
93
+ readonly status?: PipelineValidationStatus;
90
94
  readonly skipped?: string;
91
95
  };
92
96
 
@@ -860,10 +864,47 @@ export async function applyProject(
860
864
  const issueLocation = (issue: { nodeId?: string; checkId?: string }) =>
861
865
  [issue.nodeId, issue.checkId].filter((value): value is string => value !== undefined).join("/");
862
866
 
867
+ const PENDING_CHECK_CODE = "not_ran_node_check";
868
+
869
+ const normalizeValidationIssue = (issue: PipelineValidationIssue): PipelineValidationIssue =>
870
+ issue.code === "empty_node_checks"
871
+ ? { ...issue, message: "No stored check cases are configured for this node" }
872
+ : issue;
873
+
874
+ const normalizeValidationReport = (
875
+ report: PipelineValidationReport,
876
+ ): { readonly report: PipelineValidationReport; readonly status: PipelineValidationStatus } => {
877
+ const seen = new Set<string>();
878
+ const issues = report.issues.flatMap((rawIssue) => {
879
+ const issue = normalizeValidationIssue(rawIssue);
880
+ const key = [
881
+ issue.code,
882
+ issue.severity,
883
+ issue.nodeId ?? "",
884
+ issue.checkId ?? "",
885
+ issue.message,
886
+ ].join("\n");
887
+ if (seen.has(key)) return [];
888
+ seen.add(key);
889
+ return [issue];
890
+ });
891
+ const pending = issues.some((issue) => issue.code === PENDING_CHECK_CODE);
892
+ const blocking = issues.some(
893
+ (issue) => issue.severity === "error" && issue.code !== PENDING_CHECK_CODE,
894
+ );
895
+ const status: PipelineValidationStatus =
896
+ blocking || (!report.valid && !pending) ? "invalid" : pending ? "checks_pending" : "valid";
897
+ return {
898
+ status,
899
+ report: { ...report, valid: status !== "invalid", issues },
900
+ };
901
+ };
902
+
863
903
  const formatValidationIssues = (report: PipelineValidationReport) =>
864
904
  report.issues.map((issue) => {
865
905
  const location = issueLocation(issue);
866
- return ` ${issue.severity} ${issue.code}${location.length === 0 ? "" : ` ${location}`}: ${issue.message}`;
906
+ const severity = issue.code === PENDING_CHECK_CODE ? "pending" : issue.severity;
907
+ return ` ${severity} ${issue.code}${location.length === 0 ? "" : ` ${location}`}: ${issue.message}`;
867
908
  });
868
909
 
869
910
  export async function validateProject(
@@ -898,24 +939,26 @@ export async function validateProject(
898
939
  resolvePipelineDefinition(pipeline.source.definition, { datasetId: dataset.id }),
899
940
  pipeline.snapshot.graph,
900
941
  );
901
- const report = await client.pipelines.validateGraph({
902
- pipelineId: pipeline.pipeline.id,
903
- graph,
904
- });
942
+ const normalized = normalizeValidationReport(
943
+ await client.pipelines.validateGraph({
944
+ pipelineId: pipeline.pipeline.id,
945
+ graph,
946
+ }),
947
+ );
905
948
  results.push({
906
949
  sourceDirectory: pipeline.source.sourceDirectory,
907
950
  pipelineId: pipeline.pipeline.id,
908
- report,
951
+ report: normalized.report,
952
+ status: normalized.status,
909
953
  });
910
954
  }
911
- const valid = results.every((result) => result.report?.valid !== false);
955
+ const valid = results.every((result) => result.status !== "invalid");
912
956
  const lines = results.flatMap((result) => {
913
957
  if (result.skipped !== undefined)
914
958
  return [`- ${result.sourceDirectory}: skipped (${result.skipped})`];
915
959
  assert(result.report, `Pipeline ${result.sourceDirectory} is missing a validation report`);
916
- const header = result.report.valid
917
- ? `- ${result.sourceDirectory}: valid`
918
- : `- ${result.sourceDirectory}: invalid`;
960
+ assert(result.status, `Pipeline ${result.sourceDirectory} is missing a validation status`);
961
+ const header = `- ${result.sourceDirectory}: ${result.status.replace("_", "-")}`;
919
962
  return [header, ...formatValidationIssues(result.report)];
920
963
  });
921
964
  return {
@@ -0,0 +1,68 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve, sep } from "node:path";
4
+
5
+ import { assert } from "es-toolkit";
6
+ import { SDK_VERSION } from "./init";
7
+
8
+ export type CliInstallChannel = "global-registry" | "linked" | "project-local";
9
+
10
+ type VersionInfoOptions = {
11
+ readonly globalBinDirectory?: string;
12
+ readonly globalPackageDirectory?: string;
13
+ };
14
+
15
+ const GLOBAL_PACKAGE_MARKER = `${sep}install${sep}global${sep}`;
16
+
17
+ const realpathOrSelf = (path: string): string => {
18
+ try {
19
+ return realpathSync(path);
20
+ } catch {
21
+ return path;
22
+ }
23
+ };
24
+
25
+ const classifyInstallChannel = (
26
+ entryPath: string,
27
+ packageRoot: string,
28
+ globalBinDirectory: string,
29
+ globalPackageDirectory: string,
30
+ ): CliInstallChannel => {
31
+ const resolvedEntryPath = realpathOrSelf(entryPath);
32
+ if (resolvedEntryPath.includes(GLOBAL_PACKAGE_MARKER)) return "global-registry";
33
+ if (realpathOrSelf(globalPackageDirectory) === packageRoot) return "linked";
34
+ if (dirname(resolve(entryPath)) === resolve(globalBinDirectory)) return "linked";
35
+ return "project-local";
36
+ };
37
+
38
+ export const cliVersionInfo = (entryPath = process.argv[1], options: VersionInfoOptions = {}) => {
39
+ assert(entryPath, "CLI entry path is unavailable");
40
+ const globalBinDirectory =
41
+ options.globalBinDirectory ??
42
+ resolve(process.env.BUN_INSTALL ?? join(homedir(), ".bun"), "bin");
43
+ const globalPackageDirectory =
44
+ options.globalPackageDirectory ??
45
+ resolve(
46
+ globalBinDirectory,
47
+ "..",
48
+ "install",
49
+ "global",
50
+ "node_modules",
51
+ "@toloka-platform",
52
+ "sdk",
53
+ );
54
+ const packageRoot = resolve(dirname(realpathOrSelf(entryPath)), "..");
55
+ const channel = classifyInstallChannel(
56
+ entryPath,
57
+ packageRoot,
58
+ globalBinDirectory,
59
+ globalPackageDirectory,
60
+ );
61
+
62
+ return {
63
+ version: SDK_VERSION,
64
+ commit: process.env.TOLOKA_BUILD_COMMIT ?? null,
65
+ channel,
66
+ ...(channel === "linked" ? { path: packageRoot } : {}),
67
+ };
68
+ };
package/src/index.ts CHANGED
@@ -69,6 +69,7 @@ export {
69
69
  connect,
70
70
  definePipeline,
71
71
  field,
72
+ optional,
72
73
  generation,
73
74
  isPipelineDefinition,
74
75
  match,
@@ -91,6 +92,7 @@ export type {
91
92
  InputPort,
92
93
  MatchAuthoringSettings,
93
94
  OutputPort,
95
+ OptionalFieldBinding,
94
96
  PipelineDefinition,
95
97
  PipelineDefinitionBindings,
96
98
  PipelineDefinitionInput,