@morya-ui/setup 0.2.5
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/LICENSE +21 -0
- package/README.md +93 -0
- package/bin/morya-ui-setup.js +14 -0
- package/package.json +35 -0
- package/src/cli.mjs +200 -0
- package/src/copy-template.mjs +62 -0
- package/src/fs-utils.mjs +23 -0
- package/src/install.mjs +63 -0
- package/src/mcp.mjs +50 -0
- package/src/package-json.mjs +30 -0
- package/src/styles.mjs +109 -0
- package/template/.agents/skills/morya-ui-pages/SKILL.md +150 -0
- package/template/.agents/skills/morya-ui-pages/evals/evals.json +53 -0
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +72 -0
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +90 -0
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +66 -0
- package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +29 -0
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +76 -0
- package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +49 -0
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +89 -0
- package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +83 -0
- package/template/.cursor/rules/coding-style.mdc +41 -0
- package/template/.cursor/rules/component-usage.mdc +41 -0
- package/template/.cursor/rules/design-system.mdc +17 -0
- package/template/.cursor/rules/page-layout.mdc +67 -0
- package/template/DESIGN.md +121 -0
- package/template/design-tokens/tokens.css +55 -0
- package/template/design-tokens/tokens.json +77 -0
- package/template/docs/components.md +144 -0
- package/template/docs/feedback-message-vs-toast.md +103 -0
- package/template/docs/golden-pages/dashboard-page.vue +103 -0
- package/template/docs/golden-pages/empty-state.vue +66 -0
- package/template/docs/golden-pages/form-page.vue +107 -0
- package/template/docs/golden-pages/landing-page.vue +328 -0
- package/template/docs/golden-pages/list-page.vue +127 -0
- package/template/docs/golden-pages/login-page.vue +191 -0
- package/template/scripts/check-raw-colors.mjs +74 -0
- package/template/src/examples/DashboardPageExample.vue +103 -0
- package/template/src/examples/EmptyStateExample.vue +66 -0
- package/template/src/examples/FormPageExample.vue +107 -0
- package/template/src/examples/LandingPageExample.vue +328 -0
- package/template/src/examples/ListPageExample.vue +127 -0
- package/template/src/examples/LoginPageExample.vue +191 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Morya UI contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @morya-ui/setup
|
|
2
|
+
|
|
3
|
+
One-shot setup for consumer Vue projects using [`morya-ui`](https://www.npmjs.com/package/morya-ui).
|
|
4
|
+
|
|
5
|
+
Docs (zh/en):
|
|
6
|
+
|
|
7
|
+
- [AI setup](https://morya-space.github.io/morya-ui/docs/ai-setup) — one-shot CLI
|
|
8
|
+
- [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
|
|
9
|
+
|
|
10
|
+
Installs the UI library, copies Agent skill / Cursor rules / design docs from `ai-design-config`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and tries to inject `import 'morya-ui/styles.css'` into the app entry.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
In your app project root:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @morya-ui/setup
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Options
|
|
21
|
+
|
|
22
|
+
| Flag | Meaning |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `--cwd <dir>` | Target project root (default: current directory) |
|
|
25
|
+
| `--pm pnpm\|yarn\|npm` | Package manager (default: detect from lockfile, else `pnpm`) |
|
|
26
|
+
| `--force` | Overwrite existing template files and the `morya-ui` MCP entry |
|
|
27
|
+
| `--dry-run` | Print actions without writing or installing |
|
|
28
|
+
| `--skip-install` | Do not install `morya-ui` |
|
|
29
|
+
| `--skip-template` | Do not copy AI skill / rules / docs / tokens |
|
|
30
|
+
| `--skip-mcp` | Do not write `.cursor/mcp.json` |
|
|
31
|
+
| `--skip-styles` | Do not inject `styles.css` import |
|
|
32
|
+
| `--skip-scripts` | Do not add `check:colors` to `package.json` |
|
|
33
|
+
| `-h`, `--help` | Show help |
|
|
34
|
+
|
|
35
|
+
### Conflict policy
|
|
36
|
+
|
|
37
|
+
- Template files and `.cursor/rules/*`: **skip** if the destination exists (unless `--force`).
|
|
38
|
+
- `.cursor/mcp.json`: merge other servers; skip existing `morya-ui` entry unless `--force`.
|
|
39
|
+
- `package.json` `check:colors`: add only if missing (unless `--force`).
|
|
40
|
+
- Styles: inject only when an entry file is found and the import is not already present.
|
|
41
|
+
|
|
42
|
+
### What gets copied
|
|
43
|
+
|
|
44
|
+
From the package `template/` (synced from repo `ai-design-config/`):
|
|
45
|
+
|
|
46
|
+
- `DESIGN.md`
|
|
47
|
+
- `.agents/skills/morya-ui-pages/`
|
|
48
|
+
- `.cursor/rules/`
|
|
49
|
+
- `docs/` (component index, golden pages, feedback guide)
|
|
50
|
+
- `design-tokens/`
|
|
51
|
+
- `scripts/check-raw-colors.mjs`
|
|
52
|
+
- `src/examples/`
|
|
53
|
+
|
|
54
|
+
### MCP (Cursor)
|
|
55
|
+
|
|
56
|
+
Writes / merges:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"morya-ui": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "@morya-ui/mcp"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Restart Cursor (or reload MCP) after setup.
|
|
70
|
+
|
|
71
|
+
Other clients can use the same stdio command; this CLI only writes `.cursor/mcp.json`.
|
|
72
|
+
|
|
73
|
+
### Styles
|
|
74
|
+
|
|
75
|
+
Looks for `src/main.ts` / `.js` / `.tsx` / `.jsx`, root `main.*`, `src/app.ts` / `.js`, or the module script in `index.html`. If none match, add manually:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import 'morya-ui/styles.css'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
82
|
+
|
|
83
|
+
## Local development (this monorepo)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm setup:sync-template # refresh packages/setup/template from ai-design-config
|
|
87
|
+
pnpm setup:build
|
|
88
|
+
node packages/setup/bin/morya-ui-setup.js --cwd /path/to/app --dry-run
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs, printHelp, runSetup } from '../src/cli.mjs'
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
const options = parseArgs(process.argv.slice(2))
|
|
6
|
+
if (options.help) {
|
|
7
|
+
printHelp()
|
|
8
|
+
process.exit(0)
|
|
9
|
+
}
|
|
10
|
+
await runSetup(options)
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(error instanceof Error ? error.message : error)
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morya-ui/setup",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.2.5",
|
|
5
|
+
"description": "One-shot setup for morya-ui: install the library, Cursor MCP, Agent skills, and design config.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/setup#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/morya-space/morya-ui.git",
|
|
11
|
+
"directory": "packages/setup"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/morya-space/morya-ui/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"morya-ui-setup": "./bin/morya-ui-setup.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"README.md",
|
|
24
|
+
"bin",
|
|
25
|
+
"src",
|
|
26
|
+
"template"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.19.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"sync-template": "node ./scripts/sync-template.mjs",
|
|
33
|
+
"build": "pnpm run sync-template"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { dirname, join, resolve } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { copyTemplate } from './copy-template.mjs'
|
|
4
|
+
import { installMoryaUi } from './install.mjs'
|
|
5
|
+
import { mergeMcpConfig } from './mcp.mjs'
|
|
6
|
+
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
7
|
+
import { ensureStylesImport } from './styles.mjs'
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const PKG_ROOT = resolve(__dirname, '..')
|
|
11
|
+
const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
12
|
+
|
|
13
|
+
const SKIP_FLAGS = {
|
|
14
|
+
'--skip-install': 'skipInstall',
|
|
15
|
+
'--skip-template': 'skipTemplate',
|
|
16
|
+
'--skip-mcp': 'skipMcp',
|
|
17
|
+
'--skip-styles': 'skipStyles',
|
|
18
|
+
'--skip-scripts': 'skipScripts',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function printHelp() {
|
|
22
|
+
console.log(`Usage: morya-ui-setup [options]
|
|
23
|
+
|
|
24
|
+
One-shot setup for morya-ui in a consumer Vue project:
|
|
25
|
+
- install morya-ui
|
|
26
|
+
- copy DESIGN.md, Agent skill, Cursor rules, docs, tokens, examples
|
|
27
|
+
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
28
|
+
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
29
|
+
- add check:colors script when missing
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--cwd <dir> Target project root (default: process.cwd())
|
|
33
|
+
--pm <name> Package manager: pnpm | yarn | npm (auto-detect by lockfile)
|
|
34
|
+
--force Overwrite existing template files and MCP server entry
|
|
35
|
+
--dry-run Print actions without writing or installing
|
|
36
|
+
--skip-install Skip dependency install
|
|
37
|
+
--skip-template Skip copying AI template files
|
|
38
|
+
--skip-mcp Skip writing .cursor/mcp.json
|
|
39
|
+
--skip-styles Skip injecting styles.css import
|
|
40
|
+
--skip-scripts Skip adding check:colors to package.json
|
|
41
|
+
-h, --help Show this help
|
|
42
|
+
`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string[]} argv
|
|
47
|
+
*/
|
|
48
|
+
export function parseArgs(argv) {
|
|
49
|
+
const options = {
|
|
50
|
+
cwd: process.cwd(),
|
|
51
|
+
pm: undefined,
|
|
52
|
+
force: false,
|
|
53
|
+
dryRun: false,
|
|
54
|
+
skipInstall: false,
|
|
55
|
+
skipTemplate: false,
|
|
56
|
+
skipMcp: false,
|
|
57
|
+
skipStyles: false,
|
|
58
|
+
skipScripts: false,
|
|
59
|
+
help: false,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const arg = argv[i]
|
|
64
|
+
if (arg === '-h' || arg === '--help') {
|
|
65
|
+
options.help = true
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
if (arg === '--force') {
|
|
69
|
+
options.force = true
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--dry-run') {
|
|
73
|
+
options.dryRun = true
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (SKIP_FLAGS[arg]) {
|
|
77
|
+
options[SKIP_FLAGS[arg]] = true
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
if (arg === '--cwd') {
|
|
81
|
+
const value = argv[++i]
|
|
82
|
+
if (!value) throw new Error('--cwd requires a path')
|
|
83
|
+
options.cwd = resolve(value)
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
if (arg.startsWith('--cwd=')) {
|
|
87
|
+
options.cwd = resolve(arg.slice('--cwd='.length))
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
if (arg === '--pm') {
|
|
91
|
+
const value = argv[++i]
|
|
92
|
+
if (!value) throw new Error('--pm requires pnpm, yarn, or npm')
|
|
93
|
+
options.pm = value
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (arg.startsWith('--pm=')) {
|
|
97
|
+
options.pm = arg.slice('--pm='.length)
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return options
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function rel(cwd, path) {
|
|
107
|
+
if (!path) return ''
|
|
108
|
+
return path.startsWith(cwd) ? path.slice(cwd.length).replace(/^[\\/]/, '') || path : path
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {ReturnType<typeof parseArgs>} options
|
|
113
|
+
*/
|
|
114
|
+
export async function runSetup(options) {
|
|
115
|
+
const {
|
|
116
|
+
cwd,
|
|
117
|
+
force,
|
|
118
|
+
dryRun,
|
|
119
|
+
skipInstall,
|
|
120
|
+
skipTemplate,
|
|
121
|
+
skipMcp,
|
|
122
|
+
skipStyles,
|
|
123
|
+
skipScripts,
|
|
124
|
+
pm,
|
|
125
|
+
} = options
|
|
126
|
+
|
|
127
|
+
console.log(`@morya-ui/setup → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
128
|
+
console.log('')
|
|
129
|
+
|
|
130
|
+
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
131
|
+
|
|
132
|
+
const template = skipTemplate
|
|
133
|
+
? { copied: [], skipped: [], forced: [], skippedStep: true }
|
|
134
|
+
: copyTemplate(TEMPLATE_ROOT, cwd, { force, dryRun })
|
|
135
|
+
|
|
136
|
+
const mcp = skipMcp
|
|
137
|
+
? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
|
|
138
|
+
: mergeMcpConfig(cwd, { force, dryRun })
|
|
139
|
+
|
|
140
|
+
const styles = skipStyles
|
|
141
|
+
? { action: 'skipped', reason: 'skip-styles' }
|
|
142
|
+
: ensureStylesImport(cwd, { dryRun })
|
|
143
|
+
|
|
144
|
+
const scripts = skipScripts
|
|
145
|
+
? { action: 'skipped-flag' }
|
|
146
|
+
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
147
|
+
|
|
148
|
+
console.log('--- Summary ---')
|
|
149
|
+
if (install.skipped) {
|
|
150
|
+
console.log(`Install: skipped (${install.reason}) — would run: ${install.command}`)
|
|
151
|
+
} else {
|
|
152
|
+
console.log(`Install: ok (${install.pm}) — ${install.command}`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (template.skippedStep) {
|
|
156
|
+
console.log('Template: skipped (--skip-template)')
|
|
157
|
+
} else {
|
|
158
|
+
console.log(
|
|
159
|
+
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
160
|
+
)
|
|
161
|
+
if (template.skipped.length && template.skipped.length <= 8) {
|
|
162
|
+
for (const file of template.skipped) console.log(` skip ${file}`)
|
|
163
|
+
} else if (template.skipped.length > 8) {
|
|
164
|
+
for (const file of template.skipped.slice(0, 5)) console.log(` skip ${file}`)
|
|
165
|
+
console.log(` … and ${template.skipped.length - 5} more (use --force to overwrite)`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (mcp.action === 'skipped-flag') {
|
|
170
|
+
console.log('MCP: skipped (--skip-mcp)')
|
|
171
|
+
} else {
|
|
172
|
+
console.log(`MCP: ${mcp.action} (${rel(cwd, mcp.path) || '.cursor/mcp.json'})`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (styles.reason === 'skip-styles') {
|
|
176
|
+
console.log('Styles: skipped (--skip-styles)')
|
|
177
|
+
} else if (styles.action === 'injected') {
|
|
178
|
+
console.log(`Styles: injected into ${rel(cwd, styles.path)}`)
|
|
179
|
+
} else if (styles.action === 'skipped') {
|
|
180
|
+
console.log(`Styles: skipped (${styles.reason}) in ${rel(cwd, styles.path)}`)
|
|
181
|
+
} else {
|
|
182
|
+
console.log('Styles: no entry file found — add manually:')
|
|
183
|
+
console.log(" import 'morya-ui/styles.css'")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (scripts.action === 'skipped-flag') {
|
|
187
|
+
console.log('Scripts: skipped (--skip-scripts)')
|
|
188
|
+
} else {
|
|
189
|
+
console.log(`Scripts: check:colors ${scripts.action}`)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('')
|
|
193
|
+
console.log('Next:')
|
|
194
|
+
console.log(' 1. Ensure the styles import is in your app entry (if not injected).')
|
|
195
|
+
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
196
|
+
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
197
|
+
console.log(' 4. Optional: pnpm check:colors')
|
|
198
|
+
|
|
199
|
+
return { install, template, mcp, styles, scripts }
|
|
200
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, relative } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Recursively list files under dir (relative to dir).
|
|
6
|
+
* @param {string} dir
|
|
7
|
+
* @returns {string[]}
|
|
8
|
+
*/
|
|
9
|
+
function listFiles(dir) {
|
|
10
|
+
const out = []
|
|
11
|
+
function walk(current) {
|
|
12
|
+
for (const name of readdirSync(current)) {
|
|
13
|
+
const full = join(current, name)
|
|
14
|
+
const st = statSync(full)
|
|
15
|
+
if (st.isDirectory()) walk(full)
|
|
16
|
+
else out.push(relative(dir, full))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
walk(dir)
|
|
20
|
+
return out
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Copy template tree into target cwd.
|
|
25
|
+
* @returns {{ copied: string[], skipped: string[], forced: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false } = {}) {
|
|
28
|
+
const copied = []
|
|
29
|
+
const skipped = []
|
|
30
|
+
const forced = []
|
|
31
|
+
|
|
32
|
+
if (!existsSync(templateRoot)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Template missing at ${templateRoot}. Run sync-template / build before publishing.`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const files = listFiles(templateRoot)
|
|
39
|
+
for (const rel of files) {
|
|
40
|
+
const from = join(templateRoot, rel)
|
|
41
|
+
const to = join(cwd, rel)
|
|
42
|
+
const exists = existsSync(to)
|
|
43
|
+
|
|
44
|
+
if (exists && !force) {
|
|
45
|
+
skipped.push(rel)
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
if (exists) forced.push(rel)
|
|
51
|
+
else copied.push(rel)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
mkdirSync(dirname(to), { recursive: true })
|
|
56
|
+
cpSync(from, to)
|
|
57
|
+
if (exists) forced.push(rel)
|
|
58
|
+
else copied.push(rel)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { copied, skipped, forced }
|
|
62
|
+
}
|
package/src/fs-utils.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export function readJson(path) {
|
|
5
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function writeJson(path, value, { dryRun = false } = {}) {
|
|
9
|
+
const text = `${JSON.stringify(value, null, 2)}\n`
|
|
10
|
+
if (dryRun) return text
|
|
11
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
12
|
+
writeFileSync(path, text, 'utf8')
|
|
13
|
+
return text
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ensureDir(path, { dryRun = false } = {}) {
|
|
17
|
+
if (dryRun || existsSync(path)) return
|
|
18
|
+
mkdirSync(path, { recursive: true })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function fileExists(path) {
|
|
22
|
+
return existsSync(path)
|
|
23
|
+
}
|
package/src/install.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { execSync } from 'node:child_process'
|
|
4
|
+
|
|
5
|
+
const LOCKFILES = [
|
|
6
|
+
{ pm: 'pnpm', file: 'pnpm-lock.yaml' },
|
|
7
|
+
{ pm: 'yarn', file: 'yarn.lock' },
|
|
8
|
+
{ pm: 'npm', file: 'package-lock.json' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export function detectPackageManager(cwd, explicit) {
|
|
12
|
+
if (explicit) {
|
|
13
|
+
if (!['pnpm', 'yarn', 'npm'].includes(explicit)) {
|
|
14
|
+
throw new Error(`Unsupported --pm value: ${explicit}. Use pnpm, yarn, or npm.`)
|
|
15
|
+
}
|
|
16
|
+
return explicit
|
|
17
|
+
}
|
|
18
|
+
for (const { pm, file } of LOCKFILES) {
|
|
19
|
+
if (existsSync(join(cwd, file))) return pm
|
|
20
|
+
}
|
|
21
|
+
return 'pnpm'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function installCommand(pm) {
|
|
25
|
+
switch (pm) {
|
|
26
|
+
case 'pnpm':
|
|
27
|
+
return 'pnpm add morya-ui'
|
|
28
|
+
case 'yarn':
|
|
29
|
+
return 'yarn add morya-ui'
|
|
30
|
+
case 'npm':
|
|
31
|
+
return 'npm install morya-ui'
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown package manager: ${pm}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @returns {{ pm: string, command: string, skipped?: boolean, reason?: string }}
|
|
39
|
+
*/
|
|
40
|
+
export function installMoryaUi(cwd, { pm, dryRun = false, skipInstall = false } = {}) {
|
|
41
|
+
const resolved = detectPackageManager(cwd, pm)
|
|
42
|
+
const command = installCommand(resolved)
|
|
43
|
+
|
|
44
|
+
if (skipInstall) {
|
|
45
|
+
return { pm: resolved, command, skipped: true, reason: 'skip-install' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!existsSync(join(cwd, 'package.json'))) {
|
|
49
|
+
return {
|
|
50
|
+
pm: resolved,
|
|
51
|
+
command,
|
|
52
|
+
skipped: true,
|
|
53
|
+
reason: 'no-package-json',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (dryRun) {
|
|
58
|
+
return { pm: resolved, command, skipped: true, reason: 'dry-run' }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
execSync(command, { cwd, stdio: 'inherit', shell: true })
|
|
62
|
+
return { pm: resolved, command }
|
|
63
|
+
}
|
package/src/mcp.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { readJson, writeJson } from './fs-utils.mjs'
|
|
4
|
+
|
|
5
|
+
const SERVER_NAME = 'morya-ui'
|
|
6
|
+
const SERVER_CONFIG = {
|
|
7
|
+
command: 'npx',
|
|
8
|
+
args: ['-y', '@morya-ui/mcp'],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Merge Cursor MCP config for morya-ui.
|
|
13
|
+
* @returns {{ path: string, action: 'created' | 'merged' | 'skipped' | 'forced', dryRun?: boolean }}
|
|
14
|
+
*/
|
|
15
|
+
export function mergeMcpConfig(cwd, { force = false, dryRun = false } = {}) {
|
|
16
|
+
const path = join(cwd, '.cursor', 'mcp.json')
|
|
17
|
+
const exists = existsSync(path)
|
|
18
|
+
|
|
19
|
+
let data = { mcpServers: {} }
|
|
20
|
+
if (exists) {
|
|
21
|
+
try {
|
|
22
|
+
data = readJson(path)
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(`Invalid JSON in ${path}`)
|
|
25
|
+
}
|
|
26
|
+
if (!data || typeof data !== 'object') {
|
|
27
|
+
throw new Error(`Invalid MCP config shape in ${path}`)
|
|
28
|
+
}
|
|
29
|
+
if (!data.mcpServers || typeof data.mcpServers !== 'object') {
|
|
30
|
+
data.mcpServers = {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const hasExisting = Boolean(data.mcpServers[SERVER_NAME])
|
|
35
|
+
if (hasExisting && !force) {
|
|
36
|
+
return { path, action: 'skipped' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
data.mcpServers[SERVER_NAME] = { ...SERVER_CONFIG }
|
|
40
|
+
|
|
41
|
+
if (!dryRun) {
|
|
42
|
+
writeJson(path, data)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let action = 'created'
|
|
46
|
+
if (exists && hasExisting && force) action = 'forced'
|
|
47
|
+
else if (exists) action = 'merged'
|
|
48
|
+
|
|
49
|
+
return { path, action, dryRun }
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { readJson, writeJson } from './fs-utils.mjs'
|
|
4
|
+
|
|
5
|
+
const CHECK_COLORS = 'node scripts/check-raw-colors.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @returns {{ action: 'added' | 'skipped' | 'missing-package-json', dryRun?: boolean }}
|
|
9
|
+
*/
|
|
10
|
+
export function ensureCheckColorsScript(cwd, { force = false, dryRun = false } = {}) {
|
|
11
|
+
const path = join(cwd, 'package.json')
|
|
12
|
+
if (!existsSync(path)) {
|
|
13
|
+
return { action: 'missing-package-json' }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pkg = readJson(path)
|
|
17
|
+
pkg.scripts = pkg.scripts || {}
|
|
18
|
+
|
|
19
|
+
if (pkg.scripts['check:colors'] && !force) {
|
|
20
|
+
return { action: 'skipped' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pkg.scripts['check:colors'] = CHECK_COLORS
|
|
24
|
+
|
|
25
|
+
if (!dryRun) {
|
|
26
|
+
writeJson(path, pkg)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { action: 'added', dryRun }
|
|
30
|
+
}
|