deepline 0.1.100 → 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 +434 -20
- package/dist/cli/index.mjs +437 -22
- package/dist/index.js +69 -3
- package/dist/index.mjs +69 -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 +7 -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
|
@@ -232,10 +232,15 @@ var SDK_RELEASE = {
|
|
|
232
232
|
// 0.1.94 is claimed by PR #1527 — this watch-render fix ships as 0.1.95.
|
|
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
|
+
// 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",
|
|
236
241
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
237
242
|
supportPolicy: {
|
|
238
|
-
latest: "0.1.
|
|
243
|
+
latest: "0.1.102",
|
|
239
244
|
minimumSupported: "0.1.53",
|
|
240
245
|
deprecatedBelow: "0.1.53"
|
|
241
246
|
}
|
|
@@ -386,6 +391,22 @@ var HttpClient = class {
|
|
|
386
391
|
parsed = body;
|
|
387
392
|
}
|
|
388
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
|
+
}
|
|
389
410
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
390
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}`;
|
|
391
412
|
throw new DeeplineError(msg, response.status, "API_ERROR", {
|
|
@@ -446,6 +467,22 @@ var HttpClient = class {
|
|
|
446
467
|
}
|
|
447
468
|
if (!response.ok) {
|
|
448
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
|
+
}
|
|
449
486
|
const parsed = parseResponseBody(body);
|
|
450
487
|
throw new DeeplineError(
|
|
451
488
|
apiErrorMessage(parsed, response.status),
|
|
@@ -511,6 +548,31 @@ function parseResponseBody(body) {
|
|
|
511
548
|
return body;
|
|
512
549
|
}
|
|
513
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
|
+
}
|
|
514
576
|
function apiErrorMessage(parsed, status) {
|
|
515
577
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
516
578
|
if (typeof errorValue === "string") {
|
|
@@ -606,7 +668,7 @@ function isTransientPlayStreamError(error) {
|
|
|
606
668
|
return error.statusCode >= 500 && error.statusCode < 600;
|
|
607
669
|
}
|
|
608
670
|
const text = error instanceof Error ? error.message : String(error);
|
|
609
|
-
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(
|
|
610
672
|
text
|
|
611
673
|
);
|
|
612
674
|
}
|
|
@@ -838,6 +900,10 @@ function buildSnapshotFromLedger(snapshot) {
|
|
|
838
900
|
return {
|
|
839
901
|
runId: snapshot.runId,
|
|
840
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,
|
|
841
907
|
updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
|
|
842
908
|
logs: snapshot.logTail,
|
|
843
909
|
totalLogCount: snapshot.totalLogCount,
|
|
@@ -4548,14 +4614,25 @@ var import_node_fs5 = require("fs");
|
|
|
4548
4614
|
var import_node_path6 = require("path");
|
|
4549
4615
|
|
|
4550
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
|
+
}
|
|
4551
4624
|
function datasetSummaryPercentText(numerator, denominator) {
|
|
4552
4625
|
return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
|
|
4553
4626
|
}
|
|
4554
4627
|
function readCount(value) {
|
|
4555
4628
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
|
|
4556
4629
|
}
|
|
4557
|
-
function
|
|
4558
|
-
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 = {
|
|
4559
4636
|
queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
|
|
4560
4637
|
running: datasetSummaryPercentText(readCount(raw.running), denominator),
|
|
4561
4638
|
"completed:executed": datasetSummaryPercentText(
|
|
@@ -4576,6 +4653,17 @@ function formatDatasetExecutionStats(raw, denominator) {
|
|
|
4576
4653
|
),
|
|
4577
4654
|
failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
|
|
4578
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;
|
|
4579
4667
|
}
|
|
4580
4668
|
|
|
4581
4669
|
// src/cli/dataset-stats.ts
|
|
@@ -4725,7 +4813,8 @@ function canonicalRowsInfoFromCandidate(input2) {
|
|
|
4725
4813
|
complete: rows2.length === totalRows2,
|
|
4726
4814
|
source: candidate.source,
|
|
4727
4815
|
datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
|
|
4728
|
-
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 } : {}
|
|
4729
4818
|
};
|
|
4730
4819
|
}
|
|
4731
4820
|
if (candidate.serializedOnly) {
|
|
@@ -4875,18 +4964,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
|
|
|
4875
4964
|
}
|
|
4876
4965
|
return infos;
|
|
4877
4966
|
}
|
|
4878
|
-
function
|
|
4967
|
+
function collectPackagedStepDatasetCandidates(statusOrResult) {
|
|
4879
4968
|
const root = isRecord3(statusOrResult) ? statusOrResult : null;
|
|
4880
|
-
|
|
4881
|
-
if (!result) {
|
|
4969
|
+
if (!root) {
|
|
4882
4970
|
return [];
|
|
4883
4971
|
}
|
|
4972
|
+
const pkg = isRecord3(root.package) ? root.package : root;
|
|
4973
|
+
const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
|
|
4884
4974
|
const candidates = [];
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
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));
|
|
4890
5007
|
const seen = /* @__PURE__ */ new Set();
|
|
4891
5008
|
const infos = [];
|
|
4892
5009
|
for (const candidate of candidates) {
|
|
@@ -5575,6 +5692,7 @@ var import_node_path13 = require("path");
|
|
|
5575
5692
|
var import_node_crypto3 = require("crypto");
|
|
5576
5693
|
var import_node_fs10 = require("fs");
|
|
5577
5694
|
var import_node_path12 = require("path");
|
|
5695
|
+
var import_sync5 = require("csv-parse/sync");
|
|
5578
5696
|
|
|
5579
5697
|
// src/plays/bundle-play-file.ts
|
|
5580
5698
|
var import_node_os6 = require("os");
|
|
@@ -9373,6 +9491,177 @@ function inputContainsLocalFilePath(value) {
|
|
|
9373
9491
|
}
|
|
9374
9492
|
return false;
|
|
9375
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
|
+
}
|
|
9376
9665
|
function namedRunNeedsPlayDefinition(input2) {
|
|
9377
9666
|
return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
|
|
9378
9667
|
}
|
|
@@ -9607,6 +9896,21 @@ function isRetryablePendingStartFailure(status) {
|
|
|
9607
9896
|
if (status.runId && status.runId !== "pending") return false;
|
|
9608
9897
|
return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
|
|
9609
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
|
+
}
|
|
9610
9914
|
var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
|
|
9611
9915
|
"completed",
|
|
9612
9916
|
"failed",
|
|
@@ -10386,6 +10690,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10386
10690
|
progress: input2.progress
|
|
10387
10691
|
});
|
|
10388
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
|
+
}
|
|
10389
10710
|
throw error;
|
|
10390
10711
|
} finally {
|
|
10391
10712
|
if (timeout) {
|
|
@@ -10437,6 +10758,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10437
10758
|
function formatInteger(value) {
|
|
10438
10759
|
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
|
|
10439
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
|
+
}
|
|
10440
10783
|
var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
|
|
10441
10784
|
"contract",
|
|
10442
10785
|
"staticPipeline",
|
|
@@ -10521,8 +10864,7 @@ function formatJsonPreview(value) {
|
|
|
10521
10864
|
return [];
|
|
10522
10865
|
}
|
|
10523
10866
|
const MAX_CHARS = 4e3;
|
|
10524
|
-
const truncated = json
|
|
10525
|
-
... truncated; use --json for full output` : json;
|
|
10867
|
+
const truncated = clampJsonPreviewText(json, MAX_CHARS);
|
|
10526
10868
|
return truncated.split("\n").map((line) => ` ${line}`);
|
|
10527
10869
|
}
|
|
10528
10870
|
function formatReturnValue(result) {
|
|
@@ -11053,6 +11395,9 @@ function readRecordArray(value) {
|
|
|
11053
11395
|
function readRecord(value) {
|
|
11054
11396
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
11055
11397
|
}
|
|
11398
|
+
function readNonNegativeInteger(value) {
|
|
11399
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
11400
|
+
}
|
|
11056
11401
|
function formatSummaryScalar(value) {
|
|
11057
11402
|
if (typeof value === "number") {
|
|
11058
11403
|
return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
|
|
@@ -11085,7 +11430,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
|
|
|
11085
11430
|
return [];
|
|
11086
11431
|
}
|
|
11087
11432
|
const lines = [];
|
|
11088
|
-
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
|
+
);
|
|
11089
11452
|
if (parts.length > 0) {
|
|
11090
11453
|
lines.push(`${indent2}summary: ${parts.join(" ")}`);
|
|
11091
11454
|
}
|
|
@@ -11146,7 +11509,7 @@ function buildRunPackageTextLines(packaged) {
|
|
|
11146
11509
|
];
|
|
11147
11510
|
const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
|
|
11148
11511
|
if (runError && (status === "failed" || status === "cancelled")) {
|
|
11149
|
-
lines.push(` error: ${runError
|
|
11512
|
+
lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
|
|
11150
11513
|
}
|
|
11151
11514
|
for (const step of readRecordArray(packaged.steps)) {
|
|
11152
11515
|
const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
|
|
@@ -11231,7 +11594,7 @@ function writePlayResult(status, jsonOutput, options) {
|
|
|
11231
11594
|
lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
|
|
11232
11595
|
}
|
|
11233
11596
|
const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
|
|
11234
|
-
lines.push(` error: ${displayError
|
|
11597
|
+
lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
|
|
11235
11598
|
}
|
|
11236
11599
|
const renderedServerView = renderServerResultView(status.resultView);
|
|
11237
11600
|
if (result) {
|
|
@@ -11402,12 +11765,51 @@ async function fetchBackingDatasetRows(input2) {
|
|
|
11402
11765
|
source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
|
|
11403
11766
|
};
|
|
11404
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
|
+
}
|
|
11405
11807
|
async function exportPlayStatusRows(client2, status, outPath, options = {}) {
|
|
11406
11808
|
if (!outPath) {
|
|
11407
11809
|
return null;
|
|
11408
11810
|
}
|
|
11409
11811
|
const availableRows = collectSerializedDatasetRowsInfos(status);
|
|
11410
|
-
const rowsInfo = options.datasetPath ? availableRows
|
|
11812
|
+
const rowsInfo = options.datasetPath ? resolveDatasetByName(availableRows, options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
|
|
11411
11813
|
if (!rowsInfo && options.datasetPath) {
|
|
11412
11814
|
const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
|
|
11413
11815
|
throw new DeeplineError(
|
|
@@ -12153,6 +12555,12 @@ async function handleFileBackedRun(options) {
|
|
|
12153
12555
|
() => (0, import_node_fs10.readFileSync)(absolutePlayPath, "utf-8")
|
|
12154
12556
|
);
|
|
12155
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
|
+
}
|
|
12156
12564
|
let graph;
|
|
12157
12565
|
try {
|
|
12158
12566
|
graph = await traceCliSpan(
|
|
@@ -12310,6 +12718,12 @@ async function handleNamedRun(options) {
|
|
|
12310
12718
|
const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
|
|
12311
12719
|
const playName = options.target.name;
|
|
12312
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
|
+
}
|
|
12313
12727
|
const needsPlayDefinition = namedRunNeedsPlayDefinition({
|
|
12314
12728
|
runtimeInput,
|
|
12315
12729
|
revisionSelector: options.revisionSelector
|