@metaobjectsdev/cli 0.5.0-rc.1

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 (67) hide show
  1. package/LICENSE +189 -0
  2. package/README.md +184 -0
  3. package/dist/bin/meta.d.ts +3 -0
  4. package/dist/bin/meta.d.ts.map +1 -0
  5. package/dist/bin/meta.js +7 -0
  6. package/dist/bin/meta.js.map +1 -0
  7. package/dist/src/commands/export.d.ts +2 -0
  8. package/dist/src/commands/export.d.ts.map +1 -0
  9. package/dist/src/commands/export.js +45 -0
  10. package/dist/src/commands/export.js.map +1 -0
  11. package/dist/src/commands/gen.d.ts +2 -0
  12. package/dist/src/commands/gen.d.ts.map +1 -0
  13. package/dist/src/commands/gen.js +86 -0
  14. package/dist/src/commands/gen.js.map +1 -0
  15. package/dist/src/commands/init.d.ts +16 -0
  16. package/dist/src/commands/init.d.ts.map +1 -0
  17. package/dist/src/commands/init.js +222 -0
  18. package/dist/src/commands/init.js.map +1 -0
  19. package/dist/src/commands/migrate.d.ts +2 -0
  20. package/dist/src/commands/migrate.d.ts.map +1 -0
  21. package/dist/src/commands/migrate.js +361 -0
  22. package/dist/src/commands/migrate.js.map +1 -0
  23. package/dist/src/index.d.ts +4 -0
  24. package/dist/src/index.d.ts.map +1 -0
  25. package/dist/src/index.js +105 -0
  26. package/dist/src/index.js.map +1 -0
  27. package/dist/src/lib/args.d.ts +34 -0
  28. package/dist/src/lib/args.d.ts.map +1 -0
  29. package/dist/src/lib/args.js +103 -0
  30. package/dist/src/lib/args.js.map +1 -0
  31. package/dist/src/lib/config.d.ts +17 -0
  32. package/dist/src/lib/config.d.ts.map +1 -0
  33. package/dist/src/lib/config.js +48 -0
  34. package/dist/src/lib/config.js.map +1 -0
  35. package/dist/src/lib/kysely.d.ts +28 -0
  36. package/dist/src/lib/kysely.d.ts.map +1 -0
  37. package/dist/src/lib/kysely.js +100 -0
  38. package/dist/src/lib/kysely.js.map +1 -0
  39. package/dist/src/lib/load-metaobjects-config.d.ts +3 -0
  40. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -0
  41. package/dist/src/lib/load-metaobjects-config.js +83 -0
  42. package/dist/src/lib/load-metaobjects-config.js.map +1 -0
  43. package/dist/src/lib/log.d.ts +6 -0
  44. package/dist/src/lib/log.d.ts.map +1 -0
  45. package/dist/src/lib/log.js +6 -0
  46. package/dist/src/lib/log.js.map +1 -0
  47. package/dist/src/lib/output.d.ts +38 -0
  48. package/dist/src/lib/output.d.ts.map +1 -0
  49. package/dist/src/lib/output.js +96 -0
  50. package/dist/src/lib/output.js.map +1 -0
  51. package/dist/src/lib/projection-migrations.d.ts +34 -0
  52. package/dist/src/lib/projection-migrations.d.ts.map +1 -0
  53. package/dist/src/lib/projection-migrations.js +112 -0
  54. package/dist/src/lib/projection-migrations.js.map +1 -0
  55. package/package.json +71 -0
  56. package/src/commands/export.ts +50 -0
  57. package/src/commands/gen.ts +88 -0
  58. package/src/commands/init.ts +272 -0
  59. package/src/commands/migrate.ts +390 -0
  60. package/src/index.ts +109 -0
  61. package/src/lib/args.ts +157 -0
  62. package/src/lib/config.ts +78 -0
  63. package/src/lib/kysely.ts +114 -0
  64. package/src/lib/load-metaobjects-config.ts +89 -0
  65. package/src/lib/log.ts +5 -0
  66. package/src/lib/output.ts +156 -0
  67. package/src/lib/projection-migrations.ts +159 -0
@@ -0,0 +1,96 @@
1
+ // Output formatters for meta gen and meta migrate.
2
+ //
3
+ // TTY-gated glyphs: unicode (✓ ↺ ✗ = ⚠) when stdout is a TTY, plain words
4
+ // (NEW MERGED CONFLICT UNCHANGED REFUSED) otherwise. Per SP5 §5.1.
5
+ const GEN_GLYPHS = {
6
+ new: "✓",
7
+ merged: "↺",
8
+ conflict: "✗",
9
+ unchanged: "=",
10
+ refused: "⚠",
11
+ };
12
+ const GEN_WORDS = {
13
+ new: "NEW",
14
+ merged: "MERGED",
15
+ conflict: "CONFLICT",
16
+ unchanged: "UNCHANGED",
17
+ refused: "REFUSED",
18
+ };
19
+ export function formatGenResult(result, opts) {
20
+ const symbols = opts.isTTY ? GEN_GLYPHS : GEN_WORDS;
21
+ const header = `meta gen${result.dryRun ? " --dry-run" : ""} — ${result.dialect}, ${result.outDir}`;
22
+ if (result.files.length === 0) {
23
+ return `${header}\n\n No entities to generate.\n`;
24
+ }
25
+ const lines = [header, ""];
26
+ const maxPathLen = Math.max(...result.files.map((f) => f.path.length));
27
+ for (const file of result.files) {
28
+ const sym = symbols[file.status];
29
+ const pathPadded = file.path.padEnd(maxPathLen);
30
+ const infoSegment = file.info.length > 0 ? ` (${file.info})` : "";
31
+ if (opts.isTTY) {
32
+ lines.push(` ${sym} ${pathPadded} ${file.status}${infoSegment}`);
33
+ }
34
+ else {
35
+ lines.push(` ${sym.padEnd(9)} ${pathPadded}${infoSegment}`);
36
+ }
37
+ }
38
+ const counts = result.files.reduce((acc, f) => {
39
+ acc[f.status] = (acc[f.status] ?? 0) + 1;
40
+ return acc;
41
+ }, { new: 0, merged: 0, conflict: 0, unchanged: 0, refused: 0 });
42
+ const parts = [];
43
+ if (counts.new > 0)
44
+ parts.push(`${counts.new} written`);
45
+ if (counts.merged > 0)
46
+ parts.push(`${counts.merged} merged`);
47
+ if (counts.conflict > 0)
48
+ parts.push(`${counts.conflict} conflict`);
49
+ if (counts.unchanged > 0)
50
+ parts.push(`${counts.unchanged} unchanged`);
51
+ if (counts.refused > 0)
52
+ parts.push(`${counts.refused} refused`);
53
+ lines.push("", ` ${parts.join(", ")}`, "");
54
+ if (result.warnings.length > 0) {
55
+ lines.push("Warnings:", ...result.warnings.map((w) => ` - ${w}`), "");
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+ export function formatMigrateResult(result, _opts) {
60
+ const header = `meta migrate${result.dryRun ? " --dry-run" : ""} — ${result.dialect}, ${result.displayUrl}`;
61
+ const lines = [header, ""];
62
+ const changeEntries = Object.entries(result.changeCounts).filter(([, v]) => v > 0);
63
+ if (changeEntries.length === 0 && result.blocked.length === 0 && result.ambiguous.length === 0) {
64
+ return `${header}\n\n No schema changes.\n`;
65
+ }
66
+ if (changeEntries.length > 0) {
67
+ const summary = changeEntries.map(([k, v]) => `${v} ${k}`).join(", ");
68
+ lines.push(` Changes: ${summary}`, "");
69
+ }
70
+ if (result.blocked.length > 0) {
71
+ lines.push(" Blocked (re-run with --allow):");
72
+ for (const b of result.blocked) {
73
+ lines.push(` ${b.kind} ${b.description} (--allow ${b.allowFlag})`);
74
+ }
75
+ lines.push("");
76
+ }
77
+ if (result.ambiguous.length > 0) {
78
+ lines.push(" Ambiguous (re-run with --on-ambiguous):");
79
+ for (const a of result.ambiguous) {
80
+ lines.push(` ${a.kind} ${a.description} (${a.hint})`);
81
+ }
82
+ lines.push("");
83
+ }
84
+ if (result.writtenPaths.length > 0) {
85
+ lines.push(" Written:");
86
+ for (const p of result.writtenPaths) {
87
+ lines.push(` ${p}`);
88
+ }
89
+ lines.push("");
90
+ }
91
+ else if (result.blocked.length > 0 || result.ambiguous.length > 0) {
92
+ lines.push(" No migration written. Resolve flags and re-run.", "");
93
+ }
94
+ return lines.join("\n");
95
+ }
96
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/lib/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type MetaData } from "@metaobjectsdev/metadata";
2
+ import { type ViewMigrationsResult } from "@metaobjectsdev/migrate-ts";
3
+ /** view-name → set of source-table names the view's SELECT depends on. */
4
+ export type ProjectionViewDependencies = ReadonlyMap<string, ReadonlySet<string>>;
5
+ export interface ProjectionMigrationsOpts {
6
+ readonly metadata: MetaData;
7
+ readonly dialect: "postgres" | "sqlite";
8
+ readonly allowBreaking?: boolean;
9
+ /** Column naming strategy forwarded to extractViewSpec. Defaults to "snake_case". */
10
+ readonly columnNamingStrategy?: "snake_case" | "literal" | "kebab-case";
11
+ /**
12
+ * Existing view SQL keyed by view name (from sqlite_master.sql or
13
+ * pg_views.definition). When provided, projections whose emitted CREATE
14
+ * SQL matches the existing definition (whitespace-normalized) are skipped
15
+ * — no DROP+CREATE noise for unchanged views.
16
+ */
17
+ readonly existingViewSql?: ReadonlyMap<string, string>;
18
+ }
19
+ /**
20
+ * Walk all projection entities in metadata, extract their ViewSpec, emit CREATE
21
+ * VIEW DDL, and compute view migration SQL via computeViewMigrations.
22
+ *
23
+ * Currently treats every projection as a new view (no previous-shape tracking).
24
+ * Future: introspect existing views from the live DB to do safe-append/replace
25
+ * detection.
26
+ */
27
+ export declare function computeProjectionMigrations(opts: ProjectionMigrationsOpts): ViewMigrationsResult;
28
+ /**
29
+ * For each projection view, compute the set of source table names that the
30
+ * view's SELECT depends on (base entity + all joined entities). Used by the
31
+ * CLI to pre-drop only the views whose source tables are being recreated.
32
+ */
33
+ export declare function computeProjectionViewDependencies(opts: Pick<ProjectionMigrationsOpts, "metadata" | "columnNamingStrategy">): ProjectionViewDependencies;
34
+ //# sourceMappingURL=projection-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection-migrations.d.ts","sourceRoot":"","sources":["../../../src/lib/projection-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,4BAA4B,CAAC;AAEpC,0EAA0E;AAC1E,MAAM,MAAM,0BAA0B,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;AAElF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,qFAAqF;IACrF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,YAAY,CAAC;IACxE;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxD;AAOD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,wBAAwB,GAC7B,oBAAoB,CAoEtB;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,sBAAsB,CAAC,GACxE,0BAA0B,CAgC5B"}
@@ -0,0 +1,112 @@
1
+ import { resolveTableName, MetaRoot, } from "@metaobjectsdev/metadata";
2
+ import { isProjection, extractViewSpec, emitViewDdl, } from "@metaobjectsdev/codegen-ts";
3
+ import { computeViewMigrations, } from "@metaobjectsdev/migrate-ts";
4
+ /** Collapse whitespace + strip trailing ";" for textual view-SQL comparison. */
5
+ function normalizeViewSql(sql) {
6
+ return sql.replace(/\s+/g, " ").replace(/;\s*$/, "").trim();
7
+ }
8
+ /**
9
+ * Walk all projection entities in metadata, extract their ViewSpec, emit CREATE
10
+ * VIEW DDL, and compute view migration SQL via computeViewMigrations.
11
+ *
12
+ * Currently treats every projection as a new view (no previous-shape tracking).
13
+ * Future: introspect existing views from the live DB to do safe-append/replace
14
+ * detection.
15
+ */
16
+ export function computeProjectionMigrations(opts) {
17
+ // loadMemory now returns MetaRoot; guard here also covers callers that pass a
18
+ // plain MetaData (e.g. test helpers or external callers with non-MetaRoot roots).
19
+ if (!(opts.metadata instanceof MetaRoot)) {
20
+ throw new Error("computeProjectionMigrations: opts.metadata must be a loaded MetaRoot.");
21
+ }
22
+ const root = opts.metadata;
23
+ const columnNamingStrategy = opts.columnNamingStrategy ?? "snake_case";
24
+ // Collect all writable entities for table name resolution.
25
+ const joinTables = {};
26
+ for (const obj of root.objects()) {
27
+ joinTables[obj.name] = resolveTableName(obj);
28
+ }
29
+ // Find projection entities.
30
+ const projections = root.objects().filter(isProjection);
31
+ if (projections.length === 0) {
32
+ return { migrations: [], errors: [] };
33
+ }
34
+ const views = [];
35
+ for (const projection of projections) {
36
+ const spec = extractViewSpec(projection, root, { columnNamingStrategy });
37
+ const baseTableName = joinTables[spec.joinTree.baseEntity];
38
+ if (!baseTableName) {
39
+ return {
40
+ migrations: [],
41
+ errors: [
42
+ `Projection ${projection.name}: base entity "${spec.joinTree.baseEntity}" has no resolvable table name.`,
43
+ ],
44
+ };
45
+ }
46
+ const createSql = emitViewDdl(spec, {
47
+ dialect: opts.dialect,
48
+ baseTableName,
49
+ joinTables,
50
+ });
51
+ // Skip if the existing DB view's CREATE SQL matches what we'd emit.
52
+ // Avoids the "every migration re-creates every view" noise when nothing
53
+ // about the view's body actually changed.
54
+ const existing = opts.existingViewSql?.get(spec.viewName);
55
+ if (existing !== undefined && normalizeViewSql(existing) === normalizeViewSql(createSql)) {
56
+ continue;
57
+ }
58
+ views.push({
59
+ viewName: spec.viewName,
60
+ // prevShape intentionally absent — treated as "safe-append" by
61
+ // computeViewMigrations (source-aware-diff.ts line 32). On Postgres
62
+ // this rewrites the emitted "CREATE VIEW" to "CREATE OR REPLACE VIEW",
63
+ // so re-running migrate is idempotent.
64
+ nextShape: {
65
+ columns: spec.selectSpec.columns.map((c) => c.dbColAlias),
66
+ },
67
+ createSql,
68
+ });
69
+ }
70
+ return computeViewMigrations({
71
+ dialect: opts.dialect,
72
+ allowBreaking: opts.allowBreaking ?? false,
73
+ views,
74
+ });
75
+ }
76
+ /**
77
+ * For each projection view, compute the set of source table names that the
78
+ * view's SELECT depends on (base entity + all joined entities). Used by the
79
+ * CLI to pre-drop only the views whose source tables are being recreated.
80
+ */
81
+ export function computeProjectionViewDependencies(opts) {
82
+ if (!(opts.metadata instanceof MetaRoot)) {
83
+ throw new Error("computeProjectionViewDependencies: opts.metadata must be a loaded MetaRoot.");
84
+ }
85
+ const root = opts.metadata;
86
+ const columnNamingStrategy = opts.columnNamingStrategy ?? "snake_case";
87
+ const tableByEntity = {};
88
+ for (const obj of root.objects()) {
89
+ tableByEntity[obj.name] = resolveTableName(obj);
90
+ }
91
+ const result = new Map();
92
+ const projections = root.objects().filter(isProjection);
93
+ for (const projection of projections) {
94
+ const spec = extractViewSpec(projection, root, { columnNamingStrategy });
95
+ const tables = new Set();
96
+ const baseTable = tableByEntity[spec.joinTree.baseEntity];
97
+ if (baseTable)
98
+ tables.add(baseTable);
99
+ const walk = (joins) => {
100
+ for (const j of joins) {
101
+ const t = tableByEntity[j.targetEntity];
102
+ if (t)
103
+ tables.add(t);
104
+ walk(j.children);
105
+ }
106
+ };
107
+ walk(spec.joinTree.joins);
108
+ result.set(spec.viewName, tables);
109
+ }
110
+ return result;
111
+ }
112
+ //# sourceMappingURL=projection-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection-migrations.js","sourceRoot":"","sources":["../../../src/lib/projection-migrations.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@metaobjectsdev/cli",
3
+ "version": "0.5.0-rc.1",
4
+ "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "bin": {
9
+ "meta": "./dist/bin/meta.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "bun": "./src/index.ts",
17
+ "types": "./dist/src/index.d.ts",
18
+ "default": "./dist/src/index.js"
19
+ }
20
+ },
21
+ "files": ["dist", "src", "README.md", "LICENSE"],
22
+ "scripts": {
23
+ "build": "tsc -p .",
24
+ "typecheck": "tsc -p tsconfig.typecheck.json"
25
+ },
26
+ "license": "Apache-2.0",
27
+ "author": "Doug Mealing <doug@dougmealing.com>",
28
+ "homepage": "https://metaobjects.dev",
29
+ "bugs": {
30
+ "url": "https://github.com/metaobjectsdev/metaobjects/issues"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/metaobjectsdev/metaobjects.git",
35
+ "directory": "server/typescript/packages/cli"
36
+ },
37
+ "keywords": ["metaobjects", "cli", "scaffold", "codegen", "drift-detection"],
38
+ "dependencies": {
39
+ "@metaobjectsdev/sdk": "0.5.0",
40
+ "@metaobjectsdev/metadata": "0.5.0",
41
+ "@metaobjectsdev/codegen-ts": "0.5.0",
42
+ "@metaobjectsdev/codegen-ts-react": "0.5.0",
43
+ "@metaobjectsdev/codegen-ts-tanstack": "0.5.0",
44
+ "@metaobjectsdev/migrate-ts": "0.5.0",
45
+ "jiti": "^2.4.0"
46
+ },
47
+ "peerDependencies": {
48
+ "kysely": ">=0.27.0",
49
+ "pg": ">=8.0.0",
50
+ "@libsql/kysely-libsql": ">=0.4.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "pg": {
54
+ "optional": true
55
+ },
56
+ "@libsql/kysely-libsql": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "bun-types": "latest",
62
+ "typescript": "^5.6.0",
63
+ "@types/node": "^22.0.0",
64
+ "@libsql/kysely-libsql": "^0.4.1",
65
+ "@libsql/client": "^0.14.0",
66
+ "kysely": "^0.27.0",
67
+ "pg": "^8.0.0",
68
+ "@types/pg": "^8.0.0",
69
+ "pg-mem": "^3.0.4"
70
+ }
71
+ }
@@ -0,0 +1,50 @@
1
+ import { resolve, join } from "node:path";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { parseExportArgs } from "../lib/args.js";
4
+ import { log } from "../lib/log.js";
5
+ import { loadAndExportJson } from "@metaobjectsdev/metadata/core";
6
+ import { TypeRegistry, registerCoreTypes } from "@metaobjectsdev/metadata";
7
+ import { DEFAULT_METADATA_DIR, registerForgeTypes } from "@metaobjectsdev/sdk";
8
+
9
+ export async function exportCommand(args: string[], cwd: string): Promise<number> {
10
+ let flags;
11
+ try {
12
+ flags = parseExportArgs(args);
13
+ } catch (err) {
14
+ log.error((err as Error).message);
15
+ return 2;
16
+ }
17
+
18
+ const projectRoot = cwd;
19
+ const metadataDir = join(projectRoot, DEFAULT_METADATA_DIR);
20
+
21
+ // Build a registry with core + forge types so metadata that includes
22
+ // descriptive types (decision, principle, etc.) loads without errors.
23
+ const registry = new TypeRegistry();
24
+ registerCoreTypes(registry);
25
+ registerForgeTypes(registry);
26
+
27
+ const result = await loadAndExportJson(metadataDir, { registry });
28
+
29
+ for (const w of result.warnings) {
30
+ log.warn(w);
31
+ }
32
+
33
+ if (result.errors.length > 0) {
34
+ for (const err of result.errors) {
35
+ log.error(err.message);
36
+ }
37
+ return 1;
38
+ }
39
+
40
+ if (flags.out !== undefined) {
41
+ const outPath = resolve(projectRoot, flags.out);
42
+ await writeFile(outPath, result.json, "utf8");
43
+ const byteCount = Buffer.byteLength(result.json, "utf8");
44
+ log.info(`meta export — wrote ${outPath} (${byteCount} bytes)`);
45
+ } else {
46
+ process.stdout.write(result.json);
47
+ }
48
+
49
+ return 0;
50
+ }
@@ -0,0 +1,88 @@
1
+ import { relative } from "node:path";
2
+ import { parseGenArgs } from "../lib/args.js";
3
+ import { resolveGenConfig } from "../lib/config.js";
4
+ import { loadMetaobjectsConfig } from "../lib/load-metaobjects-config.js";
5
+ import { formatGenResult, type GenFileEntry, type GenFileStatus } from "../lib/output.js";
6
+ import { log } from "../lib/log.js";
7
+ import { loadMemory } from "@metaobjectsdev/sdk";
8
+ import { runGen } from "@metaobjectsdev/codegen-ts";
9
+ import type { WriteStatus } from "@metaobjectsdev/codegen-ts";
10
+
11
+ function mapStatus(s: WriteStatus): GenFileStatus {
12
+ switch (s) {
13
+ case "new":
14
+ case "overwrite": return "new";
15
+ case "skipped": return "unchanged";
16
+ case "refused": return "refused";
17
+ }
18
+ }
19
+
20
+ export async function genCommand(args: string[], cwd: string): Promise<number> {
21
+ let flags;
22
+ try { flags = parseGenArgs(args); }
23
+ catch (err) { log.error((err as Error).message); return 2; }
24
+
25
+ const projectRoot = cwd;
26
+ const cliConfig = resolveGenConfig(flags);
27
+
28
+ let forgeConfig;
29
+ try {
30
+ forgeConfig = await loadMetaobjectsConfig(projectRoot);
31
+ } catch (err) {
32
+ log.error((err as Error).message);
33
+ return 2;
34
+ }
35
+
36
+ let metadata;
37
+ try {
38
+ metadata = await loadMemory(projectRoot);
39
+ } catch (err) {
40
+ const msg = (err as Error).message;
41
+ if (msg.includes("ENOENT") || msg.includes("no such") || msg.includes("cannot read")) {
42
+ log.error(`no metaobjects/ found in ${projectRoot}; run 'meta init' to scaffold`);
43
+ } else {
44
+ log.error(`failed to load metadata: ${msg}`);
45
+ }
46
+ return 2;
47
+ }
48
+
49
+ let result;
50
+ try {
51
+ result = await runGen({
52
+ config: forgeConfig,
53
+ metadata,
54
+ ...(cliConfig.entities.length > 0 ? { entityFilter: cliConfig.entities } : {}),
55
+ });
56
+ } catch (err) {
57
+ log.error(`gen failed: ${(err as Error).message}`);
58
+ return 1;
59
+ }
60
+
61
+ for (const w of result.warnings) { log.warn(w); }
62
+
63
+ // result.files[].path is the absolute full path from decideAndWrite. With
64
+ // per-target output, show each path relative to the project root so files in
65
+ // different targets are distinguishable.
66
+ const files: GenFileEntry[] = result.files.map((f) => ({
67
+ path: relative(projectRoot, f.path),
68
+ status: mapStatus(f.status),
69
+ info: "",
70
+ }));
71
+
72
+ const targetDirs = Array.from(new Set(
73
+ (forgeConfig.targets ? Object.values(forgeConfig.targets).map((t) => t.outDir) : [])
74
+ .concat([forgeConfig.outDir]),
75
+ ));
76
+ const output = formatGenResult({
77
+ files,
78
+ outDir: targetDirs.length > 1 ? targetDirs.join(", ") : forgeConfig.outDir,
79
+ dialect: forgeConfig.dialect,
80
+ dryRun: cliConfig.dryRun,
81
+ warnings: [],
82
+ }, { isTTY: !!process.stdout.isTTY });
83
+
84
+ log.info(output);
85
+
86
+ const hasFailure = files.some((f) => f.status === "conflict" || f.status === "refused");
87
+ return hasFailure ? 1 : 0;
88
+ }