@sleepy-hollow/framework 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,8 +8,7 @@ import {
8
8
  } from "./chunk-D4U3ZY4O.js";
9
9
  import "./chunk-BAKXP7IR.js";
10
10
  import {
11
- composeProjectSecurity,
12
- redactSecurityData
11
+ composeProjectSecurity
13
12
  } from "./chunk-DGTHFZPZ.js";
14
13
  import {
15
14
  normalizeRoutes
@@ -25,7 +24,7 @@ import "./chunk-5WRI5ZAA.js";
25
24
  import { pathToFileURL as pathToFileURL4 } from "url";
26
25
 
27
26
  // cli/adapters.ts
28
- import { isAbsolute as isAbsolute5, join as join3, relative as relative5, sep as sep6 } from "path";
27
+ import { isAbsolute as isAbsolute5, join as join4, relative as relative5, sep as sep6 } from "path";
29
28
  import { pathToFileURL as pathToFileURL2 } from "url";
30
29
 
31
30
  // cli/check/render.ts
@@ -652,13 +651,13 @@ function verify(inventory2) {
652
651
 
653
652
  // cli/check/command.ts
654
653
  function parse(args) {
655
- let json3 = false;
654
+ let json2 = false;
656
655
  let scope = { kind: "full" };
657
656
  let scopeSeen = false;
658
657
  for (let index = 0; index < args.length; index++) {
659
658
  const argument = args[index];
660
659
  if (argument === "--json") {
661
- json3 = true;
660
+ json2 = true;
662
661
  continue;
663
662
  }
664
663
  if (argument === "--full") {
@@ -699,7 +698,7 @@ function parse(args) {
699
698
  if (scope.kind === "route" && (!/^[A-Z]+$/.test(scope.method) || !scope.path.startsWith("/") || scope.path.includes(".."))) {
700
699
  throw new TypeError("Route scope uses an unsafe method or path");
701
700
  }
702
- return { json: json3, scope };
701
+ return { json: json2, scope };
703
702
  }
704
703
  function safeMessage(error) {
705
704
  return error instanceof Error ? error.message.slice(0, 500) : "Verification evidence could not be collected";
@@ -753,299 +752,245 @@ async function runCheckCommand(args, io, load) {
753
752
  var MAX_OUTPUT = 1024 * 1024;
754
753
  var MAX_TIMEOUT = 10 * 60 * 1e3;
755
754
 
756
- // cli/check/mod.ts
757
- function verifyProject(inventory2) {
758
- return verify(inventory2);
759
- }
760
-
761
- // cli/deploy/adapter.ts
762
- var TOKEN_VARIABLE = "FLY_API_TOKEN";
763
- function resolveToken(env) {
764
- const raw = env[TOKEN_VARIABLE];
765
- if (typeof raw !== "string" || raw.trim().length === 0) {
766
- throw new Error(
767
- `No Fly.io access token is available. Set ${TOKEN_VARIABLE} to an app-scoped token before deploying.`
768
- );
769
- }
770
- const token = raw.trim();
771
- if (/\s/.test(token)) {
772
- throw new Error(`${TOKEN_VARIABLE} must be a single token with no whitespace.`);
755
+ // cli/deploy/prepare.ts
756
+ import { createHash } from "crypto";
757
+ import { join } from "path";
758
+ var OWNER = "# Generated by Sleepy Hollow. Managed deployment artifact.";
759
+ var APP = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
760
+ var REGION = /^[a-z]{3}$/;
761
+ var DeploymentPreparationError = class extends Error {
762
+ diagnostics;
763
+ constructor(diagnostics) {
764
+ super(diagnostics[0]?.summary ?? "Deployment preparation failed.");
765
+ this.name = "DeploymentPreparationError";
766
+ this.diagnostics = diagnostics;
773
767
  }
774
- return token;
775
- }
776
- function outcome(id, status, observedStatus, evidence) {
777
- return { id, status, observedStatus, evidence };
778
- }
779
- function flyAdapter(options) {
780
- return {
781
- async upload(request) {
782
- const result = await options.runner.run({
783
- command: ["flyctl", "deploy", "--app", request.target.project, "--remote-only"],
784
- environment: { [TOKEN_VARIABLE]: request.token }
785
- });
786
- const url = `https://${request.target.project}.fly.dev`;
787
- return {
788
- url,
789
- revision: result.stdout.trim() || request.revision
790
- };
791
- },
792
- async health(request) {
793
- try {
794
- const response = await options.transport(new URL("/", request.url));
795
- return outcome("HEALTH", response.ok ? "passed" : "failed", response.status, `GET / returned ${response.status}`);
796
- } catch (error) {
797
- return outcome("HEALTH", "failed", 0, `GET / failed: ${error instanceof Error ? error.message : "unknown transport error"}`);
798
- }
799
- },
800
- async smoke(request) {
801
- try {
802
- const response = await options.transport(new URL(request.test.path, request.url), { method: request.test.method });
803
- return outcome(request.test.id, response.status === request.test.expectedStatus ? "passed" : "failed", response.status, `${request.test.method} ${request.test.path} returned ${response.status}, expected ${request.test.expectedStatus}`);
804
- } catch (error) {
805
- return outcome(request.test.id, "failed", 0, `${request.test.method} ${request.test.path} failed: ${error instanceof Error ? error.message : "unknown transport error"}`);
806
- }
807
- }
808
- };
768
+ };
769
+ function failure(code, summary, correction) {
770
+ throw new DeploymentPreparationError([{ code, summary, correction }]);
809
771
  }
810
-
811
- // cli/deploy/plan.ts
812
- function plan(inventory2) {
813
- const deployed = new Set(inventory2.deployedEnvironmentKeys);
814
- const current = new Set(inventory2.environmentKeys);
815
- const environmentKeyChanges = [
816
- ...inventory2.environmentKeys.filter((key) => !deployed.has(key)).map((key) => ({ key, change: "added" })),
817
- ...inventory2.deployedEnvironmentKeys.filter((key) => !current.has(key)).map(
818
- (key) => ({ key, change: "removed" })
819
- )
820
- ].sort((left, right) => left.key.localeCompare(right.key));
821
- const breaking = inventory2.contractChanges.some(
822
- (change) => change.severity === "breaking"
823
- );
824
- const unchanged = inventory2.deployedRevision === inventory2.revision && environmentKeyChanges.length === 0 && inventory2.contractChanges.length === 0;
825
- return {
826
- target: inventory2.target,
827
- revision: inventory2.revision,
828
- ...inventory2.deployedRevision ? { deployedRevision: inventory2.deployedRevision } : {},
829
- environmentKeyChanges,
830
- contractChanges: [...inventory2.contractChanges],
831
- smokeTests: [...inventory2.smokeTests],
832
- requiresConfirmation: !unchanged && (inventory2.firstExternalDeployment || breaking),
833
- unchanged
834
- };
772
+ function digest(content) {
773
+ return createHash("sha256").update(content, "utf8").digest("hex");
835
774
  }
836
-
837
- // cli/deploy/types.ts
838
- var DEPLOY_TARGET_KINDS = ["fly"];
839
-
840
- // cli/deploy/deployment.ts
841
- function blocked(request, built, diagnostics) {
842
- return {
843
- schema: "sleepy-hollow-deploy-result/v1",
844
- ok: false,
845
- command: "deploy",
846
- projectRoot: request.inventory.projectRootDisplay,
847
- outcome: "blocked",
848
- plan: built,
849
- smokeResults: [],
850
- diagnostics
851
- };
775
+ function quote(value) {
776
+ return JSON.stringify(value);
852
777
  }
853
- async function deploy(request, adapter, now) {
854
- const { inventory: inventory2 } = request;
855
- const built = plan(inventory2);
856
- if (!DEPLOY_TARGET_KINDS.includes(inventory2.target.kind)) {
857
- return blocked(request, built, [{
858
- code: "SH_DEPLOY_TARGET_UNSUPPORTED",
859
- severity: "error",
860
- summary: `Deployment target ${inventory2.target.kind} is not supported in this release.`,
861
- evidence: [`requested target: ${inventory2.target.kind}`],
862
- correction: `Deploy to a supported target: ${DEPLOY_TARGET_KINDS.join(", ")}.`
863
- }]);
778
+ function normalize(request) {
779
+ if (request.target.kind !== "fly") {
780
+ return failure("SH_DEPLOY_TARGET_UNSUPPORTED", "Only Fly deployment preparation is supported.", "Use --target fly:<app>.");
864
781
  }
865
- if (!inventory2.verification.ok) {
866
- const evidence = inventory2.verification.diagnostics.filter((item) => item.severity === "error").map((item) => `${item.code}: ${item.summary}`);
867
- return blocked(request, built, [{
868
- code: "SH_DEPLOY_VERIFICATION_FAILED",
869
- severity: "error",
870
- summary: "Required verification failed, so nothing was uploaded.",
871
- evidence: evidence.length > 0 ? evidence : [
872
- `hollow check reported ${inventory2.verification.summary.failed} failed checks`
873
- ],
874
- correction: "Resolve every reported diagnostic and rerun hollow check."
875
- }]);
782
+ const app = request.target.app.trim();
783
+ if (!APP.test(app)) {
784
+ return failure("SH_DEPLOY_TARGET_INVALID", "The Fly app name is invalid.", "Use a Fly app name containing lowercase letters, numbers, and hyphens.");
876
785
  }
877
- if (built.unchanged) {
878
- return {
879
- schema: "sleepy-hollow-deploy-result/v1",
880
- ok: true,
881
- command: "deploy",
882
- projectRoot: inventory2.projectRootDisplay,
883
- outcome: "unchanged",
884
- plan: built,
885
- deployedRevision: inventory2.revision,
886
- openApiPath: inventory2.openApiPath,
887
- documentationPath: inventory2.documentationPath,
888
- smokeResults: [],
889
- completedAt: now(),
890
- diagnostics: []
891
- };
786
+ if (request.database !== "sqlite" && request.database !== "postgres") {
787
+ return failure("SH_DEPLOY_DATABASE_INVALID", "The deployment database profile is invalid.", "Use --database sqlite or --database postgres.");
892
788
  }
893
- if (built.requiresConfirmation && !request.confirmed) {
894
- return {
895
- schema: "sleepy-hollow-deploy-result/v1",
896
- ok: false,
897
- command: "deploy",
898
- projectRoot: inventory2.projectRootDisplay,
899
- outcome: "confirmation-required",
900
- plan: built,
901
- smokeResults: [],
902
- diagnostics: [{
903
- code: "SH_DEPLOY_CONFIRMATION_REQUIRED",
904
- severity: "error",
905
- summary: "The first external deployment or a materially risky change requires explicit confirmation.",
906
- evidence: [
907
- `target: ${inventory2.target.kind}:${inventory2.target.project}`,
908
- `revision: ${inventory2.revision}`
909
- ],
910
- correction: "Review the deployment plan and confirm before deploying."
911
- }]
912
- };
789
+ const region = request.region?.trim();
790
+ if (request.database === "sqlite" && !region) {
791
+ return failure("SH_DEPLOY_REGION_REQUIRED", "SQLite preparation requires a Fly region for its data volume.", "Add --region <three-letter-region>.");
913
792
  }
914
- const upload = await adapter.upload({
915
- target: inventory2.target,
916
- revision: inventory2.revision,
917
- token: request.token
918
- });
919
- const health = await adapter.health({ url: upload.url });
920
- const smokeResults = [];
921
- for (const test of inventory2.smokeTests) {
922
- smokeResults.push(await adapter.smoke({ url: upload.url, test }));
793
+ if (region && !REGION.test(region)) {
794
+ return failure("SH_DEPLOY_REGION_INVALID", "The Fly region must be a three-letter region code.", "Use a valid Fly region, such as iad.");
923
795
  }
924
- const requiredFailures = smokeResults.filter(
925
- (outcome2, index) => outcome2.status === "failed" && inventory2.smokeTests[index].required
926
- );
927
- const failed = health.status === "failed" || requiredFailures.length > 0;
928
796
  return {
929
- schema: "sleepy-hollow-deploy-result/v1",
930
- ok: !failed,
931
- command: "deploy",
932
- projectRoot: inventory2.projectRootDisplay,
933
- outcome: failed ? "smoke-failed" : "deployed",
934
- plan: built,
935
- url: upload.url,
936
- deployedRevision: upload.revision,
937
- openApiPath: inventory2.openApiPath,
938
- documentationPath: inventory2.documentationPath,
939
- health,
940
- smokeResults,
941
- completedAt: now(),
942
- diagnostics: failed ? [{
943
- code: "SH_DEPLOY_SMOKE_FAILED",
944
- severity: "error",
945
- summary: "The deployment is live but a required smoke test failed. It is not a successful deployment.",
946
- evidence: [
947
- `live revision: ${upload.revision}`,
948
- ...health.status === "failed" ? [`health: ${health.evidence}`] : [],
949
- ...requiredFailures.map(
950
- (outcome2) => `${outcome2.id}: ${outcome2.evidence}`
951
- )
952
- ],
953
- correction: "Investigate the live revision, then repair and redeploy or roll back."
954
- }] : []
797
+ projectRoot: request.projectRoot,
798
+ target: { kind: "fly", app },
799
+ database: request.database,
800
+ ...region ? { region } : {},
801
+ ...request.force ? { force: true } : {}
955
802
  };
956
803
  }
957
-
958
- // cli/deploy/render.ts
959
- var outcomes = {
960
- deployed: "Deployed",
961
- unchanged: "No change",
962
- blocked: "Blocked before upload",
963
- "confirmation-required": "Awaiting confirmation",
964
- "smoke-failed": "Deployed with failed smoke tests"
965
- };
966
- function human2(result) {
967
- const lines = [];
968
- const { plan: plan3 } = result;
969
- lines.push(
970
- `${outcomes[result.outcome]}: ${plan3.target.kind}:${plan3.target.project}`
971
- );
972
- lines.push(` revision ${plan3.revision}`);
973
- if (plan3.deployedRevision) {
974
- lines.push(` previously ${plan3.deployedRevision}`);
975
- }
976
- if (result.url) lines.push(` url ${result.url}`);
977
- if (result.deployedRevision) {
978
- lines.push(` live revision ${result.deployedRevision}`);
979
- }
980
- if (result.openApiPath) {
981
- lines.push(` openapi ${result.openApiPath}`);
982
- }
983
- if (result.documentationPath) {
984
- lines.push(` documentation ${result.documentationPath}`);
985
- }
986
- if (result.completedAt) {
987
- lines.push(` completed ${result.completedAt}`);
988
- }
989
- for (const change of plan3.environmentKeyChanges) {
990
- lines.push(` env ${change.change} ${change.key}`);
991
- }
992
- for (const change of plan3.contractChanges) {
993
- lines.push(
994
- ` contract ${change.severity} ${change.code} ${change.operationId}`
995
- );
996
- }
997
- if (result.health) {
998
- lines.push(
999
- ` health ${result.health.status} ${result.health.evidence}`
1000
- );
804
+ async function runtime(projectRoot) {
805
+ let manifest;
806
+ try {
807
+ manifest = JSON.parse(await platform.readTextFile(join(projectRoot, "package.json")));
808
+ } catch (error) {
809
+ if (platform.isNotFound(error)) {
810
+ return failure("SH_DEPLOY_PACKAGE_MISSING", "A deployable project must contain package.json.", "Create a Node or Bun package manifest with a start script.");
811
+ }
812
+ return failure("SH_DEPLOY_PACKAGE_INVALID", "package.json could not be read as JSON.", "Repair package.json and include a start script.");
1001
813
  }
1002
- for (const outcome2 of result.smokeResults) {
1003
- lines.push(
1004
- ` smoke ${outcome2.id} ${outcome2.status} ${outcome2.evidence}`
1005
- );
814
+ if (typeof manifest.scripts?.start !== "string" || manifest.scripts.start.trim().length === 0) {
815
+ return failure("SH_DEPLOY_START_MISSING", "A deployable project must declare a non-empty start script.", "Add scripts.start to package.json before preparing deployment.");
1006
816
  }
1007
- for (const diagnostic6 of result.diagnostics) {
1008
- lines.push(
1009
- ` ${diagnostic6.severity}: ${diagnostic6.code} ${diagnostic6.summary}`
1010
- );
1011
- for (const evidence of diagnostic6.evidence) {
1012
- lines.push(` ${evidence}`);
1013
- }
1014
- lines.push(` correction: ${diagnostic6.correction}`);
817
+ return typeof manifest.packageManager === "string" && manifest.packageManager.startsWith("bun@") ? "bun" : "node";
818
+ }
819
+ function dockerfile(runtime2) {
820
+ if (runtime2 === "bun") {
821
+ return `${OWNER}
822
+ FROM oven/bun:1.3.14
823
+
824
+ WORKDIR /app
825
+ COPY package.json ./
826
+ RUN bun install --production
827
+ COPY . .
828
+
829
+ ENV NODE_ENV=production
830
+ ENV PORT=3000
831
+ EXPOSE 3000
832
+ CMD ["bun", "run", "start"]
833
+ `;
1015
834
  }
1016
- return `${lines.join("\n")}
835
+ return `${OWNER}
836
+ FROM node:24-bookworm-slim
837
+
838
+ WORKDIR /app
839
+ COPY package.json ./
840
+ RUN npm install --omit=dev
841
+ COPY . .
842
+
843
+ ENV NODE_ENV=production
844
+ ENV PORT=3000
845
+ EXPOSE 3000
846
+ CMD ["npm", "run", "start"]
1017
847
  `;
1018
848
  }
1019
- function json2(result) {
1020
- return `${JSON.stringify(redactSecurityData(result), null, 2)}
849
+ function dockerignore() {
850
+ return `${OWNER}
851
+ .git
852
+ .gitignore
853
+ node_modules
854
+ .env
855
+ .env.*
856
+ !.env.example
857
+ *.db
858
+ *.sqlite
859
+ *.sqlite3
860
+ data
861
+ coverage
862
+ generated/capture.json
1021
863
  `;
1022
864
  }
865
+ function flyToml(request) {
866
+ const shared = `${OWNER}
867
+ app = ${quote(request.target.app)}
868
+ ${request.region ? `primary_region = ${quote(request.region)}
869
+ ` : ""}
870
+ [http_service]
871
+ internal_port = 3000
872
+ force_https = true
873
+ auto_stop_machines = "stop"
874
+ auto_start_machines = true
875
+ min_machines_running = 0
876
+ processes = ["app"]
877
+
878
+ [[http_service.checks]]
879
+ grace_period = "10s"
880
+ interval = "30s"
881
+ method = "GET"
882
+ timeout = "5s"
883
+ path = "/"
884
+ `;
885
+ if (request.database === "postgres") return shared;
886
+ return `${shared}
887
+ [env]
888
+ DATABASE_URL = "file:/data/sleepy-hollow.db"
1023
889
 
1024
- // cli/deploy/mod.ts
1025
- function buildDeployPlan(inventory2) {
1026
- return plan(inventory2);
890
+ [mounts]
891
+ source = "data"
892
+ destination = "/data"
893
+ processes = ["app"]
894
+ `;
1027
895
  }
1028
- function runDeployment(request, adapter, now) {
1029
- return deploy(request, adapter, now);
896
+ async function read(path) {
897
+ try {
898
+ return await platform.readTextFile(path);
899
+ } catch (error) {
900
+ if (platform.isNotFound(error)) return void 0;
901
+ throw error;
902
+ }
1030
903
  }
1031
- function renderHumanDeployResult(result) {
1032
- return human2(result);
904
+ function owned(content) {
905
+ return content.startsWith(`${OWNER}
906
+ `);
1033
907
  }
1034
- function renderJsonDeployResult(result) {
1035
- return json2(result);
908
+ async function artifacts(request, projectRuntime) {
909
+ const desired = [
910
+ { path: ".dockerignore", content: dockerignore() },
911
+ { path: "Dockerfile", content: dockerfile(projectRuntime) },
912
+ { path: "fly.toml", content: flyToml(request) }
913
+ ];
914
+ const pending = await Promise.all(desired.map(async (item) => {
915
+ const existing = await read(join(request.projectRoot, item.path));
916
+ if (existing === void 0) return { ...item, status: "created" };
917
+ if (existing === item.content) return { ...item, existing, status: "unchanged" };
918
+ if (!owned(existing)) {
919
+ return failure(
920
+ "SH_DEPLOY_ARTIFACT_UNOWNED",
921
+ `Refusing to replace unrecognized deployment artifact ${item.path}.`,
922
+ "Move the user-owned artifact or create the managed file in a clean project."
923
+ );
924
+ }
925
+ if (!request.force) {
926
+ return failure(
927
+ "SH_DEPLOY_ARTIFACT_CONFLICT",
928
+ `Managed deployment artifact ${item.path} would change.`,
929
+ "Review the new plan and rerun with --force to replace the managed artifact."
930
+ );
931
+ }
932
+ return { ...item, existing, status: "updated" };
933
+ }));
934
+ return pending;
935
+ }
936
+ function commands(request) {
937
+ const app = request.target.app;
938
+ if (request.database === "sqlite") {
939
+ return [
940
+ `fly apps create ${app}`,
941
+ `fly volumes create data --app ${app} --region ${request.region}`,
942
+ `fly deploy --app ${app}`
943
+ ];
944
+ }
945
+ return [
946
+ `fly apps create ${app}`,
947
+ `fly secrets set DATABASE_URL=<your-postgresql-connection-url> --app ${app}`,
948
+ `fly deploy --app ${app}`
949
+ ];
1036
950
  }
1037
- function resolveDeployToken(env = process.env) {
1038
- return resolveToken(env);
951
+ async function prepareFlyDeployment(input) {
952
+ const request = normalize(input);
953
+ const projectRuntime = await runtime(request.projectRoot);
954
+ const pending = await artifacts(request, projectRuntime);
955
+ for (const artifact2 of pending) {
956
+ if (artifact2.status !== "unchanged") {
957
+ await platform.writeTextFile(join(request.projectRoot, artifact2.path), artifact2.content);
958
+ }
959
+ }
960
+ return {
961
+ schema: "sleepy-hollow-deploy-prepare-result/v1",
962
+ ok: true,
963
+ command: "deploy",
964
+ action: "prepare",
965
+ target: request.target,
966
+ database: request.database,
967
+ ...request.region ? { region: request.region } : {},
968
+ artifacts: pending.map((artifact2) => ({
969
+ path: artifact2.path,
970
+ digest: digest(artifact2.content),
971
+ status: artifact2.status
972
+ })),
973
+ environmentKeys: request.database === "postgres" ? ["DATABASE_URL"] : [],
974
+ ...request.database === "sqlite" ? { storage: { volume: "data", mountPath: "/data", machines: 1 } } : {},
975
+ commands: commands(request)
976
+ };
1039
977
  }
1040
- function createFlyAdapter(options) {
1041
- return flyAdapter({
1042
- runner: options.runner,
1043
- transport: options.transport ?? globalThis.fetch
1044
- });
978
+ function renderFlyPreparation(result) {
979
+ const lines = [
980
+ `Prepared Fly deployment artifacts for ${result.target.app} (${result.database}).`,
981
+ ...result.artifacts.map((artifact2) => ` ${artifact2.status.padEnd(9)} ${artifact2.path} ${artifact2.digest}`),
982
+ ...result.storage ? [` volume ${result.storage.volume} \u2192 ${result.storage.mountPath} (${result.storage.machines} writable Machine)`] : [],
983
+ ...result.environmentKeys.length ? [` secret ${result.environmentKeys.join(", ")}`] : [],
984
+ "",
985
+ "Next (run these yourself after authenticating with Fly):",
986
+ ...result.commands.map((command) => ` ${command}`)
987
+ ];
988
+ return `${lines.join("\n")}
989
+ `;
1045
990
  }
1046
991
 
1047
992
  // cli/create/create.ts
1048
- import { join, resolve } from "path";
993
+ import { join as join2, resolve } from "path";
1049
994
 
1050
995
  // cli/create/types.ts
1051
996
  var CreationError = class extends Error {
@@ -1060,7 +1005,7 @@ var CreationError = class extends Error {
1060
1005
  };
1061
1006
 
1062
1007
  // cli/create/create.ts
1063
- var VERSION = "0.3.0";
1008
+ var VERSION = "0.3.1";
1064
1009
  var NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1065
1010
  function files(name) {
1066
1011
  return {
@@ -1105,6 +1050,15 @@ to plan this application. The planning source of truth is
1105
1050
  \`\`\`bash
1106
1051
  npm run verify
1107
1052
  \`\`\`
1053
+
1054
+ ## Prepare deployment
1055
+
1056
+ After adding a production \`start\` script, prepare reviewable Fly deployment
1057
+ files locally. This command does not log in to or deploy to Fly:
1058
+
1059
+ \`\`\`bash
1060
+ hollow deploy prepare --target fly:${name} --database sqlite --region iad
1061
+ \`\`\`
1108
1062
  `,
1109
1063
  "tests/capture.ts": `import { rename, writeFile } from "fs/promises";
1110
1064
 
@@ -1134,7 +1088,8 @@ test("capture artifact is persisted", async () => { await persist(); });
1134
1088
  type: "module",
1135
1089
  engines: { node: ">=24" },
1136
1090
  scripts: { check: "tsc --noEmit", test: "vitest run", verify: "npm run check && npm run test && node .sleepyhollow/verify.ts" },
1137
- devDependencies: { "@sleepy-hollow/framework": `^${FRAMEWORK_VERSION}`, typescript: "5.9.3", vitest: "4.1.11" }
1091
+ dependencies: { "@sleepy-hollow/framework": `^${FRAMEWORK_VERSION}` },
1092
+ devDependencies: { typescript: "5.9.3", vitest: "4.1.11" }
1138
1093
  },
1139
1094
  null,
1140
1095
  2
@@ -1186,7 +1141,7 @@ async function pathExists(path) {
1186
1141
  throw error;
1187
1142
  }
1188
1143
  }
1189
- var FRAMEWORK_VERSION = "0.3.0";
1144
+ var FRAMEWORK_VERSION = "0.3.1";
1190
1145
  async function createProject(options) {
1191
1146
  if (!NAME.test(options.name) || options.name.length > 64) {
1192
1147
  throw creationError(
@@ -1197,7 +1152,7 @@ async function createProject(options) {
1197
1152
  );
1198
1153
  }
1199
1154
  const parent = resolve(options.directory);
1200
- const destination = join(parent, options.name);
1155
+ const destination = join2(parent, options.name);
1201
1156
  if (await pathExists(destination)) {
1202
1157
  throw creationError(
1203
1158
  "SH_CREATE_DESTINATION_EXISTS",
@@ -1208,14 +1163,14 @@ async function createProject(options) {
1208
1163
  }
1209
1164
  const contents = files(options.name);
1210
1165
  const createdFiles = Object.keys(contents).sort();
1211
- const staging = join(
1166
+ const staging = join2(
1212
1167
  parent,
1213
1168
  `.${options.name}.sleepyhollow-${crypto.randomUUID()}`
1214
1169
  );
1215
1170
  try {
1216
1171
  await platform.mkdir(staging);
1217
1172
  for (const relative6 of createdFiles) {
1218
- const target = join(staging, relative6);
1173
+ const target = join2(staging, relative6);
1219
1174
  await platform.mkdir(resolve(target, ".."), { recursive: true });
1220
1175
  await platform.writeTextFile(target, contents[relative6], { createNew: true });
1221
1176
  }
@@ -1547,8 +1502,8 @@ function normalizeDiagnostics(diagnostics) {
1547
1502
  )
1548
1503
  );
1549
1504
  }
1550
- function renderDevEvent(event, json3) {
1551
- if (json3) return JSON.stringify(event);
1505
+ function renderDevEvent(event, json2) {
1506
+ if (json2) return JSON.stringify(event);
1552
1507
  const label = event.type === "diagnostic" ? `generation ${event.generation} rejected` : event.type === "shutdown" ? `development server stopped (${event.reason ?? "failure"})` : `${event.type} active at ${event.url} (generation ${event.generation}, ${event.routeCount} routes)`;
1553
1508
  const lines = [`[${event.sequence}] ${label}`];
1554
1509
  if (event.changedFiles?.length) {
@@ -1575,20 +1530,20 @@ function renderDevEvent(event, json3) {
1575
1530
  // cli/dev/command.ts
1576
1531
  var diagnostic2 = (code, summary, correction) => ({ code, severity: "error", summary, correction });
1577
1532
  function parse2(args) {
1578
- let json3 = false;
1533
+ let json2 = false;
1579
1534
  let port = 8e3;
1580
1535
  let portSeen = false;
1581
1536
  for (let index = 0; index < args.length; index += 1) {
1582
1537
  const value = args[index];
1583
1538
  if (value === "--json") {
1584
- if (json3) {
1539
+ if (json2) {
1585
1540
  return diagnostic2(
1586
1541
  "SH_DEV_USAGE_INVALID",
1587
1542
  "The --json option was provided more than once",
1588
1543
  "Use hollow dev [--port <1-65535>] [--json]."
1589
1544
  );
1590
1545
  }
1591
- json3 = true;
1546
+ json2 = true;
1592
1547
  continue;
1593
1548
  }
1594
1549
  if (value === "--port") {
@@ -1624,9 +1579,9 @@ function parse2(args) {
1624
1579
  "Use hollow dev [--port <1-65535>] [--json]."
1625
1580
  );
1626
1581
  }
1627
- return { json: json3, port };
1582
+ return { json: json2, port };
1628
1583
  }
1629
- function failure(error, fallback) {
1584
+ function failure2(error, fallback) {
1630
1585
  return normalizeDiagnostics(
1631
1586
  error instanceof DevCommandError ? error.diagnostics : [fallback]
1632
1587
  );
@@ -1733,7 +1688,7 @@ ${parsed.correction}`
1733
1688
  diagnostics: []
1734
1689
  });
1735
1690
  } catch (error) {
1736
- const diagnostics = failure(
1691
+ const diagnostics = failure2(
1737
1692
  error,
1738
1693
  diagnostic2(
1739
1694
  "SH_DEV_STARTUP_FAILED",
@@ -1759,7 +1714,7 @@ ${parsed.correction}`
1759
1714
  type: "diagnostic",
1760
1715
  state: "rejected",
1761
1716
  generation,
1762
- diagnostics: failure(
1717
+ diagnostics: failure2(
1763
1718
  error,
1764
1719
  diagnostic2(
1765
1720
  "SH_DEV_WATCH_FAILED",
@@ -1808,7 +1763,7 @@ ${parsed.correction}`
1808
1763
  state: "rejected",
1809
1764
  generation: nextGeneration,
1810
1765
  changedFiles: changes,
1811
- diagnostics: failure(
1766
+ diagnostics: failure2(
1812
1767
  error,
1813
1768
  diagnostic2(
1814
1769
  "SH_DEV_RELOAD_FAILED",
@@ -1843,7 +1798,7 @@ ${parsed.correction}`
1843
1798
  state: "rejected",
1844
1799
  generation: nextGeneration,
1845
1800
  changedFiles: changes,
1846
- diagnostics: failure(
1801
+ diagnostics: failure2(
1847
1802
  error,
1848
1803
  diagnostic2(
1849
1804
  "SH_DEV_ACTIVATION_FAILED",
@@ -1876,7 +1831,7 @@ ${parsed.correction}`
1876
1831
  type: "diagnostic",
1877
1832
  state: "rejected",
1878
1833
  generation,
1879
- diagnostics: failure(
1834
+ diagnostics: failure2(
1880
1835
  error,
1881
1836
  diagnostic2(
1882
1837
  "SH_DEV_SUPERVISOR_FAILED",
@@ -1999,12 +1954,12 @@ async function loadRuntime(projectRoot) {
1999
1954
  });
2000
1955
  }
2001
1956
  const routes2 = await discoverRoutes(realApiRoot);
2002
- const runtime = await composeProjectSecurity(routes2, {
1957
+ const runtime2 = await composeProjectSecurity(routes2, {
2003
1958
  mode: "development",
2004
1959
  root,
2005
1960
  ...project.securityModule === void 0 ? {} : { securityModule: project.securityModule }
2006
1961
  });
2007
- return { runtime, routeCount: runtime.routes.length };
1962
+ return { runtime: runtime2, routeCount: runtime2.routes.length };
2008
1963
  }
2009
1964
  async function runDevWorker(args) {
2010
1965
  if (args.length !== 4 || !["validate", "serve"].includes(args[0])) return 2;
@@ -2012,12 +1967,12 @@ async function runDevWorker(args) {
2012
1967
  const port = Number(rawPort);
2013
1968
  if (hostname !== "127.0.0.1" || !Number.isInteger(port) || port < 1 || port > 65535) return 2;
2014
1969
  try {
2015
- const { runtime, routeCount } = await loadRuntime(projectRoot);
1970
+ const { runtime: runtime2, routeCount } = await loadRuntime(projectRoot);
2016
1971
  if (intent === "validate") {
2017
1972
  console.log(JSON.stringify({ ready: true, routeCount }));
2018
1973
  return 0;
2019
1974
  }
2020
- const server = platform.serve({ hostname, port }, (request) => runtime.fetch(request));
1975
+ const server = platform.serve({ hostname, port }, (request) => runtime2.fetch(request));
2021
1976
  console.log(JSON.stringify({ ready: true, routeCount }));
2022
1977
  await new Promise((resolve5, reject) => {
2023
1978
  server.once("close", resolve5);
@@ -2036,10 +1991,10 @@ async function runDevWorker(args) {
2036
1991
  }
2037
1992
 
2038
1993
  // cli/generate/artifacts.ts
2039
- import { basename, dirname, join as join2 } from "path";
1994
+ import { basename, dirname, join as join3 } from "path";
2040
1995
 
2041
1996
  // cli/generate/canonical.ts
2042
- import { createHash } from "crypto";
1997
+ import { createHash as createHash2 } from "crypto";
2043
1998
  function stable(value) {
2044
1999
  if (Array.isArray(value)) return value.map(stable);
2045
2000
  if (value && typeof value === "object") {
@@ -2053,8 +2008,8 @@ function canonicalJson(value) {
2053
2008
  return `${JSON.stringify(stable(value), null, 2)}
2054
2009
  `;
2055
2010
  }
2056
- function digest(content) {
2057
- return createHash("sha256").update(content).digest("hex");
2011
+ function digest2(content) {
2012
+ return createHash2("sha256").update(content).digest("hex");
2058
2013
  }
2059
2014
  function stableSummary(value) {
2060
2015
  return JSON.stringify(stable(value));
@@ -2909,7 +2864,7 @@ var ownedPaths = [
2909
2864
  "manifest.json"
2910
2865
  ];
2911
2866
  function artifact(path, content) {
2912
- return { path, content, digest: digest(content) };
2867
+ return { path, content, digest: digest2(content) };
2913
2868
  }
2914
2869
  function renderArtifacts(inventory2) {
2915
2870
  const normalized = normalizeInventory(inventory2);
@@ -2924,15 +2879,15 @@ function renderArtifacts(inventory2) {
2924
2879
  ];
2925
2880
  const manifestContent = canonicalJson({
2926
2881
  schema: "sleepy-hollow-generated-manifest/v1",
2927
- generatorVersion: "0.3.0",
2882
+ generatorVersion: "0.3.1",
2928
2883
  serviceId: normalized.serviceId,
2929
- inputDigest: digest(input),
2884
+ inputDigest: digest2(input),
2930
2885
  artifacts: Object.fromEntries(
2931
2886
  rendered.map((item) => [item.path, item.digest])
2932
2887
  )
2933
2888
  });
2934
2889
  return {
2935
- inputDigest: digest(input),
2890
+ inputDigest: digest2(input),
2936
2891
  artifacts: [...rendered, artifact("manifest.json", manifestContent)]
2937
2892
  };
2938
2893
  }
@@ -2958,7 +2913,7 @@ async function copyEntry(source, target) {
2958
2913
  if (info.isDirectory()) {
2959
2914
  await platform.mkdir(target, { recursive: true });
2960
2915
  for await (const entry of platform.readDir(source)) {
2961
- await copyEntry(join2(source, entry.name), join2(target, entry.name));
2916
+ await copyEntry(join3(source, entry.name), join3(target, entry.name));
2962
2917
  }
2963
2918
  return;
2964
2919
  }
@@ -2975,28 +2930,28 @@ function outputFailure(error) {
2975
2930
  }]);
2976
2931
  }
2977
2932
  async function previousOwned(generatedDirectory) {
2978
- const source = await readText(join2(generatedDirectory, "manifest.json"));
2933
+ const source = await readText(join3(generatedDirectory, "manifest.json"));
2979
2934
  if (!source) return void 0;
2980
2935
  try {
2981
2936
  const manifest = JSON.parse(source);
2982
2937
  if (manifest.schema !== "sleepy-hollow-generated-manifest/v1") {
2983
2938
  return void 0;
2984
2939
  }
2985
- const artifacts = manifest.artifacts;
2986
- if (!artifacts || typeof artifacts !== "object" || Array.isArray(artifacts)) return void 0;
2987
- return /* @__PURE__ */ new Set([...Object.keys(artifacts), "manifest.json"]);
2940
+ const artifacts2 = manifest.artifacts;
2941
+ if (!artifacts2 || typeof artifacts2 !== "object" || Array.isArray(artifacts2)) return void 0;
2942
+ return /* @__PURE__ */ new Set([...Object.keys(artifacts2), "manifest.json"]);
2988
2943
  } catch {
2989
2944
  return void 0;
2990
2945
  }
2991
2946
  }
2992
2947
  async function writeAtomically(projectRoot, rendered) {
2993
- const target = join2(projectRoot, "generated");
2948
+ const target = join3(projectRoot, "generated");
2994
2949
  await platform.mkdir(projectRoot, { recursive: true });
2995
2950
  const targetExists = await exists(target);
2996
2951
  const priorOwned = targetExists ? await previousOwned(target) : void 0;
2997
2952
  if (targetExists && !priorOwned) {
2998
2953
  for (const path of ownedPaths) {
2999
- if (await exists(join2(target, path))) {
2954
+ if (await exists(join3(target, path))) {
3000
2955
  throw new GenerationError([{
3001
2956
  code: "SH_GENERATE_UNKNOWN_TARGET",
3002
2957
  summary: `Refusing to replace unowned file generated/${path}`,
@@ -3016,11 +2971,11 @@ async function writeAtomically(projectRoot, rendered) {
3016
2971
  if (targetExists) {
3017
2972
  for await (const entry of platform.readDir(target)) {
3018
2973
  if (priorOwned?.has(entry.name)) continue;
3019
- await copyEntry(join2(target, entry.name), join2(stage, entry.name));
2974
+ await copyEntry(join3(target, entry.name), join3(stage, entry.name));
3020
2975
  }
3021
2976
  }
3022
2977
  for (const item of rendered.artifacts) {
3023
- await platform.writeTextFile(join2(stage, item.path), item.content);
2978
+ await platform.writeTextFile(join3(stage, item.path), item.content);
3024
2979
  }
3025
2980
  if (targetExists) {
3026
2981
  await platform.rename(target, backup);
@@ -3038,10 +2993,10 @@ async function writeAtomically(projectRoot, rendered) {
3038
2993
  }
3039
2994
  async function generate(options) {
3040
2995
  const rendered = renderArtifacts(options.inventory);
3041
- const generatedDirectory = join2(options.projectRoot, "generated");
3042
- const artifacts = await Promise.all(rendered.artifacts.map(async (item) => {
3043
- const current = await readText(join2(generatedDirectory, item.path));
3044
- const actualDigest = current === void 0 ? void 0 : digest(current);
2996
+ const generatedDirectory = join3(options.projectRoot, "generated");
2997
+ const artifacts2 = await Promise.all(rendered.artifacts.map(async (item) => {
2998
+ const current = await readText(join3(generatedDirectory, item.path));
2999
+ const actualDigest = current === void 0 ? void 0 : digest2(current);
3045
3000
  return {
3046
3001
  path: `generated/${item.path}`,
3047
3002
  digest: item.digest,
@@ -3054,7 +3009,7 @@ async function generate(options) {
3054
3009
  );
3055
3010
  let previous = options.previousOpenApi;
3056
3011
  if (!previous) {
3057
- const source = await readText(join2(generatedDirectory, "openapi.json"));
3012
+ const source = await readText(join3(generatedDirectory, "openapi.json"));
3058
3013
  if (source) {
3059
3014
  try {
3060
3015
  previous = JSON.parse(source);
@@ -3072,7 +3027,7 @@ async function generate(options) {
3072
3027
  digest: "absent",
3073
3028
  stale: true
3074
3029
  }));
3075
- const inspectedArtifacts = [...artifacts, ...extraOwned];
3030
+ const inspectedArtifacts = [...artifacts2, ...extraOwned];
3076
3031
  const stale = inspectedArtifacts.filter((item) => item.stale);
3077
3032
  const diagnostics = stale.map((item) => ({
3078
3033
  code: "SH_GENERATE_ARTIFACT_STALE",
@@ -3100,7 +3055,7 @@ async function generate(options) {
3100
3055
  schema: "sleepy-hollow-generate-result/v1",
3101
3056
  serviceId: options.inventory.serviceId,
3102
3057
  inputDigest: rendered.inputDigest,
3103
- artifacts: artifacts.map((item) => ({
3058
+ artifacts: artifacts2.map((item) => ({
3104
3059
  ...item,
3105
3060
  actualDigest: item.digest,
3106
3061
  stale: false
@@ -3198,7 +3153,7 @@ function inventoryFromRoutes(routes2, options) {
3198
3153
  return {
3199
3154
  serviceId: options.serviceId,
3200
3155
  title: options.title ?? options.serviceId,
3201
- version: options.version ?? "0.3.0",
3156
+ version: options.version ?? "0.3.1",
3202
3157
  ...options.description ? { description: options.description } : {},
3203
3158
  operations,
3204
3159
  securitySchemes: options.securitySchemes ?? {}
@@ -3211,7 +3166,7 @@ function generateContracts(options) {
3211
3166
  }
3212
3167
 
3213
3168
  // cli/test/result.ts
3214
- import { createHash as createHash2 } from "crypto";
3169
+ import { createHash as createHash3 } from "crypto";
3215
3170
  var MAX_EVIDENCE = 8 * 1024;
3216
3171
  function safe(value) {
3217
3172
  return value.replace(
@@ -3235,12 +3190,12 @@ function eventKey(event) {
3235
3190
  return `${event.file}\0${event.name}`;
3236
3191
  }
3237
3192
  function unmappedId(event) {
3238
- return `unmapped:${createHash2("sha256").update(eventKey(event)).digest("hex").slice(0, 12)}`;
3193
+ return `unmapped:${createHash3("sha256").update(eventKey(event)).digest("hex").slice(0, 12)}`;
3239
3194
  }
3240
- function normalizeRunnerResult(plan3, inventory2, runner) {
3241
- const diagnostics = [...plan3.diagnostics];
3195
+ function normalizeRunnerResult(plan2, inventory2, runner) {
3196
+ const diagnostics = [...plan2.diagnostics];
3242
3197
  const selectedEntries = inventory2.manifest.tests.filter(
3243
- (test) => plan3.selectedTests.includes(test.id)
3198
+ (test) => plan2.selectedTests.includes(test.id)
3244
3199
  );
3245
3200
  const byEvent = new Map(selectedEntries.map((test) => [
3246
3201
  eventKey({ file: test.sourcePath, name: test.registeredName }),
@@ -3343,7 +3298,7 @@ function normalizeRunnerResult(plan3, inventory2, runner) {
3343
3298
  }
3344
3299
  tests.sort((left, right) => left.id.localeCompare(right.id));
3345
3300
  const selectedRequirements = inventory2.requirements.filter(
3346
- (requirement) => plan3.selectedRequirements.includes(requirement.id)
3301
+ (requirement) => plan2.selectedRequirements.includes(requirement.id)
3347
3302
  );
3348
3303
  const selectedManifest = {
3349
3304
  schema: "sleepy-hollow-test-manifest/v1",
@@ -3373,10 +3328,10 @@ function normalizeRunnerResult(plan3, inventory2, runner) {
3373
3328
  schema: "sleepy-hollow-test-result/v1",
3374
3329
  ok,
3375
3330
  command: "test",
3376
- requestedScope: plan3.requestedScope,
3377
- effectiveScope: plan3.effectiveScope,
3378
- selectedRequirements: Object.freeze([...plan3.selectedRequirements]),
3379
- selectedTests: Object.freeze([...plan3.selectedTests]),
3331
+ requestedScope: plan2.requestedScope,
3332
+ effectiveScope: plan2.effectiveScope,
3333
+ selectedRequirements: Object.freeze([...plan2.selectedRequirements]),
3334
+ selectedTests: Object.freeze([...plan2.selectedTests]),
3380
3335
  tests: Object.freeze(tests),
3381
3336
  criteria: Object.freeze(criteria),
3382
3337
  diagnostics: Object.freeze(diagnostics),
@@ -3394,8 +3349,8 @@ function normalizeRunnerResult(plan3, inventory2, runner) {
3394
3349
  verificationStateChanged: false
3395
3350
  });
3396
3351
  }
3397
- function failedWithoutRunner(plan3, inventory2) {
3398
- return normalizeRunnerResult(plan3, inventory2, {
3352
+ function failedWithoutRunner(plan2, inventory2) {
3353
+ return normalizeRunnerResult(plan2, inventory2, {
3399
3354
  status: "failed",
3400
3355
  durationMs: 0,
3401
3356
  events: []
@@ -3418,9 +3373,9 @@ function projectPath(root, path) {
3418
3373
  }
3419
3374
  return local;
3420
3375
  }
3421
- function invocation(plan3, options) {
3376
+ function invocation(plan2, options) {
3422
3377
  const root = resolve4(options.projectRoot);
3423
- const files2 = [...new Set(plan3.files.map((path) => projectPath(root, path)))].sort();
3378
+ const files2 = [...new Set(plan2.files.map((path) => projectPath(root, path)))].sort();
3424
3379
  if (files2.length === 0) {
3425
3380
  throw new TypeError(
3426
3381
  "A native test run requires at least one exact source file"
@@ -3430,7 +3385,7 @@ function invocation(plan3, options) {
3430
3385
  "./node_modules/vitest/vitest.mjs",
3431
3386
  "run",
3432
3387
  "--reporter=verbose",
3433
- ...plan3.filter ? ["--testNamePattern", plan3.filter] : [],
3388
+ ...plan2.filter ? ["--testNamePattern", plan2.filter] : [],
3434
3389
  ...files2
3435
3390
  ];
3436
3391
  return Object.freeze({
@@ -3489,14 +3444,14 @@ function parseTap(source, inventory2, failureEvidence) {
3489
3444
  }
3490
3445
  return { valid: validHeader && validPlan && validStructure, events };
3491
3446
  }
3492
- async function runNative(plan3, inventory2, options) {
3447
+ async function runNative(plan2, inventory2, options) {
3493
3448
  const timeoutMs = options.timeoutMs ?? MAX_TIMEOUT2;
3494
3449
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT2) {
3495
3450
  throw new TypeError(
3496
3451
  "Test timeout must be from 1 through 600000 milliseconds"
3497
3452
  );
3498
3453
  }
3499
- const command = invocation(plan3, options);
3454
+ const command = invocation(plan2, options);
3500
3455
  const started = performance.now();
3501
3456
  const child = new platform.Command(command.command, {
3502
3457
  cwd: command.cwd,
@@ -3556,7 +3511,7 @@ async function runNative(plan3, inventory2, options) {
3556
3511
  }
3557
3512
 
3558
3513
  // cli/test/scope.ts
3559
- import { isAbsolute as isAbsolute4, normalize, sep as sep5 } from "path";
3514
+ import { isAbsolute as isAbsolute4, normalize as normalize2, sep as sep5 } from "path";
3560
3515
  var stableId = /^[A-Za-z][A-Za-z0-9._-]*$/;
3561
3516
  function sorted2(values) {
3562
3517
  return [...new Set(values)].sort();
@@ -3572,13 +3527,13 @@ function diagnostic4(code, severity, summary, correction, requirementId) {
3572
3527
  }
3573
3528
  function safePath(path) {
3574
3529
  const portable3 = path.split(sep5).join("/");
3575
- const normalized = normalize(path).split(sep5).join("/");
3530
+ const normalized = normalize2(path).split(sep5).join("/");
3576
3531
  return !isAbsolute4(path) && portable3 !== "" && portable3 !== ".." && !portable3.startsWith("../") && normalized === portable3 && !portable3.includes("\0");
3577
3532
  }
3578
3533
  function escaped(value) {
3579
3534
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3580
3535
  }
3581
- function plan2(inventory2, requestedScope) {
3536
+ function plan(inventory2, requestedScope) {
3582
3537
  const diagnostics = [];
3583
3538
  const requirements2 = /* @__PURE__ */ new Map();
3584
3539
  for (const requirement of inventory2.requirements) {
@@ -3843,14 +3798,14 @@ function renderJson(result) {
3843
3798
 
3844
3799
  // cli/test/command.ts
3845
3800
  function parse3(args) {
3846
- let json3 = false;
3801
+ let json2 = false;
3847
3802
  let scope = { kind: "full" };
3848
3803
  let scopeSeen = false;
3849
3804
  for (let index = 0; index < args.length; index++) {
3850
3805
  const argument = args[index];
3851
3806
  if (argument === "--json") {
3852
- if (json3) throw new TypeError("--json may appear once");
3853
- json3 = true;
3807
+ if (json2) throw new TypeError("--json may appear once");
3808
+ json2 = true;
3854
3809
  continue;
3855
3810
  }
3856
3811
  if (argument === "--full") {
@@ -3893,9 +3848,9 @@ function parse3(args) {
3893
3848
  }
3894
3849
  throw new TypeError(`Unknown test argument: ${argument.slice(0, 80)}`);
3895
3850
  }
3896
- return { json: json3, scope };
3851
+ return { json: json2, scope };
3897
3852
  }
3898
- function usage(summary, json3, io) {
3853
+ function usage(summary, json2, io) {
3899
3854
  const result = {
3900
3855
  ok: false,
3901
3856
  command: "test",
@@ -3908,7 +3863,7 @@ function usage(summary, json3, io) {
3908
3863
  }]
3909
3864
  };
3910
3865
  io.stderr(
3911
- json3 ? JSON.stringify(result) : `SH_TEST_USAGE_INVALID: ${summary}
3866
+ json2 ? JSON.stringify(result) : `SH_TEST_USAGE_INVALID: ${summary}
3912
3867
  ${result.diagnostics[0].correction}`
3913
3868
  );
3914
3869
  return 2;
@@ -3944,9 +3899,9 @@ function loadFailure(scope) {
3944
3899
  verificationStateChanged: false
3945
3900
  };
3946
3901
  }
3947
- function emit(result, json3, io) {
3902
+ function emit(result, json2, io) {
3948
3903
  (result.ok ? io.stdout : io.stderr)(
3949
- json3 ? renderJson(result) : renderHuman(result)
3904
+ json2 ? renderJson(result) : renderHuman(result)
3950
3905
  );
3951
3906
  return result.ok ? 0 : 1;
3952
3907
  }
@@ -3969,7 +3924,7 @@ async function execute(args, io, load, runner) {
3969
3924
  }
3970
3925
  const capturePath = `${io.cwd}/generated/capture.json`;
3971
3926
  inventory2 = { ...inventory2, captureArtifactPath: capturePath };
3972
- const testPlan = plan2(inventory2, parsed.scope);
3927
+ const testPlan = plan(inventory2, parsed.scope);
3973
3928
  if (testPlan.diagnostics.some((item) => item.severity === "error")) {
3974
3929
  return emit(failedWithoutRunner(testPlan, inventory2), parsed.json, io);
3975
3930
  }
@@ -4052,7 +4007,7 @@ var CLI_COMMANDS = [
4052
4007
  "generate",
4053
4008
  "deploy"
4054
4009
  ];
4055
- var CLI_VERSION = "0.3.0";
4010
+ var CLI_VERSION = "0.3.1";
4056
4011
  var commandMetadata = {
4057
4012
  create: {
4058
4013
  description: "Create one deterministic Sleepy Hollow project.",
@@ -4075,8 +4030,8 @@ var commandMetadata = {
4075
4030
  usage: "hollow generate [--check] [--json]"
4076
4031
  },
4077
4032
  deploy: {
4078
- description: "Preview and deliver one verified platform Deploy revision.",
4079
- usage: "hollow deploy [--preview] [--confirm <digest>] [--json]"
4033
+ description: "Prepare local Fly deployment artifacts; Fly performs deployment.",
4034
+ usage: "hollow deploy prepare --target fly:<app> --database <sqlite|postgres> [--region <region>] [--force] [--json]"
4080
4035
  }
4081
4036
  };
4082
4037
  function topLevelHelp() {
@@ -4207,9 +4162,9 @@ function renderJson2(result) {
4207
4162
  diagnostics: result.diagnostics
4208
4163
  });
4209
4164
  }
4210
- function emitResult(result, json3, io, exitCode) {
4165
+ function emitResult(result, json2, io, exitCode) {
4211
4166
  const normalized = normalizeResult(result);
4212
- const output = json3 ? renderJson2(normalized) : renderHuman2(normalized);
4167
+ const output = json2 ? renderJson2(normalized) : renderHuman2(normalized);
4213
4168
  (normalized.ok ? io.stdout : io.stderr)(output);
4214
4169
  return exitCode ?? (normalized.ok ? 0 : 1);
4215
4170
  }
@@ -4227,9 +4182,9 @@ function usageResult(code, summary) {
4227
4182
  }]
4228
4183
  });
4229
4184
  }
4230
- function emitUsage(code, summary, json3, io) {
4185
+ function emitUsage(code, summary, json2, io) {
4231
4186
  io.stderr(
4232
- json3 ? usageResult(code, summary) : `${code}: ${summary}
4187
+ json2 ? usageResult(code, summary) : `${code}: ${summary}
4233
4188
  Run hollow --help and use one documented invocation.`
4234
4189
  );
4235
4190
  return 2;
@@ -4264,12 +4219,12 @@ function confirmationFailure(result) {
4264
4219
  };
4265
4220
  }
4266
4221
  async function runCommandSurface(args, io, handlers) {
4267
- const json3 = args.includes("--json");
4222
+ const json2 = args.includes("--json");
4268
4223
  if (containsModelOption(args)) {
4269
4224
  return emitUsage(
4270
4225
  "SH_CLI_USAGE_INVALID",
4271
4226
  "Model selection is not part of the Sleepy Hollow CLI.",
4272
- json3,
4227
+ json2,
4273
4228
  io
4274
4229
  );
4275
4230
  }
@@ -4289,7 +4244,7 @@ async function runCommandSurface(args, io, handlers) {
4289
4244
  return emitUsage(
4290
4245
  "SH_CLI_USAGE_INVALID",
4291
4246
  "Help expects exactly one supported command.",
4292
- json3,
4247
+ json2,
4293
4248
  io
4294
4249
  );
4295
4250
  }
@@ -4298,7 +4253,7 @@ async function runCommandSurface(args, io, handlers) {
4298
4253
  return emitUsage(
4299
4254
  invalid === "option" ? "SH_CLI_USAGE_INVALID" : "SH_CLI_COMMAND_UNKNOWN",
4300
4255
  `Unknown ${invalid}: ${safeToken(args[0])}`,
4301
- json3,
4256
+ json2,
4302
4257
  io
4303
4258
  );
4304
4259
  }
@@ -4311,7 +4266,7 @@ async function runCommandSurface(args, io, handlers) {
4311
4266
  return emitUsage(
4312
4267
  "SH_CLI_USAGE_INVALID",
4313
4268
  `Invalid help invocation for ${selected}.`,
4314
- json3,
4269
+ json2,
4315
4270
  io
4316
4271
  );
4317
4272
  }
@@ -4319,7 +4274,7 @@ async function runCommandSurface(args, io, handlers) {
4319
4274
  const response = await handlers[selected]({
4320
4275
  args: args.slice(1),
4321
4276
  cwd: io.cwd,
4322
- json: json3,
4277
+ json: json2,
4323
4278
  io
4324
4279
  });
4325
4280
  if (response.result.command !== selected) {
@@ -4337,14 +4292,14 @@ async function runCommandSurface(args, io, handlers) {
4337
4292
  }
4338
4293
  if (!response.result.ok) {
4339
4294
  if (response.rendered) return response.exitCode ?? 1;
4340
- return emitResult(response.result, json3, io, response.exitCode);
4295
+ return emitResult(response.result, json2, io, response.exitCode);
4341
4296
  }
4342
4297
  if (response.rendered) return response.exitCode ?? 0;
4343
4298
  if (!response.operation || response.operation.intent === "preview") {
4344
- return emitResult(response.result, json3, io, response.exitCode);
4299
+ return emitResult(response.result, json2, io, response.exitCode);
4345
4300
  }
4346
4301
  if (!response.operation.providedConfirmation || response.operation.providedConfirmation !== response.operation.confirmationDigest) {
4347
- return emitResult(confirmationFailure(response.result), json3, io);
4302
+ return emitResult(confirmationFailure(response.result), json2, io);
4348
4303
  }
4349
4304
  const applied = await response.operation.apply();
4350
4305
  if (applied.command !== selected) {
@@ -4353,7 +4308,7 @@ async function runCommandSurface(args, io, handlers) {
4353
4308
  if (!applied.schema && !applied.version) {
4354
4309
  throw new TypeError("Command application returned an unversioned result");
4355
4310
  }
4356
- return emitResult(applied, json3, io);
4311
+ return emitResult(applied, json2, io);
4357
4312
  } catch {
4358
4313
  return emitResult(
4359
4314
  {
@@ -4368,7 +4323,7 @@ async function runCommandSurface(args, io, handlers) {
4368
4323
  correction: "Inspect the project evidence and retry the documented command."
4369
4324
  }]
4370
4325
  },
4371
- json3,
4326
+ json2,
4372
4327
  io
4373
4328
  );
4374
4329
  }
@@ -4438,7 +4393,7 @@ ${created.nextActions.map((item) => ` ${item}`).join("\n")}`,
4438
4393
  summary: "Project creation failed",
4439
4394
  correction: "Inspect the protected failure and retry safely."
4440
4395
  }];
4441
- const json3 = {
4396
+ const json2 = {
4442
4397
  ok: false,
4443
4398
  command: "create",
4444
4399
  version: CLI_VERSION,
@@ -4454,7 +4409,7 @@ ${created.nextActions.map((item) => ` ${item}`).join("\n")}`,
4454
4409
  version: CLI_VERSION,
4455
4410
  summary: "Project creation failed.",
4456
4411
  diagnostics: creationDiagnostics(diagnostics, cwd),
4457
- json: json3
4412
+ json: json2
4458
4413
  }
4459
4414
  };
4460
4415
  }
@@ -4500,11 +4455,11 @@ var generate2 = async ({ args, cwd }) => {
4500
4455
  }
4501
4456
  const check = args.includes("--check");
4502
4457
  try {
4503
- const configPath = join3(cwd, "sleepyhollow.config.ts");
4458
+ const configPath = join4(cwd, "sleepyhollow.config.ts");
4504
4459
  const imported = await import(`${pathToFileURL2(configPath).href}?generate=${Date.now()}`);
4505
4460
  const config = imported.default;
4506
4461
  const routes2 = await discoverRoutes(
4507
- join3(cwd, config.apiDirectory ?? "api")
4462
+ join4(cwd, config.apiDirectory ?? "api")
4508
4463
  );
4509
4464
  const generated = await generateContracts({
4510
4465
  inventory: inventoryFromRoutes(routes2, {
@@ -4538,7 +4493,7 @@ Review ${generated.changes.length} contract change(s).` : ""}`;
4538
4493
  summary: "Contract generation failed",
4539
4494
  correction: "Inspect route, schema, configuration, and output diagnostics before retrying."
4540
4495
  }];
4541
- const json3 = {
4496
+ const json2 = {
4542
4497
  ok: false,
4543
4498
  command: "generate",
4544
4499
  schema: "sleepy-hollow-generate-result/v1",
@@ -4556,7 +4511,7 @@ Review ${generated.changes.length} contract change(s).` : ""}`;
4556
4511
  schema: "sleepy-hollow-generate-result/v1",
4557
4512
  summary: "Contract generation failed.",
4558
4513
  diagnostics: generationDiagnostics(diagnostics),
4559
- json: json3
4514
+ json: json2
4560
4515
  }
4561
4516
  };
4562
4517
  }
@@ -4618,123 +4573,76 @@ function checkHandler(loader) {
4618
4573
  };
4619
4574
  };
4620
4575
  }
4621
- function deployPlanDigest(plan3) {
4622
- const encoded = new TextEncoder().encode(JSON.stringify(plan3));
4623
- let hash = 2166136261;
4624
- for (const byte of encoded) {
4625
- hash ^= byte;
4626
- hash = Math.imul(hash, 16777619) >>> 0;
4627
- }
4628
- return hash.toString(16).padStart(8, "0");
4629
- }
4630
- function deployHandler(load, adapter, token) {
4576
+ function deployHandler() {
4631
4577
  return async ({ args, cwd }) => {
4632
- const index = args.indexOf("--confirm");
4633
- const provided = index >= 0 ? args[index + 1] : void 0;
4634
- const preview = args.includes("--preview");
4635
- let inventory2;
4636
- try {
4637
- inventory2 = await load({ projectRoot: cwd });
4638
- } catch (error) {
4639
- return deployPrecondition(error);
4578
+ if (args[0] !== "prepare") {
4579
+ return usage2(
4580
+ "deploy",
4581
+ "Expected hollow deploy prepare --target fly:<app> --database <sqlite|postgres> [--region <region>] [--force] [--json]."
4582
+ );
4640
4583
  }
4641
- const built = buildDeployPlan(inventory2);
4642
- const digest2 = deployPlanDigest(built);
4643
- const run = async (confirmed) => {
4644
- let result;
4645
- try {
4646
- result = await runDeployment(
4647
- {
4648
- inventory: inventory2,
4649
- token: (token ?? resolveDeployToken)(),
4650
- confirmed,
4651
- ...confirmed ? { confirmationSource: `operator confirmed plan ${digest2}` } : {}
4652
- },
4653
- adapter ?? createFlyAdapter({
4654
- runner: {
4655
- async run() {
4656
- throw new Error("No Fly command runner was configured for this invocation.");
4657
- }
4658
- }
4659
- }),
4660
- () => (/* @__PURE__ */ new Date()).toISOString()
4661
- );
4662
- } catch (error) {
4663
- return deployPrecondition(error).result;
4584
+ let target;
4585
+ let database;
4586
+ let region;
4587
+ let force = false;
4588
+ if (args.filter((argument) => argument === "--json").length > 1) {
4589
+ return usage2("deploy", "Deployment preparation accepts --json at most once.");
4590
+ }
4591
+ for (let index = 1; index < args.length; index++) {
4592
+ const argument = args[index];
4593
+ if (argument === "--json") continue;
4594
+ if (argument === "--force" && !force) {
4595
+ force = true;
4596
+ continue;
4597
+ }
4598
+ if ((argument === "--target" || argument === "--database" || argument === "--region") && index + 1 < args.length) {
4599
+ const value = args[++index];
4600
+ if (argument === "--target" && !target) target = value;
4601
+ else if (argument === "--database" && !database && (value === "sqlite" || value === "postgres")) database = value;
4602
+ else if (argument === "--region" && !region) region = value;
4603
+ else return usage2("deploy", "Deployment preparation options must occur once with a valid value.");
4604
+ continue;
4664
4605
  }
4606
+ return usage2("deploy", "Deployment preparation options are invalid.");
4607
+ }
4608
+ if (!target?.startsWith("fly:") || !database) {
4609
+ return usage2("deploy", "Deployment preparation requires --target fly:<app> and --database sqlite or postgres.");
4610
+ }
4611
+ try {
4612
+ const prepared = await prepareFlyDeployment({
4613
+ projectRoot: cwd,
4614
+ target: { kind: "fly", app: target.slice("fly:".length) },
4615
+ database,
4616
+ ...region ? { region } : {},
4617
+ ...force ? { force: true } : {}
4618
+ });
4665
4619
  return {
4666
- ok: result.ok,
4667
- command: "deploy",
4668
- schema: result.schema,
4669
- summary: renderHumanDeployResult(result).split("\n")[0] ?? "",
4670
- diagnostics: result.diagnostics.map((item) => ({
4671
- code: item.code,
4672
- severity: item.severity,
4673
- summary: item.summary,
4674
- correction: item.correction
4675
- })),
4676
- json: JSON.parse(renderJsonDeployResult(result))
4620
+ result: {
4621
+ ok: true,
4622
+ command: "deploy",
4623
+ schema: prepared.schema,
4624
+ summary: renderFlyPreparation(prepared).trimEnd(),
4625
+ diagnostics: [],
4626
+ json: prepared
4627
+ }
4677
4628
  };
4678
- };
4679
- if (preview || built.requiresConfirmation) {
4680
- const previewed = await run(false);
4629
+ } catch (error) {
4630
+ const diagnostics = error instanceof DeploymentPreparationError ? error.diagnostics : [{
4631
+ code: "SH_DEPLOY_PREPARE_FAILED",
4632
+ summary: "Deployment preparation failed.",
4633
+ correction: "Inspect the project files and retry the documented command."
4634
+ }];
4681
4635
  return {
4636
+ exitCode: 1,
4682
4637
  result: {
4683
- ...previewed,
4684
- summary: preview ? previewed.summary : `${previewed.summary} Confirm with --confirm ${digest2}`
4685
- },
4686
- operation: {
4687
- intent: preview ? "preview" : "apply",
4688
- confirmationDigest: digest2,
4689
- ...provided ? { providedConfirmation: provided } : {},
4690
- apply: () => run(true)
4638
+ ok: false,
4639
+ command: "deploy",
4640
+ schema: "sleepy-hollow-deploy-prepare-result/v1",
4641
+ summary: diagnostics[0].summary,
4642
+ diagnostics: diagnostics.map((item) => ({ ...item, severity: "error" }))
4691
4643
  }
4692
4644
  };
4693
4645
  }
4694
- const applied = await run(true);
4695
- return { exitCode: applied.ok ? 0 : 1, result: applied };
4696
- };
4697
- }
4698
- function deployPrecondition(error) {
4699
- const summary = error instanceof Error ? error.message : "Deployment could not start.";
4700
- return {
4701
- exitCode: 1,
4702
- result: {
4703
- ok: false,
4704
- command: "deploy",
4705
- schema: "sleepy-hollow-deploy-result/v1",
4706
- summary,
4707
- diagnostics: [{
4708
- code: "SH_DEPLOY_PRECONDITION_UNMET",
4709
- severity: "error",
4710
- summary,
4711
- correction: "Resolve the reported condition and rerun hollow deploy."
4712
- }]
4713
- }
4714
- };
4715
- }
4716
- function unavailable(command) {
4717
- return ({ args }) => {
4718
- if (args.some((argument) => argument !== "--json")) {
4719
- return usage2(
4720
- command,
4721
- `${command} options are unavailable until its canonical feature is installed.`
4722
- );
4723
- }
4724
- return {
4725
- result: {
4726
- ok: false,
4727
- command,
4728
- schema: `sleepy-hollow-${command}-result/v1`,
4729
- summary: `${command} is not available in this build.`,
4730
- diagnostics: [{
4731
- code: "SH_CLI_FEATURE_UNAVAILABLE",
4732
- severity: "error",
4733
- summary: `The canonical ${command} command API is not implemented.`,
4734
- correction: "Install a build containing the governed canonical feature."
4735
- }]
4736
- }
4737
- };
4738
4646
  };
4739
4647
  }
4740
4648
  function testHandler(loader, runner) {
@@ -4800,11 +4708,7 @@ function createCliHandlers(dependencies = {}) {
4800
4708
  ),
4801
4709
  check: checkHandler(dependencies.checkInventoryLoader),
4802
4710
  generate: generate2,
4803
- deploy: dependencies.deployInventoryLoader ? deployHandler(
4804
- dependencies.deployInventoryLoader,
4805
- dependencies.deployAdapter,
4806
- dependencies.deployToken
4807
- ) : unavailable("deploy")
4711
+ deploy: deployHandler()
4808
4712
  };
4809
4713
  return Object.fromEntries(CLI_COMMANDS.map((command) => [
4810
4714
  command,
@@ -4972,7 +4876,7 @@ async function locations(options) {
4972
4876
  }
4973
4877
 
4974
4878
  // skills/sleepy-hollow/planning/parser.ts
4975
- import { createHash as createHash3 } from "crypto";
4879
+ import { createHash as createHash4 } from "crypto";
4976
4880
  import { parse as parse4 } from "yaml";
4977
4881
 
4978
4882
  // skills/sleepy-hollow/planning/planning_error.ts
@@ -5058,13 +4962,13 @@ function frontmatterOf(source, path) {
5058
4962
  schema: "core"
5059
4963
  });
5060
4964
  } catch (error) {
5061
- const failure2 = error;
5062
- const message = failure2.message ?? String(error);
4965
+ const failure3 = error;
4966
+ const message = failure3.message ?? String(error);
5063
4967
  diagnostics.push(diagnostic5(
5064
4968
  /duplicat/i.test(message) ? "SH_PLANNING_YAML_DUPLICATE_KEY" : "SH_PLANNING_YAML_INVALID",
5065
4969
  path,
5066
- (failure2.mark?.line ?? 0) + 2,
5067
- (failure2.mark?.column ?? 0) + 1,
4970
+ (failure3.mark?.line ?? 0) + 2,
4971
+ (failure3.mark?.column ?? 0) + 1,
5068
4972
  `Invalid requirement frontmatter: ${message}`,
5069
4973
  "Correct the YAML frontmatter without adding proprietary syntax."
5070
4974
  ));
@@ -5229,9 +5133,9 @@ function digestOf(source, path) {
5229
5133
  )]);
5230
5134
  }
5231
5135
  const normalized = normalizedFrontmatter + governed.slice(frontmatter.length);
5232
- return createHash3("sha256").update(normalized).digest("hex");
5136
+ return createHash4("sha256").update(normalized).digest("hex");
5233
5137
  }
5234
- function approvalOf(source, digest2, criteria) {
5138
+ function approvalOf(source, digest3, criteria) {
5235
5139
  const governance = source.slice(source.indexOf("## Governance record"));
5236
5140
  const approvalStart = governance.indexOf("### Approval");
5237
5141
  if (approvalStart < 0) return void 0;
@@ -5250,7 +5154,7 @@ function approvalOf(source, digest2, criteria) {
5250
5154
  (item) => criterionText.includes(item.id)
5251
5155
  ).map((item) => item.id);
5252
5156
  const valid = Boolean(
5253
- approver && approvedAt && decisionSource && recordedDigest === digest2 && criteria.length > 0 && approvedCriteria.length === criteria.length
5157
+ approver && approvedAt && decisionSource && recordedDigest === digest3 && criteria.length > 0 && approvedCriteria.length === criteria.length
5254
5158
  );
5255
5159
  return {
5256
5160
  approver: approver ?? "",
@@ -5495,7 +5399,7 @@ async function collect(absoluteRoot, displayRoot, serviceId2, options) {
5495
5399
  return found;
5496
5400
  }
5497
5401
  async function requirements(project, options) {
5498
- const read = options.readTextFile ?? ((path) => platform.readTextFile(path));
5402
+ const read2 = options.readTextFile ?? ((path) => platform.readTextFile(path));
5499
5403
  const roots = project.services.length > 0 ? project.services.map((service) => ({
5500
5404
  absolute: `${project.projectRoot}/${service.apiRoot}`,
5501
5405
  display: service.apiRoot,
@@ -5527,7 +5431,7 @@ async function requirements(project, options) {
5527
5431
  }
5528
5432
  let source;
5529
5433
  try {
5530
- source = await read(file.absolutePath);
5434
+ source = await read2(file.absolutePath);
5531
5435
  } catch {
5532
5436
  diagnostics.push(issue2(
5533
5437
  "SH_EVIDENCE_REQUIREMENT_UNREADABLE",
@@ -5617,10 +5521,10 @@ async function requirements(project, options) {
5617
5521
  var CAPTURE_FILE = "capture.json";
5618
5522
  async function capture(project, options) {
5619
5523
  const path = `${project.projectRoot}/${project.generatedDirectory}/${CAPTURE_FILE}`;
5620
- const read = options.readTextFile ?? ((target) => platform.readTextFile(target));
5524
+ const read2 = options.readTextFile ?? ((target) => platform.readTextFile(target));
5621
5525
  let source;
5622
5526
  try {
5623
- source = await read(path);
5527
+ source = await read2(path);
5624
5528
  } catch {
5625
5529
  throw new EvidenceError([{
5626
5530
  code: "SH_EVIDENCE_CAPTURE_MISSING",
@@ -5804,25 +5708,6 @@ async function checkLoader(projectRoot, revision2, _scope) {
5804
5708
  }
5805
5709
  };
5806
5710
  }
5807
- async function deployLoader(projectRoot, options) {
5808
- const project = await locations({ projectRoot });
5809
- const verification = verifyProject(
5810
- await checkLoader(projectRoot, options.revision, void 0)
5811
- );
5812
- return {
5813
- projectRootDisplay: projectRoot,
5814
- target: options.target,
5815
- revision: options.revision,
5816
- verification,
5817
- environmentKeys: [],
5818
- deployedEnvironmentKeys: [],
5819
- contractChanges: [],
5820
- openApiPath: `${project.generatedDirectory}/openapi.json`,
5821
- documentationPath: `${project.generatedDirectory}/docs.html`,
5822
- smokeTests: [],
5823
- firstExternalDeployment: true
5824
- };
5825
- }
5826
5711
  async function testLoader(projectRoot) {
5827
5712
  const project = await locations({ projectRoot });
5828
5713
  const governed = await requirements(project, { projectRoot });
@@ -5857,12 +5742,6 @@ function createCheckInventoryLoader(options) {
5857
5742
  function createTestInventoryLoader() {
5858
5743
  return (request) => testLoader(request.projectRoot);
5859
5744
  }
5860
- function createDeployInventoryLoader(options) {
5861
- return (request) => deployLoader(request.projectRoot, {
5862
- revision: typeof options.revision === "function" ? options.revision() : options.revision,
5863
- target: options.target
5864
- });
5865
- }
5866
5745
 
5867
5746
  // cli/main.ts
5868
5747
  var VERSION2 = CLI_VERSION;
@@ -5875,13 +5754,6 @@ function revision() {
5875
5754
  }
5876
5755
  };
5877
5756
  }
5878
- function projectName() {
5879
- try {
5880
- return platform.env.get("SLEEPY_HOLLOW_PROJECT") ?? platform.cwd().split("/").filter(Boolean).pop() ?? "sleepy-hollow";
5881
- } catch {
5882
- return "sleepy-hollow";
5883
- }
5884
- }
5885
5757
  function runCli(args, io, dependencies = {}) {
5886
5758
  return runCommandSurface(args, io, createCliHandlers(dependencies));
5887
5759
  }
@@ -5895,11 +5767,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL4(process.argv[1]).href)
5895
5767
  stderr: console.error
5896
5768
  }, {
5897
5769
  checkInventoryLoader: createCheckInventoryLoader({ revision: revision() }),
5898
- testInventoryLoader: createTestInventoryLoader(),
5899
- deployInventoryLoader: createDeployInventoryLoader({
5900
- revision: revision(),
5901
- target: { kind: "fly", project: projectName() }
5902
- })
5770
+ testInventoryLoader: createTestInventoryLoader()
5903
5771
  });
5904
5772
  platform.exit(code);
5905
5773
  }