@atelier-ui/create-workspace 0.2.41 → 0.2.43
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/CHANGELOG.md +33 -4
- package/package.json +1 -1
- package/src/generators/preset/files/contracts/README.md +29 -0
- package/src/generators/preset/files/contracts/button.contract.ts.template +20 -0
- package/src/generators/preset/files/contracts/types.ts.template +55 -0
- package/src/generators/preset/files/figma/snapshot.json +164 -0
- package/src/generators/preset/files/storybook/angular/atl-button.stories.ts.template +38 -0
- package/src/generators/preset/files/storybook/angular/main.ts.template +39 -0
- package/src/generators/preset/files/storybook/angular/preview.ts.template +30 -0
- package/src/generators/preset/files/storybook/angular/tsconfig.json +16 -0
- package/src/generators/preset/files/storybook/angular/vitest.config.ts.template +40 -0
- package/src/generators/preset/files/storybook/angular/vitest.setup.ts.template +14 -0
- package/src/generators/preset/files/storybook/react/atl-button.stories.tsx +31 -0
- package/src/generators/preset/files/storybook/react/main.ts.template +37 -0
- package/src/generators/preset/files/storybook/react/preview.tsx +29 -0
- package/src/generators/preset/files/storybook/react/vitest.config.ts.template +32 -0
- package/src/generators/preset/files/storybook/react/vitest.setup.ts.template +8 -0
- package/src/generators/preset/files/storybook/vue/atl-button.stories.ts.template +34 -0
- package/src/generators/preset/files/storybook/vue/main.ts.template +38 -0
- package/src/generators/preset/files/storybook/vue/preview.ts.template +29 -0
- package/src/generators/preset/files/storybook/vue/vitest.config.ts.template +32 -0
- package/src/generators/preset/files/storybook/vue/vitest.setup.ts.template +9 -0
- package/src/generators/preset/files/styles/tokens.css +59 -43
- package/src/generators/preset/files/tools/scripts/check-contracts.mjs +1644 -0
- package/src/generators/preset/files/tools/scripts/figma-snapshot-contracts.mjs +370 -0
- package/src/generators/preset/files/tools/scripts/lib/docgen.mjs +573 -0
- package/src/generators/preset/files/tools/scripts/lib/ts-eval.js +126 -0
- package/src/generators/preset/files/tools/scripts/preflight.mjs +220 -30
- package/src/generators/preset/files/tools/stylelint-rules/index.js +21 -0
- package/src/generators/preset/files/tools/stylelint-rules/no-primitive-token.js +362 -0
- package/src/generators/preset/files/tools/stylelint-rules/no-raw-color-literal.js +154 -0
- package/src/generators/preset/files/tools/stylelint-rules/no-token-bypass.js +497 -0
- package/src/generators/preset/files/tools/stylelint-rules/no-undeclared-token.js +122 -0
- package/src/generators/preset/files/tools/stylelint-rules/utils.js +71 -0
- package/src/generators/preset/preset.d.ts +1 -0
- package/src/generators/preset/preset.js +955 -5
- package/src/generators/preset/preset.js.map +1 -1
- package/src/generators/preset/schema.d.ts +1 -0
- package/src/generators/preset/schema.json +5 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { execSync, spawnSync } from 'node:child_process';
|
|
15
15
|
import { createServer } from 'node:net';
|
|
16
|
-
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
16
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
17
17
|
import { homedir } from 'node:os';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -23,7 +23,8 @@ const __dirname = dirname(__filename);
|
|
|
23
23
|
const ROOT = resolve(__dirname, '../..');
|
|
24
24
|
|
|
25
25
|
// ── Styling ──────────────────────────────────────────────────────────
|
|
26
|
-
const supportsColor =
|
|
26
|
+
const supportsColor =
|
|
27
|
+
process.stdout.isTTY && process.env.NO_COLOR === undefined;
|
|
27
28
|
const c = (code, s) => (supportsColor ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
28
29
|
const red = (s) => c('31', s);
|
|
29
30
|
const green = (s) => c('32', s);
|
|
@@ -54,7 +55,8 @@ const warn = (label, detail, fix) => record('warn', label, detail, fix);
|
|
|
54
55
|
function which(cmd) {
|
|
55
56
|
const finder = process.platform === 'win32' ? 'where' : 'which';
|
|
56
57
|
const res = spawnSync(finder, [cmd], { encoding: 'utf8' });
|
|
57
|
-
if (res.status === 0 && res.stdout.trim())
|
|
58
|
+
if (res.status === 0 && res.stdout.trim())
|
|
59
|
+
return res.stdout.trim().split('\n')[0];
|
|
58
60
|
return null;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -101,7 +103,10 @@ async function mcpPost(url, body, sessionId, timeoutMs = 10000) {
|
|
|
101
103
|
sessionId: res.headers.get('mcp-session-id') ?? undefined,
|
|
102
104
|
};
|
|
103
105
|
} catch (err) {
|
|
104
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
error:
|
|
108
|
+
err?.name === 'AbortError' ? 'timeout' : String(err?.message ?? err),
|
|
109
|
+
};
|
|
105
110
|
} finally {
|
|
106
111
|
clearTimeout(timer);
|
|
107
112
|
}
|
|
@@ -141,16 +146,28 @@ async function probeMcp(url) {
|
|
|
141
146
|
if (init.error) return { level: 'unreachable', detail: init.error };
|
|
142
147
|
const session = init.sessionId;
|
|
143
148
|
if (session) {
|
|
144
|
-
await mcpPost(
|
|
149
|
+
await mcpPost(
|
|
150
|
+
url,
|
|
151
|
+
{ jsonrpc: '2.0', method: 'notifications/initialized' },
|
|
152
|
+
session,
|
|
153
|
+
5000,
|
|
154
|
+
);
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
// 2. tools/list — must be HTTP 200 with a parseable JSON-RPC tools array.
|
|
148
|
-
const list = await mcpPost(
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
const list = await mcpPost(
|
|
159
|
+
url,
|
|
160
|
+
{ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} },
|
|
161
|
+
session,
|
|
162
|
+
);
|
|
163
|
+
if (list.error)
|
|
164
|
+
return { level: 'broken', detail: `tools/list failed (${list.error})` };
|
|
165
|
+
if (list.status !== 200)
|
|
166
|
+
return { level: 'broken', detail: `tools/list → HTTP ${list.status}` };
|
|
151
167
|
const listMsg = parseJsonRpc(list.contentType, list.text, 2);
|
|
152
168
|
const tools = listMsg?.result?.tools;
|
|
153
|
-
if (!Array.isArray(tools))
|
|
169
|
+
if (!Array.isArray(tools))
|
|
170
|
+
return { level: 'broken', detail: 'tools/list → no parseable result' };
|
|
154
171
|
|
|
155
172
|
// 3. On an Atelier Storybook MCP, exercise one real tool call: manifests
|
|
156
173
|
// are only fetched inside tool calls, and a broken manifest fetch comes
|
|
@@ -166,14 +183,21 @@ async function probeMcp(url) {
|
|
|
166
183
|
},
|
|
167
184
|
session,
|
|
168
185
|
);
|
|
169
|
-
if (call.error)
|
|
170
|
-
|
|
186
|
+
if (call.error)
|
|
187
|
+
return { level: 'broken', detail: `docs-list failed (${call.error})` };
|
|
188
|
+
if (call.status !== 200)
|
|
189
|
+
return { level: 'broken', detail: `docs-list → HTTP ${call.status}` };
|
|
171
190
|
const callMsg = parseJsonRpc(call.contentType, call.text, 3);
|
|
172
191
|
if (!callMsg?.result || callMsg.result.isError) {
|
|
173
192
|
const reason =
|
|
174
|
-
callMsg?.result?.content?.[0]?.text ??
|
|
193
|
+
callMsg?.result?.content?.[0]?.text ??
|
|
194
|
+
callMsg?.error?.message ??
|
|
195
|
+
'no parseable result';
|
|
175
196
|
const oneLine = String(reason).replace(/\s+/g, ' ').trim();
|
|
176
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
level: 'broken',
|
|
199
|
+
detail: `docs-list → ${oneLine.slice(0, 120)}`,
|
|
200
|
+
};
|
|
177
201
|
}
|
|
178
202
|
}
|
|
179
203
|
|
|
@@ -205,6 +229,31 @@ function loadMcpEndpoints() {
|
|
|
205
229
|
}
|
|
206
230
|
}
|
|
207
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Reads the exact `figma-console-mcp@<version>` pin off the `figma-console`
|
|
234
|
+
* server's `args` in `.mcp.json` — the same file `loadMcpEndpoints()` reads,
|
|
235
|
+
* just a different server shape (`command: npx`, not `type: http`). Returns
|
|
236
|
+
* null when `.mcp.json` is missing, has no `figma-console` entry, or that
|
|
237
|
+
* entry isn't pinned to an exact version (e.g. `@latest`) — every caller
|
|
238
|
+
* treats null as "nothing to compare against," not as a failure.
|
|
239
|
+
*/
|
|
240
|
+
function getPinnedFigmaConsoleVersion() {
|
|
241
|
+
const mcpPath = resolve(ROOT, '.mcp.json');
|
|
242
|
+
if (!existsSync(mcpPath)) return null;
|
|
243
|
+
try {
|
|
244
|
+
const config = JSON.parse(readFileSync(mcpPath, 'utf8'));
|
|
245
|
+
const args = config.mcpServers?.['figma-console']?.args;
|
|
246
|
+
if (!Array.isArray(args)) return null;
|
|
247
|
+
for (const arg of args) {
|
|
248
|
+
const match = /^figma-console-mcp@(\d+\.\d+\.\d+)$/.exec(arg);
|
|
249
|
+
if (match) return match[1];
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
208
257
|
// ── Checks ───────────────────────────────────────────────────────────
|
|
209
258
|
function checkNode() {
|
|
210
259
|
const required = { major: 22, minor: 12, patch: 0 };
|
|
@@ -224,7 +273,11 @@ function checkNpm() {
|
|
|
224
273
|
const required = { major: 10, minor: 0, patch: 0 };
|
|
225
274
|
const path = which('npm');
|
|
226
275
|
if (!path) {
|
|
227
|
-
fail(
|
|
276
|
+
fail(
|
|
277
|
+
'npm',
|
|
278
|
+
'not found on PATH',
|
|
279
|
+
'npm ships with Node.js — reinstall Node if missing',
|
|
280
|
+
);
|
|
228
281
|
return;
|
|
229
282
|
}
|
|
230
283
|
try {
|
|
@@ -233,7 +286,11 @@ function checkNpm() {
|
|
|
233
286
|
if (isAtLeast(actual, required)) {
|
|
234
287
|
ok('npm', `v${version}`);
|
|
235
288
|
} else {
|
|
236
|
-
warn(
|
|
289
|
+
warn(
|
|
290
|
+
'npm',
|
|
291
|
+
`found v${version}, recommend >= 10.0.0`,
|
|
292
|
+
'Run `npm install -g npm@latest`',
|
|
293
|
+
);
|
|
237
294
|
}
|
|
238
295
|
} catch (err) {
|
|
239
296
|
fail('npm', String(err?.message ?? err), 'Reinstall Node.js');
|
|
@@ -243,14 +300,22 @@ function checkNpm() {
|
|
|
243
300
|
function checkGit() {
|
|
244
301
|
const path = which('git');
|
|
245
302
|
if (!path) {
|
|
246
|
-
fail(
|
|
303
|
+
fail(
|
|
304
|
+
'git',
|
|
305
|
+
'not found on PATH',
|
|
306
|
+
'Install from https://git-scm.com or `brew install git`',
|
|
307
|
+
);
|
|
247
308
|
return;
|
|
248
309
|
}
|
|
249
310
|
try {
|
|
250
311
|
const version = execSync('git --version', { encoding: 'utf8' }).trim();
|
|
251
312
|
ok('git', version);
|
|
252
313
|
} catch {
|
|
253
|
-
warn(
|
|
314
|
+
warn(
|
|
315
|
+
'git',
|
|
316
|
+
'installed but version check failed',
|
|
317
|
+
'Verify install with `git --version`',
|
|
318
|
+
);
|
|
254
319
|
}
|
|
255
320
|
}
|
|
256
321
|
|
|
@@ -265,36 +330,96 @@ function checkClaudeCli() {
|
|
|
265
330
|
return;
|
|
266
331
|
}
|
|
267
332
|
try {
|
|
268
|
-
const version = execSync('claude --version', {
|
|
333
|
+
const version = execSync('claude --version', {
|
|
334
|
+
encoding: 'utf8',
|
|
335
|
+
timeout: 5000,
|
|
336
|
+
}).trim();
|
|
269
337
|
ok('Claude Code CLI', version);
|
|
270
338
|
} catch {
|
|
271
|
-
warn(
|
|
339
|
+
warn(
|
|
340
|
+
'Claude Code CLI',
|
|
341
|
+
'installed but `claude --version` failed',
|
|
342
|
+
'Try `claude doctor`',
|
|
343
|
+
);
|
|
272
344
|
}
|
|
273
345
|
}
|
|
274
346
|
|
|
275
347
|
async function checkFigmaSetup() {
|
|
276
348
|
// 1. Desktop Bridge plugin manifest — the primary channel.
|
|
277
|
-
const manifestPath = join(
|
|
349
|
+
const manifestPath = join(
|
|
350
|
+
homedir(),
|
|
351
|
+
'.figma-console-mcp',
|
|
352
|
+
'plugin',
|
|
353
|
+
'manifest.json',
|
|
354
|
+
);
|
|
355
|
+
const pinnedVersion = getPinnedFigmaConsoleVersion();
|
|
356
|
+
const pinnedSpec = pinnedVersion
|
|
357
|
+
? `figma-console-mcp@${pinnedVersion}`
|
|
358
|
+
: 'figma-console-mcp@latest';
|
|
278
359
|
if (existsSync(manifestPath)) {
|
|
279
360
|
ok('Figma Desktop Bridge plugin', `manifest at ${manifestPath}`);
|
|
280
361
|
} else {
|
|
281
362
|
warn(
|
|
282
363
|
'Figma Desktop Bridge plugin',
|
|
283
364
|
'manifest not found',
|
|
284
|
-
|
|
365
|
+
`Run \`npx -y ${pinnedSpec} --help\` once, then Figma → Plugins → Development → Import plugin from manifest…`,
|
|
285
366
|
);
|
|
286
367
|
}
|
|
287
368
|
|
|
369
|
+
// 1b. .version marker vs. the exact version .mcp.json pins. The package
|
|
370
|
+
// writes this file (setupStablePluginDir(), dist/local.js) specifically so
|
|
371
|
+
// staleness between "what the npm package installed on disk" and "what
|
|
372
|
+
// this workspace is pinned to" can be detected — nothing read it back
|
|
373
|
+
// until now. This is a warning, not a hard failure: a stale on-disk build
|
|
374
|
+
// does not block the workshop (the Bridge still connects and runs), and
|
|
375
|
+
// the files self-heal on the next `figma-console-mcp` server start anyway
|
|
376
|
+
// — treating it as fatal would fail preflight over something that fixes
|
|
377
|
+
// itself the moment Claude Code (re)starts the MCP.
|
|
378
|
+
if (existsSync(manifestPath)) {
|
|
379
|
+
const versionPath = join(
|
|
380
|
+
homedir(),
|
|
381
|
+
'.figma-console-mcp',
|
|
382
|
+
'plugin',
|
|
383
|
+
'.version',
|
|
384
|
+
);
|
|
385
|
+
if (!existsSync(versionPath)) {
|
|
386
|
+
warn(
|
|
387
|
+
'Plugin files .version marker',
|
|
388
|
+
'manifest exists but no .version file next to it',
|
|
389
|
+
`Run \`npx -y ${pinnedSpec} --help\` once to refresh ~/.figma-console-mcp/plugin/ with a current build`,
|
|
390
|
+
);
|
|
391
|
+
} else if (pinnedVersion) {
|
|
392
|
+
const diskVersion = readFileSync(versionPath, 'utf8').trim();
|
|
393
|
+
if (diskVersion === pinnedVersion) {
|
|
394
|
+
ok('Plugin files vs. .mcp.json pin', `both ${diskVersion}`);
|
|
395
|
+
} else {
|
|
396
|
+
warn(
|
|
397
|
+
'Plugin files vs. .mcp.json pin',
|
|
398
|
+
`on-disk .version is ${diskVersion}, .mcp.json pins ${pinnedVersion}`,
|
|
399
|
+
`Run \`npx -y ${pinnedSpec} --help\` once to refresh the files, then re-run (or re-import) the plugin in Figma Desktop — see https://atelier.pieper.io/runbook#plugin-update`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
288
405
|
// 2. Bridge WebSocket port range — at least one port must be usable.
|
|
289
|
-
const bridgePorts = [
|
|
406
|
+
const bridgePorts = [
|
|
407
|
+
9223, 9224, 9225, 9226, 9227, 9228, 9229, 9230, 9231, 9232,
|
|
408
|
+
];
|
|
290
409
|
let freeCount = 0;
|
|
291
410
|
for (const p of bridgePorts) {
|
|
292
411
|
if (await portFree(p)) freeCount += 1;
|
|
293
412
|
}
|
|
294
413
|
if (freeCount === bridgePorts.length) {
|
|
295
|
-
ok(
|
|
414
|
+
ok(
|
|
415
|
+
'Bridge port range 9223–9232',
|
|
416
|
+
'all free (MCP will bind on first launch)',
|
|
417
|
+
);
|
|
296
418
|
} else if (freeCount > 0) {
|
|
297
|
-
ok(
|
|
419
|
+
ok(
|
|
420
|
+
'Bridge port range 9223–9232',
|
|
421
|
+
`${freeCount}/10 free (bridge connection OK)`,
|
|
422
|
+
);
|
|
298
423
|
} else {
|
|
299
424
|
warn(
|
|
300
425
|
'Bridge port range 9223–9232',
|
|
@@ -403,7 +528,8 @@ function isDir(path) {
|
|
|
403
528
|
}
|
|
404
529
|
|
|
405
530
|
function detectEnvironment() {
|
|
406
|
-
const isClone =
|
|
531
|
+
const isClone =
|
|
532
|
+
isDir(resolve(ROOT, 'libs/spec')) && isDir(resolve(ROOT, 'plan/adr'));
|
|
407
533
|
return isClone ? 'clone' : 'scaffold';
|
|
408
534
|
}
|
|
409
535
|
|
|
@@ -427,6 +553,50 @@ const PORTS_BY_ENV = {
|
|
|
427
553
|
],
|
|
428
554
|
};
|
|
429
555
|
|
|
556
|
+
// ── Playwright Chromium (scaffold only) ─────────────────────────────
|
|
557
|
+
// The scaffold's `npm run check:stories` runs every story headless in
|
|
558
|
+
// Chromium via @storybook/addon-vitest + @vitest/browser-playwright — a
|
|
559
|
+
// browser Playwright manages itself, downloaded by `npx playwright install
|
|
560
|
+
// chromium` (never as a postinstall hook: a workshop attendee's first
|
|
561
|
+
// `npm install` should not silently spend minutes downloading a browser).
|
|
562
|
+
// This is a best-effort, dependency-free heuristic (no `require('playwright')`
|
|
563
|
+
// here — this script has zero non-builtin imports and should keep working
|
|
564
|
+
// even before `npm install` has finished) — it looks for a `chromium-*`
|
|
565
|
+
// directory in Playwright's own browser cache, respecting
|
|
566
|
+
// `PLAYWRIGHT_BROWSERS_PATH` the same way Playwright itself does. A warning,
|
|
567
|
+
// not a hard failure: this only blocks `check:stories`, not the workshop.
|
|
568
|
+
function playwrightBrowsersDir() {
|
|
569
|
+
if (process.env.PLAYWRIGHT_BROWSERS_PATH)
|
|
570
|
+
return process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
571
|
+
const home = homedir();
|
|
572
|
+
if (process.platform === 'darwin')
|
|
573
|
+
return join(home, 'Library', 'Caches', 'ms-playwright');
|
|
574
|
+
if (process.platform === 'win32')
|
|
575
|
+
return join(home, 'AppData', 'Local', 'ms-playwright');
|
|
576
|
+
return join(home, '.cache', 'ms-playwright');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function checkPlaywrightChromium() {
|
|
580
|
+
const dir = playwrightBrowsersDir();
|
|
581
|
+
let hasChromium = false;
|
|
582
|
+
try {
|
|
583
|
+
hasChromium =
|
|
584
|
+
existsSync(dir) &&
|
|
585
|
+
readdirSync(dir).some((name) => name.startsWith('chromium'));
|
|
586
|
+
} catch {
|
|
587
|
+
hasChromium = false;
|
|
588
|
+
}
|
|
589
|
+
if (hasChromium) {
|
|
590
|
+
ok('Playwright Chromium', 'installed');
|
|
591
|
+
} else {
|
|
592
|
+
warn(
|
|
593
|
+
'Playwright Chromium',
|
|
594
|
+
'not found',
|
|
595
|
+
'Run `npx playwright install chromium` — needed for `npm run check:stories`',
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
430
600
|
async function checkPorts(env) {
|
|
431
601
|
ok(
|
|
432
602
|
'Environment',
|
|
@@ -437,7 +607,12 @@ async function checkPorts(env) {
|
|
|
437
607
|
for (const { port, label } of PORTS_BY_ENV[env]) {
|
|
438
608
|
const free = await portFree(port);
|
|
439
609
|
if (free) ok(`Port ${port} (${label})`, 'free');
|
|
440
|
-
else
|
|
610
|
+
else
|
|
611
|
+
warn(
|
|
612
|
+
`Port ${port} (${label})`,
|
|
613
|
+
'in use',
|
|
614
|
+
`Run \`lsof -ti :${port} | xargs kill\` (macOS/Linux)`,
|
|
615
|
+
);
|
|
441
616
|
}
|
|
442
617
|
}
|
|
443
618
|
|
|
@@ -471,6 +646,11 @@ async function main() {
|
|
|
471
646
|
header('Local ports');
|
|
472
647
|
await checkPorts(env);
|
|
473
648
|
|
|
649
|
+
if (env === 'scaffold') {
|
|
650
|
+
header('Storybook browser tests');
|
|
651
|
+
checkPlaywrightChromium();
|
|
652
|
+
}
|
|
653
|
+
|
|
474
654
|
// Summary
|
|
475
655
|
const failures = results.filter((r) => r.level === 'fail').length;
|
|
476
656
|
const warnings = results.filter((r) => r.level === 'warn').length;
|
|
@@ -479,19 +659,29 @@ async function main() {
|
|
|
479
659
|
console.log('');
|
|
480
660
|
if (failures === 0) {
|
|
481
661
|
console.log(
|
|
482
|
-
green(`All hard checks passed`) +
|
|
662
|
+
green(`All hard checks passed`) +
|
|
663
|
+
dim(` · ${passes} ok, ${warnings} warning(s)`),
|
|
483
664
|
);
|
|
484
665
|
if (warnings > 0) {
|
|
485
|
-
console.log(
|
|
666
|
+
console.log(
|
|
667
|
+
dim(
|
|
668
|
+
'Warnings are non-blocking — see https://atelier.pieper.io/troubleshooting',
|
|
669
|
+
),
|
|
670
|
+
);
|
|
486
671
|
}
|
|
487
672
|
console.log('');
|
|
488
673
|
process.exit(0);
|
|
489
674
|
} else {
|
|
490
675
|
console.log(
|
|
491
|
-
red(`${failures} check(s) failed`) +
|
|
676
|
+
red(`${failures} check(s) failed`) +
|
|
677
|
+
dim(` · ${passes} ok, ${warnings} warning(s)`),
|
|
492
678
|
);
|
|
493
679
|
console.log(dim('Fix the items above, then re-run `npm run preflight`.'));
|
|
494
|
-
console.log(
|
|
680
|
+
console.log(
|
|
681
|
+
dim(
|
|
682
|
+
'Full troubleshooting guide: https://atelier.pieper.io/troubleshooting',
|
|
683
|
+
),
|
|
684
|
+
);
|
|
495
685
|
console.log('');
|
|
496
686
|
process.exit(1);
|
|
497
687
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local stylelint plugin for this repo's own CSS-discipline invariants —
|
|
5
|
+
* the stylelint counterpart of `tools/eslint-rules/`. A stylelint plugin
|
|
6
|
+
* module may export a single rule definition or an array of them (stylelint
|
|
7
|
+
* flattens either shape when loading `plugins`); this file exports the
|
|
8
|
+
* array so `stylelint.config.mjs` only needs one entry in `plugins` to pick
|
|
9
|
+
* up every rule below.
|
|
10
|
+
*
|
|
11
|
+
* Each rule file calls `stylelint.createPlugin(ruleName, rule)` itself and
|
|
12
|
+
* is `require()`-able on its own — this index just collects them, the same
|
|
13
|
+
* relationship `tools/eslint-rules/index.js` has to its own rule files.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
module.exports = [
|
|
17
|
+
require('./no-raw-color-literal'),
|
|
18
|
+
require('./no-undeclared-token'),
|
|
19
|
+
require('./no-primitive-token'),
|
|
20
|
+
require('./no-token-bypass'),
|
|
21
|
+
];
|