@bobfrankston/npmglobalize 1.0.18 → 1.0.20
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/cli.js +64 -10
- package/lib.d.ts +39 -2
- package/lib.js +346 -18
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* npmglobalize CLI - Transform file: dependencies to npm versions for publishing
|
|
4
4
|
*/
|
|
5
5
|
import { globalize, readConfig } from './lib.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
6
9
|
function printHelp() {
|
|
7
10
|
console.log(`
|
|
8
11
|
npmglobalize - Transform file: dependencies to npm versions for publishing
|
|
@@ -13,9 +16,16 @@ Release Options:
|
|
|
13
16
|
--patch Bump patch version (default)
|
|
14
17
|
--minor Bump minor version
|
|
15
18
|
--major Bump major version
|
|
16
|
-
--
|
|
19
|
+
--nopublish, -np Just transform, don't publish
|
|
17
20
|
--cleanup Restore from .dependencies
|
|
18
21
|
|
|
22
|
+
Dependency Options:
|
|
23
|
+
--update-deps Update package.json to latest (minor/patch only, safe)
|
|
24
|
+
--update-major Allow major version updates (breaking changes)
|
|
25
|
+
--no-publish-deps, -npd Don't auto-publish file: dependencies (use with caution)
|
|
26
|
+
--force-publish Republish dependencies even if version exists
|
|
27
|
+
--fix Run npm audit fix after transformation
|
|
28
|
+
|
|
19
29
|
Install Options:
|
|
20
30
|
--install Global install after publish (Windows)
|
|
21
31
|
--wsl Also install globally in WSL
|
|
@@ -39,20 +49,27 @@ Other Options:
|
|
|
39
49
|
--conform Update .gitignore/.npmignore to best practices
|
|
40
50
|
--asis Skip ignore file checks (or set "asis": true in .globalize.json5)
|
|
41
51
|
--help, -h Show this help
|
|
52
|
+
--version, -v Show version number
|
|
42
53
|
|
|
43
54
|
Examples:
|
|
44
|
-
npmglobalize
|
|
45
|
-
npmglobalize --minor
|
|
46
|
-
npmglobalize --
|
|
47
|
-
npmglobalize --
|
|
48
|
-
npmglobalize
|
|
49
|
-
npmglobalize --
|
|
50
|
-
npmglobalize --
|
|
55
|
+
npmglobalize Transform + publish (auto-publishes file: deps)
|
|
56
|
+
npmglobalize --minor Release with minor version bump
|
|
57
|
+
npmglobalize --update-deps Safe updates (minor/patch only)
|
|
58
|
+
npmglobalize --update-major Allow breaking changes (major updates)
|
|
59
|
+
npmglobalize -npd Skip auto-publishing file: deps (use with caution)
|
|
60
|
+
npmglobalize --force-publish Force republish all file: dependencies
|
|
61
|
+
npmglobalize --fix Fix security vulnerabilities
|
|
62
|
+
npmglobalize --install --wsl Release + install on Windows and WSL
|
|
63
|
+
npmglobalize -np Just transform, no publish
|
|
64
|
+
npmglobalize --cleanup Restore original dependencies
|
|
65
|
+
npmglobalize --init Initialize new git repo + release
|
|
66
|
+
npmglobalize --dry-run Preview what would happen
|
|
51
67
|
`);
|
|
52
68
|
}
|
|
53
69
|
function parseArgs(args) {
|
|
54
70
|
const options = {
|
|
55
71
|
help: false,
|
|
72
|
+
version: false,
|
|
56
73
|
error: ''
|
|
57
74
|
};
|
|
58
75
|
const unrecognized = [];
|
|
@@ -63,6 +80,10 @@ function parseArgs(args) {
|
|
|
63
80
|
case '-h':
|
|
64
81
|
options.help = true;
|
|
65
82
|
break;
|
|
83
|
+
case '--version':
|
|
84
|
+
case '-v':
|
|
85
|
+
options.version = true;
|
|
86
|
+
break;
|
|
66
87
|
case '--patch':
|
|
67
88
|
options.bump = 'patch';
|
|
68
89
|
break;
|
|
@@ -72,8 +93,10 @@ function parseArgs(args) {
|
|
|
72
93
|
case '--major':
|
|
73
94
|
options.bump = 'major';
|
|
74
95
|
break;
|
|
75
|
-
case '--
|
|
76
|
-
|
|
96
|
+
case '--nopublish':
|
|
97
|
+
case '-np':
|
|
98
|
+
case '--apply': // Keep for backward compatibility
|
|
99
|
+
options.noPublish = true;
|
|
77
100
|
break;
|
|
78
101
|
case '--cleanup':
|
|
79
102
|
options.cleanup = true;
|
|
@@ -148,6 +171,29 @@ function parseArgs(args) {
|
|
|
148
171
|
case '--asis':
|
|
149
172
|
options.asis = true;
|
|
150
173
|
break;
|
|
174
|
+
case '--update-deps':
|
|
175
|
+
options.updateDeps = true;
|
|
176
|
+
break;
|
|
177
|
+
case '--update-major':
|
|
178
|
+
options.updateMajor = true;
|
|
179
|
+
options.updateDeps = true; // Implies --update-deps
|
|
180
|
+
break;
|
|
181
|
+
case '--publish-deps':
|
|
182
|
+
options.publishDeps = true; // Explicitly enable (though it's default)
|
|
183
|
+
break;
|
|
184
|
+
case '--no-publish-deps':
|
|
185
|
+
case '-npd':
|
|
186
|
+
options.publishDeps = false; // Disable auto-publishing
|
|
187
|
+
break;
|
|
188
|
+
case '--force-publish':
|
|
189
|
+
options.forcePublish = true;
|
|
190
|
+
break;
|
|
191
|
+
case '--fix':
|
|
192
|
+
options.fix = true;
|
|
193
|
+
break;
|
|
194
|
+
case '--no-fix':
|
|
195
|
+
options.fix = false;
|
|
196
|
+
break;
|
|
151
197
|
default:
|
|
152
198
|
if (arg.startsWith('-')) {
|
|
153
199
|
unrecognized.push(arg);
|
|
@@ -167,6 +213,14 @@ export async function main() {
|
|
|
167
213
|
printHelp();
|
|
168
214
|
process.exit(0);
|
|
169
215
|
}
|
|
216
|
+
if (cliOptions.version) {
|
|
217
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
218
|
+
const __dirname = path.dirname(__filename);
|
|
219
|
+
const pkgPath = path.join(__dirname, 'package.json');
|
|
220
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
221
|
+
console.log(pkg.version);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
170
224
|
if (cliOptions.error) {
|
|
171
225
|
console.error(`Error: ${cliOptions.error}`);
|
|
172
226
|
console.error('Run with --help for usage.');
|
package/lib.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface GlobalizeOptions {
|
|
|
6
6
|
/** Bump type: patch (default), minor, major */
|
|
7
7
|
bump?: 'patch' | 'minor' | 'major';
|
|
8
8
|
/** Just transform, don't publish */
|
|
9
|
-
|
|
9
|
+
noPublish?: boolean;
|
|
10
10
|
/** Restore from .dependencies */
|
|
11
11
|
cleanup?: boolean;
|
|
12
12
|
/** Global install after publish */
|
|
@@ -35,6 +35,16 @@ export interface GlobalizeOptions {
|
|
|
35
35
|
conform?: boolean;
|
|
36
36
|
/** Keep ignore files as-is without checking */
|
|
37
37
|
asis?: boolean;
|
|
38
|
+
/** Check and update existing npm dependencies to latest versions */
|
|
39
|
+
updateDeps?: boolean;
|
|
40
|
+
/** Allow major version updates (breaking changes) */
|
|
41
|
+
updateMajor?: boolean;
|
|
42
|
+
/** Publish file: dependencies before converting them */
|
|
43
|
+
publishDeps?: boolean;
|
|
44
|
+
/** Force republish dependencies even if version exists on npm */
|
|
45
|
+
forcePublish?: boolean;
|
|
46
|
+
/** Run npm audit and fix vulnerabilities */
|
|
47
|
+
fix?: boolean;
|
|
38
48
|
}
|
|
39
49
|
/** Read and parse package.json from a directory */
|
|
40
50
|
export declare function readPackageJson(dir: string): any;
|
|
@@ -48,6 +58,20 @@ export declare function writePackageJson(dir: string, pkg: any): void;
|
|
|
48
58
|
export declare function resolveFilePath(fileRef: string, baseDir: string): string;
|
|
49
59
|
/** Check if a dependency value is a file: reference */
|
|
50
60
|
export declare function isFileRef(value: string): boolean;
|
|
61
|
+
/** Get the latest version of a package from npm */
|
|
62
|
+
export declare function getLatestVersion(packageName: string): string | null;
|
|
63
|
+
/** Check if a specific version of a package exists on npm */
|
|
64
|
+
export declare function checkVersionExists(packageName: string, version: string): boolean;
|
|
65
|
+
/** Update existing npm dependencies to latest versions */
|
|
66
|
+
export declare function updateNpmDeps(pkg: any, verbose?: boolean, allowMajor?: boolean): {
|
|
67
|
+
updated: boolean;
|
|
68
|
+
changes: string[];
|
|
69
|
+
majorAvailable: Array<{
|
|
70
|
+
name: string;
|
|
71
|
+
current: string;
|
|
72
|
+
latest: string;
|
|
73
|
+
}>;
|
|
74
|
+
};
|
|
51
75
|
/** Get all file: dependencies from package.json */
|
|
52
76
|
export declare function getFileRefs(pkg: any): Map<string, {
|
|
53
77
|
key: string;
|
|
@@ -55,7 +79,14 @@ export declare function getFileRefs(pkg: any): Map<string, {
|
|
|
55
79
|
value: string;
|
|
56
80
|
}>;
|
|
57
81
|
/** Transform file: dependencies to npm versions */
|
|
58
|
-
export declare function transformDeps(pkg: any, baseDir: string, verbose?: boolean):
|
|
82
|
+
export declare function transformDeps(pkg: any, baseDir: string, verbose?: boolean, forcePublish?: boolean): {
|
|
83
|
+
transformed: boolean;
|
|
84
|
+
unpublished: Array<{
|
|
85
|
+
name: string;
|
|
86
|
+
version: string;
|
|
87
|
+
path: string;
|
|
88
|
+
}>;
|
|
89
|
+
};
|
|
59
90
|
/** Restore file: dependencies from .dependencies */
|
|
60
91
|
export declare function restoreDeps(pkg: any, verbose?: boolean): boolean;
|
|
61
92
|
/** Check if .dependencies exist (already transformed) */
|
|
@@ -94,6 +125,12 @@ export declare function confirm(message: string, defaultYes?: boolean): Promise<
|
|
|
94
125
|
/** Initialize git repository */
|
|
95
126
|
export declare function initGit(cwd: string, visibility: 'private' | 'public', dryRun: boolean): Promise<boolean>;
|
|
96
127
|
/** Main globalize function */
|
|
128
|
+
/** Run npm audit and optionally fix vulnerabilities */
|
|
129
|
+
export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolean): {
|
|
130
|
+
success: boolean;
|
|
131
|
+
report: string;
|
|
132
|
+
hasVulnerabilities: boolean;
|
|
133
|
+
};
|
|
97
134
|
export declare function globalize(cwd: string, options?: GlobalizeOptions): Promise<boolean>;
|
|
98
135
|
declare const _default: {
|
|
99
136
|
globalize: typeof globalize;
|
package/lib.js
CHANGED
|
@@ -57,7 +57,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
57
57
|
const existing = readConfig(dir);
|
|
58
58
|
// Filter out temporary flags and default values (unless explicitly set)
|
|
59
59
|
const filtered = {};
|
|
60
|
-
const omitKeys = new Set(['
|
|
60
|
+
const omitKeys = new Set(['noPublish', 'cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish']);
|
|
61
61
|
for (const [key, value] of Object.entries(config)) {
|
|
62
62
|
if (omitKeys.has(key))
|
|
63
63
|
continue;
|
|
@@ -102,6 +102,8 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
102
102
|
comment = ' // private or public (default)';
|
|
103
103
|
else if (key === 'bump')
|
|
104
104
|
comment = ' // patch (default), minor, or major';
|
|
105
|
+
else if (key === 'fix')
|
|
106
|
+
comment = ' // Auto-run npm audit fix';
|
|
105
107
|
lines.push(` "${key}": ${jsonValue}${comma}${comment}`);
|
|
106
108
|
});
|
|
107
109
|
lines.push('');
|
|
@@ -117,6 +119,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
117
119
|
lines.push(' // "verbose": false // Show detailed output');
|
|
118
120
|
lines.push(' // "gitVisibility": "private" // Git repo: private or public');
|
|
119
121
|
lines.push(' // "npmVisibility": "public" // npm package: private or public');
|
|
122
|
+
lines.push(' // "fix": false // Auto-run npm audit fix');
|
|
120
123
|
lines.push('}');
|
|
121
124
|
fs.writeFileSync(configPath, lines.join('\n') + '\n');
|
|
122
125
|
}
|
|
@@ -134,6 +137,119 @@ export function resolveFilePath(fileRef, baseDir) {
|
|
|
134
137
|
export function isFileRef(value) {
|
|
135
138
|
return typeof value === 'string' && value.startsWith('file:');
|
|
136
139
|
}
|
|
140
|
+
/** Get the latest version of a package from npm */
|
|
141
|
+
export function getLatestVersion(packageName) {
|
|
142
|
+
try {
|
|
143
|
+
const result = spawnSync('npm', ['view', packageName, 'version'], {
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
stdio: 'pipe',
|
|
146
|
+
shell: true
|
|
147
|
+
});
|
|
148
|
+
if (result.status === 0 && result.stdout) {
|
|
149
|
+
return result.stdout.trim();
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Check if a specific version of a package exists on npm */
|
|
158
|
+
export function checkVersionExists(packageName, version) {
|
|
159
|
+
try {
|
|
160
|
+
const result = spawnSync('npm', ['view', `${packageName}@${version}`, 'version'], {
|
|
161
|
+
encoding: 'utf-8',
|
|
162
|
+
stdio: 'pipe',
|
|
163
|
+
shell: true
|
|
164
|
+
});
|
|
165
|
+
return result.status === 0 && result.stdout.trim() === version;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Parse semver version string to major.minor.patch */
|
|
172
|
+
function parseSemver(version) {
|
|
173
|
+
const clean = version.replace(/^[^\d]*/, ''); // Remove ^, ~, etc.
|
|
174
|
+
const match = clean.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
175
|
+
if (!match)
|
|
176
|
+
return null;
|
|
177
|
+
return {
|
|
178
|
+
major: parseInt(match[1], 10),
|
|
179
|
+
minor: parseInt(match[2], 10),
|
|
180
|
+
patch: parseInt(match[3], 10)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Check if update is within semver range (^1.0.0 allows minor/patch, not major) */
|
|
184
|
+
function isCompatibleUpdate(currentSpec, latestVersion) {
|
|
185
|
+
const current = parseSemver(currentSpec);
|
|
186
|
+
const latest = parseSemver(latestVersion);
|
|
187
|
+
if (!current || !latest) {
|
|
188
|
+
return { compatible: true, isMajor: false }; // Can't parse, allow update
|
|
189
|
+
}
|
|
190
|
+
const isMajor = latest.major > current.major;
|
|
191
|
+
const prefix = currentSpec.match(/^[^\d]*/) ? currentSpec.match(/^[^\d]*/)[0] : '^';
|
|
192
|
+
// ^ allows minor and patch updates within same major version
|
|
193
|
+
// ~ allows only patch updates
|
|
194
|
+
if (prefix === '^') {
|
|
195
|
+
return { compatible: !isMajor, isMajor };
|
|
196
|
+
}
|
|
197
|
+
else if (prefix === '~') {
|
|
198
|
+
return { compatible: latest.major === current.major && latest.minor === current.minor, isMajor };
|
|
199
|
+
}
|
|
200
|
+
// For exact versions or other formats, allow all updates
|
|
201
|
+
return { compatible: true, isMajor };
|
|
202
|
+
}
|
|
203
|
+
/** Update existing npm dependencies to latest versions */
|
|
204
|
+
export function updateNpmDeps(pkg, verbose = false, allowMajor = false) {
|
|
205
|
+
const changes = [];
|
|
206
|
+
const majorAvailable = [];
|
|
207
|
+
let updated = false;
|
|
208
|
+
for (const key of DEP_KEYS) {
|
|
209
|
+
if (!pkg[key])
|
|
210
|
+
continue;
|
|
211
|
+
for (const [name, value] of Object.entries(pkg[key])) {
|
|
212
|
+
// Skip file: references - those are handled separately
|
|
213
|
+
if (isFileRef(value))
|
|
214
|
+
continue;
|
|
215
|
+
// Get current and latest versions
|
|
216
|
+
const currentSpec = value;
|
|
217
|
+
const latest = getLatestVersion(name);
|
|
218
|
+
if (latest) {
|
|
219
|
+
const { compatible, isMajor } = isCompatibleUpdate(currentSpec, latest);
|
|
220
|
+
const newSpec = '^' + latest;
|
|
221
|
+
if (currentSpec !== newSpec) {
|
|
222
|
+
if (isMajor && !allowMajor) {
|
|
223
|
+
// Major update available but not allowed
|
|
224
|
+
majorAvailable.push({ name, current: currentSpec, latest: newSpec });
|
|
225
|
+
if (verbose) {
|
|
226
|
+
console.log(colors.yellow(` ${name}: ${currentSpec} (major update ${newSpec} available, use --update-major)`));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else if (compatible || allowMajor) {
|
|
230
|
+
// Safe update or major updates allowed
|
|
231
|
+
pkg[key][name] = newSpec;
|
|
232
|
+
changes.push(`${name}: ${currentSpec} → ${newSpec}`);
|
|
233
|
+
if (isMajor) {
|
|
234
|
+
console.log(colors.red(` ${name}: ${currentSpec} → ${newSpec} (MAJOR)`));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.log(colors.yellow(` ${name}: ${currentSpec} → ${newSpec}`));
|
|
238
|
+
}
|
|
239
|
+
updated = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (verbose) {
|
|
243
|
+
console.log(colors.green(` ${name}: ${currentSpec} is up to date`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (verbose) {
|
|
247
|
+
console.log(colors.italic(` ${name}: couldn't check npm registry`));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { updated, changes, majorAvailable };
|
|
252
|
+
}
|
|
137
253
|
/** Get all file: dependencies from package.json */
|
|
138
254
|
export function getFileRefs(pkg) {
|
|
139
255
|
const refs = new Map();
|
|
@@ -149,8 +265,9 @@ export function getFileRefs(pkg) {
|
|
|
149
265
|
return refs;
|
|
150
266
|
}
|
|
151
267
|
/** Transform file: dependencies to npm versions */
|
|
152
|
-
export function transformDeps(pkg, baseDir, verbose = false) {
|
|
268
|
+
export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
|
|
153
269
|
let transformed = false;
|
|
270
|
+
const unpublished = [];
|
|
154
271
|
for (const key of DEP_KEYS) {
|
|
155
272
|
if (!pkg[key])
|
|
156
273
|
continue;
|
|
@@ -176,7 +293,22 @@ export function transformDeps(pkg, baseDir, verbose = false) {
|
|
|
176
293
|
const targetPath = resolveFilePath(value, baseDir);
|
|
177
294
|
try {
|
|
178
295
|
const targetPkg = readPackageJson(targetPath);
|
|
179
|
-
const
|
|
296
|
+
const targetVersion = targetPkg.version;
|
|
297
|
+
const npmVersion = '^' + targetVersion;
|
|
298
|
+
// Check if this version exists on npm (or if force publish)
|
|
299
|
+
const versionExists = forcePublish ? false : checkVersionExists(name, targetVersion);
|
|
300
|
+
if (!versionExists) {
|
|
301
|
+
unpublished.push({ name, version: targetVersion, path: targetPath });
|
|
302
|
+
if (forcePublish) {
|
|
303
|
+
console.log(colors.yellow(` ⟳ ${name}@${targetVersion} will be republished (--force-publish)`));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
console.log(colors.red(` ⚠ ${name}@${targetVersion} not found on npm (local: ${value})`));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else if (verbose) {
|
|
310
|
+
console.log(colors.green(` ✓ ${name}@${targetVersion} exists on npm`));
|
|
311
|
+
}
|
|
180
312
|
pkg[key][name] = npmVersion;
|
|
181
313
|
if (verbose) {
|
|
182
314
|
console.log(` ${name}: ${value} -> ${npmVersion}`);
|
|
@@ -189,7 +321,7 @@ export function transformDeps(pkg, baseDir, verbose = false) {
|
|
|
189
321
|
}
|
|
190
322
|
}
|
|
191
323
|
}
|
|
192
|
-
return transformed;
|
|
324
|
+
return { transformed, unpublished };
|
|
193
325
|
}
|
|
194
326
|
/** Restore file: dependencies from .dependencies */
|
|
195
327
|
export function restoreDeps(pkg, verbose = false) {
|
|
@@ -523,6 +655,20 @@ function checkIgnoreFiles(cwd, options) {
|
|
|
523
655
|
}
|
|
524
656
|
/** Update ignore files to conform to best practices */
|
|
525
657
|
function conformIgnoreFiles(cwd) {
|
|
658
|
+
// Update .gitattributes for LF line endings
|
|
659
|
+
const gitattributesPath = path.join(cwd, '.gitattributes');
|
|
660
|
+
const gitattributesContent = '* text=auto eol=lf\n';
|
|
661
|
+
if (!fs.existsSync(gitattributesPath)) {
|
|
662
|
+
fs.writeFileSync(gitattributesPath, gitattributesContent);
|
|
663
|
+
console.log(colors.green(' ✓ Created .gitattributes (LF line endings)'));
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
const content = fs.readFileSync(gitattributesPath, 'utf-8');
|
|
667
|
+
if (!content.includes('eol=lf')) {
|
|
668
|
+
fs.writeFileSync(gitattributesPath, gitattributesContent);
|
|
669
|
+
console.log(colors.green(' ✓ Updated .gitattributes (LF line endings)'));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
526
672
|
// Update .gitignore
|
|
527
673
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
528
674
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -680,8 +826,100 @@ export async function initGit(cwd, visibility, dryRun) {
|
|
|
680
826
|
return true;
|
|
681
827
|
}
|
|
682
828
|
/** Main globalize function */
|
|
829
|
+
/** Run npm audit and optionally fix vulnerabilities */
|
|
830
|
+
export function runNpmAudit(cwd, fix = false, verbose = false) {
|
|
831
|
+
console.log('');
|
|
832
|
+
console.log(colors.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
833
|
+
console.log(colors.yellow('🔒 npm audit'));
|
|
834
|
+
console.log(colors.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
835
|
+
if (fix) {
|
|
836
|
+
console.log('Running npm audit fix...');
|
|
837
|
+
const fixResult = runCommand('npm', ['audit', 'fix'], { cwd, silent: false, shell: true });
|
|
838
|
+
if (!fixResult.success) {
|
|
839
|
+
console.log(colors.yellow('⚠ Some vulnerabilities could not be automatically fixed'));
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
console.log(colors.green('✓ Audit fixes applied'));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
// Always run audit to report status
|
|
846
|
+
const auditResult = spawnSync('npm', ['audit', '--json'], {
|
|
847
|
+
cwd,
|
|
848
|
+
encoding: 'utf-8',
|
|
849
|
+
stdio: 'pipe',
|
|
850
|
+
shell: true
|
|
851
|
+
});
|
|
852
|
+
let hasVulnerabilities = false;
|
|
853
|
+
let report = '';
|
|
854
|
+
if (auditResult.status !== 0 || auditResult.stdout) {
|
|
855
|
+
try {
|
|
856
|
+
const auditData = JSON.parse(auditResult.stdout || '{}');
|
|
857
|
+
const { vulnerabilities = {} } = auditData;
|
|
858
|
+
const critical = auditData.metadata?.vulnerabilities?.critical || 0;
|
|
859
|
+
const high = auditData.metadata?.vulnerabilities?.high || 0;
|
|
860
|
+
const moderate = auditData.metadata?.vulnerabilities?.moderate || 0;
|
|
861
|
+
const low = auditData.metadata?.vulnerabilities?.low || 0;
|
|
862
|
+
const info = auditData.metadata?.vulnerabilities?.info || 0;
|
|
863
|
+
const total = critical + high + moderate + low + info;
|
|
864
|
+
if (total > 0) {
|
|
865
|
+
hasVulnerabilities = true;
|
|
866
|
+
report = `Found ${total} vulnerabilities`;
|
|
867
|
+
const parts = [];
|
|
868
|
+
if (critical > 0)
|
|
869
|
+
parts.push(colors.red(`${critical} critical`));
|
|
870
|
+
if (high > 0)
|
|
871
|
+
parts.push(colors.red(`${high} high`));
|
|
872
|
+
if (moderate > 0)
|
|
873
|
+
parts.push(colors.yellow(`${moderate} moderate`));
|
|
874
|
+
if (low > 0)
|
|
875
|
+
parts.push(`${low} low`);
|
|
876
|
+
if (info > 0)
|
|
877
|
+
parts.push(`${info} info`);
|
|
878
|
+
console.log('');
|
|
879
|
+
console.log(colors.yellow(`Vulnerabilities: ${parts.join(', ')}`));
|
|
880
|
+
if (verbose && Object.keys(vulnerabilities).length > 0) {
|
|
881
|
+
console.log('');
|
|
882
|
+
console.log('Details:');
|
|
883
|
+
for (const [pkg, data] of Object.entries(vulnerabilities)) {
|
|
884
|
+
const vulnData = data;
|
|
885
|
+
const severity = vulnData.severity || 'unknown';
|
|
886
|
+
const severityColor = severity === 'critical' || severity === 'high' ? colors.red :
|
|
887
|
+
severity === 'moderate' ? colors.yellow : (s) => s;
|
|
888
|
+
console.log(` ${severityColor(severity)}: ${pkg}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (!fix) {
|
|
892
|
+
console.log('');
|
|
893
|
+
console.log(colors.yellow('Run with --fix to automatically fix vulnerabilities'));
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
console.log(colors.green('✓ No vulnerabilities found'));
|
|
898
|
+
report = 'No vulnerabilities';
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
catch (e) {
|
|
902
|
+
// Fallback to text output if JSON parsing fails
|
|
903
|
+
console.log('Running text audit...');
|
|
904
|
+
const textResult = runCommand('npm', ['audit'], { cwd, silent: false, shell: true });
|
|
905
|
+
report = 'Audit completed (see output above)';
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
console.log(colors.green('✓ No vulnerabilities found'));
|
|
910
|
+
report = 'No vulnerabilities';
|
|
911
|
+
}
|
|
912
|
+
console.log(colors.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
913
|
+
console.log('');
|
|
914
|
+
return {
|
|
915
|
+
success: true,
|
|
916
|
+
report,
|
|
917
|
+
hasVulnerabilities
|
|
918
|
+
};
|
|
919
|
+
}
|
|
683
920
|
export async function globalize(cwd, options = {}) {
|
|
684
|
-
const { bump = 'patch',
|
|
921
|
+
const { bump = 'patch', noPublish = false, cleanup = false, install = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'public', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
922
|
+
forcePublish = false, fix = false } = options;
|
|
685
923
|
// Check ignore files first (unless cleanup mode)
|
|
686
924
|
if (!cleanup && !asis) {
|
|
687
925
|
const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
|
|
@@ -727,8 +965,8 @@ export async function globalize(cwd, options = {}) {
|
|
|
727
965
|
}
|
|
728
966
|
return true;
|
|
729
967
|
}
|
|
730
|
-
// Check npm authentication early (unless dry-run or
|
|
731
|
-
if (!dryRun && !
|
|
968
|
+
// Check npm authentication early (unless dry-run or no-publish)
|
|
969
|
+
if (!dryRun && !noPublish) {
|
|
732
970
|
const authStatus = checkNpmAuth();
|
|
733
971
|
if (!authStatus.authenticated) {
|
|
734
972
|
console.error(colors.red('\n✗ npm authentication required'));
|
|
@@ -854,15 +1092,90 @@ export async function globalize(cwd, options = {}) {
|
|
|
854
1092
|
// Default is public - just inform
|
|
855
1093
|
console.log(`Will publish '${pkg.name}' to PUBLIC npm registry (default).`);
|
|
856
1094
|
}
|
|
1095
|
+
// Update existing npm dependencies if requested
|
|
1096
|
+
if (updateDeps) {
|
|
1097
|
+
console.log('Checking for dependency updates...');
|
|
1098
|
+
const updateResult = updateNpmDeps(pkg, verbose, updateMajor);
|
|
1099
|
+
if (updateResult.updated) {
|
|
1100
|
+
console.log(colors.green(`Updated ${updateResult.changes.length} dependencies`));
|
|
1101
|
+
if (!dryRun) {
|
|
1102
|
+
writePackageJson(cwd, pkg);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
console.log('All npm dependencies are up to date.');
|
|
1107
|
+
}
|
|
1108
|
+
// Show summary of available major updates
|
|
1109
|
+
if (updateResult.majorAvailable.length > 0 && !updateMajor) {
|
|
1110
|
+
console.log('');
|
|
1111
|
+
console.log(colors.yellow(`${updateResult.majorAvailable.length} major update(s) available (use --update-major):`));
|
|
1112
|
+
for (const { name, current, latest } of updateResult.majorAvailable) {
|
|
1113
|
+
console.log(colors.yellow(` ${name}: ${current} → ${latest}`));
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
857
1117
|
// Transform dependencies
|
|
858
1118
|
console.log('Transforming file: dependencies...');
|
|
859
1119
|
const alreadyTransformed = hasBackup(pkg);
|
|
860
1120
|
if (alreadyTransformed) {
|
|
861
1121
|
console.log(' Already transformed (found .dependencies)');
|
|
862
1122
|
}
|
|
863
|
-
const
|
|
864
|
-
if (transformed) {
|
|
1123
|
+
const transformResult = transformDeps(pkg, cwd, verbose, forcePublish);
|
|
1124
|
+
if (transformResult.transformed) {
|
|
865
1125
|
console.log(' Dependencies transformed.');
|
|
1126
|
+
// Check if target packages need to be published
|
|
1127
|
+
if (transformResult.unpublished.length > 0) {
|
|
1128
|
+
console.log('');
|
|
1129
|
+
console.log(colors.red('⚠ WARNING: Some file: dependencies are not published on npm:'));
|
|
1130
|
+
for (const { name, version, path } of transformResult.unpublished) {
|
|
1131
|
+
console.log(colors.yellow(` ${name}@${version} (${path})`));
|
|
1132
|
+
}
|
|
1133
|
+
console.log('');
|
|
1134
|
+
if (publishDeps) {
|
|
1135
|
+
const action = forcePublish ? 'Publishing/updating' : 'Publishing';
|
|
1136
|
+
console.log(`${action} file: dependencies first (--publish-deps)...`);
|
|
1137
|
+
for (const { name, version, path } of transformResult.unpublished) {
|
|
1138
|
+
console.log('');
|
|
1139
|
+
console.log(colors.yellow(`━━━ Publishing ${name}@${version} ━━━`));
|
|
1140
|
+
if (!dryRun) {
|
|
1141
|
+
// Recursively call globalize on the dependency
|
|
1142
|
+
const depSuccess = await globalize(path, {
|
|
1143
|
+
bump: 'patch', // Use existing version, don't bump
|
|
1144
|
+
verbose,
|
|
1145
|
+
quiet,
|
|
1146
|
+
force,
|
|
1147
|
+
files,
|
|
1148
|
+
gitVisibility,
|
|
1149
|
+
npmVisibility
|
|
1150
|
+
});
|
|
1151
|
+
if (!depSuccess) {
|
|
1152
|
+
console.error(colors.red(`Failed to publish ${name}`));
|
|
1153
|
+
if (!force) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
console.log(` [dry-run] Would publish ${name}`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
console.log('');
|
|
1163
|
+
console.log(colors.green('✓ All dependencies published'));
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
console.log(colors.yellow('Options:'));
|
|
1167
|
+
console.log(colors.yellow(' 1. Publish them manually first'));
|
|
1168
|
+
console.log(colors.yellow(' 2. Use --publish-deps to publish them automatically'));
|
|
1169
|
+
console.log(colors.yellow(' 3. Use --force to continue anyway (NOT RECOMMENDED)'));
|
|
1170
|
+
console.log('');
|
|
1171
|
+
if (!force) {
|
|
1172
|
+
const shouldContinue = await confirm('Continue with unpublished dependencies?', false);
|
|
1173
|
+
if (!shouldContinue) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
866
1179
|
if (!dryRun) {
|
|
867
1180
|
writePackageJson(cwd, pkg);
|
|
868
1181
|
}
|
|
@@ -870,8 +1183,17 @@ export async function globalize(cwd, options = {}) {
|
|
|
870
1183
|
else if (!alreadyTransformed) {
|
|
871
1184
|
console.log(' No file: dependencies found.');
|
|
872
1185
|
}
|
|
873
|
-
if
|
|
874
|
-
|
|
1186
|
+
// Run npm audit if requested or if dependencies were transformed
|
|
1187
|
+
if ((fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps)) {
|
|
1188
|
+
if (!dryRun) {
|
|
1189
|
+
runNpmAudit(cwd, fix, verbose);
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
console.log(' [dry-run] Would run npm audit');
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (noPublish) {
|
|
1196
|
+
console.log('Transform complete (--nopublish mode).');
|
|
875
1197
|
return true;
|
|
876
1198
|
}
|
|
877
1199
|
// Skip if private
|
|
@@ -1073,7 +1395,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1073
1395
|
console.error(colors.yellow(' 3. Authentication token expired'));
|
|
1074
1396
|
console.error('');
|
|
1075
1397
|
}
|
|
1076
|
-
if (transformed) {
|
|
1398
|
+
if (transformResult.transformed) {
|
|
1077
1399
|
console.log('Run --cleanup to restore file: dependencies');
|
|
1078
1400
|
}
|
|
1079
1401
|
return false;
|
|
@@ -1100,7 +1422,8 @@ export async function globalize(cwd, options = {}) {
|
|
|
1100
1422
|
if (install) {
|
|
1101
1423
|
console.log(`Installing globally: ${pkgName}@${pkgVersion}...`);
|
|
1102
1424
|
if (!dryRun) {
|
|
1103
|
-
|
|
1425
|
+
// Install from local directory (faster and works immediately after publish)
|
|
1426
|
+
const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, shell: true });
|
|
1104
1427
|
if (installResult.success) {
|
|
1105
1428
|
// Verify installation by checking if command exists
|
|
1106
1429
|
const verifyResult = runCommand('npm', ['list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
@@ -1113,17 +1436,18 @@ export async function globalize(cwd, options = {}) {
|
|
|
1113
1436
|
}
|
|
1114
1437
|
else {
|
|
1115
1438
|
console.error(colors.red(`✗ Global install failed`));
|
|
1116
|
-
console.error(colors.yellow(' Try running manually: npm install -g '
|
|
1439
|
+
console.error(colors.yellow(' Try running manually: npm install -g .'));
|
|
1117
1440
|
}
|
|
1118
1441
|
}
|
|
1119
1442
|
else {
|
|
1120
|
-
console.log(` [dry-run] Would run: npm install -g
|
|
1443
|
+
console.log(` [dry-run] Would run: npm install -g .`);
|
|
1121
1444
|
}
|
|
1122
1445
|
}
|
|
1123
1446
|
if (wsl) {
|
|
1124
1447
|
console.log(`Installing in WSL: ${pkgName}@${pkgVersion}...`);
|
|
1125
1448
|
if (!dryRun) {
|
|
1126
|
-
|
|
1449
|
+
// Install from local directory in WSL
|
|
1450
|
+
const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false });
|
|
1127
1451
|
if (wslResult.success) {
|
|
1128
1452
|
// Verify WSL installation
|
|
1129
1453
|
const verifyResult = runCommand('wsl', ['npm', 'list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
@@ -1143,7 +1467,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1143
1467
|
}
|
|
1144
1468
|
}
|
|
1145
1469
|
// Finalize - restore file: paths if --files mode (default)
|
|
1146
|
-
if (files && transformed) {
|
|
1470
|
+
if (files && transformResult.transformed) {
|
|
1147
1471
|
console.log('Restoring file: dependencies (--files mode)...');
|
|
1148
1472
|
const finalPkg = readPackageJson(cwd);
|
|
1149
1473
|
restoreDeps(finalPkg, verbose);
|
|
@@ -1161,6 +1485,10 @@ export async function globalize(cwd, options = {}) {
|
|
|
1161
1485
|
console.log('Keeping npm versions (--nofiles mode).');
|
|
1162
1486
|
}
|
|
1163
1487
|
console.log('Done!');
|
|
1488
|
+
// Run final audit if dependencies were transformed or fix was requested
|
|
1489
|
+
if ((fix || updateDeps || transformResult.transformed) && !dryRun) {
|
|
1490
|
+
runNpmAudit(cwd, false, verbose); // Don't fix again, just report
|
|
1491
|
+
}
|
|
1164
1492
|
// Print summary
|
|
1165
1493
|
console.log('');
|
|
1166
1494
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
@@ -1177,7 +1505,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1177
1505
|
if (wsl) {
|
|
1178
1506
|
console.log(` Installed in WSL: ${colors.green('✓')}`);
|
|
1179
1507
|
}
|
|
1180
|
-
if (files && transformed) {
|
|
1508
|
+
if (files && transformResult.transformed) {
|
|
1181
1509
|
console.log(` Restored file: deps: ${colors.green('✓')}`);
|
|
1182
1510
|
}
|
|
1183
1511
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|