@dofu-lab/simui-cli 0.1.0
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 +148 -0
- package/dist/index.js +177 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @simui/cli
|
|
2
|
+
|
|
3
|
+
A CLI to add [SimUI](https://simui.dev) Angular components to your project instantly.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Using npx (no install required)
|
|
9
|
+
npx @simui/cli add accordion-01
|
|
10
|
+
|
|
11
|
+
# Or install globally
|
|
12
|
+
npm install -g @simui/cli
|
|
13
|
+
simui add accordion-01
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Commands
|
|
17
|
+
|
|
18
|
+
### `simui add <component>`
|
|
19
|
+
|
|
20
|
+
Fetches a component from the SimUI registry and writes it to your project.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
simui add select-01
|
|
24
|
+
simui add dialog-03
|
|
25
|
+
simui add button-07
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Options:**
|
|
29
|
+
|
|
30
|
+
| Flag | Default | Description |
|
|
31
|
+
|------|---------|-------------|
|
|
32
|
+
| `-p, --path <dir>` | `src/app/components` | Output directory (relative to cwd) |
|
|
33
|
+
|
|
34
|
+
**Examples:**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Default path: src/app/components/accordion-01.component.ts
|
|
38
|
+
simui add accordion-01
|
|
39
|
+
|
|
40
|
+
# Custom output directory
|
|
41
|
+
simui add badge-05 --path src/shared/ui
|
|
42
|
+
|
|
43
|
+
# Short flag alias
|
|
44
|
+
simui add input-12 -p src/features/auth
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## What it does
|
|
48
|
+
|
|
49
|
+
1. **Fetches** the component TypeScript source from `https://simui.dev/registry/<name>.json`
|
|
50
|
+
2. **Writes** the file to the resolved output path
|
|
51
|
+
3. **Detects** required `@spartan-ng/helm/*` and `@ng-icons/*` imports
|
|
52
|
+
4. **Prompts** you to install them:
|
|
53
|
+
- Spartan UI packages — installed via `npx @spartan-ng/cli@latest add <package>`
|
|
54
|
+
- `@ng-icons/*` packages — installed via your detected package manager (pnpm / yarn / npm)
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Node.js ≥ 18
|
|
59
|
+
- An Angular project with [Spartan UI](https://spartan.ng) configured
|
|
60
|
+
|
|
61
|
+
## Browse components
|
|
62
|
+
|
|
63
|
+
Visit [simui.dev](https://simui.dev) to browse all available components and their previews.
|
|
64
|
+
|
|
65
|
+
## Publishing
|
|
66
|
+
|
|
67
|
+
Follow these steps to publish a new version of `@simui/cli` to the npm registry.
|
|
68
|
+
|
|
69
|
+
1. Run tests and linters
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# from packages/cli
|
|
73
|
+
npm test # if you have tests
|
|
74
|
+
npm run lint # if you have a lint script
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
2. Bump the package version (creates a git commit and tag)
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd packages/cli
|
|
81
|
+
npm version patch # or `minor` / `major`
|
|
82
|
+
# push the commit and tag afterwards
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. Update changelog (optional)
|
|
86
|
+
|
|
87
|
+
Edit `packages/cli/CHANGELOG.md` or add release notes for the new version.
|
|
88
|
+
|
|
89
|
+
4. Build the package
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
cd packages/cli
|
|
93
|
+
npm run build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
5. Dry-run publish / pack
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# create a tarball for inspection
|
|
100
|
+
npm pack
|
|
101
|
+
|
|
102
|
+
# simulate publish (shows what will be published)
|
|
103
|
+
npm publish --dry-run
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
6. Login to npm (if needed)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm whoami || npm login
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
7. Publish to npm
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cd packages/cli
|
|
116
|
+
npm publish --access public
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
8. Push changes and tags to the remote repo
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# from repo root
|
|
123
|
+
git push origin main
|
|
124
|
+
git push --tags
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
9. Create a GitHub release (optional)
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
gh release create vX.Y.Z --title "vX.Y.Z" --notes "Short changelog"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
10. Verify installation
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# try via npx
|
|
137
|
+
npx @simui/cli@latest add accordion-01
|
|
138
|
+
|
|
139
|
+
# or install globally
|
|
140
|
+
npm install -g @simui/cli
|
|
141
|
+
simui add accordion-01
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Notes:
|
|
145
|
+
- If your package is scoped (e.g. `@simui/cli`) publishing publicly requires `--access public`.
|
|
146
|
+
- If your npm account has 2FA enabled, you'll be prompted for an OTP when publishing.
|
|
147
|
+
- For CI-based publishing, use an `NPM_TOKEN` and run `npm publish` from the pipeline.
|
|
148
|
+
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/add.ts
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
10
|
+
import { join as join2, resolve } from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
|
|
13
|
+
// src/utils/registry.ts
|
|
14
|
+
var REGISTRY_BASE_URL = "https://simui.dev/registry";
|
|
15
|
+
async function fetchComponent(name) {
|
|
16
|
+
const url = `${REGISTRY_BASE_URL}/${name}.json`;
|
|
17
|
+
let response;
|
|
18
|
+
try {
|
|
19
|
+
response = await fetch(url);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`Network error: could not reach ${url}`);
|
|
22
|
+
}
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
if (response.status === 404 || response.status >= 500) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Component "${name}" was not found in the SimUI registry.
|
|
27
|
+
Browse available components at https://simui.dev`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Failed to fetch "${name}" (HTTP ${response.status})`);
|
|
31
|
+
}
|
|
32
|
+
let data;
|
|
33
|
+
try {
|
|
34
|
+
data = await response.json();
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error(`Invalid response from registry for "${name}"`);
|
|
37
|
+
}
|
|
38
|
+
if (typeof data !== "object" || data === null || !("content" in data) || typeof data.content !== "string") {
|
|
39
|
+
throw new Error(`Unexpected registry format for "${name}" \u2014 expected { content: string }`);
|
|
40
|
+
}
|
|
41
|
+
return data.content;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/utils/deps.ts
|
|
45
|
+
import { existsSync } from "fs";
|
|
46
|
+
import { join } from "path";
|
|
47
|
+
function parseDeps(content) {
|
|
48
|
+
const spartanHelmSet = /* @__PURE__ */ new Set();
|
|
49
|
+
const ngIconsSet = /* @__PURE__ */ new Set();
|
|
50
|
+
const spartanRegex = /from\s+['"]@spartan-ng\/helm\/([a-z0-9-]+)['"]/g;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = spartanRegex.exec(content)) !== null) {
|
|
53
|
+
spartanHelmSet.add(match[1]);
|
|
54
|
+
}
|
|
55
|
+
const ngIconsRegex = /from\s+['"](@ng-icons\/[a-z0-9-]+)['"]/g;
|
|
56
|
+
while ((match = ngIconsRegex.exec(content)) !== null) {
|
|
57
|
+
ngIconsSet.add(match[1]);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
spartanHelm: [...spartanHelmSet],
|
|
61
|
+
ngIcons: [...ngIconsSet]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function detectPackageManager(cwd) {
|
|
65
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
66
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
67
|
+
return "npm";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/commands/add.ts
|
|
71
|
+
async function addComponent(componentName, options) {
|
|
72
|
+
const cwd = process.cwd();
|
|
73
|
+
p.intro(chalk.bold.cyan(`SimUI`) + chalk.dim(` \u2014 adding ${componentName}`));
|
|
74
|
+
const spinner2 = p.spinner();
|
|
75
|
+
spinner2.start("Fetching component from registry\u2026");
|
|
76
|
+
let content;
|
|
77
|
+
try {
|
|
78
|
+
content = await fetchComponent(componentName);
|
|
79
|
+
spinner2.stop(chalk.green("Component fetched"));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
spinner2.stop(chalk.red("Failed to fetch component"));
|
|
82
|
+
p.outro(chalk.red(String(err instanceof Error ? err.message : err)));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const outputDir = options.path ? resolve(cwd, options.path) : join2(cwd, "src", "app", "components");
|
|
86
|
+
const outputFile = join2(outputDir, `${componentName}.component.ts`);
|
|
87
|
+
if (existsSync2(outputFile)) {
|
|
88
|
+
const overwrite = await p.confirm({
|
|
89
|
+
message: `${chalk.yellow(outputFile.replace(cwd + "/", ""))} already exists. Overwrite?`,
|
|
90
|
+
initialValue: false
|
|
91
|
+
});
|
|
92
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
93
|
+
p.outro(chalk.dim("Aborted."));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(outputDir, { recursive: true });
|
|
99
|
+
writeFileSync(outputFile, content, "utf-8");
|
|
100
|
+
} catch (err) {
|
|
101
|
+
p.outro(chalk.red(`Could not write file: ${err instanceof Error ? err.message : err}`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const relativePath = outputFile.replace(cwd + "/", "");
|
|
105
|
+
p.log.success(`Created ${chalk.cyan(relativePath)}`);
|
|
106
|
+
const { spartanHelm, ngIcons } = parseDeps(content);
|
|
107
|
+
const totalDeps = spartanHelm.length + ngIcons.length;
|
|
108
|
+
if (totalDeps === 0) {
|
|
109
|
+
p.outro(chalk.green("Done! No extra dependencies detected."));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
p.log.info(chalk.bold("Dependencies detected in this component:"));
|
|
113
|
+
for (const d of spartanHelm) {
|
|
114
|
+
p.log.message(` ${chalk.cyan("@spartan-ng/helm/")}${d} ${chalk.dim("(via Spartan CLI)")}`);
|
|
115
|
+
}
|
|
116
|
+
for (const d of ngIcons) {
|
|
117
|
+
p.log.message(` ${chalk.cyan(d)}`);
|
|
118
|
+
}
|
|
119
|
+
const shouldInstall = await p.confirm({
|
|
120
|
+
message: `Install ${totalDeps} dependenc${totalDeps === 1 ? "y" : "ies"} now?`,
|
|
121
|
+
initialValue: true
|
|
122
|
+
});
|
|
123
|
+
if (p.isCancel(shouldInstall) || !shouldInstall) {
|
|
124
|
+
p.log.warn("Skipped dependency installation. Install them manually before using the component.");
|
|
125
|
+
p.outro(chalk.dim("Done."));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const pm = detectPackageManager(cwd);
|
|
129
|
+
p.log.info(`Using ${chalk.bold(pm)}`);
|
|
130
|
+
for (const helmPkg of spartanHelm) {
|
|
131
|
+
const installSpinner = p.spinner();
|
|
132
|
+
installSpinner.start(`Installing @spartan-ng/helm/${helmPkg}\u2026`);
|
|
133
|
+
try {
|
|
134
|
+
execSync(`npx @spartan-ng/cli@latest add ${helmPkg}`, {
|
|
135
|
+
cwd,
|
|
136
|
+
stdio: "pipe"
|
|
137
|
+
});
|
|
138
|
+
installSpinner.stop(chalk.green(`@spartan-ng/helm/${helmPkg} installed`));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
installSpinner.stop(chalk.red(`Failed to install @spartan-ng/helm/${helmPkg}`));
|
|
141
|
+
const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : "";
|
|
142
|
+
p.log.warn(chalk.dim(stderr.trim() || String(err)));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (ngIcons.length > 0) {
|
|
146
|
+
const ngIconsStr = ngIcons.join(" ");
|
|
147
|
+
const installCmd = pm === "pnpm" ? `pnpm add ${ngIconsStr}` : pm === "yarn" ? `yarn add ${ngIconsStr}` : `npm install ${ngIconsStr}`;
|
|
148
|
+
const installSpinner = p.spinner();
|
|
149
|
+
installSpinner.start(`Installing ${ngIconsStr}\u2026`);
|
|
150
|
+
try {
|
|
151
|
+
execSync(installCmd, { cwd, stdio: "pipe" });
|
|
152
|
+
installSpinner.stop(chalk.green(`${ngIconsStr} installed`));
|
|
153
|
+
} catch (err) {
|
|
154
|
+
installSpinner.stop(chalk.red(`Failed to install ${ngIconsStr}`));
|
|
155
|
+
p.log.warn(`Run manually: ${chalk.bold(installCmd)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
p.outro(chalk.green("All done! Your component is ready to use."));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/index.ts
|
|
162
|
+
import { createRequire } from "module";
|
|
163
|
+
import { fileURLToPath } from "url";
|
|
164
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
165
|
+
var require2 = createRequire(import.meta.url);
|
|
166
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
167
|
+
var __dirname = dirname2(__filename);
|
|
168
|
+
var pkg = require2(join3(__dirname, "..", "package.json"));
|
|
169
|
+
var program = new Command();
|
|
170
|
+
program.name("simui").description("Add SimUI Angular components to your project").version(pkg.version);
|
|
171
|
+
program.command("add <component>").description("Fetch a SimUI component from the registry and add it to your project").option(
|
|
172
|
+
"-p, --path <path>",
|
|
173
|
+
"Output directory relative to cwd (default: src/app/components)"
|
|
174
|
+
).action(async (component, opts) => {
|
|
175
|
+
await addComponent(component, { path: opts.path });
|
|
176
|
+
});
|
|
177
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dofu-lab/simui-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to add SimUI components to your Angular project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"simui": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@clack/prompts": "^0.9.1",
|
|
18
|
+
"chalk": "^5.4.1",
|
|
19
|
+
"commander": "^13.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"tsup": "^8.4.0",
|
|
23
|
+
"typescript": "^5.8.3"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"angular",
|
|
30
|
+
"simui",
|
|
31
|
+
"spartan",
|
|
32
|
+
"cli",
|
|
33
|
+
"components"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/tdphuong/simui"
|
|
39
|
+
}
|
|
40
|
+
}
|