@amerged/ohmyhost-cli 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @amerged/ohmyhost-cli
2
2
 
3
- ohmyho.st cli client, version 0.1.34.
3
+ ohmyho.st cli client, version 0.1.36.
4
4
 
5
5
  ```sh
6
6
  npm install --global @amerged/ohmyhost-cli
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __ohmyhostCreateRequire } from "node:module"; const require = __ohmyhostCreateRequire(import.meta.url);
3
3
  import {
4
4
  runProductionCli
5
- } from "./chunk-SRN6L765.js";
5
+ } from "./chunk-LU7ONYZG.js";
6
6
  import "./chunk-7D2RFLWM.js";
7
7
 
8
8
  // apps/product-cli/dist/bin.js
@@ -34462,6 +34462,11 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
34462
34462
  // packages/contracts/dist/framework-build-script.mjs
34463
34463
  var PINNED_BUN_VERSION = "1.2.22";
34464
34464
  var FRAMEWORK_VERSION_POLICY = Object.freeze({
34465
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
34466
+ "@astrojs/cloudflare": Object.freeze({
34467
+ floors: Object.freeze(["14.4.0"]),
34468
+ denied: Object.freeze([])
34469
+ }),
34465
34470
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
34466
34471
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
34467
34472
  "@tanstack/react-start": Object.freeze({
@@ -34539,6 +34544,8 @@ var TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
34539
34544
  function isAdmittedFrameworkBuildScript(framework, script) {
34540
34545
  if (typeof script !== "string")
34541
34546
  return false;
34547
+ if (framework === "astro")
34548
+ return script === "astro build";
34542
34549
  if (framework === "nextjs")
34543
34550
  return script === "next build" || script === "next build --webpack";
34544
34551
  if (framework !== "vite" && framework !== "tanstack-start")
@@ -34728,11 +34735,15 @@ function frameworkVersionDiagnostic(admission, lockfile) {
34728
34735
  });
34729
34736
  }
34730
34737
  var FRAMEWORK_VERSION_PACKAGES = Object.freeze([
34738
+ "astro",
34739
+ "@astrojs/cloudflare",
34731
34740
  "next",
34732
34741
  "vite",
34733
34742
  "@tanstack/react-start"
34734
34743
  ]);
34735
34744
  var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
34745
+ "astro",
34746
+ "@astrojs/cloudflare",
34736
34747
  "next",
34737
34748
  "react",
34738
34749
  "react-dom",
@@ -34743,10 +34754,23 @@ var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
34743
34754
  "@cloudflare/vite-plugin"
34744
34755
  ]);
34745
34756
  var REACT_PACKAGES = Object.freeze(["react", "react-dom"]);
34746
- var BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"]);
34757
+ var BUILD_FRAMEWORKS = Object.freeze([
34758
+ "astro",
34759
+ "nextjs",
34760
+ "vite-static",
34761
+ "tanstack-start"
34762
+ ]);
34747
34763
  var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
34748
34764
  var VITE_BUILD_SCRIPT = '"vite build", optionally joined with && to one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage';
34749
34765
  var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34766
+ astro_template_import_unsupported: {
34767
+ paths: ["module"],
34768
+ template: '"{{path}}" uses a dynamic import, import.meta or require in an Astro template. Move server imports into frontmatter and dynamic browser imports into a normal statically imported browser module, then commit and push the change and plan the new commit.'
34769
+ },
34770
+ astro_config_unsupported: {
34771
+ paths: ["framework_config"],
34772
+ template: '"{{path}}" must use the standard Astro output directories and root base. For edge use output: "server", @astrojs/cloudflare with imageService: "passthrough", and session: false. Static hosting must omit a server adapter. Additional Cloudflare bindings, custom sessions and provider cache are not supported. Commit and push the change, then plan the new commit.'
34773
+ },
34750
34774
  managed_auth_database_required: {
34751
34775
  paths: ["repository_config"],
34752
34776
  template: managedCustomerAuthDatabaseMessage("{{path}}")
@@ -34821,7 +34845,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34821
34845
  text: {
34822
34846
  nextjs: 'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
34823
34847
  "vite-static": VITE_BUILD_SCRIPT,
34824
- "tanstack-start": VITE_BUILD_SCRIPT
34848
+ "tanstack-start": VITE_BUILD_SCRIPT,
34849
+ astro: 'exactly "astro build"'
34825
34850
  }
34826
34851
  }
34827
34852
  },
@@ -34837,11 +34862,11 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34837
34862
  },
34838
34863
  framework_unsupported: {
34839
34864
  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}`
34865
+ template: `"{{path}}" declares none of astro, 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
34866
  },
34842
34867
  framework_ambiguous: {
34843
34868
  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.'
34869
+ template: '"{{path}}" declares competing application framework packages, 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
34870
  },
34846
34871
  lockfile_missing: {
34847
34872
  paths: ["manifest"],
@@ -34880,7 +34905,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34880
34905
  text: {
34881
34906
  nextjs: "runtime.mode: edge",
34882
34907
  "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"
34908
+ "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
34909
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter"
34884
34910
  }
34885
34911
  }
34886
34912
  },
@@ -34895,7 +34921,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34895
34921
  text: {
34896
34922
  nextjs: ".open-next/assets",
34897
34923
  "vite-static": "dist",
34898
- "tanstack-start": "dist/client"
34924
+ "tanstack-start": "dist/client",
34925
+ astro: "dist for static, or dist/client for edge"
34899
34926
  }
34900
34927
  }
34901
34928
  },
@@ -34954,8 +34981,8 @@ var DETAIL_DEFINITIONS = FRAMEWORK_CONVERSION_DETAILS;
34954
34981
  var PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
34955
34982
  var PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
34956
34983
  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;
34984
+ var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
34985
+ var FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
34959
34986
  var MAXIMUM_DETAIL_LENGTH = 2e3;
34960
34987
  var DETAIL_PATTERNS = Object.freeze(Object.entries(DETAIL_DEFINITIONS).map(([code, definition]) => Object.freeze({
34961
34988
  code,
@@ -35168,6 +35195,10 @@ var failures = {
35168
35195
  "The organization does not have sufficient available credits for this operation.",
35169
35196
  "Ask the organization owner to top up credits, then create a new deployment plan. Preserve existing data; do not buy a second subscription or repeatedly reconcile this terminal operation."
35170
35197
  ],
35198
+ project_budget_exceeded: [
35199
+ "The project's Stop budget prevents this deployment.",
35200
+ "This project's Stop budget for the current UTC month was reached; no build started. Read 'ohmyhost budget get --project ULID --json'. Ask the organization Owner to change the limit or mode only if they want to allow more spending; otherwise wait for the next UTC month. Then plan and deploy again or send a new push. Replaying this terminal operation cannot start a build."
35201
+ ],
35171
35202
  paid_plan_required: [
35172
35203
  "Transactional mail requires an active Paid plan for this organization.",
35173
35204
  "Ask the organization Owner to activate Paid through billing checkout; top-ups and signup credits do not activate it. Then plan the same deployment or promotion again. If the app should send no mail, instead set mail.enabled to false in ohmyhost.yaml, save or push the corrected version and plan its commit. Replaying this operation cannot enable mail. Do not reset the database."
@@ -40639,7 +40670,7 @@ function exact3(v, keys) {
40639
40670
  // apps/product-cli/package.json
40640
40671
  var package_default = {
40641
40672
  name: "@ohmyhost/product-cli",
40642
- version: "0.1.34",
40673
+ version: "0.1.36",
40643
40674
  private: true,
40644
40675
  ohmyhost: {
40645
40676
  deployment: "production",
@@ -47147,9 +47178,14 @@ var import_yaml2 = __toESM(require_dist(), 1);
47147
47178
 
47148
47179
  // packages/contracts/dist/framework-family.js
47149
47180
  function classifyFrameworkFamily(dependencies) {
47181
+ const astro = dependencies.has("astro");
47150
47182
  const next = dependencies.has("next");
47151
47183
  const tanStack = dependencies.has("@tanstack/react-start");
47152
47184
  const vite = dependencies.has("vite");
47185
+ if (astro && (next || tanStack))
47186
+ return "ambiguous";
47187
+ if (astro)
47188
+ return "astro";
47153
47189
  if (next && (tanStack || vite))
47154
47190
  return "ambiguous";
47155
47191
  if (next)
@@ -47175,6 +47211,7 @@ var DEPENDENCY_FIELDS = Object.freeze({
47175
47211
  any: Object.freeze(["dependencies", "devDependencies", "optionalDependencies"])
47176
47212
  });
47177
47213
  var REQUIRED_DEPENDENCIES = Object.freeze({
47214
+ astro: Object.freeze([["astro", "build"]]),
47178
47215
  nextjs: Object.freeze([
47179
47216
  ["next", "runtime"],
47180
47217
  ["react", "runtime"],
@@ -47193,6 +47230,7 @@ var REQUIRED_DEPENDENCIES = Object.freeze({
47193
47230
  });
47194
47231
  var TANSTACK_EDGE_DEPENDENCY = Object.freeze([CLOUDFLARE_PLUGIN, "build"]);
47195
47232
  var BUILD_SCRIPT_FRAMEWORKS = Object.freeze({
47233
+ astro: "astro",
47196
47234
  nextjs: "nextjs",
47197
47235
  "vite-static": "vite",
47198
47236
  "tanstack-start": "tanstack-start"
@@ -47201,7 +47239,11 @@ var BUILD_OUTPUTS = Object.freeze({
47201
47239
  "vite-static": "dist",
47202
47240
  "tanstack-start": "dist/client"
47203
47241
  });
47204
- var REQUIRED_CONFIG_PATHS = Object.freeze({ nextjs: "next.config.ts", "tanstack-start": "vite.config.ts" });
47242
+ var REQUIRED_CONFIG_PATHS = Object.freeze({
47243
+ astro: "astro.config.mjs",
47244
+ nextjs: "next.config.ts",
47245
+ "tanstack-start": "vite.config.ts"
47246
+ });
47205
47247
  var POSTCSS_CONFIG_PATH = /^(?:postcss\.config\.(?:[cm]?js|json)|\.postcssrc\.(?:js|json))$/u;
47206
47248
  var TAILWIND_TURBOPACK_LOADER = "@tailwindcss/turbopack";
47207
47249
  var MAXIMUM_LOCKFILE_LENGTH = 5 * 1024 * 1024;
@@ -47263,7 +47305,7 @@ function viteCompanionRequired(configuration) {
47263
47305
  function frameworkVersionAdmissions(framework, manifest2, lockfile) {
47264
47306
  const admissions = [];
47265
47307
  let locked;
47266
- for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
47308
+ for (const [name, placement] of framework === "astro" && declaredDependency(manifest2, "@astrojs/cloudflare", "build") !== null ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"]] : REQUIRED_DEPENDENCIES[framework]) {
47267
47309
  if (!isFrameworkVersionPackage(name))
47268
47310
  continue;
47269
47311
  const declared = declaredDependency(manifest2, name, placement);
@@ -47451,14 +47493,14 @@ function sourceWithoutComments(source) {
47451
47493
  return result;
47452
47494
  }
47453
47495
  function isDatabaseContractModule(path2) {
47454
- return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47496
+ return /\.(?:astro|ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47455
47497
  }
47456
47498
  function manifestFindings(input, versions) {
47457
47499
  const { configuration, framework, manifest: manifest2 } = input;
47458
47500
  const findings = [];
47459
47501
  if (framework !== "functions" && !isAdmittedFrameworkBuildScript(BUILD_SCRIPT_FRAMEWORKS[framework], ownValue(manifest2["scripts"], "build")))
47460
47502
  findings.push(diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST2, framework }));
47461
- const required = framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47503
+ const required = framework === "astro" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"]] : framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47462
47504
  for (const [name, placement] of required)
47463
47505
  if (declaredDependency(manifest2, name, placement) === null)
47464
47506
  findings.push(diagnostic({
@@ -47492,14 +47534,14 @@ function configurationFindings(input) {
47492
47534
  findings.push(diagnostic({ code: "runtime_mode_unsupported", path: REPOSITORY_CONFIG, framework }));
47493
47535
  if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== void 0)
47494
47536
  findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
47495
- const output = BUILD_OUTPUTS[framework];
47537
+ const output = framework === "astro" ? configuration.runtime.mode === "edge" ? "dist/client" : "dist" : BUILD_OUTPUTS[framework];
47496
47538
  if (build !== null && output !== void 0 && build.output !== output)
47497
47539
  findings.push(diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }));
47498
47540
  if (packageManager !== null && framework !== "nextjs" && (configuration.build.install !== packageManager.installCommand || build !== null && build.command !== packageManager.buildCommand))
47499
47541
  findings.push(diagnostic({ code: "build_command_mismatch", path: REPOSITORY_CONFIG }));
47500
47542
  if (framework === "vite-static" && viteCompanionRequired(configuration) && !files.includes(VITE_COMPANION_ENTRY_PATH))
47501
47543
  findings.push(diagnostic({ code: "companion_entry_required", path: VITE_COMPANION_ENTRY_PATH }));
47502
- if ((framework === "functions" || crons && (framework === "nextjs" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47544
+ if ((framework === "functions" || crons && (framework === "nextjs" || framework === "astro" && configuration.runtime.mode === "edge" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47503
47545
  findings.push(diagnostic({ code: "worker_module_required", path: WORKER_MODULE_PATH }));
47504
47546
  if (managedCustomerAuthAdmissionIssue(configuration) !== null)
47505
47547
  findings.push(diagnostic({ code: "managed_auth_database_required", path: REPOSITORY_CONFIG }));
@@ -47507,6 +47549,8 @@ function configurationFindings(input) {
47507
47549
  }
47508
47550
  function runtimeModeAdmitted(framework, configuration, crons) {
47509
47551
  const mode = configuration.runtime.mode;
47552
+ if (framework === "astro")
47553
+ return mode === "edge" || mode === "static" && !crons && configuration.storage === void 0 && configuration.database?.enabled !== true && configuration.mail?.enabled !== true && configuration.auth?.provider !== "better-auth";
47510
47554
  if (framework === "nextjs")
47511
47555
  return mode === "edge";
47512
47556
  if (framework === "vite-static")
@@ -47521,6 +47565,8 @@ function frameworkConfigFindings(input) {
47521
47565
  if (frameworkConfig === null || framework === "functions")
47522
47566
  return [];
47523
47567
  const { path: path2, text } = frameworkConfig;
47568
+ if (framework === "astro")
47569
+ return astroConfigFindings(text, path2, input.configuration.runtime.mode);
47524
47570
  if (framework === "nextjs")
47525
47571
  return nextConfigFindings(text, path2, hasPostcssConfiguration(input));
47526
47572
  if (framework === "vite-static")
@@ -47545,7 +47591,7 @@ function repositoryConfiguration(text) {
47545
47591
  function frameworkConfigPath(framework, files, findings) {
47546
47592
  if (framework === "functions")
47547
47593
  return null;
47548
- const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files));
47594
+ const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "astro" ? "astro.config" : framework === "nextjs" ? "next.config" : "vite.config", files));
47549
47595
  if (framework === "nextjs")
47550
47596
  admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
47551
47597
  const required = REQUIRED_CONFIG_PATHS[framework];
@@ -47847,6 +47893,15 @@ function hasNamedImport(tokens, name, moduleName) {
47847
47893
  function hasModuleImport(tokens, moduleName) {
47848
47894
  return tokens.some((token, index) => token.value === "from" && tokens[index + 1]?.kind === "string" && tokens[index + 1]?.value === moduleName);
47849
47895
  }
47896
+ function astroConfigFindings(text, path2, mode) {
47897
+ const lexed = lexFrameworkConfig(text, path2);
47898
+ if ("finding" in lexed)
47899
+ return [lexed.finding];
47900
+ const tokens = lexed.tokens;
47901
+ const hasPropertyKey = (items, name) => items.some((token, index) => token.value === name && items[index + 1]?.value === ":");
47902
+ const invalid6 = hasNonRootProperty(tokens, ["base"], ["", "/"]) || ["srcDir", "outDir", "serverEntry", "client", "server", "assetsPrefix"].some((name) => hasPropertyKey(tokens, name)) || (mode === "edge" ? !text.includes("@astrojs/cloudflare") || !hasProperty(tokens, "output", "server") || !hasProperty(tokens, "session", "false") || !hasProperty(tokens, "imageService", "passthrough") || hasPropertyKey(tokens, "cache") : text.includes("@astrojs/cloudflare") || hasPropertyKey(tokens, "adapter") || hasProperty(tokens, "output", "server"));
47903
+ return invalid6 ? [diagnostic({ code: "astro_config_unsupported", path: path2 })] : [];
47904
+ }
47850
47905
 
47851
47906
  // packages/contracts/dist/database-migration-errors.js
47852
47907
  var DatabaseMigrationAdmissionError = class extends Error {
@@ -48299,7 +48354,7 @@ async function initializeRepository(input) {
48299
48354
  const match = path2.match(/^supabase\/functions\/([^/]+)\//u);
48300
48355
  return match?.[1] === void 0 || match[1].startsWith("_") ? [] : [match[1]];
48301
48356
  }));
48302
- const apiRouteCount = applicationFiles.filter((path2) => /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path2)).length;
48357
+ const apiRouteCount = applicationFiles.filter((path2) => /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path2) || framework === "astro" && /^src\/pages\/.+\.(?:js|ts)$/u.test(path2) && !path2.endsWith(".d.ts")).length;
48303
48358
  const supabase = dependencies.has("@supabase/supabase-js") || dependencies.has("@supabase/ssr");
48304
48359
  const configuredDatabase = existingConfiguration?.database?.enabled === true;
48305
48360
  const databaseProvider = supabaseMigrationCount > 0 ? "supabase" : postgresMigrationCount > 0 || configuredDatabase ? "postgresql" : "none";
@@ -48339,14 +48394,14 @@ async function initializeRepository(input) {
48339
48394
  const targetBetterAuth = existingConfiguration?.auth?.provider === "better-auth";
48340
48395
  const targetMail = existingConfiguration?.mail?.enabled === true;
48341
48396
  const targetStorage = r2 || supabaseStorage;
48342
- const tanStackRuntime = framework !== "tanstack-start" ? null : targetStorage ? "edge" : proposedTanStackRuntime(frameworkConfig, dependencies);
48397
+ const tanStackRuntime = framework !== "tanstack-start" && framework !== "astro" ? null : targetStorage ? "edge" : framework === "astro" ? dependencies.has("@astrojs/cloudflare") ? "edge" : "static" : proposedTanStackRuntime(frameworkConfig, dependencies);
48343
48398
  const companionRequired = framework === "vite" && (existingConfiguration === null ? databaseProvider !== "none" || targetBetterAuth || targetMail || targetStorage || edgeFunctionNames.size > 0 || applicationFiles.includes(VITE_COMPANION_ENTRY_PATH) : viteCompanionRequired(existingConfiguration));
48344
48399
  const generatedConfigurationYaml = renderConfiguration({
48345
48400
  project,
48346
48401
  framework,
48347
48402
  packageManager,
48348
48403
  databaseProvider,
48349
- hasHealthcheck: applicationFiles.includes("src/app/api/health/route.ts") || applicationFiles.includes("app/api/health/route.ts"),
48404
+ hasHealthcheck: applicationFiles.includes("src/app/api/health/route.ts") || applicationFiles.includes("app/api/health/route.ts") || framework === "astro" && applicationFiles.some((path2) => /^src\/pages\/api\/health(?:\/index)?\.(?:js|ts)$/u.test(path2)),
48350
48405
  mailEnabled: targetMail,
48351
48406
  betterAuthEnabled: targetBetterAuth,
48352
48407
  storageEnabled: targetStorage,
@@ -48464,7 +48519,7 @@ function requiredCatalogBlocker(diagnostic2, applicationRoot) {
48464
48519
  async function workerModuleBlockers(context) {
48465
48520
  const { config, framework, files } = context;
48466
48521
  const mode = config?.runtime.mode;
48467
- const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48522
+ const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "astro" || framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48468
48523
  const path2 = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
48469
48524
  if (framework !== "functions" && !scheduled || !files.includes(path2))
48470
48525
  return [];
@@ -48737,7 +48792,7 @@ function hasDirectSafeBuildCommand(manifest2, framework) {
48737
48792
  const scripts = manifest2["scripts"];
48738
48793
  if (!isRecord5(scripts) || typeof scripts["build"] !== "string")
48739
48794
  return false;
48740
- return (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
48795
+ return (framework === "astro" || framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
48741
48796
  }
48742
48797
  async function assertRepositoryRoot(root) {
48743
48798
  let information;
@@ -48843,11 +48898,11 @@ async function readResolvingLockfile(applicationDirectory, admission) {
48843
48898
  return text === null ? null : Object.freeze({ name: admission.lockfile, text });
48844
48899
  }
48845
48900
  async function readFrameworkConfig(applicationDirectory, files, framework) {
48846
- if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48901
+ if (framework !== "astro" && framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48847
48902
  return null;
48848
48903
  let path2;
48849
48904
  try {
48850
- path2 = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
48905
+ path2 = resolveFrameworkConfig(framework === "astro" ? "astro.config" : framework === "nextjs" ? "next.config" : "vite.config", files);
48851
48906
  } catch (error) {
48852
48907
  if (error instanceof FrameworkAdmissionError)
48853
48908
  return null;
@@ -48913,7 +48968,7 @@ function renderConfiguration(input) {
48913
48968
  let output = "dist";
48914
48969
  if (input.framework === "nextjs")
48915
48970
  output = ".open-next/assets";
48916
- else if (input.framework === "tanstack-start")
48971
+ else if (input.framework === "tanstack-start" || input.framework === "astro" && input.tanStackRuntime === "edge")
48917
48972
  output = "dist/client";
48918
48973
  let mode = "auto";
48919
48974
  if (input.framework === "functions")
package/dist/index.js CHANGED
@@ -99,7 +99,7 @@ import {
99
99
  selectWorkspace,
100
100
  verifiedUserId,
101
101
  workspaceOf
102
- } from "./chunk-SRN6L765.js";
102
+ } from "./chunk-LU7ONYZG.js";
103
103
  import {
104
104
  CredentialStoreIntegrityError,
105
105
  CredentialStoreUnavailableError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -310,8 +310,12 @@ export async function initializeRepository(
310
310
  return match?.[1] === undefined || match[1].startsWith("_") ? [] : [match[1]];
311
311
  }),
312
312
  );
313
- const apiRouteCount = applicationFiles.filter((path) =>
314
- /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path),
313
+ const apiRouteCount = applicationFiles.filter(
314
+ (path) =>
315
+ /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path) ||
316
+ (framework === "astro" &&
317
+ /^src\/pages\/.+\.(?:js|ts)$/u.test(path) &&
318
+ !path.endsWith(".d.ts")),
315
319
  ).length;
316
320
  const supabase = dependencies.has("@supabase/supabase-js") || dependencies.has("@supabase/ssr");
317
321
  const configuredDatabase = existingConfiguration?.database?.enabled === true;
@@ -368,11 +372,15 @@ export async function initializeRepository(
368
372
  const targetStorage = r2 || supabaseStorage;
369
373
  // TanStack Start binds storage only on edge, as a Vite app does through its companion.
370
374
  const tanStackRuntime =
371
- framework !== "tanstack-start"
375
+ framework !== "tanstack-start" && framework !== "astro"
372
376
  ? null
373
377
  : targetStorage
374
378
  ? "edge"
375
- : proposedTanStackRuntime(frameworkConfig, dependencies);
379
+ : framework === "astro"
380
+ ? dependencies.has("@astrojs/cloudflare")
381
+ ? "edge"
382
+ : "static"
383
+ : proposedTanStackRuntime(frameworkConfig, dependencies);
376
384
  const companionRequired =
377
385
  framework === "vite" &&
378
386
  (existingConfiguration === null
@@ -390,7 +398,11 @@ export async function initializeRepository(
390
398
  databaseProvider,
391
399
  hasHealthcheck:
392
400
  applicationFiles.includes("src/app/api/health/route.ts") ||
393
- applicationFiles.includes("app/api/health/route.ts"),
401
+ applicationFiles.includes("app/api/health/route.ts") ||
402
+ (framework === "astro" &&
403
+ applicationFiles.some((path) =>
404
+ /^src\/pages\/api\/health(?:\/index)?\.(?:js|ts)$/u.test(path),
405
+ )),
394
406
  mailEnabled: targetMail,
395
407
  betterAuthEnabled: targetBetterAuth,
396
408
  storageEnabled: targetStorage,
@@ -596,7 +608,10 @@ async function workerModuleBlockers(context: SourceCheckContext): Promise<Reposi
596
608
  const scheduled =
597
609
  (config?.functions?.crons.length ?? 0) > 0 &&
598
610
  ((mode === "edge" &&
599
- (framework === "vite" || framework === "nextjs" || framework === "tanstack-start")) ||
611
+ (framework === "astro" ||
612
+ framework === "vite" ||
613
+ framework === "nextjs" ||
614
+ framework === "tanstack-start")) ||
600
615
  (mode === "functions" && framework === "functions"));
601
616
  const path = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
602
617
  if ((framework !== "functions" && !scheduled) || !files.includes(path)) return [];
@@ -989,7 +1004,10 @@ function hasDirectSafeBuildCommand(
989
1004
  const scripts = manifest["scripts"];
990
1005
  if (!isRecord(scripts) || typeof scripts["build"] !== "string") return false;
991
1006
  return (
992
- (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") &&
1007
+ (framework === "astro" ||
1008
+ framework === "nextjs" ||
1009
+ framework === "vite" ||
1010
+ framework === "tanstack-start") &&
993
1011
  isAdmittedFrameworkBuildScript(framework, scripts["build"])
994
1012
  );
995
1013
  }
@@ -1144,10 +1162,23 @@ async function readFrameworkConfig(
1144
1162
  files: readonly string[],
1145
1163
  framework: RepositoryFramework,
1146
1164
  ): Promise<FrameworkConfigText | null> {
1147
- if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start") return null;
1165
+ if (
1166
+ framework !== "astro" &&
1167
+ framework !== "nextjs" &&
1168
+ framework !== "vite" &&
1169
+ framework !== "tanstack-start"
1170
+ )
1171
+ return null;
1148
1172
  let path: string | null;
1149
1173
  try {
1150
- path = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
1174
+ path = resolveFrameworkConfig(
1175
+ framework === "astro"
1176
+ ? "astro.config"
1177
+ : framework === "nextjs"
1178
+ ? "next.config"
1179
+ : "vite.config",
1180
+ files,
1181
+ );
1151
1182
  } catch (error) {
1152
1183
  // The shared rules name the ambiguous variants; none of them is judged.
1153
1184
  if (error instanceof FrameworkAdmissionError) return null;
@@ -1266,7 +1297,11 @@ function renderConfiguration(input: {
1266
1297
 
1267
1298
  let output = "dist";
1268
1299
  if (input.framework === "nextjs") output = ".open-next/assets";
1269
- else if (input.framework === "tanstack-start") output = "dist/client";
1300
+ else if (
1301
+ input.framework === "tanstack-start" ||
1302
+ (input.framework === "astro" && input.tanStackRuntime === "edge")
1303
+ )
1304
+ output = "dist/client";
1270
1305
 
1271
1306
  let mode = "auto";
1272
1307
  if (input.framework === "functions") mode = "functions";
@@ -19,7 +19,11 @@ export type LockfileName =
19
19
  | "package-lock.json"
20
20
  | "pnpm-lock.yaml"
21
21
  | "yarn.lock";
22
- export type FrameworkConfigBasename = "next.config" | "open-next.config" | "vite.config";
22
+ export type FrameworkConfigBasename =
23
+ | "astro.config"
24
+ | "next.config"
25
+ | "open-next.config"
26
+ | "vite.config";
23
27
 
24
28
  export type FrameworkAdmissionReason =
25
29
  | "ambiguous_config"
@@ -311,11 +315,15 @@ interface DetailDefinition {
311
315
  }
312
316
 
313
317
  const FRAMEWORK_VERSION_PACKAGES = Object.freeze([
318
+ "astro",
319
+ "@astrojs/cloudflare",
314
320
  "next",
315
321
  "vite",
316
322
  "@tanstack/react-start",
317
323
  ] as const satisfies readonly FrameworkVersionPackage[]);
318
324
  const FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
325
+ "astro",
326
+ "@astrojs/cloudflare",
319
327
  "next",
320
328
  "react",
321
329
  "react-dom",
@@ -326,7 +334,12 @@ const FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
326
334
  "@cloudflare/vite-plugin",
327
335
  ] as const);
328
336
  const REACT_PACKAGES = Object.freeze(["react", "react-dom"] as const);
329
- const BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"] as const);
337
+ const BUILD_FRAMEWORKS = Object.freeze([
338
+ "astro",
339
+ "nextjs",
340
+ "vite-static",
341
+ "tanstack-start",
342
+ ] as const);
330
343
 
331
344
  const PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
332
345
  const VITE_BUILD_SCRIPT =
@@ -335,6 +348,16 @@ const VITE_BUILD_SCRIPT =
335
348
  // Each sentence names the file, the rule and the exact change. Render and parse both derive from
336
349
  // the template, so a client maps a detail back to exactly one typed diagnostic.
337
350
  const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
351
+ astro_template_import_unsupported: {
352
+ paths: ["module"],
353
+ template:
354
+ '"{{path}}" uses a dynamic import, import.meta or require in an Astro template. Move server imports into frontmatter and dynamic browser imports into a normal statically imported browser module, then commit and push the change and plan the new commit.',
355
+ },
356
+ astro_config_unsupported: {
357
+ paths: ["framework_config"],
358
+ template:
359
+ '"{{path}}" must use the standard Astro output directories and root base. For edge use output: "server", @astrojs/cloudflare with imageService: "passthrough", and session: false. Static hosting must omit a server adapter. Additional Cloudflare bindings, custom sessions and provider cache are not supported. Commit and push the change, then plan the new commit.',
360
+ },
338
361
  managed_auth_database_required: {
339
362
  paths: ["repository_config"],
340
363
  template: managedCustomerAuthDatabaseMessage("{{path}}"),
@@ -417,6 +440,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
417
440
  'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
418
441
  "vite-static": VITE_BUILD_SCRIPT,
419
442
  "tanstack-start": VITE_BUILD_SCRIPT,
443
+ astro: 'exactly "astro build"',
420
444
  },
421
445
  },
422
446
  },
@@ -435,12 +459,12 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
435
459
  },
436
460
  framework_unsupported: {
437
461
  paths: ["manifest"],
438
- 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}`,
462
+ template: `"{{path}}" declares none of astro, 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}`,
439
463
  },
440
464
  framework_ambiguous: {
441
465
  paths: ["manifest"],
442
466
  template:
443
- '"{{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.',
467
+ '"{{path}}" declares competing application framework packages, 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.',
444
468
  },
445
469
  lockfile_missing: {
446
470
  paths: ["manifest"],
@@ -487,6 +511,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
487
511
  "vite-static":
488
512
  "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
489
513
  "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
514
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter",
490
515
  },
491
516
  },
492
517
  },
@@ -502,6 +527,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
502
527
  nextjs: ".open-next/assets",
503
528
  "vite-static": "dist",
504
529
  "tanstack-start": "dist/client",
530
+ astro: "dist for static, or dist/client for edge",
505
531
  },
506
532
  },
507
533
  },
@@ -585,8 +611,8 @@ const DETAIL_DEFINITIONS: Readonly<Record<FrameworkConversionDiagnosticCode, Det
585
611
  const PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
586
612
  const PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
587
613
  const SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
588
- const MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
589
- const FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
614
+ const MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
615
+ const FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
590
616
  // The public problem detail bound; every rendered sentence stays far below it.
591
617
  const MAXIMUM_DETAIL_LENGTH = 2_000;
592
618
  const DETAIL_PATTERNS = Object.freeze(
@@ -1,7 +1,7 @@
1
1
  export const PINNED_BUN_VERSION = "1.2.22";
2
2
 
3
3
  /**
4
- * @typedef {"next" | "vite" | "@tanstack/react-start"} FrameworkVersionPackage
4
+ * @typedef {"next" | "vite" | "astro" | "@astrojs/cloudflare" | "@tanstack/react-start"} FrameworkVersionPackage
5
5
  * @typedef {Readonly<{
6
6
  * floors: readonly string[];
7
7
  * denied: readonly (readonly [string, string | null])[];
@@ -22,6 +22,11 @@ export const PINNED_BUN_VERSION = "1.2.22";
22
22
  * @type {FrameworkVersionPolicy}
23
23
  */
24
24
  export const FRAMEWORK_VERSION_POLICY = Object.freeze({
25
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
26
+ "@astrojs/cloudflare": Object.freeze({
27
+ floors: Object.freeze(["14.4.0"]),
28
+ denied: Object.freeze([]),
29
+ }),
25
30
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
26
31
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
27
32
  "@tanstack/react-start": Object.freeze({
@@ -164,12 +169,13 @@ const TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
164
169
 
165
170
  /**
166
171
  * The same source is consumed by local admission, framework adapters and the build sandbox.
167
- * @param {"nextjs" | "tanstack-start" | "vite"} framework
172
+ * @param {"astro" | "nextjs" | "tanstack-start" | "vite"} framework
168
173
  * @param {unknown} script
169
174
  * @returns {boolean}
170
175
  */
171
176
  export function isAdmittedFrameworkBuildScript(framework, script) {
172
177
  if (typeof script !== "string") return false;
178
+ if (framework === "astro") return script === "astro build";
173
179
  if (framework === "nextjs") return script === "next build" || script === "next build --webpack";
174
180
  if (framework !== "vite" && framework !== "tanstack-start") return false;
175
181
  const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
@@ -1,4 +1,5 @@
1
1
  export type FrameworkFamilyClassification =
2
+ | "astro"
2
3
  | "ambiguous"
3
4
  | "nextjs"
4
5
  | "tanstack-start"
@@ -8,9 +9,12 @@ export type FrameworkFamilyClassification =
8
9
  export function classifyFrameworkFamily(
9
10
  dependencies: ReadonlySet<string>,
10
11
  ): FrameworkFamilyClassification {
12
+ const astro = dependencies.has("astro");
11
13
  const next = dependencies.has("next");
12
14
  const tanStack = dependencies.has("@tanstack/react-start");
13
15
  const vite = dependencies.has("vite");
16
+ if (astro && (next || tanStack)) return "ambiguous";
17
+ if (astro) return "astro";
14
18
  if (next && (tanStack || vite)) return "ambiguous";
15
19
  if (next) return "nextjs";
16
20
  if (tanStack) return "tanstack-start";
@@ -40,7 +40,12 @@ export const WORKER_MODULE_PATH = "src/ohmyhost/worker.ts" as const;
40
40
  /** The Worker module of a Vite application with a server runtime. */
41
41
  export const VITE_COMPANION_ENTRY_PATH = "src/ohmyhost/companion.ts" as const;
42
42
 
43
- export type FrameworkSourceFramework = "functions" | "nextjs" | "tanstack-start" | "vite-static";
43
+ export type FrameworkSourceFramework =
44
+ | "astro"
45
+ | "functions"
46
+ | "nextjs"
47
+ | "tanstack-start"
48
+ | "vite-static";
44
49
  export type PackageManifest = Readonly<Record<string, unknown>>;
45
50
  /**
46
51
  * Where package.json may declare a dependency: `runtime` only in dependencies, `build` also in
@@ -91,6 +96,7 @@ const DEPENDENCY_FIELDS = Object.freeze({
91
96
  // Next.js and TanStack Start load their runtime packages in the server bundle, so package.json
92
97
  // declares them in dependencies; Vite and its plugins only build, so devDependencies suffice.
93
98
  const REQUIRED_DEPENDENCIES = Object.freeze({
99
+ astro: Object.freeze([["astro", "build"]] as const),
94
100
  nextjs: Object.freeze([
95
101
  ["next", "runtime"],
96
102
  ["react", "runtime"],
@@ -109,6 +115,7 @@ const REQUIRED_DEPENDENCIES = Object.freeze({
109
115
  } satisfies Record<FrameworkSourceFramework, readonly (readonly [string, DependencyPlacement])[]>);
110
116
  const TANSTACK_EDGE_DEPENDENCY = Object.freeze([CLOUDFLARE_PLUGIN, "build"] as const);
111
117
  const BUILD_SCRIPT_FRAMEWORKS = Object.freeze({
118
+ astro: "astro",
112
119
  nextjs: "nextjs",
113
120
  "vite-static": "vite",
114
121
  "tanstack-start": "tanstack-start",
@@ -118,7 +125,11 @@ const BUILD_OUTPUTS: Readonly<Partial<Record<FrameworkSourceFramework, string>>>
118
125
  "tanstack-start": "dist/client",
119
126
  });
120
127
  const REQUIRED_CONFIG_PATHS: Readonly<Partial<Record<FrameworkSourceFramework, string>>> =
121
- Object.freeze({ nextjs: "next.config.ts", "tanstack-start": "vite.config.ts" });
128
+ Object.freeze({
129
+ astro: "astro.config.mjs",
130
+ nextjs: "next.config.ts",
131
+ "tanstack-start": "vite.config.ts",
132
+ });
122
133
  // The PostCSS configuration the Next.js Webpack build reads: package.json "postcss" or one of these
123
134
  // files at the application root. It never reads a TypeScript one such as postcss.config.ts.
124
135
  const POSTCSS_CONFIG_PATH = /^(?:postcss\.config\.(?:[cm]?js|json)|\.postcssrc\.(?:js|json))$/u;
@@ -214,7 +225,10 @@ export function frameworkVersionAdmissions(
214
225
  ): readonly FrameworkVersionAdmission[] {
215
226
  const admissions: FrameworkVersionAdmission[] = [];
216
227
  let locked: ((packageName: string) => string | null) | undefined;
217
- for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
228
+ for (const [name, placement] of framework === "astro" &&
229
+ declaredDependency(manifest, "@astrojs/cloudflare", "build") !== null
230
+ ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"] as const]
231
+ : REQUIRED_DEPENDENCIES[framework]) {
218
232
  if (!isFrameworkVersionPackage(name)) continue;
219
233
  const declared = declaredDependency(manifest, name, placement);
220
234
  if (declared === null) continue;
@@ -504,7 +518,7 @@ export function sourceWithoutComments(source: string): string {
504
518
  * extensions planning reads, without TypeScript declaration files, which never run.
505
519
  */
506
520
  export function isDatabaseContractModule(path: string): boolean {
507
- return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path) && !/\.d\.[cm]?ts$/u.test(path);
521
+ return /\.(?:astro|ts|tsx|js|jsx|mjs|mts)$/u.test(path) && !/\.d\.[cm]?ts$/u.test(path);
508
522
  }
509
523
 
510
524
  function manifestFindings(
@@ -524,9 +538,11 @@ function manifestFindings(
524
538
  diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST, framework }),
525
539
  );
526
540
  const required =
527
- framework === "tanstack-start" && configuration.runtime.mode === "edge"
528
- ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY]
529
- : REQUIRED_DEPENDENCIES[framework];
541
+ framework === "astro" && configuration.runtime.mode === "edge"
542
+ ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"] as const]
543
+ : framework === "tanstack-start" && configuration.runtime.mode === "edge"
544
+ ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY]
545
+ : REQUIRED_DEPENDENCIES[framework];
530
546
  for (const [name, placement] of required)
531
547
  if (declaredDependency(manifest, name, placement) === null)
532
548
  findings.push(
@@ -567,7 +583,12 @@ function configurationFindings(input: FrameworkSourceRuleInput): FrameworkConver
567
583
  );
568
584
  if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== undefined)
569
585
  findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
570
- const output = BUILD_OUTPUTS[framework];
586
+ const output =
587
+ framework === "astro"
588
+ ? configuration.runtime.mode === "edge"
589
+ ? "dist/client"
590
+ : "dist"
591
+ : BUILD_OUTPUTS[framework];
571
592
  if (build !== null && output !== undefined && build.output !== output)
572
593
  findings.push(
573
594
  diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }),
@@ -592,6 +613,7 @@ function configurationFindings(input: FrameworkSourceRuleInput): FrameworkConver
592
613
  (framework === "functions" ||
593
614
  (crons &&
594
615
  (framework === "nextjs" ||
616
+ (framework === "astro" && configuration.runtime.mode === "edge") ||
595
617
  (framework === "tanstack-start" && configuration.runtime.mode === "edge")))) &&
596
618
  !files.includes(WORKER_MODULE_PATH)
597
619
  )
@@ -607,6 +629,16 @@ function runtimeModeAdmitted(
607
629
  crons: boolean,
608
630
  ): boolean {
609
631
  const mode = configuration.runtime.mode;
632
+ if (framework === "astro")
633
+ return (
634
+ mode === "edge" ||
635
+ (mode === "static" &&
636
+ !crons &&
637
+ configuration.storage === undefined &&
638
+ configuration.database?.enabled !== true &&
639
+ configuration.mail?.enabled !== true &&
640
+ configuration.auth?.provider !== "better-auth")
641
+ );
610
642
  if (framework === "nextjs") return mode === "edge";
611
643
  if (framework === "vite-static")
612
644
  return mode === (viteCompanionRequired(configuration) ? "edge" : "static");
@@ -629,6 +661,8 @@ function frameworkConfigFindings(
629
661
  const { frameworkConfig, framework } = input;
630
662
  if (frameworkConfig === null || framework === "functions") return [];
631
663
  const { path, text } = frameworkConfig;
664
+ if (framework === "astro")
665
+ return astroConfigFindings(text, path, input.configuration.runtime.mode);
632
666
  if (framework === "nextjs") return nextConfigFindings(text, path, hasPostcssConfiguration(input));
633
667
  if (framework === "vite-static") return viteConfigFindings(text, path);
634
668
  const mode = input.configuration.runtime.mode;
@@ -660,7 +694,14 @@ function frameworkConfigPath(
660
694
  ): string | null {
661
695
  if (framework === "functions") return null;
662
696
  const path = admitted(findings, () =>
663
- resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files),
697
+ resolveFrameworkConfig(
698
+ framework === "astro"
699
+ ? "astro.config"
700
+ : framework === "nextjs"
701
+ ? "next.config"
702
+ : "vite.config",
703
+ files,
704
+ ),
664
705
  );
665
706
  if (framework === "nextjs")
666
707
  admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
@@ -1116,3 +1157,31 @@ function hasModuleImport(tokens: readonly ConfigToken[], moduleName: string): bo
1116
1157
  tokens[index + 1]?.value === moduleName,
1117
1158
  );
1118
1159
  }
1160
+
1161
+ /** Astro's build validates executable config; admission rejects known incompatible contracts. */
1162
+ function astroConfigFindings(
1163
+ text: string,
1164
+ path: string,
1165
+ mode: string,
1166
+ ): readonly FrameworkConversionDiagnostic[] {
1167
+ const lexed = lexFrameworkConfig(text, path);
1168
+ if ("finding" in lexed) return [lexed.finding];
1169
+ const tokens = lexed.tokens;
1170
+ const hasPropertyKey = (items: readonly ConfigToken[], name: string) =>
1171
+ items.some((token, index) => token.value === name && items[index + 1]?.value === ":");
1172
+ const invalid =
1173
+ hasNonRootProperty(tokens, ["base"], ["", "/"]) ||
1174
+ ["srcDir", "outDir", "serverEntry", "client", "server", "assetsPrefix"].some((name) =>
1175
+ hasPropertyKey(tokens, name),
1176
+ ) ||
1177
+ (mode === "edge"
1178
+ ? !text.includes("@astrojs/cloudflare") ||
1179
+ !hasProperty(tokens, "output", "server") ||
1180
+ !hasProperty(tokens, "session", "false") ||
1181
+ !hasProperty(tokens, "imageService", "passthrough") ||
1182
+ hasPropertyKey(tokens, "cache")
1183
+ : text.includes("@astrojs/cloudflare") ||
1184
+ hasPropertyKey(tokens, "adapter") ||
1185
+ hasProperty(tokens, "output", "server"));
1186
+ return invalid ? [diagnostic({ code: "astro_config_unsupported", path })] : [];
1187
+ }
@@ -90,6 +90,10 @@ const failures = {
90
90
  "The organization does not have sufficient available credits for this operation.",
91
91
  "Ask the organization owner to top up credits, then create a new deployment plan. Preserve existing data; do not buy a second subscription or repeatedly reconcile this terminal operation.",
92
92
  ],
93
+ project_budget_exceeded: [
94
+ "The project's Stop budget prevents this deployment.",
95
+ "This project's Stop budget for the current UTC month was reached; no build started. Read 'ohmyhost budget get --project ULID --json'. Ask the organization Owner to change the limit or mode only if they want to allow more spending; otherwise wait for the next UTC month. Then plan and deploy again or send a new push. Replaying this terminal operation cannot start a build.",
96
+ ],
93
97
  paid_plan_required: [
94
98
  "Transactional mail requires an active Paid plan for this organization.",
95
99
  "Ask the organization Owner to activate Paid through billing checkout; top-ups and signup credits do not activate it. Then plan the same deployment or promotion again. If the app should send no mail, instead set mail.enabled to false in ohmyhost.yaml, save or push the corrected version and plan its commit. Replaying this operation cannot enable mail. Do not reset the database.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1485,6 +1485,7 @@ export type OperationFailure = {
1485
1485
  | "database_compute_rejected"
1486
1486
  | "database_migration_failed"
1487
1487
  | "insufficient_organization_credits"
1488
+ | "project_budget_exceeded"
1488
1489
  | "paid_plan_required"
1489
1490
  | "runtime_candidate_failed"
1490
1491
  | "runtime_candidate_rejected"