@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 CHANGED
@@ -2,31 +2,81 @@
2
2
 
3
3
  CLI tool for local package linking during active development with consumer repositories.
4
4
 
5
- ## Installation
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 install --save-dev @archon-research/uikit-cli
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
- npm run uikit:link
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
- npm run uikit:unlink
89
+ ./node_modules/.bin/uikit-cli unlink
40
90
  ```
41
91
 
42
92
  ## How it works
43
93
 
44
- The CLI manages workspace package links by:
94
+ The CLI manages local development links by:
45
95
 
46
- 1. Discovering linked `@archon-research` packages in your local uikit monorepo
47
- 2. Creating file links in your consumer repository's `node_modules`
48
- 3. Reversing the process with `unlink` to restore registry-installed packages
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
- ## See also
117
+ ### "ENOENT: no such file or directory" errors
58
118
 
59
- - [Development guide](../../DEVELOPMENT.md#local-co-development-with-a-consumer-repository) for detailed local development workflow
60
- 3. **Linking**: Uses `npm link` to establish local package resolution
61
- 4. **Graceful fallback**: On unlink, checks if packages are published; if not, keeps local links to prevent breaking changes
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 synome workspace:
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
- # Later, restore registry versions (or keep local links if not published)
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
- ## Scripts in synome package.json
146
+ ## Debug mode
76
147
 
77
- - `uikit:link` Uses the CLI with auto-detection
78
- - `uikit:unlink` — Unlinks using the CLI
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
- 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
- }
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
- const mode = args[0] === 'unlink' ? 'unlink' : 'link';
89
- let consumerRoot = null;
90
- 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) {
91
34
  const arg = args[i];
92
- if (arg === '--consumer-root' && args[i + 1]) {
93
- consumerRoot = path.resolve(process.cwd(), args[i + 1]);
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
- 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
- }
43
+ else if (arg === '--debug') {
44
+ debug = true;
45
+ i += 1;
118
46
  }
119
- if (needed.size > 0) {
120
- neededByWorkspace.set(ws.location, [...needed]);
47
+ else {
48
+ commandArgs.push(arg);
49
+ i += 1;
121
50
  }
122
51
  }
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);
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
- 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;
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
- 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);
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
- 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.`);
84
+ catch {
85
+ // Will throw error below if needed
171
86
  }
172
87
  }
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
- }
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
- 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
- }
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
- 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
- }
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
- for (const name of uniqueNames) {
201
- const ok = tryRun(`npm view ${name} version --json`, consumerRoot, true);
202
- if (!ok) {
203
- return false;
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
- 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);
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
- linkLocalPackages(consumerRoot, neededByWorkspace, dirByName);
221
- console.log('\nLinked local uikit packages into consumer workspaces.');
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 (!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.');
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
- const message = error instanceof Error ? error.message : String(error);
248
- console.error(`Error: ${message}`);
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
+ }