@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.
Files changed (56) hide show
  1. package/dist/bin/holo.mjs +192 -35
  2. package/dist/{broadcast-CSSARTSA.mjs → broadcast-2AZIC5ZP.mjs} +5 -5
  3. package/dist/{cache-4G6QGIZO.mjs → cache-5OROX4GL.mjs} +5 -5
  4. package/dist/{cache-migrations-NATT5WPQ.mjs → cache-migrations-7XFVLTOC.mjs} +15 -16
  5. package/dist/{chunk-EUIVXVJL.mjs → chunk-57SJ566R.mjs} +1 -1
  6. package/dist/chunk-5BLEC66P.mjs +284 -0
  7. package/dist/{chunk-JX2ZH6XY.mjs → chunk-5EU32E7X.mjs} +3 -3
  8. package/dist/{chunk-Q5F6C2D4.mjs → chunk-BAFQ2GOA.mjs} +1 -1
  9. package/dist/{chunk-CUL4RJTG.mjs → chunk-F4MT6GBK.mjs} +1 -1
  10. package/dist/{chunk-3OTCSFDG.mjs → chunk-MXKNQACM.mjs} +544 -82
  11. package/dist/{chunk-ZLRO7HXY.mjs → chunk-ODJA3TFG.mjs} +156 -15
  12. package/dist/{chunk-QYLSMF7V.mjs → chunk-OZUDZEAW.mjs} +142 -28
  13. package/dist/{chunk-66FHW725.mjs → chunk-R6BWRY3E.mjs} +28 -2
  14. package/dist/{chunk-MZXN2YMI.mjs → chunk-USACXIIB.mjs} +3544 -2522
  15. package/dist/{chunk-VT5IDQG6.mjs → chunk-UZTDQKIY.mjs} +61 -44
  16. package/dist/{config-LS5USBRB.mjs → config-5JSC6KJG.mjs} +3 -3
  17. package/dist/{dev-LZ3O2E3U.mjs → dev-F6QUWNCR.mjs} +7 -7
  18. package/dist/{discovery-GBLAUTXS.mjs → discovery-JLT2EOGH.mjs} +3 -3
  19. package/dist/{generators-DSN4GWJI.mjs → generators-WVKJLAYB.mjs} +134 -16
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.mjs +189 -32
  22. package/dist/media-migrations-DU7WEKAY.mjs +117 -0
  23. package/dist/{queue-FV35LLPR.mjs → queue-NOLVWPCH.mjs} +14 -14
  24. package/dist/{queue-migrations-SSIYKK5S.mjs → queue-migrations-HXNTZMGL.mjs} +24 -20
  25. package/dist/{runtime-EFZ5H5IL.mjs → runtime-462O2BDR.mjs} +9 -7
  26. package/dist/{runtime-OOSJ5JBY.mjs → runtime-ZKD6URAV.mjs} +1 -1
  27. package/dist/{scaffold-7OTDH4UR.mjs → scaffold-WOJV2ZZI.mjs} +18 -5
  28. package/dist/{security-ATKDC26E.mjs → security-5VGM467J.mjs} +10 -7
  29. package/package.json +13 -12
  30. package/dist/broadcast-YSIJCL3R.mjs +0 -85
  31. package/dist/cache-OWQY4E7W.mjs +0 -67
  32. package/dist/cache-migrations-RVEA6CEU.mjs +0 -155
  33. package/dist/chunk-BWW5TDFI.mjs +0 -4
  34. package/dist/chunk-D4GG556Y.mjs +0 -23
  35. package/dist/chunk-DMH2B4UQ.mjs +0 -343
  36. package/dist/chunk-ET7UXHHQ.mjs +0 -166
  37. package/dist/chunk-G5ADO27Q.mjs +0 -463
  38. package/dist/chunk-GSQ3HTRO.mjs +0 -165
  39. package/dist/chunk-H7TJ4FB3.mjs +0 -848
  40. package/dist/chunk-ICJR7TS4.mjs +0 -66
  41. package/dist/chunk-M7J3YTHR.mjs +0 -26
  42. package/dist/chunk-QFUSWV3J.mjs +0 -3237
  43. package/dist/chunk-S7P7EBM3.mjs +0 -787
  44. package/dist/chunk-SRWJU3A5.mjs +0 -11
  45. package/dist/chunk-URK7C3VQ.mjs +0 -538
  46. package/dist/chunk-XUYKPU5Q.mjs +0 -272
  47. package/dist/config-DMWBMMGD.mjs +0 -26
  48. package/dist/dev-KQFT7RHR.mjs +0 -43
  49. package/dist/discovery-R733D2PO.mjs +0 -29
  50. package/dist/generators-WX45BI4U.mjs +0 -426
  51. package/dist/queue-6OG7VJ34.mjs +0 -626
  52. package/dist/queue-migrations-NK2EYX3J.mjs +0 -163
  53. package/dist/runtime-4BV3JODY.mjs +0 -56
  54. package/dist/runtime-ANBO7VQM.mjs +0 -33
  55. package/dist/scaffold-DRKBGS2K.mjs +0 -120
  56. 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-3OTCSFDG.mjs";
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-66FHW725.mjs";
20
+ } from "./chunk-R6BWRY3E.mjs";
22
21
 
23
22
  // src/project/discovery.ts
24
- import { readdir } from "fs/promises";
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 = resolve(projectRoot, config.paths.models);
345
- const migrationsRoot = resolve(projectRoot, config.paths.migrations);
346
- const seedersRoot = resolve(projectRoot, config.paths.seeders);
347
- const commandsRoot = resolve(projectRoot, config.paths.commands);
348
- const jobsRoot = resolve(projectRoot, config.paths.jobs);
349
- const eventsRoot = resolve(projectRoot, config.paths.events);
350
- const listenersRoot = resolve(projectRoot, config.paths.listeners);
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 = resolve(projectRoot, broadcastPath);
354
- const channelsRoot = resolve(projectRoot, channelsPath);
355
- const policiesRoot = resolve(projectRoot, config.paths.authorizationPolicies ?? "server/policies");
356
- const abilitiesRoot = resolve(projectRoot, config.paths.authorizationAbilities ?? "server/abilities");
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 model = resolveNamedExport(moduleValue, isCliModelReference);
378
- if (!model) {
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: model.definition.name,
387
- prunable: Boolean(model.definition.prunable)
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
- const moduleValue = await importProjectModule(projectRoot, filePath);
414
- const seeder = resolveNamedExport(moduleValue, isSeederDefinition);
415
- if (!seeder) {
416
- throw new Error(`Discovered seeder "${relativePath}" does not export a Holo seeder.`);
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, resolve(projectRoot, entry.sourcePath));
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-GSQ3HTRO.mjs";
11
- import "./chunk-H7TJ4FB3.mjs";
10
+ } from "./chunk-5BLEC66P.mjs";
11
+ import "./chunk-MXKNQACM.mjs";
12
12
  import {
13
13
  readTextFile
14
- } from "./chunk-G5ADO27Q.mjs";
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-ZLRO7HXY.mjs";
16
+ } from "./chunk-ODJA3TFG.mjs";
17
17
  import {
18
18
  hasProjectDependency
19
- } from "./chunk-CUL4RJTG.mjs";
19
+ } from "./chunk-F4MT6GBK.mjs";
20
20
  import "./chunk-D7O4SU6N.mjs";
21
- import "./chunk-S7P7EBM3.mjs";
22
- import "./chunk-MZXN2YMI.mjs";
23
- import "./chunk-GSQ3HTRO.mjs";
24
- import "./chunk-H7TJ4FB3.mjs";
25
- import "./chunk-G5ADO27Q.mjs";
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-S7P7EBM3.mjs";
14
- import "./chunk-H7TJ4FB3.mjs";
15
- import "./chunk-G5ADO27Q.mjs";
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-Q5F6C2D4.mjs";
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-EUIVXVJL.mjs";
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-S7P7EBM3.mjs";
24
- import "./chunk-MZXN2YMI.mjs";
24
+ } from "./chunk-UZTDQKIY.mjs";
25
+ import "./chunk-USACXIIB.mjs";
25
26
  import {
26
27
  ensureGeneratedSchemaPlaceholder,
27
28
  ensureProjectConfig
28
- } from "./chunk-GSQ3HTRO.mjs";
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-H7TJ4FB3.mjs";
51
+ } from "./chunk-MXKNQACM.mjs";
51
52
  import {
52
53
  makeProjectRelativePath,
53
54
  resolveDefaultArtifactPath,
54
55
  writeTextFile
55
- } from "./chunk-G5ADO27Q.mjs";
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<TCommand extends HoloAppCommand>(command: TCommand): TCommand;
22
+ declare function defineCommand(command: HoloAppCommand): Readonly<HoloAppCommand>;
23
23
 
24
24
  type IoStreams = {
25
25
  readonly cwd: string;