@bobfrankston/npmglobalize 1.0.92 → 1.0.94

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.
@@ -28,7 +28,9 @@
28
28
  "Bash(echo:*)",
29
29
  "Bash(onboard --version:*)",
30
30
  "Bash(onboard --help:*)",
31
- "Bash(npmglobalize:*)"
31
+ "Bash(npmglobalize:*)",
32
+ "Bash(npm whoami:*)",
33
+ "Bash(where npmglobalize:*)"
32
34
  ]
33
35
  }
34
36
  }
package/lib.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * Current approach uses synchronous child_process calls for stability and simplicity.
10
10
  * Consider library-based approach if async operations or cross-platform issues arise.
11
11
  */
12
+ import { NpmCommonConfig } from '@bobfrankston/userconfig';
12
13
  /** Options for the globalize operation */
13
14
  export interface GlobalizeOptions {
14
15
  /** Bump type: patch (default), minor, major */
@@ -94,11 +95,8 @@ export interface WorkspaceResult {
94
95
  }
95
96
  /** Read and parse package.json from a directory */
96
97
  export declare function readPackageJson(dir: string): any;
97
- /** Global npm config from %USERPROFILE%\.userconfig\npm.json5 */
98
- export interface UserNpmConfig {
99
- scope?: string;
100
- npmVisibility?: 'private' | 'public';
101
- }
98
+ /** Global npm config from %USERPROFILE%\.userconfig\npm.json5 — via @bobfrankston/userconfig */
99
+ export type UserNpmConfig = NpmCommonConfig;
102
100
  /** Get the .userconfig directory path (%USERPROFILE%\.userconfig) */
103
101
  export declare function getUserConfigDir(): string;
104
102
  /** Read global npm config from %USERPROFILE%\.userconfig\npm.json5 */
package/lib.js CHANGED
@@ -12,6 +12,7 @@
12
12
  import fs from 'fs';
13
13
  import path from 'path';
14
14
  import { execSync, spawnSync } from 'child_process';
15
+ import { readConfig as readUserConfig, writeConfig as writeUserConfig, configDir } from '@bobfrankston/userconfig';
15
16
  /** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
16
17
  * When shell is true, joins cmd+args into a single command string. */
17
18
  function spawnSafe(cmd, args, options = {}) {
@@ -90,43 +91,17 @@ export function readPackageJson(dir) {
90
91
  }
91
92
  /** Get the .userconfig directory path (%USERPROFILE%\.userconfig) */
92
93
  export function getUserConfigDir() {
93
- const userProfile = process.env.USERPROFILE || process.env.HOME || '~';
94
- return path.join(userProfile, '.userconfig');
94
+ return configDir;
95
95
  }
96
96
  /** Read global npm config from %USERPROFILE%\.userconfig\npm.json5 */
97
97
  export function readUserNpmConfig() {
98
- const configPath = path.join(getUserConfigDir(), 'npm.json5');
99
- if (!fs.existsSync(configPath)) {
100
- return {};
101
- }
102
- try {
103
- const content = fs.readFileSync(configPath, 'utf-8');
104
- return JSON5.parse(content);
105
- }
106
- catch (error) {
107
- console.warn(`Warning: Could not parse ${configPath}: ${error.message}`);
108
- return {};
109
- }
98
+ return readUserConfig();
110
99
  }
111
100
  /** Write global npm config to %USERPROFILE%\.userconfig\npm.json5 */
112
101
  export function writeUserNpmConfig(config) {
113
- const configDir = getUserConfigDir();
114
- if (!fs.existsSync(configDir)) {
115
- fs.mkdirSync(configDir, { recursive: true });
116
- }
117
- const configPath = path.join(configDir, 'npm.json5');
118
- // Read existing to merge
119
- const existing = readUserNpmConfig();
102
+ const existing = readUserConfig();
120
103
  const merged = { ...existing, ...config };
121
- const lines = ['{'];
122
- const entries = Object.entries(merged);
123
- for (let i = 0; i < entries.length; i++) {
124
- const [key, value] = entries[i];
125
- const comma = i < entries.length - 1 ? ',' : '';
126
- lines.push(` ${key}: ${JSON.stringify(value)}${comma}`);
127
- }
128
- lines.push('}');
129
- fs.writeFileSync(configPath, lines.join('\n') + '\n', 'utf-8');
104
+ writeUserConfig(merged);
130
105
  }
131
106
  /** Read .globalize.json5 config file */
132
107
  export function readConfig(dir) {
@@ -340,13 +315,36 @@ export function checkNpmAccess(packageName) {
340
315
  return 'restricted'; // Default for scoped
341
316
  }
342
317
  else {
343
- // Unscoped packages are always public if they exist
318
+ // Unscoped packages are always public if they exist — but only if we own them
344
319
  const result = spawnSafe('npm', ['view', packageName, 'name'], {
345
320
  encoding: 'utf-8',
346
321
  stdio: 'pipe',
347
322
  shell: true
348
323
  });
349
324
  if (result.status === 0 && result.stdout && result.stdout.trim()) {
325
+ // Package exists on npm — check if current user is a maintainer
326
+ const maintResult = spawnSafe('npm', ['view', packageName, 'maintainers', '--json'], {
327
+ encoding: 'utf-8',
328
+ stdio: 'pipe',
329
+ shell: true
330
+ });
331
+ if (maintResult.status === 0 && maintResult.stdout) {
332
+ const auth = checkNpmAuth();
333
+ if (auth.username) {
334
+ try {
335
+ const maintainers = JSON.parse(maintResult.stdout.trim());
336
+ const names = Array.isArray(maintainers)
337
+ ? maintainers.map((m) => typeof m === 'string' ? m.replace(/ <.*/, '') : m.name)
338
+ : [];
339
+ if (!names.includes(auth.username)) {
340
+ return null; // Exists but we don't own it
341
+ }
342
+ }
343
+ catch {
344
+ // Parse failed — fall through to return public
345
+ }
346
+ }
347
+ }
350
348
  return 'public';
351
349
  }
352
350
  return null;
@@ -2046,8 +2044,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2046
2044
  console.log(colors.yellow('Continuing with --force despite errors...'));
2047
2045
  }
2048
2046
  // Check npm visibility and current publication status
2049
- const currentAccess = checkNpmAccess(pkg.name);
2050
- const isScoped = pkg.name.startsWith('@');
2047
+ let currentAccess = checkNpmAccess(pkg.name);
2048
+ let isScoped = pkg.name.startsWith('@');
2051
2049
  // Check if public intent was explicitly declared anywhere:
2052
2050
  // CLI (--npm public), config file (.globalize.json5), or package.json publishConfig
2053
2051
  const npmVisibilityExplicitlySet = 'npmVisibility' in options
@@ -2104,6 +2102,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2104
2102
  const normalizedScope = scope.startsWith('@') ? scope : `@${scope}`;
2105
2103
  const newName = `${normalizedScope}/${pkg.name}`;
2106
2104
  pkg.name = newName;
2105
+ isScoped = true;
2106
+ currentAccess = checkNpmAccess(newName);
2107
2107
  writePackageJson(cwd, pkg);
2108
2108
  console.log(colors.green(`✓ Renamed package to ${newName}`));
2109
2109
  // Save scope to .userconfig if not already there
@@ -2168,13 +2168,47 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2168
2168
  console.log(colors.dim(` Use --npm public to make it public`));
2169
2169
  }
2170
2170
  else {
2171
- console.log(colors.yellow(`WARNING: Package '${pkg.name}' is unscoped and will be PUBLIC.`));
2172
- console.log(colors.yellow(` Unscoped packages cannot be private on npm.`));
2171
+ // Unscoped new package prompt to add scope (same as explicit-private path)
2173
2172
  const ucfg = readUserNpmConfig();
2174
2173
  const auth2 = checkNpmAuth();
2175
- const suggestedScope = ucfg.scope || (auth2.username ? `@${auth2.username}` : '@scope');
2176
- console.log(colors.yellow(` Consider using a scoped name: ${suggestedScope}/${pkg.name}`));
2177
- console.log(colors.yellow(` Or use --npm public to confirm public publishing`));
2174
+ const defaultScope = ucfg.scope
2175
+ || (auth2.username ? `@${auth2.username}` : undefined);
2176
+ const scopedExample = defaultScope ? `${defaultScope}/${pkg.name}` : `@scope/${pkg.name}`;
2177
+ console.log(colors.yellow(`WARNING: Package '${pkg.name}' is unscoped and will be PUBLIC.`));
2178
+ console.log(colors.yellow(` Unscoped packages cannot be private on npm.`));
2179
+ if (dryRun) {
2180
+ console.log(` [dry-run] Would prompt to add scope (e.g., ${scopedExample})`);
2181
+ }
2182
+ else {
2183
+ const addScope = await confirm(`Add scope to make it private (e.g., ${scopedExample})?`, true);
2184
+ if (addScope) {
2185
+ const scope = await promptText('Scope:', defaultScope);
2186
+ if (scope) {
2187
+ const normalizedScope = scope.startsWith('@') ? scope : `@${scope}`;
2188
+ const newName = `${normalizedScope}/${pkg.name}`;
2189
+ pkg.name = newName;
2190
+ isScoped = true;
2191
+ currentAccess = checkNpmAccess(newName);
2192
+ writePackageJson(cwd, pkg);
2193
+ console.log(colors.green(`✓ Renamed package to ${newName}`));
2194
+ if (!ucfg.scope) {
2195
+ const saveScope = await confirm(`Save scope "${normalizedScope}" to global config (${getUserConfigDir()}\\npm.json5)?`, true);
2196
+ if (saveScope) {
2197
+ writeUserNpmConfig({ scope: normalizedScope });
2198
+ console.log(colors.green(`✓ Saved default scope to ${getUserConfigDir()}\\npm.json5`));
2199
+ }
2200
+ }
2201
+ }
2202
+ else {
2203
+ console.log(colors.yellow(` No scope provided. Continuing as public.`));
2204
+ console.log(colors.yellow(` Use --npm public to suppress this prompt.`));
2205
+ }
2206
+ }
2207
+ else {
2208
+ console.log(colors.yellow(` Continuing as public.`));
2209
+ console.log(colors.yellow(` Use --npm public to suppress this prompt.`));
2210
+ }
2211
+ }
2178
2212
  }
2179
2213
  }
2180
2214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.92",
3
+ "version": "1.0.94",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "author": "Bob Frankston",
23
23
  "license": "MIT",
24
24
  "devDependencies": {
25
- "@types/node": "^25.2.1",
25
+ "@types/node": "^25.3.0",
26
26
  "@types/npm-package-arg": "^6.1.4",
27
27
  "@types/pacote": "^11.1.8"
28
28
  },
@@ -31,6 +31,7 @@
31
31
  "url": "https://github.com/BobFrankston/npmglobalize.git"
32
32
  },
33
33
  "dependencies": {
34
+ "@bobfrankston/userconfig": "^1.0.3",
34
35
  "@npmcli/package-json": "^7.0.4",
35
36
  "json5": "^2.2.3",
36
37
  "libnpmversion": "^8.0.3",
@@ -40,5 +41,14 @@
40
41
  },
41
42
  "publishConfig": {
42
43
  "access": "public"
44
+ },
45
+ ".dependencies": {
46
+ "@bobfrankston/userconfig": "file:../userconfig",
47
+ "@npmcli/package-json": "^7.0.4",
48
+ "json5": "^2.2.3",
49
+ "libnpmversion": "^8.0.3",
50
+ "npm-registry-fetch": "^19.1.1",
51
+ "pacote": "^21.0.4",
52
+ "simple-git": "^3.30.0"
43
53
  }
44
54
  }