@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 +1 -1
- package/dist/{chunk-E3ZJXGBW.js → chunk-CVTJZFHD.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +35 -10
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/repository-init.ts +45 -10
- package/src/packages/agent-skills/src/generated-skill-resources.ts +1 -1
- package/src/packages/contracts/src/framework-admission.ts +32 -6
- package/src/packages/contracts/src/framework-build-script.mjs +8 -2
- package/src/packages/contracts/src/framework-family.ts +4 -0
- package/src/packages/contracts/src/framework-source-rules.ts +78 -9
- package/src/packages/sdk-ts/package.json +1 -1
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.
|
|
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.
|
|
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
package/dist/stdio-main.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
serverIdentityOf,
|
|
31
31
|
setNegotiatedProtocolVersion,
|
|
32
32
|
validateEnvelopeMeta
|
|
33
|
-
} from "./chunk-
|
|
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([
|
|
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
|
|
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.
|
|
23260
|
+
version: "0.1.35",
|
|
23236
23261
|
private: true,
|
|
23237
23262
|
ohmyhost: {
|
|
23238
23263
|
deployment: "production",
|
package/package.json
CHANGED
|
@@ -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(
|
|
314
|
-
|
|
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
|
-
:
|
|
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 === "
|
|
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 === "
|
|
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 (
|
|
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(
|
|
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 (
|
|
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 =
|
|
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([
|
|
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
|
|
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 =
|
|
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({
|
|
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
|
|
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 === "
|
|
528
|
-
? [...REQUIRED_DEPENDENCIES[
|
|
529
|
-
:
|
|
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 =
|
|
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(
|
|
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
|
+
}
|