@hobui/viui-cli 0.0.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.md +136 -0
- package/dist/assets/cursor/.design-system-version +1 -0
- package/dist/assets/cursor/commands/audit-accessibility.md +25 -0
- package/dist/assets/cursor/commands/audit-ui.md +35 -0
- package/dist/assets/cursor/commands/component.md +18 -0
- package/dist/assets/cursor/commands/fix-storybook.md +24 -0
- package/dist/assets/cursor/commands/generate-component-from-figma.md +26 -0
- package/dist/assets/cursor/commands/generate-page-from-figma.md +26 -0
- package/dist/assets/cursor/plans/DESIGN_SYSTEM_PLAN.md +177 -0
- package/dist/assets/cursor/plans/PLANS_INDEX.md +35 -0
- package/dist/assets/cursor/rules/accessibility-contrast.mdc +38 -0
- package/dist/assets/cursor/rules/bem-class-style.mdc +107 -0
- package/dist/assets/cursor/rules/component-naming.mdc +57 -0
- package/dist/assets/cursor/rules/design-system-component-library.mdc +59 -0
- package/dist/assets/cursor/rules/design-system-workflow.mdc +48 -0
- package/dist/assets/cursor/rules/figma-mapping.mdc +37 -0
- package/dist/assets/cursor/rules/icons.mdc +42 -0
- package/dist/assets/cursor/rules/project-structure.mdc +137 -0
- package/dist/assets/cursor/rules/storybook-component-template.mdc +103 -0
- package/dist/assets/cursor/rules/storybook.mdc +68 -0
- package/dist/assets/cursor/rules/tokens.mdc +32 -0
- package/dist/assets/cursor/rules/viui-themes.mdc +53 -0
- package/dist/assets/cursor/rules/vuetify-layout.mdc +52 -0
- package/dist/assets/cursor/skills/accessibility.md +75 -0
- package/dist/assets/cursor/skills/design-system-thinking.md +40 -0
- package/dist/assets/cursor/skills/figma-interpretation.md +38 -0
- package/dist/assets/cursor/skills/vue-vuetify-design-system-architect.md +60 -0
- package/dist/assets/cursor/sync-manifest.json +6 -0
- package/dist/assets/plugins/viui-conf/defaults/README.md +27 -0
- package/dist/assets/plugins/viui-conf/defaults/alerts.ts +13 -0
- package/dist/assets/plugins/viui-conf/defaults/app-bar.ts +14 -0
- package/dist/assets/plugins/viui-conf/defaults/avatars.ts +14 -0
- package/dist/assets/plugins/viui-conf/defaults/buttons.ts +15 -0
- package/dist/assets/plugins/viui-conf/defaults/by-theme/index.ts +30 -0
- package/dist/assets/plugins/viui-conf/defaults/by-theme/material.ts +15 -0
- package/dist/assets/plugins/viui-conf/defaults/by-theme/minimalist-2.ts +15 -0
- package/dist/assets/plugins/viui-conf/defaults/by-theme/neo-brutalism.ts +15 -0
- package/dist/assets/plugins/viui-conf/defaults/cards.ts +12 -0
- package/dist/assets/plugins/viui-conf/defaults/chips.ts +15 -0
- package/dist/assets/plugins/viui-conf/defaults/data-tables.ts +11 -0
- package/dist/assets/plugins/viui-conf/defaults/dialogs.ts +13 -0
- package/dist/assets/plugins/viui-conf/defaults/global.ts +12 -0
- package/dist/assets/plugins/viui-conf/defaults/index.ts +93 -0
- package/dist/assets/plugins/viui-conf/defaults/inputs.ts +42 -0
- package/dist/assets/plugins/viui-conf/defaults/lists.ts +17 -0
- package/dist/assets/plugins/viui-conf/defaults/main.ts +12 -0
- package/dist/assets/plugins/viui-conf/defaults/menus.ts +14 -0
- package/dist/assets/plugins/viui-conf/defaults/navigation-drawer.ts +14 -0
- package/dist/assets/plugins/viui-conf/defaults/pagination.ts +13 -0
- package/dist/assets/plugins/viui-conf/defaults/snackbars.ts +13 -0
- package/dist/assets/plugins/viui-conf/theme-base.ts +34 -0
- package/dist/assets/plugins/viui-conf/v-dark.ts +38 -0
- package/dist/assets/plugins/viui-conf/v-light.ts +41 -0
- package/dist/assets/plugins/vuetifies/defaults/buttons.ts +15 -0
- package/dist/assets/plugins/vuetifies/defaults/cards.ts +12 -0
- package/dist/assets/plugins/vuetifies/defaults/global.ts +12 -0
- package/dist/assets/plugins/vuetifies/defaults/index.ts +45 -0
- package/dist/assets/plugins/vuetifies/defaults/inputs.ts +42 -0
- package/dist/assets/plugins/vuetifies/defaults/lists.ts +17 -0
- package/dist/assets/plugins/vuetifies/theme-base.ts +34 -0
- package/dist/assets/plugins/vuetifies/v-dark.ts +38 -0
- package/dist/assets/plugins/vuetifies/v-light.ts +41 -0
- package/dist/assets/plugins/vuetify-defaults/buttons.ts +15 -0
- package/dist/assets/plugins/vuetify-defaults/cards.ts +12 -0
- package/dist/assets/plugins/vuetify-defaults/global.ts +12 -0
- package/dist/assets/plugins/vuetify-defaults/index.ts +45 -0
- package/dist/assets/plugins/vuetify-defaults/inputs.ts +42 -0
- package/dist/assets/plugins/vuetify-defaults/lists.ts +17 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +402 -0
- package/package.json +27 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iNET Design System — Vuetify defaults cho Cards (VCard, VCardText, VCardActions).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { VuetifyOptions } from 'vuetify'
|
|
6
|
+
|
|
7
|
+
export const cardDefaults: NonNullable<VuetifyOptions['defaults']> = {
|
|
8
|
+
VCard: {
|
|
9
|
+
elevation: 1,
|
|
10
|
+
rounded: 'lg',
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iNET Design System — Vuetify global defaults.
|
|
3
|
+
* Áp cho mọi component; dùng design tokens qua theme (colors, border-radius-root).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Global defaults (ripple, density) — class/style không dùng trong global. */
|
|
7
|
+
export const globalDefaults: Record<string, unknown> = {
|
|
8
|
+
// Ripple: bật mặc định theo Material; tắt nếu design system yêu cầu giảm motion
|
|
9
|
+
ripple: true,
|
|
10
|
+
// Density: 'default' | 'comfortable' | 'compact'
|
|
11
|
+
density: 'default',
|
|
12
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iNET Design System — Vuetify defaults (global + từng element).
|
|
3
|
+
* Merge tất cả và export cho createVuetify({ defaults }).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VuetifyOptions } from 'vuetify'
|
|
7
|
+
import { globalDefaults } from './global'
|
|
8
|
+
import { buttonDefaults } from './buttons'
|
|
9
|
+
import { cardDefaults } from './cards'
|
|
10
|
+
import { inputDefaults } from './inputs'
|
|
11
|
+
import { listDefaults } from './lists'
|
|
12
|
+
|
|
13
|
+
type DefaultsConfig = NonNullable<VuetifyOptions['defaults']>
|
|
14
|
+
|
|
15
|
+
function mergeDefaults (...sources: DefaultsConfig[]): DefaultsConfig {
|
|
16
|
+
const result: Record<string, unknown> = { global: { ...globalDefaults } }
|
|
17
|
+
for (const src of sources) {
|
|
18
|
+
if (!src) continue
|
|
19
|
+
const srcObj = src as Record<string, unknown>
|
|
20
|
+
if (srcObj.global && typeof srcObj.global === 'object') {
|
|
21
|
+
result.global = { ...(result.global as Record<string, unknown>), ...srcObj.global }
|
|
22
|
+
}
|
|
23
|
+
for (const key of Object.keys(srcObj)) {
|
|
24
|
+
if (key === 'global') continue
|
|
25
|
+
const existing = result[key] as Record<string, unknown> | undefined
|
|
26
|
+
const incoming = srcObj[key] as Record<string, unknown> | undefined
|
|
27
|
+
result[key] = existing && incoming ? { ...existing, ...incoming } : (incoming ?? existing)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result as DefaultsConfig
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const defaults: DefaultsConfig = mergeDefaults(
|
|
34
|
+
{ global: globalDefaults },
|
|
35
|
+
buttonDefaults,
|
|
36
|
+
cardDefaults,
|
|
37
|
+
inputDefaults,
|
|
38
|
+
listDefaults,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export { globalDefaults } from './global'
|
|
42
|
+
export { buttonDefaults } from './buttons'
|
|
43
|
+
export { cardDefaults } from './cards'
|
|
44
|
+
export { inputDefaults } from './inputs'
|
|
45
|
+
export { listDefaults } from './lists'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iNET Design System — Vuetify defaults cho form inputs:
|
|
3
|
+
* VTextField, VTextarea, VSelect, VAutocomplete, VCheckbox, VRadioGroup, VSwitch.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VuetifyOptions } from 'vuetify'
|
|
7
|
+
|
|
8
|
+
export const inputDefaults: NonNullable<VuetifyOptions['defaults']> = {
|
|
9
|
+
VTextField: {
|
|
10
|
+
variant: 'outlined',
|
|
11
|
+
density: 'default',
|
|
12
|
+
hideDetails: 'auto',
|
|
13
|
+
rounded: 'lg',
|
|
14
|
+
},
|
|
15
|
+
VTextarea: {
|
|
16
|
+
variant: 'outlined',
|
|
17
|
+
density: 'default',
|
|
18
|
+
hideDetails: 'auto',
|
|
19
|
+
rounded: 'lg',
|
|
20
|
+
},
|
|
21
|
+
VSelect: {
|
|
22
|
+
variant: 'outlined',
|
|
23
|
+
density: 'default',
|
|
24
|
+
hideDetails: 'auto',
|
|
25
|
+
rounded: 'lg',
|
|
26
|
+
},
|
|
27
|
+
VAutocomplete: {
|
|
28
|
+
variant: 'outlined',
|
|
29
|
+
density: 'default',
|
|
30
|
+
hideDetails: 'auto',
|
|
31
|
+
rounded: 'lg',
|
|
32
|
+
},
|
|
33
|
+
VCheckbox: {
|
|
34
|
+
hideDetails: true,
|
|
35
|
+
},
|
|
36
|
+
VRadioGroup: {
|
|
37
|
+
hideDetails: 'auto',
|
|
38
|
+
},
|
|
39
|
+
VSwitch: {
|
|
40
|
+
hideDetails: true,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iNET Design System — Vuetify defaults cho Lists (VList, VListItem, VListGroup).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { VuetifyOptions } from 'vuetify'
|
|
6
|
+
|
|
7
|
+
export const listDefaults: NonNullable<VuetifyOptions['defaults']> = {
|
|
8
|
+
VList: {
|
|
9
|
+
density: 'default',
|
|
10
|
+
},
|
|
11
|
+
VListItem: {
|
|
12
|
+
density: 'default',
|
|
13
|
+
},
|
|
14
|
+
VListGroup: {
|
|
15
|
+
density: 'default',
|
|
16
|
+
},
|
|
17
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @viui/cli — Sync .cursor (rules, skills, commands) from i-design-system into consumer repo.
|
|
4
|
+
* Commands: init, sync, setup, version.
|
|
5
|
+
* After `pnpm add @viui/cli`, postinstall runs `setup --postinstall` for step-by-step UX.
|
|
6
|
+
*/
|
|
7
|
+
import { parseArgs } from 'node:util';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import readline from 'node:readline/promises';
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
16
|
+
function getAssetsCursorDir() {
|
|
17
|
+
return path.join(__dirname, 'assets', 'cursor');
|
|
18
|
+
}
|
|
19
|
+
/** viui-conf bundle path (synced to consumer src/plugins/viui-conf). */
|
|
20
|
+
function getAssetsPluginsDir() {
|
|
21
|
+
return path.join(__dirname, 'assets', 'plugins');
|
|
22
|
+
}
|
|
23
|
+
function loadManifest(assetsDir) {
|
|
24
|
+
const p = path.join(assetsDir, 'sync-manifest.json');
|
|
25
|
+
if (!fs.existsSync(p))
|
|
26
|
+
return {};
|
|
27
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
function getDesignSystemVersion(assetsDir) {
|
|
30
|
+
const p = path.join(assetsDir, '.design-system-version');
|
|
31
|
+
if (!fs.existsSync(p))
|
|
32
|
+
return '0.0.0';
|
|
33
|
+
return fs.readFileSync(p, 'utf8').trim();
|
|
34
|
+
}
|
|
35
|
+
function copyRecursive(src, dest, opts) {
|
|
36
|
+
if (!fs.existsSync(src))
|
|
37
|
+
return;
|
|
38
|
+
if (!opts.dryRun)
|
|
39
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
40
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
const srcPath = path.join(src, e.name);
|
|
43
|
+
const destPath = path.join(dest, e.name);
|
|
44
|
+
if (e.isDirectory()) {
|
|
45
|
+
copyRecursive(srcPath, destPath, opts);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (opts.dryRun) {
|
|
49
|
+
console.log(`Would copy: ${path.relative(process.cwd(), destPath) || destPath}`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (opts.backup && fs.existsSync(destPath)) {
|
|
53
|
+
const backupPath = destPath + '.backup.' + Date.now();
|
|
54
|
+
fs.copyFileSync(destPath, backupPath);
|
|
55
|
+
}
|
|
56
|
+
fs.copyFileSync(srcPath, destPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* List all relative paths under dir (files and dirs), with forward slashes.
|
|
63
|
+
*/
|
|
64
|
+
function listRelativePaths(dir, prefix = '') {
|
|
65
|
+
if (!fs.existsSync(dir))
|
|
66
|
+
return [];
|
|
67
|
+
const out = [];
|
|
68
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
69
|
+
for (const e of entries) {
|
|
70
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
71
|
+
out.push(rel);
|
|
72
|
+
if (e.isDirectory()) {
|
|
73
|
+
out.push(...listRelativePaths(path.join(dir, e.name), rel));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove from destDir any path that does not exist in sourceSet (mirror bundle).
|
|
80
|
+
* Deletes deeper paths first so dirs can be removed after their contents.
|
|
81
|
+
*/
|
|
82
|
+
function removePathsNotInSource(destDir, sourceSet, opts) {
|
|
83
|
+
if (!fs.existsSync(destDir))
|
|
84
|
+
return;
|
|
85
|
+
const destPaths = listRelativePaths(destDir);
|
|
86
|
+
const sorted = destPaths.sort((a, b) => b.split('/').length - a.split('/').length);
|
|
87
|
+
for (const rel of sorted) {
|
|
88
|
+
const relNorm = rel.replace(/\\/g, '/');
|
|
89
|
+
if (sourceSet.has(relNorm))
|
|
90
|
+
continue;
|
|
91
|
+
const destPath = path.join(destDir, relNorm);
|
|
92
|
+
if (!fs.existsSync(destPath))
|
|
93
|
+
continue;
|
|
94
|
+
if (opts.dryRun) {
|
|
95
|
+
console.log(`Would remove: ${path.relative(process.cwd(), destPath) || destPath}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
fs.rmSync(destPath, { recursive: true });
|
|
99
|
+
console.log('Removed:', path.relative(process.cwd(), destPath) || destPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function runInit(cwd, assetsDir, opts) {
|
|
104
|
+
const manifest = loadManifest(assetsDir);
|
|
105
|
+
const version = getDesignSystemVersion(assetsDir);
|
|
106
|
+
const cursorDir = path.join(cwd, '.cursor');
|
|
107
|
+
const dirs = ['rules', 'skills', 'commands'];
|
|
108
|
+
if (!opts.noPlans)
|
|
109
|
+
dirs.push('plans');
|
|
110
|
+
if (opts.dryRun) {
|
|
111
|
+
console.log('Dry run: would create .cursor and copy:');
|
|
112
|
+
for (const key of Object.keys(manifest)) {
|
|
113
|
+
if (key === 'plans' && opts.noPlans)
|
|
114
|
+
continue;
|
|
115
|
+
const src = path.join(assetsDir, manifest[key].dir);
|
|
116
|
+
if (fs.existsSync(src)) {
|
|
117
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
118
|
+
entries.forEach(e => console.log(` .cursor/${manifest[key].dir}/${e.name}`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log(` .cursor/.design-system-version (${version})`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!fs.existsSync(cursorDir))
|
|
125
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
126
|
+
for (const d of dirs) {
|
|
127
|
+
const sub = path.join(cursorDir, d);
|
|
128
|
+
if (!fs.existsSync(sub))
|
|
129
|
+
fs.mkdirSync(sub, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
for (const key of Object.keys(manifest)) {
|
|
132
|
+
if (key === 'plans' && opts.noPlans)
|
|
133
|
+
continue;
|
|
134
|
+
const src = path.join(assetsDir, manifest[key].dir);
|
|
135
|
+
const dest = path.join(cursorDir, manifest[key].dir);
|
|
136
|
+
if (fs.existsSync(src))
|
|
137
|
+
copyRecursive(src, dest, { dryRun: false });
|
|
138
|
+
}
|
|
139
|
+
fs.writeFileSync(path.join(cursorDir, '.design-system-version'), version, 'utf8');
|
|
140
|
+
console.log('Initialized .cursor with design system version', version);
|
|
141
|
+
// Sync viui-conf (theme + defaults) — trừ vuetify.ts
|
|
142
|
+
const viuiConfSrc = path.join(getAssetsPluginsDir(), 'viui-conf');
|
|
143
|
+
const viuiConfDest = path.join(cwd, 'src', 'plugins', 'viui-conf');
|
|
144
|
+
if (fs.existsSync(viuiConfSrc)) {
|
|
145
|
+
if (opts.dryRun) {
|
|
146
|
+
console.log('Would sync viui-conf to src/plugins/viui-conf');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
if (!fs.existsSync(path.join(cwd, 'src', 'plugins')))
|
|
150
|
+
fs.mkdirSync(path.join(cwd, 'src', 'plugins'), { recursive: true });
|
|
151
|
+
copyRecursive(viuiConfSrc, viuiConfDest, { dryRun: false });
|
|
152
|
+
}
|
|
153
|
+
if (!opts.dryRun)
|
|
154
|
+
console.log('Synced viui-conf to src/plugins/viui-conf');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function runSync(cwd, assetsDir, opts) {
|
|
158
|
+
const manifest = loadManifest(assetsDir);
|
|
159
|
+
const version = getDesignSystemVersion(assetsDir);
|
|
160
|
+
const cursorDir = path.join(cwd, '.cursor');
|
|
161
|
+
console.log('Syncing .cursor at:', path.resolve(cursorDir));
|
|
162
|
+
let keys = Object.keys(manifest);
|
|
163
|
+
if (opts.rulesOnly)
|
|
164
|
+
keys = keys.filter(k => k === 'rules');
|
|
165
|
+
if (opts.skillsOnly)
|
|
166
|
+
keys = keys.filter(k => k === 'skills');
|
|
167
|
+
if (opts.commandsOnly)
|
|
168
|
+
keys = keys.filter(k => k === 'commands');
|
|
169
|
+
if (opts.dryRun) {
|
|
170
|
+
console.log('Dry run: would overwrite:');
|
|
171
|
+
for (const key of keys) {
|
|
172
|
+
const src = path.join(assetsDir, manifest[key].dir);
|
|
173
|
+
if (fs.existsSync(src)) {
|
|
174
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
175
|
+
entries.forEach(e => console.log(` .cursor/${manifest[key].dir}/${e.name}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
console.log(` .cursor/.design-system-version (${version})`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!fs.existsSync(cursorDir))
|
|
182
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
183
|
+
for (const key of keys) {
|
|
184
|
+
const src = path.join(assetsDir, manifest[key].dir);
|
|
185
|
+
const dest = path.join(cursorDir, manifest[key].dir);
|
|
186
|
+
if (fs.existsSync(src)) {
|
|
187
|
+
if (!fs.existsSync(dest))
|
|
188
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
189
|
+
copyRecursive(src, dest, { dryRun: opts.dryRun, backup: opts.backup });
|
|
190
|
+
const sourceRelPaths = listRelativePaths(src).map(p => p.replace(/\\/g, '/'));
|
|
191
|
+
const sourceSet = new Set(sourceRelPaths);
|
|
192
|
+
removePathsNotInSource(dest, sourceSet, { dryRun: opts.dryRun });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
fs.writeFileSync(path.join(cursorDir, '.design-system-version'), version, 'utf8');
|
|
196
|
+
console.log('Synced .cursor with design system version', version);
|
|
197
|
+
// Sync viui-conf (theme + defaults) — trừ vuetify.ts
|
|
198
|
+
const viuiConfSrc = path.join(getAssetsPluginsDir(), 'viui-conf');
|
|
199
|
+
const viuiConfDest = path.join(cwd, 'src', 'plugins', 'viui-conf');
|
|
200
|
+
if (fs.existsSync(viuiConfSrc)) {
|
|
201
|
+
if (opts.dryRun) {
|
|
202
|
+
console.log('Would sync viui-conf to src/plugins/viui-conf');
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
if (!fs.existsSync(path.join(cwd, 'src', 'plugins')))
|
|
206
|
+
fs.mkdirSync(path.join(cwd, 'src', 'plugins'), { recursive: true });
|
|
207
|
+
copyRecursive(viuiConfSrc, viuiConfDest, { dryRun: false, backup: opts.backup });
|
|
208
|
+
console.log('Synced viui-conf to src/plugins/viui-conf');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function detectPackageManager(cwd) {
|
|
213
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
|
|
214
|
+
return 'pnpm';
|
|
215
|
+
if (fs.existsSync(path.join(cwd, 'yarn.lock')))
|
|
216
|
+
return 'yarn';
|
|
217
|
+
return 'npm';
|
|
218
|
+
}
|
|
219
|
+
function addPostinstallScript(cwd, pm) {
|
|
220
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
221
|
+
if (!fs.existsSync(pkgPath))
|
|
222
|
+
return false;
|
|
223
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
224
|
+
const scripts = pkg.scripts ?? {};
|
|
225
|
+
const cmd = pm === 'pnpm' ? 'pnpm exec viui-cli sync' : pm === 'yarn' ? 'yarn viui-cli sync' : 'npx viui-cli sync';
|
|
226
|
+
if (scripts.postinstall?.includes('viui-cli'))
|
|
227
|
+
return false;
|
|
228
|
+
scripts.postinstall = scripts.postinstall ? `${scripts.postinstall} && ${cmd}` : cmd;
|
|
229
|
+
pkg.scripts = scripts;
|
|
230
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
async function runSetup(cwd, assetsDir, opts) {
|
|
234
|
+
const isPostinstall = opts.postinstall === true;
|
|
235
|
+
if (isPostinstall && !process.stdin.isTTY) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!fs.existsSync(assetsDir)) {
|
|
239
|
+
if (isPostinstall)
|
|
240
|
+
return;
|
|
241
|
+
console.error('Assets not found. Run "pnpm build" in packages/cli first.');
|
|
242
|
+
throw new Error('Assets not found');
|
|
243
|
+
}
|
|
244
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
245
|
+
const ask = async (q, defaultYes) => {
|
|
246
|
+
const def = defaultYes ? 'Y/n' : 'y/N';
|
|
247
|
+
const a = await rl.question(`${q} (${def}): `);
|
|
248
|
+
const s = a.trim().toLowerCase();
|
|
249
|
+
if (s === '')
|
|
250
|
+
return defaultYes;
|
|
251
|
+
return s === 'y' || s === 'yes';
|
|
252
|
+
};
|
|
253
|
+
console.log('\n@viui/cli — Setup i-design-system in this repo\n');
|
|
254
|
+
const runNow = opts.yes || (await ask('Run setup now?', true));
|
|
255
|
+
if (!runNow) {
|
|
256
|
+
console.log('Skipped. Run later: pnpm exec viui-cli setup');
|
|
257
|
+
rl.close();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const cursorDir = path.join(cwd, '.cursor');
|
|
261
|
+
const hasCursor = fs.existsSync(cursorDir);
|
|
262
|
+
if (!hasCursor) {
|
|
263
|
+
console.log('Step 1: Initializing .cursor (rules, plans) and src/plugins/viui-conf...');
|
|
264
|
+
runInit(cwd, assetsDir, { yes: true, dryRun: false, noPlans: false });
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const doSync = opts.yes || (await ask('Step 1: .cursor exists. Sync from design system?', true));
|
|
268
|
+
if (doSync) {
|
|
269
|
+
runSync(cwd, assetsDir, { dryRun: false, backup: false });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const pm = detectPackageManager(cwd);
|
|
273
|
+
const doWeb = opts.yes || (await ask('Step 2: Install @viui/web for Vi* components?', true));
|
|
274
|
+
if (doWeb) {
|
|
275
|
+
const { execSync } = await import('node:child_process');
|
|
276
|
+
const addCmd = pm === 'pnpm' ? 'pnpm add @viui/web' : pm === 'yarn' ? 'yarn add @viui/web' : 'npm install @viui/web';
|
|
277
|
+
console.log(`Running: ${addCmd}`);
|
|
278
|
+
execSync(addCmd, { cwd, stdio: 'inherit' });
|
|
279
|
+
}
|
|
280
|
+
const doPostinstall = !isPostinstall && (opts.yes || (await ask('Step 3: Add postinstall to auto-sync on install?', false)));
|
|
281
|
+
if (doPostinstall) {
|
|
282
|
+
if (addPostinstallScript(cwd, pm)) {
|
|
283
|
+
console.log('Added postinstall script to package.json.');
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
console.log('postinstall already contains viui-cli or package.json not found.');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
console.log('\nDone. Use: import { ViButton, ViInput, ... } from \'@viui/web\'');
|
|
290
|
+
rl.close();
|
|
291
|
+
}
|
|
292
|
+
const result = parseArgs({
|
|
293
|
+
options: {
|
|
294
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
295
|
+
version: { type: 'boolean', short: 'v', default: false },
|
|
296
|
+
yes: { type: 'boolean', short: 'y', default: false },
|
|
297
|
+
postinstall: { type: 'boolean', default: false },
|
|
298
|
+
'dry-run': { type: 'boolean', default: false },
|
|
299
|
+
'no-plans': { type: 'boolean', default: false },
|
|
300
|
+
backup: { type: 'boolean', default: false },
|
|
301
|
+
'rules-only': { type: 'boolean', default: false },
|
|
302
|
+
'skills-only': { type: 'boolean', default: false },
|
|
303
|
+
'commands-only': { type: 'boolean', default: false },
|
|
304
|
+
},
|
|
305
|
+
allowPositionals: true,
|
|
306
|
+
});
|
|
307
|
+
const { values } = result;
|
|
308
|
+
const positionals = result.positionals ?? [];
|
|
309
|
+
const [command] = positionals;
|
|
310
|
+
if (values.version && !command) {
|
|
311
|
+
console.log(`@viui/cli ${pkg.version}`);
|
|
312
|
+
const cwd = process.cwd();
|
|
313
|
+
const versionFile = path.join(cwd, '.cursor', '.design-system-version');
|
|
314
|
+
if (fs.existsSync(versionFile)) {
|
|
315
|
+
console.log('Design system (synced):', fs.readFileSync(versionFile, 'utf8').trim());
|
|
316
|
+
}
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
if (values.help || !command || command === 'help') {
|
|
320
|
+
console.log(`
|
|
321
|
+
@viui/cli — Sync design system .cursor into your repo
|
|
322
|
+
|
|
323
|
+
Usage: viui-cli <command> [options]
|
|
324
|
+
|
|
325
|
+
Commands:
|
|
326
|
+
init Create .cursor and copy rules, skills, commands (run once at repo root)
|
|
327
|
+
sync Update .cursor from design system (overwrite)
|
|
328
|
+
setup Interactive setup: init/sync .cursor, install @viui/web, optional postinstall (runs after pnpm add @viui/cli)
|
|
329
|
+
version Print CLI and design system version
|
|
330
|
+
|
|
331
|
+
Setup options:
|
|
332
|
+
-y, --yes Non-interactive: run all steps (init/sync, add @viui/web, skip postinstall prompt)
|
|
333
|
+
--postinstall Used by postinstall hook; skips wizard when not TTY (e.g. CI)
|
|
334
|
+
|
|
335
|
+
Init options:
|
|
336
|
+
-y, --yes Skip confirmation
|
|
337
|
+
--dry-run Print what would be copied
|
|
338
|
+
--no-plans Do not sync plans
|
|
339
|
+
|
|
340
|
+
Sync options:
|
|
341
|
+
--dry-run Print what would be overwritten
|
|
342
|
+
--backup Backup existing files before overwrite
|
|
343
|
+
--rules-only Sync only rules
|
|
344
|
+
--skills-only Sync only skills
|
|
345
|
+
--commands-only Sync only commands
|
|
346
|
+
|
|
347
|
+
Global:
|
|
348
|
+
-h, --help Show this help
|
|
349
|
+
-v, --version Print version
|
|
350
|
+
|
|
351
|
+
One-command UX: pnpm add @viui/cli — then follow the setup steps. Or run: viui-cli setup
|
|
352
|
+
`);
|
|
353
|
+
process.exit(0);
|
|
354
|
+
}
|
|
355
|
+
if (command === 'version') {
|
|
356
|
+
console.log(`@viui/cli ${pkg.version}`);
|
|
357
|
+
const cwd = process.cwd();
|
|
358
|
+
const versionFile = path.join(cwd, '.cursor', '.design-system-version');
|
|
359
|
+
if (fs.existsSync(versionFile)) {
|
|
360
|
+
console.log('Design system (synced):', fs.readFileSync(versionFile, 'utf8').trim());
|
|
361
|
+
}
|
|
362
|
+
process.exit(0);
|
|
363
|
+
}
|
|
364
|
+
const assetsDir = getAssetsCursorDir();
|
|
365
|
+
const cwd = process.cwd();
|
|
366
|
+
if (!fs.existsSync(assetsDir) && command !== 'setup') {
|
|
367
|
+
console.error('Assets not found. Run "pnpm build" in packages/cli first.');
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
if (command === 'init') {
|
|
371
|
+
runInit(cwd, assetsDir, {
|
|
372
|
+
yes: values.yes,
|
|
373
|
+
dryRun: values['dry-run'],
|
|
374
|
+
noPlans: values['no-plans'],
|
|
375
|
+
});
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
if (command === 'sync') {
|
|
379
|
+
runSync(cwd, assetsDir, {
|
|
380
|
+
dryRun: values['dry-run'],
|
|
381
|
+
backup: values.backup,
|
|
382
|
+
rulesOnly: values['rules-only'],
|
|
383
|
+
skillsOnly: values['skills-only'],
|
|
384
|
+
commandsOnly: values['commands-only'],
|
|
385
|
+
});
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
if (command === 'setup') {
|
|
389
|
+
runSetup(cwd, assetsDir, {
|
|
390
|
+
postinstall: values.postinstall,
|
|
391
|
+
yes: values.yes,
|
|
392
|
+
})
|
|
393
|
+
.then(() => process.exit(0))
|
|
394
|
+
.catch((err) => {
|
|
395
|
+
console.error(err);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
console.error(`Unknown command: ${command}. Use --help for usage.`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hobui/viui-cli",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CLI to sync i-design-system .cursor (rules, skills, commands) into consumer repos",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"bin": {
|
|
10
|
+
"viui-cli": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/node": "^22.0.0",
|
|
14
|
+
"typescript": "~5.9.3"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"postinstall": "node scripts/postinstall-check.cjs",
|
|
24
|
+
"build": "tsc && node scripts/copy-cursor.cjs",
|
|
25
|
+
"dev": "tsc --watch"
|
|
26
|
+
}
|
|
27
|
+
}
|