@twobirds/microcomponents 0.13.1 → 0.13.2

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.
@@ -18,12 +18,38 @@ export type Runner = (script: string, options?: {
18
18
  /** Asks the user to confirm a namespace long enough to be worth a second look. */
19
19
  export type Confirmer = (namespace: string) => Promise<boolean>;
20
20
  /**
21
+ Whether `mc <command>` runs the project's package.json script or the CLI's own
22
+ implementation.
23
+
24
+ A script that is a thin wrapper around the same command must not be delegated:
25
+ `mc dev` would spawn `pnpm run dev`, which runs `mc dev`, which spawns
26
+ `pnpm run dev`, forever. The scaffold writes exactly that shape (`"dev": "mc dev"`,
27
+ `"preview": "mc preview"`), so this guard is what keeps `pnpm dev` in a scaffolded
28
+ project from looping.
29
+
30
+ @example
31
+ ```ts
32
+ scriptTarget('mc dev', 'dev'); // 'self'
33
+ scriptTarget('mc dev --port 5173', 'dev'); // 'self'
34
+ scriptTarget('npx mc preview', 'preview'); // 'self'
35
+ scriptTarget('vite preview --outDir build', 'dev'); // 'delegate'
36
+ scriptTarget('tsc -p tsconfig.json && mc bundle', 'bundle'); // 'delegate'
37
+ scriptTarget(undefined, 'clean'); // 'self'
38
+ ```
39
+ @param body - The script body, or `undefined` when the project declares no such script.
40
+ @param command - The `mc` command being run.
41
+ @returns `'delegate'` to run the project script, `'self'` to run the CLI's implementation.
42
+ */
43
+ export declare function scriptTarget(body: string | undefined, command: string): 'delegate' | 'self';
44
+ /**
21
45
  Runs a package.json script through pnpm and resolves with its exit code.
22
46
 
23
47
  Thin wrappers like `clean` and `check` delegate here so the script — not the
24
48
  CLI — remains the single source of truth for what each command does. If the
25
49
  current project defines no such script, the CLI runs its own implementation
26
- against the current project, so the command works in any consumer project.
50
+ against the current project, so the command works in any consumer project. A script
51
+ that is a thin wrapper around the command itself is not delegated, because
52
+ delegating makes the CLI run itself forever — see {@link scriptTarget}.
27
53
 
28
54
  @example
29
55
  ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAwEF,MAAM,MAAM,MAAM,GAAG,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAyIhE;;;;;;;;;;;;EAYE;AACF,wBAAsB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAajB;AAED;;;;;;;;;;;;;;;;;;;;EAoBE;AACF,wBAAsB,IAAI,CACzB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,MAAkB,EACvB,OAAO,GAAE,SAA4B,GACnC,OAAO,CAAC,MAAM,CAAC,CA4IjB"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAwEF,MAAM,MAAM,MAAM,GAAG,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAgFhE;;;;;;;;;;;;;;;;;;;;;;EAsBE;AACF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAQ3F;AAgED;;;;;;;;;;;;;;EAcE;AACF,wBAAsB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAajB;AAED;;;;;;;;;;;;;;;;;;;;EAoBE;AACF,wBAAsB,IAAI,CACzB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,MAAkB,EACvB,OAAO,GAAE,SAA4B,GACnC,OAAO,CAAC,MAAM,CAAC,CA4IjB"}
package/dist/cli/index.js CHANGED
@@ -267,7 +267,7 @@ function packageJson(namespace) {
267
267
  typecheck: "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
268
268
  test: "vitest run"
269
269
  },
270
- dependencies: { "@twobirds/microcomponents": "^0.13.1" },
270
+ dependencies: { "@twobirds/microcomponents": "^0.13.2" },
271
271
  devDependencies: {
272
272
  typescript: "^6.0.3",
273
273
  vitest: "^4.1.10",
@@ -1721,23 +1721,56 @@ function spawnProcess(command, args, cwd) {
1721
1721
  });
1722
1722
  }
1723
1723
  /**
1724
- Whether the package.json in the current directory declares `script` in its
1725
- "scripts". Consumers get this check so `mc clean`/`mc check` can fall back to
1726
- the CLI's own implementation when the project defines no such script.
1724
+ The body of a package.json script in the current directory, or `undefined` when the
1725
+ project declares no such script. Consumers get this check so `mc clean`/`mc check`
1726
+ can fall back to the CLI's own implementation when the project defines no such script.
1727
1727
 
1728
1728
  @example
1729
1729
  ```ts
1730
- const ok = await hasScript('clean');
1730
+ await scriptBody('clean'); // 'rm -rf dist'
1731
+ await scriptBody('nope'); // undefined
1731
1732
  ```
1732
1733
  @param script - Script name to look up.
1733
1734
  */
1734
- async function hasScript(script) {
1735
+ async function scriptBody(script) {
1735
1736
  try {
1736
- return typeof JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"))?.scripts?.[script] === "string";
1737
+ const body = JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"))?.scripts?.[script];
1738
+ return typeof body === "string" ? body : void 0;
1737
1739
  } catch {
1738
- return false;
1740
+ return;
1739
1741
  }
1740
1742
  }
1743
+ /** The CLI's own bin names, so a script that wraps this command is recognized. */
1744
+ var SELF_BINS = /* @__PURE__ */ new Set(["mc", "microcomponents"]);
1745
+ /**
1746
+ Whether `mc <command>` runs the project's package.json script or the CLI's own
1747
+ implementation.
1748
+
1749
+ A script that is a thin wrapper around the same command must not be delegated:
1750
+ `mc dev` would spawn `pnpm run dev`, which runs `mc dev`, which spawns
1751
+ `pnpm run dev`, forever. The scaffold writes exactly that shape (`"dev": "mc dev"`,
1752
+ `"preview": "mc preview"`), so this guard is what keeps `pnpm dev` in a scaffolded
1753
+ project from looping.
1754
+
1755
+ @example
1756
+ ```ts
1757
+ scriptTarget('mc dev', 'dev'); // 'self'
1758
+ scriptTarget('mc dev --port 5173', 'dev'); // 'self'
1759
+ scriptTarget('npx mc preview', 'preview'); // 'self'
1760
+ scriptTarget('vite preview --outDir build', 'dev'); // 'delegate'
1761
+ scriptTarget('tsc -p tsconfig.json && mc bundle', 'bundle'); // 'delegate'
1762
+ scriptTarget(undefined, 'clean'); // 'self'
1763
+ ```
1764
+ @param body - The script body, or `undefined` when the project declares no such script.
1765
+ @param command - The `mc` command being run.
1766
+ @returns `'delegate'` to run the project script, `'self'` to run the CLI's implementation.
1767
+ */
1768
+ function scriptTarget(body, command) {
1769
+ if (!body) return "self";
1770
+ const tokens = body.trim().split(/\s+/);
1771
+ while (tokens[0] === "npx" || tokens[0] === "pnpm" && tokens[1] === "exec") tokens.splice(0, tokens[0] === "npx" ? 1 : 2);
1772
+ return SELF_BINS.has(tokens[0]) && tokens[1] === command ? "self" : "delegate";
1773
+ }
1741
1774
  /** Build artifacts to remove, relative to the project root. */
1742
1775
  var CLEAN_DIRS = [
1743
1776
  "dist",
@@ -1811,7 +1844,9 @@ Runs a package.json script through pnpm and resolves with its exit code.
1811
1844
  Thin wrappers like `clean` and `check` delegate here so the script — not the
1812
1845
  CLI — remains the single source of truth for what each command does. If the
1813
1846
  current project defines no such script, the CLI runs its own implementation
1814
- against the current project, so the command works in any consumer project.
1847
+ against the current project, so the command works in any consumer project. A script
1848
+ that is a thin wrapper around the command itself is not delegated, because
1849
+ delegating makes the CLI run itself forever — see {@link scriptTarget}.
1815
1850
 
1816
1851
  @example
1817
1852
  ```ts
@@ -1819,7 +1854,7 @@ const code = await runScript('check');
1819
1854
  ```
1820
1855
  */
1821
1856
  async function runScript(script, options) {
1822
- if (await hasScript(script)) return spawnProcess("pnpm", ["run", script]);
1857
+ if (scriptTarget(await scriptBody(script), script) === "delegate") return spawnProcess("pnpm", ["run", script]);
1823
1858
  if (script === "clean") return cleanSelf();
1824
1859
  if (script === "check") return checkSelf();
1825
1860
  if (script === "dev") return runDev(process.cwd(), void 0, options?.port ?? 5173, options?.host ?? "127.0.0.1");
@@ -110,7 +110,7 @@ mc check
110
110
  Reports the version of the installed library, not the version of the project it runs in.
111
111
 
112
112
  ```sh
113
- mc --version # 0.13.1
113
+ mc --version # 0.13.2
114
114
  ```
115
115
 
116
116
  ## mc --help
package/dist/mcp/api.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "libraryVersion": "0.13.1",
2
+ "libraryVersion": "0.13.2",
3
3
  "exportedCount": 59,
4
4
  "entries": {
5
5
  "MC": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twobirds/microcomponents",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "Micro Components",
5
5
  "keywords": [
6
6
  "PWA",