@archon-research/uikit-cli 0.3.0-rohit-improve-cli.2 → 0.3.2

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.
@@ -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
+ }
@@ -0,0 +1,36 @@
1
+ import path from 'node:path';
2
+ import { shellEscape } from '../shell-utils.js';
3
+ /**
4
+ * Format command - forwards to oxfmt with config detection
5
+ */
6
+ export class FormatCommand {
7
+ executor;
8
+ fs;
9
+ constructor(executor, fs) {
10
+ this.executor = executor;
11
+ this.fs = fs;
12
+ }
13
+ execute(args) {
14
+ const modifiedArgs = [...args];
15
+ const defaultConfig = './.oxfmtrc.ts';
16
+ // Add default config if not specified and file exists
17
+ if (!this.hasConfigFlag(modifiedArgs)) {
18
+ const configPath = path.join(process.cwd(), '.oxfmtrc.ts');
19
+ if (this.fs.exists(configPath)) {
20
+ modifiedArgs.unshift(defaultConfig);
21
+ modifiedArgs.unshift('-c');
22
+ }
23
+ }
24
+ const escapedArgs = modifiedArgs.map((arg) => shellEscape(arg)).join(' ');
25
+ this.executor.exec(`npm exec -- oxfmt ${escapedArgs}`.trim(), { cwd: process.cwd() });
26
+ }
27
+ hasConfigFlag(args) {
28
+ for (let i = 0; i < args.length; i += 1) {
29
+ const arg = args[i];
30
+ if (arg === '-c' || arg === '--config' || arg.startsWith('--config=')) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ }
36
+ }
@@ -0,0 +1,162 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * Link command - links uikit packages into consumer workspaces
4
+ */
5
+ export class LinkCommand {
6
+ discovery;
7
+ executor;
8
+ validator;
9
+ fs;
10
+ logger;
11
+ constructor(discovery, executor, validator, fs, logger) {
12
+ this.discovery = discovery;
13
+ this.executor = executor;
14
+ this.validator = validator;
15
+ this.fs = fs;
16
+ this.logger = logger;
17
+ }
18
+ execute(consumerRoot, uikitRoot, verify = false) {
19
+ this.logger.debug('Starting link command', { consumerRoot, uikitRoot });
20
+ const uikitWorkspaces = this.discovery.loadWorkspaces(uikitRoot);
21
+ const consumerWorkspaces = this.discovery.loadWorkspaces(consumerRoot);
22
+ const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@archon-research/'));
23
+ const dirByName = new Map(uikitPackages.map((pkg) => [pkg.name ?? '', pkg.path]));
24
+ dirByName.delete('');
25
+ const supportedNames = new Set(dirByName.keys());
26
+ const neededByWorkspace = this.collectWorkspaceRequirements(consumerWorkspaces, supportedNames);
27
+ if (neededByWorkspace.size === 0) {
28
+ this.logger.info('No local uikit packages referenced by consumer workspaces.');
29
+ return;
30
+ }
31
+ // Collect all packages that were actually linked
32
+ const linkedNames = new Set();
33
+ for (const names of neededByWorkspace.values()) {
34
+ for (const name of names) {
35
+ linkedNames.add(name);
36
+ }
37
+ }
38
+ this.linkPackages(consumerRoot, neededByWorkspace, dirByName);
39
+ if (verify) {
40
+ // Only verify packages that were actually linked
41
+ const linkedDirByName = new Map();
42
+ for (const name of linkedNames) {
43
+ const path = dirByName.get(name);
44
+ if (path) {
45
+ linkedDirByName.set(name, path);
46
+ }
47
+ }
48
+ this.runVerification(consumerRoot, linkedDirByName);
49
+ }
50
+ }
51
+ collectWorkspaceRequirements(workspaces, supportedNames) {
52
+ const neededByWorkspace = new Map();
53
+ for (const ws of workspaces) {
54
+ const fields = [ws.dependencies];
55
+ const needed = new Set();
56
+ for (const depField of fields) {
57
+ for (const depName of Object.keys(depField)) {
58
+ if (supportedNames.has(depName)) {
59
+ needed.add(depName);
60
+ }
61
+ }
62
+ }
63
+ if (needed.size > 0) {
64
+ neededByWorkspace.set(ws.location, [...needed]);
65
+ }
66
+ }
67
+ return neededByWorkspace;
68
+ }
69
+ linkPackages(consumerRoot, neededByWorkspace, dirByName) {
70
+ const allNames = new Set();
71
+ for (const names of neededByWorkspace.values()) {
72
+ for (const name of names) {
73
+ allNames.add(name);
74
+ }
75
+ }
76
+ // Link at root level
77
+ const rootPackageArgs = [...allNames].map((name) => `"${name}"`).join(' ');
78
+ if (rootPackageArgs) {
79
+ this.logger.info('Linking packages at root level...');
80
+ this.executor.exec(`npm link ${rootPackageArgs} --package-lock=false --save=false`, { cwd: consumerRoot });
81
+ }
82
+ // Ensure symlinks point to correct targets
83
+ for (const name of allNames) {
84
+ const target = dirByName.get(name);
85
+ if (target) {
86
+ this.ensureLinkedPath(consumerRoot, name, target);
87
+ }
88
+ }
89
+ // Link per workspace
90
+ for (const [workspace, names] of neededByWorkspace.entries()) {
91
+ const packageArgs = names.map((name) => `"${name}"`).join(' ');
92
+ if (!packageArgs)
93
+ continue;
94
+ this.logger.debug(`Linking packages for workspace: ${workspace}`);
95
+ this.executor.exec(`npm link ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, { cwd: consumerRoot });
96
+ // Clean up shadow installs and Vite cache
97
+ for (const name of names) {
98
+ this.removeWorkspaceShadowInstall(consumerRoot, workspace, name);
99
+ }
100
+ this.clearWorkspaceViteCache(consumerRoot, workspace);
101
+ }
102
+ this.logger.info('✓ Linked local uikit packages into consumer workspaces.');
103
+ }
104
+ ensureLinkedPath(consumerRoot, packageName, expectedTarget) {
105
+ const packagePath = path.join(consumerRoot, 'node_modules', packageName);
106
+ try {
107
+ if (this.fs.isSymlink(packagePath) &&
108
+ this.fs.realpath(packagePath) === expectedTarget) {
109
+ return;
110
+ }
111
+ this.fs.removeDir(packagePath);
112
+ }
113
+ catch {
114
+ // Path may not exist yet
115
+ }
116
+ this.fs.createDir(path.dirname(packagePath));
117
+ this.fs.createSymlink(expectedTarget, packagePath);
118
+ }
119
+ removeWorkspaceShadowInstall(consumerRoot, workspace, packageName) {
120
+ const packagePath = path.join(consumerRoot, workspace, 'node_modules', packageName);
121
+ if (!this.fs.exists(packagePath)) {
122
+ return;
123
+ }
124
+ try {
125
+ if (this.fs.isSymlink(packagePath)) {
126
+ return;
127
+ }
128
+ this.fs.removeDir(packagePath);
129
+ this.logger.info(`Removed shadow install at ${workspace}/node_modules/${packageName} to preserve local links.`);
130
+ }
131
+ catch {
132
+ // Best effort cleanup
133
+ }
134
+ }
135
+ clearWorkspaceViteCache(consumerRoot, workspace) {
136
+ const viteCachePath = path.join(consumerRoot, workspace, 'node_modules', '.vite');
137
+ if (!this.fs.exists(viteCachePath)) {
138
+ return;
139
+ }
140
+ try {
141
+ this.fs.removeDir(viteCachePath);
142
+ this.logger.debug(`Cleared Vite cache at ${workspace}/node_modules/.vite`);
143
+ }
144
+ catch {
145
+ // Best effort cleanup
146
+ }
147
+ }
148
+ runVerification(consumerRoot, dirByName) {
149
+ this.logger.info('\nVerifying link state...');
150
+ const result = this.validator.validateLinkedPackages(consumerRoot, dirByName);
151
+ if (result.valid) {
152
+ this.logger.info('✓ All links valid');
153
+ }
154
+ else {
155
+ this.logger.warn(`Found ${result.issues.length} issue(s):`);
156
+ for (const issue of result.issues) {
157
+ this.logger.warn(` ${issue.type}: ${issue.package}`);
158
+ this.logger.warn(` ${issue.details}`);
159
+ }
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,14 @@
1
+ import { shellEscape } from '../shell-utils.js';
2
+ /**
3
+ * Lint command - forwards to oxlint
4
+ */
5
+ export class LintCommand {
6
+ executor;
7
+ constructor(executor) {
8
+ this.executor = executor;
9
+ }
10
+ execute(args) {
11
+ const escapedArgs = args.map((arg) => shellEscape(arg)).join(' ');
12
+ this.executor.exec(`npm exec -- oxlint ${escapedArgs}`.trim(), { cwd: process.cwd() });
13
+ }
14
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Register command - globally links uikit packages via npm link
3
+ */
4
+ export class RegisterCommand {
5
+ discovery;
6
+ executor;
7
+ logger;
8
+ constructor(discovery, executor, logger) {
9
+ this.discovery = discovery;
10
+ this.executor = executor;
11
+ this.logger = logger;
12
+ }
13
+ execute(uikitRoot, supportedNames) {
14
+ this.logger.debug('Starting register command', { uikitRoot });
15
+ const uikitWorkspaces = this.discovery.loadWorkspaces(uikitRoot);
16
+ const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@archon-research/'));
17
+ const packagesToRegister = supportedNames
18
+ ? uikitPackages.filter((pkg) => supportedNames.has(pkg.name ?? ''))
19
+ : uikitPackages;
20
+ this.logger.info(`Registering ${packagesToRegister.length} local uikit packages...`);
21
+ for (const pkg of packagesToRegister) {
22
+ if (!pkg.name)
23
+ continue;
24
+ this.logger.debug(`Registering ${pkg.name}`, { path: pkg.path });
25
+ const result = this.executor.exec(`npm link`, { cwd: pkg.path });
26
+ if (!result.success) {
27
+ this.logger.error(`Failed to register ${pkg.name}`, {
28
+ error: result.stderr,
29
+ });
30
+ throw new Error(`Failed to register ${pkg.name}`);
31
+ }
32
+ }
33
+ this.logger.info('✓ All packages registered successfully');
34
+ }
35
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Unlink command - simplified version without registry checks
3
+ */
4
+ export class UnlinkCommand {
5
+ executor;
6
+ logger;
7
+ constructor(executor, logger) {
8
+ this.executor = executor;
9
+ this.logger = logger;
10
+ }
11
+ execute(consumerRoot, uikitRoot, discovery) {
12
+ // Load workspaces and determine requirements
13
+ const uikitWorkspaces = discovery.loadWorkspaces(uikitRoot);
14
+ const consumerWorkspaces = discovery.loadWorkspaces(consumerRoot);
15
+ const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@archon-research/'));
16
+ const supportedNames = new Set(uikitPackages.map((pkg) => pkg.name ?? '').filter((name) => name !== ''));
17
+ // Collect workspace requirements
18
+ const neededByWorkspace = new Map();
19
+ const allPackages = new Set();
20
+ for (const ws of consumerWorkspaces) {
21
+ const needed = new Set();
22
+ for (const depName of Object.keys(ws.dependencies)) {
23
+ if (supportedNames.has(depName)) {
24
+ needed.add(depName);
25
+ allPackages.add(depName);
26
+ }
27
+ }
28
+ if (needed.size > 0) {
29
+ neededByWorkspace.set(ws.location, [...needed]);
30
+ }
31
+ }
32
+ const packages = [...allPackages];
33
+ const workspaces = [...neededByWorkspace.keys()];
34
+ this.logger.debug('Starting unlink command', {
35
+ consumerRoot,
36
+ packageCount: packages.length,
37
+ });
38
+ // Unlink at root level
39
+ this.logger.info('Unlinking packages at root level...');
40
+ const packageArgs = packages.map((name) => `"${name}"`).join(' ');
41
+ if (packageArgs) {
42
+ const result = this.executor.execQuiet(`npm unlink ${packageArgs} --package-lock=false --save=false`, { cwd: consumerRoot });
43
+ if (!result) {
44
+ this.logger.warn('Root unlink had issues, continuing...');
45
+ }
46
+ }
47
+ // Unlink per workspace
48
+ for (const workspace of workspaces) {
49
+ this.logger.debug(`Unlinking packages for workspace: ${workspace}`);
50
+ this.executor.execQuiet(`npm unlink ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, { cwd: consumerRoot });
51
+ }
52
+ // Restore from registry
53
+ this.logger.info('Restoring packages from registry...');
54
+ const installResult = this.executor.exec('npm_config_min_release_age=0 npm install', {
55
+ cwd: consumerRoot,
56
+ });
57
+ if (!installResult.success) {
58
+ this.logger.error('Failed to restore packages from registry');
59
+ this.logger.warn('You may need to run `uikit-cli link` again to restore local links');
60
+ throw new Error('Unlink failed: could not restore registry packages');
61
+ }
62
+ this.logger.info('✓ Packages unlinked and restored from registry');
63
+ }
64
+ }
@@ -0,0 +1,56 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, } from 'node:fs';
2
+ /**
3
+ * Real file system implementation
4
+ */
5
+ export class RealFileSystem {
6
+ exists(path) {
7
+ return existsSync(path);
8
+ }
9
+ readFile(path) {
10
+ return readFileSync(path, 'utf8');
11
+ }
12
+ readJson(path) {
13
+ try {
14
+ if (process.env.UIKIT_DEBUG) {
15
+ console.log('[DEBUG readJson]', path);
16
+ }
17
+ const content = this.readFile(path);
18
+ return JSON.parse(content);
19
+ }
20
+ catch (error) {
21
+ console.error('[readJson ERROR] Failed to read:', path);
22
+ throw error;
23
+ }
24
+ }
25
+ realpath(path) {
26
+ return realpathSync(path);
27
+ }
28
+ isSymlink(path) {
29
+ try {
30
+ return lstatSync(path).isSymbolicLink();
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ removeDir(path, options) {
37
+ rmSync(path, { recursive: true, force: true, ...options });
38
+ }
39
+ createSymlink(target, path) {
40
+ symlinkSync(target, path, 'dir');
41
+ }
42
+ createDir(path, options) {
43
+ mkdirSync(path, { recursive: true, ...options });
44
+ }
45
+ readDir(path) {
46
+ return readdirSync(path);
47
+ }
48
+ isDirectory(path) {
49
+ try {
50
+ return statSync(path).isDirectory();
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,80 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * Validates link state without npm registry checks
4
+ */
5
+ export class LinkValidator {
6
+ fs;
7
+ logger;
8
+ constructor(fs, logger) {
9
+ this.fs = fs;
10
+ this.logger = logger;
11
+ }
12
+ /**
13
+ * Validate all expected links in consumer
14
+ */
15
+ validateLinkedPackages(consumerRoot, expectedLinks) {
16
+ const issues = [];
17
+ this.logger.debug('Validating linked packages', {
18
+ consumerRoot,
19
+ packageCount: expectedLinks.size,
20
+ });
21
+ for (const [packageName, expectedTarget] of expectedLinks) {
22
+ const linkPath = path.join(consumerRoot, 'node_modules', packageName);
23
+ const status = this.validateSymlink(linkPath, expectedTarget);
24
+ if (status !== 'valid') {
25
+ issues.push({
26
+ type: status,
27
+ package: packageName,
28
+ details: this.getStatusDetails(linkPath, expectedTarget, status),
29
+ });
30
+ }
31
+ }
32
+ return {
33
+ valid: issues.length === 0,
34
+ issues,
35
+ };
36
+ }
37
+ /**
38
+ * Validate a single symlink
39
+ */
40
+ validateSymlink(linkPath, expectedTarget) {
41
+ if (!this.fs.exists(linkPath)) {
42
+ return 'missing';
43
+ }
44
+ if (!this.fs.isSymlink(linkPath)) {
45
+ // Not a symlink, might be a regular install
46
+ return 'missing';
47
+ }
48
+ try {
49
+ const actualTarget = this.fs.realpath(linkPath);
50
+ const normalizedExpected = path.resolve(expectedTarget);
51
+ const normalizedActual = path.resolve(actualTarget);
52
+ if (normalizedActual === normalizedExpected) {
53
+ return 'valid';
54
+ }
55
+ return 'wrong-target';
56
+ }
57
+ catch {
58
+ return 'broken';
59
+ }
60
+ }
61
+ getStatusDetails(linkPath, expectedTarget, status) {
62
+ switch (status) {
63
+ case 'missing':
64
+ return `Link does not exist at ${linkPath}`;
65
+ case 'broken':
66
+ return `Symlink exists but points to non-existent location`;
67
+ case 'wrong-target': {
68
+ try {
69
+ const actualTarget = this.fs.realpath(linkPath);
70
+ return `Expected: ${expectedTarget}\nActual: ${actualTarget}`;
71
+ }
72
+ catch {
73
+ return `Could not read symlink target`;
74
+ }
75
+ }
76
+ default:
77
+ return '';
78
+ }
79
+ }
80
+ }
package/dist/logger.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Console logger with optional debug mode
3
+ */
4
+ export class ConsoleLogger {
5
+ debugMode;
6
+ constructor(debugMode = false) {
7
+ this.debugMode = debugMode;
8
+ }
9
+ info(message, context) {
10
+ if (context) {
11
+ console.log(message, context);
12
+ }
13
+ else {
14
+ console.log(message);
15
+ }
16
+ }
17
+ warn(message, context) {
18
+ if (context) {
19
+ console.warn(`⚠️ ${message}`, context);
20
+ }
21
+ else {
22
+ console.warn(`⚠️ ${message}`);
23
+ }
24
+ }
25
+ error(message, context) {
26
+ if (context) {
27
+ console.error(`❌ ${message}`, context);
28
+ }
29
+ else {
30
+ console.error(`❌ ${message}`);
31
+ }
32
+ }
33
+ debug(message, context) {
34
+ if (!this.debugMode)
35
+ return;
36
+ if (context) {
37
+ console.log(`[DEBUG] ${message}`, context);
38
+ }
39
+ else {
40
+ console.log(`[DEBUG] ${message}`);
41
+ }
42
+ }
43
+ }