@archon-research/uikit-cli 0.1.0-dev25004766367
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 +69 -0
- package/dist/cli.js +250 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @archon-research/uikit-cli
|
|
2
|
+
|
|
3
|
+
CLI for managing local uikit package linking in consumer workspaces.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Once published to GitHub Packages, install globally:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @archon-research/uikit-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For development, link locally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd /path/to/uikit/packages/uikit-cli
|
|
17
|
+
npm link
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Link local uikit packages
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uikit-cli link
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Automatically detects the consumer workspace root by walking up to the nearest `package.json` with a `workspaces` field. Links all `@archon-research/*` packages that the consumer depends on.
|
|
29
|
+
|
|
30
|
+
### Unlink local packages (restore registry versions)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uikit-cli unlink
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Attempts to unlink all local uikit packages and restore registered versions. If packages are not yet published, keeps local links in place.
|
|
37
|
+
|
|
38
|
+
### Working from any subdirectory
|
|
39
|
+
|
|
40
|
+
The CLI works from any directory within a workspace:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd my-consumer/src/explorer
|
|
44
|
+
uikit-cli link # auto-detects workspace root and links packages
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
1. **Auto-detection**: Walks up from current working directory to find the nearest `package.json` with a `workspaces` field
|
|
50
|
+
2. **Discovery**: Queries locally available uikit packages and determines which ones are needed by consumer workspaces
|
|
51
|
+
3. **Linking**: Uses `npm link` to establish local package resolution
|
|
52
|
+
4. **Graceful fallback**: On unlink, checks if packages are published; if not, keeps local links to prevent breaking changes
|
|
53
|
+
|
|
54
|
+
## Development workflow
|
|
55
|
+
|
|
56
|
+
In a synome workspace:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Link uikit packages for local development
|
|
60
|
+
npm run uikit:link
|
|
61
|
+
|
|
62
|
+
# Later, restore registry versions (or keep local links if not published)
|
|
63
|
+
npm run uikit:unlink
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Scripts in synome package.json
|
|
67
|
+
|
|
68
|
+
- `uikit:link` — Uses the CLI with auto-detection
|
|
69
|
+
- `uikit:unlink` — Unlinks using the CLI
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
function run(command, cwd) {
|
|
7
|
+
console.log(`> (${cwd}) ${command}`);
|
|
8
|
+
execSync(command, { stdio: 'inherit', cwd });
|
|
9
|
+
}
|
|
10
|
+
function tryRun(command, cwd, quiet = false) {
|
|
11
|
+
if (!quiet) {
|
|
12
|
+
console.log(`> (${cwd}) ${command}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
execSync(command, {
|
|
16
|
+
stdio: quiet ? 'ignore' : 'inherit',
|
|
17
|
+
cwd,
|
|
18
|
+
});
|
|
19
|
+
return true;
|
|
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
|
+
}
|
|
86
|
+
function parseArgs(argv) {
|
|
87
|
+
const args = argv.slice(2);
|
|
88
|
+
const mode = args[0] === 'unlink' ? 'unlink' : 'link';
|
|
89
|
+
let consumerRoot = null;
|
|
90
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
91
|
+
const arg = args[i];
|
|
92
|
+
if (arg === '--consumer-root' && args[i + 1]) {
|
|
93
|
+
consumerRoot = path.resolve(process.cwd(), args[i + 1]);
|
|
94
|
+
i += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!consumerRoot) {
|
|
98
|
+
consumerRoot = findConsumerRoot(process.cwd());
|
|
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
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (needed.size > 0) {
|
|
120
|
+
neededByWorkspace.set(ws.location, [...needed]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return neededByWorkspace;
|
|
124
|
+
}
|
|
125
|
+
function linkLocalPackages(consumerRoot, neededByWorkspace, dirByName) {
|
|
126
|
+
if (neededByWorkspace.size === 0) {
|
|
127
|
+
console.log('No local uikit packages referenced by this consumer workspaces.');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const allNames = new Set();
|
|
131
|
+
for (const names of neededByWorkspace.values()) {
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
allNames.add(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const rootPackageArgs = [...allNames]
|
|
137
|
+
.map((name) => dirByName.get(name))
|
|
138
|
+
.filter((pkgDir) => Boolean(pkgDir))
|
|
139
|
+
.map((pkgDir) => `"${pkgDir}"`)
|
|
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;
|
|
152
|
+
}
|
|
153
|
+
run(`npm link ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, consumerRoot);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function unlinkLocalPackages(consumerRoot, neededByWorkspace) {
|
|
157
|
+
if (neededByWorkspace.size === 0) {
|
|
158
|
+
console.log('No local uikit packages referenced by this consumer workspaces.');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const allNames = new Set();
|
|
162
|
+
for (const names of neededByWorkspace.values()) {
|
|
163
|
+
for (const name of names) {
|
|
164
|
+
allNames.add(name);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const name of allNames) {
|
|
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.`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
const ok = tryRun(`npm unlink "${name}" --workspace "${workspace}" --package-lock=false --save=false`, consumerRoot);
|
|
176
|
+
if (!ok) {
|
|
177
|
+
console.warn(`Unable to unlink ${name} in ${workspace}; continuing with restore flow.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function areRegistryPackagesReady(consumerRoot, neededByWorkspace) {
|
|
183
|
+
for (const [workspace, names] of neededByWorkspace.entries()) {
|
|
184
|
+
for (const name of names) {
|
|
185
|
+
const ok = tryRun(`npm_config_min_release_age=0 npm ls ${name} --depth=0 --workspace "${workspace}"`, consumerRoot, true);
|
|
186
|
+
if (!ok) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
function arePackagesPublished(consumerRoot, neededByWorkspace) {
|
|
194
|
+
const uniqueNames = new Set();
|
|
195
|
+
for (const names of neededByWorkspace.values()) {
|
|
196
|
+
for (const name of names) {
|
|
197
|
+
uniqueNames.add(name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const name of uniqueNames) {
|
|
201
|
+
const ok = tryRun(`npm view ${name} version --json`, consumerRoot, true);
|
|
202
|
+
if (!ok) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
209
|
+
const uikitRoot = path.resolve(scriptDir, '../../..');
|
|
210
|
+
try {
|
|
211
|
+
const { mode, consumerRoot } = parseArgs(process.argv);
|
|
212
|
+
const uikitWorkspaces = loadWorkspaces(uikitRoot);
|
|
213
|
+
const consumerWorkspaces = loadWorkspaces(consumerRoot);
|
|
214
|
+
const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@archon-research/'));
|
|
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);
|
|
219
|
+
if (mode === 'link') {
|
|
220
|
+
linkLocalPackages(consumerRoot, neededByWorkspace, dirByName);
|
|
221
|
+
console.log('\nLinked local uikit packages into consumer workspaces.');
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
if (!arePackagesPublished(consumerRoot, neededByWorkspace)) {
|
|
225
|
+
console.warn('\nRegistry packages are not published yet; keeping local uikit links in place.');
|
|
226
|
+
linkLocalPackages(consumerRoot, neededByWorkspace, dirByName);
|
|
227
|
+
console.log('\nConsumer remains on local uikit links.');
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
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
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
248
|
+
console.error(`Error: ${message}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archon-research/uikit-cli",
|
|
3
|
+
"version": "0.1.0-dev25004766367",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for managing local uikit package linking in consumer workspaces",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"clean": "rm -rf dist",
|
|
9
|
+
"prepare": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"uikit-cli": "./dist/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@archon-research/tsconfig": "*",
|
|
19
|
+
"@types/node": "^24.7.2"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"url": "https://github.com/archon-research/uikit"
|
|
26
|
+
}
|
|
27
|
+
}
|