@holo-js/cli 0.1.6 → 0.1.8

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.
@@ -1,12 +1,21 @@
1
1
  import {
2
2
  loadProjectConfig,
3
3
  resolveGeneratedSchemaPath
4
- } from "./chunk-5BLEC66P.mjs";
4
+ } from "./chunk-DFKX4YT4.mjs";
5
5
  import {
6
6
  loadGeneratedProjectRegistry,
7
7
  relativeImportPath,
8
- renderGeneratedModelTypes
9
- } from "./chunk-MXKNQACM.mjs";
8
+ renderAuthProviderRouteFiles,
9
+ renderAuthRouteFiles,
10
+ renderFrameworkFiles,
11
+ renderFrameworkRunner,
12
+ renderGeneratedModelTypes,
13
+ renderNextBroadcastAuthRoute,
14
+ renderNextGeneratedBroadcastAuthRoute,
15
+ renderNextHoloHelper,
16
+ renderNextManagedHostedAuthRouteFiles,
17
+ renderSvelteHoloHelper
18
+ } from "./chunk-O6AXHL7Z.mjs";
10
19
  import {
11
20
  AUTH_CONFIG_FILE_NAMES,
12
21
  AUTH_SOCIAL_PROVIDER_PACKAGE_NAMES,
@@ -50,7 +59,7 @@ import { loadConfigDirectory } from "@holo-js/config";
50
59
  // package.json
51
60
  var package_default = {
52
61
  name: "@holo-js/cli",
53
- version: "0.1.6",
62
+ version: "0.1.8",
54
63
  description: "Holo-JS Framework - project creation, discovery, and operational CLI",
55
64
  type: "module",
56
65
  license: "MIT",
@@ -101,48 +110,48 @@ var package_default = {
101
110
  var WORKSPACE_CATALOG = Object.freeze({
102
111
  "@clerk/backend": "^3.4.7",
103
112
  "@eslint/js": "^9.17.0",
104
- "@holo-js/adapter-next": "^0.1.6",
105
- "@holo-js/adapter-nuxt": "^0.1.6",
106
- "@holo-js/adapter-sveltekit": "^0.1.6",
107
- "@holo-js/auth": "^0.1.6",
108
- "@holo-js/auth-clerk": "^0.1.6",
109
- "@holo-js/auth-social": "^0.1.6",
110
- "@holo-js/auth-social-apple": "^0.1.6",
111
- "@holo-js/auth-social-discord": "^0.1.6",
112
- "@holo-js/auth-social-facebook": "^0.1.6",
113
- "@holo-js/auth-social-github": "^0.1.6",
114
- "@holo-js/auth-social-google": "^0.1.6",
115
- "@holo-js/auth-social-linkedin": "^0.1.6",
116
- "@holo-js/auth-workos": "^0.1.6",
117
- "@holo-js/authorization": "^0.1.6",
118
- "@holo-js/broadcast": "^0.1.6",
119
- "@holo-js/cache": "^0.1.6",
120
- "@holo-js/cache-db": "^0.1.6",
121
- "@holo-js/cache-redis": "^0.1.6",
122
- "@holo-js/cli": "^0.1.6",
123
- "@holo-js/config": "^0.1.6",
124
- "@holo-js/core": "^0.1.6",
125
- "@holo-js/db": "^0.1.6",
126
- "@holo-js/db-mysql": "^0.1.6",
127
- "@holo-js/db-postgres": "^0.1.6",
128
- "@holo-js/db-sqlite": "^0.1.6",
129
- "@holo-js/events": "^0.1.6",
130
- "@holo-js/flux": "^0.1.6",
131
- "@holo-js/flux-react": "^0.1.6",
132
- "@holo-js/flux-svelte": "^0.1.6",
133
- "@holo-js/flux-vue": "^0.1.6",
134
- "@holo-js/forms": "^0.1.6",
135
- "@holo-js/mail": "^0.1.6",
136
- "@holo-js/media": "^0.1.6",
137
- "@holo-js/notifications": "^0.1.6",
138
- "@holo-js/queue": "^0.1.6",
139
- "@holo-js/queue-db": "^0.1.6",
140
- "@holo-js/queue-redis": "^0.1.6",
141
- "@holo-js/security": "^0.1.6",
142
- "@holo-js/session": "^0.1.6",
143
- "@holo-js/storage": "^0.1.6",
144
- "@holo-js/storage-s3": "^0.1.6",
145
- "@holo-js/validation": "^0.1.6",
113
+ "@holo-js/adapter-next": "^0.1.8",
114
+ "@holo-js/adapter-nuxt": "^0.1.8",
115
+ "@holo-js/adapter-sveltekit": "^0.1.8",
116
+ "@holo-js/auth": "^0.1.8",
117
+ "@holo-js/auth-clerk": "^0.1.8",
118
+ "@holo-js/auth-social": "^0.1.8",
119
+ "@holo-js/auth-social-apple": "^0.1.8",
120
+ "@holo-js/auth-social-discord": "^0.1.8",
121
+ "@holo-js/auth-social-facebook": "^0.1.8",
122
+ "@holo-js/auth-social-github": "^0.1.8",
123
+ "@holo-js/auth-social-google": "^0.1.8",
124
+ "@holo-js/auth-social-linkedin": "^0.1.8",
125
+ "@holo-js/auth-workos": "^0.1.8",
126
+ "@holo-js/authorization": "^0.1.8",
127
+ "@holo-js/broadcast": "^0.1.8",
128
+ "@holo-js/cache": "^0.1.8",
129
+ "@holo-js/cache-db": "^0.1.8",
130
+ "@holo-js/cache-redis": "^0.1.8",
131
+ "@holo-js/cli": "^0.1.8",
132
+ "@holo-js/config": "^0.1.8",
133
+ "@holo-js/core": "^0.1.8",
134
+ "@holo-js/db": "^0.1.8",
135
+ "@holo-js/db-mysql": "^0.1.8",
136
+ "@holo-js/db-postgres": "^0.1.8",
137
+ "@holo-js/db-sqlite": "^0.1.8",
138
+ "@holo-js/events": "^0.1.8",
139
+ "@holo-js/flux": "^0.1.8",
140
+ "@holo-js/flux-react": "^0.1.8",
141
+ "@holo-js/flux-svelte": "^0.1.8",
142
+ "@holo-js/flux-vue": "^0.1.8",
143
+ "@holo-js/forms": "^0.1.8",
144
+ "@holo-js/mail": "^0.1.8",
145
+ "@holo-js/media": "^0.1.8",
146
+ "@holo-js/notifications": "^0.1.8",
147
+ "@holo-js/queue": "^0.1.8",
148
+ "@holo-js/queue-db": "^0.1.8",
149
+ "@holo-js/queue-redis": "^0.1.8",
150
+ "@holo-js/security": "^0.1.8",
151
+ "@holo-js/session": "^0.1.8",
152
+ "@holo-js/storage": "^0.1.8",
153
+ "@holo-js/storage-s3": "^0.1.8",
154
+ "@holo-js/validation": "^0.1.8",
146
155
  "@nuxt/kit": "^4.4.4",
147
156
  "@nuxt/module-builder": "^1.0.2",
148
157
  "@sveltejs/adapter-node": "^5.5.4",
@@ -159,7 +168,7 @@ var WORKSPACE_CATALOG = Object.freeze({
159
168
  "@vitest/coverage-v8": "^4.1.5",
160
169
  "better-sqlite3": "^11.7.0",
161
170
  "bullmq": "^5.71.0",
162
- "create-holo-js": "^0.1.6",
171
+ "create-holo-js": "^0.1.8",
163
172
  "esbuild": "^0.27.4",
164
173
  "eslint": "^9.17.0",
165
174
  "fast-check": "^4.5.3",
@@ -1287,28 +1296,6 @@ function renderBroadcastEnvFiles() {
1287
1296
  example
1288
1297
  };
1289
1298
  }
1290
- function renderNextBroadcastAuthRoute() {
1291
- return [
1292
- "import { renderBroadcastAuthResponse } from '@holo-js/broadcast/auth'",
1293
- "import { holo } from '@/server/holo'",
1294
- "",
1295
- "export async function POST(request: Request) {",
1296
- " const app = await holo.getApp()",
1297
- " const auth = await holo.getAuth()",
1298
- "",
1299
- " return await renderBroadcastAuthResponse(request, {",
1300
- " resolveUser: async () => await auth?.user(),",
1301
- " channelAuth: {",
1302
- " registry: {",
1303
- " projectRoot: app.projectRoot,",
1304
- " channels: app.registry?.channels ?? [],",
1305
- " },",
1306
- " },",
1307
- " })",
1308
- "}",
1309
- ""
1310
- ].join("\n");
1311
- }
1312
1299
  function renderNuxtBroadcastAuthRoute() {
1313
1300
  return [
1314
1301
  "import { defineEventHandler, getHeaders, getRequestURL, readRawBody } from 'h3'",
@@ -1343,28 +1330,6 @@ function renderNuxtBroadcastAuthRoute() {
1343
1330
  ""
1344
1331
  ].join("\n");
1345
1332
  }
1346
- function renderSvelteBroadcastAuthRoute() {
1347
- return [
1348
- "import { renderBroadcastAuthResponse } from '@holo-js/broadcast/auth'",
1349
- "import { holo } from '$lib/server/holo'",
1350
- "",
1351
- "export async function POST({ request }: { request: Request }) {",
1352
- " const app = await holo.getApp()",
1353
- " const auth = await holo.getAuth()",
1354
- "",
1355
- " return await renderBroadcastAuthResponse(request, {",
1356
- " resolveUser: async () => await auth?.user(),",
1357
- " channelAuth: {",
1358
- " registry: {",
1359
- " projectRoot: app.projectRoot,",
1360
- " channels: app.registry?.channels ?? [],",
1361
- " },",
1362
- " },",
1363
- " })",
1364
- "}",
1365
- ""
1366
- ].join("\n");
1367
- }
1368
1333
  async function syncBroadcastAuthSupportAfterAuthInstall(projectRoot) {
1369
1334
  const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
1370
1335
  const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
@@ -1398,10 +1363,14 @@ async function syncBroadcastAuthSupportAfterAuthInstall(projectRoot) {
1398
1363
  }
1399
1364
  if (framework === "next") {
1400
1365
  const authRoutePath = resolve2(projectRoot, "app/broadcasting/auth/route.ts");
1366
+ const holoHelperPath = resolve2(projectRoot, ".holo-js/generated/next/holo.ts");
1367
+ const generatedRoutePath = resolve2(projectRoot, ".holo-js/generated/next/broadcast-auth-route.ts");
1401
1368
  if (!await pathExists(authRoutePath)) {
1402
1369
  await writeTextFile(authRoutePath, renderNextBroadcastAuthRoute());
1403
1370
  createdBroadcastAuthRoute = true;
1404
1371
  }
1372
+ await writeTextFile(holoHelperPath, renderNextHoloHelper());
1373
+ await writeTextFile(generatedRoutePath, renderNextGeneratedBroadcastAuthRoute());
1405
1374
  return {
1406
1375
  updatedBroadcastConfig,
1407
1376
  createdBroadcastAuthRoute
@@ -1419,11 +1388,8 @@ async function syncBroadcastAuthSupportAfterAuthInstall(projectRoot) {
1419
1388
  };
1420
1389
  }
1421
1390
  if (framework === "sveltekit") {
1422
- const authRoutePath = resolve2(projectRoot, "src/routes/broadcasting/auth/+server.ts");
1423
- if (!await pathExists(authRoutePath)) {
1424
- await writeTextFile(authRoutePath, renderSvelteBroadcastAuthRoute());
1425
- createdBroadcastAuthRoute = true;
1426
- }
1391
+ const holoHelperPath = resolve2(projectRoot, ".holo-js/generated/sveltekit/holo.ts");
1392
+ await writeTextFile(holoHelperPath, renderSvelteHoloHelper());
1427
1393
  }
1428
1394
  return {
1429
1395
  updatedBroadcastConfig,
@@ -1821,7 +1787,7 @@ function renderAuthMigration(slug) {
1821
1787
  " await schema.createTable('sessions', (table) => {",
1822
1788
  " table.string('id').primaryKey()",
1823
1789
  " table.string('store').default('database')",
1824
- " table.json('data').default({})",
1790
+ " table.json('data')",
1825
1791
  " table.timestamp('created_at')",
1826
1792
  " table.timestamp('last_activity_at')",
1827
1793
  " table.timestamp('expires_at')",
@@ -1851,8 +1817,8 @@ function renderAuthMigration(slug) {
1851
1817
  " table.string('provider_user_id')",
1852
1818
  " table.string('email').nullable()",
1853
1819
  " table.boolean('email_verified').default(false)",
1854
- " table.json('profile').default({})",
1855
- " table.json('tokens').default({})",
1820
+ " table.json('profile')",
1821
+ " table.json('tokens')",
1856
1822
  " table.timestamps()",
1857
1823
  " table.index(['user_id'])",
1858
1824
  " table.unique(['provider', 'provider_user_id'], 'auth_identities_provider_user_unique')",
@@ -1876,7 +1842,7 @@ function renderAuthMigration(slug) {
1876
1842
  " table.string('user_id')",
1877
1843
  " table.string('name')",
1878
1844
  " table.string('token_hash').unique()",
1879
- " table.json('abilities').default([])",
1845
+ " table.json('abilities')",
1880
1846
  " table.timestamp('last_used_at').nullable()",
1881
1847
  " table.timestamp('expires_at').nullable()",
1882
1848
  " table.timestamps()",
@@ -1959,7 +1925,7 @@ function renderNotificationsMigration() {
1959
1925
  " table.string('type').nullable()",
1960
1926
  " table.string('notifiable_type')",
1961
1927
  " table.string('notifiable_id')",
1962
- " table.json('data').default({})",
1928
+ " table.json('data')",
1963
1929
  " table.timestamp('read_at').nullable()",
1964
1930
  " table.timestamp('created_at')",
1965
1931
  " table.timestamp('updated_at')",
@@ -2064,6 +2030,7 @@ function resolveDefaultDatabaseUrl(driver) {
2064
2030
  function renderScaffoldEnvFiles(options) {
2065
2031
  const defaultDatabaseConnection = "main";
2066
2032
  const defaultUrl = resolveFrameworkDefaultUrl(options.framework);
2033
+ const optionalPackageNames = normalizeScaffoldOptionalPackages(options.optionalPackages);
2067
2034
  const baseLines = [
2068
2035
  "APP_NAME=",
2069
2036
  "APP_KEY=",
@@ -2082,22 +2049,47 @@ function renderScaffoldEnvFiles(options) {
2082
2049
  `DB_DATABASE=${sanitizePackageName(options.projectName) || "holo_app"}`,
2083
2050
  ...options.databaseDriver === "postgres" ? ["DB_SCHEMA=public"] : []
2084
2051
  ];
2085
- const storageLines = normalizeScaffoldOptionalPackages(options.optionalPackages).includes("storage") ? [
2052
+ const storageLines = optionalPackageNames.includes("storage") ? [
2086
2053
  `STORAGE_DEFAULT_DISK=${options.storageDefaultDisk}`,
2087
2054
  "STORAGE_ROUTE_PREFIX=/storage"
2088
2055
  ] : [];
2089
- const authLines = normalizeScaffoldOptionalPackages(options.optionalPackages).includes("auth") ? [...renderAuthEnvFiles({}, defaultDatabaseConnection).env] : [];
2090
- const cacheLines = normalizeScaffoldOptionalPackages(options.optionalPackages).includes("cache") ? [...renderCacheEnvFiles("file").env] : [];
2091
- const mailLines = renderMailEnvFiles().env;
2092
- const env = [...baseLines, ...driverLines, ...storageLines, ...authLines, ...cacheLines, ...mailLines, ""].join("\n");
2056
+ const authLines = optionalPackageNames.includes("auth") ? [...renderAuthEnvFiles({}, defaultDatabaseConnection).env] : [];
2057
+ const cacheLines = optionalPackageNames.includes("cache") ? [...renderCacheEnvFiles("file").env] : [];
2058
+ const mailLines = optionalPackageNames.includes("mail") ? [...renderMailEnvFiles().env] : [];
2059
+ const mailExampleLines = optionalPackageNames.includes("mail") ? [...renderMailEnvFiles().example] : [];
2060
+ const envGroups = [
2061
+ baseLines,
2062
+ driverLines,
2063
+ storageLines,
2064
+ authLines,
2065
+ cacheLines,
2066
+ mailLines
2067
+ ];
2068
+ const exampleGroups = [
2069
+ baseLines.map(renderEnvExampleLine),
2070
+ driverLines.map(renderEnvExampleLine),
2071
+ storageLines.map(renderEnvExampleLine),
2072
+ authLines.map(renderEnvExampleLine),
2073
+ cacheLines.map(renderEnvExampleLine),
2074
+ mailExampleLines.map(renderEnvExampleLine)
2075
+ ];
2076
+ const env = renderEnvGroups(envGroups);
2093
2077
  const example = [
2094
2078
  "# Copy this file to .env and fill in your local values.",
2095
2079
  "# Supported layered env files: .env.local, .env.development, .env.production, .env.prod, .env.test",
2096
- ...[...baseLines, ...driverLines, ...storageLines, ...authLines, ...cacheLines, ...renderMailEnvFiles().example].map((line) => `${line.split("=")[0]}=`),
2080
+ renderEnvGroups(exampleGroups).trimEnd(),
2097
2081
  ""
2098
2082
  ].join("\n");
2099
2083
  return { env, example };
2100
2084
  }
2085
+ function renderEnvGroups(groups) {
2086
+ return `${groups.filter((group) => group.length > 0).map((group) => group.join("\n")).join("\n\n")}
2087
+ `;
2088
+ }
2089
+ function renderEnvExampleLine(line) {
2090
+ const [key] = line.split("=");
2091
+ return `${key ?? line}=`;
2092
+ }
2101
2093
  function renderMailEnvFiles() {
2102
2094
  return {
2103
2095
  env: [
@@ -2321,7 +2313,7 @@ function renderScaffoldTsconfig(options) {
2321
2313
  }, null, 2)}
2322
2314
  `;
2323
2315
  }
2324
- const include = ["next-env.d.ts", "instrumentation.ts", "app/**/*.ts", "app/**/*.tsx", "server/**/*.ts", "config/**/*.ts", ".holo-js/generated/**/*.ts", ".holo-js/generated/**/*.d.ts"];
2316
+ const include = ["next-env.d.ts", "app/**/*.ts", "app/**/*.tsx", "server/**/*.ts", "config/**/*.ts", ".holo-js/generated/**/*.ts", ".holo-js/generated/**/*.d.ts"];
2325
2317
  return `${JSON.stringify({
2326
2318
  compilerOptions: {
2327
2319
  target: "ES2022",
@@ -2356,1066 +2348,6 @@ function renderVSCodeSettings(options) {
2356
2348
  `;
2357
2349
  }
2358
2350
 
2359
- // src/project/scaffold/framework-renderers.ts
2360
- var HOSTED_AUTH_PROVIDERS = {
2361
- workos: {
2362
- provider: "workos",
2363
- packageName: "@holo-js/auth-workos",
2364
- loginFunction: "loginWithWorkos",
2365
- registerFunction: "registerWithWorkos",
2366
- callbackFunction: "completeWorkosAuth",
2367
- logoutFunction: "logoutWithWorkos"
2368
- },
2369
- clerk: {
2370
- provider: "clerk",
2371
- packageName: "@holo-js/auth-clerk",
2372
- loginFunction: "loginWithClerk",
2373
- registerFunction: "registerWithClerk",
2374
- callbackFunction: "completeClerkAuth",
2375
- logoutFunction: "logoutWithClerk"
2376
- }
2377
- };
2378
- function getRequestedHostedAuthProviders(features) {
2379
- return [
2380
- ...features.workos ? ["workos"] : [],
2381
- ...features.clerk ? ["clerk"] : []
2382
- ];
2383
- }
2384
- function renderNuxtAppVue(projectName) {
2385
- return [
2386
- "<template>",
2387
- ' <main class="shell">',
2388
- " <h1>{{ appName }}</h1>",
2389
- " <p>Nuxt renders the UI. Holo owns the backend runtime and canonical server directories.</p>",
2390
- " </main>",
2391
- "</template>",
2392
- "",
2393
- '<script setup lang="ts">',
2394
- `const appName = ${JSON.stringify(projectName)}`,
2395
- "</script>",
2396
- "",
2397
- "<style scoped>",
2398
- ".shell {",
2399
- " min-height: 100vh;",
2400
- " display: grid;",
2401
- " place-content: center;",
2402
- " gap: 1rem;",
2403
- " padding: 3rem;",
2404
- " font-family: sans-serif;",
2405
- "}",
2406
- "h1 {",
2407
- " margin: 0;",
2408
- " font-size: clamp(2.5rem, 6vw, 4rem);",
2409
- "}",
2410
- "p {",
2411
- " margin: 0;",
2412
- " max-width: 40rem;",
2413
- " line-height: 1.6;",
2414
- "}",
2415
- "</style>",
2416
- ""
2417
- ].join("\n");
2418
- }
2419
- function renderNuxtConfig() {
2420
- return [
2421
- "export default defineNuxtConfig({",
2422
- " modules: ['@holo-js/adapter-nuxt'],",
2423
- " sourcemap: {",
2424
- " client: false,",
2425
- " server: false,",
2426
- " },",
2427
- " vite: {",
2428
- " build: {",
2429
- " rollupOptions: {",
2430
- " onwarn(warning, defaultHandler) {",
2431
- " if (",
2432
- " warning.message.includes('nuxt:module-preload-polyfill')",
2433
- " && warning.message.includes('didn\\'t generate a sourcemap')",
2434
- " ) {",
2435
- " return",
2436
- " }",
2437
- "",
2438
- " defaultHandler(warning)",
2439
- " },",
2440
- " },",
2441
- " },",
2442
- " },",
2443
- "})",
2444
- ""
2445
- ].join("\n");
2446
- }
2447
- function renderNuxtHealthRoute() {
2448
- return [
2449
- "export default defineEventHandler(async () => {",
2450
- " const app = await holo.getApp()",
2451
- "",
2452
- " return {",
2453
- " ok: true,",
2454
- " app: app.config.app.name,",
2455
- " env: app.config.app.env,",
2456
- " models: app.registry?.models.length ?? 0,",
2457
- " commands: app.registry?.commands.length ?? 0,",
2458
- " }",
2459
- "})",
2460
- ""
2461
- ].join("\n");
2462
- }
2463
- function renderNuxtCurrentAuthRoute() {
2464
- return [
2465
- "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
2466
- "import { setResponseStatus } from 'h3'",
2467
- "",
2468
- "export default defineEventHandler(async (event) => {",
2469
- " const query = getQuery(event)",
2470
- " const guard = typeof query.guard === 'string' ? query.guard : undefined",
2471
- " try {",
2472
- " const guardAuth = guard ? auth.guard(guard) : undefined",
2473
- "",
2474
- " return {",
2475
- " authenticated: guardAuth ? await guardAuth.check() : await check(),",
2476
- " guard: guard ?? 'web',",
2477
- " provider: guardAuth ? await guardAuth.provider() : await provider(),",
2478
- " user: guardAuth ? await guardAuth.user() : await user(),",
2479
- " }",
2480
- " } catch (error) {",
2481
- " if (isAuthError(error) && error.code === 'guard_not_configured') {",
2482
- " setResponseStatus(event, 400)",
2483
- "",
2484
- " return {",
2485
- " authenticated: false,",
2486
- " guard: guard ?? 'web',",
2487
- " provider: null,",
2488
- " user: null,",
2489
- " }",
2490
- " }",
2491
- "",
2492
- " throw error",
2493
- " }",
2494
- "})",
2495
- ""
2496
- ].join("\n");
2497
- }
2498
- function renderNuxtHostedAuthLoginRoute(spec) {
2499
- return [
2500
- `import { ${spec.loginFunction} } from '${spec.packageName}'`,
2501
- "",
2502
- "export default defineEventHandler(async (event) => {",
2503
- ` return await ${spec.loginFunction}(event)`,
2504
- "})",
2505
- ""
2506
- ].join("\n");
2507
- }
2508
- function renderNuxtHostedAuthRegisterRoute(spec) {
2509
- return [
2510
- `import { ${spec.registerFunction} } from '${spec.packageName}'`,
2511
- "",
2512
- "export default defineEventHandler(async (event) => {",
2513
- ` return await ${spec.registerFunction}(event)`,
2514
- "})",
2515
- ""
2516
- ].join("\n");
2517
- }
2518
- function renderNuxtHostedAuthCallbackRoute(spec) {
2519
- return [
2520
- `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
2521
- "import { sendRedirect } from 'h3'",
2522
- "",
2523
- "export default defineEventHandler(async (event) => {",
2524
- ` const { error } = await ${spec.callbackFunction}(event)`,
2525
- " if (error) {",
2526
- " return await sendRedirect(event, `/login?error=${encodeURIComponent(error.code)}`, 303)",
2527
- " }",
2528
- "",
2529
- " return await sendRedirect(event, '/', 303)",
2530
- "})",
2531
- ""
2532
- ].join("\n");
2533
- }
2534
- function renderNuxtHostedAuthLogoutRoute(spec) {
2535
- return [
2536
- "import { provider } from '@holo-js/auth'",
2537
- `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
2538
- "import { createError, sendRedirect } from 'h3'",
2539
- "",
2540
- "export default defineEventHandler(async (event) => {",
2541
- " let currentProvider: string | null",
2542
- " try {",
2543
- " currentProvider = await provider()",
2544
- " } catch {",
2545
- " return await sendRedirect(event, '/', 303)",
2546
- " }",
2547
- "",
2548
- ` if (currentProvider !== '${spec.provider}') {`,
2549
- " return await sendRedirect(event, '/', 303)",
2550
- " }",
2551
- "",
2552
- ` const { data, error } = await ${spec.logoutFunction}(event)`,
2553
- " if (error) {",
2554
- " throw createError({",
2555
- " statusCode: error.status,",
2556
- " statusMessage: error.message,",
2557
- " })",
2558
- " }",
2559
- "",
2560
- " return await sendRedirect(event, data.url, 303)",
2561
- "})",
2562
- ""
2563
- ].join("\n");
2564
- }
2565
- function renderNuxtHostedAuthRouteFiles(provider) {
2566
- const spec = HOSTED_AUTH_PROVIDERS[provider];
2567
- return [
2568
- { path: `server/api/auth/${provider}/login.get.ts`, contents: renderNuxtHostedAuthLoginRoute(spec) },
2569
- { path: `server/api/auth/${provider}/register.get.ts`, contents: renderNuxtHostedAuthRegisterRoute(spec) },
2570
- { path: `server/api/auth/${provider}/callback.get.ts`, contents: renderNuxtHostedAuthCallbackRoute(spec) },
2571
- { path: `server/api/auth/${provider}/logout.post.ts`, contents: renderNuxtHostedAuthLogoutRoute(spec) }
2572
- ];
2573
- }
2574
- function renderNextConfig() {
2575
- return [
2576
- "import type { NextConfig } from 'next'",
2577
- "import { withHolo } from '@holo-js/adapter-next/config'",
2578
- "",
2579
- "const nextConfig: NextConfig = withHolo({",
2580
- " /* config options here */",
2581
- "})",
2582
- "",
2583
- "export default nextConfig",
2584
- ""
2585
- ].join("\n");
2586
- }
2587
- function renderNextLayout(projectName) {
2588
- return [
2589
- "import type { ReactNode } from 'react'",
2590
- "",
2591
- "export const metadata = {",
2592
- ` title: ${JSON.stringify(projectName)},`,
2593
- " description: 'Holo on Next.js',",
2594
- "}",
2595
- "",
2596
- "export default function RootLayout({ children }: { children: ReactNode }) {",
2597
- " return (",
2598
- ' <html lang="en">',
2599
- " <body>{children}</body>",
2600
- " </html>",
2601
- " )",
2602
- "}",
2603
- ""
2604
- ].join("\n");
2605
- }
2606
- function escapeHtml(value) {
2607
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("{", "&#123;").replaceAll("}", "&#125;");
2608
- }
2609
- function renderNextPage(projectName) {
2610
- const escapedProjectName = escapeHtml(projectName);
2611
- return [
2612
- "export default function HomePage() {",
2613
- " return (",
2614
- " <main style={{ padding: '3rem', fontFamily: 'sans-serif' }}>",
2615
- ` <h1>${escapedProjectName}</h1>`,
2616
- " <p>Next.js handles rendering. Holo powers the backend runtime and discovered server resources.</p>",
2617
- " </main>",
2618
- " )",
2619
- "}",
2620
- ""
2621
- ].join("\n");
2622
- }
2623
- function renderNextEnvDts() {
2624
- return [
2625
- '/// <reference types="next" />',
2626
- '/// <reference types="next/image-types/global" />',
2627
- "",
2628
- "// Generated by Holo. Do not edit.",
2629
- ""
2630
- ].join("\n");
2631
- }
2632
- function renderNextHoloHelper() {
2633
- return [
2634
- "import { createNextHoloHelpers } from '@holo-js/adapter-next'",
2635
- "",
2636
- "export const holo = createNextHoloHelpers()",
2637
- ""
2638
- ].join("\n");
2639
- }
2640
- function renderNextInstrumentation() {
2641
- return [
2642
- "export async function register() {",
2643
- " if (process.env.NEXT_RUNTIME === 'nodejs') {",
2644
- " const { holo } = await import('@/server/holo')",
2645
- " await holo.getApp()",
2646
- " }",
2647
- "}",
2648
- ""
2649
- ].join("\n");
2650
- }
2651
- function renderNextHealthRoute() {
2652
- return [
2653
- "import { holo } from '@/server/holo'",
2654
- "",
2655
- "export async function GET() {",
2656
- " const app = await holo.getApp()",
2657
- "",
2658
- " return Response.json({",
2659
- " ok: true,",
2660
- " app: app.config.app.name,",
2661
- " env: app.config.app.env,",
2662
- " models: app.registry?.models.length ?? 0,",
2663
- " commands: app.registry?.commands.length ?? 0,",
2664
- " })",
2665
- "}",
2666
- ""
2667
- ].join("\n");
2668
- }
2669
- function renderNextCurrentAuthRoute() {
2670
- return [
2671
- "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
2672
- "",
2673
- "export async function GET(request: Request) {",
2674
- " const guard = new URL(request.url).searchParams.get('guard') ?? undefined",
2675
- " try {",
2676
- " const guardAuth = guard ? auth.guard(guard) : undefined",
2677
- "",
2678
- " return Response.json({",
2679
- " authenticated: guardAuth ? await guardAuth.check() : await check(),",
2680
- " guard: guard ?? 'web',",
2681
- " provider: guardAuth ? await guardAuth.provider() : await provider(),",
2682
- " user: guardAuth ? await guardAuth.user() : await user(),",
2683
- " })",
2684
- " } catch (error) {",
2685
- " if (isAuthError(error) && error.code === 'guard_not_configured') {",
2686
- " return Response.json({",
2687
- " authenticated: false,",
2688
- " guard: guard ?? 'web',",
2689
- " provider: null,",
2690
- " user: null,",
2691
- " }, { status: 400 })",
2692
- " }",
2693
- "",
2694
- " throw error",
2695
- " }",
2696
- "}",
2697
- ""
2698
- ].join("\n");
2699
- }
2700
- function renderNextHostedAuthLoginRoute(spec) {
2701
- return [
2702
- `import { ${spec.loginFunction} } from '${spec.packageName}'`,
2703
- "",
2704
- "export async function GET(request: Request) {",
2705
- ` return await ${spec.loginFunction}(request)`,
2706
- "}",
2707
- ""
2708
- ].join("\n");
2709
- }
2710
- function renderNextHostedAuthRegisterRoute(spec) {
2711
- return [
2712
- `import { ${spec.registerFunction} } from '${spec.packageName}'`,
2713
- "",
2714
- "export async function GET(request: Request) {",
2715
- ` return await ${spec.registerFunction}(request)`,
2716
- "}",
2717
- ""
2718
- ].join("\n");
2719
- }
2720
- function renderNextHostedAuthCallbackRoute(spec) {
2721
- return [
2722
- `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
2723
- "",
2724
- "export async function GET(request: Request) {",
2725
- ` const { error } = await ${spec.callbackFunction}(request)`,
2726
- " if (error) {",
2727
- " return Response.redirect(new URL(`/login?error=${encodeURIComponent(error.code)}`, request.url))",
2728
- " }",
2729
- "",
2730
- " return Response.redirect(new URL('/', request.url))",
2731
- "}",
2732
- ""
2733
- ].join("\n");
2734
- }
2735
- function renderNextHostedAuthLogoutRoute(spec) {
2736
- return [
2737
- "import { provider } from '@holo-js/auth'",
2738
- `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
2739
- "",
2740
- "export async function POST(request: Request) {",
2741
- " let currentProvider: string | null",
2742
- " try {",
2743
- " currentProvider = await provider()",
2744
- " } catch {",
2745
- " return Response.redirect(new URL('/', request.url), 303)",
2746
- " }",
2747
- "",
2748
- ` if (currentProvider !== '${spec.provider}') {`,
2749
- " return Response.redirect(new URL('/', request.url), 303)",
2750
- " }",
2751
- "",
2752
- ` const { data, error } = await ${spec.logoutFunction}(request)`,
2753
- " if (error) {",
2754
- " return Response.json({ data, error }, { status: error.status })",
2755
- " }",
2756
- "",
2757
- " return Response.redirect(data.url, 303)",
2758
- "}",
2759
- ""
2760
- ].join("\n");
2761
- }
2762
- function renderNextHostedAuthRouteFiles(provider) {
2763
- const spec = HOSTED_AUTH_PROVIDERS[provider];
2764
- return [
2765
- { path: `app/api/auth/${provider}/login/route.ts`, contents: renderNextHostedAuthLoginRoute(spec) },
2766
- { path: `app/api/auth/${provider}/register/route.ts`, contents: renderNextHostedAuthRegisterRoute(spec) },
2767
- { path: `app/api/auth/${provider}/callback/route.ts`, contents: renderNextHostedAuthCallbackRoute(spec) },
2768
- { path: `app/api/auth/${provider}/logout/route.ts`, contents: renderNextHostedAuthLogoutRoute(spec) }
2769
- ];
2770
- }
2771
- function renderNextStorageRoute() {
2772
- return [
2773
- "import { holo } from '@/server/holo'",
2774
- "import { createPublicStorageResponse } from '@holo-js/storage'",
2775
- "",
2776
- "export async function GET(request: Request) {",
2777
- " const app = await holo.getApp()",
2778
- " return createPublicStorageResponse(app.projectRoot, app.config.storage, request)",
2779
- "}",
2780
- ""
2781
- ].join("\n");
2782
- }
2783
- function renderSvelteConfig() {
2784
- return [
2785
- "import adapter from '@sveltejs/adapter-node'",
2786
- "import { withHoloSvelteKit } from '@holo-js/adapter-sveltekit/config'",
2787
- "import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'",
2788
- "",
2789
- "/** @type {import('@sveltejs/kit').Config} */",
2790
- "const config = withHoloSvelteKit({",
2791
- " preprocess: vitePreprocess(),",
2792
- " kit: {",
2793
- " adapter: adapter(),",
2794
- " },",
2795
- "})",
2796
- "",
2797
- "export default config",
2798
- ""
2799
- ].join("\n");
2800
- }
2801
- function renderSvelteUserHooks() {
2802
- return [
2803
- "export {}",
2804
- ""
2805
- ].join("\n");
2806
- }
2807
- function renderSvelteServerUserHooks() {
2808
- return [
2809
- "export {}",
2810
- ""
2811
- ].join("\n");
2812
- }
2813
- function renderSvelteViteConfig(_storageEnabled) {
2814
- const externals = [
2815
- " '@holo-js/adapter-sveltekit',",
2816
- " '@holo-js/auth',",
2817
- " '@holo-js/auth-clerk',",
2818
- " '@holo-js/auth-social',",
2819
- " '@holo-js/auth-workos',",
2820
- " '@holo-js/authorization',",
2821
- " '@holo-js/broadcast',",
2822
- " '@holo-js/cache',",
2823
- " '@holo-js/cache-db',",
2824
- " '@holo-js/cache-redis',",
2825
- " '@holo-js/config',",
2826
- " '@holo-js/core',",
2827
- " '@holo-js/db',",
2828
- " '@holo-js/db-mysql',",
2829
- " '@holo-js/db-postgres',",
2830
- " '@holo-js/db-sqlite',",
2831
- " '@holo-js/events',",
2832
- " '@holo-js/flux',",
2833
- " '@holo-js/flux-svelte',",
2834
- " '@holo-js/forms',",
2835
- " '@holo-js/mail',",
2836
- " '@holo-js/media',",
2837
- " '@holo-js/notifications',",
2838
- " '@holo-js/queue',",
2839
- " '@holo-js/queue-db',",
2840
- " '@holo-js/queue-redis',",
2841
- " '@holo-js/security',",
2842
- " '@holo-js/session',",
2843
- " '@holo-js/storage',",
2844
- " '@holo-js/storage/runtime',",
2845
- " '@holo-js/storage-s3',",
2846
- " '@holo-js/validation',",
2847
- " 'better-sqlite3',",
2848
- " 'ioredis',",
2849
- " 'mysql2',",
2850
- " 'pg',"
2851
- ];
2852
- return [
2853
- "import { sveltekit } from '@sveltejs/kit/vite'",
2854
- "import { defineConfig } from 'vite'",
2855
- "",
2856
- "export default defineConfig({",
2857
- " plugins: [sveltekit()],",
2858
- " server: {",
2859
- " fs: {",
2860
- " allow: ['.holo-js/generated'],",
2861
- " },",
2862
- " },",
2863
- " ssr: {",
2864
- " external: [",
2865
- ...externals,
2866
- " ],",
2867
- " },",
2868
- "})",
2869
- ""
2870
- ].join("\n");
2871
- }
2872
- function renderSvelteAppHtml() {
2873
- return [
2874
- "<!doctype html>",
2875
- '<html lang="en">',
2876
- " <head>",
2877
- ' <meta charset="utf-8" />',
2878
- ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
2879
- " %sveltekit.head%",
2880
- " </head>",
2881
- ' <body data-sveltekit-preload-data="hover">',
2882
- ' <div style="display: contents">%sveltekit.body%</div>',
2883
- " </body>",
2884
- "</html>",
2885
- ""
2886
- ].join("\n");
2887
- }
2888
- function renderSveltePage(projectName) {
2889
- const escapedProjectName = escapeHtml(projectName);
2890
- return [
2891
- `<svelte:head><title>${escapedProjectName}</title></svelte:head>`,
2892
- "",
2893
- '<script lang="ts">',
2894
- ` const projectName = ${JSON.stringify(projectName)}`,
2895
- "</script>",
2896
- "",
2897
- '<main class="shell">',
2898
- " <h1>{projectName}</h1>",
2899
- " <p>SvelteKit owns rendering. Holo owns config, discovery, and backend runtime services.</p>",
2900
- "</main>",
2901
- "",
2902
- "<style>",
2903
- " .shell {",
2904
- " min-height: 100vh;",
2905
- " display: grid;",
2906
- " place-content: center;",
2907
- " gap: 1rem;",
2908
- " padding: 3rem;",
2909
- " font-family: sans-serif;",
2910
- " }",
2911
- " h1 {",
2912
- " margin: 0;",
2913
- " font-size: clamp(2.5rem, 6vw, 4rem);",
2914
- " }",
2915
- " p {",
2916
- " margin: 0;",
2917
- " max-width: 40rem;",
2918
- " line-height: 1.6;",
2919
- " }",
2920
- "</style>",
2921
- ""
2922
- ].join("\n");
2923
- }
2924
- function renderSvelteHoloHelper() {
2925
- return [
2926
- "import { createSvelteKitHoloHelpers } from '@holo-js/adapter-sveltekit'",
2927
- "",
2928
- "export const holo = createSvelteKitHoloHelpers()",
2929
- ""
2930
- ].join("\n");
2931
- }
2932
- function renderSvelteHealthRoute() {
2933
- return [
2934
- "import { json } from '@sveltejs/kit'",
2935
- "import { holo } from '$lib/server/holo'",
2936
- "",
2937
- "export async function GET() {",
2938
- " const app = await holo.getApp()",
2939
- "",
2940
- " return json({",
2941
- " ok: true,",
2942
- " app: app.config.app.name,",
2943
- " env: app.config.app.env,",
2944
- " models: app.registry?.models.length ?? 0,",
2945
- " commands: app.registry?.commands.length ?? 0,",
2946
- " })",
2947
- "}",
2948
- ""
2949
- ].join("\n");
2950
- }
2951
- function renderSvelteCurrentAuthRoute() {
2952
- return [
2953
- "import { json } from '@sveltejs/kit'",
2954
- "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
2955
- "",
2956
- "export async function GET({ url }: { url: URL }) {",
2957
- " const guard = url.searchParams.get('guard') ?? undefined",
2958
- " try {",
2959
- " const guardAuth = guard ? auth.guard(guard) : undefined",
2960
- "",
2961
- " return json({",
2962
- " authenticated: guardAuth ? await guardAuth.check() : await check(),",
2963
- " guard: guard ?? 'web',",
2964
- " provider: guardAuth ? await guardAuth.provider() : await provider(),",
2965
- " user: guardAuth ? await guardAuth.user() : await user(),",
2966
- " })",
2967
- " } catch (error) {",
2968
- " if (isAuthError(error) && error.code === 'guard_not_configured') {",
2969
- " return json({",
2970
- " authenticated: false,",
2971
- " guard: guard ?? 'web',",
2972
- " provider: null,",
2973
- " user: null,",
2974
- " }, { status: 400 })",
2975
- " }",
2976
- "",
2977
- " throw error",
2978
- " }",
2979
- "}",
2980
- ""
2981
- ].join("\n");
2982
- }
2983
- function renderSvelteHostedAuthLoginRoute(spec) {
2984
- return [
2985
- `import { ${spec.loginFunction} } from '${spec.packageName}'`,
2986
- "import type { RequestHandler } from './$types'",
2987
- "",
2988
- "export const GET = (async (event) => {",
2989
- ` return await ${spec.loginFunction}(event)`,
2990
- "}) satisfies RequestHandler",
2991
- ""
2992
- ].join("\n");
2993
- }
2994
- function renderSvelteHostedAuthRegisterRoute(spec) {
2995
- return [
2996
- `import { ${spec.registerFunction} } from '${spec.packageName}'`,
2997
- "import type { RequestHandler } from './$types'",
2998
- "",
2999
- "export const GET = (async (event) => {",
3000
- ` return await ${spec.registerFunction}(event)`,
3001
- "}) satisfies RequestHandler",
3002
- ""
3003
- ].join("\n");
3004
- }
3005
- function renderSvelteHostedAuthCallbackRoute(spec) {
3006
- return [
3007
- "import { redirect, type RequestHandler } from '@sveltejs/kit'",
3008
- `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
3009
- "",
3010
- "export const GET = (async (event) => {",
3011
- ` const { error } = await ${spec.callbackFunction}(event)`,
3012
- " if (error) {",
3013
- " throw redirect(303, `/login?error=${encodeURIComponent(error.code)}`)",
3014
- " }",
3015
- "",
3016
- " throw redirect(303, '/')",
3017
- "}) satisfies RequestHandler",
3018
- ""
3019
- ].join("\n");
3020
- }
3021
- function renderSvelteHostedAuthLogoutRoute(spec) {
3022
- return [
3023
- "import { redirect, type RequestHandler } from '@sveltejs/kit'",
3024
- "import { provider } from '@holo-js/auth'",
3025
- `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
3026
- "",
3027
- "export const POST = (async (event) => {",
3028
- " let currentProvider: string | null",
3029
- " try {",
3030
- " currentProvider = await provider()",
3031
- " } catch {",
3032
- " throw redirect(303, '/')",
3033
- " }",
3034
- "",
3035
- ` if (currentProvider !== '${spec.provider}') {`,
3036
- " throw redirect(303, '/')",
3037
- " }",
3038
- "",
3039
- ` const { data, error } = await ${spec.logoutFunction}(event)`,
3040
- " if (error) {",
3041
- " return Response.json({ data, error }, { status: error.status })",
3042
- " }",
3043
- "",
3044
- " throw redirect(303, data.url)",
3045
- "}) satisfies RequestHandler",
3046
- ""
3047
- ].join("\n");
3048
- }
3049
- function renderSvelteHostedAuthRouteFiles(provider) {
3050
- const spec = HOSTED_AUTH_PROVIDERS[provider];
3051
- return [
3052
- { path: `src/routes/api/auth/${provider}/login/+server.ts`, contents: renderSvelteHostedAuthLoginRoute(spec) },
3053
- { path: `src/routes/api/auth/${provider}/register/+server.ts`, contents: renderSvelteHostedAuthRegisterRoute(spec) },
3054
- { path: `src/routes/api/auth/${provider}/callback/+server.ts`, contents: renderSvelteHostedAuthCallbackRoute(spec) },
3055
- { path: `src/routes/api/auth/${provider}/logout/+server.ts`, contents: renderSvelteHostedAuthLogoutRoute(spec) }
3056
- ];
3057
- }
3058
- function renderAuthProviderRouteFiles(framework, features) {
3059
- return getRequestedHostedAuthProviders(features).flatMap((provider) => {
3060
- if (framework === "nuxt") {
3061
- return renderNuxtHostedAuthRouteFiles(provider);
3062
- }
3063
- if (framework === "next") {
3064
- return renderNextHostedAuthRouteFiles(provider);
3065
- }
3066
- return renderSvelteHostedAuthRouteFiles(provider);
3067
- });
3068
- }
3069
- function renderSvelteStorageRoute() {
3070
- return [
3071
- "import { holo } from '$lib/server/holo'",
3072
- "import { createPublicStorageResponse } from '@holo-js/storage'",
3073
- "",
3074
- "export async function GET({ request }: { request: Request }) {",
3075
- " const app = await holo.getApp()",
3076
- " return createPublicStorageResponse(app.projectRoot, app.config.storage, request)",
3077
- "}",
3078
- ""
3079
- ].join("\n");
3080
- }
3081
- function renderFrameworkFiles(options) {
3082
- const optionalPackages = normalizeScaffoldOptionalPackages(options.optionalPackages);
3083
- const storageEnabled = optionalPackages.includes("storage");
3084
- const authEnabled = optionalPackages.includes("auth");
3085
- if (options.framework === "nuxt") {
3086
- return [
3087
- { path: "app/app.vue", contents: renderNuxtAppVue(options.projectName) },
3088
- { path: "nuxt.config.ts", contents: renderNuxtConfig() },
3089
- { path: "server/api/holo/health.get.ts", contents: renderNuxtHealthRoute() },
3090
- { path: "shared/.gitkeep", contents: "" },
3091
- ...authEnabled ? [
3092
- { path: "server/api/auth/user.get.ts", contents: renderNuxtCurrentAuthRoute() }
3093
- ] : []
3094
- ];
3095
- }
3096
- if (options.framework === "next") {
3097
- return [
3098
- { path: "next.config.ts", contents: renderNextConfig() },
3099
- { path: "next-env.d.ts", contents: renderNextEnvDts() },
3100
- { path: "app/layout.tsx", contents: renderNextLayout(options.projectName) },
3101
- { path: "app/page.tsx", contents: renderNextPage(options.projectName) },
3102
- { path: "app/api/holo/health/route.ts", contents: renderNextHealthRoute() },
3103
- ...authEnabled ? [
3104
- { path: "app/api/auth/user/route.ts", contents: renderNextCurrentAuthRoute() }
3105
- ] : [],
3106
- ...storageEnabled ? [{ path: "app/storage/[[...path]]/route.ts", contents: renderNextStorageRoute() }] : [],
3107
- { path: "server/holo.ts", contents: renderNextHoloHelper() },
3108
- { path: "instrumentation.ts", contents: renderNextInstrumentation() }
3109
- ];
3110
- }
3111
- return [
3112
- { path: "svelte.config.js", contents: renderSvelteConfig() },
3113
- { path: "vite.config.ts", contents: renderSvelteViteConfig(storageEnabled) },
3114
- { path: "src/hooks.ts", contents: renderSvelteUserHooks() },
3115
- { path: "src/hooks.server.ts", contents: renderSvelteServerUserHooks() },
3116
- { path: "src/app.html", contents: renderSvelteAppHtml() },
3117
- { path: "src/routes/+page.svelte", contents: renderSveltePage(options.projectName) },
3118
- { path: "src/routes/api/holo/health/+server.ts", contents: renderSvelteHealthRoute() },
3119
- ...authEnabled ? [
3120
- { path: "src/routes/api/auth/user/+server.ts", contents: renderSvelteCurrentAuthRoute() }
3121
- ] : [],
3122
- ...storageEnabled ? [{ path: "src/routes/storage/[...path]/+server.ts", contents: renderSvelteStorageRoute() }] : [],
3123
- { path: "src/lib/server/holo.ts", contents: renderSvelteHoloHelper() }
3124
- ];
3125
- }
3126
- function renderFrameworkRunner(options) {
3127
- const commandName = options.framework === "nuxt" ? "nuxt" : options.framework === "next" ? "next" : "vite";
3128
- return [
3129
- "import { existsSync, readFileSync, readlinkSync } from 'node:fs'",
3130
- "import { dirname, resolve } from 'node:path'",
3131
- "import { fileURLToPath, pathToFileURL } from 'node:url'",
3132
- "import { execFileSync, spawn } from 'node:child_process'",
3133
- "",
3134
- "const mode = process.argv[2]",
3135
- "const manifestPath = fileURLToPath(new URL('./project.json', import.meta.url))",
3136
- "const projectRoot = resolve(dirname(manifestPath), '../..')",
3137
- "const runtimeSchemaPath = resolve(projectRoot, '.holo-js/generated/schema.mjs')",
3138
- "const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))",
3139
- "const framework = String(manifest.framework ?? '')",
3140
- `const commandName = ${JSON.stringify(commandName)}`,
3141
- "const commandArgs = mode === 'dev'",
3142
- " ? ['dev']",
3143
- " : mode === 'build'",
3144
- " ? framework === 'sveltekit' ? ['build', '--logLevel', 'error'] : ['build']",
3145
- " : undefined",
3146
- "",
3147
- "if (!commandArgs) {",
3148
- " console.error(`[holo] Unknown framework runner mode: ${String(mode)}`)",
3149
- " process.exit(1)",
3150
- "}",
3151
- "",
3152
- "const binaryPath = resolve(",
3153
- " projectRoot,",
3154
- " 'node_modules',",
3155
- " '.bin',",
3156
- " process.platform === 'win32' ? `${commandName}.cmd` : commandName,",
3157
- ")",
3158
- "",
3159
- "const suppressedOutput = framework === 'sveltekit'",
3160
- " ? new Set([",
3161
- ` '"try_get_request_store" is imported from external module "@sveltejs/kit/internal/server" but never used in ".svelte-kit/adapter-node/index.js".',`,
3162
- " ])",
3163
- " : new Set()",
3164
- "",
3165
- "function shouldSuppressOutput(line) {",
3166
- " if (suppressedOutput.has(line)) {",
3167
- " return true",
3168
- " }",
3169
- "",
3170
- " return framework === 'sveltekit'",
3171
- " && line.startsWith('Circular dependency: ')",
3172
- " && line.includes('/node_modules/semver/')",
3173
- "}",
3174
- "",
3175
- "function pipeOutput(stream, target, onLine) {",
3176
- " if (!stream) {",
3177
- " return",
3178
- " }",
3179
- "",
3180
- " let buffered = ''",
3181
- " stream.on('data', (chunk) => {",
3182
- " buffered += chunk.toString()",
3183
- " const lines = buffered.split(/\\r?\\n/)",
3184
- " buffered = lines.pop() ?? ''",
3185
- " for (const line of lines) {",
3186
- " onLine?.(line)",
3187
- " if (!shouldSuppressOutput(line)) {",
3188
- " target.write(`${line}\\n`)",
3189
- " }",
3190
- " }",
3191
- " })",
3192
- "",
3193
- " stream.on('end', () => {",
3194
- " if (buffered.length > 0) {",
3195
- " onLine?.(buffered)",
3196
- " }",
3197
- " if (buffered.length > 0 && !shouldSuppressOutput(buffered)) {",
3198
- " target.write(buffered)",
3199
- " }",
3200
- " })",
3201
- "}",
3202
- "",
3203
- "function extractNextConflictInfo(lines) {",
3204
- " if (framework !== 'next' || mode !== 'dev') {",
3205
- " return undefined",
3206
- " }",
3207
- "",
3208
- " if (!lines.some(line => line.includes('Another next dev server is already running.'))) {",
3209
- " return undefined",
3210
- " }",
3211
- "",
3212
- " let pid",
3213
- " let dir",
3214
- "",
3215
- " for (const line of lines) {",
3216
- " const match = line.match(/^- PID:\\s+(\\d+)\\s*$/)",
3217
- " if (match) {",
3218
- " pid = Number.parseInt(match[1], 10)",
3219
- " continue",
3220
- " }",
3221
- "",
3222
- " const dirMatch = line.match(/^- Dir:\\s+(.+?)\\s*$/)",
3223
- " if (dirMatch) {",
3224
- " dir = dirMatch[1]",
3225
- " }",
3226
- " }",
3227
- "",
3228
- " return typeof pid === 'number' ? { pid, dir } : undefined",
3229
- "}",
3230
- "",
3231
- "async function waitForProcessExit(pid, timeoutMs = 5000) {",
3232
- " const deadline = Date.now() + timeoutMs",
3233
- " while (Date.now() < deadline) {",
3234
- " try {",
3235
- " process.kill(pid, 0)",
3236
- " } catch (error) {",
3237
- " if (error && typeof error === 'object' && 'code' in error && error.code === 'ESRCH') {",
3238
- " return true",
3239
- " }",
3240
- " throw error",
3241
- " }",
3242
- "",
3243
- " await new Promise(resolve => setTimeout(resolve, 100))",
3244
- " }",
3245
- "",
3246
- " return false",
3247
- "}",
3248
- "",
3249
- "function inspectProcess(pid) {",
3250
- " try {",
3251
- " if (process.platform === 'linux' && existsSync(`/proc/${pid}`)) {",
3252
- " return {",
3253
- " cwd: readlinkSync(`/proc/${pid}/cwd`),",
3254
- " args: readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\\u0000', ' ').trim(),",
3255
- " }",
3256
- " }",
3257
- " } catch {",
3258
- " // Fall through to the portable process inspection path below.",
3259
- " }",
3260
- "",
3261
- " try {",
3262
- " return {",
3263
- " args: execFileSync('ps', ['-p', String(pid), '-o', 'args='], {",
3264
- " encoding: 'utf8',",
3265
- " }).trim(),",
3266
- " }",
3267
- " } catch {",
3268
- " return undefined",
3269
- " }",
3270
- "}",
3271
- "",
3272
- "function isOwnedNextDevServer(pid, reportedDir) {",
3273
- " const expectedDir = typeof reportedDir === 'string' ? resolve(reportedDir) : undefined",
3274
- " if (expectedDir && expectedDir !== projectRoot) {",
3275
- " return false",
3276
- " }",
3277
- "",
3278
- " const details = inspectProcess(pid)",
3279
- " if (!details) {",
3280
- " return expectedDir === projectRoot",
3281
- " }",
3282
- "",
3283
- " const argsMatch = details.args.includes('next') && details.args.includes('dev')",
3284
- " const cwdMatches = typeof details.cwd === 'string' && resolve(details.cwd) === projectRoot",
3285
- " const argsReferenceProject = details.args.includes(projectRoot)",
3286
- "",
3287
- " return argsMatch && (cwdMatches || argsReferenceProject || expectedDir === projectRoot)",
3288
- "}",
3289
- "",
3290
- "async function stopStaleNextDevServer(pid, reportedDir, force = false) {",
3291
- " if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) {",
3292
- " return false",
3293
- " }",
3294
- "",
3295
- " if (!isOwnedNextDevServer(pid, reportedDir)) {",
3296
- " return false",
3297
- " }",
3298
- "",
3299
- " if (!force) {",
3300
- " return false",
3301
- " }",
3302
- "",
3303
- " try {",
3304
- " process.kill(pid, 'SIGTERM')",
3305
- " } catch (error) {",
3306
- " if (error && typeof error === 'object' && 'code' in error && error.code === 'ESRCH') {",
3307
- " return true",
3308
- " }",
3309
- " return false",
3310
- " }",
3311
- "",
3312
- " return waitForProcessExit(pid)",
3313
- "}",
3314
- "",
3315
- "if (!existsSync(binaryPath)) {",
3316
- ' console.error(`[holo] Missing framework binary "${commandName}" for "${framework}". Run your package manager install first.`)',
3317
- " process.exit(1)",
3318
- "}",
3319
- "",
3320
- "let child = null",
3321
- "let forwardedSignal = null",
3322
- "",
3323
- "function detachSignalForwarders() {",
3324
- " process.removeListener('SIGINT', onSigint)",
3325
- " process.removeListener('SIGTERM', onSigterm)",
3326
- "}",
3327
- "",
3328
- "function forwardSignal(signal) {",
3329
- " if (forwardedSignal || !child || child.exitCode !== null) {",
3330
- " return",
3331
- " }",
3332
- "",
3333
- " forwardedSignal = signal",
3334
- " child.kill(signal)",
3335
- "}",
3336
- "",
3337
- "function onSigint() {",
3338
- " detachSignalForwarders()",
3339
- " forwardSignal('SIGINT')",
3340
- "}",
3341
- "",
3342
- "function onSigterm() {",
3343
- " detachSignalForwarders()",
3344
- " forwardSignal('SIGTERM')",
3345
- "}",
3346
- "",
3347
- "process.on('SIGINT', onSigint)",
3348
- "process.on('SIGTERM', onSigterm)",
3349
- "",
3350
- "async function run() {",
3351
- " let restartedAfterConflict = false",
3352
- " const maxStderrLines = 200",
3353
- "",
3354
- " while (true) {",
3355
- " const stderrLines = []",
3356
- " const childEnv = { ...process.env }",
3357
- " if (existsSync(runtimeSchemaPath)) {",
3358
- " const preload = `--import=${pathToFileURL(runtimeSchemaPath).href}`",
3359
- " childEnv.NODE_OPTIONS = childEnv.NODE_OPTIONS",
3360
- " ? `${childEnv.NODE_OPTIONS} ${preload}`",
3361
- " : preload",
3362
- " }",
3363
- " child = spawn(binaryPath, commandArgs, {",
3364
- " cwd: projectRoot,",
3365
- " env: childEnv,",
3366
- " stdio: ['inherit', 'pipe', 'pipe'],",
3367
- " })",
3368
- " forwardedSignal = null",
3369
- "",
3370
- " pipeOutput(child.stdout, process.stdout)",
3371
- " pipeOutput(child.stderr, process.stderr, line => {",
3372
- " if (stderrLines.length >= maxStderrLines) {",
3373
- " stderrLines.shift()",
3374
- " }",
3375
- " stderrLines.push(line)",
3376
- " })",
3377
- "",
3378
- " const result = await new Promise((resolve, reject) => {",
3379
- " child.on('error', reject)",
3380
- " child.on('close', (code, signal) => resolve({ code, signal }))",
3381
- " })",
3382
- "",
3383
- " if (result.code === 0) {",
3384
- " process.exit(0)",
3385
- " }",
3386
- "",
3387
- " const conflictInfo = extractNextConflictInfo(stderrLines)",
3388
- " if (!restartedAfterConflict && conflictInfo) {",
3389
- " const stopped = await stopStaleNextDevServer(conflictInfo.pid, conflictInfo.dir)",
3390
- " if (stopped) {",
3391
- " restartedAfterConflict = true",
3392
- " console.error(`[holo] Stopped stale Next dev server ${conflictInfo.pid}. Restarting dev server.`)",
3393
- " continue",
3394
- " }",
3395
- "",
3396
- " // Another dev server is already running (possibly in a different directory).",
3397
- " // Next.js already printed the conflict message with instructions to kill it.",
3398
- " // Exit gracefully to avoid noisy npm/bun error cascades.",
3399
- " process.exit(0)",
3400
- " }",
3401
- "",
3402
- " if (result.signal) {",
3403
- " detachSignalForwarders()",
3404
- " process.kill(process.pid, result.signal)",
3405
- " } else {",
3406
- " process.exit(result.code ?? 1)",
3407
- " }",
3408
- " }",
3409
- "}",
3410
- "",
3411
- "run().catch((error) => {",
3412
- " console.error(error instanceof Error ? error.message : String(error))",
3413
- " process.exit(1)",
3414
- "})",
3415
- ""
3416
- ].join("\n");
3417
- }
3418
-
3419
2351
  // src/project/scaffold/framework.ts
3420
2352
  function resolvePackageManagerVersion(value) {
3421
2353
  return SCAFFOLD_PACKAGE_MANAGER_VERSIONS[value];
@@ -3529,6 +2461,21 @@ function renderScaffoldPackageJson(options) {
3529
2461
  }, null, 2)}
3530
2462
  `;
3531
2463
  }
2464
+ function appendScaffoldEnvGroup(contents, group) {
2465
+ const normalizedGroup = group?.map((line) => line.trim()).filter((line) => line.length > 0) ?? [];
2466
+ if (normalizedGroup.length === 0) {
2467
+ return contents;
2468
+ }
2469
+ const normalizedContents = contents.trimEnd();
2470
+ if (normalizedContents.length === 0) {
2471
+ return `${normalizedGroup.join("\n")}
2472
+ `;
2473
+ }
2474
+ return `${normalizedContents}
2475
+
2476
+ ${normalizedGroup.join("\n")}
2477
+ `;
2478
+ }
3532
2479
  async function scaffoldProject(projectRoot, options) {
3533
2480
  const existingEntries = await readdir(projectRoot).catch(() => []);
3534
2481
  if (existingEntries.length > 0) {
@@ -3549,12 +2496,8 @@ async function scaffoldProject(projectRoot, options) {
3549
2496
  const securityEnabled = optionalPackages.includes("security");
3550
2497
  const cacheEnabled = optionalPackages.includes("cache");
3551
2498
  const broadcastEnvFiles = broadcastEnabled ? renderBroadcastEnvFiles() : void 0;
3552
- const baseEnv = normalizeScaffoldEnvSegments(env);
3553
- const baseExample = normalizeScaffoldEnvSegments(example);
3554
- const scaffoldEnvSegments = broadcastEnvFiles ? [...baseEnv, ...broadcastEnvFiles.env] : baseEnv;
3555
- const scaffoldEnvExampleSegments = broadcastEnvFiles ? [...baseExample, ...broadcastEnvFiles.example] : baseExample;
3556
- const scaffoldEnv = renderEnvFileContents(scaffoldEnvSegments);
3557
- const scaffoldEnvExample = renderEnvFileContents(scaffoldEnvExampleSegments);
2499
+ const scaffoldEnv = appendScaffoldEnvGroup(env, broadcastEnvFiles?.env);
2500
+ const scaffoldEnvExample = appendScaffoldEnvGroup(example, broadcastEnvFiles?.example);
3558
2501
  await mkdir2(projectRoot, { recursive: true });
3559
2502
  await mkdir2(resolve3(projectRoot, "config"), { recursive: true });
3560
2503
  await mkdir2(resolve3(projectRoot, ".holo-js", "framework"), { recursive: true });
@@ -3756,6 +2699,25 @@ async function syncHostedAuthRouteFiles(projectRoot, features) {
3756
2699
  await mkdir3(dirname2(targetPath), { recursive: true });
3757
2700
  await writeTextFile(targetPath, file.contents);
3758
2701
  }
2702
+ if (framework === "next") {
2703
+ for (const file of renderNextManagedHostedAuthRouteFiles(features)) {
2704
+ await writeTextFile(resolve4(projectRoot, file.path), file.contents);
2705
+ }
2706
+ }
2707
+ }
2708
+ async function syncAuthRouteFiles(projectRoot) {
2709
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
2710
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
2711
+ if (!framework) {
2712
+ return;
2713
+ }
2714
+ for (const file of renderAuthRouteFiles(framework)) {
2715
+ const targetPath = resolve4(projectRoot, file.path);
2716
+ if (await pathExists(targetPath)) {
2717
+ continue;
2718
+ }
2719
+ await writeTextFile(targetPath, file.contents);
2720
+ }
3759
2721
  }
3760
2722
  async function installAuthIntoProject(projectRoot, features = {}) {
3761
2723
  const project = await loadProjectConfig(projectRoot);
@@ -3807,6 +2769,7 @@ async function installAuthIntoProject(projectRoot, features = {}) {
3807
2769
  }
3808
2770
  const createdCorsConfig2 = await ensureCorsConfigFile(projectRoot);
3809
2771
  await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
2772
+ await syncAuthRouteFiles(projectRoot);
3810
2773
  await syncHostedAuthRouteFiles(projectRoot, nextAuthFeatures);
3811
2774
  return {
3812
2775
  updatedPackageJson: await upsertAuthPackageDependencies(projectRoot, nextAuthFeatures),
@@ -3873,6 +2836,7 @@ async function installAuthIntoProject(projectRoot, features = {}) {
3873
2836
  await writeTextFile(envExamplePath, nextEnvExample.contents);
3874
2837
  }
3875
2838
  await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
2839
+ await syncAuthRouteFiles(projectRoot);
3876
2840
  await syncHostedAuthRouteFiles(projectRoot, features);
3877
2841
  return {
3878
2842
  updatedPackageJson: await upsertAuthPackageDependencies(projectRoot, features),
@@ -4049,7 +3013,7 @@ async function installMediaIntoProject(projectRoot) {
4049
3013
  if (!mediaConfigPath) {
4050
3014
  await writeTextFile(resolve4(projectRoot, "config/media.ts"), renderMediaConfig());
4051
3015
  }
4052
- const { createMediaTableMigration } = await import("./media-migrations-DU7WEKAY.mjs");
3016
+ const { createMediaTableMigration } = await import("./media-migrations-76KFHA2U.mjs");
4053
3017
  const migrationFilePath = await createMediaTableMigration(projectRoot, {
4054
3018
  skipIfExists: true
4055
3019
  });
@@ -4164,13 +3128,13 @@ async function installBroadcastIntoProject(projectRoot) {
4164
3128
  const dependencyResult = await upsertBroadcastPackageDependencies(projectRoot);
4165
3129
  let createdFrameworkSetup = false;
4166
3130
  if (framework === "next") {
4167
- const holoHelperPath = resolve4(projectRoot, "server/holo.ts");
3131
+ const holoHelperPath = resolve4(projectRoot, ".holo-js/generated/next/holo.ts");
4168
3132
  if (!await pathExists(holoHelperPath)) {
4169
3133
  await writeTextFile(holoHelperPath, renderNextHoloHelper());
4170
3134
  createdFrameworkSetup = true;
4171
3135
  }
4172
3136
  } else if (framework === "sveltekit") {
4173
- const holoHelperPath = resolve4(projectRoot, "src/lib/server/holo.ts");
3137
+ const holoHelperPath = resolve4(projectRoot, ".holo-js/generated/sveltekit/holo.ts");
4174
3138
  if (!await pathExists(holoHelperPath)) {
4175
3139
  await writeTextFile(holoHelperPath, renderSvelteHoloHelper());
4176
3140
  createdFrameworkSetup = true;
@@ -4238,9 +3202,6 @@ export {
4238
3202
  renderScaffoldGitignore,
4239
3203
  renderScaffoldTsconfig,
4240
3204
  renderVSCodeSettings,
4241
- renderAuthProviderRouteFiles,
4242
- renderFrameworkFiles,
4243
- renderFrameworkRunner,
4244
3205
  resolvePackageManagerVersion,
4245
3206
  renderScaffoldPackageJson,
4246
3207
  scaffoldProject,