@amerged/ohmyhost-mcp 0.1.34 → 0.1.35

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-mcp
2
2
 
3
- Local MCP server of ohmyho.st, version 0.1.34. Sign-in and setting secrets need the CLI too:
3
+ Local MCP server of ohmyho.st, version 0.1.35. Sign-in and setting secrets need the CLI too:
4
4
 
5
5
  ```sh
6
6
  npm install --global @amerged/ohmyhost-cli @amerged/ohmyhost-mcp
@@ -42,7 +42,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
42
42
  // apps/mcp/package.json
43
43
  var package_default = {
44
44
  name: "@ohmyhost/mcp",
45
- version: "0.1.34",
45
+ version: "0.1.35",
46
46
  private: true,
47
47
  ohmyhost: {
48
48
  deployment: "production",
@@ -30615,7 +30615,7 @@ var GENERATED_SKILL_RESOURCES = Object.freeze([
30615
30615
  title: "ohmyhost-build-portable-app",
30616
30616
  description: "Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.",
30617
30617
  mimeType: "text/markdown",
30618
- text: "---\nname: ohmyhost-build-portable-app\ndescription: Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.\n---\n\n# Build an app for ohmyho.st\n\nPrepare the application's real capabilities, then verify them after deployment.\n\n1. Inspect the selected repository and, with a local checkout, run `ohmyhost init --dry-run --json`. In a native chat, inspect the saved source files and apply the runtime references through the remote file tools; use the actual plan/build diagnostics, without claiming a local command ran. Preserve a valid existing `ohmyhost.yaml`, application root, egress rules, auth choice and migrations. Resolve the returned blockers and requirements rather than replacing the configuration with a reduced file.\n2. Keep one exactly pinned package manager and its matching lockfile. Read `compatibility.frameworks[].status`: `admitted` meets the framework's security floor, `pending` is a version range the build checks after installing it, and `unsupported` blocks until the package is at its `minimum` or a later stable release.\n3. Read [the runtime contracts](references/stack-contracts.md) for the capabilities the app needs. Keep ordinary Next.js routes, native TanStack Start server functions, the returned Vite API companion contract, or a plain Worker module (`runtime.mode: functions`, `src/ohmyhost/worker.ts`). The service supplies its build adapter; do not add customer Wrangler/OpenNext configuration merely to host the app. Next.js 16 Cache Components and `\"use cache\"` build here: request-time `\"use cache\"` values are never stored, prerendered values stay until the next deployment, and revalidation is refused, so follow the contracts' Cache Components rules. A `create-next-app` 16.4 app with Tailwind CSS first needs the PostCSS configuration the contracts describe (`next_tailwind_turbopack_only`). Video, audio or microphone need the `_headers` opt-in from the contracts.\n4. For managed Postgres, use the supported application database binding and immutable expand-only migrations. Reach it through `createPrivateDatabaseClient`; use bounded `withConnection` for interactive transactions. Keep network transfers, email and AI calls outside that connection scope. Adding nullable columns without defaults is admitted; DROP, row changes, CREATE OR REPLACE and other changes to existing schema are refused. Preserve production records and read the database Skill for migration limits, sizing, data assignments or reset.\n5. Keep the application's own authentication provider. Better Auth and customer-owned WorkOS are the verified integrations; any other OAuth or OIDC provider is an ordinary application dependency with its own setup and runtime requirements and no completed support claim. Configure actual callback/logout URLs and server secrets, then test login, a protected route, reload and logout. A public app needs no auth provider. Detect actual Supabase capability usage before proposing a migration; an SDK declaration alone does not justify replacing it.\n6. For sending or receiving email, follow [the mail workflow](references/mail.md): choose the customer domain explicitly, prepare and verify a signed application webhook before receiving, and store incoming mail in the customer database. Enable only used capabilities. Server requests to an origin omitted from `runtime.egress.allow` answer HTTP 403 `Egress denied`; browser resources use the separate declared `runtime.browser` policy. Use the current client/runtime libraries and let `init` report missing routes or capabilities. Do not remove a required feature just to obtain a successful build.\n7. Run the application's relevant tests, typecheck and framework build through the available execution tools. Prefer focused regressions for the changed behavior and real hosted capability checks; respect the customer's requested verification scope without adding a broad test program. A native chat without test execution must identify that limitation and use actual hosted plan/build results and interaction checks, not invent test success. Use **ohmyhost-deploy** to reuse the saved source, deliver environment secrets privately and verify the hosted app's required reads, writes and integrations.\n\nResolve hosted bindings and trusted configuration from the framework's actual request context. A localhost-only test runtime or a successful health endpoint does not establish production login, tenant setup or background processing. Follow the runtime reference for first-user bootstrap, private uploads and bounded scheduled work when the application needs them.\n\nFor first account setup, use **ohmyhost-get-started**. For publishing, use **ohmyhost-deploy**;\nuse **ohmyhost-deploy-github** only for the selected GitHub source route. [The CLI reference](references/cli-deploy.md) supplies detailed commands when a local checkout is available. For a failed operation, use **ohmyhost-troubleshoot-deployment** and retain its original ID.\n\nReport a suspected hosting bug with `feedback_submit` and a minimal redacted reproduction. Return a working application URL only after its required flows pass. Promotion, rollback and deletion follow the customer's requested scope; an ordinary deploy does not require deleting their app for a cleanup test.\n"
30618
+ text: "---\nname: ohmyhost-build-portable-app\ndescription: Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.\n---\n\n# Build an app for ohmyho.st\n\nPrepare the application's real capabilities, then verify them after deployment.\n\n1. Inspect the selected repository and, with a local checkout, run `ohmyhost init --dry-run --json`. In a native chat, inspect the saved source files and apply the runtime references through the remote file tools; use the actual plan/build diagnostics, without claiming a local command ran. Preserve a valid existing `ohmyhost.yaml`, application root, egress rules, auth choice and migrations. Resolve the returned blockers and requirements rather than replacing the configuration with a reduced file.\n2. Keep one exactly pinned package manager and its matching lockfile. Read `compatibility.frameworks[].status`: `admitted` meets the framework's security floor, `pending` is a version range the build checks after installing it, and `unsupported` blocks until the package is at its `minimum` or a later stable release.\n3. Read [the runtime contracts](references/stack-contracts.md) for the capabilities the app needs. Keep ordinary Astro pages/endpoints, Next.js routes, native TanStack Start server functions, the returned Vite API companion contract, or a plain Worker module (`runtime.mode: functions`, `src/ohmyhost/worker.ts`). The service supplies its build adapter; do not add customer Wrangler/OpenNext configuration merely to host the app. Next.js 16 Cache Components and `\"use cache\"` build here: request-time `\"use cache\"` values are never stored, prerendered values stay until the next deployment, and revalidation is refused, so follow the contracts' Cache Components rules. A `create-next-app` 16.4 app with Tailwind CSS first needs the PostCSS configuration the contracts describe (`next_tailwind_turbopack_only`). Video, audio or microphone need the `_headers` opt-in from the contracts.\n4. For managed Postgres, use the supported application database binding and immutable expand-only migrations. Reach it through `createPrivateDatabaseClient`; use bounded `withConnection` for interactive transactions. Keep network transfers, email and AI calls outside that connection scope. Adding nullable columns without defaults is admitted; DROP, row changes, CREATE OR REPLACE and other changes to existing schema are refused. Preserve production records and read the database Skill for migration limits, sizing, data assignments or reset.\n5. Keep the application's own authentication provider. Better Auth and customer-owned WorkOS are the verified integrations; any other OAuth or OIDC provider is an ordinary application dependency with its own setup and runtime requirements and no completed support claim. Configure actual callback/logout URLs and server secrets, then test login, a protected route, reload and logout. A public app needs no auth provider. Detect actual Supabase capability usage before proposing a migration; an SDK declaration alone does not justify replacing it.\n6. For sending or receiving email, follow [the mail workflow](references/mail.md): choose the customer domain explicitly, prepare and verify a signed application webhook before receiving, and store incoming mail in the customer database. Enable only used capabilities. Server requests to an origin omitted from `runtime.egress.allow` answer HTTP 403 `Egress denied`; browser resources use the separate declared `runtime.browser` policy. Use the current client/runtime libraries and let `init` report missing routes or capabilities. Do not remove a required feature just to obtain a successful build.\n7. Run the application's relevant tests, typecheck and framework build through the available execution tools. Prefer focused regressions for the changed behavior and real hosted capability checks; respect the customer's requested verification scope without adding a broad test program. A native chat without test execution must identify that limitation and use actual hosted plan/build results and interaction checks, not invent test success. Use **ohmyhost-deploy** to reuse the saved source, deliver environment secrets privately and verify the hosted app's required reads, writes and integrations.\n\nResolve hosted bindings and trusted configuration from the framework's actual request context. A localhost-only test runtime or a successful health endpoint does not establish production login, tenant setup or background processing. Follow the runtime reference for first-user bootstrap, private uploads and bounded scheduled work when the application needs them.\n\nFor first account setup, use **ohmyhost-get-started**. For publishing, use **ohmyhost-deploy**;\nuse **ohmyhost-deploy-github** only for the selected GitHub source route. [The CLI reference](references/cli-deploy.md) supplies detailed commands when a local checkout is available. For a failed operation, use **ohmyhost-troubleshoot-deployment** and retain its original ID.\n\nReport a suspected hosting bug with `feedback_submit` and a minimal redacted reproduction. Return a working application URL only after its required flows pass. Promotion, rollback and deletion follow the customer's requested scope; an ordinary deploy does not require deleting their app for a cleanup test.\n\n## Astro\n\nUse the [Astro guide](https://docs.ohmyho.st/frameworks/astro). Static uses `astro build`, `runtime.mode: static` and `build.output: dist` without a server adapter. SSR uses the official `@astrojs/cloudflare` adapter, `output: \"server\"`, `runtime.mode: edge`, `build.output: dist/client`, `session: false` and `cloudflare({ imageService: \"passthrough\" })`. Keep the standard `src/` and output directories. The service supplies the deployment config. No native KV sessions, provider cache, Images or customer-defined Cloudflare resources are supported initially. Keep server imports in frontmatter or ordinary modules; put dynamic browser imports in a normal statically imported browser module rather than an Astro template. Verify request-time behavior plus browser islands under hosted CSP. Static build-time filesystem/MDX work remains ordinary Node build work; enable no request-time capabilities on a static plan.\n"
30619
30619
  },
30620
30620
  {
30621
30621
  skillName: "ohmyhost-build-portable-app",
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire as __ohmyhostCreateRequire } from "node:module"; const re
2
2
  import {
3
3
  createOhmyhostMcpServer,
4
4
  ohmyhostMcpHandler
5
- } from "./chunk-E3ZJXGBW.js";
5
+ } from "./chunk-CVTJZFHD.js";
6
6
  export {
7
7
  createOhmyhostMcpServer,
8
8
  ohmyhostMcpHandler
@@ -30,7 +30,7 @@ import {
30
30
  serverIdentityOf,
31
31
  setNegotiatedProtocolVersion,
32
32
  validateEnvelopeMeta
33
- } from "./chunk-E3ZJXGBW.js";
33
+ } from "./chunk-CVTJZFHD.js";
34
34
 
35
35
  // node_modules/.pnpm/ignore@7.0.12/node_modules/ignore/index.js
36
36
  var require_ignore = __commonJS({
@@ -20235,6 +20235,11 @@ function toError(value) {
20235
20235
  // packages/contracts/dist/framework-build-script.mjs
20236
20236
  var PINNED_BUN_VERSION = "1.2.22";
20237
20237
  var FRAMEWORK_VERSION_POLICY = Object.freeze({
20238
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
20239
+ "@astrojs/cloudflare": Object.freeze({
20240
+ floors: Object.freeze(["14.4.0"]),
20241
+ denied: Object.freeze([])
20242
+ }),
20238
20243
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
20239
20244
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
20240
20245
  "@tanstack/react-start": Object.freeze({
@@ -20289,11 +20294,15 @@ var RESOLVED_VERSION_FIELD = anchoredPattern(RESOLVED_VERSION_SOURCE);
20289
20294
  var DECLARED_VERSION_FIELD = anchoredPattern(`[~^]?${RESOLVED_VERSION_SOURCE}`);
20290
20295
  var STABLE_VERSION_FIELD = anchoredPattern(STABLE_VERSION_SOURCE);
20291
20296
  var FRAMEWORK_VERSION_PACKAGES = Object.freeze([
20297
+ "astro",
20298
+ "@astrojs/cloudflare",
20292
20299
  "next",
20293
20300
  "vite",
20294
20301
  "@tanstack/react-start"
20295
20302
  ]);
20296
20303
  var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
20304
+ "astro",
20305
+ "@astrojs/cloudflare",
20297
20306
  "next",
20298
20307
  "react",
20299
20308
  "react-dom",
@@ -20304,10 +20313,23 @@ var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
20304
20313
  "@cloudflare/vite-plugin"
20305
20314
  ]);
20306
20315
  var REACT_PACKAGES = Object.freeze(["react", "react-dom"]);
20307
- var BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"]);
20316
+ var BUILD_FRAMEWORKS = Object.freeze([
20317
+ "astro",
20318
+ "nextjs",
20319
+ "vite-static",
20320
+ "tanstack-start"
20321
+ ]);
20308
20322
  var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
20309
20323
  var VITE_BUILD_SCRIPT = '"vite build", optionally joined with && to one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage';
20310
20324
  var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20325
+ astro_template_import_unsupported: {
20326
+ paths: ["module"],
20327
+ 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.'
20328
+ },
20329
+ astro_config_unsupported: {
20330
+ paths: ["framework_config"],
20331
+ 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.'
20332
+ },
20311
20333
  managed_auth_database_required: {
20312
20334
  paths: ["repository_config"],
20313
20335
  template: managedCustomerAuthDatabaseMessage("{{path}}")
@@ -20382,7 +20404,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20382
20404
  text: {
20383
20405
  nextjs: 'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
20384
20406
  "vite-static": VITE_BUILD_SCRIPT,
20385
- "tanstack-start": VITE_BUILD_SCRIPT
20407
+ "tanstack-start": VITE_BUILD_SCRIPT,
20408
+ astro: 'exactly "astro build"'
20386
20409
  }
20387
20410
  }
20388
20411
  },
@@ -20398,11 +20421,11 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20398
20421
  },
20399
20422
  framework_unsupported: {
20400
20423
  paths: ["manifest"],
20401
- 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}`
20424
+ 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}`
20402
20425
  },
20403
20426
  framework_ambiguous: {
20404
20427
  paths: ["manifest"],
20405
- 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.'
20428
+ 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.'
20406
20429
  },
20407
20430
  lockfile_missing: {
20408
20431
  paths: ["manifest"],
@@ -20441,7 +20464,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20441
20464
  text: {
20442
20465
  nextjs: "runtime.mode: edge",
20443
20466
  "vite-static": "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
20444
- "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage"
20467
+ "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
20468
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter"
20445
20469
  }
20446
20470
  }
20447
20471
  },
@@ -20456,7 +20480,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20456
20480
  text: {
20457
20481
  nextjs: ".open-next/assets",
20458
20482
  "vite-static": "dist",
20459
- "tanstack-start": "dist/client"
20483
+ "tanstack-start": "dist/client",
20484
+ astro: "dist for static, or dist/client for edge"
20460
20485
  }
20461
20486
  }
20462
20487
  },
@@ -20515,8 +20540,8 @@ var DETAIL_DEFINITIONS = FRAMEWORK_CONVERSION_DETAILS;
20515
20540
  var PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
20516
20541
  var PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
20517
20542
  var SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
20518
- var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
20519
- var FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
20543
+ var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
20544
+ var FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
20520
20545
  var MAXIMUM_DETAIL_LENGTH = 2e3;
20521
20546
  var DETAIL_PATTERNS = Object.freeze(Object.entries(DETAIL_DEFINITIONS).map(([code, definition]) => Object.freeze({
20522
20547
  code,
@@ -23232,7 +23257,7 @@ function exact3(v, keys) {
23232
23257
  // apps/product-cli/package.json
23233
23258
  var package_default2 = {
23234
23259
  name: "@ohmyhost/product-cli",
23235
- version: "0.1.34",
23260
+ version: "0.1.35",
23236
23261
  private: true,
23237
23262
  ohmyhost: {
23238
23263
  deployment: "production",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/mcp",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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";
@@ -8,7 +8,7 @@ export const GENERATED_SKILL_RESOURCES = Object.freeze([
8
8
  description:
9
9
  "Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.",
10
10
  mimeType: "text/markdown",
11
- text: "---\nname: ohmyhost-build-portable-app\ndescription: Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.\n---\n\n# Build an app for ohmyho.st\n\nPrepare the application's real capabilities, then verify them after deployment.\n\n1. Inspect the selected repository and, with a local checkout, run `ohmyhost init --dry-run --json`. In a native chat, inspect the saved source files and apply the runtime references through the remote file tools; use the actual plan/build diagnostics, without claiming a local command ran. Preserve a valid existing `ohmyhost.yaml`, application root, egress rules, auth choice and migrations. Resolve the returned blockers and requirements rather than replacing the configuration with a reduced file.\n2. Keep one exactly pinned package manager and its matching lockfile. Read `compatibility.frameworks[].status`: `admitted` meets the framework's security floor, `pending` is a version range the build checks after installing it, and `unsupported` blocks until the package is at its `minimum` or a later stable release.\n3. Read [the runtime contracts](references/stack-contracts.md) for the capabilities the app needs. Keep ordinary Next.js routes, native TanStack Start server functions, the returned Vite API companion contract, or a plain Worker module (`runtime.mode: functions`, `src/ohmyhost/worker.ts`). The service supplies its build adapter; do not add customer Wrangler/OpenNext configuration merely to host the app. Next.js 16 Cache Components and `\"use cache\"` build here: request-time `\"use cache\"` values are never stored, prerendered values stay until the next deployment, and revalidation is refused, so follow the contracts' Cache Components rules. A `create-next-app` 16.4 app with Tailwind CSS first needs the PostCSS configuration the contracts describe (`next_tailwind_turbopack_only`). Video, audio or microphone need the `_headers` opt-in from the contracts.\n4. For managed Postgres, use the supported application database binding and immutable expand-only migrations. Reach it through `createPrivateDatabaseClient`; use bounded `withConnection` for interactive transactions. Keep network transfers, email and AI calls outside that connection scope. Adding nullable columns without defaults is admitted; DROP, row changes, CREATE OR REPLACE and other changes to existing schema are refused. Preserve production records and read the database Skill for migration limits, sizing, data assignments or reset.\n5. Keep the application's own authentication provider. Better Auth and customer-owned WorkOS are the verified integrations; any other OAuth or OIDC provider is an ordinary application dependency with its own setup and runtime requirements and no completed support claim. Configure actual callback/logout URLs and server secrets, then test login, a protected route, reload and logout. A public app needs no auth provider. Detect actual Supabase capability usage before proposing a migration; an SDK declaration alone does not justify replacing it.\n6. For sending or receiving email, follow [the mail workflow](references/mail.md): choose the customer domain explicitly, prepare and verify a signed application webhook before receiving, and store incoming mail in the customer database. Enable only used capabilities. Server requests to an origin omitted from `runtime.egress.allow` answer HTTP 403 `Egress denied`; browser resources use the separate declared `runtime.browser` policy. Use the current client/runtime libraries and let `init` report missing routes or capabilities. Do not remove a required feature just to obtain a successful build.\n7. Run the application's relevant tests, typecheck and framework build through the available execution tools. Prefer focused regressions for the changed behavior and real hosted capability checks; respect the customer's requested verification scope without adding a broad test program. A native chat without test execution must identify that limitation and use actual hosted plan/build results and interaction checks, not invent test success. Use **ohmyhost-deploy** to reuse the saved source, deliver environment secrets privately and verify the hosted app's required reads, writes and integrations.\n\nResolve hosted bindings and trusted configuration from the framework's actual request context. A localhost-only test runtime or a successful health endpoint does not establish production login, tenant setup or background processing. Follow the runtime reference for first-user bootstrap, private uploads and bounded scheduled work when the application needs them.\n\nFor first account setup, use **ohmyhost-get-started**. For publishing, use **ohmyhost-deploy**;\nuse **ohmyhost-deploy-github** only for the selected GitHub source route. [The CLI reference](references/cli-deploy.md) supplies detailed commands when a local checkout is available. For a failed operation, use **ohmyhost-troubleshoot-deployment** and retain its original ID.\n\nReport a suspected hosting bug with `feedback_submit` and a minimal redacted reproduction. Return a working application URL only after its required flows pass. Promotion, rollback and deletion follow the customer's requested scope; an ordinary deploy does not require deleting their app for a cleanup test.\n",
11
+ text: "---\nname: ohmyhost-build-portable-app\ndescription: Build or adapt a TypeScript Vite, TanStack Start, Next.js (including Cache Components) or plain Worker application for ohmyho.st, with managed PostgreSQL, private files, mail, schedules, declared browser origins, egress, video or microphone access. Use for application feature work and source preparation; use the migration Skill for selected Supabase conversion.\n---\n\n# Build an app for ohmyho.st\n\nPrepare the application's real capabilities, then verify them after deployment.\n\n1. Inspect the selected repository and, with a local checkout, run `ohmyhost init --dry-run --json`. In a native chat, inspect the saved source files and apply the runtime references through the remote file tools; use the actual plan/build diagnostics, without claiming a local command ran. Preserve a valid existing `ohmyhost.yaml`, application root, egress rules, auth choice and migrations. Resolve the returned blockers and requirements rather than replacing the configuration with a reduced file.\n2. Keep one exactly pinned package manager and its matching lockfile. Read `compatibility.frameworks[].status`: `admitted` meets the framework's security floor, `pending` is a version range the build checks after installing it, and `unsupported` blocks until the package is at its `minimum` or a later stable release.\n3. Read [the runtime contracts](references/stack-contracts.md) for the capabilities the app needs. Keep ordinary Astro pages/endpoints, Next.js routes, native TanStack Start server functions, the returned Vite API companion contract, or a plain Worker module (`runtime.mode: functions`, `src/ohmyhost/worker.ts`). The service supplies its build adapter; do not add customer Wrangler/OpenNext configuration merely to host the app. Next.js 16 Cache Components and `\"use cache\"` build here: request-time `\"use cache\"` values are never stored, prerendered values stay until the next deployment, and revalidation is refused, so follow the contracts' Cache Components rules. A `create-next-app` 16.4 app with Tailwind CSS first needs the PostCSS configuration the contracts describe (`next_tailwind_turbopack_only`). Video, audio or microphone need the `_headers` opt-in from the contracts.\n4. For managed Postgres, use the supported application database binding and immutable expand-only migrations. Reach it through `createPrivateDatabaseClient`; use bounded `withConnection` for interactive transactions. Keep network transfers, email and AI calls outside that connection scope. Adding nullable columns without defaults is admitted; DROP, row changes, CREATE OR REPLACE and other changes to existing schema are refused. Preserve production records and read the database Skill for migration limits, sizing, data assignments or reset.\n5. Keep the application's own authentication provider. Better Auth and customer-owned WorkOS are the verified integrations; any other OAuth or OIDC provider is an ordinary application dependency with its own setup and runtime requirements and no completed support claim. Configure actual callback/logout URLs and server secrets, then test login, a protected route, reload and logout. A public app needs no auth provider. Detect actual Supabase capability usage before proposing a migration; an SDK declaration alone does not justify replacing it.\n6. For sending or receiving email, follow [the mail workflow](references/mail.md): choose the customer domain explicitly, prepare and verify a signed application webhook before receiving, and store incoming mail in the customer database. Enable only used capabilities. Server requests to an origin omitted from `runtime.egress.allow` answer HTTP 403 `Egress denied`; browser resources use the separate declared `runtime.browser` policy. Use the current client/runtime libraries and let `init` report missing routes or capabilities. Do not remove a required feature just to obtain a successful build.\n7. Run the application's relevant tests, typecheck and framework build through the available execution tools. Prefer focused regressions for the changed behavior and real hosted capability checks; respect the customer's requested verification scope without adding a broad test program. A native chat without test execution must identify that limitation and use actual hosted plan/build results and interaction checks, not invent test success. Use **ohmyhost-deploy** to reuse the saved source, deliver environment secrets privately and verify the hosted app's required reads, writes and integrations.\n\nResolve hosted bindings and trusted configuration from the framework's actual request context. A localhost-only test runtime or a successful health endpoint does not establish production login, tenant setup or background processing. Follow the runtime reference for first-user bootstrap, private uploads and bounded scheduled work when the application needs them.\n\nFor first account setup, use **ohmyhost-get-started**. For publishing, use **ohmyhost-deploy**;\nuse **ohmyhost-deploy-github** only for the selected GitHub source route. [The CLI reference](references/cli-deploy.md) supplies detailed commands when a local checkout is available. For a failed operation, use **ohmyhost-troubleshoot-deployment** and retain its original ID.\n\nReport a suspected hosting bug with `feedback_submit` and a minimal redacted reproduction. Return a working application URL only after its required flows pass. Promotion, rollback and deletion follow the customer's requested scope; an ordinary deploy does not require deleting their app for a cleanup test.\n\n## Astro\n\nUse the [Astro guide](https://docs.ohmyho.st/frameworks/astro). Static uses `astro build`, `runtime.mode: static` and `build.output: dist` without a server adapter. SSR uses the official `@astrojs/cloudflare` adapter, `output: \"server\"`, `runtime.mode: edge`, `build.output: dist/client`, `session: false` and `cloudflare({ imageService: \"passthrough\" })`. Keep the standard `src/` and output directories. The service supplies the deployment config. No native KV sessions, provider cache, Images or customer-defined Cloudflare resources are supported initially. Keep server imports in frontmatter or ordinary modules; put dynamic browser imports in a normal statically imported browser module rather than an Astro template. Verify request-time behavior plus browser islands under hosted CSP. Static build-time filesystem/MDX work remains ordinary Node build work; enable no request-time capabilities on a static plan.\n",
12
12
  },
13
13
  {
14
14
  skillName: "ohmyhost-build-portable-app",
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {