@amerged/ohmyhost-cli 0.1.31 → 0.1.32

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.
@@ -140,16 +140,16 @@ var require_ignore = __commonJS({
140
140
  }
141
141
  };
142
142
  var NEVER_MATCH = "[]";
143
- var PLACEHOLDER = "\0";
143
+ var PLACEHOLDER2 = "\0";
144
144
  var REGEX_RESTORE_PLACEHOLDER = new RegExp(
145
- `${PLACEHOLDER}(\\d+)${PLACEHOLDER}`,
145
+ `${PLACEHOLDER2}(\\d+)${PLACEHOLDER2}`,
146
146
  "g"
147
147
  );
148
148
  var TRAILING_WILDCARD = "\uE000";
149
149
  var TRAILING_DOUBLESTAR = "\uE001";
150
150
  var extractBrackets = (pattern) => {
151
151
  const sources = [];
152
- const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
152
+ const hold = (source) => `${PLACEHOLDER2}${sources.push(source) - 1}${PLACEHOLDER2}`;
153
153
  const { length } = pattern;
154
154
  let out = EMPTY;
155
155
  let index = 0;
@@ -167,8 +167,8 @@ var require_ignore = __commonJS({
167
167
  );
168
168
  }
169
169
  index += 2;
170
- } else if (char === PLACEHOLDER) {
171
- out += hold(`[${PLACEHOLDER}]`);
170
+ } else if (char === PLACEHOLDER2) {
171
+ out += hold(`[${PLACEHOLDER2}]`);
172
172
  index++;
173
173
  } else if (char === "[") {
174
174
  const scanned = scanBracket(pattern, index);
@@ -25491,20 +25491,20 @@ var require_dependencies = __commonJS({
25491
25491
  const deps = propertyDeps[prop];
25492
25492
  if (deps.length === 0)
25493
25493
  continue;
25494
- const hasProperty = (0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties);
25494
+ const hasProperty2 = (0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties);
25495
25495
  cxt.setParams({
25496
25496
  property: prop,
25497
25497
  depsCount: deps.length,
25498
25498
  deps: deps.join(", ")
25499
25499
  });
25500
25500
  if (it.allErrors) {
25501
- gen.if(hasProperty, () => {
25501
+ gen.if(hasProperty2, () => {
25502
25502
  for (const depProp of deps) {
25503
25503
  (0, code_1.checkReportMissingProp)(cxt, depProp);
25504
25504
  }
25505
25505
  });
25506
25506
  } else {
25507
- gen.if((0, codegen_1._)`${hasProperty} && (${(0, code_1.checkMissingProp)(cxt, deps, missing2)})`);
25507
+ gen.if((0, codegen_1._)`${hasProperty2} && (${(0, code_1.checkMissingProp)(cxt, deps, missing2)})`);
25508
25508
  (0, code_1.reportMissingProp)(cxt, missing2);
25509
25509
  gen.else();
25510
25510
  }
@@ -34333,7 +34333,7 @@ var require_public_api = __commonJS({
34333
34333
  return docs;
34334
34334
  return Object.assign([], { empty: true }, composer$1.streamInfo());
34335
34335
  }
34336
- function parseDocument(source, options = {}) {
34336
+ function parseDocument2(source, options = {}) {
34337
34337
  const { lineCounter: lineCounter2, prettyErrors } = parseOptions2(options);
34338
34338
  const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
34339
34339
  const composer$1 = new composer.Composer(options);
@@ -34359,7 +34359,7 @@ var require_public_api = __commonJS({
34359
34359
  } else if (options === void 0 && reviver && typeof reviver === "object") {
34360
34360
  options = reviver;
34361
34361
  }
34362
- const doc = parseDocument(src, options);
34362
+ const doc = parseDocument2(src, options);
34363
34363
  if (!doc)
34364
34364
  return null;
34365
34365
  doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
@@ -34395,7 +34395,7 @@ var require_public_api = __commonJS({
34395
34395
  }
34396
34396
  exports.parse = parse;
34397
34397
  exports.parseAllDocuments = parseAllDocuments;
34398
- exports.parseDocument = parseDocument;
34398
+ exports.parseDocument = parseDocument2;
34399
34399
  exports.stringify = stringify;
34400
34400
  }
34401
34401
  });
@@ -34459,6 +34459,625 @@ import { createHash } from "node:crypto";
34459
34459
  import { constants, promises as fs } from "node:fs";
34460
34460
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
34461
34461
 
34462
+ // packages/contracts/dist/framework-build-script.mjs
34463
+ var PINNED_BUN_VERSION = "1.2.22";
34464
+ var FRAMEWORK_VERSION_POLICY = Object.freeze({
34465
+ next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
34466
+ vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
34467
+ "@tanstack/react-start": Object.freeze({
34468
+ floors: Object.freeze(["1.168.60"]),
34469
+ denied: Object.freeze([])
34470
+ })
34471
+ });
34472
+ var STABLE_VERSION = /^(0|[1-9][0-9]{0,5})\.(0|[1-9][0-9]{0,5})\.(0|[1-9][0-9]{0,5})$/u;
34473
+ function parseStableVersion(version2) {
34474
+ if (typeof version2 !== "string")
34475
+ return null;
34476
+ const match = STABLE_VERSION.exec(version2);
34477
+ return match === null ? null : Object.freeze([Number(match[1]), Number(match[2]), Number(match[3])]);
34478
+ }
34479
+ function compareStableVersions(left, right) {
34480
+ return left[0] - right[0] || left[1] - right[1] || left[2] - right[2];
34481
+ }
34482
+ function packagePolicy(packageName, policy) {
34483
+ return typeof packageName === "string" && Object.hasOwn(policy, packageName) ? policy[
34484
+ /** @type {FrameworkVersionPackage} */
34485
+ packageName
34486
+ ] : null;
34487
+ }
34488
+ function applicableFloor(entry, version2) {
34489
+ let floor = null;
34490
+ for (const candidate of entry.floors) {
34491
+ const parsed = parseStableVersion(candidate);
34492
+ if (parsed !== null && parsed[0] <= version2[0])
34493
+ floor = parsed;
34494
+ }
34495
+ return floor;
34496
+ }
34497
+ function frameworkVersionVerdict(packageName, version2, policy = FRAMEWORK_VERSION_POLICY) {
34498
+ const entry = packagePolicy(packageName, policy);
34499
+ const parsed = parseStableVersion(version2);
34500
+ if (entry === null || parsed === null)
34501
+ return "invalid_version";
34502
+ const floor = applicableFloor(entry, parsed);
34503
+ if (floor === null || compareStableVersions(parsed, floor) < 0)
34504
+ return "below_security_minimum";
34505
+ for (const [introduced, fixed] of entry.denied) {
34506
+ const from = parseStableVersion(introduced);
34507
+ const to = fixed === null ? null : parseStableVersion(fixed);
34508
+ if (from !== null && compareStableVersions(parsed, from) >= 0 && (to === null || compareStableVersions(parsed, to) < 0))
34509
+ return "known_vulnerable";
34510
+ }
34511
+ return "admitted";
34512
+ }
34513
+ function frameworkVersionMinimum(packageName, version2, policy = FRAMEWORK_VERSION_POLICY) {
34514
+ const entry = packagePolicy(packageName, policy);
34515
+ if (entry === null)
34516
+ throw new TypeError("Framework version package is not governed");
34517
+ const lowest = parseStableVersion(entry.floors[0]);
34518
+ if (lowest === null)
34519
+ throw new TypeError("Framework version policy is invalid");
34520
+ const core = typeof version2 === "string" ? /^[~^]?((?:0|[1-9][0-9]{0,5})\.(?:0|[1-9][0-9]{0,5})\.(?:0|[1-9][0-9]{0,5}))(?:[-+].*)?$/u.exec(version2) : null;
34521
+ const parsed = parseStableVersion(core?.[1]);
34522
+ let minimum = (parsed === null ? null : applicableFloor(entry, parsed)) ?? lowest;
34523
+ let candidate = parsed !== null && compareStableVersions(parsed, minimum) >= 0 ? parsed : minimum;
34524
+ for (let raised = true; raised; ) {
34525
+ raised = false;
34526
+ for (const [introduced, fixed] of entry.denied) {
34527
+ const from = parseStableVersion(introduced);
34528
+ const to = fixed === null ? null : parseStableVersion(fixed);
34529
+ if (from !== null && to !== null && compareStableVersions(candidate, from) >= 0 && compareStableVersions(candidate, to) < 0) {
34530
+ candidate = to;
34531
+ minimum = to;
34532
+ raised = true;
34533
+ }
34534
+ }
34535
+ }
34536
+ return minimum.join(".");
34537
+ }
34538
+ var TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
34539
+ function isAdmittedFrameworkBuildScript(framework, script) {
34540
+ if (typeof script !== "string")
34541
+ return false;
34542
+ if (framework === "nextjs")
34543
+ return script === "next build" || script === "next build --webpack";
34544
+ if (framework !== "vite" && framework !== "tanstack-start")
34545
+ return false;
34546
+ const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
34547
+ if (stages.length === 1)
34548
+ return stages[0] === "vite build";
34549
+ if (stages.length !== 2)
34550
+ return false;
34551
+ return stages[0] === "vite build" && TYPESCRIPT_BUILD_STAGE.test(stages[1] ?? "") || TYPESCRIPT_BUILD_STAGE.test(stages[0] ?? "") && stages[1] === "vite build";
34552
+ }
34553
+
34554
+ // packages/contracts/dist/customer-auth-admission.js
34555
+ function managedCustomerAuthDatabaseMessage(path2) {
34556
+ return `"${path2}" selects the managed Better Auth bridge, which requires the managed PostgreSQL database. Set database.enabled: true with an admitted migrations path, or set auth.provider: none and keep the application-owned authentication configuration. Commit and push the change, then plan the new commit.`;
34557
+ }
34558
+ function managedCustomerAuthAdmissionIssue(capabilities) {
34559
+ if (capabilities?.auth?.provider !== "better-auth") {
34560
+ return null;
34561
+ }
34562
+ if (capabilities.database?.enabled !== true) {
34563
+ return Object.freeze({
34564
+ code: "managed_auth_database_required",
34565
+ message: managedCustomerAuthDatabaseMessage("ohmyhost.yaml")
34566
+ });
34567
+ }
34568
+ return null;
34569
+ }
34570
+
34571
+ // packages/contracts/dist/framework-admission.js
34572
+ var ADMISSION_DIAGNOSTIC_CODES = Object.freeze({
34573
+ ambiguous_config: "framework_config_ambiguous",
34574
+ ambiguous_lockfile: "lockfile_ambiguous",
34575
+ invalid_package_manager: "package_manager_invalid",
34576
+ missing_lockfile: "lockfile_missing",
34577
+ missing_package_manager: "package_manager_missing",
34578
+ package_manager_lockfile_mismatch: "package_manager_lockfile_mismatch",
34579
+ package_manager_version_unsupported: "package_manager_version_unsupported"
34580
+ });
34581
+ var FrameworkAdmissionError = class extends Error {
34582
+ reason;
34583
+ code = "framework_admission_failed";
34584
+ /** The catalog diagnostic of the refused file, relative to the application root. */
34585
+ diagnostic;
34586
+ constructor(reason, path2) {
34587
+ super("Framework source is not admitted");
34588
+ this.reason = reason;
34589
+ this.name = "FrameworkAdmissionError";
34590
+ this.diagnostic = requiredDiagnostic({ code: ADMISSION_DIAGNOSTIC_CODES[reason], path: path2 });
34591
+ }
34592
+ };
34593
+ var PACKAGE_MANAGER_CONTRACTS = Object.freeze({
34594
+ bun: Object.freeze({
34595
+ lockfiles: Object.freeze(["bun.lock", "bun.lockb"]),
34596
+ installCommand: "bun install --frozen-lockfile --ignore-scripts",
34597
+ buildCommand: "bun run build"
34598
+ }),
34599
+ npm: Object.freeze({
34600
+ lockfiles: Object.freeze(["package-lock.json"]),
34601
+ installCommand: "npm ci --ignore-scripts",
34602
+ buildCommand: "npm run build"
34603
+ }),
34604
+ pnpm: Object.freeze({
34605
+ lockfiles: Object.freeze(["pnpm-lock.yaml"]),
34606
+ installCommand: "pnpm install --frozen-lockfile --ignore-scripts",
34607
+ buildCommand: "pnpm run build"
34608
+ }),
34609
+ yarn: Object.freeze({
34610
+ lockfiles: Object.freeze(["yarn.lock"]),
34611
+ installCommand: "yarn install --immutable --mode=skip-build",
34612
+ buildCommand: "yarn run build"
34613
+ })
34614
+ });
34615
+ var ALL_LOCKFILES = Object.freeze(Object.values(PACKAGE_MANAGER_CONTRACTS).flatMap(({ lockfiles }) => lockfiles));
34616
+ var PACKAGE_MANIFEST = "package.json";
34617
+ var PACKAGE_MANAGER_DECLARATION = /^(bun|npm|pnpm|yarn)@((?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))$/u;
34618
+ function resolvePackageManager(input) {
34619
+ const presentLockfiles = ALL_LOCKFILES.filter((lockfile2) => input.files.includes(lockfile2));
34620
+ if (presentLockfiles.length === 0) {
34621
+ throw new FrameworkAdmissionError("missing_lockfile", PACKAGE_MANIFEST);
34622
+ }
34623
+ if (presentLockfiles.length !== 1) {
34624
+ throw new FrameworkAdmissionError("ambiguous_lockfile", PACKAGE_MANIFEST);
34625
+ }
34626
+ if (input.packageManager === void 0) {
34627
+ throw new FrameworkAdmissionError("missing_package_manager", PACKAGE_MANIFEST);
34628
+ }
34629
+ const declaration = PACKAGE_MANAGER_DECLARATION.exec(input.packageManager);
34630
+ if (declaration === null) {
34631
+ throw new FrameworkAdmissionError("invalid_package_manager", PACKAGE_MANIFEST);
34632
+ }
34633
+ const manager = declaration[1];
34634
+ const version2 = declaration[2];
34635
+ const contract = PACKAGE_MANAGER_CONTRACTS[manager];
34636
+ const lockfile = presentLockfiles[0];
34637
+ if (!contract.lockfiles.includes(lockfile)) {
34638
+ throw new FrameworkAdmissionError("package_manager_lockfile_mismatch", lockfile);
34639
+ }
34640
+ if (manager === "bun" && version2 !== PINNED_BUN_VERSION) {
34641
+ throw new FrameworkAdmissionError("package_manager_version_unsupported", PACKAGE_MANIFEST);
34642
+ }
34643
+ return Object.freeze({
34644
+ manager,
34645
+ version: version2,
34646
+ lockfile,
34647
+ installCommand: contract.installCommand,
34648
+ buildCommand: contract.buildCommand
34649
+ });
34650
+ }
34651
+ function resolveFrameworkConfig(basename2, files) {
34652
+ const supported = [`${basename2}.js`, `${basename2}.mjs`, `${basename2}.ts`];
34653
+ const matches = supported.filter((candidate) => files.includes(candidate));
34654
+ if (matches.length > 1) {
34655
+ throw new FrameworkAdmissionError("ambiguous_config", matches[0]);
34656
+ }
34657
+ return matches[0] ?? null;
34658
+ }
34659
+ var STABLE_VERSION_SOURCE = "(?:0|[1-9][0-9]{0,5})\\.(?:0|[1-9][0-9]{0,5})\\.(?:0|[1-9][0-9]{0,5})";
34660
+ var VERSION_RANGE = new RegExp(`^([~^])(${STABLE_VERSION_SOURCE})$`, "u");
34661
+ function classifyFrameworkVersion(packageName, declared, resolved = null, policy = FRAMEWORK_VERSION_POLICY) {
34662
+ const judged = resolved ?? declared;
34663
+ const range = resolved === null ? VERSION_RANGE.exec(declared) : null;
34664
+ let status;
34665
+ if (range === null) {
34666
+ status = frameworkVersionVerdict(packageName, judged, policy) === "admitted" ? "admitted" : "unsupported";
34667
+ } else {
34668
+ status = rangeStatus(packageName, range[1] === "^", range[2], policy);
34669
+ }
34670
+ return Object.freeze({
34671
+ package: packageName,
34672
+ status,
34673
+ declared,
34674
+ resolved,
34675
+ minimum: frameworkVersionMinimum(packageName, judged, policy)
34676
+ });
34677
+ }
34678
+ function rangeStatus(packageName, caret, base, policy) {
34679
+ const lower = policyVersion(base);
34680
+ const upper = caret ? [lower[0] + 1, 0, 0] : [lower[0], lower[1] + 1, 0];
34681
+ if (frameworkVersionVerdict(packageName, base, policy) === "admitted" && !policy[packageName].denied.some(([introduced, fixed]) => {
34682
+ const from = parseStableVersion(introduced);
34683
+ const to = fixed === null ? null : parseStableVersion(fixed);
34684
+ return from !== null && compareStableVersions(from, upper) < 0 && (to === null || compareStableVersions(to, lower) > 0);
34685
+ }))
34686
+ return "admitted";
34687
+ const minimum = policyVersion(frameworkVersionMinimum(packageName, base, policy));
34688
+ const candidate = compareStableVersions(minimum, lower) > 0 ? minimum : lower;
34689
+ return compareStableVersions(candidate, upper) < 0 && frameworkVersionVerdict(packageName, candidate.join("."), policy) === "admitted" ? "pending" : "unsupported";
34690
+ }
34691
+ function policyVersion(version2) {
34692
+ const parsed = parseStableVersion(version2);
34693
+ if (parsed === null)
34694
+ throw new TypeError("Framework version policy is invalid");
34695
+ return parsed;
34696
+ }
34697
+ var RESOLVED_VERSION_SOURCE = `${STABLE_VERSION_SOURCE}(?:-[0-9A-Za-z.-]{1,32})?`;
34698
+ var RESOLVED_VERSION_FIELD = anchoredPattern(RESOLVED_VERSION_SOURCE);
34699
+ var DECLARED_VERSION_FIELD = anchoredPattern(`[~^]?${RESOLVED_VERSION_SOURCE}`);
34700
+ var STABLE_VERSION_FIELD = anchoredPattern(STABLE_VERSION_SOURCE);
34701
+ function isResolvedFrameworkVersion(value) {
34702
+ return typeof value === "string" && RESOLVED_VERSION_FIELD.test(value);
34703
+ }
34704
+ function frameworkVersionDiagnostic(admission, lockfile) {
34705
+ if (admission.status !== "unsupported")
34706
+ return null;
34707
+ if (admission.resolved !== null) {
34708
+ if (lockfile === null)
34709
+ throw new TypeError("Framework version lockfile is required");
34710
+ return requiredDiagnostic({
34711
+ code: "framework_version_unsupported",
34712
+ path: lockfile,
34713
+ package: admission.package,
34714
+ version: admission.resolved,
34715
+ minimum: admission.minimum
34716
+ });
34717
+ }
34718
+ return DECLARED_VERSION_FIELD.test(admission.declared) ? requiredDiagnostic({
34719
+ code: "framework_version_unsupported",
34720
+ path: PACKAGE_MANIFEST,
34721
+ package: admission.package,
34722
+ version: admission.declared,
34723
+ minimum: admission.minimum
34724
+ }) : requiredDiagnostic({
34725
+ code: "framework_version_declaration_unsupported",
34726
+ path: PACKAGE_MANIFEST,
34727
+ package: admission.package
34728
+ });
34729
+ }
34730
+ var FRAMEWORK_VERSION_PACKAGES = Object.freeze([
34731
+ "next",
34732
+ "vite",
34733
+ "@tanstack/react-start"
34734
+ ]);
34735
+ var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
34736
+ "next",
34737
+ "react",
34738
+ "react-dom",
34739
+ "vite",
34740
+ "@tanstack/react-start",
34741
+ "@tanstack/react-router",
34742
+ "@vitejs/plugin-react",
34743
+ "@cloudflare/vite-plugin"
34744
+ ]);
34745
+ var REACT_PACKAGES = Object.freeze(["react", "react-dom"]);
34746
+ var BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"]);
34747
+ var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
34748
+ var VITE_BUILD_SCRIPT = '"vite build", optionally joined with && to one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage';
34749
+ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34750
+ managed_auth_database_required: {
34751
+ paths: ["repository_config"],
34752
+ template: managedCustomerAuthDatabaseMessage("{{path}}")
34753
+ },
34754
+ database_binding_private: {
34755
+ paths: ["module"],
34756
+ template: `"{{path}}" reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`
34757
+ },
34758
+ database_driver_unsupported: {
34759
+ paths: ["module"],
34760
+ template: `"{{path}}" imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`
34761
+ },
34762
+ source_syntax_invalid: {
34763
+ paths: ["module"],
34764
+ template: 'Invalid JavaScript or TypeScript in "{{path}}". Fix the file, commit and push the change, then plan the new commit.'
34765
+ },
34766
+ worker_module_default_export_required: {
34767
+ paths: ["module"],
34768
+ template: `Worker module "{{path}}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called. ${PUSH_AND_PLAN}`
34769
+ },
34770
+ scheduled_handler_required: {
34771
+ paths: ["module"],
34772
+ template: 'functions.crons is declared but "{{path}}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit and push the change, then plan the new commit.'
34773
+ },
34774
+ framework_base_path_unsupported: {
34775
+ paths: ["framework_config"],
34776
+ template: `"{{path}}" sets a Vite base. ohmyho.st serves the app at the root origin: remove base or set base: "/". ${PUSH_AND_PLAN}`
34777
+ },
34778
+ tanstack_prerender_required: {
34779
+ paths: ["framework_config"],
34780
+ template: `"{{path}}" does not call tanstackStart({ prerender: { enabled: true } }), which the static TanStack Start runtime needs; enable prerender, or set runtime.mode: edge with the Cloudflare Vite plugin. ${PUSH_AND_PLAN}`
34781
+ },
34782
+ tanstack_vite_plugins_required: {
34783
+ paths: ["framework_config"],
34784
+ template: `"{{path}}" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack/react-start/plugin/vite"; on runtime edge also import cloudflare from "@cloudflare/vite-plugin" and call cloudflare({ viteEnvironment: { name: "ssr" } }) before tanstackStart(); on runtime static do not import the Cloudflare plugin. ${PUSH_AND_PLAN}`
34785
+ },
34786
+ next_cache_components_unsupported: {
34787
+ paths: ["framework_config"],
34788
+ template: `"{{path}}" sets cacheComponents: true, which ohmyho.st does not support; remove it or set it to false. ${PUSH_AND_PLAN}`
34789
+ },
34790
+ framework_version_unsupported: {
34791
+ paths: ["manifest", "lockfile"],
34792
+ fields: {
34793
+ package: FRAMEWORK_VERSION_PACKAGES,
34794
+ version: DECLARED_VERSION_FIELD,
34795
+ minimum: STABLE_VERSION_FIELD
34796
+ },
34797
+ derived: { verb: { from: "path", text: { manifest: "declares", lockfile: "resolves" } } },
34798
+ template: '"{{path}}" {{verb}} {{package}} {{version}}, which ohmyho.st does not admit; use {{package}} {{minimum}} or a later stable release, update package.json and the lockfile, commit and push the change, then plan the new commit.'
34799
+ },
34800
+ framework_version_declaration_unsupported: {
34801
+ paths: ["manifest"],
34802
+ fields: { package: FRAMEWORK_VERSION_PACKAGES },
34803
+ template: '"{{path}}" declares {{package}} in a form ohmyho.st cannot check against its security minimum; declare an exact X.Y.Z version or a ^X.Y.Z or ~X.Y.Z range, update the lockfile, commit and push the change, then plan the new commit.'
34804
+ },
34805
+ framework_dependency_required: {
34806
+ paths: ["manifest"],
34807
+ fields: { package: FRAMEWORK_DEPENDENCY_PACKAGES },
34808
+ template: '"{{path}}" does not declare {{package}} in dependencies, which this framework and runtime need. Add it with the package manager so the lockfile records it, commit and push the change, then plan the new commit.'
34809
+ },
34810
+ react_version_exact_required: {
34811
+ paths: ["manifest"],
34812
+ fields: { package: REACT_PACKAGES },
34813
+ template: '"{{path}}" must declare {{package}} in dependencies at an exact version such as 19.1.0, without ^ or ~, for Next.js. Pin the version the lockfile installs, commit and push the change, then plan the new commit.'
34814
+ },
34815
+ build_script_unsupported: {
34816
+ paths: ["manifest"],
34817
+ fields: { framework: BUILD_FRAMEWORKS },
34818
+ derived: {
34819
+ script: {
34820
+ from: "framework",
34821
+ text: {
34822
+ nextjs: 'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
34823
+ "vite-static": VITE_BUILD_SCRIPT,
34824
+ "tanstack-start": VITE_BUILD_SCRIPT
34825
+ }
34826
+ }
34827
+ },
34828
+ template: '"{{path}}" scripts.build is missing or not admitted for the {{framework}} adapter: it must be {{script}}. Move other steps into their own scripts, commit and push the change, then plan the new commit.'
34829
+ },
34830
+ better_auth_version_required: {
34831
+ paths: ["manifest"],
34832
+ template: '"{{path}}" must declare better-auth at exactly 1.7.1 in dependencies because ohmyhost.yaml sets auth.provider: better-auth. Pin that version or set auth.provider: none, commit and push the change, then plan the new commit.'
34833
+ },
34834
+ package_manifest_invalid: {
34835
+ paths: ["manifest"],
34836
+ template: '"{{path}}" is missing or is not one JSON object. Add package.json in the applicationRoot that ohmyhost.yaml names, or fix its syntax so the whole file parses as one object; commit and push the change, then plan the new commit.'
34837
+ },
34838
+ framework_unsupported: {
34839
+ paths: ["manifest"],
34840
+ template: `"{{path}}" declares none of next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`
34841
+ },
34842
+ framework_ambiguous: {
34843
+ paths: ["manifest"],
34844
+ template: '"{{path}}" declares next together with vite or @tanstack/react-start, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.'
34845
+ },
34846
+ lockfile_missing: {
34847
+ paths: ["manifest"],
34848
+ template: '"{{path}}" has no lockfile beside it. Install with the packageManager it names so package-lock.json, pnpm-lock.yaml, yarn.lock or bun.lock is written, commit and push the change, then plan the new commit.'
34849
+ },
34850
+ lockfile_ambiguous: {
34851
+ paths: ["manifest"],
34852
+ template: '"{{path}}" has more than one lockfile beside it. Keep only the lockfile of the packageManager it names and delete the others, commit and push the change, then plan the new commit.'
34853
+ },
34854
+ package_manager_missing: {
34855
+ paths: ["manifest"],
34856
+ template: '"{{path}}" has no packageManager field. Set it to name@X.Y.Z for the npm, pnpm, yarn or bun version that wrote the lockfile, commit and push the change, then plan the new commit.'
34857
+ },
34858
+ package_manager_invalid: {
34859
+ paths: ["manifest"],
34860
+ template: '"{{path}}" sets packageManager to a value ohmyho.st cannot use. Write npm, pnpm, yarn or bun, then @ and an exact X.Y.Z version without a +sha suffix, matching the lockfile; commit and push the change, then plan the new commit.'
34861
+ },
34862
+ package_manager_lockfile_mismatch: {
34863
+ paths: ["lockfile"],
34864
+ template: '"{{path}}" was not written by the packageManager that package.json names. Set packageManager to the manager of this lockfile, or replace the lockfile with one from the named manager; commit and push the change, then plan the new commit.'
34865
+ },
34866
+ package_manager_version_unsupported: {
34867
+ paths: ["manifest"],
34868
+ template: `"{{path}}" pins a Bun version other than ${PINNED_BUN_VERSION}, the only Bun ohmyho.st builds with. Set packageManager to bun@${PINNED_BUN_VERSION}, reinstall to update the lockfile, commit and push the change, then plan the new commit.`
34869
+ },
34870
+ repository_configuration_invalid: {
34871
+ paths: ["repository_config"],
34872
+ template: '"{{path}}" does not match the ohmyho.st configuration schema. Run ohmyhost init --dry-run --json locally to see the exact field and fix, commit and push the change, then plan the new commit.'
34873
+ },
34874
+ runtime_mode_unsupported: {
34875
+ paths: ["repository_config"],
34876
+ fields: { framework: BUILD_FRAMEWORKS },
34877
+ derived: {
34878
+ mode: {
34879
+ from: "framework",
34880
+ text: {
34881
+ nextjs: "runtime.mode: edge",
34882
+ "vite-static": "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
34883
+ "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage"
34884
+ }
34885
+ }
34886
+ },
34887
+ template: `"{{path}}" sets a runtime.mode that the {{framework}} adapter cannot run with this configuration; use {{mode}}. ${PUSH_AND_PLAN}`
34888
+ },
34889
+ build_output_unsupported: {
34890
+ paths: ["repository_config"],
34891
+ fields: { framework: BUILD_FRAMEWORKS },
34892
+ derived: {
34893
+ output: {
34894
+ from: "framework",
34895
+ text: {
34896
+ nextjs: ".open-next/assets",
34897
+ "vite-static": "dist",
34898
+ "tanstack-start": "dist/client"
34899
+ }
34900
+ }
34901
+ },
34902
+ template: `"{{path}}" sets a build.output that the {{framework}} adapter does not produce; set build.output: {{output}}. ${PUSH_AND_PLAN}`
34903
+ },
34904
+ build_command_mismatch: {
34905
+ paths: ["repository_config"],
34906
+ template: '"{{path}}" build.install or build.command differs from the commands of the packageManager in package.json (for npm: npm ci --ignore-scripts and npm run build). ohmyhost init --dry-run --json prints the exact values; set them, commit and push the change, then plan the new commit.'
34907
+ },
34908
+ build_option_unsupported: {
34909
+ paths: ["repository_config"],
34910
+ template: '"{{path}}" sets a build option this runtime ignores: build.ssg_cache_max_mib applies only to Next.js, and runtime.mode: functions takes only build.install. Remove the option, commit and push the change, then plan the new commit.'
34911
+ },
34912
+ application_root_missing: {
34913
+ paths: ["repository_config"],
34914
+ template: `"{{path}}" sets applicationRoot to a directory that is missing or empty in this commit. Set it to the directory that holds the app's package.json, or "." for the repository root; commit and push the change, then plan the new commit.`
34915
+ },
34916
+ framework_config_required: {
34917
+ paths: ["framework_config"],
34918
+ template: 'This application has no "{{path}}". Add one framework config with that name and a .js, .mjs or .ts extension that exports its config as default, commit and push the change, then plan the new commit.'
34919
+ },
34920
+ framework_config_ambiguous: {
34921
+ paths: ["framework_config"],
34922
+ template: '"{{path}}" exists beside another variant with the same name and a .js, .mjs or .ts extension. Keep exactly one and delete the others, commit and push the change, then plan the new commit.'
34923
+ },
34924
+ framework_config_too_large: {
34925
+ paths: ["framework_config"],
34926
+ template: '"{{path}}" is larger than the 256 KiB ohmyho.st inspects. Keep the config small and move large data into imported modules, commit and push the change, then plan the new commit.'
34927
+ },
34928
+ framework_config_default_export_required: {
34929
+ paths: ["framework_config"],
34930
+ template: '"{{path}}" does not export its config as default. Use export default (next.config.js may assign module.exports instead), commit and push the change, then plan the new commit.'
34931
+ },
34932
+ next_output_unsupported: {
34933
+ paths: ["framework_config"],
34934
+ template: '"{{path}}" sets output: "export" or output: "standalone", which the Next.js edge runtime on ohmyho.st cannot serve. Remove the output setting, commit and push the change, then plan the new commit.'
34935
+ },
34936
+ next_base_path_unsupported: {
34937
+ paths: ["framework_config"],
34938
+ template: `"{{path}}" sets basePath or assetPrefix. ohmyho.st serves the app at the root origin: remove both or set them to "". ${PUSH_AND_PLAN}`
34939
+ },
34940
+ companion_entry_required: {
34941
+ paths: ["module"],
34942
+ template: '"{{path}}" is missing. A Vite app with runtime.mode: edge or with database, auth, mail, storage or functions.crons needs this Worker module with export default { fetch }. Add it, or use runtime.mode: static without those settings; commit and push the change, then plan the new commit.'
34943
+ },
34944
+ worker_module_required: {
34945
+ paths: ["module"],
34946
+ template: '"{{path}}" is missing. runtime.mode: functions and functions.crons on Next.js or TanStack Start need this Worker module; export default { fetch, scheduled } with the handlers ohmyho.st invokes. Add it, commit and push the change, then plan the new commit.'
34947
+ },
34948
+ worker_module_fetch_handler_required: {
34949
+ paths: ["module"],
34950
+ template: `Worker module "{{path}}" exports no fetch handler; runtime.mode: functions serves every request and the health check through it. Add a fetch handler that receives request, env and ctx to its default export. ${PUSH_AND_PLAN}`
34951
+ }
34952
+ });
34953
+ var DETAIL_DEFINITIONS = FRAMEWORK_CONVERSION_DETAILS;
34954
+ var PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
34955
+ var PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
34956
+ var SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
34957
+ var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
34958
+ var FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
34959
+ var MAXIMUM_DETAIL_LENGTH = 2e3;
34960
+ var DETAIL_PATTERNS = Object.freeze(Object.entries(DETAIL_DEFINITIONS).map(([code, definition]) => Object.freeze({
34961
+ code,
34962
+ pattern: detailPattern(definition)
34963
+ })));
34964
+ function anchoredPattern(source) {
34965
+ return new RegExp(`^(?:${source})$`, "u");
34966
+ }
34967
+ function escapePattern(text) {
34968
+ return text.replace(/[\\^$.*+?()[\]{}|]/gu, "\\$&");
34969
+ }
34970
+ function detailPattern(definition) {
34971
+ const placed = /* @__PURE__ */ new Set();
34972
+ let pattern = "^";
34973
+ let offset = 0;
34974
+ for (const match of definition.template.matchAll(PLACEHOLDER)) {
34975
+ const name = match[1];
34976
+ pattern += escapePattern(definition.template.slice(offset, match.index));
34977
+ pattern += placed.has(name) ? `\\k<${name}>` : `(?<${name}>${placeholderPattern(definition, name)})`;
34978
+ placed.add(name);
34979
+ offset = match.index + match[0].length;
34980
+ }
34981
+ return new RegExp(`${pattern}${escapePattern(definition.template.slice(offset))}$`, "u");
34982
+ }
34983
+ function placeholderPattern(definition, name) {
34984
+ if (name === "path")
34985
+ return PATH_PATTERN;
34986
+ const grammar = definition.fields?.[name];
34987
+ if (grammar instanceof RegExp)
34988
+ return grammar.source.slice(1, -1);
34989
+ const values = grammar ?? Object.values(definition.derived?.[name]?.text ?? {});
34990
+ if (values.length === 0)
34991
+ throw new TypeError("Framework conversion detail template is invalid");
34992
+ return `(?:${[...new Set(values)].map(escapePattern).join("|")})`;
34993
+ }
34994
+ function pathClassOf(definition, path2) {
34995
+ if (path2 === "ohmyhost.yaml" && definition.paths.includes("repository_config"))
34996
+ return "repository_config";
34997
+ if (!SAFE_PATH.test(path2) || path2.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
34998
+ return null;
34999
+ const leaf = path2.slice(path2.lastIndexOf("/") + 1);
35000
+ return definition.paths.find((pathClass) => pathClass === "module" && MODULE_PATH.test(path2) || pathClass === "manifest" && leaf === PACKAGE_MANIFEST || pathClass === "lockfile" && ALL_LOCKFILES.includes(leaf) || pathClass === "framework_config" && FRAMEWORK_CONFIG_NAME.test(leaf)) ?? null;
35001
+ }
35002
+ function fieldMatches(grammar, value) {
35003
+ if (typeof value !== "string")
35004
+ return false;
35005
+ return grammar instanceof RegExp ? grammar.test(value) : grammar.includes(value);
35006
+ }
35007
+ function isFrameworkConversionDiagnosticCode(value) {
35008
+ return typeof value === "string" && Object.hasOwn(DETAIL_DEFINITIONS, value);
35009
+ }
35010
+ function parseFrameworkConversionDiagnostic(value) {
35011
+ if (typeof value !== "object" || value === null || Array.isArray(value))
35012
+ return null;
35013
+ const record6 = value;
35014
+ const code = Object.hasOwn(record6, "code") ? record6["code"] : void 0;
35015
+ const path2 = Object.hasOwn(record6, "path") ? record6["path"] : void 0;
35016
+ if (!isFrameworkConversionDiagnosticCode(code) || typeof path2 !== "string")
35017
+ return null;
35018
+ const definition = DETAIL_DEFINITIONS[code];
35019
+ const fields = Object.entries(definition.fields ?? {}).map(([name, grammar]) => [name, grammar, Object.hasOwn(record6, name) ? record6[name] : void 0]);
35020
+ if (Object.keys(record6).length !== fields.length + 2 || pathClassOf(definition, path2) === null || fields.some(([, grammar, field]) => !fieldMatches(grammar, field)))
35021
+ return null;
35022
+ return Object.freeze(Object.fromEntries([
35023
+ ["code", code],
35024
+ ["path", path2],
35025
+ ...fields.map(([name, , field]) => [name, field])
35026
+ ]));
35027
+ }
35028
+ function requiredDiagnostic(value) {
35029
+ const diagnostic2 = parseFrameworkConversionDiagnostic(value);
35030
+ if (diagnostic2 === null)
35031
+ throw new TypeError("Framework conversion diagnostic is invalid");
35032
+ return diagnostic2;
35033
+ }
35034
+ function renderDetail(diagnostic2) {
35035
+ const definition = DETAIL_DEFINITIONS[diagnostic2.code];
35036
+ const values = diagnostic2;
35037
+ const pathClass = pathClassOf(definition, diagnostic2.path);
35038
+ return definition.template.replace(PLACEHOLDER, (_placeholder, name) => {
35039
+ const derived = definition.derived?.[name];
35040
+ const value = derived === void 0 ? values[name] : derived.text[String(derived.from === "path" ? pathClass : values[derived.from])];
35041
+ if (typeof value !== "string")
35042
+ throw new TypeError("Framework conversion diagnostic is invalid");
35043
+ return value;
35044
+ });
35045
+ }
35046
+ function frameworkConversionDetail(value) {
35047
+ const diagnostic2 = parseFrameworkConversionDiagnostic(value);
35048
+ return diagnostic2 === null ? null : renderDetail(diagnostic2);
35049
+ }
35050
+ function frameworkConversionDiagnosticFromDetail(value) {
35051
+ if (typeof value !== "string" || value.length > MAXIMUM_DETAIL_LENGTH)
35052
+ return null;
35053
+ for (const { code, pattern } of DETAIL_PATTERNS) {
35054
+ const groups = pattern.exec(value)?.groups;
35055
+ if (groups === void 0)
35056
+ continue;
35057
+ const diagnostic2 = parseFrameworkConversionDiagnostic({
35058
+ code,
35059
+ path: groups["path"],
35060
+ ...Object.fromEntries(Object.keys(DETAIL_DEFINITIONS[code].fields ?? {}).map((name) => [name, groups[name]]))
35061
+ });
35062
+ if (diagnostic2 !== null && renderDetail(diagnostic2) === value)
35063
+ return diagnostic2;
35064
+ }
35065
+ return null;
35066
+ }
35067
+ function parseFrameworkConversionDetail(value) {
35068
+ const diagnostic2 = frameworkConversionDiagnosticFromDetail(value);
35069
+ return diagnostic2 === null ? null : renderDetail(diagnostic2);
35070
+ }
35071
+ function rootFrameworkConversionDiagnostic(value, applicationRoot) {
35072
+ const diagnostic2 = parseFrameworkConversionDiagnostic(value);
35073
+ if (diagnostic2 === null || applicationRoot === "." || DETAIL_DEFINITIONS[diagnostic2.code].paths.includes("repository_config"))
35074
+ return diagnostic2;
35075
+ return parseFrameworkConversionDiagnostic({
35076
+ ...diagnostic2,
35077
+ path: `${applicationRoot}/${diagnostic2.path}`
35078
+ });
35079
+ }
35080
+
34462
35081
  // packages/contracts/dist/operation-failure.js
34463
35082
  var failures = {
34464
35083
  authorization_changed: [
@@ -34575,7 +35194,7 @@ var failures = {
34575
35194
  ],
34576
35195
  auto_deploy_plan_rejected: [
34577
35196
  "The GitHub push could not be planned for deployment.",
34578
- "Read project status and the operation's error or deployment diagnostics. Correct the source or configuration named there, push the new commit and plan it before deploying. Replaying this failed operation cannot start a build."
35197
+ "Read error.detail: it names the file, the rule and the required change. Make that change, commit and push it; the next push is planned again. Without error.detail, plan the pushed commit explicitly with 'ohmyhost plan --project ULID --commit SHA --json' or MCP deployment_plan to read the reason. Replaying this failed operation cannot start a build."
34579
35198
  ],
34580
35199
  promotion_source_stale: [
34581
35200
  "The Dev deployment changed before promotion could start.",
@@ -34596,11 +35215,18 @@ function publicOperationReconciliation(observation) {
34596
35215
  suggested_action: observation.state === "required" ? "Confirm reconciliation of this same operation; preserve its existing build and resources. Do not submit another deployment." : "Reconciliation is running. Poll this same operation after 60 seconds; do not start another reconciliation or deployment."
34597
35216
  });
34598
35217
  }
34599
- function publicOperationFailure(internalCode) {
35218
+ function publicOperationFailure(internalCode, diagnostic2) {
34600
35219
  const normalized = internalCode.toLowerCase();
34601
35220
  const code = Object.hasOwn(failures, normalized) ? normalized : "operation_failed";
34602
35221
  const [message, suggested_action] = failures[code];
34603
- return Object.freeze({ code, message, retryable: false, suggested_action });
35222
+ const detail = code === "auto_deploy_plan_rejected" ? frameworkConversionDetail(diagnostic2) : null;
35223
+ return Object.freeze({
35224
+ code,
35225
+ message,
35226
+ retryable: false,
35227
+ suggested_action,
35228
+ ...detail === null ? {} : { detail }
35229
+ });
34604
35230
  }
34605
35231
 
34606
35232
  // packages/contracts/dist/managed-sources.js
@@ -35587,242 +36213,6 @@ function invalid2() {
35587
36213
  throw new TypeError("Project data change plan is invalid");
35588
36214
  }
35589
36215
 
35590
- // packages/contracts/dist/framework-build-script.mjs
35591
- var PINNED_BUN_VERSION = "1.2.22";
35592
- function isAdmittedNextVersion(version2) {
35593
- if (typeof version2 !== "string")
35594
- return false;
35595
- const match = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u.exec(version2);
35596
- if (match === null)
35597
- return false;
35598
- const major = Number(match[1]);
35599
- const minor = Number(match[2]);
35600
- const patch = Number(match[3]);
35601
- if (![major, minor, patch].every(Number.isSafeInteger))
35602
- return false;
35603
- return major === 15 && minor === 5 && patch >= 26 || major === 16 && minor === 3 && patch === 8;
35604
- }
35605
- var TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
35606
- function isAdmittedFrameworkBuildScript(framework, script) {
35607
- if (typeof script !== "string")
35608
- return false;
35609
- if (framework === "nextjs")
35610
- return script === "next build" || script === "next build --webpack";
35611
- if (framework !== "vite" && framework !== "tanstack-start")
35612
- return false;
35613
- const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
35614
- if (stages.length === 1)
35615
- return stages[0] === "vite build";
35616
- if (stages.length !== 2)
35617
- return false;
35618
- return stages[0] === "vite build" && TYPESCRIPT_BUILD_STAGE.test(stages[1] ?? "") || TYPESCRIPT_BUILD_STAGE.test(stages[0] ?? "") && stages[1] === "vite build";
35619
- }
35620
-
35621
- // packages/contracts/dist/customer-auth-admission.js
35622
- function managedCustomerAuthDatabaseMessage(path2) {
35623
- return `"${path2}" selects the managed Better Auth bridge, which requires the managed PostgreSQL database. Set database.enabled: true with an admitted migrations path, or set auth.provider: none and keep the application-owned authentication configuration. Commit and push the change, then plan the new commit.`;
35624
- }
35625
- function managedCustomerAuthAdmissionIssue(capabilities) {
35626
- if (capabilities?.auth?.provider !== "better-auth") {
35627
- return null;
35628
- }
35629
- if (capabilities.database?.enabled !== true) {
35630
- return Object.freeze({
35631
- code: "managed_auth_database_required",
35632
- message: managedCustomerAuthDatabaseMessage("ohmyhost.yaml")
35633
- });
35634
- }
35635
- return null;
35636
- }
35637
-
35638
- // packages/contracts/dist/framework-admission.js
35639
- var FrameworkAdmissionError = class extends Error {
35640
- reason;
35641
- code = "framework_admission_failed";
35642
- constructor(reason) {
35643
- super("Framework source is not admitted");
35644
- this.reason = reason;
35645
- this.name = "FrameworkAdmissionError";
35646
- }
35647
- };
35648
- var PACKAGE_MANAGER_CONTRACTS = Object.freeze({
35649
- bun: Object.freeze({
35650
- lockfiles: Object.freeze(["bun.lock", "bun.lockb"]),
35651
- installCommand: "bun install --frozen-lockfile --ignore-scripts",
35652
- buildCommand: "bun run build"
35653
- }),
35654
- npm: Object.freeze({
35655
- lockfiles: Object.freeze(["package-lock.json"]),
35656
- installCommand: "npm ci --ignore-scripts",
35657
- buildCommand: "npm run build"
35658
- }),
35659
- pnpm: Object.freeze({
35660
- lockfiles: Object.freeze(["pnpm-lock.yaml"]),
35661
- installCommand: "pnpm install --frozen-lockfile --ignore-scripts",
35662
- buildCommand: "pnpm run build"
35663
- }),
35664
- yarn: Object.freeze({
35665
- lockfiles: Object.freeze(["yarn.lock"]),
35666
- installCommand: "yarn install --immutable --mode=skip-build",
35667
- buildCommand: "yarn run build"
35668
- })
35669
- });
35670
- var ALL_LOCKFILES = Object.freeze(Object.values(PACKAGE_MANAGER_CONTRACTS).flatMap(({ lockfiles }) => lockfiles));
35671
- var EXACT_VERSION = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u;
35672
- var PACKAGE_MANAGER_DECLARATION = /^(bun|npm|pnpm|yarn)@((?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))$/u;
35673
- function resolvePackageManager(input) {
35674
- const presentLockfiles = ALL_LOCKFILES.filter((lockfile2) => input.files.includes(lockfile2));
35675
- if (presentLockfiles.length === 0)
35676
- throw new FrameworkAdmissionError("missing_lockfile");
35677
- if (presentLockfiles.length !== 1)
35678
- throw new FrameworkAdmissionError("ambiguous_lockfile");
35679
- if (input.packageManager === void 0) {
35680
- throw new FrameworkAdmissionError("missing_package_manager");
35681
- }
35682
- const declaration = PACKAGE_MANAGER_DECLARATION.exec(input.packageManager);
35683
- if (declaration === null)
35684
- throw new FrameworkAdmissionError("invalid_package_manager");
35685
- const manager = declaration[1];
35686
- const version2 = declaration[2];
35687
- const contract = PACKAGE_MANAGER_CONTRACTS[manager];
35688
- const lockfile = presentLockfiles[0];
35689
- if (!contract.lockfiles.includes(lockfile)) {
35690
- throw new FrameworkAdmissionError("package_manager_lockfile_mismatch");
35691
- }
35692
- if (manager === "bun" && version2 !== PINNED_BUN_VERSION) {
35693
- throw new FrameworkAdmissionError("package_manager_version_unsupported");
35694
- }
35695
- return Object.freeze({
35696
- manager,
35697
- version: version2,
35698
- lockfile,
35699
- installCommand: contract.installCommand,
35700
- buildCommand: contract.buildCommand
35701
- });
35702
- }
35703
- function resolveFrameworkConfig(basename2, files) {
35704
- const supported = [`${basename2}.js`, `${basename2}.mjs`, `${basename2}.ts`];
35705
- const matches = supported.filter((candidate) => files.includes(candidate));
35706
- if (matches.length > 1)
35707
- throw new FrameworkAdmissionError("ambiguous_config");
35708
- return matches[0] ?? null;
35709
- }
35710
- var VERIFIED_VERSIONS = Object.freeze({
35711
- vite: /* @__PURE__ */ new Set(["5.4.21", "8.0.16", "8.2.2"]),
35712
- "tanstack-start": /* @__PURE__ */ new Set(["1.168.26", "1.168.49"]),
35713
- nextjs: /* @__PURE__ */ new Set(["15.5.26", "16.3.8"])
35714
- });
35715
- function classifyFrameworkVersion(framework, version2) {
35716
- const parsed = parseVersionSpec(version2);
35717
- if (parsed === null)
35718
- return versionAdmission("unsupported", "invalid_version");
35719
- if (!insideAdmittedWindow(framework, parsed.version)) {
35720
- return versionAdmission("unsupported", "outside_admitted_window");
35721
- }
35722
- if (!parsed.exact) {
35723
- return versionAdmission("experimental", "range_inside_admitted_window");
35724
- }
35725
- return VERIFIED_VERSIONS[framework].has(version2) ? versionAdmission("verified", "verified_fixture") : versionAdmission("experimental", "inside_admitted_window");
35726
- }
35727
- function parseVersionSpec(version2) {
35728
- const exact5 = EXACT_VERSION.exec(version2);
35729
- const match = exact5 ?? (/^[~^](.*)$/u.exec(version2)?.[1] ? EXACT_VERSION.exec(version2.slice(1)) : null);
35730
- if (match === null)
35731
- return null;
35732
- const tuple = [Number(match[1]), Number(match[2]), Number(match[3])];
35733
- return tuple.every(Number.isSafeInteger) ? Object.freeze({ exact: exact5 !== null, version: tuple }) : null;
35734
- }
35735
- function insideAdmittedWindow(framework, version2) {
35736
- switch (framework) {
35737
- case "vite":
35738
- return compareVersions(version2, [5, 4, 0]) >= 0 && compareVersions(version2, [8, 2, 2]) <= 0;
35739
- case "tanstack-start":
35740
- return compareVersions(version2, [1, 168, 26]) >= 0 && compareVersions(version2, [1, 168, 49]) <= 0;
35741
- case "nextjs":
35742
- return isAdmittedNextVersion(version2.join("."));
35743
- }
35744
- }
35745
- function compareVersions(left, right) {
35746
- const majorDifference = left[0] - right[0];
35747
- if (majorDifference !== 0)
35748
- return majorDifference;
35749
- const minorDifference = left[1] - right[1];
35750
- return minorDifference !== 0 ? minorDifference : left[2] - right[2];
35751
- }
35752
- function versionAdmission(classification, reason) {
35753
- return Object.freeze({ classification, reason });
35754
- }
35755
- var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
35756
- var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
35757
- managed_auth_database_required: Object.freeze({
35758
- render: managedCustomerAuthDatabaseMessage,
35759
- pattern: /^"([^"]+)" selects the managed Better Auth bridge, which requires the managed PostgreSQL database\. Set database\.enabled: true with an admitted migrations path, or set auth\.provider: none and keep the application-owned authentication configuration\. Commit and push the change, then plan the new commit\.$/u
35760
- }),
35761
- database_binding_private: Object.freeze({
35762
- render: (path2) => `"${path2}" reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
35763
- pattern: /^"([^"]+)" reads a database connection string \(DATABASE_URL, HYPERDRIVE, connectionString or a postgres:\/\/ URL\); none reaches a customer Worker\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u
35764
- }),
35765
- database_driver_unsupported: Object.freeze({
35766
- render: (path2) => `"${path2}" imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
35767
- pattern: /^"([^"]+)" imports a database socket driver \(pg, postgres, pg-native or mysql2\); customer Workers cannot open sockets\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u
35768
- }),
35769
- source_syntax_invalid: Object.freeze({
35770
- render: (path2) => `Invalid JavaScript or TypeScript in "${path2}". Fix the file, commit and push the change, then plan the new commit.`,
35771
- pattern: /^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit and push the change, then plan the new commit\.$/u
35772
- }),
35773
- worker_module_default_export_required: Object.freeze({
35774
- render: (path2) => `Worker module "${path2}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called. ${PUSH_AND_PLAN}`,
35775
- pattern: /^Worker module "([^"]+)" has no default export\. Export default \{ fetch, scheduled \} with the handlers ohmyho\.st invokes; named exports are never called\. Commit and push the change, then plan the new commit\.$/u
35776
- }),
35777
- scheduled_handler_required: Object.freeze({
35778
- render: (path2) => `functions.crons is declared but "${path2}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit and push the change, then plan the new commit.`,
35779
- pattern: /^functions\.crons is declared but "([^"]+)" exports no scheduled handler\. Add scheduled\(controller, env, ctx\) to its default export, commit and push the change, then plan the new commit\.$/u
35780
- }),
35781
- framework_base_path_unsupported: Object.freeze({
35782
- render: (path2) => `"${path2}" sets a Vite base. ohmyho.st serves the app at the root origin: remove base or set base: "/". ${PUSH_AND_PLAN}`,
35783
- pattern: /^"([^"]+)" sets a Vite base\. ohmyho\.st serves the app at the root origin: remove base or set base: "\/"\. Commit and push the change, then plan the new commit\.$/u
35784
- }),
35785
- tanstack_prerender_required: Object.freeze({
35786
- render: (path2) => `"${path2}" does not call tanstackStart({ prerender: { enabled: true } }), which the static TanStack Start runtime needs; enable prerender, or set runtime.mode: edge with the Cloudflare Vite plugin. ${PUSH_AND_PLAN}`,
35787
- pattern: /^"([^"]+)" does not call tanstackStart\(\{ prerender: \{ enabled: true \} \}\), which the static TanStack Start runtime needs; enable prerender, or set runtime\.mode: edge with the Cloudflare Vite plugin\. Commit and push the change, then plan the new commit\.$/u
35788
- }),
35789
- tanstack_vite_plugins_required: Object.freeze({
35790
- render: (path2) => `"${path2}" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack/react-start/plugin/vite"; on runtime edge also import cloudflare from "@cloudflare/vite-plugin" and call cloudflare({ viteEnvironment: { name: "ssr" } }) before tanstackStart(); on runtime static do not import the Cloudflare plugin. ${PUSH_AND_PLAN}`,
35791
- pattern: /^"([^"]+)" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack\/react-start\/plugin\/vite"; on runtime edge also import cloudflare from "@cloudflare\/vite-plugin" and call cloudflare\(\{ viteEnvironment: \{ name: "ssr" \} \}\) before tanstackStart\(\); on runtime static do not import the Cloudflare plugin\. Commit and push the change, then plan the new commit\.$/u
35792
- }),
35793
- next_cache_components_unsupported: Object.freeze({
35794
- render: (path2) => `"${path2}" sets cacheComponents: true, which ohmyho.st does not support; remove it or set it to false. ${PUSH_AND_PLAN}`,
35795
- pattern: /^"([^"]+)" sets cacheComponents: true, which ohmyho\.st does not support; remove it or set it to false\. Commit and push the change, then plan the new commit\.$/u
35796
- })
35797
- });
35798
- function isFrameworkConversionDiagnosticCode(value) {
35799
- return typeof value === "string" && Object.hasOwn(FRAMEWORK_CONVERSION_DETAILS, value);
35800
- }
35801
- function parseFrameworkConversionDiagnostic(value) {
35802
- if (typeof value !== "object" || value === null || Array.isArray(value))
35803
- return null;
35804
- const record6 = value;
35805
- const path2 = record6["path"];
35806
- const code = record6["code"];
35807
- if (Object.keys(record6).length !== 2 || !isFrameworkConversionDiagnosticCode(code) || typeof path2 !== "string" || path2.length > 240 || (code === "managed_auth_database_required" ? path2 !== "ohmyhost.yaml" : !/^[A-Za-z0-9_@()[\]./-]+\.(?:[cm]?[jt]sx?)$/u.test(path2)) || path2.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
35808
- return null;
35809
- return Object.freeze({ code, path: path2 });
35810
- }
35811
- function frameworkConversionDetail(value) {
35812
- const diagnostic = parseFrameworkConversionDiagnostic(value);
35813
- return diagnostic === null ? null : FRAMEWORK_CONVERSION_DETAILS[diagnostic.code].render(diagnostic.path);
35814
- }
35815
- function parseFrameworkConversionDetail(value) {
35816
- if (typeof value !== "string")
35817
- return null;
35818
- for (const [code, detail] of Object.entries(FRAMEWORK_CONVERSION_DETAILS)) {
35819
- const match = detail.pattern.exec(value);
35820
- if (match !== null)
35821
- return frameworkConversionDetail({ code, path: match[1] });
35822
- }
35823
- return null;
35824
- }
35825
-
35826
36216
  // packages/contracts/dist/operation-deployment-result.js
35827
36217
  function exactRecord(value, keys) {
35828
36218
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -36288,9 +36678,9 @@ var PROBLEM_POLICY = {
36288
36678
  framework_conversion_required: {
36289
36679
  status: 409,
36290
36680
  retryable: false,
36291
- action: "Read detail for the file, reason and required change. Run ohmyhost init --dry-run --json at that commit, apply its blockers and the matching ohmyho.st framework Skill, then save or push the corrected version. Plan its commit; repeating the unchanged source cannot fix an admission error.",
36681
+ action: "Read detail: a catalog sentence names the file, the rule and the required change. Apply it, save or push the corrected version and plan its commit. If detail names no file, run 'ohmyhost init --dry-run --json' at that commit and apply its blockers and the matching ohmyho.st framework Skill; init lists the install, build and server-only checks it cannot run locally under pending. Repeating the unchanged source cannot fix an admission error.",
36292
36682
  title: "Framework conversion required",
36293
- restAction: "Read detail for the file, reason and required change. Run ohmyhost init --dry-run --json at that commit, apply its blockers and the matching ohmyho.st framework Skill, then save or push the corrected version. Plan its commit; repeating the unchanged source cannot fix an admission error."
36683
+ restAction: "Read detail: a catalog sentence names the file, the rule and the required change. Apply it, save or push the corrected version and plan its commit. If detail names no file, run 'ohmyhost init --dry-run --json' at that commit and apply its blockers and the matching ohmyho.st framework Skill; init lists the install, build and server-only checks it cannot run locally under pending. Repeating the unchanged source cannot fix an admission error."
36294
36684
  },
36295
36685
  repository_configuration_missing: {
36296
36686
  status: 409,
@@ -37216,11 +37606,13 @@ var parseOperation = (value) => {
37216
37606
  };
37217
37607
  };
37218
37608
  function parseOperationFailure(value) {
37219
- const input = exactRecord2(value, ["code", "message", "retryable", "suggested_action"]);
37220
- if (typeof input["code"] !== "string" || !boundedString(input["message"], 1, 512) || input["retryable"] !== false || !boundedString(input["suggested_action"], 1, 500))
37609
+ const input = exactRecord2(value, ["code", "message", "retryable", "suggested_action", "detail"]);
37610
+ const detail = input["detail"];
37611
+ const diagnostic2 = detail === void 0 || input["code"] !== "auto_deploy_plan_rejected" ? null : frameworkConversionDiagnosticFromDetail(detail);
37612
+ if (typeof input["code"] !== "string" || !boundedString(input["message"], 1, 512) || input["retryable"] !== false || !boundedString(input["suggested_action"], 1, 500) || detail !== void 0 && diagnostic2 === null)
37221
37613
  throw new ResponseContractError();
37222
- const safe = publicOperationFailure(input["code"]);
37223
- if (safe.code !== input["code"])
37614
+ const safe = publicOperationFailure(input["code"], diagnostic2);
37615
+ if (safe.code !== input["code"] || safe.detail !== detail)
37224
37616
  throw new ResponseContractError();
37225
37617
  return safe;
37226
37618
  }
@@ -40236,7 +40628,7 @@ function exact3(v, keys) {
40236
40628
  // apps/product-cli/package.json
40237
40629
  var package_default = {
40238
40630
  name: "@ohmyhost/product-cli",
40239
- version: "0.1.31",
40631
+ version: "0.1.32",
40240
40632
  private: true,
40241
40633
  ohmyhost: {
40242
40634
  deployment: "production",
@@ -46739,6 +47131,9 @@ function formatErrors(errors) {
46739
47131
  return (errors ?? []).map(({ instancePath, message }) => `${instancePath || "/"} ${message ?? "is invalid"}`).join("; ");
46740
47132
  }
46741
47133
 
47134
+ // packages/contracts/dist/framework-source-rules.js
47135
+ var import_yaml2 = __toESM(require_dist(), 1);
47136
+
46742
47137
  // packages/contracts/dist/framework-family.js
46743
47138
  function classifyFrameworkFamily(dependencies) {
46744
47139
  const next = dependencies.has("next");
@@ -46753,6 +47148,690 @@ function classifyFrameworkFamily(dependencies) {
46753
47148
  return vite ? "vite" : "unknown";
46754
47149
  }
46755
47150
 
47151
+ // packages/contracts/dist/framework-source-rules.js
47152
+ var MANAGED_BETTER_AUTH_VERSION = "1.7.1";
47153
+ var MAXIMUM_FRAMEWORK_CONFIG_BYTES = 256 * 1024;
47154
+ var WORKER_MODULE_PATH = "src/ohmyhost/worker.ts";
47155
+ var VITE_COMPANION_ENTRY_PATH = "src/ohmyhost/companion.ts";
47156
+ var PACKAGE_MANIFEST2 = "package.json";
47157
+ var REPOSITORY_CONFIG = "ohmyhost.yaml";
47158
+ var TANSTACK_START_PLUGIN = "@tanstack/react-start/plugin/vite";
47159
+ var CLOUDFLARE_PLUGIN = "@cloudflare/vite-plugin";
47160
+ var EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
47161
+ var DEPENDENCY_FIELDS = Object.freeze({
47162
+ runtime: Object.freeze(["dependencies"]),
47163
+ build: Object.freeze(["dependencies", "devDependencies"]),
47164
+ any: Object.freeze(["dependencies", "devDependencies", "optionalDependencies"])
47165
+ });
47166
+ var REQUIRED_DEPENDENCIES = Object.freeze({
47167
+ nextjs: Object.freeze([
47168
+ ["next", "runtime"],
47169
+ ["react", "runtime"],
47170
+ ["react-dom", "runtime"]
47171
+ ]),
47172
+ "vite-static": Object.freeze([["vite", "build"]]),
47173
+ "tanstack-start": Object.freeze([
47174
+ ["@tanstack/react-start", "runtime"],
47175
+ ["@tanstack/react-router", "runtime"],
47176
+ ["react", "runtime"],
47177
+ ["react-dom", "runtime"],
47178
+ ["@vitejs/plugin-react", "build"],
47179
+ ["vite", "build"]
47180
+ ]),
47181
+ functions: Object.freeze([])
47182
+ });
47183
+ var TANSTACK_EDGE_DEPENDENCY = Object.freeze([CLOUDFLARE_PLUGIN, "build"]);
47184
+ var BUILD_SCRIPT_FRAMEWORKS = Object.freeze({
47185
+ nextjs: "nextjs",
47186
+ "vite-static": "vite",
47187
+ "tanstack-start": "tanstack-start"
47188
+ });
47189
+ var BUILD_OUTPUTS = Object.freeze({
47190
+ "vite-static": "dist",
47191
+ "tanstack-start": "dist/client"
47192
+ });
47193
+ var REQUIRED_CONFIG_PATHS = Object.freeze({ nextjs: "next.config.ts", "tanstack-start": "vite.config.ts" });
47194
+ var MAXIMUM_LOCKFILE_LENGTH = 5 * 1024 * 1024;
47195
+ var MAXIMUM_PNPM_LOCKFILE_HEAD = 256 * 1024;
47196
+ var PNPM_LOCKFILE_BODY = /^(?:packages|snapshots):/mu;
47197
+ var PNPM_LOCKFILE_VERSION = /^(?:6|9)\.[0-9]{1,3}$/u;
47198
+ var PNPM_RESOLVED_VERSION = /^([^()]+)(?:\(.*\))?$/u;
47199
+ var DEFAULT_EXPORT_PATTERN = /\bexport\s+default\b/u;
47200
+ var FETCH_HANDLER_PATTERN = /(?:[{,;}]|\basync)\s*fetch\s*(?:\(|:|,|\})/u;
47201
+ var SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
47202
+ var DATABASE_CONTRACT_PATTERNS = Object.freeze([
47203
+ [
47204
+ /\bHYPERDRIVE\b|\bconnectionString\b|\bDATABASE_URL\b|postgres(?:ql)?:\/\//u,
47205
+ "database_binding_private"
47206
+ ],
47207
+ [
47208
+ /\bfrom\s*["'](?:pg|postgres|mysql2|pg-native)["']|\brequire\(\s*["'](?:pg|postgres|pg-native)["']/u,
47209
+ "database_driver_unsupported"
47210
+ ]
47211
+ ]);
47212
+ function parsePackageManifest(value) {
47213
+ let parsed = value;
47214
+ if (typeof value === "string") {
47215
+ try {
47216
+ parsed = JSON.parse(value);
47217
+ } catch {
47218
+ return null;
47219
+ }
47220
+ }
47221
+ return isRecord4(parsed) ? parsed : null;
47222
+ }
47223
+ function packageManagerDeclaration(manifest2) {
47224
+ const value = manifest2["packageManager"];
47225
+ return typeof value === "string" ? value : void 0;
47226
+ }
47227
+ function declaredDependency(manifest2, name, placement) {
47228
+ for (const field of DEPENDENCY_FIELDS[placement]) {
47229
+ const version2 = ownValue(manifest2[field], name);
47230
+ if (typeof version2 === "string" && version2.length > 0)
47231
+ return version2;
47232
+ }
47233
+ return null;
47234
+ }
47235
+ function declaredDependencyNames(manifest2) {
47236
+ const names = /* @__PURE__ */ new Set();
47237
+ for (const field of DEPENDENCY_FIELDS.any) {
47238
+ const dependencies = manifest2[field];
47239
+ if (!isRecord4(dependencies))
47240
+ continue;
47241
+ for (const [name, version2] of Object.entries(dependencies))
47242
+ if (typeof version2 === "string" && version2.length > 0)
47243
+ names.add(name);
47244
+ }
47245
+ return names;
47246
+ }
47247
+ function viteCompanionRequired(configuration) {
47248
+ return configuration.runtime.mode === "edge" || configuration.database?.enabled === true || configuration.auth?.provider === "better-auth" || configuration.mail?.enabled === true || configuration.storage !== void 0 || (configuration.functions?.crons.length ?? 0) > 0;
47249
+ }
47250
+ function frameworkVersionAdmissions(framework, manifest2, lockfile) {
47251
+ const admissions = [];
47252
+ let locked;
47253
+ for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
47254
+ if (!isFrameworkVersionPackage(name))
47255
+ continue;
47256
+ const declared = declaredDependency(manifest2, name, placement);
47257
+ if (declared === null)
47258
+ continue;
47259
+ locked ??= lockfile === null ? () => null : lockedPackageVersions(lockfile);
47260
+ admissions.push(classifyFrameworkVersion(name, declared, locked(name)));
47261
+ }
47262
+ return Object.freeze(admissions);
47263
+ }
47264
+ function lockedPackageVersions(lockfile) {
47265
+ const { name, text } = lockfile;
47266
+ const npm = name === "package-lock.json";
47267
+ if (!npm && name !== "pnpm-lock.yaml" || typeof text !== "string" || text.length > MAXIMUM_LOCKFILE_LENGTH)
47268
+ return () => null;
47269
+ const document2 = npm ? parseJsonLockfile(text) : parsePnpmLockfileHead(text);
47270
+ return (packageName) => {
47271
+ const version2 = npm ? npmLockedVersion(document2, packageName) : pnpmLockedVersion(document2, packageName);
47272
+ return isResolvedFrameworkVersion(version2) ? version2 : null;
47273
+ };
47274
+ }
47275
+ function frameworkSourceFindings(input) {
47276
+ const versions = frameworkVersionAdmissions(input.framework, input.manifest, input.lockfile);
47277
+ return Object.freeze({
47278
+ findings: Object.freeze([
47279
+ ...manifestFindings(input, versions),
47280
+ ...configurationFindings(input),
47281
+ ...betterAuthFindings(input.configuration, input.manifest),
47282
+ ...frameworkConfigFindings(input)
47283
+ ]),
47284
+ versions
47285
+ });
47286
+ }
47287
+ function repositorySourceFindings(files) {
47288
+ const configuration = repositoryConfiguration(files.get(REPOSITORY_CONFIG));
47289
+ if (configuration === null)
47290
+ return refused("repository_configuration_invalid", REPOSITORY_CONFIG);
47291
+ const prefix = configuration.applicationRoot === "." ? "" : `${configuration.applicationRoot}/`;
47292
+ const applicationFiles = [...files.keys()].filter((path2) => path2.length > prefix.length && path2.startsWith(prefix)).map((path2) => path2.slice(prefix.length));
47293
+ if (applicationFiles.length === 0)
47294
+ return refused("application_root_missing", REPOSITORY_CONFIG);
47295
+ const text = (path2) => files.get(`${prefix}${path2}`) ?? null;
47296
+ const manifest2 = parsePackageManifest(text(PACKAGE_MANIFEST2));
47297
+ if (manifest2 === null)
47298
+ return refused("package_manifest_invalid", PACKAGE_MANIFEST2);
47299
+ const family = configuration.runtime.mode === "functions" ? "functions" : classifyFrameworkFamily(declaredDependencyNames(manifest2));
47300
+ if (family === "ambiguous")
47301
+ return refused("framework_ambiguous", PACKAGE_MANIFEST2);
47302
+ if (family === "unknown")
47303
+ return refused("framework_unsupported", PACKAGE_MANIFEST2);
47304
+ const framework = family === "vite" ? "vite-static" : family;
47305
+ const findings = [];
47306
+ const packageManager = admitted(findings, () => resolvePackageManager({
47307
+ packageManager: packageManagerDeclaration(manifest2),
47308
+ files: applicationFiles
47309
+ })) ?? null;
47310
+ const configPath = frameworkConfigPath(framework, applicationFiles, findings);
47311
+ const configText = configPath === null ? null : text(configPath);
47312
+ if (configPath !== null && configText === null)
47313
+ findings.push(diagnostic({ code: "source_syntax_invalid", path: configPath }));
47314
+ const lockfileText = packageManager === null ? null : text(packageManager.lockfile);
47315
+ const result = frameworkSourceFindings({
47316
+ framework,
47317
+ files: applicationFiles,
47318
+ configuration,
47319
+ manifest: manifest2,
47320
+ packageManager,
47321
+ lockfile: packageManager === null || lockfileText === null ? null : Object.freeze({ name: packageManager.lockfile, text: lockfileText }),
47322
+ frameworkConfig: configPath === null || configText === null ? null : Object.freeze({ path: configPath, text: configText })
47323
+ });
47324
+ return Object.freeze({
47325
+ framework,
47326
+ findings: Object.freeze([...findings, ...result.findings]),
47327
+ versions: result.versions
47328
+ });
47329
+ }
47330
+ function nextConfigFindings(text, path2) {
47331
+ const lexed = lexFrameworkConfig(text, path2);
47332
+ if ("finding" in lexed)
47333
+ return Object.freeze([lexed.finding]);
47334
+ const { tokens } = lexed;
47335
+ const findings = [];
47336
+ if (hasNonRootProperty(tokens, ["basePath", "assetPrefix"], ["", "/"]))
47337
+ findings.push(diagnostic({ code: "next_base_path_unsupported", path: path2 }));
47338
+ if (hasProperty(tokens, "output", "export") || hasProperty(tokens, "output", "standalone"))
47339
+ findings.push(diagnostic({ code: "next_output_unsupported", path: path2 }));
47340
+ if (hasProperty(tokens, "cacheComponents", "true"))
47341
+ findings.push(diagnostic({ code: "next_cache_components_unsupported", path: path2 }));
47342
+ if (!hasExportDefault(tokens) && !(path2.slice(path2.lastIndexOf("/") + 1) === "next.config.js" && hasModuleExports(tokens)))
47343
+ findings.push(diagnostic({ code: "framework_config_default_export_required", path: path2 }));
47344
+ return Object.freeze(findings);
47345
+ }
47346
+ function viteConfigFindings(text, path2) {
47347
+ const lexed = lexFrameworkConfig(text, path2);
47348
+ if ("finding" in lexed)
47349
+ return Object.freeze([lexed.finding]);
47350
+ return Object.freeze(hasNonRootProperty(lexed.tokens, ["base"], ["/"]) ? [diagnostic({ code: "framework_base_path_unsupported", path: path2 })] : []);
47351
+ }
47352
+ function tanStackViteConfigFindings(text, path2, runtime) {
47353
+ const lexed = lexFrameworkConfig(text, path2);
47354
+ if ("finding" in lexed)
47355
+ return Object.freeze([lexed.finding]);
47356
+ const findings = [];
47357
+ if (hasNonRootProperty(lexed.tokens, ["base"], ["/"]))
47358
+ findings.push(diagnostic({ code: "framework_base_path_unsupported", path: path2 }));
47359
+ if (runtime === null)
47360
+ return Object.freeze(findings);
47361
+ const contract = tanStackPluginContract(lexed.tokens, runtime);
47362
+ if (contract === null)
47363
+ return Object.freeze([diagnostic({ code: "source_syntax_invalid", path: path2 })]);
47364
+ if (!contract.plugins)
47365
+ findings.push(diagnostic({ code: "tanstack_vite_plugins_required", path: path2 }));
47366
+ if (!contract.prerender)
47367
+ findings.push(diagnostic({ code: "tanstack_prerender_required", path: path2 }));
47368
+ return Object.freeze(findings);
47369
+ }
47370
+ function tanStackViteConfigRuntime(text) {
47371
+ const tokens = exceedsConfigBytes(text) ? null : configTokens(text);
47372
+ if (tokens === null)
47373
+ return null;
47374
+ for (const runtime of ["static", "edge"]) {
47375
+ const contract = tanStackPluginContract(tokens, runtime);
47376
+ if (contract?.plugins === true && contract.prerender)
47377
+ return runtime;
47378
+ }
47379
+ return null;
47380
+ }
47381
+ function databaseContractCodes(source) {
47382
+ const code = sourceWithoutComments(source);
47383
+ return Object.freeze(DATABASE_CONTRACT_PATTERNS.filter(([pattern]) => pattern.test(code)).map(([, found]) => found));
47384
+ }
47385
+ function workerModuleFindings(worker) {
47386
+ const { path: path2 } = worker;
47387
+ const code = sourceWithoutComments(worker.source);
47388
+ if (!DEFAULT_EXPORT_PATTERN.test(code))
47389
+ return Object.freeze([diagnostic({ code: "worker_module_default_export_required", path: path2 })]);
47390
+ const findings = [];
47391
+ if (worker.fetch && !FETCH_HANDLER_PATTERN.test(code))
47392
+ findings.push(diagnostic({ code: "worker_module_fetch_handler_required", path: path2 }));
47393
+ if (worker.scheduled && !SCHEDULED_HANDLER_PATTERN.test(code))
47394
+ findings.push(diagnostic({ code: "scheduled_handler_required", path: path2 }));
47395
+ return Object.freeze(findings);
47396
+ }
47397
+ function sourceWithoutComments(source) {
47398
+ let result = "";
47399
+ let quote = null;
47400
+ for (let index = 0; index < source.length; index += 1) {
47401
+ const character = source[index];
47402
+ const next = source[index + 1];
47403
+ if (quote !== null) {
47404
+ result += character;
47405
+ if (character === "\\") {
47406
+ index += 1;
47407
+ result += source[index] ?? "";
47408
+ } else if (character === quote || character === "\n" && quote !== "`")
47409
+ quote = null;
47410
+ continue;
47411
+ }
47412
+ if (character === "'" || character === '"' || character === "`") {
47413
+ quote = character;
47414
+ result += character;
47415
+ continue;
47416
+ }
47417
+ if (character === "/" && next === "/" && source[index - 1] !== "\\") {
47418
+ while (index < source.length && source[index] !== "\n") {
47419
+ result += " ";
47420
+ index += 1;
47421
+ }
47422
+ result += "\n";
47423
+ continue;
47424
+ }
47425
+ if (character === "/" && next === "*" && source[index - 1] !== "\\") {
47426
+ result += " ";
47427
+ index += 2;
47428
+ while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
47429
+ result += source[index] === "\n" ? "\n" : " ";
47430
+ index += 1;
47431
+ }
47432
+ result += " ";
47433
+ index += 1;
47434
+ continue;
47435
+ }
47436
+ result += character;
47437
+ }
47438
+ return result;
47439
+ }
47440
+ function isDatabaseContractModule(path2) {
47441
+ return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47442
+ }
47443
+ function manifestFindings(input, versions) {
47444
+ const { configuration, framework, manifest: manifest2 } = input;
47445
+ const findings = [];
47446
+ if (framework !== "functions" && !isAdmittedFrameworkBuildScript(BUILD_SCRIPT_FRAMEWORKS[framework], ownValue(manifest2["scripts"], "build")))
47447
+ findings.push(diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST2, framework }));
47448
+ const required = framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47449
+ for (const [name, placement] of required)
47450
+ if (declaredDependency(manifest2, name, placement) === null)
47451
+ findings.push(diagnostic({
47452
+ code: "framework_dependency_required",
47453
+ path: PACKAGE_MANIFEST2,
47454
+ package: name
47455
+ }));
47456
+ for (const admission of versions) {
47457
+ const finding = frameworkVersionDiagnostic(admission, input.lockfile?.name ?? null);
47458
+ if (finding !== null)
47459
+ findings.push(finding);
47460
+ }
47461
+ if (framework === "nextjs")
47462
+ for (const name of ["react", "react-dom"]) {
47463
+ const version2 = declaredDependency(manifest2, name, "runtime");
47464
+ if (version2 !== null && !EXACT_VERSION.test(version2))
47465
+ findings.push(diagnostic({
47466
+ code: "react_version_exact_required",
47467
+ path: PACKAGE_MANIFEST2,
47468
+ package: name
47469
+ }));
47470
+ }
47471
+ return findings;
47472
+ }
47473
+ function configurationFindings(input) {
47474
+ const { configuration, files, framework, packageManager } = input;
47475
+ const build = "command" in configuration.build ? configuration.build : null;
47476
+ const crons = (configuration.functions?.crons.length ?? 0) > 0;
47477
+ const findings = [];
47478
+ if (framework !== "functions" && !runtimeModeAdmitted(framework, configuration, crons))
47479
+ findings.push(diagnostic({ code: "runtime_mode_unsupported", path: REPOSITORY_CONFIG, framework }));
47480
+ if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== void 0)
47481
+ findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
47482
+ const output = BUILD_OUTPUTS[framework];
47483
+ if (build !== null && output !== void 0 && build.output !== output)
47484
+ findings.push(diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }));
47485
+ if (packageManager !== null && framework !== "nextjs" && (configuration.build.install !== packageManager.installCommand || build !== null && build.command !== packageManager.buildCommand))
47486
+ findings.push(diagnostic({ code: "build_command_mismatch", path: REPOSITORY_CONFIG }));
47487
+ if (framework === "vite-static" && viteCompanionRequired(configuration) && !files.includes(VITE_COMPANION_ENTRY_PATH))
47488
+ findings.push(diagnostic({ code: "companion_entry_required", path: VITE_COMPANION_ENTRY_PATH }));
47489
+ if ((framework === "functions" || crons && (framework === "nextjs" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47490
+ findings.push(diagnostic({ code: "worker_module_required", path: WORKER_MODULE_PATH }));
47491
+ if (managedCustomerAuthAdmissionIssue(configuration) !== null)
47492
+ findings.push(diagnostic({ code: "managed_auth_database_required", path: REPOSITORY_CONFIG }));
47493
+ return findings;
47494
+ }
47495
+ function runtimeModeAdmitted(framework, configuration, crons) {
47496
+ const mode = configuration.runtime.mode;
47497
+ if (framework === "nextjs")
47498
+ return mode === "edge";
47499
+ if (framework === "vite-static")
47500
+ return mode === (viteCompanionRequired(configuration) ? "edge" : "static");
47501
+ return mode === "edge" || mode === "static" && !crons && configuration.storage === void 0;
47502
+ }
47503
+ function betterAuthFindings(configuration, manifest2) {
47504
+ return configuration.auth?.provider === "better-auth" && declaredDependency(manifest2, "better-auth", "any") !== MANAGED_BETTER_AUTH_VERSION ? [diagnostic({ code: "better_auth_version_required", path: PACKAGE_MANIFEST2 })] : [];
47505
+ }
47506
+ function frameworkConfigFindings(input) {
47507
+ const { frameworkConfig, framework } = input;
47508
+ if (frameworkConfig === null || framework === "functions")
47509
+ return [];
47510
+ const { path: path2, text } = frameworkConfig;
47511
+ if (framework === "nextjs")
47512
+ return nextConfigFindings(text, path2);
47513
+ if (framework === "vite-static")
47514
+ return viteConfigFindings(text, path2);
47515
+ const mode = input.configuration.runtime.mode;
47516
+ return tanStackViteConfigFindings(text, path2, mode === "edge" || mode === "static" ? mode : null);
47517
+ }
47518
+ function repositoryConfiguration(text) {
47519
+ if (typeof text !== "string")
47520
+ return null;
47521
+ try {
47522
+ return parseOhmyhostConfigYaml(text);
47523
+ } catch (error) {
47524
+ if (error instanceof TypeError)
47525
+ return null;
47526
+ throw error;
47527
+ }
47528
+ }
47529
+ function frameworkConfigPath(framework, files, findings) {
47530
+ if (framework === "functions")
47531
+ return null;
47532
+ const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files));
47533
+ if (framework === "nextjs")
47534
+ admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
47535
+ const required = REQUIRED_CONFIG_PATHS[framework];
47536
+ if (path2 === null && required !== void 0)
47537
+ findings.push(diagnostic({ code: "framework_config_required", path: required }));
47538
+ return path2 ?? null;
47539
+ }
47540
+ function admitted(findings, admit) {
47541
+ try {
47542
+ return admit();
47543
+ } catch (error) {
47544
+ if (!(error instanceof FrameworkAdmissionError))
47545
+ throw error;
47546
+ findings.push(error.diagnostic);
47547
+ return void 0;
47548
+ }
47549
+ }
47550
+ function refused(code, path2) {
47551
+ return Object.freeze({
47552
+ framework: null,
47553
+ findings: Object.freeze([diagnostic({ code, path: path2 })]),
47554
+ versions: Object.freeze([])
47555
+ });
47556
+ }
47557
+ function diagnostic(value) {
47558
+ const parsed = parseFrameworkConversionDiagnostic(value);
47559
+ if (parsed === null)
47560
+ throw new TypeError("Framework source diagnostic is invalid");
47561
+ return parsed;
47562
+ }
47563
+ function isFrameworkVersionPackage(name) {
47564
+ return Object.hasOwn(FRAMEWORK_VERSION_POLICY, name);
47565
+ }
47566
+ function parseJsonLockfile(text) {
47567
+ try {
47568
+ return JSON.parse(text);
47569
+ } catch {
47570
+ return null;
47571
+ }
47572
+ }
47573
+ function parsePnpmLockfileHead(text) {
47574
+ const end = text.search(PNPM_LOCKFILE_BODY);
47575
+ const head = end === -1 ? text : text.slice(0, end);
47576
+ if (head.length > MAXIMUM_PNPM_LOCKFILE_HEAD)
47577
+ return null;
47578
+ try {
47579
+ const document2 = (0, import_yaml2.parseDocument)(head, {
47580
+ customTags: [],
47581
+ logLevel: "error",
47582
+ merge: false,
47583
+ prettyErrors: false,
47584
+ resolveKnownTags: false,
47585
+ schema: "core",
47586
+ strict: true,
47587
+ stringKeys: true,
47588
+ // The parser compares every key with every earlier one; pnpm refuses repeated keys itself.
47589
+ uniqueKeys: false,
47590
+ version: "1.2"
47591
+ });
47592
+ return document2.errors.length === 0 && document2.warnings.length === 0 ? document2.toJS({ maxAliasCount: 0 }) : null;
47593
+ } catch {
47594
+ return null;
47595
+ }
47596
+ }
47597
+ function npmLockedVersion(document2, packageName) {
47598
+ if (!isRecord4(document2) || document2["lockfileVersion"] !== 2 && document2["lockfileVersion"] !== 3)
47599
+ return null;
47600
+ const entry = ownValue(document2["packages"], `node_modules/${packageName}`);
47601
+ return isRecord4(entry) && !Object.hasOwn(entry, "link") && (entry["name"] === void 0 || entry["name"] === packageName) ? entry["version"] : null;
47602
+ }
47603
+ function pnpmLockedVersion(document2, packageName) {
47604
+ if (!isRecord4(document2))
47605
+ return null;
47606
+ const lockfileVersion = document2["lockfileVersion"];
47607
+ if (typeof lockfileVersion !== "string" || !PNPM_LOCKFILE_VERSION.test(lockfileVersion))
47608
+ return null;
47609
+ const importers = document2["importers"];
47610
+ const root = importers === void 0 && lockfileVersion.startsWith("6.") ? document2 : ownValue(importers, ".");
47611
+ if (!isRecord4(root))
47612
+ return null;
47613
+ for (const group of ["dependencies", "devDependencies", "optionalDependencies"]) {
47614
+ const dependencies = root[group];
47615
+ if (!isRecord4(dependencies) || !Object.hasOwn(dependencies, packageName))
47616
+ continue;
47617
+ const entry = dependencies[packageName];
47618
+ return isRecord4(entry) && typeof entry["version"] === "string" ? PNPM_RESOLVED_VERSION.exec(entry["version"])?.[1] ?? null : null;
47619
+ }
47620
+ return null;
47621
+ }
47622
+ function ownValue(record6, key) {
47623
+ return isRecord4(record6) && Object.hasOwn(record6, key) ? record6[key] : void 0;
47624
+ }
47625
+ function isRecord4(value) {
47626
+ return typeof value === "object" && value !== null && !Array.isArray(value);
47627
+ }
47628
+ var PUNCTUATION = "{}[]():,.;?=-!";
47629
+ function lexFrameworkConfig(text, path2) {
47630
+ const tooLarge = diagnostic({ code: "framework_config_too_large", path: path2 });
47631
+ if (exceedsConfigBytes(text))
47632
+ return { finding: tooLarge };
47633
+ const tokens = configTokens(text);
47634
+ return tokens === null ? { finding: diagnostic({ code: "source_syntax_invalid", path: path2 }) } : { tokens };
47635
+ }
47636
+ function exceedsConfigBytes(text) {
47637
+ return text.length > MAXIMUM_FRAMEWORK_CONFIG_BYTES || new TextEncoder().encode(text).byteLength > MAXIMUM_FRAMEWORK_CONFIG_BYTES;
47638
+ }
47639
+ function configTokens(source) {
47640
+ const tokens = [];
47641
+ let index = 0;
47642
+ while (index < source.length) {
47643
+ const character = source[index];
47644
+ const next = source[index + 1];
47645
+ if (character === "/" && next === "/") {
47646
+ const end = source.indexOf("\n", index + 2);
47647
+ index = end === -1 ? source.length : end + 1;
47648
+ } else if (character === "/" && next === "*") {
47649
+ const end = source.indexOf("*/", index + 2);
47650
+ if (end === -1)
47651
+ return null;
47652
+ index = end + 2;
47653
+ } else if (character === "'" || character === '"' || character === "`") {
47654
+ const string = readString(source, index, character);
47655
+ if (string === null)
47656
+ return null;
47657
+ tokens.push(string.token);
47658
+ index = string.nextIndex;
47659
+ } else if (isIdentifierStart(character) || isDigit(character)) {
47660
+ const start = index;
47661
+ const part = isDigit(character) ? isDigit : isIdentifierPart;
47662
+ index += 1;
47663
+ while (index < source.length && part(source[index]))
47664
+ index += 1;
47665
+ tokens.push({
47666
+ kind: isDigit(character) ? "number" : "identifier",
47667
+ value: source.slice(start, index)
47668
+ });
47669
+ } else {
47670
+ if (PUNCTUATION.includes(character))
47671
+ tokens.push({ kind: "punctuation", value: character });
47672
+ index += 1;
47673
+ }
47674
+ }
47675
+ return tokens;
47676
+ }
47677
+ function readString(source, index, quote) {
47678
+ let cursor = index + 1;
47679
+ let value = "";
47680
+ while (cursor < source.length) {
47681
+ const character = source[cursor];
47682
+ if (character === quote)
47683
+ return { token: { kind: "string", value }, nextIndex: cursor + 1 };
47684
+ if (character === "\\") {
47685
+ const escaped = source[cursor + 1];
47686
+ if (escaped === void 0)
47687
+ return null;
47688
+ value += escaped;
47689
+ cursor += 2;
47690
+ } else {
47691
+ value += character;
47692
+ cursor += 1;
47693
+ }
47694
+ }
47695
+ return null;
47696
+ }
47697
+ function isIdentifierStart(character) {
47698
+ return character >= "A" && character <= "Z" || character >= "a" && character <= "z" || character === "_" || character === "$";
47699
+ }
47700
+ function isIdentifierPart(character) {
47701
+ return isIdentifierStart(character) || isDigit(character);
47702
+ }
47703
+ function isDigit(character) {
47704
+ return character >= "0" && character <= "9";
47705
+ }
47706
+ function hasNonRootProperty(tokens, names, rootStrings) {
47707
+ let objectDepth = 0;
47708
+ for (let index = 0; index < tokens.length; index += 1) {
47709
+ const token = tokens[index];
47710
+ if (token.value === "{" || token.value === "}") {
47711
+ objectDepth += token.value === "{" ? 1 : -1;
47712
+ continue;
47713
+ }
47714
+ const before = tokens[index - 1]?.value;
47715
+ const directKey = before === "{" || before === ",";
47716
+ const computedKey = before === "[" && tokens[index + 1]?.value === "]" && (tokens[index - 2]?.value === "{" || tokens[index - 2]?.value === ",");
47717
+ if (objectDepth < 1 || !names.includes(token.value) || !directKey && !computedKey)
47718
+ continue;
47719
+ const separator = tokens[index + (computedKey ? 2 : 1)]?.value;
47720
+ const value = tokens[index + (computedKey ? 3 : 2)];
47721
+ const root = separator === ":" && (value?.kind === "string" && rootStrings.includes(value.value) || value?.kind === "identifier" && value.value === "undefined");
47722
+ if (!root)
47723
+ return true;
47724
+ }
47725
+ return false;
47726
+ }
47727
+ function hasProperty(tokens, name, value) {
47728
+ return tokens.some((token, index) => token.value === name && tokens[index + 1]?.value === ":" && tokens[index + 2]?.value === value);
47729
+ }
47730
+ function hasExportDefault(tokens) {
47731
+ return tokens.some((token, index) => token.value === "export" && tokens[index + 1]?.value === "default");
47732
+ }
47733
+ function hasModuleExports(tokens) {
47734
+ return tokens.some((token, index) => {
47735
+ if (token.kind !== "identifier" || token.value !== "module")
47736
+ return false;
47737
+ const property = tokens[index + 2];
47738
+ const direct = tokens[index + 1]?.value === "." && property?.kind === "identifier" && property.value === "exports";
47739
+ const computed = tokens[index + 1]?.value === "[" && property?.kind === "string" && property.value === "exports" && tokens[index + 3]?.value === "]";
47740
+ const assignment = index + (computed ? 4 : 3);
47741
+ return (direct || computed) && tokens[assignment]?.value === "=" && tokens[assignment + 1]?.value !== "=";
47742
+ });
47743
+ }
47744
+ function tanStackPluginContract(tokens, runtime) {
47745
+ const closing = closingBraces(tokens);
47746
+ const imported = hasNamedImport(tokens, "tanstackStart", TANSTACK_START_PLUGIN);
47747
+ if (runtime === "static") {
47748
+ const calls2 = callOptions(tokens, closing, "tanstackStart");
47749
+ return calls2 === null ? null : {
47750
+ plugins: imported && !hasModuleImport(tokens, CLOUDFLARE_PLUGIN),
47751
+ prerender: calls2.some(([start, end]) => nestedPropertyValue(tokens, closing, start, end, "prerender", "enabled") === "true")
47752
+ };
47753
+ }
47754
+ const calls = callOptions(tokens, closing, "cloudflare");
47755
+ return calls === null ? null : {
47756
+ plugins: imported && hasNamedImport(tokens, "cloudflare", CLOUDFLARE_PLUGIN) && calls.some(([start, end]) => nestedPropertyValue(tokens, closing, start, end, "viteEnvironment", "name") === "ssr") && callIndex(tokens, "cloudflare") < callIndex(tokens, "tanstackStart"),
47757
+ prerender: true
47758
+ };
47759
+ }
47760
+ function closingBraces(tokens) {
47761
+ const closing = tokens.map(() => -1);
47762
+ const open5 = [];
47763
+ tokens.forEach((token, index) => {
47764
+ if (token.value === "{")
47765
+ open5.push(index);
47766
+ if (token.value !== "}")
47767
+ return;
47768
+ const start = open5.pop();
47769
+ if (start !== void 0)
47770
+ closing[start] = index;
47771
+ });
47772
+ return closing;
47773
+ }
47774
+ function callOptions(tokens, closing, name) {
47775
+ const calls = [];
47776
+ for (let index = 0; index < tokens.length - 2; index += 1) {
47777
+ if (tokens[index]?.value !== name || tokens[index + 1]?.value !== "(" || tokens[index + 2]?.value !== "{")
47778
+ continue;
47779
+ const end = closing[index + 2] ?? -1;
47780
+ if (end === -1)
47781
+ return null;
47782
+ calls.push([index + 2, end]);
47783
+ }
47784
+ return calls;
47785
+ }
47786
+ function nestedPropertyValue(tokens, closing, start, end, outer, inner) {
47787
+ const object3 = directProperty(tokens, closing, start, end, outer);
47788
+ if (object3?.value !== "{")
47789
+ return null;
47790
+ return directProperty(tokens, closing, object3.start, object3.end, inner)?.value ?? null;
47791
+ }
47792
+ function directProperty(tokens, closing, start, end, name) {
47793
+ let depth = 0;
47794
+ for (let index = start + 1; index < end; index += 1) {
47795
+ const value = tokens[index]?.value;
47796
+ if (value === "{") {
47797
+ index = Math.max(index, closing[index] ?? -1);
47798
+ continue;
47799
+ }
47800
+ if (value === "[" || value === "(")
47801
+ depth += 1;
47802
+ if (value === "]" || value === ")")
47803
+ depth -= 1;
47804
+ if (depth !== 0 || value !== name || tokens[index + 1]?.value !== ":")
47805
+ continue;
47806
+ const candidate = tokens[index + 2]?.value ?? "";
47807
+ const close = candidate === "{" ? closing[index + 2] ?? -1 : index + 2;
47808
+ return { start: index + 2, end: close, value: candidate };
47809
+ }
47810
+ return null;
47811
+ }
47812
+ function callIndex(tokens, name) {
47813
+ return tokens.findIndex((token, index) => token.value === name && tokens[index + 1]?.value === "(");
47814
+ }
47815
+ function hasNamedImport(tokens, name, moduleName) {
47816
+ for (let index = 0; index < tokens.length; index += 1) {
47817
+ if (tokens[index]?.value !== "import")
47818
+ continue;
47819
+ let from = index + 1;
47820
+ while (from < tokens.length && tokens[from]?.value !== "from")
47821
+ from += 1;
47822
+ if (from === tokens.length)
47823
+ return false;
47824
+ const source = tokens[from + 1];
47825
+ if (source?.kind === "string" && source.value === moduleName && tokens.slice(index + 1, from).some((token) => token.value === name))
47826
+ return true;
47827
+ index = from;
47828
+ }
47829
+ return false;
47830
+ }
47831
+ function hasModuleImport(tokens, moduleName) {
47832
+ return tokens.some((token, index) => token.value === "from" && tokens[index + 1]?.kind === "string" && tokens[index + 1]?.value === moduleName);
47833
+ }
47834
+
46756
47835
  // packages/contracts/dist/database-migration-errors.js
46757
47836
  var DatabaseMigrationAdmissionError = class extends Error {
46758
47837
  reason;
@@ -47117,28 +48196,31 @@ function admitStatement(tokens, sql) {
47117
48196
  // apps/product-cli/dist/repository-init.js
47118
48197
  var MAXIMUM_PACKAGE_BYTES = 1024 * 1024;
47119
48198
  var MAXIMUM_CONFIGURATION_BYTES = 64 * 1024;
48199
+ var MAXIMUM_INSPECTED_FILE_BYTES = 5 * 1024 * 1024;
47120
48200
  var MAXIMUM_REPOSITORY_FILES = 2e4;
47121
- var SUPPORTED_BETTER_AUTH_VERSION = "1.7.1";
47122
48201
  var PROJECT_PATTERN = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/u;
47123
48202
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
47124
48203
  ".git",
47125
48204
  ".next",
48205
+ ".open-next",
47126
48206
  ".supabase",
47127
48207
  ".turbo",
47128
48208
  ".vercel",
48209
+ ".wrangler",
47129
48210
  "coverage",
47130
48211
  "dist",
47131
48212
  "node_modules"
47132
48213
  ]);
47133
- var WORKER_MODULE_PATH = "src/ohmyhost/worker.ts";
48214
+ var UTF8 = new TextDecoder("utf-8", { fatal: true });
47134
48215
  var MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
47135
48216
  var SOURCE_MODULE_PATTERN = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
47136
48217
  var STORAGE_CLIENT_CALL_PATTERN = /\bcreatePrivateStorageClient\s*\(/u;
47137
- var DATABASE_PRIVATE_BINDING_PATTERN = /\bHYPERDRIVE\b|\bconnectionString\b|\bDATABASE_URL\b|postgres(?:ql)?:\/\//u;
47138
- var DATABASE_SOCKET_DRIVER_PATTERN = /\bfrom\s*["'](?:pg|postgres|mysql2|pg-native)["']|\brequire\(\s*["'](?:pg|postgres|pg-native)["']/u;
47139
- var INERT_SOURCE_PATTERN = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|\bimport\s+type\b[^;]*;/gu;
48218
+ var UNNAMED_MODULE_MESSAGES = Object.freeze({
48219
+ database_binding_private: 'The module at path reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit and push the change, then plan the new commit.',
48220
+ database_driver_unsupported: 'The module at path imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit and push the change, then plan the new commit.'
48221
+ });
47140
48222
  function callsStorageClient(source) {
47141
- return STORAGE_CLIENT_CALL_PATTERN.test(source.replaceAll(INERT_SOURCE_PATTERN, " "));
48223
+ return STORAGE_CLIENT_CALL_PATTERN.test(sourceWithoutComments(source));
47142
48224
  }
47143
48225
  var STORAGE_RUNTIME_BINDINGS = Object.freeze([
47144
48226
  "OHMYHOST_ENVIRONMENT_ID",
@@ -47148,14 +48230,13 @@ var STORAGE_RUNTIME_BINDINGS = Object.freeze([
47148
48230
  "OHMYHOST_STORAGE_KEY"
47149
48231
  ]);
47150
48232
  var CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${package_default.version}`;
47151
- var SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
47152
- var DEFAULT_EXPORT_PATTERN = /\bexport\s+default\b/u;
47153
- function defaultExportBlocker(modulePath) {
47154
- return {
47155
- code: "worker_module_default_export_required",
47156
- message: `${modulePath} has no default export. Export its handlers as the default export (export default { fetch, scheduled }); named exports are never invoked.`
47157
- };
47158
- }
48233
+ var PENDING_CHECKS = Object.freeze({
48234
+ installed_version: "compatibility.frameworks lists a version range that no lockfile ohmyho.st reads (package-lock.json v2/v3 or pnpm-lock.yaml v6/v9) resolves. Planning admits it; after the frozen install, the build checks the installed version against the same security minimum.",
48235
+ frozen_install: "The build installs dependencies from the committed lockfile with the frozen install of the packageManager in package.json, without lifecycle scripts; init installs nothing.",
48236
+ framework_build: "The build itself (scripts.build with the ohmyho.st adapter build, or the Worker bundle of the functions runtime) runs only on ohmyho.st; a clean init does not prove that it succeeds.",
48237
+ reachable_runtime_sources: "Planning walks the runtime sources the build reaches for Workers-incompatible imports, database socket drivers and syntax errors; init does not walk them.",
48238
+ project_state: "Planning also checks the project itself, such as its region against storage.jurisdiction, shared data, the mail sender domain and credits; init runs offline without it."
48239
+ });
47159
48240
  var RepositoryInitError = class extends Error {
47160
48241
  code;
47161
48242
  constructor(code, message) {
@@ -47186,18 +48267,15 @@ async function initializeRepository(input) {
47186
48267
  const applicationDirectory = resolve5(root, selection.applicationRoot);
47187
48268
  const applicationFiles = applicationRelativeFiles(files, selection.applicationRoot);
47188
48269
  const packageJson = await readPackageJson(applicationDirectory);
47189
- const dependencies = dependencyNames(packageJson);
47190
- const existingConfigurationYaml = await readExistingConfiguration(root, files, selection.applicationRoot);
47191
- const existingConfiguration = existingConfigurationYaml === null ? null : parseOhmyhostConfigYaml(existingConfigurationYaml);
48270
+ const manifest2 = packageJson.manifest;
48271
+ const dependencies = declaredDependencyNames(manifest2);
48272
+ const existing2 = await readExistingConfiguration(root, files, selection.applicationRoot);
48273
+ const existingConfiguration = existing2?.configuration ?? null;
47192
48274
  const framework = detectRepositoryFramework(dependencies, applicationFiles, existingConfiguration);
47193
- const packageManagerAdmission = packageManagerAdmissionFor(packageJson, applicationFiles);
47194
- const packageManager = packageManagerInventory(packageManagerAdmission, packageJson, applicationFiles);
47195
- const frameworkConfigs = frameworkConfigurations(applicationFiles);
47196
- const compatibility = frameworkCompatibility(packageJson, framework);
47197
- const nextStandaloneOutput = await detectNextStandaloneOutput(applicationDirectory, framework, frameworkConfigs.next);
47198
- const tanStackRuntime = await detectTanStackRuntime(applicationDirectory, applicationFiles, framework, packageJson, frameworkConfigs.vite);
47199
- const project = projectName(input.project, packageJson);
47200
- const repositoryAdmissionBlockers = repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanStackRuntime, applicationFiles, packageManagerAdmission, frameworkConfigs, compatibility, existingConfiguration);
48275
+ const packageManagerAdmission = packageManagerAdmissionFor(manifest2, applicationFiles);
48276
+ const packageManager = packageManagerInventory(packageManagerAdmission, manifest2, applicationFiles);
48277
+ const frameworkConfig = await readFrameworkConfig(applicationDirectory, applicationFiles, framework);
48278
+ const project = projectName(input.project, manifest2);
47201
48279
  const supabaseMigrationCount = applicationFiles.filter((path2) => /^supabase\/migrations\/[^/]+\.sql$/u.test(path2)).length;
47202
48280
  const postgresMigrationRoot = existingConfiguration?.database?.migrations ?? "postgres/migrations";
47203
48281
  const postgresMigrationCount = applicationFiles.filter((path2) => path2.startsWith(`${postgresMigrationRoot}/`) && path2.endsWith(".sql")).length;
@@ -47245,8 +48323,8 @@ async function initializeRepository(input) {
47245
48323
  const targetBetterAuth = existingConfiguration?.auth?.provider === "better-auth";
47246
48324
  const targetMail = existingConfiguration?.mail?.enabled === true;
47247
48325
  const targetStorage = r2 || supabaseStorage;
47248
- const configuredCompanionRequired = existingConfiguration !== null && (existingConfiguration.database?.enabled === true || existingConfiguration.auth?.provider === "better-auth" || existingConfiguration.mail?.enabled === true || existingConfiguration.storage !== void 0 || (existingConfiguration.functions?.crons.length ?? 0) > 0);
47249
- const companionRequired = framework === "vite" && (existingConfiguration === null ? databaseProvider !== "none" || targetBetterAuth || targetMail || targetStorage || edgeFunctionNames.size > 0 || applicationFiles.includes("src/ohmyhost/companion.ts") : configuredCompanionRequired);
48326
+ const tanStackRuntime = framework !== "tanstack-start" ? null : targetStorage ? "edge" : proposedTanStackRuntime(frameworkConfig, dependencies);
48327
+ const companionRequired = framework === "vite" && (existingConfiguration === null ? databaseProvider !== "none" || targetBetterAuth || targetMail || targetStorage || edgeFunctionNames.size > 0 || applicationFiles.includes(VITE_COMPANION_ENTRY_PATH) : viteCompanionRequired(existingConfiguration));
47250
48328
  const generatedConfigurationYaml = renderConfiguration({
47251
48329
  project,
47252
48330
  framework,
@@ -47261,7 +48339,7 @@ async function initializeRepository(input) {
47261
48339
  tanStackRuntime,
47262
48340
  applicationRoot: selection.applicationRoot
47263
48341
  });
47264
- const configurationYaml = existingConfigurationYaml ?? generatedConfigurationYaml;
48342
+ const configurationYaml = existing2?.yaml ?? generatedConfigurationYaml;
47265
48343
  const capabilities = Object.freeze({
47266
48344
  database: selectedDatabaseProvider !== "none",
47267
48345
  auth: targetBetterAuth,
@@ -47270,16 +48348,19 @@ async function initializeRepository(input) {
47270
48348
  });
47271
48349
  const companion = companionRequired ? viteCompanionScaffold(applicationFiles, capabilities) : null;
47272
48350
  const companionSourcePresent = companion !== null && applicationFiles.includes(companion.sourceEntryPoint);
47273
- const migrationSqlAdmissionBlockers = await migrationSqlBlockers(applicationDirectory, applicationFiles, existingConfiguration?.database?.migrations, selectedDatabaseProvider);
47274
- const blockers = [
47275
- ...repositoryAdmissionBlockers,
47276
- ...await workerModuleBlockers(framework, applicationDirectory, applicationFiles),
47277
- ...await cronBlockers(framework, existingConfiguration, applicationDirectory, applicationFiles),
47278
- ...selectedDatabaseProvider === "none" ? [] : migrationFilenameBlockers(applicationFiles, existingConfiguration?.database?.migrations, selectedDatabaseProvider),
47279
- ...migrationSqlAdmissionBlockers,
47280
- ...await storageClientBlockers(existingConfiguration, applicationDirectory, applicationFiles),
47281
- ...await databaseContractBlockers(existingConfiguration, applicationDirectory, applicationFiles)
47282
- ].sort((left, right) => left.code.localeCompare(right.code));
48351
+ const { blockers, versions } = existing2 !== null && existing2.blocker !== null ? { blockers: [existing2.blocker], versions: [] } : await judgeSource({
48352
+ framework,
48353
+ config: existingConfiguration,
48354
+ applicationDirectory,
48355
+ files: applicationFiles,
48356
+ applicationRoot: selection.applicationRoot,
48357
+ provider: selectedDatabaseProvider,
48358
+ texts: sourceTexts(files, selection.applicationRoot, configurationYaml, [
48359
+ ["package.json", packageJson.text],
48360
+ ...lockfileTexts(await readResolvingLockfile(applicationDirectory, packageManagerAdmission)),
48361
+ ...frameworkConfig === null ? [] : [[frameworkConfig.path, frameworkConfig.text]]
48362
+ ])
48363
+ });
47283
48364
  const requirements = requirementsFor(inventory, companion !== null && !companionSourcePresent, mailDetected, targetBetterAuth);
47284
48365
  const configurationSha256 = createHash4("sha256").update(configurationYaml).digest("hex");
47285
48366
  if (!input.dryRun) {
@@ -47296,8 +48377,17 @@ async function initializeRepository(input) {
47296
48377
  applicationRoot: selection.applicationRoot,
47297
48378
  applicationRootSource: selection.source,
47298
48379
  inventory,
47299
- compatibility,
48380
+ compatibility: Object.freeze({
48381
+ frameworks: Object.freeze(versions.map((version2) => Object.freeze({
48382
+ package: version2.package,
48383
+ declared: version2.declared,
48384
+ resolved: version2.resolved,
48385
+ status: version2.status,
48386
+ minimum: version2.minimum
48387
+ })))
48388
+ }),
47300
48389
  blockers: Object.freeze(blockers),
48390
+ pending: pendingChecks(versions),
47301
48391
  requirements: Object.freeze(requirements),
47302
48392
  companion,
47303
48393
  worker: framework === "functions" ? Object.freeze({
@@ -47309,6 +48399,20 @@ async function initializeRepository(input) {
47309
48399
  runtime: Object.values(capabilities).some(Boolean) ? runtimeContract(capabilities) : null
47310
48400
  });
47311
48401
  }
48402
+ async function judgeSource(context) {
48403
+ const source = repositorySourceFindings(context.texts);
48404
+ return {
48405
+ blockers: [
48406
+ ...migrationFilenameBlockers(context, context.provider),
48407
+ ...source.findings.map((finding) => requiredCatalogBlocker(finding, context.applicationRoot)),
48408
+ ...await workerModuleBlockers(context),
48409
+ ...await databaseContractBlockers(context, context.provider),
48410
+ ...await storageClientBlockers(context),
48411
+ ...await migrationSqlBlockers(context, context.provider)
48412
+ ],
48413
+ versions: source.versions
48414
+ };
48415
+ }
47312
48416
  function detectRepositoryFramework(dependencies, files, existingConfiguration) {
47313
48417
  const detected = detectFramework(dependencies);
47314
48418
  if (existingConfiguration?.runtime.mode === "functions")
@@ -47317,41 +48421,47 @@ function detectRepositoryFramework(dependencies, files, existingConfiguration) {
47317
48421
  return "functions";
47318
48422
  return detected;
47319
48423
  }
47320
- async function cronBlockers(framework, config, applicationDirectory, files) {
47321
- if ((config?.functions?.crons.length ?? 0) === 0)
47322
- return [];
48424
+ function sourceTexts(files, applicationRoot, configurationYaml, applicationTexts) {
48425
+ const texts = new Map(files.map((path2) => [path2, null]));
48426
+ texts.set("ohmyhost.yaml", configurationYaml);
48427
+ for (const [path2, text] of applicationTexts)
48428
+ texts.set(repositoryPath(applicationRoot, path2), text);
48429
+ return texts;
48430
+ }
48431
+ function lockfileTexts(lockfile) {
48432
+ return lockfile === null ? [] : [[lockfile.name, lockfile.text]];
48433
+ }
48434
+ function repositoryPath(applicationRoot, path2) {
48435
+ return applicationRoot === "." ? path2 : `${applicationRoot}/${path2}`;
48436
+ }
48437
+ function catalogBlocker(diagnostic2, applicationRoot) {
48438
+ const rooted = rootFrameworkConversionDiagnostic(diagnostic2, applicationRoot);
48439
+ const message = frameworkConversionDetail(rooted);
48440
+ return rooted === null || message === null ? null : Object.freeze({ code: rooted.code, path: rooted.path, message });
48441
+ }
48442
+ function requiredCatalogBlocker(diagnostic2, applicationRoot) {
48443
+ const blocker = catalogBlocker(diagnostic2, applicationRoot);
48444
+ if (blocker === null)
48445
+ throw new TypeError("Repository init diagnostic is invalid");
48446
+ return blocker;
48447
+ }
48448
+ async function workerModuleBlockers(context) {
48449
+ const { config, framework, files } = context;
47323
48450
  const mode = config?.runtime.mode;
47324
- const runtimeSupported = mode === "edge" && (framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions";
47325
- if (!runtimeSupported) {
47326
- return [
47327
- {
47328
- code: "scheduled_functions_runtime_unsupported",
47329
- message: "functions.crons needs runtime.mode edge (Next.js, TanStack Start, or Vite with src/ohmyhost/companion.ts) or functions (src/ohmyhost/worker.ts) in ohmyhost.yaml. Change runtime.mode, or remove functions.crons."
47330
- }
47331
- ];
47332
- }
47333
- const modulePath = framework === "vite" ? "src/ohmyhost/companion.ts" : WORKER_MODULE_PATH;
47334
- const source = files.includes(modulePath) ? await readFile3(resolve5(applicationDirectory, modulePath), "utf8") : null;
47335
- if (framework !== "functions" && source !== null && !DEFAULT_EXPORT_PATTERN.test(source)) {
47336
- return [defaultExportBlocker(modulePath)];
47337
- }
47338
- if (source === null || !SCHEDULED_HANDLER_PATTERN.test(source)) {
47339
- return [
47340
- {
47341
- code: "scheduled_handler_missing",
47342
- message: `Keep a scheduled handler in the default export of ${modulePath} to run the declared crons.`
47343
- }
47344
- ];
47345
- }
47346
- return [];
48451
+ const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48452
+ const path2 = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
48453
+ if (framework !== "functions" && !scheduled || !files.includes(path2))
48454
+ return [];
48455
+ const source = await readFile3(resolve5(context.applicationDirectory, path2), "utf8");
48456
+ return workerModuleFindings({ path: path2, source, fetch: framework === "functions", scheduled }).map((finding) => requiredCatalogBlocker(finding, context.applicationRoot));
47347
48457
  }
47348
- async function storageClientBlockers(config, applicationDirectory, files) {
47349
- if (config?.storage === void 0)
48458
+ async function storageClientBlockers(context) {
48459
+ if (context.config?.storage === void 0)
47350
48460
  return [];
47351
- for (const path2 of files) {
48461
+ for (const path2 of context.files) {
47352
48462
  if (!SOURCE_MODULE_PATTERN.test(path2))
47353
48463
  continue;
47354
- const absolute = resolve5(applicationDirectory, path2);
48464
+ const absolute = resolve5(context.applicationDirectory, path2);
47355
48465
  const information = await stat2(absolute);
47356
48466
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
47357
48467
  continue;
@@ -47361,52 +48471,41 @@ async function storageClientBlockers(config, applicationDirectory, files) {
47361
48471
  return [
47362
48472
  {
47363
48473
  code: "storage_client_missing",
48474
+ path: null,
47364
48475
  message: 'ohmyhost.yaml declares storage, but no application source calls createPrivateStorageClient. Import it from "@ohmyhost/customer-runtime/storage" and build it in server code from OHMYHOST_STORAGE_GATEWAY, OHMYHOST_STORAGE_GATEWAY_URL, OHMYHOST_PROJECT_ID, OHMYHOST_ENVIRONMENT_ID and OHMYHOST_STORAGE_KEY, then reserve an upload, PUT the bytes to the returned signed URL, complete it, and serve reads with createSignedRead. There is no FILES bucket binding.'
47365
48476
  }
47366
48477
  ];
47367
48478
  }
47368
- async function databaseContractBlockers(config, applicationDirectory, files) {
47369
- if (config?.database === void 0)
48479
+ async function databaseContractBlockers(context, provider) {
48480
+ if (provider === "none")
47370
48481
  return [];
47371
48482
  const blockers = [];
47372
- for (const path2 of files) {
47373
- if (!SOURCE_MODULE_PATTERN.test(path2))
48483
+ for (const path2 of context.files) {
48484
+ if (!isDatabaseContractModule(path2))
47374
48485
  continue;
47375
- const absolute = resolve5(applicationDirectory, path2);
48486
+ const absolute = resolve5(context.applicationDirectory, path2);
47376
48487
  const information = await stat2(absolute);
47377
48488
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
47378
48489
  continue;
47379
- const source = (await readFile3(absolute, "utf8")).replaceAll(INERT_SOURCE_PATTERN, " ");
47380
- if (DATABASE_PRIVATE_BINDING_PATTERN.test(source)) {
47381
- blockers.push({
47382
- code: "database_binding_private",
47383
- message: `${path2} reaches for a managed database connection string. HYPERDRIVE is retired; DATABASE_URL and postgres:// URLs never reach a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database".`
47384
- });
47385
- }
47386
- if (DATABASE_SOCKET_DRIVER_PATTERN.test(source)) {
47387
- blockers.push({
47388
- code: "database_driver_unsupported",
47389
- message: `${path2} imports a PostgreSQL socket driver. Outbound connect() is disabled for customer Workers, so the driver can never open a connection. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database".`
47390
- });
47391
- }
48490
+ for (const code of databaseContractCodes(await readFile3(absolute, "utf8")))
48491
+ blockers.push(catalogBlocker({ code, path: path2 }, context.applicationRoot) ?? Object.freeze({
48492
+ code,
48493
+ path: repositoryPath(context.applicationRoot, path2),
48494
+ message: UNNAMED_MODULE_MESSAGES[code]
48495
+ }));
47392
48496
  }
47393
48497
  return blockers;
47394
48498
  }
47395
- async function workerModuleBlockers(framework, applicationDirectory, files) {
47396
- if (framework !== "functions" || !files.includes(WORKER_MODULE_PATH))
47397
- return [];
47398
- const source = await readFile3(resolve5(applicationDirectory, WORKER_MODULE_PATH), "utf8");
47399
- return DEFAULT_EXPORT_PATTERN.test(source) ? [] : [defaultExportBlocker(WORKER_MODULE_PATH)];
47400
- }
47401
- function migrationFilenameBlockers(files, configuredPath, provider) {
48499
+ function migrationFilenameBlockers(context, provider) {
47402
48500
  if (provider !== "postgresql")
47403
48501
  return [];
47404
- const root = configuredPath ?? "postgres/migrations";
48502
+ const root = context.config?.database?.migrations ?? "postgres/migrations";
47405
48503
  const prefix = `${root}/`;
47406
- const invalid6 = files.filter((path2) => path2.startsWith(prefix)).map((path2) => path2.slice(prefix.length)).find((path2) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path2));
48504
+ const invalid6 = context.files.filter((path2) => path2.startsWith(prefix)).map((path2) => path2.slice(prefix.length)).find((path2) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path2));
47407
48505
  return invalid6 === void 0 ? [] : [
47408
48506
  {
47409
48507
  code: "migration_filename_noncanonical",
48508
+ path: repositoryPath(context.applicationRoot, `${prefix}${invalid6}`),
47410
48509
  message: `Migration file '${invalid6}' in ${root} is not named YYYYMMDDHHMMSS_name.sql (14 digits, an underscore, then lowercase letters, digits, _ or -). Every file in that directory must match, README and .gitkeep included: rename or move it, keeping the run order.`
47411
48510
  }
47412
48511
  ];
@@ -47417,18 +48516,18 @@ var MIGRATION_REJECTION = {
47417
48516
  unsupported: "It has a statement other than CREATE TABLE, TYPE, DOMAIN, SEQUENCE, VIEW, MATERIALIZED VIEW, TRIGGER, INDEX or FUNCTION, ALTER TABLE ... ADD, COMMENT ON, CREATE SCHEMA IF NOT EXISTS auth|extensions|private or CREATE EXTENSION IF NOT EXISTS pgcrypto|btree_gist|unaccent; or a function not in LANGUAGE sql or plpgsql, SECURITY DEFINER without SET search_path, or the word ohmyhost, comments included.",
47418
48517
  invalid: "It is empty, not UTF-8 with LF line endings, has a BOM or NUL byte or an unterminated quote or comment, or exceeds 256 KiB (at most 128 files and 2 MiB in total)."
47419
48518
  };
47420
- async function migrationSqlBlockers(applicationDirectory, files, configuredPath, provider) {
48519
+ async function migrationSqlBlockers(context, provider) {
47421
48520
  if (provider !== "postgresql")
47422
48521
  return [];
47423
- const root = configuredPath ?? "postgres/migrations";
48522
+ const root = context.config?.database?.migrations ?? "postgres/migrations";
47424
48523
  const prefix = `${root}/`;
47425
- const migrationPaths = files.filter((path2) => path2.startsWith(prefix) && path2.endsWith(".sql")).map((path2) => path2.slice(prefix.length));
48524
+ const migrationPaths = context.files.filter((path2) => path2.startsWith(prefix) && path2.endsWith(".sql")).map((path2) => path2.slice(prefix.length));
47426
48525
  if (migrationPaths.some((path2) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path2))) {
47427
48526
  return [];
47428
48527
  }
47429
48528
  const migrations = await Promise.all(migrationPaths.map(async (path2) => Object.freeze({
47430
48529
  path: path2,
47431
- body: new Uint8Array(await readFile3(resolve5(applicationDirectory, root, path2)))
48530
+ body: new Uint8Array(await readFile3(resolve5(context.applicationDirectory, root, path2)))
47432
48531
  })));
47433
48532
  const blockers = [];
47434
48533
  for (const migration of migrations) {
@@ -47439,6 +48538,7 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
47439
48538
  throw error;
47440
48539
  blockers.push({
47441
48540
  code: "migration_sql_not_admitted",
48541
+ path: repositoryPath(context.applicationRoot, `${prefix}${migration.path}`),
47442
48542
  message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]} Migrations are expand-only. Fix the file before its first deployment; never edit a migration a deployment already applied.`
47443
48543
  });
47444
48544
  }
@@ -47454,11 +48554,22 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
47454
48554
  return [
47455
48555
  {
47456
48556
  code: "migration_sql_not_admitted",
48557
+ path: null,
47457
48558
  message: `The migration set is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]}`
47458
48559
  }
47459
48560
  ];
47460
48561
  }
47461
48562
  }
48563
+ function pendingChecks(versions) {
48564
+ const codes = [
48565
+ ...versions.some(({ status }) => status === "pending") ? ["installed_version"] : [],
48566
+ "frozen_install",
48567
+ "framework_build",
48568
+ "reachable_runtime_sources",
48569
+ "project_state"
48570
+ ];
48571
+ return Object.freeze(codes.map((code) => Object.freeze({ code, message: PENDING_CHECKS[code] })));
48572
+ }
47462
48573
  async function readExistingConfiguration(repositoryRoot, repositoryFiles, applicationRoot) {
47463
48574
  if (!repositoryFiles.includes("ohmyhost.yaml"))
47464
48575
  return null;
@@ -47467,17 +48578,30 @@ async function readExistingConfiguration(repositoryRoot, repositoryFiles, applic
47467
48578
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
47468
48579
  throw new RepositoryInitError("repository_configuration_invalid", "ohmyhost.yaml must be one regular file of at most 64 KiB");
47469
48580
  }
47470
- const configuration = await readFile3(configurationPath, "utf8");
48581
+ const bytes = await readFile3(configurationPath);
48582
+ const text = utf8(bytes);
48583
+ const yaml = text ?? new TextDecoder().decode(bytes);
47471
48584
  let parsed;
47472
48585
  try {
47473
- parsed = parseOhmyhostConfigYaml(configuration);
48586
+ if (text === null)
48587
+ throw new TypeError("ohmyhost.yaml is not valid UTF-8");
48588
+ parsed = parseOhmyhostConfigYaml(text);
47474
48589
  } catch (error) {
47475
- throw new RepositoryInitError("repository_configuration_invalid", error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid");
48590
+ const reason = (error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid").replace(/\.$/u, "");
48591
+ return Object.freeze({
48592
+ yaml,
48593
+ configuration: null,
48594
+ blocker: Object.freeze({
48595
+ code: "repository_configuration_invalid",
48596
+ path: "ohmyhost.yaml",
48597
+ message: `${reason}. Fix that in ohmyhost.yaml and run init again; init never rewrites an existing ohmyhost.yaml, so do not delete it.`
48598
+ })
48599
+ });
47476
48600
  }
47477
48601
  if (parsed.applicationRoot !== applicationRoot) {
47478
48602
  throw new RepositoryInitError("repository_configuration_root_mismatch", `ohmyhost.yaml sets applicationRoot to '${parsed.applicationRoot}', but init selected '${applicationRoot}'. Run 'ohmyhost init --root ${parsed.applicationRoot} --dry-run --json', or correct applicationRoot.`);
47479
48603
  }
47480
- return configuration;
48604
+ return Object.freeze({ yaml, configuration: parsed, blocker: null });
47481
48605
  }
47482
48606
  var APPLICATION_LOCKFILES = Object.freeze([
47483
48607
  "bun.lock",
@@ -47555,7 +48679,7 @@ async function isApplicationCandidate(repositoryRoot, repositoryFiles, applicati
47555
48679
  const packageJson = await tryReadPackageJson(applicationDirectory);
47556
48680
  if (packageJson === null)
47557
48681
  return false;
47558
- const framework = detectFramework(dependencyNames(packageJson));
48682
+ const framework = detectFramework(declaredDependencyNames(packageJson.manifest));
47559
48683
  return framework !== "unknown" || files.includes(WORKER_MODULE_PATH);
47560
48684
  }
47561
48685
  async function hasDirectApplicationShape(repositoryRoot, repositoryFiles, applicationRoot) {
@@ -47567,10 +48691,10 @@ async function hasDirectApplicationShape(repositoryRoot, repositoryFiles, applic
47567
48691
  const packageJson = await tryReadPackageJson(applicationDirectory);
47568
48692
  if (packageJson === null)
47569
48693
  return false;
47570
- const framework = detectFramework(dependencyNames(packageJson));
48694
+ const framework = detectFramework(declaredDependencyNames(packageJson.manifest));
47571
48695
  if (framework === "unknown")
47572
48696
  return files.includes(WORKER_MODULE_PATH);
47573
- return hasDirectSafeBuildCommand(packageJson, framework);
48697
+ return hasDirectSafeBuildCommand(packageJson.manifest, framework);
47574
48698
  }
47575
48699
  async function isRealDirectory(path2) {
47576
48700
  try {
@@ -47593,18 +48717,12 @@ async function tryReadPackageJson(root) {
47593
48717
  return null;
47594
48718
  }
47595
48719
  }
47596
- function hasDirectSafeBuildCommand(packageJson, framework) {
47597
- const scripts = packageJson["scripts"];
47598
- if (!isRecord4(scripts) || typeof scripts["build"] !== "string")
48720
+ function hasDirectSafeBuildCommand(manifest2, framework) {
48721
+ const scripts = manifest2["scripts"];
48722
+ if (!isRecord5(scripts) || typeof scripts["build"] !== "string")
47599
48723
  return false;
47600
48724
  return (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
47601
48725
  }
47602
- function buildScriptMessage(framework, command) {
47603
- const quoted = command.slice(0, 200);
47604
- if (framework === "nextjs")
47605
- return `package.json scripts.build must be exactly "next build" or "next build --webpack", not "${quoted}". Move other steps into their own scripts, then commit and push.`;
47606
- return `package.json scripts.build must be "vite build", optionally joined with && to exactly one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage before or after it, not "${quoted}". Move other steps into their own scripts, then commit and push.`;
47607
- }
47608
48726
  async function assertRepositoryRoot(root) {
47609
48727
  let information;
47610
48728
  try {
@@ -47676,105 +48794,95 @@ async function readPackageJson(root) {
47676
48794
  if (!information.isFile() || information.size > MAXIMUM_PACKAGE_BYTES) {
47677
48795
  throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
47678
48796
  }
48797
+ const text = utf8(await readFile3(path2));
48798
+ const manifest2 = text === null ? null : parsePackageManifest(text);
48799
+ if (text === null || manifest2 === null) {
48800
+ throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
48801
+ }
48802
+ return Object.freeze({ text, manifest: manifest2 });
48803
+ }
48804
+ function utf8(bytes) {
47679
48805
  try {
47680
- const value = JSON.parse(await readFile3(path2, "utf8"));
47681
- if (!isRecord4(value))
47682
- throw new TypeError();
47683
- return value;
48806
+ return UTF8.decode(bytes);
47684
48807
  } catch {
47685
- throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
48808
+ return null;
47686
48809
  }
47687
48810
  }
47688
- function dependencyNames(packageJson) {
47689
- const names = /* @__PURE__ */ new Set();
47690
- for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
47691
- const value = packageJson[field];
47692
- if (!isRecord4(value))
47693
- continue;
47694
- for (const [name, version2] of Object.entries(value)) {
47695
- if (typeof version2 === "string" && version2.length > 0)
47696
- names.add(name);
47697
- }
48811
+ async function readLeadingBytes(path2) {
48812
+ const handle = await open4(path2, "r");
48813
+ try {
48814
+ const { size } = await handle.stat();
48815
+ const bytes = new Uint8Array(Math.min(size, MAXIMUM_INSPECTED_FILE_BYTES));
48816
+ const { bytesRead } = await handle.read(bytes, 0, bytes.byteLength, 0);
48817
+ return Object.freeze({ bytes: bytes.subarray(0, bytesRead), complete: bytesRead === size });
48818
+ } finally {
48819
+ await handle.close();
47698
48820
  }
47699
- return names;
48821
+ }
48822
+ async function readResolvingLockfile(applicationDirectory, admission) {
48823
+ if (admission instanceof FrameworkAdmissionError || admission.lockfile !== "package-lock.json" && admission.lockfile !== "pnpm-lock.yaml")
48824
+ return null;
48825
+ const { bytes, complete } = await readLeadingBytes(resolve5(applicationDirectory, admission.lockfile));
48826
+ const text = complete ? utf8(bytes) : null;
48827
+ return text === null ? null : Object.freeze({ name: admission.lockfile, text });
48828
+ }
48829
+ async function readFrameworkConfig(applicationDirectory, files, framework) {
48830
+ if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48831
+ return null;
48832
+ let path2;
48833
+ try {
48834
+ path2 = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
48835
+ } catch (error) {
48836
+ if (error instanceof FrameworkAdmissionError)
48837
+ return null;
48838
+ throw error;
48839
+ }
48840
+ if (path2 === null)
48841
+ return null;
48842
+ const { bytes, complete } = await readLeadingBytes(resolve5(applicationDirectory, path2));
48843
+ return Object.freeze({ path: path2, text: complete ? utf8(bytes) : new TextDecoder().decode(bytes) });
48844
+ }
48845
+ function proposedTanStackRuntime(config, dependencies) {
48846
+ const followed = config === null || config.text === null ? null : tanStackViteConfigRuntime(config.text);
48847
+ return followed ?? (dependencies.has("@cloudflare/vite-plugin") ? "edge" : "static");
47700
48848
  }
47701
48849
  function detectFramework(dependencies) {
47702
48850
  const classification = classifyFrameworkFamily(dependencies);
47703
48851
  return classification === "ambiguous" ? "unknown" : classification;
47704
48852
  }
47705
- function packageManagerAdmissionFor(packageJson, files) {
48853
+ function packageManagerAdmissionFor(manifest2, files) {
47706
48854
  try {
47707
- return resolvePackageManager({
47708
- packageManager: typeof packageJson["packageManager"] === "string" ? packageJson["packageManager"] : void 0,
47709
- files
47710
- });
48855
+ return resolvePackageManager({ packageManager: packageManagerDeclaration(manifest2), files });
47711
48856
  } catch (error) {
47712
48857
  if (error instanceof FrameworkAdmissionError)
47713
48858
  return error;
47714
48859
  throw error;
47715
48860
  }
47716
48861
  }
47717
- function packageManagerInventory(admission, packageJson, files) {
48862
+ function packageManagerInventory(admission, manifest2, files) {
47718
48863
  if (!(admission instanceof FrameworkAdmissionError)) {
47719
48864
  return {
47720
48865
  name: admission.manager,
47721
48866
  version: admission.version,
47722
- lockfile: admission.lockfile
48867
+ lockfile: admission.lockfile,
48868
+ installCommand: admission.installCommand,
48869
+ buildCommand: admission.buildCommand
47723
48870
  };
47724
48871
  }
47725
- const declaration = packageJson["packageManager"];
47726
- const match = typeof declaration === "string" ? /^(bun|npm|pnpm|yarn)@([^\s]+)$/u.exec(declaration) : null;
48872
+ const declaration = packageManagerDeclaration(manifest2);
48873
+ const match = declaration === void 0 ? null : /^(bun|npm|pnpm|yarn)@([^\s]+)$/u.exec(declaration);
47727
48874
  const lockfile = APPLICATION_LOCKFILES.find((candidate) => files.includes(candidate)) ?? null;
47728
48875
  const inferredName = lockfile === "package-lock.json" ? "npm" : lockfile === "pnpm-lock.yaml" ? "pnpm" : lockfile === "yarn.lock" ? "yarn" : lockfile === "bun.lock" || lockfile === "bun.lockb" ? "bun" : "unknown";
47729
48876
  return {
47730
48877
  name: match === null ? inferredName : match[1],
47731
48878
  version: match?.[2] ?? null,
47732
- lockfile
47733
- };
47734
- }
47735
- function frameworkConfigurations(files) {
47736
- const ambiguous = [];
47737
- const resolve7 = (basename2) => {
47738
- try {
47739
- return resolveFrameworkConfig(basename2, files);
47740
- } catch (error) {
47741
- if (!(error instanceof FrameworkAdmissionError) || error.reason !== "ambiguous_config") {
47742
- throw error;
47743
- }
47744
- ambiguous.push(basename2.slice(0, -".config".length));
47745
- return null;
47746
- }
48879
+ lockfile,
48880
+ installCommand: null,
48881
+ buildCommand: null
47747
48882
  };
47748
- return Object.freeze({
47749
- next: resolve7("next.config"),
47750
- openNext: resolve7("open-next.config"),
47751
- vite: resolve7("vite.config"),
47752
- ambiguous: Object.freeze(ambiguous)
47753
- });
47754
48883
  }
47755
- var FRAMEWORK_PACKAGE = {
47756
- nextjs: "next",
47757
- "tanstack-start": "@tanstack/react-start",
47758
- vite: "vite"
47759
- };
47760
- function frameworkCompatibility(packageJson, framework) {
47761
- const targets = framework === "tanstack-start" ? [
47762
- { framework: "tanstack-start", packageName: FRAMEWORK_PACKAGE["tanstack-start"] },
47763
- { framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }
47764
- ] : framework === "nextjs" ? [{ framework: "nextjs", packageName: FRAMEWORK_PACKAGE.nextjs }] : framework === "vite" ? [{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }] : [];
47765
- const frameworks = targets.map(({ framework: targetFramework, packageName }) => {
47766
- const version2 = dependencyVersion(packageJson, packageName) ?? "";
47767
- return Object.freeze({
47768
- framework: targetFramework,
47769
- version: version2,
47770
- ...classifyFrameworkVersion(targetFramework, version2)
47771
- });
47772
- });
47773
- const classification = frameworks.some((entry) => entry.classification === "unsupported") ? "unsupported" : frameworks.some((entry) => entry.classification === "experimental") ? "experimental" : frameworks.length > 0 || framework === "functions" ? "verified" : "unsupported";
47774
- return Object.freeze({ classification, frameworks: Object.freeze(frameworks) });
47775
- }
47776
- function projectName(explicit, packageJson) {
47777
- const packageName = packageJson["name"];
48884
+ function projectName(explicit, manifest2) {
48885
+ const packageName = manifest2["name"];
47778
48886
  const inferred = typeof packageName === "string" ? (packageName.split("/").at(-1) ?? "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") : packageName;
47779
48887
  const value = explicit ?? inferred;
47780
48888
  if (typeof value !== "string" || value.length > 63 || !PROJECT_PATTERN.test(value)) {
@@ -47782,117 +48890,6 @@ function projectName(explicit, packageJson) {
47782
48890
  }
47783
48891
  return value;
47784
48892
  }
47785
- function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanStackRuntime, files, packageManagerAdmission, frameworkConfigs, compatibility, existingConfiguration) {
47786
- const blockers = [];
47787
- if (framework !== "nextjs" && existingConfiguration !== null && "command" in existingConfiguration.build && existingConfiguration.build.ssg_cache_max_mib !== void 0) {
47788
- blockers.push({
47789
- code: "framework_unsupported",
47790
- message: "build.ssg_cache_max_mib is supported only by Next.js; remove it from this application's configuration."
47791
- });
47792
- }
47793
- if (framework === "unknown") {
47794
- blockers.push({
47795
- code: "framework_unsupported",
47796
- message: "Declare exactly one supported Vite, TanStack Start, or Next.js framework, or add src/ohmyhost/worker.ts for the functions runtime."
47797
- });
47798
- }
47799
- if (framework === "functions" && !files.includes(WORKER_MODULE_PATH)) {
47800
- blockers.push({
47801
- code: "worker_module_missing",
47802
- message: `The functions runtime needs ${WORKER_MODULE_PATH} exporting fetch and optional scheduled handlers.`
47803
- });
47804
- }
47805
- if (tanStackRuntime === "ambiguous") {
47806
- blockers.push({
47807
- code: "framework_ambiguous",
47808
- message: 'TanStack Start needs one vite.config.js, .mjs or .ts that selects a mode. Static: tanstackStart({ prerender: { enabled: true } }) from "@tanstack/react-start/plugin/vite" and no "@cloudflare/vite-plugin". Server: cloudflare({ viteEnvironment: { name: "ssr" } }) from "@cloudflare/vite-plugin" listed before tanstackStart() in plugins, with @cloudflare/vite-plugin, @tanstack/react-router, @tanstack/react-start, react, react-dom, @vitejs/plugin-react and vite in package.json.'
47809
- });
47810
- }
47811
- const relevantAmbiguousConfigs = frameworkConfigs.ambiguous.filter((config) => framework === "nextjs" ? config === "next" || config === "open-next" : config === "vite");
47812
- for (const config of relevantAmbiguousConfigs) {
47813
- blockers.push({
47814
- code: "framework_ambiguous",
47815
- message: `Keep exactly one supported ${config}.config.js, ${config}.config.mjs, or ${config}.config.ts file.`
47816
- });
47817
- }
47818
- if (packageManagerAdmission instanceof FrameworkAdmissionError && packageManagerAdmission.reason === "ambiguous_lockfile") {
47819
- blockers.push({
47820
- code: "package_manager_ambiguous",
47821
- message: "Keep exactly one supported lockfile in the application root."
47822
- });
47823
- } else if (packageManagerAdmission instanceof FrameworkAdmissionError) {
47824
- blockers.push({
47825
- code: "package_manager_unpinned",
47826
- message: packageManagerAdmission.reason === "package_manager_version_unsupported" ? `This build image runs Bun ${PINNED_BUN_VERSION}; pin packageManager to bun@${PINNED_BUN_VERSION} and commit its matching lockfile. Repeating the unchanged version cannot work.` : "Declare one exact npm, pnpm, Yarn, or Bun packageManager version and commit its matching lockfile."
47827
- });
47828
- } else if (existingConfiguration !== null && (existingConfiguration.build.install !== packageManagerAdmission.installCommand || "command" in existingConfiguration.build && existingConfiguration.build.command !== packageManagerAdmission.buildCommand)) {
47829
- blockers.push({
47830
- code: "build_command_unsupported",
47831
- message: `ohmyhost.yaml build.install must be exactly "${packageManagerAdmission.installCommand}"${"command" in existingConfiguration.build ? ` and build.command "${packageManagerAdmission.buildCommand}"` : ""} for the packageManager in package.json. Set them, then commit and push.`
47832
- });
47833
- }
47834
- if (compatibility.classification === "unsupported" && compatibility.frameworks.length > 0) {
47835
- const unsupported = compatibility.frameworks.filter((entry) => entry.classification === "unsupported").map((entry) => `${FRAMEWORK_PACKAGE[entry.framework]}@${entry.version || "missing"}`).join(", ");
47836
- blockers.push({
47837
- code: "framework_version_unsupported",
47838
- message: `${unsupported} is outside the versions ohmyho.st builds: next 15.5.26+ (15.5.x) or 16.3.8, vite 5.4.0\u20138.2.2, @tanstack/react-start 1.168.26\u20131.168.49, as an exact version or a ^ or ~ range whose base version is inside. Pin a version in that range in package.json, update the lockfile, commit and push.`
47839
- });
47840
- }
47841
- const scripts = packageJson["scripts"];
47842
- const scriptRecord = isRecord4(scripts) ? scripts : {};
47843
- if (framework === "functions") {
47844
- } else if (!isRecord4(scripts) || typeof scripts["build"] !== "string" || scripts["build"].length === 0) {
47845
- blockers.push({ code: "build_script_missing", message: "Declare a non-empty build script." });
47846
- } else if (!hasDirectSafeBuildCommand(packageJson, framework)) {
47847
- blockers.push({
47848
- code: "build_command_unsupported",
47849
- message: buildScriptMessage(framework, scripts["build"])
47850
- });
47851
- }
47852
- const betterAuthVersion = dependencyVersion(packageJson, "better-auth");
47853
- const managedAuthIssue = managedCustomerAuthAdmissionIssue(existingConfiguration);
47854
- if (managedAuthIssue !== null)
47855
- blockers.push(managedAuthIssue);
47856
- if (existingConfiguration?.auth?.provider === "better-auth" && betterAuthVersion !== null && betterAuthVersion !== SUPPORTED_BETTER_AUTH_VERSION) {
47857
- blockers.push({
47858
- code: "better_auth_version_unsupported",
47859
- message: "Pin better-auth to exactly 1.7.1 before enabling portable customer auth."
47860
- });
47861
- }
47862
- if (framework === "nextjs") {
47863
- const dependencies = isRecord4(packageJson["dependencies"]) ? packageJson["dependencies"] : {};
47864
- const nextBuild = scriptRecord["build"] === "next build" || scriptRecord["build"] === "next build --webpack";
47865
- const nextPackageConfigured = nextBuild && compatibility.frameworks[0]?.classification !== "unsupported" && exactVersion(dependencies["react"]) && exactVersion(dependencies["react-dom"]) && frameworkConfigs.next !== null;
47866
- const nextConfigured = nextPackageConfigured && frameworkConfigs.ambiguous.every((config) => config !== "open-next");
47867
- if (nextStandaloneOutput) {
47868
- blockers.push({
47869
- code: "workers_runtime_incompatible",
47870
- message: "Next.js output: standalone does not function on Workers. Remove the output setting from the Next.js config, commit the change, then create a new plan."
47871
- });
47872
- }
47873
- if (!nextConfigured) {
47874
- blockers.push({
47875
- code: "next_adapter_unconfigured",
47876
- message: 'Next.js needs scripts.build exactly "next build" or "next build --webpack", react and react-dom at exact versions such as "19.1.0" (no ^ or ~), next within 15.5.26+ (15.5.x) or 16.3.8, and exactly one next.config.js, .mjs or .ts, with at most one open-next.config file. ohmyho.st adds the OpenNext build itself; do not make it the build script.'
47877
- });
47878
- }
47879
- }
47880
- return blockers.sort((left, right) => left.code.localeCompare(right.code));
47881
- }
47882
- function exactVersion(value) {
47883
- return typeof value === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value);
47884
- }
47885
- function dependencyVersion(packageJson, name) {
47886
- for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
47887
- const dependencies = packageJson[field];
47888
- if (!isRecord4(dependencies))
47889
- continue;
47890
- const version2 = dependencies[name];
47891
- if (typeof version2 === "string" && version2.length > 0)
47892
- return version2;
47893
- }
47894
- return null;
47895
- }
47896
48893
  function renderConfiguration(input) {
47897
48894
  const descriptor = packageManagerDescriptorFromInventory(input.packageManager);
47898
48895
  const install = descriptor?.installCommand ?? "pnpm install --frozen-lockfile --ignore-scripts";
@@ -47949,66 +48946,6 @@ function packageManagerDescriptorFromInventory(packageManager) {
47949
48946
  return null;
47950
48947
  }
47951
48948
  }
47952
- async function detectNextStandaloneOutput(root, framework, nextConfigPath) {
47953
- if (framework !== "nextjs" || nextConfigPath === null)
47954
- return false;
47955
- const path2 = resolve5(root, nextConfigPath);
47956
- const information = await stat2(path2);
47957
- if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES)
47958
- return false;
47959
- return declaresStandaloneOutput(await readFile3(path2, "utf8"));
47960
- }
47961
- function declaresStandaloneOutput(source) {
47962
- const tokens = sourceTokens(source);
47963
- return tokens.some((token, index) => token === "output" && tokens[index + 1] === ":" && tokens[index + 2] === "standalone");
47964
- }
47965
- function sourceTokens(source) {
47966
- const tokens = [];
47967
- let index = 0;
47968
- while (index < source.length) {
47969
- const character = source[index];
47970
- const next = source[index + 1];
47971
- if (character === "/" && next === "/") {
47972
- const end = source.indexOf("\n", index + 2);
47973
- index = end === -1 ? source.length : end + 1;
47974
- continue;
47975
- }
47976
- if (character === "/" && next === "*") {
47977
- const end = source.indexOf("*/", index + 2);
47978
- if (end === -1)
47979
- return Object.freeze([]);
47980
- index = end + 2;
47981
- continue;
47982
- }
47983
- if (character === "'" || character === '"' || character === "`") {
47984
- let value = "";
47985
- index += 1;
47986
- while (index < source.length && source[index] !== character) {
47987
- if (source[index] === "\\" && source[index + 1] !== void 0)
47988
- index += 1;
47989
- value += source[index] ?? "";
47990
- index += 1;
47991
- }
47992
- if (source[index] !== character)
47993
- return Object.freeze([]);
47994
- tokens.push(value);
47995
- index += 1;
47996
- continue;
47997
- }
47998
- if (character !== void 0 && /[A-Za-z_$]/u.test(character)) {
47999
- const start = index;
48000
- index += 1;
48001
- while (/[A-Za-z0-9_$]/u.test(source[index] ?? ""))
48002
- index += 1;
48003
- tokens.push(source.slice(start, index));
48004
- continue;
48005
- }
48006
- if (character !== void 0 && "{}[]():,.".includes(character))
48007
- tokens.push(character);
48008
- index += 1;
48009
- }
48010
- return Object.freeze(tokens);
48011
- }
48012
48949
  function databaseMigrationPath(provider) {
48013
48950
  if (provider === "supabase")
48014
48951
  return "postgres/migrations";
@@ -48016,99 +48953,6 @@ function databaseMigrationPath(provider) {
48016
48953
  return "postgres/migrations";
48017
48954
  return null;
48018
48955
  }
48019
- async function detectTanStackRuntime(root, files, framework, packageJson, viteConfigPath) {
48020
- if (framework !== "tanstack-start")
48021
- return null;
48022
- if (viteConfigPath === null)
48023
- return "ambiguous";
48024
- const path2 = resolve5(root, viteConfigPath);
48025
- const information = await stat2(path2);
48026
- if (!information.isFile() || information.size > MAXIMUM_PACKAGE_BYTES)
48027
- return "ambiguous";
48028
- const source = await readFile3(path2, "utf8");
48029
- const tokens = sourceTokens(source);
48030
- if (isCompleteTanStackStatic(tokens))
48031
- return "static";
48032
- if (isCompleteTanStackEdgePackage(packageJson) && isCompleteTanStackEdgeConfig(tokens)) {
48033
- return "edge";
48034
- }
48035
- return "ambiguous";
48036
- }
48037
- function isCompleteTanStackStatic(tokens) {
48038
- return hasNamedSourceImport(tokens, "tanstackStart", "@tanstack/react-start/plugin/vite") && !hasSourceModuleImport(tokens, "@cloudflare/vite-plugin") && hasNestedObjectValue(tokens, "tanstackStart", "prerender", "enabled", "true");
48039
- }
48040
- function isCompleteTanStackEdgePackage(packageJson) {
48041
- return dependencyVersion(packageJson, "@cloudflare/vite-plugin") !== null && ["@tanstack/react-router", "@tanstack/react-start", "react", "react-dom"].every((name) => dependencyVersion(packageJson, name) !== null) && ["@vitejs/plugin-react", "vite"].every((name) => dependencyVersion(packageJson, name) !== null);
48042
- }
48043
- function isCompleteTanStackEdgeConfig(tokens) {
48044
- const cloudflareCall = sourceCallIndex(tokens, "cloudflare");
48045
- const tanStackCall = sourceCallIndex(tokens, "tanstackStart");
48046
- return hasNamedSourceImport(tokens, "cloudflare", "@cloudflare/vite-plugin") && hasNamedSourceImport(tokens, "tanstackStart", "@tanstack/react-start/plugin/vite") && cloudflareCall !== -1 && tanStackCall !== -1 && cloudflareCall < tanStackCall && hasNestedObjectValue(tokens, "cloudflare", "viteEnvironment", "name", "ssr");
48047
- }
48048
- function hasNamedSourceImport(tokens, name, moduleName) {
48049
- for (let index = 0; index < tokens.length; index += 1) {
48050
- if (tokens[index] !== "import")
48051
- continue;
48052
- const fromIndex = tokens.findIndex((token, candidate) => candidate > index && token === "from");
48053
- if (fromIndex === -1)
48054
- return false;
48055
- if (tokens[fromIndex + 1] === moduleName && tokens.slice(index + 1, fromIndex).includes(name)) {
48056
- return true;
48057
- }
48058
- index = fromIndex;
48059
- }
48060
- return false;
48061
- }
48062
- function hasSourceModuleImport(tokens, moduleName) {
48063
- return tokens.some((token, index) => token === "from" && tokens[index + 1] === moduleName);
48064
- }
48065
- function sourceCallIndex(tokens, name) {
48066
- return tokens.findIndex((token, index) => token === name && tokens[index + 1] === "(");
48067
- }
48068
- function hasNestedObjectValue(tokens, call, outerProperty, innerProperty, expectedValue) {
48069
- const callIndex = sourceCallIndex(tokens, call);
48070
- if (callIndex === -1 || tokens[callIndex + 2] !== "{")
48071
- return false;
48072
- const callEnd = matchingSourceDelimiter(tokens, callIndex + 2, "{", "}");
48073
- const outer = directSourceObjectProperty(tokens, callIndex + 2, callEnd, outerProperty);
48074
- if (outer === null || outer.value !== "object")
48075
- return false;
48076
- return directSourceObjectProperty(tokens, outer.start, outer.end, innerProperty)?.value === expectedValue;
48077
- }
48078
- function directSourceObjectProperty(tokens, start, end, name) {
48079
- let depth = 0;
48080
- for (let index = start + 1; index < end; index += 1) {
48081
- const value = tokens[index];
48082
- if (value === "{" || value === "[" || value === "(")
48083
- depth += 1;
48084
- if (value === "}" || value === "]" || value === ")")
48085
- depth -= 1;
48086
- if (depth !== 0 || value !== name || tokens[index + 1] !== ":")
48087
- continue;
48088
- if (tokens[index + 2] === "{") {
48089
- const candidateEnd = matchingSourceDelimiter(tokens, index + 2, "{", "}");
48090
- return Object.freeze({ start: index + 2, end: candidateEnd, value: "object" });
48091
- }
48092
- return Object.freeze({
48093
- start: index + 2,
48094
- end: index + 2,
48095
- value: tokens[index + 2] ?? ""
48096
- });
48097
- }
48098
- return null;
48099
- }
48100
- function matchingSourceDelimiter(tokens, start, open5, close) {
48101
- let depth = 0;
48102
- for (let index = start; index < tokens.length; index += 1) {
48103
- if (tokens[index] === open5)
48104
- depth += 1;
48105
- if (tokens[index] === close)
48106
- depth -= 1;
48107
- if (depth === 0)
48108
- return index;
48109
- }
48110
- return -1;
48111
- }
48112
48956
  function requirementsFor(inventory, viteCompanion, mailConversionRequired, managedAuthSelected) {
48113
48957
  const requirements = /* @__PURE__ */ new Set();
48114
48958
  if (inventory.framework === "nextjs")
@@ -48149,7 +48993,7 @@ function runtimeContract(capabilities) {
48149
48993
  ...capabilities.mail ? ["OHMYHOST_MAIL_KEY"] : []
48150
48994
  ].sort()),
48151
48995
  packages: Object.freeze({
48152
- ...capabilities.auth ? { betterAuth: "1.7.1" } : {},
48996
+ ...capabilities.auth ? { betterAuth: MANAGED_BETTER_AUTH_VERSION } : {},
48153
48997
  // A database project was told to install pg, which cannot open a socket from a Worker at all,
48154
48998
  // and was never offered the package that actually holds the client it needs.
48155
48999
  ...capabilities.database || capabilities.storage || capabilities.mail ? { customerRuntime: CUSTOMER_RUNTIME_SOURCE } : {}
@@ -48157,7 +49001,7 @@ function runtimeContract(capabilities) {
48157
49001
  });
48158
49002
  }
48159
49003
  function viteCompanionScaffold(files, capabilities) {
48160
- const sourceEntryPoint = "src/ohmyhost/companion.ts";
49004
+ const sourceEntryPoint = VITE_COMPANION_ENTRY_PATH;
48161
49005
  const contract = runtimeContract(capabilities);
48162
49006
  return Object.freeze({
48163
49007
  schemaVersion: "ohmyhost.vite-api-companion/v1",
@@ -48189,7 +49033,7 @@ async function createConfiguration(root, configuration) {
48189
49033
  await handle?.close();
48190
49034
  }
48191
49035
  }
48192
- function isRecord4(value) {
49036
+ function isRecord5(value) {
48193
49037
  return typeof value === "object" && value !== null && !Array.isArray(value);
48194
49038
  }
48195
49039
  function isNodeError(value) {
@@ -48951,6 +49795,7 @@ var runOfflineInit = async (command, io, diagnostics) => {
48951
49795
  inventory: result.inventory,
48952
49796
  compatibility: result.compatibility,
48953
49797
  blockers: result.blockers,
49798
+ pending: result.pending,
48954
49799
  requirements: result.requirements,
48955
49800
  companion: result.companion,
48956
49801
  // The functions runtime reports its own bindings and packages; dropping it left a