@yopem-ui/cli 0.0.2 → 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 +121 -3
- package/package.json +4 -2
- package/src/cli.ts +98 -18
- package/src/init.ts +370 -164
- package/src/install.ts +422 -59
- package/src/project.ts +223 -0
package/README.md
CHANGED
|
@@ -1,19 +1,137 @@
|
|
|
1
1
|
# @yopem-ui/cli
|
|
2
2
|
|
|
3
3
|
Copy Yopem UI components and configure StyleX in an existing React project.
|
|
4
|
-
Requires Bun.
|
|
5
|
-
`http://localhost:3100` before using the CLI.
|
|
4
|
+
Requires Bun. Registry requests default to `https://ui.yopem.com/r`.
|
|
6
5
|
|
|
7
6
|
```sh
|
|
8
7
|
bunx @yopem-ui/cli init
|
|
9
8
|
bunx @yopem-ui/cli add button
|
|
10
9
|
bunx @yopem-ui/cli update button
|
|
10
|
+
bunx @yopem-ui/cli --help
|
|
11
|
+
bunx @yopem-ui/cli --version
|
|
11
12
|
```
|
|
12
13
|
|
|
14
|
+
No arguments or `--help` (`-h`) prints usage; `--version` (`-v`) prints the
|
|
15
|
+
installed CLI version. These commands need no project and exit successfully.
|
|
16
|
+
Invalid commands or arguments exit with status 1.
|
|
17
|
+
|
|
13
18
|
`init` supports Vite, TanStack Router, TanStack Start, React Router, Next.js App
|
|
14
19
|
Router, and Astro. Pass `--framework <name>` if autodetection is ambiguous.
|
|
15
20
|
Installed source changes stay intact on `init` and `add`; `update` rejects
|
|
16
|
-
modified files unless `--force` is supplied.
|
|
21
|
+
modified files unless `--force` is supplied. Installed files and their hashes
|
|
22
|
+
are tracked in `ui.json`.
|
|
23
|
+
|
|
24
|
+
## Dry run
|
|
25
|
+
|
|
26
|
+
Preview `add` or `update` before installing:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
bunx @yopem-ui/cli add button --dry-run
|
|
30
|
+
bunx @yopem-ui/cli update button --dry-run
|
|
31
|
+
bunx @yopem-ui/cli update button --dry-run --force
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The plan lists file writes (including tracking in `ui.json`), skips, all local
|
|
35
|
+
file conflicts, and runtime/dev dependency additions. Forced source overwrites
|
|
36
|
+
are marked explicitly. As in normal mode, `add` skips modified tracked files;
|
|
37
|
+
`update` reports them as conflicts without `--force`. Conflicts block a real
|
|
38
|
+
install until resolved or forced. Dependency lists show the package arguments
|
|
39
|
+
the installer would request, not package-manager version resolution.
|
|
40
|
+
|
|
41
|
+
Dry runs still read the registry and validate URLs, schemas, paths, and file
|
|
42
|
+
integrity. They write no files, create no directories or temporary files, never
|
|
43
|
+
run a package manager, and do not create or change `ui.json`, project config,
|
|
44
|
+
package manifests, lockfiles, or dependencies. `--cwd` and `--registry` work as
|
|
45
|
+
usual. `init --dry-run` and `initProject({ dryRun: true })` are rejected before
|
|
46
|
+
any changes.
|
|
47
|
+
|
|
48
|
+
Programmatic callers use `installItem(name, { dryRun: true })` through
|
|
49
|
+
`@yopem-ui/cli/install`. The result adds `preview.files` (`path`, `action`,
|
|
50
|
+
optional `reason` and `forced`), `preview.dependencies`, and
|
|
51
|
+
`preview.devDependencies`; `installed` is zero. Normal calls retain their
|
|
52
|
+
`{ installed, skipped }` result. Previewed writes apply when no conflicts block
|
|
53
|
+
the command and files have not changed before a subsequent real install.
|
|
54
|
+
|
|
55
|
+
## Registry
|
|
56
|
+
|
|
57
|
+
Pass `--registry <URL>` to `init`, `add`, or `update` to use another registry.
|
|
58
|
+
The override applies to that command only; it is not stored in `ui.json`. URLs
|
|
59
|
+
must use HTTPS, except HTTP on `localhost` or loopback addresses. Credentials,
|
|
60
|
+
query strings, and fragments are rejected.
|
|
61
|
+
|
|
62
|
+
For local development, start this repository's docs server with `bun run dev`:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
bunx @yopem-ui/cli init --registry http://localhost:3100/r
|
|
66
|
+
bunx @yopem-ui/cli add button --registry http://localhost:3100/r
|
|
67
|
+
bunx @yopem-ui/cli update button --registry http://localhost:3100/r
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Each request has a 30-second timeout covering connection and JSON body reads.
|
|
71
|
+
Failures report timeout, network, HTTP status, or malformed JSON details without
|
|
72
|
+
writing source, configuration, or dependencies. Programmatic callers can set
|
|
73
|
+
`InstallOptions.registryUrl` and `requestTimeoutMs` (milliseconds).
|
|
74
|
+
|
|
75
|
+
## Monorepos
|
|
76
|
+
|
|
77
|
+
Target an app from the repository root with `--cwd`. Package manager detection
|
|
78
|
+
uses the app and its workspace root; dependencies stay in the target package.
|
|
79
|
+
Bun, npm, pnpm, and Yarn workspaces are supported.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
bunx @yopem-ui/cli init --cwd apps/web
|
|
83
|
+
bunx @yopem-ui/cli add button --cwd apps/web
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For shared components, use an existing named React package in the same
|
|
87
|
+
workspace. `--ui` is relative to the target app, not the repository root.
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
bunx @yopem-ui/cli init --cwd apps/web --ui ../../packages/ui
|
|
91
|
+
bunx @yopem-ui/cli add button --cwd packages/ui
|
|
92
|
+
bunx @yopem-ui/cli update button --cwd packages/ui
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`init --ui` installs base files in the UI package, adds source subpath exports,
|
|
96
|
+
links the app, and configures StyleX to scan both packages. Next.js also gets
|
|
97
|
+
`transpilePackages`. Init registers shared package imports with the Yopem UI
|
|
98
|
+
Oxlint rules, preserving existing rule options and explicit opt-outs. Shared
|
|
99
|
+
source imports use the UI package name, such as `@acme/ui/components/ui/button`;
|
|
100
|
+
later `add` and `update` reuse that name from its `ui.json`. Run `init --ui` for
|
|
101
|
+
each consuming app and keep passing `--ui` when repeating init. Existing
|
|
102
|
+
conflicting exports or build configuration require manual review; init does not
|
|
103
|
+
migrate app-local components.
|
|
104
|
+
|
|
105
|
+
## Development checks
|
|
106
|
+
|
|
107
|
+
From the repository root, generate registry fixtures and build the lint plugin
|
|
108
|
+
before running CLI tests:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
bun run registry:build
|
|
112
|
+
bun run --cwd packages/oxlint-plugin build
|
|
113
|
+
bun test packages/cli
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`bun run --cwd packages/cli test` also runs the CLI suite. Tests use `bun:test`,
|
|
117
|
+
not browsers; packaged installation checks explicitly pass a local `--registry`
|
|
118
|
+
and serve registry JSON with Bun when port 3100 has no registry server. Registry
|
|
119
|
+
network tests use isolated local Bun servers for successful workflows, header
|
|
120
|
+
and body stalls, malformed JSON, and HTTP/network failures. Command logs and
|
|
121
|
+
configuration artifacts are saved in `packages/cli/test-results/`.
|
|
122
|
+
|
|
123
|
+
`bun test packages/cli/test/dry-run.test.ts` checks real fixture projects with
|
|
124
|
+
local registries, whole-project file/directory snapshots, dependency-call
|
|
125
|
+
tracking, conflict/force previews, shared import prefixes, validation failures,
|
|
126
|
+
and CLI argument rules. Snapshot and preview evidence is saved in
|
|
127
|
+
`step5-dry-run.json` under the results directory.
|
|
128
|
+
|
|
129
|
+
`bun test packages/cli/test/public.test.ts` runs packed CLI production smoke
|
|
130
|
+
checks: help, version matching the packed manifest, dry-run previews, init, add
|
|
131
|
+
Button, lint, and build in Vite and Next.js App Router apps, including shared UI
|
|
132
|
+
workspaces. Requires Node.js for Next.js and network access for fixture
|
|
133
|
+
dependencies. Tests verify build artifacts and compiled StyleX CSS, then save
|
|
134
|
+
`packaged-*.log`, `packaged-*.html`, and `packaged-*.css` evidence.
|
|
17
135
|
|
|
18
136
|
## Licence
|
|
19
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yopem-ui/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "CLI for installing Yopem UI source components and StyleX setup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
"exports": {
|
|
28
28
|
"./cli": "./src/cli.ts",
|
|
29
29
|
"./init": "./src/init.ts",
|
|
30
|
-
"./install": "./src/install.ts"
|
|
30
|
+
"./install": "./src/install.ts",
|
|
31
|
+
"./project": "./src/project.ts"
|
|
31
32
|
},
|
|
32
33
|
"publishConfig": {
|
|
33
34
|
"access": "public"
|
|
34
35
|
},
|
|
35
36
|
"scripts": {
|
|
37
|
+
"test": "bun test ./test",
|
|
36
38
|
"typecheck": "tsc --noEmit"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import type { InitOptions } from "
|
|
3
|
+
import type { InitOptions } from "@yopem-ui/cli/init"
|
|
4
|
+
import type { JsonValue } from "@yopem-ui/cli/install"
|
|
4
5
|
|
|
5
|
-
import { initProject } from "
|
|
6
|
-
import { installItem } from "
|
|
6
|
+
import { initProject } from "@yopem-ui/cli/init"
|
|
7
|
+
import { installItem, isRecord, isString } from "@yopem-ui/cli/install"
|
|
8
|
+
import { resolve } from "node:path"
|
|
7
9
|
|
|
8
10
|
const usage =
|
|
9
|
-
"Usage: yopem-ui init [--framework vite|tanstack-router|tanstack-start|react-router|next|astro] | <add|update> <name> [--force]"
|
|
11
|
+
"Usage: yopem-ui [--help | --version] | init [--framework vite|tanstack-router|tanstack-start|react-router|next|astro] [--ui <path>] [--cwd <path>] [--registry <URL>] | <add|update> <name> [--force] [--dry-run] [--cwd <path>] [--registry <URL>]"
|
|
10
12
|
|
|
11
13
|
function isFramework(
|
|
12
14
|
value: string | undefined,
|
|
@@ -22,21 +24,78 @@ function isFramework(
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export async function runCli(args: string[], options: InitOptions = {}) {
|
|
25
|
-
const [command,
|
|
27
|
+
const [command, ...rest] = args
|
|
28
|
+
|
|
29
|
+
if (command === undefined || command === "--help" || command === "-h") {
|
|
30
|
+
if (rest.length) throw new Error(usage)
|
|
31
|
+
console.info(usage)
|
|
32
|
+
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (command === "--version" || command === "-v") {
|
|
37
|
+
if (rest.length) throw new Error(usage)
|
|
38
|
+
|
|
39
|
+
const manifest: JsonValue = await Bun.file(
|
|
40
|
+
new URL("../package.json", import.meta.url),
|
|
41
|
+
).json()
|
|
42
|
+
|
|
43
|
+
if (!isRecord(manifest) || !isString(manifest.version)) {
|
|
44
|
+
throw new Error("Invalid CLI package version")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.info(manifest.version)
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const positional: string[] = []
|
|
53
|
+
const flags = new Map<string, string>()
|
|
54
|
+
|
|
55
|
+
for (let index = 0; index < rest.length; index++) {
|
|
56
|
+
const argument = rest[index]!
|
|
57
|
+
|
|
58
|
+
if (!argument.startsWith("--")) {
|
|
59
|
+
positional.push(argument)
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (flags.has(argument)) throw new Error(usage)
|
|
64
|
+
|
|
65
|
+
if (argument === "--force" || argument === "--dry-run") {
|
|
66
|
+
flags.set(argument, "true")
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!["--cwd", "--ui", "--framework", "--registry"].includes(argument))
|
|
71
|
+
throw new Error(usage)
|
|
72
|
+
const value = rest[++index]
|
|
73
|
+
|
|
74
|
+
if (!value || value.startsWith("--")) throw new Error(usage)
|
|
75
|
+
flags.set(argument, value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const cwd = resolve(options.cwd ?? process.cwd(), flags.get("--cwd") ?? ".")
|
|
79
|
+
const registryUrl = flags.get("--registry") ?? options.registryUrl
|
|
26
80
|
|
|
27
81
|
if (command === "init") {
|
|
28
|
-
|
|
82
|
+
if (flags.has("--dry-run") || options.dryRun)
|
|
83
|
+
throw new Error("--dry-run is not supported for init")
|
|
84
|
+
const framework = flags.get("--framework")
|
|
29
85
|
|
|
30
86
|
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
87
|
+
positional.length ||
|
|
88
|
+
flags.has("--force") ||
|
|
89
|
+
(framework !== undefined && !isFramework(framework))
|
|
90
|
+
)
|
|
34
91
|
throw new Error(usage)
|
|
35
|
-
}
|
|
36
92
|
|
|
37
93
|
const result = await initProject({
|
|
38
94
|
...options,
|
|
39
|
-
|
|
95
|
+
cwd,
|
|
96
|
+
registryUrl,
|
|
97
|
+
ui: flags.get("--ui") ?? options.ui,
|
|
98
|
+
framework: isFramework(framework) ? framework : options.framework,
|
|
40
99
|
})
|
|
41
100
|
|
|
42
101
|
console.info(
|
|
@@ -48,21 +107,42 @@ export async function runCli(args: string[], options: InitOptions = {}) {
|
|
|
48
107
|
|
|
49
108
|
if (
|
|
50
109
|
(command !== "add" && command !== "update") ||
|
|
51
|
-
|
|
52
|
-
flags.
|
|
110
|
+
positional.length !== 1 ||
|
|
111
|
+
flags.has("--ui") ||
|
|
112
|
+
flags.has("--framework")
|
|
53
113
|
) {
|
|
54
114
|
throw new Error(usage)
|
|
55
115
|
}
|
|
56
116
|
|
|
57
|
-
const result = await installItem(
|
|
117
|
+
const result = await installItem(positional[0]!, {
|
|
58
118
|
...options,
|
|
119
|
+
cwd,
|
|
120
|
+
registryUrl,
|
|
59
121
|
mode: command,
|
|
60
|
-
force: flags.
|
|
122
|
+
force: flags.has("--force"),
|
|
123
|
+
dryRun: flags.has("--dry-run") || options.dryRun,
|
|
61
124
|
})
|
|
62
125
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
126
|
+
if (result.preview) {
|
|
127
|
+
const { files, dependencies, devDependencies } = result.preview
|
|
128
|
+
const labels = { write: "Write", skip: "Skip", conflict: "Conflict" }
|
|
129
|
+
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
console.info(
|
|
132
|
+
`${labels[file.action]} ${file.reason ?? file.path}${file.forced ? " (forced overwrite)" : ""}`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.info(`Runtime dependencies: ${dependencies.join(", ") || "none"}`)
|
|
137
|
+
console.info(`Dev dependencies: ${devDependencies.join(", ") || "none"}`)
|
|
138
|
+
console.info(
|
|
139
|
+
`Dry run: ${files.filter((file) => file.action === "write").length} file write(s), ${files.filter((file) => file.action === "skip").length} skip(s), ${files.filter((file) => file.action === "conflict").length} conflict(s). No files written.`,
|
|
140
|
+
)
|
|
141
|
+
} else {
|
|
142
|
+
console.info(
|
|
143
|
+
`Installed ${result.installed} file(s), skipped ${result.skipped}`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
66
146
|
|
|
67
147
|
return result
|
|
68
148
|
}
|