@r0hitsharma/uikit-cli 0.12.0-rohit-fork-ci.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.
@@ -0,0 +1,252 @@
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.loadConsumerWorkspaces(consumerRoot);
22
+ const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@r0hitsharma/'));
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, uikitRoot, 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 packageDir = dirByName.get(name);
44
+ if (packageDir) {
45
+ linkedDirByName.set(name, packageDir);
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 = [
55
+ ws.dependencies,
56
+ ws.devDependencies,
57
+ ws.peerDependencies,
58
+ ws.optionalDependencies,
59
+ ];
60
+ const needed = new Set();
61
+ for (const depField of fields) {
62
+ for (const depName of Object.keys(depField)) {
63
+ if (supportedNames.has(depName)) {
64
+ needed.add(depName);
65
+ }
66
+ }
67
+ }
68
+ if (needed.size > 0) {
69
+ neededByWorkspace.set(ws.location, [...needed]);
70
+ }
71
+ }
72
+ return neededByWorkspace;
73
+ }
74
+ linkPackages(consumerRoot, uikitRoot, neededByWorkspace, dirByName) {
75
+ const allNames = new Set();
76
+ for (const names of neededByWorkspace.values()) {
77
+ for (const name of names) {
78
+ allNames.add(name);
79
+ }
80
+ }
81
+ // Track link steps that failed so we never report a partial link as success.
82
+ const failures = [];
83
+ // Link at root level
84
+ const rootPackageArgs = [...allNames].map((name) => `"${name}"`).join(' ');
85
+ if (rootPackageArgs) {
86
+ this.logger.info('Linking packages at root level...');
87
+ const result = this.executor.exec(`npm link ${rootPackageArgs} --package-lock=false --save=false`, {
88
+ cwd: consumerRoot,
89
+ });
90
+ if (!result.success) {
91
+ failures.push(`root: ${[...allNames].join(', ')}`);
92
+ }
93
+ }
94
+ // Ensure symlinks point to correct targets
95
+ for (const name of allNames) {
96
+ const target = dirByName.get(name);
97
+ if (target) {
98
+ this.ensureLinkedPath(consumerRoot, name, target);
99
+ }
100
+ }
101
+ // Link per workspace
102
+ for (const [workspace, names] of neededByWorkspace.entries()) {
103
+ const packageArgs = names.map((name) => `"${name}"`).join(' ');
104
+ if (!packageArgs)
105
+ continue;
106
+ // A single-package consumer surfaces as the root itself (location '').
107
+ // The root-level `npm link` above already linked it; there is no separate
108
+ // workspace node_modules to fix up, and `--workspace ""` is invalid.
109
+ if (workspace === '' || workspace === '.')
110
+ continue;
111
+ this.logger.debug(`Linking packages for workspace: ${workspace}`);
112
+ const result = this.executor.exec(`npm link ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, { cwd: consumerRoot });
113
+ if (!result.success) {
114
+ failures.push(`${workspace}: ${names.join(', ')}`);
115
+ }
116
+ // Clean up shadow installs and Vite cache
117
+ for (const name of names) {
118
+ this.removeWorkspaceShadowInstall(consumerRoot, workspace, name);
119
+ }
120
+ this.clearWorkspaceViteCache(consumerRoot, workspace);
121
+ }
122
+ // Ensure linked packages can resolve external runtime deps when preserve-symlinks is enabled.
123
+ // Run this last because workspace linking can mutate root node_modules.
124
+ this.linkMissingRuntimeDependencies(consumerRoot, uikitRoot, allNames, dirByName);
125
+ if (failures.length > 0) {
126
+ this.logger.warn('Some packages did not link — the local link is PARTIAL, and those ' +
127
+ 'names may resolve to a stale registry version instead:');
128
+ for (const failure of failures) {
129
+ this.logger.warn(` ${failure}`);
130
+ }
131
+ this.logger.warn('A consumer .npmrc with `min-release-age` can reject a fresh prerelease ' +
132
+ 'with ETARGET; re-run with an override (e.g. --min-release-age=0) or ' +
133
+ 'link those packages manually.');
134
+ return;
135
+ }
136
+ this.logger.info('✓ Linked local uikit packages into consumer workspaces.');
137
+ }
138
+ linkMissingRuntimeDependencies(consumerRoot, uikitRoot, linkedNames, dirByName) {
139
+ const depsToLink = this.collectExternalDependencyClosure(uikitRoot, linkedNames, dirByName);
140
+ if (depsToLink.size === 0) {
141
+ return;
142
+ }
143
+ for (const depName of depsToLink) {
144
+ const sourcePath = path.join(uikitRoot, 'node_modules', depName);
145
+ const consumerPath = path.join(consumerRoot, 'node_modules', depName);
146
+ if (!this.fs.exists(sourcePath)) {
147
+ continue;
148
+ }
149
+ // Respect existing consumer installations to avoid overriding deliberate versions.
150
+ if (this.fs.exists(consumerPath)) {
151
+ continue;
152
+ }
153
+ this.fs.createDir(path.dirname(consumerPath));
154
+ this.fs.createSymlink(sourcePath, consumerPath);
155
+ this.logger.debug(`Linked runtime dependency ${depName}`);
156
+ }
157
+ }
158
+ collectExternalDependencyClosure(uikitRoot, linkedNames, dirByName) {
159
+ const result = new Set();
160
+ const queue = [];
161
+ for (const linkedName of linkedNames) {
162
+ const packageDir = dirByName.get(linkedName);
163
+ if (!packageDir)
164
+ continue;
165
+ const packageJsonPath = path.join(packageDir, 'package.json');
166
+ if (!this.fs.exists(packageJsonPath))
167
+ continue;
168
+ const packageJson = this.fs.readJson(packageJsonPath);
169
+ for (const depName of Object.keys(packageJson.dependencies ?? {})) {
170
+ if (!depName.startsWith('@r0hitsharma/')) {
171
+ queue.push(depName);
172
+ }
173
+ }
174
+ }
175
+ while (queue.length > 0) {
176
+ const depName = queue.shift();
177
+ if (!depName || result.has(depName)) {
178
+ continue;
179
+ }
180
+ const depPackageJsonPath = path.join(uikitRoot, 'node_modules', depName, 'package.json');
181
+ if (!this.fs.exists(depPackageJsonPath)) {
182
+ continue;
183
+ }
184
+ result.add(depName);
185
+ const depPackageJson = this.fs.readJson(depPackageJsonPath);
186
+ for (const nestedDep of Object.keys(depPackageJson.dependencies ?? {})) {
187
+ if (!nestedDep.startsWith('@r0hitsharma/')) {
188
+ queue.push(nestedDep);
189
+ }
190
+ }
191
+ }
192
+ return result;
193
+ }
194
+ ensureLinkedPath(consumerRoot, packageName, expectedTarget) {
195
+ const packagePath = path.join(consumerRoot, 'node_modules', packageName);
196
+ try {
197
+ if (this.fs.isSymlink(packagePath) &&
198
+ this.fs.realpath(packagePath) === expectedTarget) {
199
+ return;
200
+ }
201
+ this.fs.removeDir(packagePath);
202
+ }
203
+ catch {
204
+ // Path may not exist yet
205
+ }
206
+ this.fs.createDir(path.dirname(packagePath));
207
+ this.fs.createSymlink(expectedTarget, packagePath);
208
+ }
209
+ removeWorkspaceShadowInstall(consumerRoot, workspace, packageName) {
210
+ const packagePath = path.join(consumerRoot, workspace, 'node_modules', packageName);
211
+ if (!this.fs.exists(packagePath)) {
212
+ return;
213
+ }
214
+ try {
215
+ if (this.fs.isSymlink(packagePath)) {
216
+ return;
217
+ }
218
+ this.fs.removeDir(packagePath);
219
+ this.logger.info(`Removed shadow install at ${workspace}/node_modules/${packageName} to preserve local links.`);
220
+ }
221
+ catch {
222
+ // Best effort cleanup
223
+ }
224
+ }
225
+ clearWorkspaceViteCache(consumerRoot, workspace) {
226
+ const viteCachePath = path.join(consumerRoot, workspace, 'node_modules', '.vite');
227
+ if (!this.fs.exists(viteCachePath)) {
228
+ return;
229
+ }
230
+ try {
231
+ this.fs.removeDir(viteCachePath);
232
+ this.logger.debug(`Cleared Vite cache at ${workspace}/node_modules/.vite`);
233
+ }
234
+ catch {
235
+ // Best effort cleanup
236
+ }
237
+ }
238
+ runVerification(consumerRoot, dirByName) {
239
+ this.logger.info('\nVerifying link state...');
240
+ const result = this.validator.validateLinkedPackages(consumerRoot, dirByName);
241
+ if (result.valid) {
242
+ this.logger.info('✓ All links valid');
243
+ }
244
+ else {
245
+ this.logger.warn(`Found ${result.issues.length} issue(s):`);
246
+ for (const issue of result.issues) {
247
+ this.logger.warn(` ${issue.type}: ${issue.package}`);
248
+ this.logger.warn(` ${issue.details}`);
249
+ }
250
+ }
251
+ }
252
+ }
@@ -0,0 +1,43 @@
1
+ import { shellEscape } from '../shell-utils.js';
2
+ import { resolveCliBinary } from '../tool-binaries.js';
3
+ /**
4
+ * Lint command - forwards to oxlint
5
+ *
6
+ * Deliberately does *not* inject `-c`, unlike `format`. oxfmt only
7
+ * auto-discovers the `.json` form of its config, so `format` has to point at
8
+ * `.oxfmtrc.ts` itself; oxlint resolves `oxlint.config.ts` per target file by
9
+ * walking up from it, so every package in a multi-target run gets its own
10
+ * config with no help. Injecting `-c ./oxlint.config.ts` would *replace* that
11
+ * per-package resolution with whatever config happens to sit in cwd — in the
12
+ * git-hook shape (cwd at the repo root, targets in several packages) that
13
+ * silently drops the packages' own rules.
14
+ */
15
+ export class LintCommand {
16
+ executor;
17
+ constructor(executor) {
18
+ this.executor = executor;
19
+ }
20
+ execute(args) {
21
+ const modifiedArgs = [...args];
22
+ // oxlint exits 0 on warnings. The shared presets set `correctness` and
23
+ // `suspicious` to `warn`, so without this every violation they describe is
24
+ // reported and then ignored. Callers who want warnings to stay advisory can
25
+ // pass their own `--max-warnings`/`--deny-warnings` and keep it.
26
+ if (!this.hasWarningLimitFlag(modifiedArgs)) {
27
+ modifiedArgs.push('--max-warnings=0');
28
+ }
29
+ const escapedArgs = modifiedArgs.map((arg) => shellEscape(arg)).join(' ');
30
+ const oxlintBinary = shellEscape(resolveCliBinary('oxlint', 'bin/oxlint'));
31
+ return this.executor.exec(`${oxlintBinary} ${escapedArgs}`.trim(), {
32
+ cwd: process.cwd(),
33
+ }).success;
34
+ }
35
+ // Only the two flags that govern the *exit code* count here. `-W`/`--warn`
36
+ // looks related but sets a rule's severity, and must not suppress the
37
+ // default.
38
+ hasWarningLimitFlag(args) {
39
+ return args.some((arg) => arg === '--max-warnings' ||
40
+ arg.startsWith('--max-warnings=') ||
41
+ arg === '--deny-warnings');
42
+ }
43
+ }
@@ -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('@r0hitsharma/'));
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,68 @@
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.loadConsumerWorkspaces(consumerRoot);
15
+ const uikitPackages = uikitWorkspaces.filter((ws) => String(ws.name ?? '').startsWith('@r0hitsharma/'));
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. The single-package consumer surfaces as the root
48
+ // (location ''); the root-level unlink above already covered it and
49
+ // `--workspace ""` is invalid.
50
+ for (const workspace of workspaces) {
51
+ if (workspace === '' || workspace === '.')
52
+ continue;
53
+ this.logger.debug(`Unlinking packages for workspace: ${workspace}`);
54
+ this.executor.execQuiet(`npm unlink ${packageArgs} --workspace "${workspace}" --package-lock=false --save=false`, { cwd: consumerRoot });
55
+ }
56
+ // Restore from registry
57
+ this.logger.info('Restoring packages from registry...');
58
+ const installResult = this.executor.exec('npm_config_min_release_age=0 npm install', {
59
+ cwd: consumerRoot,
60
+ });
61
+ if (!installResult.success) {
62
+ this.logger.error('Failed to restore packages from registry');
63
+ this.logger.warn('You may need to run `uikit-cli link` again to restore local links');
64
+ throw new Error('Unlink failed: could not restore registry packages');
65
+ }
66
+ this.logger.info('✓ Packages unlinked and restored from registry');
67
+ }
68
+ }
@@ -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
+ }