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