@varde-flyt/vfac 0.1.0 → 0.2.0-rc.1

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.
Files changed (2) hide show
  1. package/dist/vfac.mjs +281 -116
  2. package/package.json +6 -1
package/dist/vfac.mjs CHANGED
@@ -7384,7 +7384,7 @@ var VALUED = /* @__PURE__ */ new Set([
7384
7384
  "region",
7385
7385
  "profile"
7386
7386
  ]);
7387
- var BOOLEAN = /* @__PURE__ */ new Set(["help", "version", "yes"]);
7387
+ var BOOLEAN = /* @__PURE__ */ new Set(["help", "version", "yes", "wait"]);
7388
7388
  var REPEATABLE = /* @__PURE__ */ new Set(["secret"]);
7389
7389
  function parseArgs(argv) {
7390
7390
  const command = [];
@@ -7471,6 +7471,10 @@ function sessionUsable(session, endpoint, now, keyId) {
7471
7471
  }
7472
7472
 
7473
7473
  // src/client.ts
7474
+ var REQUEST_TIMEOUT_MS = 6e4;
7475
+ function requestSignal() {
7476
+ return typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(REQUEST_TIMEOUT_MS) : void 0;
7477
+ }
7474
7478
  async function call(args) {
7475
7479
  const url = new URL(args.path, args.endpoint);
7476
7480
  if (url.origin !== new URL(args.endpoint).origin) {
@@ -7488,7 +7492,8 @@ async function call(args) {
7488
7492
  authorization: `Bearer ${args.token}`,
7489
7493
  ...args.body === void 0 ? {} : { "content-type": "application/json" }
7490
7494
  },
7491
- ...args.body === void 0 ? {} : { body: JSON.stringify(args.body) }
7495
+ ...args.body === void 0 ? {} : { body: JSON.stringify(args.body) },
7496
+ signal: requestSignal()
7492
7497
  });
7493
7498
  } catch (error) {
7494
7499
  return {
@@ -7549,7 +7554,8 @@ async function signIn(args) {
7549
7554
  method: "POST",
7550
7555
  headers: { "content-type": "application/json" },
7551
7556
  body: JSON.stringify(credential),
7552
- redirect: "manual"
7557
+ redirect: "manual",
7558
+ signal: requestSignal()
7553
7559
  });
7554
7560
  } catch (error) {
7555
7561
  return {
@@ -7634,7 +7640,8 @@ async function probeMachineApi(args) {
7634
7640
  try {
7635
7641
  response = await doFetch(new URL("/api/v1", args.endpoint), {
7636
7642
  method: "GET",
7637
- redirect: "manual"
7643
+ redirect: "manual",
7644
+ signal: requestSignal()
7638
7645
  });
7639
7646
  } catch (error) {
7640
7647
  return {
@@ -7759,7 +7766,7 @@ async function ensureSession(args) {
7759
7766
  if (!checked.ok) return { ok: false, message: checked.message };
7760
7767
  const endpoint = checked.endpoint;
7761
7768
  const credential = credentialFromEnvironment();
7762
- if (sessionUsable(stored.session, endpoint, args.now ?? /* @__PURE__ */ new Date(), credential?.keyId)) {
7769
+ if (args.forceExchange !== true && sessionUsable(stored.session, endpoint, args.now ?? /* @__PURE__ */ new Date(), credential?.keyId)) {
7763
7770
  return { ok: true, ready: { endpoint, token: stored.session.token, stored } };
7764
7771
  }
7765
7772
  const signedIn = await signIn({
@@ -7775,6 +7782,133 @@ function resolveProject(flag, stored) {
7775
7782
  return flag ?? stored.context?.projectId ?? null;
7776
7783
  }
7777
7784
 
7785
+ // src/operations.ts
7786
+ var OPERATION_STATUSES = [
7787
+ "PENDING",
7788
+ "RUNNING",
7789
+ "SUCCEEDED",
7790
+ "FAILED",
7791
+ "CANCELLED"
7792
+ ];
7793
+ var TERMINAL_STATUSES = ["SUCCEEDED", "FAILED", "CANCELLED"];
7794
+ var POLL_INTERVAL_MS = 5e3;
7795
+ var MAX_WAIT_MS = 60 * 60 * 1e3;
7796
+ function withProject(path, projectId) {
7797
+ return projectId === null ? path : `${path}?project=${encodeURIComponent(projectId)}`;
7798
+ }
7799
+ function operationPath(operationId, projectId) {
7800
+ return withProject(`/api/v1/operations/${encodeURIComponent(operationId)}`, projectId);
7801
+ }
7802
+ function classify(data) {
7803
+ const status = data["status"];
7804
+ if (typeof status !== "string") return "unknown";
7805
+ if (TERMINAL_STATUSES.includes(status)) return "terminal";
7806
+ if (OPERATION_STATUSES.includes(status)) return "working";
7807
+ return "unknown";
7808
+ }
7809
+ async function readOperation(args) {
7810
+ const result = await call({
7811
+ endpoint: args.endpoint,
7812
+ path: operationPath(args.operationId, args.projectId),
7813
+ token: args.token,
7814
+ ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
7815
+ });
7816
+ return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error };
7817
+ }
7818
+ async function waitForOperation(args) {
7819
+ const deps = args.deps ?? {};
7820
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
7821
+ const now = deps.now ?? (() => Date.now());
7822
+ const deadline = now() + MAX_WAIT_MS;
7823
+ let token = args.token;
7824
+ let refreshed = false;
7825
+ let last = null;
7826
+ for (; ; ) {
7827
+ const read = await readOperation({
7828
+ endpoint: args.endpoint,
7829
+ token,
7830
+ operationId: args.operationId,
7831
+ projectId: args.projectId,
7832
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
7833
+ });
7834
+ if (!read.ok) {
7835
+ if (read.error.status === 401 && !refreshed && deps.refreshToken) {
7836
+ refreshed = true;
7837
+ const next = await deps.refreshToken();
7838
+ if (next !== null) {
7839
+ token = next;
7840
+ continue;
7841
+ }
7842
+ }
7843
+ return { kind: "read-failed", error: read.error };
7844
+ }
7845
+ last = read.data;
7846
+ const verdict = classify(read.data);
7847
+ if (verdict === "terminal") {
7848
+ return { kind: "settled", status: String(read.data["status"]), data: read.data };
7849
+ }
7850
+ if (verdict === "unknown") {
7851
+ return { kind: "unknown-status", status: String(read.data["status"]), data: read.data };
7852
+ }
7853
+ args.onProgress?.(read.data);
7854
+ if (now() + POLL_INTERVAL_MS >= deadline) return { kind: "timed-out", data: last };
7855
+ await sleep(POLL_INTERVAL_MS);
7856
+ }
7857
+ }
7858
+ function renderOperation(operationId, data) {
7859
+ const status = String(data["status"] ?? "UNKNOWN");
7860
+ const phase = data["phase"];
7861
+ const lines = [
7862
+ typeof phase === "string" && phase !== "" ? `${operationId} ${status} (${phase})` : `${operationId} ${status}`
7863
+ ];
7864
+ const type = data["type"];
7865
+ if (typeof type === "string") lines[0] += ` ${type}`;
7866
+ const steps = Array.isArray(data["steps"]) ? data["steps"] : [];
7867
+ if (steps.length > 0) {
7868
+ lines.push("");
7869
+ for (const step of steps) {
7870
+ const state = String(step["state"] ?? "");
7871
+ lines.push(` ${markerFor(state)} ${String(step["label"] ?? step["key"] ?? "")}`);
7872
+ }
7873
+ }
7874
+ const failure = asRecord(data["failure"]);
7875
+ if (failure) {
7876
+ lines.push("");
7877
+ const stage = failure["stage"];
7878
+ lines.push(` Failed${typeof stage === "string" ? ` at ${stage}` : ""}`);
7879
+ const summary = failure["summary"];
7880
+ if (typeof summary === "string") lines.push(` ${summary}`);
7881
+ const detail = failure["detail"];
7882
+ if (typeof detail === "string" && detail !== "") lines.push(` ${detail}`);
7883
+ const cause = failure["cause"];
7884
+ if (typeof cause === "string") lines.push(` cause: ${cause}`);
7885
+ const retryable = failure["retryable"];
7886
+ if (typeof retryable === "boolean") {
7887
+ lines.push(
7888
+ retryable ? " Another attempt can succeed: `vfac lifecycle reconcile <pri_\u2026>`." : " Another attempt will fail the same way. Change something first."
7889
+ );
7890
+ }
7891
+ }
7892
+ return lines.join("\n");
7893
+ }
7894
+ function markerFor(state) {
7895
+ switch (state) {
7896
+ case "completed":
7897
+ return "\u2713";
7898
+ case "failed":
7899
+ return "\u2717";
7900
+ case "active":
7901
+ return "\u203A";
7902
+ case "pending":
7903
+ return "\xB7";
7904
+ default:
7905
+ return "-";
7906
+ }
7907
+ }
7908
+ function asRecord(value) {
7909
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
7910
+ }
7911
+
7778
7912
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
7779
7913
  var external_exports = {};
7780
7914
  __export(external_exports, {
@@ -15407,7 +15541,7 @@ function printError(error, json) {
15407
15541
  }
15408
15542
 
15409
15543
  // src/commands/apply.ts
15410
- async function runApply(verb, parsed) {
15544
+ async function runApply(verb, parsed, deps = {}) {
15411
15545
  const file = parsed.flags["file"];
15412
15546
  if (!file) {
15413
15547
  process.stderr.write("Which manifest? Use -f <file>.\n");
@@ -15526,19 +15660,105 @@ Read the operation that failed: \`vfac get operation <id>\`, or \`vfac get produ
15526
15660
  body: { verb: "apply", action, payload: envelope }
15527
15661
  });
15528
15662
  if (!applied.ok) return printError(applied.error, json);
15529
- if (json) {
15530
- process.stdout.write(`${JSON.stringify({ action, ...applied.data })}
15663
+ const key = manifestKeyOf(manifest.document) ?? planned.data.manifestKey ?? "(unnamed)";
15664
+ const rawOperationId = applied.data["operationId"];
15665
+ const operationId = typeof rawOperationId === "string" && rawOperationId !== "" ? rawOperationId : null;
15666
+ if (!parsed.booleans.has("wait")) {
15667
+ if (json) {
15668
+ process.stdout.write(`${JSON.stringify({ action, ...applied.data })}
15531
15669
  `);
15670
+ return 0;
15671
+ }
15672
+ process.stdout.write(
15673
+ `${action} ${key}
15674
+ ` + (operationId !== null ? `Operation ${operationId} \u2014 follow it with: vfac get operation ${operationId}
15675
+ ` : "")
15676
+ );
15532
15677
  return 0;
15533
15678
  }
15534
- const key = manifestKeyOf(manifest.document) ?? planned.data.manifestKey ?? "(unnamed)";
15535
- const operationId = applied.data["operationId"];
15536
- process.stdout.write(
15537
- `${action} ${key}
15538
- ` + (typeof operationId === "string" ? `Operation ${operationId} \u2014 follow it with: vfac get operation ${operationId}
15539
- ` : "")
15540
- );
15541
- return 0;
15679
+ const finish = (operation, code) => {
15680
+ if (json) {
15681
+ process.stdout.write(`${JSON.stringify({ action, ...applied.data, operation })}
15682
+ `);
15683
+ }
15684
+ return code;
15685
+ };
15686
+ if (operationId === null) {
15687
+ if (!json) {
15688
+ process.stderr.write(
15689
+ `${action} ${key} was accepted, but the platform named no operation to follow, so --wait cannot confirm it. Check the resource: \`vfac get product-instance <pri_\u2026>\`.
15690
+ `
15691
+ );
15692
+ }
15693
+ return finish(null, 1);
15694
+ }
15695
+ if (!json) {
15696
+ process.stdout.write(`${action} ${key}
15697
+ `);
15698
+ process.stderr.write(`Operation ${operationId} \u2014 waiting for it to settle\u2026
15699
+ `);
15700
+ }
15701
+ const outcome = await waitForOperation({
15702
+ endpoint: session.ready.endpoint,
15703
+ token: session.ready.token,
15704
+ operationId,
15705
+ projectId,
15706
+ ...json ? {} : {
15707
+ onProgress: (data) => {
15708
+ const phase = data["phase"];
15709
+ process.stderr.write(
15710
+ ` ${String(data["status"] ?? "UNKNOWN")}` + (typeof phase === "string" && phase !== "" ? ` (${phase})` : "") + "\n"
15711
+ );
15712
+ }
15713
+ },
15714
+ // THE SESSION CAN EXPIRE UNDER A LONG WAIT. It is exchanged once per command
15715
+ // and lasts eight hours while this loop may run for one, so the two do not
15716
+ // overlap today — but a wait that died because its own token aged out would
15717
+ // report "unauthenticated" for a deploy that was fine, which is the worst
15718
+ // sentence to print at the end of an hour.
15719
+ deps: {
15720
+ ...deps,
15721
+ refreshToken: deps.refreshToken ?? (async () => {
15722
+ const again = await ensureSession({
15723
+ endpointFlag: parsed.flags["endpoint"],
15724
+ forceExchange: true
15725
+ });
15726
+ return again.ok ? again.ready.token : null;
15727
+ })
15728
+ }
15729
+ });
15730
+ switch (outcome.kind) {
15731
+ case "settled": {
15732
+ if (!json) process.stdout.write(`${renderOperation(operationId, outcome.data)}
15733
+ `);
15734
+ return finish(outcome.data, outcome.status === "SUCCEEDED" ? 0 : 1);
15735
+ }
15736
+ case "unknown-status": {
15737
+ if (!json) {
15738
+ process.stderr.write(
15739
+ `The platform answered status "${outcome.status}", which this version of vfac does not understand. Upgrade vfac, and read the operation: \`vfac get operation ${operationId}\`.
15740
+ `
15741
+ );
15742
+ }
15743
+ return finish(outcome.data, 1);
15744
+ }
15745
+ case "read-failed": {
15746
+ if (!json) process.stderr.write(`${outcome.error.message}
15747
+ `);
15748
+ return finish(null, 1);
15749
+ }
15750
+ case "timed-out": {
15751
+ if (!json) {
15752
+ const status = outcome.data?.["status"];
15753
+ const phase = outcome.data?.["phase"];
15754
+ process.stderr.write(
15755
+ `Gave up waiting for ${operationId} after an hour. It was last ${typeof status === "string" ? status : "UNKNOWN"}${typeof phase === "string" && phase !== "" ? ` (${phase})` : ""}. It is still running in the platform \u2014 this command stopped waiting, it did not cancel anything. Follow it with: vfac get operation ${operationId}
15756
+ `
15757
+ );
15758
+ }
15759
+ return finish(outcome.data, 1);
15760
+ }
15761
+ }
15542
15762
  }
15543
15763
 
15544
15764
  // src/commands/context.ts
@@ -15710,6 +15930,47 @@ function report(checks, json) {
15710
15930
 
15711
15931
  // src/commands/export.ts
15712
15932
  var import_yaml2 = __toESM(require_dist(), 1);
15933
+ async function runExport(parsed) {
15934
+ if (parsed.command[1] !== "product-instance" && parsed.command[1] !== "resource") {
15935
+ process.stderr.write("Usage: vfac export product-instance pri_\u2026\n");
15936
+ return 2;
15937
+ }
15938
+ const instanceId = parsed.command[2];
15939
+ if (!instanceId) {
15940
+ process.stderr.write("Which resource? vfac export product-instance pri_\u2026\n");
15941
+ return 2;
15942
+ }
15943
+ const session = await ensureSession({ endpointFlag: parsed.flags["endpoint"] });
15944
+ if (!session.ok) {
15945
+ process.stderr.write(`${session.message}
15946
+ `);
15947
+ return 1;
15948
+ }
15949
+ const result = await call({
15950
+ endpoint: session.ready.endpoint,
15951
+ path: withProject(
15952
+ `/api/v1/resources/${encodeURIComponent(instanceId)}/manifest`,
15953
+ resolveProject(parsed.flags["project"], session.ready.stored)
15954
+ ),
15955
+ token: session.ready.token
15956
+ });
15957
+ if (!result.ok) return printError(result.error, parsed.flags["output"] === "json");
15958
+ process.stdout.write(
15959
+ parsed.flags["output"] === "json" ? `${JSON.stringify(result.data, null, 2)}
15960
+ ` : (0, import_yaml2.stringify)(result.data)
15961
+ );
15962
+ const metadata = result.data["metadata"];
15963
+ const hasKey = typeof metadata === "object" && metadata !== null && "key" in metadata;
15964
+ if (!hasKey) {
15965
+ process.stderr.write(
15966
+ `
15967
+ This resource is not managed by a manifest yet, so the document has no "metadata.key". Applying it as-is would create a SECOND resource. Add a key and run:
15968
+ vfac apply -f <file> --adopt ${instanceId}
15969
+ `
15970
+ );
15971
+ }
15972
+ return 0;
15973
+ }
15713
15974
 
15714
15975
  // src/paging.ts
15715
15976
  var MAX_PAGES = 50;
@@ -15892,62 +16153,6 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
15892
16153
  process.stderr.write("Usage: vfac get product-instances | vfac get operation op_\u2026\n");
15893
16154
  return 2;
15894
16155
  }
15895
- function withProject(path, projectId) {
15896
- return projectId === null ? path : `${path}?project=${encodeURIComponent(projectId)}`;
15897
- }
15898
- function renderOperation(operationId, data) {
15899
- const status = String(data["status"] ?? "UNKNOWN");
15900
- const phase = data["phase"];
15901
- const lines = [
15902
- typeof phase === "string" && phase !== "" ? `${operationId} ${status} (${phase})` : `${operationId} ${status}`
15903
- ];
15904
- const type = data["type"];
15905
- if (typeof type === "string") lines[0] += ` ${type}`;
15906
- const steps = Array.isArray(data["steps"]) ? data["steps"] : [];
15907
- if (steps.length > 0) {
15908
- lines.push("");
15909
- for (const step of steps) {
15910
- const state = String(step["state"] ?? "");
15911
- lines.push(` ${markerFor(state)} ${String(step["label"] ?? step["key"] ?? "")}`);
15912
- }
15913
- }
15914
- const failure = asRecord(data["failure"]);
15915
- if (failure) {
15916
- lines.push("");
15917
- const stage = failure["stage"];
15918
- lines.push(` Failed${typeof stage === "string" ? ` at ${stage}` : ""}`);
15919
- const summary = failure["summary"];
15920
- if (typeof summary === "string") lines.push(` ${summary}`);
15921
- const detail = failure["detail"];
15922
- if (typeof detail === "string" && detail !== "") lines.push(` ${detail}`);
15923
- const cause = failure["cause"];
15924
- if (typeof cause === "string") lines.push(` cause: ${cause}`);
15925
- const retryable = failure["retryable"];
15926
- if (typeof retryable === "boolean") {
15927
- lines.push(
15928
- retryable ? " Another attempt can succeed: `vfac lifecycle reconcile <pri_\u2026>`." : " Another attempt will fail the same way. Change something first."
15929
- );
15930
- }
15931
- }
15932
- return lines.join("\n");
15933
- }
15934
- function markerFor(state) {
15935
- switch (state) {
15936
- case "completed":
15937
- return "\u2713";
15938
- case "failed":
15939
- return "\u2717";
15940
- case "active":
15941
- return "\u203A";
15942
- case "pending":
15943
- return "\xB7";
15944
- default:
15945
- return "-";
15946
- }
15947
- }
15948
- function asRecord(value) {
15949
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
15950
- }
15951
16156
  function wrapOutput(text, width) {
15952
16157
  const out = [];
15953
16158
  let current = "";
@@ -15963,49 +16168,6 @@ function wrapOutput(text, width) {
15963
16168
  return out;
15964
16169
  }
15965
16170
 
15966
- // src/commands/export.ts
15967
- async function runExport(parsed) {
15968
- if (parsed.command[1] !== "product-instance" && parsed.command[1] !== "resource") {
15969
- process.stderr.write("Usage: vfac export product-instance pri_\u2026\n");
15970
- return 2;
15971
- }
15972
- const instanceId = parsed.command[2];
15973
- if (!instanceId) {
15974
- process.stderr.write("Which resource? vfac export product-instance pri_\u2026\n");
15975
- return 2;
15976
- }
15977
- const session = await ensureSession({ endpointFlag: parsed.flags["endpoint"] });
15978
- if (!session.ok) {
15979
- process.stderr.write(`${session.message}
15980
- `);
15981
- return 1;
15982
- }
15983
- const result = await call({
15984
- endpoint: session.ready.endpoint,
15985
- path: withProject(
15986
- `/api/v1/resources/${encodeURIComponent(instanceId)}/manifest`,
15987
- resolveProject(parsed.flags["project"], session.ready.stored)
15988
- ),
15989
- token: session.ready.token
15990
- });
15991
- if (!result.ok) return printError(result.error, parsed.flags["output"] === "json");
15992
- process.stdout.write(
15993
- parsed.flags["output"] === "json" ? `${JSON.stringify(result.data, null, 2)}
15994
- ` : (0, import_yaml2.stringify)(result.data)
15995
- );
15996
- const metadata = result.data["metadata"];
15997
- const hasKey = typeof metadata === "object" && metadata !== null && "key" in metadata;
15998
- if (!hasKey) {
15999
- process.stderr.write(
16000
- `
16001
- This resource is not managed by a manifest yet, so the document has no "metadata.key". Applying it as-is would create a SECOND resource. Add a key and run:
16002
- vfac apply -f <file> --adopt ${instanceId}
16003
- `
16004
- );
16005
- }
16006
- return 0;
16007
- }
16008
-
16009
16171
  // src/commands/product.ts
16010
16172
  async function runProduct(parsed) {
16011
16173
  const json = parsed.flags["output"] === "json";
@@ -16622,7 +16784,7 @@ var USAGE = `vfac \u2014 deploy Varde Flyt resources from a manifest
16622
16784
  [--region <region>] [--profile <profile>]
16623
16785
  vfac validate -f resource.yaml
16624
16786
  vfac plan -f resource.yaml
16625
- vfac apply -f resource.yaml [--secret NAME=env:VAR] [--adopt pri_\u2026]
16787
+ vfac apply -f resource.yaml [--secret NAME=env:VAR] [--adopt pri_\u2026] [--wait]
16626
16788
  vfac get product-instances
16627
16789
  vfac get product-instance pri_\u2026 what it is, and its outputs
16628
16790
  vfac get operation op_\u2026
@@ -16632,6 +16794,9 @@ var USAGE = `vfac \u2014 deploy Varde Flyt resources from a manifest
16632
16794
 
16633
16795
  Options
16634
16796
  -f, --file the manifest to read. YAML or JSON
16797
+ --wait apply: wait for the operation to settle, and exit non-zero if it
16798
+ fails. Without it, apply exits once the change is ACCEPTED \u2014
16799
+ which a pipeline reads as success before the deploy has happened
16635
16800
  --project the Project to act in. Defaults to the stored context
16636
16801
  --endpoint your organization's portal. Defaults to VFAC_ENDPOINT, then the
16637
16802
  stored context
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc.1",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/digital-verdi/Varde-flyt-ai-cloud.git",
8
+ "directory": "packages/cli"
9
+ },
5
10
  "type": "module",
6
11
  "bin": {
7
12
  "vfac": "./dist/vfac.mjs"