tines 0.0.242 → 0.0.244
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/index.js +178 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4243,8 +4243,8 @@ function createApiClient(options) {
|
|
|
4243
4243
|
getPublicationResult: (candidateId) => get(
|
|
4244
4244
|
`/api/v1/publications/${encodeURIComponent(candidateId)}/result`
|
|
4245
4245
|
),
|
|
4246
|
-
listPublications: (workflow2) => get(
|
|
4247
|
-
`/api/v1/publications${query({ workflow: workflow2 })}`
|
|
4246
|
+
listPublications: (workflow2, page) => get(
|
|
4247
|
+
`/api/v1/publications${query({ workflow: workflow2, ...page })}`
|
|
4248
4248
|
),
|
|
4249
4249
|
withdrawPublication: (snapshotId) => request(
|
|
4250
4250
|
"POST",
|
|
@@ -4453,11 +4453,12 @@ async function* listPages(fetchPage, opts = {}) {
|
|
|
4453
4453
|
const maxItems = opts.maxItems ?? MAX_ALL_PAGES_ITEMS;
|
|
4454
4454
|
const seen = /* @__PURE__ */ new Set();
|
|
4455
4455
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
4456
|
+
const identify = opts.identify ?? ((item) => item.id);
|
|
4456
4457
|
let cursor;
|
|
4457
4458
|
for (; ; ) {
|
|
4458
4459
|
const res = await fetchPage({ limit: pageSize, cursor });
|
|
4459
|
-
const fresh = res.items.filter((item) => !seen.has(item
|
|
4460
|
-
for (const item of fresh) seen.add(item
|
|
4460
|
+
const fresh = res.items.filter((item) => !seen.has(identify(item)));
|
|
4461
|
+
for (const item of fresh) seen.add(identify(item));
|
|
4461
4462
|
if (seen.size > maxItems) {
|
|
4462
4463
|
throw new Error(
|
|
4463
4464
|
`list has more than ${maxItems} items \u2014 narrow it with filters, or page manually with --cursor`
|
|
@@ -5549,21 +5550,35 @@ var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
|
|
|
5549
5550
|
var PUBLIC_WORKFLOW_MAX_BYTES = 1048576;
|
|
5550
5551
|
var PUBLICATION_CANDIDATE_TTL_MS = 15 * 60 * 1e3;
|
|
5551
5552
|
var SNAPSHOT_ID = /^[A-Za-z0-9_-]{20,100}$/;
|
|
5552
|
-
function
|
|
5553
|
-
if (SNAPSHOT_ID.test(value)) return value;
|
|
5553
|
+
function parsePublicSnapshotUrl(value) {
|
|
5554
5554
|
let url;
|
|
5555
5555
|
try {
|
|
5556
5556
|
url = new URL(value);
|
|
5557
5557
|
} catch {
|
|
5558
|
-
throw new Error("Expected a
|
|
5558
|
+
throw new Error("Expected a canonical public URL");
|
|
5559
5559
|
}
|
|
5560
|
-
if (!["http:", "https:"].includes(url.protocol)
|
|
5561
|
-
throw new Error("Invalid public publication URL");
|
|
5562
|
-
if (
|
|
5560
|
+
if (!["http:", "https:"].includes(url.protocol))
|
|
5561
|
+
throw new Error("Invalid public publication URL: must use HTTP or HTTPS");
|
|
5562
|
+
if (url.username || url.password)
|
|
5563
|
+
throw new Error("Invalid public publication URL: must not contain credentials");
|
|
5564
|
+
if (url.hash) throw new Error("Invalid public publication URL: must not contain a fragment");
|
|
5565
|
+
if (url.search)
|
|
5566
|
+
throw new Error("Invalid public publication URL: must not contain query parameters");
|
|
5567
|
+
const match = /^\/p\/([A-Za-z0-9_-]{20,100})(?:\/download)?$/.exec(url.pathname) ?? /^\/api\/v1\/publications\/public\/([A-Za-z0-9_-]{20,100})\/download$/.exec(url.pathname);
|
|
5568
|
+
if (!match) throw new Error("Invalid public publication URL: expected a canonical path");
|
|
5569
|
+
const origin = url.origin;
|
|
5570
|
+
return {
|
|
5571
|
+
snapshotId: match[1],
|
|
5572
|
+
publicUrl: `${origin}/p/${match[1]}`,
|
|
5573
|
+
downloadUrl: `${origin}/api/v1/publications/public/${match[1]}/download`
|
|
5574
|
+
};
|
|
5575
|
+
}
|
|
5576
|
+
function parsePublicSnapshotReference(value, configuredOrigin) {
|
|
5577
|
+
if (SNAPSHOT_ID.test(value)) return value;
|
|
5578
|
+
const parsed = parsePublicSnapshotUrl(value);
|
|
5579
|
+
if (configuredOrigin && new URL(parsed.publicUrl).origin !== new URL(configuredOrigin).origin)
|
|
5563
5580
|
throw new Error("external_source_requires_download");
|
|
5564
|
-
|
|
5565
|
-
if (!match || url.search) throw new Error("Invalid public publication URL");
|
|
5566
|
-
return match[1];
|
|
5581
|
+
return parsed.snapshotId;
|
|
5567
5582
|
}
|
|
5568
5583
|
|
|
5569
5584
|
// ../shared/src/usage.ts
|
|
@@ -6125,10 +6140,14 @@ more results: rerun with --all-pages, or resume with --cursor ${res.next_cursor}
|
|
|
6125
6140
|
);
|
|
6126
6141
|
}
|
|
6127
6142
|
}
|
|
6128
|
-
async function fetchList(opts, fetchPage) {
|
|
6143
|
+
async function fetchList(opts, fetchPage, identify) {
|
|
6129
6144
|
if (opts.allPages) {
|
|
6130
6145
|
return {
|
|
6131
|
-
items: await listAll(fetchPage, {
|
|
6146
|
+
items: await listAll(fetchPage, {
|
|
6147
|
+
pageSize: opts.limit,
|
|
6148
|
+
maxItems: opts.maxItems,
|
|
6149
|
+
identify: identify ?? ((item) => item.id)
|
|
6150
|
+
}),
|
|
6132
6151
|
next_cursor: null
|
|
6133
6152
|
};
|
|
6134
6153
|
}
|
|
@@ -12937,7 +12956,7 @@ async function askToInstall(plan) {
|
|
|
12937
12956
|
rl.close();
|
|
12938
12957
|
}
|
|
12939
12958
|
}
|
|
12940
|
-
async function recoverOrInstall(api,
|
|
12959
|
+
async function recoverOrInstall(api, rawOrLoader, plan) {
|
|
12941
12960
|
const checked = (receipt) => {
|
|
12942
12961
|
if (receipt.id !== plan.plan_id || receipt.document_digest !== plan.document_digest || receipt.plan_digest !== plan.plan_digest)
|
|
12943
12962
|
throw new Error("recovered workflow package receipt does not match the saved plan");
|
|
@@ -12948,6 +12967,7 @@ async function recoverOrInstall(api, raw, plan) {
|
|
|
12948
12967
|
} catch (error) {
|
|
12949
12968
|
if (!(error instanceof ApiError) || error.status !== 404) throw error;
|
|
12950
12969
|
}
|
|
12970
|
+
const raw = typeof rawOrLoader === "function" ? await rawOrLoader() : rawOrLoader;
|
|
12951
12971
|
const request = {
|
|
12952
12972
|
document_json: raw,
|
|
12953
12973
|
plan_token: plan.plan_token,
|
|
@@ -12971,7 +12991,6 @@ import { lookup as dnsLookup } from "node:dns/promises";
|
|
|
12971
12991
|
import { request as httpRequest } from "node:http";
|
|
12972
12992
|
import { request as httpsRequest } from "node:https";
|
|
12973
12993
|
import { isIP } from "node:net";
|
|
12974
|
-
var SNAPSHOT_PATH = /^\/p\/[A-Za-z0-9_-]{20,100}(?:\/download)?$/;
|
|
12975
12994
|
var MAX_REDIRECTS = 3;
|
|
12976
12995
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
12977
12996
|
function mappedIpv4(address) {
|
|
@@ -13038,40 +13057,51 @@ function isPublicAddress(address) {
|
|
|
13038
13057
|
}
|
|
13039
13058
|
return false;
|
|
13040
13059
|
}
|
|
13041
|
-
function validateHop(url
|
|
13060
|
+
function validateHop(url) {
|
|
13042
13061
|
if (!["http:", "https:"].includes(url.protocol))
|
|
13043
13062
|
throw new Error("publication URL must use HTTPS");
|
|
13044
13063
|
if (url.username || url.password) throw new Error("publication URL must not contain credentials");
|
|
13045
13064
|
if (url.hash) throw new Error("publication URL must not contain a fragment");
|
|
13046
13065
|
if (url.search) throw new Error("publication URL must not contain query parameters");
|
|
13047
|
-
if (initial && !SNAPSHOT_PATH.test(url.pathname))
|
|
13048
|
-
throw new Error("expected a canonical public snapshot or download URL");
|
|
13049
13066
|
}
|
|
13050
|
-
async function
|
|
13067
|
+
async function pinnedAddresses(url, lookup) {
|
|
13051
13068
|
const hostname4 = url.hostname.replace(/^\[|\]$/g, "");
|
|
13052
13069
|
const literalFamily = isIP(hostname4);
|
|
13053
13070
|
const addresses = literalFamily ? [{ address: hostname4, family: literalFamily }] : await lookup(hostname4, { all: true, verbatim: true });
|
|
13054
|
-
if (!addresses.length
|
|
13055
|
-
|
|
13071
|
+
if (!addresses.length || addresses.some(({ address, family }) => isIP(address) !== family || ![4, 6].includes(family)))
|
|
13072
|
+
throw new Error(`publication host ${url.hostname} did not resolve to valid addresses`);
|
|
13073
|
+
const literalLoopback = Boolean(literalFamily && isLoopback(hostname4));
|
|
13074
|
+
const loopbackDevelopment = url.protocol === "http:" && (url.hostname === "localhost" || literalLoopback) && addresses.every(({ address }) => isLoopback(address));
|
|
13056
13075
|
if (url.protocol === "http:" && !loopbackDevelopment)
|
|
13057
13076
|
throw new Error(
|
|
13058
13077
|
"publication URL must use HTTPS (HTTP is allowed only for loopback development)"
|
|
13059
13078
|
);
|
|
13060
13079
|
if (!loopbackDevelopment && addresses.some(({ address }) => !isPublicAddress(address)))
|
|
13061
13080
|
throw new Error("publication URL resolves to a private or reserved address");
|
|
13062
|
-
return addresses
|
|
13081
|
+
return addresses;
|
|
13082
|
+
}
|
|
13083
|
+
function pinnedLookup(addresses) {
|
|
13084
|
+
return (_hostname, opts, callback) => {
|
|
13085
|
+
const family = typeof opts.family === "number" ? opts.family : 0;
|
|
13086
|
+
const matching = family === 4 || family === 6 ? addresses.filter((item) => item.family === family) : addresses;
|
|
13087
|
+
if (!matching.length)
|
|
13088
|
+
return callback(new Error(`publication host has no IPv${family} address`));
|
|
13089
|
+
if (opts.all) return callback(null, matching);
|
|
13090
|
+
return callback(null, matching[0].address, matching[0].family);
|
|
13091
|
+
};
|
|
13063
13092
|
}
|
|
13064
13093
|
async function readHop(url, options) {
|
|
13065
|
-
const pinned = await
|
|
13094
|
+
const pinned = await pinnedAddresses(url, options.lookup);
|
|
13066
13095
|
return new Promise((resolve2, reject) => {
|
|
13067
13096
|
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(
|
|
13068
13097
|
url,
|
|
13069
13098
|
{
|
|
13070
13099
|
method: "GET",
|
|
13071
13100
|
headers: { accept: "application/json" },
|
|
13072
|
-
lookup: (
|
|
13101
|
+
lookup: pinnedLookup(pinned)
|
|
13073
13102
|
},
|
|
13074
13103
|
(response) => {
|
|
13104
|
+
let responseFailure;
|
|
13075
13105
|
const status = response.statusCode ?? 0;
|
|
13076
13106
|
if (status >= 300 && status < 400) {
|
|
13077
13107
|
response.resume();
|
|
@@ -13101,10 +13131,16 @@ async function readHop(url, options) {
|
|
|
13101
13131
|
const chunks = [];
|
|
13102
13132
|
response.on("data", (chunk) => {
|
|
13103
13133
|
size += chunk.length;
|
|
13104
|
-
if (size > options.maxBytes)
|
|
13105
|
-
|
|
13106
|
-
|
|
13134
|
+
if (size > options.maxBytes) {
|
|
13135
|
+
responseFailure = new Error("publication download is too large");
|
|
13136
|
+
response.destroy(responseFailure);
|
|
13137
|
+
} else chunks.push(chunk);
|
|
13107
13138
|
});
|
|
13139
|
+
response.on(
|
|
13140
|
+
"aborted",
|
|
13141
|
+
() => reject(responseFailure ?? new Error("publication download was aborted"))
|
|
13142
|
+
);
|
|
13143
|
+
response.on("error", reject);
|
|
13108
13144
|
response.on("end", () => {
|
|
13109
13145
|
try {
|
|
13110
13146
|
resolve2({
|
|
@@ -13128,17 +13164,11 @@ async function readHop(url, options) {
|
|
|
13128
13164
|
});
|
|
13129
13165
|
}
|
|
13130
13166
|
async function fetchPublicWorkflowPackage(value, options = {}) {
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
} catch {
|
|
13135
|
-
throw new Error("expected a canonical public snapshot or download URL");
|
|
13136
|
-
}
|
|
13137
|
-
validateHop(url, true);
|
|
13138
|
-
if (!url.pathname.endsWith("/download")) url.pathname += "/download";
|
|
13139
|
-
const sourceUrl = url.toString();
|
|
13167
|
+
const parsed = parsePublicSnapshotUrl(value);
|
|
13168
|
+
let url = new URL(parsed.downloadUrl);
|
|
13169
|
+
const sourceUrl = parsed.downloadUrl;
|
|
13140
13170
|
for (let redirects = 0; ; redirects += 1) {
|
|
13141
|
-
validateHop(url
|
|
13171
|
+
validateHop(url);
|
|
13142
13172
|
const result = await readHop(url, {
|
|
13143
13173
|
lookup: options.lookup ?? dnsLookup,
|
|
13144
13174
|
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
@@ -13170,6 +13200,44 @@ function readPublicationProof(path2) {
|
|
|
13170
13200
|
die("invalid workflow publication proof file");
|
|
13171
13201
|
return saved;
|
|
13172
13202
|
}
|
|
13203
|
+
function withWorkflowExportSelectors(cmd) {
|
|
13204
|
+
return cmd.option("--project <id-or-name>", "source project for selected project-bound configuration").option("--schedule <id>", "include this source schedule ID (repeatable)", collect2, []).option("--tier <state-ref=tier>", "include a state tier preference (repeatable)", collect2, []).option("--project-routing", "make every --tier selector project scoped").option("--inputs <file>", "JSON object containing input declarations and text uses");
|
|
13205
|
+
}
|
|
13206
|
+
function hasWorkflowExportSelectors(opts) {
|
|
13207
|
+
return Boolean(
|
|
13208
|
+
opts.project || opts.schedule.length || opts.tier.length || opts.projectRouting || opts.inputs
|
|
13209
|
+
);
|
|
13210
|
+
}
|
|
13211
|
+
async function resolveWorkflowExportSelection(api, workflowRef, opts) {
|
|
13212
|
+
const all = await listAll((page) => api.listWorkflows(page));
|
|
13213
|
+
const workflow2 = pickWorkflow(all, workflowRef);
|
|
13214
|
+
const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
|
|
13215
|
+
if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
|
|
13216
|
+
die("--project is required with --schedule or --project-routing");
|
|
13217
|
+
const tiers = [];
|
|
13218
|
+
for (const selector of opts.tier) {
|
|
13219
|
+
const separator = selector.lastIndexOf("=");
|
|
13220
|
+
if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
|
|
13221
|
+
const tier = selector.slice(separator + 1);
|
|
13222
|
+
if (!["smartest", "balanced", "cheapest"].includes(tier))
|
|
13223
|
+
die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
|
|
13224
|
+
tiers.push({
|
|
13225
|
+
state_id: await resolveExportState(api, selector.slice(0, separator)),
|
|
13226
|
+
tier,
|
|
13227
|
+
project_scoped: Boolean(opts.projectRouting)
|
|
13228
|
+
});
|
|
13229
|
+
}
|
|
13230
|
+
const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
|
|
13231
|
+
return {
|
|
13232
|
+
workflow: workflow2,
|
|
13233
|
+
options: {
|
|
13234
|
+
source_project_id: sourceProject?.id,
|
|
13235
|
+
schedule_ids: opts.schedule,
|
|
13236
|
+
tiers,
|
|
13237
|
+
authoring
|
|
13238
|
+
}
|
|
13239
|
+
};
|
|
13240
|
+
}
|
|
13173
13241
|
async function confirmPublicationAction(question) {
|
|
13174
13242
|
const rl = createInterface5({ input: process.stdin, output: process.stderr });
|
|
13175
13243
|
try {
|
|
@@ -13519,14 +13587,18 @@ function registerPackageCommands(workflows) {
|
|
|
13519
13587
|
if (!result.valid) process.exitCode = 1;
|
|
13520
13588
|
});
|
|
13521
13589
|
withCommon(
|
|
13522
|
-
|
|
13523
|
-
"
|
|
13524
|
-
|
|
13525
|
-
|
|
13590
|
+
withWorkflowExportSelectors(
|
|
13591
|
+
workflows.command("publish [workflow]").description("Prepare or commit one exact immutable public workflow snapshot").option(
|
|
13592
|
+
"--from <file>",
|
|
13593
|
+
"prepare from an existing workflow package instead of an owned workflow"
|
|
13594
|
+
).option("--proof-out <file>", "save the complete prepared proof with mode 0600").option("--proof <file>", "commit a previously saved exact proof").option("--display-name <name>", "public attribution name (never an email)").option("--license <license>", "reuse license (currently MIT)", "MIT").option("--license-year <year>", "MIT copyright year", String((/* @__PURE__ */ new Date()).getFullYear())).option("--confirm <review-digest>", "exact digest of the reviewed proof").option("--sharing-rights", "confirm rights to every bundled declaration and file").option("--recover", "reconcile this same candidate after a lost response").option("--repo <local-id>", "confirm one bundled repository ID (repeatable)", collect2, [])
|
|
13595
|
+
)
|
|
13526
13596
|
).action(
|
|
13527
13597
|
async (workflow2, opts) => {
|
|
13528
13598
|
const apiBase = normalizeUrl(resolveUrl(opts));
|
|
13529
13599
|
const api = client(opts);
|
|
13600
|
+
if ((opts.from || opts.proof) && hasWorkflowExportSelectors(opts))
|
|
13601
|
+
die("workflow selectors cannot be combined with --from or --proof");
|
|
13530
13602
|
if (opts.proof) {
|
|
13531
13603
|
if (workflow2 || opts.from || opts.proofOut || opts.displayName)
|
|
13532
13604
|
die(
|
|
@@ -13540,11 +13612,13 @@ function registerPackageCommands(workflows) {
|
|
|
13540
13612
|
die(`confirmation digest does not match reviewed proof ${proof2.review_digest}`);
|
|
13541
13613
|
if (!process.stdin.isTTY && (opts.confirm !== proof2.review_digest || !opts.sharingRights))
|
|
13542
13614
|
die(`non-interactive publish requires --confirm ${proof2.review_digest} --sharing-rights`);
|
|
13543
|
-
if (process.stdin.isTTY &&
|
|
13615
|
+
if (process.stdin.isTTY && opts.confirm !== proof2.review_digest) {
|
|
13544
13616
|
process.stderr.write(`${JSON.stringify(proof2, null, 2)}
|
|
13545
13617
|
`);
|
|
13546
13618
|
if (!await confirmPublicationAction("Publish these exact immutable bytes?"))
|
|
13547
13619
|
die("publication declined");
|
|
13620
|
+
}
|
|
13621
|
+
if (process.stdin.isTTY && !opts.sharingRights) {
|
|
13548
13622
|
if (!await confirmPublicationAction("Do you have sharing rights for every bundled item?"))
|
|
13549
13623
|
die("publication declined");
|
|
13550
13624
|
}
|
|
@@ -13573,11 +13647,16 @@ function registerPackageCommands(workflows) {
|
|
|
13573
13647
|
if (opts.license !== "MIT") die("--license currently supports only MIT");
|
|
13574
13648
|
const year = Number(opts.licenseYear);
|
|
13575
13649
|
if (!Number.isSafeInteger(year)) die("--license-year must be an integer");
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13650
|
+
let source;
|
|
13651
|
+
if (opts.from) source = { kind: "file", document_json: readPackageSource(opts.from) };
|
|
13652
|
+
else {
|
|
13653
|
+
const selected = await resolveWorkflowExportSelection(api, workflow2, opts);
|
|
13654
|
+
source = {
|
|
13655
|
+
kind: "owned_workflow",
|
|
13656
|
+
workflow_id: selected.workflow.id,
|
|
13657
|
+
options: selected.options
|
|
13658
|
+
};
|
|
13659
|
+
}
|
|
13581
13660
|
const proof = await api.preparePublication({
|
|
13582
13661
|
prepare_request_id: crypto.randomUUID(),
|
|
13583
13662
|
source,
|
|
@@ -13591,22 +13670,26 @@ function registerPackageCommands(workflows) {
|
|
|
13591
13670
|
}
|
|
13592
13671
|
}
|
|
13593
13672
|
);
|
|
13594
|
-
|
|
13673
|
+
withList(
|
|
13595
13674
|
workflows.command("publications").description("List your public workflow snapshots").option("--workflow <id>", "filter by owned source workflow ID")
|
|
13596
13675
|
).action(async (opts) => {
|
|
13597
|
-
const result = await
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13676
|
+
const result = await fetchList(
|
|
13677
|
+
opts,
|
|
13678
|
+
(page) => client(opts).listPublications(opts.workflow, page),
|
|
13679
|
+
(item) => item.candidate_id
|
|
13680
|
+
);
|
|
13681
|
+
printList(result, opts, (items) => {
|
|
13682
|
+
if (!items.length) return console.log("no public snapshots");
|
|
13601
13683
|
table([
|
|
13602
13684
|
["NAME", "STATUS", "PUBLISHED", "SNAPSHOT"],
|
|
13603
|
-
...
|
|
13685
|
+
...items.map((item) => [
|
|
13604
13686
|
item.metadata.display_name,
|
|
13605
13687
|
item.owner_state === "published" && item.host_state === "active" ? "hosted" : "unavailable",
|
|
13606
13688
|
new Date(item.published_at).toISOString(),
|
|
13607
13689
|
item.snapshot_id
|
|
13608
13690
|
])
|
|
13609
13691
|
]);
|
|
13692
|
+
});
|
|
13610
13693
|
});
|
|
13611
13694
|
withCommon(
|
|
13612
13695
|
workflows.command("unpublish <public-url-or-id>").description("Withdraw a hosted public snapshot").option("-y, --yes", "skip the explicit withdrawal prompt")
|
|
@@ -13633,44 +13716,16 @@ function registerPackageCommands(workflows) {
|
|
|
13633
13716
|
else console.log(`restored ${result.receipt.public_url}`);
|
|
13634
13717
|
});
|
|
13635
13718
|
withCommon(
|
|
13636
|
-
|
|
13637
|
-
"
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
const api = client(opts);
|
|
13645
|
-
const all = await listAll((page) => api.listWorkflows(page));
|
|
13646
|
-
const workflow2 = pickWorkflow(all, ref);
|
|
13647
|
-
const sourceProject = opts.project ? await resolveProject(api, opts.project) : void 0;
|
|
13648
|
-
if ((opts.schedule.length || opts.projectRouting) && !sourceProject)
|
|
13649
|
-
die("--project is required with --schedule or --project-routing");
|
|
13650
|
-
const tiers = [];
|
|
13651
|
-
for (const selector of opts.tier) {
|
|
13652
|
-
const separator = selector.lastIndexOf("=");
|
|
13653
|
-
if (separator < 1) die(`invalid --tier "${selector}"; expected <state-ref>=<tier>`);
|
|
13654
|
-
const tier = selector.slice(separator + 1);
|
|
13655
|
-
if (!["smartest", "balanced", "cheapest"].includes(tier))
|
|
13656
|
-
die(`invalid tier "${tier}"; expected smartest, balanced, or cheapest`);
|
|
13657
|
-
tiers.push({
|
|
13658
|
-
state_id: await resolveExportState(api, selector.slice(0, separator)),
|
|
13659
|
-
tier,
|
|
13660
|
-
project_scoped: Boolean(opts.projectRouting)
|
|
13661
|
-
});
|
|
13662
|
-
}
|
|
13663
|
-
const authoring = opts.inputs ? readStrictObject(opts.inputs, "inputs file") : void 0;
|
|
13664
|
-
const document = await api.exportWorkflowPackage(workflow2.id, {
|
|
13665
|
-
source_project_id: sourceProject?.id,
|
|
13666
|
-
schedule_ids: opts.schedule,
|
|
13667
|
-
tiers,
|
|
13668
|
-
authoring
|
|
13669
|
-
});
|
|
13670
|
-
process.stdout.write(`${canonicalWorkflowPackage(document)}
|
|
13719
|
+
withWorkflowExportSelectors(
|
|
13720
|
+
workflows.command("export <workflow-id-or-unambiguous-name>").description("Export a canonical workflow package JSON document")
|
|
13721
|
+
)
|
|
13722
|
+
).action(async (ref, opts) => {
|
|
13723
|
+
const api = client(opts);
|
|
13724
|
+
const selected = await resolveWorkflowExportSelection(api, ref, opts);
|
|
13725
|
+
const document = await api.exportWorkflowPackage(selected.workflow.id, selected.options);
|
|
13726
|
+
process.stdout.write(`${canonicalWorkflowPackage(document)}
|
|
13671
13727
|
`);
|
|
13672
|
-
|
|
13673
|
-
);
|
|
13728
|
+
});
|
|
13674
13729
|
withCommon(
|
|
13675
13730
|
workflows.command("validate <file>").description("Validate a workflow package file (use - for stdin)").option("--public", "apply the stricter text-only public-hosting policy")
|
|
13676
13731
|
).action(async (path2, opts) => {
|
|
@@ -13739,39 +13794,45 @@ function registerPackageCommands(workflows) {
|
|
|
13739
13794
|
);
|
|
13740
13795
|
const apiBase = normalizeUrl(resolveUrl(opts));
|
|
13741
13796
|
const api = client(opts);
|
|
13742
|
-
|
|
13743
|
-
let raw;
|
|
13797
|
+
let loadSource;
|
|
13744
13798
|
let plan;
|
|
13745
13799
|
let planPath = opts.plan;
|
|
13746
13800
|
if (planPath) {
|
|
13747
13801
|
const saved = readWorkflowPackagePlan(planPath);
|
|
13748
13802
|
if (normalizeUrl(saved.api_base) !== apiBase)
|
|
13749
13803
|
die(`plan belongs to ${saved.api_base}, not ${apiBase}`);
|
|
13750
|
-
if (saved.remote_source) {
|
|
13751
|
-
if (source.kind !== "remote")
|
|
13752
|
-
die("saved remote plan must be installed from its public source URL");
|
|
13753
|
-
const fetched = await fetchPublicWorkflowPackage(path2);
|
|
13754
|
-
if (fetched.sourceUrl !== saved.remote_source.url || workflowPackageBytesSha256(fetched.raw) !== saved.remote_source.bytes_sha256)
|
|
13755
|
-
die("public source changed since preview; create and review a fresh plan");
|
|
13756
|
-
raw = fetched.raw;
|
|
13757
|
-
} else if (saved.plan.source?.kind === "hosted_publication") {
|
|
13758
|
-
if (source.kind !== "hosted" || source.snapshotId !== saved.plan.source.snapshot_id)
|
|
13759
|
-
die("saved hosted plan belongs to a different public snapshot");
|
|
13760
|
-
await api.getPublicSnapshotStatus(source.snapshotId);
|
|
13761
|
-
raw = canonicalWorkflowPackage(saved.plan.document);
|
|
13762
|
-
} else {
|
|
13763
|
-
if (source.kind !== "file") die("saved file plan must be installed from its file");
|
|
13764
|
-
raw = readPackageSource(path2);
|
|
13765
|
-
}
|
|
13766
|
-
const { document } = await packageDocument(raw);
|
|
13767
|
-
if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
|
|
13768
|
-
die(
|
|
13769
|
-
`package digest ${document.digest} does not match saved plan ${saved.document_digest}`
|
|
13770
|
-
);
|
|
13771
13804
|
plan = saved.plan;
|
|
13805
|
+
loadSource = async () => {
|
|
13806
|
+
const source = publicationSource(path2, apiBase);
|
|
13807
|
+
let raw;
|
|
13808
|
+
if (saved.remote_source) {
|
|
13809
|
+
if (source.kind !== "remote")
|
|
13810
|
+
die("saved remote plan must be installed from its public source URL");
|
|
13811
|
+
const fetched = await fetchPublicWorkflowPackage(path2);
|
|
13812
|
+
if (fetched.sourceUrl !== saved.remote_source.url || workflowPackageBytesSha256(fetched.raw) !== saved.remote_source.bytes_sha256)
|
|
13813
|
+
die("public source changed since preview; create and review a fresh plan");
|
|
13814
|
+
raw = fetched.raw;
|
|
13815
|
+
} else if (saved.plan.source?.kind === "hosted_publication") {
|
|
13816
|
+
if (source.kind !== "hosted" || source.snapshotId !== saved.plan.source.snapshot_id)
|
|
13817
|
+
die("saved hosted plan belongs to a different public snapshot");
|
|
13818
|
+
await api.getPublicSnapshotStatus(source.snapshotId);
|
|
13819
|
+
raw = canonicalWorkflowPackage(saved.plan.document);
|
|
13820
|
+
} else {
|
|
13821
|
+
if (source.kind !== "file") die("saved file plan must be installed from its file");
|
|
13822
|
+
raw = readPackageSource(path2);
|
|
13823
|
+
}
|
|
13824
|
+
const { document } = await packageDocument(raw);
|
|
13825
|
+
if (saved.document_digest !== document.digest || saved.plan.document_digest !== document.digest || canonicalWorkflowPackage(saved.plan.document) !== canonicalWorkflowPackage(document))
|
|
13826
|
+
die(
|
|
13827
|
+
`package digest ${document.digest} does not match saved plan ${saved.document_digest}`
|
|
13828
|
+
);
|
|
13829
|
+
return raw;
|
|
13830
|
+
};
|
|
13772
13831
|
} else {
|
|
13832
|
+
const source = publicationSource(path2, apiBase);
|
|
13773
13833
|
const choices = opts.choices ? readStrictObject(opts.choices, "choices file") : void 0;
|
|
13774
13834
|
let remoteSource;
|
|
13835
|
+
let raw;
|
|
13775
13836
|
if (source.kind === "hosted") {
|
|
13776
13837
|
plan = await api.prepareHostedWorkflowPackage(source.snapshotId, choices);
|
|
13777
13838
|
raw = canonicalWorkflowPackage(plan.document);
|
|
@@ -13791,6 +13852,7 @@ function registerPackageCommands(workflows) {
|
|
|
13791
13852
|
planPath = source.kind === "file" ? `${path2}.plan.json` : "workflow-publication.plan.json";
|
|
13792
13853
|
saveWorkflowPackagePlan(planPath, apiBase, plan, remoteSource);
|
|
13793
13854
|
console.error(`saved retryable signed plan to ${planPath}`);
|
|
13855
|
+
loadSource = async () => raw;
|
|
13794
13856
|
}
|
|
13795
13857
|
if (opts.confirm !== void 0 && opts.confirm !== plan.plan_digest)
|
|
13796
13858
|
die(`confirmation digest does not match reviewed plan ${plan.plan_digest}`);
|
|
@@ -13802,7 +13864,7 @@ function registerPackageCommands(workflows) {
|
|
|
13802
13864
|
process.stderr.write(`${formatWorkflowPackageReview(plan)}
|
|
13803
13865
|
`);
|
|
13804
13866
|
}
|
|
13805
|
-
const receipt = await recoverOrInstall(api,
|
|
13867
|
+
const receipt = await recoverOrInstall(api, loadSource, plan);
|
|
13806
13868
|
if (opts.json) printJson(receipt);
|
|
13807
13869
|
else {
|
|
13808
13870
|
console.log(`installed workflow package (${receipt.id})`);
|