@bobfrankston/npmglobalize 1.0.143 → 1.0.145

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 CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * npmglobalize CLI - Transform file: dependencies to npm versions for publishing
4
4
  */
5
- import { globalize, globalizeWorkspace, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, confirm, getBuildIssues, clearBuildIssues, recordBuildIssue, extractFirstTscError } from './lib.js';
5
+ import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, confirm, getBuildIssues, clearBuildIssues, recordBuildIssue, extractFirstTscError } from './lib.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { styleText } from 'util';
@@ -343,6 +343,7 @@ function printBuildSummary() {
343
343
  console.log('');
344
344
  }
345
345
  export async function main() {
346
+ installCleanupHandlers();
346
347
  // Show version at the very start
347
348
  const ownPkgPath = path.join(__dirname, 'package.json');
348
349
  const ownPkg = JSON.parse(fs.readFileSync(ownPkgPath, 'utf-8'));
@@ -15,7 +15,8 @@
15
15
  "*.pfx",
16
16
  "token",
17
17
  "tokens",
18
- "*.token"
18
+ "*.token",
19
+ "*.tgz"
19
20
  ],
20
21
  // Prompted or auto-added with --conform
21
22
  recommended: [
package/lib.d.ts CHANGED
@@ -132,6 +132,8 @@ export declare function readConfig(dir: string): Partial<GlobalizeOptions>;
132
132
  export declare function writeConfig(dir: string, config: Partial<GlobalizeOptions>, explicitKeys?: Set<string>): void;
133
133
  /** Write package.json to a directory */
134
134
  export declare function writePackageJson(dir: string, pkg: any): void;
135
+ /** Install signal/exit handlers that restore `.dependencies` backups on abnormal exit. */
136
+ export declare function installCleanupHandlers(): void;
135
137
  /** Resolve a file: path to absolute path */
136
138
  export declare function resolveFilePath(fileRef: string, baseDir: string): string;
137
139
  /** Check if a dependency value is a file: reference */
@@ -188,7 +190,8 @@ export declare function transformDeps(pkg: any, baseDir: string, verbose?: boole
188
190
  path: string;
189
191
  }>;
190
192
  };
191
- /** Restore file: dependencies from .dependencies */
193
+ /** Restore file: dependencies from .dependencies using a three-way merge that
194
+ * preserves any external modifications made since the last transform. */
192
195
  export declare function restoreDeps(pkg: any, verbose?: boolean): boolean;
193
196
  /** Check if .dependencies exist (already transformed) */
194
197
  export declare function hasBackup(pkg: any): boolean;
@@ -268,6 +271,7 @@ declare const _default: {
268
271
  validatePackageJson: typeof validatePackageJson;
269
272
  confirm: typeof confirm;
270
273
  initGit: typeof initGit;
274
+ installCleanupHandlers: typeof installCleanupHandlers;
271
275
  };
272
276
  export default _default;
273
277
  //# sourceMappingURL=lib.d.ts.map
package/lib.js CHANGED
@@ -313,10 +313,111 @@ export function writeConfig(dir, config, explicitKeys) {
313
313
  lines.push('}');
314
314
  fs.writeFileSync(configPath, lines.join('\n') + '\n');
315
315
  }
316
+ /** Package.json paths with a live `.dependencies` backup on disk.
317
+ * Tracked so the exit handler can restore them if the process dies mid-run. */
318
+ const dirtyPackageJsons = new Set();
316
319
  /** Write package.json to a directory */
317
320
  export function writePackageJson(dir, pkg) {
318
321
  const pkgPath = path.join(dir, 'package.json');
319
322
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
323
+ if (hasBackup(pkg)) {
324
+ dirtyPackageJsons.add(pkgPath);
325
+ }
326
+ else {
327
+ dirtyPackageJsons.delete(pkgPath);
328
+ }
329
+ }
330
+ /** Three-way merge that restores `.dependencies` backup into `dependencies` while
331
+ * preserving external modifications made since the last transform.
332
+ *
333
+ * Uses `.transformedSnapshot` (written by transformDeps) to distinguish
334
+ * "untouched transform output" from "externally modified":
335
+ * - current[name] === snapshot[name] → untouched, restore to backup value
336
+ * - current[name] !== snapshot[name] → modified externally, keep current
337
+ * - name in backup but missing from current → removed externally, leave removed
338
+ * - name in current but not in backup → added externally, keep
339
+ *
340
+ * If no snapshot exists (legacy backup from before this mechanism), falls back to
341
+ * blind restore plus new-dep merge, matching the old behavior. */
342
+ function mergeRestore(pkg, verbose = false) {
343
+ const snapshot = pkg['.transformedSnapshot'];
344
+ let restored = false;
345
+ for (const key of DEP_KEYS) {
346
+ const dotKey = '.' + key;
347
+ if (!pkg[dotKey])
348
+ continue;
349
+ const backup = pkg[dotKey];
350
+ const current = pkg[key] || {};
351
+ const snap = snapshot?.[key];
352
+ const merged = { ...current };
353
+ for (const [name, backupValue] of Object.entries(backup)) {
354
+ if (!(name in current))
355
+ continue; // removed externally — leave removed
356
+ if (snap && name in snap) {
357
+ if (current[name] === snap[name]) {
358
+ merged[name] = backupValue; // untouched — restore
359
+ }
360
+ else if (verbose) {
361
+ console.log(colors.yellow(` Preserving externally-modified ${key}.${name}: ${current[name]} (transform wrote ${snap[name]})`));
362
+ }
363
+ }
364
+ else {
365
+ merged[name] = backupValue; // legacy: no snapshot, blind restore
366
+ }
367
+ }
368
+ pkg[key] = merged;
369
+ delete pkg[dotKey];
370
+ restored = true;
371
+ }
372
+ if (restored && snapshot) {
373
+ delete pkg['.transformedSnapshot'];
374
+ }
375
+ return restored;
376
+ }
377
+ /** Synchronous emergency restore — called from signal/exit handlers.
378
+ * Reads each tracked package.json, runs the three-way merge, writes it back. */
379
+ function emergencyRestoreDeps() {
380
+ if (dirtyPackageJsons.size === 0)
381
+ return;
382
+ console.error(colors.yellow(`\nRestoring file: dependencies in ${dirtyPackageJsons.size} package.json file(s)...`));
383
+ for (const pkgPath of dirtyPackageJsons) {
384
+ try {
385
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
386
+ if (mergeRestore(pkg)) {
387
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
388
+ console.error(colors.green(` ✓ ${pkgPath}`));
389
+ }
390
+ }
391
+ catch (err) {
392
+ console.error(colors.red(` ✗ ${pkgPath}: ${err.message}`));
393
+ }
394
+ }
395
+ dirtyPackageJsons.clear();
396
+ }
397
+ let cleanupHandlersInstalled = false;
398
+ /** Install signal/exit handlers that restore `.dependencies` backups on abnormal exit. */
399
+ export function installCleanupHandlers() {
400
+ if (cleanupHandlersInstalled)
401
+ return;
402
+ cleanupHandlersInstalled = true;
403
+ process.on('exit', emergencyRestoreDeps);
404
+ const signalHandler = (signal) => {
405
+ emergencyRestoreDeps();
406
+ process.exit(128 + (signal === 'SIGINT' ? 2 : signal === 'SIGTERM' ? 15 : 1));
407
+ };
408
+ process.on('SIGINT', signalHandler);
409
+ process.on('SIGTERM', signalHandler);
410
+ process.on('SIGHUP', signalHandler);
411
+ process.on('uncaughtException', (err) => {
412
+ console.error(colors.red('\nUncaught exception:'), err);
413
+ emergencyRestoreDeps();
414
+ process.exit(1);
415
+ });
416
+ process.on('unhandledRejection', (reason) => {
417
+ console.error(colors.red('\nUnhandled rejection:'), reason);
418
+ emergencyRestoreDeps();
419
+ process.exit(1);
420
+ });
320
421
  }
321
422
  /** Resolve a file: path to absolute path */
322
423
  export function resolveFilePath(fileRef, baseDir) {
@@ -766,36 +867,21 @@ function hasLocalChanges(packageName, version, targetPath, verbose) {
766
867
  export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
767
868
  let transformed = false;
768
869
  const unpublished = [];
870
+ // If re-running on a previously-transformed pkg, merge-restore first.
871
+ // This preserves any external modifications (AI tools, npm install, manual edits)
872
+ // made since the last transform, then proceeds to re-transform from a clean slate.
873
+ if (hasBackup(pkg)) {
874
+ mergeRestore(pkg, verbose);
875
+ }
769
876
  for (const key of DEP_KEYS) {
770
877
  if (!pkg[key])
771
878
  continue;
772
879
  const dotKey = '.' + key;
773
- // If .dependencies already exists, restore it and merge any new dependencies
774
- if (pkg[dotKey]) {
775
- // Save any new dependencies that aren't in .dependencies
776
- const currentDeps = { ...pkg[key] };
777
- // Restore .dependencies back to dependencies
778
- pkg[key] = { ...pkg[dotKey] };
779
- // Merge in any NEW dependencies that were added since transformation
780
- for (const [name, value] of Object.entries(currentDeps)) {
781
- if (!(name in pkg[dotKey])) {
782
- // This is a new dependency, add it
783
- pkg[key][name] = value;
784
- // Also add to .dependencies backup
785
- pkg[dotKey][name] = value;
786
- if (verbose) {
787
- console.log(` Merged new dependency: ${name}`);
788
- }
789
- }
790
- }
791
- }
792
880
  const hasFileRefs = Object.values(pkg[key]).some(v => isFileRef(v));
793
881
  if (!hasFileRefs)
794
882
  continue;
795
- // Backup original (or update existing backup with merged deps)
796
- if (!pkg[dotKey]) {
797
- pkg[dotKey] = { ...pkg[key] };
798
- }
883
+ // Backup original
884
+ pkg[dotKey] = { ...pkg[key] };
799
885
  // Transform file: refs to npm versions
800
886
  for (const [name, value] of Object.entries(pkg[key])) {
801
887
  if (isFileRef(value)) {
@@ -846,6 +932,17 @@ export function transformDeps(pkg, baseDir, verbose = false, forcePublish = fals
846
932
  }
847
933
  }
848
934
  }
935
+ // Record snapshot of transform output so restore can distinguish untouched
936
+ // entries from those modified externally since the transform ran.
937
+ if (transformed) {
938
+ const snapshot = {};
939
+ for (const key of DEP_KEYS) {
940
+ if (pkg['.' + key]) {
941
+ snapshot[key] = { ...pkg[key] };
942
+ }
943
+ }
944
+ pkg['.transformedSnapshot'] = snapshot;
945
+ }
849
946
  return { transformed, unpublished };
850
947
  }
851
948
  /** Build and print a dependency tree of file: references.
@@ -889,21 +986,10 @@ function printDepTree(baseDir, indent = 0, visited = new Set()) {
889
986
  }
890
987
  }
891
988
  }
892
- /** Restore file: dependencies from .dependencies */
989
+ /** Restore file: dependencies from .dependencies using a three-way merge that
990
+ * preserves any external modifications made since the last transform. */
893
991
  export function restoreDeps(pkg, verbose = false) {
894
- let restored = false;
895
- for (const key of DEP_KEYS) {
896
- const dotKey = '.' + key;
897
- if (pkg[dotKey]) {
898
- pkg[key] = pkg[dotKey];
899
- delete pkg[dotKey];
900
- restored = true;
901
- if (verbose) {
902
- console.log(` Restored ${key} from ${dotKey}`);
903
- }
904
- }
905
- }
906
- return restored;
992
+ return mergeRestore(pkg, verbose);
907
993
  }
908
994
  /** Check if .dependencies exist (already transformed) */
909
995
  export function hasBackup(pkg) {
@@ -3475,8 +3561,22 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3475
3561
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
3476
3562
  }
3477
3563
  else {
3478
- console.error(colors.red(`✗ Global install failed`));
3479
- console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
3564
+ // Registry install failed — fall back to local install
3565
+ const auth = checkNpmAuth();
3566
+ if (!auth.authenticated) {
3567
+ console.log(colors.yellow(` Registry install failed (${auth.error || 'auth issue'}) — falling back to local install...`));
3568
+ }
3569
+ else {
3570
+ console.log(colors.yellow(` Registry install failed — falling back to local install...`));
3571
+ }
3572
+ const localFallback = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3573
+ if (localFallback.success) {
3574
+ console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
3575
+ }
3576
+ else {
3577
+ console.error(colors.red(`✗ Global install failed`));
3578
+ console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
3579
+ }
3480
3580
  }
3481
3581
  }
3482
3582
  else {
@@ -4026,7 +4126,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4026
4126
  }
4027
4127
  }
4028
4128
  catch (e) {
4029
- // Ignore cleanup errors
4129
+ console.error(colors.yellow(` Warning: could not delete tarball ${tarballName} — remove manually`));
4030
4130
  }
4031
4131
  if (!publishResult.success) {
4032
4132
  // Check for specific error types before recording
@@ -4180,9 +4280,25 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4180
4280
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4181
4281
  }
4182
4282
  else {
4183
- console.error(colors.red(`✗ Global install failed`));
4184
- console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
4185
- recordBuildIssue(pkgName, 'warning', 'Global install failed');
4283
+ // Registry install failed — may be auth/token issue for private packages.
4284
+ // Fall back to local install which doesn't need registry auth.
4285
+ const auth = checkNpmAuth();
4286
+ if (!auth.authenticated) {
4287
+ console.log(colors.yellow(` Registry install failed (${auth.error || 'auth issue'}) — falling back to local install...`));
4288
+ }
4289
+ else {
4290
+ console.log(colors.yellow(` Registry install failed — falling back to local install...`));
4291
+ }
4292
+ const localFallback = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4293
+ if (localFallback.success) {
4294
+ globalInstallOk = true;
4295
+ console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
4296
+ }
4297
+ else {
4298
+ console.error(colors.red(`✗ Global install failed`));
4299
+ console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
4300
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4301
+ }
4186
4302
  }
4187
4303
  }
4188
4304
  else {
@@ -4563,6 +4679,7 @@ export default {
4563
4679
  getGitStatus,
4564
4680
  validatePackageJson,
4565
4681
  confirm,
4566
- initGit
4682
+ initGit,
4683
+ installCleanupHandlers
4567
4684
  };
4568
4685
  //# sourceMappingURL=lib.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.143",
3
+ "version": "1.0.145",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -31,10 +31,10 @@
31
31
  "url": "https://github.com/BobFrankston/npmglobalize.git"
32
32
  },
33
33
  "dependencies": {
34
- "@bobfrankston/freezepak": "^0.1.6",
35
- "@bobfrankston/importgen": "^0.1.32",
36
- "@bobfrankston/themecolors": "^0.1.4",
37
- "@bobfrankston/userconfig": "^1.0.6",
34
+ "@bobfrankston/freezepak": "^0.1.7",
35
+ "@bobfrankston/importgen": "^0.1.33",
36
+ "@bobfrankston/themecolors": "^0.1.5",
37
+ "@bobfrankston/userconfig": "^1.0.7",
38
38
  "@npmcli/package-json": "^7.0.4",
39
39
  "json5": "^2.2.3",
40
40
  "libnpmversion": "^8.0.3",
@@ -56,5 +56,19 @@
56
56
  "npm-registry-fetch": "^19.1.1",
57
57
  "pacote": "^21.0.4",
58
58
  "simple-git": "^3.30.0"
59
+ },
60
+ ".transformedSnapshot": {
61
+ "dependencies": {
62
+ "@bobfrankston/freezepak": "^0.1.7",
63
+ "@bobfrankston/importgen": "^0.1.33",
64
+ "@bobfrankston/themecolors": "^0.1.5",
65
+ "@bobfrankston/userconfig": "^1.0.7",
66
+ "@npmcli/package-json": "^7.0.4",
67
+ "json5": "^2.2.3",
68
+ "libnpmversion": "^8.0.3",
69
+ "npm-registry-fetch": "^19.1.1",
70
+ "pacote": "^21.0.4",
71
+ "simple-git": "^3.30.0"
72
+ }
59
73
  }
60
74
  }