@amerged/ohmyhost-cli 0.1.31 → 0.1.33

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_tailwind_turbopack_only: {
34787
+ paths: ["framework_config"],
34788
+ template: `"{{path}}" loads Tailwind CSS through the Turbopack rule "@tailwindcss/turbopack", which the Webpack build on ohmyho.st ignores, and the application root has no PostCSS configuration that Webpack reads, so the site would deploy without its Tailwind classes. Add postcss.config.mjs to the application root with export default { plugins: { "@tailwindcss/postcss": {} } } and remove the Turbopack rule. Use the package manager to add @tailwindcss/postcss to devDependencies at the same version as tailwindcss and to remove @tailwindcss/turbopack, so the lockfile records both. ${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.33",
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,695 @@ 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 POSTCSS_CONFIG_PATH = /^(?:postcss\.config\.(?:[cm]?js|json)|\.postcssrc\.(?:js|json))$/u;
47195
+ var TAILWIND_TURBOPACK_LOADER = "@tailwindcss/turbopack";
47196
+ var MAXIMUM_LOCKFILE_LENGTH = 5 * 1024 * 1024;
47197
+ var MAXIMUM_PNPM_LOCKFILE_HEAD = 256 * 1024;
47198
+ var PNPM_LOCKFILE_BODY = /^(?:packages|snapshots):/mu;
47199
+ var PNPM_LOCKFILE_VERSION = /^(?:6|9)\.[0-9]{1,3}$/u;
47200
+ var PNPM_RESOLVED_VERSION = /^([^()]+)(?:\(.*\))?$/u;
47201
+ var DEFAULT_EXPORT_PATTERN = /\bexport\s+default\b/u;
47202
+ var FETCH_HANDLER_PATTERN = /(?:[{,;}]|\basync)\s*fetch\s*(?:\(|:|,|\})/u;
47203
+ var SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
47204
+ var DATABASE_CONTRACT_PATTERNS = Object.freeze([
47205
+ [
47206
+ /\bHYPERDRIVE\b|\bconnectionString\b|\bDATABASE_URL\b|postgres(?:ql)?:\/\//u,
47207
+ "database_binding_private"
47208
+ ],
47209
+ [
47210
+ /\bfrom\s*["'](?:pg|postgres|mysql2|pg-native)["']|\brequire\(\s*["'](?:pg|postgres|pg-native)["']/u,
47211
+ "database_driver_unsupported"
47212
+ ]
47213
+ ]);
47214
+ function parsePackageManifest(value) {
47215
+ let parsed = value;
47216
+ if (typeof value === "string") {
47217
+ try {
47218
+ parsed = JSON.parse(value);
47219
+ } catch {
47220
+ return null;
47221
+ }
47222
+ }
47223
+ return isRecord4(parsed) ? parsed : null;
47224
+ }
47225
+ function packageManagerDeclaration(manifest2) {
47226
+ const value = manifest2["packageManager"];
47227
+ return typeof value === "string" ? value : void 0;
47228
+ }
47229
+ function declaredDependency(manifest2, name, placement) {
47230
+ for (const field of DEPENDENCY_FIELDS[placement]) {
47231
+ const version2 = ownValue(manifest2[field], name);
47232
+ if (typeof version2 === "string" && version2.length > 0)
47233
+ return version2;
47234
+ }
47235
+ return null;
47236
+ }
47237
+ function declaredDependencyNames(manifest2) {
47238
+ const names = /* @__PURE__ */ new Set();
47239
+ for (const field of DEPENDENCY_FIELDS.any) {
47240
+ const dependencies = manifest2[field];
47241
+ if (!isRecord4(dependencies))
47242
+ continue;
47243
+ for (const [name, version2] of Object.entries(dependencies))
47244
+ if (typeof version2 === "string" && version2.length > 0)
47245
+ names.add(name);
47246
+ }
47247
+ return names;
47248
+ }
47249
+ function viteCompanionRequired(configuration) {
47250
+ 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;
47251
+ }
47252
+ function frameworkVersionAdmissions(framework, manifest2, lockfile) {
47253
+ const admissions = [];
47254
+ let locked;
47255
+ for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
47256
+ if (!isFrameworkVersionPackage(name))
47257
+ continue;
47258
+ const declared = declaredDependency(manifest2, name, placement);
47259
+ if (declared === null)
47260
+ continue;
47261
+ locked ??= lockfile === null ? () => null : lockedPackageVersions(lockfile);
47262
+ admissions.push(classifyFrameworkVersion(name, declared, locked(name)));
47263
+ }
47264
+ return Object.freeze(admissions);
47265
+ }
47266
+ function lockedPackageVersions(lockfile) {
47267
+ const { name, text } = lockfile;
47268
+ const npm = name === "package-lock.json";
47269
+ if (!npm && name !== "pnpm-lock.yaml" || typeof text !== "string" || text.length > MAXIMUM_LOCKFILE_LENGTH)
47270
+ return () => null;
47271
+ const document2 = npm ? parseJsonLockfile(text) : parsePnpmLockfileHead(text);
47272
+ return (packageName) => {
47273
+ const version2 = npm ? npmLockedVersion(document2, packageName) : pnpmLockedVersion(document2, packageName);
47274
+ return isResolvedFrameworkVersion(version2) ? version2 : null;
47275
+ };
47276
+ }
47277
+ function frameworkSourceFindings(input) {
47278
+ const versions = frameworkVersionAdmissions(input.framework, input.manifest, input.lockfile);
47279
+ return Object.freeze({
47280
+ findings: Object.freeze([
47281
+ ...manifestFindings(input, versions),
47282
+ ...configurationFindings(input),
47283
+ ...betterAuthFindings(input.configuration, input.manifest),
47284
+ ...frameworkConfigFindings(input)
47285
+ ]),
47286
+ versions
47287
+ });
47288
+ }
47289
+ function repositorySourceFindings(files) {
47290
+ const configuration = repositoryConfiguration(files.get(REPOSITORY_CONFIG));
47291
+ if (configuration === null)
47292
+ return refused("repository_configuration_invalid", REPOSITORY_CONFIG);
47293
+ const prefix = configuration.applicationRoot === "." ? "" : `${configuration.applicationRoot}/`;
47294
+ const applicationFiles = [...files.keys()].filter((path2) => path2.length > prefix.length && path2.startsWith(prefix)).map((path2) => path2.slice(prefix.length));
47295
+ if (applicationFiles.length === 0)
47296
+ return refused("application_root_missing", REPOSITORY_CONFIG);
47297
+ const text = (path2) => files.get(`${prefix}${path2}`) ?? null;
47298
+ const manifest2 = parsePackageManifest(text(PACKAGE_MANIFEST2));
47299
+ if (manifest2 === null)
47300
+ return refused("package_manifest_invalid", PACKAGE_MANIFEST2);
47301
+ const family = configuration.runtime.mode === "functions" ? "functions" : classifyFrameworkFamily(declaredDependencyNames(manifest2));
47302
+ if (family === "ambiguous")
47303
+ return refused("framework_ambiguous", PACKAGE_MANIFEST2);
47304
+ if (family === "unknown")
47305
+ return refused("framework_unsupported", PACKAGE_MANIFEST2);
47306
+ const framework = family === "vite" ? "vite-static" : family;
47307
+ const findings = [];
47308
+ const packageManager = admitted(findings, () => resolvePackageManager({
47309
+ packageManager: packageManagerDeclaration(manifest2),
47310
+ files: applicationFiles
47311
+ })) ?? null;
47312
+ const configPath = frameworkConfigPath(framework, applicationFiles, findings);
47313
+ const configText = configPath === null ? null : text(configPath);
47314
+ if (configPath !== null && configText === null)
47315
+ findings.push(diagnostic({ code: "source_syntax_invalid", path: configPath }));
47316
+ const lockfileText = packageManager === null ? null : text(packageManager.lockfile);
47317
+ const result = frameworkSourceFindings({
47318
+ framework,
47319
+ files: applicationFiles,
47320
+ configuration,
47321
+ manifest: manifest2,
47322
+ packageManager,
47323
+ lockfile: packageManager === null || lockfileText === null ? null : Object.freeze({ name: packageManager.lockfile, text: lockfileText }),
47324
+ frameworkConfig: configPath === null || configText === null ? null : Object.freeze({ path: configPath, text: configText })
47325
+ });
47326
+ return Object.freeze({
47327
+ framework,
47328
+ findings: Object.freeze([...findings, ...result.findings]),
47329
+ versions: result.versions
47330
+ });
47331
+ }
47332
+ function nextConfigFindings(text, path2, postcss) {
47333
+ const lexed = lexFrameworkConfig(text, path2);
47334
+ if ("finding" in lexed)
47335
+ return Object.freeze([lexed.finding]);
47336
+ const { tokens } = lexed;
47337
+ const findings = [];
47338
+ if (hasNonRootProperty(tokens, ["basePath", "assetPrefix"], ["", "/"]))
47339
+ findings.push(diagnostic({ code: "next_base_path_unsupported", path: path2 }));
47340
+ if (hasProperty(tokens, "output", "export") || hasProperty(tokens, "output", "standalone"))
47341
+ findings.push(diagnostic({ code: "next_output_unsupported", path: path2 }));
47342
+ if (!postcss && tokens.some(({ kind, value }) => kind === "string" && value === TAILWIND_TURBOPACK_LOADER))
47343
+ findings.push(diagnostic({ code: "next_tailwind_turbopack_only", path: path2 }));
47344
+ if (!hasExportDefault(tokens) && !(path2.slice(path2.lastIndexOf("/") + 1) === "next.config.js" && hasModuleExports(tokens)))
47345
+ findings.push(diagnostic({ code: "framework_config_default_export_required", path: path2 }));
47346
+ return Object.freeze(findings);
47347
+ }
47348
+ function viteConfigFindings(text, path2) {
47349
+ const lexed = lexFrameworkConfig(text, path2);
47350
+ if ("finding" in lexed)
47351
+ return Object.freeze([lexed.finding]);
47352
+ return Object.freeze(hasNonRootProperty(lexed.tokens, ["base"], ["/"]) ? [diagnostic({ code: "framework_base_path_unsupported", path: path2 })] : []);
47353
+ }
47354
+ function tanStackViteConfigFindings(text, path2, runtime) {
47355
+ const lexed = lexFrameworkConfig(text, path2);
47356
+ if ("finding" in lexed)
47357
+ return Object.freeze([lexed.finding]);
47358
+ const findings = [];
47359
+ if (hasNonRootProperty(lexed.tokens, ["base"], ["/"]))
47360
+ findings.push(diagnostic({ code: "framework_base_path_unsupported", path: path2 }));
47361
+ if (runtime === null)
47362
+ return Object.freeze(findings);
47363
+ const contract = tanStackPluginContract(lexed.tokens, runtime);
47364
+ if (contract === null)
47365
+ return Object.freeze([diagnostic({ code: "source_syntax_invalid", path: path2 })]);
47366
+ if (!contract.plugins)
47367
+ findings.push(diagnostic({ code: "tanstack_vite_plugins_required", path: path2 }));
47368
+ if (!contract.prerender)
47369
+ findings.push(diagnostic({ code: "tanstack_prerender_required", path: path2 }));
47370
+ return Object.freeze(findings);
47371
+ }
47372
+ function tanStackViteConfigRuntime(text) {
47373
+ const tokens = exceedsConfigBytes(text) ? null : configTokens(text);
47374
+ if (tokens === null)
47375
+ return null;
47376
+ for (const runtime of ["static", "edge"]) {
47377
+ const contract = tanStackPluginContract(tokens, runtime);
47378
+ if (contract?.plugins === true && contract.prerender)
47379
+ return runtime;
47380
+ }
47381
+ return null;
47382
+ }
47383
+ function databaseContractCodes(source) {
47384
+ const code = sourceWithoutComments(source);
47385
+ return Object.freeze(DATABASE_CONTRACT_PATTERNS.filter(([pattern]) => pattern.test(code)).map(([, found]) => found));
47386
+ }
47387
+ function workerModuleFindings(worker) {
47388
+ const { path: path2 } = worker;
47389
+ const code = sourceWithoutComments(worker.source);
47390
+ if (!DEFAULT_EXPORT_PATTERN.test(code))
47391
+ return Object.freeze([diagnostic({ code: "worker_module_default_export_required", path: path2 })]);
47392
+ const findings = [];
47393
+ if (worker.fetch && !FETCH_HANDLER_PATTERN.test(code))
47394
+ findings.push(diagnostic({ code: "worker_module_fetch_handler_required", path: path2 }));
47395
+ if (worker.scheduled && !SCHEDULED_HANDLER_PATTERN.test(code))
47396
+ findings.push(diagnostic({ code: "scheduled_handler_required", path: path2 }));
47397
+ return Object.freeze(findings);
47398
+ }
47399
+ function sourceWithoutComments(source) {
47400
+ let result = "";
47401
+ let quote = null;
47402
+ for (let index = 0; index < source.length; index += 1) {
47403
+ const character = source[index];
47404
+ const next = source[index + 1];
47405
+ if (quote !== null) {
47406
+ result += character;
47407
+ if (character === "\\") {
47408
+ index += 1;
47409
+ result += source[index] ?? "";
47410
+ } else if (character === quote || character === "\n" && quote !== "`")
47411
+ quote = null;
47412
+ continue;
47413
+ }
47414
+ if (character === "'" || character === '"' || character === "`") {
47415
+ quote = character;
47416
+ result += character;
47417
+ continue;
47418
+ }
47419
+ if (character === "/" && next === "/" && source[index - 1] !== "\\") {
47420
+ while (index < source.length && source[index] !== "\n") {
47421
+ result += " ";
47422
+ index += 1;
47423
+ }
47424
+ result += "\n";
47425
+ continue;
47426
+ }
47427
+ if (character === "/" && next === "*" && source[index - 1] !== "\\") {
47428
+ result += " ";
47429
+ index += 2;
47430
+ while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
47431
+ result += source[index] === "\n" ? "\n" : " ";
47432
+ index += 1;
47433
+ }
47434
+ result += " ";
47435
+ index += 1;
47436
+ continue;
47437
+ }
47438
+ result += character;
47439
+ }
47440
+ return result;
47441
+ }
47442
+ function isDatabaseContractModule(path2) {
47443
+ return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47444
+ }
47445
+ function manifestFindings(input, versions) {
47446
+ const { configuration, framework, manifest: manifest2 } = input;
47447
+ const findings = [];
47448
+ if (framework !== "functions" && !isAdmittedFrameworkBuildScript(BUILD_SCRIPT_FRAMEWORKS[framework], ownValue(manifest2["scripts"], "build")))
47449
+ findings.push(diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST2, framework }));
47450
+ const required = framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47451
+ for (const [name, placement] of required)
47452
+ if (declaredDependency(manifest2, name, placement) === null)
47453
+ findings.push(diagnostic({
47454
+ code: "framework_dependency_required",
47455
+ path: PACKAGE_MANIFEST2,
47456
+ package: name
47457
+ }));
47458
+ for (const admission of versions) {
47459
+ const finding = frameworkVersionDiagnostic(admission, input.lockfile?.name ?? null);
47460
+ if (finding !== null)
47461
+ findings.push(finding);
47462
+ }
47463
+ if (framework === "nextjs")
47464
+ for (const name of ["react", "react-dom"]) {
47465
+ const version2 = declaredDependency(manifest2, name, "runtime");
47466
+ if (version2 !== null && !EXACT_VERSION.test(version2))
47467
+ findings.push(diagnostic({
47468
+ code: "react_version_exact_required",
47469
+ path: PACKAGE_MANIFEST2,
47470
+ package: name
47471
+ }));
47472
+ }
47473
+ return findings;
47474
+ }
47475
+ function configurationFindings(input) {
47476
+ const { configuration, files, framework, packageManager } = input;
47477
+ const build = "command" in configuration.build ? configuration.build : null;
47478
+ const crons = (configuration.functions?.crons.length ?? 0) > 0;
47479
+ const findings = [];
47480
+ if (framework !== "functions" && !runtimeModeAdmitted(framework, configuration, crons))
47481
+ findings.push(diagnostic({ code: "runtime_mode_unsupported", path: REPOSITORY_CONFIG, framework }));
47482
+ if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== void 0)
47483
+ findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
47484
+ const output = BUILD_OUTPUTS[framework];
47485
+ if (build !== null && output !== void 0 && build.output !== output)
47486
+ findings.push(diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }));
47487
+ if (packageManager !== null && framework !== "nextjs" && (configuration.build.install !== packageManager.installCommand || build !== null && build.command !== packageManager.buildCommand))
47488
+ findings.push(diagnostic({ code: "build_command_mismatch", path: REPOSITORY_CONFIG }));
47489
+ if (framework === "vite-static" && viteCompanionRequired(configuration) && !files.includes(VITE_COMPANION_ENTRY_PATH))
47490
+ findings.push(diagnostic({ code: "companion_entry_required", path: VITE_COMPANION_ENTRY_PATH }));
47491
+ if ((framework === "functions" || crons && (framework === "nextjs" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47492
+ findings.push(diagnostic({ code: "worker_module_required", path: WORKER_MODULE_PATH }));
47493
+ if (managedCustomerAuthAdmissionIssue(configuration) !== null)
47494
+ findings.push(diagnostic({ code: "managed_auth_database_required", path: REPOSITORY_CONFIG }));
47495
+ return findings;
47496
+ }
47497
+ function runtimeModeAdmitted(framework, configuration, crons) {
47498
+ const mode = configuration.runtime.mode;
47499
+ if (framework === "nextjs")
47500
+ return mode === "edge";
47501
+ if (framework === "vite-static")
47502
+ return mode === (viteCompanionRequired(configuration) ? "edge" : "static");
47503
+ return mode === "edge" || mode === "static" && !crons && configuration.storage === void 0;
47504
+ }
47505
+ function betterAuthFindings(configuration, manifest2) {
47506
+ return configuration.auth?.provider === "better-auth" && declaredDependency(manifest2, "better-auth", "any") !== MANAGED_BETTER_AUTH_VERSION ? [diagnostic({ code: "better_auth_version_required", path: PACKAGE_MANIFEST2 })] : [];
47507
+ }
47508
+ function frameworkConfigFindings(input) {
47509
+ const { frameworkConfig, framework } = input;
47510
+ if (frameworkConfig === null || framework === "functions")
47511
+ return [];
47512
+ const { path: path2, text } = frameworkConfig;
47513
+ if (framework === "nextjs")
47514
+ return nextConfigFindings(text, path2, hasPostcssConfiguration(input));
47515
+ if (framework === "vite-static")
47516
+ return viteConfigFindings(text, path2);
47517
+ const mode = input.configuration.runtime.mode;
47518
+ return tanStackViteConfigFindings(text, path2, mode === "edge" || mode === "static" ? mode : null);
47519
+ }
47520
+ function hasPostcssConfiguration({ files, manifest: manifest2 }) {
47521
+ return isRecord4(ownValue(manifest2, "postcss")) || files.some((path2) => POSTCSS_CONFIG_PATH.test(path2));
47522
+ }
47523
+ function repositoryConfiguration(text) {
47524
+ if (typeof text !== "string")
47525
+ return null;
47526
+ try {
47527
+ return parseOhmyhostConfigYaml(text);
47528
+ } catch (error) {
47529
+ if (error instanceof TypeError)
47530
+ return null;
47531
+ throw error;
47532
+ }
47533
+ }
47534
+ function frameworkConfigPath(framework, files, findings) {
47535
+ if (framework === "functions")
47536
+ return null;
47537
+ const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files));
47538
+ if (framework === "nextjs")
47539
+ admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
47540
+ const required = REQUIRED_CONFIG_PATHS[framework];
47541
+ if (path2 === null && required !== void 0)
47542
+ findings.push(diagnostic({ code: "framework_config_required", path: required }));
47543
+ return path2 ?? null;
47544
+ }
47545
+ function admitted(findings, admit) {
47546
+ try {
47547
+ return admit();
47548
+ } catch (error) {
47549
+ if (!(error instanceof FrameworkAdmissionError))
47550
+ throw error;
47551
+ findings.push(error.diagnostic);
47552
+ return void 0;
47553
+ }
47554
+ }
47555
+ function refused(code, path2) {
47556
+ return Object.freeze({
47557
+ framework: null,
47558
+ findings: Object.freeze([diagnostic({ code, path: path2 })]),
47559
+ versions: Object.freeze([])
47560
+ });
47561
+ }
47562
+ function diagnostic(value) {
47563
+ const parsed = parseFrameworkConversionDiagnostic(value);
47564
+ if (parsed === null)
47565
+ throw new TypeError("Framework source diagnostic is invalid");
47566
+ return parsed;
47567
+ }
47568
+ function isFrameworkVersionPackage(name) {
47569
+ return Object.hasOwn(FRAMEWORK_VERSION_POLICY, name);
47570
+ }
47571
+ function parseJsonLockfile(text) {
47572
+ try {
47573
+ return JSON.parse(text);
47574
+ } catch {
47575
+ return null;
47576
+ }
47577
+ }
47578
+ function parsePnpmLockfileHead(text) {
47579
+ const end = text.search(PNPM_LOCKFILE_BODY);
47580
+ const head = end === -1 ? text : text.slice(0, end);
47581
+ if (head.length > MAXIMUM_PNPM_LOCKFILE_HEAD)
47582
+ return null;
47583
+ try {
47584
+ const document2 = (0, import_yaml2.parseDocument)(head, {
47585
+ customTags: [],
47586
+ logLevel: "error",
47587
+ merge: false,
47588
+ prettyErrors: false,
47589
+ resolveKnownTags: false,
47590
+ schema: "core",
47591
+ strict: true,
47592
+ stringKeys: true,
47593
+ // The parser compares every key with every earlier one; pnpm refuses repeated keys itself.
47594
+ uniqueKeys: false,
47595
+ version: "1.2"
47596
+ });
47597
+ return document2.errors.length === 0 && document2.warnings.length === 0 ? document2.toJS({ maxAliasCount: 0 }) : null;
47598
+ } catch {
47599
+ return null;
47600
+ }
47601
+ }
47602
+ function npmLockedVersion(document2, packageName) {
47603
+ if (!isRecord4(document2) || document2["lockfileVersion"] !== 2 && document2["lockfileVersion"] !== 3)
47604
+ return null;
47605
+ const entry = ownValue(document2["packages"], `node_modules/${packageName}`);
47606
+ return isRecord4(entry) && !Object.hasOwn(entry, "link") && (entry["name"] === void 0 || entry["name"] === packageName) ? entry["version"] : null;
47607
+ }
47608
+ function pnpmLockedVersion(document2, packageName) {
47609
+ if (!isRecord4(document2))
47610
+ return null;
47611
+ const lockfileVersion = document2["lockfileVersion"];
47612
+ if (typeof lockfileVersion !== "string" || !PNPM_LOCKFILE_VERSION.test(lockfileVersion))
47613
+ return null;
47614
+ const importers = document2["importers"];
47615
+ const root = importers === void 0 && lockfileVersion.startsWith("6.") ? document2 : ownValue(importers, ".");
47616
+ if (!isRecord4(root))
47617
+ return null;
47618
+ for (const group of ["dependencies", "devDependencies", "optionalDependencies"]) {
47619
+ const dependencies = root[group];
47620
+ if (!isRecord4(dependencies) || !Object.hasOwn(dependencies, packageName))
47621
+ continue;
47622
+ const entry = dependencies[packageName];
47623
+ return isRecord4(entry) && typeof entry["version"] === "string" ? PNPM_RESOLVED_VERSION.exec(entry["version"])?.[1] ?? null : null;
47624
+ }
47625
+ return null;
47626
+ }
47627
+ function ownValue(record6, key) {
47628
+ return isRecord4(record6) && Object.hasOwn(record6, key) ? record6[key] : void 0;
47629
+ }
47630
+ function isRecord4(value) {
47631
+ return typeof value === "object" && value !== null && !Array.isArray(value);
47632
+ }
47633
+ var PUNCTUATION = "{}[]():,.;?=-!";
47634
+ function lexFrameworkConfig(text, path2) {
47635
+ const tooLarge = diagnostic({ code: "framework_config_too_large", path: path2 });
47636
+ if (exceedsConfigBytes(text))
47637
+ return { finding: tooLarge };
47638
+ const tokens = configTokens(text);
47639
+ return tokens === null ? { finding: diagnostic({ code: "source_syntax_invalid", path: path2 }) } : { tokens };
47640
+ }
47641
+ function exceedsConfigBytes(text) {
47642
+ return text.length > MAXIMUM_FRAMEWORK_CONFIG_BYTES || new TextEncoder().encode(text).byteLength > MAXIMUM_FRAMEWORK_CONFIG_BYTES;
47643
+ }
47644
+ function configTokens(source) {
47645
+ const tokens = [];
47646
+ let index = 0;
47647
+ while (index < source.length) {
47648
+ const character = source[index];
47649
+ const next = source[index + 1];
47650
+ if (character === "/" && next === "/") {
47651
+ const end = source.indexOf("\n", index + 2);
47652
+ index = end === -1 ? source.length : end + 1;
47653
+ } else if (character === "/" && next === "*") {
47654
+ const end = source.indexOf("*/", index + 2);
47655
+ if (end === -1)
47656
+ return null;
47657
+ index = end + 2;
47658
+ } else if (character === "'" || character === '"' || character === "`") {
47659
+ const string = readString(source, index, character);
47660
+ if (string === null)
47661
+ return null;
47662
+ tokens.push(string.token);
47663
+ index = string.nextIndex;
47664
+ } else if (isIdentifierStart(character) || isDigit(character)) {
47665
+ const start = index;
47666
+ const part = isDigit(character) ? isDigit : isIdentifierPart;
47667
+ index += 1;
47668
+ while (index < source.length && part(source[index]))
47669
+ index += 1;
47670
+ tokens.push({
47671
+ kind: isDigit(character) ? "number" : "identifier",
47672
+ value: source.slice(start, index)
47673
+ });
47674
+ } else {
47675
+ if (PUNCTUATION.includes(character))
47676
+ tokens.push({ kind: "punctuation", value: character });
47677
+ index += 1;
47678
+ }
47679
+ }
47680
+ return tokens;
47681
+ }
47682
+ function readString(source, index, quote) {
47683
+ let cursor = index + 1;
47684
+ let value = "";
47685
+ while (cursor < source.length) {
47686
+ const character = source[cursor];
47687
+ if (character === quote)
47688
+ return { token: { kind: "string", value }, nextIndex: cursor + 1 };
47689
+ if (character === "\\") {
47690
+ const escaped = source[cursor + 1];
47691
+ if (escaped === void 0)
47692
+ return null;
47693
+ value += escaped;
47694
+ cursor += 2;
47695
+ } else {
47696
+ value += character;
47697
+ cursor += 1;
47698
+ }
47699
+ }
47700
+ return null;
47701
+ }
47702
+ function isIdentifierStart(character) {
47703
+ return character >= "A" && character <= "Z" || character >= "a" && character <= "z" || character === "_" || character === "$";
47704
+ }
47705
+ function isIdentifierPart(character) {
47706
+ return isIdentifierStart(character) || isDigit(character);
47707
+ }
47708
+ function isDigit(character) {
47709
+ return character >= "0" && character <= "9";
47710
+ }
47711
+ function hasNonRootProperty(tokens, names, rootStrings) {
47712
+ let objectDepth = 0;
47713
+ for (let index = 0; index < tokens.length; index += 1) {
47714
+ const token = tokens[index];
47715
+ if (token.value === "{" || token.value === "}") {
47716
+ objectDepth += token.value === "{" ? 1 : -1;
47717
+ continue;
47718
+ }
47719
+ const before = tokens[index - 1]?.value;
47720
+ const directKey = before === "{" || before === ",";
47721
+ const computedKey = before === "[" && tokens[index + 1]?.value === "]" && (tokens[index - 2]?.value === "{" || tokens[index - 2]?.value === ",");
47722
+ if (objectDepth < 1 || !names.includes(token.value) || !directKey && !computedKey)
47723
+ continue;
47724
+ const separator = tokens[index + (computedKey ? 2 : 1)]?.value;
47725
+ const value = tokens[index + (computedKey ? 3 : 2)];
47726
+ const root = separator === ":" && (value?.kind === "string" && rootStrings.includes(value.value) || value?.kind === "identifier" && value.value === "undefined");
47727
+ if (!root)
47728
+ return true;
47729
+ }
47730
+ return false;
47731
+ }
47732
+ function hasProperty(tokens, name, value) {
47733
+ return tokens.some((token, index) => token.value === name && tokens[index + 1]?.value === ":" && tokens[index + 2]?.value === value);
47734
+ }
47735
+ function hasExportDefault(tokens) {
47736
+ return tokens.some((token, index) => token.value === "export" && tokens[index + 1]?.value === "default");
47737
+ }
47738
+ function hasModuleExports(tokens) {
47739
+ return tokens.some((token, index) => {
47740
+ if (token.kind !== "identifier" || token.value !== "module")
47741
+ return false;
47742
+ const property = tokens[index + 2];
47743
+ const direct = tokens[index + 1]?.value === "." && property?.kind === "identifier" && property.value === "exports";
47744
+ const computed = tokens[index + 1]?.value === "[" && property?.kind === "string" && property.value === "exports" && tokens[index + 3]?.value === "]";
47745
+ const assignment = index + (computed ? 4 : 3);
47746
+ return (direct || computed) && tokens[assignment]?.value === "=" && tokens[assignment + 1]?.value !== "=";
47747
+ });
47748
+ }
47749
+ function tanStackPluginContract(tokens, runtime) {
47750
+ const closing = closingBraces(tokens);
47751
+ const imported = hasNamedImport(tokens, "tanstackStart", TANSTACK_START_PLUGIN);
47752
+ if (runtime === "static") {
47753
+ const calls2 = callOptions(tokens, closing, "tanstackStart");
47754
+ return calls2 === null ? null : {
47755
+ plugins: imported && !hasModuleImport(tokens, CLOUDFLARE_PLUGIN),
47756
+ prerender: calls2.some(([start, end]) => nestedPropertyValue(tokens, closing, start, end, "prerender", "enabled") === "true")
47757
+ };
47758
+ }
47759
+ const calls = callOptions(tokens, closing, "cloudflare");
47760
+ return calls === null ? null : {
47761
+ 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"),
47762
+ prerender: true
47763
+ };
47764
+ }
47765
+ function closingBraces(tokens) {
47766
+ const closing = tokens.map(() => -1);
47767
+ const open5 = [];
47768
+ tokens.forEach((token, index) => {
47769
+ if (token.value === "{")
47770
+ open5.push(index);
47771
+ if (token.value !== "}")
47772
+ return;
47773
+ const start = open5.pop();
47774
+ if (start !== void 0)
47775
+ closing[start] = index;
47776
+ });
47777
+ return closing;
47778
+ }
47779
+ function callOptions(tokens, closing, name) {
47780
+ const calls = [];
47781
+ for (let index = 0; index < tokens.length - 2; index += 1) {
47782
+ if (tokens[index]?.value !== name || tokens[index + 1]?.value !== "(" || tokens[index + 2]?.value !== "{")
47783
+ continue;
47784
+ const end = closing[index + 2] ?? -1;
47785
+ if (end === -1)
47786
+ return null;
47787
+ calls.push([index + 2, end]);
47788
+ }
47789
+ return calls;
47790
+ }
47791
+ function nestedPropertyValue(tokens, closing, start, end, outer, inner) {
47792
+ const object3 = directProperty(tokens, closing, start, end, outer);
47793
+ if (object3?.value !== "{")
47794
+ return null;
47795
+ return directProperty(tokens, closing, object3.start, object3.end, inner)?.value ?? null;
47796
+ }
47797
+ function directProperty(tokens, closing, start, end, name) {
47798
+ let depth = 0;
47799
+ for (let index = start + 1; index < end; index += 1) {
47800
+ const value = tokens[index]?.value;
47801
+ if (value === "{") {
47802
+ index = Math.max(index, closing[index] ?? -1);
47803
+ continue;
47804
+ }
47805
+ if (value === "[" || value === "(")
47806
+ depth += 1;
47807
+ if (value === "]" || value === ")")
47808
+ depth -= 1;
47809
+ if (depth !== 0 || value !== name || tokens[index + 1]?.value !== ":")
47810
+ continue;
47811
+ const candidate = tokens[index + 2]?.value ?? "";
47812
+ const close = candidate === "{" ? closing[index + 2] ?? -1 : index + 2;
47813
+ return { start: index + 2, end: close, value: candidate };
47814
+ }
47815
+ return null;
47816
+ }
47817
+ function callIndex(tokens, name) {
47818
+ return tokens.findIndex((token, index) => token.value === name && tokens[index + 1]?.value === "(");
47819
+ }
47820
+ function hasNamedImport(tokens, name, moduleName) {
47821
+ for (let index = 0; index < tokens.length; index += 1) {
47822
+ if (tokens[index]?.value !== "import")
47823
+ continue;
47824
+ let from = index + 1;
47825
+ while (from < tokens.length && tokens[from]?.value !== "from")
47826
+ from += 1;
47827
+ if (from === tokens.length)
47828
+ return false;
47829
+ const source = tokens[from + 1];
47830
+ if (source?.kind === "string" && source.value === moduleName && tokens.slice(index + 1, from).some((token) => token.value === name))
47831
+ return true;
47832
+ index = from;
47833
+ }
47834
+ return false;
47835
+ }
47836
+ function hasModuleImport(tokens, moduleName) {
47837
+ return tokens.some((token, index) => token.value === "from" && tokens[index + 1]?.kind === "string" && tokens[index + 1]?.value === moduleName);
47838
+ }
47839
+
46756
47840
  // packages/contracts/dist/database-migration-errors.js
46757
47841
  var DatabaseMigrationAdmissionError = class extends Error {
46758
47842
  reason;
@@ -47117,28 +48201,31 @@ function admitStatement(tokens, sql) {
47117
48201
  // apps/product-cli/dist/repository-init.js
47118
48202
  var MAXIMUM_PACKAGE_BYTES = 1024 * 1024;
47119
48203
  var MAXIMUM_CONFIGURATION_BYTES = 64 * 1024;
48204
+ var MAXIMUM_INSPECTED_FILE_BYTES = 5 * 1024 * 1024;
47120
48205
  var MAXIMUM_REPOSITORY_FILES = 2e4;
47121
- var SUPPORTED_BETTER_AUTH_VERSION = "1.7.1";
47122
48206
  var PROJECT_PATTERN = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/u;
47123
48207
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
47124
48208
  ".git",
47125
48209
  ".next",
48210
+ ".open-next",
47126
48211
  ".supabase",
47127
48212
  ".turbo",
47128
48213
  ".vercel",
48214
+ ".wrangler",
47129
48215
  "coverage",
47130
48216
  "dist",
47131
48217
  "node_modules"
47132
48218
  ]);
47133
- var WORKER_MODULE_PATH = "src/ohmyhost/worker.ts";
48219
+ var UTF8 = new TextDecoder("utf-8", { fatal: true });
47134
48220
  var MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
47135
48221
  var SOURCE_MODULE_PATTERN = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
47136
48222
  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;
48223
+ var UNNAMED_MODULE_MESSAGES = Object.freeze({
48224
+ 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.',
48225
+ 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.'
48226
+ });
47140
48227
  function callsStorageClient(source) {
47141
- return STORAGE_CLIENT_CALL_PATTERN.test(source.replaceAll(INERT_SOURCE_PATTERN, " "));
48228
+ return STORAGE_CLIENT_CALL_PATTERN.test(sourceWithoutComments(source));
47142
48229
  }
47143
48230
  var STORAGE_RUNTIME_BINDINGS = Object.freeze([
47144
48231
  "OHMYHOST_ENVIRONMENT_ID",
@@ -47148,14 +48235,13 @@ var STORAGE_RUNTIME_BINDINGS = Object.freeze([
47148
48235
  "OHMYHOST_STORAGE_KEY"
47149
48236
  ]);
47150
48237
  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
- }
48238
+ var PENDING_CHECKS = Object.freeze({
48239
+ 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.",
48240
+ 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.",
48241
+ 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.",
48242
+ 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.",
48243
+ 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."
48244
+ });
47159
48245
  var RepositoryInitError = class extends Error {
47160
48246
  code;
47161
48247
  constructor(code, message) {
@@ -47186,18 +48272,15 @@ async function initializeRepository(input) {
47186
48272
  const applicationDirectory = resolve5(root, selection.applicationRoot);
47187
48273
  const applicationFiles = applicationRelativeFiles(files, selection.applicationRoot);
47188
48274
  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);
48275
+ const manifest2 = packageJson.manifest;
48276
+ const dependencies = declaredDependencyNames(manifest2);
48277
+ const existing2 = await readExistingConfiguration(root, files, selection.applicationRoot);
48278
+ const existingConfiguration = existing2?.configuration ?? null;
47192
48279
  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);
48280
+ const packageManagerAdmission = packageManagerAdmissionFor(manifest2, applicationFiles);
48281
+ const packageManager = packageManagerInventory(packageManagerAdmission, manifest2, applicationFiles);
48282
+ const frameworkConfig = await readFrameworkConfig(applicationDirectory, applicationFiles, framework);
48283
+ const project = projectName(input.project, manifest2);
47201
48284
  const supabaseMigrationCount = applicationFiles.filter((path2) => /^supabase\/migrations\/[^/]+\.sql$/u.test(path2)).length;
47202
48285
  const postgresMigrationRoot = existingConfiguration?.database?.migrations ?? "postgres/migrations";
47203
48286
  const postgresMigrationCount = applicationFiles.filter((path2) => path2.startsWith(`${postgresMigrationRoot}/`) && path2.endsWith(".sql")).length;
@@ -47245,8 +48328,8 @@ async function initializeRepository(input) {
47245
48328
  const targetBetterAuth = existingConfiguration?.auth?.provider === "better-auth";
47246
48329
  const targetMail = existingConfiguration?.mail?.enabled === true;
47247
48330
  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);
48331
+ const tanStackRuntime = framework !== "tanstack-start" ? null : targetStorage ? "edge" : proposedTanStackRuntime(frameworkConfig, dependencies);
48332
+ const companionRequired = framework === "vite" && (existingConfiguration === null ? databaseProvider !== "none" || targetBetterAuth || targetMail || targetStorage || edgeFunctionNames.size > 0 || applicationFiles.includes(VITE_COMPANION_ENTRY_PATH) : viteCompanionRequired(existingConfiguration));
47250
48333
  const generatedConfigurationYaml = renderConfiguration({
47251
48334
  project,
47252
48335
  framework,
@@ -47261,7 +48344,7 @@ async function initializeRepository(input) {
47261
48344
  tanStackRuntime,
47262
48345
  applicationRoot: selection.applicationRoot
47263
48346
  });
47264
- const configurationYaml = existingConfigurationYaml ?? generatedConfigurationYaml;
48347
+ const configurationYaml = existing2?.yaml ?? generatedConfigurationYaml;
47265
48348
  const capabilities = Object.freeze({
47266
48349
  database: selectedDatabaseProvider !== "none",
47267
48350
  auth: targetBetterAuth,
@@ -47270,16 +48353,19 @@ async function initializeRepository(input) {
47270
48353
  });
47271
48354
  const companion = companionRequired ? viteCompanionScaffold(applicationFiles, capabilities) : null;
47272
48355
  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));
48356
+ const { blockers, versions } = existing2 !== null && existing2.blocker !== null ? { blockers: [existing2.blocker], versions: [] } : await judgeSource({
48357
+ framework,
48358
+ config: existingConfiguration,
48359
+ applicationDirectory,
48360
+ files: applicationFiles,
48361
+ applicationRoot: selection.applicationRoot,
48362
+ provider: selectedDatabaseProvider,
48363
+ texts: sourceTexts(files, selection.applicationRoot, configurationYaml, [
48364
+ ["package.json", packageJson.text],
48365
+ ...lockfileTexts(await readResolvingLockfile(applicationDirectory, packageManagerAdmission)),
48366
+ ...frameworkConfig === null ? [] : [[frameworkConfig.path, frameworkConfig.text]]
48367
+ ])
48368
+ });
47283
48369
  const requirements = requirementsFor(inventory, companion !== null && !companionSourcePresent, mailDetected, targetBetterAuth);
47284
48370
  const configurationSha256 = createHash4("sha256").update(configurationYaml).digest("hex");
47285
48371
  if (!input.dryRun) {
@@ -47296,8 +48382,17 @@ async function initializeRepository(input) {
47296
48382
  applicationRoot: selection.applicationRoot,
47297
48383
  applicationRootSource: selection.source,
47298
48384
  inventory,
47299
- compatibility,
48385
+ compatibility: Object.freeze({
48386
+ frameworks: Object.freeze(versions.map((version2) => Object.freeze({
48387
+ package: version2.package,
48388
+ declared: version2.declared,
48389
+ resolved: version2.resolved,
48390
+ status: version2.status,
48391
+ minimum: version2.minimum
48392
+ })))
48393
+ }),
47300
48394
  blockers: Object.freeze(blockers),
48395
+ pending: pendingChecks(versions),
47301
48396
  requirements: Object.freeze(requirements),
47302
48397
  companion,
47303
48398
  worker: framework === "functions" ? Object.freeze({
@@ -47309,6 +48404,20 @@ async function initializeRepository(input) {
47309
48404
  runtime: Object.values(capabilities).some(Boolean) ? runtimeContract(capabilities) : null
47310
48405
  });
47311
48406
  }
48407
+ async function judgeSource(context) {
48408
+ const source = repositorySourceFindings(context.texts);
48409
+ return {
48410
+ blockers: [
48411
+ ...migrationFilenameBlockers(context, context.provider),
48412
+ ...source.findings.map((finding) => requiredCatalogBlocker(finding, context.applicationRoot)),
48413
+ ...await workerModuleBlockers(context),
48414
+ ...await databaseContractBlockers(context, context.provider),
48415
+ ...await storageClientBlockers(context),
48416
+ ...await migrationSqlBlockers(context, context.provider)
48417
+ ],
48418
+ versions: source.versions
48419
+ };
48420
+ }
47312
48421
  function detectRepositoryFramework(dependencies, files, existingConfiguration) {
47313
48422
  const detected = detectFramework(dependencies);
47314
48423
  if (existingConfiguration?.runtime.mode === "functions")
@@ -47317,41 +48426,47 @@ function detectRepositoryFramework(dependencies, files, existingConfiguration) {
47317
48426
  return "functions";
47318
48427
  return detected;
47319
48428
  }
47320
- async function cronBlockers(framework, config, applicationDirectory, files) {
47321
- if ((config?.functions?.crons.length ?? 0) === 0)
47322
- return [];
48429
+ function sourceTexts(files, applicationRoot, configurationYaml, applicationTexts) {
48430
+ const texts = new Map(files.map((path2) => [path2, null]));
48431
+ texts.set("ohmyhost.yaml", configurationYaml);
48432
+ for (const [path2, text] of applicationTexts)
48433
+ texts.set(repositoryPath(applicationRoot, path2), text);
48434
+ return texts;
48435
+ }
48436
+ function lockfileTexts(lockfile) {
48437
+ return lockfile === null ? [] : [[lockfile.name, lockfile.text]];
48438
+ }
48439
+ function repositoryPath(applicationRoot, path2) {
48440
+ return applicationRoot === "." ? path2 : `${applicationRoot}/${path2}`;
48441
+ }
48442
+ function catalogBlocker(diagnostic2, applicationRoot) {
48443
+ const rooted = rootFrameworkConversionDiagnostic(diagnostic2, applicationRoot);
48444
+ const message = frameworkConversionDetail(rooted);
48445
+ return rooted === null || message === null ? null : Object.freeze({ code: rooted.code, path: rooted.path, message });
48446
+ }
48447
+ function requiredCatalogBlocker(diagnostic2, applicationRoot) {
48448
+ const blocker = catalogBlocker(diagnostic2, applicationRoot);
48449
+ if (blocker === null)
48450
+ throw new TypeError("Repository init diagnostic is invalid");
48451
+ return blocker;
48452
+ }
48453
+ async function workerModuleBlockers(context) {
48454
+ const { config, framework, files } = context;
47323
48455
  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 [];
48456
+ const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48457
+ const path2 = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
48458
+ if (framework !== "functions" && !scheduled || !files.includes(path2))
48459
+ return [];
48460
+ const source = await readFile3(resolve5(context.applicationDirectory, path2), "utf8");
48461
+ return workerModuleFindings({ path: path2, source, fetch: framework === "functions", scheduled }).map((finding) => requiredCatalogBlocker(finding, context.applicationRoot));
47347
48462
  }
47348
- async function storageClientBlockers(config, applicationDirectory, files) {
47349
- if (config?.storage === void 0)
48463
+ async function storageClientBlockers(context) {
48464
+ if (context.config?.storage === void 0)
47350
48465
  return [];
47351
- for (const path2 of files) {
48466
+ for (const path2 of context.files) {
47352
48467
  if (!SOURCE_MODULE_PATTERN.test(path2))
47353
48468
  continue;
47354
- const absolute = resolve5(applicationDirectory, path2);
48469
+ const absolute = resolve5(context.applicationDirectory, path2);
47355
48470
  const information = await stat2(absolute);
47356
48471
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
47357
48472
  continue;
@@ -47361,52 +48476,41 @@ async function storageClientBlockers(config, applicationDirectory, files) {
47361
48476
  return [
47362
48477
  {
47363
48478
  code: "storage_client_missing",
48479
+ path: null,
47364
48480
  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
48481
  }
47366
48482
  ];
47367
48483
  }
47368
- async function databaseContractBlockers(config, applicationDirectory, files) {
47369
- if (config?.database === void 0)
48484
+ async function databaseContractBlockers(context, provider) {
48485
+ if (provider === "none")
47370
48486
  return [];
47371
48487
  const blockers = [];
47372
- for (const path2 of files) {
47373
- if (!SOURCE_MODULE_PATTERN.test(path2))
48488
+ for (const path2 of context.files) {
48489
+ if (!isDatabaseContractModule(path2))
47374
48490
  continue;
47375
- const absolute = resolve5(applicationDirectory, path2);
48491
+ const absolute = resolve5(context.applicationDirectory, path2);
47376
48492
  const information = await stat2(absolute);
47377
48493
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
47378
48494
  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
- }
48495
+ for (const code of databaseContractCodes(await readFile3(absolute, "utf8")))
48496
+ blockers.push(catalogBlocker({ code, path: path2 }, context.applicationRoot) ?? Object.freeze({
48497
+ code,
48498
+ path: repositoryPath(context.applicationRoot, path2),
48499
+ message: UNNAMED_MODULE_MESSAGES[code]
48500
+ }));
47392
48501
  }
47393
48502
  return blockers;
47394
48503
  }
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) {
48504
+ function migrationFilenameBlockers(context, provider) {
47402
48505
  if (provider !== "postgresql")
47403
48506
  return [];
47404
- const root = configuredPath ?? "postgres/migrations";
48507
+ const root = context.config?.database?.migrations ?? "postgres/migrations";
47405
48508
  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));
48509
+ 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
48510
  return invalid6 === void 0 ? [] : [
47408
48511
  {
47409
48512
  code: "migration_filename_noncanonical",
48513
+ path: repositoryPath(context.applicationRoot, `${prefix}${invalid6}`),
47410
48514
  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
48515
  }
47412
48516
  ];
@@ -47417,18 +48521,18 @@ var MIGRATION_REJECTION = {
47417
48521
  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
48522
  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
48523
  };
47420
- async function migrationSqlBlockers(applicationDirectory, files, configuredPath, provider) {
48524
+ async function migrationSqlBlockers(context, provider) {
47421
48525
  if (provider !== "postgresql")
47422
48526
  return [];
47423
- const root = configuredPath ?? "postgres/migrations";
48527
+ const root = context.config?.database?.migrations ?? "postgres/migrations";
47424
48528
  const prefix = `${root}/`;
47425
- const migrationPaths = files.filter((path2) => path2.startsWith(prefix) && path2.endsWith(".sql")).map((path2) => path2.slice(prefix.length));
48529
+ const migrationPaths = context.files.filter((path2) => path2.startsWith(prefix) && path2.endsWith(".sql")).map((path2) => path2.slice(prefix.length));
47426
48530
  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
48531
  return [];
47428
48532
  }
47429
48533
  const migrations = await Promise.all(migrationPaths.map(async (path2) => Object.freeze({
47430
48534
  path: path2,
47431
- body: new Uint8Array(await readFile3(resolve5(applicationDirectory, root, path2)))
48535
+ body: new Uint8Array(await readFile3(resolve5(context.applicationDirectory, root, path2)))
47432
48536
  })));
47433
48537
  const blockers = [];
47434
48538
  for (const migration of migrations) {
@@ -47439,6 +48543,7 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
47439
48543
  throw error;
47440
48544
  blockers.push({
47441
48545
  code: "migration_sql_not_admitted",
48546
+ path: repositoryPath(context.applicationRoot, `${prefix}${migration.path}`),
47442
48547
  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
48548
  });
47444
48549
  }
@@ -47454,11 +48559,22 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
47454
48559
  return [
47455
48560
  {
47456
48561
  code: "migration_sql_not_admitted",
48562
+ path: null,
47457
48563
  message: `The migration set is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]}`
47458
48564
  }
47459
48565
  ];
47460
48566
  }
47461
48567
  }
48568
+ function pendingChecks(versions) {
48569
+ const codes = [
48570
+ ...versions.some(({ status }) => status === "pending") ? ["installed_version"] : [],
48571
+ "frozen_install",
48572
+ "framework_build",
48573
+ "reachable_runtime_sources",
48574
+ "project_state"
48575
+ ];
48576
+ return Object.freeze(codes.map((code) => Object.freeze({ code, message: PENDING_CHECKS[code] })));
48577
+ }
47462
48578
  async function readExistingConfiguration(repositoryRoot, repositoryFiles, applicationRoot) {
47463
48579
  if (!repositoryFiles.includes("ohmyhost.yaml"))
47464
48580
  return null;
@@ -47467,17 +48583,30 @@ async function readExistingConfiguration(repositoryRoot, repositoryFiles, applic
47467
48583
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
47468
48584
  throw new RepositoryInitError("repository_configuration_invalid", "ohmyhost.yaml must be one regular file of at most 64 KiB");
47469
48585
  }
47470
- const configuration = await readFile3(configurationPath, "utf8");
48586
+ const bytes = await readFile3(configurationPath);
48587
+ const text = utf8(bytes);
48588
+ const yaml = text ?? new TextDecoder().decode(bytes);
47471
48589
  let parsed;
47472
48590
  try {
47473
- parsed = parseOhmyhostConfigYaml(configuration);
48591
+ if (text === null)
48592
+ throw new TypeError("ohmyhost.yaml is not valid UTF-8");
48593
+ parsed = parseOhmyhostConfigYaml(text);
47474
48594
  } catch (error) {
47475
- throw new RepositoryInitError("repository_configuration_invalid", error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid");
48595
+ const reason = (error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid").replace(/\.$/u, "");
48596
+ return Object.freeze({
48597
+ yaml,
48598
+ configuration: null,
48599
+ blocker: Object.freeze({
48600
+ code: "repository_configuration_invalid",
48601
+ path: "ohmyhost.yaml",
48602
+ message: `${reason}. Fix that in ohmyhost.yaml and run init again; init never rewrites an existing ohmyhost.yaml, so do not delete it.`
48603
+ })
48604
+ });
47476
48605
  }
47477
48606
  if (parsed.applicationRoot !== applicationRoot) {
47478
48607
  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
48608
  }
47480
- return configuration;
48609
+ return Object.freeze({ yaml, configuration: parsed, blocker: null });
47481
48610
  }
47482
48611
  var APPLICATION_LOCKFILES = Object.freeze([
47483
48612
  "bun.lock",
@@ -47555,7 +48684,7 @@ async function isApplicationCandidate(repositoryRoot, repositoryFiles, applicati
47555
48684
  const packageJson = await tryReadPackageJson(applicationDirectory);
47556
48685
  if (packageJson === null)
47557
48686
  return false;
47558
- const framework = detectFramework(dependencyNames(packageJson));
48687
+ const framework = detectFramework(declaredDependencyNames(packageJson.manifest));
47559
48688
  return framework !== "unknown" || files.includes(WORKER_MODULE_PATH);
47560
48689
  }
47561
48690
  async function hasDirectApplicationShape(repositoryRoot, repositoryFiles, applicationRoot) {
@@ -47567,10 +48696,10 @@ async function hasDirectApplicationShape(repositoryRoot, repositoryFiles, applic
47567
48696
  const packageJson = await tryReadPackageJson(applicationDirectory);
47568
48697
  if (packageJson === null)
47569
48698
  return false;
47570
- const framework = detectFramework(dependencyNames(packageJson));
48699
+ const framework = detectFramework(declaredDependencyNames(packageJson.manifest));
47571
48700
  if (framework === "unknown")
47572
48701
  return files.includes(WORKER_MODULE_PATH);
47573
- return hasDirectSafeBuildCommand(packageJson, framework);
48702
+ return hasDirectSafeBuildCommand(packageJson.manifest, framework);
47574
48703
  }
47575
48704
  async function isRealDirectory(path2) {
47576
48705
  try {
@@ -47593,18 +48722,12 @@ async function tryReadPackageJson(root) {
47593
48722
  return null;
47594
48723
  }
47595
48724
  }
47596
- function hasDirectSafeBuildCommand(packageJson, framework) {
47597
- const scripts = packageJson["scripts"];
47598
- if (!isRecord4(scripts) || typeof scripts["build"] !== "string")
48725
+ function hasDirectSafeBuildCommand(manifest2, framework) {
48726
+ const scripts = manifest2["scripts"];
48727
+ if (!isRecord5(scripts) || typeof scripts["build"] !== "string")
47599
48728
  return false;
47600
48729
  return (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
47601
48730
  }
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
48731
  async function assertRepositoryRoot(root) {
47609
48732
  let information;
47610
48733
  try {
@@ -47676,105 +48799,95 @@ async function readPackageJson(root) {
47676
48799
  if (!information.isFile() || information.size > MAXIMUM_PACKAGE_BYTES) {
47677
48800
  throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
47678
48801
  }
48802
+ const text = utf8(await readFile3(path2));
48803
+ const manifest2 = text === null ? null : parsePackageManifest(text);
48804
+ if (text === null || manifest2 === null) {
48805
+ throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
48806
+ }
48807
+ return Object.freeze({ text, manifest: manifest2 });
48808
+ }
48809
+ function utf8(bytes) {
47679
48810
  try {
47680
- const value = JSON.parse(await readFile3(path2, "utf8"));
47681
- if (!isRecord4(value))
47682
- throw new TypeError();
47683
- return value;
48811
+ return UTF8.decode(bytes);
47684
48812
  } catch {
47685
- throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
48813
+ return null;
47686
48814
  }
47687
48815
  }
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
- }
48816
+ async function readLeadingBytes(path2) {
48817
+ const handle = await open4(path2, "r");
48818
+ try {
48819
+ const { size } = await handle.stat();
48820
+ const bytes = new Uint8Array(Math.min(size, MAXIMUM_INSPECTED_FILE_BYTES));
48821
+ const { bytesRead } = await handle.read(bytes, 0, bytes.byteLength, 0);
48822
+ return Object.freeze({ bytes: bytes.subarray(0, bytesRead), complete: bytesRead === size });
48823
+ } finally {
48824
+ await handle.close();
47698
48825
  }
47699
- return names;
48826
+ }
48827
+ async function readResolvingLockfile(applicationDirectory, admission) {
48828
+ if (admission instanceof FrameworkAdmissionError || admission.lockfile !== "package-lock.json" && admission.lockfile !== "pnpm-lock.yaml")
48829
+ return null;
48830
+ const { bytes, complete } = await readLeadingBytes(resolve5(applicationDirectory, admission.lockfile));
48831
+ const text = complete ? utf8(bytes) : null;
48832
+ return text === null ? null : Object.freeze({ name: admission.lockfile, text });
48833
+ }
48834
+ async function readFrameworkConfig(applicationDirectory, files, framework) {
48835
+ if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48836
+ return null;
48837
+ let path2;
48838
+ try {
48839
+ path2 = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
48840
+ } catch (error) {
48841
+ if (error instanceof FrameworkAdmissionError)
48842
+ return null;
48843
+ throw error;
48844
+ }
48845
+ if (path2 === null)
48846
+ return null;
48847
+ const { bytes, complete } = await readLeadingBytes(resolve5(applicationDirectory, path2));
48848
+ return Object.freeze({ path: path2, text: complete ? utf8(bytes) : new TextDecoder().decode(bytes) });
48849
+ }
48850
+ function proposedTanStackRuntime(config, dependencies) {
48851
+ const followed = config === null || config.text === null ? null : tanStackViteConfigRuntime(config.text);
48852
+ return followed ?? (dependencies.has("@cloudflare/vite-plugin") ? "edge" : "static");
47700
48853
  }
47701
48854
  function detectFramework(dependencies) {
47702
48855
  const classification = classifyFrameworkFamily(dependencies);
47703
48856
  return classification === "ambiguous" ? "unknown" : classification;
47704
48857
  }
47705
- function packageManagerAdmissionFor(packageJson, files) {
48858
+ function packageManagerAdmissionFor(manifest2, files) {
47706
48859
  try {
47707
- return resolvePackageManager({
47708
- packageManager: typeof packageJson["packageManager"] === "string" ? packageJson["packageManager"] : void 0,
47709
- files
47710
- });
48860
+ return resolvePackageManager({ packageManager: packageManagerDeclaration(manifest2), files });
47711
48861
  } catch (error) {
47712
48862
  if (error instanceof FrameworkAdmissionError)
47713
48863
  return error;
47714
48864
  throw error;
47715
48865
  }
47716
48866
  }
47717
- function packageManagerInventory(admission, packageJson, files) {
48867
+ function packageManagerInventory(admission, manifest2, files) {
47718
48868
  if (!(admission instanceof FrameworkAdmissionError)) {
47719
48869
  return {
47720
48870
  name: admission.manager,
47721
48871
  version: admission.version,
47722
- lockfile: admission.lockfile
48872
+ lockfile: admission.lockfile,
48873
+ installCommand: admission.installCommand,
48874
+ buildCommand: admission.buildCommand
47723
48875
  };
47724
48876
  }
47725
- const declaration = packageJson["packageManager"];
47726
- const match = typeof declaration === "string" ? /^(bun|npm|pnpm|yarn)@([^\s]+)$/u.exec(declaration) : null;
48877
+ const declaration = packageManagerDeclaration(manifest2);
48878
+ const match = declaration === void 0 ? null : /^(bun|npm|pnpm|yarn)@([^\s]+)$/u.exec(declaration);
47727
48879
  const lockfile = APPLICATION_LOCKFILES.find((candidate) => files.includes(candidate)) ?? null;
47728
48880
  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
48881
  return {
47730
48882
  name: match === null ? inferredName : match[1],
47731
48883
  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
- }
48884
+ lockfile,
48885
+ installCommand: null,
48886
+ buildCommand: null
47747
48887
  };
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
48888
  }
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"];
48889
+ function projectName(explicit, manifest2) {
48890
+ const packageName = manifest2["name"];
47778
48891
  const inferred = typeof packageName === "string" ? (packageName.split("/").at(-1) ?? "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") : packageName;
47779
48892
  const value = explicit ?? inferred;
47780
48893
  if (typeof value !== "string" || value.length > 63 || !PROJECT_PATTERN.test(value)) {
@@ -47782,117 +48895,6 @@ function projectName(explicit, packageJson) {
47782
48895
  }
47783
48896
  return value;
47784
48897
  }
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
48898
  function renderConfiguration(input) {
47897
48899
  const descriptor = packageManagerDescriptorFromInventory(input.packageManager);
47898
48900
  const install = descriptor?.installCommand ?? "pnpm install --frozen-lockfile --ignore-scripts";
@@ -47949,66 +48951,6 @@ function packageManagerDescriptorFromInventory(packageManager) {
47949
48951
  return null;
47950
48952
  }
47951
48953
  }
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
48954
  function databaseMigrationPath(provider) {
48013
48955
  if (provider === "supabase")
48014
48956
  return "postgres/migrations";
@@ -48016,99 +48958,6 @@ function databaseMigrationPath(provider) {
48016
48958
  return "postgres/migrations";
48017
48959
  return null;
48018
48960
  }
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
48961
  function requirementsFor(inventory, viteCompanion, mailConversionRequired, managedAuthSelected) {
48113
48962
  const requirements = /* @__PURE__ */ new Set();
48114
48963
  if (inventory.framework === "nextjs")
@@ -48149,7 +48998,7 @@ function runtimeContract(capabilities) {
48149
48998
  ...capabilities.mail ? ["OHMYHOST_MAIL_KEY"] : []
48150
48999
  ].sort()),
48151
49000
  packages: Object.freeze({
48152
- ...capabilities.auth ? { betterAuth: "1.7.1" } : {},
49001
+ ...capabilities.auth ? { betterAuth: MANAGED_BETTER_AUTH_VERSION } : {},
48153
49002
  // A database project was told to install pg, which cannot open a socket from a Worker at all,
48154
49003
  // and was never offered the package that actually holds the client it needs.
48155
49004
  ...capabilities.database || capabilities.storage || capabilities.mail ? { customerRuntime: CUSTOMER_RUNTIME_SOURCE } : {}
@@ -48157,7 +49006,7 @@ function runtimeContract(capabilities) {
48157
49006
  });
48158
49007
  }
48159
49008
  function viteCompanionScaffold(files, capabilities) {
48160
- const sourceEntryPoint = "src/ohmyhost/companion.ts";
49009
+ const sourceEntryPoint = VITE_COMPANION_ENTRY_PATH;
48161
49010
  const contract = runtimeContract(capabilities);
48162
49011
  return Object.freeze({
48163
49012
  schemaVersion: "ohmyhost.vite-api-companion/v1",
@@ -48189,7 +49038,7 @@ async function createConfiguration(root, configuration) {
48189
49038
  await handle?.close();
48190
49039
  }
48191
49040
  }
48192
- function isRecord4(value) {
49041
+ function isRecord5(value) {
48193
49042
  return typeof value === "object" && value !== null && !Array.isArray(value);
48194
49043
  }
48195
49044
  function isNodeError(value) {
@@ -48951,6 +49800,7 @@ var runOfflineInit = async (command, io, diagnostics) => {
48951
49800
  inventory: result.inventory,
48952
49801
  compatibility: result.compatibility,
48953
49802
  blockers: result.blockers,
49803
+ pending: result.pending,
48954
49804
  requirements: result.requirements,
48955
49805
  companion: result.companion,
48956
49806
  // The functions runtime reports its own bindings and packages; dropping it left a