@bobfrankston/npmglobalize 1.0.188 → 1.0.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +13 -1
- package/lib.js +195 -60
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -8,6 +8,18 @@ import path from 'path';
|
|
|
8
8
|
import { styleText } from 'util';
|
|
9
9
|
// npmglobalize install directory (for --version)
|
|
10
10
|
const __dirname = import.meta.dirname;
|
|
11
|
+
/** Own version, read once. Stamped into the startup banner AND the Issues Summary
|
|
12
|
+
* so a failure log always reveals which installed npmglobalize produced it — a
|
|
13
|
+
* build that fails on something the source already fixes usually just means the
|
|
14
|
+
* installed copy predates the fix. */
|
|
15
|
+
const NPMGLOBALIZE_VERSION = (() => {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8')).version;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return 'unknown';
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
11
23
|
function printHelp() {
|
|
12
24
|
console.log(`
|
|
13
25
|
npmglobalize - Transform file: dependencies to npm versions for publishing
|
|
@@ -417,7 +429,7 @@ function printBuildSummary() {
|
|
|
417
429
|
if (issues.length === 0)
|
|
418
430
|
return;
|
|
419
431
|
console.log('');
|
|
420
|
-
console.log(styleText('yellow',
|
|
432
|
+
console.log(styleText('yellow', `━━━ Issues Summary (npmglobalize v${NPMGLOBALIZE_VERSION}) ━━━`));
|
|
421
433
|
for (const issue of issues) {
|
|
422
434
|
const icon = issue.severity === 'error' ? '✗' : '⚠';
|
|
423
435
|
console.log(styleText('yellow', ` ${icon} ${issue.module}: ${issue.message}`));
|
package/lib.js
CHANGED
|
@@ -1947,12 +1947,12 @@ function getTscMajor() {
|
|
|
1947
1947
|
catch { /* leave at 0 — can't probe, assume pre-6 (no patch) */ }
|
|
1948
1948
|
return _tscMajor;
|
|
1949
1949
|
}
|
|
1950
|
-
/** Insert
|
|
1950
|
+
/** Insert `"<key>": <valueLiteral>` as the first property of `compilerOptions`,
|
|
1951
1951
|
* preserving the file's existing formatting/comments. Returns the patched text,
|
|
1952
1952
|
* or null if the object couldn't be located safely (caller leaves file alone).
|
|
1953
|
-
* Only ever called when
|
|
1954
|
-
function
|
|
1955
|
-
const
|
|
1953
|
+
* Only ever called when the key is known to be absent. */
|
|
1954
|
+
function insertCompilerOption(text, key, valueLiteral) {
|
|
1955
|
+
const prop = `"${key}": ${valueLiteral}`;
|
|
1956
1956
|
const m = text.match(/"compilerOptions"\s*:\s*\{/);
|
|
1957
1957
|
if (!m)
|
|
1958
1958
|
return null;
|
|
@@ -1965,81 +1965,196 @@ function insertTypesIntoTsconfig(text, typesArr) {
|
|
|
1965
1965
|
const lineStart = text.lastIndexOf('\n', m.index) + 1;
|
|
1966
1966
|
const baseIndent = text.slice(lineStart, m.index).match(/^\s*/)[0];
|
|
1967
1967
|
const propIndent = baseIndent + ' ';
|
|
1968
|
-
return text.slice(0, braceEnd) + '\n' + propIndent +
|
|
1968
|
+
return text.slice(0, braceEnd) + '\n' + propIndent + prop + '\n' + baseIndent + text.slice(braceEnd + ws.length);
|
|
1969
1969
|
}
|
|
1970
1970
|
// Multiline object: derive property indent from the existing first property.
|
|
1971
1971
|
if (ws.includes('\n')) {
|
|
1972
1972
|
const propIndent = ws.slice(ws.lastIndexOf('\n') + 1);
|
|
1973
|
-
return text.slice(0, braceEnd) + '\n' + propIndent +
|
|
1973
|
+
return text.slice(0, braceEnd) + '\n' + propIndent + prop + ',' + text.slice(braceEnd);
|
|
1974
1974
|
}
|
|
1975
1975
|
// Inline single-line object: `{ "target": ... }`
|
|
1976
|
-
return text.slice(0, braceEnd) + `
|
|
1976
|
+
return text.slice(0, braceEnd) + ` ${prop},` + text.slice(braceEnd);
|
|
1977
|
+
}
|
|
1978
|
+
/** Insert a `"types": [...]` line as the first property of `compilerOptions`.
|
|
1979
|
+
* Only ever called when `compilerOptions.types` is absent. */
|
|
1980
|
+
function insertTypesIntoTsconfig(text, typesArr) {
|
|
1981
|
+
return insertCompilerOption(text, 'types', '[' + typesArr.map(t => JSON.stringify(t)).join(', ') + ']');
|
|
1982
|
+
}
|
|
1983
|
+
/** `@types/*` packages that inject *global* declarations (usable with no
|
|
1984
|
+
* `import`). TS6 stopped auto-including these, so under TS6+ they must be named
|
|
1985
|
+
* in `compilerOptions.types`. Module-only types (`express`, `ws`, `nodemailer`,
|
|
1986
|
+
* ...) resolve through ordinary import resolution and never need listing — so we
|
|
1987
|
+
* deliberately do NOT enumerate every installed `@types/*`. `node` is always
|
|
1988
|
+
* added; the rest below are added only when actually installed. */
|
|
1989
|
+
const GLOBAL_AUGMENTING_TYPES = ['jest', 'mocha', 'jasmine', 'bun'];
|
|
1990
|
+
/** True if `@types/<name>` is resolvable from `start` or any ancestor's
|
|
1991
|
+
* `node_modules` — handles npm-workspace hoisting where `@types` live at the
|
|
1992
|
+
* monorepo root rather than the leaf package. */
|
|
1993
|
+
function atTypesInstalled(start, name) {
|
|
1994
|
+
let dir = path.resolve(start);
|
|
1995
|
+
for (;;) {
|
|
1996
|
+
if (fs.existsSync(path.join(dir, 'node_modules', '@types', name)))
|
|
1997
|
+
return true;
|
|
1998
|
+
const parent = path.dirname(dir);
|
|
1999
|
+
if (parent === dir)
|
|
2000
|
+
return false;
|
|
2001
|
+
dir = parent;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
/** Resolve a tsconfig's `extends` chain into an ordered list (the file itself
|
|
2005
|
+
* first, then each base it extends). Local (non-`node_modules`) entries are
|
|
2006
|
+
* marked `writable`. `truncated` is true when an `extends` could not be read or
|
|
2007
|
+
* resolved (e.g. a package specifier or an array) — the caller then can't be
|
|
2008
|
+
* sure an inherited `types` doesn't exist, so it stays its hand. Cycle- and
|
|
2009
|
+
* depth-guarded. */
|
|
2010
|
+
function resolveTsconfigChain(tsconfigPath) {
|
|
2011
|
+
const chain = [];
|
|
2012
|
+
const seen = new Set();
|
|
2013
|
+
let current = path.resolve(tsconfigPath);
|
|
2014
|
+
let truncated = false;
|
|
2015
|
+
for (let depth = 0; current && !seen.has(current) && depth < 16; depth++) {
|
|
2016
|
+
seen.add(current);
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON5.parse(fs.readFileSync(current, 'utf-8'));
|
|
2020
|
+
}
|
|
2021
|
+
catch {
|
|
2022
|
+
truncated = true;
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
chain.push({ path: current, parsed, writable: !/[\\/]node_modules[\\/]/.test(current) });
|
|
2026
|
+
const ext = parsed?.extends;
|
|
2027
|
+
if (ext === undefined)
|
|
2028
|
+
break; // natural end of chain
|
|
2029
|
+
if (typeof ext !== 'string') {
|
|
2030
|
+
truncated = true;
|
|
2031
|
+
break;
|
|
2032
|
+
} // array/object — not followed
|
|
2033
|
+
// Resolve relative to the current file's directory. A bare/extensionless
|
|
2034
|
+
// value may name a directory (→ tsconfig.json) or an extensionless file.
|
|
2035
|
+
const baseDir = path.dirname(current);
|
|
2036
|
+
let next;
|
|
2037
|
+
if (ext.endsWith('.json'))
|
|
2038
|
+
next = path.resolve(baseDir, ext);
|
|
2039
|
+
else {
|
|
2040
|
+
const asDir = path.resolve(baseDir, ext, 'tsconfig.json');
|
|
2041
|
+
next = fs.existsSync(asDir) ? asDir : path.resolve(baseDir, ext + '.json');
|
|
2042
|
+
}
|
|
2043
|
+
if (!fs.existsSync(next)) {
|
|
2044
|
+
truncated = true;
|
|
2045
|
+
break;
|
|
2046
|
+
} // e.g. a node_modules package specifier
|
|
2047
|
+
current = next;
|
|
2048
|
+
}
|
|
2049
|
+
return { chain, truncated };
|
|
1977
2050
|
}
|
|
1978
2051
|
/** TypeScript 6 dropped the legacy behavior of auto-including every installed
|
|
1979
|
-
* `@types/*` package. A tsconfig with no explicit `compilerOptions.types`
|
|
1980
|
-
* loses the Node globals (`process`, `Buffer`, ...), breaking the build with
|
|
1981
|
-
* `TS2591`. When building under TS6+, add an explicit `types` list
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
*
|
|
2052
|
+
* `@types/*` package globally. A tsconfig with no explicit `compilerOptions.types`
|
|
2053
|
+
* then loses the Node globals (`process`, `Buffer`, ...), breaking the build with
|
|
2054
|
+
* `TS2591`. When building under TS6+, add an explicit, *minimal* `types` list
|
|
2055
|
+
* (Node plus any installed global-augmenting test types) to restore them.
|
|
2056
|
+
*
|
|
2057
|
+
* Generalized for monorepos: it resolves the full `extends` chain, respects a
|
|
2058
|
+
* `types` set anywhere in that chain, finds `@types/node` even when hoisted to a
|
|
2059
|
+
* workspace-root `node_modules`, and writes the patch to the *base-most writable*
|
|
2060
|
+
* config so one edit fixes every package that shares the base (falling back to
|
|
2061
|
+
* the leaf config when the base lives in `node_modules`). Idempotent and
|
|
2062
|
+
* conservative — bails when the chain can't be fully resolved (an inherited
|
|
2063
|
+
* `types` might be hiding) or when `@types/node` isn't installed. */
|
|
1985
2064
|
function ensureTsconfigNodeTypes(cwd) {
|
|
1986
2065
|
if (getTscMajor() < 6)
|
|
1987
2066
|
return;
|
|
1988
2067
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1989
|
-
|
|
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')
|
|
2068
|
+
if (!fs.existsSync(tsconfigPath))
|
|
2005
2069
|
return;
|
|
2006
|
-
|
|
2007
|
-
if (
|
|
2070
|
+
const { chain, truncated } = resolveTsconfigChain(tsconfigPath);
|
|
2071
|
+
if (!chain.length)
|
|
2008
2072
|
return;
|
|
2009
|
-
//
|
|
2010
|
-
if (
|
|
2073
|
+
// Respect an explicit `types` anywhere in the resolved chain.
|
|
2074
|
+
if (chain.some(c => c.parsed?.compilerOptions?.types !== undefined))
|
|
2011
2075
|
return;
|
|
2012
|
-
//
|
|
2013
|
-
|
|
2014
|
-
const typesDir = path.join(cwd, 'node_modules', '@types');
|
|
2015
|
-
if (!fs.existsSync(path.join(typesDir, 'node')))
|
|
2076
|
+
// Couldn't fully resolve the chain → a hidden base may set `types`; don't risk it.
|
|
2077
|
+
if (truncated)
|
|
2016
2078
|
return;
|
|
2017
|
-
|
|
2018
|
-
|
|
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)
|
|
2079
|
+
// Only genuine Node projects, incl. workspace-hoisted @types at the repo root.
|
|
2080
|
+
if (!atTypesInstalled(cwd, 'node'))
|
|
2027
2081
|
return;
|
|
2028
|
-
//
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2082
|
+
// Minimal correct list: Node globals + any installed global-augmenting types.
|
|
2083
|
+
const typesArr = ['node', ...GLOBAL_AUGMENTING_TYPES.filter(n => atTypesInstalled(cwd, n))];
|
|
2084
|
+
// Prefer the base-most writable config (DRY: one edit fixes the whole workspace),
|
|
2085
|
+
// then walk toward the leaf, patching the first whose compilerOptions we can edit.
|
|
2086
|
+
const candidates = chain.filter(c => c.writable).reverse();
|
|
2087
|
+
for (const cand of candidates) {
|
|
2088
|
+
let text;
|
|
2089
|
+
try {
|
|
2090
|
+
text = fs.readFileSync(cand.path, 'utf-8');
|
|
2091
|
+
}
|
|
2092
|
+
catch {
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
const patched = insertTypesIntoTsconfig(text, typesArr);
|
|
2096
|
+
if (!patched)
|
|
2097
|
+
continue;
|
|
2098
|
+
try {
|
|
2099
|
+
fs.writeFileSync(cand.path, patched);
|
|
2100
|
+
const where = path.relative(cwd, cand.path) || 'tsconfig.json';
|
|
2101
|
+
console.log(colors.cyan(` Patched ${where}: added "types": [${typesArr.join(', ')}] (TypeScript ${_tscMajor}+ no longer auto-includes @types/*)`));
|
|
2102
|
+
}
|
|
2103
|
+
catch (error) {
|
|
2104
|
+
console.error(colors.yellow(` Could not write tsconfig patch (${cand.path}): ${error.message}`));
|
|
2105
|
+
}
|
|
2034
2106
|
return;
|
|
2035
2107
|
}
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2108
|
+
recordBuildIssue(chain[0].parsed?.name || path.basename(cwd), 'warning', `TS6+ build may fail: no writable compilerOptions block in the tsconfig chain of ${cwd} to add "types"`);
|
|
2109
|
+
}
|
|
2110
|
+
/** TS6 turned several long-deprecated compiler options (e.g.
|
|
2111
|
+
* `moduleResolution: "node10"`/`"node"`/`"classic"`, old `target`s) into hard
|
|
2112
|
+
* errors (`TS5107`/`TS5101`) that will be *removed* in TS7. TypeScript's own
|
|
2113
|
+
* sanctioned bridge for the 6→7 window is `"ignoreDeprecations": "<version>"`
|
|
2114
|
+
* (the error text names the version, e.g. `"6.0"`). We apply exactly that so the
|
|
2115
|
+
* build proceeds, and record a warning so the real migration (to `node16` /
|
|
2116
|
+
* `nodenext` / `bundler`) still happens before TS7. We do NOT auto-rewrite
|
|
2117
|
+
* `moduleResolution` itself — that changes resolution semantics and must be a
|
|
2118
|
+
* human, per-package decision. Returns true if it patched a file. Mirrors
|
|
2119
|
+
* `ensureTsconfigNodeTypes`: resolves the `extends` chain, respects an existing
|
|
2120
|
+
* `ignoreDeprecations`, and patches the base-most writable config. */
|
|
2121
|
+
function ensureTsconfigIgnoreDeprecations(cwd, version) {
|
|
2122
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
2123
|
+
if (!fs.existsSync(tsconfigPath))
|
|
2124
|
+
return false;
|
|
2125
|
+
const { chain, truncated } = resolveTsconfigChain(tsconfigPath);
|
|
2126
|
+
if (!chain.length)
|
|
2127
|
+
return false;
|
|
2128
|
+
// Already set somewhere in the chain (possibly with a different version) — leave it.
|
|
2129
|
+
if (chain.some(c => c.parsed?.compilerOptions?.ignoreDeprecations !== undefined))
|
|
2130
|
+
return false;
|
|
2131
|
+
if (truncated)
|
|
2132
|
+
return false;
|
|
2133
|
+
const valueLiteral = JSON.stringify(version);
|
|
2134
|
+
const candidates = chain.filter(c => c.writable).reverse();
|
|
2135
|
+
for (const cand of candidates) {
|
|
2136
|
+
let text;
|
|
2137
|
+
try {
|
|
2138
|
+
text = fs.readFileSync(cand.path, 'utf-8');
|
|
2139
|
+
}
|
|
2140
|
+
catch {
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
const patched = insertCompilerOption(text, 'ignoreDeprecations', valueLiteral);
|
|
2144
|
+
if (!patched)
|
|
2145
|
+
continue;
|
|
2146
|
+
try {
|
|
2147
|
+
fs.writeFileSync(cand.path, patched);
|
|
2148
|
+
const where = path.relative(cwd, cand.path) || 'tsconfig.json';
|
|
2149
|
+
console.log(colors.cyan(` Patched ${where}: added "ignoreDeprecations": ${valueLiteral} (silences TS6 removed-in-TS7 deprecation errors)`));
|
|
2150
|
+
recordBuildIssue(chain[0].parsed?.name || path.basename(cwd), 'warning', `Deprecated compilerOptions silenced via ignoreDeprecations=${version}. Migrate moduleResolution to "node16"/"nodenext"/"bundler" before TypeScript 7.`);
|
|
2151
|
+
return true;
|
|
2152
|
+
}
|
|
2153
|
+
catch (error) {
|
|
2154
|
+
console.error(colors.yellow(` Could not write tsconfig patch (${cand.path}): ${error.message}`));
|
|
2155
|
+
}
|
|
2042
2156
|
}
|
|
2157
|
+
return false;
|
|
2043
2158
|
}
|
|
2044
2159
|
/** Build a single project: detect tsconfig, prompt to add `build: tsc` if a
|
|
2045
2160
|
* TypeScript project lacks a build script, run `npm run build`, record
|
|
@@ -2075,7 +2190,17 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
2075
2190
|
}
|
|
2076
2191
|
ensureTsconfigNodeTypes(cwd);
|
|
2077
2192
|
console.log(`Building ${pkg.name || cwd}...`);
|
|
2078
|
-
|
|
2193
|
+
let buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
2194
|
+
if (!buildResult.success) {
|
|
2195
|
+
// TS6 deprecation error (removed-in-TS7): apply the version TS itself names
|
|
2196
|
+
// in `"ignoreDeprecations": "<v>"` and retry once.
|
|
2197
|
+
const out = (buildResult.stderr || '') + (buildResult.output || '');
|
|
2198
|
+
const dep = out.match(/error TS510[17]\b/) &&
|
|
2199
|
+
out.match(/['"]ignoreDeprecations['"]\s*:\s*['"]([\d.]+)['"]/);
|
|
2200
|
+
if (dep && ensureTsconfigIgnoreDeprecations(cwd, dep[1])) {
|
|
2201
|
+
buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2079
2204
|
if (buildResult.success) {
|
|
2080
2205
|
console.log(colors.green(`✓ Build succeeded (${pkg.name || path.basename(cwd)})`));
|
|
2081
2206
|
return true;
|
|
@@ -2433,6 +2558,16 @@ function diagnoseBuildFailure(errorText, cwd) {
|
|
|
2433
2558
|
if (!diagnosed && /error TS2591|Do you need to install type definitions for node/.test(errorText)) {
|
|
2434
2559
|
console.error(colors.yellow('\n Hint: TypeScript 6 no longer auto-includes @types/*, so Node globals (process, Buffer) are unresolved.'));
|
|
2435
2560
|
console.error(colors.yellow(` Fix: add "types": ["node"] to compilerOptions in ${path.join(cwd, 'tsconfig.json')}`));
|
|
2561
|
+
console.error(colors.yellow(' In a workspace this belongs in the shared base tsconfig the package extends (only global @types like node/jest need listing; imported types resolve on their own).'));
|
|
2562
|
+
diagnosed = true;
|
|
2563
|
+
}
|
|
2564
|
+
// Pattern: TS6 made deprecated options (moduleResolution=node10, ...) hard errors,
|
|
2565
|
+
// removed in TS7. (Safety net for when the auto ignoreDeprecations patch+retry didn't fire.)
|
|
2566
|
+
if (!diagnosed && /error TS510[17]\b/.test(errorText)) {
|
|
2567
|
+
const v = errorText.match(/['"]ignoreDeprecations['"]\s*:\s*['"]([\d.]+)['"]/);
|
|
2568
|
+
console.error(colors.yellow('\n Hint: TypeScript 6 flags long-deprecated options that TS7 will remove.'));
|
|
2569
|
+
console.error(colors.yellow(` Bridge: add "ignoreDeprecations": "${v ? v[1] : '6.0'}" to compilerOptions (shared base in a workspace).`));
|
|
2570
|
+
console.error(colors.yellow(' Real fix before TS7: migrate moduleResolution to "node16"/"nodenext"/"bundler".'));
|
|
2436
2571
|
diagnosed = true;
|
|
2437
2572
|
}
|
|
2438
2573
|
// Pattern: TypeScript compilation error
|