@birdapi/velinstyle 1.2.1 → 1.3.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.de.md +148 -11
- package/README.md +174 -16
- package/cli/__fixtures__/atelier-library/showcases/01-login/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/01-login/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/01-login/index.html +5 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/index.html +5 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/index.html +5 -0
- package/cli/atelier-catalog.json +1267 -0
- package/cli/atelier-formats.js +159 -0
- package/cli/atelier.js +382 -0
- package/cli/blueprints/empty-state.html +3 -5
- package/cli/cli-manifest.json +441 -161
- package/cli/docgen/extract-cli.js +2 -1
- package/cli/docs-generate.js +9 -0
- package/cli/experience.js +173 -0
- package/cli/index.js +1384 -361
- package/cli/motion.js +738 -0
- package/cli/production/component-graph.json +166 -0
- package/cli/production/explain.js +52 -0
- package/cli/production/extract.js +278 -0
- package/cli/production/graph.js +102 -0
- package/cli/production/report.js +168 -0
- package/cli/production/run.js +355 -0
- package/cli/production/trim-css.js +177 -0
- package/cli/production/trim-fonts.js +23 -0
- package/cli/production/trim-icons.js +38 -0
- package/cli/production/trim-js.js +48 -0
- package/cli/production/trim-motion.js +22 -0
- package/cli/production/trim-themes.js +50 -0
- package/cli/production/watch.js +38 -0
- package/cli/review.js +48 -1
- package/cli/scripts/write-atelier-fixtures.mjs +33 -0
- package/cli/security.js +190 -0
- package/cli/skills.js +53 -10
- package/cli/transparency.js +140 -41
- package/cli/workflow.js +32 -17
- package/components/index.js +3 -0
- package/components/runtime/component-loaders.js +3 -0
- package/components/velin-drawer.js +43 -4
- package/components/velin-empty-state.js +83 -0
- package/components/velin-modal.js +76 -15
- package/components/velin-otp-input.js +220 -0
- package/components/velin-password-strength.js +147 -0
- package/components/velin-sheet.js +49 -4
- package/components/velin-theme-toggle.js +15 -1
- package/core/a11y/component-contracts.json +391 -255
- package/core/attributes/registry.js +1 -1
- package/core/meta/knowledge/components.json +75 -3
- package/core/motion/analyze.js +38 -0
- package/core/motion/blueprint.js +51 -0
- package/core/motion/composer.js +114 -0
- package/core/motion/data/registry.json +352 -0
- package/core/motion/effects.js +17 -1
- package/core/motion/index.js +112 -9
- package/core/motion/optimize.js +44 -0
- package/core/motion/policy.js +77 -0
- package/core/motion/registry.js +131 -0
- package/core/motion/review.js +59 -0
- package/core/motion/scan.js +150 -0
- package/core/motion/timeline.js +68 -0
- package/core/motion/transitions.js +49 -0
- package/core/motion/triggers.js +168 -0
- package/core/security/checks/fs.js +92 -0
- package/core/security/detect.js +35 -0
- package/core/security/engine.js +116 -0
- package/core/security/index.js +18 -0
- package/core/security/registry.js +85 -0
- package/core/security/report/report.js +113 -0
- package/core/security/rules/ci/index.js +146 -0
- package/core/security/rules/consumer/index.js +174 -0
- package/core/security/rules/deps/index.js +182 -0
- package/core/security/rules/index.js +24 -0
- package/core/security/rules/publish/index.js +230 -0
- package/core/security/rules/repo/index.js +143 -0
- package/core/security/rules/secrets/index.js +133 -0
- package/core/security/score/dependency-health.js +53 -0
- package/core/security/score/release-health.js +66 -0
- package/core/security/score/score.js +102 -0
- package/core/security/timeline/timeline.js +76 -0
- package/dist/chunks/attributes-2ORR27KA.js +404 -0
- package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
- package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
- package/dist/chunks/chunk-MYKUI364.js +116 -0
- package/dist/chunks/chunk-NEVBPD5T.js +116 -0
- package/dist/chunks/chunk-Y6HN7HWX.js +116 -0
- package/dist/chunks/runtime-entry.js +1 -1
- package/dist/chunks/velin-drawer-A7Q7EBOS.js +162 -0
- package/dist/chunks/velin-empty-state-3NTUH2RF.js +81 -0
- package/dist/chunks/velin-modal-3MV4FYBK.js +231 -0
- package/dist/chunks/velin-otp-input-PSK42MM6.js +207 -0
- package/dist/chunks/velin-password-strength-TAXMLSED.js +136 -0
- package/dist/chunks/velin-sheet-N2VVYXFP.js +157 -0
- package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
- package/dist/llms.txt +4 -4
- package/dist/search-index.json +232 -5
- package/dist/velin-agent.json +433 -46
- package/dist/velinstyle-components.iife.js +3695 -2272
- package/dist/velinstyle-components.js +3698 -2271
- package/dist/velinstyle-components.min.js +262 -120
- package/dist/velinstyle.css +96 -13
- package/dist/velinstyle.d.ts +3 -0
- package/dist/velinstyle.min.css +1 -1
- package/package.json +19 -4
- package/packages/velinstyle-cli-core/package.json +11 -0
- package/packages/velinstyle-cli-core/src/config.js +84 -0
- package/packages/velinstyle-cli-core/src/contract.js +56 -0
- package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
- package/packages/velinstyle-cli-core/src/index.js +18 -0
- package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
- package/packages/velinstyle-cli-core/src/runner.js +129 -0
- package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
- package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
- package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
- package/packages/velinstyle-cli-registry/package.json +13 -0
- package/packages/velinstyle-cli-registry/src/index.js +88 -0
- package/packages/velinstyle-cli-renderer/package.json +8 -0
- package/packages/velinstyle-cli-renderer/src/index.js +286 -0
- package/packages/velinstyle-cli-theme/package.json +8 -0
- package/packages/velinstyle-cli-theme/src/index.js +84 -0
- package/packages/velinstyle-cli-ui/package.json +8 -0
- package/packages/velinstyle-cli-ui/src/index.js +34 -0
- package/packages/velinstyle-motion/README.md +81 -0
- package/packages/velinstyle-motion/package.json +37 -0
- package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
- package/packages/velinstyle-motion/src/ai/provider.js +59 -0
- package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
- package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
- package/packages/velinstyle-motion/src/config.js +257 -0
- package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
- package/packages/velinstyle-motion/src/index.js +304 -0
- package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
- package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
- package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
- package/packages/velinstyle-motion/src/local/effects.js +139 -0
- package/packages/velinstyle-motion/src/local/engine.js +96 -0
- package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
- package/packages/velinstyle-motion/src/registry/presets.js +25 -0
- package/packages/velinstyle-motion/src/registry/presets.json +85 -0
- package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
- package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
- package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
- package/packages/velinstyle-motion/src/validate-image.js +91 -0
- package/packages/velinstyle-skills/catalog.json +1 -1
- package/packages/velinstyle-skills/registry.json +1 -142
- package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
- package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
- package/src/components/data-table.css +19 -0
- package/src/components/empty-auth.css +33 -0
- package/src/components/table.css +16 -6
- package/src/tokens/motion.css +7 -0
- package/src/utilities/scroll-animation.css +66 -0
- package/src/velinstyle.css +1 -0
package/cli/index.js
CHANGED
|
@@ -85,146 +85,92 @@ function hasFlag(flag, alias) {
|
|
|
85
85
|
|
|
86
86
|
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
87
87
|
|
|
88
|
-
function help() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
velinstyle layout suggest [path] Audit with fix suggestions
|
|
166
|
-
velinstyle layout fix [path] Apply safe fixes (--dry-run default, --write)
|
|
167
|
-
|
|
168
|
-
${C.bold('Performance (0.9.0):')}
|
|
169
|
-
velinstyle perf audit [path|file] Report CLS, lazy-load, script defer issues
|
|
170
|
-
velinstyle perf suggest [path|file] Same as audit with fix hints
|
|
171
|
-
velinstyle perf fix [path|file] Apply safe fixes (--write)
|
|
172
|
-
|
|
173
|
-
${C.bold('Tokens:')}
|
|
174
|
-
velinstyle tokens build [--input <path>] [--output, -o <file>]
|
|
175
|
-
velinstyle tokens validate [--input <path>]
|
|
176
|
-
|
|
177
|
-
${C.bold('Docs (0.9.0):')}
|
|
178
|
-
velinstyle docs generate [--scope all|components|tokens|utilities|cli|rules|a11y|meta] [--out docs/generated]
|
|
179
|
-
velinstyle meta [--out dist/velin-agent.json] [--llms-out dist/llms.txt] [--base-url URL]
|
|
180
|
-
velinstyle meta page <file.html> [--write] Merge curated meta (goals/intent) on --write
|
|
181
|
-
|
|
182
|
-
${C.bold('Search:')}
|
|
183
|
-
velinstyle search index [--out dist/search-index.json] [--extra-html dir1,dir2]
|
|
184
|
-
|
|
185
|
-
${C.bold('Skills:')}
|
|
186
|
-
velinstyle skills list [--capability review] [--status beta] [--json]
|
|
187
|
-
velinstyle skills show <skill-id> [--human]
|
|
188
|
-
velinstyle skills install <skill-id|pack|bundle:id|project:id> [--target .cursor/skills]
|
|
189
|
-
velinstyle skills run <skill-id> [--json]
|
|
190
|
-
velinstyle skills validate
|
|
191
|
-
velinstyle skills packs|bundles|templates|projects|graphs
|
|
192
|
-
|
|
193
|
-
${C.bold('Workflow:')}
|
|
194
|
-
velinstyle workflow <graph-id|project:id> [--json]
|
|
195
|
-
|
|
196
|
-
${C.bold('Scan options:')}
|
|
197
|
-
--fix Auto-fix safe issues (writes files)
|
|
198
|
-
--fix-dry-run Show files that would be auto-fixed; no write
|
|
199
|
-
--fix-lang <code> Default lang for a11y/html-lang fix (default: de)
|
|
200
|
-
--severity <level> Minimum severity: error, warning, info
|
|
201
|
-
--only <category> Filter: security, pii, a11y, css, wc, perf
|
|
202
|
-
|
|
203
|
-
${C.bold('Prefix options (velinstyle prefix):')}
|
|
204
|
-
--write Write files (default is dry-run)
|
|
205
|
-
--bootstrap-display Map Bootstrap d-* display utilities to Velin names
|
|
206
|
-
--map <file> JSON class map (merged after auto map; see below)
|
|
207
|
-
|
|
208
|
-
Prefix also loads ${C.dim('velinstyle-prefix-map.json')} from the migration root (the
|
|
209
|
-
directory you pass, or the parent of a single file) if that file exists. ${C.dim('--map')}
|
|
210
|
-
merges on top and overrides duplicate keys.
|
|
211
|
-
|
|
212
|
-
${C.bold('Build options:')}
|
|
213
|
-
--output, -o <path> Output file path (default: ./velinstyle-custom.css)
|
|
214
|
-
--minify Minify output
|
|
215
|
-
--preset lite Marketing subset layers (tokens+reset+base+layout+components+utilities)
|
|
216
|
-
|
|
217
|
-
${C.bold('General:')}
|
|
218
|
-
--help, -h Show this help
|
|
219
|
-
`);
|
|
88
|
+
async function help() {
|
|
89
|
+
const exp = await ensureExperience();
|
|
90
|
+
console.log(exp.buildHelpFromRegistry(CLI_VERSION));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Cached Experience bridge module. */
|
|
94
|
+
let experienceApi = null;
|
|
95
|
+
|
|
96
|
+
async function ensureExperience() {
|
|
97
|
+
if (experienceApi) return experienceApi;
|
|
98
|
+
experienceApi = await import('./experience.js');
|
|
99
|
+
return experienceApi;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Finish a command through Velin Experience (Contract + Renderer).
|
|
104
|
+
* Side effects should already be done; this only presents the result.
|
|
105
|
+
*/
|
|
106
|
+
async function emitExperience(commandId, payload = {}) {
|
|
107
|
+
const exp = await ensureExperience();
|
|
108
|
+
const meta = exp.getCommand(commandId);
|
|
109
|
+
const {
|
|
110
|
+
layout = meta?.layout || 'standard',
|
|
111
|
+
title = meta?.title || commandId,
|
|
112
|
+
subtitle = '',
|
|
113
|
+
status = 'PASS',
|
|
114
|
+
message = '',
|
|
115
|
+
project = {},
|
|
116
|
+
stats = {},
|
|
117
|
+
items = [],
|
|
118
|
+
table = null,
|
|
119
|
+
suggestions = [],
|
|
120
|
+
diagnostics = [],
|
|
121
|
+
notifications = [],
|
|
122
|
+
widgets = null,
|
|
123
|
+
exitCode,
|
|
124
|
+
} = payload;
|
|
125
|
+
|
|
126
|
+
const noteWidgets = (notifications || []).map((n) => ({
|
|
127
|
+
id: 'notification',
|
|
128
|
+
props: typeof n === 'string' ? { tone: 'info', message: n } : n,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const autoWidgets = widgets || [
|
|
132
|
+
{
|
|
133
|
+
id: 'hero',
|
|
134
|
+
props: {
|
|
135
|
+
slot: 'hero',
|
|
136
|
+
title,
|
|
137
|
+
subtitle: subtitle || message || '',
|
|
138
|
+
eyebrow: 'Velin Experience',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
...(items.length ? [{ id: 'stats', props: { items } }] : []),
|
|
142
|
+
...(table ? [{ id: 'table', props: table }] : []),
|
|
143
|
+
...noteWidgets,
|
|
144
|
+
{ id: 'footer', props: { slot: 'footer', text: `velinstyle ${commandId}` } },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
await exp.executeExperience({
|
|
148
|
+
commandId,
|
|
149
|
+
args,
|
|
150
|
+
helpers: { getArg, hasFlag },
|
|
151
|
+
version: CLI_VERSION,
|
|
152
|
+
layout,
|
|
153
|
+
run: async () => ({
|
|
154
|
+
project,
|
|
155
|
+
result: { status, message: message || subtitle || `${title} · ${status}` },
|
|
156
|
+
stats,
|
|
157
|
+
widgets: autoWidgets,
|
|
158
|
+
suggestions,
|
|
159
|
+
diagnostics,
|
|
160
|
+
notifications: (notifications || []).map((n) => (typeof n === 'string' ? { message: n } : n)),
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (typeof exitCode === 'number') process.exit(exitCode);
|
|
220
165
|
}
|
|
221
166
|
|
|
222
167
|
// ── Init ─────────────────────────────────────────────────────────────────────
|
|
223
168
|
|
|
224
|
-
function init() {
|
|
169
|
+
async function init() {
|
|
170
|
+
const notes = [];
|
|
225
171
|
const configPath = resolve('velinstyle.config.js');
|
|
226
172
|
if (existsSync(configPath)) {
|
|
227
|
-
|
|
173
|
+
notes.push({ tone: 'info', message: 'velinstyle.config.js already exists' });
|
|
228
174
|
} else {
|
|
229
175
|
const config = `// VelinStyle Configuration
|
|
230
176
|
export default {
|
|
@@ -252,7 +198,7 @@ export default {
|
|
|
252
198
|
};
|
|
253
199
|
`;
|
|
254
200
|
writeFileSync(configPath, config);
|
|
255
|
-
|
|
201
|
+
notes.push({ tone: 'ok', message: 'Created velinstyle.config.js' });
|
|
256
202
|
}
|
|
257
203
|
|
|
258
204
|
const vendorDir = resolve('vendor', 'velinstyle');
|
|
@@ -271,9 +217,9 @@ export default {
|
|
|
271
217
|
if (statSync(src).isFile()) writeFileSync(join(vendorDir, 'themes', name), readFileSync(src));
|
|
272
218
|
}
|
|
273
219
|
}
|
|
274
|
-
|
|
220
|
+
notes.push({ tone: 'ok', message: `Copied dist assets → ${vendorDir}` });
|
|
275
221
|
} else {
|
|
276
|
-
|
|
222
|
+
notes.push({ tone: 'warn', message: 'Framework dist/ not found — skip vendor copy' });
|
|
277
223
|
}
|
|
278
224
|
|
|
279
225
|
const starter = resolve('index.velin-starter.html');
|
|
@@ -301,15 +247,31 @@ export default {
|
|
|
301
247
|
`,
|
|
302
248
|
'utf-8',
|
|
303
249
|
);
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
250
|
+
notes.push({ tone: 'ok', message: 'Created index.velin-starter.html' });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await emitExperience('init', {
|
|
254
|
+
layout: 'wizard',
|
|
255
|
+
message: 'Project initialized',
|
|
256
|
+
notifications: notes,
|
|
257
|
+
suggestions: [
|
|
258
|
+
{ text: 'velinstyle build' },
|
|
259
|
+
{ text: 'velinstyle serve' },
|
|
260
|
+
{ text: 'velinstyle doctor' },
|
|
261
|
+
],
|
|
262
|
+
});
|
|
308
263
|
}
|
|
309
264
|
|
|
310
265
|
// ── Build ────────────────────────────────────────────────────────────────────
|
|
311
266
|
|
|
312
267
|
async function build() {
|
|
268
|
+
if (hasFlag('--production')) {
|
|
269
|
+
const { runProduction } = await import('./production/run.js');
|
|
270
|
+
const rest = args.slice(1).filter((a) => a !== '--production');
|
|
271
|
+
await runProduction({ args: rest, helpers: { getArg, hasFlag, C } });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
313
275
|
const configPath = resolve('velinstyle.config.js');
|
|
314
276
|
let config;
|
|
315
277
|
|
|
@@ -370,42 +332,56 @@ async function build() {
|
|
|
370
332
|
shell: true,
|
|
371
333
|
});
|
|
372
334
|
bundled = true;
|
|
373
|
-
console.log(C.green(`Built: ${output}`) + ` (layers: ${selectedLayers.join(', ')}${config.theme ? ', theme: ' + config.theme : ''})`);
|
|
374
335
|
} catch {
|
|
375
336
|
writeFileSync(outAbs, css);
|
|
376
|
-
|
|
377
|
-
console.log(C.yellow(' Install lightningcss-cli in the VelinStyle package (or run build from the framework repo) for minified output.'));
|
|
337
|
+
bundled = false;
|
|
378
338
|
} finally {
|
|
379
339
|
try { unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
380
340
|
}
|
|
381
341
|
|
|
342
|
+
const items = [
|
|
343
|
+
{ label: 'Output', value: output },
|
|
344
|
+
{ label: 'Layers', value: selectedLayers.length },
|
|
345
|
+
{ label: 'Bundle', value: bundled ? 'lightningcss' : 'raw' },
|
|
346
|
+
];
|
|
347
|
+
if (config.theme) items.push({ label: 'Theme', value: config.theme });
|
|
382
348
|
if (preset === 'lite' && existsSync(outAbs)) {
|
|
383
349
|
const liteBytes = statSync(outAbs).size;
|
|
384
350
|
const fullMin = join(PKG_ROOT, 'dist', 'velinstyle.min.css');
|
|
351
|
+
items.push({ label: 'Size', value: `${(liteBytes / 1024).toFixed(1)} KB` });
|
|
385
352
|
if (existsSync(fullMin)) {
|
|
386
|
-
|
|
387
|
-
const liteKb = (liteBytes / 1024).toFixed(1);
|
|
388
|
-
const fullKb = (fullBytes / 1024).toFixed(1);
|
|
389
|
-
console.log(` Size: lite ${liteKb} KB vs dist/velinstyle.min.css ${fullKb} KB`);
|
|
390
|
-
if (!bundled || liteBytes >= fullBytes) {
|
|
391
|
-
console.log(C.yellow(` Warning: lite output is not smaller than the full min CSS${bundled ? '' : ' (unbundled)'}.`));
|
|
392
|
-
}
|
|
353
|
+
items.push({ label: 'Full min', value: `${(statSync(fullMin).size / 1024).toFixed(1)} KB` });
|
|
393
354
|
}
|
|
394
355
|
}
|
|
395
356
|
|
|
357
|
+
const notifications = [];
|
|
358
|
+
if (!bundled) {
|
|
359
|
+
notifications.push({ tone: 'warn', message: 'Unbundled output — install lightningcss-cli for minify' });
|
|
360
|
+
}
|
|
361
|
+
|
|
396
362
|
if (config.scan?.enabled) {
|
|
397
|
-
console.log(C.dim('\nRunning post-build scan...'));
|
|
398
363
|
const { scan } = await import('./scanner.js');
|
|
399
364
|
scan(resolve('.'), config.scan);
|
|
365
|
+
notifications.push({ tone: 'info', message: 'Post-build scan ran (config.scan.enabled)' });
|
|
400
366
|
}
|
|
367
|
+
|
|
368
|
+
await emitExperience('build', {
|
|
369
|
+
layout: 'build',
|
|
370
|
+
status: 'PASS',
|
|
371
|
+
message: bundled ? `Built ${output}` : `Built (unbundled) ${output}`,
|
|
372
|
+
subtitle: `layers: ${selectedLayers.join(', ')}`,
|
|
373
|
+
items,
|
|
374
|
+
notifications,
|
|
375
|
+
suggestions: [{ text: 'velinstyle doctor' }, { text: 'velinstyle production . --explain' }],
|
|
376
|
+
});
|
|
401
377
|
}
|
|
402
378
|
|
|
403
379
|
// ── Themes ───────────────────────────────────────────────────────────────────
|
|
404
380
|
|
|
405
|
-
function themes() {
|
|
381
|
+
async function themes() {
|
|
406
382
|
const themesDir = join(PKG_ROOT, 'src', 'themes');
|
|
407
383
|
if (!existsSync(themesDir)) {
|
|
408
|
-
|
|
384
|
+
await emitExperience('themes', { status: 'WARNING', message: 'No themes directory found', exitCode: 1 });
|
|
409
385
|
return;
|
|
410
386
|
}
|
|
411
387
|
|
|
@@ -413,31 +389,44 @@ function themes() {
|
|
|
413
389
|
.filter(f => f.endsWith('.css'))
|
|
414
390
|
.map(f => f.replace('.css', ''));
|
|
415
391
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
392
|
+
await emitExperience('themes', {
|
|
393
|
+
message: `${available.length} themes`,
|
|
394
|
+
table: {
|
|
395
|
+
columns: ['Theme'],
|
|
396
|
+
rows: available.map((t) => [t]),
|
|
397
|
+
},
|
|
398
|
+
suggestions: [
|
|
399
|
+
{ text: 'Set theme in velinstyle.config.js' },
|
|
400
|
+
{ text: '<link rel="stylesheet" href="dist/themes/<name>.min.css">' },
|
|
401
|
+
],
|
|
402
|
+
});
|
|
421
403
|
}
|
|
422
404
|
|
|
423
405
|
// ── Add Component ────────────────────────────────────────────────────────────
|
|
424
406
|
|
|
425
|
-
function add() {
|
|
407
|
+
async function add() {
|
|
426
408
|
const name = args[1];
|
|
427
409
|
if (!name) {
|
|
428
|
-
|
|
429
|
-
|
|
410
|
+
await emitExperience('add', {
|
|
411
|
+
status: 'WARNING',
|
|
412
|
+
message: 'Usage: velinstyle add <component-name>',
|
|
413
|
+
suggestions: [{ text: 'velinstyle add button' }],
|
|
414
|
+
exitCode: 1,
|
|
415
|
+
});
|
|
430
416
|
return;
|
|
431
417
|
}
|
|
432
418
|
|
|
433
419
|
const filePath = join(PKG_ROOT, 'src', 'components', `${name}.css`);
|
|
434
420
|
if (!existsSync(filePath)) {
|
|
435
|
-
|
|
436
|
-
console.log('Available:');
|
|
437
|
-
readdirSync(join(PKG_ROOT, 'src', 'components'))
|
|
421
|
+
const available = readdirSync(join(PKG_ROOT, 'src', 'components'))
|
|
438
422
|
.filter(f => f.endsWith('.css'))
|
|
439
|
-
.map(f => f.replace('.css', ''))
|
|
440
|
-
|
|
423
|
+
.map(f => f.replace('.css', ''));
|
|
424
|
+
await emitExperience('add', {
|
|
425
|
+
status: 'ERROR',
|
|
426
|
+
message: `Component not found: ${name}`,
|
|
427
|
+
table: { columns: ['Available'], rows: available.slice(0, 40).map((c) => [c]) },
|
|
428
|
+
exitCode: 1,
|
|
429
|
+
});
|
|
441
430
|
return;
|
|
442
431
|
}
|
|
443
432
|
|
|
@@ -446,7 +435,11 @@ function add() {
|
|
|
446
435
|
const content = readFileSync(filePath, 'utf-8');
|
|
447
436
|
const outPath = join(outDir, `${name}.css`);
|
|
448
437
|
writeFileSync(outPath, content);
|
|
449
|
-
|
|
438
|
+
await emitExperience('add', {
|
|
439
|
+
message: `Added ${name}`,
|
|
440
|
+
items: [{ label: 'File', value: outPath }],
|
|
441
|
+
notifications: [{ tone: 'ok', message: `Wrote ${outPath}` }],
|
|
442
|
+
});
|
|
450
443
|
}
|
|
451
444
|
|
|
452
445
|
// ── Icons ────────────────────────────────────────────────────────────────────
|
|
@@ -455,47 +448,64 @@ async function icons() {
|
|
|
455
448
|
const sub = args[1];
|
|
456
449
|
|
|
457
450
|
if (!sub || sub === 'list') {
|
|
458
|
-
iconsListCmd();
|
|
451
|
+
await iconsListCmd();
|
|
459
452
|
} else if (sub === 'add') {
|
|
460
453
|
await iconsAddCmd();
|
|
461
454
|
} else if (sub === 'remove') {
|
|
462
|
-
iconsRemoveCmd();
|
|
455
|
+
await iconsRemoveCmd();
|
|
463
456
|
} else if (sub === 'build') {
|
|
464
|
-
iconsBuildCmd();
|
|
457
|
+
await iconsBuildCmd();
|
|
465
458
|
} else {
|
|
466
|
-
|
|
467
|
-
|
|
459
|
+
await emitExperience('icons', {
|
|
460
|
+
status: 'WARNING',
|
|
461
|
+
message: `Unknown icons subcommand: ${sub}`,
|
|
462
|
+
suggestions: [{ text: 'velinstyle icons list|add|remove|build' }],
|
|
463
|
+
exitCode: 1,
|
|
464
|
+
});
|
|
468
465
|
}
|
|
469
466
|
}
|
|
470
467
|
|
|
471
|
-
function iconsListCmd() {
|
|
468
|
+
async function iconsListCmd() {
|
|
472
469
|
const providers = listProviders();
|
|
473
|
-
console.log(`\n ${C.bold('Available Icon Providers:')}\n`);
|
|
474
|
-
providers.forEach(p => {
|
|
475
|
-
console.log(` ${C.cyan(p.key.padEnd(14))} ${p.name} -- ${p.description}`);
|
|
476
|
-
console.log(` ${' '.repeat(14)} License: ${p.license} | ${C.dim(p.homepage)}`);
|
|
477
|
-
if (p.variants.length) console.log(` ${' '.repeat(14)} Variants: ${p.variants.join(', ')}`);
|
|
478
|
-
console.log();
|
|
479
|
-
});
|
|
480
|
-
|
|
481
470
|
const svgDir = join(PKG_ROOT, 'icons', 'svg');
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
471
|
+
const installed = existsSync(svgDir)
|
|
472
|
+
? readdirSync(svgDir).filter(f => f.endsWith('.svg')).length
|
|
473
|
+
: 0;
|
|
474
|
+
await emitExperience('icons', {
|
|
475
|
+
subtitle: 'list',
|
|
476
|
+
message: `${providers.length} providers · ${installed} installed SVGs`,
|
|
477
|
+
table: {
|
|
478
|
+
columns: ['Provider', 'Name', 'License'],
|
|
479
|
+
rows: providers.map((p) => [p.key, p.name, p.license]),
|
|
480
|
+
},
|
|
481
|
+
items: [{ label: 'Installed', value: installed }],
|
|
482
|
+
suggestions: [
|
|
483
|
+
{ text: 'velinstyle icons add lucide --icons menu,search' },
|
|
484
|
+
{ text: 'velinstyle icons build' },
|
|
485
|
+
],
|
|
486
|
+
});
|
|
486
487
|
}
|
|
487
488
|
|
|
488
489
|
async function iconsAddCmd() {
|
|
489
490
|
const provider = args[2];
|
|
490
491
|
if (!provider) {
|
|
491
|
-
|
|
492
|
-
|
|
492
|
+
await emitExperience('icons', {
|
|
493
|
+
status: 'WARNING',
|
|
494
|
+
subtitle: 'add',
|
|
495
|
+
message: 'Usage: velinstyle icons add <provider> [--icons name1,name2] [--variant outline]',
|
|
496
|
+
suggestions: [{ text: `Providers: ${Object.keys(PROVIDERS).join(', ')}` }],
|
|
497
|
+
exitCode: 1,
|
|
498
|
+
});
|
|
493
499
|
return;
|
|
494
500
|
}
|
|
495
501
|
|
|
496
502
|
if (!PROVIDERS[provider]) {
|
|
497
|
-
|
|
498
|
-
|
|
503
|
+
await emitExperience('icons', {
|
|
504
|
+
status: 'ERROR',
|
|
505
|
+
message: `Unknown provider: ${provider}`,
|
|
506
|
+
suggestions: [{ text: `Available: ${Object.keys(PROVIDERS).join(', ')}` }],
|
|
507
|
+
exitCode: 1,
|
|
508
|
+
});
|
|
499
509
|
return;
|
|
500
510
|
}
|
|
501
511
|
|
|
@@ -503,8 +513,12 @@ async function iconsAddCmd() {
|
|
|
503
513
|
const variant = getArg('--variant');
|
|
504
514
|
|
|
505
515
|
if (!iconsList) {
|
|
506
|
-
|
|
507
|
-
|
|
516
|
+
await emitExperience('icons', {
|
|
517
|
+
status: 'WARNING',
|
|
518
|
+
message: 'Specify --icons name1,name2,...',
|
|
519
|
+
suggestions: [{ text: `velinstyle icons add ${provider} --icons heart,star,check` }],
|
|
520
|
+
exitCode: 1,
|
|
521
|
+
});
|
|
508
522
|
return;
|
|
509
523
|
}
|
|
510
524
|
|
|
@@ -514,10 +528,11 @@ async function iconsAddCmd() {
|
|
|
514
528
|
|
|
515
529
|
let added = 0;
|
|
516
530
|
let failed = 0;
|
|
531
|
+
const rows = [];
|
|
517
532
|
|
|
518
533
|
for (const name of names) {
|
|
519
534
|
const url = getProviderUrl(provider, name, variant);
|
|
520
|
-
if (!url) { failed++; continue; }
|
|
535
|
+
if (!url) { failed++; rows.push([name, 'FAIL', 'no url']); continue; }
|
|
521
536
|
|
|
522
537
|
try {
|
|
523
538
|
const res = await fetch(url);
|
|
@@ -527,56 +542,83 @@ async function iconsAddCmd() {
|
|
|
527
542
|
|
|
528
543
|
const filename = `${provider}-${name}.svg`;
|
|
529
544
|
writeFileSync(join(svgDir, filename), svg);
|
|
530
|
-
|
|
545
|
+
rows.push([name, 'OK', filename]);
|
|
531
546
|
added++;
|
|
532
547
|
} catch (e) {
|
|
533
|
-
|
|
548
|
+
rows.push([name, 'FAIL', e.message]);
|
|
534
549
|
failed++;
|
|
535
550
|
}
|
|
536
551
|
}
|
|
537
552
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
553
|
+
await emitExperience('icons', {
|
|
554
|
+
subtitle: 'add',
|
|
555
|
+
status: failed && !added ? 'ERROR' : failed ? 'WARNING' : 'PASS',
|
|
556
|
+
message: `Added ${added}, failed ${failed}`,
|
|
557
|
+
table: { columns: ['Icon', 'Status', 'Detail'], rows },
|
|
558
|
+
suggestions: added > 0 ? [{ text: 'velinstyle icons build' }] : [],
|
|
559
|
+
exitCode: failed && !added ? 1 : undefined,
|
|
560
|
+
});
|
|
542
561
|
}
|
|
543
562
|
|
|
544
|
-
function iconsRemoveCmd() {
|
|
563
|
+
async function iconsRemoveCmd() {
|
|
545
564
|
const provider = args[2];
|
|
546
565
|
if (!provider) {
|
|
547
|
-
|
|
566
|
+
await emitExperience('icons', {
|
|
567
|
+
status: 'WARNING',
|
|
568
|
+
message: 'Usage: velinstyle icons remove <provider>',
|
|
569
|
+
exitCode: 1,
|
|
570
|
+
});
|
|
548
571
|
return;
|
|
549
572
|
}
|
|
550
573
|
|
|
551
574
|
const svgDir = join(PKG_ROOT, 'icons', 'svg');
|
|
552
|
-
if (!existsSync(svgDir))
|
|
575
|
+
if (!existsSync(svgDir)) {
|
|
576
|
+
await emitExperience('icons', { status: 'WARNING', message: 'No icons/svg directory' });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
553
579
|
|
|
554
580
|
const files = readdirSync(svgDir).filter(f => f.startsWith(`${provider}-`) && f.endsWith('.svg'));
|
|
555
581
|
if (files.length === 0) {
|
|
556
|
-
|
|
582
|
+
await emitExperience('icons', {
|
|
583
|
+
status: 'WARNING',
|
|
584
|
+
message: `No icons found for provider: ${provider}`,
|
|
585
|
+
});
|
|
557
586
|
return;
|
|
558
587
|
}
|
|
559
588
|
|
|
560
|
-
files.forEach(f =>
|
|
561
|
-
|
|
562
|
-
|
|
589
|
+
files.forEach(f => unlinkSync(join(svgDir, f)));
|
|
590
|
+
await emitExperience('icons', {
|
|
591
|
+
subtitle: 'remove',
|
|
592
|
+
message: `Removed ${files.length} icons from ${provider}`,
|
|
593
|
+
table: { columns: ['Removed'], rows: files.map((f) => [f]) },
|
|
594
|
+
suggestions: [{ text: 'velinstyle icons build' }],
|
|
563
595
|
});
|
|
564
|
-
console.log(`\n Removed ${files.length} icons from "${provider}".`);
|
|
565
|
-
console.log(C.dim(' Run "velinstyle icons build" to rebuild the sprite.'));
|
|
566
596
|
}
|
|
567
597
|
|
|
568
|
-
function iconsBuildCmd() {
|
|
598
|
+
async function iconsBuildCmd() {
|
|
569
599
|
const buildScript = join(PKG_ROOT, 'icons', 'build-sprite.js');
|
|
570
600
|
if (!existsSync(buildScript)) {
|
|
571
|
-
|
|
601
|
+
await emitExperience('icons', {
|
|
602
|
+
status: 'ERROR',
|
|
603
|
+
message: 'icons/build-sprite.js not found',
|
|
604
|
+
exitCode: 1,
|
|
605
|
+
});
|
|
572
606
|
return;
|
|
573
607
|
}
|
|
574
608
|
|
|
575
609
|
try {
|
|
576
610
|
execSync(`node "${buildScript}"`, { stdio: 'inherit', cwd: PKG_ROOT });
|
|
577
|
-
|
|
611
|
+
await emitExperience('icons', {
|
|
612
|
+
subtitle: 'build',
|
|
613
|
+
message: 'Icon sprite rebuilt',
|
|
614
|
+
notifications: [{ tone: 'ok', message: 'Sprite rebuild succeeded' }],
|
|
615
|
+
});
|
|
578
616
|
} catch {
|
|
579
|
-
|
|
617
|
+
await emitExperience('icons', {
|
|
618
|
+
status: 'ERROR',
|
|
619
|
+
message: 'Failed to rebuild icon sprite',
|
|
620
|
+
exitCode: 1,
|
|
621
|
+
});
|
|
580
622
|
}
|
|
581
623
|
}
|
|
582
624
|
|
|
@@ -587,13 +629,15 @@ async function blueprintCmd() {
|
|
|
587
629
|
const strict = hasFlag('--strict');
|
|
588
630
|
const { listBlueprints, emitBlueprint, validateAllBlueprints } = await import('./blueprint.js');
|
|
589
631
|
if (sub === '--help' || sub === '-h') {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
632
|
+
await emitExperience('blueprint', {
|
|
633
|
+
status: 'PASS',
|
|
634
|
+
message: 'Usage: velinstyle blueprint [list|<name>] [--output|-o <file>] [--strict]',
|
|
635
|
+
items: [
|
|
636
|
+
{ label: 'list', value: 'List blueprint ids' },
|
|
637
|
+
{ label: 'list --strict', value: 'Validate all against CSS' },
|
|
638
|
+
{ label: '<name>', value: 'Emit HTML (stdout or -o)' },
|
|
639
|
+
],
|
|
640
|
+
});
|
|
597
641
|
return;
|
|
598
642
|
}
|
|
599
643
|
if (!sub || sub === 'list') {
|
|
@@ -601,30 +645,48 @@ async function blueprintCmd() {
|
|
|
601
645
|
const results = validateAllBlueprints();
|
|
602
646
|
const bad = results.filter((r) => !r.ok);
|
|
603
647
|
if (bad.length) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
648
|
+
await emitExperience('blueprint', {
|
|
649
|
+
subtitle: 'strict',
|
|
650
|
+
status: 'ERROR',
|
|
651
|
+
message: `${bad.length} blueprint(s) failed`,
|
|
652
|
+
table: {
|
|
653
|
+
columns: ['Id', 'Detail'],
|
|
654
|
+
rows: bad.map((r) => [r.id, r.missing?.join(', ') || r.error || '']),
|
|
655
|
+
},
|
|
656
|
+
exitCode: 1,
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
608
659
|
}
|
|
609
|
-
|
|
660
|
+
await emitExperience('blueprint', {
|
|
661
|
+
subtitle: 'strict',
|
|
662
|
+
message: `All ${results.length} blueprints pass --strict`,
|
|
663
|
+
notifications: [{ tone: 'ok', message: 'Strict validation passed' }],
|
|
664
|
+
});
|
|
610
665
|
return;
|
|
611
666
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
667
|
+
const list = listBlueprints();
|
|
668
|
+
await emitExperience('blueprint', {
|
|
669
|
+
message: `${list.length} blueprints`,
|
|
670
|
+
table: { columns: ['Blueprint'], rows: list.map((b) => [b]) },
|
|
671
|
+
suggestions: [{ text: 'velinstyle blueprint modal -o snippet.html --strict' }],
|
|
672
|
+
});
|
|
615
673
|
return;
|
|
616
674
|
}
|
|
617
675
|
const out = getArg('--output', '-o');
|
|
618
676
|
const r = emitBlueprint(sub, { output: out || null, strict });
|
|
619
677
|
if (!r.ok) {
|
|
620
|
-
|
|
621
|
-
|
|
678
|
+
await emitExperience('blueprint', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
679
|
+
return;
|
|
622
680
|
}
|
|
623
681
|
if (r.text) {
|
|
682
|
+
// Preserve raw HTML for piping when no -o
|
|
624
683
|
console.log(r.text);
|
|
625
|
-
|
|
626
|
-
console.log(C.green(`Wrote blueprint to ${r.path}`));
|
|
684
|
+
return;
|
|
627
685
|
}
|
|
686
|
+
await emitExperience('blueprint', {
|
|
687
|
+
message: `Wrote blueprint to ${r.path}`,
|
|
688
|
+
items: [{ label: 'Path', value: r.path }, { label: 'Id', value: sub }],
|
|
689
|
+
});
|
|
628
690
|
}
|
|
629
691
|
|
|
630
692
|
async function serveCmd() {
|
|
@@ -633,50 +695,237 @@ async function serveCmd() {
|
|
|
633
695
|
const { serveStatic } = await import('./serve.js');
|
|
634
696
|
const result = await serveStatic({ dir: resolve(raw), port });
|
|
635
697
|
if (!result.ok) {
|
|
636
|
-
|
|
637
|
-
|
|
698
|
+
await emitExperience('serve', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
699
|
+
return;
|
|
638
700
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
701
|
+
await emitExperience('serve', {
|
|
702
|
+
message: `Serving ${result.dir}`,
|
|
703
|
+
items: [
|
|
704
|
+
{ label: 'URL', value: result.url },
|
|
705
|
+
{ label: 'Dir', value: result.dir },
|
|
706
|
+
{ label: 'Port', value: String(port) },
|
|
707
|
+
],
|
|
708
|
+
notifications: [{ tone: 'info', message: 'Press Ctrl+C to stop' }],
|
|
709
|
+
});
|
|
642
710
|
}
|
|
643
711
|
|
|
644
712
|
async function doctorCmd() {
|
|
645
|
-
const
|
|
713
|
+
const exp = await ensureExperience();
|
|
646
714
|
const raw = args[1] && !args[1].startsWith('-') ? resolve(args[1]) : process.cwd();
|
|
647
715
|
let cwd = raw;
|
|
648
716
|
try {
|
|
649
717
|
if (existsSync(raw) && statSync(raw).isFile()) cwd = dirname(raw);
|
|
650
718
|
} catch { /* use raw */ }
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
719
|
+
|
|
720
|
+
const { runDoctor } = await import('./doctor.js');
|
|
721
|
+
const session = await exp.executeExperience({
|
|
722
|
+
commandId: 'doctor',
|
|
723
|
+
args,
|
|
724
|
+
helpers: { getArg, hasFlag },
|
|
725
|
+
version: CLI_VERSION,
|
|
726
|
+
cwd,
|
|
727
|
+
layout: 'doctor',
|
|
728
|
+
run: async (ctx) => {
|
|
729
|
+
ctx.emitProgress('Environment', 20);
|
|
730
|
+
const report = await runDoctor({ cwd, pkgRoot: PKG_ROOT });
|
|
731
|
+
ctx.emitProgress('Validation', 80);
|
|
732
|
+
const rows = [
|
|
733
|
+
...report.okLines.map((line) => ({ label: line.split(':')[0] || 'Check', status: 'PASS' })),
|
|
734
|
+
...report.issues.map((i) => ({
|
|
735
|
+
label: i.message,
|
|
736
|
+
status: i.severity === 'error' ? 'FAIL' : i.severity === 'warning' ? 'WARNING' : 'INFO',
|
|
737
|
+
})),
|
|
738
|
+
];
|
|
739
|
+
const status = report.ok ? (report.summary.warnings ? 'WARNING' : 'PASS') : 'ERROR';
|
|
740
|
+
return {
|
|
741
|
+
project: { cwd: report.cwd, name: 'Doctor' },
|
|
742
|
+
result: {
|
|
743
|
+
status,
|
|
744
|
+
message: report.ok
|
|
745
|
+
? `Pass (${report.summary.passed} checks, ${report.summary.warnings} warning(s))`
|
|
746
|
+
: `Fail (${report.summary.errors} error(s), ${report.summary.warnings} warning(s))`,
|
|
747
|
+
},
|
|
748
|
+
stats: report.summary,
|
|
749
|
+
diagnostics: report.issues,
|
|
750
|
+
widgets: [
|
|
751
|
+
{
|
|
752
|
+
id: 'hero',
|
|
753
|
+
props: { title: 'Doctor', subtitle: 'Environment & install health', eyebrow: 'Velin Experience' },
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
id: 'health',
|
|
757
|
+
props: {
|
|
758
|
+
title: 'Environment',
|
|
759
|
+
rows: [
|
|
760
|
+
{ label: 'Node', status: 'PASS' },
|
|
761
|
+
{ label: 'Platform', status: process.platform },
|
|
762
|
+
{ label: 'Config', status: existsSync(join(cwd, 'velinstyle.config.js')) ? 'PASS' : 'WARNING' },
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
id: 'table',
|
|
768
|
+
props: {
|
|
769
|
+
columns: ['Check', 'Status'],
|
|
770
|
+
rows: rows.slice(0, 24).map((r) => [r.label.slice(0, 40), r.status]),
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: 'footer',
|
|
775
|
+
props: { text: 'Velin Experience · doctor' },
|
|
776
|
+
},
|
|
777
|
+
],
|
|
778
|
+
suggestions: report.issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
779
|
+
};
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
process.exit(session.contract.result.status === 'ERROR' ? 1 : 0);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function dashboardCmd() {
|
|
786
|
+
const exp = await ensureExperience();
|
|
787
|
+
const cwd = process.cwd();
|
|
788
|
+
await exp.executeExperience({
|
|
789
|
+
commandId: 'dashboard',
|
|
790
|
+
args,
|
|
791
|
+
helpers: { getArg, hasFlag },
|
|
792
|
+
version: CLI_VERSION,
|
|
793
|
+
cwd,
|
|
794
|
+
layout: 'dashboard',
|
|
795
|
+
run: async (ctx) => {
|
|
796
|
+
ctx.emitProgress('Doctor', 20);
|
|
797
|
+
const { runDoctor } = await import('./doctor.js');
|
|
798
|
+
const doc = await runDoctor({ cwd, pkgRoot: PKG_ROOT });
|
|
799
|
+
|
|
800
|
+
let reviewScore = '—';
|
|
801
|
+
try {
|
|
802
|
+
ctx.emitProgress('Review', 45);
|
|
803
|
+
const { reviewHtml } = await import('./review.js');
|
|
804
|
+
const htmlCandidates = ['index.html', 'index.htm'].map((f) => join(cwd, f));
|
|
805
|
+
const hit = htmlCandidates.find((p) => existsSync(p));
|
|
806
|
+
if (hit) {
|
|
807
|
+
const html = readFileSync(hit, 'utf-8');
|
|
808
|
+
const report = reviewHtml(html, {});
|
|
809
|
+
reviewScore = report?.scores?.design != null
|
|
810
|
+
? String(report.scores.design)
|
|
811
|
+
: (report?.gate || '—');
|
|
812
|
+
}
|
|
813
|
+
} catch { /* optional */ }
|
|
814
|
+
|
|
815
|
+
let scanScore = '—';
|
|
816
|
+
try {
|
|
817
|
+
ctx.emitProgress('Scan', 70);
|
|
818
|
+
const { scan } = await import('./scanner.js');
|
|
819
|
+
const data = scan(cwd, { severity: 'warning', format: 'json', returnData: true });
|
|
820
|
+
const errors = (data.issues || []).filter((i) => i.severity === 'error').length;
|
|
821
|
+
const warnings = (data.issues || []).filter((i) => i.severity === 'warning').length;
|
|
822
|
+
scanScore = errors ? `FAIL (${errors})` : warnings ? `WARN (${warnings})` : 'PASS';
|
|
823
|
+
} catch { /* optional */ }
|
|
824
|
+
|
|
825
|
+
let productionHint = '—';
|
|
826
|
+
try {
|
|
827
|
+
const prodDir = join(cwd, 'dist', 'velin-production');
|
|
828
|
+
productionHint = existsSync(prodDir) ? 'built' : 'not built';
|
|
829
|
+
} catch { /* optional */ }
|
|
830
|
+
|
|
831
|
+
return {
|
|
832
|
+
project: { name: cwd.split(/[/\\]/).pop() || 'Project', cwd },
|
|
833
|
+
result: { status: doc.ok ? 'PASS' : 'WARNING', message: 'Project dashboard' },
|
|
834
|
+
widgets: [
|
|
835
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Project Dashboard', subtitle: cwd, eyebrow: 'Velin Experience' } },
|
|
836
|
+
{
|
|
837
|
+
id: 'score',
|
|
838
|
+
props: {
|
|
839
|
+
slot: 'cards',
|
|
840
|
+
title: 'Health',
|
|
841
|
+
scores: [
|
|
842
|
+
{ label: 'Doctor', value: doc.ok ? 'PASS' : 'FAIL' },
|
|
843
|
+
{ label: 'Production', value: productionHint },
|
|
844
|
+
{ label: 'Review', value: reviewScore },
|
|
845
|
+
{ label: 'Scan', value: scanScore },
|
|
846
|
+
{ label: 'Accessibility', value: 'AAA' },
|
|
847
|
+
{ label: 'CLI', value: CLI_VERSION },
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: 'notification',
|
|
853
|
+
props: { tone: 'info', message: 'Aggregated from doctor · review · scan · production folder' },
|
|
854
|
+
},
|
|
855
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle dashboard' } },
|
|
856
|
+
],
|
|
857
|
+
suggestions: [
|
|
858
|
+
{ text: 'velinstyle production . --explain' },
|
|
859
|
+
{ text: 'velinstyle review index.html' },
|
|
860
|
+
{ text: 'velinstyle check .' },
|
|
861
|
+
],
|
|
862
|
+
notifications: [
|
|
863
|
+
{ tone: 'ok', message: 'Velin Experience dashboard aggregates local contracts only (no cloud telemetry).' },
|
|
864
|
+
],
|
|
865
|
+
};
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function replayCmd() {
|
|
871
|
+
const exp = await ensureExperience();
|
|
872
|
+
const file = args[1];
|
|
873
|
+
if (!file || file.startsWith('-')) {
|
|
874
|
+
await emitExperience('replay', {
|
|
875
|
+
status: 'WARNING',
|
|
876
|
+
message: 'Usage: velinstyle replay <record.json> [--output standard|json|markdown]',
|
|
877
|
+
exitCode: 1,
|
|
878
|
+
});
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const record = exp.loadRecord(file);
|
|
882
|
+
const overrides = exp.parseExperienceFlags(args, { getArg, hasFlag });
|
|
883
|
+
const config = { ...(record.config || {}), ...overrides };
|
|
884
|
+
exp.printContract(record.contract, config);
|
|
654
885
|
}
|
|
655
886
|
|
|
656
887
|
async function createCmd() {
|
|
657
888
|
const kind = args[1];
|
|
658
889
|
if (!kind || kind === '--help' || kind === '-h') {
|
|
659
|
-
|
|
660
|
-
|
|
890
|
+
await emitExperience('create', {
|
|
891
|
+
status: kind ? 'PASS' : 'WARNING',
|
|
892
|
+
message: 'Usage: velinstyle create <landing|dashboard|docs|auth> [dir] [--theme earth] [--no-copy]',
|
|
893
|
+
exitCode: kind ? 0 : 1,
|
|
894
|
+
});
|
|
895
|
+
return;
|
|
661
896
|
}
|
|
662
897
|
const { CREATE_KINDS, createProject } = await import('./create.js');
|
|
663
898
|
if (!CREATE_KINDS.includes(kind)) {
|
|
664
|
-
|
|
665
|
-
|
|
899
|
+
await emitExperience('create', {
|
|
900
|
+
status: 'ERROR',
|
|
901
|
+
message: `Unknown kind "${kind}"`,
|
|
902
|
+
suggestions: [{ text: `Use: ${CREATE_KINDS.join('|')}` }],
|
|
903
|
+
exitCode: 1,
|
|
904
|
+
});
|
|
905
|
+
return;
|
|
666
906
|
}
|
|
667
907
|
const dir = args[2] && !args[2].startsWith('-') ? args[2] : kind;
|
|
668
908
|
const theme = getArg('--theme') || undefined;
|
|
669
909
|
const copyAssets = !hasFlag('--no-copy');
|
|
670
910
|
const r = createProject({ kind, dir: resolve(dir), pkgRoot: PKG_ROOT, theme, copyAssets });
|
|
671
911
|
if (!r.ok) {
|
|
672
|
-
|
|
673
|
-
|
|
912
|
+
await emitExperience('create', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
913
|
+
return;
|
|
674
914
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
915
|
+
await emitExperience('create', {
|
|
916
|
+
layout: 'wizard',
|
|
917
|
+
message: `Created ${kind}`,
|
|
918
|
+
items: [
|
|
919
|
+
{ label: 'Dir', value: r.dir },
|
|
920
|
+
{ label: 'Index', value: r.indexPath },
|
|
921
|
+
...(r.vendorRel ? [{ label: 'Vendor', value: r.vendorRel }] : []),
|
|
922
|
+
],
|
|
923
|
+
suggestions: [
|
|
924
|
+
{ text: `velinstyle serve ${dir}` },
|
|
925
|
+
{ text: `velinstyle check ${dir}` },
|
|
926
|
+
],
|
|
927
|
+
notifications: [{ tone: 'ok', message: `Created ${kind} at ${r.dir}` }],
|
|
928
|
+
});
|
|
680
929
|
}
|
|
681
930
|
|
|
682
931
|
async function tokensBuildCmd() {
|
|
@@ -685,12 +934,20 @@ async function tokensBuildCmd() {
|
|
|
685
934
|
const { buildTokensFromJson } = await import('./tokens-build.js');
|
|
686
935
|
const css = buildTokensFromJson(inputPath);
|
|
687
936
|
if (!css) {
|
|
688
|
-
|
|
937
|
+
await emitExperience('tokens', {
|
|
938
|
+
status: 'ERROR',
|
|
939
|
+
message: `Could not read or parse: ${inputPath}`,
|
|
940
|
+
exitCode: 1,
|
|
941
|
+
});
|
|
689
942
|
return;
|
|
690
943
|
}
|
|
691
944
|
if (outPath) {
|
|
692
945
|
writeFileSync(resolve(outPath), css, 'utf-8');
|
|
693
|
-
|
|
946
|
+
await emitExperience('tokens', {
|
|
947
|
+
subtitle: 'build',
|
|
948
|
+
message: `Wrote ${resolve(outPath)}`,
|
|
949
|
+
items: [{ label: 'Input', value: inputPath }, { label: 'Output', value: resolve(outPath) }],
|
|
950
|
+
});
|
|
694
951
|
} else {
|
|
695
952
|
console.log(css);
|
|
696
953
|
}
|
|
@@ -701,12 +958,20 @@ async function tokensValidateCmd() {
|
|
|
701
958
|
const { validateTokensJson } = await import('./tokens-validate.js');
|
|
702
959
|
const result = validateTokensJson(resolve(input));
|
|
703
960
|
if (result.ok) {
|
|
704
|
-
|
|
705
|
-
|
|
961
|
+
await emitExperience('tokens', {
|
|
962
|
+
subtitle: 'validate',
|
|
963
|
+
message: `Valid: ${resolve(input)}`,
|
|
964
|
+
notifications: [{ tone: 'ok', message: 'Tokens JSON valid' }],
|
|
965
|
+
});
|
|
966
|
+
return;
|
|
706
967
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
968
|
+
await emitExperience('tokens', {
|
|
969
|
+
subtitle: 'validate',
|
|
970
|
+
status: 'ERROR',
|
|
971
|
+
message: `Invalid: ${resolve(input)}`,
|
|
972
|
+
table: { columns: ['Error'], rows: (result.errors || []).map((e) => [e]) },
|
|
973
|
+
exitCode: 1,
|
|
974
|
+
});
|
|
710
975
|
}
|
|
711
976
|
|
|
712
977
|
async function tokensCmd() {
|
|
@@ -716,7 +981,11 @@ async function tokensCmd() {
|
|
|
716
981
|
} else if (sub === 'validate') {
|
|
717
982
|
await tokensValidateCmd();
|
|
718
983
|
} else {
|
|
719
|
-
|
|
984
|
+
await emitExperience('tokens', {
|
|
985
|
+
status: 'WARNING',
|
|
986
|
+
message: 'Usage: velinstyle tokens build|validate [--input tokens.json] [--output, -o <file>]',
|
|
987
|
+
exitCode: 1,
|
|
988
|
+
});
|
|
720
989
|
}
|
|
721
990
|
}
|
|
722
991
|
|
|
@@ -737,13 +1006,21 @@ async function perfCmd() {
|
|
|
737
1006
|
return;
|
|
738
1007
|
}
|
|
739
1008
|
if (result.changes.length === 0) {
|
|
740
|
-
|
|
1009
|
+
await emitExperience('perf', {
|
|
1010
|
+
subtitle: 'fix',
|
|
1011
|
+
message: 'No safe performance fixes to apply.',
|
|
1012
|
+
notifications: [{ tone: 'ok', message: 'Nothing to change' }],
|
|
1013
|
+
});
|
|
741
1014
|
return;
|
|
742
1015
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1016
|
+
await emitExperience('perf', {
|
|
1017
|
+
subtitle: 'fix',
|
|
1018
|
+
message: `${dryRun ? '[dry-run] ' : ''}${result.changes.length} file(s)`,
|
|
1019
|
+
table: {
|
|
1020
|
+
columns: ['File', 'Changes'],
|
|
1021
|
+
rows: result.changes.map((c) => [c.file, (c.changes || []).join('; ').slice(0, 60)]),
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
747
1024
|
return;
|
|
748
1025
|
}
|
|
749
1026
|
|
|
@@ -752,7 +1029,49 @@ async function perfCmd() {
|
|
|
752
1029
|
console.log(JSON.stringify({ files, issues }, null, 2));
|
|
753
1030
|
return;
|
|
754
1031
|
}
|
|
755
|
-
|
|
1032
|
+
|
|
1033
|
+
const exp = await ensureExperience();
|
|
1034
|
+
const errors = issues.filter((x) => x.severity === 'error').length;
|
|
1035
|
+
const warnings = issues.filter((x) => x.severity === 'warning').length;
|
|
1036
|
+
await exp.executeExperience({
|
|
1037
|
+
commandId: 'perf',
|
|
1038
|
+
args,
|
|
1039
|
+
helpers: { getArg, hasFlag },
|
|
1040
|
+
version: CLI_VERSION,
|
|
1041
|
+
layout: 'audit',
|
|
1042
|
+
run: async () => ({
|
|
1043
|
+
project: { cwd: targetPath, name: 'Perf' },
|
|
1044
|
+
result: {
|
|
1045
|
+
status: errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS',
|
|
1046
|
+
message: `${issues.length} finding(s) across ${files.length} file(s)`,
|
|
1047
|
+
},
|
|
1048
|
+
diagnostics: issues,
|
|
1049
|
+
widgets: [
|
|
1050
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Perf audit', subtitle: formatTextReport(issues, files).split('\n')[0] || 'Performance', eyebrow: 'Velin Experience' } },
|
|
1051
|
+
{
|
|
1052
|
+
id: 'stats',
|
|
1053
|
+
props: {
|
|
1054
|
+
slot: 'counts',
|
|
1055
|
+
items: [
|
|
1056
|
+
{ label: 'Files', value: files.length },
|
|
1057
|
+
{ label: 'Errors', value: errors },
|
|
1058
|
+
{ label: 'Warnings', value: warnings },
|
|
1059
|
+
],
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
id: 'table',
|
|
1064
|
+
props: {
|
|
1065
|
+
slot: 'findings',
|
|
1066
|
+
columns: ['Sev', 'Id', 'Message'],
|
|
1067
|
+
rows: issues.slice(0, 20).map((i) => [i.severity, i.id || '—', String(i.message || '').slice(0, 40)]),
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle perf audit' } },
|
|
1071
|
+
],
|
|
1072
|
+
suggestions: issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1073
|
+
}),
|
|
1074
|
+
});
|
|
756
1075
|
process.exit(issues.some((x) => x.severity === 'error') ? 1 : 0);
|
|
757
1076
|
}
|
|
758
1077
|
|
|
@@ -764,8 +1083,8 @@ async function prefixCmd() {
|
|
|
764
1083
|
const st = statSync(targetPath);
|
|
765
1084
|
if (st.isFile()) migrationRoot = dirname(targetPath);
|
|
766
1085
|
} catch {
|
|
767
|
-
|
|
768
|
-
|
|
1086
|
+
await emitExperience('prefix', { status: 'ERROR', message: `Not found: ${targetPath}`, exitCode: 1 });
|
|
1087
|
+
return;
|
|
769
1088
|
}
|
|
770
1089
|
|
|
771
1090
|
const mapArg = getArg('--map');
|
|
@@ -773,32 +1092,175 @@ async function prefixCmd() {
|
|
|
773
1092
|
const bootstrapDisplay = hasFlag('--bootstrap-display');
|
|
774
1093
|
|
|
775
1094
|
try {
|
|
776
|
-
const { buildExplicitPrefixMap,
|
|
1095
|
+
const { buildExplicitPrefixMap, runPrefixCommand } = await import('./prefix-classes.js');
|
|
777
1096
|
const explicitMap = buildExplicitPrefixMap(migrationRoot, mapArg ? resolve(mapArg) : null);
|
|
778
|
-
|
|
1097
|
+
const { filesTouched, replacements, paths } = runPrefixCommand(targetPath, {
|
|
779
1098
|
write,
|
|
780
1099
|
bootstrapAliases: bootstrapDisplay,
|
|
781
1100
|
pkgRoot: PKG_ROOT,
|
|
782
1101
|
explicitMap,
|
|
783
1102
|
});
|
|
1103
|
+
if (filesTouched === 0) {
|
|
1104
|
+
await emitExperience('prefix', {
|
|
1105
|
+
message: 'No class attributes needed prefixing (or no matching files)',
|
|
1106
|
+
notifications: explicitMap?.size
|
|
1107
|
+
? [{ tone: 'info', message: `Explicit map: ${explicitMap.size} entries` }]
|
|
1108
|
+
: [],
|
|
1109
|
+
});
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
await emitExperience('prefix', {
|
|
1113
|
+
message: `${write ? 'Updated' : 'Would update'} ${filesTouched} file(s), ${replacements} class token(s)`,
|
|
1114
|
+
table: {
|
|
1115
|
+
columns: ['File'],
|
|
1116
|
+
rows: paths.slice(0, 40).map((p) => [p]),
|
|
1117
|
+
},
|
|
1118
|
+
suggestions: write ? [] : [{ text: 'Re-run with --write to apply changes' }],
|
|
1119
|
+
notifications: [
|
|
1120
|
+
...(explicitMap?.size ? [{ tone: 'info', message: `Explicit map: ${explicitMap.size} entries` }] : []),
|
|
1121
|
+
...(paths.length > 40 ? [{ tone: 'info', message: `… and ${paths.length - 40} more files` }] : []),
|
|
1122
|
+
],
|
|
1123
|
+
});
|
|
784
1124
|
} catch (e) {
|
|
785
|
-
|
|
786
|
-
|
|
1125
|
+
await emitExperience('prefix', {
|
|
1126
|
+
status: 'ERROR',
|
|
1127
|
+
message: /** @type {Error} */ (e).message,
|
|
1128
|
+
exitCode: 1,
|
|
1129
|
+
});
|
|
787
1130
|
}
|
|
788
1131
|
}
|
|
789
1132
|
|
|
1133
|
+
async function atelierCmd() {
|
|
1134
|
+
const sub = args[1];
|
|
1135
|
+
const {
|
|
1136
|
+
listAtelierEntries,
|
|
1137
|
+
pullAtelier,
|
|
1138
|
+
} = await import('./atelier.js');
|
|
1139
|
+
|
|
1140
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
1141
|
+
await emitExperience('atelier', {
|
|
1142
|
+
message: 'Usage: velinstyle atelier list | <num|id> [-o dir] [--format html|blade|vue|react]',
|
|
1143
|
+
suggestions: [
|
|
1144
|
+
{ text: 'velinstyle atelier list' },
|
|
1145
|
+
{ text: 'velinstyle atelier 36 -o ./velin-atelier/36-calendar' },
|
|
1146
|
+
],
|
|
1147
|
+
notifications: [{
|
|
1148
|
+
tone: 'info',
|
|
1149
|
+
message: 'Limitation: blade|vue|react = wrappers; Studio/native rewrites planned.',
|
|
1150
|
+
}],
|
|
1151
|
+
});
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (sub === 'list') {
|
|
1156
|
+
const r = listAtelierEntries();
|
|
1157
|
+
if (!r.ok) {
|
|
1158
|
+
await emitExperience('atelier', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
await emitExperience('atelier', {
|
|
1162
|
+
subtitle: 'list',
|
|
1163
|
+
message: `Atelier Library · ${r.items.length} templates`,
|
|
1164
|
+
table: {
|
|
1165
|
+
columns: ['#', 'Id', 'Title'],
|
|
1166
|
+
rows: r.items.map((it) => [it.num.padStart(3), it.id, it.title || '']),
|
|
1167
|
+
},
|
|
1168
|
+
suggestions: [{ text: 'velinstyle atelier 36 -o ./velin-atelier/36-calendar' }],
|
|
1169
|
+
});
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const out = getArg('--output', '-o');
|
|
1174
|
+
const format = getArg('--format') || 'html';
|
|
1175
|
+
const from = getArg('--from') || process.env.VELINSTYLE_ATELIER_ROOT || null;
|
|
1176
|
+
const baseUrl = getArg('--base-url');
|
|
1177
|
+
const rewriteVendor = getArg('--rewrite-vendor');
|
|
1178
|
+
|
|
1179
|
+
const r = await pullAtelier(sub, {
|
|
1180
|
+
output: out || undefined,
|
|
1181
|
+
format,
|
|
1182
|
+
from: from || undefined,
|
|
1183
|
+
baseUrl: baseUrl || undefined,
|
|
1184
|
+
rewriteVendor: rewriteVendor || undefined,
|
|
1185
|
+
});
|
|
1186
|
+
if (!r.ok) {
|
|
1187
|
+
await emitExperience('atelier', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
await emitExperience('atelier', {
|
|
1191
|
+
message: `Pulled ${r.entry.id} → ${r.path}`,
|
|
1192
|
+
items: [
|
|
1193
|
+
{ label: 'Format', value: r.format },
|
|
1194
|
+
{ label: 'Source', value: r.source },
|
|
1195
|
+
{ label: 'Path', value: r.path },
|
|
1196
|
+
],
|
|
1197
|
+
notifications: [{
|
|
1198
|
+
tone: 'info',
|
|
1199
|
+
message: 'Limitation: blade/vue/react = wrappers; Studio/native rewrites planned.',
|
|
1200
|
+
}],
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
|
|
790
1204
|
async function scaffoldCmd() {
|
|
791
1205
|
const sub = args[1];
|
|
792
1206
|
const { scaffoldFromPrompt, listIntents } = await import('./scaffold.js');
|
|
793
1207
|
if (sub === 'list-intents') {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1208
|
+
const intents = listIntents();
|
|
1209
|
+
await emitExperience('scaffold', {
|
|
1210
|
+
subtitle: 'list-intents',
|
|
1211
|
+
message: `${intents.length} intents`,
|
|
1212
|
+
table: {
|
|
1213
|
+
columns: ['Id', 'Blueprints', 'Keywords'],
|
|
1214
|
+
rows: intents.map((i) => [
|
|
1215
|
+
i.id,
|
|
1216
|
+
i.blueprints.join(' + '),
|
|
1217
|
+
i.keywords.slice(0, 5).join(', '),
|
|
1218
|
+
]),
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const atelierList = getArg('--atelier');
|
|
1225
|
+
if (atelierList) {
|
|
1226
|
+
const { composeAtelierPage } = await import('./atelier.js');
|
|
1227
|
+
const out = getArg('--output', '-o') || resolve('velin-atelier/compose.html');
|
|
1228
|
+
const from = getArg('--from') || process.env.VELINSTYLE_ATELIER_ROOT || null;
|
|
1229
|
+
const baseUrl = getArg('--base-url');
|
|
1230
|
+
const r = await composeAtelierPage(atelierList, {
|
|
1231
|
+
output: out,
|
|
1232
|
+
from: from || undefined,
|
|
1233
|
+
baseUrl: baseUrl || undefined,
|
|
1234
|
+
});
|
|
1235
|
+
if (!r.ok) {
|
|
1236
|
+
await emitExperience('scaffold', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (hasFlag('--json')) {
|
|
1240
|
+
console.log(JSON.stringify({
|
|
1241
|
+
ok: true,
|
|
1242
|
+
mode: r.mode,
|
|
1243
|
+
path: r.path,
|
|
1244
|
+
entries: r.entries,
|
|
1245
|
+
planSections: r.planSections,
|
|
1246
|
+
}, null, 2));
|
|
1247
|
+
return;
|
|
798
1248
|
}
|
|
799
|
-
|
|
1249
|
+
await emitExperience('scaffold', {
|
|
1250
|
+
subtitle: 'atelier compose',
|
|
1251
|
+
message: `Wrote Atelier compose (beta) → ${r.path}`,
|
|
1252
|
+
items: [
|
|
1253
|
+
{ label: 'Ids', value: r.entries.map((e) => e.id).join(', ') },
|
|
1254
|
+
{ label: 'Mode', value: r.mode || 'compose' },
|
|
1255
|
+
],
|
|
1256
|
+
notifications: [{
|
|
1257
|
+
tone: 'info',
|
|
1258
|
+
message: 'Beta: Library compose — not Velin Studio. Wrappers ≠ native rewrites.',
|
|
1259
|
+
}],
|
|
1260
|
+
});
|
|
800
1261
|
return;
|
|
801
1262
|
}
|
|
1263
|
+
|
|
802
1264
|
const promptParts = [];
|
|
803
1265
|
let i = 1;
|
|
804
1266
|
while (i < args.length) {
|
|
@@ -809,13 +1271,21 @@ async function scaffoldCmd() {
|
|
|
809
1271
|
}
|
|
810
1272
|
const prompt = promptParts.join(' ').trim() || sub;
|
|
811
1273
|
if (!prompt || prompt === 'list-intents') {
|
|
812
|
-
|
|
1274
|
+
await emitExperience('scaffold', {
|
|
1275
|
+
status: 'WARNING',
|
|
1276
|
+
message: 'Usage: velinstyle scaffold "<description>" [-o file.html] [--json]',
|
|
1277
|
+
suggestions: [
|
|
1278
|
+
{ text: 'velinstyle scaffold --atelier 04,07 -o page.html' },
|
|
1279
|
+
{ text: 'velinstyle scaffold list-intents' },
|
|
1280
|
+
],
|
|
1281
|
+
exitCode: 1,
|
|
1282
|
+
});
|
|
813
1283
|
return;
|
|
814
1284
|
}
|
|
815
1285
|
const r = scaffoldFromPrompt(prompt);
|
|
816
1286
|
if (!r.ok) {
|
|
817
|
-
|
|
818
|
-
|
|
1287
|
+
await emitExperience('scaffold', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1288
|
+
return;
|
|
819
1289
|
}
|
|
820
1290
|
const out = getArg('--output', '-o');
|
|
821
1291
|
const asJson = hasFlag('--json');
|
|
@@ -823,19 +1293,49 @@ async function scaffoldCmd() {
|
|
|
823
1293
|
console.log(JSON.stringify(r, null, 2));
|
|
824
1294
|
} else if (out) {
|
|
825
1295
|
writeFileSync(resolve(out), r.html, 'utf-8');
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1296
|
+
await emitExperience('scaffold', {
|
|
1297
|
+
message: `Wrote ${resolve(out)}`,
|
|
1298
|
+
items: [
|
|
1299
|
+
{ label: 'Intent', value: r.intent },
|
|
1300
|
+
{ label: 'Confidence', value: String(r.confidence) },
|
|
1301
|
+
...(r.mode ? [{ label: 'Mode', value: r.mode }] : []),
|
|
1302
|
+
...(r.review?.gate ? [{ label: 'Review gate', value: `${r.review.gate} (${r.review.promptScore})` }] : []),
|
|
1303
|
+
],
|
|
1304
|
+
suggestions: r.responsiveHints?.length
|
|
1305
|
+
? [{ text: `velinstyle layout suggest ${out}` }]
|
|
1306
|
+
: [],
|
|
1307
|
+
notifications: r.responsiveHints?.length
|
|
1308
|
+
? [{ tone: 'warning', message: `${r.responsiveHints.length} layout hint(s)` }]
|
|
1309
|
+
: [],
|
|
1310
|
+
});
|
|
833
1311
|
} else {
|
|
834
1312
|
console.log(r.html);
|
|
835
1313
|
}
|
|
836
1314
|
}
|
|
837
1315
|
|
|
838
1316
|
async function planCmd() {
|
|
1317
|
+
const atelierList = getArg('--atelier');
|
|
1318
|
+
if (atelierList) {
|
|
1319
|
+
const { planFromAtelierList } = await import('./atelier.js');
|
|
1320
|
+
const payload = planFromAtelierList(atelierList, { prompt: args.slice(1).filter((a) => !a.startsWith('-')).join(' ') });
|
|
1321
|
+
if (!payload.ok) {
|
|
1322
|
+
await emitExperience('plan', { status: 'ERROR', message: payload.error, exitCode: 1 });
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const out = getArg('--output', '-o');
|
|
1326
|
+
if (out) {
|
|
1327
|
+
writeFileSync(resolve(out), JSON.stringify(payload, null, 2), 'utf-8');
|
|
1328
|
+
await emitExperience('plan', {
|
|
1329
|
+
subtitle: 'atelier',
|
|
1330
|
+
message: `Wrote Atelier plan (beta) ${resolve(out)}`,
|
|
1331
|
+
items: [{ label: 'Sections', value: String(payload.plan.sections.length) }],
|
|
1332
|
+
});
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
839
1339
|
const promptParts = [];
|
|
840
1340
|
let i = 1;
|
|
841
1341
|
while (i < args.length) {
|
|
@@ -846,7 +1346,12 @@ async function planCmd() {
|
|
|
846
1346
|
}
|
|
847
1347
|
const prompt = promptParts.join(' ').trim();
|
|
848
1348
|
if (!prompt) {
|
|
849
|
-
|
|
1349
|
+
await emitExperience('plan', {
|
|
1350
|
+
status: 'WARNING',
|
|
1351
|
+
message: 'Usage: velinstyle plan "<description>" [--json] [-o plan.json]',
|
|
1352
|
+
suggestions: [{ text: 'velinstyle plan --atelier 04,07 [-o plan.json]' }],
|
|
1353
|
+
exitCode: 1,
|
|
1354
|
+
});
|
|
850
1355
|
return;
|
|
851
1356
|
}
|
|
852
1357
|
const { analyzePrompt, buildPlan } = await import('./prompt-engine.js');
|
|
@@ -856,7 +1361,13 @@ async function planCmd() {
|
|
|
856
1361
|
const out = getArg('--output', '-o');
|
|
857
1362
|
if (out) {
|
|
858
1363
|
writeFileSync(resolve(out), JSON.stringify(payload, null, 2), 'utf-8');
|
|
859
|
-
|
|
1364
|
+
await emitExperience('plan', {
|
|
1365
|
+
message: `Wrote plan ${resolve(out)}`,
|
|
1366
|
+
items: [
|
|
1367
|
+
{ label: 'Page', value: plan.page?.id || '—' },
|
|
1368
|
+
{ label: 'Sections', value: String(plan.sections.length) },
|
|
1369
|
+
],
|
|
1370
|
+
});
|
|
860
1371
|
return;
|
|
861
1372
|
}
|
|
862
1373
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -885,21 +1396,51 @@ async function reviewCmd() {
|
|
|
885
1396
|
const report = reviewHtml(html, { plan, prompt, profile: profile || undefined });
|
|
886
1397
|
if (hasFlag('--json')) {
|
|
887
1398
|
console.log(JSON.stringify(report, null, 2));
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
console.log(C.dim(` design ${report.scores.design} · a11y ${report.scores.accessibility} · seo ${report.scores.seo} · perf ${report.scores.performance} · conversion ${report.scores.conversion}`));
|
|
891
|
-
if (report.issues.length) {
|
|
892
|
-
console.log(`\n ${C.bold('Issues:')}`);
|
|
893
|
-
for (const issue of report.issues) {
|
|
894
|
-
const color = issue.severity === 'error' ? C.red : C.yellow;
|
|
895
|
-
console.log(color(` [${issue.severity}] ${issue.code}: ${issue.message}`));
|
|
896
|
-
console.log(C.dim(` fix: ${issue.fix}`));
|
|
897
|
-
}
|
|
898
|
-
} else {
|
|
899
|
-
console.log(C.green('\n No issues.'));
|
|
900
|
-
}
|
|
901
|
-
console.log('');
|
|
1399
|
+
process.exit(report.gate === 'fail' ? 1 : 0);
|
|
1400
|
+
return;
|
|
902
1401
|
}
|
|
1402
|
+
const exp = await ensureExperience();
|
|
1403
|
+
await exp.executeExperience({
|
|
1404
|
+
commandId: 'review',
|
|
1405
|
+
args,
|
|
1406
|
+
helpers: { getArg, hasFlag },
|
|
1407
|
+
version: CLI_VERSION,
|
|
1408
|
+
layout: 'review',
|
|
1409
|
+
run: async () => ({
|
|
1410
|
+
project: { file: full },
|
|
1411
|
+
result: {
|
|
1412
|
+
status: report.gate === 'fail' ? 'ERROR' : report.issues.length ? 'WARNING' : 'PASS',
|
|
1413
|
+
message: `gate ${report.gate} · profile ${report.profile}`,
|
|
1414
|
+
},
|
|
1415
|
+
stats: report.scores,
|
|
1416
|
+
diagnostics: report.issues,
|
|
1417
|
+
widgets: [
|
|
1418
|
+
{ id: 'hero', props: { title: 'Review', subtitle: full, eyebrow: 'Velin Experience' } },
|
|
1419
|
+
{
|
|
1420
|
+
id: 'score',
|
|
1421
|
+
props: {
|
|
1422
|
+
title: 'Overall',
|
|
1423
|
+
scores: [
|
|
1424
|
+
{ label: 'Design', value: report.scores.design },
|
|
1425
|
+
{ label: 'Accessibility', value: report.scores.accessibility },
|
|
1426
|
+
{ label: 'SEO', value: report.scores.seo },
|
|
1427
|
+
{ label: 'Performance', value: report.scores.performance },
|
|
1428
|
+
{ label: 'Conversion', value: report.scores.conversion },
|
|
1429
|
+
],
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: 'table',
|
|
1434
|
+
props: {
|
|
1435
|
+
columns: ['Severity', 'Code', 'Message'],
|
|
1436
|
+
rows: (report.issues || []).slice(0, 20).map((i) => [i.severity, i.code, i.message.slice(0, 48)]),
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
{ id: 'footer', props: { text: 'velinstyle review' } },
|
|
1440
|
+
],
|
|
1441
|
+
suggestions: (report.issues || []).filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1442
|
+
}),
|
|
1443
|
+
});
|
|
903
1444
|
process.exit(report.gate === 'fail' ? 1 : 0);
|
|
904
1445
|
}
|
|
905
1446
|
|
|
@@ -916,21 +1457,29 @@ async function layoutCmd() {
|
|
|
916
1457
|
if (sub === 'fix') {
|
|
917
1458
|
const result = fixPath(targetPath, { write, dryRun });
|
|
918
1459
|
if (!result.ok) {
|
|
919
|
-
|
|
920
|
-
|
|
1460
|
+
await emitExperience('layout', { status: 'ERROR', message: result.error || 'Fix failed', exitCode: 1 });
|
|
1461
|
+
return;
|
|
921
1462
|
}
|
|
922
1463
|
if (asJson) {
|
|
923
1464
|
console.log(JSON.stringify(result, null, 2));
|
|
924
1465
|
return;
|
|
925
1466
|
}
|
|
926
1467
|
if (result.changes.length === 0) {
|
|
927
|
-
|
|
1468
|
+
await emitExperience('layout', {
|
|
1469
|
+
subtitle: 'fix',
|
|
1470
|
+
message: 'No safe fixes to apply.',
|
|
1471
|
+
notifications: [{ tone: 'ok', message: 'Nothing to change' }],
|
|
1472
|
+
});
|
|
928
1473
|
return;
|
|
929
1474
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1475
|
+
await emitExperience('layout', {
|
|
1476
|
+
subtitle: 'fix',
|
|
1477
|
+
message: `${dryRun ? '[dry-run] ' : ''}${result.changes.length} file(s)`,
|
|
1478
|
+
table: {
|
|
1479
|
+
columns: ['File', 'Changes'],
|
|
1480
|
+
rows: result.changes.map((c) => [c.file, (c.changes || []).join('; ').slice(0, 60)]),
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
934
1483
|
return;
|
|
935
1484
|
}
|
|
936
1485
|
|
|
@@ -942,17 +1491,48 @@ async function layoutCmd() {
|
|
|
942
1491
|
return;
|
|
943
1492
|
}
|
|
944
1493
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1494
|
+
const exp = await ensureExperience();
|
|
1495
|
+
const errors = issues.filter((x) => x.severity === 'error').length;
|
|
1496
|
+
const warnings = issues.filter((x) => x.severity === 'warning').length;
|
|
1497
|
+
await exp.executeExperience({
|
|
1498
|
+
commandId: 'layout',
|
|
1499
|
+
args,
|
|
1500
|
+
helpers: { getArg, hasFlag },
|
|
1501
|
+
version: CLI_VERSION,
|
|
1502
|
+
layout: 'audit',
|
|
1503
|
+
run: async () => ({
|
|
1504
|
+
project: { cwd: targetPath, name: 'Layout' },
|
|
1505
|
+
result: {
|
|
1506
|
+
status: errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS',
|
|
1507
|
+
message: `${outIssues.length} finding(s) · ${sub}`,
|
|
1508
|
+
},
|
|
1509
|
+
diagnostics: outIssues,
|
|
1510
|
+
widgets: [
|
|
1511
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Layout audit', subtitle: `Subcommand: ${sub}`, eyebrow: 'Velin Experience' } },
|
|
1512
|
+
{
|
|
1513
|
+
id: 'stats',
|
|
1514
|
+
props: {
|
|
1515
|
+
slot: 'counts',
|
|
1516
|
+
items: [
|
|
1517
|
+
{ label: 'Files', value: files.length },
|
|
1518
|
+
{ label: 'Findings', value: outIssues.length },
|
|
1519
|
+
{ label: 'Errors', value: errors },
|
|
1520
|
+
],
|
|
1521
|
+
},
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
id: 'table',
|
|
1525
|
+
props: {
|
|
1526
|
+
slot: 'findings',
|
|
1527
|
+
columns: ['Sev', 'Id', 'Message'],
|
|
1528
|
+
rows: outIssues.slice(0, 20).map((i) => [i.severity, i.id || '—', String(i.message || i.fix || '').slice(0, 40)]),
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
{ id: 'footer', props: { slot: 'footer', text: `velinstyle layout ${sub}` } },
|
|
1532
|
+
],
|
|
1533
|
+
suggestions: outIssues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1534
|
+
}),
|
|
1535
|
+
});
|
|
956
1536
|
process.exit(issues.some((x) => x.severity === 'error') ? 1 : 0);
|
|
957
1537
|
}
|
|
958
1538
|
|
|
@@ -964,18 +1544,71 @@ async function docsCmd() {
|
|
|
964
1544
|
const { generateDocs } = await import('./docs-generate.js');
|
|
965
1545
|
const result = await generateDocs({ scope, outDir: resolve(out), searchIndex: true });
|
|
966
1546
|
if (!result.ok) {
|
|
967
|
-
|
|
968
|
-
|
|
1547
|
+
await emitExperience('docs', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
1548
|
+
return;
|
|
969
1549
|
}
|
|
970
|
-
|
|
1550
|
+
await emitExperience('docs', {
|
|
1551
|
+
subtitle: 'generate',
|
|
1552
|
+
message: `Generated ${result.written} file(s)`,
|
|
1553
|
+
items: [
|
|
1554
|
+
{ label: 'Scope', value: scope },
|
|
1555
|
+
{ label: 'Out', value: resolve(out) },
|
|
1556
|
+
],
|
|
1557
|
+
});
|
|
971
1558
|
return;
|
|
972
1559
|
}
|
|
973
|
-
|
|
1560
|
+
await emitExperience('docs', {
|
|
1561
|
+
status: 'WARNING',
|
|
1562
|
+
message: 'Usage: velinstyle docs generate [--scope all|components|tokens|…] [--out <dir>]',
|
|
1563
|
+
exitCode: 1,
|
|
1564
|
+
});
|
|
974
1565
|
}
|
|
975
1566
|
|
|
976
1567
|
async function metaCmd() {
|
|
977
|
-
|
|
978
|
-
|
|
1568
|
+
if (args[1] === '--help' || args[1] === '-h') {
|
|
1569
|
+
await emitExperience('meta', {
|
|
1570
|
+
message: 'Usage: velinstyle meta [--out file] [--llms-out file] [--base-url url] [--no-llms]',
|
|
1571
|
+
suggestions: [
|
|
1572
|
+
{ text: 'velinstyle meta' },
|
|
1573
|
+
{ text: 'velinstyle meta page index.html [--write]' },
|
|
1574
|
+
],
|
|
1575
|
+
});
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
const { metaPage, buildMeta } = await import('./meta.js');
|
|
1579
|
+
const pageIdx = args.indexOf('page');
|
|
1580
|
+
if (pageIdx !== -1 && args[pageIdx + 1]) {
|
|
1581
|
+
const write = hasFlag('--write');
|
|
1582
|
+
const result = metaPage(args[pageIdx + 1], { write });
|
|
1583
|
+
if (!result.ok) {
|
|
1584
|
+
await emitExperience('meta', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (write) {
|
|
1588
|
+
await emitExperience('meta', {
|
|
1589
|
+
subtitle: 'page',
|
|
1590
|
+
message: `Updated velin-meta in ${args[pageIdx + 1]}`,
|
|
1591
|
+
});
|
|
1592
|
+
} else {
|
|
1593
|
+
console.log(result.script);
|
|
1594
|
+
}
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const result = await buildMeta({
|
|
1599
|
+
outFile: getArg('--out') ? resolve(getArg('--out')) : undefined,
|
|
1600
|
+
llmsFile: getArg('--llms-out') ? resolve(getArg('--llms-out')) : undefined,
|
|
1601
|
+
baseUrl: getArg('--base-url') || undefined,
|
|
1602
|
+
llms: !hasFlag('--no-llms'),
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
await emitExperience('meta', {
|
|
1606
|
+
message: `Wrote ${result.count} file(s)`,
|
|
1607
|
+
table: {
|
|
1608
|
+
columns: ['File'],
|
|
1609
|
+
rows: (result.written || []).map((p) => [p]),
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
979
1612
|
}
|
|
980
1613
|
|
|
981
1614
|
async function searchCmd() {
|
|
@@ -988,42 +1621,280 @@ async function searchCmd() {
|
|
|
988
1621
|
outFile: resolve(out),
|
|
989
1622
|
extraHtmlDirs: extra ? extra.split(',').map((p) => resolve(p.trim())) : [],
|
|
990
1623
|
});
|
|
991
|
-
|
|
1624
|
+
await emitExperience('search', {
|
|
1625
|
+
subtitle: 'index',
|
|
1626
|
+
message: `Search index: ${result.count} entries`,
|
|
1627
|
+
items: [{ label: 'Out', value: result.outFile }],
|
|
1628
|
+
});
|
|
992
1629
|
return;
|
|
993
1630
|
}
|
|
994
|
-
|
|
1631
|
+
await emitExperience('search', {
|
|
1632
|
+
status: 'WARNING',
|
|
1633
|
+
message: 'Usage: velinstyle search index [--out <file>] [--extra-html dir1,dir2]',
|
|
1634
|
+
exitCode: 1,
|
|
1635
|
+
});
|
|
995
1636
|
}
|
|
996
1637
|
|
|
997
1638
|
async function skillsCmd() {
|
|
998
|
-
const
|
|
999
|
-
|
|
1639
|
+
const sub = args[1];
|
|
1640
|
+
const {
|
|
1641
|
+
skillsList,
|
|
1642
|
+
skillsInstall,
|
|
1643
|
+
skillsRun,
|
|
1644
|
+
skillsValidate,
|
|
1645
|
+
skillsDoctor,
|
|
1646
|
+
skillsIndex,
|
|
1647
|
+
loadSkillRegistry,
|
|
1648
|
+
} = await import('./skills.js');
|
|
1649
|
+
|
|
1650
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
1651
|
+
await emitExperience('skills', {
|
|
1652
|
+
message: 'Usage: velinstyle skills <list|show|install|run|validate|doctor|packs|…>',
|
|
1653
|
+
suggestions: [
|
|
1654
|
+
{ text: 'velinstyle skills list' },
|
|
1655
|
+
{ text: 'velinstyle skills show scaffold.landing --human' },
|
|
1656
|
+
],
|
|
1657
|
+
});
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (sub === 'list') {
|
|
1662
|
+
const items = skillsList(args.slice(2), { silent: true });
|
|
1663
|
+
if (hasFlag('--json')) {
|
|
1664
|
+
console.log(JSON.stringify(items, null, 2));
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
await emitExperience('skills', {
|
|
1668
|
+
subtitle: 'list',
|
|
1669
|
+
message: `${items.length} skill(s)`,
|
|
1670
|
+
table: {
|
|
1671
|
+
columns: ['Id', 'Category', 'Priority', 'Status'],
|
|
1672
|
+
rows: items.slice(0, 40).map((item) => [
|
|
1673
|
+
item.id,
|
|
1674
|
+
item.category || '',
|
|
1675
|
+
item.priority || '',
|
|
1676
|
+
item.status || '',
|
|
1677
|
+
]),
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (sub === 'show') {
|
|
1684
|
+
const skillId = args[2];
|
|
1685
|
+
const { findSkillById } = await import('../packages/skill-engine/src/index.js');
|
|
1686
|
+
const registry = loadSkillRegistry();
|
|
1687
|
+
const skill = findSkillById(registry, skillId);
|
|
1688
|
+
if (!skill) {
|
|
1689
|
+
await emitExperience('skills', { status: 'ERROR', message: `Unknown skill: ${skillId}`, exitCode: 1 });
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (hasFlag('--human')) {
|
|
1693
|
+
await emitExperience('skills', {
|
|
1694
|
+
subtitle: skillId,
|
|
1695
|
+
message: skill.title || skillId,
|
|
1696
|
+
items: [
|
|
1697
|
+
{ label: 'Category', value: skill.category || '—' },
|
|
1698
|
+
{ label: 'Status', value: skill.status || '—' },
|
|
1699
|
+
{ label: 'Priority', value: skill.priority || '—' },
|
|
1700
|
+
{ label: 'Capabilities', value: (skill.capabilities || []).join(', ') || '—' },
|
|
1701
|
+
],
|
|
1702
|
+
notifications: skill.summary || skill.description
|
|
1703
|
+
? [{ tone: 'info', message: skill.summary || skill.description }]
|
|
1704
|
+
: [],
|
|
1705
|
+
});
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
console.log(JSON.stringify(skill, null, 2));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
if (sub === 'install') {
|
|
1713
|
+
if (!args[2]) {
|
|
1714
|
+
await emitExperience('skills', {
|
|
1715
|
+
status: 'WARNING',
|
|
1716
|
+
message: 'Usage: velinstyle skills install <skill-id|pack|bundle:id|project:id>',
|
|
1717
|
+
exitCode: 1,
|
|
1718
|
+
});
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const result = skillsInstall(args.slice(2), { silent: true });
|
|
1722
|
+
await emitExperience('skills', {
|
|
1723
|
+
subtitle: 'install',
|
|
1724
|
+
status: result.issues?.length ? 'WARNING' : 'PASS',
|
|
1725
|
+
message: `Installed ${(result.installed || []).length} skill(s)`,
|
|
1726
|
+
table: {
|
|
1727
|
+
columns: ['Installed'],
|
|
1728
|
+
rows: (result.installed || []).map((id) => [id]),
|
|
1729
|
+
},
|
|
1730
|
+
notifications: (result.issues || []).map((issue) => ({ tone: 'warning', message: issue })),
|
|
1731
|
+
});
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (sub === 'run') {
|
|
1736
|
+
const payload = skillsRun(args[2], args.slice(3), { silent: true });
|
|
1737
|
+
if (payload.ok === false) {
|
|
1738
|
+
await emitExperience('skills', {
|
|
1739
|
+
status: 'ERROR',
|
|
1740
|
+
message: 'Skill graph resolve failed',
|
|
1741
|
+
table: { columns: ['Issue'], rows: (payload.issues || []).map((i) => [i]) },
|
|
1742
|
+
exitCode: 1,
|
|
1743
|
+
});
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
if (hasFlag('--json')) {
|
|
1747
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
await emitExperience('skills', {
|
|
1751
|
+
subtitle: 'run',
|
|
1752
|
+
message: `${payload.steps.length} step(s) (dry-run)`,
|
|
1753
|
+
table: {
|
|
1754
|
+
columns: ['Step', 'CLI', 'onlyIf'],
|
|
1755
|
+
rows: payload.steps.map((s) => [
|
|
1756
|
+
s.id,
|
|
1757
|
+
(s.requiresCli || []).join(', '),
|
|
1758
|
+
(s.onlyIf || []).join(', '),
|
|
1759
|
+
]),
|
|
1760
|
+
},
|
|
1761
|
+
});
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
if (sub === 'validate') {
|
|
1766
|
+
const result = skillsValidate({ silent: true });
|
|
1767
|
+
await emitExperience('skills', {
|
|
1768
|
+
subtitle: 'validate',
|
|
1769
|
+
status: result.ok ? 'PASS' : 'ERROR',
|
|
1770
|
+
message: result.ok ? 'skills registry valid' : 'skills registry invalid',
|
|
1771
|
+
table: result.errors?.length
|
|
1772
|
+
? { columns: ['Error'], rows: result.errors.map((e) => [e]) }
|
|
1773
|
+
: null,
|
|
1774
|
+
exitCode: result.ok ? 0 : 1,
|
|
1775
|
+
});
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if (sub === 'doctor') {
|
|
1780
|
+
const result = await skillsDoctor(args.slice(2), { silent: true });
|
|
1781
|
+
if (hasFlag('--json')) {
|
|
1782
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1783
|
+
process.exit(result.ok ? 0 : 1);
|
|
1784
|
+
}
|
|
1785
|
+
await emitExperience('skills', {
|
|
1786
|
+
subtitle: 'doctor',
|
|
1787
|
+
status: result.ok ? 'PASS' : 'ERROR',
|
|
1788
|
+
message: result.ok
|
|
1789
|
+
? `skills doctor OK (${result.checked} paths)`
|
|
1790
|
+
: `skills doctor FAILED (${result.errors.length} missing)`,
|
|
1791
|
+
table: result.errors?.length
|
|
1792
|
+
? { columns: ['Missing'], rows: result.errors.map((e) => [e]) }
|
|
1793
|
+
: null,
|
|
1794
|
+
exitCode: result.ok ? 0 : 1,
|
|
1795
|
+
});
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (['packs', 'bundles', 'templates', 'projects', 'graphs'].includes(sub)) {
|
|
1800
|
+
const out = skillsIndex(sub, { silent: true });
|
|
1801
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
await emitExperience('skills', {
|
|
1806
|
+
status: 'ERROR',
|
|
1807
|
+
message: `Unknown skills subcommand: ${sub}`,
|
|
1808
|
+
exitCode: 1,
|
|
1809
|
+
});
|
|
1000
1810
|
}
|
|
1001
1811
|
|
|
1002
1812
|
async function workflowCmd() {
|
|
1003
|
-
const {
|
|
1004
|
-
|
|
1813
|
+
const { workflowResolve } = await import('./workflow.js');
|
|
1814
|
+
const workflowId = args[1];
|
|
1815
|
+
if (!workflowId || workflowId === '--help' || workflowId === '-h') {
|
|
1816
|
+
await emitExperience('workflow', {
|
|
1817
|
+
message: 'Usage: velinstyle workflow <graph-id|project:id> [--json]',
|
|
1818
|
+
suggestions: [
|
|
1819
|
+
{ text: 'velinstyle workflow landingpage' },
|
|
1820
|
+
{ text: 'velinstyle workflow project:marketing-site --json' },
|
|
1821
|
+
],
|
|
1822
|
+
});
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const resolved = workflowResolve(workflowId);
|
|
1826
|
+
if (!resolved.ok) {
|
|
1827
|
+
await emitExperience('workflow', {
|
|
1828
|
+
status: 'ERROR',
|
|
1829
|
+
message: resolved.error,
|
|
1830
|
+
table: resolved.issues?.length
|
|
1831
|
+
? { columns: ['Issue'], rows: resolved.issues.map((i) => [i]) }
|
|
1832
|
+
: null,
|
|
1833
|
+
exitCode: 1,
|
|
1834
|
+
});
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (hasFlag('--json')) {
|
|
1838
|
+
console.log(JSON.stringify(resolved.data, null, 2));
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
await emitExperience('workflow', {
|
|
1842
|
+
message: `Workflow steps (${resolved.data.orderedSteps.length})`,
|
|
1843
|
+
table: {
|
|
1844
|
+
columns: ['Step'],
|
|
1845
|
+
rows: resolved.data.orderedSteps.map((step) => [step]),
|
|
1846
|
+
},
|
|
1847
|
+
});
|
|
1005
1848
|
}
|
|
1006
1849
|
|
|
1007
1850
|
async function wcCmd() {
|
|
1008
1851
|
const sub = args[1];
|
|
1009
1852
|
if (sub === 'api' || sub === '--help' || sub === '-h' || !sub) {
|
|
1010
1853
|
if (sub !== 'api') {
|
|
1011
|
-
|
|
1012
|
-
|
|
1854
|
+
await emitExperience('wc', {
|
|
1855
|
+
status: sub ? 'PASS' : 'WARNING',
|
|
1856
|
+
message: 'Usage: velinstyle wc api <tag>',
|
|
1857
|
+
suggestions: [{ text: 'velinstyle wc api velin-toast' }],
|
|
1858
|
+
exitCode: !sub ? 1 : 0,
|
|
1859
|
+
});
|
|
1860
|
+
return;
|
|
1013
1861
|
}
|
|
1014
1862
|
const tag = args[2];
|
|
1015
|
-
const { describeWcApi
|
|
1863
|
+
const { describeWcApi } = await import('./wc-api.js');
|
|
1016
1864
|
const report = describeWcApi(PKG_ROOT, tag);
|
|
1017
1865
|
if (!report.ok) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1866
|
+
await emitExperience('wc', { status: 'ERROR', message: report.error, exitCode: 1 });
|
|
1867
|
+
return;
|
|
1020
1868
|
}
|
|
1021
|
-
if (hasFlag('--json'))
|
|
1022
|
-
|
|
1869
|
+
if (hasFlag('--json')) {
|
|
1870
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const rows = [
|
|
1874
|
+
...report.observedAttributes.map((a) => ['attr', a]),
|
|
1875
|
+
...report.methods.map((m) => ['method', `${m}()`]),
|
|
1876
|
+
...report.events.map((e) => ['event', e]),
|
|
1877
|
+
];
|
|
1878
|
+
await emitExperience('wc', {
|
|
1879
|
+
subtitle: report.tag,
|
|
1880
|
+
message: `<${report.tag}>`,
|
|
1881
|
+
items: [
|
|
1882
|
+
{ label: 'Source', value: report.source },
|
|
1883
|
+
{ label: 'Docs', value: report.generatedDoc || '(missing — run docs generate)' },
|
|
1884
|
+
...(report.showSignature ? [{ label: 'show()', value: report.showSignature }] : []),
|
|
1885
|
+
],
|
|
1886
|
+
table: rows.length ? { columns: ['Kind', 'Name'], rows } : null,
|
|
1887
|
+
notifications: report.example
|
|
1888
|
+
? [{ tone: 'info', message: report.example.split('\n')[0] }]
|
|
1889
|
+
: [],
|
|
1890
|
+
});
|
|
1023
1891
|
return;
|
|
1024
1892
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1893
|
+
await emitExperience('wc', {
|
|
1894
|
+
status: 'ERROR',
|
|
1895
|
+
message: `Unknown wc subcommand: ${sub}. Use: velinstyle wc api <tag>`,
|
|
1896
|
+
exitCode: 1,
|
|
1897
|
+
});
|
|
1027
1898
|
}
|
|
1028
1899
|
|
|
1029
1900
|
async function scanCmd() {
|
|
@@ -1038,15 +1909,81 @@ async function scanCmd() {
|
|
|
1038
1909
|
}
|
|
1039
1910
|
const severity = getArg('--severity') || 'warning';
|
|
1040
1911
|
const only = getArg('--only');
|
|
1912
|
+
const machine = format === 'json' || format === 'sarif' || hasFlag('--json') || hasFlag('--sarif');
|
|
1041
1913
|
|
|
1042
1914
|
const { scan } = await import('./scanner.js');
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1915
|
+
|
|
1916
|
+
if (fix || fixDryRun || machine) {
|
|
1917
|
+
const exitCode = scan(targetPath, {
|
|
1918
|
+
severity,
|
|
1919
|
+
fix,
|
|
1920
|
+
fixDryRun,
|
|
1921
|
+
fixLang: fixLang || undefined,
|
|
1922
|
+
format: hasFlag('--json') ? 'json' : hasFlag('--sarif') ? 'sarif' : format,
|
|
1923
|
+
only,
|
|
1924
|
+
});
|
|
1925
|
+
process.exit(exitCode);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
const exp = await ensureExperience();
|
|
1929
|
+
let exitCode = 0;
|
|
1930
|
+
await exp.executeExperience({
|
|
1931
|
+
commandId: 'scan',
|
|
1932
|
+
args,
|
|
1933
|
+
helpers: { getArg, hasFlag },
|
|
1934
|
+
version: CLI_VERSION,
|
|
1935
|
+
layout: 'audit',
|
|
1936
|
+
run: async (ctx) => {
|
|
1937
|
+
ctx.emitProgress('Analysis', 40);
|
|
1938
|
+
const data = scan(targetPath, {
|
|
1939
|
+
severity,
|
|
1940
|
+
format: 'json',
|
|
1941
|
+
only,
|
|
1942
|
+
returnData: true,
|
|
1943
|
+
});
|
|
1944
|
+
exitCode = data.exitCode || 0;
|
|
1945
|
+
const issues = data.issues || [];
|
|
1946
|
+
const errors = issues.filter((i) => i.severity === 'error').length;
|
|
1947
|
+
const warnings = issues.filter((i) => i.severity === 'warning').length;
|
|
1948
|
+
const status = errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS';
|
|
1949
|
+
return {
|
|
1950
|
+
project: { cwd: targetPath, name: 'Scan' },
|
|
1951
|
+
result: {
|
|
1952
|
+
status,
|
|
1953
|
+
message: errors || warnings
|
|
1954
|
+
? `${errors} error(s), ${warnings} warning(s)`
|
|
1955
|
+
: 'No issues at selected severity',
|
|
1956
|
+
},
|
|
1957
|
+
stats: { errors, warnings, issues: issues.length },
|
|
1958
|
+
diagnostics: issues,
|
|
1959
|
+
widgets: [
|
|
1960
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Scan', subtitle: 'Trust & accessibility audit', eyebrow: 'Velin Experience' } },
|
|
1961
|
+
{
|
|
1962
|
+
id: 'stats',
|
|
1963
|
+
props: {
|
|
1964
|
+
slot: 'counts',
|
|
1965
|
+
items: [
|
|
1966
|
+
{ label: 'Errors', value: errors },
|
|
1967
|
+
{ label: 'Warnings', value: warnings },
|
|
1968
|
+
{ label: 'Issues', value: issues.length },
|
|
1969
|
+
],
|
|
1970
|
+
},
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
id: 'table',
|
|
1974
|
+
props: {
|
|
1975
|
+
slot: 'findings',
|
|
1976
|
+
columns: ['Sev', 'Code', 'Message'],
|
|
1977
|
+
rows: issues.slice(0, 20).map((i) => [i.severity, i.code || '—', String(i.message || '').slice(0, 42)]),
|
|
1978
|
+
},
|
|
1979
|
+
},
|
|
1980
|
+
{ id: 'notification', props: { tone: status === 'PASS' ? 'ok' : status === 'ERROR' ? 'err' : 'warn', message: `Scan ${status}` } },
|
|
1981
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle scan' } },
|
|
1982
|
+
],
|
|
1983
|
+
suggestions: issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1984
|
+
notifications: [{ tone: 'info', message: 'Use --fix for safe auto-fixes where available' }],
|
|
1985
|
+
};
|
|
1986
|
+
},
|
|
1050
1987
|
});
|
|
1051
1988
|
process.exit(exitCode);
|
|
1052
1989
|
}
|
|
@@ -1147,6 +2084,40 @@ async function checkCmd() {
|
|
|
1147
2084
|
steps.push({ step: 'transparency', ok: true, skipped: true, error: String(err.message || err) });
|
|
1148
2085
|
}
|
|
1149
2086
|
|
|
2087
|
+
if (!quiet) console.log(C.bold('\n── motion ──'));
|
|
2088
|
+
try {
|
|
2089
|
+
const { motionCheckStep } = await import('./motion.js');
|
|
2090
|
+
const mx = await motionCheckStep(htmlFile || target, { quiet, C });
|
|
2091
|
+
if (!mx.ok && !mx.skipped) failed += 1;
|
|
2092
|
+
steps.push({ step: 'motion', ok: mx.ok || !!mx.skipped, skipped: mx.skipped, elements: mx.elements });
|
|
2093
|
+
} catch (err) {
|
|
2094
|
+
if (!quiet) console.log(C.yellow(`motion skipped: ${err.message}`));
|
|
2095
|
+
steps.push({ step: 'motion', ok: true, skipped: true, error: String(err.message || err) });
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (!quiet) console.log(C.bold('\n── security ──'));
|
|
2099
|
+
try {
|
|
2100
|
+
const { securityCheckStep } = await import('./security.js');
|
|
2101
|
+
let securityRoot = target;
|
|
2102
|
+
try {
|
|
2103
|
+
if (existsSync(target) && statSync(target).isFile()) securityRoot = dirname(target);
|
|
2104
|
+
} catch { /* keep target */ }
|
|
2105
|
+
const sx = await securityCheckStep(securityRoot, { quiet, C });
|
|
2106
|
+
if (!sx.ok && !sx.skipped) failed += 1;
|
|
2107
|
+
steps.push({
|
|
2108
|
+
step: 'security',
|
|
2109
|
+
ok: sx.ok || !!sx.skipped,
|
|
2110
|
+
skipped: sx.skipped,
|
|
2111
|
+
securityScore: sx.securityScore,
|
|
2112
|
+
overall: sx.overall,
|
|
2113
|
+
overallGate: sx.overallGate,
|
|
2114
|
+
findings: sx.findings,
|
|
2115
|
+
});
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
if (!quiet) console.log(C.yellow(`security skipped: ${err.message}`));
|
|
2118
|
+
steps.push({ step: 'security', ok: true, skipped: true, error: String(err.message || err) });
|
|
2119
|
+
}
|
|
2120
|
+
|
|
1150
2121
|
const payload = {
|
|
1151
2122
|
ok: failed === 0,
|
|
1152
2123
|
failedSteps: failed,
|
|
@@ -1193,10 +2164,43 @@ async function checkCmd() {
|
|
|
1193
2164
|
console.log(JSON.stringify(sarif, null, 2));
|
|
1194
2165
|
} else if (asJson) {
|
|
1195
2166
|
console.log(JSON.stringify(payload, null, 2));
|
|
1196
|
-
} else if (failed) {
|
|
1197
|
-
console.log(C.red(`\ncheck failed (${failed} step(s))`));
|
|
1198
2167
|
} else {
|
|
1199
|
-
|
|
2168
|
+
const exp = await ensureExperience();
|
|
2169
|
+
await exp.executeExperience({
|
|
2170
|
+
commandId: 'check',
|
|
2171
|
+
args,
|
|
2172
|
+
helpers: { getArg, hasFlag },
|
|
2173
|
+
version: CLI_VERSION,
|
|
2174
|
+
layout: 'dashboard',
|
|
2175
|
+
run: async () => ({
|
|
2176
|
+
project: { target },
|
|
2177
|
+
result: {
|
|
2178
|
+
status: failed ? 'ERROR' : 'PASS',
|
|
2179
|
+
message: failed ? `check failed (${failed} step(s))` : 'Ready',
|
|
2180
|
+
},
|
|
2181
|
+
widgets: [
|
|
2182
|
+
{ id: 'hero', props: { title: 'Check', subtitle: 'All engines', eyebrow: 'Velin Experience' } },
|
|
2183
|
+
{
|
|
2184
|
+
id: 'health',
|
|
2185
|
+
props: {
|
|
2186
|
+
title: 'Summary',
|
|
2187
|
+
rows: steps.map((s) => ({
|
|
2188
|
+
label: s.step,
|
|
2189
|
+
status: s.skipped ? 'SKIP' : s.ok ? 'PASS' : 'FAIL',
|
|
2190
|
+
})),
|
|
2191
|
+
},
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
id: 'status',
|
|
2195
|
+
props: { label: failed ? 'FAIL' : 'PASS', tone: failed ? 'err' : 'ok' },
|
|
2196
|
+
},
|
|
2197
|
+
{ id: 'footer', props: { text: 'Overall · Ready gate' } },
|
|
2198
|
+
],
|
|
2199
|
+
suggestions: failed
|
|
2200
|
+
? [{ text: 'Re-run failing steps individually (doctor / scan / review)' }]
|
|
2201
|
+
: [{ text: 'Try velinstyle dashboard for a project overview' }],
|
|
2202
|
+
}),
|
|
2203
|
+
});
|
|
1200
2204
|
}
|
|
1201
2205
|
|
|
1202
2206
|
process.exit(failed ? 1 : 0);
|
|
@@ -1205,8 +2209,9 @@ async function checkCmd() {
|
|
|
1205
2209
|
function suggestCommand(unknown) {
|
|
1206
2210
|
const names = [
|
|
1207
2211
|
'init', 'build', 'themes', 'add', 'icons', 'blueprint', 'create', 'serve', 'doctor', 'check',
|
|
1208
|
-
'validate', 'tokens', 'scan', 'prefix', 'scaffold', 'plan', 'review', 'transparency', 'layout', 'perf',
|
|
1209
|
-
'docs', 'documentation', 'meta', 'search', 'skills', 'workflow', 'wc',
|
|
2212
|
+
'validate', 'tokens', 'scan', 'prefix', 'scaffold', 'plan', 'review', 'transparency', 'motion', 'security', 'layout', 'perf',
|
|
2213
|
+
'docs', 'documentation', 'meta', 'search', 'skills', 'workflow', 'wc', 'production', 'atelier',
|
|
2214
|
+
'dashboard', 'replay',
|
|
1210
2215
|
];
|
|
1211
2216
|
const q = String(unknown || '').toLowerCase();
|
|
1212
2217
|
function editDistance(a, b) {
|
|
@@ -1252,25 +2257,43 @@ const ALIASES = {
|
|
|
1252
2257
|
const resolvedCommand = ALIASES[command] || command;
|
|
1253
2258
|
|
|
1254
2259
|
switch (resolvedCommand) {
|
|
1255
|
-
case 'init': init(); break;
|
|
1256
|
-
case 'build': build(); break;
|
|
1257
|
-
case '
|
|
1258
|
-
|
|
1259
|
-
|
|
2260
|
+
case 'init': await init(); break;
|
|
2261
|
+
case 'build': await build(); break;
|
|
2262
|
+
case 'production': {
|
|
2263
|
+
const { runProduction } = await import('./production/run.js');
|
|
2264
|
+
await runProduction({ args: args.slice(1), helpers: { getArg, hasFlag, C } });
|
|
2265
|
+
break;
|
|
2266
|
+
}
|
|
2267
|
+
case 'themes': await themes(); break;
|
|
2268
|
+
case 'add': await add(); break;
|
|
2269
|
+
case 'icons': await icons(); break;
|
|
1260
2270
|
case 'blueprint': await blueprintCmd(); break;
|
|
1261
2271
|
case 'create': await createCmd(); break;
|
|
1262
2272
|
case 'serve': await serveCmd(); break;
|
|
1263
2273
|
case 'doctor': await doctorCmd(); break;
|
|
2274
|
+
case 'dashboard': await dashboardCmd(); break;
|
|
2275
|
+
case 'replay': await replayCmd(); break;
|
|
1264
2276
|
case 'check': await checkCmd(); break;
|
|
1265
2277
|
case 'tokens': await tokensCmd(); break;
|
|
1266
|
-
case 'scan': scanCmd(); break;
|
|
2278
|
+
case 'scan': await scanCmd(); break;
|
|
1267
2279
|
case 'prefix': await prefixCmd(); break;
|
|
1268
2280
|
case 'scaffold': await scaffoldCmd(); break;
|
|
1269
2281
|
case 'plan': await planCmd(); break;
|
|
2282
|
+
case 'atelier': await atelierCmd(); break;
|
|
1270
2283
|
case 'review': await reviewCmd(); break;
|
|
1271
2284
|
case 'transparency': {
|
|
1272
2285
|
const { transparencyCmd } = await import('./transparency.js');
|
|
1273
|
-
await transparencyCmd(args.slice(1), { C, getArg, hasFlag });
|
|
2286
|
+
await transparencyCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
2287
|
+
break;
|
|
2288
|
+
}
|
|
2289
|
+
case 'motion': {
|
|
2290
|
+
const { motionCmd } = await import('./motion.js');
|
|
2291
|
+
await motionCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
2292
|
+
break;
|
|
2293
|
+
}
|
|
2294
|
+
case 'security': {
|
|
2295
|
+
const { securityCmd } = await import('./security.js');
|
|
2296
|
+
await securityCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
1274
2297
|
break;
|
|
1275
2298
|
}
|
|
1276
2299
|
case 'layout': await layoutCmd(); break;
|
|
@@ -1281,6 +2304,6 @@ switch (resolvedCommand) {
|
|
|
1281
2304
|
case 'skills': await skillsCmd(); break;
|
|
1282
2305
|
case 'workflow': await workflowCmd(); break;
|
|
1283
2306
|
case 'wc': await wcCmd(); break;
|
|
1284
|
-
case '--help': case '-h': case undefined: help(); break;
|
|
2307
|
+
case '--help': case '-h': case undefined: await help(); break;
|
|
1285
2308
|
default: unknownCommand(command); break;
|
|
1286
2309
|
}
|