@ttsc/metro 0.18.4 → 0.19.1
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 +2 -0
- package/lib/core/upstream.js +77 -5
- package/lib/core/upstream.js.map +1 -1
- package/lib/core/upstream.mjs +77 -5
- package/lib/core/upstream.mjs.map +1 -1
- package/package.json +3 -5
- package/src/core/upstream.ts +85 -5
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ module.exports = withTtsc(getDefaultConfig(__dirname));
|
|
|
41
41
|
|
|
42
42
|
`withTtsc` sets `transformer.babelTransformerPath` and leaves the rest of your config untouched. It auto-detects the upstream transformer to delegate to (`@expo/metro-config/babel-transformer` for Expo, then `@react-native/metro-babel-transformer`, then the legacy `metro-react-native-babel-transformer`).
|
|
43
43
|
|
|
44
|
+
Auto-detection only skips a candidate whose entry point is genuinely **not available** — the package is not installed, or it is installed but the requested subpath is not exported (Expo/React Native version skew). A candidate that _does_ resolve but fails while loading — a top-level throw, an incompatible runtime ABI, or a missing peer/transitive dependency — surfaces its original error (as the `cause` of a `@ttsc/metro` wrapper) instead of being treated as absent. This stops a broken Expo/React Native install from silently falling through to the wrong transformer, and stops an explicit `upstreamTransformer` failure from being reported as if the module did not exist.
|
|
45
|
+
|
|
44
46
|
## Configuration
|
|
45
47
|
|
|
46
48
|
By default `@ttsc/metro` finds the nearest `tsconfig.json` from the file being transformed and runs the plugins configured there: the standard `ttsc` model. If that is the config you want, `withTtsc(getDefaultConfig(__dirname))` is enough.
|
package/lib/core/upstream.js
CHANGED
|
@@ -33,14 +33,33 @@ const UPSTREAM_CANDIDATES = [
|
|
|
33
33
|
*/
|
|
34
34
|
function resolveUpstreamTransformer(customPath, load = tryRequire) {
|
|
35
35
|
if (customPath !== undefined && customPath.length !== 0) {
|
|
36
|
-
|
|
36
|
+
let upstream;
|
|
37
|
+
try {
|
|
38
|
+
upstream = load(customPath);
|
|
39
|
+
}
|
|
40
|
+
catch (cause) {
|
|
41
|
+
// The module resolves but failed while initializing (a top-level throw,
|
|
42
|
+
// a missing peer/transitive dependency, or a runtime-ABI rejection).
|
|
43
|
+
// Preserve the original diagnostic instead of masking it as absence.
|
|
44
|
+
throw new Error(`[@ttsc/metro] Failed to load the configured upstream transformer "${customPath}": ${errorMessage(cause)}`, { cause });
|
|
45
|
+
}
|
|
37
46
|
if (upstream === undefined) {
|
|
38
47
|
throw new Error(`[@ttsc/metro] Could not load the configured upstream transformer: ${customPath}`);
|
|
39
48
|
}
|
|
40
49
|
return upstream;
|
|
41
50
|
}
|
|
42
51
|
for (const candidate of UPSTREAM_CANDIDATES) {
|
|
43
|
-
|
|
52
|
+
let upstream;
|
|
53
|
+
try {
|
|
54
|
+
upstream = load(candidate);
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
// A candidate that resolves but throws while initializing is a broken
|
|
58
|
+
// installation of the active stack, not an absent optional peer. Surface
|
|
59
|
+
// it rather than silently falling through to a candidate that does not
|
|
60
|
+
// match this project.
|
|
61
|
+
throw new Error(`[@ttsc/metro] The upstream Metro transformer "${candidate}" is installed but failed to initialize: ${errorMessage(cause)}`, { cause });
|
|
62
|
+
}
|
|
44
63
|
if (upstream !== undefined) {
|
|
45
64
|
return upstream;
|
|
46
65
|
}
|
|
@@ -50,13 +69,66 @@ function resolveUpstreamTransformer(customPath, load = tryRequire) {
|
|
|
50
69
|
"(React Native), or set the `upstreamTransformer` option to an explicit " +
|
|
51
70
|
"module path.");
|
|
52
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Load an upstream transformer module, separating genuine absence from a broken
|
|
74
|
+
* installation.
|
|
75
|
+
*
|
|
76
|
+
* Resolution and execution are split deliberately. `require.resolve` only walks
|
|
77
|
+
* the module graph for the requested specifier; it never executes third-party
|
|
78
|
+
* code, so a failure there proves the requested candidate itself is not present
|
|
79
|
+
* — reported as `undefined` (absence) so automatic probing continues to the
|
|
80
|
+
* next optional peer. Once resolution succeeds, any error thrown by the actual
|
|
81
|
+
* `require` comes from executing the module body, including a missing peer or
|
|
82
|
+
* transitive dependency; that is a real initialization failure and is rethrown
|
|
83
|
+
* with its original message and stack so the caller can preserve it.
|
|
84
|
+
*/
|
|
53
85
|
function tryRequire(modulePath) {
|
|
54
86
|
try {
|
|
55
|
-
|
|
87
|
+
nodeRequire.resolve(modulePath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (isCandidateAbsent(error)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
// A resolution error that is not one of the known "entry point absent"
|
|
94
|
+
// codes (e.g. an invalid specifier) is not evidence of a plain absence;
|
|
95
|
+
// surface it rather than silently skipping the candidate.
|
|
96
|
+
throw error;
|
|
56
97
|
}
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
return nodeRequire(modulePath);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether a resolution error means the requested candidate's entry point is not
|
|
102
|
+
* available here — i.e. genuine absence, not a broken initialization.
|
|
103
|
+
*
|
|
104
|
+
* Resolution never executes the module body, so a `require.resolve` failure can
|
|
105
|
+
* only concern the requested specifier, never a transitive import of it. Each
|
|
106
|
+
* recognised code says the same thing about that specifier:
|
|
107
|
+
*
|
|
108
|
+
* - `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` — the package or file itself is
|
|
109
|
+
* not installed (CJS and ESM loaders respectively).
|
|
110
|
+
* - `ERR_PACKAGE_PATH_NOT_EXPORTED` — the package is installed but the requested
|
|
111
|
+
* subpath is not exported (or its export target is missing). This matters for
|
|
112
|
+
* the `@expo/metro-config/babel-transformer` candidate, a package subpath:
|
|
113
|
+
* under Expo/React Native version skew a present but non-exporting package
|
|
114
|
+
* must stay non-fatal so auto-detection falls through to the next candidate,
|
|
115
|
+
* exactly as a wholly absent package does.
|
|
116
|
+
*
|
|
117
|
+
* An error thrown later, while the resolved module executes, is a real
|
|
118
|
+
* initialization failure and is never routed here — the caller preserves it.
|
|
119
|
+
*/
|
|
120
|
+
function isCandidateAbsent(error) {
|
|
121
|
+
const code = error?.code;
|
|
122
|
+
return (code === "MODULE_NOT_FOUND" ||
|
|
123
|
+
code === "ERR_MODULE_NOT_FOUND" ||
|
|
124
|
+
code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
|
|
125
|
+
}
|
|
126
|
+
/** Best-effort message extraction for wrapping an unknown thrown value. */
|
|
127
|
+
function errorMessage(error) {
|
|
128
|
+
if (error instanceof Error) {
|
|
129
|
+
return error.message;
|
|
59
130
|
}
|
|
131
|
+
return String(error);
|
|
60
132
|
}
|
|
61
133
|
|
|
62
134
|
exports.UPSTREAM_CANDIDATES = UPSTREAM_CANDIDATES;
|
package/lib/core/upstream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstream.js","sources":["../../src/core/upstream.ts"],"sourcesContent":[null],"names":["createRequire"],"mappings":";;;;;AAEA,MAAM,WAAW,GAAGA,yBAAa,CAAC,kQAAe,CAAC;AAqBlD;;;;AAIG;AACI,MAAM,mBAAmB,GAAG;IACjC,sCAAsC;IACtC,uCAAuC;IACvC,sCAAsC;;AAGxC;;;;;;;;;;;;;;;;AAgBG;SACa,0BAA0B,CACxC,UAAmB,EACnB,OAAgE,UAAU,EAAA;IAE1E,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;AACvD,QAAA,
|
|
1
|
+
{"version":3,"file":"upstream.js","sources":["../../src/core/upstream.ts"],"sourcesContent":[null],"names":["createRequire"],"mappings":";;;;;AAEA,MAAM,WAAW,GAAGA,yBAAa,CAAC,kQAAe,CAAC;AAqBlD;;;;AAIG;AACI,MAAM,mBAAmB,GAAG;IACjC,sCAAsC;IACtC,uCAAuC;IACvC,sCAAsC;;AAGxC;;;;;;;;;;;;;;;;AAgBG;SACa,0BAA0B,CACxC,UAAmB,EACnB,OAAgE,UAAU,EAAA;IAE1E,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;AACvD,QAAA,IAAI,QAAyC;AAC7C,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B;QAAE,OAAO,KAAK,EAAE;;;;AAId,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,kEAAA,EAAqE,UAAU,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,EAC1G,EAAE,KAAK,EAAE,CACV;QACH;AACA,QAAA,IAAI,QAAQ,KAAK,SAAS,EAAE;AAC1B,YAAA,MAAM,IAAI,KAAK,CACb,qEAAqE,UAAU,CAAA,CAAE,CAClF;QACH;AACA,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE;AAC3C,QAAA,IAAI,QAAyC;AAC7C,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC5B;QAAE,OAAO,KAAK,EAAE;;;;;AAKd,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,8CAAA,EAAiD,SAAS,4CAA4C,YAAY,CAAC,KAAK,CAAC,EAAE,EAC3H,EAAE,KAAK,EAAE,CACV;QACH;AACA,QAAA,IAAI,QAAQ,KAAK,SAAS,EAAE;AAC1B,YAAA,OAAO,QAAQ;QACjB;IACF;IAEA,MAAM,IAAI,KAAK,CACb,sEAAsE;QACpE,qEAAqE;QACrE,yEAAyE;AACzE,QAAA,cAAc,CACjB;AACH;AAEA;;;;;;;;;;;;AAYG;AACH,SAAS,UAAU,CAAC,UAAkB,EAAA;AACpC,IAAA,IAAI;AACF,QAAA,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC;IACjC;IAAE,OAAO,KAAK,EAAE;AACd,QAAA,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE;AAC5B,YAAA,OAAO,SAAS;QAClB;;;;AAIA,QAAA,MAAM,KAAK;IACb;AACA,IAAA,OAAO,WAAW,CAAC,UAAU,CAAwB;AACvD;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACH,SAAS,iBAAiB,CAAC,KAAc,EAAA;AACvC,IAAA,MAAM,IAAI,GAAI,KAA+C,EAAE,IAAI;IACnE,QACE,IAAI,KAAK,kBAAkB;AAC3B,QAAA,IAAI,KAAK,sBAAsB;QAC/B,IAAI,KAAK,+BAA+B;AAE5C;AAEA;AACA,SAAS,YAAY,CAAC,KAAc,EAAA;AAClC,IAAA,IAAI,KAAK,YAAY,KAAK,EAAE;QAC1B,OAAO,KAAK,CAAC,OAAO;IACtB;AACA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB;;;;;"}
|
package/lib/core/upstream.mjs
CHANGED
|
@@ -30,14 +30,33 @@ const UPSTREAM_CANDIDATES = [
|
|
|
30
30
|
*/
|
|
31
31
|
function resolveUpstreamTransformer(customPath, load = tryRequire) {
|
|
32
32
|
if (customPath !== undefined && customPath.length !== 0) {
|
|
33
|
-
|
|
33
|
+
let upstream;
|
|
34
|
+
try {
|
|
35
|
+
upstream = load(customPath);
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
// The module resolves but failed while initializing (a top-level throw,
|
|
39
|
+
// a missing peer/transitive dependency, or a runtime-ABI rejection).
|
|
40
|
+
// Preserve the original diagnostic instead of masking it as absence.
|
|
41
|
+
throw new Error(`[@ttsc/metro] Failed to load the configured upstream transformer "${customPath}": ${errorMessage(cause)}`, { cause });
|
|
42
|
+
}
|
|
34
43
|
if (upstream === undefined) {
|
|
35
44
|
throw new Error(`[@ttsc/metro] Could not load the configured upstream transformer: ${customPath}`);
|
|
36
45
|
}
|
|
37
46
|
return upstream;
|
|
38
47
|
}
|
|
39
48
|
for (const candidate of UPSTREAM_CANDIDATES) {
|
|
40
|
-
|
|
49
|
+
let upstream;
|
|
50
|
+
try {
|
|
51
|
+
upstream = load(candidate);
|
|
52
|
+
}
|
|
53
|
+
catch (cause) {
|
|
54
|
+
// A candidate that resolves but throws while initializing is a broken
|
|
55
|
+
// installation of the active stack, not an absent optional peer. Surface
|
|
56
|
+
// it rather than silently falling through to a candidate that does not
|
|
57
|
+
// match this project.
|
|
58
|
+
throw new Error(`[@ttsc/metro] The upstream Metro transformer "${candidate}" is installed but failed to initialize: ${errorMessage(cause)}`, { cause });
|
|
59
|
+
}
|
|
41
60
|
if (upstream !== undefined) {
|
|
42
61
|
return upstream;
|
|
43
62
|
}
|
|
@@ -47,13 +66,66 @@ function resolveUpstreamTransformer(customPath, load = tryRequire) {
|
|
|
47
66
|
"(React Native), or set the `upstreamTransformer` option to an explicit " +
|
|
48
67
|
"module path.");
|
|
49
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Load an upstream transformer module, separating genuine absence from a broken
|
|
71
|
+
* installation.
|
|
72
|
+
*
|
|
73
|
+
* Resolution and execution are split deliberately. `require.resolve` only walks
|
|
74
|
+
* the module graph for the requested specifier; it never executes third-party
|
|
75
|
+
* code, so a failure there proves the requested candidate itself is not present
|
|
76
|
+
* — reported as `undefined` (absence) so automatic probing continues to the
|
|
77
|
+
* next optional peer. Once resolution succeeds, any error thrown by the actual
|
|
78
|
+
* `require` comes from executing the module body, including a missing peer or
|
|
79
|
+
* transitive dependency; that is a real initialization failure and is rethrown
|
|
80
|
+
* with its original message and stack so the caller can preserve it.
|
|
81
|
+
*/
|
|
50
82
|
function tryRequire(modulePath) {
|
|
51
83
|
try {
|
|
52
|
-
|
|
84
|
+
nodeRequire.resolve(modulePath);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (isCandidateAbsent(error)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
// A resolution error that is not one of the known "entry point absent"
|
|
91
|
+
// codes (e.g. an invalid specifier) is not evidence of a plain absence;
|
|
92
|
+
// surface it rather than silently skipping the candidate.
|
|
93
|
+
throw error;
|
|
53
94
|
}
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
return nodeRequire(modulePath);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether a resolution error means the requested candidate's entry point is not
|
|
99
|
+
* available here — i.e. genuine absence, not a broken initialization.
|
|
100
|
+
*
|
|
101
|
+
* Resolution never executes the module body, so a `require.resolve` failure can
|
|
102
|
+
* only concern the requested specifier, never a transitive import of it. Each
|
|
103
|
+
* recognised code says the same thing about that specifier:
|
|
104
|
+
*
|
|
105
|
+
* - `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` — the package or file itself is
|
|
106
|
+
* not installed (CJS and ESM loaders respectively).
|
|
107
|
+
* - `ERR_PACKAGE_PATH_NOT_EXPORTED` — the package is installed but the requested
|
|
108
|
+
* subpath is not exported (or its export target is missing). This matters for
|
|
109
|
+
* the `@expo/metro-config/babel-transformer` candidate, a package subpath:
|
|
110
|
+
* under Expo/React Native version skew a present but non-exporting package
|
|
111
|
+
* must stay non-fatal so auto-detection falls through to the next candidate,
|
|
112
|
+
* exactly as a wholly absent package does.
|
|
113
|
+
*
|
|
114
|
+
* An error thrown later, while the resolved module executes, is a real
|
|
115
|
+
* initialization failure and is never routed here — the caller preserves it.
|
|
116
|
+
*/
|
|
117
|
+
function isCandidateAbsent(error) {
|
|
118
|
+
const code = error?.code;
|
|
119
|
+
return (code === "MODULE_NOT_FOUND" ||
|
|
120
|
+
code === "ERR_MODULE_NOT_FOUND" ||
|
|
121
|
+
code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
|
|
122
|
+
}
|
|
123
|
+
/** Best-effort message extraction for wrapping an unknown thrown value. */
|
|
124
|
+
function errorMessage(error) {
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
return error.message;
|
|
56
127
|
}
|
|
128
|
+
return String(error);
|
|
57
129
|
}
|
|
58
130
|
|
|
59
131
|
export { UPSTREAM_CANDIDATES, resolveUpstreamTransformer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstream.mjs","sources":["../../src/core/upstream.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAEA,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AAqBlD;;;;AAIG;AACI,MAAM,mBAAmB,GAAG;IACjC,sCAAsC;IACtC,uCAAuC;IACvC,sCAAsC;;AAGxC;;;;;;;;;;;;;;;;AAgBG;SACa,0BAA0B,CACxC,UAAmB,EACnB,OAAgE,UAAU,EAAA;IAE1E,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;AACvD,QAAA,
|
|
1
|
+
{"version":3,"file":"upstream.mjs","sources":["../../src/core/upstream.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAEA,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AAqBlD;;;;AAIG;AACI,MAAM,mBAAmB,GAAG;IACjC,sCAAsC;IACtC,uCAAuC;IACvC,sCAAsC;;AAGxC;;;;;;;;;;;;;;;;AAgBG;SACa,0BAA0B,CACxC,UAAmB,EACnB,OAAgE,UAAU,EAAA;IAE1E,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;AACvD,QAAA,IAAI,QAAyC;AAC7C,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B;QAAE,OAAO,KAAK,EAAE;;;;AAId,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,kEAAA,EAAqE,UAAU,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,EAC1G,EAAE,KAAK,EAAE,CACV;QACH;AACA,QAAA,IAAI,QAAQ,KAAK,SAAS,EAAE;AAC1B,YAAA,MAAM,IAAI,KAAK,CACb,qEAAqE,UAAU,CAAA,CAAE,CAClF;QACH;AACA,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE;AAC3C,QAAA,IAAI,QAAyC;AAC7C,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC5B;QAAE,OAAO,KAAK,EAAE;;;;;AAKd,YAAA,MAAM,IAAI,KAAK,CACb,CAAA,8CAAA,EAAiD,SAAS,4CAA4C,YAAY,CAAC,KAAK,CAAC,EAAE,EAC3H,EAAE,KAAK,EAAE,CACV;QACH;AACA,QAAA,IAAI,QAAQ,KAAK,SAAS,EAAE;AAC1B,YAAA,OAAO,QAAQ;QACjB;IACF;IAEA,MAAM,IAAI,KAAK,CACb,sEAAsE;QACpE,qEAAqE;QACrE,yEAAyE;AACzE,QAAA,cAAc,CACjB;AACH;AAEA;;;;;;;;;;;;AAYG;AACH,SAAS,UAAU,CAAC,UAAkB,EAAA;AACpC,IAAA,IAAI;AACF,QAAA,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC;IACjC;IAAE,OAAO,KAAK,EAAE;AACd,QAAA,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE;AAC5B,YAAA,OAAO,SAAS;QAClB;;;;AAIA,QAAA,MAAM,KAAK;IACb;AACA,IAAA,OAAO,WAAW,CAAC,UAAU,CAAwB;AACvD;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACH,SAAS,iBAAiB,CAAC,KAAc,EAAA;AACvC,IAAA,MAAM,IAAI,GAAI,KAA+C,EAAE,IAAI;IACnE,QACE,IAAI,KAAK,kBAAkB;AAC3B,QAAA,IAAI,KAAK,sBAAsB;QAC/B,IAAI,KAAK,+BAA+B;AAE5C;AAEA;AACA,SAAS,YAAY,CAAC,KAAc,EAAA;AAClC,IAAA,IAAI,KAAK,YAAY,KAAK,EAAE;QAC1B,OAAO,KAAK,CAAC,OAAO;IACtB;AACA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/metro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Metro (React Native / Expo) adapter for ttsc plugins.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"src"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ttsc/unplugin": "0.
|
|
38
|
+
"@ttsc/unplugin": "0.19.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@expo/metro-config": "*",
|
|
@@ -59,12 +59,10 @@
|
|
|
59
59
|
"@types/node": "^25.3.0",
|
|
60
60
|
"rimraf": "^6.1.2",
|
|
61
61
|
"rollup": "^4.60.3",
|
|
62
|
-
"rollup-plugin-auto-external": "^2.0.0",
|
|
63
|
-
"rollup-plugin-node-externals": "^9.0.1",
|
|
64
62
|
"tinyglobby": "^0.2.16",
|
|
65
63
|
"tslib": "^2.8.1",
|
|
66
64
|
"typescript": "^7.0.2",
|
|
67
|
-
"ttsc": "0.
|
|
65
|
+
"ttsc": "0.19.1"
|
|
68
66
|
},
|
|
69
67
|
"repository": {
|
|
70
68
|
"type": "git",
|
package/src/core/upstream.ts
CHANGED
|
@@ -54,7 +54,18 @@ export function resolveUpstreamTransformer(
|
|
|
54
54
|
load: (modulePath: string) => UpstreamTransformer | undefined = tryRequire,
|
|
55
55
|
): UpstreamTransformer {
|
|
56
56
|
if (customPath !== undefined && customPath.length !== 0) {
|
|
57
|
-
|
|
57
|
+
let upstream: UpstreamTransformer | undefined;
|
|
58
|
+
try {
|
|
59
|
+
upstream = load(customPath);
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
// The module resolves but failed while initializing (a top-level throw,
|
|
62
|
+
// a missing peer/transitive dependency, or a runtime-ABI rejection).
|
|
63
|
+
// Preserve the original diagnostic instead of masking it as absence.
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[@ttsc/metro] Failed to load the configured upstream transformer "${customPath}": ${errorMessage(cause)}`,
|
|
66
|
+
{ cause },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
58
69
|
if (upstream === undefined) {
|
|
59
70
|
throw new Error(
|
|
60
71
|
`[@ttsc/metro] Could not load the configured upstream transformer: ${customPath}`,
|
|
@@ -64,7 +75,19 @@ export function resolveUpstreamTransformer(
|
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
for (const candidate of UPSTREAM_CANDIDATES) {
|
|
67
|
-
|
|
78
|
+
let upstream: UpstreamTransformer | undefined;
|
|
79
|
+
try {
|
|
80
|
+
upstream = load(candidate);
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
// A candidate that resolves but throws while initializing is a broken
|
|
83
|
+
// installation of the active stack, not an absent optional peer. Surface
|
|
84
|
+
// it rather than silently falling through to a candidate that does not
|
|
85
|
+
// match this project.
|
|
86
|
+
throw new Error(
|
|
87
|
+
`[@ttsc/metro] The upstream Metro transformer "${candidate}" is installed but failed to initialize: ${errorMessage(cause)}`,
|
|
88
|
+
{ cause },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
68
91
|
if (upstream !== undefined) {
|
|
69
92
|
return upstream;
|
|
70
93
|
}
|
|
@@ -78,10 +101,67 @@ export function resolveUpstreamTransformer(
|
|
|
78
101
|
);
|
|
79
102
|
}
|
|
80
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Load an upstream transformer module, separating genuine absence from a broken
|
|
106
|
+
* installation.
|
|
107
|
+
*
|
|
108
|
+
* Resolution and execution are split deliberately. `require.resolve` only walks
|
|
109
|
+
* the module graph for the requested specifier; it never executes third-party
|
|
110
|
+
* code, so a failure there proves the requested candidate itself is not present
|
|
111
|
+
* — reported as `undefined` (absence) so automatic probing continues to the
|
|
112
|
+
* next optional peer. Once resolution succeeds, any error thrown by the actual
|
|
113
|
+
* `require` comes from executing the module body, including a missing peer or
|
|
114
|
+
* transitive dependency; that is a real initialization failure and is rethrown
|
|
115
|
+
* with its original message and stack so the caller can preserve it.
|
|
116
|
+
*/
|
|
81
117
|
function tryRequire(modulePath: string): UpstreamTransformer | undefined {
|
|
82
118
|
try {
|
|
83
|
-
|
|
84
|
-
} catch {
|
|
85
|
-
|
|
119
|
+
nodeRequire.resolve(modulePath);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (isCandidateAbsent(error)) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
// A resolution error that is not one of the known "entry point absent"
|
|
125
|
+
// codes (e.g. an invalid specifier) is not evidence of a plain absence;
|
|
126
|
+
// surface it rather than silently skipping the candidate.
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
return nodeRequire(modulePath) as UpstreamTransformer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether a resolution error means the requested candidate's entry point is not
|
|
134
|
+
* available here — i.e. genuine absence, not a broken initialization.
|
|
135
|
+
*
|
|
136
|
+
* Resolution never executes the module body, so a `require.resolve` failure can
|
|
137
|
+
* only concern the requested specifier, never a transitive import of it. Each
|
|
138
|
+
* recognised code says the same thing about that specifier:
|
|
139
|
+
*
|
|
140
|
+
* - `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` — the package or file itself is
|
|
141
|
+
* not installed (CJS and ESM loaders respectively).
|
|
142
|
+
* - `ERR_PACKAGE_PATH_NOT_EXPORTED` — the package is installed but the requested
|
|
143
|
+
* subpath is not exported (or its export target is missing). This matters for
|
|
144
|
+
* the `@expo/metro-config/babel-transformer` candidate, a package subpath:
|
|
145
|
+
* under Expo/React Native version skew a present but non-exporting package
|
|
146
|
+
* must stay non-fatal so auto-detection falls through to the next candidate,
|
|
147
|
+
* exactly as a wholly absent package does.
|
|
148
|
+
*
|
|
149
|
+
* An error thrown later, while the resolved module executes, is a real
|
|
150
|
+
* initialization failure and is never routed here — the caller preserves it.
|
|
151
|
+
*/
|
|
152
|
+
function isCandidateAbsent(error: unknown): boolean {
|
|
153
|
+
const code = (error as { code?: unknown } | null | undefined)?.code;
|
|
154
|
+
return (
|
|
155
|
+
code === "MODULE_NOT_FOUND" ||
|
|
156
|
+
code === "ERR_MODULE_NOT_FOUND" ||
|
|
157
|
+
code === "ERR_PACKAGE_PATH_NOT_EXPORTED"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Best-effort message extraction for wrapping an unknown thrown value. */
|
|
162
|
+
function errorMessage(error: unknown): string {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
return error.message;
|
|
86
165
|
}
|
|
166
|
+
return String(error);
|
|
87
167
|
}
|