@twobirds/microcomponents 0.4.0 → 0.5.0

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.
package/README.md CHANGED
@@ -40,17 +40,25 @@ The progress of coding is occasionally being streamed on [twitch](https://twitch
40
40
 
41
41
  ## MCP Server
42
42
 
43
- An MCP (Model Context Protocol) server ships with this repo so AI assistants can look the library up instead of answering from memory: `microcomponents-mcp-server.ts` at the repo root.
43
+ An MCP (Model Context Protocol) server ships with the package so AI assistants can look the library up instead of answering from memory. The source is `src/mcp/index.ts`, compiled by `tsconfig.mcp.json` to `dist/mcp/index.js` and published with the package, so installing it gives you the executable bin:
44
44
 
45
45
  ```bash
46
- pnpm run mcp:run # start the server on stdio
47
- pnpm run mcp:dev # restart it whenever src/ changes
48
- pnpm run mcp:docs # regenerate docs/api.json and report undocumented exports
46
+ microcomponents-mcp # stdio server, straight from the installed package
47
+ ```
48
+
49
+ In a published package the server runs from `dist/mcp` and reads the prebuilt `api.json` and the concept essays that the build step emits next to it. During development it generates the index live from `src/` at startup, so it cannot drift away from the library it documents:
50
+
51
+ ```bash
52
+ pnpm run mcp:dev # restart it whenever src/ changes
53
+ pnpm run mcp:run # start it from source on stdio
54
+ pnpm run mcp:docs # regenerate dist/mcp/api.json and report undocumented exports
49
55
  ```
50
56
 
51
57
  Tools: `list-components`, `get-component-docs`, `search-components`, `get-core-concept`, `get-source-file`, `list-undocumented`.
52
58
 
53
- There is no hand-written API list in the server. The whole index is extracted at startup from the JSDoc blocks of the public exports (the names re-exported by each `src/*/index.ts`), so it can never drift away from the code it documents — and `list-undocumented` tells you when a source file still lacks a doc block. Concept essays are hand-authored markdown, one file per concept under `docs/concepts/`, which is also where the tool's list of available concepts comes from.
59
+ There is no hand-written API list in the server. The whole index is extracted from the JSDoc blocks of the public exports (the names re-exported by each `src/*/index.ts`), so it can never drift away from the code it documents — and `list-undocumented` tells you when a source file still lacks a doc block. Concept essays are hand-authored markdown, one file per concept under `docs/concepts/`, copied into `dist/mcp/concepts` by the build, which is where the tool's list of available concepts comes from.
60
+
61
+ `get-source-file` reads package-root relative paths (`dist/core/MC.d.ts`, `dist/mcp/concepts/event-system.md`) and refuses any path outside the package sources.
54
62
 
55
63
  ## CLI
56
64
 
@@ -58,45 +66,59 @@ A small command-line tool scaffolds new components so the tag-to-file convention
58
66
 
59
67
  ```bash
60
68
  pnpm run cli -- new my-card --target-html --target-css
61
- # created my/card.ts
62
- # created my/card.target.html
63
- # created my/card.target.css
69
+ # created src/my/card.ts
70
+ # created src/my/card.target.html
71
+ # created src/my/card.target.css
64
72
  ```
65
73
 
66
- `--dry-run` prints the plan without touching disk, so you can check the derived paths first:
74
+ Paths are printed as they appear from the current directory, so every line is a file you can open with `ls`. `--dry-run` prints the plan without touching disk, so you can check the derived paths first:
67
75
 
68
76
  ```bash
69
77
  pnpm run cli -- new my-card --target-html --target-css --dry-run
70
- # would create my/card.ts
71
- # would create my/card.target.html
72
- # would create my/card.target.css
78
+ # would create src/my/card.ts
79
+ # would create src/my/card.target.html
80
+ # would create src/my/card.target.css
73
81
  ```
74
82
 
75
83
  `mc create` scaffolds a complete dev environment for a namespace — a Vite dev server serving `src/` straight, a page shell that turns on `autoload(true)`, and a starter component with companion assets:
76
84
 
77
85
  ```bash
78
86
  pnpm run cli -- create --namespace mc --dir mc-app
79
- # created package.json
80
- # created src/index.html
81
- # created src/mc/app.ts
82
- # created src/mc/app.target.html
83
- # created src/mc/app.target.css
87
+ # created mc-app/package.json
88
+ # created mc-app/src/index.html
89
+ # created mc-app/src/mc/app.ts
90
+ # created mc-app/src/mc/app.target.html
91
+ # created mc-app/src/mc/app.target.css
84
92
  # ...
85
93
  # project "mc" created in mc-app/ — run: cd mc-app && pnpm install && pnpm dev
86
94
  ```
87
95
 
88
96
  The namespace (1 to 4 lowercase letters) becomes the `src/<namespace>/` folder and the first segment of every component tag, so `mc` is the home of tags like `<mc-app>`. Because the page shell is the autoload base, `import.meta.url` + the tag-to-path rule resolves `<mc-app>` to `src/mc/app.js` — the compiled starter. `--dir` defaults to the namespace, `--dry-run` prints the plan without touching disk.
89
97
 
98
+ The project is written into a folder of its own, so its files are reported under that folder (`mc-app/src/mc/app.ts`) — the path you can find from the directory you ran the command in, not relative to the project root.
99
+
100
+ Existing files are never overwritten silently: every target is checked before the first write, so a rerun in a populated directory lists the conflicts and touches nothing. `--force` opts into overwriting:
101
+
102
+ ```bash
103
+ pnpm run cli -- create --namespace mc
104
+ # refusing to overwrite 10 existing file(s):
105
+ # mc/package.json
106
+ # mc/src/mc/app.ts
107
+ # ...
108
+ #
109
+ # rerun with --force to overwrite
110
+ ```
111
+
90
112
  Once installed from npm the same command is available as `mc`:
91
113
 
92
114
  ```bash
93
- mc new my-card [--css] [--html] [--target-html] [--shadow-html] [--target-css] [--shadow-css] [--js] [--dir <path>] [--dry-run]
94
- mc create --namespace <ns> [--dir <path>] [--dry-run]
115
+ mc new my-card [--css] [--html] [--target-html] [--shadow-html] [--target-css] [--shadow-css] [--js] [--dir <path>] [--force] [--dry-run]
116
+ mc create --namespace <ns> [--dir <path>] [--force] [--dry-run]
95
117
  mc clean
96
118
  mc check
97
119
  ```
98
120
 
99
- `clean` and `check` are thin wrappers around the package.json scripts of the same name, so the scripts remain the single source of truth for what they do — `mc check` type-checks every project context (lib, cli, test, demo, node) and exits with the script's exit code.
121
+ `clean` and `check` are thin wrappers around the package.json scripts of the same name, so the scripts remain the single source of truth for what they do — `mc check` type-checks every project context (lib, cli, mcp, test, demo, node) and exits with the script's exit code.
100
122
 
101
123
  The scaffold derives the file path from the tag using the same rule the runtime autoload loader uses (`my-card` → `my/card.ts`), names companion assets after the runtime's asset descriptors (`targetHtml` → `card.target.html`), and wires them into a `static assets` declaration. `--js` emits a plain JavaScript file for the rapid-prototyping path.
102
124
 
@@ -81,7 +81,7 @@ function packageJson(namespace) {
81
81
  typecheck: 'tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.vc.json',
82
82
  },
83
83
  dependencies: {
84
- '@twobirds/microcomponents': '^0.4.0',
84
+ '@twobirds/microcomponents': '^0.5.0',
85
85
  },
86
86
  devDependencies: {
87
87
  vite: '^8.1.5',
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAiDF,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAmFzD;;;;;;;;;;;;EAYE;AACF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/D;AAED;;;;;;;;;;;;EAYE;AACF,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0FnF"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAsDF,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAqGzD;;;;;;;;;;;;EAYE;AACF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/D;AAED;;;;;;;;;;;;EAYE;AACF,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwGnF"}
@@ -16,14 +16,14 @@ import { parseArgs } from 'node:util';
16
16
  import { readFile, rm } from 'node:fs/promises';
17
17
  import { existsSync } from 'node:fs';
18
18
  import path from 'node:path';
19
- import { writeScaffold, writeProject } from './writeScaffold.js';
19
+ import { writeScaffold, writeProject, displayPath } from './writeScaffold.js';
20
20
  import { scaffoldComponent } from './scaffold.js';
21
21
  import { scaffoldProject } from './create.js';
22
22
  const USAGE = `mc — Micro Components build tools
23
23
 
24
24
  Usage:
25
- mc new <tag> [--css] [--html] [--target-html] [--shadow-html] [--target-css] [--shadow-css] [--js] [--dir <path>] [--dry-run]
26
- mc create --namespace <ns> [--dir <path>] [--dry-run]
25
+ mc new <tag> [--css] [--html] [--target-html] [--shadow-html] [--target-css] [--shadow-css] [--js] [--dir <path>] [--force] [--dry-run]
26
+ mc create --namespace <ns> [--dir <path>] [--force] [--dry-run]
27
27
  mc clean
28
28
  mc check
29
29
 
@@ -44,7 +44,12 @@ Companion assets:
44
44
  Other options:
45
45
  --js emit plain JavaScript instead of TypeScript
46
46
  --dir <path> scaffold root (default: src)
47
+ --force overwrite files that already exist
47
48
  --dry-run print what would be written; touch nothing on disk
49
+
50
+ Created paths are printed as they appear from the current directory. mc create writes
51
+ a whole project into a folder of its own (default: the namespace), so its files show
52
+ up under that folder, e.g. mc-app/src/mc/app.ts.
48
53
  `;
49
54
  // Map CLI flags to the runtime's camelCase asset descriptors.
50
55
  const ASSET_FLAGS = {
@@ -56,6 +61,23 @@ const ASSET_FLAGS = {
56
61
  'shadow-css': 'shadowCss',
57
62
  };
58
63
  /**
64
+ A scaffolded file as it appears from the current directory: the file `rel` under
65
+ `root`, resolved and made readable. `mc create` writes a project into a folder of
66
+ its own, so `src/mc/app.ts` under `mc-app` prints as `mc-app/src/mc/app.ts` — the
67
+ path the user can find with `ls`.
68
+
69
+ @example
70
+ ```ts
71
+ createdPath('mc-app', 'src/mc/app.ts');
72
+ createdPath('src', 'my/card.ts');
73
+ ```
74
+ @param root - Scaffold root the file is written under.
75
+ @param rel - Path of the file relative to `root`.
76
+ */
77
+ function createdPath(root, rel) {
78
+ return displayPath(path.resolve(root, rel));
79
+ }
80
+ /**
59
81
  Spawns a command and resolves with its exit code.
60
82
 
61
83
  @example
@@ -181,6 +203,7 @@ export async function main(argv, run = runScript) {
181
203
  js: { type: 'boolean' },
182
204
  dir: { type: 'string' },
183
205
  namespace: { type: 'string' },
206
+ force: { type: 'boolean' },
184
207
  'dry-run': { type: 'boolean' },
185
208
  help: { type: 'boolean', short: 'h' },
186
209
  },
@@ -204,16 +227,22 @@ export async function main(argv, run = runScript) {
204
227
  }
205
228
  if (values['dry-run']) {
206
229
  const { files } = scaffoldComponent(tag, { assets, js: values.js });
207
- for (const file of Object.keys(files).sort())
208
- console.log(`would create ${file}`);
230
+ const root = values.dir ?? 'src';
231
+ for (const rel of Object.keys(files).sort())
232
+ console.log(`would create ${createdPath(root, rel)}`);
209
233
  return 0;
210
234
  }
211
- const written = await writeScaffold(tag, values.dir ?? 'src', {
212
- assets,
213
- js: values.js,
214
- });
215
- for (const file of written)
216
- console.log(`created ${file}`);
235
+ const root = values.dir ?? 'src';
236
+ let written;
237
+ try {
238
+ written = await writeScaffold(tag, root, { assets, js: values.js, force: values.force });
239
+ }
240
+ catch (e) {
241
+ console.error(e.message);
242
+ return 1;
243
+ }
244
+ for (const rel of written)
245
+ console.log(`created ${createdPath(root, rel)}`);
217
246
  return 0;
218
247
  }
219
248
  if (command === 'create') {
@@ -232,13 +261,20 @@ export async function main(argv, run = runScript) {
232
261
  return 1;
233
262
  }
234
263
  if (values['dry-run']) {
235
- for (const file of Object.keys(files).sort())
236
- console.log(`would create ${file}`);
264
+ for (const rel of Object.keys(files).sort())
265
+ console.log(`would create ${createdPath(root, rel)}`);
237
266
  return 0;
238
267
  }
239
- const written = await writeProject(namespace, root);
240
- for (const file of written)
241
- console.log(`created ${file}`);
268
+ let written;
269
+ try {
270
+ written = await writeProject(namespace, root, { force: values.force });
271
+ }
272
+ catch (e) {
273
+ console.error(e.message);
274
+ return 1;
275
+ }
276
+ for (const rel of written)
277
+ console.log(`created ${createdPath(root, rel)}`);
242
278
  console.log(`\nproject "${namespace}" created in ${root}/ — run: cd ${root} && pnpm install && pnpm dev`);
243
279
  return 0;
244
280
  }
@@ -1,10 +1,36 @@
1
1
  /**
2
- Filesystem side of {@link scaffoldComponent}: writes the generated files under a
3
- root directory, creating nested folders as needed.
2
+ Filesystem side of {@link scaffoldComponent} and {@link scaffoldProject}: writes the
3
+ generated files under a root directory, creating nested folders as needed.
4
+
5
+ Existing files are never overwritten silently: every target is checked before the
6
+ first write, so a rerun in a populated directory reports the conflicts and touches
7
+ nothing. `force` opts into overwriting.
4
8
 
5
9
  @module CLI/Scaffold
6
10
  */
7
11
  import { type ScaffoldOptions } from './scaffold.js';
12
+ /** Whether files that already exist may be overwritten. */
13
+ export interface WriteOptions {
14
+ force?: boolean;
15
+ }
16
+ /**
17
+ The path of a target file as it appears from the current working directory.
18
+ `mc create` writes a project into a folder of its own, so a path relative to the
19
+ project root cannot be found from the directory the command ran in: this turns
20
+ `src/mc/app.ts` under `mc-app/` into `mc-app/src/mc/app.ts`. Falls back to the
21
+ absolute path when the target lies outside the current directory, so the log never
22
+ prints `../../..` chains.
23
+
24
+ @example
25
+ ```ts
26
+ // in a directory containing mc-app/src/mc/app.ts
27
+ displayPath(path.resolve('mc-app', 'src/mc/app.ts'));
28
+ displayPath('/tmp/mc-app/package.json');
29
+ ```
30
+ @param abs - Absolute path of the target file.
31
+ @group CLI
32
+ */
33
+ export declare function displayPath(abs: string): string;
8
34
  /**
9
35
  Scaffolds a component onto disk under `root`.
10
36
 
@@ -15,10 +41,11 @@ console.log(written);
15
41
  ```
16
42
  @param tag - Kebab-case component tag.
17
43
  @param root - Directory to scaffold under (e.g. `src`).
18
- @param options - Which companion files to emit.
44
+ @param options - Which companion files to emit, and whether to overwrite existing ones.
19
45
  @returns The written file paths, relative to `root`.
46
+ @throws If a target file exists and `force` is false.
20
47
  */
21
- export declare function writeScaffold(tag: string, root: string, options?: ScaffoldOptions): Promise<string[]>;
48
+ export declare function writeScaffold(tag: string, root: string, options?: ScaffoldOptions & WriteOptions): Promise<string[]>;
22
49
  /**
23
50
  Filesystem side of {@link scaffoldProject}: writes a complete dev environment for a
24
51
  namespace under a project root, creating nested folders as needed.
@@ -30,8 +57,10 @@ console.log(written);
30
57
  ```
31
58
  @param namespace - 1 to 4 lowercase letters.
32
59
  @param root - Directory to create the project in (e.g. `mc-app`).
60
+ @param options - Whether to overwrite existing files.
33
61
  @returns The written file paths, relative to `root`.
34
- @throws If `namespace` is not 1 to 4 lowercase letters.
62
+ @throws If `namespace` is not 1 to 4 lowercase letters, or a target file exists and
63
+ `force` is false.
35
64
  */
36
- export declare function writeProject(namespace: string, root: string): Promise<string[]>;
65
+ export declare function writeProject(namespace: string, root: string, options?: WriteOptions): Promise<string[]>;
37
66
  //# sourceMappingURL=writeScaffold.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"writeScaffold.d.ts","sourceRoot":"","sources":["../../src/cli/writeScaffold.ts"],"names":[],"mappings":"AACA;;;;;EAKE;AAIF,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGxE;;;;;;;;;;;;EAYE;AACF,wBAAsB,aAAa,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB;AAED;;;;;;;;;;;;;EAaE;AACF,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYrF"}
1
+ {"version":3,"file":"writeScaffold.d.ts","sourceRoot":"","sources":["../../src/cli/writeScaffold.ts"],"names":[],"mappings":"AACA;;;;;;;;;EASE;AAKF,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGxE,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAE5B,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;EAgBE;AACF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAmDD;;;;;;;;;;;;;EAaE;AACF,wBAAsB,aAAa,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAe,GAAG,YAAiB,GAC1C,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB;AAED;;;;;;;;;;;;;;;EAeE;AACF,wBAAsB,YAAY,CACjC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
@@ -1,32 +1,73 @@
1
1
  // oxlint-disable no-await-in-loop
2
2
  /**
3
- Filesystem side of {@link scaffoldComponent}: writes the generated files under a
4
- root directory, creating nested folders as needed.
3
+ Filesystem side of {@link scaffoldComponent} and {@link scaffoldProject}: writes the
4
+ generated files under a root directory, creating nested folders as needed.
5
+
6
+ Existing files are never overwritten silently: every target is checked before the
7
+ first write, so a rerun in a populated directory reports the conflicts and touches
8
+ nothing. `force` opts into overwriting.
5
9
 
6
10
  @module CLI/Scaffold
7
11
  */
12
+ import { existsSync } from 'node:fs';
8
13
  import { mkdir, writeFile } from 'node:fs/promises';
9
14
  import path from 'node:path';
10
15
  import { scaffoldComponent } from './scaffold.js';
11
16
  import { scaffoldProject } from './create.js';
12
17
  /**
13
- Scaffolds a component onto disk under `root`.
18
+ The path of a target file as it appears from the current working directory.
19
+ `mc create` writes a project into a folder of its own, so a path relative to the
20
+ project root cannot be found from the directory the command ran in: this turns
21
+ `src/mc/app.ts` under `mc-app/` into `mc-app/src/mc/app.ts`. Falls back to the
22
+ absolute path when the target lies outside the current directory, so the log never
23
+ prints `../../..` chains.
14
24
 
15
25
  @example
16
26
  ```ts
17
- const written = await writeScaffold('my-card', 'src', { assets: ['css'] });
27
+ // in a directory containing mc-app/src/mc/app.ts
28
+ displayPath(path.resolve('mc-app', 'src/mc/app.ts'));
29
+ displayPath('/tmp/mc-app/package.json');
30
+ ```
31
+ @param abs - Absolute path of the target file.
32
+ @group CLI
33
+ */
34
+ export function displayPath(abs) {
35
+ const rel = path.relative(process.cwd(), abs);
36
+ return rel.startsWith('..') ? abs : rel;
37
+ }
38
+ /**
39
+ Writes generated files under `root`, creating nested folders as needed, and reports
40
+ the written paths relative to `root`.
41
+
42
+ @example
43
+ ```ts
44
+ const written = await writeGenerated({ 'my/card.ts': 'source' }, 'src', false);
18
45
  console.log(written);
19
46
  ```
20
- @param tag - Kebab-case component tag.
21
- @param root - Directory to scaffold under (e.g. `src`).
22
- @param options - Which companion files to emit.
47
+ @param files - Files to write, keyed by path relative to `root`.
48
+ @param root - Directory to write under (`src` for a component, `mc-app` for a project).
49
+ @param force - Overwrite files that already exist.
23
50
  @returns The written file paths, relative to `root`.
51
+ @throws If any target file exists and `force` is false. Nothing is written in that case.
24
52
  */
25
- export async function writeScaffold(tag, root, options = {}) {
26
- const { files } = scaffoldComponent(tag, options);
53
+ async function writeGenerated(files, root, force) {
54
+ const targets = Object.entries(files).map(([rel, contents]) => ({
55
+ rel,
56
+ abs: path.resolve(root, rel),
57
+ contents,
58
+ }));
59
+ if (!force) {
60
+ const conflicts = targets
61
+ .filter((target) => existsSync(target.abs))
62
+ .map((t) => displayPath(t.abs));
63
+ if (conflicts.length) {
64
+ throw new Error(`refusing to overwrite ${conflicts.length} existing file(s):\n${conflicts
65
+ .map((file) => `\t${file}`)
66
+ .join('\n')}\n\nrerun with --force to overwrite`);
67
+ }
68
+ }
27
69
  const written = [];
28
- for (const [rel, contents] of Object.entries(files)) {
29
- const abs = path.join(root, rel);
70
+ for (const { rel, abs, contents } of targets) {
30
71
  await mkdir(path.dirname(abs), { recursive: true });
31
72
  await writeFile(abs, contents, 'utf8');
32
73
  written.push(rel);
@@ -34,6 +75,24 @@ export async function writeScaffold(tag, root, options = {}) {
34
75
  return written;
35
76
  }
36
77
  /**
78
+ Scaffolds a component onto disk under `root`.
79
+
80
+ @example
81
+ ```ts
82
+ const written = await writeScaffold('my-card', 'src', { assets: ['css'] });
83
+ console.log(written);
84
+ ```
85
+ @param tag - Kebab-case component tag.
86
+ @param root - Directory to scaffold under (e.g. `src`).
87
+ @param options - Which companion files to emit, and whether to overwrite existing ones.
88
+ @returns The written file paths, relative to `root`.
89
+ @throws If a target file exists and `force` is false.
90
+ */
91
+ export async function writeScaffold(tag, root, options = {}) {
92
+ const { files } = scaffoldComponent(tag, options);
93
+ return writeGenerated(files, root, options.force ?? false);
94
+ }
95
+ /**
37
96
  Filesystem side of {@link scaffoldProject}: writes a complete dev environment for a
38
97
  namespace under a project root, creating nested folders as needed.
39
98
 
@@ -44,17 +103,12 @@ console.log(written);
44
103
  ```
45
104
  @param namespace - 1 to 4 lowercase letters.
46
105
  @param root - Directory to create the project in (e.g. `mc-app`).
106
+ @param options - Whether to overwrite existing files.
47
107
  @returns The written file paths, relative to `root`.
48
- @throws If `namespace` is not 1 to 4 lowercase letters.
108
+ @throws If `namespace` is not 1 to 4 lowercase letters, or a target file exists and
109
+ `force` is false.
49
110
  */
50
- export async function writeProject(namespace, root) {
111
+ export async function writeProject(namespace, root, options = {}) {
51
112
  const { files } = scaffoldProject(namespace);
52
- const written = [];
53
- for (const [rel, contents] of Object.entries(files)) {
54
- const abs = path.join(root, rel);
55
- await mkdir(path.dirname(abs), { recursive: true });
56
- await writeFile(abs, contents, 'utf8');
57
- written.push(rel);
58
- }
59
- return written;
113
+ return writeGenerated(files, root, options.force ?? false);
60
114
  }