@bobfrankston/npmglobalize 1.0.143 → 1.0.144

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) {
@@ -4026,7 +4112,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4026
4112
  }
4027
4113
  }
4028
4114
  catch (e) {
4029
- // Ignore cleanup errors
4115
+ console.error(colors.yellow(` Warning: could not delete tarball ${tarballName} — remove manually`));
4030
4116
  }
4031
4117
  if (!publishResult.success) {
4032
4118
  // Check for specific error types before recording
@@ -4563,6 +4649,7 @@ export default {
4563
4649
  getGitStatus,
4564
4650
  validatePackageJson,
4565
4651
  confirm,
4566
- initGit
4652
+ initGit,
4653
+ installCleanupHandlers
4567
4654
  };
4568
4655
  //# 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.144",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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.6",
63
+ "@bobfrankston/importgen": "^0.1.32",
64
+ "@bobfrankston/themecolors": "^0.1.4",
65
+ "@bobfrankston/userconfig": "^1.0.6",
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
  }