deepline 0.1.101 → 0.1.102
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/cli/index.js +433 -20
- package/dist/cli/index.mjs +436 -22
- package/dist/index.js +68 -3
- package/dist/index.mjs +68 -3
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +209 -68
- package/dist/repo/apps/play-runner-workers/src/entry.ts +141 -33
- package/dist/repo/sdk/src/http.ts +89 -0
- package/dist/repo/sdk/src/release.ts +6 -2
- package/dist/repo/sdk/src/stream-reconnect.ts +1 -1
- package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +204 -0
- package/dist/repo/shared_libs/play-runtime/run-ledger.ts +7 -2
- package/dist/repo/shared_libs/play-runtime/run-snapshot-stream.ts +8 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -233,10 +233,14 @@ var SDK_RELEASE = {
|
|
|
233
233
|
// 0.1.98 ships the duplicate-browser-tab fix (default-browser detection).
|
|
234
234
|
// 0.1.99 ships prebuilt job-change source-column preservation and validation fixes.
|
|
235
235
|
// 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
|
|
236
|
-
|
|
236
|
+
// 0.1.102 ships the job-change ledger fixes: recovered-dataset export on
|
|
237
|
+
// failed runs, persisted/succeeded/failed row counts, strict local CSV
|
|
238
|
+
// preflight (existence, data rows, quotes, duplicate headers), HTML error
|
|
239
|
+
// scrubbing, and word-boundary watch truncation.
|
|
240
|
+
version: "0.1.102",
|
|
237
241
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
238
242
|
supportPolicy: {
|
|
239
|
-
latest: "0.1.
|
|
243
|
+
latest: "0.1.102",
|
|
240
244
|
minimumSupported: "0.1.53",
|
|
241
245
|
deprecatedBelow: "0.1.53"
|
|
242
246
|
}
|
|
@@ -387,6 +391,22 @@ var HttpClient = class {
|
|
|
387
391
|
parsed = body;
|
|
388
392
|
}
|
|
389
393
|
if (!response.ok) {
|
|
394
|
+
const htmlError = detectHtmlErrorBody(
|
|
395
|
+
body,
|
|
396
|
+
response.headers.get("content-type")
|
|
397
|
+
);
|
|
398
|
+
if (htmlError) {
|
|
399
|
+
throw new DeeplineError(
|
|
400
|
+
htmlError.message(response.status),
|
|
401
|
+
response.status,
|
|
402
|
+
"API_ERROR",
|
|
403
|
+
{
|
|
404
|
+
htmlErrorPage: true,
|
|
405
|
+
...htmlError.title ? { title: htmlError.title } : {},
|
|
406
|
+
...htmlError.workerThrewException ? { workerThrewException: true } : {}
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
}
|
|
390
410
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
391
411
|
const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
|
|
392
412
|
throw new DeeplineError(msg, response.status, "API_ERROR", {
|
|
@@ -447,6 +467,22 @@ var HttpClient = class {
|
|
|
447
467
|
}
|
|
448
468
|
if (!response.ok) {
|
|
449
469
|
const body = await response.text();
|
|
470
|
+
const htmlError = detectHtmlErrorBody(
|
|
471
|
+
body,
|
|
472
|
+
response.headers.get("content-type")
|
|
473
|
+
);
|
|
474
|
+
if (htmlError) {
|
|
475
|
+
throw new DeeplineError(
|
|
476
|
+
htmlError.message(response.status),
|
|
477
|
+
response.status,
|
|
478
|
+
"API_ERROR",
|
|
479
|
+
{
|
|
480
|
+
htmlErrorPage: true,
|
|
481
|
+
...htmlError.title ? { title: htmlError.title } : {},
|
|
482
|
+
...htmlError.workerThrewException ? { workerThrewException: true } : {}
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
}
|
|
450
486
|
const parsed = parseResponseBody(body);
|
|
451
487
|
throw new DeeplineError(
|
|
452
488
|
apiErrorMessage(parsed, response.status),
|
|
@@ -512,6 +548,31 @@ function parseResponseBody(body) {
|
|
|
512
548
|
return body;
|
|
513
549
|
}
|
|
514
550
|
}
|
|
551
|
+
function detectHtmlErrorBody(body, contentType) {
|
|
552
|
+
const trimmed = body.trim();
|
|
553
|
+
const lower = trimmed.toLowerCase();
|
|
554
|
+
const isHtml = (contentType ?? "").toLowerCase().includes("text/html") || lower.startsWith("<!doctype") || lower.startsWith("<html");
|
|
555
|
+
if (!isHtml) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
const titleMatch = trimmed.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
559
|
+
const title = titleMatch?.[1]?.replace(/\s+/g, " ").trim() || void 0;
|
|
560
|
+
const workerThrewException = /worker threw exception/i.test(trimmed);
|
|
561
|
+
return {
|
|
562
|
+
title,
|
|
563
|
+
workerThrewException,
|
|
564
|
+
message: (status) => {
|
|
565
|
+
const segments = [`HTTP ${status}`];
|
|
566
|
+
if (workerThrewException) {
|
|
567
|
+
segments.push("Worker threw exception");
|
|
568
|
+
}
|
|
569
|
+
if (title) {
|
|
570
|
+
segments.push(title);
|
|
571
|
+
}
|
|
572
|
+
return `${segments.join(": ")} (Cloudflare HTML error page suppressed)`;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
515
576
|
function apiErrorMessage(parsed, status) {
|
|
516
577
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
517
578
|
if (typeof errorValue === "string") {
|
|
@@ -607,7 +668,7 @@ function isTransientPlayStreamError(error) {
|
|
|
607
668
|
return error.statusCode >= 500 && error.statusCode < 600;
|
|
608
669
|
}
|
|
609
670
|
const text = error instanceof Error ? error.message : String(error);
|
|
610
|
-
return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
|
|
671
|
+
return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
|
|
611
672
|
text
|
|
612
673
|
);
|
|
613
674
|
}
|
|
@@ -839,6 +900,10 @@ function buildSnapshotFromLedger(snapshot) {
|
|
|
839
900
|
return {
|
|
840
901
|
runId: snapshot.runId,
|
|
841
902
|
status: normalizePlayRunLiveStatus(snapshot.status),
|
|
903
|
+
createdAt: snapshot.createdAt ?? null,
|
|
904
|
+
startedAt: snapshot.startedAt ?? null,
|
|
905
|
+
finishedAt: snapshot.finishedAt ?? null,
|
|
906
|
+
durationMs: snapshot.durationMs ?? null,
|
|
842
907
|
updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
|
|
843
908
|
logs: snapshot.logTail,
|
|
844
909
|
totalLogCount: snapshot.totalLogCount,
|
|
@@ -4549,14 +4614,25 @@ var import_node_fs5 = require("fs");
|
|
|
4549
4614
|
var import_node_path6 = require("path");
|
|
4550
4615
|
|
|
4551
4616
|
// ../shared_libs/plays/dataset-summary.ts
|
|
4617
|
+
function formatDatasetRowCountsLine(counts) {
|
|
4618
|
+
const { persisted, succeeded, failed } = counts;
|
|
4619
|
+
if (succeeded === persisted && failed === 0) {
|
|
4620
|
+
return `${persisted} persisted`;
|
|
4621
|
+
}
|
|
4622
|
+
return `${persisted} persisted (${succeeded} succeeded, ${failed} failed)`;
|
|
4623
|
+
}
|
|
4552
4624
|
function datasetSummaryPercentText(numerator, denominator) {
|
|
4553
4625
|
return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
|
|
4554
4626
|
}
|
|
4555
4627
|
function readCount(value) {
|
|
4556
4628
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
|
|
4557
4629
|
}
|
|
4558
|
-
function
|
|
4559
|
-
return
|
|
4630
|
+
function executionAttemptTotal(raw) {
|
|
4631
|
+
return readCount(raw.queued) + readCount(raw.running) + readCount(raw.completed) + readCount(raw.cached) + readCount(raw.skipped) + readCount(raw.missed) + readCount(raw.failed);
|
|
4632
|
+
}
|
|
4633
|
+
function formatDatasetExecutionStats(raw, _persistedRowTotal) {
|
|
4634
|
+
const denominator = executionAttemptTotal(raw);
|
|
4635
|
+
const stats = {
|
|
4560
4636
|
queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
|
|
4561
4637
|
running: datasetSummaryPercentText(readCount(raw.running), denominator),
|
|
4562
4638
|
"completed:executed": datasetSummaryPercentText(
|
|
@@ -4577,6 +4653,17 @@ function formatDatasetExecutionStats(raw, denominator) {
|
|
|
4577
4653
|
),
|
|
4578
4654
|
failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
|
|
4579
4655
|
};
|
|
4656
|
+
if (Object.values(stats).some((text) => {
|
|
4657
|
+
const match = /\((\d+)%\)/.exec(text);
|
|
4658
|
+
return match ? Number(match[1]) > 100 : false;
|
|
4659
|
+
})) {
|
|
4660
|
+
throw new Error(
|
|
4661
|
+
`formatDatasetExecutionStats produced a >100% execution stat; column counts are corrupt: ${JSON.stringify(
|
|
4662
|
+
raw
|
|
4663
|
+
)}`
|
|
4664
|
+
);
|
|
4665
|
+
}
|
|
4666
|
+
return stats;
|
|
4580
4667
|
}
|
|
4581
4668
|
|
|
4582
4669
|
// src/cli/dataset-stats.ts
|
|
@@ -4726,7 +4813,8 @@ function canonicalRowsInfoFromCandidate(input2) {
|
|
|
4726
4813
|
complete: rows2.length === totalRows2,
|
|
4727
4814
|
source: candidate.source,
|
|
4728
4815
|
datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
|
|
4729
|
-
tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
|
|
4816
|
+
tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
|
|
4817
|
+
...candidate.value.recovered === true ? { recovered: true } : {}
|
|
4730
4818
|
};
|
|
4731
4819
|
}
|
|
4732
4820
|
if (candidate.serializedOnly) {
|
|
@@ -4876,18 +4964,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
|
|
|
4876
4964
|
}
|
|
4877
4965
|
return infos;
|
|
4878
4966
|
}
|
|
4879
|
-
function
|
|
4967
|
+
function collectPackagedStepDatasetCandidates(statusOrResult) {
|
|
4880
4968
|
const root = isRecord3(statusOrResult) ? statusOrResult : null;
|
|
4881
|
-
|
|
4882
|
-
if (!result) {
|
|
4969
|
+
if (!root) {
|
|
4883
4970
|
return [];
|
|
4884
4971
|
}
|
|
4972
|
+
const pkg = isRecord3(root.package) ? root.package : root;
|
|
4973
|
+
const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
|
|
4885
4974
|
const candidates = [];
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4975
|
+
for (const step of steps) {
|
|
4976
|
+
if (!isRecord3(step) || !isRecord3(step.output)) {
|
|
4977
|
+
continue;
|
|
4978
|
+
}
|
|
4979
|
+
const output2 = step.output;
|
|
4980
|
+
if (!isPackagedDatasetOutput(output2)) {
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4983
|
+
const source = typeof output2.path === "string" && output2.path.trim() ? output2.path.trim() : typeof step.id === "string" ? step.id : null;
|
|
4984
|
+
if (!source) {
|
|
4985
|
+
continue;
|
|
4986
|
+
}
|
|
4987
|
+
candidates.push({
|
|
4988
|
+
source,
|
|
4989
|
+
value: output2,
|
|
4990
|
+
total: output2.rowCount ?? (isRecord3(output2.preview) ? output2.preview.totalRows : void 0) ?? (isRecord3(step.progress) ? step.progress.total : void 0)
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
return candidates;
|
|
4994
|
+
}
|
|
4995
|
+
function collectSerializedDatasetRowsInfos(statusOrResult) {
|
|
4996
|
+
const root = isRecord3(statusOrResult) ? statusOrResult : null;
|
|
4997
|
+
const result = isRecord3(root?.result) ? root.result : root;
|
|
4998
|
+
const candidates = [];
|
|
4999
|
+
if (result) {
|
|
5000
|
+
collectDatasetCandidates({
|
|
5001
|
+
value: result,
|
|
5002
|
+
path: "result",
|
|
5003
|
+
output: candidates
|
|
5004
|
+
});
|
|
5005
|
+
}
|
|
5006
|
+
candidates.push(...collectPackagedStepDatasetCandidates(statusOrResult));
|
|
4891
5007
|
const seen = /* @__PURE__ */ new Set();
|
|
4892
5008
|
const infos = [];
|
|
4893
5009
|
for (const candidate of candidates) {
|
|
@@ -5576,6 +5692,7 @@ var import_node_path13 = require("path");
|
|
|
5576
5692
|
var import_node_crypto3 = require("crypto");
|
|
5577
5693
|
var import_node_fs10 = require("fs");
|
|
5578
5694
|
var import_node_path12 = require("path");
|
|
5695
|
+
var import_sync5 = require("csv-parse/sync");
|
|
5579
5696
|
|
|
5580
5697
|
// src/plays/bundle-play-file.ts
|
|
5581
5698
|
var import_node_os6 = require("os");
|
|
@@ -9374,6 +9491,177 @@ function inputContainsLocalFilePath(value) {
|
|
|
9374
9491
|
}
|
|
9375
9492
|
return false;
|
|
9376
9493
|
}
|
|
9494
|
+
function isUrlValue(value) {
|
|
9495
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
|
|
9496
|
+
}
|
|
9497
|
+
function looksLikeStagedFileRef(value) {
|
|
9498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9499
|
+
return false;
|
|
9500
|
+
}
|
|
9501
|
+
const record = value;
|
|
9502
|
+
return typeof record.contentHash === "string" || typeof record.contentBase64 === "string" || typeof record.logicalPath === "string" && typeof record.bytes === "number";
|
|
9503
|
+
}
|
|
9504
|
+
var CSV_DATA_INPUT_KEY = "csv";
|
|
9505
|
+
function collectLocalFileInputRefs(value, inputPath, key, out) {
|
|
9506
|
+
if (typeof value === "string") {
|
|
9507
|
+
const trimmed = value.trim();
|
|
9508
|
+
if (!trimmed || isUrlValue(trimmed)) {
|
|
9509
|
+
return;
|
|
9510
|
+
}
|
|
9511
|
+
const keyIsCsvData = key === CSV_DATA_INPUT_KEY;
|
|
9512
|
+
const endsWithCsv = /\.csv$/i.test(trimmed);
|
|
9513
|
+
const looksLikeFile = /\.[a-z0-9]{1,8}$/i.test(trimmed);
|
|
9514
|
+
if (keyIsCsvData) {
|
|
9515
|
+
out.push({ inputPath, value: trimmed, isCsvData: true });
|
|
9516
|
+
} else if (endsWithCsv || looksLikeFile && (0, import_node_fs10.existsSync)((0, import_node_path12.resolve)(trimmed))) {
|
|
9517
|
+
out.push({ inputPath, value: trimmed, isCsvData: false });
|
|
9518
|
+
}
|
|
9519
|
+
return;
|
|
9520
|
+
}
|
|
9521
|
+
if (Array.isArray(value)) {
|
|
9522
|
+
value.forEach(
|
|
9523
|
+
(entry, index) => collectLocalFileInputRefs(entry, `${inputPath}[${index}]`, key, out)
|
|
9524
|
+
);
|
|
9525
|
+
return;
|
|
9526
|
+
}
|
|
9527
|
+
if (looksLikeStagedFileRef(value)) {
|
|
9528
|
+
return;
|
|
9529
|
+
}
|
|
9530
|
+
if (value && typeof value === "object") {
|
|
9531
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
9532
|
+
collectLocalFileInputRefs(
|
|
9533
|
+
child,
|
|
9534
|
+
inputPath ? `${inputPath}.${childKey}` : childKey,
|
|
9535
|
+
childKey,
|
|
9536
|
+
out
|
|
9537
|
+
);
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
function preflightLocalFileInputs(runtimeInput) {
|
|
9542
|
+
if (CSV_DATA_INPUT_KEY in runtimeInput) {
|
|
9543
|
+
const csvValue = runtimeInput[CSV_DATA_INPUT_KEY];
|
|
9544
|
+
if (typeof csvValue === "string" && csvValue.trim().length === 0) {
|
|
9545
|
+
throw new DeeplineError(
|
|
9546
|
+
`Input ${CSV_DATA_INPUT_KEY} is an empty string. Provide a path to a CSV file (or a CSV URL). No run was created.`,
|
|
9547
|
+
void 0,
|
|
9548
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9549
|
+
{ inputPath: CSV_DATA_INPUT_KEY, reason: "csv_empty_string" }
|
|
9550
|
+
);
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
9553
|
+
const refs = [];
|
|
9554
|
+
for (const [key, value] of Object.entries(runtimeInput)) {
|
|
9555
|
+
collectLocalFileInputRefs(value, key, key, refs);
|
|
9556
|
+
}
|
|
9557
|
+
for (const ref of refs) {
|
|
9558
|
+
const absolutePath = (0, import_node_path12.resolve)(ref.value);
|
|
9559
|
+
if (!(0, import_node_fs10.existsSync)(absolutePath)) {
|
|
9560
|
+
throw new DeeplineError(
|
|
9561
|
+
`Input ${ref.inputPath} references a local file that does not exist: ${ref.value} (resolved to ${absolutePath}). No run was created.`,
|
|
9562
|
+
void 0,
|
|
9563
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9564
|
+
{ inputPath: ref.inputPath, path: ref.value, resolved: absolutePath }
|
|
9565
|
+
);
|
|
9566
|
+
}
|
|
9567
|
+
let stat4;
|
|
9568
|
+
try {
|
|
9569
|
+
stat4 = (0, import_node_fs10.statSync)(absolutePath);
|
|
9570
|
+
} catch (error) {
|
|
9571
|
+
throw new DeeplineError(
|
|
9572
|
+
`Input ${ref.inputPath} references a local file that is not readable: ${ref.value} (${error instanceof Error ? error.message : String(error)}). No run was created.`,
|
|
9573
|
+
void 0,
|
|
9574
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9575
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9576
|
+
);
|
|
9577
|
+
}
|
|
9578
|
+
if (!stat4.isFile()) {
|
|
9579
|
+
throw new DeeplineError(
|
|
9580
|
+
`Input ${ref.inputPath} references ${ref.value}, which is not a file. No run was created.`,
|
|
9581
|
+
void 0,
|
|
9582
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9583
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9584
|
+
);
|
|
9585
|
+
}
|
|
9586
|
+
if (!ref.isCsvData) {
|
|
9587
|
+
continue;
|
|
9588
|
+
}
|
|
9589
|
+
preflightCsvDataInput(ref, absolutePath);
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
function preflightCsvDataInput(ref, absolutePath) {
|
|
9593
|
+
let content;
|
|
9594
|
+
try {
|
|
9595
|
+
content = (0, import_node_fs10.readFileSync)(absolutePath, "utf-8");
|
|
9596
|
+
} catch (error) {
|
|
9597
|
+
throw new DeeplineError(
|
|
9598
|
+
`Input ${ref.inputPath} CSV ${ref.value} is not readable: ${error instanceof Error ? error.message : String(error)}. No run was created.`,
|
|
9599
|
+
void 0,
|
|
9600
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9601
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9602
|
+
);
|
|
9603
|
+
}
|
|
9604
|
+
let records;
|
|
9605
|
+
try {
|
|
9606
|
+
records = (0, import_sync5.parse)(content, {
|
|
9607
|
+
bom: true,
|
|
9608
|
+
columns: false,
|
|
9609
|
+
skip_empty_lines: true,
|
|
9610
|
+
relax_column_count: true,
|
|
9611
|
+
// STRICT RFC quoting: an unclosed quote or ragged quoting is a hard
|
|
9612
|
+
// error rather than being swallowed into one giant field.
|
|
9613
|
+
relax_quotes: false,
|
|
9614
|
+
trim: true
|
|
9615
|
+
});
|
|
9616
|
+
} catch (error) {
|
|
9617
|
+
throw new DeeplineError(
|
|
9618
|
+
`Input ${ref.inputPath} could not be parsed as CSV (${ref.value}): ${error instanceof Error ? error.message : String(error)}. This usually means an unclosed quote or ragged quoting. No run was created.`,
|
|
9619
|
+
void 0,
|
|
9620
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9621
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9622
|
+
);
|
|
9623
|
+
}
|
|
9624
|
+
if (records.length === 0) {
|
|
9625
|
+
throw new DeeplineError(
|
|
9626
|
+
`${ref.value} has a header row but no data rows. No run was created.`,
|
|
9627
|
+
void 0,
|
|
9628
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9629
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9630
|
+
);
|
|
9631
|
+
}
|
|
9632
|
+
const header = records[0];
|
|
9633
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9634
|
+
for (let i = 0; i < header.length; i++) {
|
|
9635
|
+
const raw = header[i] ?? "";
|
|
9636
|
+
const name = raw.trim();
|
|
9637
|
+
if (name.length === 0) continue;
|
|
9638
|
+
const prior = seen.get(name);
|
|
9639
|
+
if (prior) {
|
|
9640
|
+
throw new DeeplineError(
|
|
9641
|
+
`Input ${ref.inputPath} (${ref.value}) has a duplicate CSV header "${name}" (columns ${prior.index + 1} and ${i + 1}). The second column would silently overwrite the first. Rename one of them. No run was created.`,
|
|
9642
|
+
void 0,
|
|
9643
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9644
|
+
{
|
|
9645
|
+
inputPath: ref.inputPath,
|
|
9646
|
+
path: ref.value,
|
|
9647
|
+
duplicateHeader: name,
|
|
9648
|
+
columns: [prior.index + 1, i + 1],
|
|
9649
|
+
rawSpellings: [prior.raw, raw]
|
|
9650
|
+
}
|
|
9651
|
+
);
|
|
9652
|
+
}
|
|
9653
|
+
seen.set(name, { index: i, raw });
|
|
9654
|
+
}
|
|
9655
|
+
const dataRowCount = records.length - 1;
|
|
9656
|
+
if (dataRowCount < 1) {
|
|
9657
|
+
throw new DeeplineError(
|
|
9658
|
+
`${ref.value} has a header row but no data rows. No run was created.`,
|
|
9659
|
+
void 0,
|
|
9660
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9661
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9662
|
+
);
|
|
9663
|
+
}
|
|
9664
|
+
}
|
|
9377
9665
|
function namedRunNeedsPlayDefinition(input2) {
|
|
9378
9666
|
return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
|
|
9379
9667
|
}
|
|
@@ -9608,6 +9896,21 @@ function isRetryablePendingStartFailure(status) {
|
|
|
9608
9896
|
if (status.runId && status.runId !== "pending") return false;
|
|
9609
9897
|
return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
|
|
9610
9898
|
}
|
|
9899
|
+
function pendingStartFailureStatus(input2) {
|
|
9900
|
+
const message = input2.error instanceof Error ? input2.error.message : String(input2.error);
|
|
9901
|
+
return {
|
|
9902
|
+
runId: "pending",
|
|
9903
|
+
name: input2.playName,
|
|
9904
|
+
playName: input2.playName,
|
|
9905
|
+
dashboardUrl: input2.dashboardUrl,
|
|
9906
|
+
status: "failed",
|
|
9907
|
+
progress: {
|
|
9908
|
+
status: "failed",
|
|
9909
|
+
logs: [],
|
|
9910
|
+
error: message
|
|
9911
|
+
}
|
|
9912
|
+
};
|
|
9913
|
+
}
|
|
9611
9914
|
var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
|
|
9612
9915
|
"completed",
|
|
9613
9916
|
"failed",
|
|
@@ -10387,6 +10690,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10387
10690
|
progress: input2.progress
|
|
10388
10691
|
});
|
|
10389
10692
|
}
|
|
10693
|
+
if (!lastKnownWorkflowId && isTransientPlayStreamError(error)) {
|
|
10694
|
+
recordCliTrace({
|
|
10695
|
+
phase: "cli.play_start_stream_transient_failure",
|
|
10696
|
+
ms: Date.now() - startedAt,
|
|
10697
|
+
ok: false,
|
|
10698
|
+
playName: input2.playName,
|
|
10699
|
+
eventCount,
|
|
10700
|
+
firstRunIdMs,
|
|
10701
|
+
lastPhase,
|
|
10702
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
10703
|
+
});
|
|
10704
|
+
return pendingStartFailureStatus({
|
|
10705
|
+
playName: input2.playName,
|
|
10706
|
+
dashboardUrl,
|
|
10707
|
+
error
|
|
10708
|
+
});
|
|
10709
|
+
}
|
|
10390
10710
|
throw error;
|
|
10391
10711
|
} finally {
|
|
10392
10712
|
if (timeout) {
|
|
@@ -10438,6 +10758,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10438
10758
|
function formatInteger(value) {
|
|
10439
10759
|
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
|
|
10440
10760
|
}
|
|
10761
|
+
var RUN_ERROR_DISPLAY_MAX_CHARS = 2e3;
|
|
10762
|
+
function truncateErrorForDisplay(message, runId, maxChars = RUN_ERROR_DISPLAY_MAX_CHARS) {
|
|
10763
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
10764
|
+
if (compact.length <= maxChars) {
|
|
10765
|
+
return compact;
|
|
10766
|
+
}
|
|
10767
|
+
const slice = compact.slice(0, maxChars);
|
|
10768
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
10769
|
+
const wordBoundary = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
|
|
10770
|
+
const fullErrorHint = runId ? ` (full error: deepline runs get ${runId} --full)` : " (full error: deepline runs get <runId> --full)";
|
|
10771
|
+
return `${wordBoundary}\u2026${fullErrorHint}`;
|
|
10772
|
+
}
|
|
10773
|
+
function clampJsonPreviewText(json, maxChars) {
|
|
10774
|
+
if (json.length <= maxChars) {
|
|
10775
|
+
return json;
|
|
10776
|
+
}
|
|
10777
|
+
const slice = json.slice(0, maxChars);
|
|
10778
|
+
const lastNewline = slice.lastIndexOf("\n");
|
|
10779
|
+
const head = lastNewline > 0 ? slice.slice(0, lastNewline) : slice;
|
|
10780
|
+
return `${head}
|
|
10781
|
+
... truncated; use --json for full output`;
|
|
10782
|
+
}
|
|
10441
10783
|
var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
|
|
10442
10784
|
"contract",
|
|
10443
10785
|
"staticPipeline",
|
|
@@ -10522,8 +10864,7 @@ function formatJsonPreview(value) {
|
|
|
10522
10864
|
return [];
|
|
10523
10865
|
}
|
|
10524
10866
|
const MAX_CHARS = 4e3;
|
|
10525
|
-
const truncated = json
|
|
10526
|
-
... truncated; use --json for full output` : json;
|
|
10867
|
+
const truncated = clampJsonPreviewText(json, MAX_CHARS);
|
|
10527
10868
|
return truncated.split("\n").map((line) => ` ${line}`);
|
|
10528
10869
|
}
|
|
10529
10870
|
function formatReturnValue(result) {
|
|
@@ -11054,6 +11395,9 @@ function readRecordArray(value) {
|
|
|
11054
11395
|
function readRecord(value) {
|
|
11055
11396
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
11056
11397
|
}
|
|
11398
|
+
function readNonNegativeInteger(value) {
|
|
11399
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
11400
|
+
}
|
|
11057
11401
|
function formatSummaryScalar(value) {
|
|
11058
11402
|
if (typeof value === "number") {
|
|
11059
11403
|
return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
|
|
@@ -11086,7 +11430,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
|
|
|
11086
11430
|
return [];
|
|
11087
11431
|
}
|
|
11088
11432
|
const lines = [];
|
|
11089
|
-
const
|
|
11433
|
+
const rowCounts = readRecord(record.rowCounts);
|
|
11434
|
+
if (rowCounts) {
|
|
11435
|
+
const persisted = readNonNegativeInteger(rowCounts.persisted);
|
|
11436
|
+
const succeeded = readNonNegativeInteger(rowCounts.succeeded);
|
|
11437
|
+
const failed = readNonNegativeInteger(rowCounts.failed);
|
|
11438
|
+
if (persisted !== null) {
|
|
11439
|
+
lines.push(
|
|
11440
|
+
`${indent2}rows: ${formatDatasetRowCountsLine({
|
|
11441
|
+
persisted,
|
|
11442
|
+
succeeded: succeeded ?? persisted,
|
|
11443
|
+
failed: failed ?? 0
|
|
11444
|
+
})}`
|
|
11445
|
+
);
|
|
11446
|
+
}
|
|
11447
|
+
}
|
|
11448
|
+
const parts = formatSummaryScalarParts(
|
|
11449
|
+
record,
|
|
11450
|
+
/* @__PURE__ */ new Set(["columnStats", "rowCounts"])
|
|
11451
|
+
);
|
|
11090
11452
|
if (parts.length > 0) {
|
|
11091
11453
|
lines.push(`${indent2}summary: ${parts.join(" ")}`);
|
|
11092
11454
|
}
|
|
@@ -11147,7 +11509,7 @@ function buildRunPackageTextLines(packaged) {
|
|
|
11147
11509
|
];
|
|
11148
11510
|
const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
|
|
11149
11511
|
if (runError && (status === "failed" || status === "cancelled")) {
|
|
11150
|
-
lines.push(` error: ${runError
|
|
11512
|
+
lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
|
|
11151
11513
|
}
|
|
11152
11514
|
for (const step of readRecordArray(packaged.steps)) {
|
|
11153
11515
|
const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
|
|
@@ -11232,7 +11594,7 @@ function writePlayResult(status, jsonOutput, options) {
|
|
|
11232
11594
|
lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
|
|
11233
11595
|
}
|
|
11234
11596
|
const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
|
|
11235
|
-
lines.push(` error: ${displayError
|
|
11597
|
+
lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
|
|
11236
11598
|
}
|
|
11237
11599
|
const renderedServerView = renderServerResultView(status.resultView);
|
|
11238
11600
|
if (result) {
|
|
@@ -11403,12 +11765,51 @@ async function fetchBackingDatasetRows(input2) {
|
|
|
11403
11765
|
source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
|
|
11404
11766
|
};
|
|
11405
11767
|
}
|
|
11768
|
+
function resolveDatasetByName(available, datasetPath) {
|
|
11769
|
+
const target = datasetPath.trim();
|
|
11770
|
+
const exact = available.find((info) => info.source === target);
|
|
11771
|
+
if (exact) {
|
|
11772
|
+
return exact;
|
|
11773
|
+
}
|
|
11774
|
+
const byNamespace = available.find(
|
|
11775
|
+
(info) => info.tableNamespace && info.tableNamespace === target
|
|
11776
|
+
);
|
|
11777
|
+
if (byNamespace) {
|
|
11778
|
+
return byNamespace;
|
|
11779
|
+
}
|
|
11780
|
+
const trailing = target.split(".").filter(Boolean).at(-1);
|
|
11781
|
+
if (!trailing) {
|
|
11782
|
+
return null;
|
|
11783
|
+
}
|
|
11784
|
+
const byTrailing = available.find(
|
|
11785
|
+
(info) => info.tableNamespace === trailing || typeof info.source === "string" && info.source.split(".").filter(Boolean).at(-1) === trailing
|
|
11786
|
+
);
|
|
11787
|
+
if (byTrailing) {
|
|
11788
|
+
return byTrailing;
|
|
11789
|
+
}
|
|
11790
|
+
if (target.split(".").filter(Boolean)[0] === "result") {
|
|
11791
|
+
const recovered = available.filter((info) => info.recovered);
|
|
11792
|
+
if (recovered.length === 1) {
|
|
11793
|
+
return recovered[0];
|
|
11794
|
+
}
|
|
11795
|
+
if (recovered.length > 1) {
|
|
11796
|
+
const names = recovered.map((info) => info.tableNamespace ?? info.source).filter((name) => typeof name === "string");
|
|
11797
|
+
throw new DeeplineError(
|
|
11798
|
+
`Run returned multiple recovered datasets; '${target}' is ambiguous. Choose one with --dataset <path>: ${names.join(", ")}.`,
|
|
11799
|
+
void 0,
|
|
11800
|
+
"RUN_EXPORT_DATASET_AMBIGUOUS",
|
|
11801
|
+
{ dataset: target, available: names }
|
|
11802
|
+
);
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
return null;
|
|
11806
|
+
}
|
|
11406
11807
|
async function exportPlayStatusRows(client2, status, outPath, options = {}) {
|
|
11407
11808
|
if (!outPath) {
|
|
11408
11809
|
return null;
|
|
11409
11810
|
}
|
|
11410
11811
|
const availableRows = collectSerializedDatasetRowsInfos(status);
|
|
11411
|
-
const rowsInfo = options.datasetPath ? availableRows
|
|
11812
|
+
const rowsInfo = options.datasetPath ? resolveDatasetByName(availableRows, options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
|
|
11412
11813
|
if (!rowsInfo && options.datasetPath) {
|
|
11413
11814
|
const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
|
|
11414
11815
|
throw new DeeplineError(
|
|
@@ -12154,6 +12555,12 @@ async function handleFileBackedRun(options) {
|
|
|
12154
12555
|
() => (0, import_node_fs10.readFileSync)(absolutePlayPath, "utf-8")
|
|
12155
12556
|
);
|
|
12156
12557
|
const runtimeInput = options.input ? { ...options.input } : {};
|
|
12558
|
+
try {
|
|
12559
|
+
preflightLocalFileInputs(runtimeInput);
|
|
12560
|
+
} catch (error) {
|
|
12561
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
12562
|
+
return 1;
|
|
12563
|
+
}
|
|
12157
12564
|
let graph;
|
|
12158
12565
|
try {
|
|
12159
12566
|
graph = await traceCliSpan(
|
|
@@ -12311,6 +12718,12 @@ async function handleNamedRun(options) {
|
|
|
12311
12718
|
const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
|
|
12312
12719
|
const playName = options.target.name;
|
|
12313
12720
|
const runtimeInput = options.input ? { ...options.input } : {};
|
|
12721
|
+
try {
|
|
12722
|
+
preflightLocalFileInputs(runtimeInput);
|
|
12723
|
+
} catch (error) {
|
|
12724
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
12725
|
+
return 1;
|
|
12726
|
+
}
|
|
12314
12727
|
const needsPlayDefinition = namedRunNeedsPlayDefinition({
|
|
12315
12728
|
runtimeInput,
|
|
12316
12729
|
revisionSelector: options.revisionSelector
|