@morya-ui/setup 0.2.6 → 0.2.8
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 +23 -10
- package/package.json +1 -1
- package/src/cli.mjs +90 -32
- package/src/copy-template.mjs +18 -2
- package/src/fs-utils.mjs +2 -1
- package/src/styles.mjs +2 -87
- package/template/.agents/skills/morya-ui-pages/SKILL.md +1 -3
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +7 -1
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +1 -1
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +1 -1
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +10 -10
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +3 -3
- package/template/.cursor/rules/component-usage.mdc +2 -2
- package/template/.cursor/rules/design-system.mdc +5 -5
- package/template/.cursor/rules/page-layout.mdc +7 -9
- package/template/DESIGN.md +15 -15
- package/template/design-tokens/tokens.css +0 -55
- package/template/design-tokens/tokens.json +0 -77
- package/template/docs/components.md +0 -145
- package/template/docs/feedback-message-vs-toast.md +0 -103
- package/template/docs/golden-pages/dashboard-page.vue +0 -103
- package/template/docs/golden-pages/empty-state.vue +0 -66
- package/template/docs/golden-pages/form-page.vue +0 -107
- package/template/docs/golden-pages/landing-page.vue +0 -328
- package/template/docs/golden-pages/list-page.vue +0 -138
- package/template/docs/golden-pages/login-page.vue +0 -191
- package/template/src/examples/DashboardPageExample.vue +0 -103
- package/template/src/examples/EmptyStateExample.vue +0 -66
- package/template/src/examples/FormPageExample.vue +0 -107
- package/template/src/examples/LandingPageExample.vue +0 -328
- package/template/src/examples/ListPageExample.vue +0 -138
- package/template/src/examples/LoginPageExample.vue +0 -191
- package/template/src/styles/morya-app-shell.css +0 -6
package/README.md
CHANGED
|
@@ -4,10 +4,12 @@ One-shot setup for consumer Vue projects using [`morya-ui`](https://www.npmjs.co
|
|
|
4
4
|
|
|
5
5
|
Docs (zh/en):
|
|
6
6
|
|
|
7
|
-
- [
|
|
7
|
+
- [One-shot setup](https://morya-space.github.io/morya-ui/docs/setup) — CLI commands and options
|
|
8
|
+
- [AI setup](https://morya-space.github.io/morya-ui/docs/ai-setup) — Agent workflow
|
|
8
9
|
- [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
|
|
10
|
+
- [Quick start](https://morya-space.github.io/morya-ui/docs/quick-start) — install and first component
|
|
9
11
|
|
|
10
|
-
Installs the UI library, copies Agent skill / Cursor rules / design docs from `
|
|
12
|
+
Installs the UI library, copies Agent skill / Cursor rules / design docs from `design-kit`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and injects `import 'morya-ui/styles.css'`.
|
|
11
13
|
|
|
12
14
|
## Usage
|
|
13
15
|
|
|
@@ -17,6 +19,16 @@ In your app project root:
|
|
|
17
19
|
npx @morya-ui/setup
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
Other common commands:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# AI config + MCP only (library already installed)
|
|
26
|
+
npx @morya-ui/setup ai
|
|
27
|
+
|
|
28
|
+
# Library + styles only
|
|
29
|
+
npx @morya-ui/setup app
|
|
30
|
+
```
|
|
31
|
+
|
|
20
32
|
### Options
|
|
21
33
|
|
|
22
34
|
| Flag | Meaning |
|
|
@@ -28,10 +40,16 @@ npx @morya-ui/setup
|
|
|
28
40
|
| `--skip-install` | Do not install `morya-ui` |
|
|
29
41
|
| `--skip-template` | Do not copy AI skill / rules / docs / tokens |
|
|
30
42
|
| `--skip-mcp` | Do not write `.cursor/mcp.json` |
|
|
31
|
-
| `--skip-styles` | Do not inject `styles.css`
|
|
43
|
+
| `--skip-styles` | Do not inject `styles.css` |
|
|
32
44
|
| `--skip-scripts` | Do not add `check:colors` to `package.json` |
|
|
33
45
|
| `-h`, `--help` | Show help |
|
|
34
46
|
|
|
47
|
+
Example: MCP only:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @morya-ui/setup ai --skip-template --skip-scripts
|
|
51
|
+
```
|
|
52
|
+
|
|
35
53
|
### Conflict policy
|
|
36
54
|
|
|
37
55
|
- Template files and `.cursor/rules/*`: **skip** if the destination exists (unless `--force`).
|
|
@@ -41,16 +59,12 @@ npx @morya-ui/setup
|
|
|
41
59
|
|
|
42
60
|
### What gets copied
|
|
43
61
|
|
|
44
|
-
From the package `template/` (synced from repo `
|
|
62
|
+
From the package `template/` (synced from repo `design-kit/`):
|
|
45
63
|
|
|
46
64
|
- `DESIGN.md`
|
|
47
65
|
- `.agents/skills/morya-ui-pages/`
|
|
48
66
|
- `.cursor/rules/`
|
|
49
|
-
- `docs/` (component index, golden pages, feedback guide)
|
|
50
|
-
- `design-tokens/`
|
|
51
67
|
- `scripts/check-raw-colors.mjs`
|
|
52
|
-
- `src/examples/`
|
|
53
|
-
- `src/styles/morya-app-shell.css`
|
|
54
68
|
|
|
55
69
|
### MCP (Cursor)
|
|
56
70
|
|
|
@@ -77,7 +91,6 @@ Looks for `src/main.ts` / `.js` / `.tsx` / `.jsx`, root `main.*`, `src/app.ts` /
|
|
|
77
91
|
|
|
78
92
|
```ts
|
|
79
93
|
import 'morya-ui/styles.css'
|
|
80
|
-
import './styles/morya-app-shell.css'
|
|
81
94
|
```
|
|
82
95
|
|
|
83
96
|
The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
@@ -85,7 +98,7 @@ The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
|
85
98
|
## Local development (this monorepo)
|
|
86
99
|
|
|
87
100
|
```bash
|
|
88
|
-
pnpm setup:sync-template # refresh packages/setup/template from
|
|
101
|
+
pnpm setup:sync-template # refresh packages/setup/template from design-kit
|
|
89
102
|
pnpm setup:build
|
|
90
103
|
node packages/setup/bin/morya-ui-setup.js --cwd /path/to/app --dry-run
|
|
91
104
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morya-ui/setup",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"description": "One-shot setup for morya-ui: install the library, Cursor MCP, Agent skills, and design config.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/setup#readme",
|
package/src/cli.mjs
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { dirname, join, resolve } from 'node:path'
|
|
2
3
|
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import { copyTemplate } from './copy-template.mjs'
|
|
5
|
+
import { readJson } from './fs-utils.mjs'
|
|
4
6
|
import { installMoryaUi } from './install.mjs'
|
|
5
7
|
import { mergeMcpConfig } from './mcp.mjs'
|
|
6
8
|
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
7
|
-
import {
|
|
9
|
+
import { ensureStylesImport } from './styles.mjs'
|
|
8
10
|
|
|
9
11
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
12
|
const PKG_ROOT = resolve(__dirname, '..')
|
|
11
13
|
const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
12
14
|
|
|
15
|
+
const MODES = new Set(['app', 'ai', 'full'])
|
|
16
|
+
|
|
17
|
+
/** AI pack paths under template/. */
|
|
18
|
+
const AI_TEMPLATE_INCLUDE = [
|
|
19
|
+
'DESIGN.md',
|
|
20
|
+
'.agents/skills/morya-ui-pages',
|
|
21
|
+
'.cursor/rules',
|
|
22
|
+
'scripts/check-raw-colors.mjs',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
26
|
+
const MODE_DEFAULTS = {
|
|
27
|
+
full: {},
|
|
28
|
+
app: { skipTemplate: true, skipMcp: true, skipScripts: true },
|
|
29
|
+
ai: { skipInstall: true, skipStyles: true },
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
const SKIP_FLAGS = {
|
|
14
33
|
'--skip-install': 'skipInstall',
|
|
15
34
|
'--skip-template': 'skipTemplate',
|
|
@@ -19,14 +38,19 @@ const SKIP_FLAGS = {
|
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
export function printHelp() {
|
|
22
|
-
console.log(`Usage: morya-ui-setup [options]
|
|
41
|
+
console.log(`Usage: morya-ui-setup [command] [options]
|
|
23
42
|
|
|
24
|
-
|
|
43
|
+
Commands:
|
|
44
|
+
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
45
|
+
app Install morya-ui and inject styles.css
|
|
46
|
+
ai Copy Agent skill / rules / DESIGN,
|
|
47
|
+
merge Cursor MCP, add check:colors
|
|
48
|
+
|
|
49
|
+
Default command:
|
|
25
50
|
- install morya-ui
|
|
26
|
-
- copy DESIGN.md, Agent skill, Cursor rules
|
|
51
|
+
- copy DESIGN.md, Agent skill, Cursor rules
|
|
27
52
|
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
28
53
|
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
29
|
-
- write src/styles/morya-app-shell.css (html/body/#app height chain) and inject its import
|
|
30
54
|
- add check:colors script when missing
|
|
31
55
|
|
|
32
56
|
Options:
|
|
@@ -37,7 +61,7 @@ Options:
|
|
|
37
61
|
--skip-install Skip dependency install
|
|
38
62
|
--skip-template Skip copying AI template files
|
|
39
63
|
--skip-mcp Skip writing .cursor/mcp.json
|
|
40
|
-
--skip-styles Skip injecting styles.css
|
|
64
|
+
--skip-styles Skip injecting styles.css
|
|
41
65
|
--skip-scripts Skip adding check:colors to package.json
|
|
42
66
|
-h, --help Show this help
|
|
43
67
|
`)
|
|
@@ -48,6 +72,7 @@ Options:
|
|
|
48
72
|
*/
|
|
49
73
|
export function parseArgs(argv) {
|
|
50
74
|
const options = {
|
|
75
|
+
mode: 'full',
|
|
51
76
|
cwd: process.cwd(),
|
|
52
77
|
pm: undefined,
|
|
53
78
|
force: false,
|
|
@@ -66,6 +91,10 @@ export function parseArgs(argv) {
|
|
|
66
91
|
options.help = true
|
|
67
92
|
continue
|
|
68
93
|
}
|
|
94
|
+
if (MODES.has(arg)) {
|
|
95
|
+
options.mode = arg
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
69
98
|
if (arg === '--force') {
|
|
70
99
|
options.force = true
|
|
71
100
|
continue
|
|
@@ -101,6 +130,11 @@ export function parseArgs(argv) {
|
|
|
101
130
|
throw new Error(`Unknown argument: ${arg}`)
|
|
102
131
|
}
|
|
103
132
|
|
|
133
|
+
const defaults = MODE_DEFAULTS[options.mode] || {}
|
|
134
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
135
|
+
if (value) options[key] = true
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
return options
|
|
105
139
|
}
|
|
106
140
|
|
|
@@ -109,11 +143,30 @@ function rel(cwd, path) {
|
|
|
109
143
|
return path.startsWith(cwd) ? path.slice(cwd.length).replace(/^[\\/]/, '') || path : path
|
|
110
144
|
}
|
|
111
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} cwd
|
|
148
|
+
*/
|
|
149
|
+
function hasMoryaUiDependency(cwd) {
|
|
150
|
+
const path = join(cwd, 'package.json')
|
|
151
|
+
if (!existsSync(path)) return false
|
|
152
|
+
try {
|
|
153
|
+
const pkg = readJson(path)
|
|
154
|
+
return Boolean(
|
|
155
|
+
pkg.dependencies?.['morya-ui']
|
|
156
|
+
|| pkg.devDependencies?.['morya-ui']
|
|
157
|
+
|| pkg.peerDependencies?.['morya-ui'],
|
|
158
|
+
)
|
|
159
|
+
} catch {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
112
164
|
/**
|
|
113
165
|
* @param {ReturnType<typeof parseArgs>} options
|
|
114
166
|
*/
|
|
115
167
|
export async function runSetup(options) {
|
|
116
168
|
const {
|
|
169
|
+
mode,
|
|
117
170
|
cwd,
|
|
118
171
|
force,
|
|
119
172
|
dryRun,
|
|
@@ -125,14 +178,25 @@ export async function runSetup(options) {
|
|
|
125
178
|
pm,
|
|
126
179
|
} = options
|
|
127
180
|
|
|
128
|
-
console.log(`@morya-ui/setup → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
181
|
+
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
129
182
|
console.log('')
|
|
130
183
|
|
|
184
|
+
if (mode === 'ai' && !hasMoryaUiDependency(cwd)) {
|
|
185
|
+
console.log(
|
|
186
|
+
'Warning: morya-ui is not listed in package.json. Run `npx @morya-ui/setup app` first (or pnpm add morya-ui).',
|
|
187
|
+
)
|
|
188
|
+
console.log('')
|
|
189
|
+
}
|
|
190
|
+
|
|
131
191
|
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
132
192
|
|
|
133
193
|
const template = skipTemplate
|
|
134
194
|
? { copied: [], skipped: [], forced: [], skippedStep: true }
|
|
135
|
-
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
195
|
+
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
196
|
+
force,
|
|
197
|
+
dryRun,
|
|
198
|
+
include: mode === 'ai' || mode === 'full' ? AI_TEMPLATE_INCLUDE : undefined,
|
|
199
|
+
})
|
|
136
200
|
|
|
137
201
|
const mcp = skipMcp
|
|
138
202
|
? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
|
|
@@ -142,10 +206,6 @@ export async function runSetup(options) {
|
|
|
142
206
|
? { action: 'skipped', reason: 'skip-styles' }
|
|
143
207
|
: ensureStylesImport(cwd, { dryRun })
|
|
144
208
|
|
|
145
|
-
const shell = skipStyles
|
|
146
|
-
? { fileAction: 'skipped', importAction: 'skipped', reason: 'skip-styles' }
|
|
147
|
-
: ensureShellStyles(cwd, { dryRun, force })
|
|
148
|
-
|
|
149
209
|
const scripts = skipScripts
|
|
150
210
|
? { action: 'skipped-flag' }
|
|
151
211
|
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
@@ -158,7 +218,7 @@ export async function runSetup(options) {
|
|
|
158
218
|
}
|
|
159
219
|
|
|
160
220
|
if (template.skippedStep) {
|
|
161
|
-
console.log('Template: skipped (--skip-template)')
|
|
221
|
+
console.log('Template: skipped (--skip-template or app mode)')
|
|
162
222
|
} else {
|
|
163
223
|
console.log(
|
|
164
224
|
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
@@ -172,13 +232,13 @@ export async function runSetup(options) {
|
|
|
172
232
|
}
|
|
173
233
|
|
|
174
234
|
if (mcp.action === 'skipped-flag') {
|
|
175
|
-
console.log('MCP: skipped (--skip-mcp)')
|
|
235
|
+
console.log('MCP: skipped (--skip-mcp or app mode)')
|
|
176
236
|
} else {
|
|
177
237
|
console.log(`MCP: ${mcp.action} (${rel(cwd, mcp.path) || '.cursor/mcp.json'})`)
|
|
178
238
|
}
|
|
179
239
|
|
|
180
240
|
if (styles.reason === 'skip-styles') {
|
|
181
|
-
console.log('Styles: skipped (--skip-styles)')
|
|
241
|
+
console.log('Styles: skipped (--skip-styles or ai mode)')
|
|
182
242
|
} else if (styles.action === 'injected') {
|
|
183
243
|
console.log(`Styles: injected into ${rel(cwd, styles.path)}`)
|
|
184
244
|
} else if (styles.action === 'skipped') {
|
|
@@ -188,29 +248,27 @@ export async function runSetup(options) {
|
|
|
188
248
|
console.log(" import 'morya-ui/styles.css'")
|
|
189
249
|
}
|
|
190
250
|
|
|
191
|
-
if (shell.reason === 'skip-styles') {
|
|
192
|
-
console.log('Shell CSS: skipped (--skip-styles)')
|
|
193
|
-
} else {
|
|
194
|
-
console.log(
|
|
195
|
-
`Shell CSS: file ${shell.fileAction}`
|
|
196
|
-
+ (shell.path ? ` (${rel(cwd, shell.path)})` : '')
|
|
197
|
-
+ `, import ${shell.importAction}`
|
|
198
|
-
+ (shell.entry ? ` (${rel(cwd, shell.entry)})` : ''),
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
251
|
if (scripts.action === 'skipped-flag') {
|
|
203
|
-
console.log('Scripts: skipped (--skip-scripts)')
|
|
252
|
+
console.log('Scripts: skipped (--skip-scripts or app mode)')
|
|
204
253
|
} else {
|
|
205
254
|
console.log(`Scripts: check:colors ${scripts.action}`)
|
|
206
255
|
}
|
|
207
256
|
|
|
208
257
|
console.log('')
|
|
209
258
|
console.log('Next:')
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
259
|
+
if (mode === 'app') {
|
|
260
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
261
|
+
console.log(' 2. Optional AI pack: npx @morya-ui/setup ai')
|
|
262
|
+
} else if (mode === 'ai') {
|
|
263
|
+
console.log(' 1. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
264
|
+
console.log(' 2. Have the agent read DESIGN.md before generating pages.')
|
|
265
|
+
console.log(' 3. Optional: pnpm check:colors')
|
|
266
|
+
} else {
|
|
267
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
268
|
+
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
269
|
+
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
270
|
+
console.log(' 4. Optional: pnpm check:colors')
|
|
271
|
+
}
|
|
214
272
|
|
|
215
|
-
return { install, template, mcp, styles,
|
|
273
|
+
return { mode, install, template, mcp, styles, scripts }
|
|
216
274
|
}
|
package/src/copy-template.mjs
CHANGED
|
@@ -20,11 +20,27 @@ function listFiles(dir) {
|
|
|
20
20
|
return out
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} rel
|
|
25
|
+
* @param {string[] | undefined} include
|
|
26
|
+
*/
|
|
27
|
+
function matchesInclude(rel, include) {
|
|
28
|
+
if (!include || include.length === 0) return true
|
|
29
|
+
const norm = rel.replace(/\\/g, '/')
|
|
30
|
+
return include.some((prefix) => {
|
|
31
|
+
const p = prefix.replace(/\\/g, '/')
|
|
32
|
+
return norm === p || norm.startsWith(`${p}/`)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Copy template tree into target cwd.
|
|
38
|
+
* @param {string} templateRoot
|
|
39
|
+
* @param {string} cwd
|
|
40
|
+
* @param {{ force?: boolean, dryRun?: boolean, include?: string[] }} [options]
|
|
25
41
|
* @returns {{ copied: string[], skipped: string[], forced: string[] }}
|
|
26
42
|
*/
|
|
27
|
-
export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false } = {}) {
|
|
43
|
+
export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false, include } = {}) {
|
|
28
44
|
const copied = []
|
|
29
45
|
const skipped = []
|
|
30
46
|
const forced = []
|
|
@@ -35,7 +51,7 @@ export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false
|
|
|
35
51
|
)
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
const files = listFiles(templateRoot)
|
|
54
|
+
const files = listFiles(templateRoot).filter((rel) => matchesInclude(rel, include))
|
|
39
55
|
for (const rel of files) {
|
|
40
56
|
const from = join(templateRoot, rel)
|
|
41
57
|
const to = join(cwd, rel)
|
package/src/fs-utils.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import { dirname } from 'node:path'
|
|
3
3
|
|
|
4
4
|
export function readJson(path) {
|
|
5
|
-
|
|
5
|
+
const text = readFileSync(path, 'utf8').replace(/^\uFEFF/, '')
|
|
6
|
+
return JSON.parse(text)
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function writeJson(path, value, { dryRun = false } = {}) {
|
package/src/styles.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
4
|
const STYLE_IMPORT = "import 'morya-ui/styles.css'"
|
|
5
5
|
const STYLE_MARKER = 'morya-ui/styles.css'
|
|
6
|
-
const SHELL_MARKER = 'morya-app-shell.css'
|
|
7
|
-
const SHELL_REL = 'src/styles/morya-app-shell.css'
|
|
8
6
|
|
|
9
7
|
const CANDIDATES = [
|
|
10
8
|
'src/main.ts',
|
|
@@ -17,14 +15,6 @@ const CANDIDATES = [
|
|
|
17
15
|
'src/app.js',
|
|
18
16
|
]
|
|
19
17
|
|
|
20
|
-
const SHELL_CSS = `html,
|
|
21
|
-
body,
|
|
22
|
-
#app {
|
|
23
|
-
height: 100%;
|
|
24
|
-
margin: 0;
|
|
25
|
-
}
|
|
26
|
-
`
|
|
27
|
-
|
|
28
18
|
/**
|
|
29
19
|
* @param {string} cwd
|
|
30
20
|
* @returns {string | null} absolute path
|
|
@@ -100,16 +90,6 @@ export function injectStyleImport(source) {
|
|
|
100
90
|
return injectImportLine(source, STYLE_IMPORT, STYLE_MARKER)
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
/**
|
|
104
|
-
* @param {string} entryFile absolute
|
|
105
|
-
* @param {string} shellFile absolute
|
|
106
|
-
*/
|
|
107
|
-
export function shellImportLine(entryFile, shellFile) {
|
|
108
|
-
let rel = relative(dirname(entryFile), shellFile).replace(/\\/g, '/')
|
|
109
|
-
if (!rel.startsWith('.')) rel = `./${rel}`
|
|
110
|
-
return `import '${rel}'`
|
|
111
|
-
}
|
|
112
|
-
|
|
113
93
|
/**
|
|
114
94
|
* @returns {{
|
|
115
95
|
* action: 'injected' | 'skipped' | 'missing-entry',
|
|
@@ -137,68 +117,3 @@ export function ensureStylesImport(cwd, { dryRun = false } = {}) {
|
|
|
137
117
|
|
|
138
118
|
return { action: 'injected', path: entry, dryRun }
|
|
139
119
|
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Write morya-app-shell.css (height chain) and inject its import into the entry.
|
|
143
|
-
* @returns {{
|
|
144
|
-
* fileAction: 'written' | 'skipped' | 'dry-run',
|
|
145
|
-
* importAction: 'injected' | 'skipped' | 'missing-entry',
|
|
146
|
-
* path?: string,
|
|
147
|
-
* entry?: string,
|
|
148
|
-
* reason?: string,
|
|
149
|
-
* dryRun?: boolean
|
|
150
|
-
* }}
|
|
151
|
-
*/
|
|
152
|
-
export function ensureShellStyles(cwd, { dryRun = false, force = false } = {}) {
|
|
153
|
-
const shellFile = join(cwd, SHELL_REL)
|
|
154
|
-
const entry = findEntryFile(cwd)
|
|
155
|
-
|
|
156
|
-
let fileAction = 'skipped'
|
|
157
|
-
if (!existsSync(shellFile) || force) {
|
|
158
|
-
if (dryRun) {
|
|
159
|
-
fileAction = 'dry-run'
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
mkdirSync(dirname(shellFile), { recursive: true })
|
|
163
|
-
writeFileSync(shellFile, SHELL_CSS, 'utf8')
|
|
164
|
-
fileAction = 'written'
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!entry) {
|
|
169
|
-
return {
|
|
170
|
-
fileAction,
|
|
171
|
-
importAction: 'missing-entry',
|
|
172
|
-
path: shellFile,
|
|
173
|
-
reason: 'no-entry-found',
|
|
174
|
-
dryRun,
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const importLine = shellImportLine(entry, shellFile)
|
|
179
|
-
const original = readFileSync(entry, 'utf8')
|
|
180
|
-
const { source, changed, reason } = injectImportLine(original, importLine, SHELL_MARKER)
|
|
181
|
-
|
|
182
|
-
if (!changed) {
|
|
183
|
-
return {
|
|
184
|
-
fileAction,
|
|
185
|
-
importAction: 'skipped',
|
|
186
|
-
path: shellFile,
|
|
187
|
-
entry,
|
|
188
|
-
reason: reason || 'already-present',
|
|
189
|
-
dryRun,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!dryRun) {
|
|
194
|
-
writeFileSync(entry, source, 'utf8')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
fileAction,
|
|
199
|
-
importAction: 'injected',
|
|
200
|
-
path: shellFile,
|
|
201
|
-
entry,
|
|
202
|
-
dryRun,
|
|
203
|
-
}
|
|
204
|
-
}
|
|
@@ -44,9 +44,7 @@ Full taxonomy: [references/surfaces.md](references/surfaces.md).
|
|
|
44
44
|
|
|
45
45
|
1. `morya-ui` installed; `morya-ui/styles.css` imported.
|
|
46
46
|
2. Prefer `@morya-ui/mcp` — never invent prop / event / slot names.
|
|
47
|
-
3.
|
|
48
|
-
- `DESIGN.md`
|
|
49
|
-
- `docs/golden-pages/*.vue`, `docs/components.md`, `docs/feedback-message-vs-toast.md`
|
|
47
|
+
3. Golden pages, component APIs, and feedback rules come from `@morya-ui/mcp`. Without MCP, use this skill's `references/`. Project `DESIGN.md` overrides generic taste when the AI pack is merged.
|
|
50
48
|
|
|
51
49
|
## Workflow
|
|
52
50
|
|
|
@@ -32,7 +32,11 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
|
|
|
32
32
|
| `<MMessage>` | Persistent in-page error / warning |
|
|
33
33
|
| `MEmpty` | No-data / first-use / filtered empty (not an error) |
|
|
34
34
|
| `MResult` | Terminal outcome: success, failure, 403 / 404 / 500 |
|
|
35
|
-
| `
|
|
35
|
+
| `MLoading` / `v-loading` / `loading.service` | **Default** region or fullscreen loading mask |
|
|
36
|
+
| `MSkeleton` | Layout is already known; placeholder while content arrives |
|
|
37
|
+
| `MProgressBar` | Determinate progress |
|
|
38
|
+
| `MProgressSpinner` | Inline spinner only, not a region mask |
|
|
39
|
+
| `MBlockUI` | Block interaction without a loading message |
|
|
36
40
|
|
|
37
41
|
## Overlays & menus
|
|
38
42
|
|
|
@@ -59,6 +63,7 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
|
|
|
59
63
|
| Marketing landing | `landing-page` golden + `MButton` / `MTag` / `MAccordion` |
|
|
60
64
|
| Empty list / zero state | `MEmpty` (+ `empty-state` golden or `MTable` `#empty`) |
|
|
61
65
|
| Submit success / HTTP error page | `MResult` |
|
|
66
|
+
| Region or page is waiting | `MLoading`, `v-loading`, or `loading.service`; known layout → `MSkeleton` |
|
|
62
67
|
| Local capped scroll | Explicit `MScrollbar` |
|
|
63
68
|
| Admin sider nav | `MMenu` |
|
|
64
69
|
|
|
@@ -69,5 +74,6 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
|
|
|
69
74
|
| `MDropdown` as form enum | `MSelect` |
|
|
70
75
|
| Hand `<table>` | `MTable` |
|
|
71
76
|
| Hand modal div | `MDialog` |
|
|
77
|
+
| Hand spinner or `MProgressSpinner` as a region / page mask | `MLoading` / `v-loading` |
|
|
72
78
|
| Extra `MCard` around every `MPage*` block | Use page components' own surface/gap |
|
|
73
79
|
| Assume undocumented props | MCP / docs lookup |
|
|
@@ -87,7 +87,7 @@ import 'morya-ui/styles.css'
|
|
|
87
87
|
| Card radius | `--m-radius-md` |
|
|
88
88
|
| Card shadow | `--m-shadow-md` |
|
|
89
89
|
|
|
90
|
-
Full
|
|
90
|
+
Full set: MCP `get_design_rules`. Runtime truth remains `morya-ui/styles.css`.
|
|
91
91
|
|
|
92
92
|
## Bans
|
|
93
93
|
|
|
@@ -46,7 +46,7 @@ toast.success({
|
|
|
46
46
|
## Prefer `<MMessage>` / form-level errors
|
|
47
47
|
|
|
48
48
|
- Field validation: component `invalid` / `errorMessage` (preferred).
|
|
49
|
-
- Form-level persistent errors (login/auth): token-styled `role="alert"` bar as in `
|
|
49
|
+
- Form-level persistent errors (login/auth): token-styled `role="alert"` bar as in MCP `get_golden_page` `login-page`.
|
|
50
50
|
- Note: `<MMessage>` today is primarily the **message service host** (`messages` / teleport). Do not invent a `severity` + default-slot Alert API unless docs add it.
|
|
51
51
|
|
|
52
52
|
```vue
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Page layouts
|
|
2
2
|
|
|
3
|
-
When generating a full page, pick a type and **mirror the golden-page block order**.
|
|
4
|
-
|
|
5
|
-
| Type |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| List | `
|
|
8
|
-
| Form | `
|
|
9
|
-
| Dashboard | `
|
|
10
|
-
| Login | `
|
|
11
|
-
| Landing | `
|
|
12
|
-
| Empty | `
|
|
3
|
+
When generating a full page, pick a type and **mirror the golden-page block order**. Source: MCP `get_golden_page`.
|
|
4
|
+
|
|
5
|
+
| Type | `get_golden_page` id |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| List | `list-page` |
|
|
8
|
+
| Form | `form-page` |
|
|
9
|
+
| Dashboard | `dashboard-page` |
|
|
10
|
+
| Login | `login-page` |
|
|
11
|
+
| Landing | `landing-page` |
|
|
12
|
+
| Empty | `empty-state` |
|
|
13
13
|
|
|
14
14
|
Via MCP: `recommend_page` → `get_golden_page`; local edits: `get_page_snippet` (`filters`, `toolbar`, `form-actions`, `scrollable-panel`, …).
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ Pick a lane, then a surface. Prefer project golden pages for **Ops**; use the re
|
|
|
17
17
|
|
|
18
18
|
| Surface | Job | Recipe |
|
|
19
19
|
| --- | --- | --- |
|
|
20
|
-
| Login / register | Authenticate with trust | Golden: `
|
|
20
|
+
| Login / register | Authenticate with trust | Golden: MCP `get_golden_page` `login-page` — split brand + `MForm` (`MInput`, `MInputPassword`); form-level errors via token `role="alert"` (not Toast) |
|
|
21
21
|
| Invite / accept | Join org | Same shell; show org name clearly |
|
|
22
22
|
| Forgot / reset | Recover access | Short form + success state with next step |
|
|
23
23
|
| Profile / account | Edit self | Settings-like sections; avatar via `MAvatar` |
|
|
@@ -28,7 +28,7 @@ Keep credential forms quiet: one primary CTA, clear labels, no decorative noise
|
|
|
28
28
|
|
|
29
29
|
| Surface | Job | Recipe |
|
|
30
30
|
| --- | --- | --- |
|
|
31
|
-
| Empty state | Invite first action | Golden: `
|
|
31
|
+
| Empty state | Invite first action | Golden: MCP `get_golden_page` `empty-state` — `MEmpty` + primary/secondary `MButton` in `#extra` |
|
|
32
32
|
| Onboarding | Teach the product path | `MStepper` or paced cards; one decision per step; finish → Ops home |
|
|
33
33
|
| Wizard | Multi-step create | `MStepper` + `MForm` per step + sticky actions (`上一步` / `下一步` / `提交`) |
|
|
34
34
|
| Success / result | Confirm completion | `MResult` (`status="success"` / `error` …) + next actions (`查看详情` / `返回列表`) |
|
|
@@ -51,7 +51,7 @@ Use when the brief is landing, pricing, launch, or docs marketing — **not** fo
|
|
|
51
51
|
|
|
52
52
|
| Surface | Job | Recipe |
|
|
53
53
|
| --- | --- | --- |
|
|
54
|
-
| Landing | Convert / explain product | Golden: `
|
|
54
|
+
| Landing | Convert / explain product | Golden: MCP `get_golden_page` `landing-page` — one-job hero; CTAs → `MButton`; chips → `MTag`; FAQ → `MAccordion` |
|
|
55
55
|
| Pricing | Choose a plan | Clear plan cards (`MCard`) + primary CTA; highlight recommended plan without clutter |
|
|
56
56
|
| Feature showcase | Prove capability | Alternating media/copy; live `M*` demo only if lightweight |
|
|
57
57
|
| Docs marketing chrome | Frame documentation | Header + nav using `M*` where suitable; content area stays readable |
|
|
@@ -23,7 +23,7 @@ import { MButton, MInput } from 'morya-ui'
|
|
|
23
23
|
- 单行操作结果 → `message.success/info/warn/error('…')`
|
|
24
24
|
- 有标题 + 详情 / 异步通知 → `toast.*({ summary, detail })`
|
|
25
25
|
- 表单区常驻错误 → `<MMessage>` 或字段 `errorMessage`
|
|
26
|
-
- 详见 `
|
|
26
|
+
- 详见 skill `references/feedback.md`
|
|
27
27
|
|
|
28
28
|
## Props 约定
|
|
29
29
|
|
|
@@ -38,4 +38,4 @@ import { MButton, MInput } from 'morya-ui'
|
|
|
38
38
|
- 用 `MDropdown` 代替 Select 做枚举选择。
|
|
39
39
|
- 手写 table/modal 替代 `MTable` / `MDialog`。
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
完整清单:MCP,或 skill `references/component-index.md`
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Morya UI 设计系统第一信源。生成或修改 UI 前必读 DESIGN.md
|
|
2
|
+
description: Morya UI 设计系统第一信源。生成或修改 UI 前必读 DESIGN.md。
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Morya UI 设计系统
|
|
7
7
|
|
|
8
|
-
1. **先读** 项目根目录 `DESIGN.md
|
|
8
|
+
1. **先读** 项目根目录 `DESIGN.md`。组件选型用 MCP,或 skill `references/component-index.md`。
|
|
9
9
|
2. **只用** `morya-ui` 的 `M*` 组件;全局样式 `morya-ui/styles.css`。
|
|
10
10
|
3. **颜色/间距/圆角** 使用 `--m-*` CSS 变量,禁止裸 hex/rgb(见 `scripts/check-raw-colors.mjs`)。
|
|
11
|
-
4. **页面结构**
|
|
11
|
+
4. **页面结构** 用 MCP `get_golden_page`(`list-page` / `form-page` / `dashboard-page` / `login-page` / `landing-page` / `empty-state`),不要发明新布局范式。
|
|
12
12
|
5. **表单选项** 用 `MSelect` / `MTreeSelect`;**动作菜单** 用 `MDropdown`。
|
|
13
13
|
6. 不确定 API 时查文档站或 MCP,**禁止臆造 prop**。
|
|
14
14
|
7. 根节点用 `MConfigProvider`;后台布局用 `MLayout` 系列。
|
|
15
|
-
8. **操作反馈默认 `message` API**;仅 `summary + detail` 或异步通知用 `toast`;表单常驻错误用 `<MMessage>`。见 `
|
|
15
|
+
8. **操作反馈默认 `message` API**;仅 `summary + detail` 或异步通知用 `toast`;表单常驻错误用 `<MMessage>`。见 skill `references/feedback.md`。
|
|
16
16
|
|
|
17
|
-
Token
|
|
17
|
+
Token 参考:MCP `get_design_rules`(运行时以 `morya-ui/styles.css` 为准)
|