@metaobjectsdev/cli 0.15.2 → 0.15.4-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.
@@ -1 +1 @@
1
- {"version":3,"file":"load-metaobjects-config.d.ts","sourceRoot":"","sources":["../../../src/lib/load-metaobjects-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAmFvE,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgC9F"}
1
+ {"version":3,"file":"load-metaobjects-config.d.ts","sourceRoot":"","sources":["../../../src/lib/load-metaobjects-config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AA8IvE,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8E9F"}
@@ -1,9 +1,16 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, readdirSync, unlinkSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { createRequire } from "node:module";
3
- import { resolve } from "node:path";
4
+ import { dirname, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
6
+ import { randomBytes } from "node:crypto";
5
7
  import { createJiti } from "jiti";
6
8
  const CONFIG_FILE = "metaobjects.config.ts";
9
+ // Prefix for the transient, pre-processed config the loader writes next to the
10
+ // user's config (see the rewrite step below). Normally deleted in a finally
11
+ // block; the prefix is also used to sweep any copies stranded by an abnormal
12
+ // exit so a consumer's `git status` never surfaces one.
13
+ const PROC_TEMP_PREFIX = ".metaobjects-config-proc-";
7
14
  // Resolve @metaobjectsdev/codegen-ts from the CLI's own node_modules so that
8
15
  // metaobjects.config.ts (which lives in the user's project) can import it even
9
16
  // when the user's project has no direct dependency on the package.
@@ -53,8 +60,8 @@ const CLI_PKG_PATHS = {
53
60
  src: "src/index.ts",
54
61
  },
55
62
  };
56
- // Resolve a codegen specifier to an absolute path for jiti's alias map, so a
57
- // user's metaobjects.config.ts can import @metaobjectsdev/codegen-ts* without
63
+ // Resolve a codegen specifier to an absolute path, so a user's
64
+ // metaobjects.config.ts can import @metaobjectsdev/codegen-ts* without
58
65
  // declaring it directly — the CLI's own copy is used.
59
66
  //
60
67
  // Standard module resolution is tried first: it follows whatever node_modules
@@ -62,6 +69,12 @@ const CLI_PKG_PATHS = {
62
69
  // NOT nested under the CLI dir), or bun — and honors the package's export
63
70
  // conditions. The CLI_PKG_PATHS fallback only kicks in when a specifier isn't
64
71
  // require-resolvable from the CLI module.
72
+ //
73
+ // Under Bun's native loader `require.resolve` returns the TypeScript source
74
+ // path (via the "bun" export condition, e.g. `src/index.ts`). When a `.ts`
75
+ // path is returned, we prefer the compiled dist path (always a plain JS file)
76
+ // so the pre-processed config source references a deterministic artifact that
77
+ // won't be intercepted by stale ancestor node_modules directories.
65
78
  function resolveCliPkg(specifier) {
66
79
  const paths = CLI_PKG_PATHS[specifier];
67
80
  // The cli self-reference always points at this package's own entry, never a
@@ -70,7 +83,17 @@ function resolveCliPkg(specifier) {
70
83
  return resolve(_cliDir, _isCompiled ? paths.dist : paths.src);
71
84
  }
72
85
  try {
73
- return _require.resolve(specifier);
86
+ const resolved = _require.resolve(specifier);
87
+ // When Bun's native "bun" export condition returns a `.ts` source path,
88
+ // prefer the compiled dist path so the pre-processed config references a
89
+ // stable JS artifact that the loader can load without the "bun" condition
90
+ // redirecting it to a different module instance.
91
+ if (resolved.endsWith(".ts") && paths !== undefined) {
92
+ const distCandidate = resolve(_cliDir, paths.dist);
93
+ if (existsSync(distCandidate))
94
+ return distCandidate;
95
+ }
96
+ return resolved;
74
97
  }
75
98
  catch {
76
99
  if (paths !== undefined) {
@@ -81,35 +104,114 @@ function resolveCliPkg(specifier) {
81
104
  throw new Error(`metaobjects: could not resolve ${specifier} from the CLI — try reinstalling @metaobjectsdev/cli.`);
82
105
  }
83
106
  }
107
+ /**
108
+ * Rewrite `from "specifier"` / `export … from "specifier"` / bare
109
+ * `import "specifier"` occurrences for a known set of package specifiers.
110
+ * Returns the original string unchanged when no substitutions are needed.
111
+ *
112
+ * This is necessary because jiti 2.x under Bun's native ESM loader does not
113
+ * apply its `alias` map — Bun intercepts the `import()` call and resolves
114
+ * modules itself, ignoring jiti's alias configuration. By rewriting the
115
+ * specifiers in the source text before loading, we guarantee that absolute
116
+ * paths are used regardless of which loader takes over.
117
+ *
118
+ * Relative imports (`./foo`, `../bar`) are intentionally NOT rewritten; they
119
+ * must continue to resolve relative to the config file's own directory.
120
+ *
121
+ * Dynamic `import("specifier")` calls are NOT rewritten either — configs are
122
+ * loaded eagerly and none of the scaffolded templates use them; a dynamic
123
+ * import of an aliased package falls back to normal module resolution (and to
124
+ * jiti's alias map on non-Bun runtimes).
125
+ */
126
+ function rewriteImportSpecifiers(source, aliasMap) {
127
+ let result = source;
128
+ for (const [specifier, resolvedPath] of Object.entries(aliasMap)) {
129
+ const esc = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
130
+ // Rewrite: from "specifier" or from 'specifier'
131
+ // Covers: import { X } from "pkg", export { X } from "pkg"
132
+ result = result.replace(new RegExp(`(\\bfrom\\s+)(['"])${esc}\\2`, "g"), (_m, prefix, quote) => `${prefix}${quote}${resolvedPath}${quote}`);
133
+ // Rewrite: import "specifier" or import 'specifier' (side-effect imports)
134
+ result = result.replace(new RegExp(`(\\bimport\\s+)(['"])${esc}\\2`, "g"), (_m, prefix, quote) => `${prefix}${quote}${resolvedPath}${quote}`);
135
+ }
136
+ return result;
137
+ }
84
138
  export async function loadMetaobjectsConfig(projectRoot) {
85
139
  const fullPath = resolve(projectRoot, CONFIG_FILE);
86
140
  if (!existsSync(fullPath)) {
87
141
  throw new Error(`metaobjects.config.ts not found at ${fullPath}. Run 'meta init' to scaffold one.`);
88
142
  }
89
- // Use import.meta.url as base so jiti resolves workspace deps (@metaobjectsdev/*)
90
- // from the CLI's own node_modules, not from the user's project root.
91
- // The alias map redirects codegen-ts imports to the CLI's own copy so that
92
- // user projects don't need @metaobjectsdev/codegen-ts as a direct dependency.
143
+ // Self-heal: a SIGKILL mid-load can strand a pre-processed temp config
144
+ // (PROC_TEMP_PREFIX*.ts) next to the user's config, since deletion normally
145
+ // happens in the finally block below. Sweep any stale ones before loading so
146
+ // an abnormal exit never pollutes the consumer's working tree.
147
+ const configDir = dirname(fullPath);
148
+ for (const entry of readdirSync(configDir)) {
149
+ if (entry.startsWith(PROC_TEMP_PREFIX) && entry.endsWith(".ts")) {
150
+ try {
151
+ unlinkSync(resolve(configDir, entry));
152
+ }
153
+ catch { /* best-effort */ }
154
+ }
155
+ }
156
+ // Build the canonical alias map: specifier → resolved absolute path.
157
+ const aliasMap = {};
158
+ for (const specifier of Object.keys(CLI_PKG_PATHS)) {
159
+ aliasMap[specifier] = resolveCliPkg(specifier);
160
+ }
161
+ // Pre-process the config file content to rewrite @metaobjectsdev/* import
162
+ // specifiers to resolved absolute paths. This is the primary mechanism that
163
+ // ensures the CLI's own copies are used: jiti's `alias` map is kept below as
164
+ // a belt-and-suspenders fallback for runtimes where jiti's transformer IS
165
+ // active, but under Bun the pre-processed source is the operative fix.
166
+ const original = await readFile(fullPath, "utf8");
167
+ const processed = rewriteImportSpecifiers(original, aliasMap);
168
+ // When any specifiers were rewritten, write the modified source to a temp
169
+ // file in the SAME directory as the original so that relative imports inside
170
+ // the config (e.g. `from "./codegen/generators/entity"`) still resolve
171
+ // correctly. The file is deleted in the finally block below.
172
+ let loadPath = fullPath;
173
+ let tempCreated = false;
174
+ if (processed !== original) {
175
+ const tempName = `${PROC_TEMP_PREFIX}${randomBytes(4).toString("hex")}.ts`;
176
+ const tempPath = resolve(dirname(fullPath), tempName);
177
+ try {
178
+ await writeFile(tempPath, processed, "utf8");
179
+ loadPath = tempPath;
180
+ tempCreated = true;
181
+ }
182
+ catch {
183
+ // Temp write failed — fall back to the original file and let the loader
184
+ // attempt its own resolution (best-effort; may still fail in hostile envs).
185
+ loadPath = fullPath;
186
+ }
187
+ }
188
+ // jiti's alias map is a belt-and-suspenders fallback for runtimes where
189
+ // jiti's own transformer is active (non-Bun, or a future jiti version that
190
+ // re-enables alias under Bun). Under Bun the pre-processed file above is the
191
+ // operative fix.
93
192
  const jiti = createJiti(import.meta.url, {
94
193
  interopDefault: true,
95
- alias: {
96
- "@metaobjectsdev/codegen-ts": resolveCliPkg("@metaobjectsdev/codegen-ts"),
97
- "@metaobjectsdev/metadata": resolveCliPkg("@metaobjectsdev/metadata"),
98
- "@metaobjectsdev/codegen-ts/generators": resolveCliPkg("@metaobjectsdev/codegen-ts/generators"),
99
- "@metaobjectsdev/codegen-ts-react": resolveCliPkg("@metaobjectsdev/codegen-ts-react"),
100
- "@metaobjectsdev/codegen-ts-tanstack": resolveCliPkg("@metaobjectsdev/codegen-ts-tanstack"),
101
- "@metaobjectsdev/cli": resolveCliPkg("@metaobjectsdev/cli"),
102
- },
194
+ alias: aliasMap,
103
195
  });
104
- const raw = (await jiti.import(fullPath));
105
- // jiti's interopDefault doesn't always unwrap the default export when accessed
106
- // across module boundaries explicitly unwrap if present.
107
- const cfg = (raw && typeof raw === "object" && "default" in raw && raw.default
108
- ? raw.default
109
- : raw);
110
- if (!cfg || typeof cfg !== "object" || !Array.isArray(cfg.generators)) {
111
- throw new Error(`metaobjects.config.ts at ${fullPath} did not export a valid MetaobjectsGenConfig (missing 'generators' array).`);
196
+ try {
197
+ const raw = (await jiti.import(loadPath));
198
+ // jiti's interopDefault doesn't always unwrap the default export when accessed
199
+ // across module boundaries explicitly unwrap if present.
200
+ const cfg = (raw && typeof raw === "object" && "default" in raw && raw.default
201
+ ? raw.default
202
+ : raw);
203
+ if (!cfg || typeof cfg !== "object" || !Array.isArray(cfg.generators)) {
204
+ throw new Error(`metaobjects.config.ts at ${fullPath} did not export a valid MetaobjectsGenConfig (missing 'generators' array).`);
205
+ }
206
+ return cfg;
207
+ }
208
+ finally {
209
+ if (tempCreated) {
210
+ try {
211
+ unlinkSync(loadPath);
212
+ }
213
+ catch { /* best-effort cleanup */ }
214
+ }
112
215
  }
113
- return cfg;
114
216
  }
115
217
  //# sourceMappingURL=load-metaobjects-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-metaobjects-config.js","sourceRoot":"","sources":["../../../src/lib/load-metaobjects-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"load-metaobjects-config.js","sourceRoot":"","sources":["../../../src/lib/load-metaobjects-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAGlC,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAE5C,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,wDAAwD;AACxD,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AAErD,6EAA6E;AAC7E,+EAA+E;AAC/E,mEAAmE;AACnE,EAAE;AACF,mFAAmF;AACnF,mFAAmF;AACnF,+FAA+F;AAC/F,gEAAgE;AAChE,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjD,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACjD,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AAC7E,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,8EAA8E;AAC9E,4EAA4E;AAC5E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,6BAA6B;AAC7B,MAAM,aAAa,GAAkD;IACnE,4BAA4B,EAAE;QAC5B,IAAI,EAAE,uDAAuD;QAC7D,GAAG,EAAE,sDAAsD;KAC5D;IACD,0EAA0E;IAC1E,2EAA2E;IAC3E,4EAA4E;IAC5E,0BAA0B,EAAE;QAC1B,IAAI,EAAE,qDAAqD;QAC3D,GAAG,EAAE,oDAAoD;KAC1D;IACD,uCAAuC,EAAE;QACvC,IAAI,EAAE,kEAAkE;QACxE,GAAG,EAAE,iEAAiE;KACvE;IACD,kCAAkC,EAAE;QAClC,IAAI,EAAE,6DAA6D;QACnE,GAAG,EAAE,4DAA4D;KAClE;IACD,qCAAqC,EAAE;QACrC,IAAI,EAAE,gEAAgE;QACtE,GAAG,EAAE,+DAA+D;KACrE;IACD,qBAAqB,EAAE;QACrB,IAAI,EAAE,mBAAmB;QACzB,GAAG,EAAE,cAAc;KACpB;CACF,CAAC;AAEF,+DAA+D;AAC/D,uEAAuE;AACvE,sDAAsD;AACtD,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,8EAA8E;AAC9E,8EAA8E;AAC9E,mEAAmE;AACnE,SAAS,aAAa,CAAC,SAAiB;IACtC,MAAM,KAAK,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACvC,4EAA4E;IAC5E,2DAA2D;IAC3D,IAAI,SAAS,KAAK,qBAAqB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/D,OAAO,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC7C,wEAAwE;QACxE,yEAAyE;QACzE,0EAA0E;QAC1E,iDAAiD;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACpD,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACnD,IAAI,UAAU,CAAC,aAAa,CAAC;gBAAE,OAAO,aAAa,CAAC;QACtD,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzE,IAAI,UAAU,CAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAC;QAC9C,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kCAAkC,SAAS,uDAAuD,CACnG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,uBAAuB,CAAC,MAAc,EAAE,QAAgC;IAC/E,IAAI,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC7D,gDAAgD;QAChD,2DAA2D;QAC3D,MAAM,GAAG,MAAM,CAAC,OAAO,CACrB,IAAI,MAAM,CAAC,sBAAsB,GAAG,KAAK,EAAE,GAAG,CAAC,EAC/C,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,EAAE,CAClE,CAAC;QACF,0EAA0E;QAC1E,MAAM,GAAG,MAAM,CAAC,OAAO,CACrB,IAAI,MAAM,CAAC,wBAAwB,GAAG,KAAK,EAAE,GAAG,CAAC,EACjD,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,EAAE,CAClE,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,WAAmB;IAC7D,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,sCAAsC,QAAQ,oCAAoC,CACnF,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,4EAA4E;IAC5E,6EAA6E;IAC7E,+DAA+D;IAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC;gBAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QACnD,QAAQ,CAAC,SAAS,CAAC,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,uEAAuE;IACvE,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE9D,0EAA0E;IAC1E,6EAA6E;IAC7E,uEAAuE;IACvE,6DAA6D;IAC7D,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;QAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YAC7C,QAAQ,GAAG,QAAQ,CAAC;YACpB,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;YACxE,4EAA4E;YAC5E,QAAQ,GAAG,QAAQ,CAAC;QACtB,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,6EAA6E;IAC7E,iBAAiB;IACjB,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACvC,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAA6D,CAAC;QACtG,+EAA+E;QAC/E,2DAA2D;QAC3D,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO;YAC5E,CAAC,CAAE,GAAyC,CAAC,OAAO;YACpD,CAAC,CAAC,GAAG,CAAyB,CAAC;QACjC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,4EAA4E,CAAC,CAAC;QACpI,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,CAAC;gBAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -1,10 +1,24 @@
1
1
  export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
2
+ /**
3
+ * Options for `detectPackageManager`.
4
+ */
5
+ export interface DetectPackageManagerOptions {
6
+ /**
7
+ * Stop walking up the directory tree at this path (inclusive). Useful in
8
+ * tests or isolated environments where walking above the project root would
9
+ * accidentally find a lockfile in an unrelated ancestor directory (e.g. a
10
+ * stale `/tmp/package-lock.json` on a shared CI machine).
11
+ *
12
+ * If omitted the walk continues all the way to the filesystem root.
13
+ */
14
+ stopAt?: string;
15
+ }
2
16
  /**
3
17
  * Detect the package manager in use by looking for the lockfile closest to
4
- * `dir`. Walks up to the root. Returns "bun" as the default when no lockfile
5
- * is found.
18
+ * `dir`. Walks up to the filesystem root (or `options.stopAt`, if supplied).
19
+ * Returns "bun" as the default when no lockfile is found.
6
20
  */
7
- export declare function detectPackageManager(dir: string): Promise<PackageManager>;
21
+ export declare function detectPackageManager(dir: string, options?: DetectPackageManagerOptions): Promise<PackageManager>;
8
22
  /**
9
23
  * Returns the install command for a missing package, using the detected PM.
10
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"pm-detect.d.ts","sourceRoot":"","sources":["../../../src/lib/pm-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAyB/E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY9E"}
1
+ {"version":3,"file":"pm-detect.d.ts","sourceRoot":"","sources":["../../../src/lib/pm-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY9E"}
@@ -2,11 +2,11 @@ import { access } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  /**
4
4
  * Detect the package manager in use by looking for the lockfile closest to
5
- * `dir`. Walks up to the root. Returns "bun" as the default when no lockfile
6
- * is found.
5
+ * `dir`. Walks up to the filesystem root (or `options.stopAt`, if supplied).
6
+ * Returns "bun" as the default when no lockfile is found.
7
7
  */
8
- export async function detectPackageManager(dir) {
9
- // Check in the given dir first, then parent dirs up to the FS root.
8
+ export async function detectPackageManager(dir, options) {
9
+ // Check in the given dir first, then parent dirs up to the FS root (or stopAt).
10
10
  let current = dir;
11
11
  while (true) {
12
12
  const candidates = [
@@ -25,9 +25,12 @@ export async function detectPackageManager(dir) {
25
25
  // not found, try next
26
26
  }
27
27
  }
28
+ // Honor the stopAt boundary before walking up further.
29
+ if (options?.stopAt !== undefined && current === options.stopAt)
30
+ break;
28
31
  const parent = join(current, "..");
29
32
  if (parent === current)
30
- break; // reached root
33
+ break; // reached filesystem root
31
34
  current = parent;
32
35
  }
33
36
  // Default: bun (most common for this toolchain)
@@ -1 +1 @@
1
- {"version":3,"file":"pm-detect.js","sourceRoot":"","sources":["../../../src/lib/pm-detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW;IACpD,oEAAoE;IACpE,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAA+B;YAC7C,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,EAAE,KAAK,CAAC;YAC3C,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;YACzC,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC;YACpC,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,KAAK,CAAC;YACnC,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC;SACnC,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,OAAO;YAAE,MAAM,CAAC,eAAe;QAC9C,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IACD,gDAAgD;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,GAAW;IAC3D,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC3C,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,KAAK;YACR,OAAO,eAAe,GAAG,EAAE,CAAC;QAC9B,KAAK,MAAM;YACT,OAAO,YAAY,GAAG,EAAE,CAAC;QAC3B,KAAK,MAAM;YACT,OAAO,YAAY,GAAG,EAAE,CAAC;QAC3B,KAAK,KAAK;YACR,OAAO,WAAW,GAAG,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"pm-detect.js","sourceRoot":"","sources":["../../../src/lib/pm-detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmBjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAW,EACX,OAAqC;IAErC,gFAAgF;IAChF,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAA+B;YAC7C,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,EAAE,KAAK,CAAC;YAC3C,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;YACzC,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC;YACpC,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,KAAK,CAAC;YACnC,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC;SACnC,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;QACD,uDAAuD;QACvD,IAAI,OAAO,EAAE,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,OAAO,CAAC,MAAM;YAAE,MAAM;QACvE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,OAAO;YAAE,MAAM,CAAC,0BAA0B;QACzD,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IACD,gDAAgD;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,GAAW;IAC3D,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC3C,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,KAAK;YACR,OAAO,eAAe,GAAG,EAAE,CAAC;QAC9B,KAAK,MAAM;YACT,OAAO,YAAY,GAAG,EAAE,CAAC;QAC3B,KAAK,MAAM;YACT,OAAO,YAAY,GAAG,EAAE,CAAC;QAC3B,KAAK,KAAK;YACR,OAAO,WAAW,GAAG,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "0.15.2",
3
+ "version": "0.15.4-rc.1",
4
4
  "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -49,14 +49,14 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@libsql/kysely-libsql": "^0.4.0",
52
- "@metaobjectsdev/codegen-ts": "0.15.2",
53
- "@metaobjectsdev/codegen-ts-react": "0.15.2",
54
- "@metaobjectsdev/codegen-ts-tanstack": "0.15.2",
55
- "@metaobjectsdev/metadata": "0.15.2",
56
- "@metaobjectsdev/migrate-ts": "0.15.2",
57
- "@metaobjectsdev/render": "0.15.2",
58
- "@metaobjectsdev/runtime-ts": "0.15.2",
59
- "@metaobjectsdev/sdk": "0.15.2",
52
+ "@metaobjectsdev/codegen-ts": "0.15.4-rc.1",
53
+ "@metaobjectsdev/codegen-ts-react": "0.15.4-rc.1",
54
+ "@metaobjectsdev/codegen-ts-tanstack": "0.15.4-rc.1",
55
+ "@metaobjectsdev/metadata": "0.15.4-rc.1",
56
+ "@metaobjectsdev/migrate-ts": "0.15.4-rc.1",
57
+ "@metaobjectsdev/render": "0.15.4-rc.1",
58
+ "@metaobjectsdev/runtime-ts": "0.15.4-rc.1",
59
+ "@metaobjectsdev/sdk": "0.15.4-rc.1",
60
60
  "@toon-format/toon": "^2.3.0",
61
61
  "jiti": "^2.4.0"
62
62
  },
@@ -1,12 +1,20 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, readdirSync, unlinkSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { createRequire } from "node:module";
3
- import { resolve } from "node:path";
4
+ import { dirname, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
6
+ import { randomBytes } from "node:crypto";
5
7
  import { createJiti } from "jiti";
6
8
  import type { MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
7
9
 
8
10
  const CONFIG_FILE = "metaobjects.config.ts";
9
11
 
12
+ // Prefix for the transient, pre-processed config the loader writes next to the
13
+ // user's config (see the rewrite step below). Normally deleted in a finally
14
+ // block; the prefix is also used to sweep any copies stranded by an abnormal
15
+ // exit so a consumer's `git status` never surfaces one.
16
+ const PROC_TEMP_PREFIX = ".metaobjects-config-proc-";
17
+
10
18
  // Resolve @metaobjectsdev/codegen-ts from the CLI's own node_modules so that
11
19
  // metaobjects.config.ts (which lives in the user's project) can import it even
12
20
  // when the user's project has no direct dependency on the package.
@@ -57,8 +65,8 @@ const CLI_PKG_PATHS: Record<string, { dist: string; src: string }> = {
57
65
  },
58
66
  };
59
67
 
60
- // Resolve a codegen specifier to an absolute path for jiti's alias map, so a
61
- // user's metaobjects.config.ts can import @metaobjectsdev/codegen-ts* without
68
+ // Resolve a codegen specifier to an absolute path, so a user's
69
+ // metaobjects.config.ts can import @metaobjectsdev/codegen-ts* without
62
70
  // declaring it directly — the CLI's own copy is used.
63
71
  //
64
72
  // Standard module resolution is tried first: it follows whatever node_modules
@@ -66,6 +74,12 @@ const CLI_PKG_PATHS: Record<string, { dist: string; src: string }> = {
66
74
  // NOT nested under the CLI dir), or bun — and honors the package's export
67
75
  // conditions. The CLI_PKG_PATHS fallback only kicks in when a specifier isn't
68
76
  // require-resolvable from the CLI module.
77
+ //
78
+ // Under Bun's native loader `require.resolve` returns the TypeScript source
79
+ // path (via the "bun" export condition, e.g. `src/index.ts`). When a `.ts`
80
+ // path is returned, we prefer the compiled dist path (always a plain JS file)
81
+ // so the pre-processed config source references a deterministic artifact that
82
+ // won't be intercepted by stale ancestor node_modules directories.
69
83
  function resolveCliPkg(specifier: string): string {
70
84
  const paths = CLI_PKG_PATHS[specifier];
71
85
  // The cli self-reference always points at this package's own entry, never a
@@ -74,7 +88,16 @@ function resolveCliPkg(specifier: string): string {
74
88
  return resolve(_cliDir, _isCompiled ? paths.dist : paths.src);
75
89
  }
76
90
  try {
77
- return _require.resolve(specifier);
91
+ const resolved = _require.resolve(specifier);
92
+ // When Bun's native "bun" export condition returns a `.ts` source path,
93
+ // prefer the compiled dist path so the pre-processed config references a
94
+ // stable JS artifact that the loader can load without the "bun" condition
95
+ // redirecting it to a different module instance.
96
+ if (resolved.endsWith(".ts") && paths !== undefined) {
97
+ const distCandidate = resolve(_cliDir, paths.dist);
98
+ if (existsSync(distCandidate)) return distCandidate;
99
+ }
100
+ return resolved;
78
101
  } catch {
79
102
  if (paths !== undefined) {
80
103
  const candidate = resolve(_cliDir, _isCompiled ? paths.dist : paths.src);
@@ -86,6 +109,44 @@ function resolveCliPkg(specifier: string): string {
86
109
  }
87
110
  }
88
111
 
112
+ /**
113
+ * Rewrite `from "specifier"` / `export … from "specifier"` / bare
114
+ * `import "specifier"` occurrences for a known set of package specifiers.
115
+ * Returns the original string unchanged when no substitutions are needed.
116
+ *
117
+ * This is necessary because jiti 2.x under Bun's native ESM loader does not
118
+ * apply its `alias` map — Bun intercepts the `import()` call and resolves
119
+ * modules itself, ignoring jiti's alias configuration. By rewriting the
120
+ * specifiers in the source text before loading, we guarantee that absolute
121
+ * paths are used regardless of which loader takes over.
122
+ *
123
+ * Relative imports (`./foo`, `../bar`) are intentionally NOT rewritten; they
124
+ * must continue to resolve relative to the config file's own directory.
125
+ *
126
+ * Dynamic `import("specifier")` calls are NOT rewritten either — configs are
127
+ * loaded eagerly and none of the scaffolded templates use them; a dynamic
128
+ * import of an aliased package falls back to normal module resolution (and to
129
+ * jiti's alias map on non-Bun runtimes).
130
+ */
131
+ function rewriteImportSpecifiers(source: string, aliasMap: Record<string, string>): string {
132
+ let result = source;
133
+ for (const [specifier, resolvedPath] of Object.entries(aliasMap)) {
134
+ const esc = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
135
+ // Rewrite: from "specifier" or from 'specifier'
136
+ // Covers: import { X } from "pkg", export { X } from "pkg"
137
+ result = result.replace(
138
+ new RegExp(`(\\bfrom\\s+)(['"])${esc}\\2`, "g"),
139
+ (_m, prefix, quote) => `${prefix}${quote}${resolvedPath}${quote}`,
140
+ );
141
+ // Rewrite: import "specifier" or import 'specifier' (side-effect imports)
142
+ result = result.replace(
143
+ new RegExp(`(\\bimport\\s+)(['"])${esc}\\2`, "g"),
144
+ (_m, prefix, quote) => `${prefix}${quote}${resolvedPath}${quote}`,
145
+ );
146
+ }
147
+ return result;
148
+ }
149
+
89
150
  export async function loadMetaobjectsConfig(projectRoot: string): Promise<MetaobjectsGenConfig> {
90
151
  const fullPath = resolve(projectRoot, CONFIG_FILE);
91
152
  if (!existsSync(fullPath)) {
@@ -93,29 +154,75 @@ export async function loadMetaobjectsConfig(projectRoot: string): Promise<Metaob
93
154
  `metaobjects.config.ts not found at ${fullPath}. Run 'meta init' to scaffold one.`,
94
155
  );
95
156
  }
96
- // Use import.meta.url as base so jiti resolves workspace deps (@metaobjectsdev/*)
97
- // from the CLI's own node_modules, not from the user's project root.
98
- // The alias map redirects codegen-ts imports to the CLI's own copy so that
99
- // user projects don't need @metaobjectsdev/codegen-ts as a direct dependency.
157
+
158
+ // Self-heal: a SIGKILL mid-load can strand a pre-processed temp config
159
+ // (PROC_TEMP_PREFIX*.ts) next to the user's config, since deletion normally
160
+ // happens in the finally block below. Sweep any stale ones before loading so
161
+ // an abnormal exit never pollutes the consumer's working tree.
162
+ const configDir = dirname(fullPath);
163
+ for (const entry of readdirSync(configDir)) {
164
+ if (entry.startsWith(PROC_TEMP_PREFIX) && entry.endsWith(".ts")) {
165
+ try { unlinkSync(resolve(configDir, entry)); } catch { /* best-effort */ }
166
+ }
167
+ }
168
+
169
+ // Build the canonical alias map: specifier → resolved absolute path.
170
+ const aliasMap: Record<string, string> = {};
171
+ for (const specifier of Object.keys(CLI_PKG_PATHS)) {
172
+ aliasMap[specifier] = resolveCliPkg(specifier);
173
+ }
174
+
175
+ // Pre-process the config file content to rewrite @metaobjectsdev/* import
176
+ // specifiers to resolved absolute paths. This is the primary mechanism that
177
+ // ensures the CLI's own copies are used: jiti's `alias` map is kept below as
178
+ // a belt-and-suspenders fallback for runtimes where jiti's transformer IS
179
+ // active, but under Bun the pre-processed source is the operative fix.
180
+ const original = await readFile(fullPath, "utf8");
181
+ const processed = rewriteImportSpecifiers(original, aliasMap);
182
+
183
+ // When any specifiers were rewritten, write the modified source to a temp
184
+ // file in the SAME directory as the original so that relative imports inside
185
+ // the config (e.g. `from "./codegen/generators/entity"`) still resolve
186
+ // correctly. The file is deleted in the finally block below.
187
+ let loadPath = fullPath;
188
+ let tempCreated = false;
189
+ if (processed !== original) {
190
+ const tempName = `${PROC_TEMP_PREFIX}${randomBytes(4).toString("hex")}.ts`;
191
+ const tempPath = resolve(dirname(fullPath), tempName);
192
+ try {
193
+ await writeFile(tempPath, processed, "utf8");
194
+ loadPath = tempPath;
195
+ tempCreated = true;
196
+ } catch {
197
+ // Temp write failed — fall back to the original file and let the loader
198
+ // attempt its own resolution (best-effort; may still fail in hostile envs).
199
+ loadPath = fullPath;
200
+ }
201
+ }
202
+
203
+ // jiti's alias map is a belt-and-suspenders fallback for runtimes where
204
+ // jiti's own transformer is active (non-Bun, or a future jiti version that
205
+ // re-enables alias under Bun). Under Bun the pre-processed file above is the
206
+ // operative fix.
100
207
  const jiti = createJiti(import.meta.url, {
101
208
  interopDefault: true,
102
- alias: {
103
- "@metaobjectsdev/codegen-ts": resolveCliPkg("@metaobjectsdev/codegen-ts"),
104
- "@metaobjectsdev/metadata": resolveCliPkg("@metaobjectsdev/metadata"),
105
- "@metaobjectsdev/codegen-ts/generators": resolveCliPkg("@metaobjectsdev/codegen-ts/generators"),
106
- "@metaobjectsdev/codegen-ts-react": resolveCliPkg("@metaobjectsdev/codegen-ts-react"),
107
- "@metaobjectsdev/codegen-ts-tanstack": resolveCliPkg("@metaobjectsdev/codegen-ts-tanstack"),
108
- "@metaobjectsdev/cli": resolveCliPkg("@metaobjectsdev/cli"),
109
- },
209
+ alias: aliasMap,
110
210
  });
111
- const raw = (await jiti.import(fullPath)) as MetaobjectsGenConfig | { default: MetaobjectsGenConfig };
112
- // jiti's interopDefault doesn't always unwrap the default export when accessed
113
- // across module boundaries explicitly unwrap if present.
114
- const cfg = (raw && typeof raw === "object" && "default" in raw && raw.default
115
- ? (raw as { default: MetaobjectsGenConfig }).default
116
- : raw) as MetaobjectsGenConfig;
117
- if (!cfg || typeof cfg !== "object" || !Array.isArray(cfg.generators)) {
118
- throw new Error(`metaobjects.config.ts at ${fullPath} did not export a valid MetaobjectsGenConfig (missing 'generators' array).`);
211
+
212
+ try {
213
+ const raw = (await jiti.import(loadPath)) as MetaobjectsGenConfig | { default: MetaobjectsGenConfig };
214
+ // jiti's interopDefault doesn't always unwrap the default export when accessed
215
+ // across module boundaries explicitly unwrap if present.
216
+ const cfg = (raw && typeof raw === "object" && "default" in raw && raw.default
217
+ ? (raw as { default: MetaobjectsGenConfig }).default
218
+ : raw) as MetaobjectsGenConfig;
219
+ if (!cfg || typeof cfg !== "object" || !Array.isArray(cfg.generators)) {
220
+ throw new Error(`metaobjects.config.ts at ${fullPath} did not export a valid MetaobjectsGenConfig (missing 'generators' array).`);
221
+ }
222
+ return cfg;
223
+ } finally {
224
+ if (tempCreated) {
225
+ try { unlinkSync(loadPath); } catch { /* best-effort cleanup */ }
226
+ }
119
227
  }
120
- return cfg;
121
228
  }
@@ -3,13 +3,31 @@ import { join } from "node:path";
3
3
 
4
4
  export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
5
5
 
6
+ /**
7
+ * Options for `detectPackageManager`.
8
+ */
9
+ export interface DetectPackageManagerOptions {
10
+ /**
11
+ * Stop walking up the directory tree at this path (inclusive). Useful in
12
+ * tests or isolated environments where walking above the project root would
13
+ * accidentally find a lockfile in an unrelated ancestor directory (e.g. a
14
+ * stale `/tmp/package-lock.json` on a shared CI machine).
15
+ *
16
+ * If omitted the walk continues all the way to the filesystem root.
17
+ */
18
+ stopAt?: string;
19
+ }
20
+
6
21
  /**
7
22
  * Detect the package manager in use by looking for the lockfile closest to
8
- * `dir`. Walks up to the root. Returns "bun" as the default when no lockfile
9
- * is found.
23
+ * `dir`. Walks up to the filesystem root (or `options.stopAt`, if supplied).
24
+ * Returns "bun" as the default when no lockfile is found.
10
25
  */
11
- export async function detectPackageManager(dir: string): Promise<PackageManager> {
12
- // Check in the given dir first, then parent dirs up to the FS root.
26
+ export async function detectPackageManager(
27
+ dir: string,
28
+ options?: DetectPackageManagerOptions,
29
+ ): Promise<PackageManager> {
30
+ // Check in the given dir first, then parent dirs up to the FS root (or stopAt).
13
31
  let current = dir;
14
32
  while (true) {
15
33
  const candidates: [string, PackageManager][] = [
@@ -27,8 +45,10 @@ export async function detectPackageManager(dir: string): Promise<PackageManager>
27
45
  // not found, try next
28
46
  }
29
47
  }
48
+ // Honor the stopAt boundary before walking up further.
49
+ if (options?.stopAt !== undefined && current === options.stopAt) break;
30
50
  const parent = join(current, "..");
31
- if (parent === current) break; // reached root
51
+ if (parent === current) break; // reached filesystem root
32
52
  current = parent;
33
53
  }
34
54
  // Default: bun (most common for this toolchain)