@crvouga/mockingbird-service-stripe 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — @crvouga/mockingbird-service-stripe
2
2
 
3
+ ## 3.6.0 (2026-10-17)
4
+
5
+ ### Features
6
+
7
+ - resolve fleet and emulator issue batch ([26d2f8c](https://github.com/crvouga/mockingbird/commit/26d2f8cee99fefed93192204bac44fb1be35c1da))
8
+
9
+ ## 3.5.0 (2026-10-15)
10
+
11
+ ### Features
12
+
13
+ - resolve SQL, routing and writer gaps ([73f97d0](https://github.com/crvouga/mockingbird/commit/73f97d0d2379f44b9131180498f4fab76648fed9))
14
+
3
15
  ## 3.4.0 (2026-10-15)
4
16
 
5
17
  ### Features
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-EUF2MBTV.js";
5
5
  import {
6
6
  listen
7
- } from "./chunk-XEIHQZPI.js";
7
+ } from "./chunk-Q3J6JUZM.js";
8
8
 
9
9
  // src/server.ts
10
10
  import { readFileSync } from "node:fs";
@@ -87,4 +87,4 @@ export {
87
87
  createServer,
88
88
  serveTarget
89
89
  };
90
- //# sourceMappingURL=chunk-VV2ZADSQ.js.map
90
+ //# sourceMappingURL=chunk-53CRCMWH.js.map
@@ -12,6 +12,38 @@ import {
12
12
 
13
13
  // ../../adapters/node/dist/fleet-archive.js
14
14
  import { createHash } from "node:crypto";
15
+ function validateCompatibilityTags(tags) {
16
+ if (!tags || typeof tags !== "object" || Array.isArray(tags))
17
+ throw new Error("invalid archive compatibility tags");
18
+ const entries = Object.entries(tags);
19
+ if (entries.length > 32 || entries.some(([key, value]) => !/^[A-Za-z0-9_.-]{1,64}$/.test(key) || typeof value !== "string" || value.length < 1 || value.length > 256 || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)))
20
+ throw new Error("invalid archive compatibility tags");
21
+ }
22
+ function assertCompatibilityTags(tags, policy) {
23
+ if (tags !== void 0)
24
+ validateCompatibilityTags(tags);
25
+ if (!policy)
26
+ return;
27
+ validateCompatibilityTags(policy.tags);
28
+ if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
29
+ throw new Error("invalid missing compatibility policy");
30
+ if (tags === void 0 && policy.missing === "allow")
31
+ return;
32
+ if (canonical(tags) !== canonical(policy.tags))
33
+ throw new Error("application archive compatibility mismatch");
34
+ }
35
+ function assertArchiveCompatibility(input, policy) {
36
+ const archive = validateArchive(input);
37
+ assertCompatibilityTags(archive.compatibility, policy);
38
+ return archive;
39
+ }
40
+ async function migrateApplicationArchive(input, migration) {
41
+ const source = assertArchiveCompatibility(input, migration.from);
42
+ validateCompatibilityTags(migration.to);
43
+ const transformed = await migration.transform(structuredClone(source));
44
+ const { sha256: _sha, ...content } = transformed;
45
+ return validateArchive(sealArchive({ ...content, compatibility: structuredClone(migration.to) }));
46
+ }
15
47
  var canonical = (value) => {
16
48
  if (Array.isArray(value))
17
49
  return `[${value.map(canonical).join(",")}]`;
@@ -31,6 +63,8 @@ function validateArchive(value) {
31
63
  const { sha256, ...content } = doc;
32
64
  if (doc.format !== "mockingbird.fleet/v1" || typeof doc.sourceId !== "string" || typeof doc.createdAt !== "string" || !doc.namespaces || typeof doc.namespaces !== "object" || Array.isArray(doc.namespaces) || sha256 !== digest(content))
33
65
  throw new Error("unsupported or corrupt fleet archive");
66
+ if (doc.compatibility !== void 0)
67
+ validateCompatibilityTags(doc.compatibility);
34
68
  for (const [namespace, entry] of Object.entries(doc.namespaces)) {
35
69
  if (!/^[A-Za-z0-9_.-]{1,64}$/.test(namespace) || !entry || !Number.isFinite(entry.clock?.now) || typeof entry.clock?.frozen !== "boolean" || !entry.services || !Object.keys(entry.services).length)
36
70
  throw new Error("invalid archive namespace");
@@ -46,6 +80,7 @@ function inspectArchive(input) {
46
80
  format: archive.format,
47
81
  createdAt: archive.createdAt,
48
82
  sha256: archive.sha256,
83
+ ...archive.compatibility ? { compatibility: archive.compatibility } : {},
49
84
  namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name, ns]) => [
50
85
  name,
51
86
  {
@@ -746,9 +781,9 @@ var RepositoryWriter = class {
746
781
  if (!owner || !Number.isSafeInteger(ttlMs) || ttlMs < 1)
747
782
  throw new Error("owner and positive lease duration required");
748
783
  const current = await this.control.read();
749
- if (current && current.value.lease.expiresAt > this.now())
784
+ if (current && !current.value.released && current.value.lease.expiresAt > this.now())
750
785
  throw new Error("repository writer already owned");
751
- if (current && !takeoverExpired)
786
+ if (current && !current.value.released && !takeoverExpired)
752
787
  throw new Error("expired writer requires explicit takeover policy");
753
788
  const lease = {
754
789
  owner,
@@ -764,8 +799,26 @@ var RepositoryWriter = class {
764
799
  return lease;
765
800
  }
766
801
  valid(state, lease) {
767
- if (!state || state.lease.id !== lease.id || state.lease.generation !== lease.generation || state.lease.owner !== lease.owner || state.lease.expiresAt <= this.now())
802
+ if (!state || state.released || state.lease.id !== lease.id || state.lease.generation !== lease.generation || state.lease.owner !== lease.owner || state.lease.expiresAt <= this.now())
803
+ throw new Error("stale or expired repository writer");
804
+ }
805
+ /** Release after draining workloads and awaiting the final verified publish.
806
+ * A failed/uncertain storage operation throws; retry with the same lease to
807
+ * discover whether it completed. Never clears the verified recovery pointer.
808
+ */
809
+ async release(lease) {
810
+ const current = await this.control.read();
811
+ if (!current)
768
812
  throw new Error("stale or expired repository writer");
813
+ if (current.value.released && current.value.lease.id === lease.id && current.value.lease.generation === lease.generation && current.value.lease.owner === lease.owner)
814
+ return "already-released";
815
+ this.valid(current.value, lease);
816
+ if (!await this.control.compareAndSwap(current.revision, {
817
+ ...current.value,
818
+ released: true
819
+ }))
820
+ throw new Error("writer release conflict");
821
+ return "released";
769
822
  }
770
823
  async renew(lease, ttlMs) {
771
824
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 1)
@@ -1262,11 +1315,28 @@ var ingressSupport = {
1262
1315
  streaming: true,
1263
1316
  signing: "vendor request signing is not used; webhook signatures remain payload based"
1264
1317
  },
1318
+ cognito: {
1319
+ publicOrigin: true,
1320
+ pathPrefix: true,
1321
+ namespaces: "path-or-header",
1322
+ streaming: true,
1323
+ signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
1324
+ },
1325
+ medplum: {
1326
+ publicOrigin: true,
1327
+ pathPrefix: true,
1328
+ namespaces: "path-or-header",
1329
+ streaming: true,
1330
+ signing: "FHIR, OAuth and storage URLs use the configured public namespace"
1331
+ },
1265
1332
  other: {
1266
1333
  publicOrigin: false,
1267
1334
  reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
1268
1335
  }
1269
1336
  };
1337
+ function supportsPublicIngress(service) {
1338
+ return service === "stripe" || service === "cognito" || service === "medplum";
1339
+ }
1270
1340
  function advertisedHttpBase(entry) {
1271
1341
  if (entry.publicPath !== void 0 && !entry.publicOrigin)
1272
1342
  throw new Error("publicPath requires publicOrigin");
@@ -1285,7 +1355,7 @@ function createFleetIngress(config, manifest, transport = fetch) {
1285
1355
  const base = advertisedHttpBase(entry);
1286
1356
  if (!base)
1287
1357
  return [];
1288
- if ((entry.target ?? service) !== "stripe")
1358
+ if (!supportsPublicIngress(entry.target ?? service))
1289
1359
  throw new Error(`public ingress unsupported for ${entry.target ?? service}; host/path-sensitive signatures require a direct endpoint`);
1290
1360
  const privateEndpoint = manifest.services[service];
1291
1361
  if (!privateEndpoint || entry.protocol && entry.protocol !== "http")
@@ -1309,6 +1379,8 @@ function createFleetIngress(config, manifest, transport = fetch) {
1309
1379
  const route = routes.find((r) => url.origin === r.origin && (url.pathname === r.prefix || url.pathname.startsWith(`${r.prefix}/`)));
1310
1380
  if (!route)
1311
1381
  return new Response("public ingress route not found", { status: 404 });
1382
+ if ((route.entry.target ?? route.service) === "cognito" && request.headers.get("authorization")?.startsWith("AWS4-HMAC-SHA256"))
1383
+ return new Response("SigV4 requests require a direct endpoint", { status: 403 });
1312
1384
  const path = url.pathname.slice(route.prefix.length) || "/";
1313
1385
  const adminPrefix = route.entry.adminPrefix ?? config.adminPrefix ?? "/__admin";
1314
1386
  if ((path === adminPrefix || path.startsWith(`${adminPrefix}/`)) && !path.startsWith(`${adminPrefix}/ns/`))
@@ -2329,7 +2401,7 @@ data: ${JSON.stringify(data)}
2329
2401
  {
2330
2402
  protocol: c.protocol,
2331
2403
  coverage: c.coverage,
2332
- ingress: c.targetName === "stripe" ? ingressSupport.stripe : ingressSupport.other,
2404
+ ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
2333
2405
  target: c.targetName,
2334
2406
  outboundPolicy: c.outboundPolicy ?? "unsupported",
2335
2407
  version: 1,
@@ -3413,11 +3485,12 @@ data: ${JSON.stringify(data)}
3413
3485
  format: "mockingbird.fleet/v1",
3414
3486
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3415
3487
  sourceId: this.ctx.id,
3488
+ ...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
3416
3489
  namespaces: contents
3417
3490
  });
3418
3491
  }
3419
3492
  async restoreArchive(input, selectedNamespace) {
3420
- const archive = validateArchive(input);
3493
+ const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
3421
3494
  const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
3422
3495
  if (!entries.length)
3423
3496
  throw new Error("selected namespace absent from archive");
@@ -3701,7 +3774,7 @@ async function preflightFleet(config, options) {
3701
3774
  if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
3702
3775
  throw new Error("target must name a supported service suffix");
3703
3776
  const service = entry.target ?? name;
3704
- if (advertisedHttpBase(entry) && service !== "stripe")
3777
+ if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
3705
3778
  throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
3706
3779
  const target = await options.load(service);
3707
3780
  const protocol = "protocol" in target ? target.protocol : "http";
@@ -3752,7 +3825,7 @@ async function preflightFleet(config, options) {
3752
3825
  break;
3753
3826
  }
3754
3827
  }
3755
- targets.set(name, { ...target, packageVersion: packageVersion ?? "3.4.0" });
3828
+ targets.set(name, { ...target, packageVersion: packageVersion ?? "3.6.0" });
3756
3829
  } catch (error) {
3757
3830
  throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3758
3831
  }
@@ -3796,6 +3869,11 @@ async function preflightFleet(config, options) {
3796
3869
  return ordered;
3797
3870
  }
3798
3871
  function validateFleetPolicies(options) {
3872
+ if (options.archiveCompatibility) {
3873
+ validateCompatibilityTags(options.archiveCompatibility.tags);
3874
+ if (options.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options.archiveCompatibility.missing))
3875
+ throw new Error("archiveCompatibility.missing must be allow or reject");
3876
+ }
3799
3877
  if (options.journal && !options.repository)
3800
3878
  throw new Error("durable operation journal requires an archive repository");
3801
3879
  if (options.cors) {
@@ -4627,6 +4705,40 @@ async function startFleet(config, options) {
4627
4705
  }
4628
4706
  }
4629
4707
 
4708
+ // ../../adapters/node/dist/fleet-loader.js
4709
+ import { createRequire as createRequire2 } from "node:module";
4710
+ import { resolve as resolve2 } from "node:path";
4711
+ import { pathToFileURL } from "node:url";
4712
+ var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4713
+ function createInstalledServiceLoader(options) {
4714
+ if (!options.projectRoot)
4715
+ throw new Error("consumer projectRoot required");
4716
+ const allowed = new Set(options.services);
4717
+ for (const name of allowed)
4718
+ if (!suffix.test(name))
4719
+ throw new Error("invalid service suffix");
4720
+ const require2 = createRequire2(resolve2(options.projectRoot, "package.json"));
4721
+ const targets = new Map((options.targets ?? []).map((target) => [target.name, target]));
4722
+ return async (name) => {
4723
+ if (!suffix.test(name) || !allowed.has(name))
4724
+ throw new Error("service is not declared in loader allowlist");
4725
+ const own = targets.get(name);
4726
+ if (own)
4727
+ return own;
4728
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
4729
+ let location;
4730
+ try {
4731
+ location = require2.resolve(specifier);
4732
+ } catch {
4733
+ throw new Error(`cannot load service "${name}": install @crvouga/mockingbird-service-${name} in the consumer project; its /server export is required`);
4734
+ }
4735
+ const mod = await import(pathToFileURL(location).href);
4736
+ if (!mod.serveTarget || mod.serveTarget.name !== name || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
4737
+ throw new Error(`${specifier} exports no matching serveTarget`);
4738
+ return mod.serveTarget;
4739
+ };
4740
+ }
4741
+
4630
4742
  // ../../adapters/node/dist/fleet-client.js
4631
4743
  import { randomUUID as randomUUID7 } from "node:crypto";
4632
4744
  import { chmod as chmod2, readFile as readFile5, rename as rename4, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
@@ -4639,6 +4751,14 @@ var FleetClientError = class extends Error {
4639
4751
  this.details = details;
4640
4752
  }
4641
4753
  };
4754
+ var FleetClientResponseError = class extends FleetClientError {
4755
+ code;
4756
+ constructor(code, status, origin) {
4757
+ super(status, { code, body: "omitted", ...origin ? { origin } : {} });
4758
+ this.code = code;
4759
+ this.name = "FleetClientResponseError";
4760
+ }
4761
+ };
4642
4762
  var FleetClient = class {
4643
4763
  base;
4644
4764
  options;
@@ -4651,7 +4771,14 @@ var FleetClient = class {
4651
4771
  if (url.username || url.password)
4652
4772
  throw new Error("fleet client URL must not contain credentials");
4653
4773
  url.searchParams.set("namespace", options.namespace ?? this.options.namespace ?? "default");
4774
+ const limit = options.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
4775
+ if (!Number.isSafeInteger(limit) || limit < 1)
4776
+ throw new Error("invalid response byte limit");
4777
+ const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
4778
+ const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
4779
+ signal.throwIfAborted();
4654
4780
  const response = await (this.options.fetch ?? fetch)(url, {
4781
+ redirect: "manual",
4655
4782
  method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
4656
4783
  headers: {
4657
4784
  "content-type": "application/json",
@@ -4659,11 +4786,71 @@ var FleetClient = class {
4659
4786
  ...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
4660
4787
  },
4661
4788
  ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
4662
- signal: options.signal ?? AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
4789
+ signal
4663
4790
  });
4664
- const data = await response.json();
4665
- if (!response.ok)
4666
- throw new FleetClientError(response.status, data);
4791
+ if (response.status >= 300 && response.status < 400) {
4792
+ await response.body?.cancel();
4793
+ throw new FleetClientResponseError("redirect", response.status, url.origin);
4794
+ }
4795
+ if (Number(response.headers.get("content-length")) > limit) {
4796
+ await response.body?.cancel();
4797
+ throw new FleetClientResponseError("response_too_large", response.status);
4798
+ }
4799
+ const reader = response.body?.getReader();
4800
+ const chunks = [];
4801
+ let size = 0;
4802
+ const abort = () => {
4803
+ void reader?.cancel(signal.reason).catch(() => {
4804
+ });
4805
+ };
4806
+ signal.addEventListener("abort", abort, { once: true });
4807
+ try {
4808
+ if (reader)
4809
+ while (true) {
4810
+ signal.throwIfAborted();
4811
+ const { done, value } = await reader.read();
4812
+ signal.throwIfAborted();
4813
+ if (done)
4814
+ break;
4815
+ size += value.byteLength;
4816
+ if (size > limit) {
4817
+ await reader.cancel();
4818
+ throw new FleetClientResponseError("response_too_large", response.status);
4819
+ }
4820
+ chunks.push(value);
4821
+ }
4822
+ } finally {
4823
+ signal.removeEventListener("abort", abort);
4824
+ if (signal.aborted)
4825
+ await reader?.cancel(signal.reason).catch(() => {
4826
+ });
4827
+ reader?.releaseLock();
4828
+ }
4829
+ const bytes2 = new Uint8Array(size);
4830
+ let offset = 0;
4831
+ for (const chunk of chunks) {
4832
+ bytes2.set(chunk, offset);
4833
+ offset += chunk.byteLength;
4834
+ }
4835
+ const text = new TextDecoder().decode(bytes2);
4836
+ let data;
4837
+ try {
4838
+ data = text.length ? JSON.parse(text) : void 0;
4839
+ } catch {
4840
+ if (response.ok)
4841
+ throw new FleetClientResponseError("invalid_json", response.status);
4842
+ }
4843
+ if (!response.ok) {
4844
+ const error = data && typeof data === "object" ? data.error : void 0;
4845
+ const detail = { body: "omitted" };
4846
+ if (error && typeof error === "object")
4847
+ for (const key of ["type", "code"]) {
4848
+ const value = error[key];
4849
+ if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
4850
+ detail[key] = value;
4851
+ }
4852
+ throw new FleetClientError(response.status, detail);
4853
+ }
4667
4854
  return data;
4668
4855
  }
4669
4856
  discover() {
@@ -4729,17 +4916,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
4729
4916
  const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
4730
4917
  if (!child || !secret)
4731
4918
  throw new Error(`missing service mapping ${entry.service}`);
4919
+ let endpoint;
4732
4920
  if (child.protocol === "http") {
4733
4921
  const template = child.namespaces.path;
4734
4922
  if (typeof template !== "string")
4735
4923
  throw new Error(`missing HTTP namespace mapping ${entry.service}`);
4736
- result[variable] = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4924
+ endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4737
4925
  } else {
4738
- const endpoint = secret.namespaces?.[namespace];
4739
- if (!endpoint)
4926
+ const selected = secret.namespaces?.[namespace];
4927
+ if (!selected)
4740
4928
  throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
4741
- result[variable] = endpoint;
4929
+ endpoint = selected;
4930
+ }
4931
+ const url = new URL(endpoint);
4932
+ const field = entry.field ?? "url";
4933
+ let value;
4934
+ switch (field) {
4935
+ case "url":
4936
+ value = endpoint;
4937
+ break;
4938
+ case "hostname":
4939
+ value = url.hostname.replace(/^\[|\]$/g, "");
4940
+ break;
4941
+ case "port":
4942
+ value = url.port;
4943
+ break;
4944
+ case "username":
4945
+ value = decodeURIComponent(url.username);
4946
+ break;
4947
+ case "password":
4948
+ value = decodeURIComponent(url.password);
4949
+ break;
4950
+ case "pathname":
4951
+ value = decodeURIComponent(url.pathname);
4952
+ break;
4953
+ case "database":
4954
+ value = decodeURIComponent(url.pathname.replace(/^\//, ""));
4955
+ break;
4956
+ default:
4957
+ throw new Error("unsupported connection field");
4742
4958
  }
4959
+ if (!value) {
4960
+ if (typeof entry.fallback !== "string")
4961
+ throw new Error(`missing connection component ${field}`);
4962
+ value = entry.fallback;
4963
+ }
4964
+ result[variable] = value;
4743
4965
  }
4744
4966
  return result;
4745
4967
  }
@@ -4759,14 +4981,14 @@ async function writeConnections(path, values, format = "json") {
4759
4981
 
4760
4982
  // ../../adapters/node/dist/fleet-tools.js
4761
4983
  import { readFile as readFile6 } from "node:fs/promises";
4762
- import { createRequire as createRequire2 } from "node:module";
4763
- import { resolve as resolve2 } from "node:path";
4984
+ import { createRequire as createRequire3 } from "node:module";
4985
+ import { resolve as resolve3 } from "node:path";
4764
4986
  async function doctorFleet(config, options, probes = []) {
4765
4987
  const findings = [];
4766
4988
  const plan = [];
4767
4989
  try {
4768
4990
  const targets = await preflightFleet(config, options);
4769
- const require2 = createRequire2(resolve2(process.cwd(), "package.json"));
4991
+ const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
4770
4992
  for (const [service, target] of targets) {
4771
4993
  let version = "development";
4772
4994
  try {
@@ -4821,7 +5043,7 @@ async function doctorFleet(config, options, probes = []) {
4821
5043
  };
4822
5044
  }
4823
5045
  async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e4) {
4824
- const archive = validateArchive(input);
5046
+ const archive = assertArchiveCompatibility(input, options.archiveCompatibility);
4825
5047
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4826
5048
  const isolated = {
4827
5049
  ...config,
@@ -4833,7 +5055,7 @@ async function rehearseFleet(config, options, input, probes = [], timeoutMs = 3e
4833
5055
  };
4834
5056
  let fleet;
4835
5057
  try {
4836
- fleet = await startFleet(isolated, { load: options.load });
5058
+ fleet = await startFleet(isolated, options);
4837
5059
  const client = new FleetClient(fleet.manifest.adminBase, {
4838
5060
  ...config.adminKey ? { adminKey: config.adminKey } : {}
4839
5061
  });
@@ -4972,9 +5194,6 @@ function fleetCommands(load) {
4972
5194
 
4973
5195
  // ../../adapters/node/dist/cli.js
4974
5196
  import { readFile as readFile7 } from "node:fs/promises";
4975
- import { createRequire as createRequire3 } from "node:module";
4976
- import { resolve as resolve3 } from "node:path";
4977
- import { pathToFileURL } from "node:url";
4978
5197
  import { parseArgs } from "node:util";
4979
5198
  var optionHelp = (options) => Object.entries(options).map(([name, option]) => {
4980
5199
  const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
@@ -5213,26 +5432,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
5213
5432
  normalizeBootControls(document, "adminControls");
5214
5433
  return document;
5215
5434
  };
5216
- var loadTarget = async (name, own) => {
5217
- if (name === own.name)
5218
- return own;
5219
- const specifier = `@crvouga/mockingbird-service-${name}/server`;
5220
- try {
5221
- let mod;
5222
- try {
5223
- mod = await import(specifier);
5224
- } catch {
5225
- const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
5226
- mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
5227
- }
5228
- if (!mod.serveTarget)
5229
- throw new Error(`${specifier} exports no serveTarget`);
5230
- return mod.serveTarget;
5231
- } catch (error) {
5232
- const reason = error instanceof Error ? error.message : String(error);
5233
- throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
5234
- }
5235
- };
5435
+ var loadTarget = (name, own) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name], targets: [own] })(name);
5236
5436
  var start = async (target, values, config) => {
5237
5437
  const adminPrefix = resolveAdminPrefix(config.adminPrefix);
5238
5438
  const runtime = await target.create(values, {
@@ -5882,9 +6082,14 @@ var sourceArchive = (input) => {
5882
6082
  throw new Error("corrupt migration source");
5883
6083
  return structuredClone(source);
5884
6084
  };
5885
- function archiveCompatibility(input, registry) {
6085
+ function archiveCompatibility(input, registry, policy) {
5886
6086
  const source = sourceArchive(input);
5887
6087
  const problems = [], steps = [];
6088
+ try {
6089
+ assertCompatibilityTags(source.compatibility, policy);
6090
+ } catch (error) {
6091
+ problems.push(error instanceof Error ? error.message : "invalid application compatibility");
6092
+ }
5888
6093
  if (source.format !== "mockingbird.fleet/v1" && !registry.sourceFormats.includes(source.format))
5889
6094
  problems.push(`unsupported source format ${source.format}`);
5890
6095
  for (const [namespace, ns] of Object.entries(source.namespaces))
@@ -5936,6 +6141,7 @@ async function migrateArchive(input, registry, parameters = {}) {
5936
6141
  const content = {
5937
6142
  format: "mockingbird.fleet/v1",
5938
6143
  sourceId: source.sourceId,
6144
+ ...source.compatibility ? { compatibility: source.compatibility } : {},
5939
6145
  createdAt: source.createdAt,
5940
6146
  namespaces,
5941
6147
  migration: {
@@ -6302,6 +6508,8 @@ var NamespacePool = class {
6302
6508
  };
6303
6509
 
6304
6510
  export {
6511
+ assertArchiveCompatibility,
6512
+ migrateApplicationArchive,
6305
6513
  validateArchive,
6306
6514
  inspectArchive,
6307
6515
  diffArchives,
@@ -6327,7 +6535,9 @@ export {
6327
6535
  serve,
6328
6536
  listen,
6329
6537
  startFleet,
6538
+ createInstalledServiceLoader,
6330
6539
  FleetClientError,
6540
+ FleetClientResponseError,
6331
6541
  FleetClient,
6332
6542
  renderConnections,
6333
6543
  writeConnections,
@@ -6355,4 +6565,4 @@ export {
6355
6565
  withFleetRun,
6356
6566
  NamespacePool
6357
6567
  };
6358
- //# sourceMappingURL=chunk-XEIHQZPI.js.map
6568
+ //# sourceMappingURL=chunk-Q3J6JUZM.js.map