@varde-flyt/vfac 0.4.0 → 0.5.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 +12 -2
- package/dist/vfac.mjs +169 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,8 +26,18 @@ vfac guide
|
|
|
26
26
|
vfac whoami
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`doctor` checks the endpoint, the API,
|
|
30
|
-
|
|
29
|
+
`doctor` checks the endpoint, the API, whether this client is new enough for the
|
|
30
|
+
platform it is pointed at, your credential and what that credential reaches, in
|
|
31
|
+
that order, and reports the first thing that is actually wrong. A platform
|
|
32
|
+
declares the oldest client it supports; below that, `doctor` fails and says so
|
|
33
|
+
before sending your credential anywhere.
|
|
34
|
+
|
|
35
|
+
`doctor` is also the one command that contacts a host other than your own
|
|
36
|
+
platform: it asks `registry.npmjs.org` whether a newer `vfac` has been published.
|
|
37
|
+
That request is anonymous — no credential, no session, no endpoint, nothing
|
|
38
|
+
identifying you — and it can never change the exit code, so an unreachable or
|
|
39
|
+
blocked registry is reported and otherwise ignored. Set `VFAC_NO_UPDATE_CHECK` to
|
|
40
|
+
any value to skip it entirely; nothing else in this tool makes that request.
|
|
31
41
|
|
|
32
42
|
`vfac guide` is the documentation. It is served by the platform you are pointed
|
|
33
43
|
at rather than bundled here, so it always describes the deployment in front of
|
package/dist/vfac.mjs
CHANGED
|
@@ -7670,7 +7670,9 @@ async function probeMachineApi(args) {
|
|
|
7670
7670
|
}
|
|
7671
7671
|
const authentication = payload["authentication"];
|
|
7672
7672
|
const contextGate = typeof authentication === "object" && authentication !== null && typeof authentication["contextGate"] === "string" ? authentication["contextGate"] : null;
|
|
7673
|
-
|
|
7673
|
+
const cli = payload["cli"];
|
|
7674
|
+
const minimumCliVersion = typeof cli === "object" && cli !== null && typeof cli["minimumVersion"] === "string" ? cli["minimumVersion"] : null;
|
|
7675
|
+
return { ok: true, service, apiVersion, contextGate, minimumCliVersion };
|
|
7674
7676
|
}
|
|
7675
7677
|
|
|
7676
7678
|
// src/manifest.ts
|
|
@@ -12678,6 +12680,15 @@ var RESOURCE_API_VERSION = "resources.vardeflyt.no/v1";
|
|
|
12678
12680
|
var PRODUCT_INSTANCE_KIND = "ProductInstance";
|
|
12679
12681
|
|
|
12680
12682
|
// ../product-configuration/dist/product-instance-example.js
|
|
12683
|
+
function resolveExampleChoice(options, selected, declaredDefault) {
|
|
12684
|
+
if (selected !== void 0)
|
|
12685
|
+
return options.includes(selected) ? { value: selected, issue: null } : { value: null, issue: "invalid" };
|
|
12686
|
+
if (declaredDefault !== void 0 && options.includes(declaredDefault))
|
|
12687
|
+
return { value: declaredDefault, issue: null };
|
|
12688
|
+
if (options.length === 1)
|
|
12689
|
+
return { value: options[0], issue: null };
|
|
12690
|
+
return { value: null, issue: options.length === 0 ? "unavailable" : "required" };
|
|
12691
|
+
}
|
|
12681
12692
|
function isSecret(declaration) {
|
|
12682
12693
|
return declaration.writeOnly === true || declaration["x-secret"] === true;
|
|
12683
12694
|
}
|
|
@@ -15736,9 +15747,9 @@ async function runApply(verb, parsed, deps = {}) {
|
|
|
15736
15747
|
process.stderr.write("Which manifest? Use -f <file>.\n");
|
|
15737
15748
|
return 2;
|
|
15738
15749
|
}
|
|
15739
|
-
const
|
|
15740
|
-
if (!
|
|
15741
|
-
process.stderr.write(`${
|
|
15750
|
+
const manifest2 = readManifest(file);
|
|
15751
|
+
if (!manifest2.ok) {
|
|
15752
|
+
process.stderr.write(`${manifest2.message}
|
|
15742
15753
|
`);
|
|
15743
15754
|
return 2;
|
|
15744
15755
|
}
|
|
@@ -15764,7 +15775,7 @@ async function runApply(verb, parsed, deps = {}) {
|
|
|
15764
15775
|
const json = parsed.flags["output"] === "json";
|
|
15765
15776
|
const path = `/api/v1/projects/${encodeURIComponent(projectId)}/manifest`;
|
|
15766
15777
|
const envelope = {
|
|
15767
|
-
template:
|
|
15778
|
+
template: manifest2.document,
|
|
15768
15779
|
...Object.keys(secrets.secrets).length > 0 ? { secrets: secrets.secrets } : {},
|
|
15769
15780
|
...parsed.flags["adopt"] ? { adopt: parsed.flags["adopt"] } : {}
|
|
15770
15781
|
};
|
|
@@ -15776,7 +15787,7 @@ async function runApply(verb, parsed, deps = {}) {
|
|
|
15776
15787
|
token: session.ready.token,
|
|
15777
15788
|
// The DOCUMENT, not the envelope: `validate` asks whether a file is well
|
|
15778
15789
|
// formed, which is a question about the file and not about a request.
|
|
15779
|
-
body: { verb: "validate", payload:
|
|
15790
|
+
body: { verb: "validate", payload: manifest2.document }
|
|
15780
15791
|
});
|
|
15781
15792
|
if (!result.ok) return printError(result.error, json);
|
|
15782
15793
|
process.stdout.write(
|
|
@@ -15849,7 +15860,7 @@ Read the operation that failed: \`vfac get operation <id>\`, or \`vfac get produ
|
|
|
15849
15860
|
body: { verb: "apply", action, payload: envelope }
|
|
15850
15861
|
});
|
|
15851
15862
|
if (!applied.ok) return printError(applied.error, json);
|
|
15852
|
-
const key = manifestKeyOf(
|
|
15863
|
+
const key = manifestKeyOf(manifest2.document) ?? planned.data.manifestKey ?? "(unnamed)";
|
|
15853
15864
|
const rawOperationId = applied.data["operationId"];
|
|
15854
15865
|
const operationId = typeof rawOperationId === "string" && rawOperationId !== "" ? rawOperationId : null;
|
|
15855
15866
|
if (!parsed.booleans.has("wait")) {
|
|
@@ -15993,17 +16004,63 @@ Project ${projectId ?? "(none)"}
|
|
|
15993
16004
|
|
|
15994
16005
|
// src/version.ts
|
|
15995
16006
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
15996
|
-
function
|
|
16007
|
+
function manifest() {
|
|
15997
16008
|
try {
|
|
15998
|
-
const
|
|
16009
|
+
const parsed = JSON.parse(
|
|
15999
16010
|
readFileSync3(new URL("../package.json", import.meta.url), "utf8")
|
|
16000
16011
|
);
|
|
16001
|
-
|
|
16002
|
-
return typeof version === "string" ? version : "unknown";
|
|
16012
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
16003
16013
|
} catch {
|
|
16004
|
-
return
|
|
16014
|
+
return null;
|
|
16005
16015
|
}
|
|
16006
16016
|
}
|
|
16017
|
+
function cliVersion() {
|
|
16018
|
+
const version = manifest()?.["version"];
|
|
16019
|
+
return typeof version === "string" ? version : "unknown";
|
|
16020
|
+
}
|
|
16021
|
+
function cliPackageName() {
|
|
16022
|
+
const name = manifest()?.["name"];
|
|
16023
|
+
return typeof name === "string" ? name : null;
|
|
16024
|
+
}
|
|
16025
|
+
function cliInstallCommand() {
|
|
16026
|
+
const name = cliPackageName();
|
|
16027
|
+
return name === null ? null : `npm install -g ${name}`;
|
|
16028
|
+
}
|
|
16029
|
+
|
|
16030
|
+
// src/registry.ts
|
|
16031
|
+
var UPDATE_CHECK_TIMEOUT_MS = 3e3;
|
|
16032
|
+
var REGISTRY_ORIGIN = "https://registry.npmjs.org";
|
|
16033
|
+
var UPDATE_CHECK_OPT_OUT = "VFAC_NO_UPDATE_CHECK";
|
|
16034
|
+
async function latestPublishedVersion(args) {
|
|
16035
|
+
const env = args?.env ?? process.env;
|
|
16036
|
+
if (env[UPDATE_CHECK_OPT_OUT]) return { ok: false };
|
|
16037
|
+
const name = cliPackageName();
|
|
16038
|
+
if (name === null) return { ok: false };
|
|
16039
|
+
const doFetch = args?.fetchImpl ?? fetch;
|
|
16040
|
+
let response;
|
|
16041
|
+
try {
|
|
16042
|
+
response = await doFetch(`${REGISTRY_ORIGIN}/-/package/${encodeURIComponent(name)}/dist-tags`, {
|
|
16043
|
+
method: "GET",
|
|
16044
|
+
redirect: "manual",
|
|
16045
|
+
signal: signal()
|
|
16046
|
+
});
|
|
16047
|
+
} catch {
|
|
16048
|
+
return { ok: false };
|
|
16049
|
+
}
|
|
16050
|
+
if (!response.ok) return { ok: false };
|
|
16051
|
+
let payload;
|
|
16052
|
+
try {
|
|
16053
|
+
payload = await response.json();
|
|
16054
|
+
} catch {
|
|
16055
|
+
return { ok: false };
|
|
16056
|
+
}
|
|
16057
|
+
if (typeof payload !== "object" || payload === null) return { ok: false };
|
|
16058
|
+
const latest = payload["latest"];
|
|
16059
|
+
return typeof latest === "string" && latest !== "" ? { ok: true, latest } : { ok: false };
|
|
16060
|
+
}
|
|
16061
|
+
function signal() {
|
|
16062
|
+
return typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) : void 0;
|
|
16063
|
+
}
|
|
16007
16064
|
|
|
16008
16065
|
// src/commands/doctor.ts
|
|
16009
16066
|
async function runDoctor(parsed) {
|
|
@@ -16036,6 +16093,9 @@ async function runDoctor(parsed) {
|
|
|
16036
16093
|
return report(checks, json);
|
|
16037
16094
|
}
|
|
16038
16095
|
checks.push({ name: "Machine API", ok: true, detail: probe.apiVersion });
|
|
16096
|
+
const compatible = compatibility(cliVersion(), probe.minimumCliVersion);
|
|
16097
|
+
checks.push(compatible);
|
|
16098
|
+
if (!compatible.ok) return report(checks, json);
|
|
16039
16099
|
const signedIn = await signIn({ endpoint });
|
|
16040
16100
|
if (!signedIn.ok) {
|
|
16041
16101
|
checks.push({ name: "Context Gate", ok: false, detail: signedIn.message });
|
|
@@ -16110,8 +16170,87 @@ async function runDoctor(parsed) {
|
|
|
16110
16170
|
note: true,
|
|
16111
16171
|
detail: "run `vfac guide` for the platform's own instructions, written for a machine"
|
|
16112
16172
|
});
|
|
16173
|
+
checks.push(await updateAvailable(cliVersion()));
|
|
16113
16174
|
return report(checks, json);
|
|
16114
16175
|
}
|
|
16176
|
+
function compatibility(local, declared) {
|
|
16177
|
+
const name = "Compatibility";
|
|
16178
|
+
const facts = { version: local, minimumVersion: declared };
|
|
16179
|
+
if (declared === null) {
|
|
16180
|
+
return {
|
|
16181
|
+
name,
|
|
16182
|
+
ok: true,
|
|
16183
|
+
detail: "this Tenant Portal names no minimum version, so there is nothing to check against.",
|
|
16184
|
+
facts
|
|
16185
|
+
};
|
|
16186
|
+
}
|
|
16187
|
+
if (!PLAIN_SEMVER_REGEX.test(declared)) {
|
|
16188
|
+
return {
|
|
16189
|
+
name,
|
|
16190
|
+
ok: true,
|
|
16191
|
+
note: true,
|
|
16192
|
+
detail: `this Tenant Portal asks for "${declared}", which is not a version this client can read. Treated as no requirement rather than as a refusal.`,
|
|
16193
|
+
facts
|
|
16194
|
+
};
|
|
16195
|
+
}
|
|
16196
|
+
if (!PLAIN_SEMVER_REGEX.test(local)) {
|
|
16197
|
+
return {
|
|
16198
|
+
name,
|
|
16199
|
+
ok: true,
|
|
16200
|
+
note: true,
|
|
16201
|
+
detail: `this build does not report its own version, so it cannot be checked against the vfac ${declared} this Tenant Portal requires.`,
|
|
16202
|
+
facts
|
|
16203
|
+
};
|
|
16204
|
+
}
|
|
16205
|
+
if (compareSemver(local, declared) >= 0) {
|
|
16206
|
+
return { name, ok: true, detail: `OK \u2014 requires vfac ${declared} or newer`, facts };
|
|
16207
|
+
}
|
|
16208
|
+
const install = cliInstallCommand();
|
|
16209
|
+
return {
|
|
16210
|
+
name,
|
|
16211
|
+
ok: false,
|
|
16212
|
+
detail: `UPGRADE REQUIRED. This Tenant Portal supports vfac ${declared} and newer; this is ${local}. ` + (local.includes("-") ? `A prerelease sorts below the release it is named for, so ${local} does not satisfy ${declared}. ` : "") + (install === null ? "" : `Run: ${install}`),
|
|
16213
|
+
facts
|
|
16214
|
+
};
|
|
16215
|
+
}
|
|
16216
|
+
async function updateAvailable(local) {
|
|
16217
|
+
const name = "Update";
|
|
16218
|
+
const result = await latestPublishedVersion();
|
|
16219
|
+
if (!result.ok) {
|
|
16220
|
+
return {
|
|
16221
|
+
name,
|
|
16222
|
+
ok: true,
|
|
16223
|
+
detail: "could not check for updates",
|
|
16224
|
+
facts: { version: local, latest: null }
|
|
16225
|
+
};
|
|
16226
|
+
}
|
|
16227
|
+
const facts = { version: local, latest: result.latest };
|
|
16228
|
+
if (!PLAIN_SEMVER_REGEX.test(local) || !PLAIN_SEMVER_REGEX.test(result.latest)) {
|
|
16229
|
+
return {
|
|
16230
|
+
name,
|
|
16231
|
+
ok: true,
|
|
16232
|
+
detail: `the latest published release is vfac ${result.latest}`,
|
|
16233
|
+
facts
|
|
16234
|
+
};
|
|
16235
|
+
}
|
|
16236
|
+
const order = compareSemver(local, result.latest);
|
|
16237
|
+
if (order > 0) {
|
|
16238
|
+
return {
|
|
16239
|
+
name,
|
|
16240
|
+
ok: true,
|
|
16241
|
+
detail: `vfac ${local} is ahead of the latest release (${result.latest})`,
|
|
16242
|
+
facts
|
|
16243
|
+
};
|
|
16244
|
+
}
|
|
16245
|
+
if (order === 0) return { name, ok: true, detail: `vfac ${local} is the latest release`, facts };
|
|
16246
|
+
return {
|
|
16247
|
+
name,
|
|
16248
|
+
ok: true,
|
|
16249
|
+
note: true,
|
|
16250
|
+
detail: `vfac ${result.latest} is available (running ${local})`,
|
|
16251
|
+
facts
|
|
16252
|
+
};
|
|
16253
|
+
}
|
|
16115
16254
|
function report(checks, json) {
|
|
16116
16255
|
const failed = checks.filter((check) => !check.ok);
|
|
16117
16256
|
if (json) {
|
|
@@ -16724,42 +16863,27 @@ async function runManifestInit(parsed) {
|
|
|
16724
16863
|
}
|
|
16725
16864
|
const regions = product.supportedRegions ?? [];
|
|
16726
16865
|
const chosenRegion = parsed.flags["region"];
|
|
16727
|
-
|
|
16728
|
-
if (
|
|
16729
|
-
if (!regions.includes(chosenRegion)) {
|
|
16730
|
-
return refuse(
|
|
16731
|
-
`${product.productId} ${product.version} does not offer the region "${chosenRegion}".${regions.length > 0 ? ` It offers: ${regions.join(", ")}.` : ""}`
|
|
16732
|
-
);
|
|
16733
|
-
}
|
|
16734
|
-
region = chosenRegion;
|
|
16735
|
-
} else if (regions.length === 1) {
|
|
16736
|
-
region = regions[0];
|
|
16737
|
-
} else {
|
|
16866
|
+
const regionChoice = resolveExampleChoice(regions, chosenRegion);
|
|
16867
|
+
if (regionChoice.value === null) {
|
|
16738
16868
|
return refuse(
|
|
16739
|
-
regions.length
|
|
16869
|
+
regionChoice.issue === "invalid" ? `${product.productId} ${product.version} does not offer the region "${chosenRegion}".${regions.length > 0 ? ` It offers: ${regions.join(", ")}.` : ""}` : regionChoice.issue === "unavailable" ? `${product.productId} ${product.version} declares no region. Ask the publisher.` : `This Product offers several regions. Pass --region <${regions.join("|")}>.`
|
|
16740
16870
|
);
|
|
16741
16871
|
}
|
|
16872
|
+
const region = regionChoice.value;
|
|
16742
16873
|
const profiles = product.profiles ?? [];
|
|
16743
16874
|
const offered = profiles.map((entry) => entry.id);
|
|
16744
16875
|
const chosenProfile = parsed.flags["profile"];
|
|
16745
|
-
const
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
);
|
|
16752
|
-
}
|
|
16753
|
-
profile = chosenProfile;
|
|
16754
|
-
} else if (declaredDefault) {
|
|
16755
|
-
profile = declaredDefault.id;
|
|
16756
|
-
} else if (offered.length === 1) {
|
|
16757
|
-
profile = offered[0];
|
|
16758
|
-
} else {
|
|
16876
|
+
const profileChoice = resolveExampleChoice(
|
|
16877
|
+
offered,
|
|
16878
|
+
chosenProfile,
|
|
16879
|
+
profiles.find((entry) => entry.default === true)?.id
|
|
16880
|
+
);
|
|
16881
|
+
if (profileChoice.value === null) {
|
|
16759
16882
|
return refuse(
|
|
16760
|
-
offered.length
|
|
16883
|
+
profileChoice.issue === "invalid" ? `${product.productId} ${product.version} does not offer the service profile "${chosenProfile}".${offered.length > 0 ? ` It offers: ${offered.join(", ")}.` : ""}` : profileChoice.issue === "unavailable" ? `${product.productId} ${product.version} declares no service profile. Ask the publisher.` : `This Product offers several service profiles. Pass --profile <${offered.join("|")}>.`
|
|
16761
16884
|
);
|
|
16762
16885
|
}
|
|
16886
|
+
const profile = profileChoice.value;
|
|
16763
16887
|
const seed = seedConfiguration(product.configuration);
|
|
16764
16888
|
for (const property of seed.requiredSecrets) {
|
|
16765
16889
|
notes.push(`--secret ${property}=env:VAR \u2014 required, and never written to this file`);
|
|
@@ -16979,9 +17103,12 @@ Options
|
|
|
16979
17103
|
--output json, for a pipeline that reads the answer
|
|
16980
17104
|
|
|
16981
17105
|
Diagnosing
|
|
16982
|
-
vfac doctor checks the endpoint, the machine API,
|
|
16983
|
-
that
|
|
16984
|
-
|
|
17106
|
+
vfac doctor checks the endpoint, the machine API, whether this client is new
|
|
17107
|
+
enough for that platform, the credential and what that credential
|
|
17108
|
+
can reach \u2014 in that order, so the first thing that is actually
|
|
17109
|
+
wrong is the thing it reports. It ends by asking npm whether a
|
|
17110
|
+
newer vfac has been published, which is a note and never a
|
|
17111
|
+
failure. Set VFAC_NO_UPDATE_CHECK to skip that one request
|
|
16985
17112
|
vfac whoami what this credential is, and which Project scopes it has been
|
|
16986
17113
|
GRANTED. A credential scoped to one Project deploys fine and
|
|
16987
17114
|
cannot make organization-wide reads \u2014 this is where that is said.
|