@holo-js/cli 0.1.1 → 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 +10 -9
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_EXTENSION_PRIORITY,
|
|
4
|
+
GENERATED_AUTHORIZATION_REGISTRY_PATH,
|
|
5
|
+
GENERATED_AUTHORIZATION_TYPES_PATH,
|
|
6
|
+
GENERATED_BROADCAST_MANIFEST_PATH,
|
|
7
|
+
GENERATED_BROADCAST_PATH,
|
|
8
|
+
GENERATED_BROADCAST_TYPES_PATH,
|
|
9
|
+
GENERATED_CHANNELS_PATH,
|
|
10
|
+
GENERATED_COMMANDS_PATH,
|
|
11
|
+
GENERATED_CONFIG_TYPES_PATH,
|
|
12
|
+
GENERATED_EVENTS_PATH,
|
|
13
|
+
GENERATED_EVENT_TYPES_PATH,
|
|
14
|
+
GENERATED_GITIGNORE_PATH,
|
|
15
|
+
GENERATED_INDEX_PATH,
|
|
16
|
+
GENERATED_JOBS_PATH,
|
|
17
|
+
GENERATED_LISTENERS_PATH,
|
|
18
|
+
GENERATED_METADATA_PATH,
|
|
19
|
+
GENERATED_MIGRATIONS_PATH,
|
|
20
|
+
GENERATED_MODELS_PATH,
|
|
21
|
+
GENERATED_QUEUE_TYPES_PATH,
|
|
22
|
+
GENERATED_REGISTRY_JSON_PATH,
|
|
23
|
+
GENERATED_ROOT,
|
|
24
|
+
GENERATED_SEEDERS_PATH,
|
|
25
|
+
GENERATED_TSCONFIG_PATH,
|
|
26
|
+
SUPPORTED_CONFIG_EXTENSIONS,
|
|
27
|
+
importProjectModule,
|
|
28
|
+
isRecord,
|
|
29
|
+
makeProjectRelativePath,
|
|
30
|
+
pathExists
|
|
31
|
+
} from "./chunk-66FHW725.mjs";
|
|
32
|
+
|
|
33
|
+
// src/templates.ts
|
|
34
|
+
import { dirname, relative, resolve } from "path";
|
|
35
|
+
function toPosixPath(value) {
|
|
36
|
+
return value.replace(/\\/g, "/");
|
|
37
|
+
}
|
|
38
|
+
function splitRequestedName(value) {
|
|
39
|
+
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
40
|
+
if (!normalized) {
|
|
41
|
+
throw new Error("A name is required.");
|
|
42
|
+
}
|
|
43
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
44
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
45
|
+
throw new Error("Names must stay within the project root.");
|
|
46
|
+
}
|
|
47
|
+
const rawBaseName = segments.pop();
|
|
48
|
+
if (!rawBaseName) {
|
|
49
|
+
throw new Error("A name is required.");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
directory: segments.join("/"),
|
|
53
|
+
rawBaseName
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function toPascalCase(value) {
|
|
57
|
+
return value.replace(/[^a-z0-9]+/gi, " ").split(" ").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
58
|
+
}
|
|
59
|
+
function toSnakeCase(value) {
|
|
60
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
function toKebabCase(value) {
|
|
63
|
+
return toSnakeCase(value).replaceAll("_", "-");
|
|
64
|
+
}
|
|
65
|
+
function pluralize(word) {
|
|
66
|
+
if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) {
|
|
67
|
+
return `${word.slice(0, -1)}ies`;
|
|
68
|
+
}
|
|
69
|
+
if (/(?:[sxz]|ch|sh)$/i.test(word)) {
|
|
70
|
+
return `${word}es`;
|
|
71
|
+
}
|
|
72
|
+
return `${word}s`;
|
|
73
|
+
}
|
|
74
|
+
function ensureSuffix(name, suffix) {
|
|
75
|
+
return name.endsWith(suffix) ? name : `${name}${suffix}`;
|
|
76
|
+
}
|
|
77
|
+
function relativeImportPath(fromFile, toFile) {
|
|
78
|
+
const fromDir = dirname(fromFile);
|
|
79
|
+
const target = toPosixPath(relative(fromDir, toFile)).replace(/\.[^.]+$/, "");
|
|
80
|
+
return target.startsWith(".") ? target : `./${target}`;
|
|
81
|
+
}
|
|
82
|
+
function renderModelTemplate(options) {
|
|
83
|
+
const imports = [
|
|
84
|
+
`import { tables as holoGeneratedTables } from '${options.generatedSchemaImportPath}'`,
|
|
85
|
+
"import { defineModel, type TableDefinition } from '@holo-js/db'"
|
|
86
|
+
];
|
|
87
|
+
if (options.observerImportPath && options.observerClassName) {
|
|
88
|
+
imports.push(`import { ${options.observerClassName} } from '${options.observerImportPath}'`);
|
|
89
|
+
}
|
|
90
|
+
return [
|
|
91
|
+
...imports,
|
|
92
|
+
"",
|
|
93
|
+
`const holoModelTable = (holoGeneratedTables as Partial<Record<string, TableDefinition>>)[${JSON.stringify(options.tableName)}]`,
|
|
94
|
+
"export const holoModelPendingSchema = typeof holoModelTable === 'undefined'",
|
|
95
|
+
"",
|
|
96
|
+
"export default holoModelPendingSchema",
|
|
97
|
+
" ? undefined",
|
|
98
|
+
" : defineModel(",
|
|
99
|
+
" holoModelTable,",
|
|
100
|
+
" {",
|
|
101
|
+
" fillable: [],",
|
|
102
|
+
...options.observerClassName ? [` observers: [${options.observerClassName}],`] : [],
|
|
103
|
+
" },",
|
|
104
|
+
" )",
|
|
105
|
+
""
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
function renderSeederTemplate(seederName) {
|
|
109
|
+
return [
|
|
110
|
+
"import { defineSeeder } from '@holo-js/db'",
|
|
111
|
+
"",
|
|
112
|
+
"export default defineSeeder({",
|
|
113
|
+
` name: '${seederName}',`,
|
|
114
|
+
" async run({ db, schema }) {",
|
|
115
|
+
" void db",
|
|
116
|
+
" void schema",
|
|
117
|
+
" },",
|
|
118
|
+
"})",
|
|
119
|
+
""
|
|
120
|
+
].join("\n");
|
|
121
|
+
}
|
|
122
|
+
function renderObserverTemplate(className) {
|
|
123
|
+
return [
|
|
124
|
+
`export class ${className} {`,
|
|
125
|
+
" created() {}",
|
|
126
|
+
"",
|
|
127
|
+
" updated() {}",
|
|
128
|
+
"",
|
|
129
|
+
" deleted() {}",
|
|
130
|
+
"}",
|
|
131
|
+
"",
|
|
132
|
+
`export default ${className}`,
|
|
133
|
+
""
|
|
134
|
+
].join("\n");
|
|
135
|
+
}
|
|
136
|
+
function renderFactoryTemplate(modelImportPath, modelName) {
|
|
137
|
+
return [
|
|
138
|
+
"import { defineFactory } from '@holo-js/db'",
|
|
139
|
+
`import ${modelName} from '${modelImportPath}'`,
|
|
140
|
+
"",
|
|
141
|
+
`export default defineFactory(${modelName}, () => ({`,
|
|
142
|
+
"}))",
|
|
143
|
+
""
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
function renderJobTemplate() {
|
|
147
|
+
return [
|
|
148
|
+
"import { defineJob } from '@holo-js/queue'",
|
|
149
|
+
"",
|
|
150
|
+
"export default defineJob({",
|
|
151
|
+
" async handle(payload, context) {",
|
|
152
|
+
" void payload",
|
|
153
|
+
" void context",
|
|
154
|
+
" },",
|
|
155
|
+
"})",
|
|
156
|
+
""
|
|
157
|
+
].join("\n");
|
|
158
|
+
}
|
|
159
|
+
function renderEventTemplate(eventName) {
|
|
160
|
+
return [
|
|
161
|
+
"import { defineEvent } from '@holo-js/events'",
|
|
162
|
+
"",
|
|
163
|
+
"export default defineEvent<Record<string, unknown>>({",
|
|
164
|
+
` name: '${eventName}',`,
|
|
165
|
+
"})",
|
|
166
|
+
""
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
169
|
+
function renderBroadcastTemplate(eventName) {
|
|
170
|
+
return [
|
|
171
|
+
"import { channel, defineBroadcast } from '@holo-js/broadcast'",
|
|
172
|
+
"",
|
|
173
|
+
"export default defineBroadcast({",
|
|
174
|
+
` name: '${eventName}',`,
|
|
175
|
+
" channels: [",
|
|
176
|
+
` channel('${eventName}'),`,
|
|
177
|
+
" ],",
|
|
178
|
+
" payload: {},",
|
|
179
|
+
"})",
|
|
180
|
+
""
|
|
181
|
+
].join("\n");
|
|
182
|
+
}
|
|
183
|
+
function renderChannelTemplate(pattern) {
|
|
184
|
+
return [
|
|
185
|
+
"import { defineChannel } from '@holo-js/broadcast'",
|
|
186
|
+
"",
|
|
187
|
+
`export default defineChannel('${pattern}', {`,
|
|
188
|
+
" type: 'private',",
|
|
189
|
+
" authorize() {",
|
|
190
|
+
" return false",
|
|
191
|
+
" },",
|
|
192
|
+
"})",
|
|
193
|
+
""
|
|
194
|
+
].join("\n");
|
|
195
|
+
}
|
|
196
|
+
function renderListenerTemplate(eventImportStatement, eventName) {
|
|
197
|
+
return [
|
|
198
|
+
"import { defineListener } from '@holo-js/events'",
|
|
199
|
+
eventImportStatement,
|
|
200
|
+
"",
|
|
201
|
+
"export default defineListener({",
|
|
202
|
+
` listensTo: [${eventName}],`,
|
|
203
|
+
" async handle(event) {",
|
|
204
|
+
" void event",
|
|
205
|
+
" },",
|
|
206
|
+
"})",
|
|
207
|
+
""
|
|
208
|
+
].join("\n");
|
|
209
|
+
}
|
|
210
|
+
function renderMultiListenerTemplate(events) {
|
|
211
|
+
return [
|
|
212
|
+
"import { defineListener } from '@holo-js/events'",
|
|
213
|
+
...events.map((event) => event.importStatement),
|
|
214
|
+
"",
|
|
215
|
+
"export default defineListener({",
|
|
216
|
+
` listensTo: [${events.map((event) => event.importName).join(", ")}],`,
|
|
217
|
+
" async handle(event) {",
|
|
218
|
+
" void event",
|
|
219
|
+
" },",
|
|
220
|
+
"})",
|
|
221
|
+
""
|
|
222
|
+
].join("\n");
|
|
223
|
+
}
|
|
224
|
+
function renderMarkdownMailTemplate(mailName, inputTypeName) {
|
|
225
|
+
return [
|
|
226
|
+
"import { defineMail } from '@holo-js/mail'",
|
|
227
|
+
"",
|
|
228
|
+
`export type ${inputTypeName} = {`,
|
|
229
|
+
" readonly to: string",
|
|
230
|
+
" readonly name: string",
|
|
231
|
+
"}",
|
|
232
|
+
"",
|
|
233
|
+
`function ${mailName}(input: ${inputTypeName}) {`,
|
|
234
|
+
" return defineMail({",
|
|
235
|
+
" to: input.to,",
|
|
236
|
+
" subject: `Welcome, ${input.name}`,",
|
|
237
|
+
" markdown: [",
|
|
238
|
+
" '# Welcome',",
|
|
239
|
+
" '',",
|
|
240
|
+
" `Hello ${input.name},`,",
|
|
241
|
+
" '',",
|
|
242
|
+
" 'Your mail definition is ready.',",
|
|
243
|
+
" ].join('\\n'),",
|
|
244
|
+
" })",
|
|
245
|
+
"}",
|
|
246
|
+
"",
|
|
247
|
+
`export default ${mailName}`,
|
|
248
|
+
""
|
|
249
|
+
].join("\n");
|
|
250
|
+
}
|
|
251
|
+
function resolveNameInfo(requestedName, options = {}) {
|
|
252
|
+
const parts = splitRequestedName(requestedName);
|
|
253
|
+
const baseName = ensureSuffix(toPascalCase(parts.rawBaseName), options.suffix ?? "");
|
|
254
|
+
const baseStem = options.suffix && baseName.endsWith(options.suffix) ? baseName.slice(0, -options.suffix.length) : baseName;
|
|
255
|
+
const snakeStem = toSnakeCase(baseStem);
|
|
256
|
+
const tableName = pluralize(snakeStem);
|
|
257
|
+
return {
|
|
258
|
+
directory: parts.directory,
|
|
259
|
+
baseName,
|
|
260
|
+
baseStem,
|
|
261
|
+
snakeStem,
|
|
262
|
+
tableName
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function resolveArtifactPath(root, subdir, directory, fileName) {
|
|
266
|
+
return resolve(root, subdir, directory, fileName);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/project/registry.ts
|
|
270
|
+
import { mkdir, readFile, readdir, writeFile } from "fs/promises";
|
|
271
|
+
import { dirname as dirname2, extname, join, resolve as resolve2 } from "path";
|
|
272
|
+
import { loadConfigDirectory } from "@holo-js/config";
|
|
273
|
+
import { DEFAULT_HOLO_PROJECT_PATHS } from "@holo-js/db";
|
|
274
|
+
function renderGeneratedModule(exportName, value) {
|
|
275
|
+
return [
|
|
276
|
+
"// Generated by holo prepare. Do not edit.",
|
|
277
|
+
"",
|
|
278
|
+
`export const ${exportName} = ${JSON.stringify(value, null, 2)}`,
|
|
279
|
+
"",
|
|
280
|
+
`export default ${exportName}`,
|
|
281
|
+
""
|
|
282
|
+
].join("\n");
|
|
283
|
+
}
|
|
284
|
+
function renderGeneratedIndexModule() {
|
|
285
|
+
return [
|
|
286
|
+
"// Generated by holo prepare. Do not edit.",
|
|
287
|
+
"",
|
|
288
|
+
"/* eslint-disable @typescript-eslint/triple-slash-reference */",
|
|
289
|
+
"",
|
|
290
|
+
'/// <reference path="./broadcast.d.ts" />',
|
|
291
|
+
'/// <reference path="./authorization/types.d.ts" />',
|
|
292
|
+
'/// <reference path="./config.d.ts" />',
|
|
293
|
+
'/// <reference path="./events.d.ts" />',
|
|
294
|
+
'/// <reference path="./queue.d.ts" />',
|
|
295
|
+
"",
|
|
296
|
+
"import metadata from './metadata'",
|
|
297
|
+
"import models from './models'",
|
|
298
|
+
"import migrations from './migrations'",
|
|
299
|
+
"import seeders from './seeders'",
|
|
300
|
+
"import commands from './commands'",
|
|
301
|
+
"import jobs from './jobs'",
|
|
302
|
+
"import events from './events'",
|
|
303
|
+
"import listeners from './listeners'",
|
|
304
|
+
"import broadcast from './broadcast'",
|
|
305
|
+
"import channels from './channels'",
|
|
306
|
+
"import authorization from './authorization/registry'",
|
|
307
|
+
"import broadcastManifest from './broadcast-manifest'",
|
|
308
|
+
"",
|
|
309
|
+
"export { metadata, models, migrations, seeders, commands, jobs, events, listeners, broadcast, channels, authorization, broadcastManifest }",
|
|
310
|
+
"",
|
|
311
|
+
"export const registry = {",
|
|
312
|
+
" ...metadata,",
|
|
313
|
+
" models,",
|
|
314
|
+
" migrations,",
|
|
315
|
+
" seeders,",
|
|
316
|
+
" commands,",
|
|
317
|
+
" jobs,",
|
|
318
|
+
" events,",
|
|
319
|
+
" listeners,",
|
|
320
|
+
" broadcast,",
|
|
321
|
+
" channels,",
|
|
322
|
+
" authorizationPolicies: authorization.policies,",
|
|
323
|
+
" authorizationAbilities: authorization.abilities,",
|
|
324
|
+
" broadcastManifest,",
|
|
325
|
+
"}",
|
|
326
|
+
"",
|
|
327
|
+
"export default registry",
|
|
328
|
+
""
|
|
329
|
+
].join("\n");
|
|
330
|
+
}
|
|
331
|
+
function renderAugmentationInterface(name, members) {
|
|
332
|
+
if (members.length === 0) {
|
|
333
|
+
return [
|
|
334
|
+
" // eslint-disable-next-line @typescript-eslint/no-empty-object-type",
|
|
335
|
+
` interface ${name} {}`
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
return [
|
|
339
|
+
` interface ${name} {`,
|
|
340
|
+
...members,
|
|
341
|
+
" }"
|
|
342
|
+
];
|
|
343
|
+
}
|
|
344
|
+
function renderGeneratedBroadcastTypes(broadcast, channels) {
|
|
345
|
+
const typedBroadcastEntries = broadcast.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
346
|
+
const typedChannelEntries = channels.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
347
|
+
const broadcastImportNameByName = new Map(typedBroadcastEntries.map((entry, index) => [entry.name, `holoBroadcastModule${index}`]));
|
|
348
|
+
const channelImportNameByPattern = new Map(typedChannelEntries.map((entry, index) => [entry.pattern, `holoChannelModule${index}`]));
|
|
349
|
+
const broadcastNeedsDefinitionType = broadcast.some((entry) => !broadcastImportNameByName.get(entry.name) || !entry.exportName);
|
|
350
|
+
const broadcastNeedsExportedType = broadcast.some((entry) => Boolean(broadcastImportNameByName.get(entry.name) && entry.exportName));
|
|
351
|
+
const channelNeedsDefinitionType = channels.some((entry) => !channelImportNameByPattern.get(entry.pattern) || !entry.exportName);
|
|
352
|
+
const channelNeedsExportedType = channels.some((entry) => Boolean(channelImportNameByPattern.get(entry.pattern) && entry.exportName));
|
|
353
|
+
const typeImports = [];
|
|
354
|
+
if (broadcastNeedsDefinitionType) typeImports.push("BroadcastDefinition");
|
|
355
|
+
if (channelNeedsDefinitionType) typeImports.push("ChannelDefinition");
|
|
356
|
+
if (broadcastNeedsExportedType) typeImports.push("ExportedBroadcastDefinition");
|
|
357
|
+
if (channelNeedsExportedType) typeImports.push("ExportedChannelDefinition");
|
|
358
|
+
const imports = [];
|
|
359
|
+
if (typeImports.length > 0) {
|
|
360
|
+
imports.push("import type {", ...typeImports.map((typeName) => ` ${typeName},`), "} from '@holo-js/broadcast'");
|
|
361
|
+
}
|
|
362
|
+
imports.push(
|
|
363
|
+
...typedBroadcastEntries.map((entry, index) => {
|
|
364
|
+
return `import type * as holoBroadcastModule${index} from '${relativeImportPath(GENERATED_BROADCAST_TYPES_PATH, entry.sourcePath)}'`;
|
|
365
|
+
}),
|
|
366
|
+
...typedChannelEntries.map((entry, index) => {
|
|
367
|
+
return `import type * as holoChannelModule${index} from '${relativeImportPath(GENERATED_BROADCAST_TYPES_PATH, entry.sourcePath)}'`;
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
const broadcastMembers = broadcast.map((entry) => {
|
|
371
|
+
const importName = broadcastImportNameByName.get(entry.name);
|
|
372
|
+
if (!importName || !entry.exportName) {
|
|
373
|
+
return ` ${JSON.stringify(entry.name)}: BroadcastDefinition`;
|
|
374
|
+
}
|
|
375
|
+
return ` ${JSON.stringify(entry.name)}: ExportedBroadcastDefinition<typeof ${importName}[${JSON.stringify(entry.exportName)}]>`;
|
|
376
|
+
});
|
|
377
|
+
const channelMembers = channels.map((entry) => {
|
|
378
|
+
const importName = channelImportNameByPattern.get(entry.pattern);
|
|
379
|
+
if (!importName || !entry.exportName) {
|
|
380
|
+
return ` ${JSON.stringify(entry.pattern)}: ChannelDefinition`;
|
|
381
|
+
}
|
|
382
|
+
return ` ${JSON.stringify(entry.pattern)}: ExportedChannelDefinition<typeof ${importName}[${JSON.stringify(entry.exportName)}]>`;
|
|
383
|
+
});
|
|
384
|
+
return [
|
|
385
|
+
"// Generated by holo prepare. Do not edit.",
|
|
386
|
+
"",
|
|
387
|
+
...imports,
|
|
388
|
+
...imports.length > 0 ? [""] : [],
|
|
389
|
+
"declare module '@holo-js/broadcast' {",
|
|
390
|
+
...renderAugmentationInterface("HoloBroadcastRegistry", broadcastMembers),
|
|
391
|
+
"",
|
|
392
|
+
...renderAugmentationInterface("HoloChannelRegistry", channelMembers),
|
|
393
|
+
"}",
|
|
394
|
+
"",
|
|
395
|
+
"export {}",
|
|
396
|
+
""
|
|
397
|
+
].join("\n");
|
|
398
|
+
}
|
|
399
|
+
function renderGeneratedBroadcastManifest(registry) {
|
|
400
|
+
const hasPresenceChannels = registry.channels.some((entry) => entry.type === "presence");
|
|
401
|
+
const manifestImports = hasPresenceChannels ? ["import type { ChannelPresenceMemberFor, GeneratedBroadcastManifest } from '@holo-js/broadcast'"] : ["import type { GeneratedBroadcastManifest } from '@holo-js/broadcast'"];
|
|
402
|
+
const eventLines = registry.broadcast.flatMap((entry, index) => {
|
|
403
|
+
const lines = [
|
|
404
|
+
" {",
|
|
405
|
+
` name: ${JSON.stringify(entry.name)},`,
|
|
406
|
+
" channels: [",
|
|
407
|
+
...entry.channels.flatMap((channel, channelIndex) => {
|
|
408
|
+
return [
|
|
409
|
+
" {",
|
|
410
|
+
` type: ${JSON.stringify(channel.type)},`,
|
|
411
|
+
` pattern: ${JSON.stringify(channel.pattern)},`,
|
|
412
|
+
` }${channelIndex < entry.channels.length - 1 ? "," : ""}`
|
|
413
|
+
];
|
|
414
|
+
}),
|
|
415
|
+
" ],",
|
|
416
|
+
` }${index < registry.broadcast.length - 1 ? "," : ""}`
|
|
417
|
+
];
|
|
418
|
+
return lines;
|
|
419
|
+
});
|
|
420
|
+
const channelLines = registry.channels.flatMap((entry, index) => {
|
|
421
|
+
const lines = [
|
|
422
|
+
" {",
|
|
423
|
+
` name: ${JSON.stringify(entry.pattern)},`,
|
|
424
|
+
` pattern: ${JSON.stringify(entry.pattern)},`,
|
|
425
|
+
` type: ${JSON.stringify(entry.type)},`,
|
|
426
|
+
` params: ${JSON.stringify(entry.params)},`,
|
|
427
|
+
` whispers: ${JSON.stringify(entry.whispers)},`
|
|
428
|
+
];
|
|
429
|
+
if (entry.type === "presence") {
|
|
430
|
+
lines.push(` member: undefined as unknown as ChannelPresenceMemberFor<${JSON.stringify(entry.pattern)}>,`);
|
|
431
|
+
}
|
|
432
|
+
lines.push(` }${index < registry.channels.length - 1 ? "," : ""}`);
|
|
433
|
+
return lines;
|
|
434
|
+
});
|
|
435
|
+
return [
|
|
436
|
+
"// Generated by holo prepare. Do not edit.",
|
|
437
|
+
"",
|
|
438
|
+
...manifestImports,
|
|
439
|
+
"",
|
|
440
|
+
"export const broadcastManifest = {",
|
|
441
|
+
" version: 1,",
|
|
442
|
+
` generatedAt: ${JSON.stringify(registry.generatedAt)},`,
|
|
443
|
+
" events: [",
|
|
444
|
+
...eventLines,
|
|
445
|
+
" ],",
|
|
446
|
+
" channels: [",
|
|
447
|
+
...channelLines,
|
|
448
|
+
" ],",
|
|
449
|
+
"} as const satisfies GeneratedBroadcastManifest",
|
|
450
|
+
"",
|
|
451
|
+
"export default broadcastManifest",
|
|
452
|
+
""
|
|
453
|
+
].join("\n");
|
|
454
|
+
}
|
|
455
|
+
function renderGeneratedEventTypes(events, listeners) {
|
|
456
|
+
const typedEventEntries = events.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
457
|
+
const typedListenerEntries = listeners.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
458
|
+
const eventImportNameByName = new Map(typedEventEntries.map((entry, index) => [entry.name, `holoEventModule${index}`]));
|
|
459
|
+
const listenerImportNameById = new Map(typedListenerEntries.map((entry, index) => [entry.id, `holoListenerModule${index}`]));
|
|
460
|
+
const needsEventDefinitionType = events.some((entry) => !eventImportNameByName.get(entry.name) || !entry.exportName);
|
|
461
|
+
const needsExportedEventType = events.some((entry) => Boolean(eventImportNameByName.get(entry.name) && entry.exportName));
|
|
462
|
+
const needsListenerDefinitionType = listeners.some((entry) => !listenerImportNameById.get(entry.id) || !entry.exportName);
|
|
463
|
+
const needsTypedListenerDefinitionType = listeners.some((entry) => Boolean(listenerImportNameById.get(entry.id) && entry.exportName));
|
|
464
|
+
const typeImports = [];
|
|
465
|
+
if (needsEventDefinitionType) typeImports.push("EventDefinition");
|
|
466
|
+
if (needsExportedEventType) typeImports.push("ExportedEventDefinition");
|
|
467
|
+
if (needsListenerDefinitionType || needsTypedListenerDefinitionType) typeImports.push("ListenerDefinition");
|
|
468
|
+
const imports = [];
|
|
469
|
+
if (typeImports.length > 0) {
|
|
470
|
+
imports.push("import type {", ...typeImports.map((typeName) => ` ${typeName},`), "} from '@holo-js/events'");
|
|
471
|
+
}
|
|
472
|
+
imports.push(
|
|
473
|
+
...typedEventEntries.map((entry, index) => {
|
|
474
|
+
return `import type * as holoEventModule${index} from '${relativeImportPath(GENERATED_EVENT_TYPES_PATH, entry.sourcePath)}'`;
|
|
475
|
+
}),
|
|
476
|
+
...typedListenerEntries.map((entry, index) => {
|
|
477
|
+
return `import type * as holoListenerModule${index} from '${relativeImportPath(GENERATED_EVENT_TYPES_PATH, entry.sourcePath)}'`;
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
const eventMembers = events.map((entry) => {
|
|
481
|
+
const importName = eventImportNameByName.get(entry.name);
|
|
482
|
+
if (!importName || !entry.exportName) {
|
|
483
|
+
return ` ${JSON.stringify(entry.name)}: EventDefinition`;
|
|
484
|
+
}
|
|
485
|
+
return ` ${JSON.stringify(entry.name)}: ExportedEventDefinition<typeof ${importName}[${JSON.stringify(entry.exportName)}]>`;
|
|
486
|
+
});
|
|
487
|
+
const listenerMembers = listeners.map((entry) => {
|
|
488
|
+
const importName = listenerImportNameById.get(entry.id);
|
|
489
|
+
if (!importName || !entry.exportName) {
|
|
490
|
+
return ` ${JSON.stringify(entry.id)}: ListenerDefinition`;
|
|
491
|
+
}
|
|
492
|
+
return ` ${JSON.stringify(entry.id)}: Extract<typeof ${importName}[${JSON.stringify(entry.exportName)}], ListenerDefinition>`;
|
|
493
|
+
});
|
|
494
|
+
return [
|
|
495
|
+
"// Generated by holo prepare. Do not edit.",
|
|
496
|
+
"",
|
|
497
|
+
...imports,
|
|
498
|
+
...imports.length > 0 ? [""] : [],
|
|
499
|
+
"declare module '@holo-js/events' {",
|
|
500
|
+
...renderAugmentationInterface("HoloEventRegistry", eventMembers),
|
|
501
|
+
"",
|
|
502
|
+
...renderAugmentationInterface("HoloListenerRegistry", listenerMembers),
|
|
503
|
+
"}",
|
|
504
|
+
"",
|
|
505
|
+
"export {}",
|
|
506
|
+
""
|
|
507
|
+
].join("\n");
|
|
508
|
+
}
|
|
509
|
+
function renderGeneratedQueueTypes(jobs) {
|
|
510
|
+
const typedJobs = jobs.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
511
|
+
const typeImportNameByJob = new Map(
|
|
512
|
+
typedJobs.map((entry, index) => {
|
|
513
|
+
return [entry.name, `holoQueueJobModule${index}`];
|
|
514
|
+
})
|
|
515
|
+
);
|
|
516
|
+
const needsQueueJobDefinitionType = jobs.some((entry) => !typeImportNameByJob.get(entry.name) || !entry.exportName);
|
|
517
|
+
const needsExportedQueueJobDefinitionType = jobs.some((entry) => Boolean(typeImportNameByJob.get(entry.name) && entry.exportName));
|
|
518
|
+
const imports = typedJobs.map((entry, index) => {
|
|
519
|
+
return `import type * as holoQueueJobModule${index} from '${relativeImportPath(GENERATED_QUEUE_TYPES_PATH, entry.sourcePath)}'`;
|
|
520
|
+
});
|
|
521
|
+
const queueTypeImports = [];
|
|
522
|
+
if (needsQueueJobDefinitionType) queueTypeImports.push("QueueJobDefinition");
|
|
523
|
+
if (needsExportedQueueJobDefinitionType) queueTypeImports.push("ExportedQueueJobDefinition");
|
|
524
|
+
const members = jobs.map((entry) => {
|
|
525
|
+
const importName = typeImportNameByJob.get(entry.name);
|
|
526
|
+
if (!importName || !entry.exportName) {
|
|
527
|
+
return ` ${JSON.stringify(entry.name)}: QueueJobDefinition`;
|
|
528
|
+
}
|
|
529
|
+
return ` ${JSON.stringify(entry.name)}: ExportedQueueJobDefinition<typeof ${importName}[${JSON.stringify(entry.exportName)}]>`;
|
|
530
|
+
});
|
|
531
|
+
return [
|
|
532
|
+
"// Generated by holo prepare. Do not edit.",
|
|
533
|
+
"",
|
|
534
|
+
...queueTypeImports.length > 0 ? ["import type {", ...queueTypeImports.map((typeName) => ` ${typeName},`), "} from '@holo-js/queue'"] : [],
|
|
535
|
+
...queueTypeImports.length > 0 ? [""] : [],
|
|
536
|
+
...imports,
|
|
537
|
+
...imports.length > 0 ? [""] : [],
|
|
538
|
+
"declare module '@holo-js/queue' {",
|
|
539
|
+
...renderAugmentationInterface("HoloQueueJobRegistry", members),
|
|
540
|
+
"}",
|
|
541
|
+
"",
|
|
542
|
+
"export {}",
|
|
543
|
+
""
|
|
544
|
+
].join("\n");
|
|
545
|
+
}
|
|
546
|
+
function renderGeneratedAuthorizationRegistry(registry) {
|
|
547
|
+
return [
|
|
548
|
+
"// Generated by holo prepare. Do not edit.",
|
|
549
|
+
"",
|
|
550
|
+
"export const authorization = {",
|
|
551
|
+
` generatedAt: ${JSON.stringify(registry.generatedAt)},`,
|
|
552
|
+
" policies: [",
|
|
553
|
+
...registry.authorizationPolicies.flatMap((entry, index) => {
|
|
554
|
+
return [
|
|
555
|
+
" {",
|
|
556
|
+
` sourcePath: ${JSON.stringify(entry.sourcePath)},`,
|
|
557
|
+
` name: ${JSON.stringify(entry.name)},`,
|
|
558
|
+
...entry.exportName ? [` exportName: ${JSON.stringify(entry.exportName)},`] : [],
|
|
559
|
+
` target: ${JSON.stringify(entry.target)},`,
|
|
560
|
+
` classActions: ${JSON.stringify(entry.classActions)},`,
|
|
561
|
+
` recordActions: ${JSON.stringify(entry.recordActions)},`,
|
|
562
|
+
` }${index < registry.authorizationPolicies.length - 1 ? "," : ""}`
|
|
563
|
+
];
|
|
564
|
+
}),
|
|
565
|
+
" ],",
|
|
566
|
+
" abilities: [",
|
|
567
|
+
...registry.authorizationAbilities.flatMap((entry, index) => {
|
|
568
|
+
return [
|
|
569
|
+
" {",
|
|
570
|
+
` sourcePath: ${JSON.stringify(entry.sourcePath)},`,
|
|
571
|
+
` name: ${JSON.stringify(entry.name)},`,
|
|
572
|
+
...entry.exportName ? [` exportName: ${JSON.stringify(entry.exportName)},`] : [],
|
|
573
|
+
` }${index < registry.authorizationAbilities.length - 1 ? "," : ""}`
|
|
574
|
+
];
|
|
575
|
+
}),
|
|
576
|
+
" ],",
|
|
577
|
+
"} as const",
|
|
578
|
+
"",
|
|
579
|
+
"export default authorization",
|
|
580
|
+
""
|
|
581
|
+
].join("\n");
|
|
582
|
+
}
|
|
583
|
+
function renderGeneratedAuthorizationTypes(authorizationPolicies, authorizationAbilities, guardNames) {
|
|
584
|
+
const typedPolicyEntries = authorizationPolicies.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
585
|
+
const typedAbilityEntries = authorizationAbilities.filter((entry) => [".ts", ".mts", ".cts"].includes(extname(entry.sourcePath)));
|
|
586
|
+
const policyImportNameByName = new Map(typedPolicyEntries.map((entry, index) => [entry.name, `holoAuthorizationPolicyModule${index}`]));
|
|
587
|
+
const abilityImportNameByName = new Map(typedAbilityEntries.map((entry, index) => [entry.name, `holoAuthorizationAbilityModule${index}`]));
|
|
588
|
+
const imports = [
|
|
589
|
+
...typedPolicyEntries.map((entry, index) => {
|
|
590
|
+
return `import type * as holoAuthorizationPolicyModule${index} from '${relativeImportPath(GENERATED_AUTHORIZATION_TYPES_PATH, entry.sourcePath)}'`;
|
|
591
|
+
}),
|
|
592
|
+
...typedAbilityEntries.map((entry, index) => {
|
|
593
|
+
return `import type * as holoAuthorizationAbilityModule${index} from '${relativeImportPath(GENERATED_AUTHORIZATION_TYPES_PATH, entry.sourcePath)}'`;
|
|
594
|
+
})
|
|
595
|
+
];
|
|
596
|
+
const policyMembers = authorizationPolicies.map((entry) => {
|
|
597
|
+
const importName = policyImportNameByName.get(entry.name);
|
|
598
|
+
const classActionEntries = entry.classActions.length > 0 ? entry.classActions.map((action) => ` ${JSON.stringify(action)}: true`) : [];
|
|
599
|
+
const recordActionEntries = entry.recordActions.length > 0 ? entry.recordActions.map((action) => ` ${JSON.stringify(action)}: true`) : [];
|
|
600
|
+
if (!importName || !entry.exportName) {
|
|
601
|
+
return [
|
|
602
|
+
` ${JSON.stringify(entry.name)}: {`,
|
|
603
|
+
" actor: object",
|
|
604
|
+
` target: object`,
|
|
605
|
+
" classActions: {",
|
|
606
|
+
...classActionEntries,
|
|
607
|
+
" }",
|
|
608
|
+
" recordActions: {",
|
|
609
|
+
...recordActionEntries,
|
|
610
|
+
" }",
|
|
611
|
+
" }"
|
|
612
|
+
].join("\n");
|
|
613
|
+
}
|
|
614
|
+
return [
|
|
615
|
+
` ${JSON.stringify(entry.name)}: {`,
|
|
616
|
+
" actor: object",
|
|
617
|
+
` target: typeof ${importName}[${JSON.stringify(entry.exportName)}] extends import('@holo-js/authorization/contracts').AuthorizationPolicyDefinition<infer _TName, infer TTarget, infer _TClassActions, infer _TRecordActions, infer _TActor> ? TTarget : object`,
|
|
618
|
+
` classActions: typeof ${importName}[${JSON.stringify(entry.exportName)}] extends import('@holo-js/authorization/contracts').AuthorizationPolicyDefinition<infer _TName, infer _TTarget, infer TClassActions, infer _TRecordActions, infer _TActor> ? {`,
|
|
619
|
+
...classActionEntries,
|
|
620
|
+
" } : {",
|
|
621
|
+
...classActionEntries,
|
|
622
|
+
" }",
|
|
623
|
+
` recordActions: typeof ${importName}[${JSON.stringify(entry.exportName)}] extends import('@holo-js/authorization/contracts').AuthorizationPolicyDefinition<infer _TName, infer _TTarget, infer _TClassActions, infer TRecordActions, infer _TActor> ? {`,
|
|
624
|
+
...recordActionEntries,
|
|
625
|
+
" } : {",
|
|
626
|
+
...recordActionEntries,
|
|
627
|
+
" }",
|
|
628
|
+
" }"
|
|
629
|
+
].join("\n");
|
|
630
|
+
});
|
|
631
|
+
const abilityMembers = authorizationAbilities.map((entry) => {
|
|
632
|
+
const importName = abilityImportNameByName.get(entry.name);
|
|
633
|
+
if (!importName || !entry.exportName) {
|
|
634
|
+
return ` ${JSON.stringify(entry.name)}: { actor: object, input: object }`;
|
|
635
|
+
}
|
|
636
|
+
return [
|
|
637
|
+
` ${JSON.stringify(entry.name)}: {`,
|
|
638
|
+
" actor: object",
|
|
639
|
+
` input: typeof ${importName}[${JSON.stringify(entry.exportName)}] extends import('@holo-js/authorization/contracts').AuthorizationAbilityDefinition<infer _TName, infer TInput, infer _TActor> ? TInput : object`,
|
|
640
|
+
" }"
|
|
641
|
+
].join("\n");
|
|
642
|
+
});
|
|
643
|
+
const guardMembers = guardNames.map((name) => {
|
|
644
|
+
return [
|
|
645
|
+
` ${JSON.stringify(name)}: {`,
|
|
646
|
+
` user: import('@holo-js/auth').AuthUser`,
|
|
647
|
+
" }"
|
|
648
|
+
].join("\n");
|
|
649
|
+
});
|
|
650
|
+
return [
|
|
651
|
+
"// Generated by holo prepare. Do not edit.",
|
|
652
|
+
"",
|
|
653
|
+
...imports.length > 0 ? [...imports, ""] : [],
|
|
654
|
+
"declare module '@holo-js/authorization/contracts' {",
|
|
655
|
+
...renderAugmentationInterface("AuthorizationPolicyRegistry", policyMembers),
|
|
656
|
+
"",
|
|
657
|
+
...renderAugmentationInterface("AuthorizationAbilityRegistry", abilityMembers),
|
|
658
|
+
"",
|
|
659
|
+
...renderAugmentationInterface("AuthorizationGuardRegistry", guardMembers),
|
|
660
|
+
"}",
|
|
661
|
+
"",
|
|
662
|
+
"export {}",
|
|
663
|
+
""
|
|
664
|
+
].join("\n");
|
|
665
|
+
}
|
|
666
|
+
function renderGeneratedTsconfig() {
|
|
667
|
+
return `${JSON.stringify({
|
|
668
|
+
extends: "../../tsconfig.json",
|
|
669
|
+
include: ["./**/*.ts", "./**/*.d.ts"]
|
|
670
|
+
}, null, 2)}
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
function getConfigExtensionPriority(fileName) {
|
|
674
|
+
const extension = extname(fileName);
|
|
675
|
+
const index = CONFIG_EXTENSION_PRIORITY.indexOf(extension);
|
|
676
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
677
|
+
}
|
|
678
|
+
async function collectProjectConfigEntries(projectRoot) {
|
|
679
|
+
const configDir = resolve2(projectRoot, "config");
|
|
680
|
+
const entries = await readdir(configDir, { withFileTypes: true }).catch(() => []);
|
|
681
|
+
const selectedByName = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const entry of entries) {
|
|
683
|
+
if (!entry.isFile()) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
const extension = extname(entry.name);
|
|
687
|
+
if (!SUPPORTED_CONFIG_EXTENSIONS.has(extension)) {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
const configName = entry.name.slice(0, entry.name.length - extension.length);
|
|
691
|
+
const filePath = join(configDir, entry.name);
|
|
692
|
+
const priority = getConfigExtensionPriority(entry.name);
|
|
693
|
+
const current = selectedByName.get(configName);
|
|
694
|
+
if (!current || priority < current.priority) {
|
|
695
|
+
selectedByName.set(configName, { filePath, priority });
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return [...selectedByName.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([configName, entry]) => ({
|
|
699
|
+
configName,
|
|
700
|
+
filePath: entry.filePath
|
|
701
|
+
}));
|
|
702
|
+
}
|
|
703
|
+
function renderGeneratedConfigTypes(projectRoot, entries) {
|
|
704
|
+
const customEntries = entries.filter((entry) => !["app", "database", "redis", "cache", "storage", "queue", "broadcast", "notifications", "mail", "media", "session", "security", "auth"].includes(entry.configName));
|
|
705
|
+
if (customEntries.length === 0) {
|
|
706
|
+
return [
|
|
707
|
+
"// Generated by holo prepare. Do not edit.",
|
|
708
|
+
"",
|
|
709
|
+
"declare module '@holo-js/config' {",
|
|
710
|
+
...renderAugmentationInterface("HoloConfigRegistry", []),
|
|
711
|
+
"}",
|
|
712
|
+
"",
|
|
713
|
+
"export {}",
|
|
714
|
+
""
|
|
715
|
+
].join("\n");
|
|
716
|
+
}
|
|
717
|
+
const imports = customEntries.map((entry, index) => {
|
|
718
|
+
return `import type holoConfig${index} from '${relativeImportPath(GENERATED_CONFIG_TYPES_PATH, makeProjectRelativePath(projectRoot, entry.filePath))}'`;
|
|
719
|
+
});
|
|
720
|
+
const members = customEntries.map((entry, index) => {
|
|
721
|
+
return ` ${JSON.stringify(entry.configName)}: typeof holoConfig${index}`;
|
|
722
|
+
});
|
|
723
|
+
return [
|
|
724
|
+
"// Generated by holo prepare. Do not edit.",
|
|
725
|
+
"",
|
|
726
|
+
...imports,
|
|
727
|
+
"",
|
|
728
|
+
"declare module '@holo-js/config' {",
|
|
729
|
+
...renderAugmentationInterface("HoloConfigRegistry", members),
|
|
730
|
+
"}",
|
|
731
|
+
"",
|
|
732
|
+
"export {}",
|
|
733
|
+
""
|
|
734
|
+
].join("\n");
|
|
735
|
+
}
|
|
736
|
+
async function writeFileIfChanged(path, contents) {
|
|
737
|
+
try {
|
|
738
|
+
if (await readFile(path, "utf8") === contents) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
744
|
+
await writeFile(path, contents, "utf8");
|
|
745
|
+
}
|
|
746
|
+
function stripGeneratedAt(registry) {
|
|
747
|
+
const { generatedAt: _generatedAt, ...stableRegistry } = registry;
|
|
748
|
+
return stableRegistry;
|
|
749
|
+
}
|
|
750
|
+
async function ensureGeneratedRegistryOwnership(projectRoot) {
|
|
751
|
+
await mkdir(resolve2(projectRoot, GENERATED_ROOT), { recursive: true });
|
|
752
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_GITIGNORE_PATH), "*\n!.gitignore\n");
|
|
753
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_TSCONFIG_PATH), renderGeneratedTsconfig());
|
|
754
|
+
}
|
|
755
|
+
async function writeGeneratedProjectRegistry(projectRoot, registry) {
|
|
756
|
+
await ensureGeneratedRegistryOwnership(projectRoot);
|
|
757
|
+
const loadedConfig = await loadConfigDirectory(projectRoot);
|
|
758
|
+
const configEntries = await collectProjectConfigEntries(projectRoot);
|
|
759
|
+
const existingRegistry = await loadGeneratedProjectRegistry(projectRoot);
|
|
760
|
+
const nextRegistry = existingRegistry && JSON.stringify(stripGeneratedAt(existingRegistry)) === JSON.stringify(stripGeneratedAt(registry)) ? {
|
|
761
|
+
...registry,
|
|
762
|
+
generatedAt: existingRegistry.generatedAt
|
|
763
|
+
} : registry;
|
|
764
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_METADATA_PATH), renderGeneratedModule("metadata", {
|
|
765
|
+
version: nextRegistry.version,
|
|
766
|
+
generatedAt: nextRegistry.generatedAt,
|
|
767
|
+
paths: nextRegistry.paths
|
|
768
|
+
}));
|
|
769
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_MODELS_PATH), renderGeneratedModule("models", nextRegistry.models));
|
|
770
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_MIGRATIONS_PATH), renderGeneratedModule("migrations", nextRegistry.migrations));
|
|
771
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_SEEDERS_PATH), renderGeneratedModule("seeders", nextRegistry.seeders));
|
|
772
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_COMMANDS_PATH), renderGeneratedModule("commands", nextRegistry.commands));
|
|
773
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_JOBS_PATH), renderGeneratedModule("jobs", nextRegistry.jobs));
|
|
774
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_EVENTS_PATH), renderGeneratedModule("events", nextRegistry.events));
|
|
775
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_LISTENERS_PATH), renderGeneratedModule("listeners", nextRegistry.listeners));
|
|
776
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_BROADCAST_PATH), renderGeneratedModule("broadcast", nextRegistry.broadcast));
|
|
777
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_CHANNELS_PATH), renderGeneratedModule("channels", nextRegistry.channels));
|
|
778
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_BROADCAST_MANIFEST_PATH), renderGeneratedBroadcastManifest(nextRegistry));
|
|
779
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_BROADCAST_TYPES_PATH), renderGeneratedBroadcastTypes(nextRegistry.broadcast, nextRegistry.channels));
|
|
780
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_CONFIG_TYPES_PATH), renderGeneratedConfigTypes(projectRoot, configEntries));
|
|
781
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_EVENT_TYPES_PATH), renderGeneratedEventTypes(nextRegistry.events, nextRegistry.listeners));
|
|
782
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_QUEUE_TYPES_PATH), renderGeneratedQueueTypes(nextRegistry.jobs));
|
|
783
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_AUTHORIZATION_REGISTRY_PATH), renderGeneratedAuthorizationRegistry(nextRegistry));
|
|
784
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_AUTHORIZATION_TYPES_PATH), renderGeneratedAuthorizationTypes(
|
|
785
|
+
nextRegistry.authorizationPolicies,
|
|
786
|
+
nextRegistry.authorizationAbilities,
|
|
787
|
+
Object.keys(loadedConfig?.auth?.guards ?? {}).sort((left, right) => left.localeCompare(right))
|
|
788
|
+
));
|
|
789
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_INDEX_PATH), renderGeneratedIndexModule());
|
|
790
|
+
await writeFileIfChanged(resolve2(projectRoot, GENERATED_REGISTRY_JSON_PATH), `${JSON.stringify(nextRegistry, null, 2)}
|
|
791
|
+
`);
|
|
792
|
+
}
|
|
793
|
+
function isGeneratedProjectRegistry(value) {
|
|
794
|
+
if (isRecord(value) && isRecord(value.paths)) {
|
|
795
|
+
value.paths.events ??= DEFAULT_HOLO_PROJECT_PATHS.events;
|
|
796
|
+
value.paths.listeners ??= DEFAULT_HOLO_PROJECT_PATHS.listeners;
|
|
797
|
+
value.paths.broadcast ??= "server/broadcast";
|
|
798
|
+
value.paths.channels ??= "server/channels";
|
|
799
|
+
value.paths.authorizationPolicies ??= "server/policies";
|
|
800
|
+
value.paths.authorizationAbilities ??= "server/abilities";
|
|
801
|
+
}
|
|
802
|
+
if (isRecord(value)) {
|
|
803
|
+
value.events ??= [];
|
|
804
|
+
value.listeners ??= [];
|
|
805
|
+
value.broadcast ??= [];
|
|
806
|
+
value.channels ??= [];
|
|
807
|
+
value.authorizationPolicies ??= [];
|
|
808
|
+
value.authorizationAbilities ??= [];
|
|
809
|
+
}
|
|
810
|
+
return isRecord(value) && value.version === 1 && isRecord(value.paths) && Array.isArray(value.models) && Array.isArray(value.migrations) && Array.isArray(value.seeders) && Array.isArray(value.commands) && Array.isArray(value.jobs) && Array.isArray(value.events) && Array.isArray(value.listeners) && Array.isArray(value.broadcast) && Array.isArray(value.channels) && Array.isArray(value.authorizationPolicies) && Array.isArray(value.authorizationAbilities);
|
|
811
|
+
}
|
|
812
|
+
async function loadGeneratedProjectRegistry(projectRoot) {
|
|
813
|
+
const filePath = resolve2(projectRoot, GENERATED_INDEX_PATH);
|
|
814
|
+
if (!await pathExists(filePath)) {
|
|
815
|
+
return void 0;
|
|
816
|
+
}
|
|
817
|
+
const moduleValue = await importProjectModule(projectRoot, filePath);
|
|
818
|
+
if (isRecord(moduleValue) && isGeneratedProjectRegistry(moduleValue.default)) {
|
|
819
|
+
return moduleValue.default;
|
|
820
|
+
}
|
|
821
|
+
if (isRecord(moduleValue) && isGeneratedProjectRegistry(moduleValue.registry)) {
|
|
822
|
+
return moduleValue.registry;
|
|
823
|
+
}
|
|
824
|
+
return void 0;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export {
|
|
828
|
+
splitRequestedName,
|
|
829
|
+
toPascalCase,
|
|
830
|
+
toSnakeCase,
|
|
831
|
+
toKebabCase,
|
|
832
|
+
ensureSuffix,
|
|
833
|
+
relativeImportPath,
|
|
834
|
+
renderModelTemplate,
|
|
835
|
+
renderSeederTemplate,
|
|
836
|
+
renderObserverTemplate,
|
|
837
|
+
renderFactoryTemplate,
|
|
838
|
+
renderJobTemplate,
|
|
839
|
+
renderEventTemplate,
|
|
840
|
+
renderBroadcastTemplate,
|
|
841
|
+
renderChannelTemplate,
|
|
842
|
+
renderListenerTemplate,
|
|
843
|
+
renderMultiListenerTemplate,
|
|
844
|
+
renderMarkdownMailTemplate,
|
|
845
|
+
resolveNameInfo,
|
|
846
|
+
resolveArtifactPath,
|
|
847
|
+
writeGeneratedProjectRegistry,
|
|
848
|
+
loadGeneratedProjectRegistry
|
|
849
|
+
};
|