@bobfrankston/npmglobalize 1.0.199 → 1.0.201

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/lib/npm.js DELETED
@@ -1,306 +0,0 @@
1
- /**
2
- * npm registry operations: version checks, access control, authentication, dependency updates
3
- */
4
- import path from 'path';
5
- import { spawnSafe, colors, DEP_KEYS } from './types.js';
6
- import { isFileRef } from './config.js';
7
- /** Get the latest version of a package from npm */
8
- export function getLatestVersion(packageName) {
9
- try {
10
- const result = spawnSafe('npm', ['view', packageName, 'version'], {
11
- encoding: 'utf-8',
12
- stdio: 'pipe',
13
- shell: true // Required on Windows to find npm.cmd
14
- });
15
- if (result.status === 0 && result.stdout) {
16
- return result.stdout.trim();
17
- }
18
- return null;
19
- }
20
- catch (error) {
21
- return null;
22
- }
23
- }
24
- /** Check if a specific version of a package exists on npm */
25
- export function checkVersionExists(packageName, version) {
26
- try {
27
- const result = spawnSafe('npm', ['view', `${packageName}@${version}`, 'version'], {
28
- encoding: 'utf-8',
29
- stdio: 'pipe',
30
- shell: true // Required on Windows to find npm.cmd
31
- });
32
- return result.status === 0 && result.stdout.trim() === version;
33
- }
34
- catch (error) {
35
- return false;
36
- }
37
- }
38
- /** Check if a package exists on npm (any version) */
39
- export function checkPackageExists(packageName) {
40
- try {
41
- const result = spawnSafe('npm', ['view', packageName, 'version'], {
42
- encoding: 'utf-8',
43
- stdio: 'pipe',
44
- shell: true // Required on Windows to find npm.cmd
45
- });
46
- return result.status === 0 && result.stdout.trim().length > 0;
47
- }
48
- catch (error) {
49
- return false;
50
- }
51
- }
52
- /** Check npm package access level (public/restricted/null if not published) */
53
- export function checkNpmAccess(packageName) {
54
- try {
55
- // For scoped packages, use npm access to check actual access level
56
- if (packageName.startsWith('@')) {
57
- // First check if the package actually exists on npm
58
- // npm access returns read-write for unpublished packages in owned scopes
59
- const viewResult = spawnSafe('npm', ['view', packageName, 'name'], {
60
- encoding: 'utf-8',
61
- stdio: 'pipe',
62
- shell: true
63
- });
64
- if (viewResult.status === 0 && viewResult.stdout && viewResult.stdout.trim()) {
65
- // Package exists - check public/private status
66
- const accessResult = spawnSafe('npm', ['access', 'get', 'status', packageName], {
67
- encoding: 'utf-8',
68
- stdio: 'pipe',
69
- shell: true
70
- });
71
- if (accessResult.status === 0 && accessResult.stdout) {
72
- const output = accessResult.stdout.trim();
73
- if (output.includes('public')) {
74
- return 'public';
75
- }
76
- }
77
- return 'restricted'; // Exists but not publicly accessible
78
- }
79
- // Package not viewable - check if it's restricted or unpublished
80
- const checkResult = spawnSafe('npm', ['view', packageName, '--json'], {
81
- encoding: 'utf-8',
82
- stdio: 'pipe',
83
- shell: true
84
- });
85
- if (checkResult.status !== 0) {
86
- // Check if it's a 404 (not found) vs 401/403 (restricted)
87
- const stderr = checkResult.stderr || '';
88
- if (stderr.includes('404') || stderr.includes('not found')) {
89
- return null; // Not published
90
- }
91
- return 'restricted'; // Exists but restricted
92
- }
93
- return 'restricted'; // Default for scoped
94
- }
95
- else {
96
- // Unscoped packages are always public if they exist — but only if we own them
97
- const result = spawnSafe('npm', ['view', packageName, 'name'], {
98
- encoding: 'utf-8',
99
- stdio: 'pipe',
100
- shell: true
101
- });
102
- if (result.status === 0 && result.stdout && result.stdout.trim()) {
103
- // Package exists on npm — check if current user is a maintainer
104
- const maintResult = spawnSafe('npm', ['view', packageName, 'maintainers', '--json'], {
105
- encoding: 'utf-8',
106
- stdio: 'pipe',
107
- shell: true
108
- });
109
- if (maintResult.status === 0 && maintResult.stdout) {
110
- const auth = checkNpmAuth();
111
- if (auth.username) {
112
- try {
113
- const maintainers = JSON.parse(maintResult.stdout.trim());
114
- const names = Array.isArray(maintainers)
115
- ? maintainers.map((m) => typeof m === 'string' ? m.replace(/ <.*/, '') : m.name)
116
- : [];
117
- if (!names.includes(auth.username)) {
118
- return null; // Exists but we don't own it
119
- }
120
- }
121
- catch {
122
- // Parse failed — fall through to return public
123
- }
124
- }
125
- }
126
- return 'public';
127
- }
128
- return null;
129
- }
130
- }
131
- catch (error) {
132
- return null;
133
- }
134
- }
135
- /** Check if public package has private/inaccessible dependencies */
136
- export function checkPrivateDependencies(pkg, verbose = false) {
137
- const privateDeps = [];
138
- for (const depType of DEP_KEYS) {
139
- if (!pkg[depType])
140
- continue;
141
- for (const [name, value] of Object.entries(pkg[depType])) {
142
- // Skip file: references - those are handled separately
143
- if (isFileRef(value))
144
- continue;
145
- // Check if this dependency is publicly accessible
146
- if (!checkPackageExists(name)) {
147
- privateDeps.push({ name, depType });
148
- if (verbose) {
149
- console.log(` Checking ${name}... not publicly accessible`);
150
- }
151
- }
152
- else if (verbose) {
153
- console.log(` Checking ${name}... ok`);
154
- }
155
- }
156
- }
157
- return privateDeps;
158
- }
159
- /** Parse semver version string to major.minor.patch */
160
- function parseSemver(version) {
161
- const clean = version.replace(/^[^\d]*/, ''); // Remove ^, ~, etc.
162
- const match = clean.match(/^(\d+)\.(\d+)\.(\d+)/);
163
- if (!match)
164
- return null;
165
- return {
166
- major: parseInt(match[1], 10),
167
- minor: parseInt(match[2], 10),
168
- patch: parseInt(match[3], 10)
169
- };
170
- }
171
- /** Check if update is within semver range (^1.0.0 allows minor/patch, not major) */
172
- function isCompatibleUpdate(currentSpec, latestVersion) {
173
- const current = parseSemver(currentSpec);
174
- const latest = parseSemver(latestVersion);
175
- if (!current || !latest) {
176
- return { compatible: true, isMajor: false }; // Can't parse, allow update
177
- }
178
- const isMajor = latest.major > current.major;
179
- const prefix = currentSpec.match(/^[^\d]*/) ? currentSpec.match(/^[^\d]*/)[0] : '^';
180
- // ^ allows minor and patch updates within same major version
181
- // ~ allows only patch updates
182
- if (prefix === '^') {
183
- return { compatible: !isMajor, isMajor };
184
- }
185
- else if (prefix === '~') {
186
- return { compatible: latest.major === current.major && latest.minor === current.minor, isMajor };
187
- }
188
- // For exact versions or other formats, allow all updates
189
- return { compatible: true, isMajor };
190
- }
191
- /** Update existing npm dependencies to latest versions */
192
- export function updateNpmDeps(pkg, verbose = false, allowMajor = false) {
193
- const changes = [];
194
- const majorAvailable = [];
195
- let updated = false;
196
- for (const key of DEP_KEYS) {
197
- if (!pkg[key])
198
- continue;
199
- for (const [name, value] of Object.entries(pkg[key])) {
200
- // Skip file: references - those are handled separately
201
- if (isFileRef(value))
202
- continue;
203
- // Get current and latest versions
204
- const currentSpec = value;
205
- const latest = getLatestVersion(name);
206
- if (latest) {
207
- const { compatible, isMajor } = isCompatibleUpdate(currentSpec, latest);
208
- const newSpec = '^' + latest;
209
- if (currentSpec !== newSpec) {
210
- if (isMajor && !allowMajor) {
211
- // Major update available but not allowed
212
- majorAvailable.push({ name, current: currentSpec, latest: newSpec });
213
- if (verbose) {
214
- console.log(colors.yellow(` ${name}: ${currentSpec} (major update ${newSpec} available, use --update-major)`));
215
- }
216
- }
217
- else if (compatible || allowMajor) {
218
- // Safe update or major updates allowed
219
- pkg[key][name] = newSpec;
220
- changes.push(`${name}: ${currentSpec} → ${newSpec}`);
221
- if (isMajor) {
222
- console.log(colors.red(` ${name}: ${currentSpec} → ${newSpec} (MAJOR)`));
223
- }
224
- else {
225
- console.log(colors.yellow(` ${name}: ${currentSpec} → ${newSpec}`));
226
- }
227
- updated = true;
228
- }
229
- }
230
- else if (verbose) {
231
- console.log(colors.green(` ${name}: ${currentSpec} is up to date`));
232
- }
233
- }
234
- else if (verbose) {
235
- console.log(colors.italic(` ${name}: couldn't check npm registry`));
236
- }
237
- }
238
- }
239
- return { updated, changes, majorAvailable };
240
- }
241
- /** Check npm authentication status */
242
- export function checkNpmAuth() {
243
- try {
244
- // Must use shell:true on Windows to find npm.cmd in PATH
245
- // Must pass env: process.env to inherit NPM_TOKEN environment variable
246
- const result = spawnSafe('npm', ['whoami'], {
247
- encoding: 'utf-8',
248
- stdio: ['ignore', 'pipe', 'pipe'],
249
- env: process.env,
250
- shell: true
251
- });
252
- if (result.status === 0 && result.stdout && result.stdout.trim()) {
253
- return { authenticated: true, username: result.stdout.trim() };
254
- }
255
- // Parse error message to determine the issue
256
- const stderr = (result.stderr || '').toLowerCase();
257
- if (stderr.includes('code eneedauth') || stderr.includes('not logged in')) {
258
- return { authenticated: false, error: 'not logged in' };
259
- }
260
- else if (stderr.includes('token') && (stderr.includes('expired') || stderr.includes('revoked'))) {
261
- return { authenticated: false, error: 'token expired' };
262
- }
263
- else if (stderr) {
264
- return { authenticated: false, error: 'unknown auth error' };
265
- }
266
- else {
267
- // npm whoami failed but with no stderr - likely auth issue
268
- return { authenticated: false, error: 'not logged in' };
269
- }
270
- }
271
- catch (error) {
272
- return { authenticated: false, error: error.message };
273
- }
274
- }
275
- /** Get authentication setup instructions */
276
- function getAuthInstructions() {
277
- const npmrcPath = path.join(process.env.USERPROFILE || process.env.HOME || '~', '.npmrc');
278
- return `
279
- Authentication Options:
280
-
281
- 1. ${colors.yellow('Create a Granular Access Token')} (recommended):
282
- - Go to: https://www.npmjs.com/settings/[username]/tokens
283
- - Click "Generate New Token" → "Granular Access Token"
284
- - Set permissions: ${colors.green('Read and write')} for packages
285
- - Enable: ${colors.green('Bypass 2FA requirement')} (if available)
286
- - Copy the token and save it securely
287
-
288
- 2. ${colors.yellow('Set token via environment variable')}:
289
- - Set: ${colors.green('NPM_TOKEN=npm_xxx...')}
290
- - Or run: ${colors.green('$env:NPM_TOKEN="npm_xxx..."')} (PowerShell)
291
-
292
- 3. ${colors.yellow('Set token in .npmrc')}:
293
- - Edit: ${colors.green(npmrcPath)}
294
- - Add: ${colors.green('//registry.npmjs.org/:_authToken=npm_xxx...')}
295
-
296
- 4. ${colors.yellow('Use classic login')} (may require 2FA):
297
- - Run: ${colors.green('npm login')}
298
- - Follow interactive prompts
299
-
300
- ${colors.italic('Note: y:\\dev\\utils\\npmglobalize has set-npm-token.ps1 that may help fix token')}
301
- ${colors.italic(' problems, but no promises.')}
302
-
303
- Note: npm now requires either 2FA or a granular token with bypass enabled.
304
- `;
305
- }
306
- //# sourceMappingURL=npm.js.map
package/lib/types.d.ts DELETED
@@ -1,135 +0,0 @@
1
- /**
2
- * npmglobalize shared types, constants, and utility functions.
3
- * Leaf module — no internal dependencies.
4
- */
5
- import { type SpawnSyncOptions, type SpawnSyncReturns } from 'child_process';
6
- /** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
7
- * When shell is true, joins cmd+args into a single command string. */
8
- export declare function spawnSafe(cmd: string, args: string[], options?: SpawnSyncOptions): SpawnSyncReturns<string>;
9
- /** Semantic color functions — adapts to terminal light/dark theme */
10
- export declare const colors: import("@bobfrankston/themecolors").SemanticColors;
11
- /** Get npm command for current platform (npm.cmd on Windows, npm elsewhere) */
12
- export declare function getNpmCommand(): string;
13
- /** Options for the globalize operation */
14
- export interface GlobalizeOptions {
15
- /** Bump type: patch (default), minor, major */
16
- bump?: 'patch' | 'minor' | 'major';
17
- /** Just transform, don't publish */
18
- noPublish?: boolean;
19
- /** Restore from .dependencies */
20
- cleanup?: boolean;
21
- /** Global install after publish (from registry) */
22
- install?: boolean;
23
- /** Global install via symlink (npm install -g .) */
24
- link?: boolean;
25
- /** Also install in WSL */
26
- wsl?: boolean;
27
- /** Continue despite git errors */
28
- force?: boolean;
29
- /** Keep file: paths after publish (default true) */
30
- files?: boolean;
31
- /** Show what would happen */
32
- dryRun?: boolean;
33
- /** Suppress npm warnings (default true) */
34
- quiet?: boolean;
35
- /** Show verbose output */
36
- verbose?: boolean;
37
- /** Initialize git/npm if needed */
38
- init?: boolean;
39
- /** Git visibility: private (default) or public */
40
- gitVisibility?: 'private' | 'public';
41
- /** npm visibility: private (default) or public */
42
- npmVisibility?: 'private' | 'public';
43
- /** Custom commit message */
44
- message?: string;
45
- /** Check and update ignore files to conform to best practices */
46
- conform?: boolean;
47
- /** Keep ignore files as-is without checking */
48
- asis?: boolean;
49
- /** Check and update existing npm dependencies to latest versions */
50
- updateDeps?: boolean;
51
- /** Allow major version updates (breaking changes) */
52
- updateMajor?: boolean;
53
- /** Publish file: dependencies before converting them */
54
- publishDeps?: boolean;
55
- /** Auto-yes to dep-cascade prompts (add scope for private); does NOT auto-yes public prompts */
56
- publishDepsYes?: boolean;
57
- /** Cascade npmVisibility:"public" to all transitive workspace/file: deps without prompting. */
58
- publicDeps?: boolean;
59
- /** Skip the upfront dep-graph prescan */
60
- noPrescan?: boolean;
61
- /** Force republish dependencies even if version exists on npm */
62
- forcePublish?: boolean;
63
- /** Run npm audit and fix vulnerabilities */
64
- fix?: boolean;
65
- /** Automatically fix version/tag mismatches */
66
- fixTags?: boolean;
67
- /** Automatically rebase if local is behind remote */
68
- rebase?: boolean;
69
- /** Show package.json dependency changes */
70
- show?: boolean;
71
- /** Filter to specific workspace packages (by name or dir name) */
72
- workspaceFilter?: string[];
73
- /** Disable workspace mode even at a workspace root */
74
- noWorkspace?: boolean;
75
- /** Continue processing remaining packages if one fails (workspace mode) */
76
- continueOnError?: boolean;
77
- /** Update package.json scripts to use npmglobalize */
78
- package?: boolean;
79
- /** Don't persist CLI flags to .globalize.json5 */
80
- once?: boolean;
81
- /** Run importgen to update import maps before publishing */
82
- importgen?: boolean;
83
- /** Use filesystem paths for `file:` deps (default true). Set false to
84
- * mark a package as publishable/installable even when sibling checkouts
85
- * are absent. Currently declarative (recorded in config and displayed);
86
- * the actual conversion path is TODO. */
87
- usePaths?: boolean;
88
- /** Local install only — skip transform/publish, just npm install -g . */
89
- local?: boolean;
90
- /** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
91
- freeze?: boolean;
92
- /** Before `npm pack`, delete `node_modules/` inside each `file:` dep target.
93
- * Works around arborist crashes when siblings have nested node_modules. */
94
- cleanNestedModules?: boolean;
95
- /** Internal: signals this call is from workspace orchestrator */
96
- _fromWorkspace?: boolean;
97
- /** Internal: signals this call is from CLI (version already printed) */
98
- _fromCli?: boolean;
99
- /** Internal: tracks which options were explicitly set on the CLI */
100
- explicitKeys?: Set<string>;
101
- }
102
- /** Result from a single package in workspace mode */
103
- export interface WorkspacePackageResult {
104
- name: string;
105
- dir: string;
106
- success: boolean;
107
- version?: string;
108
- error?: string;
109
- }
110
- /** Aggregate result from workspace orchestration */
111
- export interface WorkspaceResult {
112
- success: boolean;
113
- packages: WorkspacePackageResult[];
114
- publishOrder: string[];
115
- }
116
- export declare const DEP_KEYS: string[];
117
- /** Git status checks */
118
- export interface GitStatus {
119
- isRepo: boolean;
120
- hasRemote: boolean;
121
- hasUncommitted: boolean;
122
- hasUnpushed: boolean;
123
- hasMergeConflict: boolean;
124
- isDetachedHead: boolean;
125
- currentBranch: string;
126
- remoteBranch: string;
127
- isBehindRemote: boolean;
128
- }
129
- /** Prompt user for confirmation */
130
- export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
131
- /** Prompt user for free text input */
132
- export declare function promptText(message: string, defaultValue?: string): Promise<string>;
133
- /** Prompt user for multiple choice */
134
- export declare function promptChoice(message: string, choices: string[]): Promise<string | null>;
135
- //# sourceMappingURL=types.d.ts.map
package/lib/types.js DELETED
@@ -1,80 +0,0 @@
1
- /**
2
- * npmglobalize shared types, constants, and utility functions.
3
- * Leaf module — no internal dependencies.
4
- */
5
- import { spawnSync } from 'child_process';
6
- import { themeColors } from '@bobfrankston/themecolors';
7
- import readline from 'readline';
8
- /** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
9
- * When shell is true, joins cmd+args into a single command string. */
10
- export function spawnSafe(cmd, args, options = {}) {
11
- const opts = { ...options, encoding: 'utf-8' };
12
- if (opts.shell && args.length > 0) {
13
- // Join into a single command string to avoid DEP0190
14
- const cmdStr = [cmd, ...args].map(a => /[\s"&|<>^]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).join(' ');
15
- return spawnSync(cmdStr, opts);
16
- }
17
- return spawnSync(cmd, args, opts);
18
- }
19
- /** Semantic color functions — adapts to terminal light/dark theme */
20
- export const colors = themeColors();
21
- /** Get npm command for current platform (npm.cmd on Windows, npm elsewhere) */
22
- export function getNpmCommand() {
23
- return process.platform === 'win32' ? 'npm.cmd' : 'npm';
24
- }
25
- export const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
26
- /** Prompt user for confirmation */
27
- export async function confirm(message, defaultYes = false) {
28
- const rl = readline.createInterface({
29
- input: process.stdin,
30
- output: process.stdout
31
- });
32
- const suffix = defaultYes ? '[Y/n]' : '[y/N]';
33
- return new Promise((resolve) => {
34
- rl.question(`${message} ${suffix} `, (answer) => {
35
- rl.close();
36
- const a = answer.trim().toLowerCase();
37
- if (a === '') {
38
- resolve(defaultYes);
39
- }
40
- else {
41
- resolve(a === 'y' || a === 'yes');
42
- }
43
- });
44
- });
45
- }
46
- /** Prompt user for free text input */
47
- export async function promptText(message, defaultValue) {
48
- const rl = readline.createInterface({
49
- input: process.stdin,
50
- output: process.stdout
51
- });
52
- const suffix = defaultValue ? ` [${defaultValue}]` : '';
53
- return new Promise((resolve) => {
54
- rl.question(`${message}${suffix} `, (answer) => {
55
- rl.close();
56
- const a = answer.trim();
57
- resolve(a || defaultValue || '');
58
- });
59
- });
60
- }
61
- /** Prompt user for multiple choice */
62
- export async function promptChoice(message, choices) {
63
- const rl = readline.createInterface({
64
- input: process.stdin,
65
- output: process.stdout
66
- });
67
- return new Promise((resolve) => {
68
- rl.question(`${message} `, (answer) => {
69
- rl.close();
70
- const a = answer.trim().toLowerCase();
71
- if (choices.includes(a)) {
72
- resolve(a);
73
- }
74
- else {
75
- resolve(null);
76
- }
77
- });
78
- });
79
- }
80
- //# sourceMappingURL=types.js.map