@bobfrankston/npmglobalize 1.0.209 → 1.0.210

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.
Files changed (4) hide show
  1. package/README.md +904 -910
  2. package/lib.d.ts +3 -0
  3. package/lib.js +153 -59
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,910 +1,904 @@
1
- # npmglobalize
2
-
3
- Transform `file:` dependencies to npm versions for publishing.
4
-
5
- ## Overview
6
-
7
- `npmglobalize` automates the workflow of publishing npm packages that use local `file:` references during development. It converts those references to proper npm versions, publishes everything in dependency order, and optionally restores the local references afterward.
8
-
9
- ## Installation
10
-
11
- ```bash
12
- npm install -g @bobfrankston/npmglobalize
13
- ```
14
-
15
- ## Basic Usage
16
-
17
- ```bash
18
- cd your-package
19
- npmglobalize # Transform + publish (patch version)
20
- npmglobalize --minor # Bump minor version
21
- npmglobalize --major # Bump major version
22
-
23
- # Or run from anywhere with a path
24
- npmglobalize y:\path\to\your-package
25
- ```
26
-
27
- ## Key Features
28
-
29
- ### 🔗 Automatic Dependency Publishing (Default)
30
-
31
- By default, `npmglobalize` ensures all `file:` dependencies are published **before** converting them:
32
-
33
- ```bash
34
- npmglobalize # Auto-publishes file: deps in correct order
35
- ```
36
-
37
- If you have:
38
- ```
39
- lxtest
40
- ├── file:../lxlan-node
41
- │ └── file:../lxland
42
- └── file:../lxland
43
- ```
44
-
45
- It automatically:
46
- 1. Publishes `lxland` (root dependency)
47
- 2. Publishes `lxlan-node` (depends on lxlan)
48
- 3. Converts and publishes `lxtest`
49
-
50
- **Settings Propagation (Default Behavior):**
51
- When publishing `file:` dependencies, these settings are **automatically inherited**:
52
- - `--update-deps` / `--update-major` (update dependencies)
53
- - `--fix` (run npm audit fix)
54
- - `--conform` (fix .gitignore/.npmignore/.gitattributes and git config)
55
- - `--verbose` / `--quiet`
56
- - `--force` / `--files`
57
-
58
- **⚠️ Visibility Settings (Smart Inheritance):**
59
- - `--npmVisibility` is **only inherited by NEW repositories** (never published to npm before)
60
- - **Existing repositories** keep their current npm visibility (public/private) unchanged
61
- - `--gitVisibility` is inherited by all dependencies
62
-
63
- This ensures you can safely set `--npmVisibility private` as a default for new packages without accidentally changing the visibility of your existing published packages.
64
-
65
- **Why This Matters:**
66
- Once a package is published to npm (public or private), changing its visibility later requires careful consideration. This smart inheritance protects your existing packages while making new ones default to safe settings.
67
-
68
- Example - safely publish with new packages private by default:
69
- ```bash
70
- npmglobalize --npmVisibility private
71
- # ✓ Existing npm packages: keep their current visibility
72
- # ✓ New packages (never published): default to private (safe!)
73
- # ✓ Regular npm dependencies (express, etc.): unchanged
74
- ```
75
-
76
- Example with configuration file (recommended):
77
- ```bash
78
- # In main package: create .globalize.json5 with "npmVisibility": "private"
79
- npmglobalize
80
- # ✓ Existing repos: publish with their current npm visibility
81
- # ✓ Brand new repos: inherit private setting
82
- ```
83
-
84
- **Skip auto-publishing** (use with caution):
85
- ```bash
86
- npmglobalize -npd # --no-publish-deps
87
- ```
88
-
89
- ### 🔍 Prescan (Default)
90
-
91
- Before any transform/publish, `npmglobalize` walks the full `file:` dep graph and reports all problems up front (unresolvable paths, missing `package.json`, unscoped packages with private intent, etc.). This lets you fix the whole punch list at once rather than being interrupted mid-cascade.
92
-
93
- Errors abort (unless `--force`); warnings prompt to continue.
94
-
95
- Skip the prescan with `-no-prescan` / `-nps`.
96
-
97
- #### Workspace skip prescan
98
-
99
- In workspace mode, before processing begins, each package is also checked to decide whether it actually needs work. A package is **skipped** (no rebuild, no version bump, no publish) only if **all** of these are true:
100
-
101
- 1. **Working tree clean** — `git status --porcelain .` reports no uncommitted changes for that package's directory.
102
- 2. **Version already on npm** — the version in its `package.json` is published to the registry, and no non-bookkeeping commit (anything outside "Pre-release commit" / "Restore file: dependencies" / "Pre-version cleanup" / "Untrack node_modules") is newer than that version's publish timestamp.
103
- 3. **Build is fresh** — every `.ts` source file (excluding `.d.ts`) has a sibling `.js` whose mtime is ≥ the `.ts` mtime. A missing `.js` or an older `.js` counts as stale.
104
- 4. **No sibling workspace `file:` dep flagged for work** — if another workspace package depends on this one via `file:`, and that dep is being updated in this run, this one is also processed (propagates through the graph in topological order).
105
-
106
- If any condition fails, the package is processed. The prescan prints one line per package with `⟳` for "will process" (and the reason) or `✓` for "skip". Example:
107
-
108
- ```
109
- ⟳ mlproc — uncommitted changes
110
- ⟳ pzip — stale build (index.ts newer than index.js)
111
- ⟳ stage — dep pzip is being updated
112
- ✓ puller — skip (clean, published, build fresh)
113
- ```
114
-
115
- The summary row for a skipped package shows `– name v1.0.0 (skipped — already up to date)`.
116
-
117
- Use `--force` or `--force-publish` to bypass the skip prescan and process every package.
118
-
119
- **Force republish** all file: dependencies even if versions exist:
120
- ```bash
121
- npmglobalize --force-publish
122
- ```
123
-
124
- ### 📦 Dependency Updates
125
-
126
- **Safe updates** (minor/patch only, respects semver):
127
- ```bash
128
- npmglobalize --update-deps
129
- ```
130
- - `express ^4.18.0` → `^4.21.0` ✓
131
- - `lodash ^4.17.0` → `^4.17.21` ✓
132
- - Won't update to `express ^5.0.0` (breaking change)
133
-
134
- **Include major updates** (breaking changes):
135
- ```bash
136
- npmglobalize --update-major
137
- ```
138
- - Updates to latest including major versions
139
- - Shows "(MAJOR)" indicator for breaking changes
140
-
141
- **Note:** The `--update-deps` flag propagates to all file: dependencies, so one command updates your entire dependency tree.
142
-
143
- ### 🔒 Security Auditing
144
-
145
- **Check vulnerabilities**:
146
- ```bash
147
- npmglobalize # Shows audit at end
148
- ```
149
-
150
- **Auto-fix vulnerabilities**:
151
- ```bash
152
- npmglobalize --fix # Runs npm audit fix
153
- ```
154
-
155
- **Disable audit**:
156
- ```bash
157
- npmglobalize --no-fix
158
- ```
159
-
160
- ### 🧩 Install Scripts (npm `allowScripts`)
161
-
162
- npm 11.17+ skips install-time lifecycle scripts (`preinstall`/`install`/
163
- `postinstall`) for packages that aren't on an allowlist. A **global** install
164
- has no project `package.json` to record approvals in, so without help a
165
- postinstall you wrote yourself silently never runs while the install still
166
- exits 0 — e.g. `@bobfrankston/msger`'s postinstall copies its native binary
167
- into the per-user bin dir its launcher reads from, and skipping it leaves a
168
- stale exe behind a green checkmark.
169
-
170
- npmglobalize therefore allowlists **your own packages** on the global install
171
- it performs — it just built and published them from your source, so they're
172
- trusted — and leaves third-party packages gated:
173
-
174
- ```
175
- > npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,@bobfrankston/msgcommon
176
- ```
177
-
178
- Anything npm skips is reported rather than buried in the captured output:
179
-
180
- ```
181
- · Skipped koffi@2.16.3 install script — prebuilt-binary fetcher, the package ships binaries; normally harmless.
182
- ⚠ npm skipped install scripts for third-party packages: sharp@0.33.0
183
- sharp@0.33.0 (install: node install/check)
184
- These may be broken at runtime — no prebuilt fallback recognized.
185
- To run them anyway: npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,koffi,sharp
186
- ```
187
-
188
- Two details worth knowing:
189
-
190
- - **Prebuilt fetchers are called out separately.** npm names the skipped
191
- script, so a runner like `cnoke`, `prebuild-install`, `node-gyp-build`,
192
- `prebuildify` or `napi-postinstall` is recognized as one that only compiles
193
- when no prebuild matches. Those packages ship working binaries — koffi
194
- carries `build/koffi/<platform>/koffi.node` for every platform it supports —
195
- so the skip gets an informational note, not a warning.
196
- - **The suggested re-run command includes your own packages.** npm takes the
197
- allowlist from the first source that has one rather than merging sources, so
198
- a command naming only the third-party package would silently re-gate yours.
199
- Always run the full list.
200
-
201
- An own-scope package showing up as skipped is reported louder — that means the
202
- allowlist missed it, usually a transitive dep not present in the local
203
- `node_modules` tree.
204
-
205
- ### 🔑 OAuth Credentials Handling
206
-
207
- `npmglobalize` automatically detects `credentials.json` files and handles them based on OAuth app type:
208
-
209
- - **Desktop/installed apps** (`"installed"` key in JSON): The `client_secret` is just a public app registration ID, not a real secret. Google's own docs state: *"the client_secret is obviously not treated as a secret."* These files are kept in the repo — `!credentials.json` is added to `.gitignore`/`.npmignore` to override any broader ignore patterns.
210
-
211
- - **Web apps** (`"web"` key in JSON): The `client_secret` is a real secret. These files are automatically added to `.gitignore`/`.npmignore` to prevent accidental exposure.
212
-
213
- This distinction also drives the **push protection auto-bypass**: when GitHub blocks a push because it detects an OAuth client secret, `npmglobalize` checks the credential file type. For installed apps, it auto-bypasses (marking as `false_positive`) since the credential is public by design.
214
-
215
- ### 🔄 File Reference Management
216
-
217
- **Default behavior** (restore file: references after publish):
218
- ```bash
219
- npmglobalize # Converts file: → npm, publishes, then restores file:
220
- ```
221
-
222
- **Keep npm references** permanently:
223
- ```bash
224
- npmglobalize --nofiles # Don't restore file: references
225
- ```
226
-
227
- **Just transform** without publishing:
228
- ```bash
229
- npmglobalize -np # --nopublish (formerly --apply)
230
- ```
231
-
232
- **Restore** from backup:
233
- ```bash
234
- npmglobalize --cleanup # Restore original file: references
235
- ```
236
-
237
- ### 📝 Release Notes via `.commitmsg`
238
-
239
- For multi-line or reusable release notes, write them to a `.commitmsg` file in the package root instead of passing them on the command line:
240
-
241
- ```bash
242
- cat > .commitmsg <<'EOF'
243
- Added foo feature
244
- Fixed bar regression
245
- EOF
246
- npmglobalize
247
- ```
248
-
249
- Behavior:
250
- - If `-m` / `-message` is **not** given and `.commitmsg` exists, its contents are used as the commit message (and force a release even if the working tree is otherwise clean).
251
- - After a successful `npm publish`, npmglobalize:
252
- 1. Appends the contents to `npmchanges.md` under a `## v<version> — <YYYY-MM-DD>` header (creating the file if needed)
253
- 2. Deletes `.commitmsg`
254
- 3. Commits both changes as `Log v<version> to npmchanges.md` and pushes
255
-
256
- Notes:
257
- - **Git/GitHub only.** npm publish does not consume git commit messages; `npmchanges.md` lives in the git repo and on GitHub but is excluded from the published npm tarball (the standard `*.md` rule keeps only `README.md`).
258
- - If both `-m` and `.commitmsg` are present, `-m` wins and `.commitmsg` is left alone (not consumed).
259
- - If publish fails, `.commitmsg` is preserved for the next attempt.
260
- - `.commitmsg` is auto-added to `.npmignore` (security pattern) so it never leaks into the tarball.
261
-
262
- ### 🔧 Git Integration & Error Recovery
263
-
264
- **Automatic tag conflict resolution**:
265
- ```bash
266
- npmglobalize --fix-tags # Auto-fix version/tag mismatches
267
- ```
268
-
269
- When a previous publish fails, git tags may conflict with package.json version. The `--fix-tags` option (or automatic detection) will clean up these conflicts.
270
-
271
- **Automatic rebase** when local is behind remote:
272
- ```bash
273
- npmglobalize --rebase # Auto-rebase if behind remote
274
- ```
275
-
276
- For single-developer projects, this safely pulls remote changes before publishing.
277
-
278
- **Failed publish recovery**: If a previous run bumped the version locally but the publish or push failed (e.g., network error, push protection), `npmglobalize` detects this automatically on the next run. It checks whether the current version actually exists on npm — if not, it republishes without re-bumping the version. Unpushed commits from a failed push are also pushed automatically.
279
-
280
- **GitHub Push Protection (GH013)**: When GitHub's secret scanning blocks a push, `npmglobalize` detects the error and checks whether the flagged secrets are actually safe. For Google OAuth desktop/installed app credentials (where the "client_secret" is just a public app identifier, not a real secret), it automatically bypasses push protection via the GitHub API (`gh` CLI required) and retries the push. For other secret types, it displays the unblock URLs with guidance.
281
-
282
- **Note:** This tool is designed for single-developer, single-branch workflows where automatic rebase and tag cleanup are safe operations.
283
-
284
- ### 📂 Git Repository Setup
285
-
286
- **Change visibility of an existing repo:**
287
- ```bash
288
- npmglobalize -git public # Makes the GitHub repo public (with confirmation)
289
- npmglobalize -git private # Makes the GitHub repo private
290
- ```
291
-
292
- ### Missing local `.git` (adopt vs fresh init)
293
-
294
- When npmglobalize runs in a directory with no `.git`, it first checks for a
295
- reachable `repository.url` in `package.json`. If found, it offers two paths:
296
-
297
- ```
298
- How would you like to set up git?
299
- 1) Adopt history from existing remote (recommended)
300
- 2) Initialize fresh git repository
301
- a) Adopt ALL (don't ask again for remaining deps)
302
- 3) Use local install only (skip git/publish)
303
- 4) Abort
304
- ```
305
-
306
- **Adopt** runs:
307
-
308
- ```bash
309
- git init
310
- git remote add origin <package.json repository.url>
311
- git fetch origin
312
- # Point HEAD at origin/<defaultBranch> without touching the working tree
313
- git update-ref refs/heads/<branch> origin/<branch>
314
- git symbolic-ref HEAD refs/heads/<branch>
315
- git reset # mixed: refresh index, keep working tree
316
- ```
317
-
318
- After adoption, your local files appear as **uncommitted changes on top of the
319
- remote's HEAD** — `git status` shows the drift, and npmglobalize's normal flow
320
- will commit them on the next publish. **No force-push is required**, and the
321
- remote's history is preserved.
322
-
323
- If the remote is not reachable (no `repository` field, network/auth failure,
324
- deleted repo), the original "Initialize fresh git repository" prompt is shown
325
- instead.
326
-
327
- CLI flags:
328
-
329
- - `-init` — auto path; adopts if a reachable remote is found, otherwise falls
330
- back to fresh `git init` + `gh repo create`.
331
- - `-adopt` — **strict**: aborts if no reachable remote in `package.json.repository`.
332
- Use this when you want to be sure no new GitHub repo is created (e.g. in
333
- scripts, or when re-attaching a tree of packages to existing repos).
334
-
335
- When initializing a new repository with `--init`, npmglobalize automatically sets up:
336
-
337
- **File structure** (per programming.md standards):
338
- - `.gitignore` - Node.js best practices (node_modules, secrets, certificates, etc.)
339
- - `.npmignore` - Publishing filters (excludes .git, tests, source files, etc.)
340
- - **noEmit projects:** `*.ts`, `*.map`, and `tsconfig.json` are kept (not ignored) since TS files are the runtime files
341
- - `.gitattributes` - Forces LF line endings for all text files
342
-
343
- **Git configuration** (ensures cross-platform compatibility):
344
- ```bash
345
- git config core.autocrlf false # Disable CRLF conversion
346
- git config core.eol lf # Force LF line endings
347
- ```
348
-
349
- This ensures consistent line endings across Windows, macOS, and Linux, preventing "modified file" issues caused by line ending differences.
350
-
351
- ### 🔍 Understanding "Detached HEAD" Error
352
-
353
- **What is Detached HEAD?**
354
- Your git repository is not currently on a branch (like `master` or `main`). This happens when you:
355
- - Check out a specific commit: `git checkout abc123`
356
- - Check out a tag: `git checkout v1.0.0`
357
- - Have some git operations leave you in this state
358
-
359
- **Why does it matter?**
360
- Publishing requires being on a branch so commits and tags can be properly tracked in your repository history.
361
-
362
- **Common scenarios:**
363
-
364
- 1. **Just fixing files with `--conform`:**
365
- ```bash
366
- npmglobalize --conform # Files get fixed, then exits with helpful message
367
- ```
368
- The files are already updated! You don't need to run it again.
369
-
370
- 2. **Want to publish (have commits to keep):**
371
- ```bash
372
- git checkout -B master # Moves master branch to current commit (merges)
373
- npmglobalize # Now works normally
374
- ```
375
- The `-B` flag moves your branch pointer to include the detached commits.
376
-
377
- 3. **Want to publish (no commits made, safe to discard):**
378
- ```bash
379
- git checkout master # Just switch back to branch
380
- npmglobalize # Now works normally
381
- ```
382
-
383
- 4. **Force publish anyway (not recommended):**
384
- ```bash
385
- npmglobalize --force # Proceeds despite detached HEAD
386
- ```
387
- Warning: Commits may be hard to track later.
388
-
389
- ## Command Reference
390
-
391
- <!-- NOTE: Keep this in sync with the -help output in cli.ts printHelp().
392
- The README expands on options with examples and context;
393
- cli.ts is the concise quick-reference. Update both when adding/changing flags.
394
- Both -flag and --flag are accepted; single-dash is shown as primary. -->
395
-
396
- ### Release Options
397
- ```
398
- -patch Bump patch version (default)
399
- -minor Bump minor version
400
- -major Bump major version
401
- -nopublish, -np Just transform, don't publish (persisted to config)
402
- -cleanup Restore file: dependencies from .dependencies backup
403
- -m, -message <msg> Custom commit message (forces release even without changes)
404
- If -m not given, a `.commitmsg` file (if present) is used instead.
405
- See "Release Notes via .commitmsg" below.
406
- ```
407
-
408
- ### Dependency Options
409
- ```
410
- -update-deps, -ud Update package.json to latest versions (safe: minor/patch)
411
- -update-major Allow major version updates (breaking changes)
412
- -publish-deps Auto-publish file: dependencies (default)
413
- -pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only)
414
- -no-publish-deps, -npd Skip auto-publishing file: dependencies
415
- -no-prescan, -nps Skip upfront dep-graph prescan
416
- -force-publish Republish dependencies even if version exists
417
- -fix Run npm audit fix after transformation
418
- -no-fix Don't run npm audit
419
- -no-use-paths, -nup Declare package standalone; do not resolve file: deps
420
- from sibling checkouts (see Configuration File)
421
- ```
422
-
423
- ### Install Options
424
- ```
425
- -install, -i Install globally after publish (from registry)
426
- -link Install globally via symlink (npm install -g .)
427
- -local Local install only — skip transform/publish, just npm install -g .
428
- -wsl Also install in WSL
429
- -once Don't persist flags to .globalize.json5
430
- ```
431
-
432
- ### Mode Options
433
- ```
434
- -files Keep file: paths after publish (default)
435
- -nofiles Keep npm versions permanently
436
- ```
437
-
438
- ### Git/npm Visibility
439
- ```
440
- -git private Set GitHub repo to private (default for new repos)
441
- -git public Set GitHub repo to public (requires confirmation)
442
- Works on both new and existing repos
443
- -npm <values> Comma-separated list of npm options:
444
- private (default) | public — package visibility
445
- ts — keep .ts source (and *.map,
446
- tsconfig.json) in npm tarball
447
- nts exclude .ts source
448
- (default for non-noEmit projects)
449
- Example: -npm public,ts
450
- ```
451
-
452
- `ts` is already the default on git (source is tracked). Pass `-npm ts` to ship
453
- the same files to npm — useful for debugging installed packages or "source on
454
- demand" packages. `noEmit` projects automatically ship `.ts` files (they *are*
455
- the runtime); pass `-npm nts` to override. `allowTs` persists to
456
- `.globalize.json5`.
457
-
458
- ### Workspace Options
459
- ```
460
- -w, -workspace <pkg> Filter to specific package (repeatable)
461
- -no-workspace Disable workspace mode at a workspace root
462
- -continue-on-error Continue if a package fails in workspace mode
463
- ```
464
-
465
- Workspace mode is auto-detected when run from a root with `"private": true` and a `workspaces` field.
466
-
467
- ### Other Options
468
- ```
469
- -init Initialize git/npm if needed (creates .gitignore, .npmignore,
470
- .gitattributes, and configures git for LF line endings).
471
- If package.json.repository.url is reachable, adopts its
472
- history instead of creating a fresh repo.
473
- -adopt Strict adopt: require a reachable git remote in
474
- package.json.repository. Abort if probe fails. Skips prompt.
475
- -strict-imports, -import-check
476
- Opt in to scanning .ts/.js source for imports of packages not
477
- declared in any dependencies bucket. Off by default the
478
- always-declare style this enforces doesn't hold in monorepos
479
- that rely on workspace cross-refs or the -public-deps cascade
480
- (those produce noisy prompts that get dismissed reflexively,
481
- which defeats the safety purpose). Use only on packages where
482
- every import is meant to be a direct package.json declaration.
483
- When it does fire, it catches silent runtime failures —
484
- ERR_MODULE_NOT_FOUND on a clean install of a published tarball
485
- that was resolving via an ambient parent/global node_modules
486
- at dev time.
487
- -force Continue despite git errors
488
- -dry-run Preview what would happen
489
- -quiet Suppress npm warnings (default)
490
- -verbose Show detailed output
491
- -conform Update .gitignore/.npmignore/.gitattributes to best practices
492
- and configure git for LF line endings (fixes existing repos)
493
- For noEmit projects: removes *.ts/*.map/tsconfig.json from .npmignore
494
- -asis Skip ignore file checks (or set "asis": true in .globalize.json5)
495
- -fix-tags Automatically fix version/tag mismatches
496
- -rebase Automatically rebase if local is behind remote
497
- -clean-nested-modules, -clean-nested
498
- Before npm pack, wipe node_modules/ inside each file: dep
499
- target. Fixes arborist "Cannot read properties of null"
500
- crashes caused by sibling file: deps with nested
501
- node_modules. Suggested automatically when the error hits.
502
- -ts7-report, -deprecation-report
503
- Report-only: scan this package and its file: deps for
504
- compilerOptions removed in TypeScript 7 and list a migration
505
- to-do. Writes nothing.
506
- -tsfix, -ts7-fix
507
- One-off utility, NOT part of the main flow: apply the TS7
508
- tsconfig migration to a package (and its file: deps) and exit —
509
- no build, no commit, no push, no publish. Intended as a
510
- temporary tool for fixing a local/subdirectory tsconfig in
511
- place. The normal release flow already applies the same
512
- migration automatically when a build hits a TS7 deprecation
513
- error, so day-to-day you never need this flag.
514
- npmglobalize <path> -tsfix
515
- -show Show package.json dependency changes
516
- -package, -pkg Update package.json scripts to use npmglobalize (see below)
517
- -h, -help Show help
518
- -v, -version Show version
519
- ```
520
-
521
- ## Using in package.json
522
-
523
- You can wire npmglobalize into your package.json `scripts` so that `npm run release` handles publishing:
524
-
525
- ```json
526
- {
527
- "scripts": {
528
- "release": "npmglobalize"
529
- }
530
- }
531
- ```
532
-
533
- The `--package` (`-pkg`) flag does this automatically — it adds a `release` script (renaming any existing `release`/`installer` scripts to `old-release`/`old-installer`):
534
-
535
- ```bash
536
- npmglobalize --package # Sets up "release": "npmglobalize" in package.json
537
- ```
538
-
539
- After that, publishing is just:
540
- ```bash
541
- npm run release # Same as running npmglobalize directly
542
- npm run release -- --minor # Pass flags through
543
- ```
544
-
545
- You can also combine it with `.globalize.json5` for persistent options so `npm run release` always uses your preferred settings (install, visibility, etc.).
546
-
547
- ## Configuration File
548
-
549
- Settings can be saved in `.globalize.json5`:
550
-
551
- ```json5
552
- {
553
- // npmglobalize configuration (JSON5 format)
554
- "bump": "patch", // Version bump type
555
- "install": true, // Auto-install globally
556
- "wsl": false, // Also install in WSL
557
- "fix": true, // Auto-run npm audit fix
558
- "verbose": false, // Show detailed output
559
- "gitVisibility": "private",
560
- "npmVisibility": "public",
561
- "usePaths": true // Resolve file: deps from sibling checkouts (see below)
562
- }
563
- ```
564
-
565
- Configuration persists across runs. CLI flags override config file.
566
-
567
- ### `usePaths` — Standalone packages
568
-
569
- Default: `true`. Set to `false` (or pass `-no-use-paths` / `-nup`) to mark a
570
- package as **standalone** one that should be publishable/installable on a
571
- machine that does not have sibling `file:` dep checkouts available. Example:
572
- a backup/recovery utility you want to `npm install -g` on any host.
573
-
574
- Currently this setting is **declarative** it is parsed, persisted to
575
- `.globalize.json5`, and surfaced in the settings banner, but the tool does
576
- not yet change its behavior based on it. Behavior wiring (e.g. resolving
577
- `file:` deps to the latest published npm version instead of walking siblings)
578
- is planned.
579
-
580
- ### `upstream` — who consumes this package
581
-
582
- **Experimental.** Bookkeeping, not a setting. When a package with `file:` deps
583
- publishes, npmglobalize appends an entry to each **dependency's**
584
- `.globalize.json5`:
585
-
586
- ```json5
587
- {
588
- "install": true,
589
-
590
- // FYI: packages that depend on this one (immediate consumers,
591
- // recorded when each of them publishes). Nothing is updated
592
- // automatically; follow each path's own .globalize.json5 to
593
- // walk further out.
594
- "upstream": [
595
- {"path":"Y:\\dev\\utils\\winpos","version":"2.0.51","updated":"2026-08-09"},
596
- ],
597
- }
598
- ```
599
-
600
- So publishing `winpos` (which has `"@bobfrankston/msger": "file:../msgx/msger"`)
601
- records winpos in **msger's** config. Each entry carries the consumer's
602
- checkout path, its version at the time, and the date.
603
-
604
- Only **immediate** consumers are recorded. A full consumer tree is a walk, not
605
- a copy: follow each entry's path and read that package's own `upstream` list.
606
- That keeps each file small and self-maintaining no package has to know about
607
- anything beyond its own direct consumers.
608
-
609
- The list is **FYI** — it is recorded, preserved across publishes, and printed
610
- in the Release Summary of the package that owns it. Nothing is rebuilt,
611
- republished, or reinstalled on its behalf. An entry appears the first time a
612
- consumer publishes, and is refreshed in place on every publish after that.
613
-
614
- #### Scope and limits
615
-
616
- **`file:` deps only.** A dependency referenced by npm version (`"^0.1.39"`)
617
- is never recorded, because npmglobalize has no checkout path for it — it
618
- consumes the published tarball, not a sibling directory. To have a consumer
619
- show up in a library's list, that consumer must reference it as
620
- `file:../<lib>`.
621
-
622
- **Usually not committed.** The entry is written into the dependency's own
623
- checkout, and npmglobalize then tries to commit it there
624
- (`Record upstream <consumer>@<version>`) and push if that repo has a remote.
625
- In practice that commit is usually skipped: `.globalize.json5` is in the
626
- standard ignore template, so most repos ignore it and the entry stays as
627
- untracked local state. That is a reasonable failure mode for an experimental
628
- mechanismthe list rewrites itself on every publish, so there is nothing to
629
- merge and nothing to reconcile between machines. It also means the list is
630
- **per-machine**, not shared history.
631
-
632
- When the commit does happen (a repo that tracks its `.globalize.json5`, as
633
- npmglobalize itself does), only that one file is staged and committed by
634
- pathspec anything else the dependency had staged or modified is left exactly
635
- as it was. A dependency that isn't a git repo is written and skipped the same
636
- way.
637
-
638
- The alternative would be recording this in `package.json`, which is tracked
639
- and published — every consumer of a library would then download that library's
640
- list of local checkout paths in its tarball. Keeping it in `.globalize.json5`
641
- keeps it out of the package entirely.
642
-
643
- ## Common Workflows
644
-
645
- ### Standard Release
646
- ```bash
647
- npmglobalize --install # Publish + install globally
648
- ```
649
-
650
- ### Release with Dependency Chain
651
- ```bash
652
- cd my-app # Has file: deps
653
- npmglobalize # Publishes all deps automatically
654
- ```
655
-
656
- ### Safe Dependency Updates
657
- ```bash
658
- npmglobalize --update-deps # Update to latest safe versions
659
- ```
660
-
661
- ### Security Fixes
662
- ```bash
663
- npmglobalize --fix # Fix vulnerabilities + release
664
- ```
665
-
666
- ### Force Update Everything
667
- ```bash
668
- npmglobalize --force-publish --update-major
669
- ```
670
-
671
- ### Preview Changes
672
- ```bash
673
- npmglobalize --dry-run # See what would happen
674
- ```
675
-
676
- ## How It Works
677
-
678
- 1. **Validates** package.json and git status
679
- 2. **Checks** if current version is on npm (recovers from failed publishes)
680
- 3. **Updates dependencies** (if `--update-deps`)
681
- 4. **Builds `file:` deps in topological order**, then the target itself, so consumers' `tsc` reads up-to-date `.d.ts` from sibling checkouts whose source has changed (see [Build Cascade](#build-cascade))
682
- 5. **Publishes file: dependencies** (if needed)
683
- 6. **Backs up** original file: references to `.dependencies`
684
- 7. **Converts** `file:` npm version references
685
- 8. **Commits** changes
686
- 9. **Bumps** version (using npm version) — skipped if recovering a failed publish
687
- 10. **Publishes** to npm
688
- 11. **Pushes** to git (with push-protection detection and auto-bypass)
689
- 12. **Installs** globally (if `--install`)
690
- 13. **Restores** file: references (if `--files`, default)
691
- 14. **Runs audit** (shows security status)
692
-
693
- ## Operational Details
694
-
695
- ### Build Cascade
696
-
697
- Before transforming or publishing anything, `npmglobalize` builds `file:` dependencies in topological order — deps before consumers — and then builds the target itself. This guarantees the target's `tsc` reads up-to-date `.d.ts` and `.js` from sibling checkouts even when a dep's source has changed since its last build.
698
-
699
- For each project visited (the target and every transitive `file:` dep):
700
-
701
- - If `tsconfig.json` is missing or has `"noEmit": true` **skip** (not a TypeScript build), unless the `build` script runs `importgen` a plain-JS browser app still needs its import map regenerated.
702
- - If `tsconfig.json` exists but `package.json` has no `"build"` script → **prompt** to add `"build": "tsc"` (plus a `tsc -p <dir>` per sub-project — see below). Decline and that project is skipped.
703
- - Otherwise run `npm run build`. A failure halts the cascade unless `-force` is passed.
704
-
705
- Cycle-safe via a shared visited set; each project is built at most once per run.
706
-
707
- This complements the existing publish cascade (which ensures version refs are correct) by closing the build-freshness gap that `npm install` alone left open.
708
-
709
- #### Import maps (`importgen`) as a build step
710
-
711
- Browser projects that use [`importgen`](https://www.npmjs.com/package/@bobfrankston/importgen) have historically regenerated their import map from `.vscode/tasks.json`, which only runs when VS Code opens the folder — so a command-line or CI build could publish a stale map. `npmglobalize` treats the import map as a build product and moves the step into the package's own `build` script, where every build path picks it up.
712
-
713
- Before building each project, it checks whether the project is an importgen project, in this order:
714
-
715
- 1. `.vscode/tasks.json` has a task whose `command` is `importgen` (the HTML file, if the task names one, is reused).
716
- 2. A root-level `.htm`/`.html` file already contains a `<script type="importmap">` block (`index.html`, `default.html`, `default.htm` are checked first, so a stray `temp.htm` doesn't win).
717
- 3. `importgen` is in `dependencies`/`devDependencies` **and** the project has one of those HTML files — the HTML requirement keeps packages that merely *use* importgen as a library from matching.
718
-
719
- If a signal matches and the `build` script doesn't already run `importgen`, you're prompted to rewrite it e.g. `"build": "tsc"` `"build": "importgen default.htm && tsc"`. The HTML file is named explicitly so importgen doesn't have to guess. Decline and `"importgen": false` is written to `.globalize.json5`, which suppresses the prompt for good; `-noimportgen` does the same from the command line.
720
-
721
- Once wired, the freshness check gains a second condition: a project whose build runs importgen is only considered up to date if the generated HTML is at least as new as `package.json`. Adding a dependency changes the import map without touching any `.ts` file, which the source-vs-output comparison alone would miss.
722
-
723
- #### Sub-projects (a second `tsconfig.json` in a sub-directory)
724
-
725
- A package can hold more than one TypeScript project. The common shape is a service worker in `Sw/` with its own `tsconfig.json` (`lib: ["WebWorker"]`, its own `outDir`) that the root `tsconfig.json` lists under `exclude` — so a bare `"build": "tsc"` compiles everything *except* the service worker, and the stale `sw2.js` ships. As with import maps, these have historically been built only by a second `.vscode/tasks.json` watcher, which runs on folder open and nowhere else.
726
-
727
- Before building, `npmglobalize` looks for sub-projects, in this order:
728
-
729
- 1. `.vscode/tasks.json` has a task that runs `tsc` with `"options": { "cwd": "${workspaceFolder}/Sw" }` or with `-p`/`--project` naming a sub-directory (authoritative — it's how the project is actually built today; the task's `label` is quoted back in the prompt).
730
- 2. An immediate sub-directory containing its own `tsconfig.json`. Build output and vendored trees (`node_modules`, `prev`, `dist`, `built`, `out`, `wwwroot`, `coverage`, `temp`, `preflight`, dot-directories) are never scanned.
731
-
732
- Nothing is detected when the root `tsconfig.json` uses project `references` — that build graph belongs to `tsc -b` and isn't second-guessed.
733
-
734
- When the `build` script is `tsc`-driven and doesn't already compile a detected sub-project, you're prompted to append it `"build": "tsc"` `"build": "tsc && tsc -p Sw"`, or for an importgen project `"build": "importgen default.htm && tsc && tsc -p Sw"`. Already-wired scripts are recognized in any of their spellings (`tsc -p Sw`, `tsc --project ./Sw/tsconfig.json`, `cd Sw && tsc`), and the directory is emitted with its real on-disk casing so the script still works on WSL and CI. Decline and `"subProjects": false` is written to `.globalize.json5`, suppressing the prompt for good; a `"subProjects": ["Sw"]` array pins the list instead of detecting it.
735
-
736
- Sub-projects the build actually compiles are also folded into the freshness check the package rebuilds when a sub-project's sources are newer than its output. A sub-project that emits *outside* its own directory (`"outDir": ".."`, the usual service-worker case) always reports stale: comparing its sources against the whole package would prove nothing.
737
-
738
- #### TypeScript 6 `types` auto-fix
739
-
740
- TypeScript 6 dropped the legacy behavior of auto-including every installed `@types/*` package. A `tsconfig.json` with no explicit `compilerOptions.types` then loses the Node globals (`process`, `Buffer`, …) and the build fails with `TS2591`.
741
-
742
- Before each build, when the global `tsc` is version 6 or newer, `npmglobalize` patches the project's `tsconfig.json` to add an explicit `types` list enumerating the installed `@types/*` packages (e.g. `"types": ["node", …]`) — restoring the old behavior. The edit is:
743
-
744
- - **Conservative** — only applied when `compilerOptions.types` is **absent**, `node_modules/@types/node` is actually installed, and there is no `extends` (whose merged `types` can't be seen). An explicit `types` you already set is never overridden.
745
- - **Format-preserving** — the single `types` key is inserted into the existing `compilerOptions` block; comments, ordering, and indentation are left intact.
746
- - **Idempotent** once the list is present, subsequent runs skip it.
747
-
748
- If the patch can't be applied for some reason and the build still fails with `TS2591`, the failure summary prints a hint to add `"types": ["node"]` manually.
749
-
750
- ### The `.dependencies` Backup (Internal/Transient)
751
-
752
- **You should never see `.dependencies` in your `package.json` under normal operation.** It is a temporary internal backup that exists only during the brief publish cycle and is removed automatically when the cycle completes.
753
-
754
- During publishing, `npmglobalize` temporarily replaces `file:` references with npm version strings. The original `file:` entries are stashed in `.dependencies` (and `.devDependencies`, etc.) so they can be restored afterward. Once the publish succeeds and `file:` paths are restored, `.dependencies` is deleted. A normal run leaves no trace of it.
755
-
756
- **If you see `.dependencies` in your `package.json`, something went wrong** the tool crashed, was killed, or the publish failed partway through. It is not a feature to rely on or edit manually.
757
-
758
- **Recovery:**
759
- - **Re-run `npmglobalize`**: It detects leftover `.dependencies`, restores the originals, and continues normally. Self-healing is automatic.
760
- - **Manual restore**: `npmglobalize -cleanup` restores file: deps and removes the `.dependencies` backup.
761
-
762
- **Why a persistent backup?** If the tool crashes hard (killed process, power failure, npm timeout), there's no cleanup code to run. The backup in `package.json` survives because it was written before the risky operations began. The next run self-heals.
763
-
764
- ### Flag Conventions
765
-
766
- Both `-flag` and `--flag` are accepted. Single-dash is the primary convention:
767
- ```bash
768
- npmglobalize -patch # same as --patch
769
- npmglobalize -np # same as --nopublish
770
- npmglobalize -local # same as --local
771
- ```
772
-
773
- ### Persistent vs One-Shot Flags
774
-
775
- Some flags are **persisted** to `.globalize.json5` when set from the CLI:
776
- - `-install`, `-link`, `-wsl`, `-files`, `-fix` — install/build preferences
777
- - `-np` (noPublish) once set, prevents accidental publishes
778
- - `-local` — remembers "this project is local-only"
779
- - `-git`/`-npm` visibility
780
-
781
- Other flags are **one-shot** (never persisted):
782
- - `-cleanup`, `-init`, `-dry-run`, `-message` situational actions
783
- - `-update-deps`, `-update-major`, `-force-publish` — explicit per-run choices
784
- - `-conform`, `-asis` — one-time fixes
785
-
786
- Use `-once` to prevent any flag from persisting on that run:
787
- ```bash
788
- npmglobalize -np -once # No-publish this run only, don't remember it
789
- ```
790
-
791
- ### Local Install (`-local`)
792
-
793
- Skip all transform/publish logic and just run `npm install -g .` with `file:` deps as-is. Use this when you want to install a CLI tool locally for your own use without publishing anything:
794
-
795
- ```bash
796
- npmglobalize -local # Install globally from local directory
797
- npmglobalize -local -wsl # Also install in WSL
798
- ```
799
-
800
- This is useful for:
801
- - Development tools you don't publish
802
- - Testing a CLI before publishing
803
- - Projects with `file:` deps that should stay as-is
804
-
805
- ### Private Packages
806
-
807
- Packages with `"private": true` in `package.json` skip the npm publish step. Dependencies are still transformed and restored — the publish is the only thing skipped.
808
-
809
- ## Version Checking
810
-
811
- When publishing file: dependencies, checks if each version exists on npm:
812
- - ✅ Exists → Skip, use existing version
813
- - Missing → Publish it first
814
- - 🔄 Force → Use `--force-publish` to republish
815
-
816
- ## Examples
817
-
818
- ```bash
819
- # Basic release
820
- npmglobalize
821
-
822
- # Run on a different project
823
- npmglobalize y:\dev\myproject
824
-
825
- # Auto-fix tag conflicts and rebase
826
- npmglobalize -fix-tags -rebase
827
-
828
- # Release with updates and security fixes
829
- npmglobalize -update-deps -fix
830
-
831
- # Just update package.json, don't publish
832
- npmglobalize -np -update-deps
833
-
834
- # Force republish all dependencies
835
- npmglobalize -force-publish -update-major
836
-
837
- # Release + install on Windows and WSL (from registry)
838
- npmglobalize -install -wsl
839
-
840
- # Release + link on Windows and WSL (symlink)
841
- npmglobalize -link -wsl
842
-
843
- # Install locally without publishing (file: deps stay as-is)
844
- npmglobalize -local
845
-
846
- # Restore original file: references
847
- npmglobalize -cleanup
848
-
849
- # Initialize git (adopt existing remote if reachable, else fresh) + release
850
- npmglobalize -init
851
-
852
- # Strict adopt: re-attach to remote in package.json.repository (or abort)
853
- npmglobalize -adopt
854
-
855
- # Migrate package.json scripts to use npmglobalize
856
- npmglobalize -package
857
-
858
- # Preview what would happen
859
- npmglobalize -dry-run -verbose
860
- ```
861
-
862
- ## Authentication
863
-
864
- Requires npm authentication:
865
- ```bash
866
- npm login
867
- ```
868
-
869
- Check authentication:
870
- ```bash
871
- npm whoami
872
- ```
873
-
874
- ## Development
875
-
876
- ### Build Check
877
-
878
- `npmglobalize` includes automatic build verification to ensure TypeScript files are compiled before execution. This prevents runtime errors from outdated JavaScript files.
879
-
880
- **How it works:**
881
- - When you run `npmglobalize`, it automatically checks if `.js` files are newer than their `.ts` sources
882
- - If `.js` files are missing or outdated, execution stops with an error
883
- - The check is skipped if `noEmit: true` is set in `tsconfig.json`
884
-
885
- **Building the project:**
886
- ```bash
887
- npm run build # Compile TypeScript files
888
- npm run watch # Watch mode for development
889
- npm run check # Manually verify build status
890
- ```
891
-
892
- **Bypassing the check:**
893
- ```bash
894
- npmglobalize --force # Skip build check (not recommended)
895
- ```
896
-
897
- **Error example:**
898
- ```
899
- ❌ Error: TypeScript files not compiled
900
- cli.js is older than cli.ts
901
-
902
- Please run: npm run build
903
- Or use --force to skip this check
904
- ```
905
-
906
- This ensures you never accidentally run outdated code when the TypeScript source has changed.
907
-
908
- ## License
909
-
910
- MIT
1
+ # npmglobalize
2
+
3
+ Transform `file:` dependencies to npm versions for publishing.
4
+
5
+ ## Overview
6
+
7
+ `npmglobalize` automates the workflow of publishing npm packages that use local `file:` references during development. It converts those references to proper npm versions, publishes everything in dependency order, and optionally restores the local references afterward.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install -g @bobfrankston/npmglobalize
13
+ ```
14
+
15
+ ## Basic Usage
16
+
17
+ ```bash
18
+ cd your-package
19
+ npmglobalize # Transform + publish (patch version)
20
+ npmglobalize --minor # Bump minor version
21
+ npmglobalize --major # Bump major version
22
+
23
+ # Or run from anywhere with a path
24
+ npmglobalize y:\path\to\your-package
25
+ ```
26
+
27
+ ## Key Features
28
+
29
+ ### 🔗 Automatic Dependency Publishing (Default)
30
+
31
+ By default, `npmglobalize` ensures all `file:` dependencies are published **before** converting them:
32
+
33
+ ```bash
34
+ npmglobalize # Auto-publishes file: deps in correct order
35
+ ```
36
+
37
+ If you have:
38
+ ```
39
+ lxtest
40
+ ├── file:../lxlan-node
41
+ │ └── file:../lxland
42
+ └── file:../lxland
43
+ ```
44
+
45
+ It automatically:
46
+ 1. Publishes `lxland` (root dependency)
47
+ 2. Publishes `lxlan-node` (depends on lxlan)
48
+ 3. Converts and publishes `lxtest`
49
+
50
+ **Settings Propagation (Default Behavior):**
51
+ When publishing `file:` dependencies, these settings are **automatically inherited**:
52
+ - `--update-deps` / `--update-major` (update dependencies)
53
+ - `--fix` (run npm audit fix)
54
+ - `--conform` (fix .gitignore/.npmignore/.gitattributes and git config)
55
+ - `--verbose` / `--quiet`
56
+ - `--force` / `--files`
57
+
58
+ **⚠️ Visibility Settings (Smart Inheritance):**
59
+ - `--npmVisibility` is **only inherited by NEW repositories** (never published to npm before)
60
+ - **Existing repositories** keep their current npm visibility (public/private) unchanged
61
+ - `--gitVisibility` is inherited by all dependencies
62
+
63
+ This ensures you can safely set `--npmVisibility private` as a default for new packages without accidentally changing the visibility of your existing published packages.
64
+
65
+ **Why This Matters:**
66
+ Once a package is published to npm (public or private), changing its visibility later requires careful consideration. This smart inheritance protects your existing packages while making new ones default to safe settings.
67
+
68
+ Example - safely publish with new packages private by default:
69
+ ```bash
70
+ npmglobalize --npmVisibility private
71
+ # ✓ Existing npm packages: keep their current visibility
72
+ # ✓ New packages (never published): default to private (safe!)
73
+ # ✓ Regular npm dependencies (express, etc.): unchanged
74
+ ```
75
+
76
+ Example with configuration file (recommended):
77
+ ```bash
78
+ # In main package: create .globalize.json5 with "npmVisibility": "private"
79
+ npmglobalize
80
+ # ✓ Existing repos: publish with their current npm visibility
81
+ # ✓ Brand new repos: inherit private setting
82
+ ```
83
+
84
+ **Skip auto-publishing** (use with caution):
85
+ ```bash
86
+ npmglobalize -npd # --no-publish-deps
87
+ ```
88
+
89
+ ### 🔍 Prescan (Default)
90
+
91
+ Before any transform/publish, `npmglobalize` walks the full `file:` dep graph and reports all problems up front (unresolvable paths, missing `package.json`, unscoped packages with private intent, etc.). This lets you fix the whole punch list at once rather than being interrupted mid-cascade.
92
+
93
+ Errors abort (unless `--force`); warnings prompt to continue.
94
+
95
+ Skip the prescan with `-no-prescan` / `-nps`.
96
+
97
+ #### Workspace skip prescan
98
+
99
+ In workspace mode, before processing begins, each package is also checked to decide whether it actually needs work. A package is **skipped** (no rebuild, no version bump, no publish) only if **all** of these are true:
100
+
101
+ 1. **Working tree clean** — `git status --porcelain .` reports no uncommitted changes for that package's directory.
102
+ 2. **Version already on npm** — the version in its `package.json` is published to the registry, and no non-bookkeeping commit (anything outside "Pre-release commit" / "Restore file: dependencies" / "Pre-version cleanup" / "Untrack node_modules") is newer than that version's publish timestamp.
103
+ 3. **Build is fresh** — every `.ts` source file (excluding `.d.ts`) has a sibling `.js` whose mtime is ≥ the `.ts` mtime. A missing `.js` or an older `.js` counts as stale.
104
+ 4. **No sibling workspace `file:` dep flagged for work** — if another workspace package depends on this one via `file:`, and that dep is being updated in this run, this one is also processed (propagates through the graph in topological order).
105
+
106
+ If any condition fails, the package is processed. The prescan prints one line per package with `⟳` for "will process" (and the reason) or `✓` for "skip". Example:
107
+
108
+ ```
109
+ ⟳ mlproc — uncommitted changes
110
+ ⟳ pzip — stale build (index.ts newer than index.js)
111
+ ⟳ stage — dep pzip is being updated
112
+ ✓ puller — skip (clean, published, build fresh)
113
+ ```
114
+
115
+ The summary row for a skipped package shows `– name v1.0.0 (skipped — already up to date)`.
116
+
117
+ Use `--force` or `--force-publish` to bypass the skip prescan and process every package.
118
+
119
+ **Force republish** all file: dependencies even if versions exist:
120
+ ```bash
121
+ npmglobalize --force-publish
122
+ ```
123
+
124
+ ### 📦 Dependency Updates
125
+
126
+ **Safe updates** (minor/patch only, respects semver):
127
+ ```bash
128
+ npmglobalize --update-deps
129
+ ```
130
+ - `express ^4.18.0` → `^4.21.0` ✓
131
+ - `lodash ^4.17.0` → `^4.17.21` ✓
132
+ - Won't update to `express ^5.0.0` (breaking change)
133
+
134
+ **Include major updates** (breaking changes):
135
+ ```bash
136
+ npmglobalize --update-major
137
+ ```
138
+ - Updates to latest including major versions
139
+ - Shows "(MAJOR)" indicator for breaking changes
140
+
141
+ **Note:** The `--update-deps` flag propagates to all file: dependencies, so one command updates your entire dependency tree.
142
+
143
+ ### 🔒 Security Auditing
144
+
145
+ **Check vulnerabilities**:
146
+ ```bash
147
+ npmglobalize # Shows audit at end
148
+ ```
149
+
150
+ **Auto-fix vulnerabilities**:
151
+ ```bash
152
+ npmglobalize --fix # Runs npm audit fix
153
+ ```
154
+
155
+ **Disable audit**:
156
+ ```bash
157
+ npmglobalize --no-fix
158
+ ```
159
+
160
+ ### 🧩 Install Scripts (npm `allowScripts`)
161
+
162
+ npm 11.17+ skips install-time lifecycle scripts (`preinstall`/`install`/
163
+ `postinstall`) for packages that aren't on an allowlist. A **global** install
164
+ has no project `package.json` to record approvals in, so without help a
165
+ postinstall you wrote yourself silently never runs while the install still
166
+ exits 0 — e.g. `@bobfrankston/msger`'s postinstall copies its native binary
167
+ into the per-user bin dir its launcher reads from, and skipping it leaves a
168
+ stale exe behind a green checkmark.
169
+
170
+ npmglobalize therefore allowlists **your own packages** on the global install
171
+ it performs — it just built and published them from your source, so they're
172
+ trusted — and leaves third-party packages gated:
173
+
174
+ ```
175
+ > npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,@bobfrankston/msgcommon
176
+ ```
177
+
178
+ Anything npm skips is reported rather than buried in the captured output:
179
+
180
+ ```
181
+ · Skipped koffi@2.16.3 install script — prebuilt-binary fetcher, the package ships binaries; normally harmless.
182
+ ⚠ npm skipped install scripts for third-party packages: sharp@0.33.0
183
+ sharp@0.33.0 (install: node install/check)
184
+ These may be broken at runtime — no prebuilt fallback recognized.
185
+ To run them anyway: npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,koffi,sharp
186
+ ```
187
+
188
+ Two details worth knowing:
189
+
190
+ - **Prebuilt fetchers are called out separately.** npm names the skipped
191
+ script, so a runner like `cnoke`, `prebuild-install`, `node-gyp-build`,
192
+ `prebuildify` or `napi-postinstall` is recognized as one that only compiles
193
+ when no prebuild matches. Those packages ship working binaries — koffi
194
+ carries `build/koffi/<platform>/koffi.node` for every platform it supports —
195
+ so the skip gets an informational note, not a warning.
196
+ - **The suggested re-run command includes your own packages.** npm takes the
197
+ allowlist from the first source that has one rather than merging sources, so
198
+ a command naming only the third-party package would silently re-gate yours.
199
+ Always run the full list.
200
+
201
+ An own-scope package showing up as skipped is reported louder — that means the
202
+ allowlist missed it, usually a transitive dep not present in the local
203
+ `node_modules` tree.
204
+
205
+ ### 🔑 OAuth Credentials Handling
206
+
207
+ `npmglobalize` automatically detects `credentials.json` files and handles them based on OAuth app type:
208
+
209
+ - **Desktop/installed apps** (`"installed"` key in JSON): The `client_secret` is just a public app registration ID, not a real secret. Google's own docs state: *"the client_secret is obviously not treated as a secret."* These files are kept in the repo — `!credentials.json` is added to `.gitignore`/`.npmignore` to override any broader ignore patterns.
210
+
211
+ - **Web apps** (`"web"` key in JSON): The `client_secret` is a real secret. These files are automatically added to `.gitignore`/`.npmignore` to prevent accidental exposure.
212
+
213
+ This distinction also drives the **push protection auto-bypass**: when GitHub blocks a push because it detects an OAuth client secret, `npmglobalize` checks the credential file type. For installed apps, it auto-bypasses (marking as `false_positive`) since the credential is public by design.
214
+
215
+ ### 🔄 File Reference Management
216
+
217
+ **Default behavior** (restore file: references after publish):
218
+ ```bash
219
+ npmglobalize # Converts file: → npm, publishes, then restores file:
220
+ ```
221
+
222
+ **Keep npm references** permanently:
223
+ ```bash
224
+ npmglobalize --nofiles # Don't restore file: references
225
+ ```
226
+
227
+ **Just transform** without publishing:
228
+ ```bash
229
+ npmglobalize -np # --nopublish (formerly --apply)
230
+ ```
231
+
232
+ **Restore** from backup:
233
+ ```bash
234
+ npmglobalize --cleanup # Restore original file: references
235
+ ```
236
+
237
+ ### 📝 Release Notes via `.commitmsg`
238
+
239
+ For multi-line or reusable release notes, write them to a `.commitmsg` file in the package root instead of passing them on the command line:
240
+
241
+ ```bash
242
+ cat > .commitmsg <<'EOF'
243
+ Added foo feature
244
+ Fixed bar regression
245
+ EOF
246
+ npmglobalize
247
+ ```
248
+
249
+ Behavior:
250
+ - If `-m` / `-message` is **not** given and `.commitmsg` exists, its contents are used as the commit message (and force a release even if the working tree is otherwise clean).
251
+ - After a successful `npm publish`, npmglobalize:
252
+ 1. Appends the contents to `npmchanges.md` under a `## v<version> — <YYYY-MM-DD>` header (creating the file if needed)
253
+ 2. Deletes `.commitmsg`
254
+ 3. Commits both changes as `Log v<version> to npmchanges.md` and pushes
255
+
256
+ Notes:
257
+ - **Git/GitHub only.** npm publish does not consume git commit messages; `npmchanges.md` lives in the git repo and on GitHub but is excluded from the published npm tarball (the standard `*.md` rule keeps only `README.md`).
258
+ - If both `-m` and `.commitmsg` are present, `-m` wins and `.commitmsg` is left alone (not consumed).
259
+ - If publish fails, `.commitmsg` is preserved for the next attempt.
260
+ - `.commitmsg` is auto-added to `.npmignore` (security pattern) so it never leaks into the tarball.
261
+
262
+ ### 🔧 Git Integration & Error Recovery
263
+
264
+ **Automatic tag conflict resolution**:
265
+ ```bash
266
+ npmglobalize --fix-tags # Auto-fix version/tag mismatches
267
+ ```
268
+
269
+ When a previous publish fails, git tags may conflict with package.json version. The `--fix-tags` option (or automatic detection) will clean up these conflicts.
270
+
271
+ **Automatic rebase** when local is behind remote:
272
+ ```bash
273
+ npmglobalize --rebase # Auto-rebase if behind remote
274
+ ```
275
+
276
+ For single-developer projects, this safely pulls remote changes before publishing.
277
+
278
+ **Failed publish recovery**: If a previous run bumped the version locally but the publish or push failed (e.g., network error, push protection), `npmglobalize` detects this automatically on the next run. It checks whether the current version actually exists on npm — if not, it republishes without re-bumping the version. Unpushed commits from a failed push are also pushed automatically.
279
+
280
+ **GitHub Push Protection (GH013)**: When GitHub's secret scanning blocks a push, `npmglobalize` detects the error and checks whether the flagged secrets are actually safe. For Google OAuth desktop/installed app credentials (where the "client_secret" is just a public app identifier, not a real secret), it automatically bypasses push protection via the GitHub API (`gh` CLI required) and retries the push. For other secret types, it displays the unblock URLs with guidance.
281
+
282
+ **Note:** This tool is designed for single-developer, single-branch workflows where automatic rebase and tag cleanup are safe operations.
283
+
284
+ ### 📂 Git Repository Setup
285
+
286
+ **Change visibility of an existing repo:**
287
+ ```bash
288
+ npmglobalize -git public # Makes the GitHub repo public (with confirmation)
289
+ npmglobalize -git private # Makes the GitHub repo private
290
+ ```
291
+
292
+ ### Missing local `.git` (adopt vs fresh init)
293
+
294
+ When npmglobalize runs in a directory with no `.git`, it first checks for a
295
+ reachable `repository.url` in `package.json`. If found, it offers two paths:
296
+
297
+ ```
298
+ How would you like to set up git?
299
+ 1) Adopt history from existing remote (recommended)
300
+ 2) Initialize fresh git repository
301
+ a) Adopt ALL (don't ask again for remaining deps)
302
+ 3) Use local install only (skip git/publish)
303
+ 4) Abort
304
+ ```
305
+
306
+ **Adopt** runs:
307
+
308
+ ```bash
309
+ git init
310
+ git remote add origin <package.json repository.url>
311
+ git fetch origin
312
+ # Point HEAD at origin/<defaultBranch> without touching the working tree
313
+ git update-ref refs/heads/<branch> origin/<branch>
314
+ git symbolic-ref HEAD refs/heads/<branch>
315
+ git reset # mixed: refresh index, keep working tree
316
+ ```
317
+
318
+ After adoption, your local files appear as **uncommitted changes on top of the
319
+ remote's HEAD** — `git status` shows the drift, and npmglobalize's normal flow
320
+ will commit them on the next publish. **No force-push is required**, and the
321
+ remote's history is preserved.
322
+
323
+ If the remote is not reachable (no `repository` field, network/auth failure,
324
+ deleted repo), the original "Initialize fresh git repository" prompt is shown
325
+ instead.
326
+
327
+ CLI flags:
328
+
329
+ - `-init` — auto path; adopts if a reachable remote is found, otherwise falls
330
+ back to fresh `git init` + `gh repo create`.
331
+ - `-adopt` — **strict**: aborts if no reachable remote in `package.json.repository`.
332
+ Use this when you want to be sure no new GitHub repo is created (e.g. in
333
+ scripts, or when re-attaching a tree of packages to existing repos).
334
+
335
+ When initializing a new repository with `--init`, npmglobalize automatically sets up:
336
+
337
+ **File structure** (per programming.md standards):
338
+ - `.gitignore` - Node.js best practices (node_modules, secrets, certificates, etc.)
339
+ - `.npmignore` - Publishing filters (excludes .git, tests, source files, etc.)
340
+ - **noEmit projects:** `*.ts`, `*.map`, and `tsconfig.json` are kept (not ignored) since TS files are the runtime files
341
+ - `.gitattributes` - Forces LF line endings for all text files
342
+
343
+ **Git configuration** (ensures cross-platform compatibility):
344
+ ```bash
345
+ git config core.autocrlf false # Disable CRLF conversion
346
+ git config core.eol lf # Force LF line endings
347
+ ```
348
+
349
+ This ensures consistent line endings across Windows, macOS, and Linux, preventing "modified file" issues caused by line ending differences.
350
+
351
+ ### 🔍 HEAD not on a branch — repaired automatically
352
+
353
+ Git leaves HEAD "detached" (pointing at a commit rather than at a branch) after checking out a
354
+ commit or a tag, and far more often in practice after a `git pull --rebase` that was
355
+ interrupted. The rebase applies its commits, then stops before moving the branch ref, so the
356
+ work exists on a real commit that no branch names.
357
+
358
+ npmglobalize repairs that itself rather than stopping and asking you to type git commands. The
359
+ working tree is authoritative, so it:
360
+
361
+ 1. Clears the unfinished rebase / cherry-pick / revert bookkeeping with `--quit`, which keeps
362
+ HEAD and the working tree exactly as they are (unlike `--abort`, which would throw away the
363
+ commits the interrupted run produced).
364
+ 2. Moves the branch to the current commit with `git checkout -B <branch>`, and restores its
365
+ upstream tracking.
366
+ 3. Prints what moved, naming the branch and both commits:
367
+
368
+ ```
369
+ ⚠ HEAD is not on a branch — left by an unfinished rebase, at 344431e "Restore file: dependencies"
370
+ Cleared the unfinished rebase (its commits are kept)
371
+ ✓ On branch main at 344431e "Restore file: dependencies"
372
+ Branch main previously pointed at f3ec2e6 "Pre-release commit", which is not in this history.
373
+ To put main back there: git reset --hard f3ec2e6
374
+ ```
375
+
376
+ The branch it picks is, in order: the branch the unfinished rebase was rebuilding (from the
377
+ rebase state), the remote's default branch (`origin/HEAD`), or whichever of `main` / `master`
378
+ the repository actually has. It is never assumed to be `master`.
379
+
380
+ **The one case it refuses:** files with unresolved conflict markers. There the working tree is
381
+ not what you meant to publish, so npmglobalize lists the conflicted files and stops.
382
+
383
+ ## Command Reference
384
+
385
+ <!-- NOTE: Keep this in sync with the -help output in cli.ts printHelp().
386
+ The README expands on options with examples and context;
387
+ cli.ts is the concise quick-reference. Update both when adding/changing flags.
388
+ Both -flag and --flag are accepted; single-dash is shown as primary. -->
389
+
390
+ ### Release Options
391
+ ```
392
+ -patch Bump patch version (default)
393
+ -minor Bump minor version
394
+ -major Bump major version
395
+ -nopublish, -np Just transform, don't publish (persisted to config)
396
+ -cleanup Restore file: dependencies from .dependencies backup
397
+ -m, -message <msg> Custom commit message (forces release even without changes)
398
+ If -m not given, a `.commitmsg` file (if present) is used instead.
399
+ See "Release Notes via .commitmsg" below.
400
+ ```
401
+
402
+ ### Dependency Options
403
+ ```
404
+ -update-deps, -ud Update package.json to latest versions (safe: minor/patch)
405
+ -update-major Allow major version updates (breaking changes)
406
+ -publish-deps Auto-publish file: dependencies (default)
407
+ -pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only)
408
+ -no-publish-deps, -npd Skip auto-publishing file: dependencies
409
+ -no-prescan, -nps Skip upfront dep-graph prescan
410
+ -force-publish Republish dependencies even if version exists
411
+ -fix Run npm audit fix after transformation
412
+ -no-fix Don't run npm audit
413
+ -no-use-paths, -nup Declare package standalone; do not resolve file: deps
414
+ from sibling checkouts (see Configuration File)
415
+ ```
416
+
417
+ ### Install Options
418
+ ```
419
+ -install, -i Install globally after publish (from registry)
420
+ -link Install globally via symlink (npm install -g .)
421
+ -local Local install only — skip transform/publish, just npm install -g .
422
+ -wsl Also install in WSL
423
+ -once Don't persist flags to .globalize.json5
424
+ ```
425
+
426
+ ### Mode Options
427
+ ```
428
+ -files Keep file: paths after publish (default)
429
+ -nofiles Keep npm versions permanently
430
+ ```
431
+
432
+ ### Git/npm Visibility
433
+ ```
434
+ -git private Set GitHub repo to private (default for new repos)
435
+ -git public Set GitHub repo to public (requires confirmation)
436
+ Works on both new and existing repos
437
+ -npm <values> Comma-separated list of npm options:
438
+ private (default) | public — package visibility
439
+ ts — keep .ts source (and *.map,
440
+ tsconfig.json) in npm tarball
441
+ nts — exclude .ts source
442
+ (default for non-noEmit projects)
443
+ Example: -npm public,ts
444
+ ```
445
+
446
+ `ts` is already the default on git (source is tracked). Pass `-npm ts` to ship
447
+ the same files to npm useful for debugging installed packages or "source on
448
+ demand" packages. `noEmit` projects automatically ship `.ts` files (they *are*
449
+ the runtime); pass `-npm nts` to override. `allowTs` persists to
450
+ `.globalize.json5`.
451
+
452
+ ### Workspace Options
453
+ ```
454
+ -w, -workspace <pkg> Filter to specific package (repeatable)
455
+ -no-workspace Disable workspace mode at a workspace root
456
+ -continue-on-error Continue if a package fails in workspace mode
457
+ ```
458
+
459
+ Workspace mode is auto-detected when run from a root with `"private": true` and a `workspaces` field.
460
+
461
+ ### Other Options
462
+ ```
463
+ -init Initialize git/npm if needed (creates .gitignore, .npmignore,
464
+ .gitattributes, and configures git for LF line endings).
465
+ If package.json.repository.url is reachable, adopts its
466
+ history instead of creating a fresh repo.
467
+ -adopt Strict adopt: require a reachable git remote in
468
+ package.json.repository. Abort if probe fails. Skips prompt.
469
+ -strict-imports, -import-check
470
+ Opt in to scanning .ts/.js source for imports of packages not
471
+ declared in any dependencies bucket. Off by default — the
472
+ always-declare style this enforces doesn't hold in monorepos
473
+ that rely on workspace cross-refs or the -public-deps cascade
474
+ (those produce noisy prompts that get dismissed reflexively,
475
+ which defeats the safety purpose). Use only on packages where
476
+ every import is meant to be a direct package.json declaration.
477
+ When it does fire, it catches silent runtime failures
478
+ ERR_MODULE_NOT_FOUND on a clean install of a published tarball
479
+ that was resolving via an ambient parent/global node_modules
480
+ at dev time.
481
+ -force Continue despite git errors
482
+ -dry-run Preview what would happen
483
+ -quiet Suppress npm warnings (default)
484
+ -verbose Show detailed output
485
+ -conform Update .gitignore/.npmignore/.gitattributes to best practices
486
+ and configure git for LF line endings (fixes existing repos)
487
+ For noEmit projects: removes *.ts/*.map/tsconfig.json from .npmignore
488
+ -asis Skip ignore file checks (or set "asis": true in .globalize.json5)
489
+ -fix-tags Automatically fix version/tag mismatches
490
+ -rebase Automatically rebase if local is behind remote
491
+ -clean-nested-modules, -clean-nested
492
+ Before npm pack, wipe node_modules/ inside each file: dep
493
+ target. Fixes arborist "Cannot read properties of null"
494
+ crashes caused by sibling file: deps with nested
495
+ node_modules. Suggested automatically when the error hits.
496
+ -ts7-report, -deprecation-report
497
+ Report-only: scan this package and its file: deps for
498
+ compilerOptions removed in TypeScript 7 and list a migration
499
+ to-do. Writes nothing.
500
+ -tsfix, -ts7-fix
501
+ One-off utility, NOT part of the main flow: apply the TS7
502
+ tsconfig migration to a package (and its file: deps) and exit —
503
+ no build, no commit, no push, no publish. Intended as a
504
+ temporary tool for fixing a local/subdirectory tsconfig in
505
+ place. The normal release flow already applies the same
506
+ migration automatically when a build hits a TS7 deprecation
507
+ error, so day-to-day you never need this flag.
508
+ npmglobalize <path> -tsfix
509
+ -show Show package.json dependency changes
510
+ -package, -pkg Update package.json scripts to use npmglobalize (see below)
511
+ -h, -help Show help
512
+ -v, -version Show version
513
+ ```
514
+
515
+ ## Using in package.json
516
+
517
+ You can wire npmglobalize into your package.json `scripts` so that `npm run release` handles publishing:
518
+
519
+ ```json
520
+ {
521
+ "scripts": {
522
+ "release": "npmglobalize"
523
+ }
524
+ }
525
+ ```
526
+
527
+ The `--package` (`-pkg`) flag does this automatically — it adds a `release` script (renaming any existing `release`/`installer` scripts to `old-release`/`old-installer`):
528
+
529
+ ```bash
530
+ npmglobalize --package # Sets up "release": "npmglobalize" in package.json
531
+ ```
532
+
533
+ After that, publishing is just:
534
+ ```bash
535
+ npm run release # Same as running npmglobalize directly
536
+ npm run release -- --minor # Pass flags through
537
+ ```
538
+
539
+ You can also combine it with `.globalize.json5` for persistent options so `npm run release` always uses your preferred settings (install, visibility, etc.).
540
+
541
+ ## Configuration File
542
+
543
+ Settings can be saved in `.globalize.json5`:
544
+
545
+ ```json5
546
+ {
547
+ // npmglobalize configuration (JSON5 format)
548
+ "bump": "patch", // Version bump type
549
+ "install": true, // Auto-install globally
550
+ "wsl": false, // Also install in WSL
551
+ "fix": true, // Auto-run npm audit fix
552
+ "verbose": false, // Show detailed output
553
+ "gitVisibility": "private",
554
+ "npmVisibility": "public",
555
+ "usePaths": true // Resolve file: deps from sibling checkouts (see below)
556
+ }
557
+ ```
558
+
559
+ Configuration persists across runs. CLI flags override config file.
560
+
561
+ ### `usePaths` Standalone packages
562
+
563
+ Default: `true`. Set to `false` (or pass `-no-use-paths` / `-nup`) to mark a
564
+ package as **standalone** — one that should be publishable/installable on a
565
+ machine that does not have sibling `file:` dep checkouts available. Example:
566
+ a backup/recovery utility you want to `npm install -g` on any host.
567
+
568
+ Currently this setting is **declarative** — it is parsed, persisted to
569
+ `.globalize.json5`, and surfaced in the settings banner, but the tool does
570
+ not yet change its behavior based on it. Behavior wiring (e.g. resolving
571
+ `file:` deps to the latest published npm version instead of walking siblings)
572
+ is planned.
573
+
574
+ ### `upstream`who consumes this package
575
+
576
+ **Experimental.** Bookkeeping, not a setting. When a package with `file:` deps
577
+ publishes, npmglobalize appends an entry to each **dependency's**
578
+ `.globalize.json5`:
579
+
580
+ ```json5
581
+ {
582
+ "install": true,
583
+
584
+ // FYI: packages that depend on this one (immediate consumers,
585
+ // recorded when each of them publishes). Nothing is updated
586
+ // automatically; follow each path's own .globalize.json5 to
587
+ // walk further out.
588
+ "upstream": [
589
+ {"path":"Y:\\dev\\utils\\winpos","version":"2.0.51","updated":"2026-08-09"},
590
+ ],
591
+ }
592
+ ```
593
+
594
+ So publishing `winpos` (which has `"@bobfrankston/msger": "file:../msgx/msger"`)
595
+ records winpos in **msger's** config. Each entry carries the consumer's
596
+ checkout path, its version at the time, and the date.
597
+
598
+ Only **immediate** consumers are recorded. A full consumer tree is a walk, not
599
+ a copy: follow each entry's path and read that package's own `upstream` list.
600
+ That keeps each file small and self-maintaining — no package has to know about
601
+ anything beyond its own direct consumers.
602
+
603
+ The list is **FYI** — it is recorded, preserved across publishes, and printed
604
+ in the Release Summary of the package that owns it. Nothing is rebuilt,
605
+ republished, or reinstalled on its behalf. An entry appears the first time a
606
+ consumer publishes, and is refreshed in place on every publish after that.
607
+
608
+ #### Scope and limits
609
+
610
+ **`file:` deps only.** A dependency referenced by npm version (`"^0.1.39"`)
611
+ is never recorded, because npmglobalize has no checkout path for it it
612
+ consumes the published tarball, not a sibling directory. To have a consumer
613
+ show up in a library's list, that consumer must reference it as
614
+ `file:../<lib>`.
615
+
616
+ **Usually not committed.** The entry is written into the dependency's own
617
+ checkout, and npmglobalize then tries to commit it there
618
+ (`Record upstream <consumer>@<version>`) and push if that repo has a remote.
619
+ In practice that commit is usually skipped: `.globalize.json5` is in the
620
+ standard ignore template, so most repos ignore it and the entry stays as
621
+ untracked local state. That is a reasonable failure mode for an experimental
622
+ mechanism the list rewrites itself on every publish, so there is nothing to
623
+ merge and nothing to reconcile between machines. It also means the list is
624
+ **per-machine**, not shared history.
625
+
626
+ When the commit does happen (a repo that tracks its `.globalize.json5`, as
627
+ npmglobalize itself does), only that one file is staged and committed by
628
+ pathspecanything else the dependency had staged or modified is left exactly
629
+ as it was. A dependency that isn't a git repo is written and skipped the same
630
+ way.
631
+
632
+ The alternative would be recording this in `package.json`, which is tracked
633
+ and published every consumer of a library would then download that library's
634
+ list of local checkout paths in its tarball. Keeping it in `.globalize.json5`
635
+ keeps it out of the package entirely.
636
+
637
+ ## Common Workflows
638
+
639
+ ### Standard Release
640
+ ```bash
641
+ npmglobalize --install # Publish + install globally
642
+ ```
643
+
644
+ ### Release with Dependency Chain
645
+ ```bash
646
+ cd my-app # Has file: deps
647
+ npmglobalize # Publishes all deps automatically
648
+ ```
649
+
650
+ ### Safe Dependency Updates
651
+ ```bash
652
+ npmglobalize --update-deps # Update to latest safe versions
653
+ ```
654
+
655
+ ### Security Fixes
656
+ ```bash
657
+ npmglobalize --fix # Fix vulnerabilities + release
658
+ ```
659
+
660
+ ### Force Update Everything
661
+ ```bash
662
+ npmglobalize --force-publish --update-major
663
+ ```
664
+
665
+ ### Preview Changes
666
+ ```bash
667
+ npmglobalize --dry-run # See what would happen
668
+ ```
669
+
670
+ ## How It Works
671
+
672
+ 1. **Validates** package.json and git status
673
+ 2. **Checks** if current version is on npm (recovers from failed publishes)
674
+ 3. **Updates dependencies** (if `--update-deps`)
675
+ 4. **Builds `file:` deps in topological order**, then the target itself, so consumers' `tsc` reads up-to-date `.d.ts` from sibling checkouts whose source has changed (see [Build Cascade](#build-cascade))
676
+ 5. **Publishes file: dependencies** (if needed)
677
+ 6. **Backs up** original file: references to `.dependencies`
678
+ 7. **Converts** `file:` npm version references
679
+ 8. **Commits** changes
680
+ 9. **Bumps** version (using npm version) — skipped if recovering a failed publish
681
+ 10. **Publishes** to npm
682
+ 11. **Pushes** to git (with push-protection detection and auto-bypass)
683
+ 12. **Installs** globally (if `--install`)
684
+ 13. **Restores** file: references (if `--files`, default)
685
+ 14. **Runs audit** (shows security status)
686
+
687
+ ## Operational Details
688
+
689
+ ### Build Cascade
690
+
691
+ Before transforming or publishing anything, `npmglobalize` builds `file:` dependencies in topological order — deps before consumers — and then builds the target itself. This guarantees the target's `tsc` reads up-to-date `.d.ts` and `.js` from sibling checkouts even when a dep's source has changed since its last build.
692
+
693
+ For each project visited (the target and every transitive `file:` dep):
694
+
695
+ - If `tsconfig.json` is missing or has `"noEmit": true` → **skip** (not a TypeScript build), unless the `build` script runs `importgen` — a plain-JS browser app still needs its import map regenerated.
696
+ - If `tsconfig.json` exists but `package.json` has no `"build"` script → **prompt** to add `"build": "tsc"` (plus a `tsc -p <dir>` per sub-project — see below). Decline and that project is skipped.
697
+ - Otherwise run `npm run build`. A failure halts the cascade unless `-force` is passed.
698
+
699
+ Cycle-safe via a shared visited set; each project is built at most once per run.
700
+
701
+ This complements the existing publish cascade (which ensures version refs are correct) by closing the build-freshness gap that `npm install` alone left open.
702
+
703
+ #### Import maps (`importgen`) as a build step
704
+
705
+ Browser projects that use [`importgen`](https://www.npmjs.com/package/@bobfrankston/importgen) have historically regenerated their import map from `.vscode/tasks.json`, which only runs when VS Code opens the folder — so a command-line or CI build could publish a stale map. `npmglobalize` treats the import map as a build product and moves the step into the package's own `build` script, where every build path picks it up.
706
+
707
+ Before building each project, it checks whether the project is an importgen project, in this order:
708
+
709
+ 1. `.vscode/tasks.json` has a task whose `command` is `importgen` (the HTML file, if the task names one, is reused).
710
+ 2. A root-level `.htm`/`.html` file already contains a `<script type="importmap">` block (`index.html`, `default.html`, `default.htm` are checked first, so a stray `temp.htm` doesn't win).
711
+ 3. `importgen` is in `dependencies`/`devDependencies` **and** the project has one of those HTML files the HTML requirement keeps packages that merely *use* importgen as a library from matching.
712
+
713
+ If a signal matches and the `build` script doesn't already run `importgen`, you're prompted to rewrite it e.g. `"build": "tsc"` → `"build": "importgen default.htm && tsc"`. The HTML file is named explicitly so importgen doesn't have to guess. Decline and `"importgen": false` is written to `.globalize.json5`, which suppresses the prompt for good; `-noimportgen` does the same from the command line.
714
+
715
+ Once wired, the freshness check gains a second condition: a project whose build runs importgen is only considered up to date if the generated HTML is at least as new as `package.json`. Adding a dependency changes the import map without touching any `.ts` file, which the source-vs-output comparison alone would miss.
716
+
717
+ #### Sub-projects (a second `tsconfig.json` in a sub-directory)
718
+
719
+ A package can hold more than one TypeScript project. The common shape is a service worker in `Sw/` with its own `tsconfig.json` (`lib: ["WebWorker"]`, its own `outDir`) that the root `tsconfig.json` lists under `exclude` — so a bare `"build": "tsc"` compiles everything *except* the service worker, and the stale `sw2.js` ships. As with import maps, these have historically been built only by a second `.vscode/tasks.json` watcher, which runs on folder open and nowhere else.
720
+
721
+ Before building, `npmglobalize` looks for sub-projects, in this order:
722
+
723
+ 1. `.vscode/tasks.json` has a task that runs `tsc` with `"options": { "cwd": "${workspaceFolder}/Sw" }` or with `-p`/`--project` naming a sub-directory (authoritative — it's how the project is actually built today; the task's `label` is quoted back in the prompt).
724
+ 2. An immediate sub-directory containing its own `tsconfig.json`. Build output and vendored trees (`node_modules`, `prev`, `dist`, `built`, `out`, `wwwroot`, `coverage`, `temp`, `preflight`, dot-directories) are never scanned.
725
+
726
+ Nothing is detected when the root `tsconfig.json` uses project `references` — that build graph belongs to `tsc -b` and isn't second-guessed.
727
+
728
+ When the `build` script is `tsc`-driven and doesn't already compile a detected sub-project, you're prompted to append it — `"build": "tsc"` → `"build": "tsc && tsc -p Sw"`, or for an importgen project `"build": "importgen default.htm && tsc && tsc -p Sw"`. Already-wired scripts are recognized in any of their spellings (`tsc -p Sw`, `tsc --project ./Sw/tsconfig.json`, `cd Sw && tsc`), and the directory is emitted with its real on-disk casing so the script still works on WSL and CI. Decline and `"subProjects": false` is written to `.globalize.json5`, suppressing the prompt for good; a `"subProjects": ["Sw"]` array pins the list instead of detecting it.
729
+
730
+ Sub-projects the build actually compiles are also folded into the freshness check — the package rebuilds when a sub-project's sources are newer than its output. A sub-project that emits *outside* its own directory (`"outDir": ".."`, the usual service-worker case) always reports stale: comparing its sources against the whole package would prove nothing.
731
+
732
+ #### TypeScript 6 `types` auto-fix
733
+
734
+ TypeScript 6 dropped the legacy behavior of auto-including every installed `@types/*` package. A `tsconfig.json` with no explicit `compilerOptions.types` then loses the Node globals (`process`, `Buffer`, ) and the build fails with `TS2591`.
735
+
736
+ Before each build, when the global `tsc` is version 6 or newer, `npmglobalize` patches the project's `tsconfig.json` to add an explicit `types` list enumerating the installed `@types/*` packages (e.g. `"types": ["node", …]`) restoring the old behavior. The edit is:
737
+
738
+ - **Conservative** only applied when `compilerOptions.types` is **absent**, `node_modules/@types/node` is actually installed, and there is no `extends` (whose merged `types` can't be seen). An explicit `types` you already set is never overridden.
739
+ - **Format-preserving** — the single `types` key is inserted into the existing `compilerOptions` block; comments, ordering, and indentation are left intact.
740
+ - **Idempotent** once the list is present, subsequent runs skip it.
741
+
742
+ If the patch can't be applied for some reason and the build still fails with `TS2591`, the failure summary prints a hint to add `"types": ["node"]` manually.
743
+
744
+ ### The `.dependencies` Backup (Internal/Transient)
745
+
746
+ **You should never see `.dependencies` in your `package.json` under normal operation.** It is a temporary internal backup that exists only during the brief publish cycle and is removed automatically when the cycle completes.
747
+
748
+ During publishing, `npmglobalize` temporarily replaces `file:` references with npm version strings. The original `file:` entries are stashed in `.dependencies` (and `.devDependencies`, etc.) so they can be restored afterward. Once the publish succeeds and `file:` paths are restored, `.dependencies` is deleted. A normal run leaves no trace of it.
749
+
750
+ **If you see `.dependencies` in your `package.json`, something went wrong** — the tool crashed, was killed, or the publish failed partway through. It is not a feature to rely on or edit manually.
751
+
752
+ **Recovery:**
753
+ - **Re-run `npmglobalize`**: It detects leftover `.dependencies`, restores the originals, and continues normally. Self-healing is automatic.
754
+ - **Manual restore**: `npmglobalize -cleanup` restores file: deps and removes the `.dependencies` backup.
755
+
756
+ **Why a persistent backup?** If the tool crashes hard (killed process, power failure, npm timeout), there's no cleanup code to run. The backup in `package.json` survives because it was written before the risky operations began. The next run self-heals.
757
+
758
+ ### Flag Conventions
759
+
760
+ Both `-flag` and `--flag` are accepted. Single-dash is the primary convention:
761
+ ```bash
762
+ npmglobalize -patch # same as --patch
763
+ npmglobalize -np # same as --nopublish
764
+ npmglobalize -local # same as --local
765
+ ```
766
+
767
+ ### Persistent vs One-Shot Flags
768
+
769
+ Some flags are **persisted** to `.globalize.json5` when set from the CLI:
770
+ - `-install`, `-link`, `-wsl`, `-files`, `-fix` — install/build preferences
771
+ - `-np` (noPublish) — once set, prevents accidental publishes
772
+ - `-local` — remembers "this project is local-only"
773
+ - `-git`/`-npm` visibility
774
+
775
+ Other flags are **one-shot** (never persisted):
776
+ - `-cleanup`, `-init`, `-dry-run`, `-message` — situational actions
777
+ - `-update-deps`, `-update-major`, `-force-publish` — explicit per-run choices
778
+ - `-conform`, `-asis` — one-time fixes
779
+
780
+ Use `-once` to prevent any flag from persisting on that run:
781
+ ```bash
782
+ npmglobalize -np -once # No-publish this run only, don't remember it
783
+ ```
784
+
785
+ ### Local Install (`-local`)
786
+
787
+ Skip all transform/publish logic and just run `npm install -g .` with `file:` deps as-is. Use this when you want to install a CLI tool locally for your own use without publishing anything:
788
+
789
+ ```bash
790
+ npmglobalize -local # Install globally from local directory
791
+ npmglobalize -local -wsl # Also install in WSL
792
+ ```
793
+
794
+ This is useful for:
795
+ - Development tools you don't publish
796
+ - Testing a CLI before publishing
797
+ - Projects with `file:` deps that should stay as-is
798
+
799
+ ### Private Packages
800
+
801
+ Packages with `"private": true` in `package.json` skip the npm publish step. Dependencies are still transformed and restored — the publish is the only thing skipped.
802
+
803
+ ## Version Checking
804
+
805
+ When publishing file: dependencies, checks if each version exists on npm:
806
+ - ✅ Exists → Skip, use existing version
807
+ - Missing Publish it first
808
+ - 🔄 Force → Use `--force-publish` to republish
809
+
810
+ ## Examples
811
+
812
+ ```bash
813
+ # Basic release
814
+ npmglobalize
815
+
816
+ # Run on a different project
817
+ npmglobalize y:\dev\myproject
818
+
819
+ # Auto-fix tag conflicts and rebase
820
+ npmglobalize -fix-tags -rebase
821
+
822
+ # Release with updates and security fixes
823
+ npmglobalize -update-deps -fix
824
+
825
+ # Just update package.json, don't publish
826
+ npmglobalize -np -update-deps
827
+
828
+ # Force republish all dependencies
829
+ npmglobalize -force-publish -update-major
830
+
831
+ # Release + install on Windows and WSL (from registry)
832
+ npmglobalize -install -wsl
833
+
834
+ # Release + link on Windows and WSL (symlink)
835
+ npmglobalize -link -wsl
836
+
837
+ # Install locally without publishing (file: deps stay as-is)
838
+ npmglobalize -local
839
+
840
+ # Restore original file: references
841
+ npmglobalize -cleanup
842
+
843
+ # Initialize git (adopt existing remote if reachable, else fresh) + release
844
+ npmglobalize -init
845
+
846
+ # Strict adopt: re-attach to remote in package.json.repository (or abort)
847
+ npmglobalize -adopt
848
+
849
+ # Migrate package.json scripts to use npmglobalize
850
+ npmglobalize -package
851
+
852
+ # Preview what would happen
853
+ npmglobalize -dry-run -verbose
854
+ ```
855
+
856
+ ## Authentication
857
+
858
+ Requires npm authentication:
859
+ ```bash
860
+ npm login
861
+ ```
862
+
863
+ Check authentication:
864
+ ```bash
865
+ npm whoami
866
+ ```
867
+
868
+ ## Development
869
+
870
+ ### Build Check
871
+
872
+ `npmglobalize` includes automatic build verification to ensure TypeScript files are compiled before execution. This prevents runtime errors from outdated JavaScript files.
873
+
874
+ **How it works:**
875
+ - When you run `npmglobalize`, it automatically checks if `.js` files are newer than their `.ts` sources
876
+ - If `.js` files are missing or outdated, execution stops with an error
877
+ - The check is skipped if `noEmit: true` is set in `tsconfig.json`
878
+
879
+ **Building the project:**
880
+ ```bash
881
+ npm run build # Compile TypeScript files
882
+ npm run watch # Watch mode for development
883
+ npm run check # Manually verify build status
884
+ ```
885
+
886
+ **Bypassing the check:**
887
+ ```bash
888
+ npmglobalize --force # Skip build check (not recommended)
889
+ ```
890
+
891
+ **Error example:**
892
+ ```
893
+ ❌ Error: TypeScript files not compiled
894
+ cli.js is older than cli.ts
895
+
896
+ Please run: npm run build
897
+ Or use --force to skip this check
898
+ ```
899
+
900
+ This ensures you never accidentally run outdated code when the TypeScript source has changed.
901
+
902
+ ## License
903
+
904
+ MIT