@bobfrankston/npmglobalize 1.0.19 → 1.0.21
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 +348 -16
- 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,21 @@ 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
|
+
else if (fix && !dryRun) {
|
|
1196
|
+
// Run fix even if no deps changed
|
|
1197
|
+
runNpmAudit(cwd, fix, verbose);
|
|
1198
|
+
}
|
|
1199
|
+
if (noPublish) {
|
|
1200
|
+
console.log('Transform complete (--nopublish mode).');
|
|
875
1201
|
return true;
|
|
876
1202
|
}
|
|
877
1203
|
// Skip if private
|
|
@@ -1073,7 +1399,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1073
1399
|
console.error(colors.yellow(' 3. Authentication token expired'));
|
|
1074
1400
|
console.error('');
|
|
1075
1401
|
}
|
|
1076
|
-
if (transformed) {
|
|
1402
|
+
if (transformResult.transformed) {
|
|
1077
1403
|
console.log('Run --cleanup to restore file: dependencies');
|
|
1078
1404
|
}
|
|
1079
1405
|
return false;
|
|
@@ -1095,8 +1421,9 @@ export async function globalize(cwd, options = {}) {
|
|
|
1095
1421
|
}
|
|
1096
1422
|
}
|
|
1097
1423
|
// Global install
|
|
1098
|
-
const
|
|
1099
|
-
const
|
|
1424
|
+
const updatedPkg = readPackageJson(cwd); // Re-read to get updated version
|
|
1425
|
+
const pkgName = updatedPkg.name;
|
|
1426
|
+
const pkgVersion = updatedPkg.version;
|
|
1100
1427
|
if (install) {
|
|
1101
1428
|
console.log(`Installing globally: ${pkgName}@${pkgVersion}...`);
|
|
1102
1429
|
if (!dryRun) {
|
|
@@ -1145,7 +1472,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1145
1472
|
}
|
|
1146
1473
|
}
|
|
1147
1474
|
// Finalize - restore file: paths if --files mode (default)
|
|
1148
|
-
if (files && transformed) {
|
|
1475
|
+
if (files && transformResult.transformed) {
|
|
1149
1476
|
console.log('Restoring file: dependencies (--files mode)...');
|
|
1150
1477
|
const finalPkg = readPackageJson(cwd);
|
|
1151
1478
|
restoreDeps(finalPkg, verbose);
|
|
@@ -1163,6 +1490,11 @@ export async function globalize(cwd, options = {}) {
|
|
|
1163
1490
|
console.log('Keeping npm versions (--nofiles mode).');
|
|
1164
1491
|
}
|
|
1165
1492
|
console.log('Done!');
|
|
1493
|
+
// Run final audit report if not already run
|
|
1494
|
+
const auditAlreadyRun = (fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps);
|
|
1495
|
+
if (!auditAlreadyRun && (fix || updateDeps || transformResult.transformed) && !dryRun) {
|
|
1496
|
+
runNpmAudit(cwd, false, verbose); // Just report, don't fix again
|
|
1497
|
+
}
|
|
1166
1498
|
// Print summary
|
|
1167
1499
|
console.log('');
|
|
1168
1500
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
@@ -1179,7 +1511,7 @@ export async function globalize(cwd, options = {}) {
|
|
|
1179
1511
|
if (wsl) {
|
|
1180
1512
|
console.log(` Installed in WSL: ${colors.green('✓')}`);
|
|
1181
1513
|
}
|
|
1182
|
-
if (files && transformed) {
|
|
1514
|
+
if (files && transformResult.transformed) {
|
|
1183
1515
|
console.log(` Restored file: deps: ${colors.green('✓')}`);
|
|
1184
1516
|
}
|
|
1185
1517
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|