@holo-js/cli 0.1.9 → 0.2.1

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.
Files changed (46) hide show
  1. package/dist/bin/holo.mjs +179 -81
  2. package/dist/broadcast-III5MB3R.mjs +203 -0
  3. package/dist/broadcast-ZIFYFOUQ.mjs +203 -0
  4. package/dist/{cache-ETOIQ5IG.mjs → cache-634WUR3T.mjs} +6 -6
  5. package/dist/cache-7J7DIOP6.mjs +66 -0
  6. package/dist/{cache-migrations-2GGI4TJK.mjs → cache-migrations-2NBEUF2T.mjs} +50 -30
  7. package/dist/cache-migrations-S2LJMDOQ.mjs +173 -0
  8. package/dist/{chunk-WRZFATUT.mjs → chunk-4OHJC3GL.mjs} +232 -143
  9. package/dist/{chunk-ASTSSSL2.mjs → chunk-5TEH2QPK.mjs} +99 -122
  10. package/dist/{chunk-F4MT6GBK.mjs → chunk-FGQ2I2YH.mjs} +1 -1
  11. package/dist/chunk-I7QBCEV7.mjs +33 -0
  12. package/dist/{chunk-R6BWRY3E.mjs → chunk-ILU426CF.mjs} +3 -1
  13. package/dist/{chunk-IMOGEKB4.mjs → chunk-J76GH2DR.mjs} +229 -119
  14. package/dist/{chunk-HB4Q7VYK.mjs → chunk-KS5TWO75.mjs} +30 -273
  15. package/dist/{chunk-57SJ566R.mjs → chunk-LBJAJLKU.mjs} +1 -1
  16. package/dist/{chunk-BAFQ2GOA.mjs → chunk-LXGQCG56.mjs} +1 -1
  17. package/dist/chunk-MCVRN7KX.mjs +3308 -0
  18. package/dist/chunk-SFRAGRHY.mjs +472 -0
  19. package/dist/{chunk-7JR73TOH.mjs → chunk-VP2E62DF.mjs} +36 -25
  20. package/dist/{chunk-5EU32E7X.mjs → chunk-VRGB6DIS.mjs} +116 -12
  21. package/dist/{chunk-SRPGIWCF.mjs → chunk-YEFJBN56.mjs} +2 -2
  22. package/dist/{config-ARLE6PKR.mjs → config-MD27U4FM.mjs} +3 -3
  23. package/dist/{dev-6RG5SSZ7.mjs → dev-M2GGURAX.mjs} +9 -7
  24. package/dist/dev-PBNFQK6Y.mjs +44 -0
  25. package/dist/{discovery-FCVGQQVD.mjs → discovery-GWTBF5RZ.mjs} +3 -3
  26. package/dist/{generators-UI2LJK3O.mjs → generators-BZJ53PUU.mjs} +13 -36
  27. package/dist/generators-DEPLONDJ.mjs +520 -0
  28. package/dist/index.mjs +181 -83
  29. package/dist/{media-migrations-JQSDCC7S.mjs → media-migrations-5EISZBSD.mjs} +9 -20
  30. package/dist/media-migrations-NMUWBEKE.mjs +106 -0
  31. package/dist/{queue-BY3PLH4I.mjs → queue-FRAVPNFJ.mjs} +12 -12
  32. package/dist/queue-GY7BWGTX.mjs +625 -0
  33. package/dist/{queue-migrations-YZUKEZK7.mjs → queue-migrations-J7YPIKRB.mjs} +13 -12
  34. package/dist/queue-migrations-O24ERNFF.mjs +167 -0
  35. package/dist/{runtime-BI343WHS.mjs → runtime-2AA7ZLJ6.mjs} +9 -9
  36. package/dist/{runtime-ZKD6URAV.mjs → runtime-GSXF4NB3.mjs} +1 -1
  37. package/dist/runtime-HGK2MWSC.mjs +57 -0
  38. package/dist/runtime-worker.d.ts +2 -0
  39. package/dist/runtime-worker.mjs +276 -0
  40. package/dist/{scaffold-UBOS2NZR.mjs → scaffold-DEOTRALR.mjs} +9 -5
  41. package/dist/scaffold-Y232IGYS.mjs +139 -0
  42. package/dist/{security-TYPVOYGF.mjs → security-MZW2CJKS.mjs} +6 -6
  43. package/dist/security-XVG673UR.mjs +71 -0
  44. package/package.json +9 -7
  45. package/dist/broadcast-VR46UZEL.mjs +0 -84
  46. package/dist/chunk-ZXDU7RHU.mjs +0 -9
@@ -0,0 +1,3308 @@
1
+ import {
2
+ loadProjectConfig,
3
+ resolveGeneratedSchemaPath
4
+ } from "./chunk-YEFJBN56.mjs";
5
+ import {
6
+ loadGeneratedProjectRegistry,
7
+ relativeImportPath,
8
+ renderAuthProviderRouteFiles,
9
+ renderAuthRouteFiles,
10
+ renderFrameworkFiles,
11
+ renderFrameworkRunner,
12
+ renderGeneratedModelTypes,
13
+ renderNextBroadcastAuthRoute,
14
+ renderNextBroadcastConfigRoute,
15
+ renderNextGeneratedBroadcastAuthRoute,
16
+ renderNextGeneratedBroadcastConfigRoute,
17
+ renderNextGeneratedRealtimeDefinitions,
18
+ renderNextHoloHelper,
19
+ renderNextManagedHostedAuthRouteFiles,
20
+ renderNextRuntimeBootstrap,
21
+ renderSvelteHoloHelper,
22
+ renderSvelteViteConfig
23
+ } from "./chunk-J76GH2DR.mjs";
24
+ import {
25
+ AUTH_CONFIG_FILE_NAMES,
26
+ AUTH_SOCIAL_PROVIDER_PACKAGE_NAMES,
27
+ BROADCAST_CONFIG_FILE_NAMES,
28
+ CACHE_CONFIG_FILE_NAMES,
29
+ CORS_CONFIG_FILE_NAMES,
30
+ DB_DRIVER_PACKAGE_NAMES,
31
+ GENERATED_MODEL_TYPES_PATH,
32
+ MAIL_CONFIG_FILE_NAMES,
33
+ MEDIA_CONFIG_FILE_NAMES,
34
+ NOTIFICATIONS_CONFIG_FILE_NAMES,
35
+ QUEUE_CONFIG_FILE_NAMES,
36
+ REDIS_CONFIG_FILE_NAMES,
37
+ SECURITY_CONFIG_FILE_NAMES,
38
+ SESSION_CONFIG_FILE_NAMES,
39
+ SUPPORTED_AUTH_SOCIAL_PROVIDERS,
40
+ isSupportedCacheInstallerDriver,
41
+ isSupportedQueueInstallerDriver,
42
+ normalizeScaffoldOptionalPackages,
43
+ pathExists,
44
+ readTextFile,
45
+ resolveFirstExistingPath,
46
+ sanitizePackageName,
47
+ writeTextFile
48
+ } from "./chunk-ILU426CF.mjs";
49
+
50
+ // src/project/scaffold.ts
51
+ import { mkdir as mkdir3, readdir as readdir2 } from "fs/promises";
52
+ import { dirname as dirname2, extname as extname2, relative, resolve as resolve4, sep } from "path";
53
+ import { loadConfigDirectory as loadConfigDirectory2 } from "@holo-js/config";
54
+
55
+ // src/project/scaffold/config-renderers.ts
56
+ import { appendFile, mkdir } from "fs/promises";
57
+ import { extname, resolve as resolve2 } from "path";
58
+ import { holoStorageDefaults } from "@holo-js/config";
59
+
60
+ // src/project/scaffold/dependencies.ts
61
+ import { resolve } from "path";
62
+ import { loadConfigDirectory } from "@holo-js/config";
63
+
64
+ // package.json
65
+ var package_default = {
66
+ name: "@holo-js/cli",
67
+ version: "0.2.1",
68
+ description: "Holo-JS Framework - project creation, discovery, and operational CLI",
69
+ type: "module",
70
+ license: "MIT",
71
+ bin: {
72
+ holo: "./dist/bin/holo.mjs",
73
+ "holo-js": "./dist/bin/holo.mjs"
74
+ },
75
+ exports: {
76
+ ".": {
77
+ types: "./dist/index.d.ts",
78
+ import: "./dist/index.mjs"
79
+ }
80
+ },
81
+ main: "./dist/index.mjs",
82
+ types: "./dist/index.d.ts",
83
+ files: [
84
+ "dist"
85
+ ],
86
+ scripts: {
87
+ build: "node ../../scripts/generate-cli-workspace-catalog.mjs && tsup",
88
+ stub: "node ../../scripts/generate-cli-workspace-catalog.mjs && tsup --watch",
89
+ typecheck: "tsc -p tsconfig.json --noEmit",
90
+ test: "vitest --run && HOLO_CLI_INCLUDE_INTEGRATION=1 vitest --run tests/cli.test.ts",
91
+ "test:integration": "HOLO_CLI_INCLUDE_INTEGRATION=1 vitest --run tests/cli.test.ts"
92
+ },
93
+ dependencies: {
94
+ "@clack/prompts": "catalog:",
95
+ "@holo-js/cache-db": "catalog:",
96
+ "@holo-js/config": "catalog:",
97
+ "@holo-js/core": "catalog:",
98
+ "@holo-js/db": "catalog:",
99
+ esbuild: "catalog:",
100
+ inflection: "catalog:"
101
+ },
102
+ devDependencies: {
103
+ "@holo-js/events": "catalog:",
104
+ "@holo-js/queue": "catalog:",
105
+ "@holo-js/queue-db": "catalog:",
106
+ "@types/node": "catalog:",
107
+ tsup: "catalog:",
108
+ typescript: "catalog:",
109
+ vitest: "catalog:"
110
+ },
111
+ engines: {
112
+ node: ">=20.11.0"
113
+ }
114
+ };
115
+
116
+ // src/generated/workspaceCatalog.ts
117
+ var WORKSPACE_CATALOG = Object.freeze({
118
+ "@clerk/backend": "^3.4.7",
119
+ "@clack/prompts": "^1.5.1",
120
+ "@eslint/js": "^9.17.0",
121
+ "@holo-js/adapter-next": "^0.2.1",
122
+ "@holo-js/adapter-nuxt": "^0.2.1",
123
+ "@holo-js/adapter-sveltekit": "^0.2.1",
124
+ "@holo-js/auth": "^0.2.1",
125
+ "@holo-js/auth-clerk": "^0.2.1",
126
+ "@holo-js/auth-social": "^0.2.1",
127
+ "@holo-js/auth-social-apple": "^0.2.1",
128
+ "@holo-js/auth-social-discord": "^0.2.1",
129
+ "@holo-js/auth-social-facebook": "^0.2.1",
130
+ "@holo-js/auth-social-github": "^0.2.1",
131
+ "@holo-js/auth-social-google": "^0.2.1",
132
+ "@holo-js/auth-social-linkedin": "^0.2.1",
133
+ "@holo-js/auth-workos": "^0.2.1",
134
+ "@holo-js/authorization": "^0.2.1",
135
+ "@holo-js/broadcast": "^0.2.1",
136
+ "@holo-js/cache": "^0.2.1",
137
+ "@holo-js/cache-db": "^0.2.1",
138
+ "@holo-js/cache-redis": "^0.2.1",
139
+ "@holo-js/cli": "^0.2.1",
140
+ "@holo-js/config": "^0.2.1",
141
+ "@holo-js/core": "^0.2.1",
142
+ "@holo-js/db": "^0.2.1",
143
+ "@holo-js/db-mysql": "^0.2.1",
144
+ "@holo-js/db-postgres": "^0.2.1",
145
+ "@holo-js/db-sqlite": "^0.2.1",
146
+ "@holo-js/events": "^0.2.1",
147
+ "@holo-js/flux": "^0.2.1",
148
+ "@holo-js/flux-react": "^0.2.1",
149
+ "@holo-js/flux-svelte": "^0.2.1",
150
+ "@holo-js/flux-vue": "^0.2.1",
151
+ "@holo-js/forms": "^0.2.1",
152
+ "@holo-js/mail": "^0.2.1",
153
+ "@holo-js/media": "^0.2.1",
154
+ "@holo-js/notifications": "^0.2.1",
155
+ "@holo-js/queue": "^0.2.1",
156
+ "@holo-js/queue-db": "^0.2.1",
157
+ "@holo-js/queue-redis": "^0.2.1",
158
+ "@holo-js/realtime": "^0.2.1",
159
+ "@holo-js/security": "^0.2.1",
160
+ "@holo-js/session": "^0.2.1",
161
+ "@holo-js/storage": "^0.2.1",
162
+ "@holo-js/storage-s3": "^0.2.1",
163
+ "@holo-js/validation": "^0.2.1",
164
+ "@nuxt/kit": "^4.4.4",
165
+ "@nuxt/module-builder": "^1.0.2",
166
+ "@sveltejs/adapter-node": "^5.5.4",
167
+ "@sveltejs/kit": "^2.59.1",
168
+ "@sveltejs/vite-plugin-svelte": "^7.1.0",
169
+ "@types/better-sqlite3": "^7.6.12",
170
+ "@types/node": "^22.10.2",
171
+ "@types/pg": "^8.11.0",
172
+ "@types/react": "^19.2.14",
173
+ "@types/react-dom": "^19.2.3",
174
+ "@types/react-test-renderer": "^19.1.0",
175
+ "@types/ws": "^8.18.1",
176
+ "@vitest/coverage-istanbul": "^4.1.5",
177
+ "@vitest/coverage-v8": "^4.1.5",
178
+ "better-sqlite3": "^11.7.0",
179
+ "bullmq": "^5.71.0",
180
+ "create-holo-js": "^0.2.1",
181
+ "esbuild": "^0.27.4",
182
+ "eslint": "^9.17.0",
183
+ "fast-check": "^4.5.3",
184
+ "globals": "^15.14.0",
185
+ "h3": "^1.15.11",
186
+ "inflection": "^3.0.2",
187
+ "ioredis": "^5.4.2",
188
+ "mysql2": "^3.17.1",
189
+ "next": "^16.2.4",
190
+ "nodemailer": "^6.10.1",
191
+ "nuxt": "^4.4.4",
192
+ "pg": "^8.13.0",
193
+ "playwright": "^1.57.0",
194
+ "react": "^19.2.6",
195
+ "react-dom": "^19.2.6",
196
+ "react-test-renderer": "^19.2.6",
197
+ "sharp": "^0.34.4",
198
+ "svelte": "^5.55.5",
199
+ "svelte-check": "^4.4.6",
200
+ "tslib": "^2.8.1",
201
+ "tsup": "^8.3.5",
202
+ "typescript": "^5.7.2",
203
+ "typescript-eslint": "^8.30.1",
204
+ "ulid": "^3.0.1",
205
+ "uuid": "^12.0.0",
206
+ "valibot": "^1.1.0",
207
+ "vite": "^8.0.10",
208
+ "vitepress": "^1.6.3",
209
+ "vitest": "^4.1.5",
210
+ "vue": "^3.5.13",
211
+ "vue-router": "^5.0.4",
212
+ "vue-tsc": "^2.2.0",
213
+ "ws": "^8.18.3"
214
+ });
215
+
216
+ // src/metadata.ts
217
+ var HOLO_PACKAGE_VERSION = package_default.version;
218
+ var HOLO_PACKAGE_RANGE = `^${HOLO_PACKAGE_VERSION}`;
219
+ function catalogVersion(packageName) {
220
+ return WORKSPACE_CATALOG[packageName];
221
+ }
222
+ var ESBUILD_PACKAGE_VERSION = catalogVersion("esbuild");
223
+ var SCAFFOLD_PACKAGE_MANAGER_VERSIONS = Object.freeze({
224
+ npm: "npm@latest",
225
+ pnpm: "pnpm@latest",
226
+ yarn: "yarn@stable",
227
+ bun: "bun@1.3.9"
228
+ });
229
+ var SCAFFOLD_FRAMEWORK_VERSIONS = Object.freeze({
230
+ nuxt: catalogVersion("nuxt"),
231
+ next: catalogVersion("next"),
232
+ sveltekit: catalogVersion("@sveltejs/kit")
233
+ });
234
+ var SCAFFOLD_NEXT_REACT_VERSIONS = Object.freeze({
235
+ react: catalogVersion("react"),
236
+ "react-dom": catalogVersion("react-dom"),
237
+ "@types/react": catalogVersion("@types/react"),
238
+ "@types/react-dom": catalogVersion("@types/react-dom")
239
+ });
240
+ var SCAFFOLD_BASE_DEV_DEPENDENCY_VERSIONS = Object.freeze({
241
+ typescript: catalogVersion("typescript"),
242
+ "@types/node": catalogVersion("@types/node"),
243
+ eslint: catalogVersion("eslint")
244
+ });
245
+ var SCAFFOLD_NUXT_DEPENDENCY_VERSIONS = Object.freeze({
246
+ vue: catalogVersion("vue"),
247
+ "vue-router": catalogVersion("vue-router"),
248
+ "vue-tsc": catalogVersion("vue-tsc")
249
+ });
250
+ var SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS = Object.freeze({
251
+ "@sveltejs/adapter-node": catalogVersion("@sveltejs/adapter-node"),
252
+ "@sveltejs/vite-plugin-svelte": catalogVersion("@sveltejs/vite-plugin-svelte"),
253
+ svelte: catalogVersion("svelte"),
254
+ "svelte-check": catalogVersion("svelte-check"),
255
+ vite: catalogVersion("vite")
256
+ });
257
+ var IOREDIS_PACKAGE_VERSION = catalogVersion("ioredis");
258
+ var SCAFFOLD_FRAMEWORK_ADAPTER_VERSIONS = Object.freeze({
259
+ nuxt: HOLO_PACKAGE_RANGE,
260
+ next: HOLO_PACKAGE_RANGE,
261
+ sveltekit: HOLO_PACKAGE_RANGE
262
+ });
263
+ var SCAFFOLD_FRAMEWORK_RUNTIME_VERSIONS = Object.freeze({
264
+ nuxt: {
265
+ "@holo-js/storage": HOLO_PACKAGE_RANGE
266
+ },
267
+ next: {
268
+ "@holo-js/storage": HOLO_PACKAGE_RANGE
269
+ },
270
+ sveltekit: {
271
+ "@holo-js/storage": HOLO_PACKAGE_RANGE
272
+ }
273
+ });
274
+
275
+ // src/project/scaffold/dependencies.ts
276
+ function normalizeDependencyMap(value) {
277
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
278
+ return {};
279
+ }
280
+ return Object.fromEntries(
281
+ Object.entries(value).filter(([, dependencyVersion]) => typeof dependencyVersion === "string").sort(([left], [right]) => left.localeCompare(right))
282
+ );
283
+ }
284
+ function isWorkspaceDependencyVersion(value) {
285
+ return typeof value === "string" && value.startsWith("workspace:");
286
+ }
287
+ function resolveManagedHoloPackageVersion(packageName, dependencies, devDependencies) {
288
+ const currentPackageVersion = dependencies[packageName] ?? devDependencies[packageName];
289
+ if (isWorkspaceDependencyVersion(currentPackageVersion)) {
290
+ return currentPackageVersion;
291
+ }
292
+ const workspaceVersion = Object.entries({
293
+ ...dependencies,
294
+ ...devDependencies
295
+ }).find(([dependencyName, dependencyVersion]) => dependencyName.startsWith("@holo-js/") && isWorkspaceDependencyVersion(dependencyVersion))?.[1];
296
+ return workspaceVersion ?? `^${HOLO_PACKAGE_VERSION}`;
297
+ }
298
+ async function readPackageJsonDependencyState(projectRoot) {
299
+ const packageJsonPath = resolve(projectRoot, "package.json");
300
+ const existing = await readTextFile(packageJsonPath);
301
+ if (!existing) {
302
+ throw new Error(`Missing package.json in ${projectRoot}.`);
303
+ }
304
+ let parsed;
305
+ try {
306
+ parsed = JSON.parse(existing);
307
+ } catch {
308
+ throw new Error(`Invalid package.json in ${projectRoot}.`);
309
+ }
310
+ return {
311
+ packageJsonPath,
312
+ parsed,
313
+ dependencies: normalizeDependencyMap(parsed.dependencies),
314
+ devDependencies: normalizeDependencyMap(parsed.devDependencies)
315
+ };
316
+ }
317
+ async function writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies) {
318
+ parsed.dependencies = Object.fromEntries(
319
+ Object.entries(dependencies).sort(([left], [right]) => left.localeCompare(right))
320
+ );
321
+ if (Object.keys(devDependencies).length > 0) {
322
+ parsed.devDependencies = Object.fromEntries(
323
+ Object.entries(devDependencies).sort(([left], [right]) => left.localeCompare(right))
324
+ );
325
+ } else {
326
+ delete parsed.devDependencies;
327
+ }
328
+ await writeTextFile(packageJsonPath, `${JSON.stringify(parsed, null, 2)}
329
+ `);
330
+ }
331
+ function hasLoadedConfigFile(loadedFiles, configName) {
332
+ return loadedFiles.some((filePath) => {
333
+ const normalizedPath = filePath.replaceAll("\\", "/");
334
+ return normalizedPath.endsWith(`/config/${configName}.ts`) || normalizedPath.endsWith(`/config/${configName}.mts`) || normalizedPath.endsWith(`/config/${configName}.js`) || normalizedPath.endsWith(`/config/${configName}.mjs`) || normalizedPath.endsWith(`/config/${configName}.cts`) || normalizedPath.endsWith(`/config/${configName}.cjs`);
335
+ });
336
+ }
337
+ function inferDatabaseDriverFromUrl(value) {
338
+ if (!value) {
339
+ return void 0;
340
+ }
341
+ const normalized = value.trim().toLowerCase();
342
+ if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://")) {
343
+ return "postgres";
344
+ }
345
+ if (normalized.startsWith("mysql://") || normalized.startsWith("mysql2://")) {
346
+ return "mysql";
347
+ }
348
+ if (normalized === ":memory:" || normalized.startsWith("file:") || normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../") || normalized.endsWith(".db") || normalized.endsWith(".sqlite") || normalized.endsWith(".sqlite3")) {
349
+ return "sqlite";
350
+ }
351
+ return void 0;
352
+ }
353
+ function inferConnectionDriver(connection) {
354
+ if (typeof connection === "string") {
355
+ return inferDatabaseDriverFromUrl(connection);
356
+ }
357
+ const explicitDriver = connection.driver;
358
+ if (explicitDriver === "sqlite" || explicitDriver === "postgres" || explicitDriver === "mysql") {
359
+ return explicitDriver;
360
+ }
361
+ return inferDatabaseDriverFromUrl(connection.url ?? connection.filename);
362
+ }
363
+ function registryHasJobs(registry) {
364
+ return (registry?.jobs.length ?? 0) > 0;
365
+ }
366
+ function registryHasEvents(registry) {
367
+ return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
368
+ }
369
+ function registryHasBroadcastDefinitions(registry) {
370
+ return (registry?.broadcast.length ?? 0) > 0 || (registry?.channels.length ?? 0) > 0;
371
+ }
372
+ function registryHasAuthorizationDefinitions(registry) {
373
+ return (registry?.authorizationPolicies.length ?? 0) > 0 || (registry?.authorizationAbilities.length ?? 0) > 0;
374
+ }
375
+ function authConfigUsesSocialProviders(loaded) {
376
+ return Object.keys(loaded.auth.social).length > 0;
377
+ }
378
+ function authConfigUsesWorkosProviders(loaded) {
379
+ return Object.entries(loaded.auth.workos).some(([name, provider]) => name !== "provider" && typeof provider === "object" && provider !== null);
380
+ }
381
+ function authConfigUsesClerkProviders(loaded) {
382
+ return Object.keys(loaded.auth.clerk).length > 0;
383
+ }
384
+ function mailConfigUsesQueue(loaded) {
385
+ return loaded.mail.queue.queued || Object.values(loaded.mail.mailers).some((mailer) => mailer.queue.queued);
386
+ }
387
+ async function projectHasAuthorizationScaffold(projectRoot) {
388
+ const project = await loadProjectConfig(projectRoot);
389
+ const policiesRoot = resolve(projectRoot, project.config.paths.authorizationPolicies ?? "server/policies");
390
+ const abilitiesRoot = resolve(projectRoot, project.config.paths.authorizationAbilities ?? "server/abilities");
391
+ return await pathExists(policiesRoot) || await pathExists(abilitiesRoot);
392
+ }
393
+ async function projectHasEventsScaffold(projectRoot) {
394
+ const project = await loadProjectConfig(projectRoot);
395
+ const eventsRoot = resolve(projectRoot, project.config.paths.events);
396
+ const listenersRoot = resolve(projectRoot, project.config.paths.listeners);
397
+ return await pathExists(eventsRoot) || await pathExists(listenersRoot);
398
+ }
399
+ async function projectHasRealtimeScaffold(projectRoot) {
400
+ return await pathExists(resolve(projectRoot, "server/realtime"));
401
+ }
402
+ async function syncManagedDriverDependencies(projectRoot, registry) {
403
+ const loaded = await loadConfigDirectory(projectRoot, {
404
+ preferCache: false,
405
+ processEnv: process.env
406
+ });
407
+ const discoveredRegistry = registry ?? await loadGeneratedProjectRegistry(projectRoot);
408
+ const authConfigured = hasLoadedConfigFile(loaded.loadedFiles, "auth");
409
+ const broadcastConfigured = hasLoadedConfigFile(loaded.loadedFiles, "broadcast");
410
+ const cacheConfigured = hasLoadedConfigFile(loaded.loadedFiles, "cache");
411
+ const mailConfigured = hasLoadedConfigFile(loaded.loadedFiles, "mail");
412
+ const mediaConfigured = hasLoadedConfigFile(loaded.loadedFiles, "media");
413
+ const notificationsConfigured = hasLoadedConfigFile(loaded.loadedFiles, "notifications");
414
+ const queueConfigured = hasLoadedConfigFile(loaded.loadedFiles, "queue");
415
+ const securityConfigured = hasLoadedConfigFile(loaded.loadedFiles, "security");
416
+ const sessionConfigured = hasLoadedConfigFile(loaded.loadedFiles, "session");
417
+ const storageConfigured = hasLoadedConfigFile(loaded.loadedFiles, "storage");
418
+ const requiredPackages = /* @__PURE__ */ new Set();
419
+ const hasAuthorizationScaffold = await projectHasAuthorizationScaffold(projectRoot);
420
+ const hasEventsScaffold = await projectHasEventsScaffold(projectRoot);
421
+ const hasRealtimeScaffold = await projectHasRealtimeScaffold(projectRoot);
422
+ const {
423
+ packageJsonPath,
424
+ parsed,
425
+ dependencies,
426
+ devDependencies
427
+ } = await readPackageJsonDependencyState(projectRoot);
428
+ const cachePackageInstalled = typeof dependencies["@holo-js/cache"] !== "undefined" || typeof devDependencies["@holo-js/cache"] !== "undefined";
429
+ const cacheDesired = cacheConfigured || cachePackageInstalled;
430
+ requiredPackages.add("@holo-js/core");
431
+ for (const connection of Object.values(loaded.database.connections)) {
432
+ const inferredDriver = inferConnectionDriver(connection);
433
+ if (inferredDriver) {
434
+ requiredPackages.add(DB_DRIVER_PACKAGE_NAMES[inferredDriver]);
435
+ }
436
+ }
437
+ if (authConfigured || sessionConfigured) {
438
+ requiredPackages.add("@holo-js/session");
439
+ }
440
+ if (authConfigured || securityConfigured) {
441
+ requiredPackages.add("@holo-js/security");
442
+ }
443
+ if (authConfigured) {
444
+ requiredPackages.add("@holo-js/auth");
445
+ if (authConfigUsesSocialProviders(loaded)) {
446
+ requiredPackages.add("@holo-js/auth-social");
447
+ for (const [providerName, provider] of Object.entries(loaded.auth.social)) {
448
+ if (typeof provider.runtime === "string" && provider.runtime.trim()) {
449
+ continue;
450
+ }
451
+ const builtinPackage = AUTH_SOCIAL_PROVIDER_PACKAGE_NAMES[providerName];
452
+ if (builtinPackage) {
453
+ requiredPackages.add(builtinPackage);
454
+ }
455
+ }
456
+ }
457
+ if (authConfigUsesWorkosProviders(loaded)) {
458
+ requiredPackages.add("@holo-js/auth-workos");
459
+ }
460
+ if (authConfigUsesClerkProviders(loaded)) {
461
+ requiredPackages.add("@holo-js/auth-clerk");
462
+ }
463
+ }
464
+ if (mailConfigured) {
465
+ requiredPackages.add("@holo-js/mail");
466
+ }
467
+ if (mediaConfigured) {
468
+ requiredPackages.add("@holo-js/media");
469
+ }
470
+ if (cacheDesired) {
471
+ requiredPackages.add("@holo-js/cache");
472
+ }
473
+ if (cacheConfigured) {
474
+ const cacheDrivers = Object.values(loaded.cache.drivers);
475
+ if (cacheDrivers.some((driver) => driver.driver === "redis")) {
476
+ requiredPackages.add("@holo-js/cache-redis");
477
+ }
478
+ if (cacheDrivers.some((driver) => driver.driver === "database")) {
479
+ requiredPackages.add("@holo-js/cache-db");
480
+ }
481
+ }
482
+ if (notificationsConfigured) {
483
+ requiredPackages.add("@holo-js/notifications");
484
+ }
485
+ if (broadcastConfigured || registryHasBroadcastDefinitions(discoveredRegistry) || hasRealtimeScaffold) {
486
+ requiredPackages.add("@holo-js/broadcast");
487
+ requiredPackages.add("@holo-js/flux");
488
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
489
+ if (framework === "next") {
490
+ requiredPackages.add("@holo-js/flux-react");
491
+ requiredPackages.add("@holo-js/adapter-next");
492
+ } else if (framework === "nuxt") {
493
+ requiredPackages.add("@holo-js/flux-vue");
494
+ requiredPackages.add("@holo-js/adapter-nuxt");
495
+ } else if (framework === "sveltekit") {
496
+ requiredPackages.add("@holo-js/flux-svelte");
497
+ requiredPackages.add("@holo-js/adapter-sveltekit");
498
+ }
499
+ }
500
+ if (hasRealtimeScaffold) {
501
+ requiredPackages.add("@holo-js/realtime");
502
+ }
503
+ if (registryHasAuthorizationDefinitions(discoveredRegistry) || hasAuthorizationScaffold) {
504
+ requiredPackages.add("@holo-js/authorization");
505
+ }
506
+ if (registryHasEvents(discoveredRegistry) || hasEventsScaffold) {
507
+ requiredPackages.add("@holo-js/events");
508
+ requiredPackages.add("@holo-js/queue");
509
+ }
510
+ if (queueConfigured || registryHasJobs(discoveredRegistry) || mailConfigUsesQueue(loaded)) {
511
+ requiredPackages.add("@holo-js/queue");
512
+ if (queueConfigured) {
513
+ const queueConnections = Object.values(loaded.queue.connections);
514
+ if (queueConnections.some((connection) => connection.driver === "redis")) {
515
+ requiredPackages.add("@holo-js/queue-redis");
516
+ }
517
+ if (queueConnections.some((connection) => connection.driver === "database") || loaded.queue.failed !== false) {
518
+ requiredPackages.add("@holo-js/queue-db");
519
+ }
520
+ }
521
+ }
522
+ if (Object.values(loaded.cache?.drivers ?? {}).some((driver) => driver.driver === "redis") || loaded.security?.rateLimit?.driver === "redis" || Object.values(loaded.session?.stores ?? {}).some((store) => store.driver === "redis") || loaded.broadcast?.worker != null && loaded.broadcast.worker.scaling !== false) {
523
+ requiredPackages.add("ioredis");
524
+ }
525
+ if (storageConfigured) {
526
+ requiredPackages.add("@holo-js/storage");
527
+ if (Object.values(loaded.storage.disks).some((disk) => disk.driver === "s3")) {
528
+ requiredPackages.add("@holo-js/storage-s3");
529
+ }
530
+ }
531
+ let changed = false;
532
+ const removableManagedPackages = /* @__PURE__ */ new Set([
533
+ "@holo-js/core",
534
+ ...Object.values(DB_DRIVER_PACKAGE_NAMES),
535
+ "@holo-js/auth",
536
+ "@holo-js/auth-clerk",
537
+ "@holo-js/auth-social",
538
+ "@holo-js/auth-workos",
539
+ "@holo-js/authorization",
540
+ "@holo-js/broadcast",
541
+ "@holo-js/cache-db",
542
+ "@holo-js/cache-redis",
543
+ "@holo-js/events",
544
+ "@holo-js/flux",
545
+ "@holo-js/flux-react",
546
+ "@holo-js/flux-svelte",
547
+ "@holo-js/flux-vue",
548
+ "@holo-js/mail",
549
+ "@holo-js/media",
550
+ "@holo-js/notifications",
551
+ "@holo-js/queue",
552
+ "@holo-js/queue-db",
553
+ "@holo-js/queue-redis",
554
+ "@holo-js/realtime",
555
+ "@holo-js/security",
556
+ "@holo-js/session",
557
+ "@holo-js/storage",
558
+ "@holo-js/storage-s3",
559
+ ...Object.values(AUTH_SOCIAL_PROVIDER_PACKAGE_NAMES),
560
+ "ioredis"
561
+ ]);
562
+ if (cacheDesired || cachePackageInstalled) {
563
+ removableManagedPackages.add("@holo-js/cache");
564
+ }
565
+ for (const packageName of requiredPackages) {
566
+ const requiredVersion = packageName === "ioredis" ? IOREDIS_PACKAGE_VERSION : resolveManagedHoloPackageVersion(packageName, dependencies, devDependencies);
567
+ if (dependencies[packageName] !== requiredVersion || typeof devDependencies[packageName] !== "undefined") {
568
+ dependencies[packageName] = requiredVersion;
569
+ delete devDependencies[packageName];
570
+ changed = true;
571
+ }
572
+ }
573
+ for (const packageName of removableManagedPackages) {
574
+ if (requiredPackages.has(packageName)) {
575
+ continue;
576
+ }
577
+ if (typeof dependencies[packageName] !== "undefined" || typeof devDependencies[packageName] !== "undefined") {
578
+ delete dependencies[packageName];
579
+ delete devDependencies[packageName];
580
+ changed = true;
581
+ }
582
+ }
583
+ if (!changed) {
584
+ return false;
585
+ }
586
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
587
+ return true;
588
+ }
589
+ async function upsertQueuePackageDependency(projectRoot, driver) {
590
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
591
+ const queueConfigPath = await resolveFirstExistingPath(projectRoot, ["config/queue.ts", "config/queue.mts", "config/queue.js", "config/queue.mjs", "config/queue.cts", "config/queue.cjs"]);
592
+ const loadedQueueConfig = queueConfigPath ? loadConfigDirectory(projectRoot, {
593
+ preferCache: false,
594
+ processEnv: process.env
595
+ }).then((config) => config.queue).catch(() => void 0) : Promise.resolve(void 0);
596
+ const nextVersion = resolveManagedHoloPackageVersion("@holo-js/queue", dependencies, devDependencies);
597
+ const nextQueueDbVersion = resolveManagedHoloPackageVersion("@holo-js/queue-db", dependencies, devDependencies);
598
+ const nextQueueRedisVersion = resolveManagedHoloPackageVersion("@holo-js/queue-redis", dependencies, devDependencies);
599
+ const nextEsbuildVersion = ESBUILD_PACKAGE_VERSION;
600
+ const queueConfig = typeof driver === "undefined" ? await loadedQueueConfig : void 0;
601
+ const resolvedQueueDriver = driver && driver !== "sync" ? driver : queueConfig?.connections[queueConfig.default]?.driver ?? driver;
602
+ const requiresQueueDb = resolvedQueueDriver === "database" || (queueConfig?.failed ?? false) !== false || Object.values(queueConfig?.connections ?? {}).some((connection) => connection.driver === "database");
603
+ const requiresQueueRedis = resolvedQueueDriver === "redis" || Object.values(queueConfig?.connections ?? {}).some((connection) => connection.driver === "redis");
604
+ const currentVersion = dependencies["@holo-js/queue"];
605
+ const currentQueueDbVersion = dependencies["@holo-js/queue-db"];
606
+ const currentQueueRedisVersion = dependencies["@holo-js/queue-redis"];
607
+ const currentDevVersion = devDependencies["@holo-js/queue"];
608
+ const currentDevQueueDbVersion = devDependencies["@holo-js/queue-db"];
609
+ const currentDevQueueRedisVersion = devDependencies["@holo-js/queue-redis"];
610
+ const currentEsbuildVersion = dependencies.esbuild;
611
+ const currentDevEsbuildVersion = devDependencies.esbuild;
612
+ if (currentVersion === nextVersion && (requiresQueueDb ? currentQueueDbVersion === nextQueueDbVersion : typeof currentQueueDbVersion === "undefined") && (requiresQueueRedis ? currentQueueRedisVersion === nextQueueRedisVersion : typeof currentQueueRedisVersion === "undefined") && typeof currentDevVersion === "undefined" && typeof currentDevQueueDbVersion === "undefined" && typeof currentDevQueueRedisVersion === "undefined" && currentEsbuildVersion === nextEsbuildVersion && typeof currentDevEsbuildVersion === "undefined") {
613
+ return false;
614
+ }
615
+ dependencies["@holo-js/queue"] = nextVersion;
616
+ if (requiresQueueDb) {
617
+ dependencies["@holo-js/queue-db"] = nextQueueDbVersion;
618
+ } else {
619
+ delete dependencies["@holo-js/queue-db"];
620
+ }
621
+ if (requiresQueueRedis) {
622
+ dependencies["@holo-js/queue-redis"] = nextQueueRedisVersion;
623
+ } else {
624
+ delete dependencies["@holo-js/queue-redis"];
625
+ }
626
+ dependencies.esbuild = nextEsbuildVersion;
627
+ delete devDependencies["@holo-js/queue"];
628
+ delete devDependencies["@holo-js/queue-db"];
629
+ delete devDependencies["@holo-js/queue-redis"];
630
+ delete devDependencies.esbuild;
631
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
632
+ return true;
633
+ }
634
+ async function upsertEventsPackageDependency(projectRoot) {
635
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
636
+ const nextVersion = resolveManagedHoloPackageVersion("@holo-js/events", dependencies, devDependencies);
637
+ const nextQueueVersion = resolveManagedHoloPackageVersion("@holo-js/queue", dependencies, devDependencies);
638
+ const currentVersion = dependencies["@holo-js/events"];
639
+ const currentDevVersion = devDependencies["@holo-js/events"];
640
+ const currentQueueVersion = dependencies["@holo-js/queue"];
641
+ const currentQueueDevVersion = devDependencies["@holo-js/queue"];
642
+ if (currentVersion === nextVersion && typeof currentDevVersion === "undefined" && currentQueueVersion === nextQueueVersion && typeof currentQueueDevVersion === "undefined") {
643
+ return false;
644
+ }
645
+ dependencies["@holo-js/events"] = nextVersion;
646
+ dependencies["@holo-js/queue"] = nextQueueVersion;
647
+ delete devDependencies["@holo-js/events"];
648
+ delete devDependencies["@holo-js/queue"];
649
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
650
+ return true;
651
+ }
652
+ async function upsertManagedPackageDependency(projectRoot, packageName) {
653
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
654
+ const nextVersion = resolveManagedHoloPackageVersion(packageName, dependencies, devDependencies);
655
+ const currentVersion = dependencies[packageName];
656
+ const currentDevVersion = devDependencies[packageName];
657
+ if (currentVersion === nextVersion && typeof currentDevVersion === "undefined") {
658
+ return false;
659
+ }
660
+ dependencies[packageName] = nextVersion;
661
+ delete devDependencies[packageName];
662
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
663
+ return true;
664
+ }
665
+ async function upsertNotificationsPackageDependency(projectRoot) {
666
+ return await upsertManagedPackageDependency(projectRoot, "@holo-js/notifications");
667
+ }
668
+ async function upsertMailPackageDependency(projectRoot) {
669
+ return await upsertManagedPackageDependency(projectRoot, "@holo-js/mail");
670
+ }
671
+ async function upsertMediaPackageDependency(projectRoot) {
672
+ return await upsertManagedPackageDependency(projectRoot, "@holo-js/media");
673
+ }
674
+ async function upsertSecurityPackageDependency(projectRoot) {
675
+ return await upsertManagedPackageDependency(projectRoot, "@holo-js/security");
676
+ }
677
+ async function upsertCachePackageDependencies(projectRoot, driver = "file") {
678
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
679
+ const cacheConfigPath = await resolveFirstExistingPath(projectRoot, CACHE_CONFIG_FILE_NAMES);
680
+ const cacheConfig = cacheConfigPath ? await loadConfigDirectory(projectRoot, {
681
+ preferCache: false,
682
+ processEnv: process.env
683
+ }).then((config) => config.cache).catch(() => void 0) : void 0;
684
+ const nextVersion = resolveManagedHoloPackageVersion("@holo-js/cache", dependencies, devDependencies);
685
+ const nextCacheDbVersion = resolveManagedHoloPackageVersion("@holo-js/cache-db", dependencies, devDependencies);
686
+ const nextCacheRedisVersion = resolveManagedHoloPackageVersion("@holo-js/cache-redis", dependencies, devDependencies);
687
+ const requiresCacheRedis = driver === "redis" || Object.values(cacheConfig?.drivers ?? {}).some((connection) => connection.driver === "redis");
688
+ const requiresCacheDb = driver === "database" || Object.values(cacheConfig?.drivers ?? {}).some((connection) => connection.driver === "database");
689
+ const currentVersion = dependencies["@holo-js/cache"];
690
+ const currentCacheDbVersion = dependencies["@holo-js/cache-db"];
691
+ const currentCacheRedisVersion = dependencies["@holo-js/cache-redis"];
692
+ const currentDevVersion = devDependencies["@holo-js/cache"];
693
+ const currentDevCacheDbVersion = devDependencies["@holo-js/cache-db"];
694
+ const currentDevCacheRedisVersion = devDependencies["@holo-js/cache-redis"];
695
+ if (currentVersion === nextVersion && (requiresCacheDb ? currentCacheDbVersion === nextCacheDbVersion : typeof currentCacheDbVersion === "undefined") && (requiresCacheRedis ? currentCacheRedisVersion === nextCacheRedisVersion : typeof currentCacheRedisVersion === "undefined") && typeof currentDevVersion === "undefined" && typeof currentDevCacheDbVersion === "undefined" && typeof currentDevCacheRedisVersion === "undefined") {
696
+ return false;
697
+ }
698
+ dependencies["@holo-js/cache"] = nextVersion;
699
+ if (requiresCacheDb) {
700
+ dependencies["@holo-js/cache-db"] = nextCacheDbVersion;
701
+ } else {
702
+ delete dependencies["@holo-js/cache-db"];
703
+ }
704
+ if (requiresCacheRedis) {
705
+ dependencies["@holo-js/cache-redis"] = nextCacheRedisVersion;
706
+ } else {
707
+ delete dependencies["@holo-js/cache-redis"];
708
+ }
709
+ delete devDependencies["@holo-js/cache"];
710
+ delete devDependencies["@holo-js/cache-db"];
711
+ delete devDependencies["@holo-js/cache-redis"];
712
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
713
+ return true;
714
+ }
715
+ function detectProjectFrameworkFromPackageJson(dependencies, devDependencies) {
716
+ if (dependencies.next || devDependencies.next) {
717
+ return "next";
718
+ }
719
+ if (dependencies.nuxt || devDependencies.nuxt) {
720
+ return "nuxt";
721
+ }
722
+ if (dependencies["@sveltejs/kit"] || devDependencies["@sveltejs/kit"]) {
723
+ return "sveltekit";
724
+ }
725
+ return void 0;
726
+ }
727
+ async function upsertBroadcastPackageDependencies(projectRoot) {
728
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
729
+ const nextVersion = resolveManagedHoloPackageVersion("@holo-js/broadcast", dependencies, devDependencies);
730
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
731
+ let changed = false;
732
+ const requestedPackages = /* @__PURE__ */ new Set([
733
+ "@holo-js/broadcast",
734
+ "@holo-js/flux"
735
+ ]);
736
+ if (framework === "next") {
737
+ requestedPackages.add("@holo-js/flux-react");
738
+ requestedPackages.add("@holo-js/adapter-next");
739
+ } else if (framework === "nuxt") {
740
+ requestedPackages.add("@holo-js/flux-vue");
741
+ requestedPackages.add("@holo-js/adapter-nuxt");
742
+ } else if (framework === "sveltekit") {
743
+ requestedPackages.add("@holo-js/flux-svelte");
744
+ requestedPackages.add("@holo-js/adapter-sveltekit");
745
+ }
746
+ const frameworkPackages = /* @__PURE__ */ new Set([
747
+ "@holo-js/flux-react",
748
+ "@holo-js/adapter-next",
749
+ "@holo-js/flux-vue",
750
+ "@holo-js/adapter-nuxt",
751
+ "@holo-js/flux-svelte",
752
+ "@holo-js/adapter-sveltekit"
753
+ ]);
754
+ const managedPackages = /* @__PURE__ */ new Set([
755
+ ...requestedPackages,
756
+ ...frameworkPackages
757
+ ]);
758
+ for (const packageName of managedPackages) {
759
+ if (!requestedPackages.has(packageName)) {
760
+ if (typeof dependencies[packageName] !== "undefined" || typeof devDependencies[packageName] !== "undefined") {
761
+ delete dependencies[packageName];
762
+ delete devDependencies[packageName];
763
+ changed = true;
764
+ }
765
+ continue;
766
+ }
767
+ if (dependencies[packageName] !== nextVersion || typeof devDependencies[packageName] !== "undefined") {
768
+ dependencies[packageName] = nextVersion;
769
+ delete devDependencies[packageName];
770
+ changed = true;
771
+ }
772
+ }
773
+ if (!changed) {
774
+ return {
775
+ updated: false,
776
+ framework
777
+ };
778
+ }
779
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
780
+ return {
781
+ updated: true,
782
+ framework
783
+ };
784
+ }
785
+ async function upsertAuthPackageDependencies(projectRoot, features = {}) {
786
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
787
+ const socialEnabled = features.social === true || (features.socialProviders?.length ?? 0) > 0;
788
+ const requestedPackages = {
789
+ "@holo-js/auth": true,
790
+ "@holo-js/session": true,
791
+ "@holo-js/security": true,
792
+ "@holo-js/auth-social": socialEnabled,
793
+ "@holo-js/auth-workos": features.workos === true,
794
+ "@holo-js/auth-clerk": features.clerk === true
795
+ };
796
+ const requestedSocialProviders = new Set(features.socialProviders ?? (socialEnabled ? ["google"] : []));
797
+ let changed = false;
798
+ for (const [packageName, enabled] of Object.entries(requestedPackages)) {
799
+ const currentDependency = dependencies[packageName];
800
+ const currentDevDependency = devDependencies[packageName];
801
+ const nextVersion = resolveManagedHoloPackageVersion(packageName, dependencies, devDependencies);
802
+ if (enabled) {
803
+ if (currentDependency !== nextVersion || typeof currentDevDependency !== "undefined") {
804
+ dependencies[packageName] = nextVersion;
805
+ delete devDependencies[packageName];
806
+ changed = true;
807
+ }
808
+ continue;
809
+ }
810
+ if (typeof currentDevDependency !== "undefined") {
811
+ delete devDependencies[packageName];
812
+ changed = true;
813
+ }
814
+ if (typeof currentDependency !== "undefined") {
815
+ delete dependencies[packageName];
816
+ changed = true;
817
+ }
818
+ }
819
+ for (const [providerName, packageName] of Object.entries(AUTH_SOCIAL_PROVIDER_PACKAGE_NAMES)) {
820
+ const enabled = requestedSocialProviders.has(providerName);
821
+ const currentDependency = dependencies[packageName];
822
+ const currentDevDependency = devDependencies[packageName];
823
+ const nextVersion = resolveManagedHoloPackageVersion(packageName, dependencies, devDependencies);
824
+ if (enabled) {
825
+ if (currentDependency !== nextVersion || typeof currentDevDependency !== "undefined") {
826
+ dependencies[packageName] = nextVersion;
827
+ delete devDependencies[packageName];
828
+ changed = true;
829
+ }
830
+ continue;
831
+ }
832
+ if (typeof currentDevDependency !== "undefined") {
833
+ delete devDependencies[packageName];
834
+ changed = true;
835
+ }
836
+ if (typeof currentDependency !== "undefined") {
837
+ delete dependencies[packageName];
838
+ changed = true;
839
+ }
840
+ }
841
+ if (!changed) {
842
+ return false;
843
+ }
844
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
845
+ return true;
846
+ }
847
+ async function upsertAuthorizationPackageDependency(projectRoot) {
848
+ return await upsertManagedPackageDependency(projectRoot, "@holo-js/authorization");
849
+ }
850
+ async function upsertRealtimePackageDependency(projectRoot) {
851
+ const broadcastResult = await upsertBroadcastPackageDependencies(projectRoot);
852
+ const { packageJsonPath, parsed, dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
853
+ const nextVersion = resolveManagedHoloPackageVersion("@holo-js/realtime", dependencies, devDependencies);
854
+ const currentVersion = dependencies["@holo-js/realtime"];
855
+ const currentDevVersion = devDependencies["@holo-js/realtime"];
856
+ if (currentVersion === nextVersion && typeof currentDevVersion === "undefined") {
857
+ return broadcastResult.updated;
858
+ }
859
+ dependencies["@holo-js/realtime"] = nextVersion;
860
+ delete devDependencies["@holo-js/realtime"];
861
+ await writePackageJsonDependencyState(packageJsonPath, parsed, dependencies, devDependencies);
862
+ return true;
863
+ }
864
+
865
+ // src/project/scaffold/config-renderers.ts
866
+ function renderStorageConfig() {
867
+ return [
868
+ "import { defineStorageConfig, env } from '@holo-js/config'",
869
+ "",
870
+ "export default defineStorageConfig({",
871
+ ` defaultDisk: env('STORAGE_DEFAULT_DISK', '${holoStorageDefaults.defaultDisk}'),`,
872
+ ` routePrefix: env('STORAGE_ROUTE_PREFIX', '${holoStorageDefaults.routePrefix}'),`,
873
+ " disks: {",
874
+ " local: {",
875
+ " driver: 'local',",
876
+ " root: './storage/app',",
877
+ " },",
878
+ " public: {",
879
+ " driver: 'public',",
880
+ " root: './storage/app/public',",
881
+ " visibility: 'public',",
882
+ " },",
883
+ " },",
884
+ "})",
885
+ ""
886
+ ].join("\n");
887
+ }
888
+ function renderMediaConfig() {
889
+ return [
890
+ "import { defineMediaConfig } from '@holo-js/config'",
891
+ "",
892
+ "export default defineMediaConfig({})",
893
+ ""
894
+ ].join("\n");
895
+ }
896
+ function renderQueueConfig(options = {}) {
897
+ const driver = options.driver ?? "sync";
898
+ const defaultDatabaseConnection = options.defaultDatabaseConnection?.trim() || "default";
899
+ if (driver === "redis") {
900
+ return [
901
+ "import { defineQueueConfig, env } from '@holo-js/config'",
902
+ "",
903
+ "export default defineQueueConfig({",
904
+ " default: 'redis',",
905
+ " failed: false,",
906
+ " connections: {",
907
+ " redis: {",
908
+ " driver: 'redis',",
909
+ " connection: 'default',",
910
+ " queue: 'default',",
911
+ " retryAfter: 90,",
912
+ " blockFor: 5,",
913
+ " },",
914
+ " },",
915
+ "})",
916
+ ""
917
+ ].join("\n");
918
+ }
919
+ if (driver === "database") {
920
+ return [
921
+ "import { defineQueueConfig } from '@holo-js/config'",
922
+ "",
923
+ "export default defineQueueConfig({",
924
+ " default: 'database',",
925
+ " failed: {",
926
+ " driver: 'database',",
927
+ ` connection: '${defaultDatabaseConnection}',`,
928
+ " table: 'failed_jobs',",
929
+ " },",
930
+ " connections: {",
931
+ " database: {",
932
+ " driver: 'database',",
933
+ ` connection: '${defaultDatabaseConnection}',`,
934
+ " table: 'jobs',",
935
+ " queue: 'default',",
936
+ " retryAfter: 90,",
937
+ " sleep: 1,",
938
+ " },",
939
+ " },",
940
+ "})",
941
+ ""
942
+ ].join("\n");
943
+ }
944
+ return [
945
+ "import { defineQueueConfig } from '@holo-js/config'",
946
+ "",
947
+ "export default defineQueueConfig({",
948
+ " default: 'sync',",
949
+ " failed: false,",
950
+ " connections: {",
951
+ " sync: {",
952
+ " driver: 'sync',",
953
+ " queue: 'default',",
954
+ " },",
955
+ " },",
956
+ "})",
957
+ ""
958
+ ].join("\n");
959
+ }
960
+ function renderCacheConfig(driver = "file", defaultDatabaseConnection = "default", defaultRedisConnection = "default") {
961
+ const lines = [
962
+ "import { defineCacheConfig, env } from '@holo-js/config'",
963
+ "",
964
+ "export default defineCacheConfig({",
965
+ ` default: '${driver}',`,
966
+ " prefix: env('CACHE_PREFIX', ''),",
967
+ " drivers: {",
968
+ " file: {",
969
+ " driver: 'file',",
970
+ " path: './storage/framework/cache/data',",
971
+ " },",
972
+ " memory: {",
973
+ " driver: 'memory',",
974
+ " maxEntries: 1000,",
975
+ " },"
976
+ ];
977
+ if (driver === "redis") {
978
+ lines.push(
979
+ " redis: {",
980
+ " driver: 'redis',",
981
+ ` connection: '${defaultRedisConnection}',`,
982
+ " prefix: 'cache:',",
983
+ " },"
984
+ );
985
+ }
986
+ if (driver === "database") {
987
+ lines.push(
988
+ " database: {",
989
+ " driver: 'database',",
990
+ ` connection: '${defaultDatabaseConnection}',`,
991
+ " table: 'cache',",
992
+ " lockTable: 'cache_locks',",
993
+ " },"
994
+ );
995
+ }
996
+ lines.push(
997
+ " },",
998
+ "})",
999
+ ""
1000
+ );
1001
+ return lines.join("\n");
1002
+ }
1003
+ function renderRedisConfig() {
1004
+ return [
1005
+ "import { defineRedisConfig, env } from '@holo-js/config'",
1006
+ "",
1007
+ "export default defineRedisConfig({",
1008
+ " default: 'default',",
1009
+ " connections: {",
1010
+ " default: {",
1011
+ " url: env('REDIS_URL') || undefined,",
1012
+ " host: env('REDIS_HOST', '127.0.0.1'),",
1013
+ " port: env('REDIS_PORT', 6379),",
1014
+ " username: env('REDIS_USERNAME'),",
1015
+ " password: env('REDIS_PASSWORD'),",
1016
+ " db: env('REDIS_DB', 0),",
1017
+ " },",
1018
+ " },",
1019
+ "})",
1020
+ ""
1021
+ ].join("\n");
1022
+ }
1023
+ async function ensureRedisConfigFile(projectRoot) {
1024
+ const redisConfigPath = await resolveFirstExistingPath(projectRoot, REDIS_CONFIG_FILE_NAMES) ?? resolve2(projectRoot, "config/redis.ts");
1025
+ const redisConfigExists = await pathExists(redisConfigPath);
1026
+ if (!redisConfigExists) {
1027
+ await writeTextFile(redisConfigPath, renderRedisConfig());
1028
+ }
1029
+ return !redisConfigExists;
1030
+ }
1031
+ function renderNotificationsConfig() {
1032
+ return [
1033
+ "import { defineNotificationsConfig } from '@holo-js/config'",
1034
+ "",
1035
+ "export default defineNotificationsConfig({",
1036
+ " table: 'notifications',",
1037
+ " queue: {",
1038
+ " afterCommit: false,",
1039
+ " },",
1040
+ "})",
1041
+ ""
1042
+ ].join("\n");
1043
+ }
1044
+ function renderMailConfig() {
1045
+ return [
1046
+ "import { defineMailConfig, env } from '@holo-js/config'",
1047
+ "",
1048
+ "export default defineMailConfig({",
1049
+ " default: env('MAIL_MAILER', 'preview'),",
1050
+ " from: {",
1051
+ " email: env('MAIL_FROM_ADDRESS', 'hello@app.test'),",
1052
+ " name: env('MAIL_FROM_NAME', 'Holo App'),",
1053
+ " },",
1054
+ " preview: {",
1055
+ " allowedEnvironments: ['development'],",
1056
+ " },",
1057
+ " mailers: {",
1058
+ " preview: {",
1059
+ " driver: 'preview',",
1060
+ " },",
1061
+ " log: {",
1062
+ " driver: 'log',",
1063
+ " logBodies: env('MAIL_LOG_BODIES', false),",
1064
+ " },",
1065
+ " fake: {",
1066
+ " driver: 'fake',",
1067
+ " },",
1068
+ " smtp: {",
1069
+ " driver: 'smtp',",
1070
+ " host: env('MAIL_HOST', '127.0.0.1'),",
1071
+ " port: env('MAIL_PORT', 1025),",
1072
+ " secure: env('MAIL_SECURE', false),",
1073
+ " user: env('MAIL_USERNAME') || undefined,",
1074
+ " password: env('MAIL_PASSWORD') || undefined,",
1075
+ " },",
1076
+ " },",
1077
+ "})",
1078
+ ""
1079
+ ].join("\n");
1080
+ }
1081
+ function renderSecurityConfig() {
1082
+ return [
1083
+ `import { defineSecurityConfig, limit } from '@holo-js/security'`,
1084
+ "",
1085
+ "export default defineSecurityConfig({",
1086
+ " csrf: {",
1087
+ " enabled: true,",
1088
+ " field: '_token',",
1089
+ " header: 'X-CSRF-TOKEN',",
1090
+ " cookie: 'XSRF-TOKEN',",
1091
+ " except: [],",
1092
+ " },",
1093
+ " rateLimit: {",
1094
+ " driver: 'file',",
1095
+ " file: {",
1096
+ " path: './storage/framework/rate-limits',",
1097
+ " },",
1098
+ " redis: {",
1099
+ " connection: 'default',",
1100
+ " prefix: 'holo:rate-limit:',",
1101
+ " },",
1102
+ " limiters: {",
1103
+ " login: limit.perMinute(5).define(),",
1104
+ " register: limit.perHour(10).define(),",
1105
+ " },",
1106
+ " },",
1107
+ "})",
1108
+ ""
1109
+ ].join("\n");
1110
+ }
1111
+ function renderCorsConfig() {
1112
+ return [
1113
+ "import { defineCorsConfig, env } from '@holo-js/config'",
1114
+ "",
1115
+ "export default defineCorsConfig({",
1116
+ " paths: ['/api/*', '/broadcasting/auth'],",
1117
+ " origins: [",
1118
+ " env('FRONTEND_URL', 'http://localhost:3000'),",
1119
+ " ],",
1120
+ " methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],",
1121
+ " headers: ['Content-Type', 'Authorization', 'X-CSRF-TOKEN', 'X-Requested-With'],",
1122
+ " credentials: true,",
1123
+ " maxAge: 7200,",
1124
+ " statefulDomains: [",
1125
+ " env('FRONTEND_DOMAIN', 'localhost:3000'),",
1126
+ " ],",
1127
+ "})",
1128
+ ""
1129
+ ].join("\n");
1130
+ }
1131
+ async function ensureCorsConfigFile(projectRoot) {
1132
+ const corsConfigPath = await resolveFirstExistingPath(projectRoot, CORS_CONFIG_FILE_NAMES) ?? resolve2(projectRoot, "config/cors.ts");
1133
+ const corsConfigExists = await pathExists(corsConfigPath);
1134
+ if (!corsConfigExists) {
1135
+ await writeTextFile(corsConfigPath, renderCorsConfig());
1136
+ }
1137
+ return !corsConfigExists;
1138
+ }
1139
+ async function ensureRateLimitStorageIgnore(projectRoot) {
1140
+ const rateLimitRoot = resolve2(projectRoot, "storage/framework/rate-limits");
1141
+ const ignorePath = resolve2(rateLimitRoot, ".gitignore");
1142
+ await mkdir(rateLimitRoot, { recursive: true });
1143
+ if (!await pathExists(ignorePath)) {
1144
+ await writeTextFile(ignorePath, "*\n!.gitignore\n");
1145
+ return;
1146
+ }
1147
+ const currentContents = await readTextFile(ignorePath) ?? "";
1148
+ const existingLines = new Set(currentContents.split(/\r?\n/));
1149
+ const missingLines = [
1150
+ "*",
1151
+ "!.gitignore"
1152
+ ].filter((line) => !existingLines.has(line));
1153
+ if (missingLines.length === 0) {
1154
+ return;
1155
+ }
1156
+ await appendFile(
1157
+ ignorePath,
1158
+ `${currentContents.length > 0 && !currentContents.endsWith("\n") ? "\n" : ""}${missingLines.join("\n")}
1159
+ `,
1160
+ "utf8"
1161
+ );
1162
+ }
1163
+ function renderBroadcastConfig(moduleFormat, includeAuthEndpoint, useTypeScriptSyntax) {
1164
+ const renderBroadcastScheme = () => {
1165
+ return useTypeScriptSyntax ? "env('BROADCAST_SCHEME') === 'https' ? 'https' : 'http'" : "(process.env.BROADCAST_SCHEME === 'https' ? 'https' : 'http')";
1166
+ };
1167
+ if (moduleFormat === "cjs") {
1168
+ return [
1169
+ "const { defineBroadcastConfig, env } = require('@holo-js/config')",
1170
+ "",
1171
+ `const broadcastScheme = ${renderBroadcastScheme()}`,
1172
+ "",
1173
+ "module.exports = defineBroadcastConfig({",
1174
+ " default: env('BROADCAST_CONNECTION', 'holo'),",
1175
+ " connections: {",
1176
+ " holo: {",
1177
+ " driver: 'holo',",
1178
+ " appId: env('BROADCAST_APP_ID', 'app-id'),",
1179
+ " key: env('BROADCAST_APP_KEY', 'app-key'),",
1180
+ " secret: env('BROADCAST_APP_SECRET', 'app-secret'),",
1181
+ " options: {",
1182
+ " host: env('BROADCAST_HOST', '127.0.0.1'),",
1183
+ " port: env('BROADCAST_PORT', 8080),",
1184
+ " scheme: broadcastScheme,",
1185
+ " useTLS: broadcastScheme === 'https',",
1186
+ " },",
1187
+ ...includeAuthEndpoint ? [
1188
+ " clientOptions: {",
1189
+ " authEndpoint: `${env('APP_URL', 'http://localhost:3000')}/broadcasting/auth`,",
1190
+ " },"
1191
+ ] : [],
1192
+ " },",
1193
+ " log: {",
1194
+ " driver: 'log',",
1195
+ " },",
1196
+ " null: {",
1197
+ " driver: 'null',",
1198
+ " },",
1199
+ " },",
1200
+ "})",
1201
+ ""
1202
+ ].join("\n");
1203
+ }
1204
+ return [
1205
+ "import { defineBroadcastConfig, env } from '@holo-js/config'",
1206
+ "",
1207
+ `const broadcastScheme = ${renderBroadcastScheme()}`,
1208
+ "",
1209
+ "export default defineBroadcastConfig({",
1210
+ " default: env('BROADCAST_CONNECTION', 'holo'),",
1211
+ " connections: {",
1212
+ " holo: {",
1213
+ " driver: 'holo',",
1214
+ " appId: env('BROADCAST_APP_ID', 'app-id'),",
1215
+ " key: env('BROADCAST_APP_KEY', 'app-key'),",
1216
+ " secret: env('BROADCAST_APP_SECRET', 'app-secret'),",
1217
+ " options: {",
1218
+ " host: env('BROADCAST_HOST', '127.0.0.1'),",
1219
+ " port: env('BROADCAST_PORT', 8080),",
1220
+ " scheme: broadcastScheme,",
1221
+ " useTLS: broadcastScheme === 'https',",
1222
+ " },",
1223
+ ...includeAuthEndpoint ? [
1224
+ " clientOptions: {",
1225
+ " authEndpoint: `${env('APP_URL', 'http://localhost:3000')}/broadcasting/auth`,",
1226
+ " },"
1227
+ ] : [],
1228
+ " },",
1229
+ " log: {",
1230
+ " driver: 'log',",
1231
+ " },",
1232
+ " null: {",
1233
+ " driver: 'null',",
1234
+ " },",
1235
+ " },",
1236
+ "})",
1237
+ ""
1238
+ ].join("\n");
1239
+ }
1240
+ function stripBroadcastAuthEndpointBlock(value) {
1241
+ return value.replace(
1242
+ /(^|\n)\s*clientOptions:\s*\{\n\s*authEndpoint:\s*.*,\n\s*\},/m,
1243
+ ""
1244
+ );
1245
+ }
1246
+ function hasUnaliasedEnvSpecifier(specifiers, aliasToken) {
1247
+ return specifiers.split(",").some((specifier) => {
1248
+ const normalized = specifier.trim().replace(/\s+/g, " ");
1249
+ return normalized === "env" || normalized === `env ${aliasToken} env`;
1250
+ });
1251
+ }
1252
+ function hasHoloConfigEnvBinding(value) {
1253
+ for (const match of value.matchAll(/import\s*\{([^}]+)\}\s*from\s*['"]@holo-js\/config['"]/g)) {
1254
+ const specifiers = match[1];
1255
+ if (specifiers && hasUnaliasedEnvSpecifier(specifiers, "as")) {
1256
+ return true;
1257
+ }
1258
+ }
1259
+ for (const match of value.matchAll(/\{([^}]+)\}\s*=\s*require\(['"]@holo-js\/config['"]\)/g)) {
1260
+ const specifiers = match[1];
1261
+ if (specifiers && hasUnaliasedEnvSpecifier(specifiers, ":")) {
1262
+ return true;
1263
+ }
1264
+ }
1265
+ return false;
1266
+ }
1267
+ function injectBroadcastAuthEndpoint(value) {
1268
+ if (value.includes("authEndpoint:")) {
1269
+ return value;
1270
+ }
1271
+ const authEndpointExpression = hasHoloConfigEnvBinding(value) ? "`${env('APP_URL', 'http://localhost:3000')}/broadcasting/auth`" : "`${process.env.APP_URL ?? 'http://localhost:3000'}/broadcasting/auth`";
1272
+ const nextValue = value.replace(
1273
+ /(holo:\s*\{[\s\S]*?options:\s*\{[\s\S]*?\n)([ \t]*)\},/m,
1274
+ (_match, prefix, indent) => {
1275
+ return [
1276
+ `${prefix}${indent}},`,
1277
+ `${indent}clientOptions: {`,
1278
+ `${indent} authEndpoint: ${authEndpointExpression},`,
1279
+ `${indent}},`
1280
+ ].join("\n");
1281
+ }
1282
+ );
1283
+ return nextValue === value ? void 0 : nextValue;
1284
+ }
1285
+ function canSafelyRewriteBroadcastConfig(currentContents, moduleFormat, useTypeScriptSyntax) {
1286
+ return stripBroadcastAuthEndpointBlock(currentContents) === stripBroadcastAuthEndpointBlock(
1287
+ renderBroadcastConfig(moduleFormat, false, useTypeScriptSyntax)
1288
+ );
1289
+ }
1290
+ function resolveBroadcastConfigTargetPath(projectRoot, manifestPath, moduleFormat) {
1291
+ const extension = extname(manifestPath);
1292
+ const targetExtension = extension === ".cjs" || extension === ".cts" || extension === ".mjs" || extension === ".mts" ? extension : moduleFormat === "cjs" ? ".cjs" : extension === ".ts" || extension === ".js" ? extension : ".ts";
1293
+ return resolve2(projectRoot, `config/broadcast${targetExtension}`);
1294
+ }
1295
+ function renderBroadcastEnvFiles() {
1296
+ const env = [
1297
+ "BROADCAST_CONNECTION=holo"
1298
+ ];
1299
+ const example = [
1300
+ "BROADCAST_CONNECTION=holo",
1301
+ "BROADCAST_APP_ID=",
1302
+ "BROADCAST_APP_KEY=",
1303
+ "BROADCAST_APP_SECRET="
1304
+ ];
1305
+ return {
1306
+ env,
1307
+ example
1308
+ };
1309
+ }
1310
+ async function syncBroadcastAuthSupportAfterAuthInstall(projectRoot) {
1311
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
1312
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
1313
+ const canCreateBroadcastAuthRoute = framework === "next" || framework === "nuxt" || framework === "sveltekit";
1314
+ const authConfigPath = await resolveFirstExistingPath(projectRoot, AUTH_CONFIG_FILE_NAMES);
1315
+ const broadcastConfigPath = await resolveFirstExistingPath(projectRoot, BROADCAST_CONFIG_FILE_NAMES);
1316
+ if (!authConfigPath || !broadcastConfigPath || !canCreateBroadcastAuthRoute) {
1317
+ return {
1318
+ updatedBroadcastConfig: false,
1319
+ createdBroadcastAuthRoute: false
1320
+ };
1321
+ }
1322
+ const currentBroadcastConfig = await readTextFile(broadcastConfigPath);
1323
+ let updatedBroadcastConfig = false;
1324
+ let createdBroadcastAuthRoute = false;
1325
+ if (!currentBroadcastConfig.includes("authEndpoint:")) {
1326
+ const broadcastConfigModuleFormat = resolveConfigModuleFormat(broadcastConfigPath, currentBroadcastConfig);
1327
+ const broadcastConfigIsTypeScript = [".ts", ".mts", ".cts"].includes(extname(broadcastConfigPath));
1328
+ const rewrittenBroadcastConfig = canSafelyRewriteBroadcastConfig(
1329
+ currentBroadcastConfig,
1330
+ broadcastConfigModuleFormat,
1331
+ broadcastConfigIsTypeScript
1332
+ ) ? renderBroadcastConfig(broadcastConfigModuleFormat, true, broadcastConfigIsTypeScript) : injectBroadcastAuthEndpoint(currentBroadcastConfig);
1333
+ if (rewrittenBroadcastConfig) {
1334
+ await writeTextFile(
1335
+ broadcastConfigPath,
1336
+ rewrittenBroadcastConfig
1337
+ );
1338
+ updatedBroadcastConfig = true;
1339
+ }
1340
+ }
1341
+ if (framework === "next") {
1342
+ const authRoutePath = resolve2(projectRoot, "app/broadcasting/auth/route.ts");
1343
+ const holoHelperPath = resolve2(projectRoot, ".holo-js/generated/next/holo.ts");
1344
+ const generatedRoutePath = resolve2(projectRoot, ".holo-js/generated/next/broadcast-auth-route.ts");
1345
+ if (!await pathExists(authRoutePath)) {
1346
+ await writeTextFile(authRoutePath, renderNextBroadcastAuthRoute());
1347
+ createdBroadcastAuthRoute = true;
1348
+ }
1349
+ await writeTextFile(holoHelperPath, renderNextHoloHelper());
1350
+ await writeTextFile(generatedRoutePath, renderNextGeneratedBroadcastAuthRoute());
1351
+ return {
1352
+ updatedBroadcastConfig,
1353
+ createdBroadcastAuthRoute
1354
+ };
1355
+ }
1356
+ if (framework === "sveltekit") {
1357
+ const holoHelperPath = resolve2(projectRoot, ".holo-js/generated/sveltekit/holo.ts");
1358
+ await writeTextFile(holoHelperPath, renderSvelteHoloHelper());
1359
+ }
1360
+ return {
1361
+ updatedBroadcastConfig,
1362
+ createdBroadcastAuthRoute
1363
+ };
1364
+ }
1365
+ function renderSessionConfig(defaultDatabaseConnection = "default") {
1366
+ return [
1367
+ "import { defineSessionConfig, env } from '@holo-js/config'",
1368
+ "",
1369
+ "const sessionSameSite = env('SESSION_SAME_SITE') === 'strict'",
1370
+ " ? 'strict'",
1371
+ " : env('SESSION_SAME_SITE') === 'none'",
1372
+ " ? 'none'",
1373
+ " : 'lax'",
1374
+ "",
1375
+ "export default defineSessionConfig({",
1376
+ " driver: env('SESSION_DRIVER', 'file'),",
1377
+ " stores: {",
1378
+ " database: {",
1379
+ " driver: 'database',",
1380
+ ` connection: env('SESSION_CONNECTION', '${defaultDatabaseConnection}'),`,
1381
+ " table: 'sessions',",
1382
+ " },",
1383
+ " file: {",
1384
+ " driver: 'file',",
1385
+ " path: './storage/framework/sessions',",
1386
+ " },",
1387
+ " },",
1388
+ " cookie: {",
1389
+ " name: env('SESSION_COOKIE', 'holo_session'),",
1390
+ " path: env('SESSION_PATH', '/'),",
1391
+ " domain: env('SESSION_DOMAIN'),",
1392
+ " secure: env('SESSION_SECURE', false),",
1393
+ " httpOnly: true,",
1394
+ " sameSite: sessionSameSite,",
1395
+ " },",
1396
+ " idleTimeout: env('SESSION_IDLE_TIMEOUT', 120),",
1397
+ " absoluteLifetime: env('SESSION_LIFETIME', 120),",
1398
+ " rememberMeLifetime: env('SESSION_REMEMBER_ME_LIFETIME', 43200),",
1399
+ "})",
1400
+ ""
1401
+ ].join("\n");
1402
+ }
1403
+ function renderAuthConfig(features = {}, moduleFormat = "esm") {
1404
+ const envValue = (name, fallback) => {
1405
+ if (moduleFormat === "cjs") {
1406
+ return typeof fallback === "string" ? `process.env.${name} || ${JSON.stringify(fallback)}` : `process.env.${name}`;
1407
+ }
1408
+ return typeof fallback === "string" ? `env('${name}', ${JSON.stringify(fallback)})` : `env('${name}')`;
1409
+ };
1410
+ const socialEnabled = features.social === true || (features.socialProviders?.length ?? 0) > 0;
1411
+ const socialProviders = features.socialProviders && features.socialProviders.length > 0 ? features.socialProviders : socialEnabled ? ["google"] : [];
1412
+ const lines = [
1413
+ moduleFormat === "cjs" ? "module.exports = {" : "import { defineAuthConfig, env } from '@holo-js/config'",
1414
+ "",
1415
+ ...moduleFormat === "cjs" ? [] : ["export default defineAuthConfig({"],
1416
+ " defaults: {",
1417
+ " guard: 'web',",
1418
+ " passwords: 'users',",
1419
+ " },",
1420
+ " guards: {",
1421
+ " web: {",
1422
+ " driver: 'session',",
1423
+ " provider: 'users',",
1424
+ " },",
1425
+ " // admin: {",
1426
+ " // driver: 'session',",
1427
+ " // provider: 'admins',",
1428
+ " // },",
1429
+ " },",
1430
+ " providers: {",
1431
+ " users: {",
1432
+ " model: 'User',",
1433
+ " identifiers: ['email'],",
1434
+ " },",
1435
+ " // admins: {",
1436
+ " // model: 'Admin',",
1437
+ " // identifiers: ['email'],",
1438
+ " // },",
1439
+ " },",
1440
+ " passwords: {",
1441
+ " users: {",
1442
+ " provider: 'users',",
1443
+ " table: 'password_reset_tokens',",
1444
+ " expire: 60,",
1445
+ " throttle: 60,",
1446
+ " route: '/reset-password',",
1447
+ " },",
1448
+ " },",
1449
+ " emailVerification: {",
1450
+ " required: false,",
1451
+ " route: '/verify-email',",
1452
+ " },"
1453
+ ];
1454
+ if (socialProviders.length > 0) {
1455
+ lines.push(" social: {");
1456
+ for (const provider of socialProviders) {
1457
+ const upper = provider.toUpperCase();
1458
+ const defaultScopes = provider === "google" ? ["openid", "email", "profile"] : provider === "github" ? ["read:user", "user:email"] : provider === "discord" ? ["identify", "email"] : provider === "facebook" ? ["email", "public_profile"] : provider === "apple" ? ["name", "email"] : ["openid", "profile", "email"];
1459
+ lines.push(
1460
+ ` ${provider}: {`,
1461
+ ` clientId: ${envValue(`AUTH_${upper}_CLIENT_ID`)},`,
1462
+ ` clientSecret: ${envValue(`AUTH_${upper}_CLIENT_SECRET`)},`,
1463
+ ` redirectUri: ${envValue(`AUTH_${upper}_REDIRECT_URI`)},`,
1464
+ ` scopes: [${defaultScopes.map((scope) => `'${scope}'`).join(", ")}],`,
1465
+ " },"
1466
+ );
1467
+ }
1468
+ lines.push(" },");
1469
+ }
1470
+ if (features.workos) {
1471
+ lines.push(
1472
+ " workos: {",
1473
+ ` provider: ${envValue("AUTH_WORKOS_PROVIDER", "dashboard")},`,
1474
+ " dashboard: {",
1475
+ ` clientId: ${envValue("WORKOS_CLIENT_ID")},`,
1476
+ ` apiKey: ${envValue("WORKOS_API_KEY")},`,
1477
+ ` redirectUri: ${envValue("WORKOS_REDIRECT_URI")},`,
1478
+ " },",
1479
+ " },",
1480
+ " // Add a dedicated guard and provider if WorkOS users should resolve through a different model."
1481
+ );
1482
+ }
1483
+ if (features.clerk) {
1484
+ lines.push(
1485
+ " clerk: {",
1486
+ ` provider: ${envValue("AUTH_CLERK_PROVIDER", "app")},`,
1487
+ " app: {",
1488
+ ` publishableKey: ${envValue("CLERK_PUBLISHABLE_KEY")},`,
1489
+ ` secretKey: ${envValue("CLERK_SECRET_KEY")},`,
1490
+ ` apiUrl: ${envValue("CLERK_API_URL")},`,
1491
+ ` frontendApi: ${envValue("CLERK_FRONTEND_API")},`,
1492
+ ` redirectUri: ${envValue("CLERK_REDIRECT_URI")},`,
1493
+ ` sessionCookie: ${envValue("CLERK_SESSION_COOKIE", "__session")},`,
1494
+ " },",
1495
+ " },",
1496
+ " // Add a dedicated guard and provider if Clerk users should resolve through a different model."
1497
+ );
1498
+ }
1499
+ lines.push(moduleFormat === "cjs" ? "}" : "})", "");
1500
+ return lines.join("\n");
1501
+ }
1502
+ function authFeaturesRequireConfigUpdate(features) {
1503
+ return features.workos === true || features.clerk === true || features.social === true || (features.socialProviders?.length ?? 0) > 0;
1504
+ }
1505
+ function detectAuthInstallFeaturesFromConfig(contents) {
1506
+ const socialProviders = SUPPORTED_AUTH_SOCIAL_PROVIDERS.filter((provider) => {
1507
+ const pattern = new RegExp(`\\b${provider}\\s*:\\s*\\{`);
1508
+ return pattern.test(contents);
1509
+ });
1510
+ return Object.freeze({
1511
+ ...socialProviders.length > 0 ? { social: true, socialProviders } : {},
1512
+ ...contents.includes(" workos: {") ? { workos: true } : {},
1513
+ ...contents.includes(" clerk: {") ? { clerk: true } : {}
1514
+ });
1515
+ }
1516
+ function mergeAuthInstallFeatures(current, requested) {
1517
+ const socialProviders = Array.from(/* @__PURE__ */ new Set([
1518
+ ...current.socialProviders ?? [],
1519
+ ...requested.socialProviders ?? []
1520
+ ]));
1521
+ return Object.freeze({
1522
+ ...current.social === true || requested.social === true || socialProviders.length > 0 ? { social: true } : {},
1523
+ ...socialProviders.length > 0 ? { socialProviders } : {},
1524
+ ...current.workos === true || requested.workos === true ? { workos: true } : {},
1525
+ ...current.clerk === true || requested.clerk === true ? { clerk: true } : {}
1526
+ });
1527
+ }
1528
+ function canSafelyRewriteAuthConfig(currentContents, currentFeatures, moduleFormat) {
1529
+ const stripLegacyCurrentUserEndpoint = (value) => value.replace(
1530
+ /(^|\n)\s*currentUserEndpoint:\s*\{\n\s*path:\s*.*,\n\s*\},/m,
1531
+ ""
1532
+ );
1533
+ return stripLegacyCurrentUserEndpoint(currentContents) === stripLegacyCurrentUserEndpoint(
1534
+ renderAuthConfig(currentFeatures, moduleFormat)
1535
+ );
1536
+ }
1537
+ function resolveConfigModuleFormat(filePath, contents) {
1538
+ if (filePath?.endsWith(".cjs") || filePath?.endsWith(".cts") || contents.includes("module.exports =")) {
1539
+ return "cjs";
1540
+ }
1541
+ return "esm";
1542
+ }
1543
+ function mergeInstalledAuthFeatures(current, requested) {
1544
+ return mergeAuthInstallFeatures(current, requested);
1545
+ }
1546
+
1547
+ // src/project/scaffold/framework.ts
1548
+ import { mkdir as mkdir2, readdir, writeFile } from "fs/promises";
1549
+ import { dirname, resolve as resolve3 } from "path";
1550
+ import {
1551
+ normalizeHoloProjectConfig,
1552
+ renderGeneratedSchemaPlaceholder
1553
+ } from "@holo-js/db";
1554
+
1555
+ // src/project/scaffold/project-renderers.ts
1556
+ import {
1557
+ createMigrationFileName
1558
+ } from "@holo-js/db";
1559
+
1560
+ // src/project/scaffold/types.ts
1561
+ var AUTH_MIGRATION_SLUGS = [
1562
+ "create_users",
1563
+ "create_sessions",
1564
+ "create_auth_identities",
1565
+ "create_personal_access_tokens",
1566
+ "create_password_reset_tokens",
1567
+ "create_email_verification_tokens"
1568
+ ];
1569
+
1570
+ // src/project/scaffold/project-renderers.ts
1571
+ function renderAuthEnvFiles(features = {}, defaultDatabaseConnection = "default") {
1572
+ const socialEnabled = features.social === true || (features.socialProviders?.length ?? 0) > 0;
1573
+ const socialProviders = features.socialProviders && features.socialProviders.length > 0 ? features.socialProviders : socialEnabled ? ["google"] : [];
1574
+ const env = [
1575
+ "AUTH_EMAIL_VERIFICATION_ROUTE=/verify-email",
1576
+ "AUTH_PASSWORD_RESET_ROUTE=/reset-password",
1577
+ "FRONTEND_URL=",
1578
+ "FRONTEND_DOMAIN=",
1579
+ "SESSION_DRIVER=file",
1580
+ `SESSION_CONNECTION=${defaultDatabaseConnection}`,
1581
+ "SESSION_COOKIE=holo_session",
1582
+ "SESSION_PATH=/",
1583
+ "SESSION_DOMAIN=",
1584
+ "SESSION_SECURE=false",
1585
+ "SESSION_SAME_SITE=lax",
1586
+ "SESSION_IDLE_TIMEOUT=120",
1587
+ "SESSION_LIFETIME=120",
1588
+ "SESSION_REMEMBER_ME_LIFETIME=43200"
1589
+ ];
1590
+ for (const provider of socialProviders) {
1591
+ const upper = provider.toUpperCase();
1592
+ env.push(
1593
+ `AUTH_${upper}_CLIENT_ID=`,
1594
+ `AUTH_${upper}_CLIENT_SECRET=`,
1595
+ `AUTH_${upper}_REDIRECT_URI=`
1596
+ );
1597
+ }
1598
+ if (features.workos) {
1599
+ env.push(
1600
+ "AUTH_WORKOS_PROVIDER=dashboard",
1601
+ "WORKOS_CLIENT_ID=",
1602
+ "WORKOS_API_KEY=",
1603
+ "WORKOS_REDIRECT_URI="
1604
+ );
1605
+ }
1606
+ if (features.clerk) {
1607
+ env.push(
1608
+ "CLERK_PUBLISHABLE_KEY=",
1609
+ "CLERK_SECRET_KEY=",
1610
+ "CLERK_API_URL=",
1611
+ "CLERK_FRONTEND_API=",
1612
+ "CLERK_REDIRECT_URI=",
1613
+ "CLERK_SESSION_COOKIE=__session"
1614
+ );
1615
+ }
1616
+ return {
1617
+ env,
1618
+ example: env.map((line) => `${line.split("=")[0]}=`)
1619
+ };
1620
+ }
1621
+ function renderAuthUserModel(_generatedSchemaImportPath = "../../.holo-js/generated/schema.generated") {
1622
+ return [
1623
+ "import { defineModel } from '@holo-js/db'",
1624
+ "",
1625
+ "export default defineModel('users', {",
1626
+ " fillable: ['name', 'email', 'password', 'avatar'],",
1627
+ " hidden: ['password'],",
1628
+ "})",
1629
+ ""
1630
+ ].join("\n");
1631
+ }
1632
+ function renderAuthEmailVerificationNotification() {
1633
+ return [
1634
+ "import { defineNotification } from '@holo-js/notifications'",
1635
+ "",
1636
+ "interface EmailVerificationNotification {",
1637
+ " readonly email: string",
1638
+ " readonly name?: string",
1639
+ " readonly url: string",
1640
+ " readonly expiresAt: Date",
1641
+ "}",
1642
+ "",
1643
+ "export default defineNotification({",
1644
+ " type: 'auth.email-verification',",
1645
+ " via() {",
1646
+ " return ['email']",
1647
+ " },",
1648
+ " build: {",
1649
+ " email(data: EmailVerificationNotification) {",
1650
+ " return {",
1651
+ " subject: 'Verify your email address',",
1652
+ " greeting: data.name ? `Hello ${data.name},` : undefined,",
1653
+ " lines: [",
1654
+ " 'Confirm your account to finish signing in.',",
1655
+ " `This verification link expires at ${data.expiresAt.toUTCString()}.`,",
1656
+ " ],",
1657
+ " action: {",
1658
+ " label: 'Verify email address',",
1659
+ " url: data.url,",
1660
+ " },",
1661
+ " }",
1662
+ " },",
1663
+ " },",
1664
+ "})",
1665
+ ""
1666
+ ].join("\n");
1667
+ }
1668
+ function renderAuthPasswordResetNotification() {
1669
+ return [
1670
+ "import { defineNotification } from '@holo-js/notifications'",
1671
+ "",
1672
+ "interface PasswordResetNotification {",
1673
+ " readonly email: string",
1674
+ " readonly url: string",
1675
+ " readonly expiresAt: Date",
1676
+ "}",
1677
+ "",
1678
+ "export default defineNotification({",
1679
+ " type: 'auth.password-reset',",
1680
+ " via() {",
1681
+ " return ['email']",
1682
+ " },",
1683
+ " build: {",
1684
+ " email(data: PasswordResetNotification) {",
1685
+ " return {",
1686
+ " subject: 'Reset your password',",
1687
+ " lines: [",
1688
+ " 'Click the link below to choose a new password.',",
1689
+ " `This reset link expires at ${data.expiresAt.toUTCString()}.`,",
1690
+ " ],",
1691
+ " action: {",
1692
+ " label: 'Reset password',",
1693
+ " url: data.url,",
1694
+ " },",
1695
+ " }",
1696
+ " },",
1697
+ " },",
1698
+ "})",
1699
+ ""
1700
+ ].join("\n");
1701
+ }
1702
+ function renderAuthorizationPoliciesReadme() {
1703
+ return [
1704
+ "# Authorization Policies",
1705
+ "",
1706
+ "Place policy files in this directory.",
1707
+ "Export `definePolicy(...)` definitions from `@holo-js/authorization`.",
1708
+ ""
1709
+ ].join("\n");
1710
+ }
1711
+ function renderAuthorizationAbilitiesReadme() {
1712
+ return [
1713
+ "# Authorization Abilities",
1714
+ "",
1715
+ "Place ability files in this directory.",
1716
+ "Export `defineAbility(...)` definitions from `@holo-js/authorization`.",
1717
+ ""
1718
+ ].join("\n");
1719
+ }
1720
+ function resolveAuthUserModelSchemaImportPath(userModelPath, generatedSchemaPath) {
1721
+ return relativeImportPath(userModelPath, generatedSchemaPath);
1722
+ }
1723
+ function renderAuthMigration(slug) {
1724
+ switch (slug) {
1725
+ case "create_users":
1726
+ return [
1727
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1728
+ "",
1729
+ "export default defineMigration({",
1730
+ " async up({ schema }: MigrationContext) {",
1731
+ " await schema.createTable('users', (table) => {",
1732
+ " table.id()",
1733
+ " table.string('name')",
1734
+ " table.string('email').unique()",
1735
+ " table.string('password').nullable()",
1736
+ " table.string('avatar').nullable()",
1737
+ " table.timestamp('email_verified_at').nullable()",
1738
+ " table.timestamps()",
1739
+ " })",
1740
+ " },",
1741
+ " async down({ schema }: MigrationContext) {",
1742
+ " await schema.dropTable('users')",
1743
+ " },",
1744
+ "})",
1745
+ ""
1746
+ ].join("\n");
1747
+ case "create_sessions":
1748
+ return [
1749
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1750
+ "",
1751
+ "export default defineMigration({",
1752
+ " async up({ schema }: MigrationContext) {",
1753
+ " await schema.createTable('sessions', (table) => {",
1754
+ " table.string('id').primaryKey()",
1755
+ " table.string('store').default('database')",
1756
+ " table.json('data')",
1757
+ " table.timestamp('created_at')",
1758
+ " table.timestamp('last_activity_at')",
1759
+ " table.timestamp('expires_at')",
1760
+ " table.timestamp('invalidated_at').nullable()",
1761
+ " table.string('remember_token_hash').nullable()",
1762
+ " table.index(['expires_at'])",
1763
+ " })",
1764
+ " },",
1765
+ " async down({ schema }: MigrationContext) {",
1766
+ " await schema.dropTable('sessions')",
1767
+ " },",
1768
+ "})",
1769
+ ""
1770
+ ].join("\n");
1771
+ case "create_auth_identities":
1772
+ return [
1773
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1774
+ "",
1775
+ "export default defineMigration({",
1776
+ " async up({ schema }: MigrationContext) {",
1777
+ " await schema.createTable('auth_identities', (table) => {",
1778
+ " table.id()",
1779
+ " table.string('user_id')",
1780
+ " table.string('guard').default('web')",
1781
+ " table.string('auth_provider').default('users')",
1782
+ " table.string('provider')",
1783
+ " table.string('provider_user_id')",
1784
+ " table.string('email').nullable()",
1785
+ " table.boolean('email_verified').default(false)",
1786
+ " table.json('profile')",
1787
+ " table.json('tokens')",
1788
+ " table.timestamps()",
1789
+ " table.index(['user_id'])",
1790
+ " table.unique(['provider', 'provider_user_id'], 'auth_identities_provider_user_unique')",
1791
+ " })",
1792
+ " },",
1793
+ " async down({ schema }: MigrationContext) {",
1794
+ " await schema.dropTable('auth_identities')",
1795
+ " },",
1796
+ "})",
1797
+ ""
1798
+ ].join("\n");
1799
+ case "create_personal_access_tokens":
1800
+ return [
1801
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1802
+ "",
1803
+ "export default defineMigration({",
1804
+ " async up({ schema }: MigrationContext) {",
1805
+ " await schema.createTable('personal_access_tokens', (table) => {",
1806
+ " table.uuid('id').primaryKey()",
1807
+ " table.string('provider').default('users')",
1808
+ " table.string('user_id')",
1809
+ " table.string('name')",
1810
+ " table.string('token_hash').unique()",
1811
+ " table.json('abilities')",
1812
+ " table.timestamp('last_used_at').nullable()",
1813
+ " table.timestamp('expires_at').nullable()",
1814
+ " table.timestamps()",
1815
+ " table.index(['provider'])",
1816
+ " table.index(['user_id'])",
1817
+ " })",
1818
+ " },",
1819
+ " async down({ schema }: MigrationContext) {",
1820
+ " await schema.dropTable('personal_access_tokens')",
1821
+ " },",
1822
+ "})",
1823
+ ""
1824
+ ].join("\n");
1825
+ case "create_password_reset_tokens":
1826
+ return [
1827
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1828
+ "",
1829
+ "export default defineMigration({",
1830
+ " async up({ schema }: MigrationContext) {",
1831
+ " await schema.createTable('password_reset_tokens', (table) => {",
1832
+ " table.uuid('id').primaryKey()",
1833
+ " table.string('provider').default('users')",
1834
+ " table.string('email')",
1835
+ " table.string('token_hash')",
1836
+ " table.timestamp('expires_at')",
1837
+ " table.timestamp('used_at').nullable()",
1838
+ " table.timestamps()",
1839
+ " table.index(['provider'])",
1840
+ " table.index(['email'])",
1841
+ " })",
1842
+ " },",
1843
+ " async down({ schema }: MigrationContext) {",
1844
+ " await schema.dropTable('password_reset_tokens')",
1845
+ " },",
1846
+ "})",
1847
+ ""
1848
+ ].join("\n");
1849
+ case "create_email_verification_tokens":
1850
+ return [
1851
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1852
+ "",
1853
+ "export default defineMigration({",
1854
+ " async up({ schema }: MigrationContext) {",
1855
+ " await schema.createTable('email_verification_tokens', (table) => {",
1856
+ " table.uuid('id').primaryKey()",
1857
+ " table.string('provider').default('users')",
1858
+ " table.string('user_id')",
1859
+ " table.string('email')",
1860
+ " table.string('token_hash')",
1861
+ " table.timestamp('expires_at')",
1862
+ " table.timestamp('used_at').nullable()",
1863
+ " table.timestamps()",
1864
+ " table.index(['provider'])",
1865
+ " table.index(['user_id'])",
1866
+ " table.index(['email'])",
1867
+ " })",
1868
+ " },",
1869
+ " async down({ schema }: MigrationContext) {",
1870
+ " await schema.dropTable('email_verification_tokens')",
1871
+ " },",
1872
+ "})",
1873
+ ""
1874
+ ].join("\n");
1875
+ }
1876
+ }
1877
+ function createAuthMigrationFiles(date = /* @__PURE__ */ new Date()) {
1878
+ return AUTH_MIGRATION_SLUGS.map((slug, index) => ({
1879
+ path: createMigrationFileName(slug, new Date(date.getTime() + index * 1e3)),
1880
+ contents: renderAuthMigration(slug)
1881
+ }));
1882
+ }
1883
+ function renderNotificationsMigration() {
1884
+ return [
1885
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
1886
+ "",
1887
+ "export default defineMigration({",
1888
+ " async up({ schema }: MigrationContext) {",
1889
+ " await schema.createTable('notifications', (table) => {",
1890
+ " table.string('id').primaryKey()",
1891
+ " table.string('type').nullable()",
1892
+ " table.string('notifiable_type')",
1893
+ " table.string('notifiable_id')",
1894
+ " table.json('data')",
1895
+ " table.timestamp('read_at').nullable()",
1896
+ " table.timestamp('created_at')",
1897
+ " table.timestamp('updated_at')",
1898
+ " table.index(['notifiable_type', 'notifiable_id'])",
1899
+ " table.index(['read_at'])",
1900
+ " })",
1901
+ " },",
1902
+ " async down({ schema }: MigrationContext) {",
1903
+ " await schema.dropTable('notifications')",
1904
+ " },",
1905
+ "})",
1906
+ ""
1907
+ ].join("\n");
1908
+ }
1909
+ function createNotificationsMigrationFiles(date = /* @__PURE__ */ new Date()) {
1910
+ return [{
1911
+ path: createMigrationFileName("create_notifications", date),
1912
+ contents: renderNotificationsMigration()
1913
+ }];
1914
+ }
1915
+ function resolveFrameworkDefaultUrl(framework) {
1916
+ return framework === "sveltekit" ? "http://localhost:5173" : "http://localhost:3000";
1917
+ }
1918
+ function renderScaffoldAppConfig(projectName, framework) {
1919
+ const defaultUrl = resolveFrameworkDefaultUrl(framework);
1920
+ return [
1921
+ "import { defineAppConfig, env } from '@holo-js/config'",
1922
+ "",
1923
+ "const appEnv = env('APP_ENV') === 'production'",
1924
+ " ? 'production'",
1925
+ " : env('APP_ENV') === 'test'",
1926
+ " ? 'test'",
1927
+ " : 'development'",
1928
+ "",
1929
+ "export default defineAppConfig({",
1930
+ ` name: env('APP_NAME', ${JSON.stringify(projectName)}),`,
1931
+ " key: env('APP_KEY'),",
1932
+ ` url: env('APP_URL', '${defaultUrl}'),`,
1933
+ " env: appEnv,",
1934
+ " debug: env('APP_DEBUG', true),",
1935
+ " paths: {",
1936
+ " models: 'server/models',",
1937
+ " migrations: 'server/db/migrations',",
1938
+ " seeders: 'server/db/seeders',",
1939
+ " commands: 'server/commands',",
1940
+ " jobs: 'server/jobs',",
1941
+ " events: 'server/events',",
1942
+ " listeners: 'server/listeners',",
1943
+ " generatedSchema: '.holo-js/generated/schema.generated.ts',",
1944
+ " },",
1945
+ "})",
1946
+ ""
1947
+ ].join("\n");
1948
+ }
1949
+ function renderScaffoldDatabaseConfig(options) {
1950
+ const packageName = sanitizePackageName(options.projectName) || "holo-app";
1951
+ if (options.databaseDriver === "sqlite") {
1952
+ return [
1953
+ "import { defineDatabaseConfig, env } from '@holo-js/config'",
1954
+ "",
1955
+ "export default defineDatabaseConfig({",
1956
+ " defaultConnection: 'main',",
1957
+ " connections: {",
1958
+ " main: {",
1959
+ " driver: 'sqlite',",
1960
+ " url: env('DB_URL', './storage/database.sqlite'),",
1961
+ " },",
1962
+ " },",
1963
+ "})",
1964
+ ""
1965
+ ].join("\n");
1966
+ }
1967
+ const port = options.databaseDriver === "mysql" ? "3306" : "5432";
1968
+ const username = options.databaseDriver === "mysql" ? "root" : "postgres";
1969
+ const schemaLine = options.databaseDriver === "postgres" ? " schema: env('DB_SCHEMA', 'public')," : void 0;
1970
+ return [
1971
+ "import { defineDatabaseConfig, env } from '@holo-js/config'",
1972
+ "",
1973
+ "export default defineDatabaseConfig({",
1974
+ " defaultConnection: 'main',",
1975
+ " connections: {",
1976
+ " main: {",
1977
+ ` driver: '${options.databaseDriver}',`,
1978
+ " host: env('DB_HOST', '127.0.0.1'),",
1979
+ ` port: env('DB_PORT', '${port}'),`,
1980
+ ` username: env('DB_USERNAME', '${username}'),`,
1981
+ " password: env('DB_PASSWORD'),",
1982
+ ` database: env('DB_DATABASE', '${packageName}'),`,
1983
+ ...schemaLine ? [schemaLine] : [],
1984
+ " },",
1985
+ " },",
1986
+ "})",
1987
+ ""
1988
+ ].join("\n");
1989
+ }
1990
+ function resolveDefaultDatabaseUrl(driver) {
1991
+ if (driver === "sqlite") {
1992
+ return "./storage/database.sqlite";
1993
+ }
1994
+ return void 0;
1995
+ }
1996
+ function renderScaffoldEnvFiles(options) {
1997
+ const defaultDatabaseConnection = "main";
1998
+ const defaultUrl = resolveFrameworkDefaultUrl(options.framework);
1999
+ const optionalPackageNames = normalizeScaffoldOptionalPackages(options.optionalPackages);
2000
+ const baseLines = [
2001
+ "APP_NAME=",
2002
+ "APP_KEY=",
2003
+ `APP_URL=${defaultUrl}`,
2004
+ "APP_ENV=development",
2005
+ "APP_DEBUG=true",
2006
+ `DB_DRIVER=${options.databaseDriver}`
2007
+ ];
2008
+ const driverLines = options.databaseDriver === "sqlite" ? [
2009
+ `DB_URL=${resolveDefaultDatabaseUrl(options.databaseDriver)}`
2010
+ ] : [
2011
+ "DB_HOST=127.0.0.1",
2012
+ `DB_PORT=${options.databaseDriver === "mysql" ? "3306" : "5432"}`,
2013
+ `DB_USERNAME=${options.databaseDriver === "mysql" ? "root" : "postgres"}`,
2014
+ "DB_PASSWORD=",
2015
+ `DB_DATABASE=${sanitizePackageName(options.projectName) || "holo_app"}`,
2016
+ ...options.databaseDriver === "postgres" ? ["DB_SCHEMA=public"] : []
2017
+ ];
2018
+ const storageLines = optionalPackageNames.includes("storage") ? [
2019
+ `STORAGE_DEFAULT_DISK=${options.storageDefaultDisk}`,
2020
+ "STORAGE_ROUTE_PREFIX=/storage"
2021
+ ] : [];
2022
+ const authLines = optionalPackageNames.includes("auth") ? [...renderAuthEnvFiles({}, defaultDatabaseConnection).env] : [];
2023
+ const cacheLines = optionalPackageNames.includes("cache") ? [...renderCacheEnvFiles("file").env] : [];
2024
+ const mailLines = optionalPackageNames.includes("mail") ? [...renderMailEnvFiles().env] : [];
2025
+ const mailExampleLines = optionalPackageNames.includes("mail") ? [...renderMailEnvFiles().example] : [];
2026
+ const envGroups = [
2027
+ baseLines,
2028
+ driverLines,
2029
+ storageLines,
2030
+ authLines,
2031
+ cacheLines,
2032
+ mailLines
2033
+ ];
2034
+ const exampleGroups = [
2035
+ baseLines.map(renderEnvExampleLine),
2036
+ driverLines.map(renderEnvExampleLine),
2037
+ storageLines.map(renderEnvExampleLine),
2038
+ authLines.map(renderEnvExampleLine),
2039
+ cacheLines.map(renderEnvExampleLine),
2040
+ mailExampleLines.map(renderEnvExampleLine)
2041
+ ];
2042
+ const env = renderEnvGroups(envGroups);
2043
+ const example = [
2044
+ "# Copy this file to .env and fill in your local values.",
2045
+ "# Supported layered env files: .env.local, .env.development, .env.production, .env.prod, .env.test",
2046
+ renderEnvGroups(exampleGroups).trimEnd(),
2047
+ ""
2048
+ ].join("\n");
2049
+ return { env, example };
2050
+ }
2051
+ function renderEnvGroups(groups) {
2052
+ return `${groups.filter((group) => group.length > 0).map((group) => group.join("\n")).join("\n\n")}
2053
+ `;
2054
+ }
2055
+ function renderEnvExampleLine(line) {
2056
+ const [key] = line.split("=");
2057
+ return `${key ?? line}=`;
2058
+ }
2059
+ function renderMailEnvFiles() {
2060
+ return {
2061
+ env: [
2062
+ "MAIL_MAILER=preview",
2063
+ "MAIL_FROM_ADDRESS=hello@app.test",
2064
+ "MAIL_FROM_NAME=Holo App",
2065
+ "MAIL_LOG_BODIES=false",
2066
+ "MAIL_HOST=127.0.0.1",
2067
+ "MAIL_PORT=1025",
2068
+ "MAIL_SECURE=false",
2069
+ "MAIL_USERNAME=",
2070
+ "MAIL_PASSWORD="
2071
+ ],
2072
+ example: [
2073
+ "MAIL_MAILER=",
2074
+ "MAIL_FROM_ADDRESS=",
2075
+ "MAIL_FROM_NAME=",
2076
+ "MAIL_LOG_BODIES=",
2077
+ "MAIL_HOST=",
2078
+ "MAIL_PORT=",
2079
+ "MAIL_SECURE=",
2080
+ "MAIL_USERNAME=",
2081
+ "MAIL_PASSWORD="
2082
+ ]
2083
+ };
2084
+ }
2085
+ function renderRedisConnectionEnvFiles() {
2086
+ return {
2087
+ env: [
2088
+ "REDIS_URL=",
2089
+ "REDIS_HOST=127.0.0.1",
2090
+ "REDIS_PORT=6379",
2091
+ "REDIS_USERNAME=",
2092
+ "REDIS_PASSWORD=",
2093
+ "REDIS_DB=0"
2094
+ ],
2095
+ example: [
2096
+ "REDIS_URL=",
2097
+ "REDIS_HOST=",
2098
+ "REDIS_PORT=",
2099
+ "REDIS_USERNAME=",
2100
+ "REDIS_PASSWORD=",
2101
+ "REDIS_DB="
2102
+ ]
2103
+ };
2104
+ }
2105
+ function renderQueueEnvFiles(driver) {
2106
+ if (driver !== "redis") {
2107
+ return {
2108
+ env: [],
2109
+ example: []
2110
+ };
2111
+ }
2112
+ return renderRedisConnectionEnvFiles();
2113
+ }
2114
+ function renderCacheEnvFiles(driver) {
2115
+ if (driver === "redis") {
2116
+ const redis = renderRedisConnectionEnvFiles();
2117
+ return {
2118
+ env: [
2119
+ "CACHE_PREFIX=",
2120
+ ...redis.env
2121
+ ],
2122
+ example: [
2123
+ "CACHE_PREFIX=",
2124
+ ...redis.example
2125
+ ]
2126
+ };
2127
+ }
2128
+ return {
2129
+ env: [
2130
+ "CACHE_PREFIX="
2131
+ ],
2132
+ example: [
2133
+ "CACHE_PREFIX="
2134
+ ]
2135
+ };
2136
+ }
2137
+ function parseEnvKey(line) {
2138
+ const trimmed = line.trim();
2139
+ if (!trimmed || trimmed.startsWith("#")) {
2140
+ return void 0;
2141
+ }
2142
+ const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
2143
+ const separatorIndex = normalized.indexOf("=");
2144
+ if (separatorIndex <= 0) {
2145
+ return void 0;
2146
+ }
2147
+ return normalized.slice(0, separatorIndex).trim();
2148
+ }
2149
+ function renderEnvFileContents(segments) {
2150
+ const normalized = segments.map((segment) => segment.replace(/\n+$/, "")).filter((segment) => segment.length > 0);
2151
+ return normalized.length > 0 ? `${normalized.join("\n")}
2152
+ ` : "";
2153
+ }
2154
+ function normalizeScaffoldEnvSegments(segments) {
2155
+ return segments.split("\n").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
2156
+ }
2157
+ function upsertEnvContents(existingContents, additions) {
2158
+ if (additions.length === 0) {
2159
+ return {
2160
+ contents: existingContents,
2161
+ changed: false
2162
+ };
2163
+ }
2164
+ const nextLines = existingContents ? existingContents.replace(/\r\n/g, "\n").split("\n") : [];
2165
+ const existingKeys = new Set(nextLines.map(parseEnvKey).filter((value) => typeof value === "string"));
2166
+ const additionKeys = /* @__PURE__ */ new Set();
2167
+ const missingLines = additions.flatMap((line) => {
2168
+ const normalizedLine = line.trim();
2169
+ if (normalizedLine.length === 0 || normalizedLine.startsWith("#")) {
2170
+ return [];
2171
+ }
2172
+ const key = parseEnvKey(normalizedLine);
2173
+ if (!key || additionKeys.has(key) || existingKeys.has(key)) {
2174
+ return [];
2175
+ }
2176
+ additionKeys.add(key);
2177
+ return [normalizedLine];
2178
+ });
2179
+ if (missingLines.length === 0) {
2180
+ return {
2181
+ contents: existingContents,
2182
+ changed: false
2183
+ };
2184
+ }
2185
+ if (nextLines.length > 0 && nextLines[nextLines.length - 1]?.trim() !== "") {
2186
+ nextLines.push("");
2187
+ }
2188
+ nextLines.push(...missingLines);
2189
+ return {
2190
+ contents: `${nextLines.join("\n").replace(/\n*$/, "")}
2191
+ `,
2192
+ changed: true
2193
+ };
2194
+ }
2195
+
2196
+ // src/project/scaffold/workspace-renderers.ts
2197
+ function renderScaffoldGitignore() {
2198
+ return [
2199
+ "# Dependencies",
2200
+ "node_modules",
2201
+ "",
2202
+ "# Environment",
2203
+ ".env",
2204
+ ".env.local",
2205
+ ".env.development",
2206
+ ".env.production",
2207
+ ".env.prod",
2208
+ ".env.test",
2209
+ ".env.*.local",
2210
+ "",
2211
+ "# Holo",
2212
+ ".holo-js/generated",
2213
+ ".holo-js/runtime",
2214
+ ".holo-cli",
2215
+ "",
2216
+ "# Nuxt",
2217
+ ".nuxt",
2218
+ ".output",
2219
+ ".nitro",
2220
+ ".data",
2221
+ ".netlify",
2222
+ ".vercel",
2223
+ "",
2224
+ "# Next.js",
2225
+ ".next",
2226
+ "out",
2227
+ "next-env.d.ts",
2228
+ "",
2229
+ "# SvelteKit",
2230
+ ".svelte-kit",
2231
+ "build",
2232
+ "",
2233
+ "# Build / Misc",
2234
+ "dist",
2235
+ "coverage",
2236
+ "*.log",
2237
+ "*.tsbuildinfo",
2238
+ "",
2239
+ "# Database",
2240
+ "*.db",
2241
+ "*.sqlite",
2242
+ "*.sqlite3",
2243
+ "*.sqlite-wal",
2244
+ "*.sqlite-shm",
2245
+ "",
2246
+ "# Storage",
2247
+ "storage",
2248
+ "",
2249
+ "# OS",
2250
+ ".DS_Store",
2251
+ "Thumbs.db",
2252
+ ""
2253
+ ].join("\n");
2254
+ }
2255
+ function renderScaffoldTsconfig(options) {
2256
+ if (options.framework === "nuxt") {
2257
+ return `${JSON.stringify({
2258
+ extends: "./.nuxt/tsconfig.json"
2259
+ }, null, 2)}
2260
+ `;
2261
+ }
2262
+ if (options.framework === "sveltekit") {
2263
+ return `${JSON.stringify({
2264
+ extends: "./.svelte-kit/tsconfig.json",
2265
+ compilerOptions: {
2266
+ strict: true,
2267
+ noEmit: true,
2268
+ skipLibCheck: true
2269
+ },
2270
+ include: [
2271
+ "src/**/*.ts",
2272
+ "src/**/*.svelte",
2273
+ "server/**/*.ts",
2274
+ "config/**/*.ts",
2275
+ ".holo-js/generated/**/*.ts",
2276
+ ".holo-js/generated/**/*.d.ts",
2277
+ "vite.config.ts"
2278
+ ]
2279
+ }, null, 2)}
2280
+ `;
2281
+ }
2282
+ const include = ["next-env.d.ts", "app/**/*.ts", "app/**/*.tsx", "server/**/*.ts", "config/**/*.ts", ".holo-js/generated/**/*.ts", ".holo-js/generated/**/*.d.ts"];
2283
+ return `${JSON.stringify({
2284
+ compilerOptions: {
2285
+ target: "ES2022",
2286
+ module: "ESNext",
2287
+ moduleResolution: "Bundler",
2288
+ strict: true,
2289
+ noEmit: true,
2290
+ skipLibCheck: true,
2291
+ baseUrl: ".",
2292
+ jsx: "preserve",
2293
+ paths: {
2294
+ "~/*": ["./*"],
2295
+ "@/*": ["./*"]
2296
+ }
2297
+ },
2298
+ include
2299
+ }, null, 2)}
2300
+ `;
2301
+ }
2302
+ function renderVSCodeSettings(options) {
2303
+ if (options.framework !== "nuxt" && options.framework !== "sveltekit") {
2304
+ return void 0;
2305
+ }
2306
+ const settings = {
2307
+ "typescript.tsdk": "node_modules/typescript/lib",
2308
+ "typescript.enablePromptUseWorkspaceTsdk": true
2309
+ };
2310
+ if (options.framework === "nuxt") {
2311
+ settings["vue.server.hybridMode"] = true;
2312
+ }
2313
+ return `${JSON.stringify(settings, null, 2)}
2314
+ `;
2315
+ }
2316
+
2317
+ // src/project/scaffold/framework.ts
2318
+ function resolvePackageManagerVersion(value) {
2319
+ return SCAFFOLD_PACKAGE_MANAGER_VERSIONS[value];
2320
+ }
2321
+ function renderScaffoldPackageJson(options) {
2322
+ const packageName = sanitizePackageName(options.projectName) || "holo-app";
2323
+ const optionalPackages = normalizeScaffoldOptionalPackages(options.optionalPackages);
2324
+ const dependencies = {
2325
+ "@holo-js/cli": `^${HOLO_PACKAGE_VERSION}`,
2326
+ "@holo-js/config": `^${HOLO_PACKAGE_VERSION}`,
2327
+ "@holo-js/core": `^${HOLO_PACKAGE_VERSION}`,
2328
+ "@holo-js/db": `^${HOLO_PACKAGE_VERSION}`,
2329
+ [DB_DRIVER_PACKAGE_NAMES[options.databaseDriver]]: `^${HOLO_PACKAGE_VERSION}`,
2330
+ esbuild: ESBUILD_PACKAGE_VERSION
2331
+ };
2332
+ const devDependencies = {
2333
+ typescript: SCAFFOLD_BASE_DEV_DEPENDENCY_VERSIONS.typescript,
2334
+ "@types/node": SCAFFOLD_BASE_DEV_DEPENDENCY_VERSIONS["@types/node"],
2335
+ eslint: SCAFFOLD_BASE_DEV_DEPENDENCY_VERSIONS.eslint
2336
+ };
2337
+ if (options.framework === "nuxt") {
2338
+ dependencies.nuxt = SCAFFOLD_FRAMEWORK_VERSIONS.nuxt;
2339
+ dependencies.vue = SCAFFOLD_NUXT_DEPENDENCY_VERSIONS.vue;
2340
+ dependencies["vue-router"] = SCAFFOLD_NUXT_DEPENDENCY_VERSIONS["vue-router"];
2341
+ dependencies["@holo-js/adapter-nuxt"] = SCAFFOLD_FRAMEWORK_ADAPTER_VERSIONS.nuxt;
2342
+ devDependencies["vue-tsc"] = SCAFFOLD_NUXT_DEPENDENCY_VERSIONS["vue-tsc"];
2343
+ }
2344
+ if (options.framework === "next") {
2345
+ dependencies.next = SCAFFOLD_FRAMEWORK_VERSIONS.next;
2346
+ dependencies.react = SCAFFOLD_NEXT_REACT_VERSIONS.react;
2347
+ dependencies["react-dom"] = SCAFFOLD_NEXT_REACT_VERSIONS["react-dom"];
2348
+ dependencies["@holo-js/adapter-next"] = SCAFFOLD_FRAMEWORK_ADAPTER_VERSIONS.next;
2349
+ devDependencies["@types/react"] = SCAFFOLD_NEXT_REACT_VERSIONS["@types/react"];
2350
+ devDependencies["@types/react-dom"] = SCAFFOLD_NEXT_REACT_VERSIONS["@types/react-dom"];
2351
+ }
2352
+ if (options.framework === "sveltekit") {
2353
+ dependencies["@holo-js/adapter-sveltekit"] = SCAFFOLD_FRAMEWORK_ADAPTER_VERSIONS.sveltekit;
2354
+ dependencies["@sveltejs/adapter-node"] = SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS["@sveltejs/adapter-node"];
2355
+ dependencies["@sveltejs/kit"] = SCAFFOLD_FRAMEWORK_VERSIONS.sveltekit;
2356
+ dependencies["@sveltejs/vite-plugin-svelte"] = SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS["@sveltejs/vite-plugin-svelte"];
2357
+ dependencies.svelte = SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS.svelte;
2358
+ dependencies.vite = SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS.vite;
2359
+ devDependencies["svelte-check"] = SCAFFOLD_SVELTEKIT_DEPENDENCY_VERSIONS["svelte-check"];
2360
+ }
2361
+ if (optionalPackages.includes("storage")) {
2362
+ dependencies["@holo-js/storage"] = SCAFFOLD_FRAMEWORK_RUNTIME_VERSIONS[options.framework]["@holo-js/storage"];
2363
+ }
2364
+ if (optionalPackages.includes("events")) {
2365
+ dependencies["@holo-js/events"] = `^${HOLO_PACKAGE_VERSION}`;
2366
+ dependencies["@holo-js/queue"] = `^${HOLO_PACKAGE_VERSION}`;
2367
+ }
2368
+ if (optionalPackages.includes("queue")) {
2369
+ dependencies["@holo-js/queue"] = `^${HOLO_PACKAGE_VERSION}`;
2370
+ }
2371
+ if (optionalPackages.includes("validation")) {
2372
+ dependencies["@holo-js/validation"] = `^${HOLO_PACKAGE_VERSION}`;
2373
+ }
2374
+ if (optionalPackages.includes("forms")) {
2375
+ dependencies["@holo-js/forms"] = `^${HOLO_PACKAGE_VERSION}`;
2376
+ }
2377
+ if (optionalPackages.includes("auth")) {
2378
+ dependencies["@holo-js/auth"] = `^${HOLO_PACKAGE_VERSION}`;
2379
+ dependencies["@holo-js/session"] = `^${HOLO_PACKAGE_VERSION}`;
2380
+ dependencies["@holo-js/security"] = `^${HOLO_PACKAGE_VERSION}`;
2381
+ }
2382
+ if (optionalPackages.includes("authorization")) {
2383
+ dependencies["@holo-js/authorization"] = `^${HOLO_PACKAGE_VERSION}`;
2384
+ }
2385
+ if (optionalPackages.includes("notifications")) {
2386
+ dependencies["@holo-js/notifications"] = `^${HOLO_PACKAGE_VERSION}`;
2387
+ }
2388
+ if (optionalPackages.includes("mail")) {
2389
+ dependencies["@holo-js/mail"] = `^${HOLO_PACKAGE_VERSION}`;
2390
+ }
2391
+ if (optionalPackages.includes("broadcast") || optionalPackages.includes("realtime")) {
2392
+ dependencies["@holo-js/broadcast"] = `^${HOLO_PACKAGE_VERSION}`;
2393
+ dependencies["@holo-js/flux"] = `^${HOLO_PACKAGE_VERSION}`;
2394
+ if (options.framework === "next") {
2395
+ dependencies["@holo-js/flux-react"] = `^${HOLO_PACKAGE_VERSION}`;
2396
+ } else if (options.framework === "nuxt") {
2397
+ dependencies["@holo-js/flux-vue"] = `^${HOLO_PACKAGE_VERSION}`;
2398
+ } else if (options.framework === "sveltekit") {
2399
+ dependencies["@holo-js/flux-svelte"] = `^${HOLO_PACKAGE_VERSION}`;
2400
+ }
2401
+ }
2402
+ if (optionalPackages.includes("realtime")) {
2403
+ dependencies["@holo-js/realtime"] = `^${HOLO_PACKAGE_VERSION}`;
2404
+ }
2405
+ if (optionalPackages.includes("security")) {
2406
+ dependencies["@holo-js/security"] = `^${HOLO_PACKAGE_VERSION}`;
2407
+ }
2408
+ if (optionalPackages.includes("cache")) {
2409
+ dependencies["@holo-js/cache"] = `^${HOLO_PACKAGE_VERSION}`;
2410
+ }
2411
+ return `${JSON.stringify({
2412
+ name: packageName,
2413
+ private: true,
2414
+ type: "module",
2415
+ packageManager: resolvePackageManagerVersion(options.packageManager),
2416
+ scripts: {
2417
+ ...options.framework === "nuxt" ? { postinstall: "nuxt prepare" } : {},
2418
+ prepare: "holo key:generate && holo prepare",
2419
+ dev: "holo dev",
2420
+ build: "holo build",
2421
+ start: "holo start",
2422
+ lint: options.framework === "nuxt" ? "eslint app config server shared tests *.d.ts --fix --no-warn-ignored --no-error-on-unmatched-pattern" : options.framework === "next" ? "eslint app config server tests --fix --no-warn-ignored --no-error-on-unmatched-pattern" : "eslint src config server tests --fix --no-warn-ignored --no-error-on-unmatched-pattern",
2423
+ typecheck: options.framework === "nuxt" ? "nuxt typecheck" : options.framework === "next" ? "tsc -p tsconfig.json --noEmit" : "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
2424
+ ["config:cache"]: "holo config:cache",
2425
+ ["config:clear"]: "holo config:clear",
2426
+ ["holo:dev"]: "node ./.holo-js/framework/run.mjs dev",
2427
+ ["holo:build"]: "node ./.holo-js/framework/run.mjs build",
2428
+ ["holo:start"]: "node ./.holo-js/framework/run.mjs start"
2429
+ },
2430
+ dependencies,
2431
+ devDependencies
2432
+ }, null, 2)}
2433
+ `;
2434
+ }
2435
+ function appendScaffoldEnvGroup(contents, group) {
2436
+ const normalizedGroup = group?.map((line) => line.trim()).filter((line) => line.length > 0) ?? [];
2437
+ if (normalizedGroup.length === 0) {
2438
+ return contents;
2439
+ }
2440
+ const normalizedContents = contents.trimEnd();
2441
+ if (normalizedContents.length === 0) {
2442
+ return `${normalizedGroup.join("\n")}
2443
+ `;
2444
+ }
2445
+ return `${normalizedContents}
2446
+
2447
+ ${normalizedGroup.join("\n")}
2448
+ `;
2449
+ }
2450
+ async function scaffoldProject(projectRoot, options) {
2451
+ const existingEntries = await readdir(projectRoot).catch(() => []);
2452
+ if (existingEntries.length > 0) {
2453
+ throw new Error(`Refusing to scaffold into a non-empty directory: ${projectRoot}`);
2454
+ }
2455
+ const { env, example } = renderScaffoldEnvFiles(options);
2456
+ const config = normalizeHoloProjectConfig();
2457
+ const generatedSchemaPath = resolveGeneratedSchemaPath(projectRoot, config);
2458
+ const optionalPackages = normalizeScaffoldOptionalPackages(options.optionalPackages);
2459
+ const storageEnabled = optionalPackages.includes("storage");
2460
+ const queueEnabled = optionalPackages.includes("queue");
2461
+ const eventsEnabled = optionalPackages.includes("events");
2462
+ const authEnabled = optionalPackages.includes("auth");
2463
+ const authorizationEnabled = optionalPackages.includes("authorization");
2464
+ const notificationsEnabled = optionalPackages.includes("notifications");
2465
+ const mailEnabled = optionalPackages.includes("mail");
2466
+ const broadcastEnabled = optionalPackages.includes("broadcast");
2467
+ const realtimeEnabled = optionalPackages.includes("realtime");
2468
+ const securityEnabled = optionalPackages.includes("security");
2469
+ const cacheEnabled = optionalPackages.includes("cache");
2470
+ const broadcastEnvFiles = broadcastEnabled ? renderBroadcastEnvFiles() : void 0;
2471
+ const scaffoldEnv = appendScaffoldEnvGroup(env, broadcastEnvFiles?.env);
2472
+ const scaffoldEnvExample = appendScaffoldEnvGroup(example, broadcastEnvFiles?.example);
2473
+ await mkdir2(projectRoot, { recursive: true });
2474
+ await mkdir2(resolve3(projectRoot, "config"), { recursive: true });
2475
+ await mkdir2(resolve3(projectRoot, ".holo-js", "framework"), { recursive: true });
2476
+ await mkdir2(resolve3(projectRoot, config.paths.models), { recursive: true });
2477
+ await mkdir2(resolve3(projectRoot, config.paths.commands), { recursive: true });
2478
+ if (queueEnabled) {
2479
+ await mkdir2(resolve3(projectRoot, config.paths.jobs), { recursive: true });
2480
+ }
2481
+ if (eventsEnabled) {
2482
+ await mkdir2(resolve3(projectRoot, config.paths.events), { recursive: true });
2483
+ await mkdir2(resolve3(projectRoot, config.paths.listeners), { recursive: true });
2484
+ }
2485
+ if (authorizationEnabled) {
2486
+ await mkdir2(resolve3(projectRoot, "server/policies"), { recursive: true });
2487
+ await mkdir2(resolve3(projectRoot, "server/abilities"), { recursive: true });
2488
+ }
2489
+ if (mailEnabled) {
2490
+ await mkdir2(resolve3(projectRoot, "server/mail"), { recursive: true });
2491
+ }
2492
+ if (broadcastEnabled) {
2493
+ await mkdir2(resolve3(projectRoot, "server/broadcast"), { recursive: true });
2494
+ await mkdir2(resolve3(projectRoot, "server/channels"), { recursive: true });
2495
+ }
2496
+ if (realtimeEnabled) {
2497
+ await mkdir2(resolve3(projectRoot, "server/realtime"), { recursive: true });
2498
+ }
2499
+ await mkdir2(resolve3(projectRoot, "server/db/factories"), { recursive: true });
2500
+ await mkdir2(resolve3(projectRoot, "server/db/migrations"), { recursive: true });
2501
+ await mkdir2(resolve3(projectRoot, "server/db/seeders"), { recursive: true });
2502
+ await mkdir2(resolve3(projectRoot, "server/db/schema"), { recursive: true });
2503
+ await mkdir2(resolve3(projectRoot, config.paths.observers), { recursive: true });
2504
+ await mkdir2(resolve3(projectRoot, "storage"), { recursive: true });
2505
+ if (storageEnabled) {
2506
+ await mkdir2(resolve3(projectRoot, "storage/app/public"), { recursive: true });
2507
+ }
2508
+ await writeFile(resolve3(projectRoot, "package.json"), renderScaffoldPackageJson(options), "utf8");
2509
+ await writeFile(resolve3(projectRoot, ".gitignore"), renderScaffoldGitignore(), "utf8");
2510
+ await writeFile(resolve3(projectRoot, ".env"), scaffoldEnv, "utf8");
2511
+ await writeFile(resolve3(projectRoot, ".env.example"), scaffoldEnvExample, "utf8");
2512
+ await writeFile(resolve3(projectRoot, "config/app.ts"), renderScaffoldAppConfig(options.projectName, options.framework), "utf8");
2513
+ await writeFile(resolve3(projectRoot, "config/database.ts"), renderScaffoldDatabaseConfig(options), "utf8");
2514
+ await writeFile(resolve3(projectRoot, "config/redis.ts"), renderRedisConfig(), "utf8");
2515
+ if (queueEnabled) {
2516
+ await writeFile(resolve3(projectRoot, "config/queue.ts"), renderQueueConfig({
2517
+ driver: "sync",
2518
+ defaultDatabaseConnection: "main"
2519
+ }), "utf8");
2520
+ }
2521
+ if (notificationsEnabled) {
2522
+ await writeFile(resolve3(projectRoot, "config/notifications.ts"), renderNotificationsConfig(), "utf8");
2523
+ for (const migrationFile of createNotificationsMigrationFiles()) {
2524
+ await writeFile(resolve3(projectRoot, config.paths.migrations, migrationFile.path), migrationFile.contents, "utf8");
2525
+ }
2526
+ }
2527
+ if (mailEnabled) {
2528
+ await writeFile(resolve3(projectRoot, "config/mail.ts"), renderMailConfig(), "utf8");
2529
+ }
2530
+ if (broadcastEnabled) {
2531
+ await writeFile(resolve3(projectRoot, "config/broadcast.ts"), renderBroadcastConfig("esm", false, true), "utf8");
2532
+ }
2533
+ if (securityEnabled) {
2534
+ await writeFile(resolve3(projectRoot, "config/security.ts"), renderSecurityConfig(), "utf8");
2535
+ await writeFile(resolve3(projectRoot, "config/cors.ts"), renderCorsConfig(), "utf8");
2536
+ await ensureRateLimitStorageIgnore(projectRoot);
2537
+ }
2538
+ if (cacheEnabled) {
2539
+ await writeFile(resolve3(projectRoot, "config/cache.ts"), renderCacheConfig("file", "main"), "utf8");
2540
+ }
2541
+ if (authEnabled) {
2542
+ await writeFile(resolve3(projectRoot, "config/auth.ts"), renderAuthConfig(), "utf8");
2543
+ await writeFile(resolve3(projectRoot, "config/session.ts"), renderSessionConfig("main"), "utf8");
2544
+ if (!securityEnabled) {
2545
+ await writeFile(resolve3(projectRoot, "config/security.ts"), renderSecurityConfig(), "utf8");
2546
+ await writeFile(resolve3(projectRoot, "config/cors.ts"), renderCorsConfig(), "utf8");
2547
+ await ensureRateLimitStorageIgnore(projectRoot);
2548
+ }
2549
+ const userModelPath = resolve3(projectRoot, config.paths.models, "User.ts");
2550
+ await writeFile(
2551
+ userModelPath,
2552
+ renderAuthUserModel(resolveAuthUserModelSchemaImportPath(
2553
+ userModelPath,
2554
+ generatedSchemaPath
2555
+ )),
2556
+ "utf8"
2557
+ );
2558
+ for (const migrationFile of createAuthMigrationFiles()) {
2559
+ await writeFile(resolve3(projectRoot, config.paths.migrations, migrationFile.path), migrationFile.contents, "utf8");
2560
+ }
2561
+ }
2562
+ if (authEnabled && notificationsEnabled) {
2563
+ await mkdir2(resolve3(projectRoot, "server/notifications/auth"), { recursive: true });
2564
+ await writeFile(
2565
+ resolve3(projectRoot, "server/notifications/auth/email-verification.ts"),
2566
+ renderAuthEmailVerificationNotification(),
2567
+ "utf8"
2568
+ );
2569
+ await writeFile(
2570
+ resolve3(projectRoot, "server/notifications/auth/password-reset.ts"),
2571
+ renderAuthPasswordResetNotification(),
2572
+ "utf8"
2573
+ );
2574
+ }
2575
+ if (broadcastEnabled && authEnabled) {
2576
+ await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
2577
+ }
2578
+ if (authorizationEnabled) {
2579
+ await writeFile(resolve3(projectRoot, "server/policies/README.md"), renderAuthorizationPoliciesReadme(), "utf8");
2580
+ await writeFile(resolve3(projectRoot, "server/abilities/README.md"), renderAuthorizationAbilitiesReadme(), "utf8");
2581
+ }
2582
+ if (storageEnabled) {
2583
+ await writeFile(resolve3(projectRoot, "config/storage.ts"), renderStorageConfig(), "utf8");
2584
+ }
2585
+ await writeFile(resolve3(projectRoot, ".holo-js/framework/run.mjs"), renderFrameworkRunner(options), "utf8");
2586
+ await writeFile(resolve3(projectRoot, ".holo-js/framework/project.json"), `${JSON.stringify(options, null, 2)}
2587
+ `, "utf8");
2588
+ await writeFile(resolve3(projectRoot, "tsconfig.json"), renderScaffoldTsconfig(options), "utf8");
2589
+ const vscodeSettings = renderVSCodeSettings(options);
2590
+ if (vscodeSettings) {
2591
+ await mkdir2(resolve3(projectRoot, ".vscode"), { recursive: true });
2592
+ await writeFile(resolve3(projectRoot, ".vscode/settings.json"), vscodeSettings, "utf8");
2593
+ }
2594
+ await mkdir2(dirname(generatedSchemaPath), { recursive: true });
2595
+ await writeFile(generatedSchemaPath, renderGeneratedSchemaPlaceholder(), "utf8");
2596
+ await writeFile(resolve3(projectRoot, GENERATED_MODEL_TYPES_PATH), renderGeneratedModelTypes([]), "utf8");
2597
+ for (const file of renderFrameworkFiles(options)) {
2598
+ await writeTextFile(resolve3(projectRoot, file.path), file.contents);
2599
+ }
2600
+ if (options.databaseDriver === "sqlite") {
2601
+ await writeFile(resolve3(projectRoot, "storage/database.sqlite"), "", "utf8");
2602
+ }
2603
+ }
2604
+
2605
+ // src/project/scaffold.ts
2606
+ async function resolveExistingModelPath(modelsRoot, modelName) {
2607
+ const supportedExtensions = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"];
2608
+ for (const extension of supportedExtensions) {
2609
+ const candidate = resolve4(modelsRoot, `${modelName}${extension}`);
2610
+ if (await pathExists(candidate)) {
2611
+ return candidate;
2612
+ }
2613
+ }
2614
+ return void 0;
2615
+ }
2616
+ function injectSvelteRealtimeVitePlugin(viteConfigContents) {
2617
+ if (/\bholoSvelteKitRealtime\b/.test(viteConfigContents)) {
2618
+ return void 0;
2619
+ }
2620
+ const adapterImportPattern = /import\s*\{([^}]*)\}\s*from\s*(['"])@holo-js\/adapter-sveltekit\/vite\2/;
2621
+ const svelteKitImportPattern = /(import\s*\{[^}]*\bsveltekit\b[^}]*\}\s*from\s*(['"])@sveltejs\/kit\/vite\2)/;
2622
+ const existingAdapterImport = adapterImportPattern.exec(viteConfigContents);
2623
+ let withImport = viteConfigContents;
2624
+ if (existingAdapterImport) {
2625
+ const imports = existingAdapterImport[1]?.split(",").map((value) => value.trim()).filter(Boolean) ?? [];
2626
+ withImport = viteConfigContents.replace(adapterImportPattern, `import { ${["holoSvelteKitRealtime", ...imports].join(", ")} } from ${existingAdapterImport[2]}@holo-js/adapter-sveltekit/vite${existingAdapterImport[2]}`);
2627
+ } else {
2628
+ withImport = viteConfigContents.replace(
2629
+ svelteKitImportPattern,
2630
+ [
2631
+ "$1",
2632
+ "import { holoSvelteKitRealtime } from '@holo-js/adapter-sveltekit/vite'"
2633
+ ].join("\n")
2634
+ );
2635
+ }
2636
+ if (withImport === viteConfigContents && !existingAdapterImport) {
2637
+ return {
2638
+ error: "Unable to add holoSvelteKitRealtime() to vite.config.ts because the SvelteKit Vite import could not be found."
2639
+ };
2640
+ }
2641
+ const withPlugin = withImport.replace(/plugins\s*:\s*\[([\s\S]*?)\]/, (_match, plugins) => {
2642
+ const separator = plugins.trim() ? " " : "";
2643
+ return `plugins: [holoSvelteKitRealtime(),${separator}${plugins}]`;
2644
+ });
2645
+ if (withPlugin === withImport) {
2646
+ if (/\bplugins\s*:\s*(?!\[)|\bplugins\s*(?:,|})/.test(withImport)) {
2647
+ return {
2648
+ error: "Unable to add holoSvelteKitRealtime() to vite.config.ts because the plugins option is not an inline array."
2649
+ };
2650
+ }
2651
+ return {
2652
+ error: "Unable to add holoSvelteKitRealtime() to vite.config.ts because no plugins array was found."
2653
+ };
2654
+ }
2655
+ return withPlugin;
2656
+ }
2657
+ var realtimeDefinitionExtensions = /* @__PURE__ */ new Set([".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]);
2658
+ async function collectRealtimeDefinitionFiles(root) {
2659
+ const entries = await readdir2(root, { withFileTypes: true }).catch(() => []);
2660
+ const files = await Promise.all(entries.map(async (entry) => {
2661
+ const entryPath = resolve4(root, entry.name);
2662
+ if (entry.isDirectory()) {
2663
+ return await collectRealtimeDefinitionFiles(entryPath);
2664
+ }
2665
+ return realtimeDefinitionExtensions.has(extname2(entry.name)) ? [entryPath] : [];
2666
+ }));
2667
+ return files.flat().sort((left, right) => left.localeCompare(right));
2668
+ }
2669
+ async function renderNextRealtimeDefinitions(projectRoot) {
2670
+ const generatedRoot = resolve4(projectRoot, ".holo-js/generated/next");
2671
+ const files = await collectRealtimeDefinitionFiles(resolve4(projectRoot, "server/realtime"));
2672
+ const importPaths = files.map((filePath) => {
2673
+ const withoutExtension = filePath.slice(0, -extname2(filePath).length);
2674
+ const importPath = relative(generatedRoot, withoutExtension).split(sep).join("/");
2675
+ return importPath.startsWith(".") ? importPath : `./${importPath}`;
2676
+ });
2677
+ return renderNextGeneratedRealtimeDefinitions(importPaths);
2678
+ }
2679
+ async function resolveExistingAuthMigrationFiles(migrationsRoot) {
2680
+ const entries = await readdir2(migrationsRoot).catch(() => []);
2681
+ const resolved = /* @__PURE__ */ new Map();
2682
+ for (const entry of entries) {
2683
+ for (const slug of AUTH_MIGRATION_SLUGS) {
2684
+ if (entry.endsWith(`_${slug}.ts`) || entry.endsWith(`_${slug}.mts`) || entry.endsWith(`_${slug}.js`) || entry.endsWith(`_${slug}.mjs`) || entry.endsWith(`_${slug}.cts`) || entry.endsWith(`_${slug}.cjs`)) {
2685
+ resolved.set(slug, resolve4(migrationsRoot, entry));
2686
+ }
2687
+ }
2688
+ }
2689
+ return resolved;
2690
+ }
2691
+ async function resolveExistingNotificationsMigrationFiles(migrationsRoot) {
2692
+ const entries = await readdir2(migrationsRoot).catch(() => []);
2693
+ return entries.filter((entry) => entry.endsWith("_create_notifications.ts") || entry.endsWith("_create_notifications.mts") || entry.endsWith("_create_notifications.js") || entry.endsWith("_create_notifications.mjs") || entry.endsWith("_create_notifications.cts") || entry.endsWith("_create_notifications.cjs")).map((entry) => resolve4(migrationsRoot, entry));
2694
+ }
2695
+ var AUTH_NOTIFICATION_FILES = Object.freeze([
2696
+ Object.freeze({
2697
+ path: "server/notifications/auth/email-verification.ts",
2698
+ render: renderAuthEmailVerificationNotification
2699
+ }),
2700
+ Object.freeze({
2701
+ path: "server/notifications/auth/password-reset.ts",
2702
+ render: renderAuthPasswordResetNotification
2703
+ })
2704
+ ]);
2705
+ async function writeAuthNotificationFiles(projectRoot) {
2706
+ const createdFiles = [];
2707
+ const skippedFiles = [];
2708
+ await mkdir3(resolve4(projectRoot, "server/notifications/auth"), { recursive: true });
2709
+ for (const file of AUTH_NOTIFICATION_FILES) {
2710
+ const filePath = resolve4(projectRoot, file.path);
2711
+ if (await pathExists(filePath)) {
2712
+ skippedFiles.push(filePath);
2713
+ continue;
2714
+ }
2715
+ await writeTextFile(filePath, file.render());
2716
+ createdFiles.push(filePath);
2717
+ }
2718
+ return {
2719
+ createdFiles,
2720
+ skippedFiles
2721
+ };
2722
+ }
2723
+ async function syncHostedAuthRouteFiles(projectRoot, features) {
2724
+ if (!features.workos && !features.clerk) {
2725
+ return;
2726
+ }
2727
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
2728
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
2729
+ if (!framework) {
2730
+ return;
2731
+ }
2732
+ for (const file of renderAuthProviderRouteFiles(framework, features)) {
2733
+ const targetPath = resolve4(projectRoot, file.path);
2734
+ if (await pathExists(targetPath)) {
2735
+ continue;
2736
+ }
2737
+ await mkdir3(dirname2(targetPath), { recursive: true });
2738
+ await writeTextFile(targetPath, file.contents);
2739
+ }
2740
+ if (framework === "next") {
2741
+ for (const file of renderNextManagedHostedAuthRouteFiles(features)) {
2742
+ await writeTextFile(resolve4(projectRoot, file.path), file.contents);
2743
+ }
2744
+ }
2745
+ }
2746
+ async function syncAuthRouteFiles(projectRoot) {
2747
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
2748
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
2749
+ if (!framework) {
2750
+ return;
2751
+ }
2752
+ for (const file of renderAuthRouteFiles(framework)) {
2753
+ const targetPath = resolve4(projectRoot, file.path);
2754
+ if (await pathExists(targetPath)) {
2755
+ continue;
2756
+ }
2757
+ await writeTextFile(targetPath, file.contents);
2758
+ }
2759
+ }
2760
+ async function installAuthIntoProject(projectRoot, features = {}) {
2761
+ const project = await loadProjectConfig(projectRoot);
2762
+ const modelsRoot = resolve4(projectRoot, project.config.paths.models);
2763
+ const migrationsRoot = resolve4(projectRoot, project.config.paths.migrations);
2764
+ const defaultDatabaseConnection = project.config.database?.defaultConnection ?? "default";
2765
+ const authConfigPath = await resolveFirstExistingPath(projectRoot, AUTH_CONFIG_FILE_NAMES);
2766
+ const sessionConfigPath = await resolveFirstExistingPath(projectRoot, SESSION_CONFIG_FILE_NAMES);
2767
+ const securityConfigPath = await resolveFirstExistingPath(projectRoot, SECURITY_CONFIG_FILE_NAMES);
2768
+ const corsConfigPath = await resolveFirstExistingPath(projectRoot, CORS_CONFIG_FILE_NAMES);
2769
+ const userModelPath = await resolveExistingModelPath(modelsRoot, "User");
2770
+ const existingMigrationFiles = await resolveExistingAuthMigrationFiles(migrationsRoot);
2771
+ const hasAllAuthMigrations = AUTH_MIGRATION_SLUGS.every((slug) => existingMigrationFiles.has(slug));
2772
+ const existingAuthArtifacts = [
2773
+ authConfigPath,
2774
+ userModelPath,
2775
+ ...AUTH_MIGRATION_SLUGS.map((slug) => existingMigrationFiles.get(slug))
2776
+ ].filter((value) => typeof value === "string");
2777
+ if (authConfigPath && userModelPath && hasAllAuthMigrations) {
2778
+ const envPath2 = resolve4(projectRoot, ".env");
2779
+ const envExamplePath2 = resolve4(projectRoot, ".env.example");
2780
+ const currentAuthConfig = await readTextFile(authConfigPath) ?? "";
2781
+ const currentAuthFeatures = detectAuthInstallFeaturesFromConfig(currentAuthConfig);
2782
+ const nextAuthFeatures = mergeInstalledAuthFeatures(currentAuthFeatures, features);
2783
+ const authConfigModuleFormat = resolveConfigModuleFormat(authConfigPath, currentAuthConfig);
2784
+ const nextAuthConfig = renderAuthConfig(nextAuthFeatures, authConfigModuleFormat);
2785
+ const authEnvFiles2 = renderAuthEnvFiles(nextAuthFeatures, defaultDatabaseConnection);
2786
+ const nextEnv2 = upsertEnvContents(await readTextFile(envPath2), authEnvFiles2.env);
2787
+ const nextEnvExample2 = upsertEnvContents(await readTextFile(envExamplePath2), authEnvFiles2.example);
2788
+ const authConfigChanged = authFeaturesRequireConfigUpdate(features) && currentAuthConfig !== nextAuthConfig;
2789
+ if (authConfigChanged) {
2790
+ if (!canSafelyRewriteAuthConfig(currentAuthConfig, currentAuthFeatures, authConfigModuleFormat)) {
2791
+ throw new Error(
2792
+ `Auth support is already installed in ${projectRoot}, but ${authConfigPath} contains manual changes. Refusing to overwrite the existing auth config automatically.`
2793
+ );
2794
+ }
2795
+ await writeTextFile(authConfigPath, nextAuthConfig);
2796
+ }
2797
+ if (nextEnv2.changed && typeof nextEnv2.contents === "string") {
2798
+ await writeTextFile(envPath2, nextEnv2.contents);
2799
+ }
2800
+ if (nextEnvExample2.changed && typeof nextEnvExample2.contents === "string") {
2801
+ await writeTextFile(envExamplePath2, nextEnvExample2.contents);
2802
+ }
2803
+ let createdSecurityConfig2 = false;
2804
+ if (!securityConfigPath) {
2805
+ await writeTextFile(resolve4(projectRoot, "config/security.ts"), renderSecurityConfig());
2806
+ createdSecurityConfig2 = true;
2807
+ }
2808
+ const createdCorsConfig2 = await ensureCorsConfigFile(projectRoot);
2809
+ await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
2810
+ await syncAuthRouteFiles(projectRoot);
2811
+ await syncHostedAuthRouteFiles(projectRoot, nextAuthFeatures);
2812
+ return {
2813
+ updatedPackageJson: await upsertAuthPackageDependencies(projectRoot, nextAuthFeatures),
2814
+ createdAuthConfig: authConfigChanged,
2815
+ createdSessionConfig: false,
2816
+ createdSecurityConfig: createdSecurityConfig2,
2817
+ createdCorsConfig: createdCorsConfig2,
2818
+ createdUserModel: false,
2819
+ createdMigrationFiles: [],
2820
+ updatedEnv: nextEnv2.changed,
2821
+ updatedEnvExample: nextEnvExample2.changed
2822
+ };
2823
+ }
2824
+ const collisions = sessionConfigPath && existingAuthArtifacts.length === 0 ? [] : [
2825
+ ...existingAuthArtifacts,
2826
+ ...sessionConfigPath && existingAuthArtifacts.length > 0 ? [sessionConfigPath] : []
2827
+ ];
2828
+ if (collisions.length > 0) {
2829
+ throw new Error(
2830
+ `Auth support is partially installed. Refusing to overwrite existing files in ${projectRoot}: ${collisions.join(", ")}`
2831
+ );
2832
+ }
2833
+ const authConfigTargetPath = resolve4(projectRoot, "config/auth.ts");
2834
+ const sessionConfigTargetPath = resolve4(projectRoot, "config/session.ts");
2835
+ const userModelTargetPath = resolve4(modelsRoot, "User.ts");
2836
+ const generatedSchemaPath = resolveGeneratedSchemaPath(projectRoot, project.config);
2837
+ const migrationFiles = createAuthMigrationFiles();
2838
+ const authEnvFiles = renderAuthEnvFiles(features, defaultDatabaseConnection);
2839
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
2840
+ await mkdir3(modelsRoot, { recursive: true });
2841
+ await mkdir3(migrationsRoot, { recursive: true });
2842
+ await ensureRedisConfigFile(projectRoot);
2843
+ await writeTextFile(authConfigTargetPath, renderAuthConfig(features));
2844
+ if (!sessionConfigPath) {
2845
+ await writeTextFile(sessionConfigTargetPath, renderSessionConfig(defaultDatabaseConnection));
2846
+ }
2847
+ let createdSecurityConfig = false;
2848
+ if (!securityConfigPath) {
2849
+ await writeTextFile(resolve4(projectRoot, "config/security.ts"), renderSecurityConfig());
2850
+ createdSecurityConfig = true;
2851
+ }
2852
+ const createdCorsConfig = corsConfigPath ? false : await ensureCorsConfigFile(projectRoot);
2853
+ await writeTextFile(
2854
+ userModelTargetPath,
2855
+ renderAuthUserModel(resolveAuthUserModelSchemaImportPath(
2856
+ userModelTargetPath,
2857
+ generatedSchemaPath
2858
+ ))
2859
+ );
2860
+ const createdMigrationFiles = [];
2861
+ for (const migrationFile of migrationFiles) {
2862
+ const migrationPath = resolve4(migrationsRoot, migrationFile.path);
2863
+ await writeTextFile(migrationPath, migrationFile.contents);
2864
+ createdMigrationFiles.push(migrationPath);
2865
+ }
2866
+ const envPath = resolve4(projectRoot, ".env");
2867
+ const envExamplePath = resolve4(projectRoot, ".env.example");
2868
+ const nextEnv = upsertEnvContents(await readTextFile(envPath), authEnvFiles.env);
2869
+ const nextEnvExample = upsertEnvContents(await readTextFile(envExamplePath), authEnvFiles.example);
2870
+ if (nextEnv.changed && typeof nextEnv.contents === "string") {
2871
+ await writeTextFile(envPath, nextEnv.contents);
2872
+ }
2873
+ if (nextEnvExample.changed && typeof nextEnvExample.contents === "string") {
2874
+ await writeTextFile(envExamplePath, nextEnvExample.contents);
2875
+ }
2876
+ await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
2877
+ await syncAuthRouteFiles(projectRoot);
2878
+ await syncHostedAuthRouteFiles(projectRoot, features);
2879
+ return {
2880
+ updatedPackageJson: await upsertAuthPackageDependencies(projectRoot, features),
2881
+ createdAuthConfig: true,
2882
+ createdSessionConfig: !sessionConfigPath,
2883
+ createdSecurityConfig,
2884
+ createdCorsConfig,
2885
+ createdUserModel: true,
2886
+ createdMigrationFiles,
2887
+ updatedEnv: nextEnv.changed,
2888
+ updatedEnvExample: nextEnvExample.changed
2889
+ };
2890
+ }
2891
+ async function installAuthorizationIntoProject(projectRoot) {
2892
+ await loadProjectConfig(projectRoot, { required: true });
2893
+ const policiesRoot = resolve4(projectRoot, "server/policies");
2894
+ const abilitiesRoot = resolve4(projectRoot, "server/abilities");
2895
+ const policiesDirectoryExists = await pathExists(policiesRoot);
2896
+ const abilitiesDirectoryExists = await pathExists(abilitiesRoot);
2897
+ const policiesReadmePath = resolve4(policiesRoot, "README.md");
2898
+ const abilitiesReadmePath = resolve4(abilitiesRoot, "README.md");
2899
+ const policiesReadmeExists = await pathExists(policiesReadmePath);
2900
+ const abilitiesReadmeExists = await pathExists(abilitiesReadmePath);
2901
+ await mkdir3(policiesRoot, { recursive: true });
2902
+ await mkdir3(abilitiesRoot, { recursive: true });
2903
+ if (!policiesReadmeExists) {
2904
+ await writeTextFile(policiesReadmePath, renderAuthorizationPoliciesReadme());
2905
+ }
2906
+ if (!abilitiesReadmeExists) {
2907
+ await writeTextFile(abilitiesReadmePath, renderAuthorizationAbilitiesReadme());
2908
+ }
2909
+ return {
2910
+ updatedPackageJson: await upsertAuthorizationPackageDependency(projectRoot),
2911
+ createdPoliciesDirectory: !policiesDirectoryExists,
2912
+ createdAbilitiesDirectory: !abilitiesDirectoryExists,
2913
+ createdPoliciesReadme: !policiesReadmeExists,
2914
+ createdAbilitiesReadme: !abilitiesReadmeExists
2915
+ };
2916
+ }
2917
+ async function installQueueIntoProject(projectRoot, options = {}) {
2918
+ const driver = options.driver ?? "sync";
2919
+ if (!isSupportedQueueInstallerDriver(driver)) {
2920
+ throw new Error(`Unsupported queue driver: ${driver}.`);
2921
+ }
2922
+ const project = await loadProjectConfig(projectRoot);
2923
+ const defaultDatabaseConnection = project.config.database?.defaultConnection ?? "default";
2924
+ const queueConfigPath = await resolveFirstExistingPath(projectRoot, QUEUE_CONFIG_FILE_NAMES) ?? resolve4(projectRoot, "config/queue.ts");
2925
+ const queueConfigExists = await pathExists(queueConfigPath);
2926
+ const jobsRoot = resolve4(projectRoot, project.config.paths.jobs);
2927
+ const jobsDirectoryExists = await pathExists(jobsRoot);
2928
+ const queueEnvFiles = renderQueueEnvFiles(driver);
2929
+ if (!queueConfigExists) {
2930
+ await writeTextFile(queueConfigPath, renderQueueConfig({
2931
+ driver,
2932
+ defaultDatabaseConnection
2933
+ }));
2934
+ }
2935
+ if (driver === "redis") {
2936
+ await ensureRedisConfigFile(projectRoot);
2937
+ }
2938
+ await mkdir3(jobsRoot, { recursive: true });
2939
+ const updatedPackageJson = await upsertQueuePackageDependency(
2940
+ projectRoot,
2941
+ !queueConfigExists || driver !== "sync" ? driver : void 0
2942
+ );
2943
+ const envPath = resolve4(projectRoot, ".env");
2944
+ const envExamplePath = resolve4(projectRoot, ".env.example");
2945
+ const nextEnv = upsertEnvContents(await readTextFile(envPath), queueEnvFiles.env);
2946
+ const nextEnvExample = upsertEnvContents(await readTextFile(envExamplePath), queueEnvFiles.example);
2947
+ if (nextEnv.changed && typeof nextEnv.contents === "string") {
2948
+ await writeTextFile(envPath, nextEnv.contents);
2949
+ }
2950
+ if (nextEnvExample.changed && typeof nextEnvExample.contents === "string") {
2951
+ await writeTextFile(envExamplePath, nextEnvExample.contents);
2952
+ }
2953
+ return {
2954
+ createdQueueConfig: !queueConfigExists,
2955
+ updatedPackageJson,
2956
+ updatedEnv: nextEnv.changed,
2957
+ updatedEnvExample: nextEnvExample.changed,
2958
+ createdJobsDirectory: !jobsDirectoryExists
2959
+ };
2960
+ }
2961
+ async function installEventsIntoProject(projectRoot) {
2962
+ const project = await loadProjectConfig(projectRoot);
2963
+ const eventsRoot = resolve4(projectRoot, project.config.paths.events);
2964
+ const listenersRoot = resolve4(projectRoot, project.config.paths.listeners);
2965
+ const eventsDirectoryExists = await pathExists(eventsRoot);
2966
+ const listenersDirectoryExists = await pathExists(listenersRoot);
2967
+ await mkdir3(eventsRoot, { recursive: true });
2968
+ await mkdir3(listenersRoot, { recursive: true });
2969
+ return {
2970
+ updatedPackageJson: await upsertEventsPackageDependency(projectRoot),
2971
+ createdEventsDirectory: !eventsDirectoryExists,
2972
+ createdListenersDirectory: !listenersDirectoryExists
2973
+ };
2974
+ }
2975
+ async function installRealtimeIntoProject(projectRoot) {
2976
+ await loadProjectConfig(projectRoot, { required: true });
2977
+ const realtimeRoot = resolve4(projectRoot, "server/realtime");
2978
+ const realtimeDirectoryExists = await pathExists(realtimeRoot);
2979
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
2980
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
2981
+ let createdFrameworkSetup = false;
2982
+ await mkdir3(realtimeRoot, { recursive: true });
2983
+ if (framework === "next") {
2984
+ const routes = [
2985
+ { path: ".holo-js/generated/next/holo.ts", contents: renderNextHoloHelper() },
2986
+ { path: ".holo-js/generated/next/bootstrap.mjs", contents: renderNextRuntimeBootstrap() },
2987
+ { path: ".holo-js/generated/next/realtime-definitions.ts", contents: await renderNextRealtimeDefinitions(projectRoot) }
2988
+ ];
2989
+ for (const route of routes) {
2990
+ const routePath = resolve4(projectRoot, route.path);
2991
+ if (!await pathExists(routePath)) {
2992
+ createdFrameworkSetup = true;
2993
+ }
2994
+ await writeTextFile(routePath, route.contents);
2995
+ }
2996
+ } else if (framework === "sveltekit") {
2997
+ const viteConfigPath = resolve4(projectRoot, "vite.config.ts");
2998
+ if (await pathExists(viteConfigPath)) {
2999
+ const existingViteConfig = await readTextFile(viteConfigPath);
3000
+ const viteConfigContents = existingViteConfig ? injectSvelteRealtimeVitePlugin(existingViteConfig) : void 0;
3001
+ if (typeof viteConfigContents === "string") {
3002
+ createdFrameworkSetup = true;
3003
+ await writeTextFile(viteConfigPath, viteConfigContents);
3004
+ } else if (viteConfigContents) {
3005
+ throw new Error(viteConfigContents.error);
3006
+ }
3007
+ } else {
3008
+ createdFrameworkSetup = true;
3009
+ await writeTextFile(viteConfigPath, renderSvelteViteConfig(false, true));
3010
+ }
3011
+ }
3012
+ return {
3013
+ updatedPackageJson: await upsertRealtimePackageDependency(projectRoot),
3014
+ createdRealtimeDirectory: !realtimeDirectoryExists,
3015
+ createdFrameworkSetup
3016
+ };
3017
+ }
3018
+ async function installNotificationsIntoProject(projectRoot) {
3019
+ const project = await loadProjectConfig(projectRoot);
3020
+ const migrationsRoot = resolve4(projectRoot, project.config.paths.migrations);
3021
+ const notificationsConfigPath = await resolveFirstExistingPath(projectRoot, NOTIFICATIONS_CONFIG_FILE_NAMES);
3022
+ const existingMigrationFiles = await resolveExistingNotificationsMigrationFiles(migrationsRoot);
3023
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3024
+ await mkdir3(migrationsRoot, { recursive: true });
3025
+ if (!notificationsConfigPath) {
3026
+ await writeTextFile(resolve4(projectRoot, "config/notifications.ts"), renderNotificationsConfig());
3027
+ }
3028
+ const createdMigrationFiles = [];
3029
+ if (existingMigrationFiles.length === 0) {
3030
+ for (const migrationFile of createNotificationsMigrationFiles()) {
3031
+ const migrationPath = resolve4(migrationsRoot, migrationFile.path);
3032
+ await writeTextFile(migrationPath, migrationFile.contents);
3033
+ createdMigrationFiles.push(migrationPath);
3034
+ }
3035
+ }
3036
+ return {
3037
+ updatedPackageJson: await upsertNotificationsPackageDependency(projectRoot),
3038
+ createdNotificationsConfig: !notificationsConfigPath,
3039
+ createdMigrationFiles
3040
+ };
3041
+ }
3042
+ async function publishAuthNotificationsIntoProject(projectRoot) {
3043
+ await loadProjectConfig(projectRoot, { required: true });
3044
+ const dependencies = await readPackageJsonDependencyState(projectRoot);
3045
+ if (!dependencies.dependencies["@holo-js/auth"] && !dependencies.devDependencies["@holo-js/auth"]) {
3046
+ throw new Error(
3047
+ "Auth notification publishing requires @holo-js/auth. Install auth first with `holo install auth`."
3048
+ );
3049
+ }
3050
+ if (!dependencies.dependencies["@holo-js/notifications"] && !dependencies.devDependencies["@holo-js/notifications"]) {
3051
+ throw new Error(
3052
+ "Auth notification publishing requires @holo-js/notifications. Install notifications first with `holo install notifications`."
3053
+ );
3054
+ }
3055
+ const result = await writeAuthNotificationFiles(projectRoot);
3056
+ return {
3057
+ ...result,
3058
+ hasMailDependency: !!dependencies.dependencies["@holo-js/mail"] || !!dependencies.devDependencies["@holo-js/mail"]
3059
+ };
3060
+ }
3061
+ async function installMailIntoProject(projectRoot) {
3062
+ await loadProjectConfig(projectRoot, { required: true });
3063
+ const mailConfigPath = await resolveFirstExistingPath(projectRoot, MAIL_CONFIG_FILE_NAMES);
3064
+ const mailRoot = resolve4(projectRoot, "server/mail");
3065
+ const mailDirectoryExists = await pathExists(mailRoot);
3066
+ const envPath = resolve4(projectRoot, ".env");
3067
+ const envExamplePath = resolve4(projectRoot, ".env.example");
3068
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3069
+ await mkdir3(mailRoot, { recursive: true });
3070
+ if (!mailConfigPath) {
3071
+ await writeTextFile(resolve4(projectRoot, "config/mail.ts"), renderMailConfig());
3072
+ }
3073
+ const mailEnvFiles = renderMailEnvFiles();
3074
+ const nextEnv = upsertEnvContents(await readTextFile(envPath), mailEnvFiles.env);
3075
+ const nextEnvExample = upsertEnvContents(await readTextFile(envExamplePath), mailEnvFiles.example);
3076
+ if (nextEnv.changed && typeof nextEnv.contents === "string") {
3077
+ await writeTextFile(envPath, nextEnv.contents);
3078
+ }
3079
+ if (nextEnvExample.changed && typeof nextEnvExample.contents === "string") {
3080
+ await writeTextFile(envExamplePath, nextEnvExample.contents);
3081
+ }
3082
+ return {
3083
+ updatedPackageJson: await upsertMailPackageDependency(projectRoot),
3084
+ createdMailConfig: !mailConfigPath,
3085
+ createdMailDirectory: !mailDirectoryExists,
3086
+ updatedEnv: nextEnv.changed,
3087
+ updatedEnvExample: nextEnvExample.changed
3088
+ };
3089
+ }
3090
+ async function installMediaIntoProject(projectRoot) {
3091
+ await loadProjectConfig(projectRoot, { required: true });
3092
+ const mediaConfigPath = await resolveFirstExistingPath(projectRoot, MEDIA_CONFIG_FILE_NAMES);
3093
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3094
+ if (!mediaConfigPath) {
3095
+ await writeTextFile(resolve4(projectRoot, "config/media.ts"), renderMediaConfig());
3096
+ }
3097
+ const { createMediaTableMigration } = await import("./media-migrations-NMUWBEKE.mjs");
3098
+ const migrationFilePath = await createMediaTableMigration(projectRoot, {
3099
+ skipIfExists: true
3100
+ });
3101
+ return {
3102
+ updatedPackageJson: await upsertMediaPackageDependency(projectRoot),
3103
+ createdMediaConfig: !mediaConfigPath,
3104
+ createdMigrationFiles: migrationFilePath ? [migrationFilePath] : []
3105
+ };
3106
+ }
3107
+ async function installSecurityIntoProject(projectRoot) {
3108
+ await loadProjectConfig(projectRoot, { required: true });
3109
+ const securityConfigPath = await resolveFirstExistingPath(projectRoot, SECURITY_CONFIG_FILE_NAMES);
3110
+ const corsConfigPath = await resolveFirstExistingPath(projectRoot, CORS_CONFIG_FILE_NAMES);
3111
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3112
+ await ensureRateLimitStorageIgnore(projectRoot);
3113
+ await ensureRedisConfigFile(projectRoot);
3114
+ if (!securityConfigPath) {
3115
+ await writeTextFile(resolve4(projectRoot, "config/security.ts"), renderSecurityConfig());
3116
+ }
3117
+ const createdCorsConfig = corsConfigPath ? false : await ensureCorsConfigFile(projectRoot);
3118
+ return {
3119
+ updatedPackageJson: await upsertSecurityPackageDependency(projectRoot),
3120
+ createdSecurityConfig: !securityConfigPath,
3121
+ createdCorsConfig
3122
+ };
3123
+ }
3124
+ async function installCacheIntoProject(projectRoot, options = {}) {
3125
+ const project = await loadProjectConfig(projectRoot, { required: true });
3126
+ const driver = options.driver ?? "file";
3127
+ if (!isSupportedCacheInstallerDriver(driver)) {
3128
+ throw new Error(`Unsupported cache driver: ${driver}.`);
3129
+ }
3130
+ const cacheConfigPath = await resolveFirstExistingPath(projectRoot, CACHE_CONFIG_FILE_NAMES);
3131
+ const loadedConfig = await loadConfigDirectory2(projectRoot, {
3132
+ preferCache: false,
3133
+ processEnv: process.env
3134
+ });
3135
+ const defaultDatabaseConnection = project.config.database?.defaultConnection ?? "default";
3136
+ const defaultRedisConnection = loadedConfig.redis.default;
3137
+ const loadedCacheConfig = cacheConfigPath ? loadedConfig.cache : void 0;
3138
+ if (loadedCacheConfig && !Object.values(loadedCacheConfig.drivers).some((entry) => entry.driver === driver)) {
3139
+ throw new Error(
3140
+ `config/cache.ts already exists and does not configure the "${driver}" cache driver. Update your cache config first, then rerun "holo install cache".`
3141
+ );
3142
+ }
3143
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3144
+ if (!cacheConfigPath) {
3145
+ await writeTextFile(
3146
+ resolve4(projectRoot, "config/cache.ts"),
3147
+ renderCacheConfig(driver, defaultDatabaseConnection, defaultRedisConnection)
3148
+ );
3149
+ }
3150
+ let createdRedisConfig = false;
3151
+ if (driver === "redis") {
3152
+ createdRedisConfig = await ensureRedisConfigFile(projectRoot);
3153
+ }
3154
+ const cacheEnvFiles = renderCacheEnvFiles(driver);
3155
+ const envPath = resolve4(projectRoot, ".env");
3156
+ const envExamplePath = resolve4(projectRoot, ".env.example");
3157
+ const nextEnv = upsertEnvContents(await readTextFile(envPath), cacheEnvFiles.env);
3158
+ const nextEnvExample = upsertEnvContents(await readTextFile(envExamplePath), cacheEnvFiles.example);
3159
+ if (nextEnv.changed && typeof nextEnv.contents === "string") {
3160
+ await writeTextFile(envPath, nextEnv.contents);
3161
+ }
3162
+ if (nextEnvExample.changed && typeof nextEnvExample.contents === "string") {
3163
+ await writeTextFile(envExamplePath, nextEnvExample.contents);
3164
+ }
3165
+ return {
3166
+ updatedPackageJson: await upsertCachePackageDependencies(projectRoot, driver),
3167
+ createdCacheConfig: !cacheConfigPath,
3168
+ createdRedisConfig,
3169
+ updatedEnv: nextEnv.changed,
3170
+ updatedEnvExample: nextEnvExample.changed,
3171
+ databaseDriver: driver === "database"
3172
+ };
3173
+ }
3174
+ async function installBroadcastIntoProject(projectRoot) {
3175
+ const project = await loadProjectConfig(projectRoot, { required: true });
3176
+ const manifestPath = project.manifestPath;
3177
+ const manifestContents = await readTextFile(manifestPath);
3178
+ const manifestFormat = resolveConfigModuleFormat(manifestPath, manifestContents);
3179
+ const broadcastConfigTargetPath = resolveBroadcastConfigTargetPath(projectRoot, manifestPath, manifestFormat);
3180
+ const broadcastConfigIsTypeScript = [".ts", ".mts", ".cts"].includes(extname2(broadcastConfigTargetPath));
3181
+ const broadcastConfigPath = await resolveFirstExistingPath(projectRoot, BROADCAST_CONFIG_FILE_NAMES);
3182
+ const authConfigPath = await resolveFirstExistingPath(projectRoot, AUTH_CONFIG_FILE_NAMES);
3183
+ const { dependencies, devDependencies } = await readPackageJsonDependencyState(projectRoot);
3184
+ const framework = detectProjectFrameworkFromPackageJson(dependencies, devDependencies);
3185
+ const canCreateBroadcastAuthRoute = framework === "next" || framework === "nuxt" || framework === "sveltekit";
3186
+ const broadcastRoot = resolve4(projectRoot, "server/broadcast");
3187
+ const channelsRoot = resolve4(projectRoot, "server/channels");
3188
+ const broadcastDirectoryExists = await pathExists(broadcastRoot);
3189
+ const channelsDirectoryExists = await pathExists(channelsRoot);
3190
+ await mkdir3(resolve4(projectRoot, "config"), { recursive: true });
3191
+ await mkdir3(broadcastRoot, { recursive: true });
3192
+ await mkdir3(channelsRoot, { recursive: true });
3193
+ await ensureRedisConfigFile(projectRoot);
3194
+ if (!broadcastConfigPath) {
3195
+ await writeTextFile(
3196
+ broadcastConfigTargetPath,
3197
+ renderBroadcastConfig(
3198
+ manifestFormat,
3199
+ Boolean(authConfigPath) && canCreateBroadcastAuthRoute,
3200
+ broadcastConfigIsTypeScript
3201
+ )
3202
+ );
3203
+ }
3204
+ const broadcastEnvFiles = renderBroadcastEnvFiles();
3205
+ const envPath = resolve4(projectRoot, ".env");
3206
+ const envExamplePath = resolve4(projectRoot, ".env.example");
3207
+ const nextEnv = upsertEnvContents(await readTextFile(envPath), broadcastEnvFiles.env);
3208
+ const nextEnvExample = upsertEnvContents(await readTextFile(envExamplePath), broadcastEnvFiles.example);
3209
+ const dependencyResult = await upsertBroadcastPackageDependencies(projectRoot);
3210
+ let createdFrameworkSetup = false;
3211
+ if (framework === "next") {
3212
+ const holoHelperPath = resolve4(projectRoot, ".holo-js/generated/next/holo.ts");
3213
+ const broadcastConfigRoutePath = resolve4(projectRoot, "app/broadcasting/config/route.ts");
3214
+ const generatedBroadcastConfigRoutePath = resolve4(projectRoot, ".holo-js/generated/next/broadcast-config-route.ts");
3215
+ if (!await pathExists(holoHelperPath)) {
3216
+ await writeTextFile(holoHelperPath, renderNextHoloHelper());
3217
+ createdFrameworkSetup = true;
3218
+ }
3219
+ if (!await pathExists(broadcastConfigRoutePath)) {
3220
+ await writeTextFile(broadcastConfigRoutePath, renderNextBroadcastConfigRoute());
3221
+ createdFrameworkSetup = true;
3222
+ }
3223
+ await writeTextFile(generatedBroadcastConfigRoutePath, renderNextGeneratedBroadcastConfigRoute());
3224
+ } else if (framework === "sveltekit") {
3225
+ const holoHelperPath = resolve4(projectRoot, ".holo-js/generated/sveltekit/holo.ts");
3226
+ if (!await pathExists(holoHelperPath)) {
3227
+ await writeTextFile(holoHelperPath, renderSvelteHoloHelper());
3228
+ createdFrameworkSetup = true;
3229
+ }
3230
+ }
3231
+ const broadcastAuthSupport = await syncBroadcastAuthSupportAfterAuthInstall(projectRoot);
3232
+ if (nextEnv.changed && typeof nextEnv.contents === "string") {
3233
+ await writeTextFile(envPath, nextEnv.contents);
3234
+ }
3235
+ if (nextEnvExample.changed && typeof nextEnvExample.contents === "string") {
3236
+ await writeTextFile(envExamplePath, nextEnvExample.contents);
3237
+ }
3238
+ return {
3239
+ updatedPackageJson: dependencyResult.updated,
3240
+ createdBroadcastConfig: !broadcastConfigPath,
3241
+ createdBroadcastDirectory: !broadcastDirectoryExists,
3242
+ createdChannelsDirectory: !channelsDirectoryExists,
3243
+ createdBroadcastAuthRoute: broadcastAuthSupport.createdBroadcastAuthRoute,
3244
+ createdFrameworkSetup,
3245
+ updatedEnv: nextEnv.changed,
3246
+ updatedEnvExample: nextEnvExample.changed
3247
+ };
3248
+ }
3249
+
3250
+ export {
3251
+ hasLoadedConfigFile,
3252
+ inferDatabaseDriverFromUrl,
3253
+ inferConnectionDriver,
3254
+ syncManagedDriverDependencies,
3255
+ upsertEventsPackageDependency,
3256
+ upsertNotificationsPackageDependency,
3257
+ upsertMailPackageDependency,
3258
+ upsertMediaPackageDependency,
3259
+ upsertSecurityPackageDependency,
3260
+ upsertCachePackageDependencies,
3261
+ upsertAuthPackageDependencies,
3262
+ renderStorageConfig,
3263
+ renderMediaConfig,
3264
+ renderQueueConfig,
3265
+ renderCacheConfig,
3266
+ renderRedisConfig,
3267
+ renderNotificationsConfig,
3268
+ renderMailConfig,
3269
+ renderSecurityConfig,
3270
+ renderCorsConfig,
3271
+ injectBroadcastAuthEndpoint,
3272
+ resolveBroadcastConfigTargetPath,
3273
+ renderSessionConfig,
3274
+ renderAuthConfig,
3275
+ authFeaturesRequireConfigUpdate,
3276
+ detectAuthInstallFeaturesFromConfig,
3277
+ renderAuthEnvFiles,
3278
+ renderAuthUserModel,
3279
+ renderAuthMigration,
3280
+ renderNotificationsMigration,
3281
+ renderScaffoldAppConfig,
3282
+ renderScaffoldDatabaseConfig,
3283
+ resolveDefaultDatabaseUrl,
3284
+ renderScaffoldEnvFiles,
3285
+ renderMailEnvFiles,
3286
+ renderQueueEnvFiles,
3287
+ renderCacheEnvFiles,
3288
+ renderEnvFileContents,
3289
+ normalizeScaffoldEnvSegments,
3290
+ renderScaffoldGitignore,
3291
+ renderScaffoldTsconfig,
3292
+ renderVSCodeSettings,
3293
+ resolvePackageManagerVersion,
3294
+ renderScaffoldPackageJson,
3295
+ scaffoldProject,
3296
+ installAuthIntoProject,
3297
+ installAuthorizationIntoProject,
3298
+ installQueueIntoProject,
3299
+ installEventsIntoProject,
3300
+ installRealtimeIntoProject,
3301
+ installNotificationsIntoProject,
3302
+ publishAuthNotificationsIntoProject,
3303
+ installMailIntoProject,
3304
+ installMediaIntoProject,
3305
+ installSecurityIntoProject,
3306
+ installCacheIntoProject,
3307
+ installBroadcastIntoProject
3308
+ };