@backifydev/cli 0.3.8 → 0.3.9

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 +3 -1
  2. package/dist/index.js +1461 -1204
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4598,9 +4598,9 @@ var require_resolve_block_scalar = __commonJS({
4598
4598
  default: {
4599
4599
  const message = `Unexpected token in block scalar header: ${token.type}`;
4600
4600
  onError(token, "UNEXPECTED_TOKEN", message);
4601
- const ts2 = token.source;
4602
- if (ts2 && typeof ts2 === "string")
4603
- length += ts2.length;
4601
+ const ts3 = token.source;
4602
+ if (ts3 && typeof ts3 === "string")
4603
+ length += ts3.length;
4604
4604
  }
4605
4605
  }
4606
4606
  }
@@ -4910,9 +4910,9 @@ var require_compose_scalar = __commonJS({
4910
4910
  if (schema.compat) {
4911
4911
  const compat = schema.compat.find((tag2) => tag2.default && tag2.test?.test(value)) ?? schema[identity.SCALAR];
4912
4912
  if (tag.tag !== compat.tag) {
4913
- const ts2 = directives.tagString(tag.tag);
4913
+ const ts3 = directives.tagString(tag.tag);
4914
4914
  const cs = directives.tagString(compat.tag);
4915
- const msg = `Value may be parsed as either ${ts2} or ${cs}`;
4915
+ const msg = `Value may be parsed as either ${ts3} or ${cs}`;
4916
4916
  onError(token, "TAG_RESOLVE_FAILED", msg, true);
4917
4917
  }
4918
4918
  }
@@ -7812,7 +7812,7 @@ var require_luxon = __commonJS({
7812
7812
  * @param {string} opts.locale - What locale to return the offset name in.
7813
7813
  * @return {string}
7814
7814
  */
7815
- offsetName(ts2, opts) {
7815
+ offsetName(ts3, opts) {
7816
7816
  throw new ZoneIsAbstractError();
7817
7817
  }
7818
7818
  /**
@@ -7823,7 +7823,7 @@ var require_luxon = __commonJS({
7823
7823
  * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
7824
7824
  * @return {string}
7825
7825
  */
7826
- formatOffset(ts2, format) {
7826
+ formatOffset(ts3, format) {
7827
7827
  throw new ZoneIsAbstractError();
7828
7828
  }
7829
7829
  /**
@@ -7832,7 +7832,7 @@ var require_luxon = __commonJS({
7832
7832
  * @param {number} ts - Epoch milliseconds for which to compute the offset
7833
7833
  * @return {number}
7834
7834
  */
7835
- offset(ts2) {
7835
+ offset(ts3) {
7836
7836
  throw new ZoneIsAbstractError();
7837
7837
  }
7838
7838
  /**
@@ -7878,19 +7878,19 @@ var require_luxon = __commonJS({
7878
7878
  return false;
7879
7879
  }
7880
7880
  /** @override **/
7881
- offsetName(ts2, {
7881
+ offsetName(ts3, {
7882
7882
  format,
7883
7883
  locale
7884
7884
  }) {
7885
- return parseZoneInfo(ts2, format, locale);
7885
+ return parseZoneInfo(ts3, format, locale);
7886
7886
  }
7887
7887
  /** @override **/
7888
- formatOffset(ts2, format) {
7889
- return formatOffset(this.offset(ts2), format);
7888
+ formatOffset(ts3, format) {
7889
+ return formatOffset(this.offset(ts3), format);
7890
7890
  }
7891
7891
  /** @override **/
7892
- offset(ts2) {
7893
- return -new Date(ts2).getTimezoneOffset();
7892
+ offset(ts3) {
7893
+ return -new Date(ts3).getTimezoneOffset();
7894
7894
  }
7895
7895
  /** @override **/
7896
7896
  equals(otherZone) {
@@ -8042,11 +8042,11 @@ var require_luxon = __commonJS({
8042
8042
  * @param {string} opts.locale - What locale to return the offset name in.
8043
8043
  * @return {string}
8044
8044
  */
8045
- offsetName(ts2, {
8045
+ offsetName(ts3, {
8046
8046
  format,
8047
8047
  locale
8048
8048
  }) {
8049
- return parseZoneInfo(ts2, format, locale, this.name);
8049
+ return parseZoneInfo(ts3, format, locale, this.name);
8050
8050
  }
8051
8051
  /**
8052
8052
  * Returns the offset's value as a string
@@ -8056,8 +8056,8 @@ var require_luxon = __commonJS({
8056
8056
  * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
8057
8057
  * @return {string}
8058
8058
  */
8059
- formatOffset(ts2, format) {
8060
- return formatOffset(this.offset(ts2), format);
8059
+ formatOffset(ts3, format) {
8060
+ return formatOffset(this.offset(ts3), format);
8061
8061
  }
8062
8062
  /**
8063
8063
  * Return the offset in minutes for this zone at the specified timestamp.
@@ -8065,9 +8065,9 @@ var require_luxon = __commonJS({
8065
8065
  * @param {number} ts - Epoch milliseconds for which to compute the offset
8066
8066
  * @return {number}
8067
8067
  */
8068
- offset(ts2) {
8068
+ offset(ts3) {
8069
8069
  if (!this.valid) return NaN;
8070
- const date = new Date(ts2);
8070
+ const date = new Date(ts3);
8071
8071
  if (isNaN(date)) return NaN;
8072
8072
  const dtf = makeDTF(this.name);
8073
8073
  let [year, month, day, adOrBc, hour, minute, second] = dtf.formatToParts ? partsOffset(dtf, date) : hackyOffset(dtf, date);
@@ -8654,7 +8654,7 @@ var require_luxon = __commonJS({
8654
8654
  * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
8655
8655
  * @return {string}
8656
8656
  */
8657
- formatOffset(ts2, format) {
8657
+ formatOffset(ts3, format) {
8658
8658
  return formatOffset(this.fixed, format);
8659
8659
  }
8660
8660
  /**
@@ -9340,8 +9340,8 @@ var require_luxon = __commonJS({
9340
9340
  return year;
9341
9341
  } else return year > Settings.twoDigitCutoffYear ? 1900 + year : 2e3 + year;
9342
9342
  }
9343
- function parseZoneInfo(ts2, offsetFormat, locale, timeZone = null) {
9344
- const date = new Date(ts2), intlOpts = {
9343
+ function parseZoneInfo(ts3, offsetFormat, locale, timeZone = null) {
9344
+ const date = new Date(ts3), intlOpts = {
9345
9345
  hourCycle: "h23",
9346
9346
  year: "numeric",
9347
9347
  month: "2-digit",
@@ -12218,9 +12218,9 @@ var require_luxon = __commonJS({
12218
12218
  }
12219
12219
  return [localTS - Math.min(o2, o3) * 60 * 1e3, Math.max(o2, o3)];
12220
12220
  }
12221
- function tsToObj(ts2, offset2) {
12222
- ts2 += offset2 * 60 * 1e3;
12223
- const d = new Date(ts2);
12221
+ function tsToObj(ts3, offset2) {
12222
+ ts3 += offset2 * 60 * 1e3;
12223
+ const d = new Date(ts3);
12224
12224
  return {
12225
12225
  year: d.getUTCFullYear(),
12226
12226
  month: d.getUTCMonth() + 1,
@@ -12251,13 +12251,13 @@ var require_luxon = __commonJS({
12251
12251
  seconds: dur.seconds,
12252
12252
  milliseconds: dur.milliseconds
12253
12253
  }).as("milliseconds"), localTS = objToLocalTS(c);
12254
- let [ts2, o] = fixOffset(localTS, oPre, inst.zone);
12254
+ let [ts3, o] = fixOffset(localTS, oPre, inst.zone);
12255
12255
  if (millisToAdd !== 0) {
12256
- ts2 += millisToAdd;
12257
- o = inst.zone.offset(ts2);
12256
+ ts3 += millisToAdd;
12257
+ o = inst.zone.offset(ts3);
12258
12258
  }
12259
12259
  return {
12260
- ts: ts2,
12260
+ ts: ts3,
12261
12261
  o
12262
12262
  };
12263
12263
  }
@@ -12444,7 +12444,7 @@ var require_luxon = __commonJS({
12444
12444
  return DateTime.invalid(unsupportedZone(zone));
12445
12445
  }
12446
12446
  const loc = Locale.fromObject(opts);
12447
- let ts2, o;
12447
+ let ts3, o;
12448
12448
  if (!isUndefined(obj.year)) {
12449
12449
  for (const u of orderedUnits) {
12450
12450
  if (isUndefined(obj[u])) {
@@ -12456,12 +12456,12 @@ var require_luxon = __commonJS({
12456
12456
  return DateTime.invalid(invalid2);
12457
12457
  }
12458
12458
  const offsetProvis = guessOffsetForZone(zone);
12459
- [ts2, o] = objToTS(obj, offsetProvis, zone);
12459
+ [ts3, o] = objToTS(obj, offsetProvis, zone);
12460
12460
  } else {
12461
- ts2 = Settings.now();
12461
+ ts3 = Settings.now();
12462
12462
  }
12463
12463
  return new DateTime({
12464
- ts: ts2,
12464
+ ts: ts3,
12465
12465
  zone,
12466
12466
  loc,
12467
12467
  o
@@ -12624,8 +12624,8 @@ var require_luxon = __commonJS({
12624
12624
  * @return {DateTime}
12625
12625
  */
12626
12626
  static fromJSDate(date, options = {}) {
12627
- const ts2 = isDate(date) ? date.valueOf() : NaN;
12628
- if (Number.isNaN(ts2)) {
12627
+ const ts3 = isDate(date) ? date.valueOf() : NaN;
12628
+ if (Number.isNaN(ts3)) {
12629
12629
  return _DateTime.invalid("invalid input");
12630
12630
  }
12631
12631
  const zoneToUse = normalizeZone(options.zone, Settings.defaultZone);
@@ -12633,7 +12633,7 @@ var require_luxon = __commonJS({
12633
12633
  return _DateTime.invalid(unsupportedZone(zoneToUse));
12634
12634
  }
12635
12635
  return new _DateTime({
12636
- ts: ts2,
12636
+ ts: ts3,
12637
12637
  zone: zoneToUse,
12638
12638
  loc: Locale.fromObject(options)
12639
12639
  });
@@ -13287,14 +13287,14 @@ var require_luxon = __commonJS({
13287
13287
  return [this];
13288
13288
  }
13289
13289
  const ts1 = localTS - o1 * minuteMs;
13290
- const ts2 = localTS - o2 * minuteMs;
13290
+ const ts22 = localTS - o2 * minuteMs;
13291
13291
  const c1 = tsToObj(ts1, o1);
13292
- const c2 = tsToObj(ts2, o2);
13292
+ const c2 = tsToObj(ts22, o2);
13293
13293
  if (c1.hour === c2.hour && c1.minute === c2.minute && c1.second === c2.second && c1.millisecond === c2.millisecond) {
13294
13294
  return [clone(this, {
13295
13295
  ts: ts1
13296
13296
  }), clone(this, {
13297
- ts: ts2
13297
+ ts: ts22
13298
13298
  })];
13299
13299
  }
13300
13300
  return [this];
@@ -13493,9 +13493,9 @@ var require_luxon = __commonJS({
13493
13493
  mixed.day = Math.min(daysInMonth(mixed.year, mixed.month), mixed.day);
13494
13494
  }
13495
13495
  }
13496
- const [ts2, o] = objToTS(mixed, this.o, this.zone);
13496
+ const [ts3, o] = objToTS(mixed, this.o, this.zone);
13497
13497
  return clone(this, {
13498
- ts: ts2,
13498
+ ts: ts3,
13499
13499
  o
13500
13500
  });
13501
13501
  }
@@ -17666,7 +17666,7 @@ import { hostname, platform, release, arch } from "node:os";
17666
17666
  // package.json
17667
17667
  var package_default = {
17668
17668
  name: "@backifydev/cli",
17669
- version: "0.3.8",
17669
+ version: "0.3.9",
17670
17670
  description: "Command-line setup and diagnostics for Backify applications",
17671
17671
  type: "module",
17672
17672
  bin: {
@@ -17725,154 +17725,20 @@ async function registerCliSession(client, applicationId, apiUrl) {
17725
17725
  return { ...session, apiUrl };
17726
17726
  }
17727
17727
 
17728
- // src/local-runtime.ts
17729
- import { createHash, randomUUID } from "node:crypto";
17730
- import { chmodSync, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
17731
- import { dirname as dirname2, join, resolve as resolve2, sep } from "node:path";
17732
- import { homedir } from "node:os";
17733
- import { spawnSync } from "node:child_process";
17734
-
17735
- // src/local-state.ts
17736
- import { renameSync } from "node:fs";
17737
- function replaceLocalFile(source, destination, rename, platform2, wait) {
17738
- for (let attempt = 0; ; attempt++) {
17739
- try {
17740
- rename(source, destination);
17741
- return;
17742
- } catch (error) {
17743
- if (platform2 !== "win32" || attempt >= 4 || !(error instanceof Error) || !("code" in error) || !["EPERM", "EACCES", "EBUSY"].includes(String(error.code))) throw error;
17744
- wait(10 * 2 ** attempt);
17745
- }
17746
- }
17747
- }
17748
- function renameLocalFile(source, destination) {
17749
- replaceLocalFile(source, destination, renameSync, process.platform, (milliseconds) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds));
17750
- }
17751
-
17752
- // src/local-runtime.ts
17753
- var maxArchiveBytes = 256 * 1024 * 1024;
17754
- var maxExpandedBytes = 512 * 1024 * 1024;
17755
- var hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
17756
- function runtimeIdentifier(platform2 = process.platform, architecture = process.arch) {
17757
- const system = { win32: "win", darwin: "osx", linux: "linux" }[platform2];
17758
- if (!system || !["x64", "arm64"].includes(architecture)) throw new Error("Local runtime supports Windows, macOS and Linux on x64/arm64");
17759
- return system + "-" + architecture;
17760
- }
17761
- function checkedPath(root, filename2) {
17762
- const target = resolve2(root, filename2);
17763
- if (target !== resolve2(root) && !target.startsWith(resolve2(root) + sep)) throw new Error("Runtime path escapes its installation");
17764
- let current = target;
17765
- while (true) {
17766
- if (existsSync2(current) && lstatSync(current).isSymbolicLink()) throw new Error("Runtime installation must not follow links");
17767
- if (dirname2(current) === current) break;
17768
- current = dirname2(current);
17769
- }
17770
- return target;
17771
- }
17772
- function verifyInstallationFiles(root, expected, directory2 = root) {
17773
- for (const entry of readdirSync(directory2, { withFileTypes: true })) {
17774
- const path = checkedPath(root, join(directory2, entry.name));
17775
- if (entry.isDirectory()) verifyInstallationFiles(root, expected, path);
17776
- else if (!entry.isFile() || !Object.hasOwn(expected, path.slice(root.length + 1).split(sep).join("/"))) throw new Error("Installed runtime contains unexpected files; reinstall a reviewed version");
17777
- }
17778
- }
17779
- function installedLocalRuntime(base = join(homedir(), ".backify")) {
17780
- const pointer = checkedPath(base, "runtime.json");
17781
- if (!existsSync2(pointer)) return void 0;
17782
- const installed = JSON.parse(readFileSync(pointer, "utf8"));
17783
- if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(installed.version) || installed.rid !== runtimeIdentifier() || !installed.files || Object.keys(installed.files).length > 3e3) throw new Error("Invalid local runtime installation receipt");
17784
- const root = checkedPath(base, join("runtimes", installed.version, installed.rid));
17785
- verifyInstallationFiles(root, installed.files);
17786
- for (const [filename2, digest4] of Object.entries(installed.files)) {
17787
- const path = checkedPath(root, filename2);
17788
- if (!/^[a-f0-9]{64}$/.test(digest4) || !lstatSync(path).isFile() || lstatSync(path).size > maxArchiveBytes || hash(readFileSync(path)) !== digest4) throw new Error("Installed runtime bytes changed; reinstall a reviewed version");
17789
- }
17790
- const executableName = installed.rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
17791
- if (!installed.files["Backify.Hcr.dll"] || !installed.files[executableName]) throw new Error("Runtime archive requires the self-contained HCR executable and DLL");
17792
- return { path: join(root, "Backify.Hcr.dll"), executable: join(root, executableName), version: installed.version, rid: installed.rid };
17793
- }
17794
- function installLocalRuntime(archive, options) {
17795
- if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(options.version) || !/^[a-f0-9]{64}$/.test(options.sha256)) throw new Error("Provide a release version vX.Y.Z and an independently reviewed SHA-256");
17796
- if (archive.byteLength > maxArchiveBytes || hash(archive) !== options.sha256) throw new Error("Runtime archive digest does not match the reviewed SHA-256");
17797
- let expanded = 0, count = 0;
17798
- const files = unzipSync(archive, { filter: (file) => {
17799
- if (++count > 3e3 || (expanded += file.originalSize) > maxExpandedBytes || file.originalSize > maxArchiveBytes) throw new Error("Runtime archive exceeds extraction limits");
17800
- if (!file.name || file.name.includes("\\") || file.name.startsWith("/") || file.name.includes(":") || file.name.split("/").some((part) => part === "." || part === "..")) throw new Error("Runtime archive contains an unsafe path");
17801
- return !file.name.endsWith("/");
17802
- } });
17803
- const rid = runtimeIdentifier();
17804
- const executable = rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
17805
- if (!files["Backify.Hcr.dll"] || !files[executable] || !files["sql/events-v1.sql"] || !files["sql/jobs-v1.sql"]) throw new Error("Runtime archive is missing required host assets");
17806
- const base = options.base ?? join(homedir(), ".backify");
17807
- const parent = checkedPath(base, join("runtimes", options.version));
17808
- mkdirSync2(parent, { recursive: true, mode: 448 });
17809
- const destination = checkedPath(parent, rid);
17810
- const receipt = { version: options.version, rid, files: Object.fromEntries(Object.entries(files).map(([name2, bytes]) => [name2, hash(bytes)])) };
17811
- if (!existsSync2(destination)) {
17812
- const staging = checkedPath(parent, ".install-" + randomUUID());
17813
- mkdirSync2(staging, { mode: 448 });
17814
- for (const [name2, bytes] of Object.entries(files)) {
17815
- const filename2 = checkedPath(staging, name2);
17816
- mkdirSync2(dirname2(filename2), { recursive: true, mode: 448 });
17817
- writeFileSync2(filename2, bytes, { flag: "wx", mode: name2 === executable ? 448 : 384 });
17818
- }
17819
- if (process.platform !== "win32") chmodSync(join(staging, executable), 448);
17820
- renameLocalFile(staging, destination);
17821
- } else {
17822
- verifyInstallationFiles(destination, receipt.files);
17823
- for (const [name2, digest4] of Object.entries(receipt.files)) if (hash(readFileSync(checkedPath(destination, name2))) !== digest4) throw new Error("Runtime version already exists with different bytes; never overwrite an installation");
17824
- }
17825
- const pointer = checkedPath(base, "runtime.json");
17826
- const temporary = checkedPath(base, ".runtime-" + randomUUID() + ".json");
17827
- writeFileSync2(temporary, JSON.stringify(receipt), { flag: "wx", mode: 384 });
17828
- renameLocalFile(temporary, pointer);
17829
- return installedLocalRuntime(base);
17830
- }
17831
- async function downloadLocalRuntime(version) {
17832
- if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(version)) throw new Error("Provide a release version vX.Y.Z");
17833
- let url = new URL("https://github.com/frejnorling/backify/releases/download/runtime-" + version + "/backify-hcr-" + runtimeIdentifier() + ".zip");
17834
- for (let redirects = 0; redirects < 5; redirects++) {
17835
- if (url.protocol !== "https:" || !["github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com"].includes(url.hostname) || url.username || url.password) throw new Error("Runtime download left the approved release hosts");
17836
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(6e4) });
17837
- if ([301, 302, 303, 307, 308].includes(response.status)) {
17838
- url = new URL(response.headers.get("location") ?? "", url);
17839
- continue;
17840
- }
17841
- if (!response.ok || !response.body) throw new Error("The requested runtime release is unavailable");
17842
- const chunks = [];
17843
- let size = 0;
17844
- for await (const chunk of response.body) {
17845
- size += chunk.byteLength;
17846
- if (size > maxArchiveBytes) throw new Error("Runtime archive exceeds its size limit");
17847
- chunks.push(chunk);
17848
- }
17849
- return Buffer.concat(chunks);
17850
- }
17851
- throw new Error("Too many runtime download redirects");
17852
- }
17853
- function localPrerequisites() {
17854
- const runtime = installedLocalRuntime();
17855
- const directory2 = process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
17856
- const checks = ["initdb", "pg_ctl", "psql"].map((name2) => {
17857
- const executable = directory2 ? join(directory2, name2 + (process.platform === "win32" ? ".exe" : "")) : name2;
17858
- const result = spawnSync(executable, ["--version"], { windowsHide: true, timeout: 1e4, encoding: "utf8" });
17859
- return { name: name2, available: result.status === 0 };
17860
- });
17861
- return { rid: runtimeIdentifier(), runtime: runtime ? { installed: true, version: runtime.version } : { installed: false }, postgres: checks, ready: !!runtime && checks.every((check) => check.available) };
17862
- }
17728
+ // src/browser-auth.ts
17729
+ import { randomBytes } from "node:crypto";
17730
+ import { createServer } from "node:http";
17863
17731
 
17864
- // src/workload.ts
17865
- import { build as build2 } from "esbuild";
17866
- import { createHash as createHash13 } from "node:crypto";
17867
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync16, realpathSync as realpathSync6, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
17868
- import { dirname as dirname10, isAbsolute as isAbsolute13, join as join10, relative as relative8, resolve as resolve16 } from "node:path";
17732
+ // src/development.ts
17733
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync4, renameSync, rmSync, mkdtempSync, lstatSync as lstatSync2, cpSync } from "node:fs";
17734
+ import { dirname as dirname4, join as join3, resolve as resolve3 } from "node:path";
17735
+ import { spawnSync as spawnSync2 } from "node:child_process";
17736
+ import { randomUUID } from "node:crypto";
17869
17737
 
17870
- // src/local-development.ts
17871
- import { context as context2 } from "esbuild";
17872
- import { spawn as spawn4 } from "node:child_process";
17873
- import { randomBytes as randomBytes5, randomUUID as randomUUID8, createHash as createHash12 } from "node:crypto";
17874
- import { existsSync as existsSync8, lstatSync as lstatSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync15, realpathSync as realpathSync5, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4 } from "node:fs";
17875
- import { resolve as resolve15, join as join9, relative as relative7, isAbsolute as isAbsolute12, dirname as dirname9 } from "node:path";
17738
+ // src/setup.ts
17739
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readdirSync, lstatSync } from "node:fs";
17740
+ import { basename, dirname as dirname3, join as join2, resolve as resolve2, isAbsolute } from "node:path";
17741
+ import { spawnSync } from "node:child_process";
17876
17742
 
17877
17743
  // ../platform/dist/manifest.js
17878
17744
  var import_yaml = __toESM(require_dist(), 1);
@@ -17905,6 +17771,8 @@ function parseApplicationManifest(source) {
17905
17771
  if (!object(settings))
17906
17772
  throw new Error("Capability configuration must be a mapping: " + name2);
17907
17773
  validatePortableSettings(settings);
17774
+ if (name2 === "auth")
17775
+ validateAuthSettings(settings);
17908
17776
  if (settings.enabled !== void 0 && typeof settings.enabled !== "boolean")
17909
17777
  throw new Error("enabled must be boolean: " + name2);
17910
17778
  if (name2 === "database" && settings.engine !== void 0 && settings.engine !== "postgres")
@@ -17934,8 +17802,32 @@ function validatePortableSettings(settings) {
17934
17802
  }
17935
17803
  }
17936
17804
  }
17937
- function manifestDiagnostics(manifest, used) {
17938
- return [...new Set(used)].filter((name2) => !manifest.capabilities[name2] || manifest.capabilities[name2]?.enabled === false).map((name2) => ({ code: "undeclared-capability", severity: "error", message: "Declare capabilities." + name2 + " in backify.yaml before deployment" }));
17805
+ function validateAuthSettings(settings) {
17806
+ const supported = ["entra", "google", "email"];
17807
+ if (Object.keys(settings).some((key) => !["enabled", "providers", "defaultProvider", "session", "roles"].includes(key)))
17808
+ throw new Error("Unknown authentication setting");
17809
+ if (settings.providers !== void 0 && (!Array.isArray(settings.providers) || settings.providers.length === 0 || settings.providers.some((provider) => typeof provider !== "string" || !supported.includes(provider)) || new Set(settings.providers).size !== settings.providers.length))
17810
+ throw new Error("Authentication requires distinct supported providers: entra, google, email");
17811
+ if (settings.defaultProvider !== void 0 && (!Array.isArray(settings.providers) || !settings.providers.includes(settings.defaultProvider)))
17812
+ throw new Error("The default authentication provider must be declared");
17813
+ if (settings.session !== void 0 && (!object(settings.session) || Object.keys(settings.session).some((key) => !["duration", "refresh"].includes(key)) || typeof settings.session.duration !== "string" || settings.session.duration.trim() !== settings.session.duration || !/^(?:[1-9]|1[0-9]|2[0-4])h$/.test(settings.session.duration) || settings.session.refresh !== void 0 && typeof settings.session.refresh !== "boolean"))
17814
+ throw new Error("Authentication sessions require a 1h-24h duration and an optional boolean refresh setting");
17815
+ if (settings.roles !== void 0 && (!Array.isArray(settings.roles) || settings.roles.length > 32 || settings.roles.some((role) => typeof role !== "string" || !role.trim() || role.length > 100) || new Set(settings.roles).size !== settings.roles.length))
17816
+ throw new Error("Authentication roles must be distinct bounded names");
17817
+ }
17818
+ function manifestDiagnostics(manifest, used, authentication) {
17819
+ const diagnostics = [...new Set(used)].filter((name2) => !manifest.capabilities[name2] || manifest.capabilities[name2]?.enabled === false).map((name2) => ({ code: "undeclared-capability", severity: "error", message: "Declare capabilities." + name2 + " in backify.yaml before deployment" }));
17820
+ if (authentication) {
17821
+ const declared = manifest.capabilities.auth?.providers;
17822
+ if (authentication.dynamicProviders === true && (!Array.isArray(declared) || declared.length === 0))
17823
+ diagnostics.push({ code: "undeclared-auth-providers", severity: "error", message: "Dynamic sign-in requires an explicit capabilities.auth.providers list" });
17824
+ if (Array.isArray(authentication.providers)) {
17825
+ for (const provider of authentication.providers)
17826
+ if (!Array.isArray(declared) || !declared.includes(provider))
17827
+ diagnostics.push({ code: "undeclared-auth-provider", severity: "error", message: "Declare authentication provider " + String(provider) + " before deployment" });
17828
+ }
17829
+ }
17830
+ return diagnostics;
17939
17831
  }
17940
17832
 
17941
17833
  // ../platform/dist/delivery.js
@@ -17968,6 +17860,10 @@ Run the following to discover the connected hosting target, repository, branch,
17968
17860
 
17969
17861
  Connection and initialization may proceed without application requirements. Then ask what to build if none were supplied; never invent requirements or record this setup text as a user requirement.
17970
17862
 
17863
+ ## Reauthenticate CLI access
17864
+
17865
+ If CLI authentication expires or is rejected, ask the user to reauthenticate. Run npx @backifydev/cli@latest connect --reauth from the application folder (or supply --app <app-id> and --organization <organization-id>). It opens https://auth.backify.dev for sign-in and explicit approval of 7, 14, 30, or 90 days of application CLI access. Keep the command running while the user signs in; it saves the new credential automatically and the browser can then be closed. Never ask the user to paste tokens, inspect credential files, or copy a new setup prompt just to reconnect. Ordinary authenticated commands also open browser recovery on expiry or HTTP 401 and retry once after approval. For unattended execution use --no-browser; with --json an authentication_required error includes a reauthenticationCommand. After explicit reconnection, retry only an operation known to have failed authentication; do not repeat a completed deployment or other successful write.
17866
+
17971
17867
  ## Record each user request
17972
17868
 
17973
17869
  For EVERY user request to build or modify this application, including the initial requirements supplied alongside or after setup, record only the user's own requirement text before changing application code. Submit the text through standard input to:
@@ -18049,7 +17945,7 @@ var MAIL_LIMITS = Object.freeze({
18049
17945
  });
18050
17946
  var capability = (name2, imp, description, stateful = false, provisioner = name2) => ({ name: name2, import: imp, version: 1, stateful, provisioner, docs: `${DOCS_URL}/capabilities/${name2}`, description, destructiveRemovalRequiresApproval: stateful });
18051
17947
  var CAPABILITIES = [
18052
- capability("auth", "auth", "Application users, organizations, roles, and permissions"),
17948
+ capability("auth", "auth", "Application users, organizations, roles, and permissions", true),
18053
17949
  capability("database", "db", "Persistent application data", true),
18054
17950
  capability("storage", "storage", "Persistent files and objects", true),
18055
17951
  capability("functions", "functions", "Server function declarations"),
@@ -18163,113 +18059,931 @@ function validateConfig(value) {
18163
18059
  }
18164
18060
  var BACKIFY_SCHEMA = { $schema: "https://json-schema.org/draft/2020-12/schema", $id: `${DOCS_URL}/schema/backify.json`, type: "object", additionalProperties: false, required: ["$schema", "platform", "sdk", "appId", "instructionVersion", "docs", "instructions"], properties: { $schema: { const: `${DOCS_URL}/schema/backify.json` }, platform: { const: "backify" }, sdk: { const: SDK_PACKAGE }, appId: { type: "string", pattern: "^app_[A-Za-z0-9_-]+$" }, instructionVersion: { type: "integer", minimum: 1 }, agent: { enum: ["codex", "claude", "copilot", "gemini", "custom"] }, instructionFile: { type: "string", minLength: 1 }, docs: { type: "string", format: "uri" }, instructions: { type: "object", additionalProperties: false, required: ["preferBackifyCapabilities", "allowDirectCloudInfrastructure"], properties: { preferBackifyCapabilities: { const: true }, allowDirectCloudInfrastructure: { const: false } } } } };
18165
18061
 
18166
- // ../analyzer/dist/index.js
18167
- import ts from "typescript";
18168
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "node:fs";
18169
- import { join as join2, extname } from "node:path";
18170
- var bypass = { "@azure/storage-blob": { capability: "storage", replacement: "storage" }, "@aws-sdk/client-s3": { capability: "storage", replacement: "storage" }, "firebase-admin": { capability: "database", replacement: "db" }, "@supabase/supabase-js": { capability: "database", replacement: "db" }, "@azure/service-bus": { capability: "jobs", replacement: "jobs" } };
18171
- function analyzeSources(files, options = {}) {
18172
- const used = /* @__PURE__ */ new Set(), diagnostics = [];
18173
- for (const file of files)
18174
- analyzeFile(file, used, diagnostics);
18175
- if (used.has("http"))
18176
- used.add("events");
18177
- return { schemaVersion: 1, applicationId: options.applicationId ?? "unknown", commit: options.commit ?? "unknown", sdkVersion: options.sdkVersion ?? "unknown", capabilities: Object.fromEntries([...used].sort().map((name2) => [name2, { version: CAPABILITY_REGISTRY[name2].version }])), diagnostics };
18062
+ // src/delivery.ts
18063
+ import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
18064
+ import { dirname as dirname2, join, relative as relativePath } from "node:path";
18065
+ import { createHash } from "node:crypto";
18066
+ var previousGeneratedWorkflowHashes = /* @__PURE__ */ new Set(["33e962274e8493f7cbac2afbac05a4e96afeec49ee08cf2bf82f8add9081b28b", "578dfb4c771c623fd04454e8a604efd701d5563d11e11191324862369bd24718", "6eb280ae7bdb14c390011686c41400f59c023304c3015d8440b7e80aaafa1c11", "545e03a1d5b996ade179e0e60ed5c86accdadc1982c1c25efd094e34f72f47ee"]);
18067
+ function validateDeliveryInstructions(root) {
18068
+ for (const filename2 of ["BACKIFY.md", "BACKIFY-WORKFLOW.md"]) {
18069
+ const path = join(root, filename2);
18070
+ if (existsSync2(path)) {
18071
+ const existing = readFileSync(path, "utf8").replace(/\r\n/g, "\n");
18072
+ if (existing !== deliveryInstructions && !previousGeneratedWorkflowHashes.has(createHash("sha256").update(existing).digest("hex"))) throw new Error(filename2 + " contains different instructions. Review it before initializing; it will not be overwritten.");
18073
+ }
18074
+ }
18178
18075
  }
18179
- function analyzeFile(file, used, diagnostics) {
18180
- const source = ts.createSourceFile(file.path, file.content, ts.ScriptTarget.Latest, true, file.path.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS), named = /* @__PURE__ */ new Map(), namespaces = /* @__PURE__ */ new Set(), roots = /* @__PURE__ */ new Set(), factories = /* @__PURE__ */ new Set();
18181
- source.forEachChild((node) => {
18182
- if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier))
18183
- return;
18184
- const module = node.moduleSpecifier.text;
18185
- if (bypass[module]) {
18186
- const rule = bypass[module];
18187
- diagnostics.push({ severity: "warning", code: "direct-provider-sdk", file: file.path, docs: CAPABILITY_REGISTRY[rule.capability].docs, message: `Direct provider dependency '${module}' detected. Use import { ${rule.replacement} } from "@backifydev/sdk" so Backify can provision and bind ${rule.capability}.` });
18076
+ function installDeliveryInstructions(root, instructionFiles2) {
18077
+ validateDeliveryInstructions(root);
18078
+ writeFileSync2(join(root, "BACKIFY.md"), deliveryInstructions);
18079
+ for (const relative11 of instructionFiles2) {
18080
+ const instructionPath = join(root, relative11), content = readFileSync(instructionPath, "utf8");
18081
+ const link = relativePath(dirname2(instructionPath), join(root, "BACKIFY.md")).replaceAll("\\", "/");
18082
+ const legacyLink = relativePath(dirname2(instructionPath), join(root, "BACKIFY-WORKFLOW.md")).replaceAll("\\", "/");
18083
+ const legacy = "For application delivery and recording each user request, follow [BACKIFY-WORKFLOW.md](" + legacyLink + ").";
18084
+ const guidance = "For application delivery and recording each user request, follow [BACKIFY.md](" + link + ").";
18085
+ if (content.includes(legacy)) writeFileSync2(instructionPath, content.replace(legacy, guidance));
18086
+ else if (!content.includes("## Backify delivery")) writeFileSync2(instructionPath, content.trimEnd() + "\n\n## Backify delivery\n\n" + guidance + "\n");
18087
+ }
18088
+ }
18089
+
18090
+ // src/setup.ts
18091
+ var instructionFiles = ["AGENTS.md"];
18092
+ var agentInstructionFiles = { codex: "AGENTS.md", claude: "CLAUDE.md", copilot: join2(".github", "copilot-instructions.md"), gemini: "GEMINI.md" };
18093
+ function instructionSelection(root, agent, instructionFile) {
18094
+ const configPath2 = join2(root, "backify.json"), configured = existsSync3(configPath2) ? JSON.parse(readFileSync2(configPath2, "utf8")) : {};
18095
+ const custom = instructionFile ?? (agent === void 0 ? configured.instructionFile : void 0);
18096
+ const selected = instructionFile !== void 0 ? "custom" : agent ?? configured.agent ?? "codex";
18097
+ if (typeof selected !== "string" || selected !== "custom" && !Object.hasOwn(agentInstructionFiles, selected)) throw new Error("Select the current tool with --agent or --instruction-file <relative-path>.");
18098
+ const filename2 = custom ?? agentInstructionFiles[selected];
18099
+ if (typeof filename2 !== "string" || !filename2.trim()) throw new Error("Select the current tool with --agent codex, claude, copilot, or gemini, or --instruction-file <relative-path> for any other tool.");
18100
+ const normalized = filename2.replaceAll("\\", "/"), parts = normalized.split("/");
18101
+ if (isAbsolute(filename2) || /^[A-Za-z]:/.test(filename2) || parts.some((part) => !part || part === "." || part === ".." || part.toLowerCase() === ".git") || ["backify.md", "backify-workflow.md", "backify.json", "package.json", "package-lock.json"].includes(normalized.toLowerCase())) throw new Error("Instruction file must be a relative tool-instruction path inside the project, not Git metadata or a Backify configuration file.");
18102
+ for (let index = 1; index <= parts.length; index++) {
18103
+ const entry = join2(root, ...parts.slice(0, index));
18104
+ try {
18105
+ const metadata = lstatSync(entry);
18106
+ if (metadata.isSymbolicLink() || index < parts.length && !metadata.isDirectory() || index === parts.length && !metadata.isFile()) throw new Error("Instruction file path cannot traverse symlinks or replace a directory.");
18107
+ } catch (reason) {
18108
+ if (reason.code !== "ENOENT") throw reason;
18188
18109
  }
18189
- if (module !== "@backifydev/sdk" && module !== "@backifydev/sdk/server" || !node.importClause)
18190
- return;
18191
- const bindings = node.importClause.namedBindings;
18192
- if (!bindings)
18193
- return;
18194
- if (ts.isNamespaceImport(bindings))
18195
- namespaces.add(bindings.name.text);
18196
- else if (ts.isNamedImports(bindings))
18197
- for (const element of bindings.elements) {
18198
- const imported = (element.propertyName ?? element.name).text, local = element.name.text;
18199
- if (imported === "createServerClient")
18200
- factories.add(local);
18201
- else if (imported === "backify")
18202
- roots.add(local);
18203
- else {
18204
- const cap = SDK_IMPORT_CAPABILITIES[imported];
18205
- if (cap)
18206
- named.set(local, cap);
18207
- }
18208
- }
18110
+ }
18111
+ return { agent: selected, instructionFile: normalized, files: [normalized] };
18112
+ }
18113
+ function selectedInstructionFiles(root = process.cwd(), agent, instructionFile) {
18114
+ return instructionSelection(root, agent, instructionFile).files;
18115
+ }
18116
+ function isToolWorkspaceEntry(entry, root = process.cwd()) {
18117
+ if (entry.name.toLowerCase() === ".git" || entry.name.toLowerCase() === ".env" || entry.name.toLowerCase().startsWith(".env.")) return false;
18118
+ return entry.isDirectory() && (entry.name.startsWith(".") || ["work", "outputs"].includes(entry.name) || readdirSync(join2(root, entry.name)).length === 0) || entry.isFile() && (entry.name.startsWith(".") || /\.(md|mdc)$/i.test(entry.name));
18119
+ }
18120
+ function detectPackageManager(root = process.cwd()) {
18121
+ for (const [file, name2] of [["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lockb", "bun"], ["bun.lock", "bun"], ["package-lock.json", "npm"]]) if (existsSync3(join2(root, file))) return name2;
18122
+ return "npm";
18123
+ }
18124
+ function updateInstructions(root = process.cwd(), agent, instructionFile) {
18125
+ const updates = selectedInstructionFiles(root, agent, instructionFile).map((relative11) => {
18126
+ const path = join2(root, relative11);
18127
+ return { path, content: updateManagedInstructions(existsSync3(path) ? readFileSync2(path, "utf8") : "") };
18209
18128
  });
18210
- const visit = (node) => {
18211
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && factories.has(node.initializer.expression.text))
18212
- roots.add(node.name.text);
18213
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && named.get(node.expression.text) === "jobs" && node.arguments.some((argument) => ts.isObjectLiteralExpression(argument) && argument.properties.some((property) => ts.isPropertyAssignment(property) && property.name.getText(source) === "schedule")))
18214
- used.add("scheduler");
18215
- if (ts.isIdentifier(node)) {
18216
- const parent = node.parent;
18217
- if (named.has(node.text) && !ts.isImportSpecifier(parent) && isValueUse(node))
18218
- used.add(named.get(node.text));
18219
- }
18220
- if (ts.isPropertyAccessExpression(node)) {
18221
- if (ts.isIdentifier(node.expression)) {
18222
- if (namespaces.has(node.expression.text)) {
18223
- const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18224
- if (cap)
18225
- used.add(cap);
18226
- }
18227
- if (roots.has(node.expression.text)) {
18228
- const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18229
- if (cap)
18230
- used.add(cap);
18231
- }
18232
- }
18233
- if (node.name.text === "schedule" && rootCapability(node.expression, named, namespaces, roots) === "jobs")
18234
- used.add("scheduler");
18235
- }
18236
- ts.forEachChild(node, visit);
18237
- };
18238
- visit(source);
18129
+ for (const { path, content } of updates) {
18130
+ mkdirSync2(dirname3(path), { recursive: true });
18131
+ writeFileSync3(path, content);
18132
+ }
18239
18133
  }
18240
- function isValueUse(node) {
18241
- const p = node.parent;
18242
- return !(ts.isImportSpecifier(p) || ts.isExportSpecifier(p) || ts.isTypeReferenceNode(p) || ts.isTypeQueryNode(p));
18134
+ function synchronize(root = process.cwd(), agent, instructionFile) {
18135
+ const path = join2(root, "backify.json");
18136
+ if (!existsSync3(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
18137
+ const value = JSON.parse(readFileSync2(path, "utf8")), issues = validateConfig(value);
18138
+ if (issues.length) throw new Error(issues.join("; "));
18139
+ if (value.instructionVersion > INSTRUCTION_VERSION) throw new Error("This app uses newer instructions. Install a matching or newer CLI before syncing.");
18140
+ const selected = instructionSelection(root, agent, instructionFile);
18141
+ validateDeliveryInstructions(root);
18142
+ updateInstructions(root, selected.agent, selected.instructionFile);
18143
+ installDeliveryInstructions(root, selected.files);
18144
+ value.agent = selected.agent;
18145
+ value.instructionFile = selected.instructionFile;
18146
+ value.instructionVersion = INSTRUCTION_VERSION;
18147
+ writeFileSync3(path, JSON.stringify(value, null, 2) + "\n");
18243
18148
  }
18244
- function rootCapability(node, named, namespaces, roots) {
18245
- if (ts.isIdentifier(node))
18246
- return named.get(node.text);
18247
- if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
18248
- if (namespaces.has(node.expression.text) || roots.has(node.expression.text))
18249
- return SDK_IMPORT_CAPABILITIES[node.name.text];
18149
+ function sdkInstalled(root = process.cwd()) {
18150
+ try {
18151
+ const pkg = JSON.parse(readFileSync2(join2(root, "package.json"), "utf8"));
18152
+ return Boolean(pkg.dependencies?.[SDK_PACKAGE] || pkg.devDependencies?.[SDK_PACKAGE]);
18153
+ } catch {
18154
+ return false;
18250
18155
  }
18251
- return void 0;
18252
18156
  }
18253
- function repositorySources(root) {
18254
- const result = [];
18255
- const walk = (dir) => {
18256
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
18257
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".backify" || entry.name.startsWith(".git"))
18258
- continue;
18259
- const path = join2(dir, entry.name);
18260
- if (entry.isDirectory())
18261
- walk(path);
18262
- else if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"].includes(extname(path)))
18263
- result.push({ path, content: readFileSync2(path, "utf8") });
18264
- }
18265
- };
18266
- if (existsSync3(root))
18267
- walk(root);
18268
- return result;
18157
+ function installSdk(root = process.cwd()) {
18158
+ if (sdkInstalled(root)) return;
18159
+ const manager = detectPackageManager(root), args2 = manager === "npm" ? ["install", SDK_PACKAGE] : ["add", SDK_PACKAGE], result = spawnSync(manager, args2, { cwd: root, stdio: "inherit", shell: process.platform === "win32" });
18160
+ if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
18269
18161
  }
18270
-
18271
- // src/local-inspection.ts
18272
- function createLocalDataInspector(runtime, options) {
18162
+ function ensurePackageManifest(root) {
18163
+ const path = join2(root, "package.json");
18164
+ if (existsSync3(path)) return;
18165
+ const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "GEMINI.md", "BACKIFY.md", "BACKIFY-WORKFLOW.md", "README.md", "LICENSE", "backify.json"]);
18166
+ if (readdirSync(root, { withFileTypes: true }).some((entry) => !setupFiles.has(entry.name) && !isToolWorkspaceEntry(entry, root))) throw new Error("package.json is missing. Run backify init from the existing application's JavaScript/TypeScript package directory, or start in an empty folder. See https://agent.backify.dev/");
18167
+ const name2 = basename(resolve2(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
18168
+ writeFileSync3(path, JSON.stringify({ name: name2, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
18169
+ }
18170
+ function initialize(appId, root = process.cwd(), install = true, agent, instructionFile) {
18171
+ if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
18172
+ const selected = instructionSelection(root, agent, instructionFile), files = selected.files;
18173
+ validateDeliveryInstructions(root);
18174
+ for (const relative11 of files) if (existsSync3(join2(root, relative11))) updateManagedInstructions(readFileSync2(join2(root, relative11), "utf8"));
18175
+ ensurePackageManifest(root);
18176
+ if (install) installSdk(root);
18177
+ const configPath2 = join2(root, "backify.json"), existing = existsSync3(configPath2) ? JSON.parse(readFileSync2(configPath2, "utf8")) : {};
18178
+ writeFileSync3(configPath2, JSON.stringify({ ...existing, ...createConfig(appId), agent: selected.agent, instructionFile: selected.instructionFile }, null, 2) + "\n");
18179
+ updateInstructions(root, selected.agent, selected.instructionFile);
18180
+ installDeliveryInstructions(root, files);
18181
+ }
18182
+ function doctor(root = process.cwd(), agent, instructionFile) {
18183
+ const issues = [], warnings = [];
18184
+ if (!sdkInstalled(root)) issues.push(`${SDK_PACKAGE} is not installed`);
18185
+ let config;
18186
+ try {
18187
+ config = JSON.parse(readFileSync2(join2(root, "backify.json"), "utf8"));
18188
+ issues.push(...validateConfig(config));
18189
+ } catch {
18190
+ issues.push("backify.json is missing or invalid JSON");
18191
+ }
18192
+ if (config && config.instructionVersion !== INSTRUCTION_VERSION) issues.push(`Repository instruction version ${config.instructionVersion}; CLI version ${INSTRUCTION_VERSION}. Use the matching CLI or run backify sync to upgrade.`);
18193
+ let files = instructionFiles;
18194
+ try {
18195
+ files = selectedInstructionFiles(root, agent, instructionFile);
18196
+ } catch (reason) {
18197
+ issues.push(reason instanceof Error ? reason.message : String(reason));
18198
+ }
18199
+ for (const relative11 of files) {
18200
+ const path = join2(root, relative11);
18201
+ if (!existsSync3(path)) {
18202
+ issues.push(`${relative11} is missing`);
18203
+ continue;
18204
+ }
18205
+ issues.push(...managedInstructionIssues(readFileSync2(path, "utf8")).map((issue) => `${relative11} ${issue}`));
18206
+ }
18207
+ let version = "unknown";
18208
+ try {
18209
+ const pkg = JSON.parse(readFileSync2(join2(root, "package.json"), "utf8"));
18210
+ version = pkg.dependencies?.[SDK_PACKAGE] ?? pkg.devDependencies?.[SDK_PACKAGE] ?? "missing";
18211
+ } catch {
18212
+ }
18213
+ return { valid: issues.length === 0, appId: config?.appId, sdkVersion: version, instructionVersion: config?.instructionVersion, currentInstructionVersion: INSTRUCTION_VERSION, issues, warnings };
18214
+ }
18215
+
18216
+ // src/development.ts
18217
+ function promptMetadata(args2) {
18218
+ const fields5 = [["--model", "model", 200], ["--ai-tool", "aiTool", 200], ["--access-level", "accessLevel", 500]];
18219
+ const metadata = {};
18220
+ for (const [flag, field, maxLength] of fields5) {
18221
+ const index = args2.indexOf(flag);
18222
+ if (index < 0) continue;
18223
+ const value = args2[index + 1]?.trim();
18224
+ if (!value || value.startsWith("--") || value.length > maxLength || /[\u0000-\u001f\u007f-\u009f]|bfcli_[0-9a-f]{64}|--pair\s+[0-9a-f]{64}/i.test(value))
18225
+ throw new Error(`Provide ${flag} with a nonempty value of at most ${maxLength} characters without control characters or credentials.`);
18226
+ metadata[field] = value;
18227
+ }
18228
+ return metadata;
18229
+ }
18230
+ function applicationGuid(appId) {
18231
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(appId)) return appId.toLowerCase();
18232
+ const match = /^app_([0-9a-f]{32})$/i.exec(appId);
18233
+ if (!match) throw new Error("Use the application ID supplied by Backify.");
18234
+ const value = match[1];
18235
+ return [value.slice(0, 8), value.slice(8, 12), value.slice(12, 16), value.slice(16, 20), value.slice(20)].join("-");
18236
+ }
18237
+ function projectApplication(root = process.cwd()) {
18238
+ if (!existsSync4(join3(root, "backify.json"))) throw new Error("Run backify connect --app <application-id>, then backify init.");
18239
+ return JSON.parse(readFileSync3(join3(root, "backify.json"), "utf8")).appId;
18240
+ }
18241
+ function apiAddress(value) {
18242
+ const address = new URL(value);
18243
+ if (address.username || address.password || address.search || address.hash || address.protocol !== "https:" && !(address.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(address.hostname))) throw new Error("Backify API must use HTTPS (HTTP is allowed only on loopback for local development).");
18244
+ return address.toString().replace(/\/$/, "");
18245
+ }
18246
+ var CliAuthenticationRequired = class extends Error {
18247
+ code = "authentication_required";
18248
+ constructor() {
18249
+ super("Backify CLI authentication expired or was revoked. Ask the user to sign in, then run backify connect --reauth. The browser lets them choose 7, 14, 30, or 90 days.");
18250
+ }
18251
+ };
18252
+ function apiClient(session, transport = fetch) {
18253
+ const base = apiAddress(session.apiUrl);
18254
+ return async (path, init) => {
18255
+ if (!path.startsWith("/") || path.startsWith("//")) throw new Error("Invalid API path.");
18256
+ if (session.expiresAt && Date.parse(session.expiresAt) <= Date.now()) throw new CliAuthenticationRequired();
18257
+ const response = await transport(base + path, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4), headers: { "content-type": "application/json", "user-agent": cliUserAgent, "X-Organization-ID": session.organizationId, ...session.token ? { authorization: "Bearer " + session.token } : {}, ...init?.headers } });
18258
+ if (!response.ok) {
18259
+ if (response.status === 401) throw new CliAuthenticationRequired();
18260
+ let message = "Backify request failed (" + response.status + ").";
18261
+ try {
18262
+ const problem = await response.json();
18263
+ message = [problem.code, problem.title].filter(Boolean).join(": ") || message;
18264
+ } catch {
18265
+ }
18266
+ throw new Error(message);
18267
+ }
18268
+ return response.status === 204 ? void 0 : await response.json();
18269
+ };
18270
+ }
18271
+ function saveUserConfig(path, config) {
18272
+ const temporary = path + "." + randomUUID() + ".tmp";
18273
+ try {
18274
+ writeFileSync4(temporary, JSON.stringify(config, null, 2) + "\n", { mode: 384, flag: "wx" });
18275
+ if (process.platform === "win32") {
18276
+ const account = [process.env.USERDOMAIN, process.env.USERNAME].filter(Boolean).join("\\");
18277
+ if (!account) throw new Error("Cannot identify the account to protect CLI credentials.");
18278
+ const secured = spawnSync2("icacls", [temporary, "/inheritance:r", "/grant:r", account + ":(F)"], { windowsHide: true, encoding: "utf8" });
18279
+ if (secured.status !== 0) throw new Error("Could not restrict CLI credential file permissions.");
18280
+ }
18281
+ renameSync(temporary, path);
18282
+ } finally {
18283
+ rmSync(temporary, { force: true });
18284
+ }
18285
+ }
18286
+ function repositoryName(remote) {
18287
+ const match = /^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i.exec(remote);
18288
+ if (!match) throw new Error("Repository must be a credential-free GitHub URL.");
18289
+ return match[1].toLowerCase();
18290
+ }
18291
+ function git(args2, root = process.cwd(), environment = process.env) {
18292
+ const result = spawnSync2("git", args2, { cwd: root, env: environment, encoding: "utf8", windowsHide: true, maxBuffer: 2 * 1024 * 1024 });
18293
+ if (result.status !== 0) throw new Error("Git " + args2[0] + " failed. Check repository access, branch protection, and local changes.");
18294
+ return result.stdout.trim();
18295
+ }
18296
+ function gitEnvironment(repository, token) {
18297
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) throw new Error("Invalid repository.");
18298
+ return { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_CONFIG_COUNT: "3", GIT_CONFIG_KEY_0: "http.https://github.com/" + repository + ".git.extraheader", GIT_CONFIG_VALUE_0: "Authorization: Basic " + Buffer.from("x-access-token:" + token).toString("base64"), GIT_CONFIG_KEY_1: "credential.helper", GIT_CONFIG_VALUE_1: "", GIT_CONFIG_KEY_2: "http.followRedirects", GIT_CONFIG_VALUE_2: "false" };
18299
+ }
18300
+ function validateCloneMerge(source, target) {
18301
+ for (const entry of readdirSync2(source, { withFileTypes: true })) {
18302
+ const destination = join3(target, entry.name);
18303
+ let existing;
18304
+ try {
18305
+ existing = lstatSync2(destination);
18306
+ } catch (reason) {
18307
+ if (reason.code === "ENOENT") continue;
18308
+ throw reason;
18309
+ }
18310
+ if (entry.isDirectory() && existing.isDirectory() && !existing.isSymbolicLink()) validateCloneMerge(join3(source, entry.name), destination);
18311
+ else throw new Error("Clone conflicts with existing workspace entry: " + destination + ". Existing files are never overwritten; reconcile the conflict or use a new folder.");
18312
+ }
18313
+ }
18314
+ async function cloneProject(call2, appId, root = process.cwd()) {
18315
+ const status = await call2("/apps/" + applicationGuid(appId) + "/developer");
18316
+ if (!status.repository || !status.branch || !status.workflowReady) throw new Error("Connect the repository in Backify and wait for workflow setup.");
18317
+ if (readdirSync2(root, { withFileTypes: true }).some((entry) => !isToolWorkspaceEntry(entry, root))) throw new Error("--clone requires an empty directory or a workspace containing only AI tool metadata and default work/outputs directories. Existing application files and Git repositories are never overwritten.");
18318
+ const credential = await call2("/apps/" + applicationGuid(appId) + "/developer/git-credential", { method: "POST" });
18319
+ if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed. Run connect again.");
18320
+ const staging = mkdtempSync(join3(dirname4(resolve3(root)), ".backify-clone-"));
18321
+ try {
18322
+ git(["clone", "--single-branch", "--branch", status.branch, "--", "https://github.com/" + status.repository + ".git", "."], staging, gitEnvironment(status.repository, credential.token));
18323
+ validateCloneMerge(staging, root);
18324
+ for (const entry of readdirSync2(staging)) cpSync(join3(staging, entry), join3(root, entry), { recursive: true, force: false, errorOnExist: true, dereference: false });
18325
+ } finally {
18326
+ if (dirname4(staging) !== dirname4(resolve3(root)) || !staging.startsWith(join3(dirname4(resolve3(root)), ".backify-clone-"))) throw new Error("Refusing to clean up an unexpected clone staging path.");
18327
+ rmSync(staging, { recursive: true, force: true });
18328
+ }
18329
+ }
18330
+ async function pullProject(call2, appId, root = process.cwd()) {
18331
+ if (existsSync4(join3(root, "backify.json")) && applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the connected application.");
18332
+ const prefix = "/apps/" + applicationGuid(appId);
18333
+ const status = await call2(prefix + "/developer");
18334
+ if (!status.repository || !status.branch) throw new Error("Connect the repository and branch in Backify before pulling.");
18335
+ if (resolve3(git(["rev-parse", "--show-toplevel"], root)) !== resolve3(root)) throw new Error("Run pull from the connected repository root.");
18336
+ if (repositoryName(git(["remote", "get-url", "origin"], root)) !== status.repository.toLowerCase()) throw new Error("Local origin does not match the repository connected in Backify.");
18337
+ if (git(["branch", "--show-current"], root) !== status.branch) throw new Error("Local branch does not match the branch connected in Backify. The CLI never switches branches automatically.");
18338
+ git(["check-ref-format", "--branch", status.branch], root);
18339
+ const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
18340
+ if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before pulling.");
18341
+ git(["fetch", "--no-tags", "--", "https://github.com/" + status.repository + ".git", "refs/heads/" + status.branch + ":refs/remotes/origin/" + status.branch], root, gitEnvironment(status.repository, credential.token));
18342
+ const remoteCommit = git(["rev-parse", "FETCH_HEAD"], root);
18343
+ const counts = git(["rev-list", "--left-right", "--count", "HEAD..." + remoteCommit], root).split(/\s+/).map(Number);
18344
+ if (counts.length !== 2 || counts.some((count) => !Number.isSafeInteger(count) || count < 0)) throw new Error("Could not compare local and remote history.");
18345
+ const [ahead, behind] = counts;
18346
+ if (!behind) return { status: "up-to-date", ahead, behind, commit: git(["rev-parse", "HEAD"], root) };
18347
+ if (ahead) return { status: "blocked", reason: "Local and GitHub history have diverged. Review and reconcile them explicitly; no rebase, reset, or force-push was performed.", ahead, behind };
18348
+ if (git(["status", "--porcelain"], root)) return { status: "blocked", reason: "Local changes or untracked files are present. Commit, ignore, or otherwise resolve them explicitly before updating; nothing was stashed or overwritten.", ahead, behind };
18349
+ git(["merge", "--ff-only", "--", remoteCommit], root);
18350
+ return { status: "updated", ahead: 0, behind: 0, commit: git(["rev-parse", "HEAD"], root) };
18351
+ }
18352
+ async function readPrompt(input) {
18353
+ const chunks = [];
18354
+ let size = 0;
18355
+ for await (const chunk of input) {
18356
+ const bytes = Buffer.from(chunk);
18357
+ size += bytes.length;
18358
+ if (size > 128e3) throw new Error("Prompt exceeds the size limit.");
18359
+ chunks.push(bytes);
18360
+ }
18361
+ const text3 = Buffer.concat(chunks).toString("utf8");
18362
+ if (!text3.trim() || text3.length > 32e3) throw new Error("Provide between 1 and 32000 characters of user prompt text.");
18363
+ if (/\bbfcli_[0-9a-f]{64}\b|--pair\s+[0-9a-f]{64}/i.test(text3)) throw new Error("Do not record setup prompts or Backify credentials.");
18364
+ return text3;
18365
+ }
18366
+ function attachmentNames(values) {
18367
+ if (values.length > 50) throw new Error("At most 50 attachment filenames are allowed.");
18368
+ return [...new Set(values.map((value) => {
18369
+ const filename2 = value.replace(/\\/g, "/").split("/").pop();
18370
+ if (!filename2 || filename2 === "." || filename2 === ".." || filename2.length > 255 || /[\x00-\x1f\x7f]/.test(filename2)) throw new Error("Invalid attachment filename.");
18371
+ return filename2;
18372
+ }))];
18373
+ }
18374
+ function localReport(runChecks, root = process.cwd()) {
18375
+ const pkg = JSON.parse(readFileSync3(join3(root, "package.json"), "utf8"));
18376
+ const dependencies = { ...pkg.devDependencies, ...pkg.dependencies };
18377
+ const framework = ["next", "vite", "astro", "@sveltejs/kit", "nuxt", "react", "express"].find((name2) => dependencies[name2]) ?? "unknown";
18378
+ const setup = doctor(root);
18379
+ const checks = [{ name: "backify doctor", status: setup.valid ? "passed" : "failed" }];
18380
+ const manager = detectPackageManager(root);
18381
+ for (const name2 of ["typecheck", "lint", "test", "build"]) {
18382
+ if (!pkg.scripts?.[name2]) continue;
18383
+ const result = runChecks ? spawnSync2(manager, ["run", name2], { cwd: root, stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, CI: "true" }, shell: process.platform === "win32", windowsHide: true, timeout: 10 * 6e4 }) : void 0;
18384
+ checks.push({ name: name2, status: result ? result.status === 0 ? "passed" : "failed" : "not-run" });
18385
+ }
18386
+ let branch = null, commitSha = null, dirty = true;
18387
+ try {
18388
+ branch = git(["branch", "--show-current"], root);
18389
+ commitSha = git(["rev-parse", "HEAD"], root);
18390
+ dirty = Boolean(git(["status", "--porcelain"], root));
18391
+ } catch {
18392
+ }
18393
+ const outputDirectory = ["dist", "build", "out", "."].find((directory2) => existsSync4(join3(root, directory2, "index.html"))) ?? null;
18394
+ return { framework, sdkVersion: dependencies["@backifydev/sdk"] ?? null, branch, commitSha, dirty, buildCommand: pkg.scripts?.build ? manager + " run build" : null, outputDirectory, hasDockerfile: existsSync4(join3(root, "Dockerfile")), checks };
18395
+ }
18396
+ function matchesSource(release2, commit) {
18397
+ return release2?.commitSha === commit || release2?.sourceCommitSha === commit;
18398
+ }
18399
+ function pushSource(appId, commit, branch, destination, root, environment) {
18400
+ const remoteHead = git(["ls-remote", "--", destination, "refs/heads/" + branch], root, environment).split(/\s+/)[0];
18401
+ if (remoteHead === commit) return true;
18402
+ if (remoteHead) {
18403
+ git(["fetch", "--no-tags", "--", destination, "refs/heads/" + branch], root, environment);
18404
+ const fetchedHead = git(["rev-parse", "FETCH_HEAD"], root);
18405
+ if (fetchedHead === commit) return true;
18406
+ const parent = git(["show", "-s", "--format=%P", fetchedHead], root);
18407
+ const message = git(["show", "-s", "--format=%B", fetchedHead], root);
18408
+ if (parent === commit && message === "Backify build " + applicationGuid(appId).replaceAll("-", "") + ": " + commit) return true;
18409
+ if (git(["merge-base", commit, fetchedHead], root) !== fetchedHead) throw new Error("Remote history contains other changes. Run backify pull and reconcile them before shipping; nothing was pushed or overwritten.");
18410
+ }
18411
+ git(["push", "--", destination, commit + ":refs/heads/" + branch], root, environment);
18412
+ return false;
18413
+ }
18414
+ async function waitForPreview(call2, prefix, commit, options = {}) {
18415
+ const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
18416
+ const sleep = options.sleep ?? ((delay) => new Promise((resolve24) => setTimeout(resolve24, delay)));
18417
+ do {
18418
+ const deployments = await call2(prefix + "/deployments");
18419
+ const matching = deployments.find((release2) => release2.kind === "preview" && matchesSource(release2, commit) && !options.previousDeploymentIds?.includes(release2.id));
18420
+ if (matching?.status === "failed") throw new Error("Deployment of this source failed. Run backify status and inspect its deployment steps.");
18421
+ const overview = await call2(prefix + "/releases");
18422
+ if (matchesSource(overview.preview, commit) && overview.preview?.status === "succeeded" && overview.preview.healthResult === "healthy") return overview;
18423
+ const build3 = await call2(prefix + "/developer/build?commitSha=" + commit);
18424
+ if ((options.previousBuildRunId === void 0 || build3.runId !== options.previousBuildRunId) && ["failure", "cancelled", "timed_out", "action_required", "startup_failure"].includes(build3.conclusion ?? "")) throw new Error("GitHub build " + build3.conclusion + ". " + (build3.url ?? "Run backify status for details."));
18425
+ await sleep(options.intervalMs ?? 4e3);
18426
+ } while (Date.now() < deadline);
18427
+ throw new Error("Timed out waiting for this commit's healthy preview. Run backify status; no production release was requested.");
18428
+ }
18429
+ async function ship(call2, appId, options = {}) {
18430
+ const root = options.root ?? process.cwd(), prefix = "/apps/" + applicationGuid(appId);
18431
+ const status = await call2(prefix + "/developer");
18432
+ if (!status.repository || !status.branch || !status.workflowReady) throw new Error("The application's repository and build workflow must be connected in Backify.");
18433
+ if (!doctor(root).valid) throw new Error("Run backify doctor and fix project setup before shipping.");
18434
+ if (applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the application being shipped.");
18435
+ const actualRoot = git(["rev-parse", "--show-toplevel"], root);
18436
+ if (resolve3(actualRoot).toLowerCase() !== resolve3(root).toLowerCase()) throw new Error("Shipping currently requires the application at the repository root.");
18437
+ const origin = git(["remote", "get-url", "origin"], root);
18438
+ const pushOrigin = git(["remote", "get-url", "--push", "origin"], root);
18439
+ if (repositoryName(origin) !== status.repository.toLowerCase() || repositoryName(pushOrigin) !== status.repository.toLowerCase()) throw new Error("Local repository does not match this Backify application.");
18440
+ if (git(["branch", "--show-current"], root) !== status.branch) throw new Error("Use the connected branch: " + status.branch);
18441
+ if (git(["status", "--porcelain"], root)) throw new Error("Commit application changes before shipping. The CLI never stages or commits unrelated files.");
18442
+ const commit = git(["rev-parse", "HEAD"], root);
18443
+ const report = localReport(true, root);
18444
+ await call2(prefix + "/developer/report", { method: "POST", body: JSON.stringify(report) });
18445
+ if (report.checks.some((check) => check.status === "failed")) throw new Error("Checks failed; nothing was pushed.");
18446
+ if (status.hostingTargetType === "backify-cloud" && !report.outputDirectory && !report.hasDockerfile) throw new Error("Backify Cloud requires a static build containing index.html or a Dockerfile serving port 8080.");
18447
+ if (status.hostingTargetType === "azure" && !report.hasDockerfile) throw new Error("Customer Azure requires a repository-root Dockerfile.");
18448
+ if (git(["status", "--porcelain"], root) || git(["rev-parse", "HEAD"], root) !== commit) throw new Error("Checks changed tracked source. Review and commit those changes before shipping.");
18449
+ const previousDeployments = await call2(prefix + "/deployments");
18450
+ const previousBuild = await call2(prefix + "/developer/build?commitSha=" + commit);
18451
+ const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
18452
+ if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before shipping.");
18453
+ const environment = gitEnvironment(status.repository, credential.token);
18454
+ const destination = "https://github.com/" + status.repository + ".git";
18455
+ const alreadyPushed = pushSource(appId, commit, status.branch, destination, root, environment);
18456
+ const currentPreview = previousDeployments.find((release2) => release2.kind === "preview" && matchesSource(release2, commit));
18457
+ if (alreadyPushed && (!currentPreview || currentPreview.status === "failed") && !(matchesSource(status.releases.preview, commit) && status.releases.preview?.status === "succeeded")) await call2(prefix + "/deployments/preview", { method: "POST", body: JSON.stringify({ sourceRevision: "latest" }) });
18458
+ const overview = await waitForPreview(call2, prefix, commit, { timeoutMs: options.timeoutMs, previousDeploymentIds: previousDeployments.filter((deployment) => deployment.status === "failed").map((deployment) => deployment.id), previousBuildRunId: previousBuild.runId ?? null });
18459
+ if (overview.preview.commitSha !== commit) {
18460
+ if (git(["status", "--porcelain"], root) || git(["rev-parse", "HEAD"], root) !== commit) throw new Error("Local work changed while the preview built. Review it before synchronizing the build commit.");
18461
+ const refreshed = await call2(prefix + "/developer/git-credential", { method: "POST" });
18462
+ if (refreshed.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed while building.");
18463
+ git(["fetch", "--", destination, "refs/heads/" + status.branch], root, gitEnvironment(status.repository, refreshed.token));
18464
+ git(["merge", "--ff-only", overview.preview.commitSha], root);
18465
+ }
18466
+ return publishPreview(call2, prefix, overview, options);
18467
+ }
18468
+ async function publishPreview(call2, prefix, overview, options = {}) {
18469
+ if (!overview.preview || overview.preview.status !== "succeeded" || overview.preview.healthResult !== "healthy") throw new Error("Only a verified healthy preview can be published.");
18470
+ const preview = overview.preview;
18471
+ if (options.previewOnly) return { status: "preview-ready", url: preview.url };
18472
+ if (overview.policy.requirePreviewTesting && !preview.testedAt) return { status: "testing-required", url: preview.url };
18473
+ if (overview.live?.previewDeploymentId === preview.id && overview.live.status === "succeeded") return { status: "live", url: overview.live.url };
18474
+ const promotion = await call2(prefix + "/deployments/promote", { method: "POST", headers: { "Idempotency-Key": "cli-ship-" + preview.id }, body: JSON.stringify({ previewDeploymentId: preview.id, tested: false }) });
18475
+ if (promotion.status === "awaiting-approval" || promotion.approvalId) return { status: "awaiting-approval", url: preview.url };
18476
+ if (!promotion.operationId) throw new Error("Publication returned no operation to verify. Run backify status.");
18477
+ const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
18478
+ do {
18479
+ const operation = await call2("/operations/" + promotion.operationId);
18480
+ if (operation.status === "failed" || operation.status === "cancelled") throw new Error(operation.errorMessage ?? "Production deployment did not complete.");
18481
+ if (operation.status === "succeeded") {
18482
+ const releases = await call2(prefix + "/releases");
18483
+ if (releases.live?.previewDeploymentId !== preview.id || releases.live.status !== "succeeded" || releases.live.healthResult !== "healthy") throw new Error("Production did not activate this healthy release. Run backify status.");
18484
+ return { status: "live", url: releases.live.url };
18485
+ }
18486
+ await new Promise((resolve24) => setTimeout(resolve24, 4e3));
18487
+ } while (Date.now() < deadline);
18488
+ throw new Error("Publication is still pending. Run backify status; deployment success has not been verified.");
18489
+ }
18490
+
18491
+ // src/browser-auth.ts
18492
+ async function browserAuthenticate(options) {
18493
+ const auth = new URL(apiAddress(options.authUrl));
18494
+ const state = randomBytes(32).toString("base64url");
18495
+ let resolveSession = () => {
18496
+ };
18497
+ let rejectSession = () => {
18498
+ };
18499
+ const completed = new Promise((resolve24, reject) => {
18500
+ resolveSession = resolve24;
18501
+ rejectSession = reject;
18502
+ });
18503
+ let accepted = false;
18504
+ const server = createServer((request, response) => {
18505
+ if (request.headers.origin !== auth.origin) {
18506
+ response.writeHead(403);
18507
+ response.end();
18508
+ return;
18509
+ }
18510
+ response.setHeader("Access-Control-Allow-Origin", auth.origin);
18511
+ response.setHeader("Vary", "Origin");
18512
+ response.setHeader("Cache-Control", "no-store");
18513
+ if (accepted) {
18514
+ response.writeHead(409);
18515
+ response.end();
18516
+ return;
18517
+ }
18518
+ if (request.url !== "/callback") {
18519
+ response.writeHead(404);
18520
+ response.end();
18521
+ return;
18522
+ }
18523
+ if (request.method === "OPTIONS") {
18524
+ response.writeHead(204, { "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "content-type", "Access-Control-Allow-Private-Network": "true" });
18525
+ response.end();
18526
+ return;
18527
+ }
18528
+ if (request.method !== "POST" || !request.headers["content-type"]?.startsWith("application/json")) {
18529
+ response.writeHead(405);
18530
+ response.end();
18531
+ return;
18532
+ }
18533
+ let raw = "";
18534
+ request.setEncoding("utf8");
18535
+ request.on("data", (part) => {
18536
+ raw += part;
18537
+ if (raw.length > 65536) request.destroy();
18538
+ });
18539
+ request.on("end", () => {
18540
+ try {
18541
+ const value = JSON.parse(raw);
18542
+ if (value.state !== state || typeof value.token !== "string" || !value.token || value.token.length > 32768 || typeof value.organizationId !== "string" || !/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(value.organizationId) || options.organizationId && value.organizationId !== options.organizationId) throw new Error("Invalid callback");
18543
+ if (options.applicationId && (value.applicationId !== options.applicationId || !/^bfcli_[0-9a-f]{64}$/i.test(value.token) || typeof value.expiresAt !== "string" || !Number.isFinite(Date.parse(value.expiresAt)) || Date.parse(value.expiresAt) <= Date.now() || Date.parse(value.expiresAt) > Date.now() + 90 * 864e5 + 6e4)) throw new Error("Invalid application session");
18544
+ const session = { token: value.token, organizationId: value.organizationId, apiUrl: options.apiUrl, ...options.applicationId ? { expiresAt: value.expiresAt } : {} };
18545
+ options.save(session);
18546
+ accepted = true;
18547
+ response.writeHead(200, { "Content-Type": "text/plain" });
18548
+ response.end("Backify CLI connected. You can close this tab.");
18549
+ resolveSession(session);
18550
+ } catch {
18551
+ response.writeHead(400);
18552
+ response.end("The CLI could not accept or save this connection. Retry or start a new CLI sign-in.");
18553
+ }
18554
+ });
18555
+ });
18556
+ server.on("error", rejectSession);
18557
+ server.listen(0, "127.0.0.1", () => {
18558
+ try {
18559
+ const address = server.address();
18560
+ if (!address || typeof address === "string") throw new Error("Could not start the local CLI callback.");
18561
+ auth.searchParams.set("cli_callback", "http://127.0.0.1:" + address.port + "/callback");
18562
+ auth.searchParams.set("cli_state", state);
18563
+ if (options.applicationId) auth.searchParams.set("cli_app", options.applicationId);
18564
+ if (options.organizationId) auth.searchParams.set("cli_organization", options.organizationId);
18565
+ auth.searchParams.set("cli_device", JSON.stringify(cliDevice()));
18566
+ options.openBrowser(auth.toString());
18567
+ } catch (error) {
18568
+ rejectSession(error instanceof Error ? error : new Error("Could not open sign-in."));
18569
+ }
18570
+ });
18571
+ const timeout = setTimeout(() => rejectSession(new Error("Backify sign-in timed out. Ask the user to sign in and run backify connect --reauth again.")), options.timeoutMs ?? 5 * 6e4);
18572
+ try {
18573
+ return await completed;
18574
+ } finally {
18575
+ clearTimeout(timeout);
18576
+ server.close();
18577
+ server.closeAllConnections();
18578
+ }
18579
+ }
18580
+ function reconnectingApiClient(session, reconnect, transport = fetch) {
18581
+ let current = session;
18582
+ let pending;
18583
+ return async (path, init) => {
18584
+ const attempted = current;
18585
+ try {
18586
+ return await apiClient(attempted, transport)(path, init);
18587
+ } catch (error) {
18588
+ if (!(error instanceof CliAuthenticationRequired)) throw error;
18589
+ if (current === attempted) {
18590
+ pending ??= reconnect().then((updated) => {
18591
+ current = updated;
18592
+ return updated;
18593
+ }).finally(() => {
18594
+ pending = void 0;
18595
+ });
18596
+ await pending;
18597
+ }
18598
+ return apiClient(current, transport)(path, init);
18599
+ }
18600
+ };
18601
+ }
18602
+
18603
+ // src/local-runtime.ts
18604
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
18605
+ import { chmodSync, existsSync as existsSync5, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "node:fs";
18606
+ import { dirname as dirname5, join as join4, resolve as resolve4, sep } from "node:path";
18607
+ import { homedir } from "node:os";
18608
+ import { spawnSync as spawnSync3 } from "node:child_process";
18609
+
18610
+ // src/local-state.ts
18611
+ import { renameSync as renameSync2 } from "node:fs";
18612
+ function replaceLocalFile(source, destination, rename, platform2, wait) {
18613
+ for (let attempt = 0; ; attempt++) {
18614
+ try {
18615
+ rename(source, destination);
18616
+ return;
18617
+ } catch (error) {
18618
+ if (platform2 !== "win32" || attempt >= 4 || !(error instanceof Error) || !("code" in error) || !["EPERM", "EACCES", "EBUSY"].includes(String(error.code))) throw error;
18619
+ wait(10 * 2 ** attempt);
18620
+ }
18621
+ }
18622
+ }
18623
+ function renameLocalFile(source, destination) {
18624
+ replaceLocalFile(source, destination, renameSync2, process.platform, (milliseconds) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds));
18625
+ }
18626
+
18627
+ // src/local-runtime.ts
18628
+ var maxArchiveBytes = 256 * 1024 * 1024;
18629
+ var maxExpandedBytes = 512 * 1024 * 1024;
18630
+ var hash = (bytes) => createHash2("sha256").update(bytes).digest("hex");
18631
+ function runtimeIdentifier(platform2 = process.platform, architecture = process.arch) {
18632
+ const system = { win32: "win", darwin: "osx", linux: "linux" }[platform2];
18633
+ if (!system || !["x64", "arm64"].includes(architecture)) throw new Error("Local runtime supports Windows, macOS and Linux on x64/arm64");
18634
+ return system + "-" + architecture;
18635
+ }
18636
+ function checkedPath(root, filename2) {
18637
+ const target = resolve4(root, filename2);
18638
+ if (target !== resolve4(root) && !target.startsWith(resolve4(root) + sep)) throw new Error("Runtime path escapes its installation");
18639
+ let current = target;
18640
+ while (true) {
18641
+ if (existsSync5(current) && lstatSync3(current).isSymbolicLink()) throw new Error("Runtime installation must not follow links");
18642
+ if (dirname5(current) === current) break;
18643
+ current = dirname5(current);
18644
+ }
18645
+ return target;
18646
+ }
18647
+ function verifyInstallationFiles(root, expected, directory2 = root) {
18648
+ for (const entry of readdirSync3(directory2, { withFileTypes: true })) {
18649
+ const path = checkedPath(root, join4(directory2, entry.name));
18650
+ if (entry.isDirectory()) verifyInstallationFiles(root, expected, path);
18651
+ else if (!entry.isFile() || !Object.hasOwn(expected, path.slice(root.length + 1).split(sep).join("/"))) throw new Error("Installed runtime contains unexpected files; reinstall a reviewed version");
18652
+ }
18653
+ }
18654
+ function installedLocalRuntime(base = join4(homedir(), ".backify")) {
18655
+ const pointer = checkedPath(base, "runtime.json");
18656
+ if (!existsSync5(pointer)) return void 0;
18657
+ const installed = JSON.parse(readFileSync4(pointer, "utf8"));
18658
+ if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(installed.version) || installed.rid !== runtimeIdentifier() || !installed.files || Object.keys(installed.files).length > 3e3) throw new Error("Invalid local runtime installation receipt");
18659
+ const root = checkedPath(base, join4("runtimes", installed.version, installed.rid));
18660
+ verifyInstallationFiles(root, installed.files);
18661
+ for (const [filename2, digest4] of Object.entries(installed.files)) {
18662
+ const path = checkedPath(root, filename2);
18663
+ if (!/^[a-f0-9]{64}$/.test(digest4) || !lstatSync3(path).isFile() || lstatSync3(path).size > maxArchiveBytes || hash(readFileSync4(path)) !== digest4) throw new Error("Installed runtime bytes changed; reinstall a reviewed version");
18664
+ }
18665
+ const executableName = installed.rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
18666
+ if (!installed.files["Backify.Hcr.dll"] || !installed.files[executableName]) throw new Error("Runtime archive requires the self-contained HCR executable and DLL");
18667
+ return { path: join4(root, "Backify.Hcr.dll"), executable: join4(root, executableName), version: installed.version, rid: installed.rid };
18668
+ }
18669
+ function installLocalRuntime(archive, options) {
18670
+ if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(options.version) || !/^[a-f0-9]{64}$/.test(options.sha256)) throw new Error("Provide a release version vX.Y.Z and an independently reviewed SHA-256");
18671
+ if (archive.byteLength > maxArchiveBytes || hash(archive) !== options.sha256) throw new Error("Runtime archive digest does not match the reviewed SHA-256");
18672
+ let expanded = 0, count = 0;
18673
+ const files = unzipSync(archive, { filter: (file) => {
18674
+ if (++count > 3e3 || (expanded += file.originalSize) > maxExpandedBytes || file.originalSize > maxArchiveBytes) throw new Error("Runtime archive exceeds extraction limits");
18675
+ if (!file.name || file.name.includes("\\") || file.name.startsWith("/") || file.name.includes(":") || file.name.split("/").some((part) => part === "." || part === "..")) throw new Error("Runtime archive contains an unsafe path");
18676
+ return !file.name.endsWith("/");
18677
+ } });
18678
+ const rid = runtimeIdentifier();
18679
+ const executable = rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
18680
+ if (!files["Backify.Hcr.dll"] || !files[executable] || !files["sql/events-v1.sql"] || !files["sql/jobs-v1.sql"]) throw new Error("Runtime archive is missing required host assets");
18681
+ const base = options.base ?? join4(homedir(), ".backify");
18682
+ const parent = checkedPath(base, join4("runtimes", options.version));
18683
+ mkdirSync3(parent, { recursive: true, mode: 448 });
18684
+ const destination = checkedPath(parent, rid);
18685
+ const receipt = { version: options.version, rid, files: Object.fromEntries(Object.entries(files).map(([name2, bytes]) => [name2, hash(bytes)])) };
18686
+ if (!existsSync5(destination)) {
18687
+ const staging = checkedPath(parent, ".install-" + randomUUID2());
18688
+ mkdirSync3(staging, { mode: 448 });
18689
+ for (const [name2, bytes] of Object.entries(files)) {
18690
+ const filename2 = checkedPath(staging, name2);
18691
+ mkdirSync3(dirname5(filename2), { recursive: true, mode: 448 });
18692
+ writeFileSync5(filename2, bytes, { flag: "wx", mode: name2 === executable ? 448 : 384 });
18693
+ }
18694
+ if (process.platform !== "win32") chmodSync(join4(staging, executable), 448);
18695
+ renameLocalFile(staging, destination);
18696
+ } else {
18697
+ verifyInstallationFiles(destination, receipt.files);
18698
+ for (const [name2, digest4] of Object.entries(receipt.files)) if (hash(readFileSync4(checkedPath(destination, name2))) !== digest4) throw new Error("Runtime version already exists with different bytes; never overwrite an installation");
18699
+ }
18700
+ const pointer = checkedPath(base, "runtime.json");
18701
+ const temporary = checkedPath(base, ".runtime-" + randomUUID2() + ".json");
18702
+ writeFileSync5(temporary, JSON.stringify(receipt), { flag: "wx", mode: 384 });
18703
+ renameLocalFile(temporary, pointer);
18704
+ return installedLocalRuntime(base);
18705
+ }
18706
+ async function downloadLocalRuntime(version) {
18707
+ if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(version)) throw new Error("Provide a release version vX.Y.Z");
18708
+ let url = new URL("https://github.com/frejnorling/backify/releases/download/runtime-" + version + "/backify-hcr-" + runtimeIdentifier() + ".zip");
18709
+ for (let redirects = 0; redirects < 5; redirects++) {
18710
+ if (url.protocol !== "https:" || !["github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com"].includes(url.hostname) || url.username || url.password) throw new Error("Runtime download left the approved release hosts");
18711
+ const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(6e4) });
18712
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
18713
+ url = new URL(response.headers.get("location") ?? "", url);
18714
+ continue;
18715
+ }
18716
+ if (!response.ok || !response.body) throw new Error("The requested runtime release is unavailable");
18717
+ const chunks = [];
18718
+ let size = 0;
18719
+ for await (const chunk of response.body) {
18720
+ size += chunk.byteLength;
18721
+ if (size > maxArchiveBytes) throw new Error("Runtime archive exceeds its size limit");
18722
+ chunks.push(chunk);
18723
+ }
18724
+ return Buffer.concat(chunks);
18725
+ }
18726
+ throw new Error("Too many runtime download redirects");
18727
+ }
18728
+ function localPrerequisites() {
18729
+ const runtime = installedLocalRuntime();
18730
+ const directory2 = process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join4(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
18731
+ const checks = ["initdb", "pg_ctl", "psql"].map((name2) => {
18732
+ const executable = directory2 ? join4(directory2, name2 + (process.platform === "win32" ? ".exe" : "")) : name2;
18733
+ const result = spawnSync3(executable, ["--version"], { windowsHide: true, timeout: 1e4, encoding: "utf8" });
18734
+ return { name: name2, available: result.status === 0 };
18735
+ });
18736
+ return { rid: runtimeIdentifier(), runtime: runtime ? { installed: true, version: runtime.version } : { installed: false }, postgres: checks, ready: !!runtime && checks.every((check) => check.available) };
18737
+ }
18738
+
18739
+ // src/workload.ts
18740
+ import { build as build2 } from "esbuild";
18741
+ import { createHash as createHash14 } from "node:crypto";
18742
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync19, realpathSync as realpathSync6, readdirSync as readdirSync6, writeFileSync as writeFileSync11 } from "node:fs";
18743
+ import { dirname as dirname13, isAbsolute as isAbsolute14, join as join13, relative as relative8, resolve as resolve18 } from "node:path";
18744
+
18745
+ // src/local-development.ts
18746
+ import { context as context2 } from "esbuild";
18747
+ import { spawn as spawn4 } from "node:child_process";
18748
+ import { randomBytes as randomBytes6, randomUUID as randomUUID9, createHash as createHash13 } from "node:crypto";
18749
+ import { existsSync as existsSync11, lstatSync as lstatSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync18, realpathSync as realpathSync5, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "node:fs";
18750
+ import { resolve as resolve17, join as join12, relative as relative7, isAbsolute as isAbsolute13, dirname as dirname12 } from "node:path";
18751
+
18752
+ // ../analyzer/dist/index.js
18753
+ import ts2 from "typescript";
18754
+ import { readFileSync as readFileSync5, readdirSync as readdirSync4, existsSync as existsSync6 } from "node:fs";
18755
+ import { join as join5, extname } from "node:path";
18756
+
18757
+ // ../analyzer/dist/authentication.js
18758
+ import ts from "typescript";
18759
+ function analyzeAuthentication(files) {
18760
+ const providers = /* @__PURE__ */ new Set();
18761
+ const dynamicFiles = /* @__PURE__ */ new Set();
18762
+ let used = false;
18763
+ for (const file of files) {
18764
+ const sourcePath = file.path.replaceAll(String.fromCharCode(92), "/");
18765
+ const source = ts.createSourceFile(sourcePath, file.content, ts.ScriptTarget.Latest, true, file.path.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
18766
+ const program = ts.createProgram([sourcePath], { noLib: true, noResolve: true, allowJs: true }, {
18767
+ getSourceFile: (path) => path === sourcePath ? source : void 0,
18768
+ getDefaultLibFileName: () => "",
18769
+ writeFile: () => {
18770
+ },
18771
+ getCurrentDirectory: () => "/",
18772
+ getDirectories: () => [],
18773
+ fileExists: (path) => path === sourcePath,
18774
+ readFile: (path) => path === sourcePath ? file.content : void 0,
18775
+ getCanonicalFileName: (path) => path,
18776
+ useCaseSensitiveFileNames: () => true,
18777
+ getNewLine: () => String.fromCharCode(10)
18778
+ });
18779
+ const checker = program.getTypeChecker();
18780
+ const authSymbols = /* @__PURE__ */ new Set(), namespaceSymbols = /* @__PURE__ */ new Set(), rootSymbols = /* @__PURE__ */ new Set(), factorySymbols = /* @__PURE__ */ new Set();
18781
+ const contains = (symbols, node) => {
18782
+ const symbol = checker.getSymbolAtLocation(node);
18783
+ return symbol !== void 0 && symbols.has(symbol);
18784
+ };
18785
+ for (const statement of source.statements) {
18786
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !["@backifydev/sdk", "@backifydev/sdk/server"].includes(statement.moduleSpecifier.text) || statement.importClause?.isTypeOnly)
18787
+ continue;
18788
+ const bindings = statement.importClause?.namedBindings;
18789
+ if (bindings && ts.isNamespaceImport(bindings)) {
18790
+ const symbol = checker.getSymbolAtLocation(bindings.name);
18791
+ if (symbol)
18792
+ namespaceSymbols.add(symbol);
18793
+ }
18794
+ if (bindings && ts.isNamedImports(bindings))
18795
+ for (const item of bindings.elements) {
18796
+ if (item.isTypeOnly)
18797
+ continue;
18798
+ const imported = (item.propertyName ?? item.name).text, symbol = checker.getSymbolAtLocation(item.name);
18799
+ if (!symbol)
18800
+ continue;
18801
+ if (imported === "auth")
18802
+ authSymbols.add(symbol);
18803
+ if (imported === "backify")
18804
+ rootSymbols.add(symbol);
18805
+ if (imported === "createServerClient")
18806
+ factorySymbols.add(symbol);
18807
+ }
18808
+ }
18809
+ const isNamespace = (expression) => ts.isIdentifier(expression) && contains(namespaceSymbols, expression);
18810
+ const isFactory = (expression) => ts.isIdentifier(expression) ? contains(factorySymbols, expression) : ts.isPropertyAccessExpression(expression) && expression.name.text === "createServerClient" && isNamespace(expression.expression);
18811
+ const isRoot = (expression) => ts.isIdentifier(expression) ? contains(rootSymbols, expression) : ts.isCallExpression(expression) ? isFactory(expression.expression) : ts.isPropertyAccessExpression(expression) && expression.name.text === "backify" && isNamespace(expression.expression);
18812
+ const isAuth = (expression) => ts.isIdentifier(expression) ? contains(authSymbols, expression) : ts.isPropertyAccessExpression(expression) && expression.name.text === "auth" && (isRoot(expression.expression) || isNamespace(expression.expression));
18813
+ const register = (symbols, identifier2) => {
18814
+ const symbol = checker.getSymbolAtLocation(identifier2);
18815
+ if (symbol)
18816
+ symbols.add(symbol);
18817
+ };
18818
+ const collectRoots = (node) => {
18819
+ if (ts.isVariableDeclaration(node) && node.initializer) {
18820
+ if (ts.isIdentifier(node.name)) {
18821
+ if (isRoot(node.initializer))
18822
+ register(rootSymbols, node.name);
18823
+ if (isAuth(node.initializer))
18824
+ register(authSymbols, node.name);
18825
+ } else if (ts.isObjectBindingPattern(node.name)) {
18826
+ for (const binding of node.name.elements) {
18827
+ if (!ts.isIdentifier(binding.name) || binding.dotDotDotToken || binding.initializer)
18828
+ continue;
18829
+ const property = binding.propertyName ?? binding.name;
18830
+ if (!ts.isIdentifier(property) && !ts.isStringLiteral(property))
18831
+ continue;
18832
+ if (property.text === "auth" && (isRoot(node.initializer) || isNamespace(node.initializer)))
18833
+ register(authSymbols, binding.name);
18834
+ if (property.text === "backify" && isNamespace(node.initializer))
18835
+ register(rootSymbols, binding.name);
18836
+ if (property.text === "createServerClient" && isNamespace(node.initializer))
18837
+ register(factorySymbols, binding.name);
18838
+ }
18839
+ }
18840
+ }
18841
+ ts.forEachChild(node, collectRoots);
18842
+ };
18843
+ let previousSize = -1;
18844
+ while (previousSize !== authSymbols.size + rootSymbols.size + factorySymbols.size) {
18845
+ previousSize = authSymbols.size + rootSymbols.size + factorySymbols.size;
18846
+ collectRoots(source);
18847
+ }
18848
+ const visit = (node) => {
18849
+ if (ts.isIdentifier(node) && contains(authSymbols, node) && !ts.isImportSpecifier(node.parent) && !ts.isTypeReferenceNode(node.parent) && !ts.isTypeQueryNode(node.parent) && !ts.isExportSpecifier(node.parent))
18850
+ used = true;
18851
+ if (ts.isPropertyAccessExpression(node) && isAuth(node))
18852
+ used = true;
18853
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "signIn" && isAuth(node.expression.expression)) {
18854
+ const options = node.arguments[0];
18855
+ if (options) {
18856
+ const property = ts.isObjectLiteralExpression(options) ? options.properties.find((value) => ts.isPropertyAssignment(value) && (ts.isIdentifier(value.name) || ts.isStringLiteral(value.name)) && value.name.text === "provider") : void 0;
18857
+ if (property && ts.isPropertyAssignment(property) && ts.isStringLiteral(property.initializer) && (!ts.isObjectLiteralExpression(options) || !options.properties.some(ts.isSpreadAssignment)))
18858
+ providers.add(property.initializer.text);
18859
+ else
18860
+ dynamicFiles.add(file.path);
18861
+ }
18862
+ }
18863
+ ts.forEachChild(node, visit);
18864
+ };
18865
+ visit(source);
18866
+ }
18867
+ return { used, providers: [...providers].sort(), dynamicFiles: [...dynamicFiles].sort() };
18868
+ }
18869
+
18870
+ // ../analyzer/dist/index.js
18871
+ var bypass = { "@azure/storage-blob": { capability: "storage", replacement: "storage" }, "@aws-sdk/client-s3": { capability: "storage", replacement: "storage" }, "firebase-admin": { capability: "database", replacement: "db" }, "@supabase/supabase-js": { capability: "database", replacement: "db" }, "@azure/service-bus": { capability: "jobs", replacement: "jobs" } };
18872
+ function analyzeSources(files, options = {}) {
18873
+ const used = /* @__PURE__ */ new Set(), diagnostics = [];
18874
+ for (const file of files)
18875
+ analyzeFile(file, used, diagnostics);
18876
+ if (used.has("http"))
18877
+ used.add("events");
18878
+ const authentication = analyzeAuthentication(files);
18879
+ if (authentication.used)
18880
+ used.add("auth");
18881
+ else
18882
+ used.delete("auth");
18883
+ const capabilities = Object.fromEntries([...used].sort().map((name2) => [name2, { version: CAPABILITY_REGISTRY[name2].version }]));
18884
+ if (capabilities.auth && (authentication.providers.length || authentication.dynamicFiles.length))
18885
+ capabilities.auth.configuration = { providers: authentication.providers, dynamicProviders: authentication.dynamicFiles.length > 0 };
18886
+ for (const file of authentication.dynamicFiles)
18887
+ diagnostics.push({ severity: "warning", code: "dynamic-auth-provider", file, docs: CAPABILITY_REGISTRY.auth.docs, message: "Declare every possible authentication provider in backify.yaml; dynamic provider selection cannot be provisioned from source alone." });
18888
+ for (const provider of authentication.providers)
18889
+ if (!["entra", "google", "email"].includes(provider))
18890
+ diagnostics.push({ severity: "error", code: "unsupported-auth-provider", docs: CAPABILITY_REGISTRY.auth.docs, message: "Authentication provider is not implemented: " + provider });
18891
+ return { schemaVersion: 1, applicationId: options.applicationId ?? "unknown", commit: options.commit ?? "unknown", sdkVersion: options.sdkVersion ?? "unknown", capabilities, diagnostics };
18892
+ }
18893
+ function analyzeFile(file, used, diagnostics) {
18894
+ const source = ts2.createSourceFile(file.path, file.content, ts2.ScriptTarget.Latest, true, file.path.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS), named = /* @__PURE__ */ new Map(), namespaces = /* @__PURE__ */ new Set(), roots = /* @__PURE__ */ new Set(), factories = /* @__PURE__ */ new Set();
18895
+ source.forEachChild((node) => {
18896
+ if (!ts2.isImportDeclaration(node) || !ts2.isStringLiteral(node.moduleSpecifier))
18897
+ return;
18898
+ const module = node.moduleSpecifier.text;
18899
+ if (bypass[module]) {
18900
+ const rule = bypass[module];
18901
+ diagnostics.push({ severity: "warning", code: "direct-provider-sdk", file: file.path, docs: CAPABILITY_REGISTRY[rule.capability].docs, message: `Direct provider dependency '${module}' detected. Use import { ${rule.replacement} } from "@backifydev/sdk" so Backify can provision and bind ${rule.capability}.` });
18902
+ }
18903
+ if (module !== "@backifydev/sdk" && module !== "@backifydev/sdk/server" || !node.importClause)
18904
+ return;
18905
+ const bindings = node.importClause.namedBindings;
18906
+ if (!bindings)
18907
+ return;
18908
+ if (ts2.isNamespaceImport(bindings))
18909
+ namespaces.add(bindings.name.text);
18910
+ else if (ts2.isNamedImports(bindings))
18911
+ for (const element of bindings.elements) {
18912
+ const imported = (element.propertyName ?? element.name).text, local = element.name.text;
18913
+ if (imported === "createServerClient")
18914
+ factories.add(local);
18915
+ else if (imported === "backify")
18916
+ roots.add(local);
18917
+ else {
18918
+ const cap = SDK_IMPORT_CAPABILITIES[imported];
18919
+ if (cap)
18920
+ named.set(local, cap);
18921
+ }
18922
+ }
18923
+ });
18924
+ const visit = (node) => {
18925
+ if (ts2.isVariableDeclaration(node) && ts2.isIdentifier(node.name) && node.initializer && ts2.isCallExpression(node.initializer) && ts2.isIdentifier(node.initializer.expression) && factories.has(node.initializer.expression.text))
18926
+ roots.add(node.name.text);
18927
+ if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && named.get(node.expression.text) === "jobs" && node.arguments.some((argument) => ts2.isObjectLiteralExpression(argument) && argument.properties.some((property) => ts2.isPropertyAssignment(property) && property.name.getText(source) === "schedule")))
18928
+ used.add("scheduler");
18929
+ if (ts2.isIdentifier(node)) {
18930
+ const parent = node.parent;
18931
+ if (named.has(node.text) && !ts2.isImportSpecifier(parent) && isValueUse(node))
18932
+ used.add(named.get(node.text));
18933
+ }
18934
+ if (ts2.isPropertyAccessExpression(node)) {
18935
+ if (ts2.isIdentifier(node.expression)) {
18936
+ if (namespaces.has(node.expression.text)) {
18937
+ const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18938
+ if (cap)
18939
+ used.add(cap);
18940
+ }
18941
+ if (roots.has(node.expression.text)) {
18942
+ const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18943
+ if (cap)
18944
+ used.add(cap);
18945
+ }
18946
+ }
18947
+ if (node.name.text === "schedule" && rootCapability(node.expression, named, namespaces, roots) === "jobs")
18948
+ used.add("scheduler");
18949
+ }
18950
+ ts2.forEachChild(node, visit);
18951
+ };
18952
+ visit(source);
18953
+ }
18954
+ function isValueUse(node) {
18955
+ const p = node.parent;
18956
+ return !(ts2.isImportSpecifier(p) || ts2.isExportSpecifier(p) || ts2.isTypeReferenceNode(p) || ts2.isTypeQueryNode(p));
18957
+ }
18958
+ function rootCapability(node, named, namespaces, roots) {
18959
+ if (ts2.isIdentifier(node))
18960
+ return named.get(node.text);
18961
+ if (ts2.isPropertyAccessExpression(node) && ts2.isIdentifier(node.expression)) {
18962
+ if (namespaces.has(node.expression.text) || roots.has(node.expression.text))
18963
+ return SDK_IMPORT_CAPABILITIES[node.name.text];
18964
+ }
18965
+ return void 0;
18966
+ }
18967
+ function repositorySources(root) {
18968
+ const result = [];
18969
+ const walk = (dir) => {
18970
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
18971
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".backify" || entry.name.startsWith(".git"))
18972
+ continue;
18973
+ const path = join5(dir, entry.name);
18974
+ if (entry.isDirectory())
18975
+ walk(path);
18976
+ else if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"].includes(extname(path)))
18977
+ result.push({ path, content: readFileSync5(path, "utf8") });
18978
+ }
18979
+ };
18980
+ if (existsSync6(root))
18981
+ walk(root);
18982
+ return result;
18983
+ }
18984
+
18985
+ // src/local-inspection.ts
18986
+ function createLocalDataInspector(runtime, options) {
18273
18987
  const tableName = (value) => typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value) && !/^__backify/i.test(value);
18274
18988
  const bucketName = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(value);
18275
18989
  if (typeof window !== "undefined" || options.buckets.length > 100 || options.buckets.some((name2) => !bucketName(name2)) || new Set(options.buckets).size !== options.buckets.length) throw new Error("invalid_local_inspection");
@@ -18472,9 +19186,9 @@ function createLocalNativeEvents(local, native) {
18472
19186
 
18473
19187
  // src/local-job-executor.ts
18474
19188
  import { spawn as spawn2 } from "node:child_process";
18475
- import { createHash as createHash9 } from "node:crypto";
18476
- import { lstatSync as lstatSync9, readFileSync as readFileSync9 } from "node:fs";
18477
- import { isAbsolute as isAbsolute7, join as join5, relative as relative3, resolve as resolve9 } from "node:path";
19189
+ import { createHash as createHash10 } from "node:crypto";
19190
+ import { lstatSync as lstatSync11, readFileSync as readFileSync12 } from "node:fs";
19191
+ import { isAbsolute as isAbsolute8, join as join8, relative as relative3, resolve as resolve11 } from "node:path";
18478
19192
 
18479
19193
  // ../sdk/dist/schedules.js
18480
19194
  var import_cron_parser = __toESM(require_dist2(), 1);
@@ -18685,28 +19399,28 @@ function createApplicationAuthProvider(source, options) {
18685
19399
  }
18686
19400
 
18687
19401
  // ../sdk/dist/application-auth-state.js
18688
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
18689
- import { closeSync, constants, fstatSync, fsyncSync, lstatSync as lstatSync2, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
18690
- import { dirname as dirname3, isAbsolute, parse, resolve as resolve3, sep as sep2 } from "node:path";
19402
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
19403
+ import { closeSync, constants, fstatSync, fsyncSync, lstatSync as lstatSync4, openSync, readSync, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
19404
+ import { dirname as dirname6, isAbsolute as isAbsolute2, parse, resolve as resolve5, sep as sep2 } from "node:path";
18691
19405
  function unavailable2() {
18692
19406
  throw new ApplicationAuthenticationError("application_identity_unavailable");
18693
19407
  }
18694
19408
  function safePath(filename2) {
18695
- if (!isAbsolute(filename2) || resolve3(filename2) !== filename2 || filename2 === parse(filename2).root)
19409
+ if (!isAbsolute2(filename2) || resolve5(filename2) !== filename2 || filename2 === parse(filename2).root)
18696
19410
  unavailable2();
18697
19411
  if (process.platform === "win32" && filename2.slice(parse(filename2).root.length).split(sep2).some((component) => component.includes(":") || /[. ]$/.test(component) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(component)))
18698
19412
  unavailable2();
18699
- const directory2 = dirname3(filename2);
19413
+ const directory2 = dirname6(filename2);
18700
19414
  let ancestor = directory2;
18701
19415
  for (; ; ) {
18702
- const metadata = lstatSync2(ancestor);
19416
+ const metadata = lstatSync4(ancestor);
18703
19417
  if (!metadata.isDirectory() || metadata.isSymbolicLink())
18704
19418
  unavailable2();
18705
19419
  if (ancestor === parse(ancestor).root)
18706
19420
  break;
18707
- ancestor = dirname3(ancestor);
19421
+ ancestor = dirname6(ancestor);
18708
19422
  }
18709
- const parent = lstatSync2(directory2);
19423
+ const parent = lstatSync4(directory2);
18710
19424
  if (process.platform !== "win32" && ((parent.mode & 63) !== 0 || parent.uid !== process.getuid?.()))
18711
19425
  unavailable2();
18712
19426
  }
@@ -18717,7 +19431,7 @@ function safeFile(metadata) {
18717
19431
  function syncDirectory(filename2) {
18718
19432
  if (process.platform === "win32")
18719
19433
  return;
18720
- const descriptor2 = openSync(dirname3(filename2), constants.O_RDONLY | constants.O_DIRECTORY);
19434
+ const descriptor2 = openSync(dirname6(filename2), constants.O_RDONLY | constants.O_DIRECTORY);
18721
19435
  try {
18722
19436
  fsyncSync(descriptor2);
18723
19437
  } finally {
@@ -18725,7 +19439,7 @@ function syncDirectory(filename2) {
18725
19439
  }
18726
19440
  }
18727
19441
  function readState(filename2) {
18728
- const metadata = lstatSync2(filename2);
19442
+ const metadata = lstatSync4(filename2);
18729
19443
  safeFile(metadata);
18730
19444
  if (metadata.size > 2048)
18731
19445
  unavailable2();
@@ -18758,30 +19472,30 @@ function readState(filename2) {
18758
19472
  }
18759
19473
  }
18760
19474
  function persist(filename2, state) {
18761
- const temporary = filename2 + "." + randomUUID2() + ".tmp";
19475
+ const temporary = filename2 + "." + randomUUID3() + ".tmp";
18762
19476
  const descriptor2 = openSync(temporary, "wx", 384);
18763
19477
  try {
18764
- writeFileSync3(descriptor2, JSON.stringify(state));
19478
+ writeFileSync6(descriptor2, JSON.stringify(state));
18765
19479
  fsyncSync(descriptor2);
18766
19480
  } finally {
18767
19481
  closeSync(descriptor2);
18768
19482
  }
18769
- renameSync2(temporary, filename2);
19483
+ renameSync3(temporary, filename2);
18770
19484
  syncDirectory(filename2);
18771
19485
  }
18772
19486
  function checkpoint(filename2, trust, initialize2) {
18773
19487
  try {
18774
- if (!isAbsolute(filename2))
19488
+ if (!isAbsolute2(filename2))
18775
19489
  unavailable2();
18776
- filename2 = resolve3(filename2);
19490
+ filename2 = resolve5(filename2);
18777
19491
  safePath(filename2);
18778
19492
  const lock = filename2 + ".lock";
18779
- const next = { version: 1, scope: trust.scope, revision: trust.revision, digest: createHash2("sha256").update(trust.content).digest("hex") };
19493
+ const next = { version: 1, scope: trust.scope, revision: trust.revision, digest: createHash3("sha256").update(trust.content).digest("hex") };
18780
19494
  if (!initialize2) {
18781
19495
  const previous = readState(filename2);
18782
19496
  if (previous.scope.organizationId === next.scope.organizationId && previous.scope.applicationId === next.scope.applicationId && previous.scope.environmentId === next.scope.environmentId && previous.revision === next.revision && previous.digest === next.digest) {
18783
19497
  try {
18784
- lstatSync2(lock);
19498
+ lstatSync4(lock);
18785
19499
  } catch (error) {
18786
19500
  if (error.code === "ENOENT")
18787
19501
  return;
@@ -18792,7 +19506,7 @@ function checkpoint(filename2, trust, initialize2) {
18792
19506
  }
18793
19507
  const descriptor2 = openSync(lock, "wx", 384);
18794
19508
  try {
18795
- writeFileSync3(descriptor2, "backify-application-auth-state-v1");
19509
+ writeFileSync6(descriptor2, "backify-application-auth-state-v1");
18796
19510
  fsyncSync(descriptor2);
18797
19511
  } finally {
18798
19512
  closeSync(descriptor2);
@@ -18802,7 +19516,7 @@ function checkpoint(filename2, trust, initialize2) {
18802
19516
  try {
18803
19517
  if (initialize2) {
18804
19518
  try {
18805
- lstatSync2(filename2);
19519
+ lstatSync4(filename2);
18806
19520
  unavailable2();
18807
19521
  } catch (error) {
18808
19522
  if (error.code !== "ENOENT")
@@ -18839,18 +19553,18 @@ function createPersistentApplicationAuthProvider(source, options) {
18839
19553
  }
18840
19554
 
18841
19555
  // ../sdk/dist/application-auth-sync.js
18842
- import { createHash as createHash3, createPublicKey as createPublicKey2, randomUUID as randomUUID3, verify as verify2 } from "node:crypto";
18843
- import { closeSync as closeSync2, constants as constants2, fsyncSync as fsyncSync2, lstatSync as lstatSync3, openSync as openSync2, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
18844
- import { dirname as dirname4, isAbsolute as isAbsolute2, parse as parse2, resolve as resolve4, sep as sep3 } from "node:path";
19556
+ import { createHash as createHash4, createPublicKey as createPublicKey2, randomUUID as randomUUID4, verify as verify2 } from "node:crypto";
19557
+ import { closeSync as closeSync2, constants as constants2, fsyncSync as fsyncSync2, lstatSync as lstatSync5, openSync as openSync2, readFileSync as readFileSync6, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
19558
+ import { dirname as dirname7, isAbsolute as isAbsolute3, parse as parse2, resolve as resolve6, sep as sep3 } from "node:path";
18845
19559
  var maximumBytes = 512 * 1024;
18846
19560
  var identifier = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value) && value !== "00000000-0000-0000-0000-000000000000";
18847
19561
  function unavailable3() {
18848
19562
  throw new ApplicationAuthenticationError("application_identity_unavailable");
18849
19563
  }
18850
19564
  function filename(value) {
18851
- if (typeof value !== "string" || /[\x00-\x1f\x7f]/.test(value) || !isAbsolute2(value))
19565
+ if (typeof value !== "string" || /[\x00-\x1f\x7f]/.test(value) || !isAbsolute3(value))
18852
19566
  unavailable3();
18853
- const result = resolve4(value);
19567
+ const result = resolve6(value);
18854
19568
  if (result === parse2(result).root || process.platform === "win32" && result.slice(parse2(result).root.length).split(sep3).some((component) => component.includes(":") || /[. ]$/.test(component) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(component)))
18855
19569
  unavailable3();
18856
19570
  return result;
@@ -18879,28 +19593,28 @@ function prepare(options) {
18879
19593
  function planApplicationAuthSynchronization(options) {
18880
19594
  try {
18881
19595
  const prepared = prepare(options);
18882
- return { schemaVersion: 1, status: "planned", scope: structuredClone(options.expectedScope), agentId: options.agentId, endpoint: prepared.endpoint.href, managementKeySha256: createHash3("sha256").update(prepared.key.export({ type: "spki", format: "der" })).digest("hex"), trustFile: prepared.trustFile, stateFile: prepared.stateFile, requiresInitializedOperatorJournal: true, workloadCredentialsRequired: false, deploymentReady: false };
19596
+ return { schemaVersion: 1, status: "planned", scope: structuredClone(options.expectedScope), agentId: options.agentId, endpoint: prepared.endpoint.href, managementKeySha256: createHash4("sha256").update(prepared.key.export({ type: "spki", format: "der" })).digest("hex"), trustFile: prepared.trustFile, stateFile: prepared.stateFile, requiresInitializedOperatorJournal: true, workloadCredentialsRequired: false, deploymentReady: false };
18883
19597
  } catch {
18884
19598
  return unavailable3();
18885
19599
  }
18886
19600
  }
18887
19601
  function directory(filename2, privateState) {
18888
- const parent = dirname4(filename2);
18889
- for (let ancestor = parent; ; ancestor = dirname4(ancestor)) {
18890
- const metadata2 = lstatSync3(ancestor);
19602
+ const parent = dirname7(filename2);
19603
+ for (let ancestor = parent; ; ancestor = dirname7(ancestor)) {
19604
+ const metadata2 = lstatSync5(ancestor);
18891
19605
  if (!metadata2.isDirectory() || metadata2.isSymbolicLink())
18892
19606
  unavailable3();
18893
19607
  if (ancestor === parse2(ancestor).root)
18894
19608
  break;
18895
19609
  }
18896
- const metadata = lstatSync3(parent);
19610
+ const metadata = lstatSync5(parent);
18897
19611
  if (process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & (privateState ? 63 : 18)) !== 0))
18898
19612
  unavailable3();
18899
19613
  }
18900
19614
  function syncDirectory2(filename2) {
18901
19615
  if (process.platform === "win32")
18902
19616
  return;
18903
- const descriptor2 = openSync2(dirname4(filename2), constants2.O_RDONLY | constants2.O_DIRECTORY);
19617
+ const descriptor2 = openSync2(dirname7(filename2), constants2.O_RDONLY | constants2.O_DIRECTORY);
18904
19618
  try {
18905
19619
  fsyncSync2(descriptor2);
18906
19620
  } finally {
@@ -18909,10 +19623,10 @@ function syncDirectory2(filename2) {
18909
19623
  }
18910
19624
  function currentPublicFile(filename2) {
18911
19625
  try {
18912
- const metadata = lstatSync3(filename2);
19626
+ const metadata = lstatSync5(filename2);
18913
19627
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 131072 || process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & 18) !== 0))
18914
19628
  unavailable3();
18915
- const bytes = readFileSync3(filename2);
19629
+ const bytes = readFileSync6(filename2);
18916
19630
  if (bytes.length > 131072)
18917
19631
  unavailable3();
18918
19632
  return new TextDecoder("utf8", { fatal: true }).decode(bytes);
@@ -18979,7 +19693,7 @@ async function synchronizeApplicationAuthTrustWithDigest(options) {
18979
19693
  const lock = prepared.stateFile + ".delivery.lock";
18980
19694
  const descriptor2 = openSync2(lock, "wx", 384);
18981
19695
  try {
18982
- writeFileSync4(descriptor2, "backify-application-auth-delivery-v1");
19696
+ writeFileSync7(descriptor2, "backify-application-auth-delivery-v1");
18983
19697
  fsyncSync2(descriptor2);
18984
19698
  } finally {
18985
19699
  closeSync2(descriptor2);
@@ -18990,19 +19704,19 @@ async function synchronizeApplicationAuthTrustWithDigest(options) {
18990
19704
  const current = currentPublicFile(prepared.trustFile);
18991
19705
  createPersistentApplicationAuthProvider(trust, { expectedScope: options.expectedScope, stateFile: prepared.stateFile, now: options.now });
18992
19706
  const content = JSON.stringify(trust);
18993
- const trustDigest = createHash3("sha256").update(content).digest("hex");
19707
+ const trustDigest = createHash4("sha256").update(content).digest("hex");
18994
19708
  if (current === content)
18995
19709
  return { status: "unchanged", revision: trust.revision, validUntil: trust.validUntil, trustDigest };
18996
19710
  writePending = true;
18997
- const temporary = prepared.trustFile + "." + randomUUID3() + ".tmp";
19711
+ const temporary = prepared.trustFile + "." + randomUUID4() + ".tmp";
18998
19712
  const output = openSync2(temporary, "wx", process.platform === "win32" ? 384 : 292);
18999
19713
  try {
19000
- writeFileSync4(output, content);
19714
+ writeFileSync7(output, content);
19001
19715
  fsyncSync2(output);
19002
19716
  } finally {
19003
19717
  closeSync2(output);
19004
19718
  }
19005
- renameSync3(temporary, prepared.trustFile);
19719
+ renameSync4(temporary, prepared.trustFile);
19006
19720
  syncDirectory2(prepared.trustFile);
19007
19721
  writePending = false;
19008
19722
  return { status: "updated", revision: trust.revision, validUntil: trust.validUntil, trustDigest };
@@ -19579,16 +20293,16 @@ function jsonValue(value) {
19579
20293
 
19580
20294
  // src/local-services.ts
19581
20295
  import { spawn, execFile } from "node:child_process";
19582
- import { createHash as createHash5, generateKeyPairSync, randomBytes as randomBytes2, randomUUID as randomUUID5, sign } from "node:crypto";
19583
- import { createServer } from "node:net";
19584
- import { existsSync as existsSync5, lstatSync as lstatSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, realpathSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
19585
- import { basename, dirname as dirname6, isAbsolute as isAbsolute3, join as join4, relative, resolve as resolve5 } from "node:path";
20296
+ import { createHash as createHash6, generateKeyPairSync, randomBytes as randomBytes3, randomUUID as randomUUID6, sign } from "node:crypto";
20297
+ import { createServer as createServer2 } from "node:net";
20298
+ import { existsSync as existsSync8, lstatSync as lstatSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync5, realpathSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync8 } from "node:fs";
20299
+ import { basename as basename2, dirname as dirname9, isAbsolute as isAbsolute4, join as join7, relative, resolve as resolve7 } from "node:path";
19586
20300
 
19587
20301
  // src/local-job-services.ts
19588
- import { createHash as createHash4, randomBytes, randomUUID as randomUUID4 } from "node:crypto";
19589
- import { existsSync as existsSync4, lstatSync as lstatSync4, readFileSync as readFileSync4 } from "node:fs";
20302
+ import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID5 } from "node:crypto";
20303
+ import { existsSync as existsSync7, lstatSync as lstatSync6, readFileSync as readFileSync7 } from "node:fs";
19590
20304
  import { isIP } from "node:net";
19591
- import { dirname as dirname5, join as join3 } from "node:path";
20305
+ import { dirname as dirname8, join as join6 } from "node:path";
19592
20306
  function fields3(value, names) {
19593
20307
  return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === names.length && names.every((name2) => Object.hasOwn(value, name2));
19594
20308
  }
@@ -19639,12 +20353,12 @@ function validLocalJobState(value, otherIds) {
19639
20353
  function prepareLocalJobService(hcrPath, retained, artifacts, applyMigrations) {
19640
20354
  const requested = validateLocalJobArtifacts(artifacts);
19641
20355
  const readSchema = (name2) => {
19642
- const filename2 = join3(dirname5(hcrPath), "sql", name2), info = existsSync4(filename2) ? lstatSync4(filename2) : void 0;
20356
+ const filename2 = join6(dirname8(hcrPath), "sql", name2), info = existsSync7(filename2) ? lstatSync6(filename2) : void 0;
19643
20357
  if (!info?.isFile() || info.isSymbolicLink() || info.size > 65536) throw new Error("Native jobs require the reviewed jobs-v1.sql and jobs-producer-v1.sql assets beside the HCR host");
19644
- return readFileSync4(filename2, "utf8");
20358
+ return readFileSync7(filename2, "utf8");
19645
20359
  };
19646
20360
  const base = readSchema("jobs-v1.sql"), producer = readSchema("jobs-producer-v1.sql");
19647
- const schemaDigests = { base: createHash4("sha256").update(base).digest("hex"), producer: createHash4("sha256").update(producer).digest("hex") };
20361
+ const schemaDigests = { base: createHash5("sha256").update(base).digest("hex"), producer: createHash5("sha256").update(producer).digest("hex") };
19648
20362
  if (retained && (retained.schemaDigests.base !== schemaDigests.base || retained.schemaDigests.producer !== schemaDigests.producer)) throw new Error("Native job schema changed; preserve its database and plan an explicit migration");
19649
20363
  if (!retained?.initialized && !applyMigrations) throw new Error("Review both HCR job SQL assets and pass --apply-migrations for explicit native job schema approval");
19650
20364
  const retainedArtifacts = retained ? validateLocalJobArtifacts(retained.artifacts) : [];
@@ -19653,20 +20367,20 @@ function prepareLocalJobService(hcrPath, retained, artifacts, applyMigrations) {
19653
20367
  if (previous && JSON.stringify(previous) !== JSON.stringify(artifact)) throw new Error("Retained local job artifact metadata cannot change; preserve the original pins and policies");
19654
20368
  if (!previous) retainedArtifacts.push(artifact);
19655
20369
  }
19656
- const state = { ...retained ?? { id: randomUUID4(), password: randomBytes(32).toString("hex"), initialized: false }, schemaDigests, artifacts: validateLocalJobArtifacts(retainedArtifacts) };
20370
+ const state = { ...retained ?? { id: randomUUID5(), password: randomBytes2(32).toString("hex"), initialized: false }, schemaDigests, artifacts: validateLocalJobArtifacts(retainedArtifacts) };
19657
20371
  return { state, base, producer };
19658
20372
  }
19659
20373
 
19660
20374
  // src/local-services.ts
19661
20375
  function writePrivate(filename2, value) {
19662
20376
  if (isLink(filename2)) throw new Error("Local service files must not be symbolic links");
19663
- const temporary = filename2 + "." + randomUUID5() + ".tmp";
19664
- writeFileSync5(temporary, value, { flag: "wx", mode: 384 });
20377
+ const temporary = filename2 + "." + randomUUID6() + ".tmp";
20378
+ writeFileSync8(temporary, value, { flag: "wx", mode: 384 });
19665
20379
  renameLocalFile(temporary, filename2);
19666
20380
  }
19667
20381
  function isLink(filename2) {
19668
20382
  try {
19669
- return lstatSync5(filename2).isSymbolicLink();
20383
+ return lstatSync7(filename2).isSymbolicLink();
19670
20384
  } catch (error) {
19671
20385
  if (error.code === "ENOENT") return false;
19672
20386
  throw error;
@@ -19674,9 +20388,9 @@ function isLink(filename2) {
19674
20388
  }
19675
20389
  function assertLocalStatePath(root, filename2) {
19676
20390
  const base = realpathSync(root);
19677
- for (let current = resolve5(filename2); current !== base; current = resolve5(current, "..")) {
20391
+ for (let current = resolve7(filename2); current !== base; current = resolve7(current, "..")) {
19678
20392
  const part = relative(base, current);
19679
- if (part === ".." || part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) || isAbsolute3(part)) throw new Error("Local service state must stay inside the repository");
20393
+ if (part === ".." || part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) || isAbsolute4(part)) throw new Error("Local service state must stay inside the repository");
19680
20394
  if (isLink(current)) throw new Error("Local service paths must not contain links");
19681
20395
  }
19682
20396
  }
@@ -19687,22 +20401,22 @@ function stateValid(value) {
19687
20401
  return value?.version === 1 && value.scope && [value.scope.organizationId, value.scope.applicationId, value.scope.environmentId, value.databaseId, value.storageId].every((identifier2) => typeof identifier2 === "string" && uuid5.test(identifier2)) && Number.isSafeInteger(value.configurationVersion) && value.configurationVersion >= 0 && /^[a-f0-9]{64}$/.test(value.adminPassword) && /^[a-f0-9]{64}$/.test(value.databasePassword) && (value.retainedDatabasePort === void 0 || Number.isInteger(value.retainedDatabasePort) && value.retainedDatabasePort > 0 && value.retainedDatabasePort <= 65535);
19688
20402
  }
19689
20403
  function acquireLocalLease(root, relativePath2 = ".backify/dev/development.lock") {
19690
- const filename2 = join4(root, relativePath2);
20404
+ const filename2 = join7(root, relativePath2);
19691
20405
  assertLocalStatePath(root, filename2);
19692
- mkdirSync3(resolve5(filename2, ".."), { recursive: true, mode: 448 });
19693
- const owner = JSON.stringify({ version: 1, pid: process.pid, id: randomUUID5() });
20406
+ mkdirSync4(resolve7(filename2, ".."), { recursive: true, mode: 448 });
20407
+ const owner = JSON.stringify({ version: 1, pid: process.pid, id: randomUUID6() });
19694
20408
  try {
19695
- writeFileSync5(filename2, owner, { flag: "wx", mode: 384 });
20409
+ writeFileSync8(filename2, owner, { flag: "wx", mode: 384 });
19696
20410
  } catch {
19697
20411
  throw new Error("Local development already owns this repository, or a stale lifecycle lock requires explicit recovery");
19698
20412
  }
19699
20413
  return () => {
19700
- if (existsSync5(filename2) && readFileSync5(filename2, "utf8") === owner) unlinkSync3(filename2);
20414
+ if (existsSync8(filename2) && readFileSync8(filename2, "utf8") === owner) unlinkSync3(filename2);
19701
20415
  };
19702
20416
  }
19703
20417
  async function allocateLocalPort(requested) {
19704
20418
  if (requested !== void 0 && (!Number.isInteger(requested) || requested < 0 || requested > 65535)) throw new Error("Local service ports must be 0-65535");
19705
- const server = createServer();
20419
+ const server = createServer2();
19706
20420
  return new Promise((resolvePort, reject) => {
19707
20421
  server.once("error", reject);
19708
20422
  server.listen(requested ?? 0, "127.0.0.1", () => {
@@ -19718,7 +20432,7 @@ async function allocateLocalPort(requested) {
19718
20432
  }
19719
20433
  function command(executable, args2, options = {}) {
19720
20434
  const failure2 = () => new Error("Local service command failed; verify installed tools, port availability and local state permissions");
19721
- if (basename(executable).replace(/\.exe$/i, "") === "pg_ctl") return new Promise((resolveOutput, reject) => {
20435
+ if (basename2(executable).replace(/\.exe$/i, "") === "pg_ctl") return new Promise((resolveOutput, reject) => {
19722
20436
  const child = spawn(executable, args2, { ...options, windowsHide: true, stdio: "ignore" });
19723
20437
  const timer = setTimeout(() => {
19724
20438
  child.kill("SIGKILL");
@@ -19740,29 +20454,29 @@ function command(executable, args2, options = {}) {
19740
20454
  });
19741
20455
  }
19742
20456
  function postgresExecutable(name2, bin) {
19743
- const directory2 = bin ?? process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join4(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
19744
- return directory2 ? join4(directory2, name2 + (process.platform === "win32" ? ".exe" : "")) : name2;
20457
+ const directory2 = bin ?? process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join7(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
20458
+ return directory2 ? join7(directory2, name2 + (process.platform === "win32" ? ".exe" : "")) : name2;
19745
20459
  }
19746
20460
  function localMigrationFiles(root, directory2) {
19747
20461
  if (directory2 === void 0) return [];
19748
20462
  if (typeof directory2 !== "string") throw new Error("Local migrations require a repository-relative directory");
19749
- const folder = resolve5(root, directory2);
20463
+ const folder = resolve7(root, directory2);
19750
20464
  assertLocalStatePath(root, folder);
19751
- if (!existsSync5(folder)) throw new Error("Declared migration directory does not exist");
19752
- const entries = readdirSync3(folder);
20465
+ if (!existsSync8(folder)) throw new Error("Declared migration directory does not exist");
20466
+ const entries = readdirSync5(folder);
19753
20467
  const filenames = entries.filter((entry) => entry.endsWith(".sql")).sort();
19754
20468
  if (filenames.length > 100 || entries.some((entry) => !entry.endsWith(".sql"))) throw new Error("Migrations require at most 100 flat .sql files");
19755
20469
  let total = 0;
19756
20470
  return filenames.map((name2) => {
19757
20471
  if (!/^[A-Za-z0-9_.-]{1,150}\.sql$/.test(name2)) throw new Error("Invalid migration filename");
19758
- const filename2 = join4(folder, name2);
20472
+ const filename2 = join7(folder, name2);
19759
20473
  assertLocalStatePath(root, filename2);
19760
- const info = lstatSync5(filename2);
20474
+ const info = lstatSync7(filename2);
19761
20475
  if (!info.isFile() || info.size > 1024 * 1024 || (total += info.size) > 4 * 1024 * 1024) throw new Error("Migration SQL exceeds supported limits");
19762
- const sql = readFileSync5(filename2, "utf8");
20476
+ const sql = readFileSync8(filename2, "utf8");
19763
20477
  if (!sql.trim()) throw new Error("Migration SQL must not be empty");
19764
20478
  validateLocalMigrationSql(sql);
19765
- return { name: name2, digest: createHash5("sha256").update(sql).digest("hex"), sql };
20479
+ return { name: name2, digest: createHash6("sha256").update(sql).digest("hex"), sql };
19766
20480
  });
19767
20481
  }
19768
20482
  function validateLocalMigrationSql(sql) {
@@ -19836,19 +20550,19 @@ async function startLocalServices(root, manifest, options = {}) {
19836
20550
  const reviewedScope = options.jobIdentityScope;
19837
20551
  if (reviewedScope !== void 0 && (!jobs || !manifest.capabilities.auth || manifest.capabilities.auth.enabled === false || !reviewedScope || Object.keys(reviewedScope).sort().join(",") !== "applicationId,environmentId,organizationId" || Object.values(reviewedScope).some((value) => typeof value !== "string" || value.length !== 36 || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value) || value === "00000000-0000-0000-0000-000000000000"))) throw new Error("Native worker identity scope requires reviewed jobs and enabled auth");
19838
20552
  const installed = options.hcrPath || process.env.BACKIFY_LOCAL_HCR_PATH ? void 0 : installedLocalRuntime();
19839
- const hcrPath = resolve5(options.hcrPath ?? process.env.BACKIFY_LOCAL_HCR_PATH ?? installed?.path ?? join4(root, "apps/hcr/bin/Debug/net9.0/Backify.Hcr.dll"));
19840
- if (!existsSync5(hcrPath) || !lstatSync5(hcrPath).isFile() || !hcrPath.endsWith(".dll")) throw new Error("Full-fidelity mode requires a built Backify.Hcr.dll; set --hcr-path or BACKIFY_LOCAL_HCR_PATH");
19841
- const directory2 = join4(root, ".backify", "dev", "services");
20553
+ const hcrPath = resolve7(options.hcrPath ?? process.env.BACKIFY_LOCAL_HCR_PATH ?? installed?.path ?? join7(root, "apps/hcr/bin/Debug/net9.0/Backify.Hcr.dll"));
20554
+ if (!existsSync8(hcrPath) || !lstatSync7(hcrPath).isFile() || !hcrPath.endsWith(".dll")) throw new Error("Full-fidelity mode requires a built Backify.Hcr.dll; set --hcr-path or BACKIFY_LOCAL_HCR_PATH");
20555
+ const directory2 = join7(root, ".backify", "dev", "services");
19842
20556
  assertLocalStatePath(root, directory2);
19843
- mkdirSync3(directory2, { recursive: true, mode: 448 });
20557
+ mkdirSync4(directory2, { recursive: true, mode: 448 });
19844
20558
  assertLocalStatePath(root, directory2);
19845
- const statePath = join4(directory2, "host.json");
20559
+ const statePath = join7(directory2, "host.json");
19846
20560
  assertLocalStatePath(root, statePath);
19847
20561
  let existing;
19848
- if (existsSync5(statePath)) {
19849
- if (lstatSync5(statePath).size > 16384) throw new Error("Local ownership state exceeds supported limits");
20562
+ if (existsSync8(statePath)) {
20563
+ if (lstatSync7(statePath).size > 16384) throw new Error("Local ownership state exceeds supported limits");
19850
20564
  try {
19851
- existing = JSON.parse(readFileSync5(statePath, "utf8"));
20565
+ existing = JSON.parse(readFileSync8(statePath, "utf8"));
19852
20566
  } catch {
19853
20567
  throw new Error("Invalid local service ownership state; preserve the data and repair explicitly");
19854
20568
  }
@@ -19856,31 +20570,31 @@ async function startLocalServices(root, manifest, options = {}) {
19856
20570
  }
19857
20571
  const state = existing ?? {
19858
20572
  version: 1,
19859
- scope: reviewedScope ? structuredClone(reviewedScope) : { organizationId: randomUUID5(), applicationId: randomUUID5(), environmentId: randomUUID5() },
19860
- databaseId: randomUUID5(),
19861
- storageId: randomUUID5(),
20573
+ scope: reviewedScope ? structuredClone(reviewedScope) : { organizationId: randomUUID6(), applicationId: randomUUID6(), environmentId: randomUUID6() },
20574
+ databaseId: randomUUID6(),
20575
+ storageId: randomUUID6(),
19862
20576
  configurationVersion: 0,
19863
- adminPassword: randomBytes2(32).toString("hex"),
19864
- databasePassword: randomBytes2(32).toString("hex")
20577
+ adminPassword: randomBytes3(32).toString("hex"),
20578
+ databasePassword: randomBytes3(32).toString("hex")
19865
20579
  };
19866
20580
  if (!stateValid(state)) throw new Error("Invalid local service ownership state; preserve the data and repair explicitly");
19867
20581
  if (reviewedScope && ["organizationId", "applicationId", "environmentId"].some((name2) => state.scope[name2] !== reviewedScope[name2])) throw new Error("Reviewed native worker identity scope conflicts with retained local ownership; preserve data and use a separate reviewed workspace");
19868
20582
  let eventSchema;
19869
20583
  if (events) {
19870
- const filename2 = join4(dirname6(hcrPath), "sql", "events-v1.sql");
19871
- if (!existsSync5(filename2) || lstatSync5(filename2).isSymbolicLink() || !lstatSync5(filename2).isFile() || lstatSync5(filename2).size > 65536) throw new Error("Native events require the reviewed events-v1.sql asset beside the HCR host.");
19872
- eventSchema = readFileSync5(filename2, "utf8");
19873
- const digest4 = createHash5("sha256").update(eventSchema).digest("hex");
20584
+ const filename2 = join7(dirname9(hcrPath), "sql", "events-v1.sql");
20585
+ if (!existsSync8(filename2) || lstatSync7(filename2).isSymbolicLink() || !lstatSync7(filename2).isFile() || lstatSync7(filename2).size > 65536) throw new Error("Native events require the reviewed events-v1.sql asset beside the HCR host.");
20586
+ eventSchema = readFileSync8(filename2, "utf8");
20587
+ const digest4 = createHash6("sha256").update(eventSchema).digest("hex");
19874
20588
  if (state.events && state.events.schemaDigest !== digest4) throw new Error("Native event schema changed; preserve its database and plan an explicit migration.");
19875
20589
  if (!state.events?.initialized && !options.applyMigrations) throw new Error("Review the HCR events-v1.sql asset and pass --apply-migrations for explicit native event schema approval.");
19876
- state.events ??= { id: randomUUID5(), password: randomBytes2(32).toString("hex"), schemaDigest: digest4, initialized: false };
20590
+ state.events ??= { id: randomUUID6(), password: randomBytes3(32).toString("hex"), schemaDigest: digest4, initialized: false };
19877
20591
  }
19878
20592
  const jobSchema = jobs ? prepareLocalJobService(hcrPath, state.jobs, options.jobArtifacts, options.applyMigrations) : void 0;
19879
20593
  if (jobSchema) state.jobs = jobSchema.state;
19880
20594
  const storageNames = manifest.capabilities.storage?.buckets === void 0 ? ["default"] : manifest.capabilities.storage.buckets.map((bucket) => bucket.name);
19881
20595
  state.storageBindings ??= { default: state.storageId };
19882
20596
  if (typeof state.storageBindings !== "object" || state.storageBindings === null || Array.isArray(state.storageBindings) || Object.entries(state.storageBindings).some(([name2, identifier2]) => !/^[A-Za-z0-9_-]{1,100}$/.test(name2) || typeof identifier2 !== "string" || !/^[0-9a-f-]{36}$/i.test(identifier2))) throw new Error("Invalid local storage binding ownership state");
19883
- for (const name2 of storageNames) state.storageBindings[name2] ??= randomUUID5();
20597
+ for (const name2 of storageNames) state.storageBindings[name2] ??= randomUUID6();
19884
20598
  if (Object.keys(state.storageBindings).length > 100) throw new Error("Local storage ownership history exceeds 100 bindings; preserve the data and plan an explicit migration");
19885
20599
  if (Buffer.byteLength(JSON.stringify({ ...state, configurationVersion: Number.MAX_SAFE_INTEGER, retainedDatabasePort: 65535 }), "utf8") > 16384) throw new Error("Local ownership history exceeds supported limits; preserve state and plan an explicit migration");
19886
20600
  const migrations = database ? localMigrationFiles(root, manifest.capabilities.database?.migrations) : [];
@@ -19888,7 +20602,7 @@ async function startLocalServices(root, manifest, options = {}) {
19888
20602
  const databasePort = database || events || jobs ? await allocateLocalPort(options.databasePort) : void 0;
19889
20603
  if (databasePort !== void 0) state.retainedDatabasePort = databasePort;
19890
20604
  if (databasePort === hcrPort) throw new Error("HCR and PostgreSQL require separate ports");
19891
- const cluster = join4(directory2, "postgres");
20605
+ const cluster = join7(directory2, "postgres");
19892
20606
  assertLocalStatePath(root, cluster);
19893
20607
  const release2 = acquireLocalLease(root, ".backify/dev/services/lifecycle.lock");
19894
20608
  let postgresStarted = false;
@@ -19904,7 +20618,7 @@ async function startLocalServices(root, manifest, options = {}) {
19904
20618
  }
19905
20619
  };
19906
20620
  const runtimeUrl = "http://127.0.0.1:" + hcrPort;
19907
- const tokenPath = join4(directory2, "workload-token");
20621
+ const tokenPath = join7(directory2, "workload-token");
19908
20622
  const stop = () => stopping ??= (async () => {
19909
20623
  if (timer) clearInterval(timer);
19910
20624
  if (snapshotTimer) clearInterval(snapshotTimer);
@@ -19923,9 +20637,9 @@ async function startLocalServices(root, manifest, options = {}) {
19923
20637
  }
19924
20638
  notify("hcr", "stopped");
19925
20639
  try {
19926
- if (postgresStarted && existsSync5(join4(cluster, "postmaster.pid"))) await command(postgresExecutable("pg_ctl", options.postgresBin), ["-D", cluster, "-m", "fast", "-w", "stop"]);
20640
+ if (postgresStarted && existsSync8(join7(cluster, "postmaster.pid"))) await command(postgresExecutable("pg_ctl", options.postgresBin), ["-D", cluster, "-m", "fast", "-w", "stop"]);
19927
20641
  if (postgresStarted) notify("postgres", "stopped");
19928
- if (existsSync5(tokenPath)) unlinkSync3(tokenPath);
20642
+ if (existsSync8(tokenPath)) unlinkSync3(tokenPath);
19929
20643
  } finally {
19930
20644
  release2();
19931
20645
  }
@@ -19935,28 +20649,28 @@ async function startLocalServices(root, manifest, options = {}) {
19935
20649
  writePrivate(statePath, JSON.stringify(state));
19936
20650
  if (database || events || jobs) {
19937
20651
  notify("postgres", "starting");
19938
- if (!existsSync5(join4(cluster, "PG_VERSION"))) {
19939
- const passwordPath = join4(directory2, "init-password");
20652
+ if (!existsSync8(join7(cluster, "PG_VERSION"))) {
20653
+ const passwordPath = join7(directory2, "init-password");
19940
20654
  writePrivate(passwordPath, state.adminPassword);
19941
20655
  try {
19942
20656
  await command(postgresExecutable("initdb", options.postgresBin), ["-D", cluster, "-U", "backify_admin", "--auth-host=scram-sha-256", "--auth-local=scram-sha-256", "--pwfile=" + passwordPath, "--no-locale", "--encoding=UTF8"]);
19943
20657
  } finally {
19944
- if (existsSync5(passwordPath)) unlinkSync3(passwordPath);
20658
+ if (existsSync8(passwordPath)) unlinkSync3(passwordPath);
19945
20659
  }
19946
20660
  }
19947
- if (existsSync5(join4(cluster, "postmaster.pid"))) throw new Error("A retained local PostgreSQL server is still running; stop or recover that exact owned cluster before restarting dev");
20661
+ if (existsSync8(join7(cluster, "postmaster.pid"))) throw new Error("A retained local PostgreSQL server is still running; stop or recover that exact owned cluster before restarting dev");
19948
20662
  postgresStarted = true;
19949
- await command(postgresExecutable("pg_ctl", options.postgresBin), ["-D", cluster, "-l", join4(directory2, "postgres.log"), "-o", "-h 127.0.0.1 -p " + databasePort, "-w", "start"]);
20663
+ await command(postgresExecutable("pg_ctl", options.postgresBin), ["-D", cluster, "-l", join7(directory2, "postgres.log"), "-o", "-h 127.0.0.1 -p " + databasePort, "-w", "start"]);
19950
20664
  notify("postgres", "ready");
19951
20665
  const sql = (query, application = false) => command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", application ? "backify_local" : "backify_admin", "-d", application ? "backify_local" : "postgres", "-X", "-q", "-t", "-A", "-v", "ON_ERROR_STOP=1", "-c", query], { env: { ...process.env, PGPASSWORD: application ? state.databasePassword : state.adminPassword } });
19952
20666
  if (database) {
19953
20667
  if ((await sql("SELECT count(*) FROM pg_roles WHERE rolname='backify_local'")).trim() === "0") {
19954
- const filename2 = join4(directory2, "role-" + randomUUID5() + ".sql");
20668
+ const filename2 = join7(directory2, "role-" + randomUUID6() + ".sql");
19955
20669
  writePrivate(filename2, "CREATE ROLE backify_local LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '" + state.databasePassword + "';");
19956
20670
  try {
19957
20671
  await command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", "backify_admin", "-d", "postgres", "-X", "-q", "-v", "ON_ERROR_STOP=1", "-f", filename2], { env: { ...process.env, PGPASSWORD: state.adminPassword } });
19958
20672
  } finally {
19959
- if (existsSync5(filename2)) unlinkSync3(filename2);
20673
+ if (existsSync8(filename2)) unlinkSync3(filename2);
19960
20674
  }
19961
20675
  }
19962
20676
  if ((await sql("SELECT count(*) FROM pg_database WHERE datname='backify_local'")).trim() === "0") await sql("CREATE DATABASE backify_local OWNER backify_local");
@@ -19971,12 +20685,12 @@ async function startLocalServices(root, manifest, options = {}) {
19971
20685
  notify("migrations", "starting");
19972
20686
  if (pending.length) await sql("CREATE TABLE IF NOT EXISTS public.__backify_migrations (name text PRIMARY KEY, digest text NOT NULL, applied_at timestamptz NOT NULL DEFAULT now())", true);
19973
20687
  for (const migration of pending) {
19974
- const filename2 = join4(directory2, "apply-" + randomUUID5() + ".sql");
20688
+ const filename2 = join7(directory2, "apply-" + randomUUID6() + ".sql");
19975
20689
  writePrivate(filename2, "BEGIN;\n" + migration.sql + "\n;\nINSERT INTO public.__backify_migrations(name,digest) VALUES ('" + migration.name + "','" + migration.digest + "');\nCOMMIT;\n");
19976
20690
  try {
19977
20691
  await command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", "backify_local", "-d", "backify_local", "-X", "-q", "-v", "ON_ERROR_STOP=1", "-f", filename2], { env: { ...process.env, PGPASSWORD: state.databasePassword } });
19978
20692
  } finally {
19979
- if (existsSync5(filename2)) unlinkSync3(filename2);
20693
+ if (existsSync8(filename2)) unlinkSync3(filename2);
19980
20694
  }
19981
20695
  applied.push(migration.name);
19982
20696
  }
@@ -19987,20 +20701,20 @@ async function startLocalServices(root, manifest, options = {}) {
19987
20701
  const eventSql = (query) => command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", "backify_events", "-d", "backify_events", "-X", "-q", "-t", "-A", "-v", "ON_ERROR_STOP=1", "-c", query], { env: { ...process.env, PGPASSWORD: eventState.password } });
19988
20702
  if (!eventState.initialized) {
19989
20703
  if ((await sql("SELECT count(*) FROM pg_database WHERE datname='backify_events'")).trim() !== "0" || (await sql("SELECT count(*) FROM pg_roles WHERE rolname='backify_events'")).trim() !== "0") throw new Error("Uninitialized native event ownership conflicts with retained state; preserve it and repair explicitly.");
19990
- const roleFile = join4(directory2, "event-role-" + randomUUID5() + ".sql");
20704
+ const roleFile = join7(directory2, "event-role-" + randomUUID6() + ".sql");
19991
20705
  writePrivate(roleFile, "CREATE ROLE backify_events LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '" + eventState.password + "';");
19992
20706
  try {
19993
20707
  await command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", "backify_admin", "-d", "postgres", "-X", "-q", "-v", "ON_ERROR_STOP=1", "-f", roleFile], { env: { ...process.env, PGPASSWORD: state.adminPassword } });
19994
20708
  } finally {
19995
- if (existsSync5(roleFile)) unlinkSync3(roleFile);
20709
+ if (existsSync8(roleFile)) unlinkSync3(roleFile);
19996
20710
  }
19997
20711
  await sql("CREATE DATABASE backify_events OWNER backify_events");
19998
- const schemaFile = join4(directory2, "event-schema-" + randomUUID5() + ".sql");
20712
+ const schemaFile = join7(directory2, "event-schema-" + randomUUID6() + ".sql");
19999
20713
  writePrivate(schemaFile, eventSchema);
20000
20714
  try {
20001
20715
  await command(postgresExecutable("psql", options.postgresBin), ["-h", "127.0.0.1", "-p", String(databasePort), "-U", "backify_events", "-d", "backify_events", "-X", "-q", "-v", "ON_ERROR_STOP=1", "-f", schemaFile], { env: { ...process.env, PGPASSWORD: eventState.password } });
20002
20716
  } finally {
20003
- if (existsSync5(schemaFile)) unlinkSync3(schemaFile);
20717
+ if (existsSync8(schemaFile)) unlinkSync3(schemaFile);
20004
20718
  }
20005
20719
  eventState.initialized = true;
20006
20720
  writePrivate(statePath, JSON.stringify(state));
@@ -20014,21 +20728,21 @@ async function startLocalServices(root, manifest, options = {}) {
20014
20728
  const adminSql = (query, databaseName = "postgres") => jobCommand("backify_admin", state.adminPassword, databaseName, ["-c", query]);
20015
20729
  if (!jobState.initialized) {
20016
20730
  if ((await adminSql("SELECT count(*) FROM pg_database WHERE datname='backify_jobs'")).trim() !== "0" || (await adminSql("SELECT count(*) FROM pg_roles WHERE rolname='backify_jobs'")).trim() !== "0") throw new Error("Uninitialized native job ownership conflicts with retained state; preserve it and repair explicitly");
20017
- const roleFile = join4(directory2, "job-role-" + randomUUID5() + ".sql");
20731
+ const roleFile = join7(directory2, "job-role-" + randomUUID6() + ".sql");
20018
20732
  writePrivate(roleFile, "CREATE ROLE backify_jobs LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD '" + jobState.password + "';");
20019
20733
  try {
20020
20734
  await jobCommand("backify_admin", state.adminPassword, "postgres", ["-f", roleFile]);
20021
20735
  } finally {
20022
- if (existsSync5(roleFile)) unlinkSync3(roleFile);
20736
+ if (existsSync8(roleFile)) unlinkSync3(roleFile);
20023
20737
  }
20024
20738
  await adminSql("CREATE DATABASE backify_jobs OWNER backify_admin");
20025
20739
  for (const schema of [jobSchema.base, jobSchema.producer]) {
20026
- const filename2 = join4(directory2, "job-schema-" + randomUUID5() + ".sql");
20740
+ const filename2 = join7(directory2, "job-schema-" + randomUUID6() + ".sql");
20027
20741
  writePrivate(filename2, schema);
20028
20742
  try {
20029
20743
  await jobCommand("backify_admin", state.adminPassword, "backify_jobs", ["-f", filename2]);
20030
20744
  } finally {
20031
- if (existsSync5(filename2)) unlinkSync3(filename2);
20745
+ if (existsSync8(filename2)) unlinkSync3(filename2);
20032
20746
  }
20033
20747
  }
20034
20748
  await adminSql("REVOKE ALL ON DATABASE backify_jobs FROM PUBLIC; GRANT CONNECT ON DATABASE backify_jobs TO backify_jobs; REVOKE CREATE ON SCHEMA public FROM PUBLIC; GRANT USAGE ON SCHEMA __backify_jobs TO backify_jobs; GRANT SELECT ON __backify_jobs.schema_version,__backify_jobs.producer_schema_version TO backify_jobs; GRANT SELECT,INSERT,UPDATE ON __backify_jobs.runs,__backify_jobs.attempts TO backify_jobs; GRANT SELECT,INSERT ON __backify_jobs.producers TO backify_jobs", "backify_jobs");
@@ -20057,7 +20771,7 @@ async function startLocalServices(root, manifest, options = {}) {
20057
20771
  ...events ? [{ id: state.events.id, scope: state.scope, capability: "events", contractVersion: 1, logicalBinding: "default", provider: "postgres-events", resourceReference: "backify_events", ownership: "Managed", accessMode: "Gateway", permissions: ["events.emit", "events.receive", "events.complete", "events.inspect"], status: "ready" }] : [],
20058
20772
  ...jobs ? [{ id: state.jobs.id, scope: state.scope, capability: "jobs", contractVersion: 1, logicalBinding: "default", provider: "postgres-jobs", resourceReference: "backify_jobs", ownership: "Managed", accessMode: "Gateway", permissions: ["jobs.enqueue", "jobs.receive", "jobs.complete", "jobs.cancel", "jobs.inspect", "jobs.retry"], status: "ready" }] : []
20059
20773
  ];
20060
- const snapshotPath = join4(directory2, "snapshot.json");
20774
+ const snapshotPath = join7(directory2, "snapshot.json");
20061
20775
  const writeSnapshot = () => writePrivate(snapshotPath, JSON.stringify({ schemaVersion: 1, configurationVersion: state.configurationVersion, expiresAt: new Date(Date.now() + 7 * 864e5).toISOString(), bindings }));
20062
20776
  const renewConfiguration = () => {
20063
20777
  if (stopping) throw new Error("Stopped local services cannot renew configuration");
@@ -20067,15 +20781,15 @@ async function startLocalServices(root, manifest, options = {}) {
20067
20781
  writeSnapshot();
20068
20782
  };
20069
20783
  writeSnapshot();
20070
- const settingsPath = join4(directory2, "appsettings.json");
20784
+ const settingsPath = join7(directory2, "appsettings.json");
20071
20785
  writePrivate(settingsPath, JSON.stringify({ Hcr: {
20072
20786
  Authentication: { PublicKeyPem: publicKey.export({ type: "spki", format: "pem" }), Issuer: "backify-local", Audience: "backify-local-hcr" },
20073
20787
  SnapshotPath: snapshotPath,
20074
- SnapshotJournalPath: join4(directory2, "journal.json"),
20788
+ SnapshotJournalPath: join7(directory2, "journal.json"),
20075
20789
  Postgres: { Bindings: state.retainedDatabasePort ? { [state.databaseId]: { Scope: state.scope, ConnectionString: "Host=127.0.0.1;Port=" + state.retainedDatabasePort + ";Database=backify_local;Username=backify_local;Password=" + state.databasePassword } } : {} },
20076
20790
  Events: { Bindings: state.retainedDatabasePort && state.events?.initialized ? { [state.events.id]: { Scope: state.scope, ConnectionString: "Host=127.0.0.1;Port=" + state.retainedDatabasePort + ";Database=backify_events;Username=backify_events;Password=" + state.events.password } } : {} },
20077
20791
  Jobs: { Bindings: state.retainedDatabasePort && state.jobs?.initialized ? { [state.jobs.id]: { Scope: state.scope, ConnectionString: "Host=127.0.0.1;Port=" + state.retainedDatabasePort + ";Database=backify_jobs;Username=backify_jobs;Password=" + state.jobs.password, Artifacts: state.jobs.artifacts } } : {} },
20078
- Storage: { Bindings: Object.fromEntries(Object.entries(state.storageBindings).map(([name2, identifier2]) => [identifier2, { Scope: state.scope, ResourceReference: name2, RootPath: join4(directory2, "objects", identifier2) }])) }
20792
+ Storage: { Bindings: Object.fromEntries(Object.entries(state.storageBindings).map(([name2, identifier2]) => [identifier2, { Scope: state.scope, ResourceReference: name2, RootPath: join7(directory2, "objects", identifier2) }])) }
20079
20793
  } }));
20080
20794
  issueToken();
20081
20795
  notify("hcr", "starting");
@@ -20089,7 +20803,7 @@ async function startLocalServices(root, manifest, options = {}) {
20089
20803
  while (true) {
20090
20804
  if (spawnFailed || hcr.exitCode !== null || hcr.signalCode !== null || Date.now() > deadline) throw new Error("Local HCR could not become ready; check the .NET runtime, bindings and retained journal");
20091
20805
  try {
20092
- const response = await fetch(runtimeUrl + "/v1/health", { headers: { authorization: "Bearer " + readFileSync5(tokenPath, "utf8") }, signal: AbortSignal.timeout(500), redirect: "error", credentials: "omit", cache: "no-store" });
20806
+ const response = await fetch(runtimeUrl + "/v1/health", { headers: { authorization: "Bearer " + readFileSync8(tokenPath, "utf8") }, signal: AbortSignal.timeout(500), redirect: "error", credentials: "omit", cache: "no-store" });
20093
20807
  if (response.ok) {
20094
20808
  const status = await response.json();
20095
20809
  if (status.status === "ready" && status.configurationVersion === state.configurationVersion) break;
@@ -20098,8 +20812,8 @@ async function startLocalServices(root, manifest, options = {}) {
20098
20812
  }
20099
20813
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
20100
20814
  }
20101
- if (events) await createHcrEventsProvider({ runtimeUrl, token: () => readFileSync5(tokenPath, "utf8") }).inspect();
20102
- if (jobs) await createHcrJobsProvider({ runtimeUrl, token: () => readFileSync5(tokenPath, "utf8") }).inspect();
20815
+ if (events) await createHcrEventsProvider({ runtimeUrl, token: () => readFileSync8(tokenPath, "utf8") }).inspect();
20816
+ if (jobs) await createHcrJobsProvider({ runtimeUrl, token: () => readFileSync8(tokenPath, "utf8") }).inspect();
20103
20817
  timer = setInterval(() => {
20104
20818
  try {
20105
20819
  issueToken();
@@ -20115,7 +20829,7 @@ async function startLocalServices(root, manifest, options = {}) {
20115
20829
  }
20116
20830
  }, 36e5);
20117
20831
  notify("hcr", "ready");
20118
- const configurationFingerprint = createHash5("sha256").update(JSON.stringify({ mode: "full", capabilities: manifest.capabilities, migrations: migrations.map(({ name: name2, digest: digest4 }) => ({ name: name2, digest: digest4 })), ...jobs ? { jobArtifacts: state.jobs.artifacts } : {} })).digest("hex");
20832
+ const configurationFingerprint = createHash6("sha256").update(JSON.stringify({ mode: "full", capabilities: manifest.capabilities, migrations: migrations.map(({ name: name2, digest: digest4 }) => ({ name: name2, digest: digest4 })), ...jobs ? { jobArtifacts: state.jobs.artifacts } : {} })).digest("hex");
20119
20833
  return { identityScope: { ...state.scope }, runtimeUrl, tokenPath, database, storage, events, jobs, databasePort, migrations: applied, configurationFingerprint, renewConfiguration, stop };
20120
20834
  } catch (error) {
20121
20835
  await stop();
@@ -20124,29 +20838,29 @@ async function startLocalServices(root, manifest, options = {}) {
20124
20838
  }
20125
20839
 
20126
20840
  // src/local-job-auth.ts
20127
- import { createHash as createHash6 } from "node:crypto";
20128
- import { lstatSync as lstatSync6, readFileSync as readFileSync6 } from "node:fs";
20129
- import { isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
20841
+ import { createHash as createHash7 } from "node:crypto";
20842
+ import { lstatSync as lstatSync8, readFileSync as readFileSync9 } from "node:fs";
20843
+ import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
20130
20844
  function prepareLocalJobAuth(root, filename2) {
20131
20845
  const read = (path, maximum) => {
20132
20846
  assertLocalStatePath(root, path);
20133
- const metadata = lstatSync6(path);
20847
+ const metadata = lstatSync8(path);
20134
20848
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > maximum) throw new Error();
20135
- const bytes = readFileSync6(path);
20849
+ const bytes = readFileSync9(path);
20136
20850
  if (bytes.length > maximum) throw new Error();
20137
20851
  return bytes;
20138
20852
  };
20139
20853
  try {
20140
- if (!filename2 || isAbsolute4(filename2)) throw new Error();
20141
- const input = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(read(resolve6(root, filename2), 16384)));
20854
+ if (!filename2 || isAbsolute5(filename2)) throw new Error();
20855
+ const input = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(read(resolve8(root, filename2), 16384)));
20142
20856
  if (!input || Object.keys(input).sort().join(",") !== "revision,schemaVersion,scope,stateFile,trustDigest,trustFile" || input.schemaVersion !== 1 || !Number.isSafeInteger(input.revision) || input.revision < 1 || typeof input.trustDigest !== "string" || input.trustDigest.length !== 64 || !/^[a-f0-9]{64}$/.test(input.trustDigest)) throw new Error();
20143
20857
  if (!input.scope || Object.keys(input.scope).sort().join(",") !== "applicationId,environmentId,organizationId" || Object.values(input.scope).some((value) => typeof value !== "string" || value.length !== 36 || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value) || value === "00000000-0000-0000-0000-000000000000")) throw new Error();
20144
- if ([input.trustFile, input.stateFile].some((path) => typeof path !== "string" || !path || isAbsolute4(path))) throw new Error();
20145
- const trustFile = resolve6(root, input.trustFile), stateFile = resolve6(root, input.stateFile);
20858
+ if ([input.trustFile, input.stateFile].some((path) => typeof path !== "string" || !path || isAbsolute5(path))) throw new Error();
20859
+ const trustFile = resolve8(root, input.trustFile), stateFile = resolve8(root, input.stateFile);
20146
20860
  if (trustFile === stateFile) throw new Error();
20147
20861
  const bytes = read(trustFile, 131072), trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
20148
20862
  read(stateFile, 2048);
20149
- if (createHash6("sha256").update(bytes).digest("hex") !== input.trustDigest || trust.revision !== input.revision) throw new Error();
20863
+ if (createHash7("sha256").update(bytes).digest("hex") !== input.trustDigest || trust.revision !== input.revision) throw new Error();
20150
20864
  createPersistentApplicationAuthProvider(trust, { expectedScope: input.scope, stateFile });
20151
20865
  return { trustFile, stateFile, scope: structuredClone(input.scope), revision: input.revision, trustDigest: input.trustDigest };
20152
20866
  } catch {
@@ -20155,39 +20869,39 @@ function prepareLocalJobAuth(root, filename2) {
20155
20869
  }
20156
20870
 
20157
20871
  // src/local-job-trust-renewal.ts
20158
- import { createHash as createHash8 } from "node:crypto";
20159
- import { lstatSync as lstatSync8, readFileSync as readFileSync8 } from "node:fs";
20160
- import { isAbsolute as isAbsolute6, relative as relative2, resolve as resolve8 } from "node:path";
20872
+ import { createHash as createHash9 } from "node:crypto";
20873
+ import { lstatSync as lstatSync10, readFileSync as readFileSync11 } from "node:fs";
20874
+ import { isAbsolute as isAbsolute7, relative as relative2, resolve as resolve10 } from "node:path";
20161
20875
 
20162
20876
  // src/workload-trust-sync.ts
20163
- import { lstatSync as lstatSync7, readFileSync as readFileSync7 } from "node:fs";
20164
- import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
20165
- import { createHash as createHash7 } from "node:crypto";
20877
+ import { lstatSync as lstatSync9, readFileSync as readFileSync10 } from "node:fs";
20878
+ import { isAbsolute as isAbsolute6, resolve as resolve9 } from "node:path";
20879
+ import { createHash as createHash8 } from "node:crypto";
20166
20880
  function prepareWorkloadTrustSynchronization(filename2) {
20167
20881
  try {
20168
- if (!isAbsolute5(filename2)) throw new Error();
20169
- const metadata = lstatSync7(filename2);
20882
+ if (!isAbsolute6(filename2)) throw new Error();
20883
+ const metadata = lstatSync9(filename2);
20170
20884
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 65536) throw new Error();
20171
- const bytes = readFileSync7(filename2);
20885
+ const bytes = readFileSync10(filename2);
20172
20886
  if (bytes.length > 65536) throw new Error();
20173
20887
  const value = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
20174
20888
  const allowed = ["controlPlaneUrl", "agentId", "expectedScope", "managementIssuer", "managementAudience", "managementPublicKeyPem", "credentialFile", "trustFile", "stateFile", "intervalSeconds"];
20175
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((name2) => !allowed.includes(name2)) || typeof value.credentialFile !== "string" || !isAbsolute5(value.credentialFile) || typeof value.trustFile !== "string" || !isAbsolute5(value.trustFile) || typeof value.stateFile !== "string" || !isAbsolute5(value.stateFile)) throw new Error();
20176
- const credentialFile = resolve7(value.credentialFile), trustFile = resolve7(value.trustFile), stateFile = resolve7(value.stateFile);
20889
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((name2) => !allowed.includes(name2)) || typeof value.credentialFile !== "string" || !isAbsolute6(value.credentialFile) || typeof value.trustFile !== "string" || !isAbsolute6(value.trustFile) || typeof value.stateFile !== "string" || !isAbsolute6(value.stateFile)) throw new Error();
20890
+ const credentialFile = resolve9(value.credentialFile), trustFile = resolve9(value.trustFile), stateFile = resolve9(value.stateFile);
20177
20891
  const comparison = (filename3) => process.platform === "win32" ? filename3.toLowerCase() : filename3;
20178
20892
  if ([trustFile, stateFile, stateFile + ".lock", stateFile + ".delivery.lock"].map(comparison).includes(comparison(credentialFile))) throw new Error();
20179
20893
  const intervalSeconds = value.intervalSeconds ?? 30;
20180
20894
  if (!Number.isSafeInteger(intervalSeconds) || intervalSeconds < 10 || intervalSeconds > 300) throw new Error();
20181
20895
  const credential = () => {
20182
- const metadata2 = lstatSync7(credentialFile);
20896
+ const metadata2 = lstatSync9(credentialFile);
20183
20897
  if (!metadata2.isFile() || metadata2.isSymbolicLink() || metadata2.nlink !== 1 || metadata2.size > 256 || process.platform !== "win32" && (metadata2.uid !== process.getuid?.() || (metadata2.mode & 63) !== 0)) throw new Error("Management credential unavailable");
20184
- const bytes2 = readFileSync7(credentialFile);
20898
+ const bytes2 = readFileSync10(credentialFile);
20185
20899
  if (bytes2.length > 256) throw new Error("Management credential unavailable");
20186
20900
  return new TextDecoder("utf8", { fatal: true }).decode(bytes2).trim();
20187
20901
  };
20188
20902
  const options = { controlPlaneUrl: value.controlPlaneUrl, agentId: value.agentId, expectedScope: value.expectedScope, managementIssuer: value.managementIssuer, managementAudience: value.managementAudience, managementPublicKeyPem: value.managementPublicKeyPem, credential, trustFile, stateFile };
20189
20903
  const plan = planApplicationAuthSynchronization(options);
20190
- return { options, plan, intervalSeconds, credentialFile, configurationDigest: createHash7("sha256").update(bytes).digest("hex") };
20904
+ return { options, plan, intervalSeconds, credentialFile, configurationDigest: createHash8("sha256").update(bytes).digest("hex") };
20191
20905
  } catch {
20192
20906
  throw new Error("Invalid operator trust-sync configuration");
20193
20907
  }
@@ -20238,22 +20952,22 @@ async function workloadTrustSynchronization(args2, settings = {}) {
20238
20952
  // src/local-job-trust-renewal.ts
20239
20953
  function prepareLocalJobTrustRenewal(root, filename2, mount) {
20240
20954
  try {
20241
- if (!filename2 || isAbsolute6(filename2)) throw new Error();
20242
- const configurationFile = resolve8(root, filename2);
20955
+ if (!filename2 || isAbsolute7(filename2)) throw new Error();
20956
+ const configurationFile = resolve10(root, filename2);
20243
20957
  assertLocalStatePath(root, configurationFile);
20244
20958
  const configured = prepareWorkloadTrustSynchronization(configurationFile);
20245
20959
  const { credential, ...options } = configured.options;
20246
20960
  const same = (first, second) => process.platform === "win32" ? first.toLowerCase() === second.toLowerCase() : first === second;
20247
20961
  if (!same(options.trustFile, mount.trustFile) || !same(options.stateFile, mount.stateFile) || ["organizationId", "applicationId", "environmentId"].some((name2) => options.expectedScope[name2] !== mount.scope[name2])) throw new Error();
20248
20962
  assertLocalStatePath(root, configured.credentialFile);
20249
- const metadata = lstatSync8(configured.credentialFile);
20963
+ const metadata = lstatSync10(configured.credentialFile);
20250
20964
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 256 || process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & 63) !== 0)) throw new Error();
20251
20965
  return { configurationFile, configurationDigest: configured.configurationDigest, credentialFile: configured.credentialFile, intervalSeconds: configured.intervalSeconds, options };
20252
20966
  } catch {
20253
20967
  throw new Error("Invalid reviewed native worker trust renewal; preserve trust and state and review explicitly");
20254
20968
  }
20255
20969
  }
20256
- function createLocalJobTrustRenewal(root, mounts, settings = {}, dependencies = { createHash: createHash8, lstatSync: lstatSync8, readFileSync: readFileSync8, isAbsolute: isAbsolute6, relative: relative2, resolve: resolve8, createPersistentApplicationAuthProvider, synchronizeApplicationAuthTrustWithDigest }) {
20970
+ function createLocalJobTrustRenewal(root, mounts, settings = {}, dependencies = { createHash: createHash9, lstatSync: lstatSync10, readFileSync: readFileSync11, isAbsolute: isAbsolute7, relative: relative2, resolve: resolve10, createPersistentApplicationAuthProvider, synchronizeApplicationAuthTrustWithDigest }) {
20257
20971
  const { createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2, synchronizeApplicationAuthTrustWithDigest: synchronizeApplicationAuthTrustWithDigest2 } = dependencies;
20258
20972
  const key = (filename2) => process.platform === "win32" ? resolve24(filename2).toLowerCase() : resolve24(filename2);
20259
20973
  const groups = [];
@@ -20352,34 +21066,34 @@ function applyLocalJobTrustRenewal(workers, updates, check) {
20352
21066
 
20353
21067
  // src/local-job-executor.ts
20354
21068
  function prepareLocalJobWorker(root, directory2, artifacts, manifest, authFile, trustSyncFile) {
20355
- if (typeof directory2 !== "string" || !directory2 || isAbsolute7(directory2)) throw new Error("Native jobs require a repository-relative packaged worker directory");
21069
+ if (typeof directory2 !== "string" || !directory2 || isAbsolute8(directory2)) throw new Error("Native jobs require a repository-relative packaged worker directory");
20356
21070
  const authenticationEnabled = !!manifest.capabilities.auth && manifest.capabilities.auth.enabled !== false;
20357
21071
  if (authenticationEnabled && authFile === void 0) throw new Error("Native local packaged workers require separately reviewed application-auth trust mounts; local identity fixtures are not production issuer trust");
20358
21072
  if (!authenticationEnabled && authFile !== void 0) throw new Error("Native worker trust mounts require an enabled auth capability");
20359
21073
  if (trustSyncFile !== void 0 && authFile === void 0) throw new Error("Native worker trust renewal requires explicit reviewed auth mounts");
20360
21074
  const authTrust = authFile === void 0 ? void 0 : prepareLocalJobAuth(root, authFile);
20361
21075
  if (authTrust && trustSyncFile !== void 0) authTrust.renewal = prepareLocalJobTrustRenewal(root, trustSyncFile, authTrust);
20362
- const workerFile = join5(resolve9(root, directory2), "workload.mjs"), metadataFile = join5(resolve9(root, directory2), "artifact.json");
21076
+ const workerFile = join8(resolve11(root, directory2), "workload.mjs"), metadataFile = join8(resolve11(root, directory2), "artifact.json");
20363
21077
  for (const filename2 of [workerFile, metadataFile]) assertLocalStatePath(root, filename2);
20364
- if (!lstatSync9(workerFile).isFile() || lstatSync9(workerFile).size > 16777216 || !lstatSync9(metadataFile).isFile() || lstatSync9(metadataFile).size > 65536) throw new Error("Invalid bounded native worker artifact");
20365
- const metadata = JSON.parse(readFileSync9(metadataFile, "utf8"));
20366
- const bundle = readFileSync9(workerFile);
20367
- const actual = createHash9("sha256").update(bundle).digest("hex");
21078
+ if (!lstatSync11(workerFile).isFile() || lstatSync11(workerFile).size > 16777216 || !lstatSync11(metadataFile).isFile() || lstatSync11(metadataFile).size > 65536) throw new Error("Invalid bounded native worker artifact");
21079
+ const metadata = JSON.parse(readFileSync12(metadataFile, "utf8"));
21080
+ const bundle = readFileSync12(workerFile);
21081
+ const actual = createHash10("sha256").update(bundle).digest("hex");
20368
21082
  const scheduleDiscovery = bundle.subarray(0, 100).toString("utf8").startsWith('var __backifyWorkerProtocols = ["hcr-v1","hcr-schedules-v1"];\n');
20369
- if (!metadata || metadata.schemaVersion !== 1 || metadata.kind !== "worker" || metadata.artifactVersion !== actual || metadata.manifestVersion !== createHash9("sha256").update(JSON.stringify(manifest)).digest("hex")) throw new Error("Native worker kind, bundle or manifest pin does not match the reviewed artifact");
21083
+ if (!metadata || metadata.schemaVersion !== 1 || metadata.kind !== "worker" || metadata.artifactVersion !== actual || metadata.manifestVersion !== createHash10("sha256").update(JSON.stringify(manifest)).digest("hex")) throw new Error("Native worker kind, bundle or manifest pin does not match the reviewed artifact");
20370
21084
  const artifact = validateLocalJobArtifacts(artifacts).find((item) => item.artifactVersion === actual);
20371
21085
  if (!artifact) throw new Error("Native worker bundle is not an explicitly reviewed job artifact");
20372
21086
  if (metadata.workerProtocols !== void 0 && (!scheduleDiscovery || JSON.stringify(metadata.workerProtocols) !== JSON.stringify(["hcr-v1", "hcr-schedules-v1"]))) throw new Error("Invalid worker protocol admission");
20373
21087
  return { root, workerFile, artifact, schedulerEnabled: !!manifest.capabilities.scheduler && manifest.capabilities.scheduler.enabled !== false, ...scheduleDiscovery ? { scheduleDiscovery: true } : {}, ...authTrust ? { authTrust } : {} };
20374
21088
  }
20375
21089
  function readLocalJobArtifacts(root, filename2) {
20376
- if (!filename2 || isAbsolute7(filename2)) throw new Error("Job artifact admission requires a repository-relative reviewed JSON file");
20377
- const path = resolve9(root, filename2);
21090
+ if (!filename2 || isAbsolute8(filename2)) throw new Error("Job artifact admission requires a repository-relative reviewed JSON file");
21091
+ const path = resolve11(root, filename2);
20378
21092
  assertLocalStatePath(root, path);
20379
- if (!lstatSync9(path).isFile() || lstatSync9(path).size > 16384) throw new Error("Reviewed job artifact admission exceeds supported bounds");
20380
- return validateLocalJobArtifacts(JSON.parse(readFileSync9(path, "utf8")));
21093
+ if (!lstatSync11(path).isFile() || lstatSync11(path).size > 16384) throw new Error("Reviewed job artifact admission exceeds supported bounds");
21094
+ return validateLocalJobArtifacts(JSON.parse(readFileSync12(path, "utf8")));
20381
21095
  }
20382
- function createLocalJobExecutor(options, dependencies = { spawn: spawn2, createHash: createHash9, lstatSync: lstatSync9, readFileSync: readFileSync9, isAbsolute: isAbsolute7, relative: relative3, resolve: resolve9, validateWorkloadJobSchedules, createPersistentApplicationAuthProvider }) {
21096
+ function createLocalJobExecutor(options, dependencies = { spawn: spawn2, createHash: createHash10, lstatSync: lstatSync11, readFileSync: readFileSync12, isAbsolute: isAbsolute8, relative: relative3, resolve: resolve11, validateWorkloadJobSchedules, createPersistentApplicationAuthProvider }) {
20383
21097
  const { spawn: spawn6, createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, validateWorkloadJobSchedules: validateWorkloadJobSchedules2, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2 } = dependencies;
20384
21098
  const assertReady = () => {
20385
21099
  for (let current = resolve24(options.workerFile); current !== options.root; current = resolve24(current, "..")) {
@@ -20512,9 +21226,9 @@ function createLocalJobExecutor(options, dependencies = { spawn: spawn2, createH
20512
21226
  }
20513
21227
 
20514
21228
  // src/local-native-jobs.ts
20515
- import { createHash as createHash10 } from "node:crypto";
21229
+ import { createHash as createHash11 } from "node:crypto";
20516
21230
  function createLocalNativeJobs(local, native, executor, artifact, scope, onFailure = () => {
20517
- }, hash2 = createHash10, catalog = { workloadJobSchedules, validateWorkloadJobSchedules, HcrJobsRequestError }, historical = []) {
21231
+ }, hash2 = createHash11, catalog = { workloadJobSchedules, validateWorkloadJobSchedules, HcrJobsRequestError }, historical = []) {
20518
21232
  const workers = [{ artifact, executor }, ...historical];
20519
21233
  if (historical.length > 15 || new Set(workers.map((worker) => worker.artifact.artifactVersion)).size !== workers.length) throw new Error("Historical workers require bounded unique artifact pins");
20520
21234
  const lanes = workers.flatMap((worker) => worker.artifact.jobs.map((policy) => ({ ...worker, policy })));
@@ -20772,9 +21486,9 @@ process.on("SIGTERM", stop); process.on("SIGINT", stop); process.once("disconnec
20772
21486
 
20773
21487
  // src/local-frontend.ts
20774
21488
  import { spawn as spawn3 } from "node:child_process";
20775
- import { readFileSync as readFileSync11, realpathSync as realpathSync3, existsSync as existsSync6, statSync } from "node:fs";
21489
+ import { readFileSync as readFileSync14, realpathSync as realpathSync3, existsSync as existsSync9, statSync } from "node:fs";
20776
21490
  import { createRequire as createRequire3 } from "node:module";
20777
- import { dirname as dirname7, isAbsolute as isAbsolute9, join as join7, relative as relative5, resolve as resolve11 } from "node:path";
21491
+ import { dirname as dirname10, isAbsolute as isAbsolute10, join as join10, relative as relative5, resolve as resolve13 } from "node:path";
20778
21492
  import { pathToFileURL } from "node:url";
20779
21493
 
20780
21494
  // src/local-frontend-guard.ts
@@ -20841,24 +21555,24 @@ await server.listen();
20841
21555
  `;
20842
21556
 
20843
21557
  // src/local-static-frontend.ts
20844
- import { createServer as createServer2 } from "node:http";
20845
- import { lstatSync as lstatSync10, readFileSync as readFileSync10, realpathSync as realpathSync2, watch } from "node:fs";
20846
- import { extname as extname2, isAbsolute as isAbsolute8, join as join6, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
21558
+ import { createServer as createServer3 } from "node:http";
21559
+ import { lstatSync as lstatSync12, readFileSync as readFileSync13, realpathSync as realpathSync2, watch } from "node:fs";
21560
+ import { extname as extname2, isAbsolute as isAbsolute9, join as join9, relative as relative4, resolve as resolve12, sep as sep4 } from "node:path";
20847
21561
  var contentTypes = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".mjs": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json", ".txt": "text/plain; charset=utf-8", ".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".ico": "image/x-icon", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf" };
20848
21562
  var publicParts = (parts) => parts.every((part) => part.length > 0 && !part.startsWith(".") && !part.includes(":") && !part.includes("\\") && !part.includes("\0") && !["node_modules", "__backify"].includes(part.toLowerCase()));
20849
21563
  function publicFile(directory2, pathname) {
20850
21564
  const parts = pathname.split("/").slice(1);
20851
21565
  if (parts.at(-1) === "") parts[parts.length - 1] = "index.html";
20852
21566
  if (!publicParts(parts)) throw new Error("refused");
20853
- const filename2 = resolve10(directory2, ...parts), part = relative4(directory2, filename2);
20854
- if (part === ".." || part.startsWith(".." + sep4) || isAbsolute8(part) || !contentTypes[extname2(filename2).toLowerCase()]) throw new Error("refused");
21567
+ const filename2 = resolve12(directory2, ...parts), part = relative4(directory2, filename2);
21568
+ if (part === ".." || part.startsWith(".." + sep4) || isAbsolute9(part) || !contentTypes[extname2(filename2).toLowerCase()]) throw new Error("refused");
20855
21569
  let current = directory2;
20856
- if (lstatSync10(current).isSymbolicLink() || realpathSync2(current) !== directory2) throw new Error("refused");
21570
+ if (lstatSync12(current).isSymbolicLink() || realpathSync2(current) !== directory2) throw new Error("refused");
20857
21571
  for (const segment of parts) {
20858
- current = join6(current, segment);
20859
- if (lstatSync10(current).isSymbolicLink()) throw new Error("refused");
21572
+ current = join9(current, segment);
21573
+ if (lstatSync12(current).isSymbolicLink()) throw new Error("refused");
20860
21574
  }
20861
- const stat = lstatSync10(filename2);
21575
+ const stat = lstatSync12(filename2);
20862
21576
  if (!stat.isFile() || stat.nlink !== 1 || stat.size > 5242880) throw new Error("refused");
20863
21577
  return filename2;
20864
21578
  }
@@ -20873,7 +21587,7 @@ async function startLocalStaticFrontend(directory2, port, backendUrl) {
20873
21587
  const watcher = watch(directory2, { recursive: true }, (_, filename2) => {
20874
21588
  if (filename2 && publicParts(filename2.toString().split(/[\\/]/)) && contentTypes[extname2(filename2.toString()).toLowerCase()]) revision++;
20875
21589
  });
20876
- const server = createServer2((request, response) => {
21590
+ const server = createServer3((request, response) => {
20877
21591
  const send = (status, type, body) => {
20878
21592
  response.writeHead(status, { "content-type": type, "cache-control": "no-store", "x-content-type-options": "nosniff", "content-length": Buffer.byteLength(body) });
20879
21593
  response.end(request.method === "HEAD" ? void 0 : body);
@@ -20886,7 +21600,7 @@ async function startLocalStaticFrontend(directory2, port, backendUrl) {
20886
21600
  if (pathname === "/__backify/revision") return send(200, "text/plain", String(revision));
20887
21601
  if (pathname === "/__backify/config.json") return send(200, "application/json", JSON.stringify({ backendUrl: backendUrl ?? null }));
20888
21602
  const filename2 = publicFile(directory2, pathname);
20889
- const content = readFileSync10(filename2);
21603
+ const content = readFileSync13(filename2);
20890
21604
  if (content.length > 5242880) throw new Error("refused");
20891
21605
  const extension = extname2(filename2).toLowerCase();
20892
21606
  const body = extension === ".html" ? content.toString("utf8") + '\n<script src="/__backify/reload.js?revision=' + revision + '"></script>\n' : content;
@@ -20929,31 +21643,31 @@ function prepareLocalFrontend(root, options, privateFiles = []) {
20929
21643
  if (options.frontendPort !== void 0) throw new Error("Frontend port requires an explicit frontend directory");
20930
21644
  return void 0;
20931
21645
  }
20932
- const directory2 = realpathSync3(resolve11(root, options.frontend));
21646
+ const directory2 = realpathSync3(resolve13(root, options.frontend));
20933
21647
  const part = relative5(realpathSync3(root), directory2);
20934
- if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute9(part) || !statSync(directory2).isDirectory()) throw new Error("Frontend must be a directory inside this repository");
21648
+ if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute10(part) || !statSync(directory2).isDirectory()) throw new Error("Frontend must be a directory inside this repository");
20935
21649
  if (options.frontendAdapter === "static") {
20936
21650
  if (part && part.split(/[\\/]/).some((segment) => segment.startsWith(".") || segment.toLowerCase() === "node_modules")) throw new Error("Static frontend must not use a private state or dependency directory");
20937
21651
  return prepareLocalStaticFrontend(directory2);
20938
21652
  }
20939
- if (!existsSync6(join7(directory2, "package.json"))) throw new Error("Frontend requires an existing package.json and installed Vite");
20940
- const require3 = createRequire3(join7(directory2, "package.json"));
21653
+ if (!existsSync9(join10(directory2, "package.json"))) throw new Error("Frontend requires an existing package.json and installed Vite");
21654
+ const require3 = createRequire3(join10(directory2, "package.json"));
20941
21655
  let packagePath;
20942
21656
  try {
20943
21657
  packagePath = require3.resolve("vite/package.json");
20944
21658
  } catch {
20945
21659
  throw new Error("Frontend requires locally installed Vite; Backify does not install dependencies automatically");
20946
21660
  }
20947
- const metadata = JSON.parse(readFileSync11(packagePath, "utf8"));
21661
+ const metadata = JSON.parse(readFileSync14(packagePath, "utf8"));
20948
21662
  if (metadata.name !== "vite" || typeof metadata.bin?.vite !== "string") throw new Error("Installed Vite has no supported CLI entrypoint");
20949
- const executable = realpathSync3(resolve11(dirname7(packagePath), metadata.bin.vite));
20950
- const executablePart = relative5(realpathSync3(dirname7(packagePath)), executable);
20951
- if (executablePart === ".." || executablePart.startsWith("../") || executablePart.startsWith("..\\") || isAbsolute9(executablePart)) throw new Error("Vite CLI entrypoint must remain inside its package");
21663
+ const executable = realpathSync3(resolve13(dirname10(packagePath), metadata.bin.vite));
21664
+ const executablePart = relative5(realpathSync3(dirname10(packagePath)), executable);
21665
+ if (executablePart === ".." || executablePart.startsWith("../") || executablePart.startsWith("..\\") || isAbsolute10(executablePart)) throw new Error("Vite CLI entrypoint must remain inside its package");
20952
21666
  const timeout = options.frontendStartupTimeoutMs ?? 3e4;
20953
21667
  if (!Number.isInteger(timeout) || timeout < 100 || timeout > 12e4) throw new Error("Frontend startup timeout must be 100-120000 milliseconds");
20954
21668
  const modulePath = realpathSync3(require3.resolve("vite"));
20955
- const modulePart = relative5(realpathSync3(dirname7(packagePath)), modulePath);
20956
- if (modulePart === ".." || modulePart.startsWith("../") || modulePart.startsWith("..\\") || isAbsolute9(modulePart)) throw new Error("Vite module must remain inside its package");
21669
+ const modulePart = relative5(realpathSync3(dirname10(packagePath)), modulePath);
21670
+ if (modulePart === ".." || modulePart.startsWith("../") || modulePart.startsWith("..\\") || isAbsolute10(modulePart)) throw new Error("Vite module must remain inside its package");
20957
21671
  return { adapter: "vite", directory: directory2, modulePath, timeout, privateFiles };
20958
21672
  }
20959
21673
  async function stopFrontend(child) {
@@ -21008,17 +21722,19 @@ async function startLocalFrontend(prepared, port, backendUrl) {
21008
21722
  }
21009
21723
 
21010
21724
  // src/local-identity.ts
21011
- import { readFileSync as readFileSync12, statSync as statSync2 } from "node:fs";
21012
- import { resolve as resolve12 } from "node:path";
21725
+ import { readFileSync as readFileSync15, statSync as statSync2 } from "node:fs";
21726
+ import { resolve as resolve14 } from "node:path";
21013
21727
 
21014
21728
  // ../sdk/dist/local-identity.js
21015
- import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
21729
+ import { createHash as createHash12, randomBytes as randomBytes4 } from "node:crypto";
21016
21730
  function createLocalIdentityProvider(options) {
21017
21731
  const serverOnly = () => {
21018
21732
  if (typeof window !== "undefined")
21019
21733
  throw new Error("Local development identities are server-only");
21020
21734
  };
21021
21735
  serverOnly();
21736
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production")
21737
+ throw new Error("Local Backify identities must not run in production");
21022
21738
  const uuid5 = (value) => typeof value === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value) && value !== "00000000-0000-0000-0000-000000000000";
21023
21739
  const text3 = (value, maximum) => typeof value === "string" && value.length > 0 && value.length <= maximum && !/[\x00-\x1f\x7f]/.test(value);
21024
21740
  if (!options.scope || Object.keys(options.scope).length !== 3 || !uuid5(options.scope.organizationId) || !uuid5(options.scope.applicationId) || !uuid5(options.scope.environmentId) || !text3(options.organizationName, 200) || !Array.isArray(options.users) || options.users.length < 1 || options.users.length > 32 || !Array.isArray(options.requestOrigins) || options.requestOrigins.length < 1 || options.requestOrigins.length > 8)
@@ -21026,9 +21742,11 @@ function createLocalIdentityProvider(options) {
21026
21742
  const scope = { ...options.scope }, organizationName = options.organizationName;
21027
21743
  const users = /* @__PURE__ */ new Map();
21028
21744
  for (const user of options.users) {
21029
- if (!user || Object.keys(user).some((name2) => !["id", "displayName", "email"].includes(name2)) || !uuid5(user.id) || users.has(user.id) || !text3(user.displayName, 200) || user.email !== void 0 && !text3(user.email, 320))
21745
+ if (!user || Object.keys(user).some((name2) => !["id", "displayName", "email", "roles"].includes(name2)) || !uuid5(user.id) || users.has(user.id) || !text3(user.displayName, 200) || user.email !== void 0 && !text3(user.email, 320))
21030
21746
  throw new Error("Invalid local identity user");
21031
- users.set(user.id, { ...user });
21747
+ if (user.roles !== void 0 && (!Array.isArray(user.roles) || user.roles.length > 32 || user.roles.some((role) => !role || Object.keys(role).some((name2) => !["name", "permissions"].includes(name2)) || !text3(role.name, 100) || !Array.isArray(role.permissions) || role.permissions.length > 128 || role.permissions.some((permission) => !text3(permission, 100)) || new Set(role.permissions).size !== role.permissions.length) || new Set(user.roles.map((role) => role.name)).size !== user.roles.length))
21748
+ throw new Error("Invalid local identity roles");
21749
+ users.set(user.id, structuredClone(user));
21032
21750
  }
21033
21751
  const origins = /* @__PURE__ */ new Set();
21034
21752
  for (const value of options.requestOrigins) {
@@ -21051,7 +21769,7 @@ function createLocalIdentityProvider(options) {
21051
21769
  return value;
21052
21770
  };
21053
21771
  const sessions = /* @__PURE__ */ new Map();
21054
- const digest4 = (token) => createHash11("sha256").update(token).digest("hex");
21772
+ const digest4 = (token) => createHash12("sha256").update(token).digest("hex");
21055
21773
  const tokenPattern = /^backify-local-user_[a-zA-Z0-9_-]{43}$/;
21056
21774
  const requestAuth = {
21057
21775
  forRequest(request) {
@@ -21068,7 +21786,7 @@ function createLocalIdentityProvider(options) {
21068
21786
  if (!session || session.issued > time || session.expires <= time)
21069
21787
  throw new ApplicationAuthenticationError("application_identity_invalid");
21070
21788
  const user = users.get(session.userId);
21071
- return { ...user, roles: [], claims: { ...scope, localDevelopment: true } };
21789
+ return { ...structuredClone(user), roles: structuredClone(user.roles ?? []), claims: { ...scope, localDevelopment: true } };
21072
21790
  };
21073
21791
  return { currentUser: async () => identity(), currentOrganization: async () => identity() ? { id: scope.organizationId, name: organizationName } : null };
21074
21792
  }
@@ -21077,7 +21795,7 @@ function createLocalIdentityProvider(options) {
21077
21795
  requestAuth,
21078
21796
  profiles() {
21079
21797
  serverOnly();
21080
- return [...users.values()].map((user) => ({ ...user }));
21798
+ return [...users.values()].map((user) => structuredClone(user));
21081
21799
  },
21082
21800
  issue(userId, lifetimeSeconds = 300) {
21083
21801
  const time = now();
@@ -21088,7 +21806,7 @@ function createLocalIdentityProvider(options) {
21088
21806
  sessions.delete(hash2);
21089
21807
  if (sessions.size >= 128)
21090
21808
  throw new ApplicationAuthenticationError("application_identity_unavailable");
21091
- const accessToken = "backify-local-user_" + randomBytes3(32).toString("base64url"), expires = time + lifetimeSeconds;
21809
+ const accessToken = "backify-local-user_" + randomBytes4(32).toString("base64url"), expires = time + lifetimeSeconds;
21092
21810
  sessions.set(digest4(accessToken), { userId, issued: time, expires });
21093
21811
  return { accessToken, tokenType: "Bearer", expiresAt: new Date(expires * 1e3).toISOString() };
21094
21812
  },
@@ -21104,22 +21822,22 @@ function createLocalIdentityProvider(options) {
21104
21822
  }
21105
21823
 
21106
21824
  // src/local-identity.ts
21107
- import { randomUUID as randomUUID6 } from "node:crypto";
21825
+ import { randomUUID as randomUUID7 } from "node:crypto";
21108
21826
  function prepareLocalIdentity(root, filename2) {
21109
21827
  if (filename2 === void 0) return void 0;
21110
- const path = resolve12(root, filename2);
21828
+ const path = resolve14(root, filename2);
21111
21829
  assertLocalStatePath(root, path);
21112
21830
  if (!statSync2(path).isFile() || statSync2(path).size > 16384) throw new Error("Local identity profiles require a file at most 16 KiB");
21113
21831
  let input;
21114
21832
  try {
21115
- input = JSON.parse(readFileSync12(path, "utf8"));
21833
+ input = JSON.parse(readFileSync15(path, "utf8"));
21116
21834
  } catch {
21117
21835
  throw new Error("Invalid local identity profile JSON");
21118
21836
  }
21119
21837
  if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((name2) => !["version", "organizationName", "users"].includes(name2))) throw new Error("Invalid local identity profiles");
21120
21838
  const value = input;
21121
21839
  if (value.version !== 1) throw new Error("Invalid local identity profile version");
21122
- const identity = createLocalIdentityProvider({ scope: { organizationId: randomUUID6(), applicationId: randomUUID6(), environmentId: randomUUID6() }, organizationName: value.organizationName, users: value.users, requestOrigins: ["http://127.0.0.1:3000"] });
21840
+ const identity = createLocalIdentityProvider({ scope: { organizationId: randomUUID7(), applicationId: randomUUID7(), environmentId: randomUUID7() }, organizationName: value.organizationName, users: value.users, requestOrigins: ["http://127.0.0.1:3000"] });
21123
21841
  return { organizationName: value.organizationName, users: identity.profiles() };
21124
21842
  }
21125
21843
  function localIdentityCommand(args2) {
@@ -21145,17 +21863,17 @@ function localIdentityCommand(args2) {
21145
21863
  }
21146
21864
 
21147
21865
  // src/local-job-history.ts
21148
- import { lstatSync as lstatSync11, readFileSync as readFileSync13 } from "node:fs";
21149
- import { isAbsolute as isAbsolute10, resolve as resolve13 } from "node:path";
21866
+ import { lstatSync as lstatSync13, readFileSync as readFileSync16 } from "node:fs";
21867
+ import { isAbsolute as isAbsolute11, resolve as resolve15 } from "node:path";
21150
21868
  function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21151
21869
  if (filename2 === void 0) return [];
21152
21870
  const read = (filename3, maximum) => {
21153
- if (typeof filename3 !== "string" || !filename3 || isAbsolute10(filename3)) throw new Error();
21154
- const path = resolve13(root, filename3);
21871
+ if (typeof filename3 !== "string" || !filename3 || isAbsolute11(filename3)) throw new Error();
21872
+ const path = resolve15(root, filename3);
21155
21873
  assertLocalStatePath(root, path);
21156
- const metadata = lstatSync11(path);
21874
+ const metadata = lstatSync13(path);
21157
21875
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > maximum) throw new Error();
21158
- const bytes = readFileSync13(path);
21876
+ const bytes = readFileSync16(path);
21159
21877
  if (bytes.length > maximum) throw new Error();
21160
21878
  return new TextDecoder("utf8", { fatal: true }).decode(bytes);
21161
21879
  };
@@ -21165,7 +21883,7 @@ function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21165
21883
  if (!input || Object.keys(input).sort().join(",") !== "schemaVersion,workers" || input.schemaVersion !== 1 || !Array.isArray(input.workers) || input.workers.length < 1 || input.workers.length > 15) throw new Error();
21166
21884
  const versions = /* @__PURE__ */ new Set([current.artifact.artifactVersion]);
21167
21885
  for (const item of input.workers) {
21168
- if (!item || Object.keys(item).some((name2) => !["artifactVersion", "authFile", "trustSyncFile", "manifestFile", "workerDirectory"].includes(name2)) || ["artifactVersion", "manifestFile", "workerDirectory"].some((name2) => !Object.hasOwn(item, name2)) || typeof item.artifactVersion !== "string" || item.artifactVersion.length !== 64 || !/^[a-f0-9]{64}$/.test(item.artifactVersion) || versions.has(item.artifactVersion) || !artifacts.some((artifact) => artifact.artifactVersion === item.artifactVersion) || typeof item.workerDirectory !== "string" || !item.workerDirectory || isAbsolute10(item.workerDirectory)) throw new Error();
21886
+ if (!item || Object.keys(item).some((name2) => !["artifactVersion", "authFile", "trustSyncFile", "manifestFile", "workerDirectory"].includes(name2)) || ["artifactVersion", "manifestFile", "workerDirectory"].some((name2) => !Object.hasOwn(item, name2)) || typeof item.artifactVersion !== "string" || item.artifactVersion.length !== 64 || !/^[a-f0-9]{64}$/.test(item.artifactVersion) || versions.has(item.artifactVersion) || !artifacts.some((artifact) => artifact.artifactVersion === item.artifactVersion) || typeof item.workerDirectory !== "string" || !item.workerDirectory || isAbsolute11(item.workerDirectory)) throw new Error();
21169
21887
  versions.add(item.artifactVersion);
21170
21888
  }
21171
21889
  return input.workers.map((item) => {
@@ -21201,30 +21919,30 @@ function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21201
21919
  }
21202
21920
 
21203
21921
  // src/local-secrets.ts
21204
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, randomUUID as randomUUID7 } from "node:crypto";
21205
- import { closeSync as closeSync3, existsSync as existsSync7, fsyncSync as fsyncSync3, lstatSync as lstatSync12, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync14, realpathSync as realpathSync4, writeFileSync as writeFileSync6 } from "node:fs";
21206
- import { dirname as dirname8, isAbsolute as isAbsolute11, join as join8, relative as relative6, resolve as resolve14 } from "node:path";
21922
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes5, randomUUID as randomUUID8 } from "node:crypto";
21923
+ import { closeSync as closeSync3, existsSync as existsSync10, fsyncSync as fsyncSync3, lstatSync as lstatSync14, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync17, realpathSync as realpathSync4, writeFileSync as writeFileSync9 } from "node:fs";
21924
+ import { dirname as dirname11, isAbsolute as isAbsolute12, join as join11, relative as relative6, resolve as resolve16 } from "node:path";
21207
21925
  var maximumBytes2 = 1048576;
21208
21926
  var secretName = /^[A-Z][A-Z0-9_]{0,127}$/;
21209
21927
  var failure = () => new Error("Local secret vault unavailable or invalid; review the vault and external key privately");
21210
21928
  function vaultPath(root) {
21211
- return join8(realpathSync4(root), ".backify", "dev", "secrets.enc.json");
21929
+ return join11(realpathSync4(root), ".backify", "dev", "secrets.enc.json");
21212
21930
  }
21213
21931
  function inside(root, filename2) {
21214
21932
  const part = relative6(root, filename2);
21215
- return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute11(part);
21933
+ return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute12(part);
21216
21934
  }
21217
21935
  function privateFile(filename2, limit) {
21218
- for (let current = resolve14(filename2); ; current = dirname8(current)) {
21219
- if (lstatSync12(current).isSymbolicLink()) throw failure();
21220
- if (dirname8(current) === current) break;
21936
+ for (let current = resolve16(filename2); ; current = dirname11(current)) {
21937
+ if (lstatSync14(current).isSymbolicLink()) throw failure();
21938
+ if (dirname11(current) === current) break;
21221
21939
  }
21222
- const stat = lstatSync12(filename2);
21940
+ const stat = lstatSync14(filename2);
21223
21941
  if (!stat.isFile() || stat.size > limit || stat.nlink !== 1 || process.platform !== "win32" && (stat.mode & 63) !== 0) throw failure();
21224
- return readFileSync14(filename2);
21942
+ return readFileSync17(filename2);
21225
21943
  }
21226
21944
  function externalKey(root, filename2) {
21227
- const keyFile = resolve14(filename2);
21945
+ const keyFile = resolve16(filename2);
21228
21946
  if (inside(realpathSync4(root), keyFile) || inside(realpathSync4(root), realpathSync4(keyFile))) throw failure();
21229
21947
  const encoded = privateFile(keyFile, 64).toString("utf8");
21230
21948
  if (!/^[a-f0-9]{64}$/.test(encoded)) throw failure();
@@ -21238,7 +21956,7 @@ function context(root, application) {
21238
21956
  }
21239
21957
  function encrypt(root, application, key, secrets) {
21240
21958
  validateSecrets(secrets);
21241
- const nonce = randomBytes4(12), cipher = createCipheriv("aes-256-gcm", key, nonce);
21959
+ const nonce = randomBytes5(12), cipher = createCipheriv("aes-256-gcm", key, nonce);
21242
21960
  cipher.setAAD(context(root, application));
21243
21961
  const plaintext = Buffer.from(JSON.stringify(secrets));
21244
21962
  try {
@@ -21253,22 +21971,22 @@ function encrypt(root, application, key, secrets) {
21253
21971
  function writePrivate2(filename2, value) {
21254
21972
  const descriptor2 = openSync3(filename2, "wx", 384);
21255
21973
  try {
21256
- writeFileSync6(descriptor2, value);
21974
+ writeFileSync9(descriptor2, value);
21257
21975
  fsyncSync3(descriptor2);
21258
21976
  } finally {
21259
21977
  closeSync3(descriptor2);
21260
21978
  }
21261
21979
  }
21262
21980
  function ensureIgnored(root) {
21263
- const filename2 = join8(root, ".gitignore");
21981
+ const filename2 = join11(root, ".gitignore");
21264
21982
  assertLocalStatePath(root, filename2);
21265
- const current = existsSync7(filename2) ? readFileSync14(filename2, "utf8") : "";
21266
- if (!current.split(/\r?\n/).some((line) => line.trim() === ".backify/")) writeFileSync6(filename2, current + (current && !current.endsWith("\n") ? "\n" : "") + ".backify/\n");
21983
+ const current = existsSync10(filename2) ? readFileSync17(filename2, "utf8") : "";
21984
+ if (!current.split(/\r?\n/).some((line) => line.trim() === ".backify/")) writeFileSync9(filename2, current + (current && !current.endsWith("\n") ? "\n" : "") + ".backify/\n");
21267
21985
  }
21268
21986
  function readLocalSecrets(root, application, keyFile) {
21269
21987
  const filename2 = vaultPath(root);
21270
21988
  assertLocalStatePath(root, filename2);
21271
- if (!existsSync7(filename2) && keyFile === void 0) return {};
21989
+ if (!existsSync10(filename2) && keyFile === void 0) return {};
21272
21990
  if (!keyFile) throw new Error("Encrypted local secrets require --local-secrets-key <external-key-file>");
21273
21991
  let key, plaintext;
21274
21992
  try {
@@ -21296,22 +22014,22 @@ function readLocalSecrets(root, application, keyFile) {
21296
22014
  }
21297
22015
  }
21298
22016
  function initializeLocalSecrets(root, application, keyFile) {
21299
- const filename2 = vaultPath(root), external = resolve14(keyFile);
22017
+ const filename2 = vaultPath(root), external = resolve16(keyFile);
21300
22018
  assertLocalStatePath(root, filename2);
21301
- if (existsSync7(filename2) || existsSync7(external)) throw new Error("Local secret initialization never overwrites an existing vault or key");
21302
- if (inside(realpathSync4(root), external) || inside(realpathSync4(root), realpathSync4(dirname8(external)))) throw new Error("Local secret key must be outside the repository in an existing private directory");
21303
- for (let current = dirname8(external); ; current = dirname8(current)) {
21304
- const stat = lstatSync12(current);
22019
+ if (existsSync10(filename2) || existsSync10(external)) throw new Error("Local secret initialization never overwrites an existing vault or key");
22020
+ if (inside(realpathSync4(root), external) || inside(realpathSync4(root), realpathSync4(dirname11(external)))) throw new Error("Local secret key must be outside the repository in an existing private directory");
22021
+ for (let current = dirname11(external); ; current = dirname11(current)) {
22022
+ const stat = lstatSync14(current);
21305
22023
  if (stat.isSymbolicLink()) throw failure();
21306
- if (current === dirname8(external) && process.platform !== "win32" && (stat.mode & 63) !== 0) throw failure();
21307
- if (dirname8(current) === current) break;
22024
+ if (current === dirname11(external) && process.platform !== "win32" && (stat.mode & 63) !== 0) throw failure();
22025
+ if (dirname11(current) === current) break;
21308
22026
  }
21309
22027
  ensureIgnored(root);
21310
22028
  const release2 = acquireLocalLease(root, ".backify/dev/secrets.lock");
21311
- const key = randomBytes4(32);
22029
+ const key = randomBytes5(32);
21312
22030
  try {
21313
- if (existsSync7(filename2)) throw failure();
21314
- mkdirSync4(dirname8(filename2), { recursive: true, mode: 448 });
22031
+ if (existsSync10(filename2)) throw failure();
22032
+ mkdirSync5(dirname11(filename2), { recursive: true, mode: 448 });
21315
22033
  writePrivate2(external, key.toString("hex"));
21316
22034
  writePrivate2(filename2, encrypt(root, application, key, {}));
21317
22035
  } finally {
@@ -21330,7 +22048,7 @@ function setLocalSecret(root, application, keyFile, name2, value) {
21330
22048
  const secrets = readLocalSecrets(root, application, keyFile);
21331
22049
  secrets[name2] = value;
21332
22050
  key = externalKey(root, keyFile);
21333
- const temporary = filename2 + "." + randomUUID7() + ".tmp";
22051
+ const temporary = filename2 + "." + randomUUID8() + ".tmp";
21334
22052
  assertLocalStatePath(root, temporary);
21335
22053
  writePrivate2(temporary, encrypt(root, application, key, secrets));
21336
22054
  renameLocalFile(temporary, filename2);
@@ -21367,27 +22085,27 @@ async function localSecretsCommand(args2, root, application, input = process.std
21367
22085
 
21368
22086
  // src/local-development.ts
21369
22087
  function localManifest(root = process.cwd()) {
21370
- const filename2 = join9(root, "backify.yaml");
21371
- if (!existsSync8(filename2)) throw new Error("Create backify.yaml with version, application, runtime and explicit capabilities before running backify dev");
21372
- const manifest = parseApplicationManifest(readFileSync15(filename2, "utf8"));
21373
- const entry = resolve15(root, manifest.runtime.entrypoint);
21374
- if (!existsSync8(entry) || !inside2(root, realpathSync5(entry))) throw new Error("Runtime entrypoint must exist inside this repository");
22088
+ const filename2 = join12(root, "backify.yaml");
22089
+ if (!existsSync11(filename2)) throw new Error("Create backify.yaml with version, application, runtime and explicit capabilities before running backify dev");
22090
+ const manifest = parseApplicationManifest(readFileSync18(filename2, "utf8"));
22091
+ const entry = resolve17(root, manifest.runtime.entrypoint);
22092
+ if (!existsSync11(entry) || !inside2(root, realpathSync5(entry))) throw new Error("Runtime entrypoint must exist inside this repository");
21375
22093
  const discovered = analyzeSources(repositorySources(root));
21376
- const diagnostics = manifestDiagnostics(manifest, Object.keys(discovered.capabilities));
22094
+ const diagnostics = manifestDiagnostics(manifest, Object.keys(discovered.capabilities), discovered.capabilities.auth?.configuration);
21377
22095
  if (diagnostics.length) throw new Error(diagnostics.map((diagnostic) => diagnostic.message).join("; "));
21378
22096
  return manifest;
21379
22097
  }
21380
22098
  function inside2(root, path) {
21381
22099
  const part = relative7(realpathSync5(root), path);
21382
- return part !== ".." && !part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) && !isAbsolute12(part);
22100
+ return part !== ".." && !part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) && !isAbsolute13(part);
21383
22101
  }
21384
22102
  function sessionPath(root) {
21385
- return join9(root, ".backify", "dev", "session.json");
22103
+ return join12(root, ".backify", "dev", "session.json");
21386
22104
  }
21387
22105
  async function localRequest(path, input, root = process.cwd()) {
21388
22106
  if (!/^\/v1\/[a-z/-]+$/.test(path)) throw new Error("Invalid local command path");
21389
- if (!existsSync8(sessionPath(root))) throw new Error("Start backify dev before using local commands");
21390
- const session = JSON.parse(readFileSync15(sessionPath(root), "utf8"));
22107
+ if (!existsSync11(sessionPath(root))) throw new Error("Start backify dev before using local commands");
22108
+ const session = JSON.parse(readFileSync18(sessionPath(root), "utf8"));
21391
22109
  if (session.version !== 1 || !Number.isInteger(session.port) || session.port < 1 || session.port > 65535 || !/^[a-f0-9]{64}$/.test(session.token)) throw new Error("Invalid local session");
21392
22110
  const response = await fetch("http://127.0.0.1:" + session.port + path, { method: input === void 0 ? "GET" : "POST", headers: { authorization: "Bearer " + session.token, "content-type": "application/json" }, body: input === void 0 ? void 0 : JSON.stringify(input), redirect: "error", signal: AbortSignal.timeout(35e3) });
21393
22111
  if (!response.ok) {
@@ -21420,7 +22138,7 @@ async function startLocalDevelopment(options = {}) {
21420
22138
  if (options.jobTrustSyncFile !== void 0 && options.jobAuthFile === void 0) throw new Error("Native worker trust renewal requires explicit reviewed auth mounts");
21421
22139
  if (options.jobHistoryFile !== void 0 && options.jobWorkerDirectory === void 0) throw new Error("Historical workers require paired reviewed job artifacts and current worker");
21422
22140
  if (options.jobAuthFile !== void 0 && options.identityUsers !== void 0) throw new Error("Reviewed worker issuer trust cannot be mixed with local identity fixtures");
21423
- const root = resolve15(options.root ?? process.cwd());
22141
+ const root = resolve17(options.root ?? process.cwd());
21424
22142
  const manifest = localManifest(root);
21425
22143
  const validateSecretIntent = (values, intent = manifest) => {
21426
22144
  if (Object.keys(values).length && (!intent.capabilities.secrets || intent.capabilities.secrets.enabled === false)) throw new Error("Encrypted local secrets require enabled capabilities.secrets");
@@ -21437,18 +22155,18 @@ async function startLocalDevelopment(options = {}) {
21437
22155
  createLocalJobTrustRenewal(root, authMounts);
21438
22156
  const identityProfiles = prepareLocalIdentity(root, options.identityUsers);
21439
22157
  if (identityProfiles && (!manifest.capabilities.auth || manifest.capabilities.auth.enabled === false || options.backendPort === void 0)) throw new Error("Explicit local identities require capabilities.auth and --backend-port");
21440
- const preparedFrontend = prepareLocalFrontend(root, options, options.localSecretsKey ? [realpathSync5(resolve15(options.localSecretsKey))] : []);
21441
- const ignorePath = join9(root, ".gitignore");
21442
- if (existsSync8(ignorePath) && lstatSync13(ignorePath).isSymbolicLink()) throw new Error("Local development cannot update a symlinked .gitignore");
21443
- const ignore = existsSync8(ignorePath) ? readFileSync15(ignorePath, "utf8") : "";
21444
- if (!ignore.split(/\r?\n/).some((line) => line.trim() === ".backify/")) writeFileSync7(ignorePath, ignore + (ignore.endsWith("\n") || !ignore ? "" : "\n") + ".backify/\n");
22158
+ const preparedFrontend = prepareLocalFrontend(root, options, options.localSecretsKey ? [realpathSync5(resolve17(options.localSecretsKey))] : []);
22159
+ const ignorePath = join12(root, ".gitignore");
22160
+ if (existsSync11(ignorePath) && lstatSync15(ignorePath).isSymbolicLink()) throw new Error("Local development cannot update a symlinked .gitignore");
22161
+ const ignore = existsSync11(ignorePath) ? readFileSync18(ignorePath, "utf8") : "";
22162
+ if (!ignore.split(/\r?\n/).some((line) => line.trim() === ".backify/")) writeFileSync10(ignorePath, ignore + (ignore.endsWith("\n") || !ignore ? "" : "\n") + ".backify/\n");
21445
22163
  if (options.port !== void 0 && (!Number.isInteger(options.port) || options.port < 0 || options.port > 65535)) throw new Error("Dashboard port must be 0-65535");
21446
- const directory2 = join9(root, ".backify", "dev");
22164
+ const directory2 = join12(root, ".backify", "dev");
21447
22165
  assertLocalStatePath(root, directory2);
21448
- for (const filename2 of ["session.json", "session.json.tmp", "worker.mjs", "worker.mjs.map", "state.json", "state.json.tmp"]) assertLocalStatePath(root, join9(directory2, filename2));
21449
- mkdirSync5(directory2, { recursive: true });
22166
+ for (const filename2 of ["session.json", "session.json.tmp", "worker.mjs", "worker.mjs.map", "state.json", "state.json.tmp"]) assertLocalStatePath(root, join12(directory2, filename2));
22167
+ mkdirSync6(directory2, { recursive: true });
21450
22168
  if (!inside2(root, realpathSync5(directory2))) throw new Error("Local state directory must remain inside this repository");
21451
- if (existsSync8(sessionPath(root))) {
22169
+ if (existsSync11(sessionPath(root))) {
21452
22170
  try {
21453
22171
  await localRequest("/v1/status", void 0, root);
21454
22172
  throw new Error("Backify dev is already running for this repository");
@@ -21478,15 +22196,15 @@ async function startLocalDevelopment(options = {}) {
21478
22196
  }
21479
22197
  throw error;
21480
22198
  }
21481
- const token = randomBytes5(32).toString("hex");
21482
- const identityScope = services?.identityScope ?? { organizationId: randomUUID8(), applicationId: randomUUID8(), environmentId: randomUUID8() };
22199
+ const token = randomBytes6(32).toString("hex");
22200
+ const identityScope = services?.identityScope ?? { organizationId: randomUUID9(), applicationId: randomUUID9(), environmentId: randomUUID9() };
21483
22201
  const workerEnvironment = (worker) => {
21484
22202
  if (!worker.authTrust) return worker;
21485
22203
  const { renewal, ...authTrust } = worker.authTrust;
21486
22204
  return { ...worker, authTrust };
21487
22205
  };
21488
22206
  const identityEnvironment = () => ({ BACKIFY_LOCAL_IDENTITY: identityProfiles ? JSON.stringify({ ...identityProfiles, scope: identityScope, requestOrigins: [backendUrl, frontend?.url].filter(Boolean) }) : "", BACKIFY_LOCAL_JOB_WORKER: jobWorker ? JSON.stringify({ ...workerEnvironment(jobWorker), scope: identityScope, historicalWorkers: historicalWorkers.map(workerEnvironment) }) : "", BACKIFY_LOCAL_JOBS: services?.jobs ? "postgres-jobs" : "snapshot-emulator" });
21489
- const outfile = join9(directory2, "worker.mjs");
22207
+ const outfile = join12(directory2, "worker.mjs");
21490
22208
  let child;
21491
22209
  const trustRenewal = createLocalJobTrustRenewal(root, authMounts, { onFailure: () => console.error("Native worker trust renewal unavailable; existing validity and pin guards remain enforced."), onUpdate: () => {
21492
22210
  if (child?.connected) child.send({ type: "trust-renewed", workers: activeWorkers.filter((worker) => worker.authTrust).map((worker) => ({ artifactVersion: worker.artifact.artifactVersion, revision: worker.authTrust.revision, trustDigest: worker.authTrust.trustDigest })) }, (error) => {
@@ -21524,8 +22242,8 @@ async function startLocalDevelopment(options = {}) {
21524
22242
  }
21525
22243
  await stopChild(child);
21526
22244
  if (stopping) return;
21527
- const version = createHash12("sha256").update(readFileSync15(outfile)).update(services?.configurationFingerprint ?? "lightweight").digest("hex");
21528
- const worker = spawn4(process.execPath, [outfile], { cwd: root, windowsHide: true, stdio: ["ignore", 2, 2, "ipc"], env: { ...process.env, ...identityEnvironment(), BACKIFY_LOCAL_SECRETS_IPC: "1", HOST: "127.0.0.1", ...backendPort === void 0 ? {} : { PORT: String(backendPort) }, BACKIFY_LOCAL_BACKEND_URL: backendUrl ?? "", BACKIFY_LOCAL_FRONTEND_URL: frontend?.url ?? "", BACKIFY_LOCAL_DATABASE_ENABLED: manifest.capabilities.database && manifest.capabilities.database.enabled !== false ? "1" : "0", BACKIFY_LOCAL_STORAGE_ENABLED: manifest.capabilities.storage && manifest.capabilities.storage.enabled !== false ? "1" : "0", BACKIFY_LOCAL_STORAGE_BUCKETS: JSON.stringify(manifest.capabilities.storage?.buckets?.map((bucket) => bucket.name) ?? (manifest.capabilities.storage ? ["default"] : [])), BACKIFY_LOCAL_TOKEN: token, BACKIFY_LOCAL_PORT: String(dashboardPort), BACKIFY_LOCAL_STATE: join9(directory2, "state.json"), BACKIFY_LOCAL_VERSION: version, BACKIFY_LOCAL_HCR_URL: services?.runtimeUrl ?? "", BACKIFY_LOCAL_WORKLOAD_TOKEN_PATH: services?.tokenPath ?? "", BACKIFY_LOCAL_DATABASE: services ? services.database ? "postgres" : "disabled" : "document-emulator", BACKIFY_LOCAL_EVENTS: services?.events ? "postgres-events" : services ? "disabled" : "snapshot-emulator", BACKIFY_LOCAL_STORAGE: services ? services.storage ? "filesystem" : "disabled" : "snapshot-emulator" } });
22245
+ const version = createHash13("sha256").update(readFileSync18(outfile)).update(services?.configurationFingerprint ?? "lightweight").digest("hex");
22246
+ const worker = spawn4(process.execPath, [outfile], { cwd: root, windowsHide: true, stdio: ["ignore", 2, 2, "ipc"], env: { ...process.env, ...identityEnvironment(), BACKIFY_LOCAL_SECRETS_IPC: "1", HOST: "127.0.0.1", ...backendPort === void 0 ? {} : { PORT: String(backendPort) }, BACKIFY_LOCAL_BACKEND_URL: backendUrl ?? "", BACKIFY_LOCAL_FRONTEND_URL: frontend?.url ?? "", BACKIFY_LOCAL_DATABASE_ENABLED: manifest.capabilities.database && manifest.capabilities.database.enabled !== false ? "1" : "0", BACKIFY_LOCAL_STORAGE_ENABLED: manifest.capabilities.storage && manifest.capabilities.storage.enabled !== false ? "1" : "0", BACKIFY_LOCAL_STORAGE_BUCKETS: JSON.stringify(manifest.capabilities.storage?.buckets?.map((bucket) => bucket.name) ?? (manifest.capabilities.storage ? ["default"] : [])), BACKIFY_LOCAL_TOKEN: token, BACKIFY_LOCAL_PORT: String(dashboardPort), BACKIFY_LOCAL_STATE: join12(directory2, "state.json"), BACKIFY_LOCAL_VERSION: version, BACKIFY_LOCAL_HCR_URL: services?.runtimeUrl ?? "", BACKIFY_LOCAL_WORKLOAD_TOKEN_PATH: services?.tokenPath ?? "", BACKIFY_LOCAL_DATABASE: services ? services.database ? "postgres" : "disabled" : "document-emulator", BACKIFY_LOCAL_EVENTS: services?.events ? "postgres-events" : services ? "disabled" : "snapshot-emulator", BACKIFY_LOCAL_STORAGE: services ? services.storage ? "filesystem" : "disabled" : "snapshot-emulator" } });
21529
22247
  child = worker;
21530
22248
  worker.send({ type: "local-secrets", secrets }, (error) => {
21531
22249
  if (error) {
@@ -21547,7 +22265,7 @@ async function startLocalDevelopment(options = {}) {
21547
22265
  workerReady = true;
21548
22266
  const session = { version: 1, port: message.port, token, pid: worker.pid };
21549
22267
  const temp = sessionPath(root) + ".tmp";
21550
- writeFileSync7(temp, JSON.stringify(session), { mode: 384 });
22268
+ writeFileSync10(temp, JSON.stringify(session), { mode: 384 });
21551
22269
  renameLocalFile(temp, sessionPath(root));
21552
22270
  const url = "http://127.0.0.1:" + message.port;
21553
22271
  options.onReady?.(url, { frontendUrl: frontend?.url, backendUrl });
@@ -21559,17 +22277,17 @@ async function startLocalDevelopment(options = {}) {
21559
22277
  });
21560
22278
  };
21561
22279
  try {
21562
- buildContext = await context2({ absWorkingDir: root, stdin: { contents: localWorkerSource(resolve15(root, manifest.runtime.entrypoint)), resolveDir: root, sourcefile: "backify-local-worker.ts", loader: "ts" }, bundle: true, platform: "node", format: "esm", target: "node20", outfile, sourcemap: true, banner: { js: 'import { createRequire as __backifyCreateRequire } from "node:module"; var require = __backifyCreateRequire(import.meta.url);' }, plugins: [{ name: "backify-local-lifecycle", setup(build3) {
22280
+ buildContext = await context2({ absWorkingDir: root, stdin: { contents: localWorkerSource(resolve17(root, manifest.runtime.entrypoint)), resolveDir: root, sourcefile: "backify-local-worker.ts", loader: "ts" }, bundle: true, platform: "node", format: "esm", target: "node20", outfile, sourcemap: true, banner: { js: 'import { createRequire as __backifyCreateRequire } from "node:module"; var require = __backifyCreateRequire(import.meta.url);' }, plugins: [{ name: "backify-local-lifecycle", setup(build3) {
21563
22281
  build3.onLoad({ filter: /./, namespace: "file" }, (input) => {
21564
- if (input.path !== resolve15(root, manifest.runtime.entrypoint)) return;
22282
+ if (input.path !== resolve17(root, manifest.runtime.entrypoint)) return;
21565
22283
  try {
21566
22284
  const updated = localManifest(root);
21567
22285
  if (services && JSON.stringify(updated.capabilities) !== JSON.stringify(manifest.capabilities)) throw new Error("Full-fidelity capability or migration intent changed; restart backify dev to review and apply it");
21568
22286
  if (updated.runtime.entrypoint !== manifest.runtime.entrypoint) throw new Error("Runtime entrypoint changed; restart backify dev");
21569
22287
  } catch (error) {
21570
- return { errors: [{ text: error instanceof Error ? error.message : "Manifest validation failed" }], watchFiles: [join9(root, "backify.yaml")] };
22288
+ return { errors: [{ text: error instanceof Error ? error.message : "Manifest validation failed" }], watchFiles: [join12(root, "backify.yaml")] };
21571
22289
  }
21572
- return { contents: readFileSync15(input.path, "utf8"), loader: input.path.endsWith(".tsx") ? "tsx" : /\.(ts|mts|cts)$/.test(input.path) ? "ts" : input.path.endsWith(".jsx") ? "jsx" : "js", watchFiles: [join9(root, "backify.yaml")] };
22290
+ return { contents: readFileSync18(input.path, "utf8"), loader: input.path.endsWith(".tsx") ? "tsx" : /\.(ts|mts|cts)$/.test(input.path) ? "ts" : input.path.endsWith(".jsx") ? "jsx" : "js", watchFiles: [join12(root, "backify.yaml")] };
21573
22291
  });
21574
22292
  build3.onEnd((result) => {
21575
22293
  if (result.errors.length) rejectStartup(new Error("Local application build failed"));
@@ -21625,8 +22343,8 @@ async function startLocalDevelopment(options = {}) {
21625
22343
  }
21626
22344
  }
21627
22345
  }
21628
- if (existsSync8(sessionPath(root))) {
21629
- const session = JSON.parse(readFileSync15(sessionPath(root), "utf8"));
22346
+ if (existsSync11(sessionPath(root))) {
22347
+ const session = JSON.parse(readFileSync18(sessionPath(root), "utf8"));
21630
22348
  if (session.token === token) unlinkSync4(sessionPath(root));
21631
22349
  }
21632
22350
  };
@@ -21640,11 +22358,11 @@ async function startLocalDevelopment(options = {}) {
21640
22358
  }
21641
22359
  async function runLocalTests(root = process.cwd()) {
21642
22360
  const manifest = localManifest(root);
21643
- const project = JSON.parse(readFileSync15(join9(root, "package.json"), "utf8"));
22361
+ const project = JSON.parse(readFileSync18(join12(root, "package.json"), "utf8"));
21644
22362
  if (!project.scripts?.test) throw new Error("Add a test script to package.json so AI agents can run backify test");
21645
22363
  const result = await new Promise((resolveResult, reject) => {
21646
- const npmPath = process.platform === "win32" ? process.env.npm_execpath ?? join9(dirname9(process.execPath), "node_modules", "npm", "bin", "npm-cli.js") : void 0;
21647
- if (npmPath && !existsSync8(npmPath)) {
22364
+ const npmPath = process.platform === "win32" ? process.env.npm_execpath ?? join12(dirname12(process.execPath), "node_modules", "npm", "bin", "npm-cli.js") : void 0;
22365
+ if (npmPath && !existsSync11(npmPath)) {
21648
22366
  reject(new Error("Cannot locate npm-cli.js; run backify test through npx or npm"));
21649
22367
  return;
21650
22368
  }
@@ -21664,15 +22382,15 @@ async function runLocalTests(root = process.cwd()) {
21664
22382
  // src/workload.ts
21665
22383
  function inside3(root, filename2) {
21666
22384
  const part = relative8(root, filename2);
21667
- return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute13(part);
22385
+ return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute14(part);
21668
22386
  }
21669
22387
  function digest3(contents) {
21670
- return createHash13("sha256").update(contents).digest("hex");
22388
+ return createHash14("sha256").update(contents).digest("hex");
21671
22389
  }
21672
22390
  function packageDirectory(filename2) {
21673
- for (let current = dirname10(filename2); ; current = dirname10(current)) {
21674
- if (existsSync9(join10(current, "package.json"))) return current;
21675
- if (dirname10(current) === current) throw new Error("Cannot locate an installed dependency package");
22391
+ for (let current = dirname13(filename2); ; current = dirname13(current)) {
22392
+ if (existsSync12(join13(current, "package.json"))) return current;
22393
+ if (dirname13(current) === current) throw new Error("Cannot locate an installed dependency package");
21676
22394
  }
21677
22395
  }
21678
22396
  function workloadSource(entry, kind, capabilities) {
@@ -21753,11 +22471,11 @@ main().catch(() => finish({ schemaVersion: 1, status: "failed", error: "workload
21753
22471
  `.replace("__KIND__", JSON.stringify(kind)).replace("__CAPABILITIES__", JSON.stringify(capabilities)).replace("__AUTH_ENABLED__", String(capabilities.includes("auth"))).replace("__ENTRY__", JSON.stringify(entry));
21754
22472
  }
21755
22473
  async function prepareWorkload(options) {
21756
- const root = realpathSync6(resolve16(options.root ?? process.cwd()));
22474
+ const root = realpathSync6(resolve18(options.root ?? process.cwd()));
21757
22475
  const manifest = localManifest(root);
21758
22476
  if (options.kind !== "http" && options.kind !== "worker") throw new Error("Workload kind must be http or worker");
21759
22477
  if (options.kind === "worker" && !options.entry) throw new Error("Workers require an explicit declaration-only entrypoint");
21760
- const entry = realpathSync6(resolve16(root, options.entry ?? manifest.runtime.entrypoint));
22478
+ const entry = realpathSync6(resolve18(root, options.entry ?? manifest.runtime.entrypoint));
21761
22479
  if (!inside3(root, entry)) throw new Error("Workload entrypoint must stay inside this repository");
21762
22480
  const enabled = Object.entries(manifest.capabilities).filter(([, settings]) => settings?.enabled !== false).map(([name2]) => name2).sort();
21763
22481
  if (options.kind === "worker" && !enabled.includes("jobs")) throw new Error("Workers require declared jobs capability");
@@ -21783,14 +22501,14 @@ async function prepareWorkload(options) {
21783
22501
  context3.onResolve({ filter: /./ }, async (input) => {
21784
22502
  if (input.pluginData?.backifyResolved) return;
21785
22503
  if (input.path === "@backifydev/sdk/local" || input.path === "@backifydev/sdk/runtime" && input.namespace !== "backify-workload-host") return { errors: [{ text: "Production application code must not import local emulators or host-only runtime configuration" }] };
21786
- const resolved = await context3.resolve(input.path, { importer: input.namespace === "backify-workload-host" ? join10(root, input.importer) : input.importer, resolveDir: input.resolveDir, kind: input.kind, pluginData: { backifyResolved: true } });
22504
+ const resolved = await context3.resolve(input.path, { importer: input.namespace === "backify-workload-host" ? join13(root, input.importer) : input.importer, resolveDir: input.resolveDir, kind: input.kind, pluginData: { backifyResolved: true } });
21787
22505
  if (!resolved.external && !resolved.errors.length && resolved.path) {
21788
22506
  const directory2 = realpathSync6(packageDirectory(resolved.path));
21789
22507
  const target = relative8(directory2, realpathSync6(resolved.path)).replaceAll("\\", "/");
21790
- if (JSON.parse(readFileSync16(join10(directory2, "package.json"), "utf8")).name === "@backifydev/sdk") {
22508
+ if (JSON.parse(readFileSync19(join13(directory2, "package.json"), "utf8")).name === "@backifydev/sdk") {
21791
22509
  if (/^(src|dist)\/local\.(ts|js)$/.test(target) || /^(src|dist)\/runtime\.(ts|js)$/.test(target) && input.namespace !== "backify-workload-host" && !inside3(directory2, realpathSync6(input.importer))) return { errors: [{ text: "Production application code must not import local emulators or host-only runtime configuration" }] };
21792
22510
  }
21793
- if (!isAbsolute13(input.path) && !input.path.startsWith(".")) trustedPackages.add(directory2);
22511
+ if (!isAbsolute14(input.path) && !input.path.startsWith(".")) trustedPackages.add(directory2);
21794
22512
  }
21795
22513
  return resolved;
21796
22514
  });
@@ -21849,34 +22567,34 @@ async function packageWorkload(options) {
21849
22567
  if (plan.blockers.includes("bundle-warning-review-required")) throw new Error("Workload bundle warnings require review before packaging");
21850
22568
  const dockerfile = ["FROM " + options.baseImage, "WORKDIR /app", "COPY --chown=1000:1000 workload.mjs artifact.json ./", "ENV NODE_ENV=production PORT=8080 BACKIFY_ARTIFACT_SHA256=" + plan.artifactVersion + " BACKIFY_WORKLOAD_TOKEN_FILE=/run/backify/workload/token", "USER 1000:1000", ...options.kind === "http" ? ["EXPOSE 8080"] : [], "STOPSIGNAL SIGTERM", 'ENTRYPOINT ["node", "workload.mjs"]', ""].join("\n");
21851
22569
  const files = { "workload.mjs": bundle, "artifact.json": JSON.stringify(plan, null, 2) + "\n", "Dockerfile": dockerfile, ".dockerignore": "**\n!Dockerfile\n!workload.mjs\n!artifact.json\n!.dockerignore\n" };
21852
- const output = resolve16(root, options.output ?? ".backify/artifacts/" + options.kind + "-" + plan.artifactVersion.slice(0, 16));
22570
+ const output = resolve18(root, options.output ?? ".backify/artifacts/" + options.kind + "-" + plan.artifactVersion.slice(0, 16));
21853
22571
  assertLocalStatePath(root, output);
21854
- if (existsSync9(output)) {
21855
- if (JSON.stringify(readdirSync4(output).sort()) !== JSON.stringify(Object.keys(files).sort()) || Object.entries(files).some(([name2, value]) => {
21856
- assertLocalStatePath(root, join10(output, name2));
21857
- return !readFileSync16(join10(output, name2)).equals(Buffer.from(value));
22572
+ if (existsSync12(output)) {
22573
+ if (JSON.stringify(readdirSync6(output).sort()) !== JSON.stringify(Object.keys(files).sort()) || Object.entries(files).some(([name2, value]) => {
22574
+ assertLocalStatePath(root, join13(output, name2));
22575
+ return !readFileSync19(join13(output, name2)).equals(Buffer.from(value));
21858
22576
  })) throw new Error("Refusing to overwrite an existing workload artifact directory");
21859
22577
  } else {
21860
- mkdirSync6(output, { recursive: true });
21861
- for (const [name2, value] of Object.entries(files)) writeFileSync8(join10(output, name2), value, { flag: "wx", mode: 384 });
22578
+ mkdirSync7(output, { recursive: true });
22579
+ for (const [name2, value] of Object.entries(files)) writeFileSync11(join13(output, name2), value, { flag: "wx", mode: 384 });
21862
22580
  }
21863
22581
  return { ...plan, outputDirectory: relative8(root, output).replaceAll("\\", "/") };
21864
22582
  }
21865
22583
 
21866
22584
  // src/workload-provenance.ts
21867
22585
  import { execFileSync } from "node:child_process";
21868
- import { mkdirSync as mkdirSync7, mkdtempSync, readdirSync as readdirSync5, rmSync, writeFileSync as writeFileSync9 } from "node:fs";
22586
+ import { mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "node:fs";
21869
22587
  import { tmpdir } from "node:os";
21870
- import { basename as basename2, dirname as dirname12, isAbsolute as isAbsolute14, join as join12, relative as relative9, resolve as resolve18 } from "node:path";
22588
+ import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute15, join as join15, relative as relative9, resolve as resolve20 } from "node:path";
21871
22589
 
21872
22590
  // src/workload-provenance-oci.ts
21873
- import { createHash as createHash14 } from "node:crypto";
21874
- import { createReadStream, lstatSync as lstatSync14, readFileSync as readFileSync17 } from "node:fs";
21875
- import { dirname as dirname11, join as join11, parse as parse3, resolve as resolve17 } from "node:path";
22591
+ import { createHash as createHash15 } from "node:crypto";
22592
+ import { createReadStream, lstatSync as lstatSync16, readFileSync as readFileSync20 } from "node:fs";
22593
+ import { dirname as dirname14, join as join14, parse as parse3, resolve as resolve19 } from "node:path";
21876
22594
  import { Transform, Writable } from "node:stream";
21877
22595
  import { pipeline } from "node:stream/promises";
21878
22596
  import { createGunzip } from "node:zlib";
21879
- var provenanceHash = (bytes) => "sha256:" + createHash14("sha256").update(bytes).digest("hex");
22597
+ var provenanceHash = (bytes) => "sha256:" + createHash15("sha256").update(bytes).digest("hex");
21880
22598
  var maximumLayer = 256 * 1024 * 1024;
21881
22599
  var maximumImage = 1024 * 1024 * 1024;
21882
22600
  var manifestTypes = ["application/vnd.oci.image.manifest.v1+json", "application/vnd.docker.distribution.manifest.v2+json"];
@@ -21887,14 +22605,14 @@ function provenanceRef(value) {
21887
22605
  return value.slice(value.lastIndexOf("@") + 1);
21888
22606
  }
21889
22607
  function provenanceFile(filename2, maximum) {
21890
- const absolute = resolve17(filename2);
21891
- for (let ancestor = absolute; ; ancestor = dirname11(ancestor)) {
21892
- if (lstatSync14(ancestor).isSymbolicLink()) throw new Error("Provenance paths must not contain links");
22608
+ const absolute = resolve19(filename2);
22609
+ for (let ancestor = absolute; ; ancestor = dirname14(ancestor)) {
22610
+ if (lstatSync16(ancestor).isSymbolicLink()) throw new Error("Provenance paths must not contain links");
21893
22611
  if (ancestor === parse3(absolute).root) break;
21894
22612
  }
21895
- const stat = lstatSync14(absolute);
22613
+ const stat = lstatSync16(absolute);
21896
22614
  if (!stat.isFile() || stat.nlink !== 1 || stat.size < 1 || stat.size > maximum) throw new Error("Invalid provenance file");
21897
- const bytes = readFileSync17(absolute);
22615
+ const bytes = readFileSync20(absolute);
21898
22616
  if (bytes.length !== stat.size) throw new Error("Provenance file changed during read");
21899
22617
  return bytes;
21900
22618
  }
@@ -21903,7 +22621,7 @@ function descriptor(value) {
21903
22621
  return value;
21904
22622
  }
21905
22623
  function blobPath(root, value) {
21906
- return join11(root, "blobs", "sha256", descriptor(value).digest.slice(7));
22624
+ return join14(root, "blobs", "sha256", descriptor(value).digest.slice(7));
21907
22625
  }
21908
22626
  function jsonBlob(root, value) {
21909
22627
  const bytes = provenanceFile(blobPath(root, value), 1024 * 1024);
@@ -21912,8 +22630,8 @@ function jsonBlob(root, value) {
21912
22630
  }
21913
22631
  async function inspectProvenanceImage(root, architecture, requestedDigest) {
21914
22632
  if (!["amd64", "arm64"].includes(architecture) || requestedDigest && !digestPattern.test(requestedDigest)) throw new Error("Invalid OCI platform or digest");
21915
- if (JSON.parse(provenanceFile(join11(root, "oci-layout"), 1024).toString()).imageLayoutVersion !== "1.0.0") throw new Error("OCI layout required");
21916
- const indexBytes = provenanceFile(join11(root, "index.json"), 1024 * 1024);
22633
+ if (JSON.parse(provenanceFile(join14(root, "oci-layout"), 1024).toString()).imageLayoutVersion !== "1.0.0") throw new Error("OCI layout required");
22634
+ const indexBytes = provenanceFile(join14(root, "index.json"), 1024 * 1024);
21917
22635
  let current = JSON.parse(indexBytes.toString());
21918
22636
  let selected;
21919
22637
  let manifestDigest = provenanceHash(indexBytes);
@@ -21944,7 +22662,7 @@ async function inspectProvenanceImage(root, architecture, requestedDigest) {
21944
22662
  if (!digestPattern.test(config.rootfs.diff_ids[index])) throw new Error("Invalid OCI diff ID");
21945
22663
  const filename2 = blobPath(root, layer);
21946
22664
  provenanceFileMetadata(filename2, layer.size);
21947
- const storedHash = createHash14("sha256"), expandedHash = createHash14("sha256");
22665
+ const storedHash = createHash15("sha256"), expandedHash = createHash15("sha256");
21948
22666
  let stored = 0, expanded = 0;
21949
22667
  const counter = new Transform({ transform(chunk, _encoding, callback) {
21950
22668
  stored += chunk.length;
@@ -21970,12 +22688,12 @@ async function inspectProvenanceImage(root, architecture, requestedDigest) {
21970
22688
  return { manifestDigest, configDigest: configuration.digest, architecture, layers };
21971
22689
  }
21972
22690
  function provenanceFileMetadata(filename2, expectedSize) {
21973
- const absolute = resolve17(filename2);
21974
- for (let ancestor = absolute; ; ancestor = dirname11(ancestor)) {
21975
- if (lstatSync14(ancestor).isSymbolicLink()) throw new Error("Provenance paths must not contain links");
22691
+ const absolute = resolve19(filename2);
22692
+ for (let ancestor = absolute; ; ancestor = dirname14(ancestor)) {
22693
+ if (lstatSync16(ancestor).isSymbolicLink()) throw new Error("Provenance paths must not contain links");
21976
22694
  if (ancestor === parse3(absolute).root) break;
21977
22695
  }
21978
- const stat = lstatSync14(absolute);
22696
+ const stat = lstatSync16(absolute);
21979
22697
  if (!stat.isFile() || stat.nlink !== 1 || stat.size !== expectedSize) throw new Error("Invalid OCI layer file");
21980
22698
  }
21981
22699
  function verifyBaseLineage(base, image2) {
@@ -21998,7 +22716,7 @@ function canonical(value) {
21998
22716
  if (value && typeof value === "object") return "{" + Object.entries(value).sort(([left], [right]) => left.localeCompare(right, "en")).map(([name2, item]) => JSON.stringify(name2) + ":" + canonical(item)).join(",") + "}";
21999
22717
  return JSON.stringify(value);
22000
22718
  }
22001
- function git(root, args2) {
22719
+ function git2(root, args2) {
22002
22720
  try {
22003
22721
  return execFileSync("git", ["-C", root, ...args2], { encoding: "utf8", windowsHide: true, timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }).trim();
22004
22722
  } catch {
@@ -22033,28 +22751,28 @@ function dependencyMaterials(lock) {
22033
22751
  return { digest: provenanceHash(canonical(materials.sort((left, right) => left.path.localeCompare(right.path, "en")))), count: materials.length };
22034
22752
  }
22035
22753
  async function captureWorkloadProvenance(options) {
22036
- const root = resolve18(options.root ?? process.cwd());
22754
+ const root = resolve20(options.root ?? process.cwd());
22037
22755
  if (!repositoryPattern.test(options.repository) || !["first", "second"].includes(options.replica)) throw new Error("Invalid provenance repository or replica");
22038
- const applicationPath = relative9(root, resolve18(root, options.application)).replaceAll("\\", "/");
22039
- if (!applicationPath || isAbsolute14(applicationPath) || applicationPath.split("/").includes("..")) throw new Error("Application must be inside the source checkout");
22040
- if (git(root, ["rev-parse", "--show-toplevel"]).replaceAll("\\", "/").toLowerCase() !== root.replaceAll("\\", "/").toLowerCase() || git(root, ["status", "--porcelain", "--untracked-files=all"]) || git(root, ["ls-files", "--stage"]).split("\n").some((line) => line.startsWith("160000 "))) throw new Error("Clean root checkout without submodules required");
22041
- const commit = git(root, ["rev-parse", "HEAD"]), tree = git(root, ["rev-parse", "HEAD^{tree}"]);
22756
+ const applicationPath = relative9(root, resolve20(root, options.application)).replaceAll("\\", "/");
22757
+ if (!applicationPath || isAbsolute15(applicationPath) || applicationPath.split("/").includes("..")) throw new Error("Application must be inside the source checkout");
22758
+ if (git2(root, ["rev-parse", "--show-toplevel"]).replaceAll("\\", "/").toLowerCase() !== root.replaceAll("\\", "/").toLowerCase() || git2(root, ["status", "--porcelain", "--untracked-files=all"]) || git2(root, ["ls-files", "--stage"]).split("\n").some((line) => line.startsWith("160000 "))) throw new Error("Clean root checkout without submodules required");
22759
+ const commit = git2(root, ["rev-parse", "HEAD"]), tree = git2(root, ["rev-parse", "HEAD^{tree}"]);
22042
22760
  if (!sha.test(commit) || !sha.test(tree)) throw new Error("Full Git commit and tree required");
22043
- const origin = git(root, ["remote", "get-url", "origin"]);
22761
+ const origin = git2(root, ["remote", "get-url", "origin"]);
22044
22762
  if (!["https://github.com/" + options.repository, "https://github.com/" + options.repository + ".git", "git@github.com:" + options.repository + ".git"].includes(origin)) throw new Error("Source repository does not match origin");
22045
- const lockBytes = provenanceFile(join12(root, "package-lock.json"), 16 * 1024 * 1024);
22763
+ const lockBytes = provenanceFile(join15(root, "package-lock.json"), 16 * 1024 * 1024);
22046
22764
  const dependencies = dependencyMaterials(JSON.parse(lockBytes.toString("utf8")));
22047
- const context3 = resolve18(root, options.context);
22048
- if (canonical(readdirSync5(context3).sort()) !== canonical(contextNames)) throw new Error("Exact allowlisted workload context required");
22765
+ const context3 = resolve20(root, options.context);
22766
+ if (canonical(readdirSync7(context3).sort()) !== canonical(contextNames)) throw new Error("Exact allowlisted workload context required");
22049
22767
  const contextDigests = {};
22050
- for (const name2 of contextNames) contextDigests[name2] = provenanceHash(provenanceFile(join12(context3, name2), name2 === "workload.mjs" ? 16 * 1024 * 1024 : 65536));
22051
- const plan = boundedJson(join12(context3, "artifact.json"));
22768
+ for (const name2 of contextNames) contextDigests[name2] = provenanceHash(provenanceFile(join15(context3, name2), name2 === "workload.mjs" ? 16 * 1024 * 1024 : 65536));
22769
+ const plan = boundedJson(join15(context3, "artifact.json"));
22052
22770
  const baseDigest = provenanceRef(options.baseImage);
22053
22771
  if (plan.schemaVersion !== 1 || plan.kind !== "worker" || plan.baseImage !== options.baseImage || !/^[a-f0-9]{64}$/.test(plan.artifactVersion) || !/^[a-f0-9]{64}$/.test(plan.manifestVersion) || contextDigests["workload.mjs"] !== "sha256:" + plan.artifactVersion || typeof plan.entrypoint !== "string") throw new Error("Worker context does not match artifact metadata");
22054
22772
  const dockerfile = ["FROM " + options.baseImage, "WORKDIR /app", "COPY --chown=1000:1000 workload.mjs artifact.json ./", "ENV NODE_ENV=production PORT=8080 BACKIFY_ARTIFACT_SHA256=" + plan.artifactVersion + " BACKIFY_WORKLOAD_TOKEN_FILE=/run/backify/workload/token", "USER 1000:1000", "STOPSIGNAL SIGTERM", 'ENTRYPOINT ["node", "workload.mjs"]', ""].join("\n");
22055
- if (!provenanceFile(join12(context3, "Dockerfile"), 65536).equals(Buffer.from(dockerfile)) || provenanceFile(join12(context3, ".dockerignore"), 65536).toString() !== "**\n!Dockerfile\n!workload.mjs\n!artifact.json\n!.dockerignore\n") throw new Error("Unreviewed workload build recipe");
22056
- const base = await inspectProvenanceImage(resolve18(root, options.baseLayout), options.architecture, baseDigest);
22057
- const image2 = await inspectProvenanceImage(resolve18(root, options.imageLayout), options.architecture);
22773
+ if (!provenanceFile(join15(context3, "Dockerfile"), 65536).equals(Buffer.from(dockerfile)) || provenanceFile(join15(context3, ".dockerignore"), 65536).toString() !== "**\n!Dockerfile\n!workload.mjs\n!artifact.json\n!.dockerignore\n") throw new Error("Unreviewed workload build recipe");
22774
+ const base = await inspectProvenanceImage(resolve20(root, options.baseLayout), options.architecture, baseDigest);
22775
+ const image2 = await inspectProvenanceImage(resolve20(root, options.imageLayout), options.architecture);
22058
22776
  verifyBaseLineage(base, image2);
22059
22777
  let npm;
22060
22778
  try {
@@ -22076,7 +22794,7 @@ function validateReport(report) {
22076
22794
  if (report.schemaVersion !== 1 || report.kind !== "worker-build-provenance" || !repositoryPattern.test(report.source.repository) || !sha.test(report.source.commit) || !sha.test(report.source.tree) || !digestPattern.test(report.source.lockfileDigest) || !digestPattern.test(report.source.dependencyMaterialsDigest) || !Number.isSafeInteger(report.source.dependencyCount) || report.source.dependencyCount < 1 || !/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(report.toolchain.node) || !/^[0-9]+\.[0-9]+\.[0-9]+$/.test(report.toolchain.npm) || !/^[a-f0-9]{64}$/.test(report.build.artifactVersion) || !/^[a-f0-9]{64}$/.test(report.build.manifestVersion) || Object.values(report.build.context).some((value) => !digestPattern.test(value)) || report.build.context["workload.mjs"] !== "sha256:" + report.build.artifactVersion || !/^[1-9][0-9]*$/.test(report.invocation.runId) || !/^[1-9][0-9]*$/.test(report.invocation.attempt) || !["first", "second"].includes(report.invocation.replica)) throw new Error("Invalid build provenance report");
22077
22795
  for (const path of [report.build.applicationPath, report.build.entry]) {
22078
22796
  text2(path);
22079
- if (isAbsolute14(path) || path.includes(":") || path.includes("\\") || path.split("/").includes("..")) throw new Error("Invalid build source path");
22797
+ if (isAbsolute15(path) || path.includes(":") || path.includes("\\") || path.split("/").includes("..")) throw new Error("Invalid build source path");
22080
22798
  }
22081
22799
  provenanceRef(report.build.baseImage);
22082
22800
  for (const image2 of [report.base, report.image]) {
@@ -22127,17 +22845,17 @@ function verifyWorkloadProvenance(options, now = /* @__PURE__ */ new Date()) {
22127
22845
  if (provenanceHash(baseSubject) !== provenanceRef(policy.baseImage)) throw new Error("Base attestation subject does not match the approved image digest");
22128
22846
  evidence.push({ bytes: baseSubject, bundle: provenanceFile(options.baseBundle, 4 * 1024 * 1024), identity: { ...policy, ...supplier } });
22129
22847
  }
22130
- const scratchRoot = resolve18(tmpdir());
22131
- const scratch = mkdtempSync(join12(scratchRoot, "backify-provenance-"));
22132
- if (dirname12(scratch) !== scratchRoot || !basename2(scratch).startsWith("backify-provenance-")) throw new Error("Unsafe provenance evidence directory");
22848
+ const scratchRoot = resolve20(tmpdir());
22849
+ const scratch = mkdtempSync2(join15(scratchRoot, "backify-provenance-"));
22850
+ if (dirname15(scratch) !== scratchRoot || !basename3(scratch).startsWith("backify-provenance-")) throw new Error("Unsafe provenance evidence directory");
22133
22851
  try {
22134
- mkdirSync7(join12(scratch, "evidence"), { mode: 448 });
22135
- const trustedRoot = join12(scratch, "evidence", "trusted-root.jsonl");
22136
- writeFileSync9(trustedRoot, rootBytes, { flag: "wx", mode: 384 });
22852
+ mkdirSync8(join15(scratch, "evidence"), { mode: 448 });
22853
+ const trustedRoot = join15(scratch, "evidence", "trusted-root.jsonl");
22854
+ writeFileSync12(trustedRoot, rootBytes, { flag: "wx", mode: 384 });
22137
22855
  for (const [index, { bytes, bundle: bundleBytes, identity }] of evidence.entries()) {
22138
- const report = join12(scratch, "evidence", "report-" + index + ".json"), bundle = join12(scratch, "evidence", "bundle-" + index + ".jsonl");
22139
- writeFileSync9(report, bytes, { flag: "wx", mode: 384 });
22140
- writeFileSync9(bundle, bundleBytes, { flag: "wx", mode: 384 });
22856
+ const report = join15(scratch, "evidence", "report-" + index + ".json"), bundle = join15(scratch, "evidence", "bundle-" + index + ".jsonl");
22857
+ writeFileSync12(report, bytes, { flag: "wx", mode: 384 });
22858
+ writeFileSync12(bundle, bundleBytes, { flag: "wx", mode: 384 });
22141
22859
  try {
22142
22860
  const result = execFileSync("gh", ["attestation", "verify", report, "--bundle", bundle, "--custom-trusted-root", trustedRoot, "--hostname", "github.com", "--repo", identity.repository, "--signer-workflow", identity.signerWorkflow, "--signer-digest", identity.signerDigest, "--source-digest", identity.sourceCommit, "--source-ref", identity.sourceRef, "--deny-self-hosted-runners", "--predicate-type", "https://slsa.dev/provenance/v1", "--format", "json"], { encoding: "utf8", windowsHide: true, timeout: 12e4, maxBuffer: 4 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
22143
22861
  if (!Array.isArray(JSON.parse(result)) || !JSON.parse(result).length) throw new Error();
@@ -22146,7 +22864,7 @@ function verifyWorkloadProvenance(options, now = /* @__PURE__ */ new Date()) {
22146
22864
  }
22147
22865
  }
22148
22866
  } finally {
22149
- rmSync(scratch, { recursive: true, force: true });
22867
+ rmSync2(scratch, { recursive: true, force: true });
22150
22868
  }
22151
22869
  return { schemaVersion: 1, status: "build-provenance-verified", sourceCommitVerified: true, dependencyMaterialsVerified: true, dependencyReproductionVerified: Boolean(second), imageReproductionVerified: Boolean(second), baseDigestPinned: true, baseLineageVerified: true, baseSupplierProvenanceVerified: Boolean(supplier), baseImageVerified: Boolean(supplier), imageContentVerified: false, deploymentReady: false, verifiedAt: now.toISOString(), expiresAt: policy.expiresAt, policyDigest: provenanceHash(policyBytes), sourceCommit: policy.sourceCommit, imageDigest: first.image.manifestDigest, artifactVersion: first.build.artifactVersion, manifestVersion: first.build.manifestVersion, reportDigests: reportBytes.map(provenanceHash), blockers: ["separate-hcr-image-content-verification-required", "base-runtime-job-conformance-required", "reviewed-signed-fleet-installation-required", "production-workload-credential-isolation-required"] };
22152
22870
  }
@@ -22168,13 +22886,13 @@ async function workloadProvenanceCommand(args2) {
22168
22886
  return verifyWorkloadProvenance({ policy: required("--policy"), firstReport: options.get("--report") ?? required("--first-report"), firstBundle: options.get("--bundle") ?? required("--first-bundle"), secondReport: options.get("--second-report"), secondBundle: options.get("--second-bundle"), baseSubject: options.get("--base-subject"), baseBundle: options.get("--base-bundle"), trustedRoot: required("--trusted-root") });
22169
22887
  }
22170
22888
  const result = await captureWorkloadProvenance({ root: options.get("--root"), repository: required("--repository"), application: required("--application"), context: required("--context"), baseLayout: required("--base-layout"), imageLayout: required("--image-layout"), baseImage: required("--base-image"), architecture: required("--architecture"), replica: options.get("--replica") ?? "first" });
22171
- writeFileSync9(resolve18(required("--output")), JSON.stringify(result, null, 2) + "\n", { flag: "wx", mode: 384 });
22889
+ writeFileSync12(resolve20(required("--output")), JSON.stringify(result, null, 2) + "\n", { flag: "wx", mode: 384 });
22172
22890
  return { schemaVersion: 1, status: "captured-unsigned", deploymentReady: false, reportDigest: provenanceHash(JSON.stringify(result, null, 2) + "\n") };
22173
22891
  }
22174
22892
 
22175
22893
  // src/workload-artifact-plan.ts
22176
- import { existsSync as existsSync10, lstatSync as lstatSync15, readFileSync as readFileSync18, realpathSync as realpathSync7 } from "node:fs";
22177
- import { resolve as resolve19 } from "node:path";
22894
+ import { existsSync as existsSync13, lstatSync as lstatSync17, readFileSync as readFileSync21, realpathSync as realpathSync7 } from "node:fs";
22895
+ import { resolve as resolve21 } from "node:path";
22178
22896
  function uuid4(value) {
22179
22897
  return typeof value === "string" && value.length === 36 && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(value) && value !== "00000000-0000-0000-0000-000000000000";
22180
22898
  }
@@ -22212,14 +22930,14 @@ function parseWorkerArtifactPlan(args2) {
22212
22930
  }
22213
22931
  async function planWorkerArtifactRegistration(options) {
22214
22932
  if (![options.organizationId, options.applicationId, options.environmentId, options.deploymentId].every(uuid4) || typeof options.sourceCommit !== "string" || ![40, 64].includes(options.sourceCommit.length) || !/^[a-f0-9]+$/.test(options.sourceCommit) || !validImage(options.imageReference)) throw new Error("Invalid worker artifact scope, source or image pin");
22215
- const root = realpathSync7(resolve19(options.root ?? process.cwd()));
22933
+ const root = realpathSync7(resolve21(options.root ?? process.cwd()));
22216
22934
  if (typeof options.jobsFile !== "string" || !options.jobsFile || typeof options.entry !== "string" || !options.entry) throw new Error("Worker artifact planning requires explicit jobs and entry files");
22217
- const filename2 = resolve19(root, options.jobsFile);
22935
+ const filename2 = resolve21(root, options.jobsFile);
22218
22936
  assertLocalStatePath(root, filename2);
22219
- if (!existsSync10(filename2) || !lstatSync15(filename2).isFile() || lstatSync15(filename2).size > 32768) throw new Error("Worker job metadata file must be a bounded repository file");
22937
+ if (!existsSync13(filename2) || !lstatSync17(filename2).isFile() || lstatSync17(filename2).size > 32768) throw new Error("Worker job metadata file must be a bounded repository file");
22220
22938
  let jobs;
22221
22939
  try {
22222
- jobs = jobMetadata(JSON.parse(readFileSync18(filename2, "utf8")));
22940
+ jobs = jobMetadata(JSON.parse(readFileSync21(filename2, "utf8")));
22223
22941
  } catch {
22224
22942
  throw new Error("Invalid bounded worker job metadata");
22225
22943
  }
@@ -22240,8 +22958,8 @@ async function planWorkerArtifactRegistration(options) {
22240
22958
  }
22241
22959
 
22242
22960
  // src/workload-trust-state.ts
22243
- import { lstatSync as lstatSync16, readFileSync as readFileSync19 } from "node:fs";
22244
- import { isAbsolute as isAbsolute15, resolve as resolve20 } from "node:path";
22961
+ import { lstatSync as lstatSync18, readFileSync as readFileSync22 } from "node:fs";
22962
+ import { isAbsolute as isAbsolute16, resolve as resolve22 } from "node:path";
22245
22963
  function parseWorkloadTrustState(args2) {
22246
22964
  const action = args2[0];
22247
22965
  if (action !== "plan" && action !== "initialize") throw new Error("Use backify workload trust-state plan|initialize");
@@ -22263,12 +22981,12 @@ function parseWorkloadTrustState(args2) {
22263
22981
  return { action, trustFile: values.get("--trust-file"), stateFile: values.get("--state-file"), expectedRevision: Number(values.get("--revision")), expectedScope: { organizationId: values.get("--organization"), applicationId: values.get("--application"), environmentId: values.get("--environment") }, confirmed: values.has("--confirm") };
22264
22982
  }
22265
22983
  function workloadTrustState(options) {
22266
- if (!["plan", "initialize"].includes(options.action) || !isAbsolute15(options.trustFile) || !isAbsolute15(options.stateFile) || resolve20(options.trustFile) === resolve20(options.stateFile) || !Number.isSafeInteger(options.expectedRevision) || options.expectedRevision < 1) throw new Error("Specify separate absolute trust/state paths and a positive reviewed revision");
22984
+ if (!["plan", "initialize"].includes(options.action) || !isAbsolute16(options.trustFile) || !isAbsolute16(options.stateFile) || resolve22(options.trustFile) === resolve22(options.stateFile) || !Number.isSafeInteger(options.expectedRevision) || options.expectedRevision < 1) throw new Error("Specify separate absolute trust/state paths and a positive reviewed revision");
22267
22985
  let trust;
22268
22986
  try {
22269
- const metadata = lstatSync16(options.trustFile);
22987
+ const metadata = lstatSync18(options.trustFile);
22270
22988
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 131072) throw new Error();
22271
- const bytes = readFileSync19(options.trustFile);
22989
+ const bytes = readFileSync22(options.trustFile);
22272
22990
  if (bytes.length > 131072) throw new Error();
22273
22991
  trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
22274
22992
  } catch {
@@ -22278,14 +22996,14 @@ function workloadTrustState(options) {
22278
22996
  if (trust.revision !== options.expectedRevision) throw new Error("Application identity trust revision changed; review again");
22279
22997
  if (options.action === "initialize") {
22280
22998
  if (!options.confirmed) throw new Error("Initialization requires explicit --confirm after reviewing the plan");
22281
- initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve20(options.stateFile) });
22999
+ initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve22(options.stateFile) });
22282
23000
  }
22283
- return { schemaVersion: 1, status: options.action === "plan" ? "planned" : "initialized", scope: options.expectedScope, revision: trust.revision, validUntil: trust.validUntil, stateFile: resolve20(options.stateFile), requiresPrivatePersistentDirectory: true, recoveryAutomatic: false, productionChanges: false };
23001
+ return { schemaVersion: 1, status: options.action === "plan" ? "planned" : "initialized", scope: options.expectedScope, revision: trust.revision, validUntil: trust.validUntil, stateFile: resolve22(options.stateFile), requiresPrivatePersistentDirectory: true, recoveryAutomatic: false, productionChanges: false };
22284
23002
  }
22285
23003
 
22286
23004
  // src/scaffold.ts
22287
- import { existsSync as existsSync11, lstatSync as lstatSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync20, readdirSync as readdirSync6, realpathSync as realpathSync8, writeFileSync as writeFileSync10 } from "node:fs";
22288
- import { dirname as dirname13, join as join13, relative as relative10, resolve as resolve21 } from "node:path";
23005
+ import { existsSync as existsSync14, lstatSync as lstatSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync23, readdirSync as readdirSync8, realpathSync as realpathSync8, writeFileSync as writeFileSync13 } from "node:fs";
23006
+ import { dirname as dirname16, join as join16, relative as relative10, resolve as resolve23 } from "node:path";
22289
23007
  function json2(value) {
22290
23008
  return JSON.stringify(value, null, 2) + "\n";
22291
23009
  }
@@ -22457,519 +23175,84 @@ server/jobs.ts; it is a declaration-only entry loaded by a trusted Backify host.
22457
23175
  The Dockerfile builds the HTTP server and web/dist frontend and serves both from
22458
23176
  one origin, with HOST/PORT from its host. Keep package-lock.json in source control.
22459
23177
  Use backify ship for Preview and promote that tested release to Live. No production
22460
- provider-readiness claim, cloud deployment or browser authentication is implied.
22461
- `
22462
- };
22463
- }
22464
- function scaffoldFullStack(options) {
22465
- const files = fullStackTemplate(options.appId, options.name);
22466
- const root = resolve21(options.root ?? process.cwd());
22467
- if (!existsSync11(root) || !lstatSync17(root).isDirectory() || lstatSync17(root).isSymbolicLink()) throw new Error("Scaffold destination must be an existing non-linked directory");
22468
- const allowed = /* @__PURE__ */ new Set([...Object.keys(files).map((filename2) => filename2.split("/")[0]), ".git", "node_modules", "package-lock.json", ".backify"]);
22469
- if (readdirSync6(root).some((filename2) => !allowed.has(filename2))) throw new Error("Scaffolding requires an empty folder or an unchanged generated starter");
22470
- const canonicalRoot = realpathSync8(root);
22471
- const pending = [];
22472
- for (const [filename2, content] of Object.entries(files)) {
22473
- const destination = join13(root, filename2);
22474
- let current = destination;
22475
- while (current !== root) {
22476
- try {
22477
- const stat = lstatSync17(current);
22478
- if (stat.isSymbolicLink() || current !== destination && !stat.isDirectory()) throw new Error("Scaffolding refuses linked or invalid output paths: " + filename2);
22479
- if (relative10(canonicalRoot, realpathSync8(current)).startsWith("..")) throw new Error("Scaffold output must remain inside its directory");
22480
- if (current === destination && (!stat.isFile() || readFileSync20(current, "utf8") !== content)) throw new Error("Scaffolding refuses to overwrite existing content: " + filename2);
22481
- } catch (error) {
22482
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
22483
- if (current === destination) pending.push(filename2);
22484
- }
22485
- current = dirname13(current);
22486
- }
22487
- }
22488
- for (const filename2 of pending) {
22489
- const destination = join13(root, filename2);
22490
- mkdirSync8(dirname13(destination), { recursive: true });
22491
- writeFileSync10(destination, files[filename2], { flag: "wx" });
22492
- }
22493
- return { template: "react-vite-hono", created: pending, unchanged: Object.keys(files).length - pending.length, installed: false, deployed: false };
22494
- }
22495
-
22496
- // src/local-inspection-command.ts
22497
- function localInspectionCommand(args2) {
22498
- const routes = { tables: "/v1/database/tables", rows: "/v1/database/rows", buckets: "/v1/storage/buckets", objects: "/v1/storage/objects", preview: "/v1/storage/preview", events: "/v1/events" };
22499
- const kind = args2[0];
22500
- if (!Object.hasOwn(routes, kind)) throw new Error("Use backify inspect tables|rows|buckets|objects|preview|events --local [--json]");
22501
- const allowed = kind === "rows" ? ["--table", "--limit", "--offset"] : kind === "objects" ? ["--bucket", "--prefix", "--limit", "--offset"] : kind === "preview" ? ["--bucket", "--key"] : [];
22502
- const values = /* @__PURE__ */ new Map();
22503
- const flags = /* @__PURE__ */ new Set();
22504
- for (let index = 1; index < args2.length; index++) {
22505
- const name2 = args2[index];
22506
- if (["--local", "--json"].includes(name2)) {
22507
- if (flags.has(name2)) throw new Error("Duplicate inspection flag");
22508
- flags.add(name2);
22509
- continue;
22510
- }
22511
- if (!allowed.includes(name2) || values.has(name2) || args2[index + 1] === void 0 || args2[index + 1].startsWith("--")) throw new Error("Invalid or missing inspection option");
22512
- values.set(name2, args2[++index]);
22513
- }
22514
- if (!flags.has("--local")) throw new Error("Inspection requires --local; no cloud or production data is queried");
22515
- const required = (name2) => {
22516
- const value = values.get(name2);
22517
- if (!value) throw new Error("Required inspection option: " + name2);
22518
- return value;
22519
- };
22520
- const page = () => {
22521
- const number = (name2, fallback, minimum, maximum) => {
22522
- const value = values.get(name2);
22523
- if (value !== void 0 && !/^\d+$/.test(value)) throw new Error("Invalid inspection page");
22524
- const parsed = value === void 0 ? fallback : Number(value);
22525
- if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) throw new Error("Invalid inspection page");
22526
- return parsed;
22527
- };
22528
- return { limit: number("--limit", 25, 1, 50), offset: number("--offset", 0, 0, 1e4) };
22529
- };
22530
- const input = kind === "rows" ? { table: required("--table"), ...page() } : kind === "objects" ? { bucket: required("--bucket"), ...page(), ...values.has("--prefix") ? { prefix: values.get("--prefix") } : {} } : kind === "preview" ? { bucket: required("--bucket"), key: required("--key") } : void 0;
22531
- return { kind, path: routes[kind], input };
22532
- }
22533
-
22534
- // src/development.ts
22535
- import { existsSync as existsSync14, readFileSync as readFileSync23, readdirSync as readdirSync8, writeFileSync as writeFileSync13, renameSync as renameSync4, rmSync as rmSync2, mkdtempSync as mkdtempSync2, lstatSync as lstatSync19, cpSync } from "node:fs";
22536
- import { dirname as dirname16, join as join16, resolve as resolve23 } from "node:path";
22537
- import { spawnSync as spawnSync3 } from "node:child_process";
22538
- import { randomUUID as randomUUID9 } from "node:crypto";
22539
-
22540
- // src/setup.ts
22541
- import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync7, lstatSync as lstatSync18 } from "node:fs";
22542
- import { basename as basename3, dirname as dirname15, join as join15, resolve as resolve22, isAbsolute as isAbsolute16 } from "node:path";
22543
- import { spawnSync as spawnSync2 } from "node:child_process";
22544
-
22545
- // src/delivery.ts
22546
- import { existsSync as existsSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
22547
- import { dirname as dirname14, join as join14, relative as relativePath } from "node:path";
22548
- import { createHash as createHash15 } from "node:crypto";
22549
- var previousGeneratedWorkflowHashes = /* @__PURE__ */ new Set(["33e962274e8493f7cbac2afbac05a4e96afeec49ee08cf2bf82f8add9081b28b", "578dfb4c771c623fd04454e8a604efd701d5563d11e11191324862369bd24718", "6eb280ae7bdb14c390011686c41400f59c023304c3015d8440b7e80aaafa1c11", "545e03a1d5b996ade179e0e60ed5c86accdadc1982c1c25efd094e34f72f47ee"]);
22550
- function validateDeliveryInstructions(root) {
22551
- for (const filename2 of ["BACKIFY.md", "BACKIFY-WORKFLOW.md"]) {
22552
- const path = join14(root, filename2);
22553
- if (existsSync12(path)) {
22554
- const existing = readFileSync21(path, "utf8").replace(/\r\n/g, "\n");
22555
- if (existing !== deliveryInstructions && !previousGeneratedWorkflowHashes.has(createHash15("sha256").update(existing).digest("hex"))) throw new Error(filename2 + " contains different instructions. Review it before initializing; it will not be overwritten.");
22556
- }
22557
- }
22558
- }
22559
- function installDeliveryInstructions(root, instructionFiles2) {
22560
- validateDeliveryInstructions(root);
22561
- writeFileSync11(join14(root, "BACKIFY.md"), deliveryInstructions);
22562
- for (const relative11 of instructionFiles2) {
22563
- const instructionPath = join14(root, relative11), content = readFileSync21(instructionPath, "utf8");
22564
- const link = relativePath(dirname14(instructionPath), join14(root, "BACKIFY.md")).replaceAll("\\", "/");
22565
- const legacyLink = relativePath(dirname14(instructionPath), join14(root, "BACKIFY-WORKFLOW.md")).replaceAll("\\", "/");
22566
- const legacy = "For application delivery and recording each user request, follow [BACKIFY-WORKFLOW.md](" + legacyLink + ").";
22567
- const guidance = "For application delivery and recording each user request, follow [BACKIFY.md](" + link + ").";
22568
- if (content.includes(legacy)) writeFileSync11(instructionPath, content.replace(legacy, guidance));
22569
- else if (!content.includes("## Backify delivery")) writeFileSync11(instructionPath, content.trimEnd() + "\n\n## Backify delivery\n\n" + guidance + "\n");
22570
- }
22571
- }
22572
-
22573
- // src/setup.ts
22574
- var instructionFiles = ["AGENTS.md"];
22575
- var agentInstructionFiles = { codex: "AGENTS.md", claude: "CLAUDE.md", copilot: join15(".github", "copilot-instructions.md"), gemini: "GEMINI.md" };
22576
- function instructionSelection(root, agent, instructionFile) {
22577
- const configPath2 = join15(root, "backify.json"), configured = existsSync13(configPath2) ? JSON.parse(readFileSync22(configPath2, "utf8")) : {};
22578
- const custom = instructionFile ?? (agent === void 0 ? configured.instructionFile : void 0);
22579
- const selected = instructionFile !== void 0 ? "custom" : agent ?? configured.agent ?? "codex";
22580
- if (typeof selected !== "string" || selected !== "custom" && !Object.hasOwn(agentInstructionFiles, selected)) throw new Error("Select the current tool with --agent or --instruction-file <relative-path>.");
22581
- const filename2 = custom ?? agentInstructionFiles[selected];
22582
- if (typeof filename2 !== "string" || !filename2.trim()) throw new Error("Select the current tool with --agent codex, claude, copilot, or gemini, or --instruction-file <relative-path> for any other tool.");
22583
- const normalized = filename2.replaceAll("\\", "/"), parts = normalized.split("/");
22584
- if (isAbsolute16(filename2) || /^[A-Za-z]:/.test(filename2) || parts.some((part) => !part || part === "." || part === ".." || part.toLowerCase() === ".git") || ["backify.md", "backify-workflow.md", "backify.json", "package.json", "package-lock.json"].includes(normalized.toLowerCase())) throw new Error("Instruction file must be a relative tool-instruction path inside the project, not Git metadata or a Backify configuration file.");
22585
- for (let index = 1; index <= parts.length; index++) {
22586
- const entry = join15(root, ...parts.slice(0, index));
22587
- try {
22588
- const metadata = lstatSync18(entry);
22589
- if (metadata.isSymbolicLink() || index < parts.length && !metadata.isDirectory() || index === parts.length && !metadata.isFile()) throw new Error("Instruction file path cannot traverse symlinks or replace a directory.");
22590
- } catch (reason) {
22591
- if (reason.code !== "ENOENT") throw reason;
22592
- }
22593
- }
22594
- return { agent: selected, instructionFile: normalized, files: [normalized] };
22595
- }
22596
- function selectedInstructionFiles(root = process.cwd(), agent, instructionFile) {
22597
- return instructionSelection(root, agent, instructionFile).files;
22598
- }
22599
- function isToolWorkspaceEntry(entry, root = process.cwd()) {
22600
- if (entry.name.toLowerCase() === ".git" || entry.name.toLowerCase() === ".env" || entry.name.toLowerCase().startsWith(".env.")) return false;
22601
- return entry.isDirectory() && (entry.name.startsWith(".") || ["work", "outputs"].includes(entry.name) || readdirSync7(join15(root, entry.name)).length === 0) || entry.isFile() && (entry.name.startsWith(".") || /\.(md|mdc)$/i.test(entry.name));
22602
- }
22603
- function detectPackageManager(root = process.cwd()) {
22604
- for (const [file, name2] of [["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lockb", "bun"], ["bun.lock", "bun"], ["package-lock.json", "npm"]]) if (existsSync13(join15(root, file))) return name2;
22605
- return "npm";
22606
- }
22607
- function updateInstructions(root = process.cwd(), agent, instructionFile) {
22608
- const updates = selectedInstructionFiles(root, agent, instructionFile).map((relative11) => {
22609
- const path = join15(root, relative11);
22610
- return { path, content: updateManagedInstructions(existsSync13(path) ? readFileSync22(path, "utf8") : "") };
22611
- });
22612
- for (const { path, content } of updates) {
22613
- mkdirSync9(dirname15(path), { recursive: true });
22614
- writeFileSync12(path, content);
22615
- }
22616
- }
22617
- function synchronize(root = process.cwd(), agent, instructionFile) {
22618
- const path = join15(root, "backify.json");
22619
- if (!existsSync13(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
22620
- const value = JSON.parse(readFileSync22(path, "utf8")), issues = validateConfig(value);
22621
- if (issues.length) throw new Error(issues.join("; "));
22622
- if (value.instructionVersion > INSTRUCTION_VERSION) throw new Error("This app uses newer instructions. Install a matching or newer CLI before syncing.");
22623
- const selected = instructionSelection(root, agent, instructionFile);
22624
- validateDeliveryInstructions(root);
22625
- updateInstructions(root, selected.agent, selected.instructionFile);
22626
- installDeliveryInstructions(root, selected.files);
22627
- value.agent = selected.agent;
22628
- value.instructionFile = selected.instructionFile;
22629
- value.instructionVersion = INSTRUCTION_VERSION;
22630
- writeFileSync12(path, JSON.stringify(value, null, 2) + "\n");
22631
- }
22632
- function sdkInstalled(root = process.cwd()) {
22633
- try {
22634
- const pkg = JSON.parse(readFileSync22(join15(root, "package.json"), "utf8"));
22635
- return Boolean(pkg.dependencies?.[SDK_PACKAGE] || pkg.devDependencies?.[SDK_PACKAGE]);
22636
- } catch {
22637
- return false;
22638
- }
22639
- }
22640
- function installSdk(root = process.cwd()) {
22641
- if (sdkInstalled(root)) return;
22642
- const manager = detectPackageManager(root), args2 = manager === "npm" ? ["install", SDK_PACKAGE] : ["add", SDK_PACKAGE], result = spawnSync2(manager, args2, { cwd: root, stdio: "inherit", shell: process.platform === "win32" });
22643
- if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
22644
- }
22645
- function ensurePackageManifest(root) {
22646
- const path = join15(root, "package.json");
22647
- if (existsSync13(path)) return;
22648
- const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "GEMINI.md", "BACKIFY.md", "BACKIFY-WORKFLOW.md", "README.md", "LICENSE", "backify.json"]);
22649
- if (readdirSync7(root, { withFileTypes: true }).some((entry) => !setupFiles.has(entry.name) && !isToolWorkspaceEntry(entry, root))) throw new Error("package.json is missing. Run backify init from the existing application's JavaScript/TypeScript package directory, or start in an empty folder. See https://agent.backify.dev/");
22650
- const name2 = basename3(resolve22(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
22651
- writeFileSync12(path, JSON.stringify({ name: name2, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
22652
- }
22653
- function initialize(appId, root = process.cwd(), install = true, agent, instructionFile) {
22654
- if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
22655
- const selected = instructionSelection(root, agent, instructionFile), files = selected.files;
22656
- validateDeliveryInstructions(root);
22657
- for (const relative11 of files) if (existsSync13(join15(root, relative11))) updateManagedInstructions(readFileSync22(join15(root, relative11), "utf8"));
22658
- ensurePackageManifest(root);
22659
- if (install) installSdk(root);
22660
- const configPath2 = join15(root, "backify.json"), existing = existsSync13(configPath2) ? JSON.parse(readFileSync22(configPath2, "utf8")) : {};
22661
- writeFileSync12(configPath2, JSON.stringify({ ...existing, ...createConfig(appId), agent: selected.agent, instructionFile: selected.instructionFile }, null, 2) + "\n");
22662
- updateInstructions(root, selected.agent, selected.instructionFile);
22663
- installDeliveryInstructions(root, files);
22664
- }
22665
- function doctor(root = process.cwd(), agent, instructionFile) {
22666
- const issues = [], warnings = [];
22667
- if (!sdkInstalled(root)) issues.push(`${SDK_PACKAGE} is not installed`);
22668
- let config;
22669
- try {
22670
- config = JSON.parse(readFileSync22(join15(root, "backify.json"), "utf8"));
22671
- issues.push(...validateConfig(config));
22672
- } catch {
22673
- issues.push("backify.json is missing or invalid JSON");
22674
- }
22675
- if (config && config.instructionVersion !== INSTRUCTION_VERSION) issues.push(`Repository instruction version ${config.instructionVersion}; CLI version ${INSTRUCTION_VERSION}. Use the matching CLI or run backify sync to upgrade.`);
22676
- let files = instructionFiles;
22677
- try {
22678
- files = selectedInstructionFiles(root, agent, instructionFile);
22679
- } catch (reason) {
22680
- issues.push(reason instanceof Error ? reason.message : String(reason));
22681
- }
22682
- for (const relative11 of files) {
22683
- const path = join15(root, relative11);
22684
- if (!existsSync13(path)) {
22685
- issues.push(`${relative11} is missing`);
22686
- continue;
22687
- }
22688
- issues.push(...managedInstructionIssues(readFileSync22(path, "utf8")).map((issue) => `${relative11} ${issue}`));
22689
- }
22690
- let version = "unknown";
22691
- try {
22692
- const pkg = JSON.parse(readFileSync22(join15(root, "package.json"), "utf8"));
22693
- version = pkg.dependencies?.[SDK_PACKAGE] ?? pkg.devDependencies?.[SDK_PACKAGE] ?? "missing";
22694
- } catch {
22695
- }
22696
- return { valid: issues.length === 0, appId: config?.appId, sdkVersion: version, instructionVersion: config?.instructionVersion, currentInstructionVersion: INSTRUCTION_VERSION, issues, warnings };
22697
- }
22698
-
22699
- // src/development.ts
22700
- function promptMetadata(args2) {
22701
- const fields5 = [["--model", "model", 200], ["--ai-tool", "aiTool", 200], ["--access-level", "accessLevel", 500]];
22702
- const metadata = {};
22703
- for (const [flag, field, maxLength] of fields5) {
22704
- const index = args2.indexOf(flag);
22705
- if (index < 0) continue;
22706
- const value = args2[index + 1]?.trim();
22707
- if (!value || value.startsWith("--") || value.length > maxLength || /[\u0000-\u001f\u007f-\u009f]|bfcli_[0-9a-f]{64}|--pair\s+[0-9a-f]{64}/i.test(value))
22708
- throw new Error(`Provide ${flag} with a nonempty value of at most ${maxLength} characters without control characters or credentials.`);
22709
- metadata[field] = value;
22710
- }
22711
- return metadata;
22712
- }
22713
- function applicationGuid(appId) {
22714
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(appId)) return appId.toLowerCase();
22715
- const match = /^app_([0-9a-f]{32})$/i.exec(appId);
22716
- if (!match) throw new Error("Use the application ID supplied by Backify.");
22717
- const value = match[1];
22718
- return [value.slice(0, 8), value.slice(8, 12), value.slice(12, 16), value.slice(16, 20), value.slice(20)].join("-");
22719
- }
22720
- function projectApplication(root = process.cwd()) {
22721
- if (!existsSync14(join16(root, "backify.json"))) throw new Error("Run backify connect --app <application-id>, then backify init.");
22722
- return JSON.parse(readFileSync23(join16(root, "backify.json"), "utf8")).appId;
22723
- }
22724
- function apiAddress(value) {
22725
- const address = new URL(value);
22726
- if (address.username || address.password || address.search || address.hash || address.protocol !== "https:" && !(address.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(address.hostname))) throw new Error("Backify API must use HTTPS (HTTP is allowed only on loopback for local development).");
22727
- return address.toString().replace(/\/$/, "");
23178
+ provider-readiness claim, cloud deployment or browser authentication is implied.
23179
+ `
23180
+ };
22728
23181
  }
22729
- function apiClient(session, transport = fetch) {
22730
- const base = apiAddress(session.apiUrl);
22731
- return async (path, init) => {
22732
- if (!path.startsWith("/") || path.startsWith("//")) throw new Error("Invalid API path.");
22733
- if (session.expiresAt && Date.parse(session.expiresAt) <= Date.now()) throw new Error("CLI session expired. Copy a new setup prompt or run backify login.");
22734
- const response = await transport(base + path, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4), headers: { "content-type": "application/json", "user-agent": cliUserAgent, "X-Organization-ID": session.organizationId, ...session.token ? { authorization: "Bearer " + session.token } : {}, ...init?.headers } });
22735
- if (!response.ok) {
22736
- let message = "Backify request failed (" + response.status + ").";
23182
+ function scaffoldFullStack(options) {
23183
+ const files = fullStackTemplate(options.appId, options.name);
23184
+ const root = resolve23(options.root ?? process.cwd());
23185
+ if (!existsSync14(root) || !lstatSync19(root).isDirectory() || lstatSync19(root).isSymbolicLink()) throw new Error("Scaffold destination must be an existing non-linked directory");
23186
+ const allowed = /* @__PURE__ */ new Set([...Object.keys(files).map((filename2) => filename2.split("/")[0]), ".git", "node_modules", "package-lock.json", ".backify"]);
23187
+ if (readdirSync8(root).some((filename2) => !allowed.has(filename2))) throw new Error("Scaffolding requires an empty folder or an unchanged generated starter");
23188
+ const canonicalRoot = realpathSync8(root);
23189
+ const pending = [];
23190
+ for (const [filename2, content] of Object.entries(files)) {
23191
+ const destination = join16(root, filename2);
23192
+ let current = destination;
23193
+ while (current !== root) {
22737
23194
  try {
22738
- const problem = await response.json();
22739
- message = [problem.code, problem.title].filter(Boolean).join(": ") || message;
22740
- } catch {
23195
+ const stat = lstatSync19(current);
23196
+ if (stat.isSymbolicLink() || current !== destination && !stat.isDirectory()) throw new Error("Scaffolding refuses linked or invalid output paths: " + filename2);
23197
+ if (relative10(canonicalRoot, realpathSync8(current)).startsWith("..")) throw new Error("Scaffold output must remain inside its directory");
23198
+ if (current === destination && (!stat.isFile() || readFileSync23(current, "utf8") !== content)) throw new Error("Scaffolding refuses to overwrite existing content: " + filename2);
23199
+ } catch (error) {
23200
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
23201
+ if (current === destination) pending.push(filename2);
22741
23202
  }
22742
- throw new Error(message);
22743
- }
22744
- return response.status === 204 ? void 0 : await response.json();
22745
- };
22746
- }
22747
- function saveUserConfig(path, config) {
22748
- const temporary = path + "." + randomUUID9() + ".tmp";
22749
- try {
22750
- writeFileSync13(temporary, JSON.stringify(config, null, 2) + "\n", { mode: 384, flag: "wx" });
22751
- if (process.platform === "win32") {
22752
- const account = [process.env.USERDOMAIN, process.env.USERNAME].filter(Boolean).join("\\");
22753
- if (!account) throw new Error("Cannot identify the account to protect CLI credentials.");
22754
- const secured = spawnSync3("icacls", [temporary, "/inheritance:r", "/grant:r", account + ":(F)"], { windowsHide: true, encoding: "utf8" });
22755
- if (secured.status !== 0) throw new Error("Could not restrict CLI credential file permissions.");
22756
- }
22757
- renameSync4(temporary, path);
22758
- } finally {
22759
- rmSync2(temporary, { force: true });
22760
- }
22761
- }
22762
- function repositoryName(remote) {
22763
- const match = /^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i.exec(remote);
22764
- if (!match) throw new Error("Repository must be a credential-free GitHub URL.");
22765
- return match[1].toLowerCase();
22766
- }
22767
- function git2(args2, root = process.cwd(), environment = process.env) {
22768
- const result = spawnSync3("git", args2, { cwd: root, env: environment, encoding: "utf8", windowsHide: true, maxBuffer: 2 * 1024 * 1024 });
22769
- if (result.status !== 0) throw new Error("Git " + args2[0] + " failed. Check repository access, branch protection, and local changes.");
22770
- return result.stdout.trim();
22771
- }
22772
- function gitEnvironment(repository, token) {
22773
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) throw new Error("Invalid repository.");
22774
- return { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_CONFIG_COUNT: "3", GIT_CONFIG_KEY_0: "http.https://github.com/" + repository + ".git.extraheader", GIT_CONFIG_VALUE_0: "Authorization: Basic " + Buffer.from("x-access-token:" + token).toString("base64"), GIT_CONFIG_KEY_1: "credential.helper", GIT_CONFIG_VALUE_1: "", GIT_CONFIG_KEY_2: "http.followRedirects", GIT_CONFIG_VALUE_2: "false" };
22775
- }
22776
- function validateCloneMerge(source, target) {
22777
- for (const entry of readdirSync8(source, { withFileTypes: true })) {
22778
- const destination = join16(target, entry.name);
22779
- let existing;
22780
- try {
22781
- existing = lstatSync19(destination);
22782
- } catch (reason) {
22783
- if (reason.code === "ENOENT") continue;
22784
- throw reason;
23203
+ current = dirname16(current);
22785
23204
  }
22786
- if (entry.isDirectory() && existing.isDirectory() && !existing.isSymbolicLink()) validateCloneMerge(join16(source, entry.name), destination);
22787
- else throw new Error("Clone conflicts with existing workspace entry: " + destination + ". Existing files are never overwritten; reconcile the conflict or use a new folder.");
22788
- }
22789
- }
22790
- async function cloneProject(call2, appId, root = process.cwd()) {
22791
- const status = await call2("/apps/" + applicationGuid(appId) + "/developer");
22792
- if (!status.repository || !status.branch || !status.workflowReady) throw new Error("Connect the repository in Backify and wait for workflow setup.");
22793
- if (readdirSync8(root, { withFileTypes: true }).some((entry) => !isToolWorkspaceEntry(entry, root))) throw new Error("--clone requires an empty directory or a workspace containing only AI tool metadata and default work/outputs directories. Existing application files and Git repositories are never overwritten.");
22794
- const credential = await call2("/apps/" + applicationGuid(appId) + "/developer/git-credential", { method: "POST" });
22795
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed. Run connect again.");
22796
- const staging = mkdtempSync2(join16(dirname16(resolve23(root)), ".backify-clone-"));
22797
- try {
22798
- git2(["clone", "--single-branch", "--branch", status.branch, "--", "https://github.com/" + status.repository + ".git", "."], staging, gitEnvironment(status.repository, credential.token));
22799
- validateCloneMerge(staging, root);
22800
- for (const entry of readdirSync8(staging)) cpSync(join16(staging, entry), join16(root, entry), { recursive: true, force: false, errorOnExist: true, dereference: false });
22801
- } finally {
22802
- if (dirname16(staging) !== dirname16(resolve23(root)) || !staging.startsWith(join16(dirname16(resolve23(root)), ".backify-clone-"))) throw new Error("Refusing to clean up an unexpected clone staging path.");
22803
- rmSync2(staging, { recursive: true, force: true });
22804
- }
22805
- }
22806
- async function pullProject(call2, appId, root = process.cwd()) {
22807
- if (existsSync14(join16(root, "backify.json")) && applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the connected application.");
22808
- const prefix = "/apps/" + applicationGuid(appId);
22809
- const status = await call2(prefix + "/developer");
22810
- if (!status.repository || !status.branch) throw new Error("Connect the repository and branch in Backify before pulling.");
22811
- if (resolve23(git2(["rev-parse", "--show-toplevel"], root)) !== resolve23(root)) throw new Error("Run pull from the connected repository root.");
22812
- if (repositoryName(git2(["remote", "get-url", "origin"], root)) !== status.repository.toLowerCase()) throw new Error("Local origin does not match the repository connected in Backify.");
22813
- if (git2(["branch", "--show-current"], root) !== status.branch) throw new Error("Local branch does not match the branch connected in Backify. The CLI never switches branches automatically.");
22814
- git2(["check-ref-format", "--branch", status.branch], root);
22815
- const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
22816
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before pulling.");
22817
- git2(["fetch", "--no-tags", "--", "https://github.com/" + status.repository + ".git", "refs/heads/" + status.branch + ":refs/remotes/origin/" + status.branch], root, gitEnvironment(status.repository, credential.token));
22818
- const remoteCommit = git2(["rev-parse", "FETCH_HEAD"], root);
22819
- const counts = git2(["rev-list", "--left-right", "--count", "HEAD..." + remoteCommit], root).split(/\s+/).map(Number);
22820
- if (counts.length !== 2 || counts.some((count) => !Number.isSafeInteger(count) || count < 0)) throw new Error("Could not compare local and remote history.");
22821
- const [ahead, behind] = counts;
22822
- if (!behind) return { status: "up-to-date", ahead, behind, commit: git2(["rev-parse", "HEAD"], root) };
22823
- if (ahead) return { status: "blocked", reason: "Local and GitHub history have diverged. Review and reconcile them explicitly; no rebase, reset, or force-push was performed.", ahead, behind };
22824
- if (git2(["status", "--porcelain"], root)) return { status: "blocked", reason: "Local changes or untracked files are present. Commit, ignore, or otherwise resolve them explicitly before updating; nothing was stashed or overwritten.", ahead, behind };
22825
- git2(["merge", "--ff-only", "--", remoteCommit], root);
22826
- return { status: "updated", ahead: 0, behind: 0, commit: git2(["rev-parse", "HEAD"], root) };
22827
- }
22828
- async function readPrompt(input) {
22829
- const chunks = [];
22830
- let size = 0;
22831
- for await (const chunk of input) {
22832
- const bytes = Buffer.from(chunk);
22833
- size += bytes.length;
22834
- if (size > 128e3) throw new Error("Prompt exceeds the size limit.");
22835
- chunks.push(bytes);
22836
- }
22837
- const text3 = Buffer.concat(chunks).toString("utf8");
22838
- if (!text3.trim() || text3.length > 32e3) throw new Error("Provide between 1 and 32000 characters of user prompt text.");
22839
- if (/\bbfcli_[0-9a-f]{64}\b|--pair\s+[0-9a-f]{64}/i.test(text3)) throw new Error("Do not record setup prompts or Backify credentials.");
22840
- return text3;
22841
- }
22842
- function attachmentNames(values) {
22843
- if (values.length > 50) throw new Error("At most 50 attachment filenames are allowed.");
22844
- return [...new Set(values.map((value) => {
22845
- const filename2 = value.replace(/\\/g, "/").split("/").pop();
22846
- if (!filename2 || filename2 === "." || filename2 === ".." || filename2.length > 255 || /[\x00-\x1f\x7f]/.test(filename2)) throw new Error("Invalid attachment filename.");
22847
- return filename2;
22848
- }))];
22849
- }
22850
- function localReport(runChecks, root = process.cwd()) {
22851
- const pkg = JSON.parse(readFileSync23(join16(root, "package.json"), "utf8"));
22852
- const dependencies = { ...pkg.devDependencies, ...pkg.dependencies };
22853
- const framework = ["next", "vite", "astro", "@sveltejs/kit", "nuxt", "react", "express"].find((name2) => dependencies[name2]) ?? "unknown";
22854
- const setup = doctor(root);
22855
- const checks = [{ name: "backify doctor", status: setup.valid ? "passed" : "failed" }];
22856
- const manager = detectPackageManager(root);
22857
- for (const name2 of ["typecheck", "lint", "test", "build"]) {
22858
- if (!pkg.scripts?.[name2]) continue;
22859
- const result = runChecks ? spawnSync3(manager, ["run", name2], { cwd: root, stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, CI: "true" }, shell: process.platform === "win32", windowsHide: true, timeout: 10 * 6e4 }) : void 0;
22860
- checks.push({ name: name2, status: result ? result.status === 0 ? "passed" : "failed" : "not-run" });
22861
- }
22862
- let branch = null, commitSha = null, dirty = true;
22863
- try {
22864
- branch = git2(["branch", "--show-current"], root);
22865
- commitSha = git2(["rev-parse", "HEAD"], root);
22866
- dirty = Boolean(git2(["status", "--porcelain"], root));
22867
- } catch {
22868
- }
22869
- const outputDirectory = ["dist", "build", "out", "."].find((directory2) => existsSync14(join16(root, directory2, "index.html"))) ?? null;
22870
- return { framework, sdkVersion: dependencies["@backifydev/sdk"] ?? null, branch, commitSha, dirty, buildCommand: pkg.scripts?.build ? manager + " run build" : null, outputDirectory, hasDockerfile: existsSync14(join16(root, "Dockerfile")), checks };
22871
- }
22872
- function matchesSource(release2, commit) {
22873
- return release2?.commitSha === commit || release2?.sourceCommitSha === commit;
22874
- }
22875
- function pushSource(appId, commit, branch, destination, root, environment) {
22876
- const remoteHead = git2(["ls-remote", "--", destination, "refs/heads/" + branch], root, environment).split(/\s+/)[0];
22877
- if (remoteHead === commit) return true;
22878
- if (remoteHead) {
22879
- git2(["fetch", "--no-tags", "--", destination, "refs/heads/" + branch], root, environment);
22880
- const fetchedHead = git2(["rev-parse", "FETCH_HEAD"], root);
22881
- if (fetchedHead === commit) return true;
22882
- const parent = git2(["show", "-s", "--format=%P", fetchedHead], root);
22883
- const message = git2(["show", "-s", "--format=%B", fetchedHead], root);
22884
- if (parent === commit && message === "Backify build " + applicationGuid(appId).replaceAll("-", "") + ": " + commit) return true;
22885
- if (git2(["merge-base", commit, fetchedHead], root) !== fetchedHead) throw new Error("Remote history contains other changes. Run backify pull and reconcile them before shipping; nothing was pushed or overwritten.");
22886
23205
  }
22887
- git2(["push", "--", destination, commit + ":refs/heads/" + branch], root, environment);
22888
- return false;
22889
- }
22890
- async function waitForPreview(call2, prefix, commit, options = {}) {
22891
- const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
22892
- const sleep = options.sleep ?? ((delay) => new Promise((resolve24) => setTimeout(resolve24, delay)));
22893
- do {
22894
- const deployments = await call2(prefix + "/deployments");
22895
- const matching = deployments.find((release2) => release2.kind === "preview" && matchesSource(release2, commit) && !options.previousDeploymentIds?.includes(release2.id));
22896
- if (matching?.status === "failed") throw new Error("Deployment of this source failed. Run backify status and inspect its deployment steps.");
22897
- const overview = await call2(prefix + "/releases");
22898
- if (matchesSource(overview.preview, commit) && overview.preview?.status === "succeeded" && overview.preview.healthResult === "healthy") return overview;
22899
- const build3 = await call2(prefix + "/developer/build?commitSha=" + commit);
22900
- if ((options.previousBuildRunId === void 0 || build3.runId !== options.previousBuildRunId) && ["failure", "cancelled", "timed_out", "action_required", "startup_failure"].includes(build3.conclusion ?? "")) throw new Error("GitHub build " + build3.conclusion + ". " + (build3.url ?? "Run backify status for details."));
22901
- await sleep(options.intervalMs ?? 4e3);
22902
- } while (Date.now() < deadline);
22903
- throw new Error("Timed out waiting for this commit's healthy preview. Run backify status; no production release was requested.");
22904
- }
22905
- async function ship(call2, appId, options = {}) {
22906
- const root = options.root ?? process.cwd(), prefix = "/apps/" + applicationGuid(appId);
22907
- const status = await call2(prefix + "/developer");
22908
- if (!status.repository || !status.branch || !status.workflowReady) throw new Error("The application's repository and build workflow must be connected in Backify.");
22909
- if (!doctor(root).valid) throw new Error("Run backify doctor and fix project setup before shipping.");
22910
- if (applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the application being shipped.");
22911
- const actualRoot = git2(["rev-parse", "--show-toplevel"], root);
22912
- if (resolve23(actualRoot).toLowerCase() !== resolve23(root).toLowerCase()) throw new Error("Shipping currently requires the application at the repository root.");
22913
- const origin = git2(["remote", "get-url", "origin"], root);
22914
- const pushOrigin = git2(["remote", "get-url", "--push", "origin"], root);
22915
- if (repositoryName(origin) !== status.repository.toLowerCase() || repositoryName(pushOrigin) !== status.repository.toLowerCase()) throw new Error("Local repository does not match this Backify application.");
22916
- if (git2(["branch", "--show-current"], root) !== status.branch) throw new Error("Use the connected branch: " + status.branch);
22917
- if (git2(["status", "--porcelain"], root)) throw new Error("Commit application changes before shipping. The CLI never stages or commits unrelated files.");
22918
- const commit = git2(["rev-parse", "HEAD"], root);
22919
- const report = localReport(true, root);
22920
- await call2(prefix + "/developer/report", { method: "POST", body: JSON.stringify(report) });
22921
- if (report.checks.some((check) => check.status === "failed")) throw new Error("Checks failed; nothing was pushed.");
22922
- if (status.hostingTargetType === "backify-cloud" && !report.outputDirectory && !report.hasDockerfile) throw new Error("Backify Cloud requires a static build containing index.html or a Dockerfile serving port 8080.");
22923
- if (status.hostingTargetType === "azure" && !report.hasDockerfile) throw new Error("Customer Azure requires a repository-root Dockerfile.");
22924
- if (git2(["status", "--porcelain"], root) || git2(["rev-parse", "HEAD"], root) !== commit) throw new Error("Checks changed tracked source. Review and commit those changes before shipping.");
22925
- const previousDeployments = await call2(prefix + "/deployments");
22926
- const previousBuild = await call2(prefix + "/developer/build?commitSha=" + commit);
22927
- const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
22928
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before shipping.");
22929
- const environment = gitEnvironment(status.repository, credential.token);
22930
- const destination = "https://github.com/" + status.repository + ".git";
22931
- const alreadyPushed = pushSource(appId, commit, status.branch, destination, root, environment);
22932
- const currentPreview = previousDeployments.find((release2) => release2.kind === "preview" && matchesSource(release2, commit));
22933
- if (alreadyPushed && (!currentPreview || currentPreview.status === "failed") && !(matchesSource(status.releases.preview, commit) && status.releases.preview?.status === "succeeded")) await call2(prefix + "/deployments/preview", { method: "POST", body: JSON.stringify({ sourceRevision: "latest" }) });
22934
- const overview = await waitForPreview(call2, prefix, commit, { timeoutMs: options.timeoutMs, previousDeploymentIds: previousDeployments.filter((deployment) => deployment.status === "failed").map((deployment) => deployment.id), previousBuildRunId: previousBuild.runId ?? null });
22935
- if (overview.preview.commitSha !== commit) {
22936
- if (git2(["status", "--porcelain"], root) || git2(["rev-parse", "HEAD"], root) !== commit) throw new Error("Local work changed while the preview built. Review it before synchronizing the build commit.");
22937
- const refreshed = await call2(prefix + "/developer/git-credential", { method: "POST" });
22938
- if (refreshed.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed while building.");
22939
- git2(["fetch", "--", destination, "refs/heads/" + status.branch], root, gitEnvironment(status.repository, refreshed.token));
22940
- git2(["merge", "--ff-only", overview.preview.commitSha], root);
23206
+ for (const filename2 of pending) {
23207
+ const destination = join16(root, filename2);
23208
+ mkdirSync9(dirname16(destination), { recursive: true });
23209
+ writeFileSync13(destination, files[filename2], { flag: "wx" });
22941
23210
  }
22942
- return publishPreview(call2, prefix, overview, options);
23211
+ return { template: "react-vite-hono", created: pending, unchanged: Object.keys(files).length - pending.length, installed: false, deployed: false };
22943
23212
  }
22944
- async function publishPreview(call2, prefix, overview, options = {}) {
22945
- if (!overview.preview || overview.preview.status !== "succeeded" || overview.preview.healthResult !== "healthy") throw new Error("Only a verified healthy preview can be published.");
22946
- const preview = overview.preview;
22947
- if (options.previewOnly) return { status: "preview-ready", url: preview.url };
22948
- if (overview.policy.requirePreviewTesting && !preview.testedAt) return { status: "testing-required", url: preview.url };
22949
- if (overview.live?.previewDeploymentId === preview.id && overview.live.status === "succeeded") return { status: "live", url: overview.live.url };
22950
- const promotion = await call2(prefix + "/deployments/promote", { method: "POST", headers: { "Idempotency-Key": "cli-ship-" + preview.id }, body: JSON.stringify({ previewDeploymentId: preview.id, tested: false }) });
22951
- if (promotion.status === "awaiting-approval" || promotion.approvalId) return { status: "awaiting-approval", url: preview.url };
22952
- if (!promotion.operationId) throw new Error("Publication returned no operation to verify. Run backify status.");
22953
- const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
22954
- do {
22955
- const operation = await call2("/operations/" + promotion.operationId);
22956
- if (operation.status === "failed" || operation.status === "cancelled") throw new Error(operation.errorMessage ?? "Production deployment did not complete.");
22957
- if (operation.status === "succeeded") {
22958
- const releases = await call2(prefix + "/releases");
22959
- if (releases.live?.previewDeploymentId !== preview.id || releases.live.status !== "succeeded" || releases.live.healthResult !== "healthy") throw new Error("Production did not activate this healthy release. Run backify status.");
22960
- return { status: "live", url: releases.live.url };
23213
+
23214
+ // src/local-inspection-command.ts
23215
+ function localInspectionCommand(args2) {
23216
+ const routes = { tables: "/v1/database/tables", rows: "/v1/database/rows", buckets: "/v1/storage/buckets", objects: "/v1/storage/objects", preview: "/v1/storage/preview", events: "/v1/events" };
23217
+ const kind = args2[0];
23218
+ if (!Object.hasOwn(routes, kind)) throw new Error("Use backify inspect tables|rows|buckets|objects|preview|events --local [--json]");
23219
+ const allowed = kind === "rows" ? ["--table", "--limit", "--offset"] : kind === "objects" ? ["--bucket", "--prefix", "--limit", "--offset"] : kind === "preview" ? ["--bucket", "--key"] : [];
23220
+ const values = /* @__PURE__ */ new Map();
23221
+ const flags = /* @__PURE__ */ new Set();
23222
+ for (let index = 1; index < args2.length; index++) {
23223
+ const name2 = args2[index];
23224
+ if (["--local", "--json"].includes(name2)) {
23225
+ if (flags.has(name2)) throw new Error("Duplicate inspection flag");
23226
+ flags.add(name2);
23227
+ continue;
22961
23228
  }
22962
- await new Promise((resolve24) => setTimeout(resolve24, 4e3));
22963
- } while (Date.now() < deadline);
22964
- throw new Error("Publication is still pending. Run backify status; deployment success has not been verified.");
23229
+ if (!allowed.includes(name2) || values.has(name2) || args2[index + 1] === void 0 || args2[index + 1].startsWith("--")) throw new Error("Invalid or missing inspection option");
23230
+ values.set(name2, args2[++index]);
23231
+ }
23232
+ if (!flags.has("--local")) throw new Error("Inspection requires --local; no cloud or production data is queried");
23233
+ const required = (name2) => {
23234
+ const value = values.get(name2);
23235
+ if (!value) throw new Error("Required inspection option: " + name2);
23236
+ return value;
23237
+ };
23238
+ const page = () => {
23239
+ const number = (name2, fallback, minimum, maximum) => {
23240
+ const value = values.get(name2);
23241
+ if (value !== void 0 && !/^\d+$/.test(value)) throw new Error("Invalid inspection page");
23242
+ const parsed = value === void 0 ? fallback : Number(value);
23243
+ if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) throw new Error("Invalid inspection page");
23244
+ return parsed;
23245
+ };
23246
+ return { limit: number("--limit", 25, 1, 50), offset: number("--offset", 0, 0, 1e4) };
23247
+ };
23248
+ const input = kind === "rows" ? { table: required("--table"), ...page() } : kind === "objects" ? { bucket: required("--bucket"), ...page(), ...values.has("--prefix") ? { prefix: values.get("--prefix") } : {} } : kind === "preview" ? { bucket: required("--bucket"), key: required("--key") } : void 0;
23249
+ return { kind, path: routes[kind], input };
22965
23250
  }
22966
23251
 
22967
23252
  // src/index.ts
22968
23253
  import { readFileSync as readFileSync24, existsSync as existsSync15, rmSync as rmSync3 } from "node:fs";
22969
23254
  import { homedir as homedir2 } from "node:os";
22970
23255
  import { join as join17 } from "node:path";
22971
- import { randomBytes as randomBytes6 } from "node:crypto";
22972
- import { createServer as createServer3 } from "node:http";
22973
23256
  import { spawn as spawn5 } from "node:child_process";
22974
23257
  var configPath = join17(homedir2(), ".backify.json");
22975
23258
  var userConfig = existsSync15(configPath) ? JSON.parse(readFileSync24(configPath, "utf8")) : {};
@@ -22977,73 +23260,42 @@ var [command2, ...args] = process.argv.slice(2);
22977
23260
  async function call(path, init) {
22978
23261
  let appId = option("--app");
22979
23262
  if (!appId && existsSync15(join17(process.cwd(), "backify.json"))) appId = projectApplication();
22980
- const session = appId ? userConfig.sessions?.["app_" + applicationGuid(appId).replaceAll("-", "")] : void 0;
22981
- return apiClient(session ?? { token: userConfig.token ?? "", organizationId: userConfig.organizationId ?? "", apiUrl: process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api" })(path, init);
23263
+ const session = appId ? userConfig.sessions?.[sessionKey(appId)] : void 0;
23264
+ const credential = session ?? { token: userConfig.token ?? "", organizationId: userConfig.organizationId ?? "", apiUrl: process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api" };
23265
+ return (appId ? applicationClient(credential, appId) : apiClient(credential))(path, init);
22982
23266
  }
22983
23267
  var option = (name2) => {
22984
23268
  const index = args.indexOf(name2);
22985
23269
  return index >= 0 ? args[index + 1] : void 0;
22986
23270
  };
22987
23271
  function openBrowser(url) {
22988
- const command3 = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open", args2 = process.platform === "win32" ? ["/c", "start", "", '"' + url + '"'] : [url];
22989
- const child = spawn5(command3, args2, { stdio: "ignore", detached: true, shell: process.platform === "win32" });
23272
+ const executable = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open", browserArgs = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
23273
+ const child = spawn5(executable, browserArgs, { stdio: "ignore", detached: true, windowsHide: true });
23274
+ child.on("error", () => console.error("Could not open a browser automatically. Open the sign-in URL printed above."));
22990
23275
  child.unref();
22991
23276
  }
22992
- async function browserLogin() {
22993
- const state = randomBytes6(24).toString("base64url"), server = createServer3((req, res) => {
22994
- if (req.method === "OPTIONS") {
22995
- res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "content-type" });
22996
- return res.end();
22997
- }
22998
- if (req.method !== "POST" || req.url !== "/callback") {
22999
- res.writeHead(404);
23000
- return res.end();
23277
+ function sessionKey(appId) {
23278
+ return "app_" + applicationGuid(appId).replaceAll("-", "");
23279
+ }
23280
+ async function browserLogin(appId, organizationId, apiUrl) {
23281
+ if (args.includes("--no-browser")) throw new CliAuthenticationRequired();
23282
+ const address = apiAddress(apiUrl ?? process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api");
23283
+ return browserAuthenticate({ authUrl: process.env.BACKIFY_AUTH_URL ?? process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://auth.backify.dev", apiUrl: address, applicationId: appId ? applicationGuid(appId) : void 0, organizationId, openBrowser: (url) => {
23284
+ console.error("Sign in to reconnect Backify CLI access. Choose the expiration in your browser; the CLI will continue automatically.\n" + url);
23285
+ openBrowser(url);
23286
+ }, save: (session) => {
23287
+ if (appId) {
23288
+ userConfig.sessions ??= {};
23289
+ userConfig.sessions[sessionKey(appId)] = session;
23290
+ } else {
23291
+ userConfig.token = session.token;
23292
+ userConfig.organizationId = session.organizationId;
23001
23293
  }
23002
- let raw = "";
23003
- req.setEncoding("utf8");
23004
- req.on("data", (part) => raw += part);
23005
- req.on("end", () => {
23006
- try {
23007
- const value = JSON.parse(raw);
23008
- if (value.state !== state || typeof value.token !== "string" || typeof value.organizationId !== "string") throw new Error("Invalid callback");
23009
- res.writeHead(200, { "access-control-allow-origin": "*", "content-type": "text/plain" });
23010
- res.end("Backify CLI connected. You can close this tab.");
23011
- userConfig.token = value.token;
23012
- userConfig.organizationId = value.organizationId;
23013
- saveUserConfig(configPath, userConfig);
23014
- server.close();
23015
- resolveLogin();
23016
- } catch {
23017
- res.writeHead(400, { "access-control-allow-origin": "*" });
23018
- res.end("Invalid or expired Backify CLI login.");
23019
- }
23020
- });
23021
- });
23022
- let resolveLogin = () => {
23023
- };
23024
- const completed = new Promise((resolve24) => resolveLogin = resolve24);
23025
- server.listen(0, "127.0.0.1", () => {
23026
- const address = server.address();
23027
- if (!address || typeof address === "string") throw new Error("Could not start local login callback");
23028
- const callback = `http://127.0.0.1:${address.port}/callback`;
23029
- const portal = new URL(process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://backify.dev");
23030
- portal.searchParams.set("cli_callback", callback);
23031
- portal.searchParams.set("cli_state", state);
23032
- console.log(`Opening ${portal.origin} to sign in to Backify\u2026`);
23033
- openBrowser(portal.toString());
23034
- });
23035
- const timeout = setTimeout(() => {
23036
- server.close();
23037
- rejectLogin(new Error("Backify login timed out. Run backify login to try again."));
23038
- }, 5 * 6e4);
23039
- let rejectLogin = () => {
23040
- };
23041
- const failed = new Promise((_, reject) => rejectLogin = reject);
23042
- try {
23043
- await Promise.race([completed, failed]);
23044
- } finally {
23045
- clearTimeout(timeout);
23046
- }
23294
+ saveUserConfig(configPath, userConfig);
23295
+ } });
23296
+ }
23297
+ function applicationClient(session, appId) {
23298
+ return reconnectingApiClient(session, () => browserLogin(appId, session.organizationId, session.apiUrl));
23047
23299
  }
23048
23300
  async function main() {
23049
23301
  switch (command2) {
@@ -23191,11 +23443,11 @@ async function main() {
23191
23443
  break;
23192
23444
  }
23193
23445
  case "login":
23194
- if (args[0]) {
23446
+ if (args[0] && !args[0].startsWith("--")) {
23195
23447
  userConfig.token = args[0];
23196
23448
  userConfig.organizationId = args[1] ?? userConfig.organizationId;
23197
23449
  saveUserConfig(configPath, userConfig);
23198
- } else await browserLogin();
23450
+ } else await browserLogin(option("--app") ?? (existsSync15(join17(process.cwd(), "backify.json")) ? projectApplication() : void 0), option("--organization"));
23199
23451
  console.log("Logged in to Backify");
23200
23452
  break;
23201
23453
  case "logout":
@@ -23204,7 +23456,7 @@ async function main() {
23204
23456
  break;
23205
23457
  case "connect": {
23206
23458
  const appId = option("--app") ?? projectApplication(), key = "app_" + applicationGuid(appId).replaceAll("-", "");
23207
- const code = option("--pair"), organizationId = option("--organization") ?? userConfig.organizationId;
23459
+ const code = option("--pair"), organizationId = option("--organization") ?? userConfig.sessions?.[key]?.organizationId ?? userConfig.organizationId;
23208
23460
  const address = apiAddress(option("--api") ?? process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api");
23209
23461
  if (code) {
23210
23462
  if (!organizationId) throw new Error("The setup prompt must supply the organization.");
@@ -23213,20 +23465,25 @@ async function main() {
23213
23465
  userConfig.sessions ??= {};
23214
23466
  userConfig.sessions[key] = { ...paired, apiUrl: address };
23215
23467
  saveUserConfig(configPath, userConfig);
23216
- } else if (!userConfig.sessions?.[key] && !userConfig.token) {
23217
- await browserLogin();
23468
+ } else if (args.includes("--reauth") || !userConfig.sessions?.[key] && !userConfig.token) {
23469
+ await browserLogin(appId, organizationId, address);
23218
23470
  }
23219
23471
  let session = userConfig.sessions?.[key] ?? { token: userConfig.token ?? "", organizationId: organizationId ?? userConfig.organizationId ?? "", apiUrl: address };
23220
23472
  if (!session.token.startsWith("bfcli_")) {
23221
- session = await registerCliSession(apiClient(session), applicationGuid(appId), session.apiUrl);
23473
+ try {
23474
+ session = await registerCliSession(apiClient(session), applicationGuid(appId), session.apiUrl);
23475
+ } catch (error) {
23476
+ if (!(error instanceof CliAuthenticationRequired)) throw error;
23477
+ session = await browserLogin(appId, session.organizationId, session.apiUrl);
23478
+ }
23222
23479
  userConfig.sessions ??= {};
23223
23480
  userConfig.sessions[key] = session;
23224
23481
  saveUserConfig(configPath, userConfig);
23225
23482
  }
23226
- const client = apiClient(session);
23483
+ const client = applicationClient(session, appId);
23227
23484
  await client("/apps/" + applicationGuid(appId) + "/developer");
23228
23485
  userConfig.sessions ??= {};
23229
- userConfig.sessions[key] = session;
23486
+ userConfig.sessions[key] ??= session;
23230
23487
  saveUserConfig(configPath, userConfig);
23231
23488
  if (args.includes("--clone")) await cloneProject(client, appId);
23232
23489
  else if (existsSync15(join17(process.cwd(), ".git"))) {
@@ -23260,7 +23517,7 @@ async function main() {
23260
23517
  const latest = deployments[0];
23261
23518
  if (latest?.status === "failed") console.log(JSON.stringify(await call(prefix + "/deployments/" + latest.id), null, 2));
23262
23519
  try {
23263
- const commit = git2(["rev-parse", "HEAD"]);
23520
+ const commit = git(["rev-parse", "HEAD"]);
23264
23521
  console.log(JSON.stringify(await call(prefix + "/developer/build?commitSha=" + commit), null, 2));
23265
23522
  } catch {
23266
23523
  console.log("Build status is unavailable for the current local commit.");
@@ -23340,7 +23597,7 @@ async function main() {
23340
23597
  }
23341
23598
  main().catch((error) => {
23342
23599
  const message = error instanceof Error ? error.message : String(error);
23343
- if (args.includes("--json")) console.log(JSON.stringify({ schemaVersion: 1, status: "failed", exitCode: 1, error: message }));
23600
+ if (args.includes("--json")) console.log(JSON.stringify({ schemaVersion: 1, status: "failed", exitCode: 1, error: message, ...error instanceof CliAuthenticationRequired ? { code: error.code, reauthenticationCommand: "backify connect --reauth" + (option("--app") ? " --app " + applicationGuid(option("--app")) : "") } : {} }));
23344
23601
  else console.error(message);
23345
23602
  process.exitCode = 1;
23346
23603
  });