@backifydev/cli 0.3.7 → 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 +5 -1
  2. package/dist/index.js +1537 -1209
  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
  }
@@ -17660,154 +17660,85 @@ async function packageFunctions(entry, output) {
17660
17660
  return outputPath;
17661
17661
  }
17662
17662
 
17663
- // src/local-runtime.ts
17664
- import { createHash, randomUUID } from "node:crypto";
17665
- import { chmodSync, existsSync as existsSync2, lstatSync, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
17666
- import { dirname as dirname2, join, resolve as resolve2, sep } from "node:path";
17667
- import { homedir } from "node:os";
17668
- import { spawnSync } from "node:child_process";
17663
+ // src/cli-session.ts
17664
+ import { hostname, platform, release, arch } from "node:os";
17669
17665
 
17670
- // src/local-state.ts
17671
- import { renameSync } from "node:fs";
17672
- function replaceLocalFile(source, destination, rename, platform, wait) {
17673
- for (let attempt = 0; ; attempt++) {
17674
- try {
17675
- rename(source, destination);
17676
- return;
17677
- } catch (error) {
17678
- if (platform !== "win32" || attempt >= 4 || !(error instanceof Error) || !("code" in error) || !["EPERM", "EACCES", "EBUSY"].includes(String(error.code))) throw error;
17679
- wait(10 * 2 ** attempt);
17680
- }
17666
+ // package.json
17667
+ var package_default = {
17668
+ name: "@backifydev/cli",
17669
+ version: "0.3.9",
17670
+ description: "Command-line setup and diagnostics for Backify applications",
17671
+ type: "module",
17672
+ bin: {
17673
+ backify: "dist/index.js"
17674
+ },
17675
+ files: [
17676
+ "dist/index.js",
17677
+ "README.md"
17678
+ ],
17679
+ scripts: {
17680
+ prebuild: "npm run build -w @backify/platform && npm run build -w @backifydev/sdk && npm run build -w @backify/analyzer",
17681
+ build: `esbuild src/index.ts --bundle --platform=node --format=esm --external:esbuild --external:typescript --outfile=dist/index.js --banner:js="import { createRequire as __backifyCreateRequire } from 'node:module'; var require = __backifyCreateRequire(import.meta.url);"`,
17682
+ prepack: "npm run build",
17683
+ pretypecheck: "npm run build -w @backify/platform && npm run build -w @backifydev/sdk && npm run build -w @backify/analyzer",
17684
+ typecheck: "tsc --noEmit",
17685
+ pretest: "npm run build -w @backify/platform && npm run build -w @backifydev/sdk && npm run build -w @backify/analyzer",
17686
+ test: "vitest run"
17687
+ },
17688
+ publishConfig: {
17689
+ access: "public"
17690
+ },
17691
+ repository: {
17692
+ type: "git",
17693
+ url: "git+https://github.com/frejnorling/backify.git",
17694
+ directory: "packages/cli"
17695
+ },
17696
+ homepage: "https://agent.backify.dev/",
17697
+ license: "MIT",
17698
+ engines: {
17699
+ node: ">=20"
17700
+ },
17701
+ dependencies: {
17702
+ "@azure/functions": "^4.7.2",
17703
+ "@backifydev/sdk": "^0.1.1",
17704
+ esbuild: "^0.25.10",
17705
+ fflate: "^0.8.2",
17706
+ typescript: "^5.9.2"
17707
+ },
17708
+ devDependencies: {
17709
+ "@backify/analyzer": "*",
17710
+ "@backify/platform": "*",
17711
+ "@hono/node-server": "2.1.3",
17712
+ "@types/node": "^24.3.1",
17713
+ hono: "4.13.12",
17714
+ vitest: "^4.1.11"
17681
17715
  }
17682
- }
17683
- function renameLocalFile(source, destination) {
17684
- replaceLocalFile(source, destination, renameSync, process.platform, (milliseconds) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds));
17685
- }
17716
+ };
17686
17717
 
17687
- // src/local-runtime.ts
17688
- var maxArchiveBytes = 256 * 1024 * 1024;
17689
- var maxExpandedBytes = 512 * 1024 * 1024;
17690
- var hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
17691
- function runtimeIdentifier(platform = process.platform, architecture = process.arch) {
17692
- const system = { win32: "win", darwin: "osx", linux: "linux" }[platform];
17693
- if (!system || !["x64", "arm64"].includes(architecture)) throw new Error("Local runtime supports Windows, macOS and Linux on x64/arm64");
17694
- return system + "-" + architecture;
17695
- }
17696
- function checkedPath(root, filename2) {
17697
- const target = resolve2(root, filename2);
17698
- if (target !== resolve2(root) && !target.startsWith(resolve2(root) + sep)) throw new Error("Runtime path escapes its installation");
17699
- let current = target;
17700
- while (true) {
17701
- if (existsSync2(current) && lstatSync(current).isSymbolicLink()) throw new Error("Runtime installation must not follow links");
17702
- if (dirname2(current) === current) break;
17703
- current = dirname2(current);
17704
- }
17705
- return target;
17706
- }
17707
- function verifyInstallationFiles(root, expected, directory2 = root) {
17708
- for (const entry of readdirSync(directory2, { withFileTypes: true })) {
17709
- const path = checkedPath(root, join(directory2, entry.name));
17710
- if (entry.isDirectory()) verifyInstallationFiles(root, expected, path);
17711
- 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");
17712
- }
17713
- }
17714
- function installedLocalRuntime(base = join(homedir(), ".backify")) {
17715
- const pointer = checkedPath(base, "runtime.json");
17716
- if (!existsSync2(pointer)) return void 0;
17717
- const installed = JSON.parse(readFileSync(pointer, "utf8"));
17718
- 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");
17719
- const root = checkedPath(base, join("runtimes", installed.version, installed.rid));
17720
- verifyInstallationFiles(root, installed.files);
17721
- for (const [filename2, digest4] of Object.entries(installed.files)) {
17722
- const path = checkedPath(root, filename2);
17723
- 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");
17724
- }
17725
- const executableName = installed.rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
17726
- if (!installed.files["Backify.Hcr.dll"] || !installed.files[executableName]) throw new Error("Runtime archive requires the self-contained HCR executable and DLL");
17727
- return { path: join(root, "Backify.Hcr.dll"), executable: join(root, executableName), version: installed.version, rid: installed.rid };
17728
- }
17729
- function installLocalRuntime(archive, options) {
17730
- 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");
17731
- if (archive.byteLength > maxArchiveBytes || hash(archive) !== options.sha256) throw new Error("Runtime archive digest does not match the reviewed SHA-256");
17732
- let expanded = 0, count = 0;
17733
- const files = unzipSync(archive, { filter: (file) => {
17734
- if (++count > 3e3 || (expanded += file.originalSize) > maxExpandedBytes || file.originalSize > maxArchiveBytes) throw new Error("Runtime archive exceeds extraction limits");
17735
- 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");
17736
- return !file.name.endsWith("/");
17737
- } });
17738
- const rid = runtimeIdentifier();
17739
- const executable = rid.startsWith("win-") ? "Backify.Hcr.exe" : "Backify.Hcr";
17740
- 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");
17741
- const base = options.base ?? join(homedir(), ".backify");
17742
- const parent = checkedPath(base, join("runtimes", options.version));
17743
- mkdirSync2(parent, { recursive: true, mode: 448 });
17744
- const destination = checkedPath(parent, rid);
17745
- const receipt = { version: options.version, rid, files: Object.fromEntries(Object.entries(files).map(([name2, bytes]) => [name2, hash(bytes)])) };
17746
- if (!existsSync2(destination)) {
17747
- const staging = checkedPath(parent, ".install-" + randomUUID());
17748
- mkdirSync2(staging, { mode: 448 });
17749
- for (const [name2, bytes] of Object.entries(files)) {
17750
- const filename2 = checkedPath(staging, name2);
17751
- mkdirSync2(dirname2(filename2), { recursive: true, mode: 448 });
17752
- writeFileSync2(filename2, bytes, { flag: "wx", mode: name2 === executable ? 448 : 384 });
17753
- }
17754
- if (process.platform !== "win32") chmodSync(join(staging, executable), 448);
17755
- renameLocalFile(staging, destination);
17756
- } else {
17757
- verifyInstallationFiles(destination, receipt.files);
17758
- 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");
17759
- }
17760
- const pointer = checkedPath(base, "runtime.json");
17761
- const temporary = checkedPath(base, ".runtime-" + randomUUID() + ".json");
17762
- writeFileSync2(temporary, JSON.stringify(receipt), { flag: "wx", mode: 384 });
17763
- renameLocalFile(temporary, pointer);
17764
- return installedLocalRuntime(base);
17765
- }
17766
- async function downloadLocalRuntime(version) {
17767
- if (!/^v[0-9]+\.[0-9]+\.[0-9]+$/.test(version)) throw new Error("Provide a release version vX.Y.Z");
17768
- let url = new URL("https://github.com/frejnorling/backify/releases/download/runtime-" + version + "/backify-hcr-" + runtimeIdentifier() + ".zip");
17769
- for (let redirects = 0; redirects < 5; redirects++) {
17770
- 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");
17771
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(6e4) });
17772
- if ([301, 302, 303, 307, 308].includes(response.status)) {
17773
- url = new URL(response.headers.get("location") ?? "", url);
17774
- continue;
17775
- }
17776
- if (!response.ok || !response.body) throw new Error("The requested runtime release is unavailable");
17777
- const chunks = [];
17778
- let size = 0;
17779
- for await (const chunk of response.body) {
17780
- size += chunk.byteLength;
17781
- if (size > maxArchiveBytes) throw new Error("Runtime archive exceeds its size limit");
17782
- chunks.push(chunk);
17783
- }
17784
- return Buffer.concat(chunks);
17785
- }
17786
- throw new Error("Too many runtime download redirects");
17718
+ // src/cli-session.ts
17719
+ function cliDevice() {
17720
+ return { deviceName: hostname(), operatingSystem: platform() + " " + release(), architecture: arch(), cliVersion: package_default.version };
17787
17721
  }
17788
- function localPrerequisites() {
17789
- const runtime = installedLocalRuntime();
17790
- const directory2 = process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
17791
- const checks = ["initdb", "pg_ctl", "psql"].map((name2) => {
17792
- const executable = directory2 ? join(directory2, name2 + (process.platform === "win32" ? ".exe" : "")) : name2;
17793
- const result = spawnSync(executable, ["--version"], { windowsHide: true, timeout: 1e4, encoding: "utf8" });
17794
- return { name: name2, available: result.status === 0 };
17795
- });
17796
- return { rid: runtimeIdentifier(), runtime: runtime ? { installed: true, version: runtime.version } : { installed: false }, postgres: checks, ready: !!runtime && checks.every((check) => check.available) };
17722
+ var cliUserAgent = "BackifyCLI/" + package_default.version;
17723
+ async function registerCliSession(client, applicationId, apiUrl) {
17724
+ const session = await client("/apps/" + applicationId + "/developer/session", { method: "POST", body: JSON.stringify(cliDevice()) });
17725
+ return { ...session, apiUrl };
17797
17726
  }
17798
17727
 
17799
- // src/workload.ts
17800
- import { build as build2 } from "esbuild";
17801
- import { createHash as createHash13 } from "node:crypto";
17802
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync16, realpathSync as realpathSync6, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
17803
- import { dirname as dirname10, isAbsolute as isAbsolute13, join as join10, relative as relative8, resolve as resolve16 } from "node:path";
17728
+ // src/browser-auth.ts
17729
+ import { randomBytes } from "node:crypto";
17730
+ import { createServer } from "node:http";
17804
17731
 
17805
- // src/local-development.ts
17806
- import { context as context2 } from "esbuild";
17807
- import { spawn as spawn4 } from "node:child_process";
17808
- import { randomBytes as randomBytes5, randomUUID as randomUUID8, createHash as createHash12 } from "node:crypto";
17809
- 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";
17810
- import { resolve as resolve15, join as join9, relative as relative7, isAbsolute as isAbsolute12, dirname as dirname9 } 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";
17737
+
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";
17811
17742
 
17812
17743
  // ../platform/dist/manifest.js
17813
17744
  var import_yaml = __toESM(require_dist(), 1);
@@ -17840,6 +17771,8 @@ function parseApplicationManifest(source) {
17840
17771
  if (!object(settings))
17841
17772
  throw new Error("Capability configuration must be a mapping: " + name2);
17842
17773
  validatePortableSettings(settings);
17774
+ if (name2 === "auth")
17775
+ validateAuthSettings(settings);
17843
17776
  if (settings.enabled !== void 0 && typeof settings.enabled !== "boolean")
17844
17777
  throw new Error("enabled must be boolean: " + name2);
17845
17778
  if (name2 === "database" && settings.engine !== void 0 && settings.engine !== "postgres")
@@ -17847,7 +17780,7 @@ function parseApplicationManifest(source) {
17847
17780
  if (name2 === "database" && settings.migrations !== void 0 && !portablePath(settings.migrations))
17848
17781
  throw new Error("Migrations must be repository-relative");
17849
17782
  if (name2 === "storage" && settings.buckets !== void 0) {
17850
- if (!Array.isArray(settings.buckets) || settings.buckets.length > 50 || settings.buckets.some((bucket) => !object(bucket) || typeof bucket.name !== "string" || !/^[A-Za-z0-9_-]{1,100}$/.test(bucket.name) || bucket.access !== void 0 && bucket.access !== "private" || Object.keys(bucket).some((key) => !["name", "access"].includes(key))) || new Set(settings.buckets.map((bucket) => bucket.name)).size !== settings.buckets.length)
17783
+ if (!Array.isArray(settings.buckets) || settings.buckets.length > 50 || settings.buckets.some((bucket) => !object(bucket) || typeof bucket.name !== "string" || !/^[A-Za-z0-9_-]{1,100}$/.test(bucket.name) || bucket.access !== void 0 && bucket.access !== "private" || bucket.isolation !== void 0 && !["private", "user", "tenant", "authenticated", "public", "selected"].includes(String(bucket.isolation)) || bucket.allowSignedUrls !== void 0 && typeof bucket.allowSignedUrls !== "boolean" || Object.keys(bucket).some((key) => !["name", "access", "isolation", "allowSignedUrls"].includes(key))) || new Set(settings.buckets.map((bucket) => bucket.name)).size !== settings.buckets.length)
17851
17784
  throw new Error("Storage requires at most 50 distinct private logical buckets");
17852
17785
  }
17853
17786
  capabilities[name2] = structuredClone(settings);
@@ -17869,8 +17802,32 @@ function validatePortableSettings(settings) {
17869
17802
  }
17870
17803
  }
17871
17804
  }
17872
- function manifestDiagnostics(manifest, used) {
17873
- 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;
17874
17831
  }
17875
17832
 
17876
17833
  // ../platform/dist/delivery.js
@@ -17903,6 +17860,10 @@ Run the following to discover the connected hosting target, repository, branch,
17903
17860
 
17904
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.
17905
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
+
17906
17867
  ## Record each user request
17907
17868
 
17908
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:
@@ -17984,7 +17945,7 @@ var MAIL_LIMITS = Object.freeze({
17984
17945
  });
17985
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 });
17986
17947
  var CAPABILITIES = [
17987
- capability("auth", "auth", "Application users, organizations, roles, and permissions"),
17948
+ capability("auth", "auth", "Application users, organizations, roles, and permissions", true),
17988
17949
  capability("database", "db", "Persistent application data", true),
17989
17950
  capability("storage", "storage", "Persistent files and objects", true),
17990
17951
  capability("functions", "functions", "Server function declarations"),
@@ -18098,114 +18059,932 @@ function validateConfig(value) {
18098
18059
  }
18099
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 } } } } };
18100
18061
 
18101
- // ../analyzer/dist/index.js
18102
- import ts from "typescript";
18103
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "node:fs";
18104
- import { join as join2, extname } from "node:path";
18105
- 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" } };
18106
- function analyzeSources(files, options = {}) {
18107
- const used = /* @__PURE__ */ new Set(), diagnostics = [];
18108
- for (const file of files)
18109
- analyzeFile(file, used, diagnostics);
18110
- if (used.has("http"))
18111
- used.add("events");
18112
- 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
+ }
18113
18075
  }
18114
- function analyzeFile(file, used, diagnostics) {
18115
- 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();
18116
- source.forEachChild((node) => {
18117
- if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier))
18118
- return;
18119
- const module = node.moduleSpecifier.text;
18120
- if (bypass[module]) {
18121
- const rule = bypass[module];
18122
- 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;
18123
18109
  }
18124
- if (module !== "@backifydev/sdk" && module !== "@backifydev/sdk/server" || !node.importClause)
18125
- return;
18126
- const bindings = node.importClause.namedBindings;
18127
- if (!bindings)
18128
- return;
18129
- if (ts.isNamespaceImport(bindings))
18130
- namespaces.add(bindings.name.text);
18131
- else if (ts.isNamedImports(bindings))
18132
- for (const element of bindings.elements) {
18133
- const imported = (element.propertyName ?? element.name).text, local = element.name.text;
18134
- if (imported === "createServerClient")
18135
- factories.add(local);
18136
- else if (imported === "backify")
18137
- roots.add(local);
18138
- else {
18139
- const cap = SDK_IMPORT_CAPABILITIES[imported];
18140
- if (cap)
18141
- named.set(local, cap);
18142
- }
18143
- }
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") : "") };
18144
18128
  });
18145
- const visit = (node) => {
18146
- 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))
18147
- roots.add(node.name.text);
18148
- 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")))
18149
- used.add("scheduler");
18150
- if (ts.isIdentifier(node)) {
18151
- const parent = node.parent;
18152
- if (named.has(node.text) && !ts.isImportSpecifier(parent) && isValueUse(node))
18153
- used.add(named.get(node.text));
18154
- }
18155
- if (ts.isPropertyAccessExpression(node)) {
18156
- if (ts.isIdentifier(node.expression)) {
18157
- if (namespaces.has(node.expression.text)) {
18158
- const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18159
- if (cap)
18160
- used.add(cap);
18161
- }
18162
- if (roots.has(node.expression.text)) {
18163
- const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18164
- if (cap)
18165
- used.add(cap);
18166
- }
18167
- }
18168
- if (node.name.text === "schedule" && rootCapability(node.expression, named, namespaces, roots) === "jobs")
18169
- used.add("scheduler");
18170
- }
18171
- ts.forEachChild(node, visit);
18172
- };
18173
- visit(source);
18129
+ for (const { path, content } of updates) {
18130
+ mkdirSync2(dirname3(path), { recursive: true });
18131
+ writeFileSync3(path, content);
18132
+ }
18174
18133
  }
18175
- function isValueUse(node) {
18176
- const p = node.parent;
18177
- 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");
18178
18148
  }
18179
- function rootCapability(node, named, namespaces, roots) {
18180
- if (ts.isIdentifier(node))
18181
- return named.get(node.text);
18182
- if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
18183
- if (namespaces.has(node.expression.text) || roots.has(node.expression.text))
18184
- 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;
18185
18155
  }
18186
- return void 0;
18187
18156
  }
18188
- function repositorySources(root) {
18189
- const result = [];
18190
- const walk = (dir) => {
18191
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
18192
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".backify" || entry.name.startsWith(".git"))
18193
- continue;
18194
- const path = join2(dir, entry.name);
18195
- if (entry.isDirectory())
18196
- walk(path);
18197
- else if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"].includes(extname(path)))
18198
- result.push({ path, content: readFileSync2(path, "utf8") });
18199
- }
18200
- };
18201
- if (existsSync3(root))
18202
- walk(root);
18203
- 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}`);
18204
18161
  }
18205
-
18206
- // src/local-inspection.ts
18207
- function createLocalDataInspector(runtime, options) {
18208
- const tableName = (value) => typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value) && !/^__backify/i.test(value);
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) {
18987
+ const tableName = (value) => typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value) && !/^__backify/i.test(value);
18209
18988
  const bucketName = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(value);
18210
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");
18211
18990
  const fail = () => {
@@ -18407,9 +19186,9 @@ function createLocalNativeEvents(local, native) {
18407
19186
 
18408
19187
  // src/local-job-executor.ts
18409
19188
  import { spawn as spawn2 } from "node:child_process";
18410
- import { createHash as createHash9 } from "node:crypto";
18411
- import { lstatSync as lstatSync9, readFileSync as readFileSync9 } from "node:fs";
18412
- 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";
18413
19192
 
18414
19193
  // ../sdk/dist/schedules.js
18415
19194
  var import_cron_parser = __toESM(require_dist2(), 1);
@@ -18620,28 +19399,28 @@ function createApplicationAuthProvider(source, options) {
18620
19399
  }
18621
19400
 
18622
19401
  // ../sdk/dist/application-auth-state.js
18623
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
18624
- import { closeSync, constants, fstatSync, fsyncSync, lstatSync as lstatSync2, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
18625
- 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";
18626
19405
  function unavailable2() {
18627
19406
  throw new ApplicationAuthenticationError("application_identity_unavailable");
18628
19407
  }
18629
19408
  function safePath(filename2) {
18630
- if (!isAbsolute(filename2) || resolve3(filename2) !== filename2 || filename2 === parse(filename2).root)
19409
+ if (!isAbsolute2(filename2) || resolve5(filename2) !== filename2 || filename2 === parse(filename2).root)
18631
19410
  unavailable2();
18632
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)))
18633
19412
  unavailable2();
18634
- const directory2 = dirname3(filename2);
19413
+ const directory2 = dirname6(filename2);
18635
19414
  let ancestor = directory2;
18636
19415
  for (; ; ) {
18637
- const metadata = lstatSync2(ancestor);
19416
+ const metadata = lstatSync4(ancestor);
18638
19417
  if (!metadata.isDirectory() || metadata.isSymbolicLink())
18639
19418
  unavailable2();
18640
19419
  if (ancestor === parse(ancestor).root)
18641
19420
  break;
18642
- ancestor = dirname3(ancestor);
19421
+ ancestor = dirname6(ancestor);
18643
19422
  }
18644
- const parent = lstatSync2(directory2);
19423
+ const parent = lstatSync4(directory2);
18645
19424
  if (process.platform !== "win32" && ((parent.mode & 63) !== 0 || parent.uid !== process.getuid?.()))
18646
19425
  unavailable2();
18647
19426
  }
@@ -18652,7 +19431,7 @@ function safeFile(metadata) {
18652
19431
  function syncDirectory(filename2) {
18653
19432
  if (process.platform === "win32")
18654
19433
  return;
18655
- const descriptor2 = openSync(dirname3(filename2), constants.O_RDONLY | constants.O_DIRECTORY);
19434
+ const descriptor2 = openSync(dirname6(filename2), constants.O_RDONLY | constants.O_DIRECTORY);
18656
19435
  try {
18657
19436
  fsyncSync(descriptor2);
18658
19437
  } finally {
@@ -18660,7 +19439,7 @@ function syncDirectory(filename2) {
18660
19439
  }
18661
19440
  }
18662
19441
  function readState(filename2) {
18663
- const metadata = lstatSync2(filename2);
19442
+ const metadata = lstatSync4(filename2);
18664
19443
  safeFile(metadata);
18665
19444
  if (metadata.size > 2048)
18666
19445
  unavailable2();
@@ -18693,30 +19472,30 @@ function readState(filename2) {
18693
19472
  }
18694
19473
  }
18695
19474
  function persist(filename2, state) {
18696
- const temporary = filename2 + "." + randomUUID2() + ".tmp";
19475
+ const temporary = filename2 + "." + randomUUID3() + ".tmp";
18697
19476
  const descriptor2 = openSync(temporary, "wx", 384);
18698
19477
  try {
18699
- writeFileSync3(descriptor2, JSON.stringify(state));
19478
+ writeFileSync6(descriptor2, JSON.stringify(state));
18700
19479
  fsyncSync(descriptor2);
18701
19480
  } finally {
18702
19481
  closeSync(descriptor2);
18703
19482
  }
18704
- renameSync2(temporary, filename2);
19483
+ renameSync3(temporary, filename2);
18705
19484
  syncDirectory(filename2);
18706
19485
  }
18707
19486
  function checkpoint(filename2, trust, initialize2) {
18708
19487
  try {
18709
- if (!isAbsolute(filename2))
19488
+ if (!isAbsolute2(filename2))
18710
19489
  unavailable2();
18711
- filename2 = resolve3(filename2);
19490
+ filename2 = resolve5(filename2);
18712
19491
  safePath(filename2);
18713
19492
  const lock = filename2 + ".lock";
18714
- 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") };
18715
19494
  if (!initialize2) {
18716
19495
  const previous = readState(filename2);
18717
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) {
18718
19497
  try {
18719
- lstatSync2(lock);
19498
+ lstatSync4(lock);
18720
19499
  } catch (error) {
18721
19500
  if (error.code === "ENOENT")
18722
19501
  return;
@@ -18727,7 +19506,7 @@ function checkpoint(filename2, trust, initialize2) {
18727
19506
  }
18728
19507
  const descriptor2 = openSync(lock, "wx", 384);
18729
19508
  try {
18730
- writeFileSync3(descriptor2, "backify-application-auth-state-v1");
19509
+ writeFileSync6(descriptor2, "backify-application-auth-state-v1");
18731
19510
  fsyncSync(descriptor2);
18732
19511
  } finally {
18733
19512
  closeSync(descriptor2);
@@ -18737,7 +19516,7 @@ function checkpoint(filename2, trust, initialize2) {
18737
19516
  try {
18738
19517
  if (initialize2) {
18739
19518
  try {
18740
- lstatSync2(filename2);
19519
+ lstatSync4(filename2);
18741
19520
  unavailable2();
18742
19521
  } catch (error) {
18743
19522
  if (error.code !== "ENOENT")
@@ -18774,18 +19553,18 @@ function createPersistentApplicationAuthProvider(source, options) {
18774
19553
  }
18775
19554
 
18776
19555
  // ../sdk/dist/application-auth-sync.js
18777
- import { createHash as createHash3, createPublicKey as createPublicKey2, randomUUID as randomUUID3, verify as verify2 } from "node:crypto";
18778
- 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";
18779
- 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";
18780
19559
  var maximumBytes = 512 * 1024;
18781
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";
18782
19561
  function unavailable3() {
18783
19562
  throw new ApplicationAuthenticationError("application_identity_unavailable");
18784
19563
  }
18785
19564
  function filename(value) {
18786
- if (typeof value !== "string" || /[\x00-\x1f\x7f]/.test(value) || !isAbsolute2(value))
19565
+ if (typeof value !== "string" || /[\x00-\x1f\x7f]/.test(value) || !isAbsolute3(value))
18787
19566
  unavailable3();
18788
- const result = resolve4(value);
19567
+ const result = resolve6(value);
18789
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)))
18790
19569
  unavailable3();
18791
19570
  return result;
@@ -18814,28 +19593,28 @@ function prepare(options) {
18814
19593
  function planApplicationAuthSynchronization(options) {
18815
19594
  try {
18816
19595
  const prepared = prepare(options);
18817
- 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 };
18818
19597
  } catch {
18819
19598
  return unavailable3();
18820
19599
  }
18821
19600
  }
18822
19601
  function directory(filename2, privateState) {
18823
- const parent = dirname4(filename2);
18824
- for (let ancestor = parent; ; ancestor = dirname4(ancestor)) {
18825
- const metadata2 = lstatSync3(ancestor);
19602
+ const parent = dirname7(filename2);
19603
+ for (let ancestor = parent; ; ancestor = dirname7(ancestor)) {
19604
+ const metadata2 = lstatSync5(ancestor);
18826
19605
  if (!metadata2.isDirectory() || metadata2.isSymbolicLink())
18827
19606
  unavailable3();
18828
19607
  if (ancestor === parse2(ancestor).root)
18829
19608
  break;
18830
19609
  }
18831
- const metadata = lstatSync3(parent);
19610
+ const metadata = lstatSync5(parent);
18832
19611
  if (process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & (privateState ? 63 : 18)) !== 0))
18833
19612
  unavailable3();
18834
19613
  }
18835
19614
  function syncDirectory2(filename2) {
18836
19615
  if (process.platform === "win32")
18837
19616
  return;
18838
- const descriptor2 = openSync2(dirname4(filename2), constants2.O_RDONLY | constants2.O_DIRECTORY);
19617
+ const descriptor2 = openSync2(dirname7(filename2), constants2.O_RDONLY | constants2.O_DIRECTORY);
18839
19618
  try {
18840
19619
  fsyncSync2(descriptor2);
18841
19620
  } finally {
@@ -18844,10 +19623,10 @@ function syncDirectory2(filename2) {
18844
19623
  }
18845
19624
  function currentPublicFile(filename2) {
18846
19625
  try {
18847
- const metadata = lstatSync3(filename2);
19626
+ const metadata = lstatSync5(filename2);
18848
19627
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 131072 || process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & 18) !== 0))
18849
19628
  unavailable3();
18850
- const bytes = readFileSync3(filename2);
19629
+ const bytes = readFileSync6(filename2);
18851
19630
  if (bytes.length > 131072)
18852
19631
  unavailable3();
18853
19632
  return new TextDecoder("utf8", { fatal: true }).decode(bytes);
@@ -18914,7 +19693,7 @@ async function synchronizeApplicationAuthTrustWithDigest(options) {
18914
19693
  const lock = prepared.stateFile + ".delivery.lock";
18915
19694
  const descriptor2 = openSync2(lock, "wx", 384);
18916
19695
  try {
18917
- writeFileSync4(descriptor2, "backify-application-auth-delivery-v1");
19696
+ writeFileSync7(descriptor2, "backify-application-auth-delivery-v1");
18918
19697
  fsyncSync2(descriptor2);
18919
19698
  } finally {
18920
19699
  closeSync2(descriptor2);
@@ -18925,19 +19704,19 @@ async function synchronizeApplicationAuthTrustWithDigest(options) {
18925
19704
  const current = currentPublicFile(prepared.trustFile);
18926
19705
  createPersistentApplicationAuthProvider(trust, { expectedScope: options.expectedScope, stateFile: prepared.stateFile, now: options.now });
18927
19706
  const content = JSON.stringify(trust);
18928
- const trustDigest = createHash3("sha256").update(content).digest("hex");
19707
+ const trustDigest = createHash4("sha256").update(content).digest("hex");
18929
19708
  if (current === content)
18930
19709
  return { status: "unchanged", revision: trust.revision, validUntil: trust.validUntil, trustDigest };
18931
19710
  writePending = true;
18932
- const temporary = prepared.trustFile + "." + randomUUID3() + ".tmp";
19711
+ const temporary = prepared.trustFile + "." + randomUUID4() + ".tmp";
18933
19712
  const output = openSync2(temporary, "wx", process.platform === "win32" ? 384 : 292);
18934
19713
  try {
18935
- writeFileSync4(output, content);
19714
+ writeFileSync7(output, content);
18936
19715
  fsyncSync2(output);
18937
19716
  } finally {
18938
19717
  closeSync2(output);
18939
19718
  }
18940
- renameSync3(temporary, prepared.trustFile);
19719
+ renameSync4(temporary, prepared.trustFile);
18941
19720
  syncDirectory2(prepared.trustFile);
18942
19721
  writePending = false;
18943
19722
  return { status: "updated", revision: trust.revision, validUntil: trust.validUntil, trustDigest };
@@ -19514,16 +20293,16 @@ function jsonValue(value) {
19514
20293
 
19515
20294
  // src/local-services.ts
19516
20295
  import { spawn, execFile } from "node:child_process";
19517
- import { createHash as createHash5, generateKeyPairSync, randomBytes as randomBytes2, randomUUID as randomUUID5, sign } from "node:crypto";
19518
- import { createServer } from "node:net";
19519
- 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";
19520
- 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";
19521
20300
 
19522
20301
  // src/local-job-services.ts
19523
- import { createHash as createHash4, randomBytes, randomUUID as randomUUID4 } from "node:crypto";
19524
- 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";
19525
20304
  import { isIP } from "node:net";
19526
- import { dirname as dirname5, join as join3 } from "node:path";
20305
+ import { dirname as dirname8, join as join6 } from "node:path";
19527
20306
  function fields3(value, names) {
19528
20307
  return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === names.length && names.every((name2) => Object.hasOwn(value, name2));
19529
20308
  }
@@ -19574,12 +20353,12 @@ function validLocalJobState(value, otherIds) {
19574
20353
  function prepareLocalJobService(hcrPath, retained, artifacts, applyMigrations) {
19575
20354
  const requested = validateLocalJobArtifacts(artifacts);
19576
20355
  const readSchema = (name2) => {
19577
- 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;
19578
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");
19579
- return readFileSync4(filename2, "utf8");
20358
+ return readFileSync7(filename2, "utf8");
19580
20359
  };
19581
20360
  const base = readSchema("jobs-v1.sql"), producer = readSchema("jobs-producer-v1.sql");
19582
- 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") };
19583
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");
19584
20363
  if (!retained?.initialized && !applyMigrations) throw new Error("Review both HCR job SQL assets and pass --apply-migrations for explicit native job schema approval");
19585
20364
  const retainedArtifacts = retained ? validateLocalJobArtifacts(retained.artifacts) : [];
@@ -19588,20 +20367,20 @@ function prepareLocalJobService(hcrPath, retained, artifacts, applyMigrations) {
19588
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");
19589
20368
  if (!previous) retainedArtifacts.push(artifact);
19590
20369
  }
19591
- 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) };
19592
20371
  return { state, base, producer };
19593
20372
  }
19594
20373
 
19595
20374
  // src/local-services.ts
19596
20375
  function writePrivate(filename2, value) {
19597
20376
  if (isLink(filename2)) throw new Error("Local service files must not be symbolic links");
19598
- const temporary = filename2 + "." + randomUUID5() + ".tmp";
19599
- writeFileSync5(temporary, value, { flag: "wx", mode: 384 });
20377
+ const temporary = filename2 + "." + randomUUID6() + ".tmp";
20378
+ writeFileSync8(temporary, value, { flag: "wx", mode: 384 });
19600
20379
  renameLocalFile(temporary, filename2);
19601
20380
  }
19602
20381
  function isLink(filename2) {
19603
20382
  try {
19604
- return lstatSync5(filename2).isSymbolicLink();
20383
+ return lstatSync7(filename2).isSymbolicLink();
19605
20384
  } catch (error) {
19606
20385
  if (error.code === "ENOENT") return false;
19607
20386
  throw error;
@@ -19609,9 +20388,9 @@ function isLink(filename2) {
19609
20388
  }
19610
20389
  function assertLocalStatePath(root, filename2) {
19611
20390
  const base = realpathSync(root);
19612
- for (let current = resolve5(filename2); current !== base; current = resolve5(current, "..")) {
20391
+ for (let current = resolve7(filename2); current !== base; current = resolve7(current, "..")) {
19613
20392
  const part = relative(base, current);
19614
- 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");
19615
20394
  if (isLink(current)) throw new Error("Local service paths must not contain links");
19616
20395
  }
19617
20396
  }
@@ -19622,22 +20401,22 @@ function stateValid(value) {
19622
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);
19623
20402
  }
19624
20403
  function acquireLocalLease(root, relativePath2 = ".backify/dev/development.lock") {
19625
- const filename2 = join4(root, relativePath2);
20404
+ const filename2 = join7(root, relativePath2);
19626
20405
  assertLocalStatePath(root, filename2);
19627
- mkdirSync3(resolve5(filename2, ".."), { recursive: true, mode: 448 });
19628
- 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() });
19629
20408
  try {
19630
- writeFileSync5(filename2, owner, { flag: "wx", mode: 384 });
20409
+ writeFileSync8(filename2, owner, { flag: "wx", mode: 384 });
19631
20410
  } catch {
19632
20411
  throw new Error("Local development already owns this repository, or a stale lifecycle lock requires explicit recovery");
19633
20412
  }
19634
20413
  return () => {
19635
- if (existsSync5(filename2) && readFileSync5(filename2, "utf8") === owner) unlinkSync3(filename2);
20414
+ if (existsSync8(filename2) && readFileSync8(filename2, "utf8") === owner) unlinkSync3(filename2);
19636
20415
  };
19637
20416
  }
19638
20417
  async function allocateLocalPort(requested) {
19639
20418
  if (requested !== void 0 && (!Number.isInteger(requested) || requested < 0 || requested > 65535)) throw new Error("Local service ports must be 0-65535");
19640
- const server = createServer();
20419
+ const server = createServer2();
19641
20420
  return new Promise((resolvePort, reject) => {
19642
20421
  server.once("error", reject);
19643
20422
  server.listen(requested ?? 0, "127.0.0.1", () => {
@@ -19653,7 +20432,7 @@ async function allocateLocalPort(requested) {
19653
20432
  }
19654
20433
  function command(executable, args2, options = {}) {
19655
20434
  const failure2 = () => new Error("Local service command failed; verify installed tools, port availability and local state permissions");
19656
- 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) => {
19657
20436
  const child = spawn(executable, args2, { ...options, windowsHide: true, stdio: "ignore" });
19658
20437
  const timer = setTimeout(() => {
19659
20438
  child.kill("SIGKILL");
@@ -19675,29 +20454,29 @@ function command(executable, args2, options = {}) {
19675
20454
  });
19676
20455
  }
19677
20456
  function postgresExecutable(name2, bin) {
19678
- const directory2 = bin ?? process.env.BACKIFY_LOCAL_POSTGRES_BIN ?? (process.platform === "win32" ? join4(process.env.ProgramFiles ?? "C:/Program Files", "PostgreSQL", "17", "bin") : void 0);
19679
- 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;
19680
20459
  }
19681
20460
  function localMigrationFiles(root, directory2) {
19682
20461
  if (directory2 === void 0) return [];
19683
20462
  if (typeof directory2 !== "string") throw new Error("Local migrations require a repository-relative directory");
19684
- const folder = resolve5(root, directory2);
20463
+ const folder = resolve7(root, directory2);
19685
20464
  assertLocalStatePath(root, folder);
19686
- if (!existsSync5(folder)) throw new Error("Declared migration directory does not exist");
19687
- const entries = readdirSync3(folder);
20465
+ if (!existsSync8(folder)) throw new Error("Declared migration directory does not exist");
20466
+ const entries = readdirSync5(folder);
19688
20467
  const filenames = entries.filter((entry) => entry.endsWith(".sql")).sort();
19689
20468
  if (filenames.length > 100 || entries.some((entry) => !entry.endsWith(".sql"))) throw new Error("Migrations require at most 100 flat .sql files");
19690
20469
  let total = 0;
19691
20470
  return filenames.map((name2) => {
19692
20471
  if (!/^[A-Za-z0-9_.-]{1,150}\.sql$/.test(name2)) throw new Error("Invalid migration filename");
19693
- const filename2 = join4(folder, name2);
20472
+ const filename2 = join7(folder, name2);
19694
20473
  assertLocalStatePath(root, filename2);
19695
- const info = lstatSync5(filename2);
20474
+ const info = lstatSync7(filename2);
19696
20475
  if (!info.isFile() || info.size > 1024 * 1024 || (total += info.size) > 4 * 1024 * 1024) throw new Error("Migration SQL exceeds supported limits");
19697
- const sql = readFileSync5(filename2, "utf8");
20476
+ const sql = readFileSync8(filename2, "utf8");
19698
20477
  if (!sql.trim()) throw new Error("Migration SQL must not be empty");
19699
20478
  validateLocalMigrationSql(sql);
19700
- return { name: name2, digest: createHash5("sha256").update(sql).digest("hex"), sql };
20479
+ return { name: name2, digest: createHash6("sha256").update(sql).digest("hex"), sql };
19701
20480
  });
19702
20481
  }
19703
20482
  function validateLocalMigrationSql(sql) {
@@ -19771,19 +20550,19 @@ async function startLocalServices(root, manifest, options = {}) {
19771
20550
  const reviewedScope = options.jobIdentityScope;
19772
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");
19773
20552
  const installed = options.hcrPath || process.env.BACKIFY_LOCAL_HCR_PATH ? void 0 : installedLocalRuntime();
19774
- const hcrPath = resolve5(options.hcrPath ?? process.env.BACKIFY_LOCAL_HCR_PATH ?? installed?.path ?? join4(root, "apps/hcr/bin/Debug/net9.0/Backify.Hcr.dll"));
19775
- 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");
19776
- 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");
19777
20556
  assertLocalStatePath(root, directory2);
19778
- mkdirSync3(directory2, { recursive: true, mode: 448 });
20557
+ mkdirSync4(directory2, { recursive: true, mode: 448 });
19779
20558
  assertLocalStatePath(root, directory2);
19780
- const statePath = join4(directory2, "host.json");
20559
+ const statePath = join7(directory2, "host.json");
19781
20560
  assertLocalStatePath(root, statePath);
19782
20561
  let existing;
19783
- if (existsSync5(statePath)) {
19784
- 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");
19785
20564
  try {
19786
- existing = JSON.parse(readFileSync5(statePath, "utf8"));
20565
+ existing = JSON.parse(readFileSync8(statePath, "utf8"));
19787
20566
  } catch {
19788
20567
  throw new Error("Invalid local service ownership state; preserve the data and repair explicitly");
19789
20568
  }
@@ -19791,31 +20570,31 @@ async function startLocalServices(root, manifest, options = {}) {
19791
20570
  }
19792
20571
  const state = existing ?? {
19793
20572
  version: 1,
19794
- scope: reviewedScope ? structuredClone(reviewedScope) : { organizationId: randomUUID5(), applicationId: randomUUID5(), environmentId: randomUUID5() },
19795
- databaseId: randomUUID5(),
19796
- storageId: randomUUID5(),
20573
+ scope: reviewedScope ? structuredClone(reviewedScope) : { organizationId: randomUUID6(), applicationId: randomUUID6(), environmentId: randomUUID6() },
20574
+ databaseId: randomUUID6(),
20575
+ storageId: randomUUID6(),
19797
20576
  configurationVersion: 0,
19798
- adminPassword: randomBytes2(32).toString("hex"),
19799
- databasePassword: randomBytes2(32).toString("hex")
20577
+ adminPassword: randomBytes3(32).toString("hex"),
20578
+ databasePassword: randomBytes3(32).toString("hex")
19800
20579
  };
19801
20580
  if (!stateValid(state)) throw new Error("Invalid local service ownership state; preserve the data and repair explicitly");
19802
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");
19803
20582
  let eventSchema;
19804
20583
  if (events) {
19805
- const filename2 = join4(dirname6(hcrPath), "sql", "events-v1.sql");
19806
- 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.");
19807
- eventSchema = readFileSync5(filename2, "utf8");
19808
- 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");
19809
20588
  if (state.events && state.events.schemaDigest !== digest4) throw new Error("Native event schema changed; preserve its database and plan an explicit migration.");
19810
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.");
19811
- 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 };
19812
20591
  }
19813
20592
  const jobSchema = jobs ? prepareLocalJobService(hcrPath, state.jobs, options.jobArtifacts, options.applyMigrations) : void 0;
19814
20593
  if (jobSchema) state.jobs = jobSchema.state;
19815
20594
  const storageNames = manifest.capabilities.storage?.buckets === void 0 ? ["default"] : manifest.capabilities.storage.buckets.map((bucket) => bucket.name);
19816
20595
  state.storageBindings ??= { default: state.storageId };
19817
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");
19818
- for (const name2 of storageNames) state.storageBindings[name2] ??= randomUUID5();
20597
+ for (const name2 of storageNames) state.storageBindings[name2] ??= randomUUID6();
19819
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");
19820
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");
19821
20600
  const migrations = database ? localMigrationFiles(root, manifest.capabilities.database?.migrations) : [];
@@ -19823,9 +20602,9 @@ async function startLocalServices(root, manifest, options = {}) {
19823
20602
  const databasePort = database || events || jobs ? await allocateLocalPort(options.databasePort) : void 0;
19824
20603
  if (databasePort !== void 0) state.retainedDatabasePort = databasePort;
19825
20604
  if (databasePort === hcrPort) throw new Error("HCR and PostgreSQL require separate ports");
19826
- const cluster = join4(directory2, "postgres");
20605
+ const cluster = join7(directory2, "postgres");
19827
20606
  assertLocalStatePath(root, cluster);
19828
- const release = acquireLocalLease(root, ".backify/dev/services/lifecycle.lock");
20607
+ const release2 = acquireLocalLease(root, ".backify/dev/services/lifecycle.lock");
19829
20608
  let postgresStarted = false;
19830
20609
  let hcr;
19831
20610
  let timer;
@@ -19839,7 +20618,7 @@ async function startLocalServices(root, manifest, options = {}) {
19839
20618
  }
19840
20619
  };
19841
20620
  const runtimeUrl = "http://127.0.0.1:" + hcrPort;
19842
- const tokenPath = join4(directory2, "workload-token");
20621
+ const tokenPath = join7(directory2, "workload-token");
19843
20622
  const stop = () => stopping ??= (async () => {
19844
20623
  if (timer) clearInterval(timer);
19845
20624
  if (snapshotTimer) clearInterval(snapshotTimer);
@@ -19858,11 +20637,11 @@ async function startLocalServices(root, manifest, options = {}) {
19858
20637
  }
19859
20638
  notify("hcr", "stopped");
19860
20639
  try {
19861
- 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"]);
19862
20641
  if (postgresStarted) notify("postgres", "stopped");
19863
- if (existsSync5(tokenPath)) unlinkSync3(tokenPath);
20642
+ if (existsSync8(tokenPath)) unlinkSync3(tokenPath);
19864
20643
  } finally {
19865
- release();
20644
+ release2();
19866
20645
  }
19867
20646
  })();
19868
20647
  const applied = [];
@@ -19870,28 +20649,28 @@ async function startLocalServices(root, manifest, options = {}) {
19870
20649
  writePrivate(statePath, JSON.stringify(state));
19871
20650
  if (database || events || jobs) {
19872
20651
  notify("postgres", "starting");
19873
- if (!existsSync5(join4(cluster, "PG_VERSION"))) {
19874
- const passwordPath = join4(directory2, "init-password");
20652
+ if (!existsSync8(join7(cluster, "PG_VERSION"))) {
20653
+ const passwordPath = join7(directory2, "init-password");
19875
20654
  writePrivate(passwordPath, state.adminPassword);
19876
20655
  try {
19877
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"]);
19878
20657
  } finally {
19879
- if (existsSync5(passwordPath)) unlinkSync3(passwordPath);
20658
+ if (existsSync8(passwordPath)) unlinkSync3(passwordPath);
19880
20659
  }
19881
20660
  }
19882
- 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");
19883
20662
  postgresStarted = true;
19884
- 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"]);
19885
20664
  notify("postgres", "ready");
19886
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 } });
19887
20666
  if (database) {
19888
20667
  if ((await sql("SELECT count(*) FROM pg_roles WHERE rolname='backify_local'")).trim() === "0") {
19889
- const filename2 = join4(directory2, "role-" + randomUUID5() + ".sql");
20668
+ const filename2 = join7(directory2, "role-" + randomUUID6() + ".sql");
19890
20669
  writePrivate(filename2, "CREATE ROLE backify_local LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '" + state.databasePassword + "';");
19891
20670
  try {
19892
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 } });
19893
20672
  } finally {
19894
- if (existsSync5(filename2)) unlinkSync3(filename2);
20673
+ if (existsSync8(filename2)) unlinkSync3(filename2);
19895
20674
  }
19896
20675
  }
19897
20676
  if ((await sql("SELECT count(*) FROM pg_database WHERE datname='backify_local'")).trim() === "0") await sql("CREATE DATABASE backify_local OWNER backify_local");
@@ -19906,12 +20685,12 @@ async function startLocalServices(root, manifest, options = {}) {
19906
20685
  notify("migrations", "starting");
19907
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);
19908
20687
  for (const migration of pending) {
19909
- const filename2 = join4(directory2, "apply-" + randomUUID5() + ".sql");
20688
+ const filename2 = join7(directory2, "apply-" + randomUUID6() + ".sql");
19910
20689
  writePrivate(filename2, "BEGIN;\n" + migration.sql + "\n;\nINSERT INTO public.__backify_migrations(name,digest) VALUES ('" + migration.name + "','" + migration.digest + "');\nCOMMIT;\n");
19911
20690
  try {
19912
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 } });
19913
20692
  } finally {
19914
- if (existsSync5(filename2)) unlinkSync3(filename2);
20693
+ if (existsSync8(filename2)) unlinkSync3(filename2);
19915
20694
  }
19916
20695
  applied.push(migration.name);
19917
20696
  }
@@ -19922,20 +20701,20 @@ async function startLocalServices(root, manifest, options = {}) {
19922
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 } });
19923
20702
  if (!eventState.initialized) {
19924
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.");
19925
- const roleFile = join4(directory2, "event-role-" + randomUUID5() + ".sql");
20704
+ const roleFile = join7(directory2, "event-role-" + randomUUID6() + ".sql");
19926
20705
  writePrivate(roleFile, "CREATE ROLE backify_events LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '" + eventState.password + "';");
19927
20706
  try {
19928
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 } });
19929
20708
  } finally {
19930
- if (existsSync5(roleFile)) unlinkSync3(roleFile);
20709
+ if (existsSync8(roleFile)) unlinkSync3(roleFile);
19931
20710
  }
19932
20711
  await sql("CREATE DATABASE backify_events OWNER backify_events");
19933
- const schemaFile = join4(directory2, "event-schema-" + randomUUID5() + ".sql");
20712
+ const schemaFile = join7(directory2, "event-schema-" + randomUUID6() + ".sql");
19934
20713
  writePrivate(schemaFile, eventSchema);
19935
20714
  try {
19936
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 } });
19937
20716
  } finally {
19938
- if (existsSync5(schemaFile)) unlinkSync3(schemaFile);
20717
+ if (existsSync8(schemaFile)) unlinkSync3(schemaFile);
19939
20718
  }
19940
20719
  eventState.initialized = true;
19941
20720
  writePrivate(statePath, JSON.stringify(state));
@@ -19949,21 +20728,21 @@ async function startLocalServices(root, manifest, options = {}) {
19949
20728
  const adminSql = (query, databaseName = "postgres") => jobCommand("backify_admin", state.adminPassword, databaseName, ["-c", query]);
19950
20729
  if (!jobState.initialized) {
19951
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");
19952
- const roleFile = join4(directory2, "job-role-" + randomUUID5() + ".sql");
20731
+ const roleFile = join7(directory2, "job-role-" + randomUUID6() + ".sql");
19953
20732
  writePrivate(roleFile, "CREATE ROLE backify_jobs LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD '" + jobState.password + "';");
19954
20733
  try {
19955
20734
  await jobCommand("backify_admin", state.adminPassword, "postgres", ["-f", roleFile]);
19956
20735
  } finally {
19957
- if (existsSync5(roleFile)) unlinkSync3(roleFile);
20736
+ if (existsSync8(roleFile)) unlinkSync3(roleFile);
19958
20737
  }
19959
20738
  await adminSql("CREATE DATABASE backify_jobs OWNER backify_admin");
19960
20739
  for (const schema of [jobSchema.base, jobSchema.producer]) {
19961
- const filename2 = join4(directory2, "job-schema-" + randomUUID5() + ".sql");
20740
+ const filename2 = join7(directory2, "job-schema-" + randomUUID6() + ".sql");
19962
20741
  writePrivate(filename2, schema);
19963
20742
  try {
19964
20743
  await jobCommand("backify_admin", state.adminPassword, "backify_jobs", ["-f", filename2]);
19965
20744
  } finally {
19966
- if (existsSync5(filename2)) unlinkSync3(filename2);
20745
+ if (existsSync8(filename2)) unlinkSync3(filename2);
19967
20746
  }
19968
20747
  }
19969
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");
@@ -19992,7 +20771,7 @@ async function startLocalServices(root, manifest, options = {}) {
19992
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" }] : [],
19993
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" }] : []
19994
20773
  ];
19995
- const snapshotPath = join4(directory2, "snapshot.json");
20774
+ const snapshotPath = join7(directory2, "snapshot.json");
19996
20775
  const writeSnapshot = () => writePrivate(snapshotPath, JSON.stringify({ schemaVersion: 1, configurationVersion: state.configurationVersion, expiresAt: new Date(Date.now() + 7 * 864e5).toISOString(), bindings }));
19997
20776
  const renewConfiguration = () => {
19998
20777
  if (stopping) throw new Error("Stopped local services cannot renew configuration");
@@ -20002,15 +20781,15 @@ async function startLocalServices(root, manifest, options = {}) {
20002
20781
  writeSnapshot();
20003
20782
  };
20004
20783
  writeSnapshot();
20005
- const settingsPath = join4(directory2, "appsettings.json");
20784
+ const settingsPath = join7(directory2, "appsettings.json");
20006
20785
  writePrivate(settingsPath, JSON.stringify({ Hcr: {
20007
20786
  Authentication: { PublicKeyPem: publicKey.export({ type: "spki", format: "pem" }), Issuer: "backify-local", Audience: "backify-local-hcr" },
20008
20787
  SnapshotPath: snapshotPath,
20009
- SnapshotJournalPath: join4(directory2, "journal.json"),
20788
+ SnapshotJournalPath: join7(directory2, "journal.json"),
20010
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 } } : {} },
20011
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 } } : {} },
20012
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 } } : {} },
20013
- 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) }])) }
20014
20793
  } }));
20015
20794
  issueToken();
20016
20795
  notify("hcr", "starting");
@@ -20024,7 +20803,7 @@ async function startLocalServices(root, manifest, options = {}) {
20024
20803
  while (true) {
20025
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");
20026
20805
  try {
20027
- 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" });
20028
20807
  if (response.ok) {
20029
20808
  const status = await response.json();
20030
20809
  if (status.status === "ready" && status.configurationVersion === state.configurationVersion) break;
@@ -20033,8 +20812,8 @@ async function startLocalServices(root, manifest, options = {}) {
20033
20812
  }
20034
20813
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
20035
20814
  }
20036
- if (events) await createHcrEventsProvider({ runtimeUrl, token: () => readFileSync5(tokenPath, "utf8") }).inspect();
20037
- 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();
20038
20817
  timer = setInterval(() => {
20039
20818
  try {
20040
20819
  issueToken();
@@ -20050,7 +20829,7 @@ async function startLocalServices(root, manifest, options = {}) {
20050
20829
  }
20051
20830
  }, 36e5);
20052
20831
  notify("hcr", "ready");
20053
- 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");
20054
20833
  return { identityScope: { ...state.scope }, runtimeUrl, tokenPath, database, storage, events, jobs, databasePort, migrations: applied, configurationFingerprint, renewConfiguration, stop };
20055
20834
  } catch (error) {
20056
20835
  await stop();
@@ -20059,29 +20838,29 @@ async function startLocalServices(root, manifest, options = {}) {
20059
20838
  }
20060
20839
 
20061
20840
  // src/local-job-auth.ts
20062
- import { createHash as createHash6 } from "node:crypto";
20063
- import { lstatSync as lstatSync6, readFileSync as readFileSync6 } from "node:fs";
20064
- 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";
20065
20844
  function prepareLocalJobAuth(root, filename2) {
20066
20845
  const read = (path, maximum) => {
20067
20846
  assertLocalStatePath(root, path);
20068
- const metadata = lstatSync6(path);
20847
+ const metadata = lstatSync8(path);
20069
20848
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > maximum) throw new Error();
20070
- const bytes = readFileSync6(path);
20849
+ const bytes = readFileSync9(path);
20071
20850
  if (bytes.length > maximum) throw new Error();
20072
20851
  return bytes;
20073
20852
  };
20074
20853
  try {
20075
- if (!filename2 || isAbsolute4(filename2)) throw new Error();
20076
- 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)));
20077
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();
20078
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();
20079
- if ([input.trustFile, input.stateFile].some((path) => typeof path !== "string" || !path || isAbsolute4(path))) throw new Error();
20080
- 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);
20081
20860
  if (trustFile === stateFile) throw new Error();
20082
20861
  const bytes = read(trustFile, 131072), trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
20083
20862
  read(stateFile, 2048);
20084
- 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();
20085
20864
  createPersistentApplicationAuthProvider(trust, { expectedScope: input.scope, stateFile });
20086
20865
  return { trustFile, stateFile, scope: structuredClone(input.scope), revision: input.revision, trustDigest: input.trustDigest };
20087
20866
  } catch {
@@ -20090,39 +20869,39 @@ function prepareLocalJobAuth(root, filename2) {
20090
20869
  }
20091
20870
 
20092
20871
  // src/local-job-trust-renewal.ts
20093
- import { createHash as createHash8 } from "node:crypto";
20094
- import { lstatSync as lstatSync8, readFileSync as readFileSync8 } from "node:fs";
20095
- 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";
20096
20875
 
20097
20876
  // src/workload-trust-sync.ts
20098
- import { lstatSync as lstatSync7, readFileSync as readFileSync7 } from "node:fs";
20099
- import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
20100
- 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";
20101
20880
  function prepareWorkloadTrustSynchronization(filename2) {
20102
20881
  try {
20103
- if (!isAbsolute5(filename2)) throw new Error();
20104
- const metadata = lstatSync7(filename2);
20882
+ if (!isAbsolute6(filename2)) throw new Error();
20883
+ const metadata = lstatSync9(filename2);
20105
20884
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 65536) throw new Error();
20106
- const bytes = readFileSync7(filename2);
20885
+ const bytes = readFileSync10(filename2);
20107
20886
  if (bytes.length > 65536) throw new Error();
20108
20887
  const value = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
20109
20888
  const allowed = ["controlPlaneUrl", "agentId", "expectedScope", "managementIssuer", "managementAudience", "managementPublicKeyPem", "credentialFile", "trustFile", "stateFile", "intervalSeconds"];
20110
- 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();
20111
- 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);
20112
20891
  const comparison = (filename3) => process.platform === "win32" ? filename3.toLowerCase() : filename3;
20113
20892
  if ([trustFile, stateFile, stateFile + ".lock", stateFile + ".delivery.lock"].map(comparison).includes(comparison(credentialFile))) throw new Error();
20114
20893
  const intervalSeconds = value.intervalSeconds ?? 30;
20115
20894
  if (!Number.isSafeInteger(intervalSeconds) || intervalSeconds < 10 || intervalSeconds > 300) throw new Error();
20116
20895
  const credential = () => {
20117
- const metadata2 = lstatSync7(credentialFile);
20896
+ const metadata2 = lstatSync9(credentialFile);
20118
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");
20119
- const bytes2 = readFileSync7(credentialFile);
20898
+ const bytes2 = readFileSync10(credentialFile);
20120
20899
  if (bytes2.length > 256) throw new Error("Management credential unavailable");
20121
20900
  return new TextDecoder("utf8", { fatal: true }).decode(bytes2).trim();
20122
20901
  };
20123
20902
  const options = { controlPlaneUrl: value.controlPlaneUrl, agentId: value.agentId, expectedScope: value.expectedScope, managementIssuer: value.managementIssuer, managementAudience: value.managementAudience, managementPublicKeyPem: value.managementPublicKeyPem, credential, trustFile, stateFile };
20124
20903
  const plan = planApplicationAuthSynchronization(options);
20125
- 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") };
20126
20905
  } catch {
20127
20906
  throw new Error("Invalid operator trust-sync configuration");
20128
20907
  }
@@ -20173,22 +20952,22 @@ async function workloadTrustSynchronization(args2, settings = {}) {
20173
20952
  // src/local-job-trust-renewal.ts
20174
20953
  function prepareLocalJobTrustRenewal(root, filename2, mount) {
20175
20954
  try {
20176
- if (!filename2 || isAbsolute6(filename2)) throw new Error();
20177
- const configurationFile = resolve8(root, filename2);
20955
+ if (!filename2 || isAbsolute7(filename2)) throw new Error();
20956
+ const configurationFile = resolve10(root, filename2);
20178
20957
  assertLocalStatePath(root, configurationFile);
20179
20958
  const configured = prepareWorkloadTrustSynchronization(configurationFile);
20180
20959
  const { credential, ...options } = configured.options;
20181
20960
  const same = (first, second) => process.platform === "win32" ? first.toLowerCase() === second.toLowerCase() : first === second;
20182
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();
20183
20962
  assertLocalStatePath(root, configured.credentialFile);
20184
- const metadata = lstatSync8(configured.credentialFile);
20963
+ const metadata = lstatSync10(configured.credentialFile);
20185
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();
20186
20965
  return { configurationFile, configurationDigest: configured.configurationDigest, credentialFile: configured.credentialFile, intervalSeconds: configured.intervalSeconds, options };
20187
20966
  } catch {
20188
20967
  throw new Error("Invalid reviewed native worker trust renewal; preserve trust and state and review explicitly");
20189
20968
  }
20190
20969
  }
20191
- 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 }) {
20192
20971
  const { createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2, synchronizeApplicationAuthTrustWithDigest: synchronizeApplicationAuthTrustWithDigest2 } = dependencies;
20193
20972
  const key = (filename2) => process.platform === "win32" ? resolve24(filename2).toLowerCase() : resolve24(filename2);
20194
20973
  const groups = [];
@@ -20287,34 +21066,34 @@ function applyLocalJobTrustRenewal(workers, updates, check) {
20287
21066
 
20288
21067
  // src/local-job-executor.ts
20289
21068
  function prepareLocalJobWorker(root, directory2, artifacts, manifest, authFile, trustSyncFile) {
20290
- 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");
20291
21070
  const authenticationEnabled = !!manifest.capabilities.auth && manifest.capabilities.auth.enabled !== false;
20292
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");
20293
21072
  if (!authenticationEnabled && authFile !== void 0) throw new Error("Native worker trust mounts require an enabled auth capability");
20294
21073
  if (trustSyncFile !== void 0 && authFile === void 0) throw new Error("Native worker trust renewal requires explicit reviewed auth mounts");
20295
21074
  const authTrust = authFile === void 0 ? void 0 : prepareLocalJobAuth(root, authFile);
20296
21075
  if (authTrust && trustSyncFile !== void 0) authTrust.renewal = prepareLocalJobTrustRenewal(root, trustSyncFile, authTrust);
20297
- 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");
20298
21077
  for (const filename2 of [workerFile, metadataFile]) assertLocalStatePath(root, filename2);
20299
- if (!lstatSync9(workerFile).isFile() || lstatSync9(workerFile).size > 16777216 || !lstatSync9(metadataFile).isFile() || lstatSync9(metadataFile).size > 65536) throw new Error("Invalid bounded native worker artifact");
20300
- const metadata = JSON.parse(readFileSync9(metadataFile, "utf8"));
20301
- const bundle = readFileSync9(workerFile);
20302
- 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");
20303
21082
  const scheduleDiscovery = bundle.subarray(0, 100).toString("utf8").startsWith('var __backifyWorkerProtocols = ["hcr-v1","hcr-schedules-v1"];\n');
20304
- 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");
20305
21084
  const artifact = validateLocalJobArtifacts(artifacts).find((item) => item.artifactVersion === actual);
20306
21085
  if (!artifact) throw new Error("Native worker bundle is not an explicitly reviewed job artifact");
20307
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");
20308
21087
  return { root, workerFile, artifact, schedulerEnabled: !!manifest.capabilities.scheduler && manifest.capabilities.scheduler.enabled !== false, ...scheduleDiscovery ? { scheduleDiscovery: true } : {}, ...authTrust ? { authTrust } : {} };
20309
21088
  }
20310
21089
  function readLocalJobArtifacts(root, filename2) {
20311
- if (!filename2 || isAbsolute7(filename2)) throw new Error("Job artifact admission requires a repository-relative reviewed JSON file");
20312
- 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);
20313
21092
  assertLocalStatePath(root, path);
20314
- if (!lstatSync9(path).isFile() || lstatSync9(path).size > 16384) throw new Error("Reviewed job artifact admission exceeds supported bounds");
20315
- 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")));
20316
21095
  }
20317
- 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 }) {
20318
21097
  const { spawn: spawn6, createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, validateWorkloadJobSchedules: validateWorkloadJobSchedules2, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2 } = dependencies;
20319
21098
  const assertReady = () => {
20320
21099
  for (let current = resolve24(options.workerFile); current !== options.root; current = resolve24(current, "..")) {
@@ -20447,9 +21226,9 @@ function createLocalJobExecutor(options, dependencies = { spawn: spawn2, createH
20447
21226
  }
20448
21227
 
20449
21228
  // src/local-native-jobs.ts
20450
- import { createHash as createHash10 } from "node:crypto";
21229
+ import { createHash as createHash11 } from "node:crypto";
20451
21230
  function createLocalNativeJobs(local, native, executor, artifact, scope, onFailure = () => {
20452
- }, hash2 = createHash10, catalog = { workloadJobSchedules, validateWorkloadJobSchedules, HcrJobsRequestError }, historical = []) {
21231
+ }, hash2 = createHash11, catalog = { workloadJobSchedules, validateWorkloadJobSchedules, HcrJobsRequestError }, historical = []) {
20453
21232
  const workers = [{ artifact, executor }, ...historical];
20454
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");
20455
21234
  const lanes = workers.flatMap((worker) => worker.artifact.jobs.map((policy) => ({ ...worker, policy })));
@@ -20707,9 +21486,9 @@ process.on("SIGTERM", stop); process.on("SIGINT", stop); process.once("disconnec
20707
21486
 
20708
21487
  // src/local-frontend.ts
20709
21488
  import { spawn as spawn3 } from "node:child_process";
20710
- 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";
20711
21490
  import { createRequire as createRequire3 } from "node:module";
20712
- 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";
20713
21492
  import { pathToFileURL } from "node:url";
20714
21493
 
20715
21494
  // src/local-frontend-guard.ts
@@ -20776,24 +21555,24 @@ await server.listen();
20776
21555
  `;
20777
21556
 
20778
21557
  // src/local-static-frontend.ts
20779
- import { createServer as createServer2 } from "node:http";
20780
- import { lstatSync as lstatSync10, readFileSync as readFileSync10, realpathSync as realpathSync2, watch } from "node:fs";
20781
- 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";
20782
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" };
20783
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()));
20784
21563
  function publicFile(directory2, pathname) {
20785
21564
  const parts = pathname.split("/").slice(1);
20786
21565
  if (parts.at(-1) === "") parts[parts.length - 1] = "index.html";
20787
21566
  if (!publicParts(parts)) throw new Error("refused");
20788
- const filename2 = resolve10(directory2, ...parts), part = relative4(directory2, filename2);
20789
- 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");
20790
21569
  let current = directory2;
20791
- if (lstatSync10(current).isSymbolicLink() || realpathSync2(current) !== directory2) throw new Error("refused");
21570
+ if (lstatSync12(current).isSymbolicLink() || realpathSync2(current) !== directory2) throw new Error("refused");
20792
21571
  for (const segment of parts) {
20793
- current = join6(current, segment);
20794
- if (lstatSync10(current).isSymbolicLink()) throw new Error("refused");
21572
+ current = join9(current, segment);
21573
+ if (lstatSync12(current).isSymbolicLink()) throw new Error("refused");
20795
21574
  }
20796
- const stat = lstatSync10(filename2);
21575
+ const stat = lstatSync12(filename2);
20797
21576
  if (!stat.isFile() || stat.nlink !== 1 || stat.size > 5242880) throw new Error("refused");
20798
21577
  return filename2;
20799
21578
  }
@@ -20808,7 +21587,7 @@ async function startLocalStaticFrontend(directory2, port, backendUrl) {
20808
21587
  const watcher = watch(directory2, { recursive: true }, (_, filename2) => {
20809
21588
  if (filename2 && publicParts(filename2.toString().split(/[\\/]/)) && contentTypes[extname2(filename2.toString()).toLowerCase()]) revision++;
20810
21589
  });
20811
- const server = createServer2((request, response) => {
21590
+ const server = createServer3((request, response) => {
20812
21591
  const send = (status, type, body) => {
20813
21592
  response.writeHead(status, { "content-type": type, "cache-control": "no-store", "x-content-type-options": "nosniff", "content-length": Buffer.byteLength(body) });
20814
21593
  response.end(request.method === "HEAD" ? void 0 : body);
@@ -20821,7 +21600,7 @@ async function startLocalStaticFrontend(directory2, port, backendUrl) {
20821
21600
  if (pathname === "/__backify/revision") return send(200, "text/plain", String(revision));
20822
21601
  if (pathname === "/__backify/config.json") return send(200, "application/json", JSON.stringify({ backendUrl: backendUrl ?? null }));
20823
21602
  const filename2 = publicFile(directory2, pathname);
20824
- const content = readFileSync10(filename2);
21603
+ const content = readFileSync13(filename2);
20825
21604
  if (content.length > 5242880) throw new Error("refused");
20826
21605
  const extension = extname2(filename2).toLowerCase();
20827
21606
  const body = extension === ".html" ? content.toString("utf8") + '\n<script src="/__backify/reload.js?revision=' + revision + '"></script>\n' : content;
@@ -20864,31 +21643,31 @@ function prepareLocalFrontend(root, options, privateFiles = []) {
20864
21643
  if (options.frontendPort !== void 0) throw new Error("Frontend port requires an explicit frontend directory");
20865
21644
  return void 0;
20866
21645
  }
20867
- const directory2 = realpathSync3(resolve11(root, options.frontend));
21646
+ const directory2 = realpathSync3(resolve13(root, options.frontend));
20868
21647
  const part = relative5(realpathSync3(root), directory2);
20869
- 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");
20870
21649
  if (options.frontendAdapter === "static") {
20871
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");
20872
21651
  return prepareLocalStaticFrontend(directory2);
20873
21652
  }
20874
- if (!existsSync6(join7(directory2, "package.json"))) throw new Error("Frontend requires an existing package.json and installed Vite");
20875
- 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"));
20876
21655
  let packagePath;
20877
21656
  try {
20878
21657
  packagePath = require3.resolve("vite/package.json");
20879
21658
  } catch {
20880
21659
  throw new Error("Frontend requires locally installed Vite; Backify does not install dependencies automatically");
20881
21660
  }
20882
- const metadata = JSON.parse(readFileSync11(packagePath, "utf8"));
21661
+ const metadata = JSON.parse(readFileSync14(packagePath, "utf8"));
20883
21662
  if (metadata.name !== "vite" || typeof metadata.bin?.vite !== "string") throw new Error("Installed Vite has no supported CLI entrypoint");
20884
- const executable = realpathSync3(resolve11(dirname7(packagePath), metadata.bin.vite));
20885
- const executablePart = relative5(realpathSync3(dirname7(packagePath)), executable);
20886
- 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");
20887
21666
  const timeout = options.frontendStartupTimeoutMs ?? 3e4;
20888
21667
  if (!Number.isInteger(timeout) || timeout < 100 || timeout > 12e4) throw new Error("Frontend startup timeout must be 100-120000 milliseconds");
20889
21668
  const modulePath = realpathSync3(require3.resolve("vite"));
20890
- const modulePart = relative5(realpathSync3(dirname7(packagePath)), modulePath);
20891
- 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");
20892
21671
  return { adapter: "vite", directory: directory2, modulePath, timeout, privateFiles };
20893
21672
  }
20894
21673
  async function stopFrontend(child) {
@@ -20943,17 +21722,19 @@ async function startLocalFrontend(prepared, port, backendUrl) {
20943
21722
  }
20944
21723
 
20945
21724
  // src/local-identity.ts
20946
- import { readFileSync as readFileSync12, statSync as statSync2 } from "node:fs";
20947
- 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";
20948
21727
 
20949
21728
  // ../sdk/dist/local-identity.js
20950
- import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
21729
+ import { createHash as createHash12, randomBytes as randomBytes4 } from "node:crypto";
20951
21730
  function createLocalIdentityProvider(options) {
20952
21731
  const serverOnly = () => {
20953
21732
  if (typeof window !== "undefined")
20954
21733
  throw new Error("Local development identities are server-only");
20955
21734
  };
20956
21735
  serverOnly();
21736
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production")
21737
+ throw new Error("Local Backify identities must not run in production");
20957
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";
20958
21739
  const text3 = (value, maximum) => typeof value === "string" && value.length > 0 && value.length <= maximum && !/[\x00-\x1f\x7f]/.test(value);
20959
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)
@@ -20961,9 +21742,11 @@ function createLocalIdentityProvider(options) {
20961
21742
  const scope = { ...options.scope }, organizationName = options.organizationName;
20962
21743
  const users = /* @__PURE__ */ new Map();
20963
21744
  for (const user of options.users) {
20964
- 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))
20965
21746
  throw new Error("Invalid local identity user");
20966
- 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));
20967
21750
  }
20968
21751
  const origins = /* @__PURE__ */ new Set();
20969
21752
  for (const value of options.requestOrigins) {
@@ -20986,7 +21769,7 @@ function createLocalIdentityProvider(options) {
20986
21769
  return value;
20987
21770
  };
20988
21771
  const sessions = /* @__PURE__ */ new Map();
20989
- const digest4 = (token) => createHash11("sha256").update(token).digest("hex");
21772
+ const digest4 = (token) => createHash12("sha256").update(token).digest("hex");
20990
21773
  const tokenPattern = /^backify-local-user_[a-zA-Z0-9_-]{43}$/;
20991
21774
  const requestAuth = {
20992
21775
  forRequest(request) {
@@ -21003,7 +21786,7 @@ function createLocalIdentityProvider(options) {
21003
21786
  if (!session || session.issued > time || session.expires <= time)
21004
21787
  throw new ApplicationAuthenticationError("application_identity_invalid");
21005
21788
  const user = users.get(session.userId);
21006
- return { ...user, roles: [], claims: { ...scope, localDevelopment: true } };
21789
+ return { ...structuredClone(user), roles: structuredClone(user.roles ?? []), claims: { ...scope, localDevelopment: true } };
21007
21790
  };
21008
21791
  return { currentUser: async () => identity(), currentOrganization: async () => identity() ? { id: scope.organizationId, name: organizationName } : null };
21009
21792
  }
@@ -21012,7 +21795,7 @@ function createLocalIdentityProvider(options) {
21012
21795
  requestAuth,
21013
21796
  profiles() {
21014
21797
  serverOnly();
21015
- return [...users.values()].map((user) => ({ ...user }));
21798
+ return [...users.values()].map((user) => structuredClone(user));
21016
21799
  },
21017
21800
  issue(userId, lifetimeSeconds = 300) {
21018
21801
  const time = now();
@@ -21023,7 +21806,7 @@ function createLocalIdentityProvider(options) {
21023
21806
  sessions.delete(hash2);
21024
21807
  if (sessions.size >= 128)
21025
21808
  throw new ApplicationAuthenticationError("application_identity_unavailable");
21026
- 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;
21027
21810
  sessions.set(digest4(accessToken), { userId, issued: time, expires });
21028
21811
  return { accessToken, tokenType: "Bearer", expiresAt: new Date(expires * 1e3).toISOString() };
21029
21812
  },
@@ -21039,22 +21822,22 @@ function createLocalIdentityProvider(options) {
21039
21822
  }
21040
21823
 
21041
21824
  // src/local-identity.ts
21042
- import { randomUUID as randomUUID6 } from "node:crypto";
21825
+ import { randomUUID as randomUUID7 } from "node:crypto";
21043
21826
  function prepareLocalIdentity(root, filename2) {
21044
21827
  if (filename2 === void 0) return void 0;
21045
- const path = resolve12(root, filename2);
21828
+ const path = resolve14(root, filename2);
21046
21829
  assertLocalStatePath(root, path);
21047
21830
  if (!statSync2(path).isFile() || statSync2(path).size > 16384) throw new Error("Local identity profiles require a file at most 16 KiB");
21048
21831
  let input;
21049
21832
  try {
21050
- input = JSON.parse(readFileSync12(path, "utf8"));
21833
+ input = JSON.parse(readFileSync15(path, "utf8"));
21051
21834
  } catch {
21052
21835
  throw new Error("Invalid local identity profile JSON");
21053
21836
  }
21054
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");
21055
21838
  const value = input;
21056
21839
  if (value.version !== 1) throw new Error("Invalid local identity profile version");
21057
- 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"] });
21058
21841
  return { organizationName: value.organizationName, users: identity.profiles() };
21059
21842
  }
21060
21843
  function localIdentityCommand(args2) {
@@ -21080,17 +21863,17 @@ function localIdentityCommand(args2) {
21080
21863
  }
21081
21864
 
21082
21865
  // src/local-job-history.ts
21083
- import { lstatSync as lstatSync11, readFileSync as readFileSync13 } from "node:fs";
21084
- 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";
21085
21868
  function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21086
21869
  if (filename2 === void 0) return [];
21087
21870
  const read = (filename3, maximum) => {
21088
- if (typeof filename3 !== "string" || !filename3 || isAbsolute10(filename3)) throw new Error();
21089
- const path = resolve13(root, filename3);
21871
+ if (typeof filename3 !== "string" || !filename3 || isAbsolute11(filename3)) throw new Error();
21872
+ const path = resolve15(root, filename3);
21090
21873
  assertLocalStatePath(root, path);
21091
- const metadata = lstatSync11(path);
21874
+ const metadata = lstatSync13(path);
21092
21875
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > maximum) throw new Error();
21093
- const bytes = readFileSync13(path);
21876
+ const bytes = readFileSync16(path);
21094
21877
  if (bytes.length > maximum) throw new Error();
21095
21878
  return new TextDecoder("utf8", { fatal: true }).decode(bytes);
21096
21879
  };
@@ -21100,7 +21883,7 @@ function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21100
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();
21101
21884
  const versions = /* @__PURE__ */ new Set([current.artifact.artifactVersion]);
21102
21885
  for (const item of input.workers) {
21103
- 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();
21104
21887
  versions.add(item.artifactVersion);
21105
21888
  }
21106
21889
  return input.workers.map((item) => {
@@ -21136,30 +21919,30 @@ function prepareLocalJobHistory(root, filename2, artifacts, manifest, current) {
21136
21919
  }
21137
21920
 
21138
21921
  // src/local-secrets.ts
21139
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, randomUUID as randomUUID7 } from "node:crypto";
21140
- 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";
21141
- 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";
21142
21925
  var maximumBytes2 = 1048576;
21143
21926
  var secretName = /^[A-Z][A-Z0-9_]{0,127}$/;
21144
21927
  var failure = () => new Error("Local secret vault unavailable or invalid; review the vault and external key privately");
21145
21928
  function vaultPath(root) {
21146
- return join8(realpathSync4(root), ".backify", "dev", "secrets.enc.json");
21929
+ return join11(realpathSync4(root), ".backify", "dev", "secrets.enc.json");
21147
21930
  }
21148
21931
  function inside(root, filename2) {
21149
21932
  const part = relative6(root, filename2);
21150
- return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute11(part);
21933
+ return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute12(part);
21151
21934
  }
21152
21935
  function privateFile(filename2, limit) {
21153
- for (let current = resolve14(filename2); ; current = dirname8(current)) {
21154
- if (lstatSync12(current).isSymbolicLink()) throw failure();
21155
- 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;
21156
21939
  }
21157
- const stat = lstatSync12(filename2);
21940
+ const stat = lstatSync14(filename2);
21158
21941
  if (!stat.isFile() || stat.size > limit || stat.nlink !== 1 || process.platform !== "win32" && (stat.mode & 63) !== 0) throw failure();
21159
- return readFileSync14(filename2);
21942
+ return readFileSync17(filename2);
21160
21943
  }
21161
21944
  function externalKey(root, filename2) {
21162
- const keyFile = resolve14(filename2);
21945
+ const keyFile = resolve16(filename2);
21163
21946
  if (inside(realpathSync4(root), keyFile) || inside(realpathSync4(root), realpathSync4(keyFile))) throw failure();
21164
21947
  const encoded = privateFile(keyFile, 64).toString("utf8");
21165
21948
  if (!/^[a-f0-9]{64}$/.test(encoded)) throw failure();
@@ -21173,7 +21956,7 @@ function context(root, application) {
21173
21956
  }
21174
21957
  function encrypt(root, application, key, secrets) {
21175
21958
  validateSecrets(secrets);
21176
- const nonce = randomBytes4(12), cipher = createCipheriv("aes-256-gcm", key, nonce);
21959
+ const nonce = randomBytes5(12), cipher = createCipheriv("aes-256-gcm", key, nonce);
21177
21960
  cipher.setAAD(context(root, application));
21178
21961
  const plaintext = Buffer.from(JSON.stringify(secrets));
21179
21962
  try {
@@ -21188,22 +21971,22 @@ function encrypt(root, application, key, secrets) {
21188
21971
  function writePrivate2(filename2, value) {
21189
21972
  const descriptor2 = openSync3(filename2, "wx", 384);
21190
21973
  try {
21191
- writeFileSync6(descriptor2, value);
21974
+ writeFileSync9(descriptor2, value);
21192
21975
  fsyncSync3(descriptor2);
21193
21976
  } finally {
21194
21977
  closeSync3(descriptor2);
21195
21978
  }
21196
21979
  }
21197
21980
  function ensureIgnored(root) {
21198
- const filename2 = join8(root, ".gitignore");
21981
+ const filename2 = join11(root, ".gitignore");
21199
21982
  assertLocalStatePath(root, filename2);
21200
- const current = existsSync7(filename2) ? readFileSync14(filename2, "utf8") : "";
21201
- 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");
21202
21985
  }
21203
21986
  function readLocalSecrets(root, application, keyFile) {
21204
21987
  const filename2 = vaultPath(root);
21205
21988
  assertLocalStatePath(root, filename2);
21206
- if (!existsSync7(filename2) && keyFile === void 0) return {};
21989
+ if (!existsSync10(filename2) && keyFile === void 0) return {};
21207
21990
  if (!keyFile) throw new Error("Encrypted local secrets require --local-secrets-key <external-key-file>");
21208
21991
  let key, plaintext;
21209
21992
  try {
@@ -21231,27 +22014,27 @@ function readLocalSecrets(root, application, keyFile) {
21231
22014
  }
21232
22015
  }
21233
22016
  function initializeLocalSecrets(root, application, keyFile) {
21234
- const filename2 = vaultPath(root), external = resolve14(keyFile);
22017
+ const filename2 = vaultPath(root), external = resolve16(keyFile);
21235
22018
  assertLocalStatePath(root, filename2);
21236
- if (existsSync7(filename2) || existsSync7(external)) throw new Error("Local secret initialization never overwrites an existing vault or key");
21237
- 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");
21238
- for (let current = dirname8(external); ; current = dirname8(current)) {
21239
- 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);
21240
22023
  if (stat.isSymbolicLink()) throw failure();
21241
- if (current === dirname8(external) && process.platform !== "win32" && (stat.mode & 63) !== 0) throw failure();
21242
- 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;
21243
22026
  }
21244
22027
  ensureIgnored(root);
21245
- const release = acquireLocalLease(root, ".backify/dev/secrets.lock");
21246
- const key = randomBytes4(32);
22028
+ const release2 = acquireLocalLease(root, ".backify/dev/secrets.lock");
22029
+ const key = randomBytes5(32);
21247
22030
  try {
21248
- if (existsSync7(filename2)) throw failure();
21249
- mkdirSync4(dirname8(filename2), { recursive: true, mode: 448 });
22031
+ if (existsSync10(filename2)) throw failure();
22032
+ mkdirSync5(dirname11(filename2), { recursive: true, mode: 448 });
21250
22033
  writePrivate2(external, key.toString("hex"));
21251
22034
  writePrivate2(filename2, encrypt(root, application, key, {}));
21252
22035
  } finally {
21253
22036
  key.fill(0);
21254
- release();
22037
+ release2();
21255
22038
  }
21256
22039
  return { schemaVersion: 1, status: "initialized", keyFile: external, names: [] };
21257
22040
  }
@@ -21259,19 +22042,19 @@ function setLocalSecret(root, application, keyFile, name2, value) {
21259
22042
  if (!secretName.test(name2) || Buffer.byteLength(value) < 1 || Buffer.byteLength(value) > 65536) throw new Error("Local secrets require an uppercase logical name and 1-65536 UTF-8 bytes from stdin");
21260
22043
  const filename2 = vaultPath(root);
21261
22044
  assertLocalStatePath(root, filename2);
21262
- const release = acquireLocalLease(root, ".backify/dev/secrets.lock");
22045
+ const release2 = acquireLocalLease(root, ".backify/dev/secrets.lock");
21263
22046
  let key;
21264
22047
  try {
21265
22048
  const secrets = readLocalSecrets(root, application, keyFile);
21266
22049
  secrets[name2] = value;
21267
22050
  key = externalKey(root, keyFile);
21268
- const temporary = filename2 + "." + randomUUID7() + ".tmp";
22051
+ const temporary = filename2 + "." + randomUUID8() + ".tmp";
21269
22052
  assertLocalStatePath(root, temporary);
21270
22053
  writePrivate2(temporary, encrypt(root, application, key, secrets));
21271
22054
  renameLocalFile(temporary, filename2);
21272
22055
  } finally {
21273
22056
  key?.fill(0);
21274
- release();
22057
+ release2();
21275
22058
  }
21276
22059
  return { schemaVersion: 1, status: "stored", name: name2 };
21277
22060
  }
@@ -21302,27 +22085,27 @@ async function localSecretsCommand(args2, root, application, input = process.std
21302
22085
 
21303
22086
  // src/local-development.ts
21304
22087
  function localManifest(root = process.cwd()) {
21305
- const filename2 = join9(root, "backify.yaml");
21306
- if (!existsSync8(filename2)) throw new Error("Create backify.yaml with version, application, runtime and explicit capabilities before running backify dev");
21307
- const manifest = parseApplicationManifest(readFileSync15(filename2, "utf8"));
21308
- const entry = resolve15(root, manifest.runtime.entrypoint);
21309
- 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");
21310
22093
  const discovered = analyzeSources(repositorySources(root));
21311
- const diagnostics = manifestDiagnostics(manifest, Object.keys(discovered.capabilities));
22094
+ const diagnostics = manifestDiagnostics(manifest, Object.keys(discovered.capabilities), discovered.capabilities.auth?.configuration);
21312
22095
  if (diagnostics.length) throw new Error(diagnostics.map((diagnostic) => diagnostic.message).join("; "));
21313
22096
  return manifest;
21314
22097
  }
21315
22098
  function inside2(root, path) {
21316
22099
  const part = relative7(realpathSync5(root), path);
21317
- return part !== ".." && !part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) && !isAbsolute12(part);
22100
+ return part !== ".." && !part.startsWith(".." + (process.platform === "win32" ? "\\" : "/")) && !isAbsolute13(part);
21318
22101
  }
21319
22102
  function sessionPath(root) {
21320
- return join9(root, ".backify", "dev", "session.json");
22103
+ return join12(root, ".backify", "dev", "session.json");
21321
22104
  }
21322
22105
  async function localRequest(path, input, root = process.cwd()) {
21323
22106
  if (!/^\/v1\/[a-z/-]+$/.test(path)) throw new Error("Invalid local command path");
21324
- if (!existsSync8(sessionPath(root))) throw new Error("Start backify dev before using local commands");
21325
- 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"));
21326
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");
21327
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) });
21328
22111
  if (!response.ok) {
@@ -21355,7 +22138,7 @@ async function startLocalDevelopment(options = {}) {
21355
22138
  if (options.jobTrustSyncFile !== void 0 && options.jobAuthFile === void 0) throw new Error("Native worker trust renewal requires explicit reviewed auth mounts");
21356
22139
  if (options.jobHistoryFile !== void 0 && options.jobWorkerDirectory === void 0) throw new Error("Historical workers require paired reviewed job artifacts and current worker");
21357
22140
  if (options.jobAuthFile !== void 0 && options.identityUsers !== void 0) throw new Error("Reviewed worker issuer trust cannot be mixed with local identity fixtures");
21358
- const root = resolve15(options.root ?? process.cwd());
22141
+ const root = resolve17(options.root ?? process.cwd());
21359
22142
  const manifest = localManifest(root);
21360
22143
  const validateSecretIntent = (values, intent = manifest) => {
21361
22144
  if (Object.keys(values).length && (!intent.capabilities.secrets || intent.capabilities.secrets.enabled === false)) throw new Error("Encrypted local secrets require enabled capabilities.secrets");
@@ -21372,18 +22155,18 @@ async function startLocalDevelopment(options = {}) {
21372
22155
  createLocalJobTrustRenewal(root, authMounts);
21373
22156
  const identityProfiles = prepareLocalIdentity(root, options.identityUsers);
21374
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");
21375
- const preparedFrontend = prepareLocalFrontend(root, options, options.localSecretsKey ? [realpathSync5(resolve15(options.localSecretsKey))] : []);
21376
- const ignorePath = join9(root, ".gitignore");
21377
- if (existsSync8(ignorePath) && lstatSync13(ignorePath).isSymbolicLink()) throw new Error("Local development cannot update a symlinked .gitignore");
21378
- const ignore = existsSync8(ignorePath) ? readFileSync15(ignorePath, "utf8") : "";
21379
- 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");
21380
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");
21381
- const directory2 = join9(root, ".backify", "dev");
22164
+ const directory2 = join12(root, ".backify", "dev");
21382
22165
  assertLocalStatePath(root, directory2);
21383
- for (const filename2 of ["session.json", "session.json.tmp", "worker.mjs", "worker.mjs.map", "state.json", "state.json.tmp"]) assertLocalStatePath(root, join9(directory2, filename2));
21384
- 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 });
21385
22168
  if (!inside2(root, realpathSync5(directory2))) throw new Error("Local state directory must remain inside this repository");
21386
- if (existsSync8(sessionPath(root))) {
22169
+ if (existsSync11(sessionPath(root))) {
21387
22170
  try {
21388
22171
  await localRequest("/v1/status", void 0, root);
21389
22172
  throw new Error("Backify dev is already running for this repository");
@@ -21413,15 +22196,15 @@ async function startLocalDevelopment(options = {}) {
21413
22196
  }
21414
22197
  throw error;
21415
22198
  }
21416
- const token = randomBytes5(32).toString("hex");
21417
- 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() };
21418
22201
  const workerEnvironment = (worker) => {
21419
22202
  if (!worker.authTrust) return worker;
21420
22203
  const { renewal, ...authTrust } = worker.authTrust;
21421
22204
  return { ...worker, authTrust };
21422
22205
  };
21423
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" });
21424
- const outfile = join9(directory2, "worker.mjs");
22207
+ const outfile = join12(directory2, "worker.mjs");
21425
22208
  let child;
21426
22209
  const trustRenewal = createLocalJobTrustRenewal(root, authMounts, { onFailure: () => console.error("Native worker trust renewal unavailable; existing validity and pin guards remain enforced."), onUpdate: () => {
21427
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) => {
@@ -21459,8 +22242,8 @@ async function startLocalDevelopment(options = {}) {
21459
22242
  }
21460
22243
  await stopChild(child);
21461
22244
  if (stopping) return;
21462
- const version = createHash12("sha256").update(readFileSync15(outfile)).update(services?.configurationFingerprint ?? "lightweight").digest("hex");
21463
- 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" } });
21464
22247
  child = worker;
21465
22248
  worker.send({ type: "local-secrets", secrets }, (error) => {
21466
22249
  if (error) {
@@ -21482,7 +22265,7 @@ async function startLocalDevelopment(options = {}) {
21482
22265
  workerReady = true;
21483
22266
  const session = { version: 1, port: message.port, token, pid: worker.pid };
21484
22267
  const temp = sessionPath(root) + ".tmp";
21485
- writeFileSync7(temp, JSON.stringify(session), { mode: 384 });
22268
+ writeFileSync10(temp, JSON.stringify(session), { mode: 384 });
21486
22269
  renameLocalFile(temp, sessionPath(root));
21487
22270
  const url = "http://127.0.0.1:" + message.port;
21488
22271
  options.onReady?.(url, { frontendUrl: frontend?.url, backendUrl });
@@ -21494,17 +22277,17 @@ async function startLocalDevelopment(options = {}) {
21494
22277
  });
21495
22278
  };
21496
22279
  try {
21497
- 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) {
21498
22281
  build3.onLoad({ filter: /./, namespace: "file" }, (input) => {
21499
- if (input.path !== resolve15(root, manifest.runtime.entrypoint)) return;
22282
+ if (input.path !== resolve17(root, manifest.runtime.entrypoint)) return;
21500
22283
  try {
21501
22284
  const updated = localManifest(root);
21502
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");
21503
22286
  if (updated.runtime.entrypoint !== manifest.runtime.entrypoint) throw new Error("Runtime entrypoint changed; restart backify dev");
21504
22287
  } catch (error) {
21505
- 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")] };
21506
22289
  }
21507
- 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")] };
21508
22291
  });
21509
22292
  build3.onEnd((result) => {
21510
22293
  if (result.errors.length) rejectStartup(new Error("Local application build failed"));
@@ -21560,8 +22343,8 @@ async function startLocalDevelopment(options = {}) {
21560
22343
  }
21561
22344
  }
21562
22345
  }
21563
- if (existsSync8(sessionPath(root))) {
21564
- const session = JSON.parse(readFileSync15(sessionPath(root), "utf8"));
22346
+ if (existsSync11(sessionPath(root))) {
22347
+ const session = JSON.parse(readFileSync18(sessionPath(root), "utf8"));
21565
22348
  if (session.token === token) unlinkSync4(sessionPath(root));
21566
22349
  }
21567
22350
  };
@@ -21575,11 +22358,11 @@ async function startLocalDevelopment(options = {}) {
21575
22358
  }
21576
22359
  async function runLocalTests(root = process.cwd()) {
21577
22360
  const manifest = localManifest(root);
21578
- const project = JSON.parse(readFileSync15(join9(root, "package.json"), "utf8"));
22361
+ const project = JSON.parse(readFileSync18(join12(root, "package.json"), "utf8"));
21579
22362
  if (!project.scripts?.test) throw new Error("Add a test script to package.json so AI agents can run backify test");
21580
22363
  const result = await new Promise((resolveResult, reject) => {
21581
- const npmPath = process.platform === "win32" ? process.env.npm_execpath ?? join9(dirname9(process.execPath), "node_modules", "npm", "bin", "npm-cli.js") : void 0;
21582
- 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)) {
21583
22366
  reject(new Error("Cannot locate npm-cli.js; run backify test through npx or npm"));
21584
22367
  return;
21585
22368
  }
@@ -21599,15 +22382,15 @@ async function runLocalTests(root = process.cwd()) {
21599
22382
  // src/workload.ts
21600
22383
  function inside3(root, filename2) {
21601
22384
  const part = relative8(root, filename2);
21602
- return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute13(part);
22385
+ return part !== ".." && !part.startsWith("../") && !part.startsWith("..\\") && !isAbsolute14(part);
21603
22386
  }
21604
22387
  function digest3(contents) {
21605
- return createHash13("sha256").update(contents).digest("hex");
22388
+ return createHash14("sha256").update(contents).digest("hex");
21606
22389
  }
21607
22390
  function packageDirectory(filename2) {
21608
- for (let current = dirname10(filename2); ; current = dirname10(current)) {
21609
- if (existsSync9(join10(current, "package.json"))) return current;
21610
- 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");
21611
22394
  }
21612
22395
  }
21613
22396
  function workloadSource(entry, kind, capabilities) {
@@ -21688,11 +22471,11 @@ main().catch(() => finish({ schemaVersion: 1, status: "failed", error: "workload
21688
22471
  `.replace("__KIND__", JSON.stringify(kind)).replace("__CAPABILITIES__", JSON.stringify(capabilities)).replace("__AUTH_ENABLED__", String(capabilities.includes("auth"))).replace("__ENTRY__", JSON.stringify(entry));
21689
22472
  }
21690
22473
  async function prepareWorkload(options) {
21691
- const root = realpathSync6(resolve16(options.root ?? process.cwd()));
22474
+ const root = realpathSync6(resolve18(options.root ?? process.cwd()));
21692
22475
  const manifest = localManifest(root);
21693
22476
  if (options.kind !== "http" && options.kind !== "worker") throw new Error("Workload kind must be http or worker");
21694
22477
  if (options.kind === "worker" && !options.entry) throw new Error("Workers require an explicit declaration-only entrypoint");
21695
- const entry = realpathSync6(resolve16(root, options.entry ?? manifest.runtime.entrypoint));
22478
+ const entry = realpathSync6(resolve18(root, options.entry ?? manifest.runtime.entrypoint));
21696
22479
  if (!inside3(root, entry)) throw new Error("Workload entrypoint must stay inside this repository");
21697
22480
  const enabled = Object.entries(manifest.capabilities).filter(([, settings]) => settings?.enabled !== false).map(([name2]) => name2).sort();
21698
22481
  if (options.kind === "worker" && !enabled.includes("jobs")) throw new Error("Workers require declared jobs capability");
@@ -21718,14 +22501,14 @@ async function prepareWorkload(options) {
21718
22501
  context3.onResolve({ filter: /./ }, async (input) => {
21719
22502
  if (input.pluginData?.backifyResolved) return;
21720
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" }] };
21721
- 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 } });
21722
22505
  if (!resolved.external && !resolved.errors.length && resolved.path) {
21723
22506
  const directory2 = realpathSync6(packageDirectory(resolved.path));
21724
22507
  const target = relative8(directory2, realpathSync6(resolved.path)).replaceAll("\\", "/");
21725
- 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") {
21726
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" }] };
21727
22510
  }
21728
- if (!isAbsolute13(input.path) && !input.path.startsWith(".")) trustedPackages.add(directory2);
22511
+ if (!isAbsolute14(input.path) && !input.path.startsWith(".")) trustedPackages.add(directory2);
21729
22512
  }
21730
22513
  return resolved;
21731
22514
  });
@@ -21784,34 +22567,34 @@ async function packageWorkload(options) {
21784
22567
  if (plan.blockers.includes("bundle-warning-review-required")) throw new Error("Workload bundle warnings require review before packaging");
21785
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");
21786
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" };
21787
- 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));
21788
22571
  assertLocalStatePath(root, output);
21789
- if (existsSync9(output)) {
21790
- if (JSON.stringify(readdirSync4(output).sort()) !== JSON.stringify(Object.keys(files).sort()) || Object.entries(files).some(([name2, value]) => {
21791
- assertLocalStatePath(root, join10(output, name2));
21792
- 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));
21793
22576
  })) throw new Error("Refusing to overwrite an existing workload artifact directory");
21794
22577
  } else {
21795
- mkdirSync6(output, { recursive: true });
21796
- 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 });
21797
22580
  }
21798
22581
  return { ...plan, outputDirectory: relative8(root, output).replaceAll("\\", "/") };
21799
22582
  }
21800
22583
 
21801
22584
  // src/workload-provenance.ts
21802
22585
  import { execFileSync } from "node:child_process";
21803
- 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";
21804
22587
  import { tmpdir } from "node:os";
21805
- 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";
21806
22589
 
21807
22590
  // src/workload-provenance-oci.ts
21808
- import { createHash as createHash14 } from "node:crypto";
21809
- import { createReadStream, lstatSync as lstatSync14, readFileSync as readFileSync17 } from "node:fs";
21810
- 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";
21811
22594
  import { Transform, Writable } from "node:stream";
21812
22595
  import { pipeline } from "node:stream/promises";
21813
22596
  import { createGunzip } from "node:zlib";
21814
- var provenanceHash = (bytes) => "sha256:" + createHash14("sha256").update(bytes).digest("hex");
22597
+ var provenanceHash = (bytes) => "sha256:" + createHash15("sha256").update(bytes).digest("hex");
21815
22598
  var maximumLayer = 256 * 1024 * 1024;
21816
22599
  var maximumImage = 1024 * 1024 * 1024;
21817
22600
  var manifestTypes = ["application/vnd.oci.image.manifest.v1+json", "application/vnd.docker.distribution.manifest.v2+json"];
@@ -21822,14 +22605,14 @@ function provenanceRef(value) {
21822
22605
  return value.slice(value.lastIndexOf("@") + 1);
21823
22606
  }
21824
22607
  function provenanceFile(filename2, maximum) {
21825
- const absolute = resolve17(filename2);
21826
- for (let ancestor = absolute; ; ancestor = dirname11(ancestor)) {
21827
- 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");
21828
22611
  if (ancestor === parse3(absolute).root) break;
21829
22612
  }
21830
- const stat = lstatSync14(absolute);
22613
+ const stat = lstatSync16(absolute);
21831
22614
  if (!stat.isFile() || stat.nlink !== 1 || stat.size < 1 || stat.size > maximum) throw new Error("Invalid provenance file");
21832
- const bytes = readFileSync17(absolute);
22615
+ const bytes = readFileSync20(absolute);
21833
22616
  if (bytes.length !== stat.size) throw new Error("Provenance file changed during read");
21834
22617
  return bytes;
21835
22618
  }
@@ -21838,7 +22621,7 @@ function descriptor(value) {
21838
22621
  return value;
21839
22622
  }
21840
22623
  function blobPath(root, value) {
21841
- return join11(root, "blobs", "sha256", descriptor(value).digest.slice(7));
22624
+ return join14(root, "blobs", "sha256", descriptor(value).digest.slice(7));
21842
22625
  }
21843
22626
  function jsonBlob(root, value) {
21844
22627
  const bytes = provenanceFile(blobPath(root, value), 1024 * 1024);
@@ -21847,8 +22630,8 @@ function jsonBlob(root, value) {
21847
22630
  }
21848
22631
  async function inspectProvenanceImage(root, architecture, requestedDigest) {
21849
22632
  if (!["amd64", "arm64"].includes(architecture) || requestedDigest && !digestPattern.test(requestedDigest)) throw new Error("Invalid OCI platform or digest");
21850
- if (JSON.parse(provenanceFile(join11(root, "oci-layout"), 1024).toString()).imageLayoutVersion !== "1.0.0") throw new Error("OCI layout required");
21851
- 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);
21852
22635
  let current = JSON.parse(indexBytes.toString());
21853
22636
  let selected;
21854
22637
  let manifestDigest = provenanceHash(indexBytes);
@@ -21879,7 +22662,7 @@ async function inspectProvenanceImage(root, architecture, requestedDigest) {
21879
22662
  if (!digestPattern.test(config.rootfs.diff_ids[index])) throw new Error("Invalid OCI diff ID");
21880
22663
  const filename2 = blobPath(root, layer);
21881
22664
  provenanceFileMetadata(filename2, layer.size);
21882
- const storedHash = createHash14("sha256"), expandedHash = createHash14("sha256");
22665
+ const storedHash = createHash15("sha256"), expandedHash = createHash15("sha256");
21883
22666
  let stored = 0, expanded = 0;
21884
22667
  const counter = new Transform({ transform(chunk, _encoding, callback) {
21885
22668
  stored += chunk.length;
@@ -21905,12 +22688,12 @@ async function inspectProvenanceImage(root, architecture, requestedDigest) {
21905
22688
  return { manifestDigest, configDigest: configuration.digest, architecture, layers };
21906
22689
  }
21907
22690
  function provenanceFileMetadata(filename2, expectedSize) {
21908
- const absolute = resolve17(filename2);
21909
- for (let ancestor = absolute; ; ancestor = dirname11(ancestor)) {
21910
- 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");
21911
22694
  if (ancestor === parse3(absolute).root) break;
21912
22695
  }
21913
- const stat = lstatSync14(absolute);
22696
+ const stat = lstatSync16(absolute);
21914
22697
  if (!stat.isFile() || stat.nlink !== 1 || stat.size !== expectedSize) throw new Error("Invalid OCI layer file");
21915
22698
  }
21916
22699
  function verifyBaseLineage(base, image2) {
@@ -21933,7 +22716,7 @@ function canonical(value) {
21933
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(",") + "}";
21934
22717
  return JSON.stringify(value);
21935
22718
  }
21936
- function git(root, args2) {
22719
+ function git2(root, args2) {
21937
22720
  try {
21938
22721
  return execFileSync("git", ["-C", root, ...args2], { encoding: "utf8", windowsHide: true, timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }).trim();
21939
22722
  } catch {
@@ -21968,28 +22751,28 @@ function dependencyMaterials(lock) {
21968
22751
  return { digest: provenanceHash(canonical(materials.sort((left, right) => left.path.localeCompare(right.path, "en")))), count: materials.length };
21969
22752
  }
21970
22753
  async function captureWorkloadProvenance(options) {
21971
- const root = resolve18(options.root ?? process.cwd());
22754
+ const root = resolve20(options.root ?? process.cwd());
21972
22755
  if (!repositoryPattern.test(options.repository) || !["first", "second"].includes(options.replica)) throw new Error("Invalid provenance repository or replica");
21973
- const applicationPath = relative9(root, resolve18(root, options.application)).replaceAll("\\", "/");
21974
- if (!applicationPath || isAbsolute14(applicationPath) || applicationPath.split("/").includes("..")) throw new Error("Application must be inside the source checkout");
21975
- 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");
21976
- 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}"]);
21977
22760
  if (!sha.test(commit) || !sha.test(tree)) throw new Error("Full Git commit and tree required");
21978
- const origin = git(root, ["remote", "get-url", "origin"]);
22761
+ const origin = git2(root, ["remote", "get-url", "origin"]);
21979
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");
21980
- const lockBytes = provenanceFile(join12(root, "package-lock.json"), 16 * 1024 * 1024);
22763
+ const lockBytes = provenanceFile(join15(root, "package-lock.json"), 16 * 1024 * 1024);
21981
22764
  const dependencies = dependencyMaterials(JSON.parse(lockBytes.toString("utf8")));
21982
- const context3 = resolve18(root, options.context);
21983
- 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");
21984
22767
  const contextDigests = {};
21985
- for (const name2 of contextNames) contextDigests[name2] = provenanceHash(provenanceFile(join12(context3, name2), name2 === "workload.mjs" ? 16 * 1024 * 1024 : 65536));
21986
- 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"));
21987
22770
  const baseDigest = provenanceRef(options.baseImage);
21988
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");
21989
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");
21990
- 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");
21991
- const base = await inspectProvenanceImage(resolve18(root, options.baseLayout), options.architecture, baseDigest);
21992
- 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);
21993
22776
  verifyBaseLineage(base, image2);
21994
22777
  let npm;
21995
22778
  try {
@@ -22011,7 +22794,7 @@ function validateReport(report) {
22011
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");
22012
22795
  for (const path of [report.build.applicationPath, report.build.entry]) {
22013
22796
  text2(path);
22014
- 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");
22015
22798
  }
22016
22799
  provenanceRef(report.build.baseImage);
22017
22800
  for (const image2 of [report.base, report.image]) {
@@ -22062,17 +22845,17 @@ function verifyWorkloadProvenance(options, now = /* @__PURE__ */ new Date()) {
22062
22845
  if (provenanceHash(baseSubject) !== provenanceRef(policy.baseImage)) throw new Error("Base attestation subject does not match the approved image digest");
22063
22846
  evidence.push({ bytes: baseSubject, bundle: provenanceFile(options.baseBundle, 4 * 1024 * 1024), identity: { ...policy, ...supplier } });
22064
22847
  }
22065
- const scratchRoot = resolve18(tmpdir());
22066
- const scratch = mkdtempSync(join12(scratchRoot, "backify-provenance-"));
22067
- 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");
22068
22851
  try {
22069
- mkdirSync7(join12(scratch, "evidence"), { mode: 448 });
22070
- const trustedRoot = join12(scratch, "evidence", "trusted-root.jsonl");
22071
- 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 });
22072
22855
  for (const [index, { bytes, bundle: bundleBytes, identity }] of evidence.entries()) {
22073
- const report = join12(scratch, "evidence", "report-" + index + ".json"), bundle = join12(scratch, "evidence", "bundle-" + index + ".jsonl");
22074
- writeFileSync9(report, bytes, { flag: "wx", mode: 384 });
22075
- 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 });
22076
22859
  try {
22077
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"] });
22078
22861
  if (!Array.isArray(JSON.parse(result)) || !JSON.parse(result).length) throw new Error();
@@ -22081,7 +22864,7 @@ function verifyWorkloadProvenance(options, now = /* @__PURE__ */ new Date()) {
22081
22864
  }
22082
22865
  }
22083
22866
  } finally {
22084
- rmSync(scratch, { recursive: true, force: true });
22867
+ rmSync2(scratch, { recursive: true, force: true });
22085
22868
  }
22086
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"] };
22087
22870
  }
@@ -22103,13 +22886,13 @@ async function workloadProvenanceCommand(args2) {
22103
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") });
22104
22887
  }
22105
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" });
22106
- 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 });
22107
22890
  return { schemaVersion: 1, status: "captured-unsigned", deploymentReady: false, reportDigest: provenanceHash(JSON.stringify(result, null, 2) + "\n") };
22108
22891
  }
22109
22892
 
22110
22893
  // src/workload-artifact-plan.ts
22111
- import { existsSync as existsSync10, lstatSync as lstatSync15, readFileSync as readFileSync18, realpathSync as realpathSync7 } from "node:fs";
22112
- 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";
22113
22896
  function uuid4(value) {
22114
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";
22115
22898
  }
@@ -22147,14 +22930,14 @@ function parseWorkerArtifactPlan(args2) {
22147
22930
  }
22148
22931
  async function planWorkerArtifactRegistration(options) {
22149
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");
22150
- const root = realpathSync7(resolve19(options.root ?? process.cwd()));
22933
+ const root = realpathSync7(resolve21(options.root ?? process.cwd()));
22151
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");
22152
- const filename2 = resolve19(root, options.jobsFile);
22935
+ const filename2 = resolve21(root, options.jobsFile);
22153
22936
  assertLocalStatePath(root, filename2);
22154
- 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");
22155
22938
  let jobs;
22156
22939
  try {
22157
- jobs = jobMetadata(JSON.parse(readFileSync18(filename2, "utf8")));
22940
+ jobs = jobMetadata(JSON.parse(readFileSync21(filename2, "utf8")));
22158
22941
  } catch {
22159
22942
  throw new Error("Invalid bounded worker job metadata");
22160
22943
  }
@@ -22175,8 +22958,8 @@ async function planWorkerArtifactRegistration(options) {
22175
22958
  }
22176
22959
 
22177
22960
  // src/workload-trust-state.ts
22178
- import { lstatSync as lstatSync16, readFileSync as readFileSync19 } from "node:fs";
22179
- 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";
22180
22963
  function parseWorkloadTrustState(args2) {
22181
22964
  const action = args2[0];
22182
22965
  if (action !== "plan" && action !== "initialize") throw new Error("Use backify workload trust-state plan|initialize");
@@ -22198,12 +22981,12 @@ function parseWorkloadTrustState(args2) {
22198
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") };
22199
22982
  }
22200
22983
  function workloadTrustState(options) {
22201
- 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");
22202
22985
  let trust;
22203
22986
  try {
22204
- const metadata = lstatSync16(options.trustFile);
22987
+ const metadata = lstatSync18(options.trustFile);
22205
22988
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 131072) throw new Error();
22206
- const bytes = readFileSync19(options.trustFile);
22989
+ const bytes = readFileSync22(options.trustFile);
22207
22990
  if (bytes.length > 131072) throw new Error();
22208
22991
  trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
22209
22992
  } catch {
@@ -22213,14 +22996,14 @@ function workloadTrustState(options) {
22213
22996
  if (trust.revision !== options.expectedRevision) throw new Error("Application identity trust revision changed; review again");
22214
22997
  if (options.action === "initialize") {
22215
22998
  if (!options.confirmed) throw new Error("Initialization requires explicit --confirm after reviewing the plan");
22216
- initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve20(options.stateFile) });
22999
+ initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve22(options.stateFile) });
22217
23000
  }
22218
- 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 };
22219
23002
  }
22220
23003
 
22221
23004
  // src/scaffold.ts
22222
- 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";
22223
- 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";
22224
23007
  function json2(value) {
22225
23008
  return JSON.stringify(value, null, 2) + "\n";
22226
23009
  }
@@ -22390,521 +23173,86 @@ Packaging requires an explicitly approved digest-pinned base image. Hosted durab
22390
23173
  dispatch/scheduling is not implemented by this template. Do not directly execute
22391
23174
  server/jobs.ts; it is a declaration-only entry loaded by a trusted Backify host.
22392
23175
  The Dockerfile builds the HTTP server and web/dist frontend and serves both from
22393
- one origin, with HOST/PORT from its host. Keep package-lock.json in source control.
22394
- Use backify ship for Preview and promote that tested release to Live. No production
22395
- provider-readiness claim, cloud deployment or browser authentication is implied.
22396
- `
22397
- };
22398
- }
22399
- function scaffoldFullStack(options) {
22400
- const files = fullStackTemplate(options.appId, options.name);
22401
- const root = resolve21(options.root ?? process.cwd());
22402
- if (!existsSync11(root) || !lstatSync17(root).isDirectory() || lstatSync17(root).isSymbolicLink()) throw new Error("Scaffold destination must be an existing non-linked directory");
22403
- const allowed = /* @__PURE__ */ new Set([...Object.keys(files).map((filename2) => filename2.split("/")[0]), ".git", "node_modules", "package-lock.json", ".backify"]);
22404
- if (readdirSync6(root).some((filename2) => !allowed.has(filename2))) throw new Error("Scaffolding requires an empty folder or an unchanged generated starter");
22405
- const canonicalRoot = realpathSync8(root);
22406
- const pending = [];
22407
- for (const [filename2, content] of Object.entries(files)) {
22408
- const destination = join13(root, filename2);
22409
- let current = destination;
22410
- while (current !== root) {
22411
- try {
22412
- const stat = lstatSync17(current);
22413
- if (stat.isSymbolicLink() || current !== destination && !stat.isDirectory()) throw new Error("Scaffolding refuses linked or invalid output paths: " + filename2);
22414
- if (relative10(canonicalRoot, realpathSync8(current)).startsWith("..")) throw new Error("Scaffold output must remain inside its directory");
22415
- if (current === destination && (!stat.isFile() || readFileSync20(current, "utf8") !== content)) throw new Error("Scaffolding refuses to overwrite existing content: " + filename2);
22416
- } catch (error) {
22417
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
22418
- if (current === destination) pending.push(filename2);
22419
- }
22420
- current = dirname13(current);
22421
- }
22422
- }
22423
- for (const filename2 of pending) {
22424
- const destination = join13(root, filename2);
22425
- mkdirSync8(dirname13(destination), { recursive: true });
22426
- writeFileSync10(destination, files[filename2], { flag: "wx" });
22427
- }
22428
- return { template: "react-vite-hono", created: pending, unchanged: Object.keys(files).length - pending.length, installed: false, deployed: false };
22429
- }
22430
-
22431
- // src/local-inspection-command.ts
22432
- function localInspectionCommand(args2) {
22433
- 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" };
22434
- const kind = args2[0];
22435
- if (!Object.hasOwn(routes, kind)) throw new Error("Use backify inspect tables|rows|buckets|objects|preview|events --local [--json]");
22436
- const allowed = kind === "rows" ? ["--table", "--limit", "--offset"] : kind === "objects" ? ["--bucket", "--prefix", "--limit", "--offset"] : kind === "preview" ? ["--bucket", "--key"] : [];
22437
- const values = /* @__PURE__ */ new Map();
22438
- const flags = /* @__PURE__ */ new Set();
22439
- for (let index = 1; index < args2.length; index++) {
22440
- const name2 = args2[index];
22441
- if (["--local", "--json"].includes(name2)) {
22442
- if (flags.has(name2)) throw new Error("Duplicate inspection flag");
22443
- flags.add(name2);
22444
- continue;
22445
- }
22446
- if (!allowed.includes(name2) || values.has(name2) || args2[index + 1] === void 0 || args2[index + 1].startsWith("--")) throw new Error("Invalid or missing inspection option");
22447
- values.set(name2, args2[++index]);
22448
- }
22449
- if (!flags.has("--local")) throw new Error("Inspection requires --local; no cloud or production data is queried");
22450
- const required = (name2) => {
22451
- const value = values.get(name2);
22452
- if (!value) throw new Error("Required inspection option: " + name2);
22453
- return value;
22454
- };
22455
- const page = () => {
22456
- const number = (name2, fallback, minimum, maximum) => {
22457
- const value = values.get(name2);
22458
- if (value !== void 0 && !/^\d+$/.test(value)) throw new Error("Invalid inspection page");
22459
- const parsed = value === void 0 ? fallback : Number(value);
22460
- if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) throw new Error("Invalid inspection page");
22461
- return parsed;
22462
- };
22463
- return { limit: number("--limit", 25, 1, 50), offset: number("--offset", 0, 0, 1e4) };
22464
- };
22465
- 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;
22466
- return { kind, path: routes[kind], input };
22467
- }
22468
-
22469
- // src/development.ts
22470
- 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";
22471
- import { dirname as dirname16, join as join16, resolve as resolve23 } from "node:path";
22472
- import { spawnSync as spawnSync3 } from "node:child_process";
22473
- import { randomUUID as randomUUID9 } from "node:crypto";
22474
-
22475
- // src/setup.ts
22476
- import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync7, lstatSync as lstatSync18 } from "node:fs";
22477
- import { basename as basename3, dirname as dirname15, join as join15, resolve as resolve22, isAbsolute as isAbsolute16 } from "node:path";
22478
- import { spawnSync as spawnSync2 } from "node:child_process";
22479
-
22480
- // src/delivery.ts
22481
- import { existsSync as existsSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
22482
- import { dirname as dirname14, join as join14, relative as relativePath } from "node:path";
22483
- import { createHash as createHash15 } from "node:crypto";
22484
- var previousGeneratedWorkflowHashes = /* @__PURE__ */ new Set(["33e962274e8493f7cbac2afbac05a4e96afeec49ee08cf2bf82f8add9081b28b", "578dfb4c771c623fd04454e8a604efd701d5563d11e11191324862369bd24718", "6eb280ae7bdb14c390011686c41400f59c023304c3015d8440b7e80aaafa1c11", "545e03a1d5b996ade179e0e60ed5c86accdadc1982c1c25efd094e34f72f47ee"]);
22485
- function validateDeliveryInstructions(root) {
22486
- for (const filename2 of ["BACKIFY.md", "BACKIFY-WORKFLOW.md"]) {
22487
- const path = join14(root, filename2);
22488
- if (existsSync12(path)) {
22489
- const existing = readFileSync21(path, "utf8").replace(/\r\n/g, "\n");
22490
- 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.");
22491
- }
22492
- }
22493
- }
22494
- function installDeliveryInstructions(root, instructionFiles2) {
22495
- validateDeliveryInstructions(root);
22496
- writeFileSync11(join14(root, "BACKIFY.md"), deliveryInstructions);
22497
- for (const relative11 of instructionFiles2) {
22498
- const instructionPath = join14(root, relative11), content = readFileSync21(instructionPath, "utf8");
22499
- const link = relativePath(dirname14(instructionPath), join14(root, "BACKIFY.md")).replaceAll("\\", "/");
22500
- const legacyLink = relativePath(dirname14(instructionPath), join14(root, "BACKIFY-WORKFLOW.md")).replaceAll("\\", "/");
22501
- const legacy = "For application delivery and recording each user request, follow [BACKIFY-WORKFLOW.md](" + legacyLink + ").";
22502
- const guidance = "For application delivery and recording each user request, follow [BACKIFY.md](" + link + ").";
22503
- if (content.includes(legacy)) writeFileSync11(instructionPath, content.replace(legacy, guidance));
22504
- else if (!content.includes("## Backify delivery")) writeFileSync11(instructionPath, content.trimEnd() + "\n\n## Backify delivery\n\n" + guidance + "\n");
22505
- }
22506
- }
22507
-
22508
- // src/setup.ts
22509
- var instructionFiles = ["AGENTS.md"];
22510
- var agentInstructionFiles = { codex: "AGENTS.md", claude: "CLAUDE.md", copilot: join15(".github", "copilot-instructions.md"), gemini: "GEMINI.md" };
22511
- function instructionSelection(root, agent, instructionFile) {
22512
- const configPath2 = join15(root, "backify.json"), configured = existsSync13(configPath2) ? JSON.parse(readFileSync22(configPath2, "utf8")) : {};
22513
- const custom = instructionFile ?? (agent === void 0 ? configured.instructionFile : void 0);
22514
- const selected = instructionFile !== void 0 ? "custom" : agent ?? configured.agent ?? "codex";
22515
- if (typeof selected !== "string" || selected !== "custom" && !Object.hasOwn(agentInstructionFiles, selected)) throw new Error("Select the current tool with --agent or --instruction-file <relative-path>.");
22516
- const filename2 = custom ?? agentInstructionFiles[selected];
22517
- 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.");
22518
- const normalized = filename2.replaceAll("\\", "/"), parts = normalized.split("/");
22519
- 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.");
22520
- for (let index = 1; index <= parts.length; index++) {
22521
- const entry = join15(root, ...parts.slice(0, index));
22522
- try {
22523
- const metadata = lstatSync18(entry);
22524
- 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.");
22525
- } catch (reason) {
22526
- if (reason.code !== "ENOENT") throw reason;
22527
- }
22528
- }
22529
- return { agent: selected, instructionFile: normalized, files: [normalized] };
22530
- }
22531
- function selectedInstructionFiles(root = process.cwd(), agent, instructionFile) {
22532
- return instructionSelection(root, agent, instructionFile).files;
22533
- }
22534
- function isToolWorkspaceEntry(entry, root = process.cwd()) {
22535
- if (entry.name.toLowerCase() === ".git" || entry.name.toLowerCase() === ".env" || entry.name.toLowerCase().startsWith(".env.")) return false;
22536
- 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));
22537
- }
22538
- function detectPackageManager(root = process.cwd()) {
22539
- 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;
22540
- return "npm";
22541
- }
22542
- function updateInstructions(root = process.cwd(), agent, instructionFile) {
22543
- const updates = selectedInstructionFiles(root, agent, instructionFile).map((relative11) => {
22544
- const path = join15(root, relative11);
22545
- return { path, content: updateManagedInstructions(existsSync13(path) ? readFileSync22(path, "utf8") : "") };
22546
- });
22547
- for (const { path, content } of updates) {
22548
- mkdirSync9(dirname15(path), { recursive: true });
22549
- writeFileSync12(path, content);
22550
- }
22551
- }
22552
- function synchronize(root = process.cwd(), agent, instructionFile) {
22553
- const path = join15(root, "backify.json");
22554
- if (!existsSync13(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
22555
- const value = JSON.parse(readFileSync22(path, "utf8")), issues = validateConfig(value);
22556
- if (issues.length) throw new Error(issues.join("; "));
22557
- if (value.instructionVersion > INSTRUCTION_VERSION) throw new Error("This app uses newer instructions. Install a matching or newer CLI before syncing.");
22558
- const selected = instructionSelection(root, agent, instructionFile);
22559
- validateDeliveryInstructions(root);
22560
- updateInstructions(root, selected.agent, selected.instructionFile);
22561
- installDeliveryInstructions(root, selected.files);
22562
- value.agent = selected.agent;
22563
- value.instructionFile = selected.instructionFile;
22564
- value.instructionVersion = INSTRUCTION_VERSION;
22565
- writeFileSync12(path, JSON.stringify(value, null, 2) + "\n");
22566
- }
22567
- function sdkInstalled(root = process.cwd()) {
22568
- try {
22569
- const pkg = JSON.parse(readFileSync22(join15(root, "package.json"), "utf8"));
22570
- return Boolean(pkg.dependencies?.[SDK_PACKAGE] || pkg.devDependencies?.[SDK_PACKAGE]);
22571
- } catch {
22572
- return false;
22573
- }
22574
- }
22575
- function installSdk(root = process.cwd()) {
22576
- if (sdkInstalled(root)) return;
22577
- 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" });
22578
- if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
22579
- }
22580
- function ensurePackageManifest(root) {
22581
- const path = join15(root, "package.json");
22582
- if (existsSync13(path)) return;
22583
- const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "GEMINI.md", "BACKIFY.md", "BACKIFY-WORKFLOW.md", "README.md", "LICENSE", "backify.json"]);
22584
- 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/");
22585
- const name2 = basename3(resolve22(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
22586
- writeFileSync12(path, JSON.stringify({ name: name2, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
22587
- }
22588
- function initialize(appId, root = process.cwd(), install = true, agent, instructionFile) {
22589
- if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
22590
- const selected = instructionSelection(root, agent, instructionFile), files = selected.files;
22591
- validateDeliveryInstructions(root);
22592
- for (const relative11 of files) if (existsSync13(join15(root, relative11))) updateManagedInstructions(readFileSync22(join15(root, relative11), "utf8"));
22593
- ensurePackageManifest(root);
22594
- if (install) installSdk(root);
22595
- const configPath2 = join15(root, "backify.json"), existing = existsSync13(configPath2) ? JSON.parse(readFileSync22(configPath2, "utf8")) : {};
22596
- writeFileSync12(configPath2, JSON.stringify({ ...existing, ...createConfig(appId), agent: selected.agent, instructionFile: selected.instructionFile }, null, 2) + "\n");
22597
- updateInstructions(root, selected.agent, selected.instructionFile);
22598
- installDeliveryInstructions(root, files);
22599
- }
22600
- function doctor(root = process.cwd(), agent, instructionFile) {
22601
- const issues = [], warnings = [];
22602
- if (!sdkInstalled(root)) issues.push(`${SDK_PACKAGE} is not installed`);
22603
- let config;
22604
- try {
22605
- config = JSON.parse(readFileSync22(join15(root, "backify.json"), "utf8"));
22606
- issues.push(...validateConfig(config));
22607
- } catch {
22608
- issues.push("backify.json is missing or invalid JSON");
22609
- }
22610
- 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.`);
22611
- let files = instructionFiles;
22612
- try {
22613
- files = selectedInstructionFiles(root, agent, instructionFile);
22614
- } catch (reason) {
22615
- issues.push(reason instanceof Error ? reason.message : String(reason));
22616
- }
22617
- for (const relative11 of files) {
22618
- const path = join15(root, relative11);
22619
- if (!existsSync13(path)) {
22620
- issues.push(`${relative11} is missing`);
22621
- continue;
22622
- }
22623
- issues.push(...managedInstructionIssues(readFileSync22(path, "utf8")).map((issue) => `${relative11} ${issue}`));
22624
- }
22625
- let version = "unknown";
22626
- try {
22627
- const pkg = JSON.parse(readFileSync22(join15(root, "package.json"), "utf8"));
22628
- version = pkg.dependencies?.[SDK_PACKAGE] ?? pkg.devDependencies?.[SDK_PACKAGE] ?? "missing";
22629
- } catch {
22630
- }
22631
- return { valid: issues.length === 0, appId: config?.appId, sdkVersion: version, instructionVersion: config?.instructionVersion, currentInstructionVersion: INSTRUCTION_VERSION, issues, warnings };
22632
- }
22633
-
22634
- // src/development.ts
22635
- function promptMetadata(args2) {
22636
- const fields5 = [["--model", "model", 200], ["--ai-tool", "aiTool", 200], ["--access-level", "accessLevel", 500]];
22637
- const metadata = {};
22638
- for (const [flag, field, maxLength] of fields5) {
22639
- const index = args2.indexOf(flag);
22640
- if (index < 0) continue;
22641
- const value = args2[index + 1]?.trim();
22642
- 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))
22643
- throw new Error(`Provide ${flag} with a nonempty value of at most ${maxLength} characters without control characters or credentials.`);
22644
- metadata[field] = value;
22645
- }
22646
- return metadata;
22647
- }
22648
- function applicationGuid(appId) {
22649
- 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();
22650
- const match = /^app_([0-9a-f]{32})$/i.exec(appId);
22651
- if (!match) throw new Error("Use the application ID supplied by Backify.");
22652
- const value = match[1];
22653
- return [value.slice(0, 8), value.slice(8, 12), value.slice(12, 16), value.slice(16, 20), value.slice(20)].join("-");
22654
- }
22655
- function projectApplication(root = process.cwd()) {
22656
- if (!existsSync14(join16(root, "backify.json"))) throw new Error("Run backify connect --app <application-id>, then backify init.");
22657
- return JSON.parse(readFileSync23(join16(root, "backify.json"), "utf8")).appId;
22658
- }
22659
- function apiAddress(value) {
22660
- const address = new URL(value);
22661
- 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).");
22662
- return address.toString().replace(/\/$/, "");
23176
+ one origin, with HOST/PORT from its host. Keep package-lock.json in source control.
23177
+ Use backify ship for Preview and promote that tested release to Live. No production
23178
+ provider-readiness claim, cloud deployment or browser authentication is implied.
23179
+ `
23180
+ };
22663
23181
  }
22664
- function apiClient(session, transport = fetch) {
22665
- const base = apiAddress(session.apiUrl);
22666
- return async (path, init) => {
22667
- if (!path.startsWith("/") || path.startsWith("//")) throw new Error("Invalid API path.");
22668
- if (session.expiresAt && Date.parse(session.expiresAt) <= Date.now()) throw new Error("CLI session expired. Copy a new setup prompt or run backify login.");
22669
- const response = await transport(base + path, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4), headers: { "content-type": "application/json", "X-Organization-ID": session.organizationId, ...session.token ? { authorization: "Bearer " + session.token } : {}, ...init?.headers } });
22670
- if (!response.ok) {
22671
- 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) {
22672
23194
  try {
22673
- const problem = await response.json();
22674
- message = [problem.code, problem.title].filter(Boolean).join(": ") || message;
22675
- } 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);
22676
23202
  }
22677
- throw new Error(message);
22678
- }
22679
- return response.status === 204 ? void 0 : await response.json();
22680
- };
22681
- }
22682
- function saveUserConfig(path, config) {
22683
- const temporary = path + "." + randomUUID9() + ".tmp";
22684
- try {
22685
- writeFileSync13(temporary, JSON.stringify(config, null, 2) + "\n", { mode: 384, flag: "wx" });
22686
- if (process.platform === "win32") {
22687
- const account = [process.env.USERDOMAIN, process.env.USERNAME].filter(Boolean).join("\\");
22688
- if (!account) throw new Error("Cannot identify the account to protect CLI credentials.");
22689
- const secured = spawnSync3("icacls", [temporary, "/inheritance:r", "/grant:r", account + ":(F)"], { windowsHide: true, encoding: "utf8" });
22690
- if (secured.status !== 0) throw new Error("Could not restrict CLI credential file permissions.");
22691
- }
22692
- renameSync4(temporary, path);
22693
- } finally {
22694
- rmSync2(temporary, { force: true });
22695
- }
22696
- }
22697
- function repositoryName(remote) {
22698
- const match = /^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i.exec(remote);
22699
- if (!match) throw new Error("Repository must be a credential-free GitHub URL.");
22700
- return match[1].toLowerCase();
22701
- }
22702
- function git2(args2, root = process.cwd(), environment = process.env) {
22703
- const result = spawnSync3("git", args2, { cwd: root, env: environment, encoding: "utf8", windowsHide: true, maxBuffer: 2 * 1024 * 1024 });
22704
- if (result.status !== 0) throw new Error("Git " + args2[0] + " failed. Check repository access, branch protection, and local changes.");
22705
- return result.stdout.trim();
22706
- }
22707
- function gitEnvironment(repository, token) {
22708
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) throw new Error("Invalid repository.");
22709
- 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" };
22710
- }
22711
- function validateCloneMerge(source, target) {
22712
- for (const entry of readdirSync8(source, { withFileTypes: true })) {
22713
- const destination = join16(target, entry.name);
22714
- let existing;
22715
- try {
22716
- existing = lstatSync19(destination);
22717
- } catch (reason) {
22718
- if (reason.code === "ENOENT") continue;
22719
- throw reason;
23203
+ current = dirname16(current);
22720
23204
  }
22721
- if (entry.isDirectory() && existing.isDirectory() && !existing.isSymbolicLink()) validateCloneMerge(join16(source, entry.name), destination);
22722
- else throw new Error("Clone conflicts with existing workspace entry: " + destination + ". Existing files are never overwritten; reconcile the conflict or use a new folder.");
22723
- }
22724
- }
22725
- async function cloneProject(call2, appId, root = process.cwd()) {
22726
- const status = await call2("/apps/" + applicationGuid(appId) + "/developer");
22727
- if (!status.repository || !status.branch || !status.workflowReady) throw new Error("Connect the repository in Backify and wait for workflow setup.");
22728
- 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.");
22729
- const credential = await call2("/apps/" + applicationGuid(appId) + "/developer/git-credential", { method: "POST" });
22730
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed. Run connect again.");
22731
- const staging = mkdtempSync2(join16(dirname16(resolve23(root)), ".backify-clone-"));
22732
- try {
22733
- git2(["clone", "--single-branch", "--branch", status.branch, "--", "https://github.com/" + status.repository + ".git", "."], staging, gitEnvironment(status.repository, credential.token));
22734
- validateCloneMerge(staging, root);
22735
- for (const entry of readdirSync8(staging)) cpSync(join16(staging, entry), join16(root, entry), { recursive: true, force: false, errorOnExist: true, dereference: false });
22736
- } finally {
22737
- 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.");
22738
- rmSync2(staging, { recursive: true, force: true });
22739
- }
22740
- }
22741
- async function pullProject(call2, appId, root = process.cwd()) {
22742
- if (existsSync14(join16(root, "backify.json")) && applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the connected application.");
22743
- const prefix = "/apps/" + applicationGuid(appId);
22744
- const status = await call2(prefix + "/developer");
22745
- if (!status.repository || !status.branch) throw new Error("Connect the repository and branch in Backify before pulling.");
22746
- if (resolve23(git2(["rev-parse", "--show-toplevel"], root)) !== resolve23(root)) throw new Error("Run pull from the connected repository root.");
22747
- if (repositoryName(git2(["remote", "get-url", "origin"], root)) !== status.repository.toLowerCase()) throw new Error("Local origin does not match the repository connected in Backify.");
22748
- 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.");
22749
- git2(["check-ref-format", "--branch", status.branch], root);
22750
- const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
22751
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before pulling.");
22752
- 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));
22753
- const remoteCommit = git2(["rev-parse", "FETCH_HEAD"], root);
22754
- const counts = git2(["rev-list", "--left-right", "--count", "HEAD..." + remoteCommit], root).split(/\s+/).map(Number);
22755
- if (counts.length !== 2 || counts.some((count) => !Number.isSafeInteger(count) || count < 0)) throw new Error("Could not compare local and remote history.");
22756
- const [ahead, behind] = counts;
22757
- if (!behind) return { status: "up-to-date", ahead, behind, commit: git2(["rev-parse", "HEAD"], root) };
22758
- 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 };
22759
- 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 };
22760
- git2(["merge", "--ff-only", "--", remoteCommit], root);
22761
- return { status: "updated", ahead: 0, behind: 0, commit: git2(["rev-parse", "HEAD"], root) };
22762
- }
22763
- async function readPrompt(input) {
22764
- const chunks = [];
22765
- let size = 0;
22766
- for await (const chunk of input) {
22767
- const bytes = Buffer.from(chunk);
22768
- size += bytes.length;
22769
- if (size > 128e3) throw new Error("Prompt exceeds the size limit.");
22770
- chunks.push(bytes);
22771
- }
22772
- const text3 = Buffer.concat(chunks).toString("utf8");
22773
- if (!text3.trim() || text3.length > 32e3) throw new Error("Provide between 1 and 32000 characters of user prompt text.");
22774
- 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.");
22775
- return text3;
22776
- }
22777
- function attachmentNames(values) {
22778
- if (values.length > 50) throw new Error("At most 50 attachment filenames are allowed.");
22779
- return [...new Set(values.map((value) => {
22780
- const filename2 = value.replace(/\\/g, "/").split("/").pop();
22781
- if (!filename2 || filename2 === "." || filename2 === ".." || filename2.length > 255 || /[\x00-\x1f\x7f]/.test(filename2)) throw new Error("Invalid attachment filename.");
22782
- return filename2;
22783
- }))];
22784
- }
22785
- function localReport(runChecks, root = process.cwd()) {
22786
- const pkg = JSON.parse(readFileSync23(join16(root, "package.json"), "utf8"));
22787
- const dependencies = { ...pkg.devDependencies, ...pkg.dependencies };
22788
- const framework = ["next", "vite", "astro", "@sveltejs/kit", "nuxt", "react", "express"].find((name2) => dependencies[name2]) ?? "unknown";
22789
- const setup = doctor(root);
22790
- const checks = [{ name: "backify doctor", status: setup.valid ? "passed" : "failed" }];
22791
- const manager = detectPackageManager(root);
22792
- for (const name2 of ["typecheck", "lint", "test", "build"]) {
22793
- if (!pkg.scripts?.[name2]) continue;
22794
- 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;
22795
- checks.push({ name: name2, status: result ? result.status === 0 ? "passed" : "failed" : "not-run" });
22796
- }
22797
- let branch = null, commitSha = null, dirty = true;
22798
- try {
22799
- branch = git2(["branch", "--show-current"], root);
22800
- commitSha = git2(["rev-parse", "HEAD"], root);
22801
- dirty = Boolean(git2(["status", "--porcelain"], root));
22802
- } catch {
22803
- }
22804
- const outputDirectory = ["dist", "build", "out", "."].find((directory2) => existsSync14(join16(root, directory2, "index.html"))) ?? null;
22805
- 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 };
22806
- }
22807
- function matchesSource(release, commit) {
22808
- return release?.commitSha === commit || release?.sourceCommitSha === commit;
22809
- }
22810
- function pushSource(appId, commit, branch, destination, root, environment) {
22811
- const remoteHead = git2(["ls-remote", "--", destination, "refs/heads/" + branch], root, environment).split(/\s+/)[0];
22812
- if (remoteHead === commit) return true;
22813
- if (remoteHead) {
22814
- git2(["fetch", "--no-tags", "--", destination, "refs/heads/" + branch], root, environment);
22815
- const fetchedHead = git2(["rev-parse", "FETCH_HEAD"], root);
22816
- if (fetchedHead === commit) return true;
22817
- const parent = git2(["show", "-s", "--format=%P", fetchedHead], root);
22818
- const message = git2(["show", "-s", "--format=%B", fetchedHead], root);
22819
- if (parent === commit && message === "Backify build " + applicationGuid(appId).replaceAll("-", "") + ": " + commit) return true;
22820
- 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.");
22821
23205
  }
22822
- git2(["push", "--", destination, commit + ":refs/heads/" + branch], root, environment);
22823
- return false;
22824
- }
22825
- async function waitForPreview(call2, prefix, commit, options = {}) {
22826
- const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
22827
- const sleep = options.sleep ?? ((delay) => new Promise((resolve24) => setTimeout(resolve24, delay)));
22828
- do {
22829
- const deployments = await call2(prefix + "/deployments");
22830
- const matching = deployments.find((release) => release.kind === "preview" && matchesSource(release, commit) && !options.previousDeploymentIds?.includes(release.id));
22831
- if (matching?.status === "failed") throw new Error("Deployment of this source failed. Run backify status and inspect its deployment steps.");
22832
- const overview = await call2(prefix + "/releases");
22833
- if (matchesSource(overview.preview, commit) && overview.preview?.status === "succeeded" && overview.preview.healthResult === "healthy") return overview;
22834
- const build3 = await call2(prefix + "/developer/build?commitSha=" + commit);
22835
- 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."));
22836
- await sleep(options.intervalMs ?? 4e3);
22837
- } while (Date.now() < deadline);
22838
- throw new Error("Timed out waiting for this commit's healthy preview. Run backify status; no production release was requested.");
22839
- }
22840
- async function ship(call2, appId, options = {}) {
22841
- const root = options.root ?? process.cwd(), prefix = "/apps/" + applicationGuid(appId);
22842
- const status = await call2(prefix + "/developer");
22843
- if (!status.repository || !status.branch || !status.workflowReady) throw new Error("The application's repository and build workflow must be connected in Backify.");
22844
- if (!doctor(root).valid) throw new Error("Run backify doctor and fix project setup before shipping.");
22845
- if (applicationGuid(projectApplication(root)) !== applicationGuid(appId)) throw new Error("Local application ID does not match the application being shipped.");
22846
- const actualRoot = git2(["rev-parse", "--show-toplevel"], root);
22847
- if (resolve23(actualRoot).toLowerCase() !== resolve23(root).toLowerCase()) throw new Error("Shipping currently requires the application at the repository root.");
22848
- const origin = git2(["remote", "get-url", "origin"], root);
22849
- const pushOrigin = git2(["remote", "get-url", "--push", "origin"], root);
22850
- if (repositoryName(origin) !== status.repository.toLowerCase() || repositoryName(pushOrigin) !== status.repository.toLowerCase()) throw new Error("Local repository does not match this Backify application.");
22851
- if (git2(["branch", "--show-current"], root) !== status.branch) throw new Error("Use the connected branch: " + status.branch);
22852
- if (git2(["status", "--porcelain"], root)) throw new Error("Commit application changes before shipping. The CLI never stages or commits unrelated files.");
22853
- const commit = git2(["rev-parse", "HEAD"], root);
22854
- const report = localReport(true, root);
22855
- await call2(prefix + "/developer/report", { method: "POST", body: JSON.stringify(report) });
22856
- if (report.checks.some((check) => check.status === "failed")) throw new Error("Checks failed; nothing was pushed.");
22857
- 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.");
22858
- if (status.hostingTargetType === "azure" && !report.hasDockerfile) throw new Error("Customer Azure requires a repository-root Dockerfile.");
22859
- 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.");
22860
- const previousDeployments = await call2(prefix + "/deployments");
22861
- const previousBuild = await call2(prefix + "/developer/build?commitSha=" + commit);
22862
- const credential = await call2(prefix + "/developer/git-credential", { method: "POST" });
22863
- if (credential.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed; reconnect before shipping.");
22864
- const environment = gitEnvironment(status.repository, credential.token);
22865
- const destination = "https://github.com/" + status.repository + ".git";
22866
- const alreadyPushed = pushSource(appId, commit, status.branch, destination, root, environment);
22867
- const currentPreview = previousDeployments.find((release) => release.kind === "preview" && matchesSource(release, commit));
22868
- 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" }) });
22869
- 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 });
22870
- if (overview.preview.commitSha !== commit) {
22871
- 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.");
22872
- const refreshed = await call2(prefix + "/developer/git-credential", { method: "POST" });
22873
- if (refreshed.repository.toLowerCase() !== status.repository.toLowerCase()) throw new Error("Repository changed while building.");
22874
- git2(["fetch", "--", destination, "refs/heads/" + status.branch], root, gitEnvironment(status.repository, refreshed.token));
22875
- 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" });
22876
23210
  }
22877
- 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 };
22878
23212
  }
22879
- async function publishPreview(call2, prefix, overview, options = {}) {
22880
- if (!overview.preview || overview.preview.status !== "succeeded" || overview.preview.healthResult !== "healthy") throw new Error("Only a verified healthy preview can be published.");
22881
- const preview = overview.preview;
22882
- if (options.previewOnly) return { status: "preview-ready", url: preview.url };
22883
- if (overview.policy.requirePreviewTesting && !preview.testedAt) return { status: "testing-required", url: preview.url };
22884
- if (overview.live?.previewDeploymentId === preview.id && overview.live.status === "succeeded") return { status: "live", url: overview.live.url };
22885
- const promotion = await call2(prefix + "/deployments/promote", { method: "POST", headers: { "Idempotency-Key": "cli-ship-" + preview.id }, body: JSON.stringify({ previewDeploymentId: preview.id, tested: false }) });
22886
- if (promotion.status === "awaiting-approval" || promotion.approvalId) return { status: "awaiting-approval", url: preview.url };
22887
- if (!promotion.operationId) throw new Error("Publication returned no operation to verify. Run backify status.");
22888
- const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
22889
- do {
22890
- const operation = await call2("/operations/" + promotion.operationId);
22891
- if (operation.status === "failed" || operation.status === "cancelled") throw new Error(operation.errorMessage ?? "Production deployment did not complete.");
22892
- if (operation.status === "succeeded") {
22893
- const releases = await call2(prefix + "/releases");
22894
- 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.");
22895
- 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;
22896
23228
  }
22897
- await new Promise((resolve24) => setTimeout(resolve24, 4e3));
22898
- } while (Date.now() < deadline);
22899
- 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 };
22900
23250
  }
22901
23251
 
22902
23252
  // src/index.ts
22903
23253
  import { readFileSync as readFileSync24, existsSync as existsSync15, rmSync as rmSync3 } from "node:fs";
22904
23254
  import { homedir as homedir2 } from "node:os";
22905
23255
  import { join as join17 } from "node:path";
22906
- import { randomBytes as randomBytes6 } from "node:crypto";
22907
- import { createServer as createServer3 } from "node:http";
22908
23256
  import { spawn as spawn5 } from "node:child_process";
22909
23257
  var configPath = join17(homedir2(), ".backify.json");
22910
23258
  var userConfig = existsSync15(configPath) ? JSON.parse(readFileSync24(configPath, "utf8")) : {};
@@ -22912,73 +23260,42 @@ var [command2, ...args] = process.argv.slice(2);
22912
23260
  async function call(path, init) {
22913
23261
  let appId = option("--app");
22914
23262
  if (!appId && existsSync15(join17(process.cwd(), "backify.json"))) appId = projectApplication();
22915
- const session = appId ? userConfig.sessions?.["app_" + applicationGuid(appId).replaceAll("-", "")] : void 0;
22916
- 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);
22917
23266
  }
22918
23267
  var option = (name2) => {
22919
23268
  const index = args.indexOf(name2);
22920
23269
  return index >= 0 ? args[index + 1] : void 0;
22921
23270
  };
22922
23271
  function openBrowser(url) {
22923
- const command3 = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open", args2 = process.platform === "win32" ? ["/c", "start", "", '"' + url + '"'] : [url];
22924
- 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."));
22925
23275
  child.unref();
22926
23276
  }
22927
- async function browserLogin() {
22928
- const state = randomBytes6(24).toString("base64url"), server = createServer3((req, res) => {
22929
- if (req.method === "OPTIONS") {
22930
- res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "content-type" });
22931
- return res.end();
22932
- }
22933
- if (req.method !== "POST" || req.url !== "/callback") {
22934
- res.writeHead(404);
22935
- 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;
22936
23293
  }
22937
- let raw = "";
22938
- req.setEncoding("utf8");
22939
- req.on("data", (part) => raw += part);
22940
- req.on("end", () => {
22941
- try {
22942
- const value = JSON.parse(raw);
22943
- if (value.state !== state || typeof value.token !== "string" || typeof value.organizationId !== "string") throw new Error("Invalid callback");
22944
- res.writeHead(200, { "access-control-allow-origin": "*", "content-type": "text/plain" });
22945
- res.end("Backify CLI connected. You can close this tab.");
22946
- userConfig.token = value.token;
22947
- userConfig.organizationId = value.organizationId;
22948
- saveUserConfig(configPath, userConfig);
22949
- server.close();
22950
- resolveLogin();
22951
- } catch {
22952
- res.writeHead(400, { "access-control-allow-origin": "*" });
22953
- res.end("Invalid or expired Backify CLI login.");
22954
- }
22955
- });
22956
- });
22957
- let resolveLogin = () => {
22958
- };
22959
- const completed = new Promise((resolve24) => resolveLogin = resolve24);
22960
- server.listen(0, "127.0.0.1", () => {
22961
- const address = server.address();
22962
- if (!address || typeof address === "string") throw new Error("Could not start local login callback");
22963
- const callback = `http://127.0.0.1:${address.port}/callback`;
22964
- const portal = new URL(process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://backify.dev");
22965
- portal.searchParams.set("cli_callback", callback);
22966
- portal.searchParams.set("cli_state", state);
22967
- console.log(`Opening ${portal.origin} to sign in to Backify\u2026`);
22968
- openBrowser(portal.toString());
22969
- });
22970
- const timeout = setTimeout(() => {
22971
- server.close();
22972
- rejectLogin(new Error("Backify login timed out. Run backify login to try again."));
22973
- }, 5 * 6e4);
22974
- let rejectLogin = () => {
22975
- };
22976
- const failed = new Promise((_, reject) => rejectLogin = reject);
22977
- try {
22978
- await Promise.race([completed, failed]);
22979
- } finally {
22980
- clearTimeout(timeout);
22981
- }
23294
+ saveUserConfig(configPath, userConfig);
23295
+ } });
23296
+ }
23297
+ function applicationClient(session, appId) {
23298
+ return reconnectingApiClient(session, () => browserLogin(appId, session.organizationId, session.apiUrl));
22982
23299
  }
22983
23300
  async function main() {
22984
23301
  switch (command2) {
@@ -23126,11 +23443,11 @@ async function main() {
23126
23443
  break;
23127
23444
  }
23128
23445
  case "login":
23129
- if (args[0]) {
23446
+ if (args[0] && !args[0].startsWith("--")) {
23130
23447
  userConfig.token = args[0];
23131
23448
  userConfig.organizationId = args[1] ?? userConfig.organizationId;
23132
23449
  saveUserConfig(configPath, userConfig);
23133
- } else await browserLogin();
23450
+ } else await browserLogin(option("--app") ?? (existsSync15(join17(process.cwd(), "backify.json")) ? projectApplication() : void 0), option("--organization"));
23134
23451
  console.log("Logged in to Backify");
23135
23452
  break;
23136
23453
  case "logout":
@@ -23139,23 +23456,34 @@ async function main() {
23139
23456
  break;
23140
23457
  case "connect": {
23141
23458
  const appId = option("--app") ?? projectApplication(), key = "app_" + applicationGuid(appId).replaceAll("-", "");
23142
- const code = option("--pair"), organizationId = option("--organization") ?? userConfig.organizationId;
23459
+ const code = option("--pair"), organizationId = option("--organization") ?? userConfig.sessions?.[key]?.organizationId ?? userConfig.organizationId;
23143
23460
  const address = apiAddress(option("--api") ?? process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api");
23144
23461
  if (code) {
23145
23462
  if (!organizationId) throw new Error("The setup prompt must supply the organization.");
23146
23463
  const redeem = apiClient({ token: "", organizationId, apiUrl: address });
23147
- const paired = await redeem("/apps/" + applicationGuid(appId) + "/developer/pair", { method: "POST", body: JSON.stringify({ code }) });
23464
+ const paired = await redeem("/apps/" + applicationGuid(appId) + "/developer/pair", { method: "POST", body: JSON.stringify({ code, device: cliDevice() }) });
23148
23465
  userConfig.sessions ??= {};
23149
23466
  userConfig.sessions[key] = { ...paired, apiUrl: address };
23150
23467
  saveUserConfig(configPath, userConfig);
23151
- } else if (!userConfig.sessions?.[key] && !userConfig.token) {
23152
- await browserLogin();
23468
+ } else if (args.includes("--reauth") || !userConfig.sessions?.[key] && !userConfig.token) {
23469
+ await browserLogin(appId, organizationId, address);
23470
+ }
23471
+ let session = userConfig.sessions?.[key] ?? { token: userConfig.token ?? "", organizationId: organizationId ?? userConfig.organizationId ?? "", apiUrl: address };
23472
+ if (!session.token.startsWith("bfcli_")) {
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
+ }
23479
+ userConfig.sessions ??= {};
23480
+ userConfig.sessions[key] = session;
23481
+ saveUserConfig(configPath, userConfig);
23153
23482
  }
23154
- const session = userConfig.sessions?.[key] ?? { token: userConfig.token ?? "", organizationId: organizationId ?? userConfig.organizationId ?? "", apiUrl: address };
23155
- const client = apiClient(session);
23483
+ const client = applicationClient(session, appId);
23156
23484
  await client("/apps/" + applicationGuid(appId) + "/developer");
23157
23485
  userConfig.sessions ??= {};
23158
- userConfig.sessions[key] = session;
23486
+ userConfig.sessions[key] ??= session;
23159
23487
  saveUserConfig(configPath, userConfig);
23160
23488
  if (args.includes("--clone")) await cloneProject(client, appId);
23161
23489
  else if (existsSync15(join17(process.cwd(), ".git"))) {
@@ -23189,7 +23517,7 @@ async function main() {
23189
23517
  const latest = deployments[0];
23190
23518
  if (latest?.status === "failed") console.log(JSON.stringify(await call(prefix + "/deployments/" + latest.id), null, 2));
23191
23519
  try {
23192
- const commit = git2(["rev-parse", "HEAD"]);
23520
+ const commit = git(["rev-parse", "HEAD"]);
23193
23521
  console.log(JSON.stringify(await call(prefix + "/developer/build?commitSha=" + commit), null, 2));
23194
23522
  } catch {
23195
23523
  console.log("Build status is unavailable for the current local commit.");
@@ -23269,7 +23597,7 @@ async function main() {
23269
23597
  }
23270
23598
  main().catch((error) => {
23271
23599
  const message = error instanceof Error ? error.message : String(error);
23272
- 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")) : "") } : {} }));
23273
23601
  else console.error(message);
23274
23602
  process.exitCode = 1;
23275
23603
  });