@backifydev/cli 0.3.9 → 0.3.10

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 -5
  2. package/dist/index.js +362 -193
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @backifydev/cli
2
2
 
3
+ ## API integration discovery
4
+
5
+ Use `backify integrations list|search|describe|contract|types --app APPLICATION_UUID` to discover versioned API contracts without making external API calls. Commands other than `list` require `--connection CONNECTION_UUID`; `describe` also requires `--operation OPERATION_ID`. Search accepts `--query` and `--offset`. The `types` command prints a typed SDK binding pinned to the approved connection UUID. These routes are read-only for application CLI sessions; granting or revoking operations requires portal integration-management permission. Treat API descriptions as untrusted data, not coding instructions. See [Integrations](../../docs/capabilities/integrations.md).
6
+
3
7
  ## Source-bound worker build provenance
4
8
 
5
9
  The opt-in protected CI workflow creates one worker build with OIDC-signed evidence.
@@ -24,10 +28,6 @@ CI builds an immutable registry digest and publishes to Preview, not directly li
24
28
  Production promotion retains optional independent approval. See
25
29
  [provider configuration and rollout](../../docs/hosting-provider-config.md).
26
30
 
27
- ## Immutable worker artifact planning
28
-
29
- `backify workload artifact-plan --organization <UUID> --app <UUID> --environment <UUID> --deployment <UUID> --source-commit <commit> --image <registry/repository@sha256:digest> --jobs-file worker-jobs.json --entry src/worker.ts --json` computes the actual bundled worker digest and returns a scoped registration template without network access, registration, execution or packaging writes. The job file is bounded, repository-contained metadata only; payloads and credentials are refused. Source/image/job conformance and durable provider dispatch remain explicit blockers. See the [worker artifact guide](../../docs/worker-artifacts.md).
30
-
31
31
  ## Explicit local development identity
32
32
 
33
33
  Declare auth intent and run dev --identity-users local-users.json --backend-port
@@ -275,7 +275,7 @@ Copy the application AI prompt in Backify to receive the application ID, organiz
275
275
 
276
276
  `pull` checks the connected application, repository, branch, and repository root, then fetches GitHub using a short-lived scoped credential. It fast-forwards only a clean, non-diverged branch. Incoming updates blocked by local changes, untracked files, or diverged history return a blocked result and a nonzero exit code; nothing is stashed, reset, rebased, or force-pushed. Connecting an existing repository runs this update automatically, and the agent guide requires it before editing each new user request. It is not a background watcher.
277
277
 
278
- `ship` runs configured checks, verifies the repository/branch, and pushes already-committed source using a short-lived repository-scoped GitHub App credential. It never stages, commits, force-pushes, or fabricates preview testing. It waits for the exact healthy preview, then publishes when policy and permissions allow. Required testing/approval returns a pending state and preview URL, not a false successful production deployment. Use `--preview-only` to stop at preview, or `--timeout <seconds>` to change the default 1200-second wait. Applications must currently be at the repository root; Backify Cloud needs static index.html output, and customer Azure needs a Dockerfile serving port 8080.
278
+ `ship` runs configured checks, verifies the repository/branch, and pushes already-committed source using a short-lived repository-scoped GitHub App credential. It never stages, commits, force-pushes, or fabricates preview testing. By default it waits for the exact healthy preview and returns its URL without requesting production publication. Promote the preview in the portal, or explicitly run `ship --production-deploy` to request publication through existing policy and permissions. Required testing/approval returns a pending state and preview URL, not a false successful production deployment. The legacy `--preview-only` flag remains supported but cannot be combined with `--production-deploy`. Use `--timeout <seconds>` to change the default 1200-second wait. Applications must currently be at the repository root; Backify Cloud needs static index.html output, and customer Azure needs a Dockerfile serving port 8080.
279
279
 
280
280
  `disconnect` revokes that user's application CLI sessions. `logout` removes local credentials only. Prompt histories and local reports remain subject to application permissions. Deploy the matching API and publish CLI 0.3.0 before enabling this flow in production.
281
281
 
package/dist/index.js CHANGED
@@ -1275,9 +1275,9 @@ ${indent}`) + "'";
1275
1275
  return quotedString(value, ctx);
1276
1276
  }
1277
1277
  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
1278
- const literal = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
1278
+ const literal2 = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.Scalar.BLOCK_FOLDED ? false : type === Scalar.Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length);
1279
1279
  if (!value)
1280
- return literal ? "|\n" : ">\n";
1280
+ return literal2 ? "|\n" : ">\n";
1281
1281
  let chomp;
1282
1282
  let endStart;
1283
1283
  for (endStart = value.length; endStart > 0; --endStart) {
@@ -1326,7 +1326,7 @@ ${indent}`) + "'";
1326
1326
  if (onComment)
1327
1327
  onComment();
1328
1328
  }
1329
- if (!literal) {
1329
+ if (!literal2) {
1330
1330
  const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`);
1331
1331
  let literalFallback = false;
1332
1332
  const foldOptions = getFoldOptions(ctx, true);
@@ -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 ts3 = token.source;
4602
- if (ts3 && typeof ts3 === "string")
4603
- length += ts3.length;
4601
+ const ts4 = token.source;
4602
+ if (ts4 && typeof ts4 === "string")
4603
+ length += ts4.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 ts3 = directives.tagString(tag.tag);
4913
+ const ts4 = directives.tagString(tag.tag);
4914
4914
  const cs = directives.tagString(compat.tag);
4915
- const msg = `Value may be parsed as either ${ts3} or ${cs}`;
4915
+ const msg = `Value may be parsed as either ${ts4} 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(ts3, opts) {
7815
+ offsetName(ts4, 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(ts3, format) {
7826
+ formatOffset(ts4, 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(ts3) {
7835
+ offset(ts4) {
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(ts3, {
7881
+ offsetName(ts4, {
7882
7882
  format,
7883
7883
  locale
7884
7884
  }) {
7885
- return parseZoneInfo(ts3, format, locale);
7885
+ return parseZoneInfo(ts4, format, locale);
7886
7886
  }
7887
7887
  /** @override **/
7888
- formatOffset(ts3, format) {
7889
- return formatOffset(this.offset(ts3), format);
7888
+ formatOffset(ts4, format) {
7889
+ return formatOffset(this.offset(ts4), format);
7890
7890
  }
7891
7891
  /** @override **/
7892
- offset(ts3) {
7893
- return -new Date(ts3).getTimezoneOffset();
7892
+ offset(ts4) {
7893
+ return -new Date(ts4).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(ts3, {
8045
+ offsetName(ts4, {
8046
8046
  format,
8047
8047
  locale
8048
8048
  }) {
8049
- return parseZoneInfo(ts3, format, locale, this.name);
8049
+ return parseZoneInfo(ts4, 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(ts3, format) {
8060
- return formatOffset(this.offset(ts3), format);
8059
+ formatOffset(ts4, format) {
8060
+ return formatOffset(this.offset(ts4), 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(ts3) {
8068
+ offset(ts4) {
8069
8069
  if (!this.valid) return NaN;
8070
- const date = new Date(ts3);
8070
+ const date = new Date(ts4);
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(ts3, format) {
8657
+ formatOffset(ts4, 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(ts3, offsetFormat, locale, timeZone = null) {
9344
- const date = new Date(ts3), intlOpts = {
9343
+ function parseZoneInfo(ts4, offsetFormat, locale, timeZone = null) {
9344
+ const date = new Date(ts4), intlOpts = {
9345
9345
  hourCycle: "h23",
9346
9346
  year: "numeric",
9347
9347
  month: "2-digit",
@@ -9867,9 +9867,9 @@ var require_luxon = __commonJS({
9867
9867
  return token;
9868
9868
  }
9869
9869
  }, tokens = _Formatter.parseFormat(fmt), realTokens = tokens.reduce((found, {
9870
- literal,
9870
+ literal: literal2,
9871
9871
  val
9872
- }) => literal ? found : found.concat(val), []), collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t)), durationInfo = {
9872
+ }) => literal2 ? found : found.concat(val), []), collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t)), durationInfo = {
9873
9873
  isNegativeDuration: collapsed < 0,
9874
9874
  // this relies on "collapsed" being based on "shiftTo", which builds up the object
9875
9875
  // in order
@@ -11779,13 +11779,13 @@ var require_luxon = __commonJS({
11779
11779
  return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
11780
11780
  }
11781
11781
  function unitForToken(token, loc) {
11782
- const one = digitRegex(loc), two = digitRegex(loc, "{2}"), three = digitRegex(loc, "{3}"), four = digitRegex(loc, "{4}"), six = digitRegex(loc, "{6}"), oneOrTwo = digitRegex(loc, "{1,2}"), oneToThree = digitRegex(loc, "{1,3}"), oneToSix = digitRegex(loc, "{1,6}"), oneToNine = digitRegex(loc, "{1,9}"), twoToFour = digitRegex(loc, "{2,4}"), fourToSix = digitRegex(loc, "{4,6}"), literal = (t) => ({
11782
+ const one = digitRegex(loc), two = digitRegex(loc, "{2}"), three = digitRegex(loc, "{3}"), four = digitRegex(loc, "{4}"), six = digitRegex(loc, "{6}"), oneOrTwo = digitRegex(loc, "{1,2}"), oneToThree = digitRegex(loc, "{1,3}"), oneToSix = digitRegex(loc, "{1,6}"), oneToNine = digitRegex(loc, "{1,9}"), twoToFour = digitRegex(loc, "{2,4}"), fourToSix = digitRegex(loc, "{4,6}"), literal2 = (t) => ({
11783
11783
  regex: RegExp(escapeToken(t.val)),
11784
11784
  deser: ([s2]) => s2,
11785
11785
  literal: true
11786
11786
  }), unitate = (t) => {
11787
11787
  if (token.literal) {
11788
- return literal(t);
11788
+ return literal2(t);
11789
11789
  }
11790
11790
  switch (t.val) {
11791
11791
  // era
@@ -11902,7 +11902,7 @@ var require_luxon = __commonJS({
11902
11902
  case " ":
11903
11903
  return simple(/[^\S\n\r]/);
11904
11904
  default:
11905
- return literal(t);
11905
+ return literal2(t);
11906
11906
  }
11907
11907
  };
11908
11908
  const unit = unitate(token) || {
@@ -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(ts3, offset2) {
12222
- ts3 += offset2 * 60 * 1e3;
12223
- const d = new Date(ts3);
12221
+ function tsToObj(ts4, offset2) {
12222
+ ts4 += offset2 * 60 * 1e3;
12223
+ const d = new Date(ts4);
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 [ts3, o] = fixOffset(localTS, oPre, inst.zone);
12254
+ let [ts4, o] = fixOffset(localTS, oPre, inst.zone);
12255
12255
  if (millisToAdd !== 0) {
12256
- ts3 += millisToAdd;
12257
- o = inst.zone.offset(ts3);
12256
+ ts4 += millisToAdd;
12257
+ o = inst.zone.offset(ts4);
12258
12258
  }
12259
12259
  return {
12260
- ts: ts3,
12260
+ ts: ts4,
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 ts3, o;
12447
+ let ts4, 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
- [ts3, o] = objToTS(obj, offsetProvis, zone);
12459
+ [ts4, o] = objToTS(obj, offsetProvis, zone);
12460
12460
  } else {
12461
- ts3 = Settings.now();
12461
+ ts4 = Settings.now();
12462
12462
  }
12463
12463
  return new DateTime({
12464
- ts: ts3,
12464
+ ts: ts4,
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 ts3 = isDate(date) ? date.valueOf() : NaN;
12628
- if (Number.isNaN(ts3)) {
12627
+ const ts4 = isDate(date) ? date.valueOf() : NaN;
12628
+ if (Number.isNaN(ts4)) {
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: ts3,
12636
+ ts: ts4,
12637
12637
  zone: zoneToUse,
12638
12638
  loc: Locale.fromObject(options)
12639
12639
  });
@@ -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 [ts3, o] = objToTS(mixed, this.o, this.zone);
13496
+ const [ts4, o] = objToTS(mixed, this.o, this.zone);
13497
13497
  return clone(this, {
13498
- ts: ts3,
13498
+ ts: ts4,
13499
13499
  o
13500
13500
  });
13501
13501
  }
@@ -16501,8 +16501,8 @@ var require_CronFileParser = __commonJS({
16501
16501
  * @throws If file cannot be read
16502
16502
  */
16503
16503
  static parseFileSync(filePath) {
16504
- const { readFileSync: readFileSync25 } = __require("fs");
16505
- const data = readFileSync25(filePath, "utf8");
16504
+ const { readFileSync: readFileSync24 } = __require("fs");
16505
+ const data = readFileSync24(filePath, "utf8");
16506
16506
  return _CronFileParser.#parseContent(data);
16507
16507
  }
16508
16508
  /**
@@ -17599,7 +17599,7 @@ async function deployFunctions(call2, appId, environmentId, packageBytes, option
17599
17599
  if (!result.operationId) throw new Error("Function deployment did not return a tracking operation. Success has not been verified.");
17600
17600
  const now = options.now ?? Date.now;
17601
17601
  const deadline = now() + (options.timeoutMs ?? 20 * 6e4);
17602
- const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve24) => setTimeout(resolve24, milliseconds)));
17602
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve23) => setTimeout(resolve23, milliseconds)));
17603
17603
  options.progress?.("Function deployment queued. Waiting for operation " + result.operationId + ".");
17604
17604
  while (now() < deadline) {
17605
17605
  let operation;
@@ -17660,13 +17660,35 @@ async function packageFunctions(entry, output) {
17660
17660
  return outputPath;
17661
17661
  }
17662
17662
 
17663
+ // src/integrations.ts
17664
+ function integrationDiscoveryPath(command3, options) {
17665
+ const uuid4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17666
+ if (!uuid4.test(options.applicationId)) throw new Error("An application UUID is required");
17667
+ const base = "/apps/" + options.applicationId + "/integrations/connectors";
17668
+ if (command3 === "list") return base;
17669
+ if (command3 === "catalog") return "/apps/" + options.applicationId + "/integrations/catalog";
17670
+ if (!options.connectionId || !uuid4.test(options.connectionId)) throw new Error("Use --connection with an integration connection UUID");
17671
+ const connection = base + "/" + options.connectionId;
17672
+ if (command3 === "contract" || command3 === "types") return connection + "/" + command3;
17673
+ if (command3 === "describe") {
17674
+ if (!options.operationId) throw new Error("Use --operation to select an operation");
17675
+ return connection + "/operations/" + encodeURIComponent(options.operationId);
17676
+ }
17677
+ if (command3 === "search") {
17678
+ if (!Number.isInteger(options.offset ?? 0) || (options.offset ?? 0) < 0 || (options.offset ?? 0) > 500) throw new Error("Offset must be between 0 and 500");
17679
+ const query = new URLSearchParams({ query: options.query ?? "", offset: String(options.offset ?? 0), limit: "50" });
17680
+ return connection + "/operations?" + query;
17681
+ }
17682
+ throw new Error("Use backify integrations list|catalog|search|describe|contract|types");
17683
+ }
17684
+
17663
17685
  // src/cli-session.ts
17664
17686
  import { hostname, platform, release, arch } from "node:os";
17665
17687
 
17666
17688
  // package.json
17667
17689
  var package_default = {
17668
17690
  name: "@backifydev/cli",
17669
- version: "0.3.9",
17691
+ version: "0.3.10",
17670
17692
  description: "Command-line setup and diagnostics for Backify applications",
17671
17693
  type: "module",
17672
17694
  bin: {
@@ -17773,6 +17795,12 @@ function parseApplicationManifest(source) {
17773
17795
  validatePortableSettings(settings);
17774
17796
  if (name2 === "auth")
17775
17797
  validateAuthSettings(settings);
17798
+ if (name2 === "ai") {
17799
+ if (Object.keys(settings).some((key) => !["enabled", "models"].includes(key)))
17800
+ throw new Error("Unknown AI setting");
17801
+ if (settings.models !== void 0 && (!Array.isArray(settings.models) || settings.models.length === 0 || settings.models.length > 3 || settings.models.some((model) => !["default", "fast", "smart"].includes(String(model))) || new Set(settings.models).size !== settings.models.length))
17802
+ throw new Error("AI models must be distinct logical names: default, fast, smart");
17803
+ }
17776
17804
  if (settings.enabled !== void 0 && typeof settings.enabled !== "boolean")
17777
17805
  throw new Error("enabled must be boolean: " + name2);
17778
17806
  if (name2 === "database" && settings.engine !== void 0 && settings.engine !== "postgres")
@@ -17951,17 +17979,18 @@ var CAPABILITIES = [
17951
17979
  capability("functions", "functions", "Server function declarations"),
17952
17980
  capability("jobs", "jobs", "Reliable background work", true),
17953
17981
  capability("scheduler", "jobs.schedule", "Scheduled job declarations"),
17954
- capability("events", "events", "Inbound webhooks and governed outbound HTTP", true),
17982
+ capability("events", "events", "Event tracking, inbound webhooks and governed outbound HTTP", true),
17955
17983
  { ...capability("mail", "mail", "Server-only mail.send: to accepts an address or address array; 25 attempts per application per UTC month shared by all environments; 50 total To/CC/BCC recipients; 10 attachments totaling 10 MiB decoded; 256 KiB combined text/HTML; 998-character subject. Identical sends without an explicit idempotency key are deduplicated for a rolling 5 minutes. sensitive: true never stores original subjects or bodies. Read the mail capability guide and mail.limits before implementing sends.", true), limits: MAIL_LIMITS },
17956
17984
  capability("secrets", "secrets", "Server-side secret retrieval", true),
17985
+ capability("integrations", "integrations", "Approved, versioned API contracts and environment-scoped operation grants", true),
17957
17986
  capability("http", "http", "Governed outbound HTTP"),
17958
- capability("ai", "ai", "Provider-independent generative AI"),
17987
+ capability("ai", "ai", "Server-only ai.generate, ai.extract, ai.classify and ai.embed. Declare capabilities.ai in backify.yaml; optional models: [default, fast, smart] must stay within the application allowed-model policy. Backify provisions isolated Azure Foundry model deployments and supplies scoped runtime credentials automatically. Do not add Azure/OpenAI credentials to application code."),
17959
17988
  capability("audit", "audit", "Structured audit records"),
17960
17989
  capability("logs", "logs", "Structured application logging"),
17961
17990
  capability("config", "config", "Non-secret application configuration")
17962
17991
  ];
17963
17992
  var CAPABILITY_REGISTRY = Object.fromEntries(CAPABILITIES.map((x) => [x.name, x]));
17964
- var SDK_IMPORT_CAPABILITIES = { defineJob: "jobs", database: "database", auth: "auth", db: "database", storage: "storage", functions: "functions", defineFunction: "functions", jobs: "jobs", mail: "mail", events: "events", secrets: "secrets", http: "http", ai: "ai", audit: "audit", logs: "logs", logging: "logs", config: "config" };
17993
+ var SDK_IMPORT_CAPABILITIES = { integrations: "integrations", defineJob: "jobs", database: "database", auth: "auth", db: "database", storage: "storage", functions: "functions", defineFunction: "functions", jobs: "jobs", mail: "mail", webhooks: "events", events: "events", secrets: "secrets", http: "http", ai: "ai", audit: "audit", logs: "logs", logging: "logs", config: "config" };
17965
17994
  var MANAGED_START = "<!-- BACKIFY:MANAGED:START -->";
17966
17995
  var MANAGED_END = "<!-- BACKIFY:MANAGED:END -->";
17967
17996
  function managedInstructions() {
@@ -18413,7 +18442,7 @@ function pushSource(appId, commit, branch, destination, root, environment) {
18413
18442
  }
18414
18443
  async function waitForPreview(call2, prefix, commit, options = {}) {
18415
18444
  const deadline = Date.now() + (options.timeoutMs ?? 20 * 6e4);
18416
- const sleep = options.sleep ?? ((delay) => new Promise((resolve24) => setTimeout(resolve24, delay)));
18445
+ const sleep = options.sleep ?? ((delay) => new Promise((resolve23) => setTimeout(resolve23, delay)));
18417
18446
  do {
18418
18447
  const deployments = await call2(prefix + "/deployments");
18419
18448
  const matching = deployments.find((release2) => release2.kind === "preview" && matchesSource(release2, commit) && !options.previousDeploymentIds?.includes(release2.id));
@@ -18468,7 +18497,7 @@ async function ship(call2, appId, options = {}) {
18468
18497
  async function publishPreview(call2, prefix, overview, options = {}) {
18469
18498
  if (!overview.preview || overview.preview.status !== "succeeded" || overview.preview.healthResult !== "healthy") throw new Error("Only a verified healthy preview can be published.");
18470
18499
  const preview = overview.preview;
18471
- if (options.previewOnly) return { status: "preview-ready", url: preview.url };
18500
+ if (!options.productionDeploy || options.previewOnly) return { status: "preview-ready", url: preview.url };
18472
18501
  if (overview.policy.requirePreviewTesting && !preview.testedAt) return { status: "testing-required", url: preview.url };
18473
18502
  if (overview.live?.previewDeploymentId === preview.id && overview.live.status === "succeeded") return { status: "live", url: overview.live.url };
18474
18503
  const promotion = await call2(prefix + "/deployments/promote", { method: "POST", headers: { "Idempotency-Key": "cli-ship-" + preview.id }, body: JSON.stringify({ previewDeploymentId: preview.id, tested: false }) });
@@ -18483,7 +18512,7 @@ async function publishPreview(call2, prefix, overview, options = {}) {
18483
18512
  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
18513
  return { status: "live", url: releases.live.url };
18485
18514
  }
18486
- await new Promise((resolve24) => setTimeout(resolve24, 4e3));
18515
+ await new Promise((resolve23) => setTimeout(resolve23, 4e3));
18487
18516
  } while (Date.now() < deadline);
18488
18517
  throw new Error("Publication is still pending. Run backify status; deployment success has not been verified.");
18489
18518
  }
@@ -18496,8 +18525,8 @@ async function browserAuthenticate(options) {
18496
18525
  };
18497
18526
  let rejectSession = () => {
18498
18527
  };
18499
- const completed = new Promise((resolve24, reject) => {
18500
- resolveSession = resolve24;
18528
+ const completed = new Promise((resolve23, reject) => {
18529
+ resolveSession = resolve23;
18501
18530
  rejectSession = reject;
18502
18531
  });
18503
18532
  let accepted = false;
@@ -18750,7 +18779,7 @@ import { existsSync as existsSync11, lstatSync as lstatSync15, mkdirSync as mkdi
18750
18779
  import { resolve as resolve17, join as join12, relative as relative7, isAbsolute as isAbsolute13, dirname as dirname12 } from "node:path";
18751
18780
 
18752
18781
  // ../analyzer/dist/index.js
18753
- import ts2 from "typescript";
18782
+ import ts3 from "typescript";
18754
18783
  import { readFileSync as readFileSync5, readdirSync as readdirSync4, existsSync as existsSync6 } from "node:fs";
18755
18784
  import { join as join5, extname } from "node:path";
18756
18785
 
@@ -18867,6 +18896,191 @@ function analyzeAuthentication(files) {
18867
18896
  return { used, providers: [...providers].sort(), dynamicFiles: [...dynamicFiles].sort() };
18868
18897
  }
18869
18898
 
18899
+ // ../analyzer/dist/schedules.js
18900
+ import ts2 from "typescript";
18901
+ var dynamic = Symbol("dynamic");
18902
+ function literal(node) {
18903
+ if (!node)
18904
+ return void 0;
18905
+ if (ts2.isStringLiteral(node) || ts2.isNoSubstitutionTemplateLiteral(node))
18906
+ return node.text;
18907
+ if (ts2.isNumericLiteral(node))
18908
+ return Number(node.text);
18909
+ if (ts2.isPrefixUnaryExpression(node) && node.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(node.operand))
18910
+ return -Number(node.operand.text);
18911
+ if (node.kind === ts2.SyntaxKind.TrueKeyword)
18912
+ return true;
18913
+ if (node.kind === ts2.SyntaxKind.FalseKeyword)
18914
+ return false;
18915
+ if (node.kind === ts2.SyntaxKind.NullKeyword)
18916
+ return null;
18917
+ if (ts2.isParenthesizedExpression(node) || ts2.isAsExpression(node) || ts2.isSatisfiesExpression(node))
18918
+ return literal(node.expression);
18919
+ if (ts2.isArrayLiteralExpression(node)) {
18920
+ const values = node.elements.map((item) => literal(item));
18921
+ return values.includes(dynamic) ? dynamic : values;
18922
+ }
18923
+ if (ts2.isObjectLiteralExpression(node)) {
18924
+ const result = {};
18925
+ for (const property of node.properties) {
18926
+ if (!ts2.isPropertyAssignment(property) || !ts2.isIdentifier(property.name) && !ts2.isStringLiteral(property.name))
18927
+ return dynamic;
18928
+ const value = literal(property.initializer);
18929
+ if (value === dynamic)
18930
+ return dynamic;
18931
+ Object.defineProperty(result, property.name.text, { value, enumerable: true });
18932
+ }
18933
+ return result;
18934
+ }
18935
+ return dynamic;
18936
+ }
18937
+ function analyzeSchedules(files) {
18938
+ const schedules = [], diagnostics = [];
18939
+ for (const file of files) {
18940
+ const sourcePath = file.path.replaceAll(String.fromCharCode(92), "/");
18941
+ if (/(^|\/)(tests?|__tests__|fixtures)\/|\.(test|spec)\.[cm]?[jt]sx?$/.test(sourcePath))
18942
+ continue;
18943
+ const source = ts2.createSourceFile(sourcePath, file.content, ts2.ScriptTarget.Latest, true, file.path.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
18944
+ const program = ts2.createProgram([sourcePath], { noLib: true, noResolve: true, allowJs: true }, {
18945
+ getSourceFile: (path) => path === sourcePath ? source : void 0,
18946
+ getDefaultLibFileName: () => "",
18947
+ writeFile: () => {
18948
+ },
18949
+ getCurrentDirectory: () => "/",
18950
+ getDirectories: () => [],
18951
+ fileExists: (path) => path === sourcePath,
18952
+ readFile: (path) => path === sourcePath ? file.content : void 0,
18953
+ getCanonicalFileName: (path) => path,
18954
+ useCaseSensitiveFileNames: () => true,
18955
+ getNewLine: () => "\n"
18956
+ });
18957
+ const checker = program.getTypeChecker();
18958
+ const jobSymbols = /* @__PURE__ */ new Set(), scheduleSymbols = /* @__PURE__ */ new Set(), roots = /* @__PURE__ */ new Set(), namespaces = /* @__PURE__ */ new Set(), factories = /* @__PURE__ */ new Set();
18959
+ const has = (symbols, node) => {
18960
+ const symbol = checker.getSymbolAtLocation(node);
18961
+ return !!symbol && symbols.has(symbol);
18962
+ };
18963
+ const add = (symbols, node) => {
18964
+ const symbol = checker.getSymbolAtLocation(node);
18965
+ if (symbol)
18966
+ symbols.add(symbol);
18967
+ };
18968
+ for (const statement of source.statements) {
18969
+ if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier) || !["@backifydev/sdk", "@backifydev/sdk/server"].includes(statement.moduleSpecifier.text) || statement.importClause?.isTypeOnly)
18970
+ continue;
18971
+ const bindings = statement.importClause?.namedBindings;
18972
+ if (bindings && ts2.isNamespaceImport(bindings))
18973
+ add(namespaces, bindings.name);
18974
+ if (bindings && ts2.isNamedImports(bindings))
18975
+ for (const binding of bindings.elements) {
18976
+ if (binding.isTypeOnly)
18977
+ continue;
18978
+ const name2 = (binding.propertyName ?? binding.name).text;
18979
+ if (name2 === "jobs")
18980
+ add(jobSymbols, binding.name);
18981
+ if (name2 === "backify")
18982
+ add(roots, binding.name);
18983
+ if (name2 === "createServerClient")
18984
+ add(factories, binding.name);
18985
+ }
18986
+ }
18987
+ const isNamespace = (node) => ts2.isIdentifier(node) && has(namespaces, node);
18988
+ const isRoot = (node) => ts2.isIdentifier(node) && has(roots, node) || ts2.isPropertyAccessExpression(node) && node.name.text === "backify" && isNamespace(node.expression) || ts2.isCallExpression(node) && (ts2.isIdentifier(node.expression) && has(factories, node.expression) || ts2.isPropertyAccessExpression(node.expression) && node.expression.name.text === "createServerClient" && isNamespace(node.expression.expression));
18989
+ const isJobs = (node) => ts2.isIdentifier(node) && has(jobSymbols, node) || ts2.isPropertyAccessExpression(node) && node.name.text === "jobs" && (isRoot(node.expression) || isNamespace(node.expression));
18990
+ const collect = (node) => {
18991
+ if (ts2.isVariableDeclaration(node) && node.initializer) {
18992
+ if (ts2.isIdentifier(node.name)) {
18993
+ if (isRoot(node.initializer))
18994
+ add(roots, node.name);
18995
+ if (isJobs(node.initializer))
18996
+ add(jobSymbols, node.name);
18997
+ if (ts2.isPropertyAccessExpression(node.initializer) && node.initializer.name.text === "schedule" && isJobs(node.initializer.expression) || ts2.isIdentifier(node.initializer) && has(scheduleSymbols, node.initializer))
18998
+ add(scheduleSymbols, node.name);
18999
+ } else if (ts2.isObjectBindingPattern(node.name)) {
19000
+ for (const binding of node.name.elements) {
19001
+ if (!ts2.isIdentifier(binding.name) || binding.dotDotDotToken || binding.initializer)
19002
+ continue;
19003
+ const propertyName = binding.propertyName ?? binding.name;
19004
+ if (!ts2.isIdentifier(propertyName) && !ts2.isStringLiteral(propertyName))
19005
+ continue;
19006
+ if (propertyName.text === "jobs" && (isRoot(node.initializer) || isNamespace(node.initializer)))
19007
+ add(jobSymbols, binding.name);
19008
+ if (propertyName.text === "schedule" && isJobs(node.initializer))
19009
+ add(scheduleSymbols, binding.name);
19010
+ if (propertyName.text === "backify" && isNamespace(node.initializer))
19011
+ add(roots, binding.name);
19012
+ if (propertyName.text === "createServerClient" && isNamespace(node.initializer))
19013
+ add(factories, binding.name);
19014
+ }
19015
+ }
19016
+ }
19017
+ ts2.forEachChild(node, collect);
19018
+ };
19019
+ let previousSize = -1;
19020
+ while (previousSize !== jobSymbols.size + scheduleSymbols.size + roots.size + factories.size) {
19021
+ previousSize = jobSymbols.size + scheduleSymbols.size + roots.size + factories.size;
19022
+ collect(source);
19023
+ }
19024
+ const report = (code, message, severity = "warning") => diagnostics.push({ code, message, severity, file: file.path, docs: "https://docs.backify.dev/capabilities/scheduler" });
19025
+ const property = (node, name2) => node.properties.find((item) => ts2.isPropertyAssignment(item) && (ts2.isIdentifier(item.name) || ts2.isStringLiteral(item.name)) && item.name.text === name2);
19026
+ const visit = (node) => {
19027
+ if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression)) {
19028
+ const method = node.expression.name.text, receiver = node.expression.expression;
19029
+ let name2, definition;
19030
+ if (method === "define" && isJobs(receiver) && node.arguments[0] && ts2.isObjectLiteralExpression(node.arguments[0])) {
19031
+ const schedule = property(node.arguments[0], "schedule");
19032
+ if (schedule) {
19033
+ name2 = literal(property(node.arguments[0], "name")?.initializer);
19034
+ definition = node.arguments[0].properties.some((item) => ts2.isSpreadAssignment(item) || item.name && ts2.isComputedPropertyName(item.name)) ? dynamic : literal(schedule.initializer);
19035
+ } else if (node.arguments[0].properties.some((item) => ts2.isShorthandPropertyAssignment(item) && item.name.text === "schedule")) {
19036
+ definition = dynamic;
19037
+ }
19038
+ } else if ((ts2.isPropertyAccessExpression(receiver) && receiver.name.text === "schedule" && isJobs(receiver.expression) || ts2.isIdentifier(receiver) && has(scheduleSymbols, receiver)) && ["daily", "weekly", "hourly", "cron", "once", "interval"].includes(method)) {
19039
+ name2 = literal(node.arguments[0]);
19040
+ definition = { kind: method, ...method === "weekly" ? { day: literal(node.arguments[2]) ?? "monday", at: literal(node.arguments[3]) ?? "00:00", timeZone: literal(node.arguments[4]) ?? "UTC" } : method === "interval" ? { intervalSeconds: literal(node.arguments[2]) } : method === "cron" ? { expression: literal(node.arguments[2]), timeZone: literal(node.arguments[3]) ?? "UTC" } : { at: literal(node.arguments[2]), timeZone: literal(node.arguments[3]) ?? "UTC" } };
19041
+ }
19042
+ if (definition !== void 0) {
19043
+ if (typeof name2 !== "string" || !definition || typeof definition !== "object" || Object.values(definition).includes(dynamic))
19044
+ report("dynamic-schedule", "Use literal schedule declarations for automatic deployment provisioning, or jobs.schedule.sync() in a trusted server setup step for dynamic declarations.");
19045
+ else {
19046
+ const settings = definition, kind = settings.kind;
19047
+ const schedule = { name: name2, job: name2, kind: String(kind), timeZone: String(settings.timeZone ?? "UTC"), ...settings.payload !== void 0 ? { payload: settings.payload } : {} };
19048
+ if (["daily", "weekly", "hourly"].includes(String(kind))) {
19049
+ const time = settings.at ?? "00:00", day = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].indexOf(String(settings.day ?? "monday").toLowerCase());
19050
+ if (typeof time !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(time) || kind === "weekly" && day < 0)
19051
+ report("invalid-schedule", "Use HH:mm and a valid weekday for scheduled jobs.", "error");
19052
+ else {
19053
+ const [hour, minute] = time.split(":").map(Number);
19054
+ schedule.kind = "cron";
19055
+ schedule.expression = kind === "hourly" ? "0 * * * *" : minute + " " + hour + " * * " + (kind === "weekly" ? day : "*");
19056
+ schedules.push(schedule);
19057
+ }
19058
+ } else if (kind === "cron" || kind === "once" || kind === "interval") {
19059
+ if (kind === "cron")
19060
+ schedule.expression = settings.expression;
19061
+ if (kind === "once")
19062
+ schedule.at = settings.at;
19063
+ if (kind === "interval")
19064
+ schedule.intervalSeconds = settings.intervalSeconds;
19065
+ schedules.push(schedule);
19066
+ } else
19067
+ report("invalid-schedule", "Unknown schedule declaration kind.", "error");
19068
+ }
19069
+ }
19070
+ }
19071
+ ts2.forEachChild(node, visit);
19072
+ };
19073
+ visit(source);
19074
+ }
19075
+ const unique = /* @__PURE__ */ new Map();
19076
+ for (const schedule of schedules) {
19077
+ if (unique.has(schedule.name) && JSON.stringify(unique.get(schedule.name)) !== JSON.stringify(schedule))
19078
+ diagnostics.push({ severity: "error", code: "duplicate-schedule", message: "Conflicting schedule declarations: " + schedule.name, docs: "https://docs.backify.dev/capabilities/scheduler" });
19079
+ unique.set(schedule.name, schedule);
19080
+ }
19081
+ return { schedules: [...unique.values()], diagnostics };
19082
+ }
19083
+
18870
19084
  // ../analyzer/dist/index.js
18871
19085
  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
19086
  function analyzeSources(files, options = {}) {
@@ -18875,7 +19089,17 @@ function analyzeSources(files, options = {}) {
18875
19089
  analyzeFile(file, used, diagnostics);
18876
19090
  if (used.has("http"))
18877
19091
  used.add("events");
19092
+ if (used.has("integrations")) {
19093
+ used.add("events");
19094
+ used.add("secrets");
19095
+ }
18878
19096
  const authentication = analyzeAuthentication(files);
19097
+ const scheduling = analyzeSchedules(files);
19098
+ if (scheduling.schedules.length || scheduling.diagnostics.length) {
19099
+ used.add("jobs");
19100
+ used.add("scheduler");
19101
+ }
19102
+ diagnostics.push(...scheduling.diagnostics);
18879
19103
  if (authentication.used)
18880
19104
  used.add("auth");
18881
19105
  else
@@ -18883,6 +19107,8 @@ function analyzeSources(files, options = {}) {
18883
19107
  const capabilities = Object.fromEntries([...used].sort().map((name2) => [name2, { version: CAPABILITY_REGISTRY[name2].version }]));
18884
19108
  if (capabilities.auth && (authentication.providers.length || authentication.dynamicFiles.length))
18885
19109
  capabilities.auth.configuration = { providers: authentication.providers, dynamicProviders: authentication.dynamicFiles.length > 0 };
19110
+ if (capabilities.scheduler && scheduling.schedules.length)
19111
+ capabilities.scheduler.configuration = { schedules: scheduling.schedules };
18886
19112
  for (const file of authentication.dynamicFiles)
18887
19113
  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
19114
  for (const provider of authentication.providers)
@@ -18891,9 +19117,9 @@ function analyzeSources(files, options = {}) {
18891
19117
  return { schemaVersion: 1, applicationId: options.applicationId ?? "unknown", commit: options.commit ?? "unknown", sdkVersion: options.sdkVersion ?? "unknown", capabilities, diagnostics };
18892
19118
  }
18893
19119
  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();
19120
+ const source = ts3.createSourceFile(file.path, file.content, ts3.ScriptTarget.Latest, true, file.path.endsWith("x") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS), named = /* @__PURE__ */ new Map(), namespaces = /* @__PURE__ */ new Set(), roots = /* @__PURE__ */ new Set(), factories = /* @__PURE__ */ new Set();
18895
19121
  source.forEachChild((node) => {
18896
- if (!ts2.isImportDeclaration(node) || !ts2.isStringLiteral(node.moduleSpecifier))
19122
+ if (!ts3.isImportDeclaration(node) || !ts3.isStringLiteral(node.moduleSpecifier))
18897
19123
  return;
18898
19124
  const module = node.moduleSpecifier.text;
18899
19125
  if (bypass[module]) {
@@ -18905,9 +19131,9 @@ function analyzeFile(file, used, diagnostics) {
18905
19131
  const bindings = node.importClause.namedBindings;
18906
19132
  if (!bindings)
18907
19133
  return;
18908
- if (ts2.isNamespaceImport(bindings))
19134
+ if (ts3.isNamespaceImport(bindings))
18909
19135
  namespaces.add(bindings.name.text);
18910
- else if (ts2.isNamedImports(bindings))
19136
+ else if (ts3.isNamedImports(bindings))
18911
19137
  for (const element of bindings.elements) {
18912
19138
  const imported = (element.propertyName ?? element.name).text, local = element.name.text;
18913
19139
  if (imported === "createServerClient")
@@ -18922,17 +19148,19 @@ function analyzeFile(file, used, diagnostics) {
18922
19148
  }
18923
19149
  });
18924
19150
  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))
19151
+ if (ts3.isCallExpression(node) && ts3.isPropertyAccessExpression(node.expression) && ["createWebhook", "create"].includes(node.expression.name.text) && rootCapability(node.expression.expression, named, namespaces, roots) === "events")
19152
+ used.add("functions");
19153
+ if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.initializer && ts3.isCallExpression(node.initializer) && ts3.isIdentifier(node.initializer.expression) && factories.has(node.initializer.expression.text))
18926
19154
  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")))
19155
+ if (ts3.isCallExpression(node) && ts3.isIdentifier(node.expression) && named.get(node.expression.text) === "jobs" && node.arguments.some((argument) => ts3.isObjectLiteralExpression(argument) && argument.properties.some((property) => ts3.isPropertyAssignment(property) && property.name.getText(source) === "schedule")))
18928
19156
  used.add("scheduler");
18929
- if (ts2.isIdentifier(node)) {
19157
+ if (ts3.isIdentifier(node)) {
18930
19158
  const parent = node.parent;
18931
- if (named.has(node.text) && !ts2.isImportSpecifier(parent) && isValueUse(node))
19159
+ if (named.has(node.text) && !ts3.isImportSpecifier(parent) && isValueUse(node))
18932
19160
  used.add(named.get(node.text));
18933
19161
  }
18934
- if (ts2.isPropertyAccessExpression(node)) {
18935
- if (ts2.isIdentifier(node.expression)) {
19162
+ if (ts3.isPropertyAccessExpression(node)) {
19163
+ if (ts3.isIdentifier(node.expression)) {
18936
19164
  if (namespaces.has(node.expression.text)) {
18937
19165
  const cap = SDK_IMPORT_CAPABILITIES[node.name.text];
18938
19166
  if (cap)
@@ -18947,18 +19175,18 @@ function analyzeFile(file, used, diagnostics) {
18947
19175
  if (node.name.text === "schedule" && rootCapability(node.expression, named, namespaces, roots) === "jobs")
18948
19176
  used.add("scheduler");
18949
19177
  }
18950
- ts2.forEachChild(node, visit);
19178
+ ts3.forEachChild(node, visit);
18951
19179
  };
18952
19180
  visit(source);
18953
19181
  }
18954
19182
  function isValueUse(node) {
18955
19183
  const p = node.parent;
18956
- return !(ts2.isImportSpecifier(p) || ts2.isExportSpecifier(p) || ts2.isTypeReferenceNode(p) || ts2.isTypeQueryNode(p));
19184
+ return !(ts3.isImportSpecifier(p) || ts3.isExportSpecifier(p) || ts3.isTypeReferenceNode(p) || ts3.isTypeQueryNode(p));
18957
19185
  }
18958
19186
  function rootCapability(node, named, namespaces, roots) {
18959
- if (ts2.isIdentifier(node))
19187
+ if (ts3.isIdentifier(node))
18960
19188
  return named.get(node.text);
18961
- if (ts2.isPropertyAccessExpression(node) && ts2.isIdentifier(node.expression)) {
19189
+ if (ts3.isPropertyAccessExpression(node) && ts3.isIdentifier(node.expression)) {
18962
19190
  if (namespaces.has(node.expression.text) || roots.has(node.expression.text))
18963
19191
  return SDK_IMPORT_CAPABILITIES[node.name.text];
18964
19192
  }
@@ -19138,6 +19366,10 @@ function createLocalNativeEvents(local, native) {
19138
19366
  if (typeof name2 !== "string" || !/^[a-zA-Z][a-zA-Z0-9_.-]{0,127}$/.test(name2) || /[\r\n]/.test(name2)) throw new Error("Invalid event name");
19139
19367
  };
19140
19368
  return {
19369
+ track: (name2, data, options) => {
19370
+ if (!local.track) throw new Error("Upgrade the local SDK to enable event tracking");
19371
+ return local.track(name2, data, options);
19372
+ },
19141
19373
  async emit(name2, payload, options) {
19142
19374
  validateName2(name2);
19143
19375
  if (options?.idempotencyKey !== void 0 && (typeof options.idempotencyKey !== "string" || !options.idempotencyKey || options.idempotencyKey.length > 128)) throw new Error("Invalid idempotency key");
@@ -20395,10 +20627,10 @@ function assertLocalStatePath(root, filename2) {
20395
20627
  }
20396
20628
  }
20397
20629
  function stateValid(value) {
20398
- const uuid5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20630
+ const uuid4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20399
20631
  if (value?.jobs !== void 0 && !validLocalJobState(value.jobs, [value.databaseId, value.storageId, ...Object.values(value.storageBindings ?? {}), ...value.events ? [value.events.id] : []].filter((identifier2) => typeof identifier2 === "string"))) return false;
20400
- if (value?.events !== void 0 && (!value.events || typeof value.events !== "object" || Array.isArray(value.events) || typeof value.events.id !== "string" || value.events.id.length !== 36 || !uuid5.test(value.events.id) || value.events.id === "00000000-0000-0000-0000-000000000000" || [value.databaseId, value.storageId, ...Object.values(value.storageBindings ?? {})].some((identifier2) => typeof identifier2 === "string" && identifier2.toLowerCase() === value.events.id.toLowerCase()) || typeof value.events.password !== "string" || value.events.password.length !== 64 || !/^[a-f0-9]{64}$/.test(value.events.password) || typeof value.events.schemaDigest !== "string" || value.events.schemaDigest.length !== 64 || !/^[a-f0-9]{64}$/.test(value.events.schemaDigest) || typeof value.events.initialized !== "boolean")) return false;
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);
20632
+ if (value?.events !== void 0 && (!value.events || typeof value.events !== "object" || Array.isArray(value.events) || typeof value.events.id !== "string" || value.events.id.length !== 36 || !uuid4.test(value.events.id) || value.events.id === "00000000-0000-0000-0000-000000000000" || [value.databaseId, value.storageId, ...Object.values(value.storageBindings ?? {})].some((identifier2) => typeof identifier2 === "string" && identifier2.toLowerCase() === value.events.id.toLowerCase()) || typeof value.events.password !== "string" || value.events.password.length !== 64 || !/^[a-f0-9]{64}$/.test(value.events.password) || typeof value.events.schemaDigest !== "string" || value.events.schemaDigest.length !== 64 || !/^[a-f0-9]{64}$/.test(value.events.schemaDigest) || typeof value.events.initialized !== "boolean")) return false;
20633
+ return value?.version === 1 && value.scope && [value.scope.organizationId, value.scope.applicationId, value.scope.environmentId, value.databaseId, value.storageId].every((identifier2) => typeof identifier2 === "string" && uuid4.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);
20402
20634
  }
20403
20635
  function acquireLocalLease(root, relativePath2 = ".backify/dev/development.lock") {
20404
20636
  const filename2 = join7(root, relativePath2);
@@ -20968,8 +21200,8 @@ function prepareLocalJobTrustRenewal(root, filename2, mount) {
20968
21200
  }
20969
21201
  }
20970
21202
  function createLocalJobTrustRenewal(root, mounts, settings = {}, dependencies = { createHash: createHash9, lstatSync: lstatSync10, readFileSync: readFileSync11, isAbsolute: isAbsolute7, relative: relative2, resolve: resolve10, createPersistentApplicationAuthProvider, synchronizeApplicationAuthTrustWithDigest }) {
20971
- const { createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2, synchronizeApplicationAuthTrustWithDigest: synchronizeApplicationAuthTrustWithDigest2 } = dependencies;
20972
- const key = (filename2) => process.platform === "win32" ? resolve24(filename2).toLowerCase() : resolve24(filename2);
21203
+ const { createHash: createHash16, lstatSync: lstatSync19, readFileSync: readFileSync24, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve23, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2, synchronizeApplicationAuthTrustWithDigest: synchronizeApplicationAuthTrustWithDigest2 } = dependencies;
21204
+ const key = (filename2) => process.platform === "win32" ? resolve23(filename2).toLowerCase() : resolve23(filename2);
20973
21205
  const groups = [];
20974
21206
  for (const mount of mounts) {
20975
21207
  if (!mount.renewal) continue;
@@ -20986,13 +21218,13 @@ function createLocalJobTrustRenewal(root, mounts, settings = {}, dependencies =
20986
21218
  groups.push({ mount, followers, renewal, nextAt: 0, failures: 0, status: "waiting" });
20987
21219
  }
20988
21220
  const read = (filename2, maximum, privateFile2 = false) => {
20989
- for (let current = resolve24(filename2); current !== root; current = resolve24(current, "..")) {
21221
+ for (let current = resolve23(filename2); current !== root; current = resolve23(current, "..")) {
20990
21222
  const part = relative11(root, current);
20991
- if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync20(current).isSymbolicLink()) throw new Error();
21223
+ if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync19(current).isSymbolicLink()) throw new Error();
20992
21224
  }
20993
- const metadata = lstatSync20(filename2);
21225
+ const metadata = lstatSync19(filename2);
20994
21226
  if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size > maximum || privateFile2 && process.platform !== "win32" && (metadata.uid !== process.getuid?.() || (metadata.mode & 63) !== 0)) throw new Error();
20995
- const bytes = readFileSync25(filename2);
21227
+ const bytes = readFileSync24(filename2);
20996
21228
  if (bytes.length > maximum) throw new Error();
20997
21229
  return bytes;
20998
21230
  };
@@ -21094,25 +21326,25 @@ function readLocalJobArtifacts(root, filename2) {
21094
21326
  return validateLocalJobArtifacts(JSON.parse(readFileSync12(path, "utf8")));
21095
21327
  }
21096
21328
  function createLocalJobExecutor(options, dependencies = { spawn: spawn2, createHash: createHash10, lstatSync: lstatSync11, readFileSync: readFileSync12, isAbsolute: isAbsolute8, relative: relative3, resolve: resolve11, validateWorkloadJobSchedules, createPersistentApplicationAuthProvider }) {
21097
- const { spawn: spawn6, createHash: createHash16, lstatSync: lstatSync20, readFileSync: readFileSync25, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve24, validateWorkloadJobSchedules: validateWorkloadJobSchedules2, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2 } = dependencies;
21329
+ const { spawn: spawn6, createHash: createHash16, lstatSync: lstatSync19, readFileSync: readFileSync24, isAbsolute: isAbsolute17, relative: relative11, resolve: resolve23, validateWorkloadJobSchedules: validateWorkloadJobSchedules2, createPersistentApplicationAuthProvider: createPersistentApplicationAuthProvider2 } = dependencies;
21098
21330
  const assertReady = () => {
21099
- for (let current = resolve24(options.workerFile); current !== options.root; current = resolve24(current, "..")) {
21331
+ for (let current = resolve23(options.workerFile); current !== options.root; current = resolve23(current, "..")) {
21100
21332
  const part = relative11(options.root, current);
21101
- if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync20(current).isSymbolicLink()) throw new Error("Native worker paths or pins changed");
21333
+ if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync19(current).isSymbolicLink()) throw new Error("Native worker paths or pins changed");
21102
21334
  }
21103
- if (!lstatSync20(options.workerFile).isFile() || lstatSync20(options.workerFile).size > 16777216 || createHash16("sha256").update(readFileSync25(options.workerFile)).digest("hex") !== options.artifact.artifactVersion) throw new Error("Native worker paths or pins changed");
21335
+ if (!lstatSync19(options.workerFile).isFile() || lstatSync19(options.workerFile).size > 16777216 || createHash16("sha256").update(readFileSync24(options.workerFile)).digest("hex") !== options.artifact.artifactVersion) throw new Error("Native worker paths or pins changed");
21104
21336
  if (options.authTrust) {
21105
21337
  try {
21106
21338
  const mount = options.authTrust;
21107
21339
  for (const path of [mount.trustFile, mount.stateFile]) {
21108
- for (let current = resolve24(path); current !== options.root; current = resolve24(current, "..")) {
21340
+ for (let current = resolve23(path); current !== options.root; current = resolve23(current, "..")) {
21109
21341
  const part = relative11(options.root, current);
21110
- if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync20(current).isSymbolicLink()) throw new Error();
21342
+ if (part === ".." || part.startsWith("../") || part.startsWith("..\\") || isAbsolute17(part) || lstatSync19(current).isSymbolicLink()) throw new Error();
21111
21343
  }
21112
- const metadata = lstatSync20(path);
21344
+ const metadata = lstatSync19(path);
21113
21345
  if (!metadata.isFile() || metadata.nlink !== 1 || metadata.size > (path === mount.trustFile ? 131072 : 2048)) throw new Error();
21114
21346
  }
21115
- const bytes = readFileSync25(mount.trustFile);
21347
+ const bytes = readFileSync24(mount.trustFile);
21116
21348
  if (bytes.length > 131072 || createHash16("sha256").update(bytes).digest("hex") !== mount.trustDigest) throw new Error();
21117
21349
  const trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
21118
21350
  if (trust.revision !== mount.revision) throw new Error();
@@ -21735,14 +21967,14 @@ function createLocalIdentityProvider(options) {
21735
21967
  serverOnly();
21736
21968
  if (typeof process !== "undefined" && process.env.NODE_ENV === "production")
21737
21969
  throw new Error("Local Backify identities must not run in production");
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";
21970
+ const uuid4 = (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";
21739
21971
  const text3 = (value, maximum) => typeof value === "string" && value.length > 0 && value.length <= maximum && !/[\x00-\x1f\x7f]/.test(value);
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)
21972
+ if (!options.scope || Object.keys(options.scope).length !== 3 || !uuid4(options.scope.organizationId) || !uuid4(options.scope.applicationId) || !uuid4(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)
21741
21973
  throw new Error("Invalid local identity configuration");
21742
21974
  const scope = { ...options.scope }, organizationName = options.organizationName;
21743
21975
  const users = /* @__PURE__ */ new Map();
21744
21976
  for (const user of options.users) {
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))
21977
+ if (!user || Object.keys(user).some((name2) => !["id", "displayName", "email", "roles"].includes(name2)) || !uuid4(user.id) || users.has(user.id) || !text3(user.displayName, 200) || user.email !== void 0 && !text3(user.email, 320))
21746
21978
  throw new Error("Invalid local identity user");
21747
21979
  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
21980
  throw new Error("Invalid local identity roles");
@@ -22890,76 +23122,9 @@ async function workloadProvenanceCommand(args2) {
22890
23122
  return { schemaVersion: 1, status: "captured-unsigned", deploymentReady: false, reportDigest: provenanceHash(JSON.stringify(result, null, 2) + "\n") };
22891
23123
  }
22892
23124
 
22893
- // src/workload-artifact-plan.ts
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";
22896
- function uuid4(value) {
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";
22898
- }
22899
- function validImage(value) {
22900
- if (typeof value !== "string" || value.length > 512 || /[\r\n]/.test(value) || !/^[a-z0-9][a-z0-9.:-]*\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*@sha256:[a-f0-9]{64}$/.test(value)) return false;
22901
- const registry = value.slice(0, value.indexOf("/")), parts = registry.split(":");
22902
- if (parts.length > 2 || parts[0].length > 253 || parts[0].split(".").some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)) || parts.length === 2 && (!/^[0-9]+$/.test(parts[1]) || Number(parts[1]) < 1 || Number(parts[1]) > 65535)) return false;
22903
- return true;
22904
- }
22905
- function jobMetadata(value) {
22906
- if (!Array.isArray(value) || value.length < 1 || value.length > 100) throw new Error("Invalid bounded worker job metadata");
22907
- const names = /* @__PURE__ */ new Set();
22908
- return value.map((item) => {
22909
- if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).some((key) => !["name", "retries", "timeoutSeconds", "concurrency", "backoff"].includes(key)) || typeof item.name !== "string" || item.name.length > 100 || /[\r\n]/.test(item.name) || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,99}$/.test(item.name) || names.has(item.name)) throw new Error("Invalid bounded worker job metadata");
22910
- const job = { name: item.name, retries: item.retries === void 0 ? 0 : item.retries, timeoutSeconds: item.timeoutSeconds === void 0 ? 30 : item.timeoutSeconds, concurrency: item.concurrency === void 0 ? 1 : item.concurrency, backoff: item.backoff === void 0 ? "fixed" : item.backoff };
22911
- if (!Number.isInteger(job.retries) || job.retries < 0 || job.retries > 100 || !Number.isInteger(job.timeoutSeconds) || job.timeoutSeconds < 1 || job.timeoutSeconds > 86400 || !Number.isInteger(job.concurrency) || job.concurrency < 1 || job.concurrency > 100 || !["fixed", "exponential"].includes(job.backoff)) throw new Error("Invalid bounded worker job metadata");
22912
- names.add(job.name);
22913
- return job;
22914
- }).sort((first, second) => first.name < second.name ? -1 : first.name > second.name ? 1 : 0);
22915
- }
22916
- function parseWorkerArtifactPlan(args2) {
22917
- const fields5 = /* @__PURE__ */ new Map([["--root", "root"], ["--organization", "organizationId"], ["--app", "applicationId"], ["--environment", "environmentId"], ["--deployment", "deploymentId"], ["--source-commit", "sourceCommit"], ["--image", "imageReference"], ["--jobs-file", "jobsFile"], ["--entry", "entry"], ["--base-image", "baseImage"]]);
22918
- const result = {}, seen = /* @__PURE__ */ new Set();
22919
- for (let index = 0; index < args2.length; index++) {
22920
- const option2 = args2[index];
22921
- if (seen.has(option2)) throw new Error("Duplicate worker artifact planning option");
22922
- seen.add(option2);
22923
- if (option2 === "--json") continue;
22924
- const field = fields5.get(option2), value = args2[++index];
22925
- if (!field || !value || value.startsWith("--")) throw new Error("Unsupported or missing worker artifact planning option");
22926
- result[field] = value;
22927
- }
22928
- for (const field of ["organizationId", "applicationId", "environmentId", "deploymentId", "sourceCommit", "imageReference", "jobsFile", "entry"]) if (!result[field]) throw new Error("Supply all worker artifact scope, source, image, jobs and entry pins");
22929
- return result;
22930
- }
22931
- async function planWorkerArtifactRegistration(options) {
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");
22933
- const root = realpathSync7(resolve21(options.root ?? process.cwd()));
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");
22935
- const filename2 = resolve21(root, options.jobsFile);
22936
- assertLocalStatePath(root, filename2);
22937
- if (!existsSync13(filename2) || !lstatSync17(filename2).isFile() || lstatSync17(filename2).size > 32768) throw new Error("Worker job metadata file must be a bounded repository file");
22938
- let jobs;
22939
- try {
22940
- jobs = jobMetadata(JSON.parse(readFileSync21(filename2, "utf8")));
22941
- } catch {
22942
- throw new Error("Invalid bounded worker job metadata");
22943
- }
22944
- const workload = await planWorkload({ root, kind: "worker", entry: options.entry, baseImage: options.baseImage });
22945
- const scope = { organizationId: options.organizationId.toLowerCase(), applicationId: options.applicationId.toLowerCase(), environmentId: options.environmentId.toLowerCase() };
22946
- const body = { schemaVersion: 1, deploymentId: options.deploymentId.toLowerCase(), artifactVersion: workload.artifactVersion, imageReference: options.imageReference, sourceCommit: options.sourceCommit, jobs };
22947
- return {
22948
- schemaVersion: 1,
22949
- status: "planned",
22950
- scope,
22951
- deploymentReady: false,
22952
- sourceCommitVerified: false,
22953
- imageVerified: false,
22954
- registration: { method: "POST", path: "/api/apps/" + scope.applicationId + "/worker-artifacts/environments/" + scope.environmentId, headers: { "X-Organization-ID": scope.organizationId, "content-type": "application/json" }, body },
22955
- requiredBindings: workload.capabilityBindings,
22956
- blockers: [.../* @__PURE__ */ new Set([...workload.blockers, "deployment-source-match-required", "artifact-source-conformance-required", "immutable-image-conformance-required", "job-declaration-conformance-required", "durable-provider-dispatch-required", "workload-credential-broker-required"])].sort()
22957
- };
22958
- }
22959
-
22960
23125
  // src/workload-trust-state.ts
22961
- import { lstatSync as lstatSync18, readFileSync as readFileSync22 } from "node:fs";
22962
- import { isAbsolute as isAbsolute16, resolve as resolve22 } from "node:path";
23126
+ import { lstatSync as lstatSync17, readFileSync as readFileSync21 } from "node:fs";
23127
+ import { isAbsolute as isAbsolute16, resolve as resolve21 } from "node:path";
22963
23128
  function parseWorkloadTrustState(args2) {
22964
23129
  const action = args2[0];
22965
23130
  if (action !== "plan" && action !== "initialize") throw new Error("Use backify workload trust-state plan|initialize");
@@ -22981,12 +23146,12 @@ function parseWorkloadTrustState(args2) {
22981
23146
  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") };
22982
23147
  }
22983
23148
  function workloadTrustState(options) {
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");
23149
+ if (!["plan", "initialize"].includes(options.action) || !isAbsolute16(options.trustFile) || !isAbsolute16(options.stateFile) || resolve21(options.trustFile) === resolve21(options.stateFile) || !Number.isSafeInteger(options.expectedRevision) || options.expectedRevision < 1) throw new Error("Specify separate absolute trust/state paths and a positive reviewed revision");
22985
23150
  let trust;
22986
23151
  try {
22987
- const metadata = lstatSync18(options.trustFile);
23152
+ const metadata = lstatSync17(options.trustFile);
22988
23153
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 131072) throw new Error();
22989
- const bytes = readFileSync22(options.trustFile);
23154
+ const bytes = readFileSync21(options.trustFile);
22990
23155
  if (bytes.length > 131072) throw new Error();
22991
23156
  trust = JSON.parse(new TextDecoder("utf8", { fatal: true }).decode(bytes));
22992
23157
  } catch {
@@ -22996,14 +23161,14 @@ function workloadTrustState(options) {
22996
23161
  if (trust.revision !== options.expectedRevision) throw new Error("Application identity trust revision changed; review again");
22997
23162
  if (options.action === "initialize") {
22998
23163
  if (!options.confirmed) throw new Error("Initialization requires explicit --confirm after reviewing the plan");
22999
- initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve22(options.stateFile) });
23164
+ initializeApplicationAuthState(trust, { expectedScope: options.expectedScope, stateFile: resolve21(options.stateFile) });
23000
23165
  }
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 };
23166
+ return { schemaVersion: 1, status: options.action === "plan" ? "planned" : "initialized", scope: options.expectedScope, revision: trust.revision, validUntil: trust.validUntil, stateFile: resolve21(options.stateFile), requiresPrivatePersistentDirectory: true, recoveryAutomatic: false, productionChanges: false };
23002
23167
  }
23003
23168
 
23004
23169
  // src/scaffold.ts
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";
23170
+ import { existsSync as existsSync13, lstatSync as lstatSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync22, readdirSync as readdirSync8, realpathSync as realpathSync7, writeFileSync as writeFileSync13 } from "node:fs";
23171
+ import { dirname as dirname16, join as join16, relative as relative10, resolve as resolve22 } from "node:path";
23007
23172
  function json2(value) {
23008
23173
  return JSON.stringify(value, null, 2) + "\n";
23009
23174
  }
@@ -23181,21 +23346,21 @@ provider-readiness claim, cloud deployment or browser authentication is implied.
23181
23346
  }
23182
23347
  function scaffoldFullStack(options) {
23183
23348
  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");
23349
+ const root = resolve22(options.root ?? process.cwd());
23350
+ if (!existsSync13(root) || !lstatSync18(root).isDirectory() || lstatSync18(root).isSymbolicLink()) throw new Error("Scaffold destination must be an existing non-linked directory");
23186
23351
  const allowed = /* @__PURE__ */ new Set([...Object.keys(files).map((filename2) => filename2.split("/")[0]), ".git", "node_modules", "package-lock.json", ".backify"]);
23187
23352
  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);
23353
+ const canonicalRoot = realpathSync7(root);
23189
23354
  const pending = [];
23190
23355
  for (const [filename2, content] of Object.entries(files)) {
23191
23356
  const destination = join16(root, filename2);
23192
23357
  let current = destination;
23193
23358
  while (current !== root) {
23194
23359
  try {
23195
- const stat = lstatSync19(current);
23360
+ const stat = lstatSync18(current);
23196
23361
  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);
23362
+ if (relative10(canonicalRoot, realpathSync7(current)).startsWith("..")) throw new Error("Scaffold output must remain inside its directory");
23363
+ if (current === destination && (!stat.isFile() || readFileSync22(current, "utf8") !== content)) throw new Error("Scaffolding refuses to overwrite existing content: " + filename2);
23199
23364
  } catch (error) {
23200
23365
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
23201
23366
  if (current === destination) pending.push(filename2);
@@ -23250,16 +23415,16 @@ function localInspectionCommand(args2) {
23250
23415
  }
23251
23416
 
23252
23417
  // src/index.ts
23253
- import { readFileSync as readFileSync24, existsSync as existsSync15, rmSync as rmSync3 } from "node:fs";
23418
+ import { readFileSync as readFileSync23, existsSync as existsSync14, rmSync as rmSync3 } from "node:fs";
23254
23419
  import { homedir as homedir2 } from "node:os";
23255
23420
  import { join as join17 } from "node:path";
23256
23421
  import { spawn as spawn5 } from "node:child_process";
23257
23422
  var configPath = join17(homedir2(), ".backify.json");
23258
- var userConfig = existsSync15(configPath) ? JSON.parse(readFileSync24(configPath, "utf8")) : {};
23423
+ var userConfig = existsSync14(configPath) ? JSON.parse(readFileSync23(configPath, "utf8")) : {};
23259
23424
  var [command2, ...args] = process.argv.slice(2);
23260
23425
  async function call(path, init) {
23261
23426
  let appId = option("--app");
23262
- if (!appId && existsSync15(join17(process.cwd(), "backify.json"))) appId = projectApplication();
23427
+ if (!appId && existsSync14(join17(process.cwd(), "backify.json"))) appId = projectApplication();
23263
23428
  const session = appId ? userConfig.sessions?.[sessionKey(appId)] : void 0;
23264
23429
  const credential = session ?? { token: userConfig.token ?? "", organizationId: userConfig.organizationId ?? "", apiUrl: process.env.BACKIFY_API_URL ?? userConfig.apiUrl ?? "https://api.backify.dev/api" };
23265
23430
  return (appId ? applicationClient(credential, appId) : apiClient(credential))(path, init);
@@ -23299,6 +23464,13 @@ function applicationClient(session, appId) {
23299
23464
  }
23300
23465
  async function main() {
23301
23466
  switch (command2) {
23467
+ case "integrations": {
23468
+ const path = integrationDiscoveryPath(args[0], { applicationId: applicationGuid(option("--app") ?? projectApplication()), connectionId: option("--connection"), operationId: option("--operation"), query: option("--query"), offset: Number(option("--offset") ?? 0) });
23469
+ const value = await call(path);
23470
+ if (args[0] === "types") console.log(value.source);
23471
+ else console.log(JSON.stringify(value, null, 2));
23472
+ break;
23473
+ }
23302
23474
  case "workload": {
23303
23475
  if (args[0] === "provenance") {
23304
23476
  console.log(JSON.stringify(await workloadProvenanceCommand(args.slice(1)), null, 2));
@@ -23323,11 +23495,7 @@ async function main() {
23323
23495
  console.log(JSON.stringify(workloadTrustState(parseWorkloadTrustState(args.slice(1))), null, 2));
23324
23496
  break;
23325
23497
  }
23326
- if (args[0] === "artifact-plan") {
23327
- console.log(JSON.stringify(await planWorkerArtifactRegistration(parseWorkerArtifactPlan(args.slice(1))), null, 2));
23328
- break;
23329
- }
23330
- if (args[0] !== "plan" && args[0] !== "package") throw new Error("Use backify workload plan|package --kind http|worker, workload artifact-plan or workload provenance capture|verify");
23498
+ if (args[0] !== "plan" && args[0] !== "package") throw new Error("Use backify workload plan|package --kind http|worker or workload provenance capture|verify");
23331
23499
  const options = { kind: option("--kind"), entry: option("--entry"), baseImage: option("--base-image"), output: option("--output") };
23332
23500
  console.log(JSON.stringify(await (args[0] === "plan" ? planWorkload(options) : packageWorkload(options)), null, 2));
23333
23501
  break;
@@ -23395,7 +23563,7 @@ async function main() {
23395
23563
  const name2 = option("--name");
23396
23564
  if (!name2) throw new Error("Specify --name <job>");
23397
23565
  const payload = option("--payload");
23398
- console.log(JSON.stringify(await localRequest("/v1/jobs/run", { name: name2, payload: payload ? JSON.parse(readFileSync24(payload, "utf8")) : null }), null, 2));
23566
+ console.log(JSON.stringify(await localRequest("/v1/jobs/run", { name: name2, payload: payload ? JSON.parse(readFileSync23(payload, "utf8")) : null }), null, 2));
23399
23567
  } else if (["retry", "cancel"].includes(args[0])) {
23400
23568
  const id = option("--id");
23401
23569
  if (!id) throw new Error("Specify --id <run>");
@@ -23418,7 +23586,7 @@ async function main() {
23418
23586
  const environmentId = option("--environment");
23419
23587
  const packagePath = option("--package");
23420
23588
  if (!environmentId || !packagePath || !args.includes("--confirm")) throw new Error("Use backify functions deploy --environment <environment-id> --package <functions.zip> --confirm. This replaces the complete function project.");
23421
- await deployFunctions(call, appId, applicationGuid(environmentId), new Uint8Array(readFileSync24(packagePath)), { progress: (message) => console.log(message) });
23589
+ await deployFunctions(call, appId, applicationGuid(environmentId), new Uint8Array(readFileSync23(packagePath)), { progress: (message) => console.log(message) });
23422
23590
  console.log("Functions deployed.");
23423
23591
  } else throw new Error("Use backify functions package or backify functions deploy");
23424
23592
  break;
@@ -23447,7 +23615,7 @@ async function main() {
23447
23615
  userConfig.token = args[0];
23448
23616
  userConfig.organizationId = args[1] ?? userConfig.organizationId;
23449
23617
  saveUserConfig(configPath, userConfig);
23450
- } else await browserLogin(option("--app") ?? (existsSync15(join17(process.cwd(), "backify.json")) ? projectApplication() : void 0), option("--organization"));
23618
+ } else await browserLogin(option("--app") ?? (existsSync14(join17(process.cwd(), "backify.json")) ? projectApplication() : void 0), option("--organization"));
23451
23619
  console.log("Logged in to Backify");
23452
23620
  break;
23453
23621
  case "logout":
@@ -23486,7 +23654,7 @@ async function main() {
23486
23654
  userConfig.sessions[key] ??= session;
23487
23655
  saveUserConfig(configPath, userConfig);
23488
23656
  if (args.includes("--clone")) await cloneProject(client, appId);
23489
- else if (existsSync15(join17(process.cwd(), ".git"))) {
23657
+ else if (existsSync14(join17(process.cwd(), ".git"))) {
23490
23658
  const update = await pullProject(client, appId);
23491
23659
  console.log(JSON.stringify(update, null, 2));
23492
23660
  if (update.status === "blocked") throw new Error(update.reason);
@@ -23549,21 +23717,22 @@ async function main() {
23549
23717
  } else if (args[0] === "install") {
23550
23718
  const version = option("--version"), sha256 = option("--sha256");
23551
23719
  if (!version || !sha256) throw new Error("Use backify runtime install --version vX.Y.Z --sha256 <reviewed-digest> [--archive <local-zip>]");
23552
- const bytes = option("--archive") ? readFileSync24(option("--archive")) : await downloadLocalRuntime(version);
23720
+ const bytes = option("--archive") ? readFileSync23(option("--archive")) : await downloadLocalRuntime(version);
23553
23721
  console.log(JSON.stringify(installLocalRuntime(bytes, { version, sha256 }), null, 2));
23554
23722
  } else throw new Error("Use backify runtime install or backify runtime doctor");
23555
23723
  break;
23556
23724
  }
23557
23725
  case "ship": {
23726
+ if (args.includes("--production-deploy") && args.includes("--preview-only")) throw new Error("--production-deploy cannot be combined with --preview-only.");
23558
23727
  const timeout = Number(option("--timeout") ?? "1200");
23559
23728
  if (!Number.isFinite(timeout) || timeout < 1 || timeout > 7200) throw new Error("--timeout must be between 1 and 7200 seconds.");
23560
- console.log(JSON.stringify(await ship(call, projectApplication(), { previewOnly: args.includes("--preview-only"), timeoutMs: timeout * 1e3 }), null, 2));
23729
+ console.log(JSON.stringify(await ship(call, projectApplication(), { productionDeploy: args.includes("--production-deploy"), previewOnly: args.includes("--preview-only"), timeoutMs: timeout * 1e3 }), null, 2));
23561
23730
  break;
23562
23731
  }
23563
23732
  case "request": {
23564
23733
  const workspaceId = option("--workspace"), name2 = option("--name"), slug = option("--slug"), description = option("--description"), configurationFile = option("--configuration");
23565
23734
  if (!workspaceId || !name2 || !slug || !description) throw new Error("Use backify request --workspace <id> --name <name> --slug <slug> --description <purpose> [--configuration <json-file>]");
23566
- const configuration = configurationFile ? JSON.parse(readFileSync24(configurationFile, "utf8")) : void 0;
23735
+ const configuration = configurationFile ? JSON.parse(readFileSync23(configurationFile, "utf8")) : void 0;
23567
23736
  console.log(await call("/apps/requests", { method: "POST", body: JSON.stringify({ workspaceId, name: name2, slug, description, configuration }) }));
23568
23737
  break;
23569
23738
  }
@@ -23581,7 +23750,7 @@ async function main() {
23581
23750
  else if (args[0] === "test") {
23582
23751
  const name2 = option("--name"), payload = option("--payload");
23583
23752
  if (!name2 || !payload) throw new Error("Specify --name and --payload <json-file>. Never include secrets in event payloads.");
23584
- console.log(JSON.stringify(await call(path + "/test", { method: "POST", body: JSON.stringify({ name: name2, payload: JSON.parse(readFileSync24(payload, "utf8")), idempotencyKey: option("--idempotency-key") }) }), null, 2));
23753
+ console.log(JSON.stringify(await call(path + "/test", { method: "POST", body: JSON.stringify({ name: name2, payload: JSON.parse(readFileSync23(payload, "utf8")), idempotencyKey: option("--idempotency-key") }) }), null, 2));
23585
23754
  } else throw new Error("Use backify events environments|inspect|test");
23586
23755
  break;
23587
23756
  }
@@ -23592,7 +23761,7 @@ async function main() {
23592
23761
  console.log(await call("/apps/" + applicationGuid(args[0]) + "/deployments/preview", { method: "POST", body: JSON.stringify({ sourceRevision: args[1] ?? "latest" }) }));
23593
23762
  break;
23594
23763
  default:
23595
- console.log("backify connect | init | doctor | sync | login | logout | disconnect | status | dev | secrets local init|set|list --key-file <external-file> | test --json | inspect tables|rows|buckets|objects|preview|events --local [--json] | identity users|issue|clear --local [--json] | jobs | pull | report [--check] | prompt record --stdin --id <UUID> | ship [--preview-only] | request | events environments|inspect|test | workload plan|package|artifact-plan|trust-state|trust-sync|provenance capture|verify | apps | deploy");
23764
+ console.log("backify connect | init | doctor | sync | login | logout | disconnect | status | dev | secrets local init|set|list --key-file <external-file> | test --json | inspect tables|rows|buckets|objects|preview|events --local [--json] | identity users|issue|clear --local [--json] | jobs | pull | report [--check] | prompt record --stdin --id <UUID> | ship [--production-deploy] | request | events environments|inspect|test | integrations list|catalog|search|describe|contract|types | workload plan|package|artifact-plan|trust-state|trust-sync|provenance capture|verify | apps | deploy");
23596
23765
  }
23597
23766
  }
23598
23767
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backifydev/cli",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "Command-line setup and diagnostics for Backify applications",
5
5
  "type": "module",
6
6
  "bin": {