@r0hitsharma/uikit-cli 0.12.0-rohit-fork-ci.1
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 +286 -0
- package/dist/cli.js +225 -0
- package/dist/cli.sh +10 -0
- package/dist/command-executor.js +41 -0
- package/dist/commands/doctor.js +518 -0
- package/dist/commands/format.js +34 -0
- package/dist/commands/link.js +252 -0
- package/dist/commands/lint.js +43 -0
- package/dist/commands/register.js +35 -0
- package/dist/commands/unlink.js +68 -0
- package/dist/fs-utils.js +56 -0
- package/dist/link-validator.js +80 -0
- package/dist/logger.js +43 -0
- package/dist/package-discovery.js +218 -0
- package/dist/shell-utils.js +9 -0
- package/dist/tool-binaries.js +16 -0
- package/dist/types.js +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# @r0hitsharma/uikit-cli
|
|
2
|
+
|
|
3
|
+
CLI tool for local package linking during active development with consumer repositories.
|
|
4
|
+
|
|
5
|
+
## Setup (One-time)
|
|
6
|
+
|
|
7
|
+
### 1. Configure npm prefix for writable global packages
|
|
8
|
+
|
|
9
|
+
If using nix-managed Node.js, configure npm to use a writable location:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm config set prefix ~/.npm-global
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Add to your shell profile (e.g., `~/.zshrc`):
|
|
16
|
+
```bash
|
|
17
|
+
export PATH="$HOME/.npm-global/bin:$PATH"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Link CLI in uikit monorepo
|
|
21
|
+
|
|
22
|
+
From the uikit repository root:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm link --workspace packages/uikit-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This makes the CLI available globally via workspace linking.
|
|
29
|
+
|
|
30
|
+
### 3. Link CLI into consumer repository
|
|
31
|
+
|
|
32
|
+
From your consumer repository root:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm link @r0hitsharma/uikit-cli --workspace <workspace-name>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Example for a consuming project:
|
|
39
|
+
```bash
|
|
40
|
+
cd /path/to/your-project/ts
|
|
41
|
+
npm link @r0hitsharma/uikit-cli --workspace ui
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Run lint and format tools without downstream installs
|
|
47
|
+
|
|
48
|
+
From any consumer workspace:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
./node_modules/.bin/uikit-cli lint -c ./.oxlintrc.ts src panda.config.ts vite.config.ts
|
|
52
|
+
./node_modules/.bin/uikit-cli format -c ./.oxfmtrc.ts --write "src/**/*.ts" "src/**/*.tsx" panda.config.ts vite.config.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The CLI runs uikit-cli-managed `oxlint` and `oxfmt` versions internally (resolved via its
|
|
56
|
+
lockfile), so downstream workspaces do not need to declare those tool packages directly.
|
|
57
|
+
|
|
58
|
+
`format` fills in `-c ./.oxfmtrc.ts` when you omit a config flag and that file is in the
|
|
59
|
+
current directory, because `oxfmt` only auto-discovers the `.json` form. `lint` deliberately
|
|
60
|
+
does not do the same: `oxlint` resolves `oxlint.config.ts` per target file by walking up from
|
|
61
|
+
it, so a run spanning several packages picks up each package's own config. Injecting `-c`
|
|
62
|
+
would replace that with whichever config sits in the current directory, which in the git-hook
|
|
63
|
+
shape — cwd at the repo root, targets in several packages — silently drops those packages'
|
|
64
|
+
rules. Pass `-c` yourself when you do want one config to win.
|
|
65
|
+
|
|
66
|
+
`lint` applies `--max-warnings=0` by default, because `oxlint` exits 0 on warnings and the
|
|
67
|
+
shared presets set the `correctness` and `suspicious` categories to `warn`. Pass your own
|
|
68
|
+
`--max-warnings` or `--deny-warnings` to change that.
|
|
69
|
+
|
|
70
|
+
If your consumer workspace prefers to run tooling directly, it can install and invoke
|
|
71
|
+
`oxlint`/`oxfmt` itself. In that setup, `@r0hitsharma/oxlint-config` and
|
|
72
|
+
`@r0hitsharma/oxfmt-config` remain reusable config packages, while `uikit-cli` remains an
|
|
73
|
+
optional workflow wrapper.
|
|
74
|
+
|
|
75
|
+
### Check a generated stylesheet for silently-dropped CSS
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
./node_modules/.bin/uikit-cli doctor # finds styled-system/styles.css
|
|
79
|
+
./node_modules/.bin/uikit-cli doctor path/to/styles.css # explicit path
|
|
80
|
+
./node_modules/.bin/uikit-cli doctor --codegen # runs `panda cssgen` itself
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use `--codegen` when you use the Panda PostCSS plugin and never write a frozen
|
|
84
|
+
`styled-system/styles.css`.
|
|
85
|
+
|
|
86
|
+
Findings carry a severity. `doctor` exits non-zero on any **error**, so it gates CI;
|
|
87
|
+
**warnings** are printed and do not affect the exit code. Only roleless `colorPalette` can
|
|
88
|
+
warn — see [error or warning](#roleless-colorpalette-error-or-warning) below.
|
|
89
|
+
|
|
90
|
+
The design system's authoring model has failure modes that produce *no error anywhere* — the
|
|
91
|
+
build passes, the console is clean, and the style simply does not apply. `doctor` scans the
|
|
92
|
+
generated stylesheet for them:
|
|
93
|
+
|
|
94
|
+
| Check | Severity | What silently breaks |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `missing-static-css` | error | No design-system recipe classes were emitted at all, so runtime-selected variants (status tones, dense tables, drawer sizes) render unstyled. Fix by spreading `designSystemStaticCssRecipes` into your Panda config's `staticCss.recipes`. |
|
|
97
|
+
| `unresolved-token` | error | A declaration whose value is a bare `token.path` (e.g. `color: text.subtle`) rather than a `var(--…)`. Invalid CSS the browser drops. |
|
|
98
|
+
| `roleless-color-palette` | error or warning | A `colorPalette` value that defines no role for the property styled with it — `var(--colors-color-palette-solid-bg)` is well-formed but undefined in that scope, so the browser drops the declaration. Only `neutral`, `gray`, `green`, `red`, `amber` and `blue` carry full role sub-tokens; other hues map the 50–950 scale only. |
|
|
99
|
+
|
|
100
|
+
#### Roleless `colorPalette`: error or warning
|
|
101
|
+
|
|
102
|
+
A role reference is only a *definite* miss when every `colorPalette` its scope can be set to
|
|
103
|
+
lacks the role — then no combination of that recipe's variants avoids the dropped declaration.
|
|
104
|
+
When some assignable palette does define the role, the breaking pairing is possible but
|
|
105
|
+
unproven: a recipe exposing `colorPalette: violet` alongside a `solid` emphasis variant may
|
|
106
|
+
never combine the two in an app, and the stylesheet cannot say. That is reported as a warning —
|
|
107
|
+
worth printing, not worth failing a build over.
|
|
108
|
+
|
|
109
|
+
| Palettes assignable in the scope | Which lack the role | Severity | Exit code |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| one | it does | error | non-zero |
|
|
112
|
+
| several | all of them | error | non-zero |
|
|
113
|
+
| several | some of them | warning | 0 |
|
|
114
|
+
| several | none | no finding | 0 |
|
|
115
|
+
| none (no `colorPalette` assigned in scope) | — | no finding | 0 |
|
|
116
|
+
|
|
117
|
+
#### Roleless `colorPalette`: detection boundary
|
|
118
|
+
|
|
119
|
+
Resolving which palette applies to a declaration is the CSS cascade, and `doctor` does not
|
|
120
|
+
simulate it. It resolves exactly one scope — a **recipe**, keyed by the class-name stem Panda
|
|
121
|
+
derives from `className` — and flags a role reference only when a palette assigned somewhere in
|
|
122
|
+
that same recipe's classes fails to define the role. Slots share the scope, since the root slot's
|
|
123
|
+
palette properties cascade into the others.
|
|
124
|
+
|
|
125
|
+
These cases are *not* flagged, on purpose, because scope is ambiguous and a false pass is far
|
|
126
|
+
cheaper than a false failure:
|
|
127
|
+
|
|
128
|
+
- atomic utilities (`.color-palette_violet` plus a separate `.bg_colorPalette\.solid\.bg`) — the
|
|
129
|
+
stylesheet cannot prove the two classes land on the same element, nor that an ancestor already
|
|
130
|
+
supplied the role;
|
|
131
|
+
- a palette set on an outer recipe with the role consumed by an inner one;
|
|
132
|
+
- `var(--colors-color-palette-…, fallback)` — a fallback means the declaration survives.
|
|
133
|
+
|
|
134
|
+
The palette→roles map is read out of the stylesheet itself (each `colorPalette` assignment
|
|
135
|
+
ruleset *is* the preset's role tokens in generated form), so palettes added to the preset — or
|
|
136
|
+
defined in a consumer's own preset extension — are covered with no list to keep in sync.
|
|
137
|
+
|
|
138
|
+
### Register the local uikit packages
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
./node_modules/.bin/uikit-cli register
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Runs `npm link` in every local `@r0hitsharma/*` package so they are available to link
|
|
145
|
+
from. `link` and `unlink` do this themselves, so run it directly only to register the
|
|
146
|
+
packages without touching a consumer. Pass `--uikit-root <path>` when the uikit checkout
|
|
147
|
+
cannot be auto-discovered.
|
|
148
|
+
|
|
149
|
+
### Link uikit packages into a consumer repository
|
|
150
|
+
|
|
151
|
+
From your consumer repository:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
./node_modules/.bin/uikit-cli link
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This command links all `@r0hitsharma/*` packages from your local uikit monorepo into your consumer project, allowing you to develop packages and see changes immediately.
|
|
158
|
+
|
|
159
|
+
Verify links are working:
|
|
160
|
+
```bash
|
|
161
|
+
./node_modules/.bin/uikit-cli link --verify
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Add this script to your consumer's `package.json`:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"scripts": {
|
|
169
|
+
"uikit:link": "./node_modules/.bin/uikit-cli link",
|
|
170
|
+
"uikit:unlink": "./node_modules/.bin/uikit-cli unlink"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Restore registry versions
|
|
176
|
+
|
|
177
|
+
When co-development is complete, restore published versions from npm:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
./node_modules/.bin/uikit-cli unlink
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## How it works
|
|
184
|
+
|
|
185
|
+
The CLI manages local development links by:
|
|
186
|
+
|
|
187
|
+
1. Auto-registering local `@r0hitsharma/*` packages from your uikit checkout via `npm link`
|
|
188
|
+
2. Linking only the consumer workspaces that actually depend on those packages
|
|
189
|
+
3. Cleaning up shadow installs and Vite caches to ensure symlinks work correctly
|
|
190
|
+
4. Using `--preserve-symlinks` flag and bundling to avoid ES module resolution issues
|
|
191
|
+
|
|
192
|
+
The CLI automatically detects the consumer root and all dependent packages, working from any
|
|
193
|
+
directory within the project. Both consumer shapes are supported: an **npm-workspaces monorepo**
|
|
194
|
+
(the root with a `workspaces` field), and a **single package** that installs uikit directly (no
|
|
195
|
+
`workspaces` field) — in the single-package case the root package is linked directly.
|
|
196
|
+
|
|
197
|
+
The CLI auto-discovers the local uikit monorepo for typical sibling-checkout layouts.
|
|
198
|
+
|
|
199
|
+
## Requirements
|
|
200
|
+
|
|
201
|
+
- Local clone of the uikit monorepo
|
|
202
|
+
- Node.js 24+ and npm installed
|
|
203
|
+
- Writable npm prefix configured (see Setup)
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
### "EACCES: permission denied" when using npm link
|
|
208
|
+
|
|
209
|
+
You need to configure npm to use a writable prefix location. See Setup step 1 above.
|
|
210
|
+
|
|
211
|
+
### "ENOENT: no such file or directory" errors
|
|
212
|
+
|
|
213
|
+
Use the workspace-based linking approach (Setup steps 2-3) instead of global npm link. The CLI bundle includes `--preserve-symlinks` to handle ES module resolution with symlinks.
|
|
214
|
+
|
|
215
|
+
### Links not working after linking
|
|
216
|
+
|
|
217
|
+
Run with `--verify` flag to check link status:
|
|
218
|
+
```bash
|
|
219
|
+
./node_modules/.bin/uikit-cli link --verify
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `useTheme must be used within ThemeProvider` (duplicated React context) after linking
|
|
223
|
+
|
|
224
|
+
With `resolve.preserveSymlinks: true` (which linking relies on) Vite can serve a linked package
|
|
225
|
+
under **two** URLs at once — `/node_modules/…` (the root symlink) and `/@fs/…` (the real path) —
|
|
226
|
+
producing two module graphs and two copies of singletons like `ThemeContext`. A component and its
|
|
227
|
+
`ThemeProvider` then hold different context objects and the hook throws. This is a link artifact,
|
|
228
|
+
not a bug in the app or the design system.
|
|
229
|
+
|
|
230
|
+
To confirm: look for the same module served at both URLs in the dev server (both HTTP 200). To
|
|
231
|
+
recover: run `uikit-cli unlink` and restart the dev server (touch the Vite config so it
|
|
232
|
+
re-optimizes) — the module collapses back to a single URL. To stay linked, add the linked
|
|
233
|
+
package(s) plus `react`/`react-dom` to your Vite `resolve.dedupe`, or drop `preserveSymlinks`.
|
|
234
|
+
|
|
235
|
+
### A partial link reported as success
|
|
236
|
+
|
|
237
|
+
If `link` prints that some packages did **not** link (PARTIAL), the named packages may still
|
|
238
|
+
resolve to a stale registry version. A consumer `.npmrc` with `min-release-age` is the usual
|
|
239
|
+
cause — npm rejects a fresh prerelease with `ETARGET`. Re-run with an override (e.g.
|
|
240
|
+
`--min-release-age=0`) or link those packages manually.
|
|
241
|
+
|
|
242
|
+
### The CLI cannot repair a stale copy of itself
|
|
243
|
+
|
|
244
|
+
`link` links `@r0hitsharma/*` into the consumer — and that includes `uikit-cli` itself. So
|
|
245
|
+
`npm run uikit:link` runs whatever `uikit-cli` is currently in the consumer's `node_modules`: if
|
|
246
|
+
that copy is **stale**, the old code runs, and no source fix can change the run that needs it (a
|
|
247
|
+
stale binary can't even report its own staleness). When developing the CLI, or right after pulling
|
|
248
|
+
CLI changes, bypass the linked copy and invoke the monorepo's freshly-built binary directly so you
|
|
249
|
+
always run current code:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm run build --workspace packages/uikit-cli # in the uikit checkout
|
|
253
|
+
node <path-to-uikit>/packages/uikit-cli/dist/cli.js link --uikit-root <path-to-uikit>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Or install the published version from the registry, which sidesteps linking entirely.
|
|
257
|
+
|
|
258
|
+
## Development workflow
|
|
259
|
+
|
|
260
|
+
In a consumer workspace:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# One-time setup (see Setup section above)
|
|
264
|
+
npm link @r0hitsharma/uikit-cli --workspace <workspace-name>
|
|
265
|
+
|
|
266
|
+
# Link uikit packages for local development
|
|
267
|
+
npm run uikit:link
|
|
268
|
+
|
|
269
|
+
# Verify links
|
|
270
|
+
npm run uikit:link -- --verify
|
|
271
|
+
|
|
272
|
+
# Later, restore registry versions
|
|
273
|
+
npm run uikit:unlink
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Debug mode
|
|
277
|
+
|
|
278
|
+
Run with debug output:
|
|
279
|
+
```bash
|
|
280
|
+
UIKIT_DEBUG=1 ./node_modules/.bin/uikit-cli link --verify
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Or use the `--debug` flag:
|
|
284
|
+
```bash
|
|
285
|
+
./node_modules/.bin/uikit-cli link --debug --verify
|
|
286
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { NpmCommandExecutor } from './command-executor.js';
|
|
5
|
+
import { DoctorCommand } from './commands/doctor.js';
|
|
6
|
+
import { FormatCommand } from './commands/format.js';
|
|
7
|
+
import { LinkCommand } from './commands/link.js';
|
|
8
|
+
import { LintCommand } from './commands/lint.js';
|
|
9
|
+
import { RegisterCommand } from './commands/register.js';
|
|
10
|
+
import { UnlinkCommand } from './commands/unlink.js';
|
|
11
|
+
import { RealFileSystem } from './fs-utils.js';
|
|
12
|
+
import { LinkValidator } from './link-validator.js';
|
|
13
|
+
import { ConsoleLogger } from './logger.js';
|
|
14
|
+
import { PackageDiscovery } from './package-discovery.js';
|
|
15
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
/**
|
|
17
|
+
* Parse command line arguments
|
|
18
|
+
*/
|
|
19
|
+
function parseArgs(argv) {
|
|
20
|
+
const args = argv.slice(2);
|
|
21
|
+
if (args.length === 0) {
|
|
22
|
+
throw new Error('Usage: uikit-cli <register|link|unlink|lint|format|doctor> [--verify] [--debug] [--uikit-root <path>] [args...]');
|
|
23
|
+
}
|
|
24
|
+
const mode = args[0];
|
|
25
|
+
const validModes = [
|
|
26
|
+
'lint',
|
|
27
|
+
'format',
|
|
28
|
+
'register',
|
|
29
|
+
'link',
|
|
30
|
+
'unlink',
|
|
31
|
+
'doctor',
|
|
32
|
+
];
|
|
33
|
+
if (!validModes.includes(mode)) {
|
|
34
|
+
throw new Error(`Unknown command: ${mode}`);
|
|
35
|
+
}
|
|
36
|
+
let uikitRoot;
|
|
37
|
+
let verify = false;
|
|
38
|
+
let debug = false;
|
|
39
|
+
const commandArgs = [];
|
|
40
|
+
let i = 1;
|
|
41
|
+
while (i < args.length) {
|
|
42
|
+
const arg = args[i];
|
|
43
|
+
// Unreachable given the loop bound; narrows `arg` to `string`.
|
|
44
|
+
if (arg === undefined)
|
|
45
|
+
break;
|
|
46
|
+
if (arg === '--uikit-root' && i + 1 < args.length) {
|
|
47
|
+
uikitRoot = args[i + 1];
|
|
48
|
+
i += 2;
|
|
49
|
+
}
|
|
50
|
+
else if (arg === '--verify') {
|
|
51
|
+
verify = true;
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
else if (arg === '--debug') {
|
|
55
|
+
debug = true;
|
|
56
|
+
i += 1;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
commandArgs.push(arg);
|
|
60
|
+
i += 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Check for UIKIT_DEBUG environment variable
|
|
64
|
+
if (process.env.UIKIT_DEBUG === '1') {
|
|
65
|
+
debug = true;
|
|
66
|
+
}
|
|
67
|
+
// Resolve uikit root
|
|
68
|
+
const fs = new RealFileSystem();
|
|
69
|
+
const discovery = new PackageDiscovery(fs);
|
|
70
|
+
if (debug) {
|
|
71
|
+
console.log('[DEBUG parseArgs] Resolving uikit root...');
|
|
72
|
+
}
|
|
73
|
+
if (!uikitRoot) {
|
|
74
|
+
// Try relative to script location
|
|
75
|
+
const relativeRoot = path.resolve(scriptDir, '../../../..');
|
|
76
|
+
if (debug) {
|
|
77
|
+
console.log('[DEBUG parseArgs] Checking relative root:', relativeRoot);
|
|
78
|
+
}
|
|
79
|
+
if (discovery.isValidUIKitRoot(relativeRoot)) {
|
|
80
|
+
uikitRoot = relativeRoot;
|
|
81
|
+
if (debug) {
|
|
82
|
+
console.log('[DEBUG parseArgs] Found valid uikit root at relative location');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format' && mode !== 'doctor') {
|
|
87
|
+
// Try to find from consumer
|
|
88
|
+
try {
|
|
89
|
+
const tempConsumerRoot = discovery.findConsumerRoot(process.cwd());
|
|
90
|
+
const foundRoot = discovery.findUIKitRootFromConsumer(tempConsumerRoot);
|
|
91
|
+
if (foundRoot) {
|
|
92
|
+
uikitRoot = foundRoot;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Will throw error below if needed
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format' && mode !== 'doctor') {
|
|
100
|
+
// Try walking up from cwd
|
|
101
|
+
const foundRoot = discovery.findUIKitRoot(process.cwd());
|
|
102
|
+
if (foundRoot) {
|
|
103
|
+
uikitRoot = foundRoot;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format' && mode !== 'doctor') {
|
|
107
|
+
throw new Error('Could not find uikit root directory.\n' +
|
|
108
|
+
'Tried:\n' +
|
|
109
|
+
' - Relative to script location\n' +
|
|
110
|
+
' - Sibling to consumer root\n' +
|
|
111
|
+
' - Walking up from cwd\n' +
|
|
112
|
+
'Use --uikit-root to specify manually.');
|
|
113
|
+
}
|
|
114
|
+
let consumerRoot = null;
|
|
115
|
+
if (mode === 'link' || mode === 'unlink') {
|
|
116
|
+
consumerRoot = discovery.findConsumerRoot(process.cwd());
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
mode,
|
|
120
|
+
consumerRoot,
|
|
121
|
+
uikitRoot: uikitRoot ?? '',
|
|
122
|
+
commandArgs,
|
|
123
|
+
verify,
|
|
124
|
+
debug,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Ensure CLI binary is built before registering
|
|
129
|
+
*/
|
|
130
|
+
function ensureCliBinaryBuilt(uikitRoot, executor) {
|
|
131
|
+
const cliPackagePath = path.join(uikitRoot, 'packages/uikit-cli');
|
|
132
|
+
const distPath = path.join(cliPackagePath, 'dist/cli.js');
|
|
133
|
+
const fs = new RealFileSystem();
|
|
134
|
+
if (!fs.exists(distPath)) {
|
|
135
|
+
console.log('Building CLI binary...');
|
|
136
|
+
executor.exec('npm run build', { cwd: cliPackagePath });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Link CLI into consumer for convenience
|
|
141
|
+
*/
|
|
142
|
+
function linkCliIntoConsumer(consumerRoot, executor, logger) {
|
|
143
|
+
const result = executor.exec('npm link "@r0hitsharma/uikit-cli" --package-lock=false --save=false --no-workspaces', { cwd: consumerRoot });
|
|
144
|
+
if (!result.success) {
|
|
145
|
+
logger.warn('Could not link the local @r0hitsharma/uikit-cli — you may be running ' +
|
|
146
|
+
'the registry version, which can lag behind the local one (e.g. missing ' +
|
|
147
|
+
'`doctor --codegen`). A consumer .npmrc with `min-release-age` can reject ' +
|
|
148
|
+
'a fresh prerelease with ETARGET here.');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Main entry point
|
|
153
|
+
*/
|
|
154
|
+
try {
|
|
155
|
+
const parsed = parseArgs(process.argv);
|
|
156
|
+
const { mode, consumerRoot, uikitRoot, commandArgs, verify, debug } = parsed;
|
|
157
|
+
if (debug) {
|
|
158
|
+
console.log('[DEBUG] Parsed args:', {
|
|
159
|
+
mode,
|
|
160
|
+
consumerRoot,
|
|
161
|
+
uikitRoot,
|
|
162
|
+
verify,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Initialize dependencies
|
|
166
|
+
const fs = new RealFileSystem();
|
|
167
|
+
const executor = new NpmCommandExecutor(debug);
|
|
168
|
+
const logger = new ConsoleLogger(debug);
|
|
169
|
+
const discovery = new PackageDiscovery(fs);
|
|
170
|
+
const validator = new LinkValidator(fs, logger);
|
|
171
|
+
// Lint and format forward the tool's verdict as the exit code — exiting 0
|
|
172
|
+
// unconditionally left `--check` runs unable to gate CI.
|
|
173
|
+
if (mode === 'lint') {
|
|
174
|
+
const lintCmd = new LintCommand(executor);
|
|
175
|
+
process.exit(lintCmd.execute(commandArgs) ? 0 : 1);
|
|
176
|
+
}
|
|
177
|
+
if (mode === 'format') {
|
|
178
|
+
const formatCmd = new FormatCommand(executor, fs);
|
|
179
|
+
process.exit(formatCmd.execute(commandArgs) ? 0 : 1);
|
|
180
|
+
}
|
|
181
|
+
// Doctor scans the consumer's generated CSS; it runs in the consumer cwd and
|
|
182
|
+
// needs no uikit root. Non-zero exit on findings so it gates CI.
|
|
183
|
+
if (mode === 'doctor') {
|
|
184
|
+
const doctorCmd = new DoctorCommand(fs, logger, executor);
|
|
185
|
+
process.exit(doctorCmd.execute(commandArgs) ? 0 : 1);
|
|
186
|
+
}
|
|
187
|
+
// Handle register command
|
|
188
|
+
if (mode === 'register') {
|
|
189
|
+
ensureCliBinaryBuilt(uikitRoot, executor);
|
|
190
|
+
const registerCmd = new RegisterCommand(discovery, executor, logger);
|
|
191
|
+
registerCmd.execute(uikitRoot);
|
|
192
|
+
if (verify) {
|
|
193
|
+
logger.info('\\nVerifying registration...');
|
|
194
|
+
// Could add verification logic here
|
|
195
|
+
logger.info('✓ Registration verified');
|
|
196
|
+
}
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
// Handle link/unlink commands (require consumerRoot)
|
|
200
|
+
if (!consumerRoot) {
|
|
201
|
+
throw new Error('Consumer root is required for link/unlink operations.');
|
|
202
|
+
}
|
|
203
|
+
// Register packages and link CLI before link/unlink
|
|
204
|
+
ensureCliBinaryBuilt(uikitRoot, executor);
|
|
205
|
+
const registerCmd = new RegisterCommand(discovery, executor, logger);
|
|
206
|
+
registerCmd.execute(uikitRoot);
|
|
207
|
+
linkCliIntoConsumer(consumerRoot, executor, logger);
|
|
208
|
+
if (mode === 'link') {
|
|
209
|
+
const linkCmd = new LinkCommand(discovery, executor, validator, fs, logger);
|
|
210
|
+
linkCmd.execute(consumerRoot, uikitRoot, verify);
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
if (mode === 'unlink') {
|
|
214
|
+
const unlinkCmd = new UnlinkCommand(executor, logger);
|
|
215
|
+
unlinkCmd.execute(consumerRoot, uikitRoot, discovery);
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (process.env.UIKIT_DEBUG) {
|
|
221
|
+
console.error('Full error:', error);
|
|
222
|
+
}
|
|
223
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
package/dist/cli.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Use --preserve-symlinks to fix ES module resolution with npm link
|
|
3
|
+
# Follow symlink to find the actual script location
|
|
4
|
+
if [ -L "$0" ]; then
|
|
5
|
+
SCRIPT="$(readlink -f "$0" 2>/dev/null || readlink "$0")"
|
|
6
|
+
else
|
|
7
|
+
SCRIPT="$0"
|
|
8
|
+
fi
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
|
|
10
|
+
exec node --preserve-symlinks "$SCRIPT_DIR/cli.js" "$@"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Real npm command executor using execSync
|
|
4
|
+
*/
|
|
5
|
+
export class NpmCommandExecutor {
|
|
6
|
+
debugMode;
|
|
7
|
+
constructor(debugMode = false) {
|
|
8
|
+
this.debugMode = debugMode;
|
|
9
|
+
}
|
|
10
|
+
exec(command, options) {
|
|
11
|
+
const { cwd = process.cwd(), silent = false } = options || {};
|
|
12
|
+
if (this.debugMode) {
|
|
13
|
+
console.log(`[DEBUG] Executing: ${command}`);
|
|
14
|
+
console.log(`[DEBUG] CWD: ${cwd}`);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const stdout = execSync(command, {
|
|
18
|
+
cwd,
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: silent ? 'pipe' : 'inherit',
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
stdout: typeof stdout === 'string' ? stdout : '',
|
|
24
|
+
stderr: '',
|
|
25
|
+
success: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const err = error;
|
|
30
|
+
return {
|
|
31
|
+
stdout: err.stdout?.toString('utf8') || '',
|
|
32
|
+
stderr: err.stderr?.toString('utf8') || err.message || 'Unknown error',
|
|
33
|
+
success: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
execQuiet(command, options) {
|
|
38
|
+
const result = this.exec(command, { ...options, silent: true });
|
|
39
|
+
return result.success;
|
|
40
|
+
}
|
|
41
|
+
}
|