@sleepy-hollow/framework 0.3.0 → 0.3.2

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