@metaobjectsdev/cli 0.23.2 → 0.24.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 (89) hide show
  1. package/README.md +78 -0
  2. package/dist/src/commands/docs.d.ts.map +1 -1
  3. package/dist/src/commands/docs.js +157 -59
  4. package/dist/src/commands/docs.js.map +1 -1
  5. package/dist/src/commands/export.d.ts.map +1 -1
  6. package/dist/src/commands/export.js +28 -6
  7. package/dist/src/commands/export.js.map +1 -1
  8. package/dist/src/commands/gen.d.ts.map +1 -1
  9. package/dist/src/commands/gen.js +66 -22
  10. package/dist/src/commands/gen.js.map +1 -1
  11. package/dist/src/commands/init.d.ts +8 -0
  12. package/dist/src/commands/init.d.ts.map +1 -1
  13. package/dist/src/commands/init.js +142 -52
  14. package/dist/src/commands/init.js.map +1 -1
  15. package/dist/src/commands/migrate.d.ts +9 -2
  16. package/dist/src/commands/migrate.d.ts.map +1 -1
  17. package/dist/src/commands/migrate.js +399 -73
  18. package/dist/src/commands/migrate.js.map +1 -1
  19. package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
  20. package/dist/src/commands/prompt-snapshot.js +40 -18
  21. package/dist/src/commands/prompt-snapshot.js.map +1 -1
  22. package/dist/src/commands/upgrade.d.ts +2 -0
  23. package/dist/src/commands/upgrade.d.ts.map +1 -0
  24. package/dist/src/commands/upgrade.js +163 -0
  25. package/dist/src/commands/upgrade.js.map +1 -0
  26. package/dist/src/commands/verify.d.ts.map +1 -1
  27. package/dist/src/commands/verify.js +360 -48
  28. package/dist/src/commands/verify.js.map +1 -1
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +55 -19
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/lib/allow.d.ts.map +1 -1
  33. package/dist/src/lib/allow.js +4 -0
  34. package/dist/src/lib/allow.js.map +1 -1
  35. package/dist/src/lib/args.d.ts +16 -2
  36. package/dist/src/lib/args.d.ts.map +1 -1
  37. package/dist/src/lib/args.js +19 -3
  38. package/dist/src/lib/args.js.map +1 -1
  39. package/dist/src/lib/codegen-drift.d.ts +8 -1
  40. package/dist/src/lib/codegen-drift.d.ts.map +1 -1
  41. package/dist/src/lib/codegen-drift.js +9 -1
  42. package/dist/src/lib/codegen-drift.js.map +1 -1
  43. package/dist/src/lib/detect-stack.d.ts +1 -1
  44. package/dist/src/lib/detect-stack.d.ts.map +1 -1
  45. package/dist/src/lib/detect-stack.js +22 -37
  46. package/dist/src/lib/detect-stack.js.map +1 -1
  47. package/dist/src/lib/load-metaobjects-config.d.ts +79 -1
  48. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
  49. package/dist/src/lib/load-metaobjects-config.js +123 -1
  50. package/dist/src/lib/load-metaobjects-config.js.map +1 -1
  51. package/dist/src/lib/manifest-ignored-check.d.ts +12 -0
  52. package/dist/src/lib/manifest-ignored-check.d.ts.map +1 -0
  53. package/dist/src/lib/manifest-ignored-check.js +74 -0
  54. package/dist/src/lib/manifest-ignored-check.js.map +1 -0
  55. package/dist/src/lib/migrate-scope.d.ts +49 -0
  56. package/dist/src/lib/migrate-scope.d.ts.map +1 -0
  57. package/dist/src/lib/migrate-scope.js +92 -0
  58. package/dist/src/lib/migrate-scope.js.map +1 -0
  59. package/dist/src/lib/output.d.ts +1 -1
  60. package/dist/src/lib/output.d.ts.map +1 -1
  61. package/dist/src/lib/output.js +9 -3
  62. package/dist/src/lib/output.js.map +1 -1
  63. package/dist/src/lib/requirement-check.d.ts.map +1 -1
  64. package/dist/src/lib/requirement-check.js +10 -57
  65. package/dist/src/lib/requirement-check.js.map +1 -1
  66. package/package.json +11 -11
  67. package/src/commands/docs.ts +174 -61
  68. package/src/commands/export.ts +30 -6
  69. package/src/commands/gen.ts +68 -22
  70. package/src/commands/init.ts +161 -58
  71. package/src/commands/migrate.ts +423 -75
  72. package/src/commands/prompt-snapshot.ts +43 -18
  73. package/src/commands/upgrade.ts +179 -0
  74. package/src/commands/verify.ts +397 -48
  75. package/src/index.ts +55 -19
  76. package/src/lib/allow.ts +4 -0
  77. package/src/lib/args.ts +35 -4
  78. package/src/lib/codegen-drift.ts +9 -0
  79. package/src/lib/detect-stack.ts +20 -33
  80. package/src/lib/load-metaobjects-config.ts +129 -2
  81. package/src/lib/manifest-ignored-check.ts +75 -0
  82. package/src/lib/migrate-scope.ts +102 -0
  83. package/src/lib/output.ts +8 -4
  84. package/src/lib/requirement-check.ts +10 -58
  85. package/dist/src/lib/verified-by-scan.d.ts +0 -20
  86. package/dist/src/lib/verified-by-scan.d.ts.map +0 -1
  87. package/dist/src/lib/verified-by-scan.js +0 -344
  88. package/dist/src/lib/verified-by-scan.js.map +0 -1
  89. package/src/lib/verified-by-scan.ts +0 -375
@@ -14,8 +14,8 @@ import { parsePromptSnapshotArgs } from "../lib/args.js";
14
14
  import { log } from "../lib/log.js";
15
15
  import { FileProvider } from "../lib/file-provider.js";
16
16
  import { snapshotPaths, unifiedDiff } from "../lib/snapshot.js";
17
- import { loadMetaobjectsConfig } from "../lib/load-metaobjects-config.js";
18
- import { loadMemory } from "@metaobjectsdev/sdk";
17
+ import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
18
+ import { loadMemory, resolveCollection } from "@metaobjectsdev/sdk";
19
19
  import { TYPE_TEMPLATE, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_FORMAT } from "@metaobjectsdev/metadata";
20
20
  import { render, ESCAPERS, type RenderFormat } from "@metaobjectsdev/render";
21
21
 
@@ -30,34 +30,59 @@ export async function promptSnapshotCommand(args: string[], cwd: string): Promis
30
30
  return 2;
31
31
  }
32
32
 
33
- // Best-effort load of metaobjects.config.ts to pick up consumer-supplied
34
- // providers. prompt-snapshot doesn't require codegen config; if it's absent
33
+ // Where the metadata lives is `resolveCollection`'s decision, not this
34
+ // command's `--check` is a drift GATE, so a project declaring `sources`
35
+ // elsewhere would otherwise gate against a stale `metaobjects/` (or report
36
+ // "no metaobjects/ found" for metadata it can see perfectly well). Discovery
37
+ // and load stay separate failure modes, the `meta gen` pattern: a broad catch
38
+ // around both reports a genuine ParseError as "no metadata found".
39
+ // `resolveCollection` raises ERR_COLLECTION_NOT_FOUND with its own message,
40
+ // replacing the hand-rolled ENOENT sniff that used to live here.
41
+ let collection;
42
+ try {
43
+ collection = await resolveCollection(cwd);
44
+ } catch (err) {
45
+ log.error((err as Error).message);
46
+ return 2;
47
+ }
48
+
49
+ // Everything project-relative below hangs off the DECLARING directory, not
50
+ // ambient cwd: `.metaobjects/snapshots/` is that config's own state, and the
51
+ // prompt text belongs to the same project as the metadata that references it.
52
+ // Identical to cwd for a run from the project root, which is the only
53
+ // invocation that worked before metadata sources were resolvable at all.
54
+ const projectRoot = collection.configDir;
55
+
56
+ // Best-effort load of metaobjects.config.ts to pick up consumer-supplied providers
57
+ // and shipped `libraries`. prompt-snapshot doesn't require codegen config; if it's absent
35
58
  // or invalid, fall back to defaults — the loader still works for any
36
59
  // metadata that only uses core+forge subtypes.
37
- let configProviders: NonNullable<Awaited<ReturnType<typeof loadMetaobjectsConfig>>["providers"]> | undefined;
60
+ let configLoadOptions: ReturnType<typeof loadMemoryOptionsFrom> = {};
38
61
  try {
39
- const forgeConfig = await loadMetaobjectsConfig(cwd);
40
- configProviders = forgeConfig.providers;
62
+ // `metaobjects.config.ts` gets its own nearest-ancestor walk, not `projectRoot`:
63
+ // it answers a different question from `.metaobjects/config.json` (design §4.6)
64
+ // and in a Maven- or pip-rooted monorepo the two legitimately sit in different
65
+ // directories, so reading it from the collection's would silently drop this
66
+ // package's providers (#326). Same path whenever the two files sit together.
67
+ configLoadOptions = loadMemoryOptionsFrom(
68
+ await loadMetaobjectsConfig(resolveGenConfigDir(cwd, projectRoot)),
69
+ );
41
70
  } catch {
42
- configProviders = undefined;
71
+ configLoadOptions = {};
43
72
  }
44
73
 
45
74
  let root;
46
75
  try {
47
- root = await loadMemory(cwd, {
48
- ...(configProviders !== undefined ? { providers: configProviders } : {}),
76
+ root = await loadMemory(collection.configDir, {
77
+ files: collection.files,
78
+ ...configLoadOptions,
49
79
  });
50
80
  } catch (err) {
51
- const msg = (err as Error).message;
52
- if (msg.includes("ENOENT") || msg.includes("no such") || msg.includes("cannot read")) {
53
- log.error(`no metaobjects/ found in ${cwd}; run 'meta init' to scaffold`);
54
- return 2;
55
- }
56
- log.error(`failed to load metadata: ${msg}`);
81
+ log.error(`failed to load metadata: ${(err as Error).message}`);
57
82
  return 1;
58
83
  }
59
84
 
60
- const promptsDir = join(cwd, flags.prompts ?? DEFAULT_PROMPTS_DIR);
85
+ const promptsDir = join(projectRoot, flags.prompts ?? DEFAULT_PROMPTS_DIR);
61
86
  const provider = new FileProvider(promptsDir);
62
87
 
63
88
  // ADR-0039: effective children — resolve rather than rely on root being unextended.
@@ -79,7 +104,7 @@ export async function promptSnapshotCommand(args: string[], cwd: string): Promis
79
104
  // Absent/typeless required attrs are a loader-schema concern, not ours.
80
105
  if (typeof textRef !== "string") continue;
81
106
 
82
- const { dir, payloadPath, snapPath } = snapshotPaths(cwd, tmpl.name);
107
+ const { dir, payloadPath, snapPath } = snapshotPaths(projectRoot, tmpl.name);
83
108
  if (!existsSync(payloadPath)) {
84
109
  log.info(`[${tmpl.name}] skipped — no payload at ${payloadPath}`);
85
110
  skipped++;
@@ -0,0 +1,179 @@
1
+ // server/typescript/packages/cli/src/commands/upgrade.ts
2
+ //
3
+ // `meta upgrade` — rewrite retired vocabulary in this project's metadata.
4
+ //
5
+ // DELIBERATELY NOT `meta migrate`. That command owns DATABASE SCHEMA (ADR-0015) and an
6
+ // adopter reading `migrate` expects DDL. Overloading it with a metadata rewrite would make
7
+ // the most destructive command in the toolchain ambiguous about what it touches.
8
+ //
9
+ // IT DOES NOT LOAD THE METADATA, and cannot. Once vocabulary is deregistered, metadata
10
+ // carrying it fails the load — which is exactly the state this command exists to repair. So
11
+ // it resolves the file SET through `resolveCollection` (the single authority on where
12
+ // metadata lives) and hands each file's RAW TEXT to the rewriter. See
13
+ // `metadata/src/vocabulary-rewrite.ts` for why that is the only workable shape.
14
+ //
15
+ // DRY-RUN BY DEFAULT, per this repo's convention for anything that edits committed files.
16
+ // `--apply` writes. Refusals exit NON-ZERO even when every mechanical change succeeded, so
17
+ // CI cannot mistake a partial upgrade for a finished one.
18
+
19
+ import { readFile, writeFile } from "node:fs/promises";
20
+ import { extname, relative } from "node:path";
21
+ import { resolveCollection } from "@metaobjectsdev/sdk";
22
+ import { rewriteDocument } from "@metaobjectsdev/metadata";
23
+ import { log } from "../lib/log.js";
24
+
25
+ /** YAML authoring (ADR-0006). Rewritten by the `yaml`-backed arm, loaded on demand below. */
26
+ const YAML_EXTENSIONS = new Set([".yaml", ".yml"]);
27
+
28
+ interface UpgradeFlags {
29
+ apply: boolean;
30
+ maxVersion?: string;
31
+ projectRoot?: string;
32
+ }
33
+
34
+ function parseArgs(argv: string[]): UpgradeFlags {
35
+ const flags: UpgradeFlags = { apply: false };
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const a = argv[i] as string;
38
+ if (a === "--apply") flags.apply = true;
39
+ else if (a === "--to") {
40
+ const v = argv[++i];
41
+ if (v === undefined) throw new Error("--to needs a version");
42
+ flags.maxVersion = v;
43
+ } else if (a.startsWith("--to=")) flags.maxVersion = a.slice("--to=".length);
44
+ else if (a === "--help" || a === "-h") throw new Error("__help__");
45
+ else if (!a.startsWith("-")) flags.projectRoot = a;
46
+ else throw new Error(`unknown option: ${a}`);
47
+ }
48
+ return flags;
49
+ }
50
+
51
+ export async function upgradeCommand(args: string[], cwd: string): Promise<number> {
52
+ let flags: UpgradeFlags;
53
+ try {
54
+ flags = parseArgs(args);
55
+ } catch (err) {
56
+ if ((err as Error).message === "__help__") {
57
+ log.info(
58
+ "meta upgrade [<project>] [--to <version>] [--apply]\n\n" +
59
+ " Rewrites retired metadata vocabulary in JSON and YAML metadata alike.\n" +
60
+ " Previews by default; --apply writes.\n" +
61
+ " Retirements needing a human decision are REFUSED and listed with their guide.\n\n" +
62
+ " Exit: 0 clean · 1 refusals remain · 2 bad usage · 3 some files could not be read.",
63
+ );
64
+ return 0;
65
+ }
66
+ log.error((err as Error).message);
67
+ return 2;
68
+ }
69
+
70
+ const projectRoot = flags.projectRoot ?? cwd;
71
+
72
+ let files: readonly string[];
73
+ try {
74
+ files = (await resolveCollection(projectRoot)).files;
75
+ } catch (err) {
76
+ log.error((err as Error).message);
77
+ return 1;
78
+ }
79
+
80
+ let totalChanges = 0;
81
+ let totalRefusals = 0;
82
+ let filesChanged = 0;
83
+ let checked = 0;
84
+ // Files we could not READ AT ALL. Distinct from "checked and clean" in every report and in
85
+ // the exit code — conflating them is the whole of #339.
86
+ const notChecked: string[] = [];
87
+
88
+ // The YAML arm carries the `yaml` package, so it lives behind its own subpath and is
89
+ // loaded only when the estate actually contains YAML. Importing it eagerly would pull a
90
+ // Node-only dependency into every `meta` invocation.
91
+ const hasYaml = files.some((f) => YAML_EXTENSIONS.has(extname(f).toLowerCase()));
92
+ const rewriteYaml = hasYaml
93
+ ? (await import("@metaobjectsdev/metadata/vocabulary-rewrite-yaml")).rewriteYamlDocument
94
+ : undefined;
95
+
96
+ const opts = flags.maxVersion !== undefined ? { maxVersion: flags.maxVersion } : {};
97
+
98
+ for (const file of files) {
99
+ const rel = relative(projectRoot, file);
100
+ const before = await readFile(file, "utf8");
101
+
102
+ let r;
103
+ if (YAML_EXTENSIONS.has(extname(file).toLowerCase())) {
104
+ const y = rewriteYaml?.(before, opts);
105
+ // A document that does not parse was not examined, and must never be counted as clean.
106
+ if (y === undefined || y.unparseable) {
107
+ notChecked.push(rel);
108
+ continue;
109
+ }
110
+ r = y;
111
+ } else {
112
+ r = rewriteDocument(before, opts);
113
+ }
114
+ checked++;
115
+ if (r.changes.length === 0 && r.refusals.length === 0) continue;
116
+
117
+ log.info(`\n${rel}`);
118
+ for (const c of r.changes) log.info(` ${c.line}: @${c.from} → ${c.to}`);
119
+ for (const f of r.refusals) {
120
+ log.warn(
121
+ ` ${f.line}: ${f.subject}${f.value !== undefined ? `: ${f.value}` : ""} — needs a decision. ` +
122
+ `Retired in ${f.since}. ${f.migration !== undefined ? `See ${f.migration}` : f.why}`,
123
+ );
124
+ }
125
+
126
+ totalChanges += r.changes.length;
127
+ totalRefusals += r.refusals.length;
128
+ if (r.changes.length > 0) {
129
+ filesChanged++;
130
+ if (flags.apply) await writeFile(file, r.text, "utf8");
131
+ }
132
+ }
133
+
134
+ log.info("");
135
+ if (notChecked.length > 0) {
136
+ log.warn(
137
+ `${notChecked.length} file(s) could not be parsed and were NOT checked — fix these ` +
138
+ `first, then re-run:\n ${notChecked.join("\n ")}`,
139
+ );
140
+ }
141
+
142
+ // Every conclusion states how many files it is a conclusion ABOUT. "No retired vocabulary
143
+ // found" read on its own says the estate is clean, and it is the last line, so it is the
144
+ // one that sticks — on the estate that reported this, it was the opposite of the truth.
145
+ const scope = `${checked} file(s) checked${notChecked.length > 0 ? `, ${notChecked.length} NOT checked` : ""}`;
146
+
147
+ if (totalChanges === 0 && totalRefusals === 0) {
148
+ log.info(`meta upgrade — no retired vocabulary found (${scope}).`);
149
+ } else if (totalChanges === 0) {
150
+ // Refusals only. Reporting "rewrote 0 declarations", or advertising `--apply`, both
151
+ // promise an action guaranteed to change nothing and bury the fact that the remaining
152
+ // work is entirely human.
153
+ log.info("meta upgrade — nothing here can be rewritten automatically.");
154
+ } else if (flags.apply) {
155
+ log.info(`meta upgrade — rewrote ${totalChanges} declaration(s) across ${filesChanged} file(s).`);
156
+ } else {
157
+ log.info(
158
+ `meta upgrade — ${totalChanges} declaration(s) in ${filesChanged} file(s) can be rewritten. ` +
159
+ `Re-run with --apply to write.`,
160
+ );
161
+ }
162
+
163
+ // Non-zero while ANY refusal stands, applied or not. A partial upgrade that exited 0 would
164
+ // let CI record the migration as done while metadata still fails to load.
165
+ if (totalRefusals > 0) {
166
+ log.error(
167
+ `${totalRefusals} declaration(s) need a human decision and were left untouched — ` +
168
+ `see the guides listed above.`,
169
+ );
170
+ return 1;
171
+ }
172
+
173
+ // "I could not look" gets its OWN code. It used to share exit 1 with "work remains", so a
174
+ // script could not tell an estate needing decisions from one the tool never opened — and
175
+ // an adopter whose whole estate was skipped got a failure exit next to a message saying
176
+ // nothing was found.
177
+ if (notChecked.length > 0) return 3;
178
+ return 0;
179
+ }