@varde-flyt/vfac 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/vfac.mjs +993 -326
- package/package.json +1 -1
package/dist/vfac.mjs
CHANGED
|
@@ -7378,6 +7378,8 @@ var VALUED = /* @__PURE__ */ new Set([
|
|
|
7378
7378
|
// an organization and part of a catalogue, silently, once either passed 25.
|
|
7379
7379
|
"cursor",
|
|
7380
7380
|
"page-size",
|
|
7381
|
+
// `get operations --resource pri_…`: the history of one resource.
|
|
7382
|
+
"resource",
|
|
7381
7383
|
"product",
|
|
7382
7384
|
"name",
|
|
7383
7385
|
"key",
|
|
@@ -7700,263 +7702,7 @@ async function probeMachineApi(args) {
|
|
|
7700
7702
|
}
|
|
7701
7703
|
|
|
7702
7704
|
// src/manifest.ts
|
|
7703
|
-
var import_yaml = __toESM(require_dist(), 1);
|
|
7704
7705
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
7705
|
-
function readManifest(path) {
|
|
7706
|
-
let text;
|
|
7707
|
-
try {
|
|
7708
|
-
text = readFileSync2(path, "utf8");
|
|
7709
|
-
} catch {
|
|
7710
|
-
return { ok: false, message: `Could not read ${path}.` };
|
|
7711
|
-
}
|
|
7712
|
-
let parsed;
|
|
7713
|
-
try {
|
|
7714
|
-
parsed = (0, import_yaml.parse)(text);
|
|
7715
|
-
} catch (error) {
|
|
7716
|
-
return {
|
|
7717
|
-
ok: false,
|
|
7718
|
-
message: `${path} is not valid YAML or JSON: ${error instanceof Error ? error.message : "unknown error"}`
|
|
7719
|
-
};
|
|
7720
|
-
}
|
|
7721
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
7722
|
-
return { ok: false, message: `${path} does not contain a manifest document.` };
|
|
7723
|
-
}
|
|
7724
|
-
return { ok: true, document: parsed };
|
|
7725
|
-
}
|
|
7726
|
-
function resolveSecrets(pairs, environment = process.env) {
|
|
7727
|
-
const secrets = {};
|
|
7728
|
-
for (const pair of pairs) {
|
|
7729
|
-
const separator = pair.indexOf("=");
|
|
7730
|
-
if (separator <= 0) {
|
|
7731
|
-
return { ok: false, message: `--secret expects name=env:VARIABLE, got "${pair}".` };
|
|
7732
|
-
}
|
|
7733
|
-
const name = pair.slice(0, separator);
|
|
7734
|
-
const source = pair.slice(separator + 1);
|
|
7735
|
-
if (!source.startsWith("env:")) {
|
|
7736
|
-
return {
|
|
7737
|
-
ok: false,
|
|
7738
|
-
message: `--secret ${name} must read from the environment: --secret ${name}=env:VARIABLE. A value on the command line is in your shell history and in the process table.`
|
|
7739
|
-
};
|
|
7740
|
-
}
|
|
7741
|
-
const variable = source.slice("env:".length);
|
|
7742
|
-
if (variable.length === 0) {
|
|
7743
|
-
return { ok: false, message: `--secret ${name}=env: names no variable.` };
|
|
7744
|
-
}
|
|
7745
|
-
const value = environment[variable];
|
|
7746
|
-
if (value === void 0 || value === "") {
|
|
7747
|
-
return { ok: false, message: `${variable} is not set, so ${name} has no value.` };
|
|
7748
|
-
}
|
|
7749
|
-
secrets[name] = value;
|
|
7750
|
-
}
|
|
7751
|
-
return { ok: true, secrets };
|
|
7752
|
-
}
|
|
7753
|
-
function manifestKeyOf(document) {
|
|
7754
|
-
const metadata = document["metadata"];
|
|
7755
|
-
if (typeof metadata !== "object" || metadata === null) return null;
|
|
7756
|
-
const key = metadata["key"];
|
|
7757
|
-
return typeof key === "string" ? key : null;
|
|
7758
|
-
}
|
|
7759
|
-
|
|
7760
|
-
// src/endpoint.ts
|
|
7761
|
-
function isLoopback(hostname) {
|
|
7762
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
7763
|
-
}
|
|
7764
|
-
function validateEndpoint(raw, source) {
|
|
7765
|
-
let url;
|
|
7766
|
-
try {
|
|
7767
|
-
url = new URL(raw);
|
|
7768
|
-
} catch {
|
|
7769
|
-
return { ok: false, message: `${source} is not a URL: "${raw}".` };
|
|
7770
|
-
}
|
|
7771
|
-
if (url.protocol === "https:") return { ok: true, endpoint: url.origin };
|
|
7772
|
-
if (url.protocol === "http:" && isLoopback(url.hostname)) {
|
|
7773
|
-
return { ok: true, endpoint: url.origin };
|
|
7774
|
-
}
|
|
7775
|
-
return {
|
|
7776
|
-
ok: false,
|
|
7777
|
-
message: `${source} must be https \u2014 a credential is sent to it, and "${url.protocol}//" would send it in the clear. Plain http is accepted for localhost only.`
|
|
7778
|
-
};
|
|
7779
|
-
}
|
|
7780
|
-
|
|
7781
|
-
// src/session.ts
|
|
7782
|
-
async function ensureSession(args) {
|
|
7783
|
-
const stored = readStored();
|
|
7784
|
-
const resolved = resolveEndpoint(args.endpointFlag, stored);
|
|
7785
|
-
if (!resolved) {
|
|
7786
|
-
return {
|
|
7787
|
-
ok: false,
|
|
7788
|
-
message: "No endpoint. Run `vfac context set --endpoint https://your-organization.cloud.vardeflyt.no`, or set VFAC_ENDPOINT."
|
|
7789
|
-
};
|
|
7790
|
-
}
|
|
7791
|
-
const checked = validateEndpoint(resolved.value, resolved.source);
|
|
7792
|
-
if (!checked.ok) return { ok: false, message: checked.message };
|
|
7793
|
-
const endpoint = checked.endpoint;
|
|
7794
|
-
const credential = credentialFromEnvironment();
|
|
7795
|
-
if (args.forceExchange !== true && sessionUsable(stored.session, endpoint, args.now ?? /* @__PURE__ */ new Date(), credential?.keyId)) {
|
|
7796
|
-
return { ok: true, ready: { endpoint, token: stored.session.token, stored, via: "stored" } };
|
|
7797
|
-
}
|
|
7798
|
-
const signedIn = await signIn({
|
|
7799
|
-
endpoint,
|
|
7800
|
-
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
7801
|
-
});
|
|
7802
|
-
if (!signedIn.ok) return { ok: false, message: signedIn.message };
|
|
7803
|
-
const next = { ...stored, session: signedIn.session };
|
|
7804
|
-
writeStored(next);
|
|
7805
|
-
return {
|
|
7806
|
-
ok: true,
|
|
7807
|
-
ready: {
|
|
7808
|
-
endpoint,
|
|
7809
|
-
token: signedIn.session.token,
|
|
7810
|
-
stored: next,
|
|
7811
|
-
via: "exchanged",
|
|
7812
|
-
...signedIn.gate ? { gate: signedIn.gate } : {}
|
|
7813
|
-
}
|
|
7814
|
-
};
|
|
7815
|
-
}
|
|
7816
|
-
function resolveProject(flag, stored) {
|
|
7817
|
-
return flag ?? stored.context?.projectId ?? null;
|
|
7818
|
-
}
|
|
7819
|
-
|
|
7820
|
-
// src/operations.ts
|
|
7821
|
-
var OPERATION_STATUSES = [
|
|
7822
|
-
"PENDING",
|
|
7823
|
-
"RUNNING",
|
|
7824
|
-
"SUCCEEDED",
|
|
7825
|
-
"FAILED",
|
|
7826
|
-
"CANCELLED"
|
|
7827
|
-
];
|
|
7828
|
-
var TERMINAL_STATUSES = ["SUCCEEDED", "FAILED", "CANCELLED"];
|
|
7829
|
-
var POLL_INTERVAL_MS = 5e3;
|
|
7830
|
-
var MAX_WAIT_MS = 60 * 60 * 1e3;
|
|
7831
|
-
function withProject(path, projectId) {
|
|
7832
|
-
return projectId === null ? path : `${path}?project=${encodeURIComponent(projectId)}`;
|
|
7833
|
-
}
|
|
7834
|
-
function operationPath(operationId, projectId) {
|
|
7835
|
-
return withProject(`/api/v1/operations/${encodeURIComponent(operationId)}`, projectId);
|
|
7836
|
-
}
|
|
7837
|
-
function classify(data) {
|
|
7838
|
-
const status = data["status"];
|
|
7839
|
-
if (typeof status !== "string") return "unknown";
|
|
7840
|
-
if (TERMINAL_STATUSES.includes(status)) return "terminal";
|
|
7841
|
-
if (OPERATION_STATUSES.includes(status)) return "working";
|
|
7842
|
-
return "unknown";
|
|
7843
|
-
}
|
|
7844
|
-
async function readOperation(args) {
|
|
7845
|
-
const result = await call({
|
|
7846
|
-
endpoint: args.endpoint,
|
|
7847
|
-
path: operationPath(args.operationId, args.projectId),
|
|
7848
|
-
token: args.token,
|
|
7849
|
-
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
7850
|
-
});
|
|
7851
|
-
return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error };
|
|
7852
|
-
}
|
|
7853
|
-
async function waitForOperation(args) {
|
|
7854
|
-
const deps = args.deps ?? {};
|
|
7855
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
7856
|
-
const now = deps.now ?? (() => Date.now());
|
|
7857
|
-
const deadline = now() + MAX_WAIT_MS;
|
|
7858
|
-
let token = args.token;
|
|
7859
|
-
let refreshed = false;
|
|
7860
|
-
let last = null;
|
|
7861
|
-
for (; ; ) {
|
|
7862
|
-
const read = await readOperation({
|
|
7863
|
-
endpoint: args.endpoint,
|
|
7864
|
-
token,
|
|
7865
|
-
operationId: args.operationId,
|
|
7866
|
-
projectId: args.projectId,
|
|
7867
|
-
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
7868
|
-
});
|
|
7869
|
-
if (!read.ok) {
|
|
7870
|
-
if (read.error.status === 401 && !refreshed && deps.refreshToken) {
|
|
7871
|
-
refreshed = true;
|
|
7872
|
-
const next = await deps.refreshToken();
|
|
7873
|
-
if (next !== null) {
|
|
7874
|
-
token = next;
|
|
7875
|
-
continue;
|
|
7876
|
-
}
|
|
7877
|
-
}
|
|
7878
|
-
return { kind: "read-failed", error: read.error };
|
|
7879
|
-
}
|
|
7880
|
-
last = read.data;
|
|
7881
|
-
const verdict = classify(read.data);
|
|
7882
|
-
if (verdict === "terminal") {
|
|
7883
|
-
return { kind: "settled", status: String(read.data["status"]), data: read.data };
|
|
7884
|
-
}
|
|
7885
|
-
if (verdict === "unknown") {
|
|
7886
|
-
return { kind: "unknown-status", status: String(read.data["status"]), data: read.data };
|
|
7887
|
-
}
|
|
7888
|
-
args.onProgress?.(read.data);
|
|
7889
|
-
if (now() + POLL_INTERVAL_MS >= deadline) return { kind: "timed-out", data: last };
|
|
7890
|
-
await sleep(POLL_INTERVAL_MS);
|
|
7891
|
-
}
|
|
7892
|
-
}
|
|
7893
|
-
function renderOperation(operationId, data) {
|
|
7894
|
-
const status = String(data["status"] ?? "UNKNOWN");
|
|
7895
|
-
const phase = data["phase"];
|
|
7896
|
-
const lines = [
|
|
7897
|
-
typeof phase === "string" && phase !== "" ? `${operationId} ${status} (${phase})` : `${operationId} ${status}`
|
|
7898
|
-
];
|
|
7899
|
-
const type = data["type"];
|
|
7900
|
-
if (typeof type === "string") lines[0] += ` ${type}`;
|
|
7901
|
-
const steps = Array.isArray(data["steps"]) ? data["steps"] : [];
|
|
7902
|
-
if (steps.length > 0) {
|
|
7903
|
-
lines.push("");
|
|
7904
|
-
for (const step of steps) {
|
|
7905
|
-
const state = String(step["state"] ?? "");
|
|
7906
|
-
lines.push(` ${markerFor(state)} ${String(step["label"] ?? step["key"] ?? "")}`);
|
|
7907
|
-
}
|
|
7908
|
-
}
|
|
7909
|
-
const failure = asRecord(data["failure"]);
|
|
7910
|
-
if (failure) {
|
|
7911
|
-
lines.push("");
|
|
7912
|
-
const stage = failure["stage"];
|
|
7913
|
-
lines.push(` Failed${typeof stage === "string" ? ` at ${stage}` : ""}`);
|
|
7914
|
-
const summary = failure["summary"];
|
|
7915
|
-
if (typeof summary === "string") lines.push(` ${summary}`);
|
|
7916
|
-
const cause = failure["cause"];
|
|
7917
|
-
if (typeof cause === "string") lines.push(` cause: ${cause}`);
|
|
7918
|
-
const services = failure["services"];
|
|
7919
|
-
if (Array.isArray(services)) {
|
|
7920
|
-
for (const entry of services) {
|
|
7921
|
-
const one = asRecord(entry);
|
|
7922
|
-
if (one === null) continue;
|
|
7923
|
-
const dependency = one["dependency"];
|
|
7924
|
-
if (typeof dependency !== "string" || dependency === "") continue;
|
|
7925
|
-
const code = one["code"];
|
|
7926
|
-
lines.push(
|
|
7927
|
-
typeof code === "string" && code !== "" ? ` ${dependency} \u2014 the service reported: ${code}` : ` ${dependency} \u2014 the service reported nothing usable`
|
|
7928
|
-
);
|
|
7929
|
-
}
|
|
7930
|
-
}
|
|
7931
|
-
const retryable = failure["retryable"];
|
|
7932
|
-
if (typeof retryable === "boolean") {
|
|
7933
|
-
const again = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "`vfac lifecycle upgrade <pri_\u2026>`" : "`vfac lifecycle reconcile <pri_\u2026>`";
|
|
7934
|
-
const changeIt = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "" : ", or `vfac apply` a corrected manifest";
|
|
7935
|
-
const cause2 = failure["cause"];
|
|
7936
|
-
lines.push(
|
|
7937
|
-
retryable ? ` Another attempt can succeed: ${again}.` : cause2 === "unknown" ? ` The platform could not classify this, so there is nothing here to correct. Read the whole record with \`vfac get operation ${operationId} --output json\`, and quote that id if you ask for help. Repeating it unchanged fails the same way.` : ` Repeating this unchanged fails again \u2014 fix what the lines above name, then ${again}${changeIt}.`
|
|
7938
|
-
);
|
|
7939
|
-
}
|
|
7940
|
-
}
|
|
7941
|
-
return lines.join("\n");
|
|
7942
|
-
}
|
|
7943
|
-
function markerFor(state) {
|
|
7944
|
-
switch (state) {
|
|
7945
|
-
case "completed":
|
|
7946
|
-
return "\u2713";
|
|
7947
|
-
case "failed":
|
|
7948
|
-
return "\u2717";
|
|
7949
|
-
case "active":
|
|
7950
|
-
return "\u203A";
|
|
7951
|
-
case "pending":
|
|
7952
|
-
return "\xB7";
|
|
7953
|
-
default:
|
|
7954
|
-
return "-";
|
|
7955
|
-
}
|
|
7956
|
-
}
|
|
7957
|
-
function asRecord(value) {
|
|
7958
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7959
|
-
}
|
|
7960
7706
|
|
|
7961
7707
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
7962
7708
|
var external_exports = {};
|
|
@@ -12245,6 +11991,29 @@ var ValueConditionSchema = external_exports.object({
|
|
|
12245
11991
|
anyOf: external_exports.array(external_exports.string().min(1).max(200)).min(1).max(20),
|
|
12246
11992
|
anyRow: external_exports.boolean().optional()
|
|
12247
11993
|
}).strict();
|
|
11994
|
+
var ValueReferenceSchema = external_exports.object({
|
|
11995
|
+
/**
|
|
11996
|
+
* The setting whose VALUES form the allowed set — a canonical declaration
|
|
11997
|
+
* path, or a bare root key, which is the same string at depth 1.
|
|
11998
|
+
*/
|
|
11999
|
+
path: external_exports.string().min(1).max(400)
|
|
12000
|
+
}).strict();
|
|
12001
|
+
var RowCardinalitySchema = external_exports.object({
|
|
12002
|
+
/** A setting declared DIRECTLY on the row. */
|
|
12003
|
+
property: PropertyNameSchema,
|
|
12004
|
+
/** The answers that count toward the limit. Non-empty. */
|
|
12005
|
+
anyOf: external_exports.array(external_exports.string().min(1).max(200)).min(1).max(20),
|
|
12006
|
+
/**
|
|
12007
|
+
* How many rows may answer that way. Zero is meaningful and allowed — "no
|
|
12008
|
+
* row may, under this release" is a thing an upgrade needs to be able to
|
|
12009
|
+
* say.
|
|
12010
|
+
*/
|
|
12011
|
+
max: external_exports.number().int().min(0).max(99)
|
|
12012
|
+
}).strict();
|
|
12013
|
+
var RowRulesSchema = external_exports.object({
|
|
12014
|
+
unique: external_exports.array(PropertyNameSchema).min(1).max(MAX_CONFIGURATION_CHILDREN).optional(),
|
|
12015
|
+
atMost: external_exports.array(RowCardinalitySchema).min(1).max(10).optional()
|
|
12016
|
+
}).strict();
|
|
12248
12017
|
var TitleSchema = external_exports.string().min(2).max(80).describe("Customer-facing field label.");
|
|
12249
12018
|
var V1_LEAF_BOUNDS = {
|
|
12250
12019
|
help: 400,
|
|
@@ -12318,7 +12087,16 @@ function leafShapes(bounds) {
|
|
|
12318
12087
|
* what `requiredSecrets` reports, which is one answer with one reader.
|
|
12319
12088
|
* Opening it wider is a separate decision with its own blast radius.
|
|
12320
12089
|
*/
|
|
12321
|
-
"x-required-when": ValueConditionSchema.optional()
|
|
12090
|
+
"x-required-when": ValueConditionSchema.optional(),
|
|
12091
|
+
/**
|
|
12092
|
+
* The values this one must be chosen from — see {@link ValueReferenceSchema}.
|
|
12093
|
+
*
|
|
12094
|
+
* ON A STRING ONLY, and not on a secret: the platform never reads a
|
|
12095
|
+
* secret's value, so a membership test on one could never be evaluated.
|
|
12096
|
+
* The publish rules say so rather than leaving it to be discovered, the
|
|
12097
|
+
* same way they already do for a condition pointed at a secret.
|
|
12098
|
+
*/
|
|
12099
|
+
"x-value-in": ValueReferenceSchema.optional()
|
|
12322
12100
|
}).strict();
|
|
12323
12101
|
withJsonSchemaAnnex(string, SECRET_COUPLING_ANNEX);
|
|
12324
12102
|
const number = external_exports.object({
|
|
@@ -12329,14 +12107,23 @@ function leafShapes(bounds) {
|
|
|
12329
12107
|
/** Both bounds required — "numeric limits" in the guide is not optional. */
|
|
12330
12108
|
minimum: external_exports.number(),
|
|
12331
12109
|
maximum: external_exports.number(),
|
|
12332
|
-
default: external_exports.number().optional()
|
|
12110
|
+
default: external_exports.number().optional(),
|
|
12111
|
+
/**
|
|
12112
|
+
* WIDENED FROM SECRETS TO EVERY ROOT SETTING, which this shape's own
|
|
12113
|
+
* comment on the string leaf used to record as a known gap: "a
|
|
12114
|
+
* value-conditioned requirement for an ORDINARY setting is a real gap
|
|
12115
|
+
* too". It is the same keyword, the same evaluator and the same publish
|
|
12116
|
+
* rules; what changes is which settings may carry it.
|
|
12117
|
+
*/
|
|
12118
|
+
"x-required-when": ValueConditionSchema.optional()
|
|
12333
12119
|
}).strict();
|
|
12334
12120
|
const boolean = external_exports.object({
|
|
12335
12121
|
type: external_exports.literal("boolean"),
|
|
12336
12122
|
title: TitleSchema,
|
|
12337
12123
|
description: help.optional(),
|
|
12338
12124
|
group: ConfigurationGroupSchema,
|
|
12339
|
-
default: external_exports.boolean().optional()
|
|
12125
|
+
default: external_exports.boolean().optional(),
|
|
12126
|
+
"x-required-when": ValueConditionSchema.optional()
|
|
12340
12127
|
}).strict();
|
|
12341
12128
|
const items = external_exports.object({
|
|
12342
12129
|
type: external_exports.literal("string"),
|
|
@@ -12353,7 +12140,8 @@ function leafShapes(bounds) {
|
|
|
12353
12140
|
/** Required: an unbounded list is an unbounded write. */
|
|
12354
12141
|
maxItems: external_exports.number().int().min(1).max(100),
|
|
12355
12142
|
uniqueItems: external_exports.boolean().optional(),
|
|
12356
|
-
default: external_exports.array(external_exports.string().max(200)).max(100).optional()
|
|
12143
|
+
default: external_exports.array(external_exports.string().max(200)).max(100).optional(),
|
|
12144
|
+
"x-required-when": ValueConditionSchema.optional()
|
|
12357
12145
|
}).strict();
|
|
12358
12146
|
return { string, number, boolean, items, array };
|
|
12359
12147
|
}
|
|
@@ -12373,9 +12161,9 @@ var ConfigurationPropertySchema = external_exports.discriminatedUnion("type", [
|
|
|
12373
12161
|
var NestedStringPropertySchema = V2_LEAVES.string.omit({ "x-secret": true, writeOnly: true, "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() });
|
|
12374
12162
|
var V2_NESTED_LEAVES = {
|
|
12375
12163
|
string: NestedStringPropertySchema,
|
|
12376
|
-
number: V2_LEAVES.number.extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12377
|
-
boolean: V2_LEAVES.boolean.extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12378
|
-
array: V2_LEAVES.array.extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12164
|
+
number: V2_LEAVES.number.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12165
|
+
boolean: V2_LEAVES.boolean.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12166
|
+
array: V2_LEAVES.array.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
|
|
12379
12167
|
items: V2_LEAVES.items
|
|
12380
12168
|
};
|
|
12381
12169
|
function childBagShape(child) {
|
|
@@ -12419,7 +12207,23 @@ function propertyUnion(levelsBelow, leafSet) {
|
|
|
12419
12207
|
// discriminator, which is also what keeps a v1 document's
|
|
12420
12208
|
// `items: {type: 'string'}` reading exactly as it always did.
|
|
12421
12209
|
leafSet.array.extend({
|
|
12422
|
-
items: external_exports.discriminatedUnion("type", [V2_LEAVES.items, groupItems])
|
|
12210
|
+
items: external_exports.discriminatedUnion("type", [V2_LEAVES.items, groupItems]),
|
|
12211
|
+
/**
|
|
12212
|
+
* Rules over the ROWS of this list — see {@link RowRulesSchema}.
|
|
12213
|
+
*
|
|
12214
|
+
* HERE RATHER THAN ON THE LEAF SHAPE, and that placement is the whole
|
|
12215
|
+
* enforcement. `leafShapes().array` is also a v1 list and a last-level
|
|
12216
|
+
* list, both of which hold STRINGS: a rule naming a field of a row would
|
|
12217
|
+
* name nothing there, and `uniqueItems` is already the right keyword for
|
|
12218
|
+
* comparing whole scalar entries. This branch is the only one in the chain
|
|
12219
|
+
* where a list can hold groups, so declaring the keyword here makes "rows
|
|
12220
|
+
* have fields" a property of the shape rather than a rule to remember.
|
|
12221
|
+
*
|
|
12222
|
+
* The refinement still has to refuse a rule on a list of STRINGS, because
|
|
12223
|
+
* `items` is a discriminated union INSIDE this branch — the shape narrows
|
|
12224
|
+
* where a row can exist, not whether this particular list has one.
|
|
12225
|
+
*/
|
|
12226
|
+
"x-row-rules": RowRulesSchema.optional()
|
|
12423
12227
|
}),
|
|
12424
12228
|
/**
|
|
12425
12229
|
* A group of related settings.
|
|
@@ -13122,6 +12926,9 @@ function canonicalJson(value) {
|
|
|
13122
12926
|
}
|
|
13123
12927
|
|
|
13124
12928
|
// ../product-configuration/dist/configuration-tree.js
|
|
12929
|
+
function immediateContainer(node) {
|
|
12930
|
+
return node.containers[node.containers.length - 1] ?? null;
|
|
12931
|
+
}
|
|
13125
12932
|
function underList(node) {
|
|
13126
12933
|
return node.containers.some((container) => container.kind === "array");
|
|
13127
12934
|
}
|
|
@@ -13183,6 +12990,11 @@ function walkDeclarations(properties, ancestors, base, onLeaf, onContainer = ()
|
|
|
13183
12990
|
walkDeclarations(children.properties, [...ancestors, container], segments, onLeaf, onContainer);
|
|
13184
12991
|
}
|
|
13185
12992
|
}
|
|
12993
|
+
function declaredDefault(property) {
|
|
12994
|
+
if (property.type === "object")
|
|
12995
|
+
return void 0;
|
|
12996
|
+
return property.default;
|
|
12997
|
+
}
|
|
13186
12998
|
function leafAt(schema, path) {
|
|
13187
12999
|
const declaration = declarationPathOf(path);
|
|
13188
13000
|
return configurationLeaves(schema).find((leaf) => leaf.path === declaration) ?? null;
|
|
@@ -13195,6 +13007,29 @@ function declaresPath(schema, path) {
|
|
|
13195
13007
|
function isPlainObject(value) {
|
|
13196
13008
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13197
13009
|
}
|
|
13010
|
+
function expandDeclarationPath(configuration, declaration) {
|
|
13011
|
+
const out = [];
|
|
13012
|
+
const walk = (cursor, remaining, soFar) => {
|
|
13013
|
+
if (remaining.length === 0) {
|
|
13014
|
+
const path = configurationPath(...soFar);
|
|
13015
|
+
if (path !== null)
|
|
13016
|
+
out.push(path);
|
|
13017
|
+
return;
|
|
13018
|
+
}
|
|
13019
|
+
if (Array.isArray(cursor)) {
|
|
13020
|
+
cursor.forEach((element, index) => walk(element, remaining, [...soFar, index]));
|
|
13021
|
+
return;
|
|
13022
|
+
}
|
|
13023
|
+
if (!isPlainObject(cursor))
|
|
13024
|
+
return;
|
|
13025
|
+
const [head, ...rest] = remaining;
|
|
13026
|
+
if (head === void 0)
|
|
13027
|
+
return;
|
|
13028
|
+
walk(cursor[String(head)], rest, [...soFar, head]);
|
|
13029
|
+
};
|
|
13030
|
+
walk(configuration, segmentsOf(declaration), []);
|
|
13031
|
+
return out;
|
|
13032
|
+
}
|
|
13198
13033
|
function valueAt(configuration, path) {
|
|
13199
13034
|
let cursor = configuration;
|
|
13200
13035
|
for (const segment of segmentsOf(path)) {
|
|
@@ -13290,6 +13125,7 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
|
|
|
13290
13125
|
const secrets = {};
|
|
13291
13126
|
const configuration = validateObject({ properties: schema.properties, required: schema.required }, input, null, walk, { secrets, supplied });
|
|
13292
13127
|
drain(walk, crossFieldIssues(schema, supplied, presence, configuration));
|
|
13128
|
+
drain(walk, rowAndReferenceIssues(schema, presence, configuration));
|
|
13293
13129
|
if (walk.suppressed > 0) {
|
|
13294
13130
|
walk.issues.push({
|
|
13295
13131
|
field: "",
|
|
@@ -13533,6 +13369,106 @@ function crossFieldIssues(schema, supplied, presence, configuration = {}) {
|
|
|
13533
13369
|
}
|
|
13534
13370
|
return issues;
|
|
13535
13371
|
}
|
|
13372
|
+
function referenceOf(property) {
|
|
13373
|
+
return property?.["x-value-in"];
|
|
13374
|
+
}
|
|
13375
|
+
function rowRulesOf(property) {
|
|
13376
|
+
return property?.["x-row-rules"];
|
|
13377
|
+
}
|
|
13378
|
+
function comparableValue(value) {
|
|
13379
|
+
if (value === void 0 || value === null)
|
|
13380
|
+
return null;
|
|
13381
|
+
if (typeof value === "string")
|
|
13382
|
+
return value === "" ? null : JSON.stringify(value);
|
|
13383
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
13384
|
+
return JSON.stringify(value);
|
|
13385
|
+
return null;
|
|
13386
|
+
}
|
|
13387
|
+
function rowAndReferenceIssues(schema, presence, configuration) {
|
|
13388
|
+
const issues = [];
|
|
13389
|
+
const referenced = new Set(presence.referenced ?? []);
|
|
13390
|
+
for (const leaf of configurationLeaves(schema)) {
|
|
13391
|
+
const reference = referenceOf(leaf.property);
|
|
13392
|
+
if (reference === void 0)
|
|
13393
|
+
continue;
|
|
13394
|
+
const targetPath = parseConfigurationPath(reference.path);
|
|
13395
|
+
if (targetPath === null)
|
|
13396
|
+
continue;
|
|
13397
|
+
const allowed = new Set(valuesAt(configuration, targetPath).filter((value) => typeof value === "string"));
|
|
13398
|
+
for (const valuePath of expandDeclarationPath(configuration, leaf.path)) {
|
|
13399
|
+
if (referenced.has(valuePath))
|
|
13400
|
+
continue;
|
|
13401
|
+
const held = valueAt(configuration, valuePath);
|
|
13402
|
+
if (typeof held !== "string" || held === "")
|
|
13403
|
+
continue;
|
|
13404
|
+
if (allowed.has(held))
|
|
13405
|
+
continue;
|
|
13406
|
+
issues.push({
|
|
13407
|
+
field: valuePath,
|
|
13408
|
+
message: `This must be one of the values configured under ${labelOfPath(schema, targetPath)}. ` + (allowed.size === 0 ? "Nothing is configured there yet." : "No entry there currently holds this one.")
|
|
13409
|
+
});
|
|
13410
|
+
}
|
|
13411
|
+
}
|
|
13412
|
+
for (const container of configurationContainers(schema)) {
|
|
13413
|
+
if (container.kind !== "array")
|
|
13414
|
+
continue;
|
|
13415
|
+
const rules = rowRulesOf(container.property);
|
|
13416
|
+
if (rules === void 0)
|
|
13417
|
+
continue;
|
|
13418
|
+
for (const listPath of expandDeclarationPath(configuration, container.path)) {
|
|
13419
|
+
const rows = valueAt(configuration, listPath);
|
|
13420
|
+
if (!Array.isArray(rows))
|
|
13421
|
+
continue;
|
|
13422
|
+
const fieldOf = (index, name) => configurationPath(...pathSegments(listPath), index, name) ?? listPath;
|
|
13423
|
+
for (const name of rules.unique ?? []) {
|
|
13424
|
+
const firstAt = /* @__PURE__ */ new Map();
|
|
13425
|
+
rows.forEach((row, index) => {
|
|
13426
|
+
const held = comparableValue(isPlainObject2(row) ? row[name] : void 0);
|
|
13427
|
+
if (held === null)
|
|
13428
|
+
return;
|
|
13429
|
+
const earlier = firstAt.get(held);
|
|
13430
|
+
if (earlier === void 0) {
|
|
13431
|
+
firstAt.set(held, index);
|
|
13432
|
+
return;
|
|
13433
|
+
}
|
|
13434
|
+
issues.push({
|
|
13435
|
+
field: fieldOf(index, name),
|
|
13436
|
+
message: `${labelOfChild(container, name)} must be different in every entry; entry ${earlier + 1} already uses this one.`
|
|
13437
|
+
});
|
|
13438
|
+
});
|
|
13439
|
+
}
|
|
13440
|
+
for (const rule of rules.atMost ?? []) {
|
|
13441
|
+
let counted = 0;
|
|
13442
|
+
rows.forEach((row, index) => {
|
|
13443
|
+
const held = isPlainObject2(row) ? row[rule.property] : void 0;
|
|
13444
|
+
if (typeof held !== "string" || !rule.anyOf.includes(held))
|
|
13445
|
+
return;
|
|
13446
|
+
counted += 1;
|
|
13447
|
+
if (counted <= rule.max)
|
|
13448
|
+
return;
|
|
13449
|
+
issues.push({
|
|
13450
|
+
field: fieldOf(index, rule.property),
|
|
13451
|
+
message: rule.max === 0 ? `No entry may be set to this.` : `At most ${rule.max} ${rule.max === 1 ? "entry" : "entries"} may be set to this, and ${counted - 1} already ${counted - 1 === 1 ? "is" : "are"}.`
|
|
13452
|
+
});
|
|
13453
|
+
});
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
}
|
|
13457
|
+
return issues;
|
|
13458
|
+
}
|
|
13459
|
+
function labelOfPath(schema, path) {
|
|
13460
|
+
const leaf = leafAt(schema, path);
|
|
13461
|
+
const titleOf = (node) => node?.property.title;
|
|
13462
|
+
const field = titleOf(leaf) ?? printableConfigurationPath(path);
|
|
13463
|
+
const container = leaf?.containers[0];
|
|
13464
|
+
const outer = container === void 0 ? void 0 : titleOf(container);
|
|
13465
|
+
return outer === void 0 ? `"${field}"` : `"${field}" in "${outer}"`;
|
|
13466
|
+
}
|
|
13467
|
+
function labelOfChild(container, name) {
|
|
13468
|
+
const items = container.property.items;
|
|
13469
|
+
const title = items?.properties?.[name]?.title;
|
|
13470
|
+
return title === void 0 ? `"${name}"` : `"${title}"`;
|
|
13471
|
+
}
|
|
13536
13472
|
function conditionOf(property) {
|
|
13537
13473
|
return property?.["x-required-when"];
|
|
13538
13474
|
}
|
|
@@ -13541,19 +13477,48 @@ function labelOf(schema, name) {
|
|
|
13541
13477
|
return property?.title === void 0 ? `"${name}"` : `"${property.title}"`;
|
|
13542
13478
|
}
|
|
13543
13479
|
function withoutCrossFieldRules(schema) {
|
|
13544
|
-
const
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
}));
|
|
13550
|
-
return withoutCrossFieldRules({ ...schema, properties });
|
|
13551
|
-
}
|
|
13552
|
-
if (schema.dependentRequired === void 0)
|
|
13553
|
-
return schema;
|
|
13554
|
-
const { dependentRequired: _removed, ...rest } = schema;
|
|
13480
|
+
const properties = withoutPropertyRules(schema.properties);
|
|
13481
|
+
const stripped = properties === null ? schema : { ...schema, properties };
|
|
13482
|
+
if (stripped.dependentRequired === void 0)
|
|
13483
|
+
return stripped;
|
|
13484
|
+
const { dependentRequired: _removed, ...rest } = stripped;
|
|
13555
13485
|
return rest;
|
|
13556
13486
|
}
|
|
13487
|
+
var PROPERTY_RULE_KEYWORDS = ["x-required-when", "x-value-in", "x-row-rules"];
|
|
13488
|
+
function withoutPropertyRules(properties) {
|
|
13489
|
+
let out = null;
|
|
13490
|
+
const edit = (name) => {
|
|
13491
|
+
out ??= { ...properties };
|
|
13492
|
+
const copy = { ...out[name] };
|
|
13493
|
+
out[name] = copy;
|
|
13494
|
+
return copy;
|
|
13495
|
+
};
|
|
13496
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
13497
|
+
if (!isPlainObject2(property))
|
|
13498
|
+
continue;
|
|
13499
|
+
for (const keyword of PROPERTY_RULE_KEYWORDS) {
|
|
13500
|
+
if (property[keyword] === void 0)
|
|
13501
|
+
continue;
|
|
13502
|
+
delete edit(name)[keyword];
|
|
13503
|
+
}
|
|
13504
|
+
const children = out?.[name] ?? property;
|
|
13505
|
+
if (isPlainObject2(children["properties"])) {
|
|
13506
|
+
const bare = withoutPropertyRules(children["properties"]);
|
|
13507
|
+
if (bare !== null)
|
|
13508
|
+
edit(name)["properties"] = bare;
|
|
13509
|
+
}
|
|
13510
|
+
const items = children["items"];
|
|
13511
|
+
if (isPlainObject2(items) && isPlainObject2(items["properties"])) {
|
|
13512
|
+
const bare = withoutPropertyRules(items["properties"]);
|
|
13513
|
+
if (bare !== null)
|
|
13514
|
+
edit(name)["items"] = { ...items, properties: bare };
|
|
13515
|
+
}
|
|
13516
|
+
}
|
|
13517
|
+
return out;
|
|
13518
|
+
}
|
|
13519
|
+
function declaredSecretNames(schema) {
|
|
13520
|
+
return Object.entries(schema.properties).filter(([, property]) => isSecretProperty(property)).map(([name]) => name).sort();
|
|
13521
|
+
}
|
|
13557
13522
|
function defaultOf(property) {
|
|
13558
13523
|
switch (property.type) {
|
|
13559
13524
|
case "string":
|
|
@@ -14760,11 +14725,19 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14760
14725
|
if (condition === void 0)
|
|
14761
14726
|
continue;
|
|
14762
14727
|
const at = ["configuration", "properties", name, "x-required-when"];
|
|
14763
|
-
if (
|
|
14728
|
+
if ((definition.configuration.required ?? []).includes(name)) {
|
|
14729
|
+
ctx.addIssue({
|
|
14730
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14731
|
+
path: [...at],
|
|
14732
|
+
message: `"${name}" is already required, so "x-required-when" can only narrow a rule that is already unconditional. Drop one: \`required\` is always, \`x-required-when\` is the condition.`
|
|
14733
|
+
});
|
|
14734
|
+
continue;
|
|
14735
|
+
}
|
|
14736
|
+
if (declaredDefault(property) !== void 0) {
|
|
14764
14737
|
ctx.addIssue({
|
|
14765
14738
|
code: external_exports.ZodIssueCode.custom,
|
|
14766
14739
|
path: [...at],
|
|
14767
|
-
message: `"${name}"
|
|
14740
|
+
message: `"${name}" declares a default, so it is never absent and this condition could never make it required. Remove the default, or the condition.`
|
|
14768
14741
|
});
|
|
14769
14742
|
continue;
|
|
14770
14743
|
}
|
|
@@ -14840,6 +14813,8 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14840
14813
|
});
|
|
14841
14814
|
}
|
|
14842
14815
|
}
|
|
14816
|
+
checkValueReferences(definition.configuration, ctx);
|
|
14817
|
+
checkRowRules(definition.configuration, ctx);
|
|
14843
14818
|
const provides = definition.serviceInterfaces?.provides ?? [];
|
|
14844
14819
|
const provideKeys = provides.map((provide) => provide.key);
|
|
14845
14820
|
const duplicateProvideKeys = provideKeys.filter((key, i) => provideKeys.indexOf(key) !== i);
|
|
@@ -14951,6 +14926,203 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14951
14926
|
});
|
|
14952
14927
|
}
|
|
14953
14928
|
}
|
|
14929
|
+
function declarationIssuePath(node) {
|
|
14930
|
+
const out = ["configuration"];
|
|
14931
|
+
for (const container of node.containers) {
|
|
14932
|
+
out.push("properties", container.name);
|
|
14933
|
+
if (container.kind === "array")
|
|
14934
|
+
out.push("items");
|
|
14935
|
+
}
|
|
14936
|
+
out.push("properties", node.name);
|
|
14937
|
+
return out;
|
|
14938
|
+
}
|
|
14939
|
+
function directChildren(leaves, container) {
|
|
14940
|
+
return leaves.filter((leaf) => immediateContainer(leaf)?.path === container.path);
|
|
14941
|
+
}
|
|
14942
|
+
function valuesOutsideSpace(configuration, leaf, values) {
|
|
14943
|
+
const single = withoutCrossFieldRules({
|
|
14944
|
+
...configuration,
|
|
14945
|
+
properties: { [leaf.name]: leaf.property },
|
|
14946
|
+
required: [leaf.name]
|
|
14947
|
+
});
|
|
14948
|
+
return values.filter((value) => !validateSubmittedConfiguration(single, { [leaf.name]: value }).ok);
|
|
14949
|
+
}
|
|
14950
|
+
function checkValueReferences(configuration, ctx) {
|
|
14951
|
+
const leaves = configurationLeaves(configuration);
|
|
14952
|
+
for (const leaf of leaves) {
|
|
14953
|
+
const reference = leaf.property["x-value-in"];
|
|
14954
|
+
if (reference === void 0)
|
|
14955
|
+
continue;
|
|
14956
|
+
const at = [...declarationIssuePath(leaf), "x-value-in"];
|
|
14957
|
+
const here = printableConfigurationPath(leaf.path);
|
|
14958
|
+
if (isSecretProperty(leaf.property)) {
|
|
14959
|
+
ctx.addIssue({
|
|
14960
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14961
|
+
path: at,
|
|
14962
|
+
message: `"${here}" is a secret. The platform never reads a secret's value, so it could never check one against a set of values.`
|
|
14963
|
+
});
|
|
14964
|
+
continue;
|
|
14965
|
+
}
|
|
14966
|
+
if (leaf.property.enum !== void 0) {
|
|
14967
|
+
ctx.addIssue({
|
|
14968
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14969
|
+
path: at,
|
|
14970
|
+
message: `"${here}" already fixes its values with "enum". A setting takes its values from the contract or from another setting, not from both.`
|
|
14971
|
+
});
|
|
14972
|
+
continue;
|
|
14973
|
+
}
|
|
14974
|
+
if (declaredDefault(leaf.property) !== void 0) {
|
|
14975
|
+
ctx.addIssue({
|
|
14976
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14977
|
+
path: at,
|
|
14978
|
+
message: `"${here}" declares a default, which would be written into every row and then refused until the setting it points at happens to hold the same value. Remove the default.`
|
|
14979
|
+
});
|
|
14980
|
+
continue;
|
|
14981
|
+
}
|
|
14982
|
+
const parsed = parseConfigurationPath(reference.path);
|
|
14983
|
+
const target = parsed === null ? null : leafAt(configuration, parsed);
|
|
14984
|
+
if (target === null) {
|
|
14985
|
+
const isGroup = parsed !== null && declaresPath(configuration, parsed);
|
|
14986
|
+
ctx.addIssue({
|
|
14987
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14988
|
+
path: [...at, "path"],
|
|
14989
|
+
message: isGroup ? `"${reference.path}" is a group of settings, which holds no value to choose from. Name one setting inside it.` : `"${reference.path}" is not a declared customer configuration property.`
|
|
14990
|
+
});
|
|
14991
|
+
continue;
|
|
14992
|
+
}
|
|
14993
|
+
if (target.path === leaf.path) {
|
|
14994
|
+
ctx.addIssue({
|
|
14995
|
+
code: external_exports.ZodIssueCode.custom,
|
|
14996
|
+
path: [...at, "path"],
|
|
14997
|
+
message: `"${here}" cannot take its values from itself.`
|
|
14998
|
+
});
|
|
14999
|
+
continue;
|
|
15000
|
+
}
|
|
15001
|
+
if (target.property.type !== "string") {
|
|
15002
|
+
ctx.addIssue({
|
|
15003
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15004
|
+
path: [...at, "path"],
|
|
15005
|
+
message: `"${reference.path}" is a ${target.property.type} setting; a reference compares string values.`
|
|
15006
|
+
});
|
|
15007
|
+
continue;
|
|
15008
|
+
}
|
|
15009
|
+
if (isSecretProperty(target.property)) {
|
|
15010
|
+
ctx.addIssue({
|
|
15011
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15012
|
+
path: [...at, "path"],
|
|
15013
|
+
message: `"${reference.path}" is a secret. The platform never reads a secret's value, so it could never be the set a reference is checked against.`
|
|
15014
|
+
});
|
|
15015
|
+
continue;
|
|
15016
|
+
}
|
|
15017
|
+
}
|
|
15018
|
+
}
|
|
15019
|
+
function checkRowRules(configuration, ctx) {
|
|
15020
|
+
const leaves = configurationLeaves(configuration);
|
|
15021
|
+
for (const leaf of leaves) {
|
|
15022
|
+
if (leaf.property["x-row-rules"] === void 0)
|
|
15023
|
+
continue;
|
|
15024
|
+
ctx.addIssue({
|
|
15025
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15026
|
+
path: [...declarationIssuePath(leaf), "x-row-rules"],
|
|
15027
|
+
message: `"${printableConfigurationPath(leaf.path)}" is a list of text entries, whose rows have no fields to name. Use "uniqueItems" to compare whole entries, or declare "items" as a group.`
|
|
15028
|
+
});
|
|
15029
|
+
}
|
|
15030
|
+
for (const container of configurationContainers(configuration)) {
|
|
15031
|
+
const rules = container.property["x-row-rules"];
|
|
15032
|
+
if (rules === void 0)
|
|
15033
|
+
continue;
|
|
15034
|
+
const at = [...declarationIssuePath(container), "x-row-rules"];
|
|
15035
|
+
const here = printableConfigurationPath(container.path);
|
|
15036
|
+
if (rules.unique === void 0 && rules.atMost === void 0) {
|
|
15037
|
+
ctx.addIssue({
|
|
15038
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15039
|
+
path: at,
|
|
15040
|
+
message: `"${here}" declares "x-row-rules" with no rule in it. Declare "unique", "atMost", or both.`
|
|
15041
|
+
});
|
|
15042
|
+
continue;
|
|
15043
|
+
}
|
|
15044
|
+
const children = directChildren(leaves, container);
|
|
15045
|
+
const childAt = (name) => children.find((child) => child.name === name);
|
|
15046
|
+
const maxItems = container.property.maxItems;
|
|
15047
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15048
|
+
(rules.unique ?? []).forEach((name, index) => {
|
|
15049
|
+
if (seen.has(name)) {
|
|
15050
|
+
ctx.addIssue({
|
|
15051
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15052
|
+
path: [...at, "unique", index],
|
|
15053
|
+
message: `"${name}" is named twice.`
|
|
15054
|
+
});
|
|
15055
|
+
return;
|
|
15056
|
+
}
|
|
15057
|
+
seen.add(name);
|
|
15058
|
+
const child = childAt(name);
|
|
15059
|
+
if (child === void 0) {
|
|
15060
|
+
ctx.addIssue({
|
|
15061
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15062
|
+
path: [...at, "unique", index],
|
|
15063
|
+
message: `"${name}" is not a setting declared on a row of "${here}".`
|
|
15064
|
+
});
|
|
15065
|
+
return;
|
|
15066
|
+
}
|
|
15067
|
+
if (child.property.type === "array") {
|
|
15068
|
+
ctx.addIssue({
|
|
15069
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15070
|
+
path: [...at, "unique", index],
|
|
15071
|
+
message: `"${name}" is a list, which holds no single value to compare across rows.`
|
|
15072
|
+
});
|
|
15073
|
+
}
|
|
15074
|
+
if (child.property.type === "boolean") {
|
|
15075
|
+
ctx.addIssue({
|
|
15076
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15077
|
+
path: [...at, "unique", index],
|
|
15078
|
+
message: `"${name}" is a true/false setting, so requiring it to be unique would cap "${here}" at two rows rather than make anything distinct. Use "atMost" to limit how many rows may answer one way.`
|
|
15079
|
+
});
|
|
15080
|
+
}
|
|
15081
|
+
});
|
|
15082
|
+
(rules.atMost ?? []).forEach((rule, index) => {
|
|
15083
|
+
const ruleAt = [...at, "atMost", index];
|
|
15084
|
+
const child = childAt(rule.property);
|
|
15085
|
+
if (child === void 0) {
|
|
15086
|
+
ctx.addIssue({
|
|
15087
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15088
|
+
path: [...ruleAt, "property"],
|
|
15089
|
+
message: `"${rule.property}" is not a setting declared on a row of "${here}".`
|
|
15090
|
+
});
|
|
15091
|
+
return;
|
|
15092
|
+
}
|
|
15093
|
+
if (child.property.type !== "string") {
|
|
15094
|
+
ctx.addIssue({
|
|
15095
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15096
|
+
path: [...ruleAt, "property"],
|
|
15097
|
+
message: `"${rule.property}" is a ${child.property.type} setting; a row rule compares string values.`
|
|
15098
|
+
});
|
|
15099
|
+
return;
|
|
15100
|
+
}
|
|
15101
|
+
if (new Set(rule.anyOf).size !== rule.anyOf.length) {
|
|
15102
|
+
ctx.addIssue({
|
|
15103
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15104
|
+
path: [...ruleAt, "anyOf"],
|
|
15105
|
+
message: "Repeated values in `anyOf`."
|
|
15106
|
+
});
|
|
15107
|
+
}
|
|
15108
|
+
if (maxItems !== void 0 && rule.max >= maxItems) {
|
|
15109
|
+
ctx.addIssue({
|
|
15110
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15111
|
+
path: [...ruleAt, "max"],
|
|
15112
|
+
message: `"${here}" holds at most ${maxItems} rows, so a limit of ${rule.max} could never be exceeded. Lower it, or remove the rule.`
|
|
15113
|
+
});
|
|
15114
|
+
}
|
|
15115
|
+
const outside = valuesOutsideSpace(configuration, child, rule.anyOf);
|
|
15116
|
+
if (outside.length > 0) {
|
|
15117
|
+
ctx.addIssue({
|
|
15118
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15119
|
+
path: [...ruleAt, "anyOf"],
|
|
15120
|
+
message: `"${rule.property}" never accepts ${outside.map((value) => `"${value}"`).join(", ")}, so this rule could never count a row.`
|
|
15121
|
+
});
|
|
15122
|
+
}
|
|
15123
|
+
});
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
14954
15126
|
|
|
14955
15127
|
// ../product-contracts/dist/announcements.js
|
|
14956
15128
|
var ANNOUNCED_PRODUCTS = Object.freeze([
|
|
@@ -16846,7 +17018,23 @@ function isTerminalAction(action) {
|
|
|
16846
17018
|
var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
|
|
16847
17019
|
|
|
16848
17020
|
// ../product-contracts/dist/contract-issue.js
|
|
17021
|
+
var PATH_SEGMENT = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
17022
|
+
var UNPRINTABLE_SEGMENT = "<unprintable>";
|
|
16849
17023
|
var MAX_SEGMENTS = MAX_CONFIGURATION_PATH_SEGMENTS + 2;
|
|
17024
|
+
function printableContractPath(path) {
|
|
17025
|
+
const printable = path.slice(0, MAX_SEGMENTS).map((segment) => typeof segment === "number" || PATH_SEGMENT.test(segment) ? String(segment) : UNPRINTABLE_SEGMENT);
|
|
17026
|
+
return printable.join(".") || "(document)";
|
|
17027
|
+
}
|
|
17028
|
+
function manifestConfigurationField(field) {
|
|
17029
|
+
if (field === "")
|
|
17030
|
+
return "spec.configuration";
|
|
17031
|
+
const parsed = parseConfigurationPath(field);
|
|
17032
|
+
return printableContractPath([
|
|
17033
|
+
"spec",
|
|
17034
|
+
"configuration",
|
|
17035
|
+
...parsed === null ? [field] : pathSegments(parsed)
|
|
17036
|
+
]);
|
|
17037
|
+
}
|
|
16850
17038
|
|
|
16851
17039
|
// ../product-contracts/dist/resource-output-reference.js
|
|
16852
17040
|
var OUTPUT_KEY_RULE = "Output keys are camelCase.";
|
|
@@ -16860,6 +17048,9 @@ var ResourceOutputReferenceSchema = external_exports.object({
|
|
|
16860
17048
|
}).strict()
|
|
16861
17049
|
}).strict()
|
|
16862
17050
|
}).strict();
|
|
17051
|
+
function looksLikeReference(value) {
|
|
17052
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "valueFrom" in value;
|
|
17053
|
+
}
|
|
16863
17054
|
|
|
16864
17055
|
// ../product-contracts/dist/dependency-binding.js
|
|
16865
17056
|
var DependencyBindingSelectorSchema = external_exports.object({
|
|
@@ -16995,6 +17186,23 @@ var ProductInstanceTemplateSchema = external_exports.object({
|
|
|
16995
17186
|
dependencyBindings: DependencyBindingsSchema.optional()
|
|
16996
17187
|
}).strict()
|
|
16997
17188
|
}).strict();
|
|
17189
|
+
function secretValuesInDocument(schema, configuration) {
|
|
17190
|
+
if (!isPlainRecord(configuration) || !isPlainRecord(schema.properties))
|
|
17191
|
+
return [];
|
|
17192
|
+
const declarations = Object.fromEntries(Object.entries(schema.properties).filter(([, property]) => isPlainRecord(property)));
|
|
17193
|
+
return declaredSecretNames({
|
|
17194
|
+
properties: declarations
|
|
17195
|
+
}).filter((name) => Object.hasOwn(configuration, name) && !looksLikeReference(configuration[name]));
|
|
17196
|
+
}
|
|
17197
|
+
function secretValueIssue(name) {
|
|
17198
|
+
return {
|
|
17199
|
+
field: manifestConfigurationField(name),
|
|
17200
|
+
message: `"${name}" is secret input and must never appear in a template. Supply it separately when applying, so it goes to the secret manager rather than into a document.`
|
|
17201
|
+
};
|
|
17202
|
+
}
|
|
17203
|
+
function isPlainRecord(value) {
|
|
17204
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17205
|
+
}
|
|
16998
17206
|
|
|
16999
17207
|
// ../product-contracts/dist/product-submission.schema.js
|
|
17000
17208
|
var PRODUCT_SUBMISSION_KIND = "ProductSubmission";
|
|
@@ -17094,50 +17302,383 @@ var PUBLISH_RULE_ANNEX = {
|
|
|
17094
17302
|
};
|
|
17095
17303
|
withJsonSchemaAnnex(ProductSubmissionSchema, PUBLISH_RULE_ANNEX);
|
|
17096
17304
|
|
|
17097
|
-
// src/
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
lines.push("Nothing else changed \u2014 sending a secret is itself the change.");
|
|
17106
|
-
lines.push(
|
|
17107
|
-
"Omit --secret on a run that is not setting or changing one; that is what answers NO_CHANGE."
|
|
17108
|
-
);
|
|
17109
|
-
}
|
|
17110
|
-
return lines;
|
|
17111
|
-
}
|
|
17112
|
-
function printPlan(plan, json) {
|
|
17113
|
-
if (json) {
|
|
17114
|
-
process.stdout.write(`${JSON.stringify(plan)}
|
|
17115
|
-
`);
|
|
17116
|
-
return;
|
|
17305
|
+
// src/manifest.ts
|
|
17306
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
17307
|
+
function readManifest(path) {
|
|
17308
|
+
let text;
|
|
17309
|
+
try {
|
|
17310
|
+
text = readFileSync2(path, "utf8");
|
|
17311
|
+
} catch {
|
|
17312
|
+
return { ok: false, message: `Could not read ${path}.` };
|
|
17117
17313
|
}
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17314
|
+
let parsed;
|
|
17315
|
+
try {
|
|
17316
|
+
parsed = (0, import_yaml.parse)(text, { prettyErrors: false });
|
|
17317
|
+
} catch (error) {
|
|
17318
|
+
return {
|
|
17319
|
+
ok: false,
|
|
17320
|
+
message: `${path} is not valid YAML or JSON${whereItFailed(text, error)}.`
|
|
17321
|
+
};
|
|
17122
17322
|
}
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
lines.push(
|
|
17126
|
-
` from ${reference.resourceKey}.${reference.output}${reference.secret ? " [secret]" : ""}`
|
|
17127
|
-
);
|
|
17323
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
17324
|
+
return { ok: false, message: `${path} does not contain a manifest document.` };
|
|
17128
17325
|
}
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17326
|
+
return { ok: true, document: parsed };
|
|
17327
|
+
}
|
|
17328
|
+
function resolveSecrets(pairs, environment = process.env) {
|
|
17329
|
+
const secrets = {};
|
|
17330
|
+
for (const [index, pair] of pairs.entries()) {
|
|
17331
|
+
const label = pairs.length === 1 ? "--secret" : `--secret #${index + 1}`;
|
|
17332
|
+
const separator = pair.indexOf("=");
|
|
17333
|
+
if (separator <= 0) {
|
|
17334
|
+
return {
|
|
17335
|
+
ok: false,
|
|
17336
|
+
message: `${label} expects name=env:VARIABLE, and there is no name before "=".`
|
|
17337
|
+
};
|
|
17338
|
+
}
|
|
17339
|
+
const name = pair.slice(0, separator);
|
|
17340
|
+
const source = pair.slice(separator + 1);
|
|
17341
|
+
if (!source.startsWith("env:")) {
|
|
17342
|
+
return {
|
|
17343
|
+
ok: false,
|
|
17344
|
+
message: `${label} must read from the environment: --secret <name>=env:VARIABLE. A value on the command line is in your shell history and in the process table.`
|
|
17345
|
+
};
|
|
17346
|
+
}
|
|
17347
|
+
const variable = source.slice("env:".length);
|
|
17348
|
+
if (variable.length === 0) {
|
|
17349
|
+
return { ok: false, message: `${label} names no variable after "env:".` };
|
|
17350
|
+
}
|
|
17351
|
+
const value = environment[variable];
|
|
17352
|
+
if (value === void 0 || value === "") {
|
|
17353
|
+
return {
|
|
17354
|
+
ok: false,
|
|
17355
|
+
message: `${label} reads an environment variable that is not set, or is empty.`
|
|
17356
|
+
};
|
|
17357
|
+
}
|
|
17358
|
+
secrets[name] = value;
|
|
17359
|
+
}
|
|
17360
|
+
return { ok: true, secrets };
|
|
17361
|
+
}
|
|
17362
|
+
function manifestKeyOf(document) {
|
|
17363
|
+
const metadata = document["metadata"];
|
|
17364
|
+
if (typeof metadata !== "object" || metadata === null) return null;
|
|
17365
|
+
const key = metadata["key"];
|
|
17366
|
+
return typeof key === "string" ? key : null;
|
|
17367
|
+
}
|
|
17368
|
+
function whereItFailed(text, error) {
|
|
17369
|
+
if (typeof error !== "object" || error === null) return "";
|
|
17370
|
+
const code = error.code;
|
|
17371
|
+
const pos = error.pos;
|
|
17372
|
+
const kind = typeof code === "string" && /^[A-Z_]+$/.test(code) ? code : null;
|
|
17373
|
+
const offset = Array.isArray(pos) && typeof pos[0] === "number" ? pos[0] : null;
|
|
17374
|
+
if (offset === null) return kind ? ` (${kind})` : "";
|
|
17375
|
+
const before = text.slice(0, offset);
|
|
17376
|
+
const line = before.split("\n").length;
|
|
17377
|
+
const column = offset - before.lastIndexOf("\n");
|
|
17378
|
+
return ` (${kind ?? "error"} at line ${line}, column ${column})`;
|
|
17379
|
+
}
|
|
17380
|
+
function pinnedRelease(document) {
|
|
17381
|
+
const spec = document["spec"];
|
|
17382
|
+
if (typeof spec !== "object" || spec === null || Array.isArray(spec)) return null;
|
|
17383
|
+
const { productId, productVersion } = spec;
|
|
17384
|
+
if (typeof productId !== "string" || productId === "") return null;
|
|
17385
|
+
if (typeof productVersion !== "string" || productVersion === "") return null;
|
|
17386
|
+
return { productId, productVersion };
|
|
17387
|
+
}
|
|
17388
|
+
function carriesLiteralSettings(document) {
|
|
17389
|
+
const configuration = configurationOf(document);
|
|
17390
|
+
return configuration !== null && Object.values(configuration).some((value) => !looksLikeReference(value));
|
|
17391
|
+
}
|
|
17392
|
+
function pastedSecretSettings(document, view) {
|
|
17393
|
+
const pinned = pinnedRelease(document);
|
|
17394
|
+
if (!pinned || typeof view !== "object" || view === null) return [];
|
|
17395
|
+
const { productId, version, configuration } = view;
|
|
17396
|
+
if (productId !== pinned.productId || version !== pinned.productVersion) return [];
|
|
17397
|
+
if (typeof configuration !== "object" || configuration === null) return [];
|
|
17398
|
+
try {
|
|
17399
|
+
return secretValuesInDocument(
|
|
17400
|
+
configuration,
|
|
17401
|
+
configurationOf(document)
|
|
17402
|
+
);
|
|
17403
|
+
} catch {
|
|
17404
|
+
return [];
|
|
17405
|
+
}
|
|
17406
|
+
}
|
|
17407
|
+
function configurationOf(document) {
|
|
17408
|
+
const spec = document["spec"];
|
|
17409
|
+
if (typeof spec !== "object" || spec === null || Array.isArray(spec)) return null;
|
|
17410
|
+
const configuration = spec["configuration"];
|
|
17411
|
+
if (typeof configuration !== "object" || configuration === null || Array.isArray(configuration)) {
|
|
17412
|
+
return null;
|
|
17413
|
+
}
|
|
17414
|
+
return configuration;
|
|
17415
|
+
}
|
|
17416
|
+
|
|
17417
|
+
// src/endpoint.ts
|
|
17418
|
+
function isLoopback(hostname) {
|
|
17419
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
17420
|
+
}
|
|
17421
|
+
function validateEndpoint(raw, source) {
|
|
17422
|
+
let url;
|
|
17423
|
+
try {
|
|
17424
|
+
url = new URL(raw);
|
|
17425
|
+
} catch {
|
|
17426
|
+
return { ok: false, message: `${source} is not a URL: "${raw}".` };
|
|
17427
|
+
}
|
|
17428
|
+
if (url.protocol === "https:") return { ok: true, endpoint: url.origin };
|
|
17429
|
+
if (url.protocol === "http:" && isLoopback(url.hostname)) {
|
|
17430
|
+
return { ok: true, endpoint: url.origin };
|
|
17431
|
+
}
|
|
17432
|
+
return {
|
|
17433
|
+
ok: false,
|
|
17434
|
+
message: `${source} must be https \u2014 a credential is sent to it, and "${url.protocol}//" would send it in the clear. Plain http is accepted for localhost only.`
|
|
17435
|
+
};
|
|
17436
|
+
}
|
|
17437
|
+
|
|
17438
|
+
// src/session.ts
|
|
17439
|
+
async function ensureSession(args) {
|
|
17440
|
+
const stored = readStored();
|
|
17441
|
+
const resolved = resolveEndpoint(args.endpointFlag, stored);
|
|
17442
|
+
if (!resolved) {
|
|
17443
|
+
return {
|
|
17444
|
+
ok: false,
|
|
17445
|
+
message: "No endpoint. Run `vfac context set --endpoint https://your-organization.cloud.vardeflyt.no`, or set VFAC_ENDPOINT."
|
|
17446
|
+
};
|
|
17447
|
+
}
|
|
17448
|
+
const checked = validateEndpoint(resolved.value, resolved.source);
|
|
17449
|
+
if (!checked.ok) return { ok: false, message: checked.message };
|
|
17450
|
+
const endpoint = checked.endpoint;
|
|
17451
|
+
const credential = credentialFromEnvironment();
|
|
17452
|
+
if (args.forceExchange !== true && sessionUsable(stored.session, endpoint, args.now ?? /* @__PURE__ */ new Date(), credential?.keyId)) {
|
|
17453
|
+
return { ok: true, ready: { endpoint, token: stored.session.token, stored, via: "stored" } };
|
|
17454
|
+
}
|
|
17455
|
+
const signedIn = await signIn({
|
|
17456
|
+
endpoint,
|
|
17457
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
17458
|
+
});
|
|
17459
|
+
if (!signedIn.ok) return { ok: false, message: signedIn.message };
|
|
17460
|
+
const next = { ...stored, session: signedIn.session };
|
|
17461
|
+
writeStored(next);
|
|
17462
|
+
return {
|
|
17463
|
+
ok: true,
|
|
17464
|
+
ready: {
|
|
17465
|
+
endpoint,
|
|
17466
|
+
token: signedIn.session.token,
|
|
17467
|
+
stored: next,
|
|
17468
|
+
via: "exchanged",
|
|
17469
|
+
...signedIn.gate ? { gate: signedIn.gate } : {}
|
|
17470
|
+
}
|
|
17471
|
+
};
|
|
17472
|
+
}
|
|
17473
|
+
function resolveProject(flag, stored) {
|
|
17474
|
+
return flag ?? stored.context?.projectId ?? null;
|
|
17475
|
+
}
|
|
17476
|
+
|
|
17477
|
+
// src/operations.ts
|
|
17478
|
+
var OPERATION_STATUSES = [
|
|
17479
|
+
"PENDING",
|
|
17480
|
+
"RUNNING",
|
|
17481
|
+
"SUCCEEDED",
|
|
17482
|
+
"FAILED",
|
|
17483
|
+
"CANCELLED"
|
|
17484
|
+
];
|
|
17485
|
+
var TERMINAL_STATUSES = ["SUCCEEDED", "FAILED", "CANCELLED"];
|
|
17486
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
17487
|
+
var MAX_WAIT_MS = 60 * 60 * 1e3;
|
|
17488
|
+
function withProject(path, projectId) {
|
|
17489
|
+
return projectId === null ? path : `${path}?project=${encodeURIComponent(projectId)}`;
|
|
17490
|
+
}
|
|
17491
|
+
function operationPath(operationId, projectId) {
|
|
17492
|
+
return withProject(`/api/v1/operations/${encodeURIComponent(operationId)}`, projectId);
|
|
17493
|
+
}
|
|
17494
|
+
function classify(data) {
|
|
17495
|
+
const status = data["status"];
|
|
17496
|
+
if (typeof status !== "string") return "unknown";
|
|
17497
|
+
if (TERMINAL_STATUSES.includes(status)) return "terminal";
|
|
17498
|
+
if (OPERATION_STATUSES.includes(status)) return "working";
|
|
17499
|
+
return "unknown";
|
|
17500
|
+
}
|
|
17501
|
+
async function readOperation(args) {
|
|
17502
|
+
const result = await call({
|
|
17503
|
+
endpoint: args.endpoint,
|
|
17504
|
+
path: operationPath(args.operationId, args.projectId),
|
|
17505
|
+
token: args.token,
|
|
17506
|
+
...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}
|
|
17507
|
+
});
|
|
17508
|
+
return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error };
|
|
17509
|
+
}
|
|
17510
|
+
async function waitForOperation(args) {
|
|
17511
|
+
const deps = args.deps ?? {};
|
|
17512
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
17513
|
+
const now = deps.now ?? (() => Date.now());
|
|
17514
|
+
const deadline = now() + MAX_WAIT_MS;
|
|
17515
|
+
let token = args.token;
|
|
17516
|
+
let refreshed = false;
|
|
17517
|
+
let last = null;
|
|
17518
|
+
for (; ; ) {
|
|
17519
|
+
const read = await readOperation({
|
|
17520
|
+
endpoint: args.endpoint,
|
|
17521
|
+
token,
|
|
17522
|
+
operationId: args.operationId,
|
|
17523
|
+
projectId: args.projectId,
|
|
17524
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
17525
|
+
});
|
|
17526
|
+
if (!read.ok) {
|
|
17527
|
+
if (read.error.status === 401 && !refreshed && deps.refreshToken) {
|
|
17528
|
+
refreshed = true;
|
|
17529
|
+
const next = await deps.refreshToken();
|
|
17530
|
+
if (next !== null) {
|
|
17531
|
+
token = next;
|
|
17532
|
+
continue;
|
|
17533
|
+
}
|
|
17534
|
+
}
|
|
17535
|
+
return { kind: "read-failed", error: read.error };
|
|
17536
|
+
}
|
|
17537
|
+
last = read.data;
|
|
17538
|
+
const verdict = classify(read.data);
|
|
17539
|
+
if (verdict === "terminal") {
|
|
17540
|
+
return { kind: "settled", status: String(read.data["status"]), data: read.data };
|
|
17541
|
+
}
|
|
17542
|
+
if (verdict === "unknown") {
|
|
17543
|
+
return { kind: "unknown-status", status: String(read.data["status"]), data: read.data };
|
|
17544
|
+
}
|
|
17545
|
+
args.onProgress?.(read.data);
|
|
17546
|
+
if (now() + POLL_INTERVAL_MS >= deadline) return { kind: "timed-out", data: last };
|
|
17547
|
+
await sleep(POLL_INTERVAL_MS);
|
|
17548
|
+
}
|
|
17549
|
+
}
|
|
17550
|
+
function renderOperation(operationId, data) {
|
|
17551
|
+
const status = String(data["status"] ?? "UNKNOWN");
|
|
17552
|
+
const phase = data["phase"];
|
|
17553
|
+
const lines = [
|
|
17554
|
+
typeof phase === "string" && phase !== "" ? `${operationId} ${status} (${phase})` : `${operationId} ${status}`
|
|
17555
|
+
];
|
|
17556
|
+
const type = data["type"];
|
|
17557
|
+
if (typeof type === "string") lines[0] += ` ${type}`;
|
|
17558
|
+
const steps = Array.isArray(data["steps"]) ? data["steps"] : [];
|
|
17559
|
+
if (steps.length > 0) {
|
|
17560
|
+
lines.push("");
|
|
17561
|
+
for (const step of steps) {
|
|
17562
|
+
const state = String(step["state"] ?? "");
|
|
17563
|
+
lines.push(` ${markerFor(state)} ${String(step["label"] ?? step["key"] ?? "")}`);
|
|
17564
|
+
}
|
|
17565
|
+
}
|
|
17566
|
+
const failure = asRecord(data["failure"]);
|
|
17567
|
+
if (failure) {
|
|
17568
|
+
lines.push("");
|
|
17569
|
+
const stage = failure["stage"];
|
|
17570
|
+
lines.push(` Failed${typeof stage === "string" ? ` at ${stage}` : ""}`);
|
|
17571
|
+
const summary = failure["summary"];
|
|
17572
|
+
if (typeof summary === "string") lines.push(` ${summary}`);
|
|
17573
|
+
const cause = failure["cause"];
|
|
17574
|
+
if (typeof cause === "string") lines.push(` cause: ${cause}`);
|
|
17575
|
+
const services = failure["services"];
|
|
17576
|
+
if (Array.isArray(services)) {
|
|
17577
|
+
for (const entry of services) {
|
|
17578
|
+
const one = asRecord(entry);
|
|
17579
|
+
if (one === null) continue;
|
|
17580
|
+
const dependency = one["dependency"];
|
|
17581
|
+
if (typeof dependency !== "string" || dependency === "") continue;
|
|
17582
|
+
const code = one["code"];
|
|
17583
|
+
lines.push(
|
|
17584
|
+
typeof code === "string" && code !== "" ? ` ${dependency} \u2014 the service reported: ${code}` : ` ${dependency} \u2014 the service reported nothing usable`
|
|
17585
|
+
);
|
|
17586
|
+
}
|
|
17587
|
+
}
|
|
17588
|
+
const product = failure["product"];
|
|
17589
|
+
const reportedSettings = [];
|
|
17590
|
+
if (Array.isArray(product)) {
|
|
17591
|
+
for (const entry of product) {
|
|
17592
|
+
const one = asRecord(entry);
|
|
17593
|
+
if (one === null) continue;
|
|
17594
|
+
const component = one["component"];
|
|
17595
|
+
const code = one["code"];
|
|
17596
|
+
if (typeof component !== "string" || typeof code !== "string") continue;
|
|
17597
|
+
if (component === "" || code === "") continue;
|
|
17598
|
+
const setting = one["setting"];
|
|
17599
|
+
if (typeof setting === "string" && setting !== "") {
|
|
17600
|
+
reportedSettings.push(setting);
|
|
17601
|
+
lines.push(` ${component} refused to start: ${code} (setting: ${setting})`);
|
|
17602
|
+
} else {
|
|
17603
|
+
lines.push(` ${component} refused to start: ${code}`);
|
|
17604
|
+
}
|
|
17605
|
+
}
|
|
17606
|
+
}
|
|
17607
|
+
const retryable = failure["retryable"];
|
|
17608
|
+
if (typeof retryable === "boolean") {
|
|
17609
|
+
const again = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "`vfac lifecycle upgrade <pri_\u2026>`" : "`vfac lifecycle reconcile <pri_\u2026>`";
|
|
17610
|
+
const changeIt = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "" : ", or `vfac apply` a corrected manifest";
|
|
17611
|
+
const cause2 = failure["cause"];
|
|
17612
|
+
const named = reportedSettings[0];
|
|
17613
|
+
lines.push(
|
|
17614
|
+
retryable ? ` Another attempt can succeed: ${again}.` : named !== void 0 ? ` Correct \`${named}\` in your manifest, then \`vfac apply\` it${data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? ` and ${again}` : ""}. Repeating it unchanged fails the same way.` : cause2 === "unknown" ? ` The platform could not classify this, so there is nothing here to correct. Read the whole record with \`vfac get operation ${operationId} --output json\`, and quote that id if you ask for help. Repeating it unchanged fails the same way.` : ` Repeating this unchanged fails again \u2014 fix what the lines above name, then ${again}${changeIt}.`
|
|
17615
|
+
);
|
|
17616
|
+
}
|
|
17617
|
+
}
|
|
17618
|
+
return lines.join("\n");
|
|
17619
|
+
}
|
|
17620
|
+
function markerFor(state) {
|
|
17621
|
+
switch (state) {
|
|
17622
|
+
case "completed":
|
|
17623
|
+
return "\u2713";
|
|
17624
|
+
case "failed":
|
|
17625
|
+
return "\u2717";
|
|
17626
|
+
case "active":
|
|
17627
|
+
return "\u203A";
|
|
17628
|
+
case "pending":
|
|
17629
|
+
return "\xB7";
|
|
17630
|
+
default:
|
|
17631
|
+
return "-";
|
|
17632
|
+
}
|
|
17633
|
+
}
|
|
17634
|
+
function asRecord(value) {
|
|
17635
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
17636
|
+
}
|
|
17637
|
+
|
|
17638
|
+
// src/render.ts
|
|
17639
|
+
function isUnconverged(plan) {
|
|
17640
|
+
return plan.health !== void 0 && !plan.health.converged;
|
|
17641
|
+
}
|
|
17642
|
+
function secretWriteConsequence(plan) {
|
|
17643
|
+
if ((plan.secretsToWrite ?? []).length === 0) return [];
|
|
17644
|
+
const lines = ["the value you send REPLACES the stored one, and the resource redeploys."];
|
|
17645
|
+
if (plan.action === "UPDATE" && (plan.changes ?? []).length === 0) {
|
|
17646
|
+
lines.push("Nothing else changed \u2014 sending a secret is itself the change.");
|
|
17647
|
+
lines.push(
|
|
17648
|
+
"Omit --secret on a run that is not setting or changing one; that is what answers NO_CHANGE."
|
|
17649
|
+
);
|
|
17650
|
+
}
|
|
17651
|
+
return lines;
|
|
17652
|
+
}
|
|
17653
|
+
function printPlan(plan, json) {
|
|
17654
|
+
if (json) {
|
|
17655
|
+
process.stdout.write(`${JSON.stringify(plan)}
|
|
17656
|
+
`);
|
|
17657
|
+
return;
|
|
17658
|
+
}
|
|
17659
|
+
const lines = [`${plan.action}${plan.manifestKey ? ` ${plan.manifestKey}` : ""}`];
|
|
17660
|
+
for (const change of plan.changes ?? []) {
|
|
17661
|
+
lines.push(` ~ ${change.field}`);
|
|
17662
|
+
lines.push(` ${change.from} \u2192 ${change.to}`);
|
|
17663
|
+
}
|
|
17664
|
+
for (const reference of plan.references ?? []) {
|
|
17665
|
+
lines.push(` ${reference.property}`);
|
|
17666
|
+
lines.push(
|
|
17667
|
+
` from ${reference.resourceKey}.${reference.output}${reference.secret ? " [secret]" : ""}`
|
|
17668
|
+
);
|
|
17669
|
+
}
|
|
17670
|
+
const bindings = plan.dependencyBindings ?? [];
|
|
17671
|
+
if (bindings.length > 0) {
|
|
17672
|
+
lines.push(" dependency bindings:");
|
|
17673
|
+
for (const binding of bindings) {
|
|
17674
|
+
lines.push(` ${binding.dependencyKey}`);
|
|
17675
|
+
lines.push(
|
|
17676
|
+
` resource: ${binding.resourceKey}${binding.projectId ? ` (in ${binding.projectId})` : ""}`
|
|
17677
|
+
);
|
|
17678
|
+
if (binding.interfaceId !== void 0) {
|
|
17679
|
+
lines.push(
|
|
17680
|
+
` interface: ${binding.interfaceId}${binding.interfaceVersion ? `/${binding.interfaceVersion}` : ""}`
|
|
17681
|
+
);
|
|
17141
17682
|
}
|
|
17142
17683
|
}
|
|
17143
17684
|
}
|
|
@@ -17206,6 +17747,12 @@ async function runApply(verb, parsed, deps = {}) {
|
|
|
17206
17747
|
return 1;
|
|
17207
17748
|
}
|
|
17208
17749
|
const json = parsed.flags["output"] === "json";
|
|
17750
|
+
const pasted = await pastedSecretsIn(manifest2.document, {
|
|
17751
|
+
endpoint: session.ready.endpoint,
|
|
17752
|
+
token: session.ready.token,
|
|
17753
|
+
projectId
|
|
17754
|
+
});
|
|
17755
|
+
if (pasted.length > 0) return refusePastedSecrets(file, pasted, json);
|
|
17209
17756
|
const path = `/api/v1/projects/${encodeURIComponent(projectId)}/manifest`;
|
|
17210
17757
|
const envelope = {
|
|
17211
17758
|
template: manifest2.document,
|
|
@@ -17399,6 +17946,39 @@ Read the operation that failed: \`vfac get operation <id>\`, or \`vfac get produ
|
|
|
17399
17946
|
}
|
|
17400
17947
|
}
|
|
17401
17948
|
}
|
|
17949
|
+
async function pastedSecretsIn(document, target) {
|
|
17950
|
+
const pinned = pinnedRelease(document);
|
|
17951
|
+
if (!pinned || !carriesLiteralSettings(document)) return [];
|
|
17952
|
+
const view = await call({
|
|
17953
|
+
endpoint: target.endpoint,
|
|
17954
|
+
path: `/api/v1/projects/${encodeURIComponent(target.projectId)}/products/${encodeURIComponent(pinned.productId)}`,
|
|
17955
|
+
token: target.token
|
|
17956
|
+
});
|
|
17957
|
+
return view.ok ? pastedSecretSettings(document, view.data) : [];
|
|
17958
|
+
}
|
|
17959
|
+
function refusePastedSecrets(file, names, json) {
|
|
17960
|
+
const fields = names.map(secretValueIssue);
|
|
17961
|
+
if (json) {
|
|
17962
|
+
return printError(
|
|
17963
|
+
{
|
|
17964
|
+
status: 400,
|
|
17965
|
+
code: "REQUEST_INVALID",
|
|
17966
|
+
message: `${file} contains a secret value, so it was not sent. Nothing was changed.`,
|
|
17967
|
+
fields
|
|
17968
|
+
},
|
|
17969
|
+
true
|
|
17970
|
+
);
|
|
17971
|
+
}
|
|
17972
|
+
for (const field of fields) {
|
|
17973
|
+
process.stderr.write(`${field.field} contains a secret value.
|
|
17974
|
+
`);
|
|
17975
|
+
}
|
|
17976
|
+
process.stderr.write(
|
|
17977
|
+
"\nSecrets must not be stored in a manifest, so this file was not sent and nothing was changed.\nRemove the value and supply it with:\n" + names.map((name) => ` --secret ${name}=env:VARIABLE
|
|
17978
|
+
`).join("") + "\nIf the value was a real credential and this file has been committed or shared,\nrotate it.\n"
|
|
17979
|
+
);
|
|
17980
|
+
return 1;
|
|
17981
|
+
}
|
|
17402
17982
|
|
|
17403
17983
|
// src/commands/context.ts
|
|
17404
17984
|
function runContext(parsed) {
|
|
@@ -17780,9 +18360,9 @@ var MAX_PAGES = 50;
|
|
|
17780
18360
|
async function readAllPages(args) {
|
|
17781
18361
|
const items = [];
|
|
17782
18362
|
let cursor = args.cursor;
|
|
17783
|
-
const onePage = args.cursor !== void 0 || args.pageSize !== void 0;
|
|
18363
|
+
const onePage = args.onePage === true || args.cursor !== void 0 || args.pageSize !== void 0;
|
|
17784
18364
|
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
17785
|
-
const query = new URLSearchParams();
|
|
18365
|
+
const query = new URLSearchParams(args.query ?? {});
|
|
17786
18366
|
if (cursor !== void 0) query.set("cursor", cursor);
|
|
17787
18367
|
if (args.pageSize !== void 0) query.set("pageSize", args.pageSize);
|
|
17788
18368
|
const suffix = query.toString();
|
|
@@ -17817,7 +18397,7 @@ async function readAllPages(args) {
|
|
|
17817
18397
|
}
|
|
17818
18398
|
|
|
17819
18399
|
// src/commands/get.ts
|
|
17820
|
-
async function runGet(parsed) {
|
|
18400
|
+
async function runGet(parsed, deps = {}) {
|
|
17821
18401
|
const noun = parsed.command[1];
|
|
17822
18402
|
const json = parsed.flags["output"] === "json";
|
|
17823
18403
|
const session = await ensureSession({ endpointFlag: parsed.flags["endpoint"] });
|
|
@@ -17894,8 +18474,10 @@ async function runGet(parsed) {
|
|
|
17894
18474
|
const label = String(one["displayName"] ?? one["key"] ?? "");
|
|
17895
18475
|
const state = String(one["state"] ?? "UNKNOWN");
|
|
17896
18476
|
const reason = one["reason"];
|
|
18477
|
+
const observedAt = one["observedAt"];
|
|
18478
|
+
const stale = one["freshness"] === "STALE" && typeof observedAt === "string" ? ` [stale \u2014 last observed ${ageOf(observedAt, (deps.now ?? (() => /* @__PURE__ */ new Date()))())} ago]` : "";
|
|
17897
18479
|
lines.push(
|
|
17898
|
-
` ${label.padEnd(width)} ${state}${typeof reason === "string" && reason !== "" ? ` (${reason})` : ""}`
|
|
18480
|
+
` ${label.padEnd(width)} ${state}${typeof reason === "string" && reason !== "" ? ` (${reason})` : ""}${stale}`
|
|
17899
18481
|
);
|
|
17900
18482
|
const description = one["description"];
|
|
17901
18483
|
if (typeof description === "string") {
|
|
@@ -17932,6 +18514,10 @@ async function runGet(parsed) {
|
|
|
17932
18514
|
if (allowed.length > 0) {
|
|
17933
18515
|
lines.push("", ` You may: ${allowed.join(", ")} (vfac lifecycle <command> ${instanceId})`);
|
|
17934
18516
|
}
|
|
18517
|
+
const notNow = (lifecycle["refused"] ?? []).filter(
|
|
18518
|
+
(entry) => typeof entry["command"] === "string" && typeof entry["reason"] === "string" && entry["reason"] !== "permission_required"
|
|
18519
|
+
).map((entry) => `${String(entry["command"])} (${String(entry["reason"])})`);
|
|
18520
|
+
if (notNow.length > 0) lines.push(` Not now: ${notNow.join(", ")}`);
|
|
17935
18521
|
process.stdout.write(`${lines.join("\n")}
|
|
17936
18522
|
`);
|
|
17937
18523
|
return 0;
|
|
@@ -17978,7 +18564,75 @@ This is one page. Continue with --cursor ${result.nextCursor ?? ""}
|
|
|
17978
18564
|
}
|
|
17979
18565
|
return 0;
|
|
17980
18566
|
}
|
|
17981
|
-
|
|
18567
|
+
if (noun === "operations") {
|
|
18568
|
+
const projectId = resolveProject(parsed.flags["project"], session.ready.stored);
|
|
18569
|
+
if (!projectId) {
|
|
18570
|
+
process.stderr.write("No Project. Use --project prj_\u2026 or set one in your context.\n");
|
|
18571
|
+
return 2;
|
|
18572
|
+
}
|
|
18573
|
+
const resource = parsed.flags["resource"];
|
|
18574
|
+
const result = await readAllPages({
|
|
18575
|
+
endpoint: session.ready.endpoint,
|
|
18576
|
+
token: session.ready.token,
|
|
18577
|
+
path: `/api/v1/projects/${encodeURIComponent(projectId)}/operations`,
|
|
18578
|
+
key: "operations",
|
|
18579
|
+
cursor: parsed.flags["cursor"],
|
|
18580
|
+
pageSize: parsed.flags["page-size"],
|
|
18581
|
+
onePage: true,
|
|
18582
|
+
...resource === void 0 ? {} : { query: { productInstanceId: resource } }
|
|
18583
|
+
});
|
|
18584
|
+
if (!result.ok) {
|
|
18585
|
+
if (result.error.status === 404 && result.error.code === "NOT_FOUND") {
|
|
18586
|
+
process.stderr.write(
|
|
18587
|
+
"This Tenant Portal does not list operations yet \u2014 it is running an older build.\n"
|
|
18588
|
+
);
|
|
18589
|
+
return 1;
|
|
18590
|
+
}
|
|
18591
|
+
return printError(result.error, json);
|
|
18592
|
+
}
|
|
18593
|
+
if (json) {
|
|
18594
|
+
process.stdout.write(
|
|
18595
|
+
`${JSON.stringify({ operations: result.items, nextCursor: result.nextCursor })}
|
|
18596
|
+
`
|
|
18597
|
+
);
|
|
18598
|
+
return 0;
|
|
18599
|
+
}
|
|
18600
|
+
if (result.items.length === 0) {
|
|
18601
|
+
process.stdout.write(
|
|
18602
|
+
resource === void 0 ? "No operations in this project.\n" : "No operations for this resource in this project.\n"
|
|
18603
|
+
);
|
|
18604
|
+
return 0;
|
|
18605
|
+
}
|
|
18606
|
+
const rows = result.items.map((operation) => [
|
|
18607
|
+
operation.operationId ?? "(no id)",
|
|
18608
|
+
// `PRODUCT_INSTANCE_UPDATE` is every line's prefix and says nothing a
|
|
18609
|
+
// reader scanning the column needs. JSON keeps the platform's word.
|
|
18610
|
+
(operation.type ?? "").replace(/^PRODUCT_INSTANCE_/, ""),
|
|
18611
|
+
operation.status ?? "",
|
|
18612
|
+
operation.productInstanceId ?? "-",
|
|
18613
|
+
operation.createdAt ?? ""
|
|
18614
|
+
]);
|
|
18615
|
+
const widths = rows[0].map(
|
|
18616
|
+
(_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length))
|
|
18617
|
+
);
|
|
18618
|
+
for (const row of rows) {
|
|
18619
|
+
process.stdout.write(
|
|
18620
|
+
`${row.map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column])).join(" ")}
|
|
18621
|
+
`
|
|
18622
|
+
);
|
|
18623
|
+
}
|
|
18624
|
+
if (!result.complete) {
|
|
18625
|
+
process.stderr.write(
|
|
18626
|
+
`
|
|
18627
|
+
This is one page. Continue with --cursor ${result.nextCursor ?? ""}
|
|
18628
|
+
`
|
|
18629
|
+
);
|
|
18630
|
+
}
|
|
18631
|
+
return 0;
|
|
18632
|
+
}
|
|
18633
|
+
process.stderr.write(
|
|
18634
|
+
"Usage: vfac get product-instances | vfac get operation op_\u2026 | vfac get operations [--resource pri_\u2026]\n"
|
|
18635
|
+
);
|
|
17982
18636
|
return 2;
|
|
17983
18637
|
}
|
|
17984
18638
|
function wrapOutput(text, width) {
|
|
@@ -17995,6 +18649,13 @@ function wrapOutput(text, width) {
|
|
|
17995
18649
|
if (current !== "") out.push(current);
|
|
17996
18650
|
return out;
|
|
17997
18651
|
}
|
|
18652
|
+
function ageOf(iso, now) {
|
|
18653
|
+
const seconds = Math.max(0, Math.floor((now.getTime() - Date.parse(iso)) / 1e3));
|
|
18654
|
+
if (Number.isNaN(seconds)) return "an unknown time";
|
|
18655
|
+
if (seconds < 3600) return `${Math.max(1, Math.floor(seconds / 60))}m`;
|
|
18656
|
+
if (seconds < 72 * 3600) return `${Math.floor(seconds / 3600)}h`;
|
|
18657
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
18658
|
+
}
|
|
17998
18659
|
|
|
17999
18660
|
// src/commands/product.ts
|
|
18000
18661
|
async function runProduct(parsed) {
|
|
@@ -18601,6 +19262,7 @@ var USAGE = `vfac \u2014 deploy Varde Flyt resources from a manifest
|
|
|
18601
19262
|
vfac get product-instances
|
|
18602
19263
|
vfac get product-instance pri_\u2026 what it is, and its outputs
|
|
18603
19264
|
vfac get operation op_\u2026
|
|
19265
|
+
vfac get operations [--resource pri_\u2026] newest first \u2014 find an operation id again
|
|
18604
19266
|
vfac lifecycle <suspend|resume|upgrade|reconcile|delete> pri_\u2026
|
|
18605
19267
|
delete is irreversible and needs --yes
|
|
18606
19268
|
vfac export product-instance pri_\u2026
|
|
@@ -18618,7 +19280,10 @@ Options
|
|
|
18618
19280
|
--secret inputs the apply will carry; without them the plan
|
|
18619
19281
|
answers about a different request. A value on the command line
|
|
18620
19282
|
is in your shell history and in the process table, so it is
|
|
18621
|
-
refused
|
|
19283
|
+
refused. A value for a secret setting written into the
|
|
19284
|
+
manifest itself is refused too: before the file is sent when it
|
|
19285
|
+
pins the release the catalog offers, by the platform otherwise
|
|
19286
|
+
--resource get operations: only that resource's history
|
|
18622
19287
|
--product which Product to write a starting manifest for
|
|
18623
19288
|
--name what a person will call the resource
|
|
18624
19289
|
--key its stable identity. Derived from --name when omitted
|
|
@@ -18646,8 +19311,10 @@ Reading the catalogue
|
|
|
18646
19311
|
Products are read UNDER a Project, because that is the scope a credential is
|
|
18647
19312
|
granted in. Pass --project, or store one with: vfac context set --project prj_\u2026
|
|
18648
19313
|
|
|
18649
|
-
project list and product
|
|
18650
|
-
cursor. Pass --cursor or --page-size only when you want
|
|
19314
|
+
project list, product list and get product-instances read the WHOLE list,
|
|
19315
|
+
following the server's cursor. Pass --cursor or --page-size only when you want
|
|
19316
|
+
a single page. get operations reads ONE page, newest first \u2014 a history only
|
|
19317
|
+
grows \u2014 and prints the --cursor that continues into older operations.
|
|
18651
19318
|
|
|
18652
19319
|
Authentication
|
|
18653
19320
|
As yourself: vfac login \u2014 approve the code in your browser. The terminal then
|