@holo-js/cli 0.1.4 → 0.1.6
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 +192 -35
- package/dist/{broadcast-CSSARTSA.mjs → broadcast-2AZIC5ZP.mjs} +5 -5
- package/dist/{cache-4G6QGIZO.mjs → cache-5OROX4GL.mjs} +5 -5
- package/dist/{cache-migrations-NATT5WPQ.mjs → cache-migrations-7XFVLTOC.mjs} +15 -16
- package/dist/{chunk-EUIVXVJL.mjs → chunk-57SJ566R.mjs} +1 -1
- package/dist/chunk-5BLEC66P.mjs +284 -0
- package/dist/{chunk-JX2ZH6XY.mjs → chunk-5EU32E7X.mjs} +3 -3
- package/dist/{chunk-Q5F6C2D4.mjs → chunk-BAFQ2GOA.mjs} +1 -1
- package/dist/{chunk-CUL4RJTG.mjs → chunk-F4MT6GBK.mjs} +1 -1
- package/dist/{chunk-3OTCSFDG.mjs → chunk-MXKNQACM.mjs} +544 -82
- package/dist/{chunk-ZLRO7HXY.mjs → chunk-ODJA3TFG.mjs} +156 -15
- package/dist/{chunk-QYLSMF7V.mjs → chunk-OZUDZEAW.mjs} +142 -28
- package/dist/{chunk-66FHW725.mjs → chunk-R6BWRY3E.mjs} +28 -2
- package/dist/{chunk-MZXN2YMI.mjs → chunk-USACXIIB.mjs} +3544 -2522
- package/dist/{chunk-VT5IDQG6.mjs → chunk-UZTDQKIY.mjs} +61 -44
- package/dist/{config-LS5USBRB.mjs → config-5JSC6KJG.mjs} +3 -3
- package/dist/{dev-LZ3O2E3U.mjs → dev-F6QUWNCR.mjs} +7 -7
- package/dist/{discovery-GBLAUTXS.mjs → discovery-JLT2EOGH.mjs} +3 -3
- package/dist/{generators-DSN4GWJI.mjs → generators-WVKJLAYB.mjs} +134 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +189 -32
- package/dist/media-migrations-DU7WEKAY.mjs +117 -0
- package/dist/{queue-FV35LLPR.mjs → queue-NOLVWPCH.mjs} +14 -14
- package/dist/{queue-migrations-SSIYKK5S.mjs → queue-migrations-HXNTZMGL.mjs} +24 -20
- package/dist/{runtime-EFZ5H5IL.mjs → runtime-462O2BDR.mjs} +9 -7
- package/dist/{runtime-OOSJ5JBY.mjs → runtime-ZKD6URAV.mjs} +1 -1
- package/dist/{scaffold-7OTDH4UR.mjs → scaffold-WOJV2ZZI.mjs} +18 -5
- package/dist/{security-ATKDC26E.mjs → security-5VGM467J.mjs} +10 -7
- package/package.json +13 -12
- package/dist/broadcast-YSIJCL3R.mjs +0 -85
- package/dist/cache-OWQY4E7W.mjs +0 -67
- package/dist/cache-migrations-RVEA6CEU.mjs +0 -155
- package/dist/chunk-BWW5TDFI.mjs +0 -4
- package/dist/chunk-D4GG556Y.mjs +0 -23
- package/dist/chunk-DMH2B4UQ.mjs +0 -343
- package/dist/chunk-ET7UXHHQ.mjs +0 -166
- package/dist/chunk-G5ADO27Q.mjs +0 -463
- package/dist/chunk-GSQ3HTRO.mjs +0 -165
- package/dist/chunk-H7TJ4FB3.mjs +0 -848
- package/dist/chunk-ICJR7TS4.mjs +0 -66
- package/dist/chunk-M7J3YTHR.mjs +0 -26
- package/dist/chunk-QFUSWV3J.mjs +0 -3237
- package/dist/chunk-S7P7EBM3.mjs +0 -787
- package/dist/chunk-SRWJU3A5.mjs +0 -11
- package/dist/chunk-URK7C3VQ.mjs +0 -538
- package/dist/chunk-XUYKPU5Q.mjs +0 -272
- package/dist/config-DMWBMMGD.mjs +0 -26
- package/dist/dev-KQFT7RHR.mjs +0 -43
- package/dist/discovery-R733D2PO.mjs +0 -29
- package/dist/generators-WX45BI4U.mjs +0 -426
- package/dist/queue-6OG7VJ34.mjs +0 -626
- package/dist/queue-migrations-NK2EYX3J.mjs +0 -163
- package/dist/runtime-4BV3JODY.mjs +0 -56
- package/dist/runtime-ANBO7VQM.mjs +0 -33
- package/dist/scaffold-DRKBGS2K.mjs +0 -120
- package/dist/security-R7VH6W5H.mjs +0 -69
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import {
|
|
3
2
|
loadGeneratedProjectRegistry,
|
|
4
3
|
writeGeneratedProjectRegistry
|
|
5
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MXKNQACM.mjs";
|
|
6
5
|
import {
|
|
7
6
|
COMMAND_FILE_PATTERN,
|
|
8
7
|
MIGRATION_NAME_PATTERN,
|
|
@@ -18,15 +17,18 @@ import {
|
|
|
18
17
|
pathExists,
|
|
19
18
|
readTextFile,
|
|
20
19
|
toPosixPath
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-R6BWRY3E.mjs";
|
|
22
21
|
|
|
23
22
|
// src/project/discovery.ts
|
|
24
|
-
import {
|
|
25
|
-
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
23
|
+
import { resolve as resolve2 } from "path";
|
|
26
24
|
import { loadConfigDirectory } from "@holo-js/config";
|
|
27
25
|
import {
|
|
28
26
|
normalizeHoloProjectConfig
|
|
29
27
|
} from "@holo-js/db";
|
|
28
|
+
|
|
29
|
+
// src/project/discovery-helpers.ts
|
|
30
|
+
import { readdir } from "fs/promises";
|
|
31
|
+
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
30
32
|
async function collectFiles(root) {
|
|
31
33
|
if (!await pathExists(root)) {
|
|
32
34
|
return [];
|
|
@@ -337,23 +339,44 @@ function resolveBroadcastArtifactsPath(config, key) {
|
|
|
337
339
|
const configuredPaths = config.paths;
|
|
338
340
|
return configuredPaths[key] ?? `server/${key}`;
|
|
339
341
|
}
|
|
342
|
+
function inferMigrationNameFromEntry(entry) {
|
|
343
|
+
const fileName = basename(entry, extname(entry));
|
|
344
|
+
return validateMigrationName(
|
|
345
|
+
fileName,
|
|
346
|
+
`Registered migration "${entry}" must use a timestamped file name matching YYYY_MM_DD_HHMMSS_description.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
function validateMigrationName(name, message) {
|
|
350
|
+
if (!MIGRATION_NAME_PATTERN.test(name)) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
message ?? `Migration name "${name}" must match YYYY_MM_DD_HHMMSS_description.`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return name;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/project/discovery.ts
|
|
340
359
|
async function prepareProjectDiscovery(projectRoot, config = normalizeHoloProjectConfig()) {
|
|
341
360
|
const loadedConfig = await loadConfigDirectory(projectRoot, {
|
|
342
361
|
processEnv: process.env
|
|
343
362
|
});
|
|
344
|
-
const modelsRoot =
|
|
345
|
-
const migrationsRoot =
|
|
346
|
-
const seedersRoot =
|
|
347
|
-
const commandsRoot =
|
|
348
|
-
const jobsRoot =
|
|
349
|
-
const eventsRoot =
|
|
350
|
-
const listenersRoot =
|
|
363
|
+
const modelsRoot = resolve2(projectRoot, config.paths.models);
|
|
364
|
+
const migrationsRoot = resolve2(projectRoot, config.paths.migrations);
|
|
365
|
+
const seedersRoot = resolve2(projectRoot, config.paths.seeders);
|
|
366
|
+
const commandsRoot = resolve2(projectRoot, config.paths.commands);
|
|
367
|
+
const jobsRoot = resolve2(projectRoot, config.paths.jobs);
|
|
368
|
+
const eventsRoot = resolve2(projectRoot, config.paths.events);
|
|
369
|
+
const listenersRoot = resolve2(projectRoot, config.paths.listeners);
|
|
351
370
|
const broadcastPath = resolveBroadcastArtifactsPath(config, "broadcast");
|
|
352
371
|
const channelsPath = resolveBroadcastArtifactsPath(config, "channels");
|
|
353
|
-
const broadcastRoot =
|
|
354
|
-
const channelsRoot =
|
|
355
|
-
const policiesRoot =
|
|
356
|
-
const abilitiesRoot =
|
|
372
|
+
const broadcastRoot = resolve2(projectRoot, broadcastPath);
|
|
373
|
+
const channelsRoot = resolve2(projectRoot, channelsPath);
|
|
374
|
+
const policiesRoot = resolve2(projectRoot, config.paths.authorizationPolicies ?? "server/policies");
|
|
375
|
+
const abilitiesRoot = resolve2(projectRoot, config.paths.authorizationAbilities ?? "server/abilities");
|
|
376
|
+
const generatedSchemaPath = resolve2(projectRoot, config.paths.generatedSchema);
|
|
377
|
+
if (await pathExists(generatedSchemaPath)) {
|
|
378
|
+
await importProjectModule(projectRoot, generatedSchemaPath);
|
|
379
|
+
}
|
|
357
380
|
const [modelFiles, migrationFiles, seederFiles, commandFiles, jobFiles, eventFiles, listenerFiles, broadcastFiles, channelFiles] = await Promise.all([
|
|
358
381
|
collectFiles(modelsRoot),
|
|
359
382
|
collectFiles(migrationsRoot),
|
|
@@ -374,8 +397,8 @@ async function prepareProjectDiscovery(projectRoot, config = normalizeHoloProjec
|
|
|
374
397
|
const relativePath = makeProjectRelativePath(projectRoot, filePath);
|
|
375
398
|
try {
|
|
376
399
|
const moduleValue = await importProjectModule(projectRoot, filePath);
|
|
377
|
-
const
|
|
378
|
-
if (!
|
|
400
|
+
const exportedModel = resolveNamedExportEntry(moduleValue, isCliModelReference);
|
|
401
|
+
if (!exportedModel) {
|
|
379
402
|
if (isInactiveGeneratedModelModule(moduleValue)) {
|
|
380
403
|
continue;
|
|
381
404
|
}
|
|
@@ -383,8 +406,10 @@ async function prepareProjectDiscovery(projectRoot, config = normalizeHoloProjec
|
|
|
383
406
|
}
|
|
384
407
|
models.push({
|
|
385
408
|
sourcePath: relativePath,
|
|
386
|
-
name:
|
|
387
|
-
|
|
409
|
+
name: exportedModel.value.definition.name,
|
|
410
|
+
tableName: exportedModel.value.definition.table.tableName,
|
|
411
|
+
prunable: Boolean(exportedModel.value.definition.prunable),
|
|
412
|
+
...exportedModel.exportName === "default" ? {} : { exportName: exportedModel.exportName }
|
|
388
413
|
});
|
|
389
414
|
} catch (error) {
|
|
390
415
|
if (!isMissingGeneratedSchemaModelError(error)) {
|
|
@@ -410,15 +435,21 @@ async function prepareProjectDiscovery(projectRoot, config = normalizeHoloProjec
|
|
|
410
435
|
const seeders = [];
|
|
411
436
|
for (const filePath of seederFiles) {
|
|
412
437
|
const relativePath = makeProjectRelativePath(projectRoot, filePath);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
438
|
+
try {
|
|
439
|
+
const moduleValue = await importProjectModule(projectRoot, filePath);
|
|
440
|
+
const seeder = resolveNamedExport(moduleValue, isSeederDefinition);
|
|
441
|
+
if (!seeder) {
|
|
442
|
+
throw new Error(`Discovered seeder "${relativePath}" does not export a Holo seeder.`);
|
|
443
|
+
}
|
|
444
|
+
seeders.push({
|
|
445
|
+
sourcePath: relativePath,
|
|
446
|
+
name: seeder.name
|
|
447
|
+
});
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (!isMissingGeneratedSchemaModelError(error)) {
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
417
452
|
}
|
|
418
|
-
seeders.push({
|
|
419
|
-
sourcePath: relativePath,
|
|
420
|
-
name: seeder.name
|
|
421
|
-
});
|
|
422
453
|
}
|
|
423
454
|
assertUniqueEntries("seeder", seeders);
|
|
424
455
|
const commands = [];
|
|
@@ -709,7 +740,7 @@ async function discoverAppCommands(projectRoot, config = normalizeHoloProjectCon
|
|
|
709
740
|
description: entry.description,
|
|
710
741
|
...entry.usage ? { usage: entry.usage } : {},
|
|
711
742
|
async load() {
|
|
712
|
-
const moduleValue = await importProjectModule(projectRoot,
|
|
743
|
+
const moduleValue = await importProjectModule(projectRoot, resolve2(projectRoot, entry.sourcePath));
|
|
713
744
|
const command = resolveCommandExport(moduleValue);
|
|
714
745
|
if (!command) {
|
|
715
746
|
throw new Error(`Discovered command "${entry.sourcePath}" does not export a Holo command.`);
|
|
@@ -745,21 +776,6 @@ async function loadRegisteredMigrations(projectRoot, config) {
|
|
|
745
776
|
}
|
|
746
777
|
return migrations;
|
|
747
778
|
}
|
|
748
|
-
function inferMigrationNameFromEntry(entry) {
|
|
749
|
-
const fileName = basename(entry, extname(entry));
|
|
750
|
-
return validateMigrationName(
|
|
751
|
-
fileName,
|
|
752
|
-
`Registered migration "${entry}" must use a timestamped file name matching YYYY_MM_DD_HHMMSS_description.`
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
function validateMigrationName(name, message) {
|
|
756
|
-
if (!MIGRATION_NAME_PATTERN.test(name)) {
|
|
757
|
-
throw new Error(
|
|
758
|
-
message ?? `Migration name "${name}" must match YYYY_MM_DD_HHMMSS_description.`
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
return name;
|
|
762
|
-
}
|
|
763
779
|
async function loadRegisteredSeeders(projectRoot, config) {
|
|
764
780
|
const seeders = [];
|
|
765
781
|
for (const entry of config.seeders) {
|
|
@@ -774,6 +790,7 @@ async function loadRegisteredSeeders(projectRoot, config) {
|
|
|
774
790
|
}
|
|
775
791
|
|
|
776
792
|
export {
|
|
793
|
+
collectFiles,
|
|
777
794
|
resolveNamedExport,
|
|
778
795
|
resolveNamedExportEntry,
|
|
779
796
|
resolveListenerEventNamesForDiscovery,
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
serializeDatabaseConfig,
|
|
8
8
|
serializeProjectConfig,
|
|
9
9
|
writeProjectConfig
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-5BLEC66P.mjs";
|
|
11
|
+
import "./chunk-MXKNQACM.mjs";
|
|
12
12
|
import {
|
|
13
13
|
readTextFile
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-R6BWRY3E.mjs";
|
|
15
15
|
export {
|
|
16
16
|
defaultProjectConfig,
|
|
17
17
|
ensureGeneratedSchemaPlaceholder,
|
|
@@ -13,16 +13,16 @@ import {
|
|
|
13
13
|
runProjectLifecycleScript,
|
|
14
14
|
runProjectPrepare,
|
|
15
15
|
toPosixSlashes
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ODJA3TFG.mjs";
|
|
17
17
|
import {
|
|
18
18
|
hasProjectDependency
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-F4MT6GBK.mjs";
|
|
20
20
|
import "./chunk-D7O4SU6N.mjs";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
25
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-UZTDQKIY.mjs";
|
|
22
|
+
import "./chunk-USACXIIB.mjs";
|
|
23
|
+
import "./chunk-5BLEC66P.mjs";
|
|
24
|
+
import "./chunk-MXKNQACM.mjs";
|
|
25
|
+
import "./chunk-R6BWRY3E.mjs";
|
|
26
26
|
export {
|
|
27
27
|
collectDirectoryTree,
|
|
28
28
|
collectDiscoveryWatchRoots,
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
resolveListenerEventNamesFromSource,
|
|
11
11
|
resolveNamedExport,
|
|
12
12
|
resolveNamedExportEntry
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-UZTDQKIY.mjs";
|
|
14
|
+
import "./chunk-MXKNQACM.mjs";
|
|
15
|
+
import "./chunk-R6BWRY3E.mjs";
|
|
16
16
|
export {
|
|
17
17
|
collectImportedBindingsBySource,
|
|
18
18
|
discoverAppCommands,
|
|
@@ -1,31 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveStringFlag
|
|
3
|
+
} from "./chunk-5EU32E7X.mjs";
|
|
4
|
+
import {
|
|
5
|
+
runProjectPrepare
|
|
6
|
+
} from "./chunk-ODJA3TFG.mjs";
|
|
7
|
+
import "./chunk-F4MT6GBK.mjs";
|
|
1
8
|
import {
|
|
2
9
|
hasRegisteredCreateTableMigration,
|
|
3
10
|
hasRegisteredMigrationSlug,
|
|
4
11
|
nextMigrationTemplate
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
resolveStringFlag
|
|
8
|
-
} from "./chunk-JX2ZH6XY.mjs";
|
|
12
|
+
} from "./chunk-BAFQ2GOA.mjs";
|
|
9
13
|
import {
|
|
10
14
|
writeLine
|
|
11
15
|
} from "./chunk-ZXDU7RHU.mjs";
|
|
12
16
|
import {
|
|
13
17
|
ensureAbsent,
|
|
14
18
|
fileExists
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import {
|
|
17
|
-
runProjectPrepare
|
|
18
|
-
} from "./chunk-ZLRO7HXY.mjs";
|
|
19
|
-
import "./chunk-CUL4RJTG.mjs";
|
|
19
|
+
} from "./chunk-57SJ566R.mjs";
|
|
20
20
|
import "./chunk-D7O4SU6N.mjs";
|
|
21
21
|
import {
|
|
22
|
+
collectFiles,
|
|
22
23
|
prepareProjectDiscovery
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-UZTDQKIY.mjs";
|
|
25
|
+
import "./chunk-USACXIIB.mjs";
|
|
25
26
|
import {
|
|
26
27
|
ensureGeneratedSchemaPlaceholder,
|
|
27
28
|
ensureProjectConfig
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-5BLEC66P.mjs";
|
|
29
30
|
import {
|
|
30
31
|
ensureSuffix,
|
|
31
32
|
loadGeneratedProjectRegistry,
|
|
@@ -47,22 +48,127 @@ import {
|
|
|
47
48
|
toKebabCase,
|
|
48
49
|
toPascalCase,
|
|
49
50
|
toSnakeCase
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-MXKNQACM.mjs";
|
|
51
52
|
import {
|
|
52
53
|
makeProjectRelativePath,
|
|
53
54
|
resolveDefaultArtifactPath,
|
|
54
55
|
writeTextFile
|
|
55
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-R6BWRY3E.mjs";
|
|
56
57
|
|
|
57
58
|
// src/generators.ts
|
|
58
59
|
import { createHash } from "crypto";
|
|
59
60
|
import { readFile } from "fs/promises";
|
|
60
|
-
import { resolve } from "path";
|
|
61
|
+
import { basename, extname, resolve } from "path";
|
|
61
62
|
import { normalizeMigrationSlug } from "@holo-js/db";
|
|
62
63
|
var MAIL_VIEW_SCAFFOLDING_UNAVAILABLE_MESSAGE = 'View-backed mail scaffolding requires a renderView runtime binding, which the first-party app scaffolds do not configure yet. Use "--markdown" instead.';
|
|
63
64
|
function hasRegisteredModelName(registry, modelName) {
|
|
64
65
|
return Boolean(registry?.models.some((entry) => entry.name === modelName));
|
|
65
66
|
}
|
|
67
|
+
function findRegisteredModelByTableName(registry, tableName) {
|
|
68
|
+
return registry?.models.find((entry) => entry.tableName === tableName);
|
|
69
|
+
}
|
|
70
|
+
async function findGeneratedModelSourceByTableName(projectRoot, modelsPath, tableName) {
|
|
71
|
+
const files = await collectFiles(resolve(projectRoot, modelsPath));
|
|
72
|
+
const generatedTableReference = tableName;
|
|
73
|
+
for (const filePath of files) {
|
|
74
|
+
const contents = await readFile(filePath, "utf8");
|
|
75
|
+
if (containsDefineModelTableReference(contents, generatedTableReference)) {
|
|
76
|
+
return basename(filePath, extname(filePath));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
function containsDefineModelTableReference(contents, tableName) {
|
|
82
|
+
let index = 0;
|
|
83
|
+
while (index < contents.length) {
|
|
84
|
+
const nextReference = contents.indexOf("defineModel", index);
|
|
85
|
+
if (nextReference === -1) return false;
|
|
86
|
+
if (isInsideComment(contents, nextReference)) {
|
|
87
|
+
index = nextReference + "defineModel".length;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const before = contents[nextReference - 1];
|
|
91
|
+
const after = contents[nextReference + "defineModel".length];
|
|
92
|
+
const hasIdentifierBoundary = !isIdentifierCharacter(before) && !isIdentifierCharacter(after);
|
|
93
|
+
if (!hasIdentifierBoundary) {
|
|
94
|
+
index = nextReference + "defineModel".length;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const openParenIndex = skipWhitespace(contents, nextReference + "defineModel".length);
|
|
98
|
+
if (contents[openParenIndex] !== "(") {
|
|
99
|
+
index = nextReference + "defineModel".length;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const firstArgumentIndex = skipWhitespace(contents, openParenIndex + 1);
|
|
103
|
+
const quote = contents[firstArgumentIndex];
|
|
104
|
+
if (quote !== "'" && quote !== '"' && quote !== "`") {
|
|
105
|
+
index = firstArgumentIndex;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const literal = readStringLiteral(contents, firstArgumentIndex, quote);
|
|
109
|
+
if (literal?.value === tableName) return true;
|
|
110
|
+
index = literal?.endIndex ?? firstArgumentIndex + 1;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function isInsideComment(contents, position) {
|
|
115
|
+
let index = 0;
|
|
116
|
+
while (index < position) {
|
|
117
|
+
const current = contents[index];
|
|
118
|
+
const next = contents[index + 1];
|
|
119
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
120
|
+
index = readStringLiteral(contents, index, current)?.endIndex ?? index + 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (current === "/" && next === "/") {
|
|
124
|
+
const end = contents.indexOf("\n", index + 2);
|
|
125
|
+
if (end === -1 || end >= position) return true;
|
|
126
|
+
index = end + 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (current === "/" && next === "*") {
|
|
130
|
+
const end = contents.indexOf("*/", index + 2);
|
|
131
|
+
if (end === -1 || end + 2 >= position) return true;
|
|
132
|
+
index = end + 2;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
index += 1;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
function isIdentifierCharacter(value) {
|
|
140
|
+
return typeof value === "string" && /[$\w]/.test(value);
|
|
141
|
+
}
|
|
142
|
+
function skipWhitespace(contents, startIndex) {
|
|
143
|
+
let index = startIndex;
|
|
144
|
+
while (/\s/.test(contents[index] ?? "")) {
|
|
145
|
+
index += 1;
|
|
146
|
+
}
|
|
147
|
+
return index;
|
|
148
|
+
}
|
|
149
|
+
function readStringLiteral(contents, startIndex, quote) {
|
|
150
|
+
let value = "";
|
|
151
|
+
let index = startIndex + 1;
|
|
152
|
+
while (index < contents.length) {
|
|
153
|
+
const current = contents[index];
|
|
154
|
+
if (current === "\\") {
|
|
155
|
+
const escaped = contents[index + 1];
|
|
156
|
+
if (typeof escaped === "string") {
|
|
157
|
+
value += escaped;
|
|
158
|
+
index += 2;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (current === quote) {
|
|
163
|
+
return { value, endIndex: index + 1 };
|
|
164
|
+
}
|
|
165
|
+
if (typeof current === "string") {
|
|
166
|
+
value += current;
|
|
167
|
+
}
|
|
168
|
+
index += 1;
|
|
169
|
+
}
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
66
172
|
function hasRegisteredJobName(registry, jobName) {
|
|
67
173
|
return Boolean(registry?.jobs.some((entry) => entry.name === jobName));
|
|
68
174
|
}
|
|
@@ -128,6 +234,7 @@ async function resolveProjectMailViewFramework(projectRoot) {
|
|
|
128
234
|
}
|
|
129
235
|
async function runMakeModel(io, projectRoot, input) {
|
|
130
236
|
const project = await ensureProjectConfig(projectRoot);
|
|
237
|
+
const generatedSchemaFilePath = await ensureGeneratedSchemaPlaceholder(projectRoot, project.config);
|
|
131
238
|
const registry = await loadGeneratedProjectRegistry(projectRoot) ?? await prepareProjectDiscovery(projectRoot, project.config);
|
|
132
239
|
const requestedName = String(input.args[0] ?? "");
|
|
133
240
|
const options = {
|
|
@@ -146,7 +253,6 @@ async function runMakeModel(io, projectRoot, input) {
|
|
|
146
253
|
const seederFilePath = resolveArtifactPath(projectRoot, project.config.paths.seeders, seederInfo.directory, `${seederInfo.baseName}.ts`);
|
|
147
254
|
const factoryInfo = resolveNameInfo(`${requestedName}Factory`, { suffix: "Factory" });
|
|
148
255
|
const factoryFilePath = resolveArtifactPath(projectRoot, project.config.paths.factories, factoryInfo.directory, `${factoryInfo.baseName}.ts`);
|
|
149
|
-
const generatedSchemaFilePath = await ensureGeneratedSchemaPlaceholder(projectRoot, project.config);
|
|
150
256
|
if (await fileExists(modelFilePath) || hasRegisteredModelName(registry, nameInfo.baseName)) {
|
|
151
257
|
throw new Error(`Model with the same name already exists: ${nameInfo.baseName}.`);
|
|
152
258
|
}
|
|
@@ -156,6 +262,18 @@ async function runMakeModel(io, projectRoot, input) {
|
|
|
156
262
|
throw new Error(`A migration for table "${tableName}" already exists.`);
|
|
157
263
|
}
|
|
158
264
|
}
|
|
265
|
+
const existingTableModel = findRegisteredModelByTableName(registry, tableName);
|
|
266
|
+
if (existingTableModel) {
|
|
267
|
+
throw new Error(`Discovered duplicate model "${existingTableModel.name}" for table "${tableName}".`);
|
|
268
|
+
}
|
|
269
|
+
const existingGeneratedModelName = await findGeneratedModelSourceByTableName(
|
|
270
|
+
projectRoot,
|
|
271
|
+
project.config.paths.models,
|
|
272
|
+
tableName
|
|
273
|
+
);
|
|
274
|
+
if (existingGeneratedModelName) {
|
|
275
|
+
throw new Error(`Discovered duplicate model "${existingGeneratedModelName}" for table "${tableName}".`);
|
|
276
|
+
}
|
|
159
277
|
await ensureAbsent(modelFilePath);
|
|
160
278
|
if (options.observer) {
|
|
161
279
|
await ensureAbsent(observerFilePath);
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ interface HoloAppCommand {
|
|
|
19
19
|
readonly usage?: string;
|
|
20
20
|
run(context: CommandExecutionContext): unknown | Promise<unknown>;
|
|
21
21
|
}
|
|
22
|
-
declare function defineCommand
|
|
22
|
+
declare function defineCommand(command: HoloAppCommand): Readonly<HoloAppCommand>;
|
|
23
23
|
|
|
24
24
|
type IoStreams = {
|
|
25
25
|
readonly cwd: string;
|