@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 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
+ ```
@@ -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
+ }