@bobfrankston/npmglobalize 1.0.185 → 1.0.187
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/README.md +12 -0
- package/lib.d.ts +1 -0
- package/lib.js +183 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -585,6 +585,18 @@ Cycle-safe via a shared visited set; each project is built at most once per run.
|
|
|
585
585
|
|
|
586
586
|
This complements the existing publish cascade (which ensures version refs are correct) by closing the build-freshness gap that `npm install` alone left open.
|
|
587
587
|
|
|
588
|
+
#### TypeScript 6 `types` auto-fix
|
|
589
|
+
|
|
590
|
+
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`.
|
|
591
|
+
|
|
592
|
+
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:
|
|
593
|
+
|
|
594
|
+
- **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.
|
|
595
|
+
- **Format-preserving** — the single `types` key is inserted into the existing `compilerOptions` block; comments, ordering, and indentation are left intact.
|
|
596
|
+
- **Idempotent** — once the list is present, subsequent runs skip it.
|
|
597
|
+
|
|
598
|
+
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.
|
|
599
|
+
|
|
588
600
|
### The `.dependencies` Backup (Internal/Transient)
|
|
589
601
|
|
|
590
602
|
**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.
|
package/lib.d.ts
CHANGED
|
@@ -296,6 +296,7 @@ export declare function parseVersionTag(tag: string): number[] | null;
|
|
|
296
296
|
export declare function compareVersions(a: number[], b: number[]): number;
|
|
297
297
|
/** Fix version/tag mismatches */
|
|
298
298
|
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
299
|
+
export declare function printPnpmSuggestionSummary(): void;
|
|
299
300
|
/** Return declared deps (dependencies + devDependencies) that don't resolve from
|
|
300
301
|
* `pkgDir`. Skips `workspace:`/`link:` specs (handled by workspace tooling /
|
|
301
302
|
* rarely used). `file:` deps are checked: npm installs them as junctions
|
package/lib.js
CHANGED
|
@@ -29,6 +29,7 @@ function spawnSafe(cmd, args, options = {}) {
|
|
|
29
29
|
return spawnSync(cmd, args, opts);
|
|
30
30
|
}
|
|
31
31
|
import readline from 'readline';
|
|
32
|
+
import { styleText } from 'util';
|
|
32
33
|
import libversion from 'libnpmversion';
|
|
33
34
|
import JSON5 from 'json5';
|
|
34
35
|
import { fileURLToPath } from 'url';
|
|
@@ -1666,6 +1667,59 @@ export function fixVersionTagMismatch(cwd, pkg, verbose = false) {
|
|
|
1666
1667
|
function sleepSync(ms) {
|
|
1667
1668
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1668
1669
|
}
|
|
1670
|
+
/** User-requested pnpm-eval hook (2026-05-14). The first local `npm install`
|
|
1671
|
+
* failure in a process prints a yellow-on-red banner and pauses 60s so the
|
|
1672
|
+
* user can read it. Every failure (first and subsequent) is captured in
|
|
1673
|
+
* `_pnpmFailureContexts` so `printPnpmSuggestionSummary()` can re-emit the
|
|
1674
|
+
* banner — listing all failure sites — at end-of-run, in case the live
|
|
1675
|
+
* banner scrolled past. Remove once pnpm-vs-npm decision is made. */
|
|
1676
|
+
let _pnpmSuggestionShown = false;
|
|
1677
|
+
const _pnpmFailureContexts = [];
|
|
1678
|
+
function _pnpmBanner(contexts) {
|
|
1679
|
+
const width = 76;
|
|
1680
|
+
const wrap = (s) => styleText(['yellow', 'bgRed', 'bold'], ' ' + s.padEnd(width - 2) + ' ');
|
|
1681
|
+
const body = [
|
|
1682
|
+
'',
|
|
1683
|
+
contexts.length > 1
|
|
1684
|
+
? `npm install FAILED -- ${contexts.length} sites -- consider trying pnpm`
|
|
1685
|
+
: 'npm install FAILED -- consider trying pnpm',
|
|
1686
|
+
'',
|
|
1687
|
+
];
|
|
1688
|
+
for (const c of contexts) {
|
|
1689
|
+
const label = ` - ${c}`;
|
|
1690
|
+
body.push(label.length > width - 2 ? label.slice(0, width - 5) + '...' : label);
|
|
1691
|
+
}
|
|
1692
|
+
body.push('', 'pnpm uses a content-addressed store with strict node_modules and', 'much faster installs. Commands mirror npm; great workspace support.', '', 'Try: npm i -g pnpm && pnpm import && pnpm install', '');
|
|
1693
|
+
return body.map(wrap);
|
|
1694
|
+
}
|
|
1695
|
+
async function suggestPnpmOnInstallFailure(context) {
|
|
1696
|
+
_pnpmFailureContexts.push(context);
|
|
1697
|
+
if (_pnpmSuggestionShown)
|
|
1698
|
+
return;
|
|
1699
|
+
_pnpmSuggestionShown = true;
|
|
1700
|
+
const lines = _pnpmBanner([context]);
|
|
1701
|
+
console.error('');
|
|
1702
|
+
for (const l of lines)
|
|
1703
|
+
console.error(l);
|
|
1704
|
+
console.error(styleText(['yellow', 'bgRed', 'bold'], ' ' + 'Pausing 60s so you can read this. Ctrl+C to abort.'.padEnd(74) + ' '));
|
|
1705
|
+
console.error('');
|
|
1706
|
+
await new Promise(resolve => setTimeout(resolve, 60_000));
|
|
1707
|
+
}
|
|
1708
|
+
/** Re-emit the pnpm banner at end-of-run if any `npm install` failed.
|
|
1709
|
+
* No-op when nothing failed. Safe to call from multiple summary paths —
|
|
1710
|
+
* guarded so the summary banner prints at most once per process. */
|
|
1711
|
+
let _pnpmSummaryPrinted = false;
|
|
1712
|
+
export function printPnpmSuggestionSummary() {
|
|
1713
|
+
if (_pnpmSummaryPrinted)
|
|
1714
|
+
return;
|
|
1715
|
+
if (_pnpmFailureContexts.length === 0)
|
|
1716
|
+
return;
|
|
1717
|
+
_pnpmSummaryPrinted = true;
|
|
1718
|
+
console.error('');
|
|
1719
|
+
for (const l of _pnpmBanner(_pnpmFailureContexts))
|
|
1720
|
+
console.error(l);
|
|
1721
|
+
console.error('');
|
|
1722
|
+
}
|
|
1669
1723
|
/** Wait for a package version to appear on the npm registry.
|
|
1670
1724
|
* First-time publishes (brand-new package name) take much longer to
|
|
1671
1725
|
* propagate than version bumps — npm has no cached metadata to update,
|
|
@@ -1828,6 +1882,7 @@ export async function ensureFileDepModules(cwd, verbose = false, visited = new S
|
|
|
1828
1882
|
console.error(colors.red(` ✗ npm install failed in ${abs}`));
|
|
1829
1883
|
if (r.stderr)
|
|
1830
1884
|
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1885
|
+
await suggestPnpmOnInstallFailure(`ensureFileDepModules (cwd): ${abs}`);
|
|
1831
1886
|
}
|
|
1832
1887
|
}
|
|
1833
1888
|
for (const key of ['dependencies', 'devDependencies']) {
|
|
@@ -1865,12 +1920,127 @@ export async function ensureFileDepModules(cwd, verbose = false, visited = new S
|
|
|
1865
1920
|
console.error(colors.red(` ✗ npm install failed in ${target}`));
|
|
1866
1921
|
if (r.stderr)
|
|
1867
1922
|
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1923
|
+
await suggestPnpmOnInstallFailure(`ensureFileDepModules (file: dep ${name}): ${target}`);
|
|
1868
1924
|
}
|
|
1869
1925
|
}
|
|
1870
1926
|
await ensureFileDepModules(target, verbose, visited);
|
|
1871
1927
|
}
|
|
1872
1928
|
}
|
|
1873
1929
|
}
|
|
1930
|
+
/** Cached major version of the global `tsc`. null = not yet probed,
|
|
1931
|
+
* 0 = probe failed / tsc not found. TypeScript 6 stopped auto-including
|
|
1932
|
+
* every `@types/*` package, so a tsconfig that relied on that needs an
|
|
1933
|
+
* explicit `types` list under TS6+. */
|
|
1934
|
+
let _tscMajor = null;
|
|
1935
|
+
/** Major version of the globally-installed `tsc`, or 0 if undeterminable.
|
|
1936
|
+
* Cached for the run. Needs shell:true on Windows to resolve `tsc.cmd`. */
|
|
1937
|
+
function getTscMajor() {
|
|
1938
|
+
if (_tscMajor !== null)
|
|
1939
|
+
return _tscMajor;
|
|
1940
|
+
_tscMajor = 0;
|
|
1941
|
+
try {
|
|
1942
|
+
const result = spawnSafe('tsc', ['--version'], { stdio: 'pipe', shell: true, env: process.env });
|
|
1943
|
+
const m = (result.stdout || '').match(/Version\s+(\d+)\./);
|
|
1944
|
+
if (m)
|
|
1945
|
+
_tscMajor = parseInt(m[1], 10);
|
|
1946
|
+
}
|
|
1947
|
+
catch { /* leave at 0 — can't probe, assume pre-6 (no patch) */ }
|
|
1948
|
+
return _tscMajor;
|
|
1949
|
+
}
|
|
1950
|
+
/** Insert a `"types": [...]` line as the first property of `compilerOptions`,
|
|
1951
|
+
* preserving the file's existing formatting/comments. Returns the patched text,
|
|
1952
|
+
* or null if the object couldn't be located safely (caller leaves file alone).
|
|
1953
|
+
* Only ever called when `compilerOptions.types` is absent. */
|
|
1954
|
+
function insertTypesIntoTsconfig(text, typesArr) {
|
|
1955
|
+
const arrLiteral = '[' + typesArr.map(t => JSON.stringify(t)).join(', ') + ']';
|
|
1956
|
+
const m = text.match(/"compilerOptions"\s*:\s*\{/);
|
|
1957
|
+
if (!m)
|
|
1958
|
+
return null;
|
|
1959
|
+
const braceEnd = m.index + m[0].length;
|
|
1960
|
+
const rest = text.slice(braceEnd);
|
|
1961
|
+
const ws = rest.match(/^\s*/)[0];
|
|
1962
|
+
// Empty object: `compilerOptions: {}` (or whitespace only before `}`)
|
|
1963
|
+
if (rest.slice(ws.length).startsWith('}')) {
|
|
1964
|
+
// Indent one level deeper than the `compilerOptions` line itself.
|
|
1965
|
+
const lineStart = text.lastIndexOf('\n', m.index) + 1;
|
|
1966
|
+
const baseIndent = text.slice(lineStart, m.index).match(/^\s*/)[0];
|
|
1967
|
+
const propIndent = baseIndent + ' ';
|
|
1968
|
+
return text.slice(0, braceEnd) + '\n' + propIndent + `"types": ${arrLiteral}\n` + baseIndent + text.slice(braceEnd + ws.length);
|
|
1969
|
+
}
|
|
1970
|
+
// Multiline object: derive property indent from the existing first property.
|
|
1971
|
+
if (ws.includes('\n')) {
|
|
1972
|
+
const propIndent = ws.slice(ws.lastIndexOf('\n') + 1);
|
|
1973
|
+
return text.slice(0, braceEnd) + '\n' + propIndent + `"types": ${arrLiteral},` + text.slice(braceEnd);
|
|
1974
|
+
}
|
|
1975
|
+
// Inline single-line object: `{ "target": ... }`
|
|
1976
|
+
return text.slice(0, braceEnd) + ` "types": ${arrLiteral},` + text.slice(braceEnd);
|
|
1977
|
+
}
|
|
1978
|
+
/** TypeScript 6 dropped the legacy behavior of auto-including every installed
|
|
1979
|
+
* `@types/*` package. A tsconfig with no explicit `compilerOptions.types` then
|
|
1980
|
+
* loses the Node globals (`process`, `Buffer`, ...), breaking the build with
|
|
1981
|
+
* `TS2591`. When building under TS6+, add an explicit `types` list enumerating
|
|
1982
|
+
* the installed `@types/*` (restoring the old behavior). Idempotent and
|
|
1983
|
+
* conservative — only patches when `types` is absent, `@types/node` is actually
|
|
1984
|
+
* installed, and there is no `extends` (whose merged `types` we can't see). */
|
|
1985
|
+
function ensureTsconfigNodeTypes(cwd) {
|
|
1986
|
+
if (getTscMajor() < 6)
|
|
1987
|
+
return;
|
|
1988
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1989
|
+
let text;
|
|
1990
|
+
try {
|
|
1991
|
+
text = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
1992
|
+
}
|
|
1993
|
+
catch {
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
let parsed;
|
|
1997
|
+
try {
|
|
1998
|
+
parsed = JSON5.parse(text);
|
|
1999
|
+
}
|
|
2000
|
+
catch {
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const co = parsed?.compilerOptions;
|
|
2004
|
+
if (!co || typeof co !== 'object')
|
|
2005
|
+
return;
|
|
2006
|
+
// Respect any explicit choice; never override a user-set `types`.
|
|
2007
|
+
if (co.types !== undefined)
|
|
2008
|
+
return;
|
|
2009
|
+
// `extends` may already supply `types`; a local array would override it, so leave it.
|
|
2010
|
+
if (parsed.extends !== undefined)
|
|
2011
|
+
return;
|
|
2012
|
+
// Gate on @types/node actually being installed so the list resolves and we
|
|
2013
|
+
// only touch genuine Node projects.
|
|
2014
|
+
const typesDir = path.join(cwd, 'node_modules', '@types');
|
|
2015
|
+
if (!fs.existsSync(path.join(typesDir, 'node')))
|
|
2016
|
+
return;
|
|
2017
|
+
let installed;
|
|
2018
|
+
try {
|
|
2019
|
+
installed = fs.readdirSync(typesDir, { withFileTypes: true })
|
|
2020
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
2021
|
+
.map(e => e.name);
|
|
2022
|
+
}
|
|
2023
|
+
catch {
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (!installed.length)
|
|
2027
|
+
return;
|
|
2028
|
+
// node first, rest sorted, for a stable readable list.
|
|
2029
|
+
installed.sort();
|
|
2030
|
+
const typesArr = ['node', ...installed.filter(n => n !== 'node')];
|
|
2031
|
+
const patched = insertTypesIntoTsconfig(text, typesArr);
|
|
2032
|
+
if (!patched) {
|
|
2033
|
+
recordBuildIssue(parsed.name || path.basename(cwd), 'warning', `TS6+ build may fail: couldn't locate compilerOptions in ${tsconfigPath} to add explicit "types"`);
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
fs.writeFileSync(tsconfigPath, patched);
|
|
2038
|
+
console.log(colors.cyan(` Patched tsconfig.json: added "types": [${typesArr.join(', ')}] (TypeScript ${_tscMajor}+ no longer auto-includes @types/*)`));
|
|
2039
|
+
}
|
|
2040
|
+
catch (error) {
|
|
2041
|
+
console.error(colors.yellow(` Could not write tsconfig.json patch: ${error.message}`));
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1874
2044
|
/** Build a single project: detect tsconfig, prompt to add `build: tsc` if a
|
|
1875
2045
|
* TypeScript project lacks a build script, run `npm run build`, record
|
|
1876
2046
|
* failures. Returns true if build succeeded (or was skipped because no
|
|
@@ -1903,6 +2073,7 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
1903
2073
|
return true;
|
|
1904
2074
|
}
|
|
1905
2075
|
}
|
|
2076
|
+
ensureTsconfigNodeTypes(cwd);
|
|
1906
2077
|
console.log(`Building ${pkg.name || cwd}...`);
|
|
1907
2078
|
const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
1908
2079
|
if (buildResult.success) {
|
|
@@ -1993,6 +2164,7 @@ export async function ensureWorkspaceDepModules(rootDir, members, verbose = fals
|
|
|
1993
2164
|
console.error(colors.red(` ✗ npm install failed in ${root}`));
|
|
1994
2165
|
if (r.stderr)
|
|
1995
2166
|
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
2167
|
+
await suggestPnpmOnInstallFailure(`ensureWorkspaceDepModules: ${root}`);
|
|
1996
2168
|
}
|
|
1997
2169
|
}
|
|
1998
2170
|
/** Run npm install -g with retries for registry propagation delay.
|
|
@@ -2256,6 +2428,13 @@ function diagnoseBuildFailure(errorText, cwd) {
|
|
|
2256
2428
|
console.error(colors.yellow(' Check file ownership and that no other process has files locked.'));
|
|
2257
2429
|
diagnosed = true;
|
|
2258
2430
|
}
|
|
2431
|
+
// Pattern: TS6 dropped @types/* auto-inclusion → Node globals missing.
|
|
2432
|
+
// (Safety net for when the proactive ensureTsconfigNodeTypes patch didn't fire.)
|
|
2433
|
+
if (!diagnosed && /error TS2591|Do you need to install type definitions for node/.test(errorText)) {
|
|
2434
|
+
console.error(colors.yellow('\n Hint: TypeScript 6 no longer auto-includes @types/*, so Node globals (process, Buffer) are unresolved.'));
|
|
2435
|
+
console.error(colors.yellow(` Fix: add "types": ["node"] to compilerOptions in ${path.join(cwd, 'tsconfig.json')}`));
|
|
2436
|
+
diagnosed = true;
|
|
2437
|
+
}
|
|
2259
2438
|
// Pattern: TypeScript compilation error
|
|
2260
2439
|
if (!diagnosed && /error TS\d+/.test(errorText)) {
|
|
2261
2440
|
console.error(colors.yellow('\n Hint: TypeScript compilation errors. Fix the type errors above before publishing.'));
|
|
@@ -4111,6 +4290,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
4111
4290
|
// workspaces[] array order; rewrite to topological order so deps build
|
|
4112
4291
|
// before consumers (avoids stale-.d.ts errors in cross-package imports).
|
|
4113
4292
|
const restoreWorkspaces = reorderWorkspacesForBuild(cwd, pkg, verbose);
|
|
4293
|
+
ensureTsconfigNodeTypes(cwd);
|
|
4114
4294
|
try {
|
|
4115
4295
|
// Always capture output so we can extract tsc errors for the summary
|
|
4116
4296
|
const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
@@ -4788,6 +4968,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
4788
4968
|
console.log(` ${colors.yellow('!')} ${s}`);
|
|
4789
4969
|
console.log(colors.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
4790
4970
|
console.log('');
|
|
4971
|
+
printPnpmSuggestionSummary();
|
|
4791
4972
|
return true;
|
|
4792
4973
|
}
|
|
4793
4974
|
// Skip if private
|
|
@@ -5901,6 +6082,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
5901
6082
|
}
|
|
5902
6083
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
5903
6084
|
console.log('');
|
|
6085
|
+
printPnpmSuggestionSummary();
|
|
5904
6086
|
// Only show "To use run" message if package provides commands (has bin field)
|
|
5905
6087
|
if (finalPkg.bin) {
|
|
5906
6088
|
const commandName = finalPkg.name.includes('/') ? finalPkg.name.split('/')[1] : finalPkg.name;
|
|
@@ -6254,6 +6436,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
|
|
|
6254
6436
|
}
|
|
6255
6437
|
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
6256
6438
|
console.log('');
|
|
6439
|
+
printPnpmSuggestionSummary();
|
|
6257
6440
|
// Global install of the workspace root (monorepo CLI)
|
|
6258
6441
|
const { install = false, link = false, wsl = false, dryRun = false, verbose = false } = options;
|
|
6259
6442
|
const rootPkgFinal = readPackageJson(rootDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.187",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@bobfrankston/freezepak": "^0.1.8",
|
|
35
|
-
"@bobfrankston/importgen": "^0.1.
|
|
36
|
-
"@bobfrankston/themecolors": "^0.1.
|
|
35
|
+
"@bobfrankston/importgen": "^0.1.37",
|
|
36
|
+
"@bobfrankston/themecolors": "^0.1.7",
|
|
37
37
|
"@bobfrankston/userconfig": "^1.0.9",
|
|
38
38
|
"@npmcli/package-json": "^7.0.4",
|
|
39
39
|
"json5": "^2.2.3",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
".transformedSnapshot": {
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@bobfrankston/freezepak": "^0.1.8",
|
|
63
|
-
"@bobfrankston/importgen": "^0.1.
|
|
64
|
-
"@bobfrankston/themecolors": "^0.1.
|
|
63
|
+
"@bobfrankston/importgen": "^0.1.37",
|
|
64
|
+
"@bobfrankston/themecolors": "^0.1.7",
|
|
65
65
|
"@bobfrankston/userconfig": "^1.0.9",
|
|
66
66
|
"@npmcli/package-json": "^7.0.4",
|
|
67
67
|
"json5": "^2.2.3",
|