@archon-research/uikit-cli 0.3.0-rohit-improve-cli.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 +84 -32
- package/dist/cli.js +151 -443
- 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/dist/cli.js
CHANGED
|
@@ -1,490 +1,198 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, } 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
|
-
stdio: quiet ? 'ignore' : 'inherit',
|
|
22
|
-
cwd,
|
|
23
|
-
});
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function removeWorkspaceShadowInstall(consumerRoot, workspace, packageName) {
|
|
31
|
-
const packagePath = path.join(consumerRoot, workspace, 'node_modules', packageName);
|
|
32
|
-
if (!existsSync(packagePath)) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
const stats = lstatSync(packagePath);
|
|
37
|
-
if (stats.isSymbolicLink()) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
// Remove workspace-local regular install so Node resolves to linked root package.
|
|
41
|
-
rmSync(packagePath, { recursive: true, force: true });
|
|
42
|
-
console.log(`Removed shadow install at ${workspace}/node_modules/${packageName} to preserve local links.`);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Best effort cleanup; linking may still succeed via root resolution.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function ensureLinkedPath(consumerRoot, packageName, expectedTarget) {
|
|
49
|
-
const packagePath = path.join(consumerRoot, 'node_modules', packageName);
|
|
50
|
-
try {
|
|
51
|
-
const stats = lstatSync(packagePath);
|
|
52
|
-
if (stats.isSymbolicLink() && realpathSync(packagePath) === expectedTarget) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
rmSync(packagePath, { recursive: true, force: true });
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Path may not exist yet; continue and create symlink.
|
|
59
|
-
}
|
|
60
|
-
mkdirSync(path.dirname(packagePath), { recursive: true });
|
|
61
|
-
symlinkSync(expectedTarget, packagePath, 'dir');
|
|
62
|
-
}
|
|
63
|
-
function clearWorkspaceViteCache(consumerRoot, workspace) {
|
|
64
|
-
const viteCachePath = path.join(consumerRoot, workspace, 'node_modules', '.vite');
|
|
65
|
-
if (!existsSync(viteCachePath)) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
rmSync(viteCachePath, { recursive: true, force: true });
|
|
70
|
-
console.log(`Cleared Vite cache at ${workspace}/node_modules/.vite.`);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// Best-effort cache cleanup; linking still succeeds without this.
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function readJson(filePath) {
|
|
77
|
-
const content = readFileSync(filePath, 'utf8');
|
|
78
|
-
return JSON.parse(content);
|
|
79
|
-
}
|
|
80
|
-
function getWorkspacePatterns(rootDir) {
|
|
81
|
-
const pkg = readJson(path.join(rootDir, 'package.json'));
|
|
82
|
-
const workspaces = pkg.workspaces;
|
|
83
|
-
if (Array.isArray(workspaces)) {
|
|
84
|
-
return workspaces;
|
|
85
|
-
}
|
|
86
|
-
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
87
|
-
return workspaces.packages;
|
|
88
|
-
}
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
function resolveWorkspacePattern(rootDir, pattern) {
|
|
92
|
-
if (pattern.endsWith('/*')) {
|
|
93
|
-
const base = pattern.slice(0, -2);
|
|
94
|
-
const absBase = path.join(rootDir, base);
|
|
95
|
-
return readdirSync(absBase, { withFileTypes: true })
|
|
96
|
-
.filter((entry) => entry.isDirectory())
|
|
97
|
-
.map((entry) => path.join(base, entry.name));
|
|
98
|
-
}
|
|
99
|
-
return [pattern];
|
|
100
|
-
}
|
|
101
|
-
function loadWorkspaces(rootDir) {
|
|
102
|
-
const patterns = getWorkspacePatterns(rootDir);
|
|
103
|
-
const locations = patterns.flatMap((pattern) => resolveWorkspacePattern(rootDir, pattern));
|
|
104
|
-
return locations
|
|
105
|
-
.map((location) => {
|
|
106
|
-
const pkgPath = path.join(rootDir, location, 'package.json');
|
|
107
|
-
const pkg = readJson(pkgPath);
|
|
108
|
-
return {
|
|
109
|
-
...pkg,
|
|
110
|
-
location,
|
|
111
|
-
path: path.join(rootDir, location),
|
|
112
|
-
};
|
|
113
|
-
})
|
|
114
|
-
.filter((ws) => Boolean(ws.name));
|
|
115
|
-
}
|
|
116
|
-
function findConsumerRoot(startDir) {
|
|
117
|
-
let current = startDir;
|
|
118
|
-
while (true) {
|
|
119
|
-
const pkgPath = path.join(current, 'package.json');
|
|
120
|
-
try {
|
|
121
|
-
const content = readFileSync(pkgPath, 'utf8');
|
|
122
|
-
const pkg = JSON.parse(content);
|
|
123
|
-
if (pkg.workspaces) {
|
|
124
|
-
return current;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
// File not found or parse error, continue up.
|
|
129
|
-
}
|
|
130
|
-
const parent = path.dirname(current);
|
|
131
|
-
if (parent === current) {
|
|
132
|
-
throw new Error('Could not find consumer workspace root (no package.json with workspaces field)');
|
|
133
|
-
}
|
|
134
|
-
current = parent;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function findUIKitRoot(startDir) {
|
|
138
|
-
let current = startDir;
|
|
139
|
-
while (true) {
|
|
140
|
-
if (isValidUIKitRoot(current)) {
|
|
141
|
-
return current;
|
|
142
|
-
}
|
|
143
|
-
const parent = path.dirname(current);
|
|
144
|
-
if (parent === current) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
current = parent;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function isValidUIKitRoot(rootDir) {
|
|
151
|
-
try {
|
|
152
|
-
const pkgPath = path.join(rootDir, 'package.json');
|
|
153
|
-
const pkg = readJson(pkgPath);
|
|
154
|
-
if (!pkg.workspaces) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
const workspaces = loadWorkspaces(rootDir);
|
|
158
|
-
return workspaces.some((ws) => ws.name === '@archon-research/design-system');
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function findUIKitRootFromConsumer(consumerRoot) {
|
|
165
|
-
const candidateNames = ['uikit'];
|
|
166
|
-
let current = consumerRoot;
|
|
167
|
-
while (true) {
|
|
168
|
-
for (const candidateName of candidateNames) {
|
|
169
|
-
const candidate = path.join(current, candidateName);
|
|
170
|
-
if (isValidUIKitRoot(candidate)) {
|
|
171
|
-
return candidate;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const parent = path.dirname(current);
|
|
175
|
-
if (parent === current) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
current = parent;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function resolveUIKitRoot(explicitUIKitRoot, consumerRoot) {
|
|
182
|
-
if (explicitUIKitRoot) {
|
|
183
|
-
const resolved = path.resolve(process.cwd(), explicitUIKitRoot);
|
|
184
|
-
if (!isValidUIKitRoot(resolved)) {
|
|
185
|
-
throw new Error(`Invalid uikit root: ${resolved}. Expected a workspace root containing @archon-research/design-system.`);
|
|
186
|
-
}
|
|
187
|
-
return resolved;
|
|
188
|
-
}
|
|
189
|
-
const scriptRelativeRoot = path.resolve(scriptDir, '../../..');
|
|
190
|
-
if (isValidUIKitRoot(scriptRelativeRoot)) {
|
|
191
|
-
return scriptRelativeRoot;
|
|
192
|
-
}
|
|
193
|
-
if (consumerRoot) {
|
|
194
|
-
const discovered = findUIKitRootFromConsumer(consumerRoot);
|
|
195
|
-
if (discovered) {
|
|
196
|
-
return discovered;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const fromCwd = findUIKitRoot(process.cwd());
|
|
200
|
-
if (fromCwd) {
|
|
201
|
-
return fromCwd;
|
|
202
|
-
}
|
|
203
|
-
throw new Error('Could not locate local uikit workspace automatically. Run from a consumer workspace near your uikit checkout, or pass --uikit-root / set UIKIT_ROOT.');
|
|
204
|
-
}
|
|
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
|
+
*/
|
|
205
18
|
function parseArgs(argv) {
|
|
206
19
|
const args = argv.slice(2);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
else if (command === 'format') {
|
|
222
|
-
mode = 'format';
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
throw new Error(`Unknown command: ${command}. Expected one of: link, unlink, register, lint, format.`);
|
|
226
|
-
}
|
|
227
|
-
const commandArgs = args.slice(1);
|
|
228
|
-
let consumerRoot = null;
|
|
229
|
-
let uikitRoot = process.env.UIKIT_ROOT ?? null;
|
|
230
|
-
for (let i = 1; i < args.length; i += 1) {
|
|
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) {
|
|
231
34
|
const arg = args[i];
|
|
232
|
-
if (arg === '--
|
|
233
|
-
|
|
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;
|
|
234
41
|
i += 1;
|
|
235
|
-
continue;
|
|
236
42
|
}
|
|
237
|
-
if (arg === '--
|
|
238
|
-
|
|
43
|
+
else if (arg === '--debug') {
|
|
44
|
+
debug = true;
|
|
239
45
|
i += 1;
|
|
240
46
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
mode !== 'format' &&
|
|
245
|
-
!consumerRoot) {
|
|
246
|
-
consumerRoot = findConsumerRoot(process.cwd());
|
|
247
|
-
}
|
|
248
|
-
if (mode === 'lint' || mode === 'format') {
|
|
249
|
-
return {
|
|
250
|
-
mode,
|
|
251
|
-
consumerRoot: null,
|
|
252
|
-
uikitRoot: '',
|
|
253
|
-
commandArgs,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
mode,
|
|
258
|
-
consumerRoot,
|
|
259
|
-
uikitRoot: resolveUIKitRoot(uikitRoot, consumerRoot),
|
|
260
|
-
commandArgs,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
function runLint(commandArgs) {
|
|
264
|
-
const forwarded = commandArgs.map(shellEscape).join(' ');
|
|
265
|
-
run(`npm exec --yes --package oxlint@${OXLINT_VERSION} -- oxlint ${forwarded}`.trim(), process.cwd());
|
|
266
|
-
}
|
|
267
|
-
function hasConfigFlag(args) {
|
|
268
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
269
|
-
const arg = args[i];
|
|
270
|
-
if (arg === '-c' || arg === '--config' || arg.startsWith('--config=')) {
|
|
271
|
-
return true;
|
|
47
|
+
else {
|
|
48
|
+
commandArgs.push(arg);
|
|
49
|
+
i += 1;
|
|
272
50
|
}
|
|
273
51
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const args = [...commandArgs];
|
|
278
|
-
const defaultConfig = './.oxfmtrc.ts';
|
|
279
|
-
if (!hasConfigFlag(args) && existsSync(path.join(process.cwd(), '.oxfmtrc.ts'))) {
|
|
280
|
-
args.unshift(defaultConfig);
|
|
281
|
-
args.unshift('-c');
|
|
52
|
+
// Check for UIKIT_DEBUG environment variable
|
|
53
|
+
if (process.env.UIKIT_DEBUG === '1') {
|
|
54
|
+
debug = true;
|
|
282
55
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@archon-research/') &&
|
|
289
|
-
!ws.private &&
|
|
290
|
-
(!supportedNames || supportedNames.has(String(ws.name))));
|
|
291
|
-
if (uikitPackages.length === 0) {
|
|
292
|
-
console.log('No public @archon-research packages found in this uikit workspace.');
|
|
293
|
-
return;
|
|
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...');
|
|
294
61
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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);
|
|
298
67
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
run('npm link', pkg.path);
|
|
305
|
-
}
|
|
306
|
-
console.log('\nRegistered local uikit packages for downstream consumers.');
|
|
307
|
-
}
|
|
308
|
-
function linkCliIntoConsumer(consumerRoot) {
|
|
309
|
-
run('npm link "@archon-research/uikit-cli" --package-lock=false --save=false', consumerRoot);
|
|
310
|
-
}
|
|
311
|
-
function collectWorkspaceRequirements(workspaces, supportedNames) {
|
|
312
|
-
const neededByWorkspace = new Map();
|
|
313
|
-
for (const ws of workspaces) {
|
|
314
|
-
const fields = [
|
|
315
|
-
ws.dependencies ?? {},
|
|
316
|
-
ws.devDependencies ?? {},
|
|
317
|
-
ws.optionalDependencies ?? {},
|
|
318
|
-
ws.peerDependencies ?? {},
|
|
319
|
-
];
|
|
320
|
-
const needed = new Set();
|
|
321
|
-
for (const depField of fields) {
|
|
322
|
-
for (const depName of Object.keys(depField)) {
|
|
323
|
-
if (supportedNames.has(depName)) {
|
|
324
|
-
needed.add(depName);
|
|
325
|
-
}
|
|
68
|
+
if (discovery.isValidUIKitRoot(relativeRoot)) {
|
|
69
|
+
uikitRoot = relativeRoot;
|
|
70
|
+
if (debug) {
|
|
71
|
+
console.log('[DEBUG parseArgs] Found valid uikit root at relative location');
|
|
326
72
|
}
|
|
327
73
|
}
|
|
328
|
-
if (needed.size > 0) {
|
|
329
|
-
neededByWorkspace.set(ws.location, [...needed]);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return neededByWorkspace;
|
|
333
|
-
}
|
|
334
|
-
function linkLocalPackages(consumerRoot, neededByWorkspace, dirByName) {
|
|
335
|
-
if (neededByWorkspace.size === 0) {
|
|
336
|
-
console.log('No local uikit packages referenced by this consumer workspaces.');
|
|
337
|
-
return;
|
|
338
74
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (rootPackageArgs) {
|
|
347
|
-
run(`npm link ${rootPackageArgs} --package-lock=false --save=false`, consumerRoot);
|
|
348
|
-
}
|
|
349
|
-
// npm can materialize regular installs in some workspace setups; enforce root links.
|
|
350
|
-
for (const name of allNames) {
|
|
351
|
-
const target = dirByName.get(name);
|
|
352
|
-
if (!target) {
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
ensureLinkedPath(consumerRoot, name, target);
|
|
356
|
-
}
|
|
357
|
-
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
358
|
-
const packageArgs = names.map((name) => `"${name}"`).join(' ');
|
|
359
|
-
if (!packageArgs) {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
run(`npm link ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, consumerRoot);
|
|
363
|
-
// npm --workspace link can leave a regular install in some workspace layouts.
|
|
364
|
-
// Removing workspace shadow installs keeps resolution aligned to linked root packages.
|
|
365
|
-
for (const name of names) {
|
|
366
|
-
removeWorkspaceShadowInstall(consumerRoot, workspace, name);
|
|
367
|
-
}
|
|
368
|
-
clearWorkspaceViteCache(consumerRoot, workspace);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
function unlinkLocalPackages(consumerRoot, neededByWorkspace) {
|
|
372
|
-
if (neededByWorkspace.size === 0) {
|
|
373
|
-
console.log('No local uikit packages referenced by this consumer workspaces.');
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
const allNames = new Set();
|
|
377
|
-
for (const names of neededByWorkspace.values()) {
|
|
378
|
-
for (const name of names) {
|
|
379
|
-
allNames.add(name);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
for (const name of allNames) {
|
|
383
|
-
const ok = tryRun(`npm unlink "${name}" --package-lock=false --save=false`, consumerRoot);
|
|
384
|
-
if (!ok) {
|
|
385
|
-
console.warn(`Unable to unlink ${name} at root; continuing.`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
389
|
-
const workspaceDir = path.join(consumerRoot, workspace);
|
|
390
|
-
for (const name of names) {
|
|
391
|
-
const ok = tryRun(`npm unlink "${name}" --workspace "${workspace}" --package-lock=false --save=false`, consumerRoot);
|
|
392
|
-
if (!ok) {
|
|
393
|
-
console.warn(`Unable to unlink ${name} in ${workspace}; continuing with restore flow.`);
|
|
394
|
-
}
|
|
395
|
-
// Fallback for cases where --workspace unlink does not clear workspace-local links.
|
|
396
|
-
const fallbackOk = tryRun(`npm unlink "${name}" --package-lock=false --save=false`, workspaceDir, true);
|
|
397
|
-
if (!fallbackOk) {
|
|
398
|
-
console.warn(`Unable to unlink ${name} directly in ${workspace}; continuing with restore flow.`);
|
|
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;
|
|
399
82
|
}
|
|
400
83
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
function areRegistryPackagesReady(consumerRoot, neededByWorkspace) {
|
|
405
|
-
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
406
|
-
for (const name of names) {
|
|
407
|
-
const ok = tryRun(`npm_config_min_release_age=0 npm ls ${name} --depth=0 --workspace "${workspace}"`, consumerRoot, true);
|
|
408
|
-
if (!ok) {
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
84
|
+
catch {
|
|
85
|
+
// Will throw error below if needed
|
|
411
86
|
}
|
|
412
87
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
for (const name of names) {
|
|
419
|
-
uniqueNames.add(name);
|
|
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;
|
|
420
93
|
}
|
|
421
94
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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());
|
|
427
106
|
}
|
|
428
|
-
return
|
|
107
|
+
return {
|
|
108
|
+
mode,
|
|
109
|
+
consumerRoot,
|
|
110
|
+
uikitRoot: uikitRoot ?? '',
|
|
111
|
+
commandArgs,
|
|
112
|
+
verify,
|
|
113
|
+
debug,
|
|
114
|
+
};
|
|
429
115
|
}
|
|
430
|
-
|
|
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 });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
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
|
+
*/
|
|
431
137
|
try {
|
|
432
|
-
const
|
|
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
|
|
433
150
|
if (mode === 'lint') {
|
|
434
|
-
|
|
151
|
+
const lintCmd = new LintCommand(executor);
|
|
152
|
+
lintCmd.execute(commandArgs);
|
|
435
153
|
process.exit(0);
|
|
436
154
|
}
|
|
437
155
|
if (mode === 'format') {
|
|
438
|
-
|
|
156
|
+
const formatCmd = new FormatCommand(executor, fs);
|
|
157
|
+
formatCmd.execute(commandArgs);
|
|
439
158
|
process.exit(0);
|
|
440
159
|
}
|
|
160
|
+
// Handle register command
|
|
441
161
|
if (mode === 'register') {
|
|
442
|
-
|
|
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');
|
|
169
|
+
}
|
|
443
170
|
process.exit(0);
|
|
444
171
|
}
|
|
172
|
+
// Handle link/unlink commands (require consumerRoot)
|
|
445
173
|
if (!consumerRoot) {
|
|
446
174
|
throw new Error('Consumer root is required for link/unlink operations.');
|
|
447
175
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const supportedNames = new Set(dirByName.keys());
|
|
454
|
-
const neededByWorkspace = collectWorkspaceRequirements(consumerWorkspaces, supportedNames);
|
|
455
|
-
registerLocalPackages(uikitRoot, supportedNames);
|
|
456
|
-
linkCliIntoConsumer(consumerRoot);
|
|
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);
|
|
457
181
|
if (mode === 'link') {
|
|
458
|
-
|
|
459
|
-
|
|
182
|
+
const linkCmd = new LinkCommand(discovery, executor, validator, fs, logger);
|
|
183
|
+
linkCmd.execute(consumerRoot, uikitRoot, verify);
|
|
460
184
|
process.exit(0);
|
|
461
185
|
}
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
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);
|
|
466
189
|
process.exit(0);
|
|
467
190
|
}
|
|
468
|
-
unlinkLocalPackages(consumerRoot, neededByWorkspace);
|
|
469
|
-
let rootInstallOk = tryRun('npm_config_min_release_age=0 npm install', consumerRoot);
|
|
470
|
-
let installOk = true;
|
|
471
|
-
for (const workspace of neededByWorkspace.keys()) {
|
|
472
|
-
installOk =
|
|
473
|
-
tryRun(`npm_config_min_release_age=0 npm install --workspace "${workspace}"`, consumerRoot) &&
|
|
474
|
-
installOk;
|
|
475
|
-
}
|
|
476
|
-
if (!rootInstallOk ||
|
|
477
|
-
!installOk ||
|
|
478
|
-
!areRegistryPackagesReady(consumerRoot, neededByWorkspace)) {
|
|
479
|
-
console.warn('\nRegistry packages are not fully resolvable; falling back to local uikit linking.');
|
|
480
|
-
linkLocalPackages(consumerRoot, neededByWorkspace, dirByName);
|
|
481
|
-
}
|
|
482
|
-
// Keep consumer on the local CLI implementation so repeated link/unlink stays stable.
|
|
483
|
-
linkCliIntoConsumer(consumerRoot);
|
|
484
|
-
console.log('\nUnlinked local uikit packages and restored consumer dependencies.');
|
|
485
191
|
}
|
|
486
192
|
catch (error) {
|
|
487
|
-
|
|
488
|
-
|
|
193
|
+
if (process.env.UIKIT_DEBUG) {
|
|
194
|
+
console.error('Full error:', error);
|
|
195
|
+
}
|
|
196
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
489
197
|
process.exit(1);
|
|
490
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" "$@"
|