@archon-research/uikit-cli 0.2.2 → 0.3.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 +98 -20
- package/dist/cli.js +165 -217
- package/dist/cli.sh +10 -0
- package/dist/command-executor.js +41 -0
- package/dist/commands/format.js +36 -0
- package/dist/commands/link.js +162 -0
- package/dist/commands/lint.js +14 -0
- package/dist/commands/register.js +35 -0
- package/dist/commands/unlink.js +64 -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 +168 -0
- package/dist/shell-utils.js +9 -0
- package/dist/types.js +1 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,31 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
CLI tool for local package linking during active development with consumer repositories.
|
|
4
4
|
|
|
5
|
-
##
|
|
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:
|
|
6
23
|
|
|
7
24
|
```bash
|
|
8
|
-
npm
|
|
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 @archon-research/uikit-cli --workspace <workspace-name>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Example for stl-verify:
|
|
39
|
+
```bash
|
|
40
|
+
cd /path/to/stl-verify/ts
|
|
41
|
+
npm link @archon-research/uikit-cli --workspace ui
|
|
9
42
|
```
|
|
10
43
|
|
|
11
44
|
## Usage
|
|
12
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 pinned `oxlint` and `oxfmt` versions internally, so downstream workspaces do not
|
|
56
|
+
need to declare those tool packages directly.
|
|
57
|
+
|
|
13
58
|
### Link uikit packages into a consumer repository
|
|
14
59
|
|
|
15
60
|
From your consumer repository:
|
|
16
61
|
|
|
17
62
|
```bash
|
|
18
|
-
|
|
63
|
+
./node_modules/.bin/uikit-cli link
|
|
19
64
|
```
|
|
20
65
|
|
|
21
66
|
This command links all `@archon-research/*` packages from your local uikit monorepo into your consumer project, allowing you to develop packages and see changes immediately.
|
|
22
67
|
|
|
68
|
+
Verify links are working:
|
|
69
|
+
```bash
|
|
70
|
+
./node_modules/.bin/uikit-cli link --verify
|
|
71
|
+
```
|
|
72
|
+
|
|
23
73
|
Add this script to your consumer's `package.json`:
|
|
24
74
|
|
|
25
75
|
```json
|
|
26
76
|
{
|
|
27
77
|
"scripts": {
|
|
28
|
-
"uikit:link": "uikit-cli link",
|
|
29
|
-
"uikit:unlink": "uikit-cli unlink"
|
|
78
|
+
"uikit:link": "./node_modules/.bin/uikit-cli link",
|
|
79
|
+
"uikit:unlink": "./node_modules/.bin/uikit-cli unlink"
|
|
30
80
|
}
|
|
31
81
|
}
|
|
32
82
|
```
|
|
@@ -36,43 +86,71 @@ Add this script to your consumer's `package.json`:
|
|
|
36
86
|
When co-development is complete, restore published versions from npm:
|
|
37
87
|
|
|
38
88
|
```bash
|
|
39
|
-
|
|
89
|
+
./node_modules/.bin/uikit-cli unlink
|
|
40
90
|
```
|
|
41
91
|
|
|
42
92
|
## How it works
|
|
43
93
|
|
|
44
|
-
The CLI manages
|
|
94
|
+
The CLI manages local development links by:
|
|
45
95
|
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
96
|
+
1. Auto-registering local `@archon-research/*` packages from your uikit checkout via `npm link`
|
|
97
|
+
2. Linking only consumer workspaces that actually depend on those packages
|
|
98
|
+
3. Cleaning up shadow installs and Vite caches to ensure symlinks work correctly
|
|
99
|
+
4. Using `--preserve-symlinks` flag and bundling to avoid ES module resolution issues
|
|
49
100
|
|
|
50
101
|
The CLI automatically detects the consumer workspace root and all dependent packages, working from any directory within the workspace.
|
|
51
102
|
|
|
103
|
+
The CLI auto-discovers the local uikit monorepo for typical sibling-checkout layouts.
|
|
104
|
+
|
|
52
105
|
## Requirements
|
|
53
106
|
|
|
54
107
|
- Local clone of the uikit monorepo
|
|
55
|
-
- Node.js and npm installed
|
|
108
|
+
- Node.js 24+ and npm installed
|
|
109
|
+
- Writable npm prefix configured (see Setup)
|
|
110
|
+
|
|
111
|
+
## Troubleshooting
|
|
112
|
+
|
|
113
|
+
### "EACCES: permission denied" when using npm link
|
|
114
|
+
|
|
115
|
+
You need to configure npm to use a writable prefix location. See Setup step 1 above.
|
|
56
116
|
|
|
57
|
-
|
|
117
|
+
### "ENOENT: no such file or directory" errors
|
|
58
118
|
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
### Links not working after linking
|
|
122
|
+
|
|
123
|
+
Run with `--verify` flag to check link status:
|
|
124
|
+
```bash
|
|
125
|
+
./node_modules/.bin/uikit-cli link --verify
|
|
126
|
+
```
|
|
62
127
|
|
|
63
128
|
## Development workflow
|
|
64
129
|
|
|
65
|
-
In a
|
|
130
|
+
In a consumer workspace:
|
|
66
131
|
|
|
67
132
|
```bash
|
|
133
|
+
# One-time setup (see Setup section above)
|
|
134
|
+
npm link @archon-research/uikit-cli --workspace <workspace-name>
|
|
135
|
+
|
|
68
136
|
# Link uikit packages for local development
|
|
69
137
|
npm run uikit:link
|
|
70
138
|
|
|
71
|
-
#
|
|
139
|
+
# Verify links
|
|
140
|
+
npm run uikit:link -- --verify
|
|
141
|
+
|
|
142
|
+
# Later, restore registry versions
|
|
72
143
|
npm run uikit:unlink
|
|
73
144
|
```
|
|
74
145
|
|
|
75
|
-
##
|
|
146
|
+
## Debug mode
|
|
76
147
|
|
|
77
|
-
|
|
78
|
-
|
|
148
|
+
Run with debug output:
|
|
149
|
+
```bash
|
|
150
|
+
UIKIT_DEBUG=1 ./node_modules/.bin/uikit-cli link --verify
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Or use the `--debug` flag:
|
|
154
|
+
```bash
|
|
155
|
+
./node_modules/.bin/uikit-cli link --debug --verify
|
|
156
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -1,250 +1,198 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
2
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function readJson(filePath) {
|
|
26
|
-
const content = readFileSync(filePath, 'utf8');
|
|
27
|
-
return JSON.parse(content);
|
|
28
|
-
}
|
|
29
|
-
function getWorkspacePatterns(rootDir) {
|
|
30
|
-
const pkg = readJson(path.join(rootDir, 'package.json'));
|
|
31
|
-
const workspaces = pkg.workspaces;
|
|
32
|
-
if (Array.isArray(workspaces)) {
|
|
33
|
-
return workspaces;
|
|
34
|
-
}
|
|
35
|
-
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
36
|
-
return workspaces.packages;
|
|
37
|
-
}
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
function resolveWorkspacePattern(rootDir, pattern) {
|
|
41
|
-
if (pattern.endsWith('/*')) {
|
|
42
|
-
const base = pattern.slice(0, -2);
|
|
43
|
-
const absBase = path.join(rootDir, base);
|
|
44
|
-
return readdirSync(absBase, { withFileTypes: true })
|
|
45
|
-
.filter((entry) => entry.isDirectory())
|
|
46
|
-
.map((entry) => path.join(base, entry.name));
|
|
47
|
-
}
|
|
48
|
-
return [pattern];
|
|
49
|
-
}
|
|
50
|
-
function loadWorkspaces(rootDir) {
|
|
51
|
-
const patterns = getWorkspacePatterns(rootDir);
|
|
52
|
-
const locations = patterns.flatMap((pattern) => resolveWorkspacePattern(rootDir, pattern));
|
|
53
|
-
return locations
|
|
54
|
-
.map((location) => {
|
|
55
|
-
const pkgPath = path.join(rootDir, location, 'package.json');
|
|
56
|
-
const pkg = readJson(pkgPath);
|
|
57
|
-
return {
|
|
58
|
-
...pkg,
|
|
59
|
-
location,
|
|
60
|
-
path: path.join(rootDir, location),
|
|
61
|
-
};
|
|
62
|
-
})
|
|
63
|
-
.filter((ws) => Boolean(ws.name));
|
|
64
|
-
}
|
|
65
|
-
function findConsumerRoot(startDir) {
|
|
66
|
-
let current = startDir;
|
|
67
|
-
while (true) {
|
|
68
|
-
const pkgPath = path.join(current, 'package.json');
|
|
69
|
-
try {
|
|
70
|
-
const content = readFileSync(pkgPath, 'utf8');
|
|
71
|
-
const pkg = JSON.parse(content);
|
|
72
|
-
if (pkg.workspaces) {
|
|
73
|
-
return current;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// File not found or parse error, continue up.
|
|
78
|
-
}
|
|
79
|
-
const parent = path.dirname(current);
|
|
80
|
-
if (parent === current) {
|
|
81
|
-
throw new Error('Could not find consumer workspace root (no package.json with workspaces field)');
|
|
82
|
-
}
|
|
83
|
-
current = parent;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { RealFileSystem } from './fs-utils.js';
|
|
5
|
+
import { NpmCommandExecutor } from './command-executor.js';
|
|
6
|
+
import { ConsoleLogger } from './logger.js';
|
|
7
|
+
import { PackageDiscovery } from './package-discovery.js';
|
|
8
|
+
import { LinkValidator } from './link-validator.js';
|
|
9
|
+
import { RegisterCommand } from './commands/register.js';
|
|
10
|
+
import { LinkCommand } from './commands/link.js';
|
|
11
|
+
import { UnlinkCommand } from './commands/unlink.js';
|
|
12
|
+
import { LintCommand } from './commands/lint.js';
|
|
13
|
+
import { FormatCommand } from './commands/format.js';
|
|
14
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
/**
|
|
16
|
+
* Parse command line arguments
|
|
17
|
+
*/
|
|
86
18
|
function parseArgs(argv) {
|
|
87
19
|
const args = argv.slice(2);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
20
|
+
if (args.length === 0) {
|
|
21
|
+
throw new Error('Usage: uikit-cli <register|link|unlink|lint|format> [--verify] [--debug] [--uikit-root <path>] [args...]');
|
|
22
|
+
}
|
|
23
|
+
const mode = args[0];
|
|
24
|
+
const validModes = ['lint', 'format', 'register', 'link', 'unlink'];
|
|
25
|
+
if (!validModes.includes(mode)) {
|
|
26
|
+
throw new Error(`Unknown command: ${mode}`);
|
|
27
|
+
}
|
|
28
|
+
let uikitRoot;
|
|
29
|
+
let verify = false;
|
|
30
|
+
let debug = false;
|
|
31
|
+
const commandArgs = [];
|
|
32
|
+
let i = 1;
|
|
33
|
+
while (i < args.length) {
|
|
91
34
|
const arg = args[i];
|
|
92
|
-
if (arg === '--
|
|
93
|
-
|
|
35
|
+
if (arg === '--uikit-root' && i + 1 < args.length) {
|
|
36
|
+
uikitRoot = args[i + 1];
|
|
37
|
+
i += 2;
|
|
38
|
+
}
|
|
39
|
+
else if (arg === '--verify') {
|
|
40
|
+
verify = true;
|
|
94
41
|
i += 1;
|
|
95
42
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
return { mode, consumerRoot };
|
|
101
|
-
}
|
|
102
|
-
function collectWorkspaceRequirements(workspaces, supportedNames) {
|
|
103
|
-
const neededByWorkspace = new Map();
|
|
104
|
-
for (const ws of workspaces) {
|
|
105
|
-
const fields = [
|
|
106
|
-
ws.dependencies ?? {},
|
|
107
|
-
ws.devDependencies ?? {},
|
|
108
|
-
ws.optionalDependencies ?? {},
|
|
109
|
-
ws.peerDependencies ?? {},
|
|
110
|
-
];
|
|
111
|
-
const needed = new Set();
|
|
112
|
-
for (const depField of fields) {
|
|
113
|
-
for (const depName of Object.keys(depField)) {
|
|
114
|
-
if (supportedNames.has(depName)) {
|
|
115
|
-
needed.add(depName);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
43
|
+
else if (arg === '--debug') {
|
|
44
|
+
debug = true;
|
|
45
|
+
i += 1;
|
|
118
46
|
}
|
|
119
|
-
|
|
120
|
-
|
|
47
|
+
else {
|
|
48
|
+
commandArgs.push(arg);
|
|
49
|
+
i += 1;
|
|
121
50
|
}
|
|
122
51
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
52
|
+
// Check for UIKIT_DEBUG environment variable
|
|
53
|
+
if (process.env.UIKIT_DEBUG === '1') {
|
|
54
|
+
debug = true;
|
|
55
|
+
}
|
|
56
|
+
// Resolve uikit root
|
|
57
|
+
const fs = new RealFileSystem();
|
|
58
|
+
const discovery = new PackageDiscovery(fs);
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log('[DEBUG parseArgs] Resolving uikit root...');
|
|
61
|
+
}
|
|
62
|
+
if (!uikitRoot) {
|
|
63
|
+
// Try relative to script location
|
|
64
|
+
const relativeRoot = path.resolve(scriptDir, '../../../..');
|
|
65
|
+
if (debug) {
|
|
66
|
+
console.log('[DEBUG parseArgs] Checking relative root:', relativeRoot);
|
|
134
67
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.join(' ');
|
|
141
|
-
if (rootPackageArgs) {
|
|
142
|
-
run(`npm link ${rootPackageArgs} --package-lock=false --save=false`, consumerRoot);
|
|
143
|
-
}
|
|
144
|
-
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
145
|
-
const packageArgs = names
|
|
146
|
-
.map((name) => dirByName.get(name))
|
|
147
|
-
.filter((pkgDir) => Boolean(pkgDir))
|
|
148
|
-
.map((pkgDir) => `"${pkgDir}"`)
|
|
149
|
-
.join(' ');
|
|
150
|
-
if (!packageArgs) {
|
|
151
|
-
continue;
|
|
68
|
+
if (discovery.isValidUIKitRoot(relativeRoot)) {
|
|
69
|
+
uikitRoot = relativeRoot;
|
|
70
|
+
if (debug) {
|
|
71
|
+
console.log('[DEBUG parseArgs] Found valid uikit root at relative location');
|
|
72
|
+
}
|
|
152
73
|
}
|
|
153
|
-
run(`npm link ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, consumerRoot);
|
|
154
74
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
for (const name of names) {
|
|
164
|
-
allNames.add(name);
|
|
75
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format') {
|
|
76
|
+
// Try to find from consumer
|
|
77
|
+
try {
|
|
78
|
+
const tempConsumerRoot = discovery.findConsumerRoot(process.cwd());
|
|
79
|
+
const foundRoot = discovery.findUIKitRootFromConsumer(tempConsumerRoot);
|
|
80
|
+
if (foundRoot) {
|
|
81
|
+
uikitRoot = foundRoot;
|
|
82
|
+
}
|
|
165
83
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const ok = tryRun(`npm unlink "${name}" --package-lock=false --save=false`, consumerRoot);
|
|
169
|
-
if (!ok) {
|
|
170
|
-
console.warn(`Unable to unlink ${name} at root; continuing.`);
|
|
84
|
+
catch {
|
|
85
|
+
// Will throw error below if needed
|
|
171
86
|
}
|
|
172
87
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
88
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format') {
|
|
89
|
+
// Try walking up from cwd
|
|
90
|
+
const foundRoot = discovery.findUIKitRoot(process.cwd());
|
|
91
|
+
if (foundRoot) {
|
|
92
|
+
uikitRoot = foundRoot;
|
|
179
93
|
}
|
|
180
94
|
}
|
|
95
|
+
if (!uikitRoot && mode !== 'lint' && mode !== 'format') {
|
|
96
|
+
throw new Error('Could not find uikit root directory.\n' +
|
|
97
|
+
'Tried:\n' +
|
|
98
|
+
' - Relative to script location\n' +
|
|
99
|
+
' - Sibling to consumer root\n' +
|
|
100
|
+
' - Walking up from cwd\n' +
|
|
101
|
+
'Use --uikit-root to specify manually.');
|
|
102
|
+
}
|
|
103
|
+
let consumerRoot = null;
|
|
104
|
+
if (mode === 'link' || mode === 'unlink') {
|
|
105
|
+
consumerRoot = discovery.findConsumerRoot(process.cwd());
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
mode,
|
|
109
|
+
consumerRoot,
|
|
110
|
+
uikitRoot: uikitRoot ?? '',
|
|
111
|
+
commandArgs,
|
|
112
|
+
verify,
|
|
113
|
+
debug,
|
|
114
|
+
};
|
|
181
115
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Ensure CLI binary is built before registering
|
|
118
|
+
*/
|
|
119
|
+
function ensureCliBinaryBuilt(uikitRoot, executor) {
|
|
120
|
+
const cliPackagePath = path.join(uikitRoot, 'packages/uikit-cli');
|
|
121
|
+
const distPath = path.join(cliPackagePath, 'dist/cli.js');
|
|
122
|
+
const fs = new RealFileSystem();
|
|
123
|
+
if (!fs.exists(distPath)) {
|
|
124
|
+
console.log('Building CLI binary...');
|
|
125
|
+
executor.exec('npm run build', { cwd: cliPackagePath });
|
|
190
126
|
}
|
|
191
|
-
return true;
|
|
192
127
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Link CLI into consumer for convenience
|
|
130
|
+
*/
|
|
131
|
+
function linkCliIntoConsumer(consumerRoot, executor) {
|
|
132
|
+
executor.exec('npm link "@archon-research/uikit-cli" --package-lock=false --save=false --no-workspaces', { cwd: consumerRoot });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Main entry point
|
|
136
|
+
*/
|
|
137
|
+
try {
|
|
138
|
+
const parsed = parseArgs(process.argv);
|
|
139
|
+
const { mode, consumerRoot, uikitRoot, commandArgs, verify, debug } = parsed;
|
|
140
|
+
if (debug) {
|
|
141
|
+
console.log('[DEBUG] Parsed args:', { mode, consumerRoot, uikitRoot, verify });
|
|
142
|
+
}
|
|
143
|
+
// Initialize dependencies
|
|
144
|
+
const fs = new RealFileSystem();
|
|
145
|
+
const executor = new NpmCommandExecutor(debug);
|
|
146
|
+
const logger = new ConsoleLogger(debug);
|
|
147
|
+
const discovery = new PackageDiscovery(fs);
|
|
148
|
+
const validator = new LinkValidator(fs, logger);
|
|
149
|
+
// Handle lint/format commands
|
|
150
|
+
if (mode === 'lint') {
|
|
151
|
+
const lintCmd = new LintCommand(executor);
|
|
152
|
+
lintCmd.execute(commandArgs);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
if (mode === 'format') {
|
|
156
|
+
const formatCmd = new FormatCommand(executor, fs);
|
|
157
|
+
formatCmd.execute(commandArgs);
|
|
158
|
+
process.exit(0);
|
|
199
159
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
160
|
+
// Handle register command
|
|
161
|
+
if (mode === 'register') {
|
|
162
|
+
ensureCliBinaryBuilt(uikitRoot, executor);
|
|
163
|
+
const registerCmd = new RegisterCommand(discovery, executor, logger);
|
|
164
|
+
registerCmd.execute(uikitRoot);
|
|
165
|
+
if (verify) {
|
|
166
|
+
logger.info('\\nVerifying registration...');
|
|
167
|
+
// Could add verification logic here
|
|
168
|
+
logger.info('✓ Registration verified');
|
|
204
169
|
}
|
|
170
|
+
process.exit(0);
|
|
205
171
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const dirByName = new Map(uikitPackages.map((pkg) => [pkg.name ?? '', pkg.path]));
|
|
216
|
-
dirByName.delete('');
|
|
217
|
-
const supportedNames = new Set(dirByName.keys());
|
|
218
|
-
const neededByWorkspace = collectWorkspaceRequirements(consumerWorkspaces, supportedNames);
|
|
172
|
+
// Handle link/unlink commands (require consumerRoot)
|
|
173
|
+
if (!consumerRoot) {
|
|
174
|
+
throw new Error('Consumer root is required for link/unlink operations.');
|
|
175
|
+
}
|
|
176
|
+
// Register packages and link CLI before link/unlink
|
|
177
|
+
ensureCliBinaryBuilt(uikitRoot, executor);
|
|
178
|
+
const registerCmd = new RegisterCommand(discovery, executor, logger);
|
|
179
|
+
registerCmd.execute(uikitRoot);
|
|
180
|
+
linkCliIntoConsumer(consumerRoot, executor);
|
|
219
181
|
if (mode === 'link') {
|
|
220
|
-
|
|
221
|
-
|
|
182
|
+
const linkCmd = new LinkCommand(discovery, executor, validator, fs, logger);
|
|
183
|
+
linkCmd.execute(consumerRoot, uikitRoot, verify);
|
|
222
184
|
process.exit(0);
|
|
223
185
|
}
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log('\nConsumer remains on local uikit links.');
|
|
186
|
+
if (mode === 'unlink') {
|
|
187
|
+
const unlinkCmd = new UnlinkCommand(executor, logger);
|
|
188
|
+
unlinkCmd.execute(consumerRoot, uikitRoot, discovery);
|
|
228
189
|
process.exit(0);
|
|
229
190
|
}
|
|
230
|
-
unlinkLocalPackages(consumerRoot, neededByWorkspace);
|
|
231
|
-
let rootInstallOk = tryRun('npm_config_min_release_age=0 npm install', consumerRoot);
|
|
232
|
-
let installOk = true;
|
|
233
|
-
for (const workspace of neededByWorkspace.keys()) {
|
|
234
|
-
installOk =
|
|
235
|
-
tryRun(`npm_config_min_release_age=0 npm install --workspace "${workspace}"`, consumerRoot) &&
|
|
236
|
-
installOk;
|
|
237
|
-
}
|
|
238
|
-
if (!rootInstallOk ||
|
|
239
|
-
!installOk ||
|
|
240
|
-
!areRegistryPackagesReady(consumerRoot, neededByWorkspace)) {
|
|
241
|
-
console.warn('\nRegistry packages are not fully resolvable; falling back to local uikit linking.');
|
|
242
|
-
linkLocalPackages(consumerRoot, neededByWorkspace, dirByName);
|
|
243
|
-
}
|
|
244
|
-
console.log('\nUnlinked local uikit packages and restored consumer dependencies.');
|
|
245
191
|
}
|
|
246
192
|
catch (error) {
|
|
247
|
-
|
|
248
|
-
|
|
193
|
+
if (process.env.UIKIT_DEBUG) {
|
|
194
|
+
console.error('Full error:', error);
|
|
195
|
+
}
|
|
196
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
249
197
|
process.exit(1);
|
|
250
198
|
}
|
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
|
+
}
|