@holo-js/cli 0.1.6 → 0.1.7

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.
@@ -30,7 +30,8 @@ import {
30
30
  SUPPORTED_CONFIG_EXTENSIONS,
31
31
  importProjectModule,
32
32
  isRecord,
33
- makeProjectRelativePath
33
+ makeProjectRelativePath,
34
+ normalizeScaffoldOptionalPackages
34
35
  } from "./chunk-R6BWRY3E.mjs";
35
36
 
36
37
  // src/templates.ts
@@ -253,6 +254,1151 @@ function resolveArtifactPath(root, subdir, directory, fileName) {
253
254
  return resolve(root, subdir, directory, fileName);
254
255
  }
255
256
 
257
+ // src/project/scaffold/framework-renderers.ts
258
+ var HOSTED_AUTH_PROVIDERS = {
259
+ workos: {
260
+ provider: "workos",
261
+ packageName: "@holo-js/auth-workos",
262
+ loginFunction: "loginWithWorkos",
263
+ registerFunction: "registerWithWorkos",
264
+ callbackFunction: "completeWorkosAuth",
265
+ logoutFunction: "logoutWithWorkos"
266
+ },
267
+ clerk: {
268
+ provider: "clerk",
269
+ packageName: "@holo-js/auth-clerk",
270
+ loginFunction: "loginWithClerk",
271
+ registerFunction: "registerWithClerk",
272
+ callbackFunction: "completeClerkAuth",
273
+ logoutFunction: "logoutWithClerk"
274
+ }
275
+ };
276
+ function getRequestedHostedAuthProviders(features) {
277
+ return [
278
+ ...features.workos ? ["workos"] : [],
279
+ ...features.clerk ? ["clerk"] : []
280
+ ];
281
+ }
282
+ function renderNuxtAppVue(projectName) {
283
+ return [
284
+ "<template>",
285
+ ' <main class="shell">',
286
+ " <h1>{{ appName }}</h1>",
287
+ " <p>Nuxt renders the UI. Holo owns the backend runtime and canonical server directories.</p>",
288
+ " </main>",
289
+ "</template>",
290
+ "",
291
+ '<script setup lang="ts">',
292
+ `const appName = ${JSON.stringify(projectName)}`,
293
+ "</script>",
294
+ "",
295
+ "<style scoped>",
296
+ ".shell {",
297
+ " min-height: 100vh;",
298
+ " display: grid;",
299
+ " place-content: center;",
300
+ " gap: 1rem;",
301
+ " padding: 3rem;",
302
+ " font-family: sans-serif;",
303
+ "}",
304
+ "h1 {",
305
+ " margin: 0;",
306
+ " font-size: clamp(2.5rem, 6vw, 4rem);",
307
+ "}",
308
+ "p {",
309
+ " margin: 0;",
310
+ " max-width: 40rem;",
311
+ " line-height: 1.6;",
312
+ "}",
313
+ "</style>",
314
+ ""
315
+ ].join("\n");
316
+ }
317
+ function renderNuxtConfig() {
318
+ return [
319
+ "export default defineNuxtConfig({",
320
+ " modules: ['@holo-js/adapter-nuxt'],",
321
+ " sourcemap: {",
322
+ " client: false,",
323
+ " server: false,",
324
+ " },",
325
+ " vite: {",
326
+ " build: {",
327
+ " rollupOptions: {",
328
+ " onwarn(warning, defaultHandler) {",
329
+ " if (",
330
+ " warning.message.includes('nuxt:module-preload-polyfill')",
331
+ " && warning.message.includes('didn\\'t generate a sourcemap')",
332
+ " ) {",
333
+ " return",
334
+ " }",
335
+ "",
336
+ " defaultHandler(warning)",
337
+ " },",
338
+ " },",
339
+ " },",
340
+ " },",
341
+ "})",
342
+ ""
343
+ ].join("\n");
344
+ }
345
+ function renderNuxtHealthRoute() {
346
+ return [
347
+ "export default defineEventHandler(async () => {",
348
+ " const app = await holo.getApp()",
349
+ "",
350
+ " return {",
351
+ " ok: true,",
352
+ " app: app.config.app.name,",
353
+ " env: app.config.app.env,",
354
+ " models: app.registry?.models.length ?? 0,",
355
+ " commands: app.registry?.commands.length ?? 0,",
356
+ " }",
357
+ "})",
358
+ ""
359
+ ].join("\n");
360
+ }
361
+ function renderNuxtCurrentAuthRoute() {
362
+ return [
363
+ "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
364
+ "import { setResponseStatus } from 'h3'",
365
+ "",
366
+ "export default defineEventHandler(async (event) => {",
367
+ " const query = getQuery(event)",
368
+ " const guard = typeof query.guard === 'string' ? query.guard : undefined",
369
+ " try {",
370
+ " const guardAuth = guard ? auth.guard(guard) : undefined",
371
+ "",
372
+ " return {",
373
+ " authenticated: guardAuth ? await guardAuth.check() : await check(),",
374
+ " guard: guard ?? 'web',",
375
+ " provider: guardAuth ? await guardAuth.provider() : await provider(),",
376
+ " user: guardAuth ? await guardAuth.user() : await user(),",
377
+ " }",
378
+ " } catch (error) {",
379
+ " if (isAuthError(error) && error.code === 'guard_not_configured') {",
380
+ " setResponseStatus(event, 400)",
381
+ "",
382
+ " return {",
383
+ " authenticated: false,",
384
+ " guard: guard ?? 'web',",
385
+ " provider: null,",
386
+ " user: null,",
387
+ " }",
388
+ " }",
389
+ "",
390
+ " throw error",
391
+ " }",
392
+ "})",
393
+ ""
394
+ ].join("\n");
395
+ }
396
+ function renderNuxtHostedAuthLoginRoute(spec) {
397
+ return [
398
+ `import { ${spec.loginFunction} } from '${spec.packageName}'`,
399
+ "",
400
+ "export default defineEventHandler(async (event) => {",
401
+ ` return await ${spec.loginFunction}(event)`,
402
+ "})",
403
+ ""
404
+ ].join("\n");
405
+ }
406
+ function renderNuxtHostedAuthRegisterRoute(spec) {
407
+ return [
408
+ `import { ${spec.registerFunction} } from '${spec.packageName}'`,
409
+ "",
410
+ "export default defineEventHandler(async (event) => {",
411
+ ` return await ${spec.registerFunction}(event)`,
412
+ "})",
413
+ ""
414
+ ].join("\n");
415
+ }
416
+ function renderNuxtHostedAuthCallbackRoute(spec) {
417
+ return [
418
+ `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
419
+ "import { sendRedirect } from 'h3'",
420
+ "",
421
+ "export default defineEventHandler(async (event) => {",
422
+ ` const { error } = await ${spec.callbackFunction}(event)`,
423
+ " if (error) {",
424
+ " return await sendRedirect(event, `/login?error=${encodeURIComponent(error.code)}`, 303)",
425
+ " }",
426
+ "",
427
+ " return await sendRedirect(event, '/', 303)",
428
+ "})",
429
+ ""
430
+ ].join("\n");
431
+ }
432
+ function renderNuxtHostedAuthLogoutRoute(spec) {
433
+ return [
434
+ "import { provider } from '@holo-js/auth'",
435
+ `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
436
+ "import { createError, sendRedirect } from 'h3'",
437
+ "",
438
+ "export default defineEventHandler(async (event) => {",
439
+ " let currentProvider: string | null",
440
+ " try {",
441
+ " currentProvider = await provider()",
442
+ " } catch {",
443
+ " return await sendRedirect(event, '/', 303)",
444
+ " }",
445
+ "",
446
+ ` if (currentProvider !== '${spec.provider}') {`,
447
+ " return await sendRedirect(event, '/', 303)",
448
+ " }",
449
+ "",
450
+ ` const { data, error } = await ${spec.logoutFunction}(event)`,
451
+ " if (error) {",
452
+ " throw createError({",
453
+ " statusCode: error.status,",
454
+ " statusMessage: error.message,",
455
+ " })",
456
+ " }",
457
+ "",
458
+ " return await sendRedirect(event, data.url, 303)",
459
+ "})",
460
+ ""
461
+ ].join("\n");
462
+ }
463
+ function renderNuxtHostedAuthRouteFiles(provider) {
464
+ const spec = HOSTED_AUTH_PROVIDERS[provider];
465
+ return [
466
+ { path: `server/api/auth/${provider}/login.get.ts`, contents: renderNuxtHostedAuthLoginRoute(spec) },
467
+ { path: `server/api/auth/${provider}/register.get.ts`, contents: renderNuxtHostedAuthRegisterRoute(spec) },
468
+ { path: `server/api/auth/${provider}/callback.get.ts`, contents: renderNuxtHostedAuthCallbackRoute(spec) },
469
+ { path: `server/api/auth/${provider}/logout.post.ts`, contents: renderNuxtHostedAuthLogoutRoute(spec) }
470
+ ];
471
+ }
472
+ function renderNextConfig() {
473
+ return [
474
+ "import type { NextConfig } from 'next'",
475
+ "import { withHolo } from '@holo-js/adapter-next/config'",
476
+ "",
477
+ "const nextConfig: NextConfig = withHolo({",
478
+ " /* config options here */",
479
+ "})",
480
+ "",
481
+ "export default nextConfig",
482
+ ""
483
+ ].join("\n");
484
+ }
485
+ function renderNextLayout(projectName) {
486
+ return [
487
+ "import type { ReactNode } from 'react'",
488
+ "",
489
+ "export const metadata = {",
490
+ ` title: ${JSON.stringify(projectName)},`,
491
+ " description: 'Holo on Next.js',",
492
+ "}",
493
+ "",
494
+ "export default function RootLayout({ children }: { children: ReactNode }) {",
495
+ " return (",
496
+ ' <html lang="en">',
497
+ " <body>{children}</body>",
498
+ " </html>",
499
+ " )",
500
+ "}",
501
+ ""
502
+ ].join("\n");
503
+ }
504
+ function escapeHtml(value) {
505
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("{", "&#123;").replaceAll("}", "&#125;");
506
+ }
507
+ function renderNextPage(projectName) {
508
+ const escapedProjectName = escapeHtml(projectName);
509
+ return [
510
+ "export default function HomePage() {",
511
+ " return (",
512
+ " <main style={{ padding: '3rem', fontFamily: 'sans-serif' }}>",
513
+ ` <h1>${escapedProjectName}</h1>`,
514
+ " <p>Next.js handles rendering. Holo powers the backend runtime and discovered server resources.</p>",
515
+ " </main>",
516
+ " )",
517
+ "}",
518
+ ""
519
+ ].join("\n");
520
+ }
521
+ function renderNextEnvDts() {
522
+ return [
523
+ '/// <reference types="next" />',
524
+ '/// <reference types="next/image-types/global" />',
525
+ "",
526
+ "// Generated by Holo. Do not edit.",
527
+ ""
528
+ ].join("\n");
529
+ }
530
+ function renderNextRouteBridge(modulePath, methods) {
531
+ return [
532
+ `export { ${methods.join(", ")} } from '${modulePath}'`,
533
+ ""
534
+ ].join("\n");
535
+ }
536
+ function renderNextHoloHelper() {
537
+ return [
538
+ "import { createNextHoloHelpers } from '@holo-js/adapter-next'",
539
+ "",
540
+ "export const holo = createNextHoloHelpers()",
541
+ ""
542
+ ].join("\n");
543
+ }
544
+ function renderNextHealthRoute() {
545
+ return renderNextRouteBridge("../../../../.holo-js/generated/next/health-route", ["GET"]);
546
+ }
547
+ function renderNextGeneratedHealthRoute() {
548
+ return [
549
+ "import { holo } from '../../../server/holo'",
550
+ "",
551
+ "export async function GET() {",
552
+ " const app = await holo.getApp()",
553
+ "",
554
+ " return Response.json({",
555
+ " ok: true,",
556
+ " app: app.config.app.name,",
557
+ " env: app.config.app.env,",
558
+ " models: app.registry?.models.length ?? 0,",
559
+ " commands: app.registry?.commands.length ?? 0,",
560
+ " })",
561
+ "}",
562
+ ""
563
+ ].join("\n");
564
+ }
565
+ function renderNextCurrentAuthRoute() {
566
+ return renderNextRouteBridge("../../../../.holo-js/generated/next/auth-user-route", ["GET"]);
567
+ }
568
+ function renderNextGeneratedCurrentAuthRoute() {
569
+ return [
570
+ "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
571
+ "import { holo } from '../../../server/holo'",
572
+ "",
573
+ "export async function GET(request: Request) {",
574
+ " await holo.getApp()",
575
+ "",
576
+ " const guard = new URL(request.url).searchParams.get('guard') ?? undefined",
577
+ " try {",
578
+ " const guardAuth = guard ? auth.guard(guard) : undefined",
579
+ "",
580
+ " return Response.json({",
581
+ " authenticated: guardAuth ? await guardAuth.check() : await check(),",
582
+ " guard: guard ?? 'web',",
583
+ " provider: guardAuth ? await guardAuth.provider() : await provider(),",
584
+ " user: guardAuth ? await guardAuth.user() : await user(),",
585
+ " })",
586
+ " } catch (error) {",
587
+ " if (isAuthError(error) && error.code === 'guard_not_configured') {",
588
+ " return Response.json({",
589
+ " authenticated: false,",
590
+ " guard: guard ?? 'web',",
591
+ " provider: null,",
592
+ " user: null,",
593
+ " }, { status: 400 })",
594
+ " }",
595
+ "",
596
+ " throw error",
597
+ " }",
598
+ "}",
599
+ ""
600
+ ].join("\n");
601
+ }
602
+ function renderNextHostedAuthLoginRoute(spec) {
603
+ return renderNextRouteBridge(`../../../../../.holo-js/generated/next/auth-${spec.provider}-login-route`, ["GET"]);
604
+ }
605
+ function renderNextGeneratedHostedAuthLoginRoute(spec) {
606
+ return [
607
+ `import { ${spec.loginFunction} } from '${spec.packageName}'`,
608
+ "import { holo } from '../../../server/holo'",
609
+ "",
610
+ "export async function GET(request: Request) {",
611
+ " await holo.getApp()",
612
+ "",
613
+ ` return await ${spec.loginFunction}(request)`,
614
+ "}",
615
+ ""
616
+ ].join("\n");
617
+ }
618
+ function renderNextHostedAuthRegisterRoute(spec) {
619
+ return renderNextRouteBridge(`../../../../../.holo-js/generated/next/auth-${spec.provider}-register-route`, ["GET"]);
620
+ }
621
+ function renderNextGeneratedHostedAuthRegisterRoute(spec) {
622
+ return [
623
+ `import { ${spec.registerFunction} } from '${spec.packageName}'`,
624
+ "import { holo } from '../../../server/holo'",
625
+ "",
626
+ "export async function GET(request: Request) {",
627
+ " await holo.getApp()",
628
+ "",
629
+ ` return await ${spec.registerFunction}(request)`,
630
+ "}",
631
+ ""
632
+ ].join("\n");
633
+ }
634
+ function renderNextHostedAuthCallbackRoute(spec) {
635
+ return renderNextRouteBridge(`../../../../../.holo-js/generated/next/auth-${spec.provider}-callback-route`, ["GET"]);
636
+ }
637
+ function renderNextGeneratedHostedAuthCallbackRoute(spec) {
638
+ return [
639
+ `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
640
+ "import { holo } from '../../../server/holo'",
641
+ "",
642
+ "export async function GET(request: Request) {",
643
+ " await holo.getApp()",
644
+ "",
645
+ ` const { error } = await ${spec.callbackFunction}(request)`,
646
+ " if (error) {",
647
+ " return Response.redirect(new URL(`/login?error=${encodeURIComponent(error.code)}`, request.url))",
648
+ " }",
649
+ "",
650
+ " return Response.redirect(new URL('/', request.url))",
651
+ "}",
652
+ ""
653
+ ].join("\n");
654
+ }
655
+ function renderNextHostedAuthLogoutRoute(spec) {
656
+ return renderNextRouteBridge(`../../../../../.holo-js/generated/next/auth-${spec.provider}-logout-route`, ["POST"]);
657
+ }
658
+ function renderNextGeneratedHostedAuthLogoutRoute(spec) {
659
+ return [
660
+ "import { provider } from '@holo-js/auth'",
661
+ `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
662
+ "import { holo } from '../../../server/holo'",
663
+ "",
664
+ "export async function POST(request: Request) {",
665
+ " await holo.getApp()",
666
+ "",
667
+ " let currentProvider: string | null",
668
+ " try {",
669
+ " currentProvider = await provider()",
670
+ " } catch {",
671
+ " return Response.redirect(new URL('/', request.url), 303)",
672
+ " }",
673
+ "",
674
+ ` if (currentProvider !== '${spec.provider}') {`,
675
+ " return Response.redirect(new URL('/', request.url), 303)",
676
+ " }",
677
+ "",
678
+ ` const { data, error } = await ${spec.logoutFunction}(request)`,
679
+ " if (error) {",
680
+ " return Response.json({ data, error }, { status: error.status })",
681
+ " }",
682
+ "",
683
+ " return Response.redirect(data.url, 303)",
684
+ "}",
685
+ ""
686
+ ].join("\n");
687
+ }
688
+ function renderNextHostedAuthRouteFiles(provider) {
689
+ const spec = HOSTED_AUTH_PROVIDERS[provider];
690
+ return [
691
+ { path: `app/api/auth/${provider}/login/route.ts`, contents: renderNextHostedAuthLoginRoute(spec) },
692
+ { path: `app/api/auth/${provider}/register/route.ts`, contents: renderNextHostedAuthRegisterRoute(spec) },
693
+ { path: `app/api/auth/${provider}/callback/route.ts`, contents: renderNextHostedAuthCallbackRoute(spec) },
694
+ { path: `app/api/auth/${provider}/logout/route.ts`, contents: renderNextHostedAuthLogoutRoute(spec) }
695
+ ];
696
+ }
697
+ function renderNextStorageRoute() {
698
+ return renderNextRouteBridge("../../../.holo-js/generated/next/storage-route", ["GET"]);
699
+ }
700
+ function renderNextGeneratedStorageRoute() {
701
+ return [
702
+ "import { createPublicStorageResponse } from '@holo-js/storage'",
703
+ "import { holo } from '../../../server/holo'",
704
+ "",
705
+ "export async function GET(request: Request) {",
706
+ " const app = await holo.getApp()",
707
+ " return createPublicStorageResponse(app.projectRoot, app.config.storage, request)",
708
+ "}",
709
+ ""
710
+ ].join("\n");
711
+ }
712
+ function renderNextBroadcastAuthRoute() {
713
+ return renderNextRouteBridge("../../../.holo-js/generated/next/broadcast-auth-route", ["POST"]);
714
+ }
715
+ function renderNextGeneratedBroadcastAuthRoute() {
716
+ return [
717
+ "import { renderBroadcastAuthResponse } from '@holo-js/broadcast/auth'",
718
+ "import { holo } from '../../../server/holo'",
719
+ "",
720
+ "export async function POST(request: Request) {",
721
+ " const app = await holo.getApp()",
722
+ " const auth = await holo.getAuth()",
723
+ "",
724
+ " return await renderBroadcastAuthResponse(request, {",
725
+ " resolveUser: async () => await auth?.user(),",
726
+ " channelAuth: {",
727
+ " registry: {",
728
+ " projectRoot: app.projectRoot,",
729
+ " channels: app.registry?.channels ?? [],",
730
+ " },",
731
+ " },",
732
+ " })",
733
+ "}",
734
+ ""
735
+ ].join("\n");
736
+ }
737
+ function renderNextManagedRouteFiles(options = {}) {
738
+ return [
739
+ { path: ".holo-js/generated/next/health-route.ts", contents: renderNextGeneratedHealthRoute() },
740
+ ...options.authEnabled ? [{ path: ".holo-js/generated/next/auth-user-route.ts", contents: renderNextGeneratedCurrentAuthRoute() }] : [],
741
+ ...options.storageEnabled ? [{ path: ".holo-js/generated/next/storage-route.ts", contents: renderNextGeneratedStorageRoute() }] : [],
742
+ ...options.broadcastAuthEnabled ? [{ path: ".holo-js/generated/next/broadcast-auth-route.ts", contents: renderNextGeneratedBroadcastAuthRoute() }] : []
743
+ ];
744
+ }
745
+ function renderNextManagedHostedAuthRouteFiles(features) {
746
+ return getRequestedHostedAuthProviders(features).flatMap((provider) => {
747
+ const spec = HOSTED_AUTH_PROVIDERS[provider];
748
+ return [
749
+ { path: `.holo-js/generated/next/auth-${provider}-login-route.ts`, contents: renderNextGeneratedHostedAuthLoginRoute(spec) },
750
+ { path: `.holo-js/generated/next/auth-${provider}-register-route.ts`, contents: renderNextGeneratedHostedAuthRegisterRoute(spec) },
751
+ { path: `.holo-js/generated/next/auth-${provider}-callback-route.ts`, contents: renderNextGeneratedHostedAuthCallbackRoute(spec) },
752
+ { path: `.holo-js/generated/next/auth-${provider}-logout-route.ts`, contents: renderNextGeneratedHostedAuthLogoutRoute(spec) }
753
+ ];
754
+ });
755
+ }
756
+ function renderSvelteConfig() {
757
+ return [
758
+ "import adapter from '@sveltejs/adapter-node'",
759
+ "import { withHoloSvelteKit } from '@holo-js/adapter-sveltekit/config'",
760
+ "import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'",
761
+ "",
762
+ "/** @type {import('@sveltejs/kit').Config} */",
763
+ "const config = withHoloSvelteKit({",
764
+ " preprocess: vitePreprocess(),",
765
+ " kit: {",
766
+ " adapter: adapter(),",
767
+ " },",
768
+ "})",
769
+ "",
770
+ "export default config",
771
+ ""
772
+ ].join("\n");
773
+ }
774
+ function renderSvelteUserHooks() {
775
+ return [
776
+ "export {}",
777
+ ""
778
+ ].join("\n");
779
+ }
780
+ function renderSvelteServerUserHooks() {
781
+ return [
782
+ "export {}",
783
+ ""
784
+ ].join("\n");
785
+ }
786
+ function renderSvelteViteConfig(_storageEnabled) {
787
+ const externals = [
788
+ " '@holo-js/adapter-sveltekit',",
789
+ " '@holo-js/auth',",
790
+ " '@holo-js/auth-clerk',",
791
+ " '@holo-js/auth-social',",
792
+ " '@holo-js/auth-workos',",
793
+ " '@holo-js/authorization',",
794
+ " '@holo-js/broadcast',",
795
+ " '@holo-js/cache',",
796
+ " '@holo-js/cache-db',",
797
+ " '@holo-js/cache-redis',",
798
+ " '@holo-js/config',",
799
+ " '@holo-js/core',",
800
+ " '@holo-js/db',",
801
+ " '@holo-js/db-mysql',",
802
+ " '@holo-js/db-postgres',",
803
+ " '@holo-js/db-sqlite',",
804
+ " '@holo-js/events',",
805
+ " '@holo-js/flux',",
806
+ " '@holo-js/flux-svelte',",
807
+ " '@holo-js/forms',",
808
+ " '@holo-js/mail',",
809
+ " '@holo-js/media',",
810
+ " '@holo-js/notifications',",
811
+ " '@holo-js/queue',",
812
+ " '@holo-js/queue-db',",
813
+ " '@holo-js/queue-redis',",
814
+ " '@holo-js/security',",
815
+ " '@holo-js/session',",
816
+ " '@holo-js/storage',",
817
+ " '@holo-js/storage/runtime',",
818
+ " '@holo-js/storage-s3',",
819
+ " '@holo-js/validation',",
820
+ " 'better-sqlite3',",
821
+ " 'ioredis',",
822
+ " 'mysql2',",
823
+ " 'pg',"
824
+ ];
825
+ return [
826
+ "import { sveltekit } from '@sveltejs/kit/vite'",
827
+ "import { defineConfig } from 'vite'",
828
+ "",
829
+ "export default defineConfig({",
830
+ " plugins: [sveltekit()],",
831
+ " server: {",
832
+ " fs: {",
833
+ " allow: ['.holo-js/generated'],",
834
+ " },",
835
+ " },",
836
+ " ssr: {",
837
+ " external: [",
838
+ ...externals,
839
+ " ],",
840
+ " },",
841
+ "})",
842
+ ""
843
+ ].join("\n");
844
+ }
845
+ function renderSvelteAppHtml() {
846
+ return [
847
+ "<!doctype html>",
848
+ '<html lang="en">',
849
+ " <head>",
850
+ ' <meta charset="utf-8" />',
851
+ ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
852
+ " %sveltekit.head%",
853
+ " </head>",
854
+ ' <body data-sveltekit-preload-data="hover">',
855
+ ' <div style="display: contents">%sveltekit.body%</div>',
856
+ " </body>",
857
+ "</html>",
858
+ ""
859
+ ].join("\n");
860
+ }
861
+ function renderSveltePage(projectName) {
862
+ const escapedProjectName = escapeHtml(projectName);
863
+ return [
864
+ `<svelte:head><title>${escapedProjectName}</title></svelte:head>`,
865
+ "",
866
+ '<script lang="ts">',
867
+ ` const projectName = ${JSON.stringify(projectName)}`,
868
+ "</script>",
869
+ "",
870
+ '<main class="shell">',
871
+ " <h1>{projectName}</h1>",
872
+ " <p>SvelteKit owns rendering. Holo owns config, discovery, and backend runtime services.</p>",
873
+ "</main>",
874
+ "",
875
+ "<style>",
876
+ " .shell {",
877
+ " min-height: 100vh;",
878
+ " display: grid;",
879
+ " place-content: center;",
880
+ " gap: 1rem;",
881
+ " padding: 3rem;",
882
+ " font-family: sans-serif;",
883
+ " }",
884
+ " h1 {",
885
+ " margin: 0;",
886
+ " font-size: clamp(2.5rem, 6vw, 4rem);",
887
+ " }",
888
+ " p {",
889
+ " margin: 0;",
890
+ " max-width: 40rem;",
891
+ " line-height: 1.6;",
892
+ " }",
893
+ "</style>",
894
+ ""
895
+ ].join("\n");
896
+ }
897
+ function renderSvelteHoloHelper() {
898
+ return [
899
+ "import { createSvelteKitHoloHelpers } from '@holo-js/adapter-sveltekit'",
900
+ "",
901
+ "export const holo = createSvelteKitHoloHelpers()",
902
+ ""
903
+ ].join("\n");
904
+ }
905
+ function renderSvelteHealthRoute() {
906
+ return [
907
+ "import { json } from '@sveltejs/kit'",
908
+ "import { holo } from '$lib/server/holo'",
909
+ "",
910
+ "export async function GET() {",
911
+ " const app = await holo.getApp()",
912
+ "",
913
+ " return json({",
914
+ " ok: true,",
915
+ " app: app.config.app.name,",
916
+ " env: app.config.app.env,",
917
+ " models: app.registry?.models.length ?? 0,",
918
+ " commands: app.registry?.commands.length ?? 0,",
919
+ " })",
920
+ "}",
921
+ ""
922
+ ].join("\n");
923
+ }
924
+ function renderSvelteCurrentAuthRoute() {
925
+ return [
926
+ "import { json } from '@sveltejs/kit'",
927
+ "import auth, { check, isAuthError, provider, user } from '@holo-js/auth'",
928
+ "",
929
+ "export async function GET({ url }: { url: URL }) {",
930
+ " const guard = url.searchParams.get('guard') ?? undefined",
931
+ " try {",
932
+ " const guardAuth = guard ? auth.guard(guard) : undefined",
933
+ "",
934
+ " return json({",
935
+ " authenticated: guardAuth ? await guardAuth.check() : await check(),",
936
+ " guard: guard ?? 'web',",
937
+ " provider: guardAuth ? await guardAuth.provider() : await provider(),",
938
+ " user: guardAuth ? await guardAuth.user() : await user(),",
939
+ " })",
940
+ " } catch (error) {",
941
+ " if (isAuthError(error) && error.code === 'guard_not_configured') {",
942
+ " return json({",
943
+ " authenticated: false,",
944
+ " guard: guard ?? 'web',",
945
+ " provider: null,",
946
+ " user: null,",
947
+ " }, { status: 400 })",
948
+ " }",
949
+ "",
950
+ " throw error",
951
+ " }",
952
+ "}",
953
+ ""
954
+ ].join("\n");
955
+ }
956
+ function renderSvelteHostedAuthLoginRoute(spec) {
957
+ return [
958
+ `import { ${spec.loginFunction} } from '${spec.packageName}'`,
959
+ "import type { RequestHandler } from './$types'",
960
+ "",
961
+ "export const GET = (async (event) => {",
962
+ ` return await ${spec.loginFunction}(event)`,
963
+ "}) satisfies RequestHandler",
964
+ ""
965
+ ].join("\n");
966
+ }
967
+ function renderSvelteHostedAuthRegisterRoute(spec) {
968
+ return [
969
+ `import { ${spec.registerFunction} } from '${spec.packageName}'`,
970
+ "import type { RequestHandler } from './$types'",
971
+ "",
972
+ "export const GET = (async (event) => {",
973
+ ` return await ${spec.registerFunction}(event)`,
974
+ "}) satisfies RequestHandler",
975
+ ""
976
+ ].join("\n");
977
+ }
978
+ function renderSvelteHostedAuthCallbackRoute(spec) {
979
+ return [
980
+ "import { redirect, type RequestHandler } from '@sveltejs/kit'",
981
+ `import { ${spec.callbackFunction} } from '${spec.packageName}'`,
982
+ "",
983
+ "export const GET = (async (event) => {",
984
+ ` const { error } = await ${spec.callbackFunction}(event)`,
985
+ " if (error) {",
986
+ " throw redirect(303, `/login?error=${encodeURIComponent(error.code)}`)",
987
+ " }",
988
+ "",
989
+ " throw redirect(303, '/')",
990
+ "}) satisfies RequestHandler",
991
+ ""
992
+ ].join("\n");
993
+ }
994
+ function renderSvelteHostedAuthLogoutRoute(spec) {
995
+ return [
996
+ "import { redirect, type RequestHandler } from '@sveltejs/kit'",
997
+ "import { provider } from '@holo-js/auth'",
998
+ `import { ${spec.logoutFunction} } from '${spec.packageName}'`,
999
+ "",
1000
+ "export const POST = (async (event) => {",
1001
+ " let currentProvider: string | null",
1002
+ " try {",
1003
+ " currentProvider = await provider()",
1004
+ " } catch {",
1005
+ " throw redirect(303, '/')",
1006
+ " }",
1007
+ "",
1008
+ ` if (currentProvider !== '${spec.provider}') {`,
1009
+ " throw redirect(303, '/')",
1010
+ " }",
1011
+ "",
1012
+ ` const { data, error } = await ${spec.logoutFunction}(event)`,
1013
+ " if (error) {",
1014
+ " return Response.json({ data, error }, { status: error.status })",
1015
+ " }",
1016
+ "",
1017
+ " throw redirect(303, data.url)",
1018
+ "}) satisfies RequestHandler",
1019
+ ""
1020
+ ].join("\n");
1021
+ }
1022
+ function renderSvelteHostedAuthRouteFiles(provider) {
1023
+ const spec = HOSTED_AUTH_PROVIDERS[provider];
1024
+ return [
1025
+ { path: `src/routes/api/auth/${provider}/login/+server.ts`, contents: renderSvelteHostedAuthLoginRoute(spec) },
1026
+ { path: `src/routes/api/auth/${provider}/register/+server.ts`, contents: renderSvelteHostedAuthRegisterRoute(spec) },
1027
+ { path: `src/routes/api/auth/${provider}/callback/+server.ts`, contents: renderSvelteHostedAuthCallbackRoute(spec) },
1028
+ { path: `src/routes/api/auth/${provider}/logout/+server.ts`, contents: renderSvelteHostedAuthLogoutRoute(spec) }
1029
+ ];
1030
+ }
1031
+ function renderAuthProviderRouteFiles(framework, features) {
1032
+ return getRequestedHostedAuthProviders(features).flatMap((provider) => {
1033
+ if (framework === "nuxt") {
1034
+ return renderNuxtHostedAuthRouteFiles(provider);
1035
+ }
1036
+ if (framework === "next") {
1037
+ return renderNextHostedAuthRouteFiles(provider);
1038
+ }
1039
+ return renderSvelteHostedAuthRouteFiles(provider);
1040
+ });
1041
+ }
1042
+ function renderAuthRouteFiles(framework) {
1043
+ if (framework === "next") {
1044
+ return [
1045
+ { path: "app/api/auth/user/route.ts", contents: renderNextCurrentAuthRoute() },
1046
+ { path: ".holo-js/generated/next/auth-user-route.ts", contents: renderNextGeneratedCurrentAuthRoute() }
1047
+ ];
1048
+ }
1049
+ if (framework === "nuxt") {
1050
+ return [
1051
+ { path: "server/api/auth/user.get.ts", contents: renderNuxtCurrentAuthRoute() }
1052
+ ];
1053
+ }
1054
+ return [
1055
+ { path: "src/routes/api/auth/user/+server.ts", contents: renderSvelteCurrentAuthRoute() }
1056
+ ];
1057
+ }
1058
+ function renderSvelteStorageRoute() {
1059
+ return [
1060
+ "import { holo } from '$lib/server/holo'",
1061
+ "import { createPublicStorageResponse } from '@holo-js/storage'",
1062
+ "",
1063
+ "export async function GET({ request }: { request: Request }) {",
1064
+ " const app = await holo.getApp()",
1065
+ " return createPublicStorageResponse(app.projectRoot, app.config.storage, request)",
1066
+ "}",
1067
+ ""
1068
+ ].join("\n");
1069
+ }
1070
+ function renderFrameworkFiles(options) {
1071
+ const optionalPackages = normalizeScaffoldOptionalPackages(options.optionalPackages);
1072
+ const storageEnabled = optionalPackages.includes("storage");
1073
+ const authEnabled = optionalPackages.includes("auth");
1074
+ if (options.framework === "nuxt") {
1075
+ return [
1076
+ { path: "app/app.vue", contents: renderNuxtAppVue(options.projectName) },
1077
+ { path: "nuxt.config.ts", contents: renderNuxtConfig() },
1078
+ { path: "server/api/holo/health.get.ts", contents: renderNuxtHealthRoute() },
1079
+ { path: "shared/.gitkeep", contents: "" },
1080
+ ...authEnabled ? renderAuthRouteFiles("nuxt") : []
1081
+ ];
1082
+ }
1083
+ if (options.framework === "next") {
1084
+ return [
1085
+ { path: "next.config.ts", contents: renderNextConfig() },
1086
+ { path: "next-env.d.ts", contents: renderNextEnvDts() },
1087
+ { path: "app/layout.tsx", contents: renderNextLayout(options.projectName) },
1088
+ { path: "app/page.tsx", contents: renderNextPage(options.projectName) },
1089
+ { path: "app/api/holo/health/route.ts", contents: renderNextHealthRoute() },
1090
+ ...authEnabled ? [{ path: "app/api/auth/user/route.ts", contents: renderNextCurrentAuthRoute() }] : [],
1091
+ ...storageEnabled ? [{ path: "app/storage/[[...path]]/route.ts", contents: renderNextStorageRoute() }] : [],
1092
+ { path: "server/holo.ts", contents: renderNextHoloHelper() },
1093
+ ...renderNextManagedRouteFiles({ authEnabled, storageEnabled })
1094
+ ];
1095
+ }
1096
+ return [
1097
+ { path: "svelte.config.js", contents: renderSvelteConfig() },
1098
+ { path: "vite.config.ts", contents: renderSvelteViteConfig(storageEnabled) },
1099
+ { path: "src/hooks.ts", contents: renderSvelteUserHooks() },
1100
+ { path: "src/hooks.server.ts", contents: renderSvelteServerUserHooks() },
1101
+ { path: "src/app.html", contents: renderSvelteAppHtml() },
1102
+ { path: "src/routes/+page.svelte", contents: renderSveltePage(options.projectName) },
1103
+ { path: "src/routes/api/holo/health/+server.ts", contents: renderSvelteHealthRoute() },
1104
+ ...authEnabled ? renderAuthRouteFiles("sveltekit") : [],
1105
+ ...storageEnabled ? [{ path: "src/routes/storage/[...path]/+server.ts", contents: renderSvelteStorageRoute() }] : [],
1106
+ { path: "src/lib/server/holo.ts", contents: renderSvelteHoloHelper() }
1107
+ ];
1108
+ }
1109
+ function renderFrameworkRunner(options) {
1110
+ const commandName = options.framework === "nuxt" ? "nuxt" : options.framework === "next" ? "next" : "vite";
1111
+ return [
1112
+ "import { existsSync, readFileSync, readlinkSync } from 'node:fs'",
1113
+ "import { dirname, resolve } from 'node:path'",
1114
+ "import { fileURLToPath, pathToFileURL } from 'node:url'",
1115
+ "import { execFileSync, spawn } from 'node:child_process'",
1116
+ "",
1117
+ "const mode = process.argv[2]",
1118
+ "const manifestPath = fileURLToPath(new URL('./project.json', import.meta.url))",
1119
+ "const projectRoot = resolve(dirname(manifestPath), '../..')",
1120
+ "const runtimeSchemaPath = resolve(projectRoot, '.holo-js/generated/schema.mjs')",
1121
+ "const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))",
1122
+ "const framework = String(manifest.framework ?? '')",
1123
+ `const commandName = ${JSON.stringify(commandName)}`,
1124
+ "const commandArgs = mode === 'dev'",
1125
+ " ? ['dev']",
1126
+ " : mode === 'build'",
1127
+ " ? framework === 'sveltekit' ? ['build', '--logLevel', 'error'] : ['build']",
1128
+ " : undefined",
1129
+ "",
1130
+ "if (!commandArgs) {",
1131
+ " console.error(`[holo] Unknown framework runner mode: ${String(mode)}`)",
1132
+ " process.exit(1)",
1133
+ "}",
1134
+ "",
1135
+ "const binaryPath = resolve(",
1136
+ " projectRoot,",
1137
+ " 'node_modules',",
1138
+ " '.bin',",
1139
+ " process.platform === 'win32' ? `${commandName}.cmd` : commandName,",
1140
+ ")",
1141
+ "",
1142
+ "const suppressedOutput = framework === 'sveltekit'",
1143
+ " ? new Set([",
1144
+ ` '"try_get_request_store" is imported from external module "@sveltejs/kit/internal/server" but never used in ".svelte-kit/adapter-node/index.js".',`,
1145
+ " ])",
1146
+ " : new Set()",
1147
+ "",
1148
+ "function shouldSuppressOutput(line) {",
1149
+ " if (suppressedOutput.has(line)) {",
1150
+ " return true",
1151
+ " }",
1152
+ "",
1153
+ " return framework === 'sveltekit'",
1154
+ " && line.startsWith('Circular dependency: ')",
1155
+ " && line.includes('/node_modules/semver/')",
1156
+ "}",
1157
+ "",
1158
+ "function pipeOutput(stream, target, onLine) {",
1159
+ " if (!stream) {",
1160
+ " return",
1161
+ " }",
1162
+ "",
1163
+ " let buffered = ''",
1164
+ " stream.on('data', (chunk) => {",
1165
+ " buffered += chunk.toString()",
1166
+ " const lines = buffered.split(/\\r?\\n/)",
1167
+ " buffered = lines.pop() ?? ''",
1168
+ " for (const line of lines) {",
1169
+ " onLine?.(line)",
1170
+ " if (!shouldSuppressOutput(line)) {",
1171
+ " target.write(`${line}\\n`)",
1172
+ " }",
1173
+ " }",
1174
+ " })",
1175
+ "",
1176
+ " stream.on('end', () => {",
1177
+ " if (buffered.length > 0) {",
1178
+ " onLine?.(buffered)",
1179
+ " }",
1180
+ " if (buffered.length > 0 && !shouldSuppressOutput(buffered)) {",
1181
+ " target.write(buffered)",
1182
+ " }",
1183
+ " })",
1184
+ "}",
1185
+ "",
1186
+ "function extractNextConflictInfo(lines) {",
1187
+ " if (framework !== 'next' || mode !== 'dev') {",
1188
+ " return undefined",
1189
+ " }",
1190
+ "",
1191
+ " if (!lines.some(line => line.includes('Another next dev server is already running.'))) {",
1192
+ " return undefined",
1193
+ " }",
1194
+ "",
1195
+ " let pid",
1196
+ " let dir",
1197
+ "",
1198
+ " for (const line of lines) {",
1199
+ " const match = line.match(/^- PID:\\s+(\\d+)\\s*$/)",
1200
+ " if (match) {",
1201
+ " pid = Number.parseInt(match[1], 10)",
1202
+ " continue",
1203
+ " }",
1204
+ "",
1205
+ " const dirMatch = line.match(/^- Dir:\\s+(.+?)\\s*$/)",
1206
+ " if (dirMatch) {",
1207
+ " dir = dirMatch[1]",
1208
+ " }",
1209
+ " }",
1210
+ "",
1211
+ " return typeof pid === 'number' ? { pid, dir } : undefined",
1212
+ "}",
1213
+ "",
1214
+ "async function waitForProcessExit(pid, timeoutMs = 5000) {",
1215
+ " const deadline = Date.now() + timeoutMs",
1216
+ " while (Date.now() < deadline) {",
1217
+ " try {",
1218
+ " process.kill(pid, 0)",
1219
+ " } catch (error) {",
1220
+ " if (error && typeof error === 'object' && 'code' in error && error.code === 'ESRCH') {",
1221
+ " return true",
1222
+ " }",
1223
+ " throw error",
1224
+ " }",
1225
+ "",
1226
+ " await new Promise(resolve => setTimeout(resolve, 100))",
1227
+ " }",
1228
+ "",
1229
+ " return false",
1230
+ "}",
1231
+ "",
1232
+ "function inspectProcess(pid) {",
1233
+ " try {",
1234
+ " if (process.platform === 'linux' && existsSync(`/proc/${pid}`)) {",
1235
+ " return {",
1236
+ " cwd: readlinkSync(`/proc/${pid}/cwd`),",
1237
+ " args: readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\\u0000', ' ').trim(),",
1238
+ " }",
1239
+ " }",
1240
+ " } catch {",
1241
+ " // Fall through to the portable process inspection path below.",
1242
+ " }",
1243
+ "",
1244
+ " try {",
1245
+ " return {",
1246
+ " args: execFileSync('ps', ['-p', String(pid), '-o', 'args='], {",
1247
+ " encoding: 'utf8',",
1248
+ " }).trim(),",
1249
+ " }",
1250
+ " } catch {",
1251
+ " return undefined",
1252
+ " }",
1253
+ "}",
1254
+ "",
1255
+ "function isOwnedNextDevServer(pid, reportedDir) {",
1256
+ " const expectedDir = typeof reportedDir === 'string' ? resolve(reportedDir) : undefined",
1257
+ " if (expectedDir && expectedDir !== projectRoot) {",
1258
+ " return false",
1259
+ " }",
1260
+ "",
1261
+ " const details = inspectProcess(pid)",
1262
+ " if (!details) {",
1263
+ " return expectedDir === projectRoot",
1264
+ " }",
1265
+ "",
1266
+ " const argsMatch = details.args.includes('next') && details.args.includes('dev')",
1267
+ " const cwdMatches = typeof details.cwd === 'string' && resolve(details.cwd) === projectRoot",
1268
+ " const argsReferenceProject = details.args.includes(projectRoot)",
1269
+ "",
1270
+ " return argsMatch && (cwdMatches || argsReferenceProject || expectedDir === projectRoot)",
1271
+ "}",
1272
+ "",
1273
+ "async function stopStaleNextDevServer(pid, reportedDir, force = false) {",
1274
+ " if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) {",
1275
+ " return false",
1276
+ " }",
1277
+ "",
1278
+ " if (!isOwnedNextDevServer(pid, reportedDir)) {",
1279
+ " return false",
1280
+ " }",
1281
+ "",
1282
+ " if (!force) {",
1283
+ " return false",
1284
+ " }",
1285
+ "",
1286
+ " try {",
1287
+ " process.kill(pid, 'SIGTERM')",
1288
+ " } catch (error) {",
1289
+ " if (error && typeof error === 'object' && 'code' in error && error.code === 'ESRCH') {",
1290
+ " return true",
1291
+ " }",
1292
+ " return false",
1293
+ " }",
1294
+ "",
1295
+ " return waitForProcessExit(pid)",
1296
+ "}",
1297
+ "",
1298
+ "if (!existsSync(binaryPath)) {",
1299
+ ' console.error(`[holo] Missing framework binary "${commandName}" for "${framework}". Run your package manager install first.`)',
1300
+ " process.exit(1)",
1301
+ "}",
1302
+ "",
1303
+ "let child = null",
1304
+ "let forwardedSignal = null",
1305
+ "",
1306
+ "function detachSignalForwarders() {",
1307
+ " process.removeListener('SIGINT', onSigint)",
1308
+ " process.removeListener('SIGTERM', onSigterm)",
1309
+ "}",
1310
+ "",
1311
+ "function forwardSignal(signal) {",
1312
+ " if (forwardedSignal || !child || child.exitCode !== null) {",
1313
+ " return",
1314
+ " }",
1315
+ "",
1316
+ " forwardedSignal = signal",
1317
+ " child.kill(signal)",
1318
+ "}",
1319
+ "",
1320
+ "function onSigint() {",
1321
+ " detachSignalForwarders()",
1322
+ " forwardSignal('SIGINT')",
1323
+ "}",
1324
+ "",
1325
+ "function onSigterm() {",
1326
+ " detachSignalForwarders()",
1327
+ " forwardSignal('SIGTERM')",
1328
+ "}",
1329
+ "",
1330
+ "process.on('SIGINT', onSigint)",
1331
+ "process.on('SIGTERM', onSigterm)",
1332
+ "",
1333
+ "async function run() {",
1334
+ " let restartedAfterConflict = false",
1335
+ " const maxStderrLines = 200",
1336
+ "",
1337
+ " while (true) {",
1338
+ " const stderrLines = []",
1339
+ " const childEnv = { ...process.env }",
1340
+ " if (existsSync(runtimeSchemaPath)) {",
1341
+ " const preload = `--import=${pathToFileURL(runtimeSchemaPath).href}`",
1342
+ " childEnv.NODE_OPTIONS = childEnv.NODE_OPTIONS",
1343
+ " ? `${childEnv.NODE_OPTIONS} ${preload}`",
1344
+ " : preload",
1345
+ " }",
1346
+ " child = spawn(binaryPath, commandArgs, {",
1347
+ " cwd: projectRoot,",
1348
+ " env: childEnv,",
1349
+ " stdio: ['inherit', 'pipe', 'pipe'],",
1350
+ " })",
1351
+ " forwardedSignal = null",
1352
+ "",
1353
+ " pipeOutput(child.stdout, process.stdout)",
1354
+ " pipeOutput(child.stderr, process.stderr, line => {",
1355
+ " if (stderrLines.length >= maxStderrLines) {",
1356
+ " stderrLines.shift()",
1357
+ " }",
1358
+ " stderrLines.push(line)",
1359
+ " })",
1360
+ "",
1361
+ " const result = await new Promise((resolve, reject) => {",
1362
+ " child.on('error', reject)",
1363
+ " child.on('close', (code, signal) => resolve({ code, signal }))",
1364
+ " })",
1365
+ "",
1366
+ " if (result.code === 0) {",
1367
+ " process.exit(0)",
1368
+ " }",
1369
+ "",
1370
+ " const conflictInfo = extractNextConflictInfo(stderrLines)",
1371
+ " if (!restartedAfterConflict && conflictInfo) {",
1372
+ " const stopped = await stopStaleNextDevServer(conflictInfo.pid, conflictInfo.dir)",
1373
+ " if (stopped) {",
1374
+ " restartedAfterConflict = true",
1375
+ " console.error(`[holo] Stopped stale Next dev server ${conflictInfo.pid}. Restarting dev server.`)",
1376
+ " continue",
1377
+ " }",
1378
+ "",
1379
+ " // Another dev server is already running (possibly in a different directory).",
1380
+ " // Next.js already printed the conflict message with instructions to kill it.",
1381
+ " // Exit gracefully to avoid noisy npm/bun error cascades.",
1382
+ " process.exit(0)",
1383
+ " }",
1384
+ "",
1385
+ " if (result.signal) {",
1386
+ " detachSignalForwarders()",
1387
+ " process.kill(process.pid, result.signal)",
1388
+ " } else {",
1389
+ " process.exit(result.code ?? 1)",
1390
+ " }",
1391
+ " }",
1392
+ "}",
1393
+ "",
1394
+ "run().catch((error) => {",
1395
+ " console.error(error instanceof Error ? error.message : String(error))",
1396
+ " process.exit(1)",
1397
+ "})",
1398
+ ""
1399
+ ].join("\n");
1400
+ }
1401
+
256
1402
  // src/project/registry.ts
257
1403
  import { constants as fsConstants } from "fs";
258
1404
  import { access, mkdir as mkdir2, readFile as readFile2, readdir, writeFile as writeFile2 } from "fs/promises";
@@ -453,6 +1599,9 @@ async function unlinkIfPresent(path) {
453
1599
  } catch {
454
1600
  }
455
1601
  }
1602
+ async function pathExists(path) {
1603
+ return await readFileIfPresent(path) !== void 0;
1604
+ }
456
1605
  var SVELTE_HOOKS_OVERRIDE_BLOCK = [
457
1606
  " files: {",
458
1607
  " hooks: {",
@@ -570,6 +1719,27 @@ async function ensureSvelteManagedHooks(projectRoot) {
570
1719
  await writeFileIfChanged(generatedServerHooksPath, renderManagedSvelteServerHooksModule());
571
1720
  await ensureSvelteConfigHooksOverride(projectRoot);
572
1721
  }
1722
+ async function ensureNextManagedRoutes(projectRoot) {
1723
+ const authEnabled = await pathExists(resolve2(projectRoot, "app/api/auth/user/route.ts"));
1724
+ const storageEnabled = await pathExists(resolve2(projectRoot, "app/storage/[[...path]]/route.ts"));
1725
+ const broadcastAuthEnabled = await pathExists(resolve2(projectRoot, "app/broadcasting/auth/route.ts"));
1726
+ const clerkEnabled = await pathExists(resolve2(projectRoot, "app/api/auth/clerk/login/route.ts"));
1727
+ const workosEnabled = await pathExists(resolve2(projectRoot, "app/api/auth/workos/login/route.ts"));
1728
+ const files = [
1729
+ ...renderNextManagedRouteFiles({
1730
+ authEnabled,
1731
+ storageEnabled,
1732
+ broadcastAuthEnabled
1733
+ }),
1734
+ ...renderNextManagedHostedAuthRouteFiles({
1735
+ clerk: clerkEnabled,
1736
+ workos: workosEnabled
1737
+ })
1738
+ ];
1739
+ for (const file of files) {
1740
+ await writeFileIfChanged(resolve2(projectRoot, file.path), file.contents);
1741
+ }
1742
+ }
573
1743
  async function syncManagedFrameworkArtifacts(projectRoot) {
574
1744
  let manifest;
575
1745
  try {
@@ -581,6 +1751,9 @@ async function syncManagedFrameworkArtifacts(projectRoot) {
581
1751
  if (manifest.framework === "sveltekit") {
582
1752
  await ensureSvelteManagedHooks(projectRoot);
583
1753
  }
1754
+ if (manifest.framework === "next") {
1755
+ await ensureNextManagedRoutes(projectRoot);
1756
+ }
584
1757
  }
585
1758
 
586
1759
  // src/project/registry.ts
@@ -1305,6 +2478,15 @@ export {
1305
2478
  renderMarkdownMailTemplate,
1306
2479
  resolveNameInfo,
1307
2480
  resolveArtifactPath,
2481
+ renderNextHoloHelper,
2482
+ renderNextBroadcastAuthRoute,
2483
+ renderNextGeneratedBroadcastAuthRoute,
2484
+ renderNextManagedHostedAuthRouteFiles,
2485
+ renderSvelteHoloHelper,
2486
+ renderAuthProviderRouteFiles,
2487
+ renderAuthRouteFiles,
2488
+ renderFrameworkFiles,
2489
+ renderFrameworkRunner,
1308
2490
  renderGeneratedModelTypes,
1309
2491
  writeGeneratedProjectRegistry,
1310
2492
  loadGeneratedProjectRegistry