@uiscore/cli 0.1.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/README.md +151 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +223 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @uiscore/cli
|
|
2
|
+
|
|
3
|
+
CLI for pulling UIScore registry components into an existing project.
|
|
4
|
+
|
|
5
|
+
At the moment the CLI supports:
|
|
6
|
+
|
|
7
|
+
- `uiscore init`
|
|
8
|
+
- `uiscore add <name>`
|
|
9
|
+
|
|
10
|
+
Current registry item verified end-to-end:
|
|
11
|
+
|
|
12
|
+
- `button`
|
|
13
|
+
- `alert`
|
|
14
|
+
- `avatar`
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Run without installing globally:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @uiscore/cli init
|
|
22
|
+
npx @uiscore/cli add button
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install it globally:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @uiscore/cli
|
|
29
|
+
uiscore init
|
|
30
|
+
uiscore add button
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
`uiscore add <name>` does four things:
|
|
36
|
+
|
|
37
|
+
1. Reads `uiscore.config.json` from the current project.
|
|
38
|
+
2. Downloads the registry item JSON from `registryUrl`.
|
|
39
|
+
3. Writes component files into your project.
|
|
40
|
+
4. Installs the dependencies listed by that registry item.
|
|
41
|
+
|
|
42
|
+
If the registry item contains CSS variables, the CLI also writes them into the configured styles file.
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
### `uiscore init`
|
|
47
|
+
|
|
48
|
+
Creates `uiscore.config.json` in the current project root.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uiscore init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Generated config:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"registryUrl": "https://core.uiscore.io/registry/{name}.json",
|
|
61
|
+
"sourceRoot": "src/shared/ui",
|
|
62
|
+
"stylesPath": "src/shared/ui/styles/uiscore.css"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `uiscore add <name>`
|
|
67
|
+
|
|
68
|
+
Downloads a component from the configured registry and writes it into the current project.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uiscore add button
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Optional overwrite mode:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uiscore add button --overwrite
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Config
|
|
83
|
+
|
|
84
|
+
Create or edit `uiscore.config.json` in the target project root:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"registryUrl": "https://core.uiscore.io/registry/{name}.json",
|
|
89
|
+
"sourceRoot": "src/shared/ui",
|
|
90
|
+
"stylesPath": "src/shared/ui/styles/uiscore.css"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Fields
|
|
95
|
+
|
|
96
|
+
- `registryUrl`: URL template for registry items. `{name}` is replaced with the component name.
|
|
97
|
+
- `sourceRoot`: base directory inside the target project where generated files are written.
|
|
98
|
+
- `stylesPath`: file where UIScore CSS variables are written.
|
|
99
|
+
|
|
100
|
+
## Example Flow
|
|
101
|
+
|
|
102
|
+
In a consumer project:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx @uiscore/cli init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Update `uiscore.config.json` if you use a custom registry domain.
|
|
109
|
+
|
|
110
|
+
Then:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx @uiscore/cli add button
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
After that, import your generated styles once in the app entrypoint or root layout:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import "@/shared/ui/styles/uiscore.css";
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The CLI does not inject this import automatically.
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- Node `18+` is required.
|
|
127
|
+
- Existing files are not overwritten unless you pass `--overwrite`.
|
|
128
|
+
- The CLI currently assumes registry items already contain final file contents.
|
|
129
|
+
- Registry hosting and npm publishing are separate concerns:
|
|
130
|
+
- `@uiscore/cli` is the CLI package.
|
|
131
|
+
- your registry JSON files can live on any public HTTPS domain.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
Build:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run build
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Type-check:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm run lint:types
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Pack locally:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm pack
|
|
151
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/add.ts
|
|
4
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5
|
+
import path3 from "path";
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
|
+
|
|
8
|
+
// src/config.ts
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
var DEFAULT_CONFIG = {
|
|
12
|
+
registryUrl: "https://core.uiscore.io/registry/{name}.json",
|
|
13
|
+
sourceRoot: "src/shared/ui",
|
|
14
|
+
stylesPath: "src/shared/ui/styles/uiscore.css"
|
|
15
|
+
};
|
|
16
|
+
function getConfigPath(cwd) {
|
|
17
|
+
return path.join(cwd, "uiscore.config.json");
|
|
18
|
+
}
|
|
19
|
+
function loadConfig(cwd) {
|
|
20
|
+
const configPath = getConfigPath(cwd);
|
|
21
|
+
if (!existsSync(configPath)) {
|
|
22
|
+
return DEFAULT_CONFIG;
|
|
23
|
+
}
|
|
24
|
+
const rawConfig = readFileSync(configPath, "utf8");
|
|
25
|
+
const parsed = JSON.parse(rawConfig);
|
|
26
|
+
return {
|
|
27
|
+
registryUrl: parsed.registryUrl ?? DEFAULT_CONFIG.registryUrl,
|
|
28
|
+
sourceRoot: parsed.sourceRoot ?? DEFAULT_CONFIG.sourceRoot,
|
|
29
|
+
stylesPath: parsed.stylesPath ?? DEFAULT_CONFIG.stylesPath
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/utils/logger.ts
|
|
34
|
+
function info(message) {
|
|
35
|
+
console.log(message);
|
|
36
|
+
}
|
|
37
|
+
function warn(message) {
|
|
38
|
+
console.warn(message);
|
|
39
|
+
}
|
|
40
|
+
function error(message) {
|
|
41
|
+
console.error(message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/utils/package-manager.ts
|
|
45
|
+
import { existsSync as existsSync2 } from "fs";
|
|
46
|
+
import path2 from "path";
|
|
47
|
+
function detectPackageManager(cwd) {
|
|
48
|
+
if (process.env.npm_config_user_agent?.includes("pnpm")) {
|
|
49
|
+
return "pnpm";
|
|
50
|
+
}
|
|
51
|
+
if (process.env.npm_config_user_agent?.includes("yarn")) {
|
|
52
|
+
return "yarn";
|
|
53
|
+
}
|
|
54
|
+
if (existsSync2(path2.join(cwd, "pnpm-lock.yaml"))) {
|
|
55
|
+
return "pnpm";
|
|
56
|
+
}
|
|
57
|
+
if (existsSync2(path2.join(cwd, "yarn.lock"))) {
|
|
58
|
+
return "yarn";
|
|
59
|
+
}
|
|
60
|
+
if (existsSync2(path2.join(cwd, "package-lock.json"))) {
|
|
61
|
+
return "npm";
|
|
62
|
+
}
|
|
63
|
+
return "npm";
|
|
64
|
+
}
|
|
65
|
+
function dependencyInstallArgs(packageManager, dependencies) {
|
|
66
|
+
if (packageManager === "pnpm") {
|
|
67
|
+
return ["add", ...dependencies];
|
|
68
|
+
}
|
|
69
|
+
if (packageManager === "yarn") {
|
|
70
|
+
return ["add", ...dependencies];
|
|
71
|
+
}
|
|
72
|
+
return ["install", ...dependencies];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/commands/add.ts
|
|
76
|
+
async function runAddCommand(options) {
|
|
77
|
+
const config = loadConfig(options.cwd);
|
|
78
|
+
const url = config.registryUrl.replace("{name}", options.name);
|
|
79
|
+
info(`Fetching ${url}`);
|
|
80
|
+
const response = await fetch(url);
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Failed to fetch registry item: ${response.status} ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
const item = await response.json();
|
|
85
|
+
if (!item.files?.length) {
|
|
86
|
+
throw new Error(`Registry item "${options.name}" has no files.`);
|
|
87
|
+
}
|
|
88
|
+
const writtenFiles = [];
|
|
89
|
+
for (const file of item.files) {
|
|
90
|
+
const outputPath = path3.join(options.cwd, config.sourceRoot, file.target);
|
|
91
|
+
if (existsSync3(outputPath) && !options.overwrite) {
|
|
92
|
+
warn(`Skipped existing file: ${path3.relative(options.cwd, outputPath)}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
mkdirSync(path3.dirname(outputPath), { recursive: true });
|
|
96
|
+
writeFileSync(outputPath, file.content, "utf8");
|
|
97
|
+
writtenFiles.push(path3.relative(options.cwd, outputPath));
|
|
98
|
+
}
|
|
99
|
+
if (item.cssVars?.light) {
|
|
100
|
+
writeCssVars({
|
|
101
|
+
cwd: options.cwd,
|
|
102
|
+
stylesPath: config.stylesPath,
|
|
103
|
+
cssVars: item.cssVars.light
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (item.dependencies?.length) {
|
|
107
|
+
const packageManager = detectPackageManager(options.cwd);
|
|
108
|
+
const installArgs = dependencyInstallArgs(packageManager, item.dependencies);
|
|
109
|
+
info(`Installing dependencies with ${packageManager}...`);
|
|
110
|
+
const installResult = spawnSync(packageManager, installArgs, {
|
|
111
|
+
cwd: options.cwd,
|
|
112
|
+
stdio: "inherit",
|
|
113
|
+
shell: process.platform === "win32"
|
|
114
|
+
});
|
|
115
|
+
if (installResult.status !== 0) {
|
|
116
|
+
throw new Error("Dependency installation failed.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!writtenFiles.length) {
|
|
120
|
+
warn("No files were written.");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
info("Installed files:");
|
|
124
|
+
for (const filePath of writtenFiles) {
|
|
125
|
+
info(`- ${filePath}`);
|
|
126
|
+
}
|
|
127
|
+
info(`Import your generated styles once if needed: ${config.stylesPath}`);
|
|
128
|
+
}
|
|
129
|
+
function writeCssVars({
|
|
130
|
+
cwd,
|
|
131
|
+
stylesPath,
|
|
132
|
+
cssVars
|
|
133
|
+
}) {
|
|
134
|
+
const outputPath = path3.join(cwd, stylesPath);
|
|
135
|
+
mkdirSync(path3.dirname(outputPath), { recursive: true });
|
|
136
|
+
const existing = existsSync3(outputPath) ? readFileSync2(outputPath, "utf8") : "";
|
|
137
|
+
const markerStart = "/* uiscore:tokens:start */";
|
|
138
|
+
const markerEnd = "/* uiscore:tokens:end */";
|
|
139
|
+
const nextBlock = buildCssVarBlock(cssVars, markerStart, markerEnd);
|
|
140
|
+
if (!existing) {
|
|
141
|
+
writeFileSync(outputPath, nextBlock, "utf8");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (existing.includes(markerStart) && existing.includes(markerEnd)) {
|
|
145
|
+
const updated = existing.replace(
|
|
146
|
+
new RegExp(`${escapeForRegExp(markerStart)}[\\s\\S]*?${escapeForRegExp(markerEnd)}`),
|
|
147
|
+
nextBlock.trimEnd()
|
|
148
|
+
);
|
|
149
|
+
writeFileSync(outputPath, `${updated.trimEnd()}
|
|
150
|
+
`, "utf8");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
writeFileSync(outputPath, `${existing.trimEnd()}
|
|
154
|
+
|
|
155
|
+
${nextBlock}`, "utf8");
|
|
156
|
+
}
|
|
157
|
+
function buildCssVarBlock(cssVars, markerStart, markerEnd) {
|
|
158
|
+
const vars = Object.entries(cssVars).map(([key, value]) => ` --${key}: ${value};`).join("\n");
|
|
159
|
+
return `${markerStart}
|
|
160
|
+
:root {
|
|
161
|
+
${vars}
|
|
162
|
+
}
|
|
163
|
+
${markerEnd}
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
function escapeForRegExp(value) {
|
|
167
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/commands/init.ts
|
|
171
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
172
|
+
import path4 from "path";
|
|
173
|
+
function runInitCommand(cwd) {
|
|
174
|
+
const configPath = getConfigPath(cwd);
|
|
175
|
+
if (existsSync4(configPath)) {
|
|
176
|
+
warn(`Config already exists: ${configPath}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
mkdirSync2(path4.dirname(configPath), { recursive: true });
|
|
180
|
+
writeFileSync2(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
|
|
181
|
+
`, "utf8");
|
|
182
|
+
info(`Created ${configPath}`);
|
|
183
|
+
info("Update registryUrl before running `uiscore add` if needed.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/index.ts
|
|
187
|
+
async function main() {
|
|
188
|
+
const [, , command, ...args] = process.argv;
|
|
189
|
+
const cwd = process.cwd();
|
|
190
|
+
if (!command || command === "--help" || command === "-h") {
|
|
191
|
+
printHelp();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (command === "init") {
|
|
195
|
+
runInitCommand(cwd);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (command === "add") {
|
|
199
|
+
const name = args.find((arg) => !arg.startsWith("-"));
|
|
200
|
+
const overwrite = args.includes("--overwrite");
|
|
201
|
+
if (!name) {
|
|
202
|
+
throw new Error("Missing component name. Usage: uiscore add <name>");
|
|
203
|
+
}
|
|
204
|
+
await runAddCommand({
|
|
205
|
+
cwd,
|
|
206
|
+
name,
|
|
207
|
+
overwrite
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Unknown command: ${command}`);
|
|
212
|
+
}
|
|
213
|
+
function printHelp() {
|
|
214
|
+
info("UIScore CLI");
|
|
215
|
+
info("");
|
|
216
|
+
info("Commands:");
|
|
217
|
+
info(" uiscore init");
|
|
218
|
+
info(" uiscore add <name> [--overwrite]");
|
|
219
|
+
}
|
|
220
|
+
main().catch((err) => {
|
|
221
|
+
error(err instanceof Error ? err.message : "Unknown error");
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uiscore/cli",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "CLI for installing UIScore registry components into projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"uiscore": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format esm --dts --clean --target node18",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"lint:types": "tsc --noEmit",
|
|
20
|
+
"pack:local": "npm pack",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.15.17",
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"tsx": "^4.19.4",
|
|
31
|
+
"typescript": "^5.8.3"
|
|
32
|
+
}
|
|
33
|
+
}
|