@birdapi/velinstyle 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +134 -11
- package/README.md +139 -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 +23 -10
- package/cli/docgen/extract-cli.js +1 -1
- package/cli/index.js +149 -3
- package/cli/production/component-graph.json +166 -0
- package/cli/production/explain.js +52 -0
- package/cli/production/extract.js +238 -0
- package/cli/production/graph.js +102 -0
- package/cli/production/report.js +78 -0
- package/cli/production/run.js +312 -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/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/core/a11y/component-contracts.json +23 -4
- package/core/meta/knowledge/components.json +75 -3
- package/dist/chunks/attributes-2ORR27KA.js +404 -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/llms.txt +3 -3
- package/dist/search-index.json +68 -5
- package/dist/velin-agent.json +283 -20
- package/dist/velinstyle-components.iife.js +2716 -2138
- package/dist/velinstyle-components.js +2772 -2190
- package/dist/velinstyle-components.min.js +254 -112
- package/dist/velinstyle.css +54 -6
- package/dist/velinstyle.d.ts +3 -0
- package/dist/velinstyle.min.css +1 -1
- package/package.json +143 -143
- package/packages/velinstyle-skills/catalog.json +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/velinstyle.css +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework integration shells for Atelier pulls.
|
|
3
|
+
* Blade/Vue/React are wrappers around vanilla showcase assets — not native rewrites.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const LIMITATION_EN = `## Limitation
|
|
7
|
+
|
|
8
|
+
\`--format blade|vue|react\` writes an **integration shell** that mounts the original Atelier vanilla showcase (HTML/JS/CSS + Web Components). It does **not** rewrite the template into idiomatic Blade/Vue/React.
|
|
9
|
+
|
|
10
|
+
Native framework blocks are **planned** for a later release / Velin Studio (not shipped).
|
|
11
|
+
|
|
12
|
+
Atelier Library ≠ Velin Studio Builder.
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const LIMITATION_DE = `## Einschränkung
|
|
16
|
+
|
|
17
|
+
\`--format blade|vue|react\` erzeugt eine **Integrations-Shell** um das originale Vanilla-Showcase (HTML/JS/CSS + Web Components). Kein idiomatischer Rewrite.
|
|
18
|
+
|
|
19
|
+
Native Framework-Bausteine sind **geplant** (spätere Version / Velin Studio — nicht shipped).
|
|
20
|
+
|
|
21
|
+
Atelier Library ≠ Velin Studio Builder.
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const FORMATS = new Set(['html', 'blade', 'vue', 'react']);
|
|
25
|
+
|
|
26
|
+
export function normalizeFormat(raw) {
|
|
27
|
+
const f = String(raw || 'html').toLowerCase().trim();
|
|
28
|
+
if (!FORMATS.has(f)) {
|
|
29
|
+
return { ok: false, error: `Unknown format "${raw}". Use: html | blade | vue | react (blade/vue/react = integration wrappers, not native rewrites).` };
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, format: f };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {{ id: string, title?: string, assetDir: string, format: string }} opts
|
|
36
|
+
*/
|
|
37
|
+
export function emitFormatShell(opts) {
|
|
38
|
+
const { id, title = id, assetDir = '.', format } = opts;
|
|
39
|
+
const n = normalizeFormat(format);
|
|
40
|
+
if (!n.ok) return n;
|
|
41
|
+
|
|
42
|
+
const rel = !assetDir || assetDir === '.' ? '.' : assetDir.replace(/\\/g, '/').replace(/\/$/, '');
|
|
43
|
+
const appJs = rel === '.' ? './app.js' : `${rel}/app.js`;
|
|
44
|
+
const appCss = rel === '.' ? './app.css' : `${rel}/app.css`;
|
|
45
|
+
|
|
46
|
+
if (n.format === 'html') {
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
format: 'html',
|
|
50
|
+
files: {},
|
|
51
|
+
note: 'html',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (n.format === 'blade') {
|
|
56
|
+
const assetPath = rel === '.' ? `velin-atelier/${id}` : rel;
|
|
57
|
+
const blade = `{{-- VelinStyle Atelier wrapper for ${id} — vanilla showcase mount, not a native Blade rewrite --}}
|
|
58
|
+
{{-- Limitation: integration shell only. Native framework blocks / Studio = planned. --}}
|
|
59
|
+
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
60
|
+
<link rel="stylesheet" href="{{ asset('${assetPath}/app.css') }}">
|
|
61
|
+
<div id="root" data-atelier-id="${id}" data-velin-atelier-mount></div>
|
|
62
|
+
<script type="module" src="{{ asset('${assetPath}/app.js') }}"></script>
|
|
63
|
+
`;
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
format: 'blade',
|
|
67
|
+
files: {
|
|
68
|
+
[`${id}.blade.php`]: blade,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (n.format === 'vue') {
|
|
74
|
+
const vue = `<!-- VelinStyle Atelier wrapper for ${id} — mounts vanilla showcase; not idiomatic Vue SFC rewrite -->
|
|
75
|
+
<script setup>
|
|
76
|
+
import { onMounted, ref } from 'vue';
|
|
77
|
+
import '${appCss}';
|
|
78
|
+
|
|
79
|
+
const root = ref(null);
|
|
80
|
+
const title = ${JSON.stringify(title)};
|
|
81
|
+
|
|
82
|
+
onMounted(async () => {
|
|
83
|
+
// Load original Atelier app.js (vanilla). Native Vue rewrite is planned later.
|
|
84
|
+
await import('${appJs}');
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<div>
|
|
90
|
+
<p class="velin-text-muted">Atelier {{ title }} (integration wrapper)</p>
|
|
91
|
+
<div id="root" ref="root" data-atelier-id="${id}" data-velin-atelier-mount></div>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
`;
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
format: 'vue',
|
|
98
|
+
files: {
|
|
99
|
+
[`${id}.vue`]: vue,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// react
|
|
105
|
+
const jsx = `/** VelinStyle Atelier wrapper for ${id} — mounts vanilla showcase; not a native JSX rewrite */
|
|
106
|
+
import { useEffect, useRef } from 'react';
|
|
107
|
+
import '${appCss}';
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Limitation: integration shell only. Native React blocks are planned / Studio later.
|
|
111
|
+
*/
|
|
112
|
+
export default function Atelier${id.replace(/[^a-zA-Z0-9]/g, '_')}() {
|
|
113
|
+
const rootRef = useRef(null);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
let cancelled = false;
|
|
117
|
+
(async () => {
|
|
118
|
+
if (cancelled) return;
|
|
119
|
+
await import('${appJs}');
|
|
120
|
+
})();
|
|
121
|
+
return () => { cancelled = true; };
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div>
|
|
126
|
+
<p>{${JSON.stringify(title)}} (Atelier integration wrapper)</p>
|
|
127
|
+
<div id="root" ref={rootRef} data-atelier-id="${id}" data-velin-atelier-mount />
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
format: 'react',
|
|
135
|
+
files: {
|
|
136
|
+
[`${id}.jsx`]: jsx,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {{ id: string, title?: string, format: string, sourceHint?: string }} opts
|
|
143
|
+
*/
|
|
144
|
+
export function buildPullReadme(opts) {
|
|
145
|
+
const { id, title = id, format, sourceHint = '' } = opts;
|
|
146
|
+
return `# Atelier pull: ${id}
|
|
147
|
+
|
|
148
|
+
**Title:** ${title}
|
|
149
|
+
**Format:** ${format}
|
|
150
|
+
${sourceHint ? `**Source:** ${sourceHint}\n` : ''}
|
|
151
|
+
Install CSS/WC: \`npm i @birdapi/velinstyle\` and point vendor/link stylesheets to \`node_modules/@birdapi/velinstyle/dist\`.
|
|
152
|
+
|
|
153
|
+
${LIMITATION_EN}
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
${LIMITATION_DE}
|
|
158
|
+
`;
|
|
159
|
+
}
|
package/cli/atelier.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atelier Library pull: resolve curated num/id → copy or HTTPS fetch showcase assets.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
readFileSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
} from 'fs';
|
|
10
|
+
import { dirname, join, resolve } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { emitFormatShell, buildPullReadme, normalizeFormat } from './atelier-formats.js';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const DEFAULT_CATALOG_PATH = join(__dirname, 'atelier-catalog.json');
|
|
16
|
+
const DEFAULT_BASE_URL = 'https://velinstyle.info/atelier/library/';
|
|
17
|
+
const ASSET_NAMES = ['index.html', 'app.js', 'app.css'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} [catalogPath]
|
|
21
|
+
*/
|
|
22
|
+
export function loadCatalog(catalogPath = DEFAULT_CATALOG_PATH) {
|
|
23
|
+
const full = resolve(catalogPath);
|
|
24
|
+
if (!existsSync(full)) {
|
|
25
|
+
return { ok: false, error: `Catalog not found: ${full}`, items: [] };
|
|
26
|
+
}
|
|
27
|
+
const data = JSON.parse(readFileSync(full, 'utf-8'));
|
|
28
|
+
return { ok: true, items: data.items || [], meta: data };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve query like "36", "036", "36-calendar", or "calendar" (unique suffix).
|
|
33
|
+
* SEO ids (e.g. 3600-…) are not matched by short number lookup.
|
|
34
|
+
* @param {string} query
|
|
35
|
+
* @param {{ catalogPath?: string, items?: object[] }} [opts]
|
|
36
|
+
*/
|
|
37
|
+
export function resolveAtelierEntry(query, opts = {}) {
|
|
38
|
+
const q = String(query || '').trim();
|
|
39
|
+
if (!q) return { ok: false, error: 'Empty atelier query' };
|
|
40
|
+
|
|
41
|
+
const items = opts.items || loadCatalog(opts.catalogPath).items;
|
|
42
|
+
if (!items?.length) return { ok: false, error: 'Atelier catalog is empty' };
|
|
43
|
+
|
|
44
|
+
// Exact id
|
|
45
|
+
const byId = items.find((it) => it.id === q);
|
|
46
|
+
if (byId) return { ok: true, entry: byId };
|
|
47
|
+
|
|
48
|
+
// Numeric: 36 / 036 → num pad match only (not 3600)
|
|
49
|
+
if (/^\d{1,3}$/.test(q)) {
|
|
50
|
+
const n = String(Number(q));
|
|
51
|
+
const entry = items.find((it) => String(Number(it.num)) === n);
|
|
52
|
+
if (entry) return { ok: true, entry };
|
|
53
|
+
return { ok: false, error: `Unknown atelier number "${q}". Try: velinstyle atelier list` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// id prefix or slug contains
|
|
57
|
+
const lower = q.toLowerCase();
|
|
58
|
+
const matches = items.filter(
|
|
59
|
+
(it) => it.id.toLowerCase() === lower
|
|
60
|
+
|| it.id.toLowerCase().startsWith(lower + '-')
|
|
61
|
+
|| it.id.toLowerCase().endsWith('-' + lower)
|
|
62
|
+
|| it.id.toLowerCase().includes('-' + lower),
|
|
63
|
+
);
|
|
64
|
+
if (matches.length === 1) return { ok: true, entry: matches[0] };
|
|
65
|
+
if (matches.length > 1) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: `Ambiguous "${q}": ${matches.slice(0, 8).map((m) => m.id).join(', ')}${matches.length > 8 ? '…' : ''}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { ok: false, error: `Unknown atelier id "${q}". Try: velinstyle atelier list` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} listStr comma-separated queries
|
|
76
|
+
* @param {{ catalogPath?: string, items?: object[] }} [opts]
|
|
77
|
+
*/
|
|
78
|
+
export function resolveAtelierList(listStr, opts = {}) {
|
|
79
|
+
const parts = String(listStr || '')
|
|
80
|
+
.split(',')
|
|
81
|
+
.map((s) => s.trim())
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
if (!parts.length) return { ok: false, error: 'Empty --atelier list', entries: [] };
|
|
84
|
+
const entries = [];
|
|
85
|
+
for (const p of parts) {
|
|
86
|
+
const r = resolveAtelierEntry(p, opts);
|
|
87
|
+
if (!r.ok) return { ok: false, error: r.error, entries };
|
|
88
|
+
entries.push(r.entry);
|
|
89
|
+
}
|
|
90
|
+
return { ok: true, entries };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function listAtelierEntries(opts = {}) {
|
|
94
|
+
const cat = opts.items ? { ok: true, items: opts.items } : loadCatalog(opts.catalogPath);
|
|
95
|
+
if (!cat.ok) return cat;
|
|
96
|
+
return { ok: true, items: cat.items };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ensureDir(dir) {
|
|
100
|
+
mkdirSync(dir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function libraryRootFromFrom(fromDir) {
|
|
104
|
+
const root = resolve(fromDir);
|
|
105
|
+
// Accept …/library or …/library/showcases or collection root with showcases/
|
|
106
|
+
if (existsSync(join(root, 'showcases'))) return root;
|
|
107
|
+
if (existsSync(join(root, '01-login')) || existsSync(join(root, '04-pricing'))) {
|
|
108
|
+
// already showcases dir
|
|
109
|
+
return dirname(root);
|
|
110
|
+
}
|
|
111
|
+
const parent = join(root, '..');
|
|
112
|
+
if (existsSync(join(parent, 'showcases'))) return parent;
|
|
113
|
+
return root;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function localShowcaseDir(libraryRoot, entry) {
|
|
117
|
+
const candidates = [
|
|
118
|
+
join(libraryRoot, 'showcases', entry.id),
|
|
119
|
+
join(libraryRoot, entry.id),
|
|
120
|
+
join(libraryRoot, 'showcases', entry.id.replace(/^0+/, '') || entry.id),
|
|
121
|
+
];
|
|
122
|
+
for (const c of candidates) {
|
|
123
|
+
if (existsSync(join(c, 'index.html')) || existsSync(join(c, 'app.js'))) return c;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {object} entry
|
|
130
|
+
* @param {{ from?: string, baseUrl?: string }} opts
|
|
131
|
+
* @returns {Promise<{ ok: boolean, files?: Record<string,string>, error?: string, source?: string }>}
|
|
132
|
+
*/
|
|
133
|
+
export async function fetchShowcaseAssets(entry, opts = {}) {
|
|
134
|
+
const from = opts.from || process.env.VELINSTYLE_ATELIER_ROOT || '';
|
|
135
|
+
if (from) {
|
|
136
|
+
const lib = libraryRootFromFrom(from);
|
|
137
|
+
const dir = localShowcaseDir(lib, entry);
|
|
138
|
+
if (!dir) {
|
|
139
|
+
return { ok: false, error: `Local showcase not found for ${entry.id} under ${lib}` };
|
|
140
|
+
}
|
|
141
|
+
const files = {};
|
|
142
|
+
for (const name of ASSET_NAMES) {
|
|
143
|
+
const p = join(dir, name);
|
|
144
|
+
if (existsSync(p)) files[name] = readFileSync(p, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
if (!Object.keys(files).length) {
|
|
147
|
+
return { ok: false, error: `No assets in ${dir}` };
|
|
148
|
+
}
|
|
149
|
+
return { ok: true, files, source: `local:${dir}` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const base = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/?$/, '/');
|
|
153
|
+
const urlBase = `${base}showcases/${entry.id}/`;
|
|
154
|
+
const files = {};
|
|
155
|
+
for (const name of ASSET_NAMES) {
|
|
156
|
+
const url = urlBase + name;
|
|
157
|
+
try {
|
|
158
|
+
const res = await fetch(url);
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
if (name === 'index.html') {
|
|
161
|
+
return { ok: false, error: `HTTP ${res.status} fetching ${url}` };
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
files[name] = await res.text();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return { ok: false, error: `Fetch failed ${url}: ${err.message}` };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!files['index.html'] && !files['app.js']) {
|
|
171
|
+
return { ok: false, error: `No assets at ${urlBase}` };
|
|
172
|
+
}
|
|
173
|
+
return { ok: true, files, source: urlBase };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Pull one atelier entry into output directory.
|
|
178
|
+
* @param {string} query
|
|
179
|
+
* @param {{
|
|
180
|
+
* output?: string,
|
|
181
|
+
* from?: string,
|
|
182
|
+
* baseUrl?: string,
|
|
183
|
+
* format?: string,
|
|
184
|
+
* catalogPath?: string,
|
|
185
|
+
* rewriteVendor?: string,
|
|
186
|
+
* }} opts
|
|
187
|
+
*/
|
|
188
|
+
export async function pullAtelier(query, opts = {}) {
|
|
189
|
+
const resolved = resolveAtelierEntry(query, { catalogPath: opts.catalogPath });
|
|
190
|
+
if (!resolved.ok) return resolved;
|
|
191
|
+
const { entry } = resolved;
|
|
192
|
+
|
|
193
|
+
const fmt = normalizeFormat(opts.format || 'html');
|
|
194
|
+
if (!fmt.ok) return fmt;
|
|
195
|
+
|
|
196
|
+
const fetched = await fetchShowcaseAssets(entry, {
|
|
197
|
+
from: opts.from,
|
|
198
|
+
baseUrl: opts.baseUrl,
|
|
199
|
+
});
|
|
200
|
+
if (!fetched.ok) return fetched;
|
|
201
|
+
|
|
202
|
+
const outRoot = resolve(opts.output || join(process.cwd(), 'velin-atelier', entry.id));
|
|
203
|
+
ensureDir(outRoot);
|
|
204
|
+
|
|
205
|
+
const files = { ...fetched.files };
|
|
206
|
+
if (opts.rewriteVendor) {
|
|
207
|
+
const vendor = opts.rewriteVendor.replace(/\\/g, '/').replace(/\/?$/, '/');
|
|
208
|
+
for (const key of Object.keys(files)) {
|
|
209
|
+
if (typeof files[key] !== 'string') continue;
|
|
210
|
+
files[key] = files[key]
|
|
211
|
+
.replace(/\.\.\/\.\.\/vendor\//g, vendor)
|
|
212
|
+
.replace(/\.\.\/vendor\//g, vendor);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const [name, content] of Object.entries(files)) {
|
|
217
|
+
writeFileSync(join(outRoot, name), content, 'utf-8');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const shell = emitFormatShell({
|
|
221
|
+
id: entry.id,
|
|
222
|
+
title: entry.title,
|
|
223
|
+
assetDir: '.',
|
|
224
|
+
format: fmt.format,
|
|
225
|
+
});
|
|
226
|
+
if (!shell.ok) return shell;
|
|
227
|
+
for (const [name, content] of Object.entries(shell.files || {})) {
|
|
228
|
+
writeFileSync(join(outRoot, name), content, 'utf-8');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const readme = buildPullReadme({
|
|
232
|
+
id: entry.id,
|
|
233
|
+
title: entry.title,
|
|
234
|
+
format: fmt.format,
|
|
235
|
+
sourceHint: fetched.source,
|
|
236
|
+
});
|
|
237
|
+
writeFileSync(join(outRoot, 'README.md'), readme, 'utf-8');
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
ok: true,
|
|
241
|
+
entry,
|
|
242
|
+
path: outRoot,
|
|
243
|
+
format: fmt.format,
|
|
244
|
+
source: fetched.source,
|
|
245
|
+
files: [...Object.keys(files), ...Object.keys(shell.files || {}), 'README.md'],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build a composite HTML page that mounts multiple atelier showcases.
|
|
251
|
+
* @param {string[]} queries
|
|
252
|
+
* @param {{
|
|
253
|
+
* output?: string,
|
|
254
|
+
* from?: string,
|
|
255
|
+
* baseUrl?: string,
|
|
256
|
+
* catalogPath?: string,
|
|
257
|
+
* assetsSubdir?: string,
|
|
258
|
+
* }} opts
|
|
259
|
+
*/
|
|
260
|
+
export async function composeAtelierPage(queries, opts = {}) {
|
|
261
|
+
const list = resolveAtelierList(Array.isArray(queries) ? queries.join(',') : String(queries), {
|
|
262
|
+
catalogPath: opts.catalogPath,
|
|
263
|
+
});
|
|
264
|
+
if (!list.ok) return list;
|
|
265
|
+
|
|
266
|
+
const outFile = opts.output
|
|
267
|
+
? resolve(opts.output)
|
|
268
|
+
: resolve(process.cwd(), 'velin-atelier', 'compose.html');
|
|
269
|
+
const outDir = dirname(outFile);
|
|
270
|
+
const assetsSub = opts.assetsSubdir || 'atelier-assets';
|
|
271
|
+
const assetsRoot = join(outDir, assetsSub);
|
|
272
|
+
ensureDir(assetsRoot);
|
|
273
|
+
|
|
274
|
+
const sections = [];
|
|
275
|
+
const planSections = [];
|
|
276
|
+
|
|
277
|
+
for (const entry of list.entries) {
|
|
278
|
+
const fetched = await fetchShowcaseAssets(entry, {
|
|
279
|
+
from: opts.from,
|
|
280
|
+
baseUrl: opts.baseUrl,
|
|
281
|
+
});
|
|
282
|
+
if (!fetched.ok) return { ok: false, error: fetched.error, entry: entry.id };
|
|
283
|
+
|
|
284
|
+
const dest = join(assetsRoot, entry.id);
|
|
285
|
+
ensureDir(dest);
|
|
286
|
+
for (const [name, content] of Object.entries(fetched.files)) {
|
|
287
|
+
writeFileSync(join(dest, name), content, 'utf-8');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const rel = `${assetsSub}/${entry.id}`.replace(/\\/g, '/');
|
|
291
|
+
sections.push(`
|
|
292
|
+
<section class="velin-atelier-compose__block" data-atelier-id="${entry.id}" aria-label="${entry.title || entry.id}">
|
|
293
|
+
<h2 class="velin-atelier-compose__title">${entry.title || entry.id} <span class="velin-text-muted">#${entry.num}</span></h2>
|
|
294
|
+
<iframe class="velin-atelier-compose__frame" title="${entry.id}" src="${rel}/index.html" loading="lazy"></iframe>
|
|
295
|
+
</section>`);
|
|
296
|
+
planSections.push({
|
|
297
|
+
atelierId: entry.id,
|
|
298
|
+
num: entry.num,
|
|
299
|
+
title: entry.title,
|
|
300
|
+
href: entry.href,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const html = `<!DOCTYPE html>
|
|
305
|
+
<html lang="en">
|
|
306
|
+
<head>
|
|
307
|
+
<meta charset="UTF-8">
|
|
308
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
309
|
+
<title>VelinStyle Atelier compose (beta)</title>
|
|
310
|
+
<style>
|
|
311
|
+
.velin-atelier-compose__frame { width: 100%; min-height: 70vh; border: 1px solid #ccc; border-radius: 8px; }
|
|
312
|
+
.velin-atelier-compose__block { margin-block: 2rem; }
|
|
313
|
+
</style>
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
<!-- Generated by velinstyle scaffold --atelier (beta). Atelier Library compose — not Velin Studio. -->
|
|
317
|
+
<!-- Limitation: mounts original vanilla showcases; native Blade/Vue/React rewrites are planned. -->
|
|
318
|
+
<main class="velin-atelier-compose">
|
|
319
|
+
<header>
|
|
320
|
+
<h1>Atelier compose (beta)</h1>
|
|
321
|
+
<p>Composed from Library ids: ${list.entries.map((e) => e.id).join(', ')}. Studio Builder remains planned.</p>
|
|
322
|
+
</header>
|
|
323
|
+
${sections.join('\n')}
|
|
324
|
+
</main>
|
|
325
|
+
</body>
|
|
326
|
+
</html>
|
|
327
|
+
`;
|
|
328
|
+
|
|
329
|
+
ensureDir(outDir);
|
|
330
|
+
writeFileSync(outFile, html, 'utf-8');
|
|
331
|
+
writeFileSync(
|
|
332
|
+
join(outDir, 'README.md'),
|
|
333
|
+
buildPullReadme({
|
|
334
|
+
id: list.entries.map((e) => e.id).join('+'),
|
|
335
|
+
title: 'Atelier compose (beta)',
|
|
336
|
+
format: 'html',
|
|
337
|
+
sourceHint: 'scaffold --atelier',
|
|
338
|
+
}),
|
|
339
|
+
'utf-8',
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
ok: true,
|
|
344
|
+
path: outFile,
|
|
345
|
+
entries: list.entries,
|
|
346
|
+
planSections,
|
|
347
|
+
mode: 'atelier-compose',
|
|
348
|
+
html,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Plan payload when --atelier is set.
|
|
354
|
+
* @param {string} listStr
|
|
355
|
+
* @param {{ catalogPath?: string, prompt?: string }} opts
|
|
356
|
+
*/
|
|
357
|
+
export function planFromAtelierList(listStr, opts = {}) {
|
|
358
|
+
const list = resolveAtelierList(listStr, { catalogPath: opts.catalogPath });
|
|
359
|
+
if (!list.ok) return list;
|
|
360
|
+
return {
|
|
361
|
+
ok: true,
|
|
362
|
+
analysis: {
|
|
363
|
+
intent: 'atelier-compose',
|
|
364
|
+
confidence: 'high',
|
|
365
|
+
pageId: 'atelier-compose',
|
|
366
|
+
prompt: opts.prompt || '',
|
|
367
|
+
},
|
|
368
|
+
plan: {
|
|
369
|
+
page: { id: 'atelier-compose', title: 'Atelier compose (beta)' },
|
|
370
|
+
sections: list.entries.map((e) => ({
|
|
371
|
+
id: e.id,
|
|
372
|
+
atelierId: e.id,
|
|
373
|
+
num: e.num,
|
|
374
|
+
title: e.title,
|
|
375
|
+
blueprint: null,
|
|
376
|
+
source: 'atelier-library',
|
|
377
|
+
})),
|
|
378
|
+
maturity: 'beta',
|
|
379
|
+
note: 'Composes Atelier Library showcases. Velin Studio Builder is planned — not shipped.',
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
<button type="button" class="velin-btn velin-btn--primary">Create item</button>
|
|
5
|
-
</section>
|
|
1
|
+
<velin-empty-state heading="No items yet" description="Create your first entry to see it listed here.">
|
|
2
|
+
<button type="button" class="velin-btn velin-btn--primary" slot="actions">Create item</button>
|
|
3
|
+
</velin-empty-state>
|
package/cli/cli-manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.
|
|
2
|
+
"version": "1.2.2",
|
|
3
3
|
"commands": [
|
|
4
4
|
{
|
|
5
5
|
"name": "init",
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"name": "build",
|
|
12
|
-
"summary": "Build custom CSS with selected layers from config",
|
|
13
|
-
"flags": ["--output", "-o", "--minify", "--preset"],
|
|
14
|
-
"example": "velinstyle build --
|
|
12
|
+
"summary": "Build custom CSS with selected layers from config, or production output with --production",
|
|
13
|
+
"flags": ["--output", "-o", "--out", "--minify", "--preset", "--production", "--explain", "--watch", "--report", "--safelist"],
|
|
14
|
+
"example": "velinstyle build --production --explain -o ./dist/velin-production"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "production",
|
|
18
|
+
"summary": "Content-aware production builder (CSS/JS/themes/icons/fonts/motion + report)",
|
|
19
|
+
"flags": ["--out", "-o", "--explain", "--watch", "--report", "--safelist", "--no-js", "--no-icons", "--no-themes", "--no-motion", "--no-minify"],
|
|
20
|
+
"example": "velinstyle production . --explain --report ./report.json"
|
|
15
21
|
},
|
|
16
22
|
{
|
|
17
23
|
"name": "themes",
|
|
@@ -78,16 +84,23 @@
|
|
|
78
84
|
},
|
|
79
85
|
{
|
|
80
86
|
"name": "scaffold",
|
|
81
|
-
"summary": "Plan-first page HTML
|
|
82
|
-
"flags": ["--output", "-o", "--json"],
|
|
87
|
+
"summary": "Plan-first page HTML, recipe fragment, or Atelier Library compose (--atelier, beta)",
|
|
88
|
+
"flags": ["--output", "-o", "--json", "--atelier", "--from", "--base-url"],
|
|
83
89
|
"subcommands": ["list-intents"],
|
|
84
|
-
"example": "velinstyle scaffold
|
|
90
|
+
"example": "velinstyle scaffold --atelier 04,07 -o compose.html --from ./atelier/library"
|
|
85
91
|
},
|
|
86
92
|
{
|
|
87
93
|
"name": "plan",
|
|
88
|
-
"summary": "Emit page plan JSON from a prompt (
|
|
89
|
-
"flags": ["--output", "-o", "--json"],
|
|
90
|
-
"example": "velinstyle plan
|
|
94
|
+
"summary": "Emit page plan JSON from a prompt or --atelier Library ids (beta)",
|
|
95
|
+
"flags": ["--output", "-o", "--json", "--atelier"],
|
|
96
|
+
"example": "velinstyle plan --atelier 04,07"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "atelier",
|
|
100
|
+
"summary": "Pull curated Atelier Library showcase by number/id; blade/vue/react = wrappers only",
|
|
101
|
+
"flags": ["--output", "-o", "--format", "--from", "--base-url", "--rewrite-vendor"],
|
|
102
|
+
"subcommands": ["list"],
|
|
103
|
+
"example": "velinstyle atelier 36 -o ./velin-atelier/36-calendar --format html"
|
|
91
104
|
},
|
|
92
105
|
{
|
|
93
106
|
"name": "review",
|
|
@@ -9,7 +9,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
export const REGISTERED_COMMANDS = [
|
|
10
10
|
'init', 'build', 'themes', 'add', 'icons', 'blueprint', 'create', 'serve', 'doctor', 'check', 'tokens',
|
|
11
11
|
'scan', 'prefix', 'scaffold', 'plan', 'review', 'layout', 'perf', 'docs', 'meta',
|
|
12
|
-
'search', 'skills', 'workflow', 'wc',
|
|
12
|
+
'search', 'skills', 'workflow', 'wc', 'production', 'atelier',
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
export function loadCliManifest() {
|