@twobirds/microcomponents 0.9.0 → 0.10.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.
- package/README.md +2 -16
- package/dist/cli/bundle.d.ts +106 -0
- package/dist/cli/bundle.d.ts.map +1 -0
- package/dist/cli/bundler.d.ts +44 -0
- package/dist/cli/bundler.d.ts.map +1 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/dev.d.ts +87 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dispatch.d.ts +6 -2
- package/dist/cli/dispatch.d.ts.map +1 -1
- package/dist/cli/index.js +1807 -1
- package/dist/cli/inliner.d.ts +42 -0
- package/dist/cli/inliner.d.ts.map +1 -0
- package/dist/cli/preview.d.ts +48 -0
- package/dist/cli/preview.d.ts.map +1 -0
- package/dist/core/elements.js +1294 -75
- package/dist/core/mcGuards.js +5 -8
- package/dist/mcp/api.json +2 -17
- package/dist/mcp/concepts/project-layout.md +9 -8
- package/dist/mcp/index.js +450 -230
- package/dist/microcomponents.js +2882 -9
- package/dist/state/formValues.js +2236 -281
- package/dist/state/observables.js +1794 -342
- package/dist/utils/events.js +171 -197
- package/package.json +7 -8
- package/dist/cli/create.js +0 -653
- package/dist/cli/dispatch.js +0 -316
- package/dist/cli/scaffold.js +0 -109
- package/dist/cli/writeScaffold.js +0 -114
- package/dist/core/MC.js +0 -837
- package/dist/core/index.js +0 -72
- package/dist/state/index.js +0 -5
- package/dist/state/placeholders.js +0 -187
- package/dist/types/index.js +0 -1
- package/dist/types/mcTypes.js +0 -9
- package/dist/utils/autoload.js +0 -160
- package/dist/utils/helpers.js +0 -532
- package/dist/utils/index.js +0 -12
- package/dist/utils/resolveComponentPath.js +0 -39
- package/dist/vite/index.d.ts +0 -7
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/index.js +0 -6
- package/dist/vite/loadAssets.d.ts +0 -36
- package/dist/vite/loadAssets.d.ts.map +0 -1
- package/dist/vite/loadAssets.js +0 -221
package/dist/cli/dispatch.js
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Command dispatcher for the `mc` CLI.
|
|
3
|
-
|
|
4
|
-
Kept free of any top-level side effects so the routing can be unit-tested:
|
|
5
|
-
`index.ts` is the thin executable entry that calls {@link main}, while this
|
|
6
|
-
module only exports the logic. Commands that wrap a package.json script
|
|
7
|
-
(`clean`, `check`) are deliberately thin — they delegate to the script so the
|
|
8
|
-
script stays the single source of truth. When the current project defines no
|
|
9
|
-
such script, the CLI runs its own implementation against the current project,
|
|
10
|
-
so the command works in any consumer project.
|
|
11
|
-
|
|
12
|
-
@module CLI/Dispatch
|
|
13
|
-
*/
|
|
14
|
-
import { spawn } from 'node:child_process';
|
|
15
|
-
import { parseArgs } from 'node:util';
|
|
16
|
-
import { readFile, rm } from 'node:fs/promises';
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
18
|
-
import path from 'node:path';
|
|
19
|
-
import { writeScaffold, writeProject, displayPath } from './writeScaffold.js';
|
|
20
|
-
import { scaffoldComponent } from './scaffold.js';
|
|
21
|
-
import { scaffoldProject } from './create.js';
|
|
22
|
-
const USAGE = `mc — Micro Components build tools
|
|
23
|
-
|
|
24
|
-
Usage:
|
|
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
|
-
mc clean
|
|
28
|
-
mc check
|
|
29
|
-
mc --version
|
|
30
|
-
mc --help
|
|
31
|
-
|
|
32
|
-
Commands:
|
|
33
|
-
new <tag> Scaffold a micro component (e.g. mc new my-card --target-html --target-css)
|
|
34
|
-
create Create a dev environment (e.g. mc create --namespace mc --dir mc-app)
|
|
35
|
-
clean Remove build artifacts (dist, public, build, coverage)
|
|
36
|
-
check Type-check every project (lib, cli, test, demo, node)
|
|
37
|
-
|
|
38
|
-
Companion assets:
|
|
39
|
-
--css document-level stylesheet (CSS custom properties / design tokens)
|
|
40
|
-
--html unvariant light-DOM template
|
|
41
|
-
--target-html template applied to target.innerHTML
|
|
42
|
-
--shadow-html template applied to shadowRoot.innerHTML
|
|
43
|
-
--target-css document-level styles scoped to the host tag
|
|
44
|
-
--shadow-css styles injected into the shadow root
|
|
45
|
-
|
|
46
|
-
Other options:
|
|
47
|
-
--js emit plain JavaScript instead of TypeScript
|
|
48
|
-
--dir <path> scaffold root (default: src)
|
|
49
|
-
--force overwrite files that already exist
|
|
50
|
-
--dry-run print what would be written; touch nothing on disk
|
|
51
|
-
--version print the version of the package the CLI ships in (-v)
|
|
52
|
-
--help print this text (-h)
|
|
53
|
-
|
|
54
|
-
Created paths are printed as they appear from the current directory. mc create writes
|
|
55
|
-
a whole project into a folder of its own (default: the namespace), so its files show
|
|
56
|
-
up under that folder, e.g. mc-app/src/mc/app.ts.
|
|
57
|
-
`;
|
|
58
|
-
// Map CLI flags to the runtime's camelCase asset descriptors.
|
|
59
|
-
const ASSET_FLAGS = {
|
|
60
|
-
css: 'css',
|
|
61
|
-
html: 'html',
|
|
62
|
-
'target-html': 'targetHtml',
|
|
63
|
-
'shadow-html': 'shadowHtml',
|
|
64
|
-
'target-css': 'targetCss',
|
|
65
|
-
'shadow-css': 'shadowCss',
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
A scaffolded file as it appears from the current directory: the file `rel` under
|
|
69
|
-
`root`, resolved and made readable. `mc create` writes a project into a folder of
|
|
70
|
-
its own, so `src/mc/app.ts` under `mc-app` prints as `mc-app/src/mc/app.ts` — the
|
|
71
|
-
path the user can find with `ls`.
|
|
72
|
-
|
|
73
|
-
@example
|
|
74
|
-
```ts
|
|
75
|
-
createdPath('mc-app', 'src/mc/app.ts');
|
|
76
|
-
createdPath('src', 'my/card.ts');
|
|
77
|
-
```
|
|
78
|
-
@param root - Scaffold root the file is written under.
|
|
79
|
-
@param rel - Path of the file relative to `root`.
|
|
80
|
-
*/
|
|
81
|
-
function createdPath(root, rel) {
|
|
82
|
-
return displayPath(path.resolve(root, rel));
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
Spawns a command and resolves with its exit code.
|
|
86
|
-
|
|
87
|
-
@example
|
|
88
|
-
```ts
|
|
89
|
-
const code = await spawnProcess('pnpm', ['run', 'clean']);
|
|
90
|
-
```
|
|
91
|
-
@param command - Executable to spawn.
|
|
92
|
-
@param args - Arguments to pass.
|
|
93
|
-
@param cwd - Working directory (defaults to the current one).
|
|
94
|
-
*/
|
|
95
|
-
function spawnProcess(command, args, cwd) {
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
const child = spawn(command, args, { stdio: 'inherit', cwd });
|
|
98
|
-
child.on('exit', (code) => resolve(code ?? 1));
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
Whether the package.json in the current directory declares `script` in its
|
|
103
|
-
"scripts". Consumers get this check so `mc clean`/`mc check` can fall back to
|
|
104
|
-
the CLI's own implementation when the project defines no such script.
|
|
105
|
-
|
|
106
|
-
@example
|
|
107
|
-
```ts
|
|
108
|
-
const ok = await hasScript('clean');
|
|
109
|
-
```
|
|
110
|
-
@param script - Script name to look up.
|
|
111
|
-
*/
|
|
112
|
-
async function hasScript(script) {
|
|
113
|
-
try {
|
|
114
|
-
const pkg = JSON.parse(await readFile(path.join(process.cwd(), 'package.json'), 'utf8'));
|
|
115
|
-
return typeof pkg?.scripts?.[script] === 'string';
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/** Build artifacts to remove, relative to the project root. */
|
|
122
|
-
const CLEAN_DIRS = ['dist', 'public', 'build', 'coverage'];
|
|
123
|
-
/**
|
|
124
|
-
Removes build artifacts in the current project.
|
|
125
|
-
|
|
126
|
-
@example
|
|
127
|
-
```ts
|
|
128
|
-
const code = await cleanSelf();
|
|
129
|
-
```
|
|
130
|
-
@param cwd - Project root (defaults to the current directory).
|
|
131
|
-
*/
|
|
132
|
-
async function cleanSelf(cwd = process.cwd()) {
|
|
133
|
-
await Promise.all(CLEAN_DIRS.map(async (dir) => {
|
|
134
|
-
const abs = path.join(cwd, dir);
|
|
135
|
-
if (existsSync(abs)) {
|
|
136
|
-
await rm(abs, { recursive: true, force: true });
|
|
137
|
-
console.log(`removed ${dir}/`);
|
|
138
|
-
}
|
|
139
|
-
}));
|
|
140
|
-
return 0;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
Type-checks the current project with its own `tsconfig.json`.
|
|
144
|
-
|
|
145
|
-
@example
|
|
146
|
-
```ts
|
|
147
|
-
const code = await checkSelf();
|
|
148
|
-
```
|
|
149
|
-
@param cwd - Project root (defaults to the current directory).
|
|
150
|
-
*/
|
|
151
|
-
async function checkSelf(cwd = process.cwd()) {
|
|
152
|
-
const config = path.join(cwd, 'tsconfig.json');
|
|
153
|
-
if (!existsSync(config)) {
|
|
154
|
-
console.error('mc check: no tsconfig.json in the current project');
|
|
155
|
-
return 1;
|
|
156
|
-
}
|
|
157
|
-
return spawnProcess('pnpm', ['exec', 'tsc', '--noEmit', '-p', 'tsconfig.json'], cwd);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
The version of the package the CLI ships in, read from its package.json. Two
|
|
161
|
-
levels above `dist/cli` (and above `src/cli` when tsx runs the source) is the
|
|
162
|
-
package root, so the CLI reports the version it was installed with, not the
|
|
163
|
-
version of the project it is run in.
|
|
164
|
-
|
|
165
|
-
@example
|
|
166
|
-
```ts
|
|
167
|
-
const version = await readVersion();
|
|
168
|
-
```
|
|
169
|
-
@param pkgPath - package.json to read (defaults to the package root of this CLI).
|
|
170
|
-
*/
|
|
171
|
-
async function readVersion(pkgPath = path.join(import.meta.dirname, '..', '..', 'package.json')) {
|
|
172
|
-
const { version } = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
173
|
-
return version;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
Runs a package.json script through pnpm and resolves with its exit code.
|
|
177
|
-
|
|
178
|
-
Thin wrappers like `clean` and `check` delegate here so the script — not the
|
|
179
|
-
CLI — remains the single source of truth for what each command does. If the
|
|
180
|
-
current project defines no such script, the CLI runs its own implementation
|
|
181
|
-
against the current project, so the command works in any consumer project.
|
|
182
|
-
|
|
183
|
-
@example
|
|
184
|
-
```ts
|
|
185
|
-
const code = await runScript('check');
|
|
186
|
-
```
|
|
187
|
-
*/
|
|
188
|
-
export async function runScript(script) {
|
|
189
|
-
if (await hasScript(script)) {
|
|
190
|
-
return spawnProcess('pnpm', ['run', script]);
|
|
191
|
-
}
|
|
192
|
-
if (script === 'clean')
|
|
193
|
-
return cleanSelf();
|
|
194
|
-
if (script === 'check')
|
|
195
|
-
return checkSelf();
|
|
196
|
-
console.error(`mc: no "${script}" script in the current project`);
|
|
197
|
-
return 1;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
Parses `argv` and routes it to the matching command, returning a process exit
|
|
201
|
-
code. `run` is injectable so the script-wrapping commands can be tested without
|
|
202
|
-
spawning anything.
|
|
203
|
-
|
|
204
|
-
@example
|
|
205
|
-
```ts
|
|
206
|
-
const code = await main(['clean'], async (script) => {
|
|
207
|
-
console.log(`would run ${script}`);
|
|
208
|
-
return 0;
|
|
209
|
-
});
|
|
210
|
-
```
|
|
211
|
-
@example
|
|
212
|
-
```ts
|
|
213
|
-
const code = await main(['--version']);
|
|
214
|
-
```
|
|
215
|
-
*/
|
|
216
|
-
export async function main(argv, run = runScript) {
|
|
217
|
-
const { values, positionals } = parseArgs({
|
|
218
|
-
args: argv,
|
|
219
|
-
allowPositionals: true,
|
|
220
|
-
options: {
|
|
221
|
-
css: { type: 'boolean' },
|
|
222
|
-
html: { type: 'boolean' },
|
|
223
|
-
'target-html': { type: 'boolean' },
|
|
224
|
-
'shadow-html': { type: 'boolean' },
|
|
225
|
-
'target-css': { type: 'boolean' },
|
|
226
|
-
'shadow-css': { type: 'boolean' },
|
|
227
|
-
js: { type: 'boolean' },
|
|
228
|
-
dir: { type: 'string' },
|
|
229
|
-
namespace: { type: 'string' },
|
|
230
|
-
force: { type: 'boolean' },
|
|
231
|
-
'dry-run': { type: 'boolean' },
|
|
232
|
-
help: { type: 'boolean', short: 'h' },
|
|
233
|
-
version: { type: 'boolean', short: 'v' },
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
const command = positionals[0];
|
|
237
|
-
if (values.version) {
|
|
238
|
-
console.log(await readVersion());
|
|
239
|
-
return 0;
|
|
240
|
-
}
|
|
241
|
-
if (values.help || !command) {
|
|
242
|
-
console.log(USAGE);
|
|
243
|
-
return 0;
|
|
244
|
-
}
|
|
245
|
-
if (command === 'new') {
|
|
246
|
-
const tag = positionals[1];
|
|
247
|
-
if (!tag) {
|
|
248
|
-
console.error('mc new: missing component tag (e.g. mc new my-card)');
|
|
249
|
-
return 1;
|
|
250
|
-
}
|
|
251
|
-
const assets = [];
|
|
252
|
-
const flags = values;
|
|
253
|
-
for (const [flag, kind] of Object.entries(ASSET_FLAGS)) {
|
|
254
|
-
if (flags[flag])
|
|
255
|
-
assets.push(kind);
|
|
256
|
-
}
|
|
257
|
-
if (values['dry-run']) {
|
|
258
|
-
const { files } = scaffoldComponent(tag, { assets, js: values.js });
|
|
259
|
-
const root = values.dir ?? 'src';
|
|
260
|
-
for (const rel of Object.keys(files).sort())
|
|
261
|
-
console.log(`would create ${createdPath(root, rel)}`);
|
|
262
|
-
return 0;
|
|
263
|
-
}
|
|
264
|
-
const root = values.dir ?? 'src';
|
|
265
|
-
let written;
|
|
266
|
-
try {
|
|
267
|
-
written = await writeScaffold(tag, root, { assets, js: values.js, force: values.force });
|
|
268
|
-
}
|
|
269
|
-
catch (e) {
|
|
270
|
-
console.error(e.message);
|
|
271
|
-
return 1;
|
|
272
|
-
}
|
|
273
|
-
for (const rel of written)
|
|
274
|
-
console.log(`created ${createdPath(root, rel)}`);
|
|
275
|
-
return 0;
|
|
276
|
-
}
|
|
277
|
-
if (command === 'create') {
|
|
278
|
-
const namespace = values.namespace;
|
|
279
|
-
if (!namespace) {
|
|
280
|
-
console.error('mc create: missing --namespace (e.g. mc create --namespace mc)');
|
|
281
|
-
return 1;
|
|
282
|
-
}
|
|
283
|
-
const root = values.dir ?? namespace;
|
|
284
|
-
let files;
|
|
285
|
-
try {
|
|
286
|
-
({ files } = scaffoldProject(namespace));
|
|
287
|
-
}
|
|
288
|
-
catch (e) {
|
|
289
|
-
console.error(e.message);
|
|
290
|
-
return 1;
|
|
291
|
-
}
|
|
292
|
-
if (values['dry-run']) {
|
|
293
|
-
for (const rel of Object.keys(files).sort())
|
|
294
|
-
console.log(`would create ${createdPath(root, rel)}`);
|
|
295
|
-
return 0;
|
|
296
|
-
}
|
|
297
|
-
let written;
|
|
298
|
-
try {
|
|
299
|
-
written = await writeProject(namespace, root, { force: values.force });
|
|
300
|
-
}
|
|
301
|
-
catch (e) {
|
|
302
|
-
console.error(e.message);
|
|
303
|
-
return 1;
|
|
304
|
-
}
|
|
305
|
-
for (const rel of written)
|
|
306
|
-
console.log(`created ${createdPath(root, rel)}`);
|
|
307
|
-
console.log(`\nproject "${namespace}" created in ${root}/ — run: cd ${root} && pnpm install && pnpm dev`);
|
|
308
|
-
return 0;
|
|
309
|
-
}
|
|
310
|
-
if (command === 'clean')
|
|
311
|
-
return run('clean');
|
|
312
|
-
if (command === 'check')
|
|
313
|
-
return run('check');
|
|
314
|
-
console.error(`mc: unknown command "${command}"\n\n${USAGE}`);
|
|
315
|
-
return 1;
|
|
316
|
-
}
|
package/dist/cli/scaffold.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Scaffolding for new micro components.
|
|
3
|
-
|
|
4
|
-
Pure, filesystem-free logic: given a tag name it returns the files to write and
|
|
5
|
-
their contents. The tag-to-path rule is not re-derived here — it comes from
|
|
6
|
-
{@link tagPath}, the same primitive the runtime autoload loader uses, so the
|
|
7
|
-
scaffold and the runtime can never disagree about where a component lives.
|
|
8
|
-
|
|
9
|
-
Companion assets follow the runtime's variant convention (see `loadAssets` /
|
|
10
|
-
`processAssets`): a camelCase descriptor maps to a dotted filename, e.g.
|
|
11
|
-
`'targetHtml'` → `{path}.target.html`, `'shadowCss'` → `{path}.shadow.css`, and
|
|
12
|
-
the unvariant `'css'` → `{path}.css` (the document-level stylesheet where CSS
|
|
13
|
-
custom properties / design tokens live).
|
|
14
|
-
|
|
15
|
-
@module CLI/Scaffold
|
|
16
|
-
*/
|
|
17
|
-
import { tagPath } from '../utils/resolveComponentPath.js';
|
|
18
|
-
// Custom element names must be lowercase kebab-case and contain at least one
|
|
19
|
-
// hyphen (defineCE rejects hyphenless tags), with no empty segments.
|
|
20
|
-
const TAG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
21
|
-
/**
|
|
22
|
-
Whether a string is a valid micro component tag name.
|
|
23
|
-
|
|
24
|
-
@example
|
|
25
|
-
```ts
|
|
26
|
-
console.log(isValidComponentTag('my-card'));
|
|
27
|
-
console.log(isValidComponentTag('mycard'));
|
|
28
|
-
```
|
|
29
|
-
*/
|
|
30
|
-
export function isValidComponentTag(tag) {
|
|
31
|
-
return TAG_RE.test(tag);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
Builds the files for a new micro component from its tag name.
|
|
35
|
-
|
|
36
|
-
@example
|
|
37
|
-
```ts
|
|
38
|
-
const { className, files } = scaffoldComponent('my-card', {
|
|
39
|
-
assets: ['targetHtml', 'targetCss'],
|
|
40
|
-
});
|
|
41
|
-
console.log(className);
|
|
42
|
-
console.log(Object.keys(files));
|
|
43
|
-
```
|
|
44
|
-
@param tag - Kebab-case component tag, e.g. `my-card`.
|
|
45
|
-
@param options - Which companion assets to emit and whether to use `.js`.
|
|
46
|
-
@throws If `tag` is not a valid custom element name.
|
|
47
|
-
*/
|
|
48
|
-
export function scaffoldComponent(tag, options = {}) {
|
|
49
|
-
if (!isValidComponentTag(tag)) {
|
|
50
|
-
throw new Error(`invalid component tag "${tag}": must be kebab-case with at least one hyphen (e.g. "my-card")`);
|
|
51
|
-
}
|
|
52
|
-
const className = tag
|
|
53
|
-
.split('-')
|
|
54
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
55
|
-
.join('');
|
|
56
|
-
const ext = options.js ? 'js' : 'ts';
|
|
57
|
-
const ctorParam = options.js ? 'target' : 'target: HTMLElement';
|
|
58
|
-
const base = tagPath(tag);
|
|
59
|
-
const assets = [...new Set(options.assets ?? [])];
|
|
60
|
-
const files = {};
|
|
61
|
-
files[`${base}.${ext}`] = componentSource(tag, ctorParam, assets);
|
|
62
|
-
for (const kind of assets) {
|
|
63
|
-
files[assetFile(tag, kind)] = assetSource(kind, tag);
|
|
64
|
-
}
|
|
65
|
-
return { files, className };
|
|
66
|
-
}
|
|
67
|
-
// Mirrors loadAssets' parseAsset: camelCase variant+ext → dotted filename,
|
|
68
|
-
// plain ext → single extension. Reuses tagPath so scaffold and runtime agree.
|
|
69
|
-
function assetFile(tag, kind) {
|
|
70
|
-
const m = kind.match(/^([a-z]+)([A-Z][a-z]*)$/);
|
|
71
|
-
const suffix = m ? `.${m[1]}.${m[2].toLowerCase()}` : `.${kind.toLowerCase()}`;
|
|
72
|
-
return `${tagPath(tag)}${suffix}`;
|
|
73
|
-
}
|
|
74
|
-
function componentSource(tag, ctorParam, assets) {
|
|
75
|
-
const lines = [
|
|
76
|
-
`import { DC, defineCE } from '@twobirds/microcomponents';`,
|
|
77
|
-
``,
|
|
78
|
-
`/**`,
|
|
79
|
-
`\`${tag}\` — describe what this micro component does.`,
|
|
80
|
-
``,
|
|
81
|
-
`@example`,
|
|
82
|
-
'```html',
|
|
83
|
-
`<${tag}></${tag}>`,
|
|
84
|
-
'```',
|
|
85
|
-
`*/`,
|
|
86
|
-
`defineCE(`,
|
|
87
|
-
`\t'${tag}',`,
|
|
88
|
-
`\tclass extends DC {`,
|
|
89
|
-
];
|
|
90
|
-
if (assets.length) {
|
|
91
|
-
lines.push(`\t\tstatic assets = [${assets.map((a) => `'${a}'`).join(', ')}];`);
|
|
92
|
-
}
|
|
93
|
-
lines.push(`\t\tconstructor(${ctorParam}) {`, `\t\t\tsuper(target);`, `\t\t}`, `\t}`, `);`, ``);
|
|
94
|
-
return lines.join('\n');
|
|
95
|
-
}
|
|
96
|
-
function assetSource(kind, tag) {
|
|
97
|
-
switch (kind) {
|
|
98
|
-
case 'css':
|
|
99
|
-
// Document-level global stylesheet — the home of CSS custom properties.
|
|
100
|
-
return [':root {', '\t/* design tokens (CSS custom properties) */', '}', ''].join('\n');
|
|
101
|
-
case 'targetCss':
|
|
102
|
-
return [`${tag} {`, '\tdisplay: block;', '}', ''].join('\n');
|
|
103
|
-
case 'shadowCss':
|
|
104
|
-
return [':host {', '\tdisplay: block;', '}', ''].join('\n');
|
|
105
|
-
default:
|
|
106
|
-
// html / targetHtml / shadowHtml
|
|
107
|
-
return '<slot></slot>\n';
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// oxlint-disable no-await-in-loop
|
|
2
|
-
/**
|
|
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.
|
|
9
|
-
|
|
10
|
-
@module CLI/Scaffold
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
13
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { scaffoldComponent } from './scaffold.js';
|
|
16
|
-
import { scaffoldProject } from './create.js';
|
|
17
|
-
/**
|
|
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.
|
|
24
|
-
|
|
25
|
-
@example
|
|
26
|
-
```ts
|
|
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);
|
|
45
|
-
console.log(written);
|
|
46
|
-
```
|
|
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.
|
|
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.
|
|
52
|
-
*/
|
|
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
|
-
}
|
|
69
|
-
const written = [];
|
|
70
|
-
for (const { rel, abs, contents } of targets) {
|
|
71
|
-
await mkdir(path.dirname(abs), { recursive: true });
|
|
72
|
-
await writeFile(abs, contents, 'utf8');
|
|
73
|
-
written.push(rel);
|
|
74
|
-
}
|
|
75
|
-
return written;
|
|
76
|
-
}
|
|
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
|
-
/**
|
|
96
|
-
Filesystem side of {@link scaffoldProject}: writes a complete dev environment for a
|
|
97
|
-
namespace under a project root, creating nested folders as needed.
|
|
98
|
-
|
|
99
|
-
@example
|
|
100
|
-
```ts
|
|
101
|
-
const written = await writeProject('mc', 'mc-app');
|
|
102
|
-
console.log(written);
|
|
103
|
-
```
|
|
104
|
-
@param namespace - 1 to 4 lowercase letters.
|
|
105
|
-
@param root - Directory to create the project in (e.g. `mc-app`).
|
|
106
|
-
@param options - Whether to overwrite existing files.
|
|
107
|
-
@returns The written file paths, relative to `root`.
|
|
108
|
-
@throws If `namespace` is not 1 to 4 lowercase letters, or a target file exists and
|
|
109
|
-
`force` is false.
|
|
110
|
-
*/
|
|
111
|
-
export async function writeProject(namespace, root, options = {}) {
|
|
112
|
-
const { files } = scaffoldProject(namespace);
|
|
113
|
-
return writeGenerated(files, root, options.force ?? false);
|
|
114
|
-
}
|