@holo-js/cli 0.1.2 → 0.1.3
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 +533 -4616
- package/dist/broadcast-YZS4OFCM.mjs +84 -0
- package/dist/broadcast-ZYFKUFM5.mjs +85 -0
- package/dist/cache-ODBZT6IP.mjs +67 -0
- package/dist/cache-V43YMG4K.mjs +66 -0
- package/dist/cache-migrations-KPOEH6GP.mjs +155 -0
- package/dist/cache-migrations-ZUOI2A7N.mjs +154 -0
- package/dist/chunk-3OTCSFDG.mjs +849 -0
- package/dist/chunk-66FHW725.mjs +465 -0
- package/dist/chunk-BWW5TDFI.mjs +4 -0
- package/dist/chunk-CUL4RJTG.mjs +22 -0
- package/dist/chunk-D4GG556Y.mjs +23 -0
- package/dist/chunk-D7O4SU6N.mjs +2 -0
- package/dist/chunk-ET7UXHHQ.mjs +166 -0
- package/dist/chunk-EUIVXVJL.mjs +25 -0
- package/dist/chunk-G5ADO27Q.mjs +463 -0
- package/dist/chunk-GSQ3HTRO.mjs +165 -0
- package/dist/chunk-H7TJ4FB3.mjs +848 -0
- package/dist/chunk-HE6FYNVN.mjs +3203 -0
- package/dist/chunk-ICJR7TS4.mjs +66 -0
- package/dist/chunk-ICKN56JY.mjs +342 -0
- package/dist/chunk-JX2ZH6XY.mjs +270 -0
- package/dist/chunk-M7J3YTHR.mjs +26 -0
- package/dist/chunk-Q5F6C2D4.mjs +65 -0
- package/dist/chunk-QYLSMF7V.mjs +539 -0
- package/dist/chunk-RB65DLR4.mjs +343 -0
- package/dist/chunk-S7P7EBM3.mjs +787 -0
- package/dist/chunk-SRWJU3A5.mjs +11 -0
- package/dist/chunk-T4OVZZEE.mjs +3204 -0
- package/dist/chunk-URK7C3VQ.mjs +538 -0
- package/dist/chunk-VT5IDQG6.mjs +788 -0
- package/dist/chunk-XUYKPU5Q.mjs +272 -0
- package/dist/chunk-ZXDU7RHU.mjs +9 -0
- package/dist/config-DMWBMMGD.mjs +26 -0
- package/dist/config-LS5USBRB.mjs +25 -0
- package/dist/dev-KGRIGLJY.mjs +42 -0
- package/dist/dev-LVHDCPVS.mjs +43 -0
- package/dist/discovery-GBLAUTXS.mjs +28 -0
- package/dist/discovery-R733D2PO.mjs +29 -0
- package/dist/generators-32R45P6Z.mjs +426 -0
- package/dist/generators-WSF23UKM.mjs +425 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +536 -4618
- package/dist/queue-6N7HQMRL.mjs +625 -0
- package/dist/queue-QG5EXOG4.mjs +626 -0
- package/dist/queue-migrations-JWKU45Y3.mjs +163 -0
- package/dist/queue-migrations-O6QSSDPQ.mjs +162 -0
- package/dist/runtime-ANBO7VQM.mjs +33 -0
- package/dist/runtime-OOSJ5JBY.mjs +32 -0
- package/dist/runtime-RI4OWTIT.mjs +55 -0
- package/dist/runtime-ZRPK5DIT.mjs +56 -0
- package/dist/scaffold-IYWZKT3W.mjs +120 -0
- package/dist/scaffold-ULATB4CA.mjs +121 -0
- package/dist/security-AE6LGNC4.mjs +68 -0
- package/dist/security-OCOPEH2V.mjs +69 -0
- package/package.json +12 -11
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fileExists
|
|
4
|
+
} from "./chunk-M7J3YTHR.mjs";
|
|
5
|
+
|
|
6
|
+
// src/migrations.ts
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import {
|
|
9
|
+
generateMigrationTemplate,
|
|
10
|
+
inferMigrationTableName,
|
|
11
|
+
inferMigrationTemplateKind,
|
|
12
|
+
normalizeMigrationSlug
|
|
13
|
+
} from "@holo-js/db";
|
|
14
|
+
var MIGRATION_NAME_PREFIX_PATTERN = /^\d{4}_\d{2}_\d{2}_\d{6}_/;
|
|
15
|
+
function stripMigrationNamePrefix(name) {
|
|
16
|
+
return name.replace(MIGRATION_NAME_PREFIX_PATTERN, "");
|
|
17
|
+
}
|
|
18
|
+
function getRegistryMigrationSlug(name) {
|
|
19
|
+
return normalizeMigrationSlug(stripMigrationNamePrefix(name));
|
|
20
|
+
}
|
|
21
|
+
function hasRegisteredMigrationSlug(registry, migrationSlug) {
|
|
22
|
+
return Boolean(registry?.migrations.some((entry) => {
|
|
23
|
+
try {
|
|
24
|
+
return getRegistryMigrationSlug(entry.name) === migrationSlug;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function hasRegisteredCreateTableMigration(registry, tableName) {
|
|
31
|
+
const expectedSlug = `create_${tableName}_table`;
|
|
32
|
+
return Boolean(registry?.migrations.some((entry) => {
|
|
33
|
+
try {
|
|
34
|
+
const slug = getRegistryMigrationSlug(entry.name);
|
|
35
|
+
if (slug === expectedSlug) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (inferMigrationTemplateKind(slug) !== "create_table") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return inferMigrationTableName(slug, "create_table") === tableName;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
async function nextMigrationTemplate(name, migrationsDir, options = {}) {
|
|
48
|
+
let offsetSeconds = 0;
|
|
49
|
+
while (true) {
|
|
50
|
+
const candidate = generateMigrationTemplate(name, {
|
|
51
|
+
date: new Date(Date.now() + offsetSeconds * 1e3),
|
|
52
|
+
...options
|
|
53
|
+
});
|
|
54
|
+
if (!await fileExists(join(migrationsDir, candidate.fileName))) {
|
|
55
|
+
return candidate;
|
|
56
|
+
}
|
|
57
|
+
offsetSeconds += 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
getRegistryMigrationSlug,
|
|
63
|
+
hasRegisteredMigrationSlug,
|
|
64
|
+
hasRegisteredCreateTableMigration,
|
|
65
|
+
nextMigrationTemplate
|
|
66
|
+
};
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import {
|
|
2
|
+
prepareProjectDiscovery
|
|
3
|
+
} from "./chunk-S7P7EBM3.mjs";
|
|
4
|
+
import {
|
|
5
|
+
syncManagedDriverDependencies
|
|
6
|
+
} from "./chunk-HE6FYNVN.mjs";
|
|
7
|
+
import {
|
|
8
|
+
ensureProjectConfig
|
|
9
|
+
} from "./chunk-GSQ3HTRO.mjs";
|
|
10
|
+
import {
|
|
11
|
+
readTextFile
|
|
12
|
+
} from "./chunk-G5ADO27Q.mjs";
|
|
13
|
+
|
|
14
|
+
// src/dev.ts
|
|
15
|
+
import { spawnSync, spawn } from "child_process";
|
|
16
|
+
import { watch } from "fs";
|
|
17
|
+
import { readdir, stat } from "fs/promises";
|
|
18
|
+
import { dirname, join, relative, resolve } from "path";
|
|
19
|
+
async function fileExists(path) {
|
|
20
|
+
try {
|
|
21
|
+
await stat(path);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function resolveProjectPackageManager(projectRoot) {
|
|
28
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
29
|
+
const packageJson = await readTextFile(packageJsonPath);
|
|
30
|
+
if (packageJson) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(packageJson);
|
|
33
|
+
const packageManager = typeof parsed.packageManager === "string" ? parsed.packageManager.split("@")[0] : void 0;
|
|
34
|
+
if (packageManager === "bun" || packageManager === "npm" || packageManager === "pnpm" || packageManager === "yarn") {
|
|
35
|
+
return packageManager;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (await fileExists(join(projectRoot, "bun.lock"))) {
|
|
41
|
+
return "bun";
|
|
42
|
+
}
|
|
43
|
+
if (await fileExists(join(projectRoot, "pnpm-lock.yaml"))) {
|
|
44
|
+
return "pnpm";
|
|
45
|
+
}
|
|
46
|
+
if (await fileExists(join(projectRoot, "yarn.lock"))) {
|
|
47
|
+
return "yarn";
|
|
48
|
+
}
|
|
49
|
+
if (await fileExists(join(projectRoot, "package-lock.json"))) {
|
|
50
|
+
return "npm";
|
|
51
|
+
}
|
|
52
|
+
return "bun";
|
|
53
|
+
}
|
|
54
|
+
async function resolvePackageManagerCommand(projectRoot, scriptName) {
|
|
55
|
+
const packageManager = await resolveProjectPackageManager(projectRoot);
|
|
56
|
+
return {
|
|
57
|
+
command: packageManager,
|
|
58
|
+
args: ["run", scriptName]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function resolvePackageManagerInstallInvocation(projectRoot) {
|
|
62
|
+
const packageManager = await resolveProjectPackageManager(projectRoot);
|
|
63
|
+
return {
|
|
64
|
+
command: packageManager,
|
|
65
|
+
args: ["install"]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function runProjectLifecycleScript(io, projectRoot, scriptName, spawn2 = spawnSync) {
|
|
69
|
+
const invocation = await resolvePackageManagerCommand(projectRoot, scriptName);
|
|
70
|
+
const result = spawn2(invocation.command, [...invocation.args], {
|
|
71
|
+
cwd: projectRoot,
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
env: process.env
|
|
74
|
+
});
|
|
75
|
+
if (result.stdout) {
|
|
76
|
+
io.stdout.write(result.stdout);
|
|
77
|
+
}
|
|
78
|
+
if (result.stderr) {
|
|
79
|
+
io.stderr.write(result.stderr);
|
|
80
|
+
}
|
|
81
|
+
if (result.status !== 0) {
|
|
82
|
+
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `Project script "${scriptName}" failed.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function runProjectDependencyInstall(io, projectRoot, spawn2 = spawnSync) {
|
|
86
|
+
const invocation = await resolvePackageManagerInstallInvocation(projectRoot);
|
|
87
|
+
const result = spawn2(invocation.command, [...invocation.args], {
|
|
88
|
+
cwd: projectRoot,
|
|
89
|
+
encoding: "utf8",
|
|
90
|
+
env: process.env
|
|
91
|
+
});
|
|
92
|
+
if (result.stdout) {
|
|
93
|
+
io.stdout.write(result.stdout);
|
|
94
|
+
}
|
|
95
|
+
if (result.stderr) {
|
|
96
|
+
io.stderr.write(result.stderr);
|
|
97
|
+
}
|
|
98
|
+
if (result.status !== 0) {
|
|
99
|
+
throw new Error(result.stderr?.trim() || result.stdout?.trim() || "Project dependency installation failed.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function runProjectPrepare(projectRoot, io) {
|
|
103
|
+
const project = await ensureProjectConfig(projectRoot);
|
|
104
|
+
await prepareProjectDiscovery(projectRoot, project.config);
|
|
105
|
+
const updatedDependencies = await syncManagedDriverDependencies(projectRoot);
|
|
106
|
+
if (updatedDependencies && io) {
|
|
107
|
+
await runProjectDependencyInstall(io, projectRoot);
|
|
108
|
+
await prepareProjectDiscovery(projectRoot, project.config);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function toPosixSlashes(value) {
|
|
112
|
+
return value.replaceAll("\\", "/");
|
|
113
|
+
}
|
|
114
|
+
function isDiscoveryRelevantPath(filePath, project) {
|
|
115
|
+
const normalized = toPosixSlashes(filePath);
|
|
116
|
+
const authorizationPoliciesPath = project.config.paths.authorizationPolicies || "server/policies";
|
|
117
|
+
const authorizationAbilitiesPath = project.config.paths.authorizationAbilities || "server/abilities";
|
|
118
|
+
const roots = [
|
|
119
|
+
project.config.paths.models,
|
|
120
|
+
project.config.paths.migrations,
|
|
121
|
+
project.config.paths.seeders,
|
|
122
|
+
project.config.paths.commands,
|
|
123
|
+
project.config.paths.jobs,
|
|
124
|
+
project.config.paths.events,
|
|
125
|
+
project.config.paths.listeners,
|
|
126
|
+
authorizationPoliciesPath,
|
|
127
|
+
authorizationAbilitiesPath,
|
|
128
|
+
"server/broadcast",
|
|
129
|
+
"server/channels",
|
|
130
|
+
project.config.paths.generatedSchema,
|
|
131
|
+
"config",
|
|
132
|
+
".holo-js/generated"
|
|
133
|
+
];
|
|
134
|
+
if (normalized === ".env" || normalized.startsWith(".env.")) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return roots.some((root) => normalized === root || normalized.startsWith(`${toPosixSlashes(root)}/`));
|
|
138
|
+
}
|
|
139
|
+
function isRecursiveWatchUnsupported(error) {
|
|
140
|
+
return error instanceof Error && (error.message.includes("recursive") || "code" in error && error.code === "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM");
|
|
141
|
+
}
|
|
142
|
+
function isIgnorableWatchError(error) {
|
|
143
|
+
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EPERM");
|
|
144
|
+
}
|
|
145
|
+
async function collectDirectoryTree(rootPath, directories) {
|
|
146
|
+
const rootStats = await stat(rootPath).catch(() => void 0);
|
|
147
|
+
if (!rootStats?.isDirectory()) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
directories.add(rootPath);
|
|
151
|
+
const entries = await readdir(rootPath, { withFileTypes: true }).catch(() => []);
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (!entry.isDirectory()) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
await collectDirectoryTree(join(rootPath, entry.name), directories);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function collectDiscoveryWatchRoots(projectRoot, project) {
|
|
160
|
+
const directories = /* @__PURE__ */ new Set();
|
|
161
|
+
const authorizationPoliciesPath = project.config.paths.authorizationPolicies || "server/policies";
|
|
162
|
+
const authorizationAbilitiesPath = project.config.paths.authorizationAbilities || "server/abilities";
|
|
163
|
+
const roots = [
|
|
164
|
+
projectRoot,
|
|
165
|
+
resolve(projectRoot, "config"),
|
|
166
|
+
resolve(projectRoot, ".holo-js/generated"),
|
|
167
|
+
resolve(projectRoot, project.config.paths.models),
|
|
168
|
+
resolve(projectRoot, project.config.paths.migrations),
|
|
169
|
+
resolve(projectRoot, project.config.paths.seeders),
|
|
170
|
+
resolve(projectRoot, project.config.paths.commands),
|
|
171
|
+
resolve(projectRoot, project.config.paths.jobs),
|
|
172
|
+
resolve(projectRoot, project.config.paths.events),
|
|
173
|
+
resolve(projectRoot, project.config.paths.listeners),
|
|
174
|
+
resolve(projectRoot, authorizationPoliciesPath),
|
|
175
|
+
resolve(projectRoot, authorizationAbilitiesPath),
|
|
176
|
+
resolve(projectRoot, "server/broadcast"),
|
|
177
|
+
resolve(projectRoot, "server/channels"),
|
|
178
|
+
dirname(resolve(projectRoot, project.config.paths.generatedSchema))
|
|
179
|
+
];
|
|
180
|
+
for (const rootPath of roots) {
|
|
181
|
+
await collectDirectoryTree(rootPath, directories);
|
|
182
|
+
}
|
|
183
|
+
return [...directories];
|
|
184
|
+
}
|
|
185
|
+
function normalizeWatchedFilePath(projectRoot, watchedRoot, fileName) {
|
|
186
|
+
return toPosixSlashes(relative(projectRoot, resolve(watchedRoot, fileName)));
|
|
187
|
+
}
|
|
188
|
+
async function runProjectDevServer(io, projectRoot, spawnProcess = spawn, createWatcher = watch, prepare = runProjectPrepare) {
|
|
189
|
+
let project = await ensureProjectConfig(projectRoot);
|
|
190
|
+
let refreshNonRecursiveWatchers;
|
|
191
|
+
let requestChildRestart;
|
|
192
|
+
const prepareDiscovery = async () => {
|
|
193
|
+
await prepare(projectRoot, io);
|
|
194
|
+
project = await ensureProjectConfig(projectRoot);
|
|
195
|
+
await refreshNonRecursiveWatchers?.();
|
|
196
|
+
};
|
|
197
|
+
await prepareDiscovery();
|
|
198
|
+
let pendingPrepare;
|
|
199
|
+
let queued = false;
|
|
200
|
+
let shuttingDown = false;
|
|
201
|
+
const rerunPrepare = () => {
|
|
202
|
+
if (shuttingDown) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (pendingPrepare) {
|
|
206
|
+
queued = true;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
pendingPrepare = prepareDiscovery().then(() => {
|
|
210
|
+
requestChildRestart?.();
|
|
211
|
+
}).catch((error) => {
|
|
212
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
213
|
+
`);
|
|
214
|
+
}).finally(() => {
|
|
215
|
+
pendingPrepare = void 0;
|
|
216
|
+
if (queued) {
|
|
217
|
+
queued = false;
|
|
218
|
+
rerunPrepare();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
const closeWatchers = (() => {
|
|
223
|
+
try {
|
|
224
|
+
const watcher = createWatcher(projectRoot, { recursive: true }, (_eventType, fileName) => {
|
|
225
|
+
if (shuttingDown || typeof fileName !== "string" || !isDiscoveryRelevantPath(fileName, project)) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
rerunPrepare();
|
|
229
|
+
});
|
|
230
|
+
return () => watcher.close();
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (!isRecursiveWatchUnsupported(error)) {
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
const watchers = [];
|
|
236
|
+
const closeAllWatchers = () => {
|
|
237
|
+
while (watchers.length > 0) {
|
|
238
|
+
watchers.pop()?.close();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
refreshNonRecursiveWatchers = async () => {
|
|
242
|
+
closeAllWatchers();
|
|
243
|
+
const watchRoots = await collectDiscoveryWatchRoots(projectRoot, project);
|
|
244
|
+
for (const watchRoot of watchRoots) {
|
|
245
|
+
try {
|
|
246
|
+
watchers.push(createWatcher(watchRoot, { recursive: false }, (_eventType, fileName) => {
|
|
247
|
+
if (shuttingDown || typeof fileName !== "string") {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const normalizedPath = normalizeWatchedFilePath(projectRoot, watchRoot, fileName);
|
|
251
|
+
if (!isDiscoveryRelevantPath(normalizedPath, project)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
rerunPrepare();
|
|
255
|
+
}));
|
|
256
|
+
} catch (watchError) {
|
|
257
|
+
if (!isIgnorableWatchError(watchError)) {
|
|
258
|
+
throw watchError;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
return () => closeAllWatchers();
|
|
264
|
+
}
|
|
265
|
+
})();
|
|
266
|
+
await refreshNonRecursiveWatchers?.();
|
|
267
|
+
const invocation = await resolvePackageManagerCommand(projectRoot, "holo:dev");
|
|
268
|
+
while (!shuttingDown) {
|
|
269
|
+
const child = spawnProcess(invocation.command, [...invocation.args], {
|
|
270
|
+
cwd: projectRoot,
|
|
271
|
+
env: process.env,
|
|
272
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
273
|
+
});
|
|
274
|
+
child.stdout?.on("data", (chunk) => io.stdout.write(chunk));
|
|
275
|
+
child.stderr?.on("data", (chunk) => io.stderr.write(chunk));
|
|
276
|
+
if (child.stdin) {
|
|
277
|
+
io.stdin.pipe(child.stdin);
|
|
278
|
+
}
|
|
279
|
+
const result = await new Promise((resolvePromise) => {
|
|
280
|
+
let restartRequested = false;
|
|
281
|
+
requestChildRestart = () => {
|
|
282
|
+
if (restartRequested || shuttingDown || typeof child.kill !== "function") {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
restartRequested = true;
|
|
286
|
+
child.kill("SIGTERM");
|
|
287
|
+
};
|
|
288
|
+
child.on("error", (error) => {
|
|
289
|
+
if (child.stdin) {
|
|
290
|
+
io.stdin.unpipe(child.stdin);
|
|
291
|
+
}
|
|
292
|
+
requestChildRestart = void 0;
|
|
293
|
+
if (restartRequested) {
|
|
294
|
+
resolvePromise({ kind: "restart" });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
resolvePromise({ kind: "error", error });
|
|
298
|
+
});
|
|
299
|
+
child.on("close", (code) => {
|
|
300
|
+
if (child.stdin) {
|
|
301
|
+
io.stdin.unpipe(child.stdin);
|
|
302
|
+
}
|
|
303
|
+
requestChildRestart = void 0;
|
|
304
|
+
if (restartRequested) {
|
|
305
|
+
resolvePromise({ kind: "restart" });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
resolvePromise({ kind: "close", code });
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
if (result.kind === "restart") {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
shuttingDown = true;
|
|
315
|
+
closeWatchers();
|
|
316
|
+
await Promise.resolve(pendingPrepare);
|
|
317
|
+
if (result.kind === "error") {
|
|
318
|
+
throw result.error;
|
|
319
|
+
}
|
|
320
|
+
if (result.code === 0) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
throw new Error(`Project script "holo:dev" failed with exit code ${result.code ?? "unknown"}.`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export {
|
|
328
|
+
resolveProjectPackageManager,
|
|
329
|
+
resolvePackageManagerCommand,
|
|
330
|
+
resolvePackageManagerInstallInvocation,
|
|
331
|
+
runProjectLifecycleScript,
|
|
332
|
+
runProjectDependencyInstall,
|
|
333
|
+
runProjectPrepare,
|
|
334
|
+
toPosixSlashes,
|
|
335
|
+
isDiscoveryRelevantPath,
|
|
336
|
+
isRecursiveWatchUnsupported,
|
|
337
|
+
isIgnorableWatchError,
|
|
338
|
+
collectDirectoryTree,
|
|
339
|
+
collectDiscoveryWatchRoots,
|
|
340
|
+
normalizeWatchedFilePath,
|
|
341
|
+
runProjectDevServer
|
|
342
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// src/parsing.ts
|
|
2
|
+
import { createInterface } from "readline/promises";
|
|
3
|
+
var SUPPORTED_NEW_FRAMEWORKS = ["nuxt", "next", "sveltekit"];
|
|
4
|
+
var SUPPORTED_NEW_DATABASE_DRIVERS = ["sqlite", "mysql", "postgres"];
|
|
5
|
+
var SUPPORTED_NEW_PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
|
|
6
|
+
var SUPPORTED_NEW_STORAGE_DISKS = ["local", "public"];
|
|
7
|
+
var SUPPORTED_NEW_OPTIONAL_PACKAGES = ["storage", "events", "queue", "validation", "forms", "auth", "authorization", "notifications", "mail", "broadcast", "security", "cache"];
|
|
8
|
+
var SUPPORTED_INSTALL_TARGETS = ["queue", "events", "auth", "authorization", "notifications", "mail", "broadcast", "security", "cache"];
|
|
9
|
+
var SUPPORTED_QUEUE_INSTALL_DRIVERS = ["sync", "redis", "database"];
|
|
10
|
+
var SUPPORTED_CACHE_INSTALL_DRIVERS = ["file", "redis", "database"];
|
|
11
|
+
function parseTokens(tokens) {
|
|
12
|
+
const args = [];
|
|
13
|
+
const flags = {};
|
|
14
|
+
const isNumericValueToken = (value) => typeof value === "string" && /^-\d+$/.test(value);
|
|
15
|
+
const assignFlag = (name, value) => {
|
|
16
|
+
const existing = flags[name];
|
|
17
|
+
if (typeof existing === "undefined") {
|
|
18
|
+
flags[name] = value;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(existing)) {
|
|
22
|
+
flags[name] = [...existing, String(value)];
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
flags[name] = [String(existing), String(value)];
|
|
26
|
+
};
|
|
27
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
28
|
+
const token = tokens[index];
|
|
29
|
+
if (typeof token === "undefined") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (token === "--") {
|
|
33
|
+
args.push(...tokens.slice(index + 1));
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (token.startsWith("--")) {
|
|
37
|
+
const flag = token.slice(2);
|
|
38
|
+
const separator = flag.indexOf("=");
|
|
39
|
+
if (separator >= 0) {
|
|
40
|
+
assignFlag(flag.slice(0, separator), flag.slice(separator + 1));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const next = tokens[index + 1];
|
|
44
|
+
if (next && (!next.startsWith("-") || isNumericValueToken(next))) {
|
|
45
|
+
assignFlag(flag, next);
|
|
46
|
+
index += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
assignFlag(flag, true);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
53
|
+
const short = token.slice(1);
|
|
54
|
+
if (short.length > 1) {
|
|
55
|
+
for (const char of short) {
|
|
56
|
+
assignFlag(char, true);
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const next = tokens[index + 1];
|
|
61
|
+
if (next && (!next.startsWith("-") || isNumericValueToken(next))) {
|
|
62
|
+
assignFlag(short, next);
|
|
63
|
+
index += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
assignFlag(short, true);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
args.push(token);
|
|
70
|
+
}
|
|
71
|
+
return { args, flags };
|
|
72
|
+
}
|
|
73
|
+
function isInteractive(io, flags) {
|
|
74
|
+
const disabled = flags["no-interactive"] === true;
|
|
75
|
+
return io.stdin.isTTY === true && io.stdout.isTTY === true && !disabled;
|
|
76
|
+
}
|
|
77
|
+
async function prompt(io, label) {
|
|
78
|
+
const rl = createInterface({
|
|
79
|
+
input: io.stdin,
|
|
80
|
+
output: io.stdout
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
return (await rl.question(label)).trim();
|
|
84
|
+
} finally {
|
|
85
|
+
rl.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function confirm(io, label, defaultValue = false) {
|
|
89
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
90
|
+
const answer = (await prompt(io, `${label}${suffix}`)).toLowerCase();
|
|
91
|
+
if (!answer) {
|
|
92
|
+
return defaultValue;
|
|
93
|
+
}
|
|
94
|
+
return answer === "y" || answer === "yes";
|
|
95
|
+
}
|
|
96
|
+
function normalizeChoice(value, allowed, label) {
|
|
97
|
+
const normalized = value?.trim().toLowerCase();
|
|
98
|
+
if (normalized && allowed.includes(normalized)) {
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Unsupported ${label}: ${value ?? "(empty)"}. Expected one of ${allowed.join(", ")}.`);
|
|
102
|
+
}
|
|
103
|
+
async function promptChoice(io, label, allowed, defaultValue) {
|
|
104
|
+
const answer = (await prompt(io, `${label} (${allowed.join("/")}) [${defaultValue}]: `)).trim().toLowerCase();
|
|
105
|
+
if (!answer) {
|
|
106
|
+
return defaultValue;
|
|
107
|
+
}
|
|
108
|
+
return normalizeChoice(answer, allowed, label);
|
|
109
|
+
}
|
|
110
|
+
function normalizeOptionalPackageName(value) {
|
|
111
|
+
const current = value.trim().toLowerCase();
|
|
112
|
+
if (current === "validate") {
|
|
113
|
+
return "validation";
|
|
114
|
+
}
|
|
115
|
+
if (current === "form") {
|
|
116
|
+
return "forms";
|
|
117
|
+
}
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
function normalizeOptionalPackages(value) {
|
|
121
|
+
if (!value || value.length === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
125
|
+
for (const raw of value) {
|
|
126
|
+
const current = normalizeOptionalPackageName(raw);
|
|
127
|
+
if (current === "none") {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (SUPPORTED_NEW_OPTIONAL_PACKAGES.includes(current)) {
|
|
131
|
+
normalized.add(current);
|
|
132
|
+
if (current === "forms") {
|
|
133
|
+
normalized.add("validation");
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Unsupported optional package: ${raw}. Expected one of ${[...SUPPORTED_NEW_OPTIONAL_PACKAGES, "none"].join(", ")}.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return [...normalized].sort((left, right) => left.localeCompare(right));
|
|
142
|
+
}
|
|
143
|
+
async function promptOptionalPackages(io) {
|
|
144
|
+
const answer = await prompt(io, `Optional packages (${[...SUPPORTED_NEW_OPTIONAL_PACKAGES, "none"].join("/")}): `);
|
|
145
|
+
return normalizeOptionalPackages(splitCsv(answer) ?? (answer ? [answer] : []));
|
|
146
|
+
}
|
|
147
|
+
async function resolveNewProjectInput(io, input, prompts = {
|
|
148
|
+
prompt: (label) => prompt(io, label),
|
|
149
|
+
choose: (label, allowed, defaultValue) => promptChoice(io, label, allowed, defaultValue),
|
|
150
|
+
optionalPackages: () => promptOptionalPackages(io)
|
|
151
|
+
}) {
|
|
152
|
+
const flagProjectName = resolveStringFlag(input.flags, "name");
|
|
153
|
+
const positionalProjectName = input.args[0]?.trim();
|
|
154
|
+
if (flagProjectName && positionalProjectName && flagProjectName !== positionalProjectName) {
|
|
155
|
+
throw new Error("Conflicting project names. Use either the positional argument or --name, not both.");
|
|
156
|
+
}
|
|
157
|
+
const interactive = isInteractive(io, input.flags);
|
|
158
|
+
const projectName = (flagProjectName ?? positionalProjectName)?.trim() || (interactive ? await prompts.prompt("Project name: ") : "");
|
|
159
|
+
if (!projectName) {
|
|
160
|
+
throw new Error(interactive ? "Project creation cancelled." : "Missing required argument: Project name.");
|
|
161
|
+
}
|
|
162
|
+
const framework = resolveStringFlag(input.flags, "framework") ? normalizeChoice(resolveStringFlag(input.flags, "framework"), SUPPORTED_NEW_FRAMEWORKS, "framework") : interactive ? await prompts.choose("Framework", SUPPORTED_NEW_FRAMEWORKS, "nuxt") : "nuxt";
|
|
163
|
+
const databaseDriver = resolveStringFlag(input.flags, "database") ? normalizeChoice(resolveStringFlag(input.flags, "database"), SUPPORTED_NEW_DATABASE_DRIVERS, "database driver") : interactive ? await prompts.choose("Database driver", SUPPORTED_NEW_DATABASE_DRIVERS, "sqlite") : "sqlite";
|
|
164
|
+
const packageManager = resolveStringFlag(input.flags, "package-manager") ? normalizeChoice(resolveStringFlag(input.flags, "package-manager"), SUPPORTED_NEW_PACKAGE_MANAGERS, "package manager") : interactive ? await prompts.choose("Package manager", SUPPORTED_NEW_PACKAGE_MANAGERS, "bun") : "bun";
|
|
165
|
+
const requestedOptionalPackages = collectMultiStringFlag(input.flags, "package");
|
|
166
|
+
let optionalPackages;
|
|
167
|
+
if (requestedOptionalPackages) {
|
|
168
|
+
const normalizedOptionalPackages = [];
|
|
169
|
+
for (const entry of requestedOptionalPackages) {
|
|
170
|
+
normalizedOptionalPackages.push(...splitCsv(entry));
|
|
171
|
+
}
|
|
172
|
+
optionalPackages = normalizeOptionalPackages(normalizedOptionalPackages);
|
|
173
|
+
} else if (interactive) {
|
|
174
|
+
optionalPackages = await prompts.optionalPackages();
|
|
175
|
+
} else {
|
|
176
|
+
optionalPackages = [];
|
|
177
|
+
}
|
|
178
|
+
const storageDefaultDisk = optionalPackages.includes("storage") ? resolveStringFlag(input.flags, "storage-default-disk") ? normalizeChoice(resolveStringFlag(input.flags, "storage-default-disk"), SUPPORTED_NEW_STORAGE_DISKS, "storage default disk") : interactive ? await prompts.choose("Default storage disk", SUPPORTED_NEW_STORAGE_DISKS, "local") : "local" : "local";
|
|
179
|
+
return {
|
|
180
|
+
projectName,
|
|
181
|
+
framework,
|
|
182
|
+
databaseDriver,
|
|
183
|
+
packageManager,
|
|
184
|
+
storageDefaultDisk,
|
|
185
|
+
optionalPackages
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
async function ensureRequiredArg(io, input, index, label) {
|
|
189
|
+
const value = input.args[index]?.trim();
|
|
190
|
+
if (value) {
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
if (!isInteractive(io, input.flags)) {
|
|
194
|
+
throw new Error(`Missing required argument: ${label}.`);
|
|
195
|
+
}
|
|
196
|
+
const prompted = await prompt(io, `${label}: `);
|
|
197
|
+
if (!prompted) {
|
|
198
|
+
throw new Error(`Missing required argument: ${label}.`);
|
|
199
|
+
}
|
|
200
|
+
return prompted;
|
|
201
|
+
}
|
|
202
|
+
function resolveStringFlag(flags, name, alias) {
|
|
203
|
+
const value = flags[name] ?? (alias ? flags[alias] : void 0);
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
return value[value.length - 1];
|
|
206
|
+
}
|
|
207
|
+
if (typeof value === "string") {
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
function collectMultiStringFlag(flags, name, alias) {
|
|
213
|
+
const value = flags[name] ?? (alias ? flags[alias] : void 0);
|
|
214
|
+
if (Array.isArray(value)) {
|
|
215
|
+
return value.map((entry) => entry.trim()).filter(Boolean);
|
|
216
|
+
}
|
|
217
|
+
if (typeof value === "string") {
|
|
218
|
+
const normalized = value.trim();
|
|
219
|
+
return normalized ? [normalized] : void 0;
|
|
220
|
+
}
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
function resolveBooleanFlag(flags, name, alias) {
|
|
224
|
+
const value = flags[name] ?? (alias ? flags[alias] : void 0);
|
|
225
|
+
if (Array.isArray(value)) {
|
|
226
|
+
return value[value.length - 1] === "true";
|
|
227
|
+
}
|
|
228
|
+
if (typeof value === "string") {
|
|
229
|
+
return value === "true";
|
|
230
|
+
}
|
|
231
|
+
return value === true;
|
|
232
|
+
}
|
|
233
|
+
function parseNumberFlag(flags, name, alias) {
|
|
234
|
+
const raw = resolveStringFlag(flags, name, alias);
|
|
235
|
+
if (typeof raw === "undefined") {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
if (!/^\d+$/.test(raw)) {
|
|
239
|
+
throw new Error(`Flag "--${name}" must be a non-negative integer.`);
|
|
240
|
+
}
|
|
241
|
+
const parsed = Number.parseInt(raw, 10);
|
|
242
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
243
|
+
throw new Error(`Flag "--${name}" must be a non-negative integer.`);
|
|
244
|
+
}
|
|
245
|
+
return parsed;
|
|
246
|
+
}
|
|
247
|
+
function splitCsv(value) {
|
|
248
|
+
if (!value) {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
return value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export {
|
|
255
|
+
SUPPORTED_INSTALL_TARGETS,
|
|
256
|
+
SUPPORTED_QUEUE_INSTALL_DRIVERS,
|
|
257
|
+
SUPPORTED_CACHE_INSTALL_DRIVERS,
|
|
258
|
+
parseTokens,
|
|
259
|
+
isInteractive,
|
|
260
|
+
confirm,
|
|
261
|
+
normalizeChoice,
|
|
262
|
+
normalizeOptionalPackages,
|
|
263
|
+
resolveNewProjectInput,
|
|
264
|
+
ensureRequiredArg,
|
|
265
|
+
resolveStringFlag,
|
|
266
|
+
collectMultiStringFlag,
|
|
267
|
+
resolveBooleanFlag,
|
|
268
|
+
parseNumberFlag,
|
|
269
|
+
splitCsv
|
|
270
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readTextFile
|
|
4
|
+
} from "./chunk-66FHW725.mjs";
|
|
5
|
+
|
|
6
|
+
// src/fs-utils.ts
|
|
7
|
+
import { stat } from "fs/promises";
|
|
8
|
+
async function fileExists(path) {
|
|
9
|
+
try {
|
|
10
|
+
await stat(path);
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function ensureAbsent(path) {
|
|
17
|
+
const existing = await readTextFile(path);
|
|
18
|
+
if (typeof existing !== "undefined") {
|
|
19
|
+
throw new TypeError(`Refusing to overwrite existing file: ${path}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
fileExists,
|
|
25
|
+
ensureAbsent
|
|
26
|
+
};
|