@varde-flyt/vfac 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +29 -5
  2. package/dist/vfac.mjs +68 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,11 +26,19 @@ vfac guide
26
26
  vfac whoami
27
27
  ```
28
28
 
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.
29
+ `doctor` checks the endpoint, which Project this terminal has selected, the API,
30
+ whether this client is new enough for the platform it is pointed at, your
31
+ credential and what that credential reaches, in that order, and reports the first
32
+ thing that is actually wrong. A platform declares the oldest client it supports;
33
+ below that, `doctor` fails and says so before sending your credential anywhere.
34
+
35
+ The selected Project and the Projects your credential reaches are two different
36
+ lines, and reading the second as the first is the mistake worth naming: a command
37
+ with no `--project` uses the SELECTED one, and having access to a Project is not
38
+ the same as being in it. `doctor` also says so when it has just started a fresh
39
+ config because the old one could not be read — the endpoint and Project you had
40
+ set are gone in that case, and the unreadable file is kept beside it rather than
41
+ discarded.
34
42
 
35
43
  `doctor` is also the one command that contacts a host other than your own
36
44
  platform: it asks `registry.npmjs.org` whether a newer `vfac` has been published.
@@ -73,6 +81,22 @@ vfac get product-instance <productInstanceId>
73
81
  Secrets are supplied per apply and never belong in the manifest — a manifest is a
74
82
  file you commit. `vfac plan` lists the ones an apply will still need.
75
83
 
84
+ ### `metadata.key` is the name you choose
85
+
86
+ `manifest init` writes one into the file, derived from `--name`. It is what the
87
+ platform recognises the resource by on every later apply, and it is the one
88
+ identifier in a manifest that is yours: a resource key is 3–63 characters of
89
+ lowercase letters, digits and hyphens, starting with a letter and ending with a
90
+ letter or digit.
91
+
92
+ **A key is claimed for good, and a delete does not release it.** The record of a
93
+ deleted resource keeps its key, so a manifest that named it can never apply again
94
+ — `vfac plan` answers `GONE` — and a replacement needs a different key. That is
95
+ worth knowing before the delete rather than after it, because after it there is
96
+ nothing to undo; `vfac lifecycle delete` says the same thing at the prompt. If
97
+ what you want is to stop the service and keep the option of bringing it back,
98
+ `vfac lifecycle suspend` is the reversible one.
99
+
76
100
  ## Reading a setting from another resource
77
101
 
78
102
  A setting may take its value from an output another resource publishes, instead
package/dist/vfac.mjs CHANGED
@@ -7424,22 +7424,46 @@ function parseArgs(argv) {
7424
7424
  }
7425
7425
 
7426
7426
  // src/config.ts
7427
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7427
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7428
7428
  import { homedir } from "node:os";
7429
7429
  import { dirname, join } from "node:path";
7430
+ var salvaged = null;
7431
+ function salvagedConfigPath() {
7432
+ return salvaged;
7433
+ }
7430
7434
  function configPath() {
7431
7435
  const base = process.env["VFAC_CONFIG_HOME"] ?? join(homedir(), ".config", "vfac");
7432
7436
  return join(base, "config.json");
7433
7437
  }
7434
7438
  function readStored(path = configPath()) {
7439
+ let raw;
7435
7440
  try {
7436
- const parsed = JSON.parse(readFileSync(path, "utf8"));
7437
- if (typeof parsed !== "object" || parsed === null) return {};
7441
+ raw = readFileSync(path, "utf8");
7442
+ } catch {
7443
+ return {};
7444
+ }
7445
+ try {
7446
+ const parsed = JSON.parse(raw);
7447
+ if (typeof parsed !== "object" || parsed === null) throw new Error("not an object");
7438
7448
  return parsed;
7439
7449
  } catch {
7450
+ salvage(path);
7440
7451
  return {};
7441
7452
  }
7442
7453
  }
7454
+ function salvage(path) {
7455
+ const kept = `${path}.corrupt`;
7456
+ try {
7457
+ readFileSync(kept, "utf8");
7458
+ return;
7459
+ } catch {
7460
+ }
7461
+ try {
7462
+ renameSync(path, kept);
7463
+ salvaged = kept;
7464
+ } catch {
7465
+ }
7466
+ }
7443
7467
  function writeStored(next, path = configPath()) {
7444
7468
  const directory = dirname(path);
7445
7469
  mkdirSync(directory, { recursive: true, mode: 448 });
@@ -12875,6 +12899,11 @@ function matches(pattern, value) {
12875
12899
  var RESOURCE_API_VERSION = "resources.vardeflyt.no/v1";
12876
12900
  var PRODUCT_INSTANCE_KIND = "ProductInstance";
12877
12901
 
12902
+ // ../product-configuration/dist/resource-key.js
12903
+ var RESOURCE_KEY_PATTERN = /^[a-z][a-z0-9-]{1,61}[a-z0-9]$/;
12904
+ var RESOURCE_KEY_RULE = "A resource key is 3\u201363 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.";
12905
+ var ResourceKeySchema = external_exports.string().regex(RESOURCE_KEY_PATTERN, RESOURCE_KEY_RULE).describe(RESOURCE_KEY_RULE);
12906
+
12878
12907
  // ../product-configuration/dist/product-instance-example.js
12879
12908
  function resolveExampleChoice(options, selected, declaredDefault) {
12880
12909
  if (selected !== void 0)
@@ -12937,7 +12966,7 @@ function productInstanceExample(input) {
12937
12966
  }
12938
12967
  function exampleResourceKey(name) {
12939
12968
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "").slice(0, 63);
12940
- return /^[a-z][a-z0-9-]{1,61}[a-z0-9]$/.test(slug) ? slug : "";
12969
+ return RESOURCE_KEY_PATTERN.test(slug) ? slug : "";
12941
12970
  }
12942
12971
 
12943
12972
  // ../platform-schemas/dist/index.js
@@ -15701,17 +15730,15 @@ function isTerminalAction(action) {
15701
15730
  }
15702
15731
  var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
15703
15732
 
15704
- // ../product-contracts/dist/resource-key.js
15705
- var ResourceKeySchema = external_exports.string().regex(/^[a-z][a-z0-9-]{1,61}[a-z0-9]$/, "A resource key is 3\u201363 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.");
15706
-
15707
15733
  // ../product-contracts/dist/resource-output-reference.js
15734
+ var OUTPUT_KEY_RULE = "Output keys are camelCase.";
15708
15735
  var ResourceOutputReferenceSchema = external_exports.object({
15709
15736
  valueFrom: external_exports.object({
15710
15737
  resourceOutput: external_exports.object({
15711
15738
  /** The producing Resource's `metadata.key`, in the same Project. */
15712
15739
  resourceKey: ResourceKeySchema,
15713
15740
  /** The key of an output that Resource's Product declares. */
15714
- output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, "Output keys are camelCase.")
15741
+ output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, OUTPUT_KEY_RULE)
15715
15742
  }).strict()
15716
15743
  }).strict()
15717
15744
  }).strict();
@@ -16327,6 +16354,16 @@ async function runDoctor(parsed) {
16327
16354
  const json = parsed.flags["output"] === "json";
16328
16355
  const checks = [{ name: "CLI", ok: true, detail: `vfac ${cliVersion()}` }];
16329
16356
  const stored = readStored();
16357
+ const salvaged2 = salvagedConfigPath();
16358
+ if (salvaged2 !== null) {
16359
+ checks.push({
16360
+ name: "Config",
16361
+ ok: true,
16362
+ note: true,
16363
+ detail: `the previous config could not be read and was kept at ${salvaged2}. What this command reports below is a fresh one \u2014 the endpoint and Project it held are gone until you set them again. Delete the kept file once you have; it may hold a stale session.`,
16364
+ facts: { salvagedConfigPath: salvaged2 }
16365
+ });
16366
+ }
16330
16367
  const resolved = resolveEndpoint(parsed.flags["endpoint"], stored);
16331
16368
  if (!resolved) {
16332
16369
  checks.push({
@@ -16345,7 +16382,16 @@ async function runDoctor(parsed) {
16345
16382
  checks.push({
16346
16383
  name: "Endpoint",
16347
16384
  ok: true,
16348
- detail: `${endpoint} (from ${resolved.source})`
16385
+ detail: `${endpoint} (from ${resolved.source})`,
16386
+ facts: { endpoint, source: resolved.source }
16387
+ });
16388
+ const selected = stored.context?.projectId ?? null;
16389
+ checks.push({
16390
+ name: "Project",
16391
+ ok: true,
16392
+ note: selected === null,
16393
+ detail: selected === null ? "none selected. Commands need `--project prj_\u2026`, or run `vfac context set --project prj_\u2026` once. This is not a fault \u2014 it is what a pipeline that names the Project per command looks like \u2014 but it is NOT the same thing as which Projects your credential reaches." : `${selected} (selected in this context; which Projects the credential reaches is a separate question)`,
16394
+ facts: { selectedProjectId: selected }
16349
16395
  });
16350
16396
  const probe = await probeMachineApi({ endpoint });
16351
16397
  if (!probe.ok) {
@@ -16391,7 +16437,8 @@ async function runDoctor(parsed) {
16391
16437
  access.tenantWide ? {
16392
16438
  name: "Access",
16393
16439
  ok: true,
16394
- detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}`
16440
+ detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}`,
16441
+ facts: { scope: "organization", grantedProjectIds: null }
16395
16442
  } : projects.length === 0 ? {
16396
16443
  // A FAILURE, NOT A NOTE. This credential authenticates and can do
16397
16444
  // nothing at all — and the check said so in words while `report`
@@ -16421,6 +16468,14 @@ async function runDoctor(parsed) {
16421
16468
  // believes it can deploy and is then refused reports a platform
16422
16469
  // defect, which is precisely the outcome the guide now tells it not
16423
16470
  // to reach for. Scope and role are two questions; this answers both.
16471
+ facts: {
16472
+ scope: "projects",
16473
+ // THE GRANT, and the name says which of the two questions it
16474
+ // answers. A reader gating on `doctor --output json` had no way
16475
+ // to tell this list from the selected Project; now the field
16476
+ // names are the difference.
16477
+ grantedProjectIds: projects.join(",")
16478
+ },
16424
16479
  detail: `${projects.join(", ")} \u2014 and nothing organization-wide. ` + (mutating.length > 0 ? "You can deploy in those Projects, read their catalogue, and list them. " : "Read-only there: catalogue, manifests, resources and operations. An apply or a lifecycle command is refused, and that is what was granted rather than a fault. ") + "Anything ACROSS the organization \u2014 a Project you hold nothing in, or its members \u2014 is refused."
16425
16480
  }
16426
16481
  );
@@ -17129,9 +17184,7 @@ async function runManifestInit(parsed) {
17129
17184
  const notes = [];
17130
17185
  const key = parsed.flags["key"] ?? exampleResourceKey(name);
17131
17186
  if (!key) {
17132
- notes.push(
17133
- "metadata.key \u2014 a stable identity, 3 to 63 characters, lower case. Required before apply"
17134
- );
17187
+ notes.push(`metadata.key \u2014 a stable identity, required before apply. ${RESOURCE_KEY_RULE}`);
17135
17188
  }
17136
17189
  const chooseable = (product.dependencies ?? []).filter(
17137
17190
  (dependency) => dependency.interface?.cardinality === "tenant-multiple"
@@ -17260,6 +17313,8 @@ Run \`vfac get product-instance pri_\u2026\` to see which ones this credential m
17260
17313
  if (command === "delete" && !parsed.booleans.has("yes")) {
17261
17314
  process.stderr.write(
17262
17315
  `delete removes ${instanceId} and everything it is serving. This cannot be undone.
17316
+ If it was deployed from a manifest, its \`metadata.key\` stays claimed: a replacement
17317
+ needs a different key, and re-applying the same file will answer GONE.
17263
17318
  Re-run with --yes to confirm: vfac lifecycle delete ${instanceId} --yes
17264
17319
  Or suspend it instead, which stops it and keeps it: \`vfac lifecycle suspend\`.
17265
17320
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",