@ttsc/metro 0.30.4 → 0.31.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 +11 -6
- package/lib/core/SnapshotReadOperations.d.ts +119 -0
- package/lib/core/SnapshotReadOperations.js +3 -0
- package/lib/core/SnapshotReadOperations.js.map +1 -0
- package/lib/core/SnapshotReadOperations.mjs +2 -0
- package/lib/core/SnapshotReadOperations.mjs.map +1 -0
- package/lib/core/TtscMetroOptions.d.ts +119 -0
- package/lib/core/TtscMetroOptions.js +3 -0
- package/lib/core/TtscMetroOptions.js.map +1 -0
- package/lib/core/TtscMetroOptions.mjs +2 -0
- package/lib/core/TtscMetroOptions.mjs.map +1 -0
- package/lib/core/TtscMetroProjectView.d.ts +64 -0
- package/lib/core/TtscMetroProjectView.js +3 -0
- package/lib/core/TtscMetroProjectView.js.map +1 -0
- package/lib/core/TtscMetroProjectView.mjs +2 -0
- package/lib/core/TtscMetroProjectView.mjs.map +1 -0
- package/lib/core/UpstreamTransformer.d.ts +127 -0
- package/lib/core/UpstreamTransformer.js +3 -0
- package/lib/core/UpstreamTransformer.js.map +1 -0
- package/lib/core/UpstreamTransformer.mjs +2 -0
- package/lib/core/UpstreamTransformer.mjs.map +1 -0
- package/lib/core/fingerprint.d.ts +442 -97
- package/lib/core/fingerprint.js +838 -83
- package/lib/core/fingerprint.js.map +1 -1
- package/lib/core/fingerprint.mjs +839 -84
- package/lib/core/fingerprint.mjs.map +1 -1
- package/lib/core/options.d.ts +81 -58
- package/lib/core/options.js +81 -4
- package/lib/core/options.js.map +1 -1
- package/lib/core/options.mjs +81 -4
- package/lib/core/options.mjs.map +1 -1
- package/lib/core/remapAstLocations.d.ts +76 -0
- package/lib/core/remapAstLocations.js +205 -0
- package/lib/core/remapAstLocations.js.map +1 -0
- package/lib/core/remapAstLocations.mjs +203 -0
- package/lib/core/remapAstLocations.mjs.map +1 -0
- package/lib/core/upstream.d.ts +97 -27
- package/lib/core/upstream.js +109 -8
- package/lib/core/upstream.js.map +1 -1
- package/lib/core/upstream.mjs +109 -9
- package/lib/core/upstream.mjs.map +1 -1
- package/lib/index.d.ts +50 -2
- package/lib/index.js +88 -24
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +89 -25
- package/lib/index.mjs.map +1 -1
- package/lib/transformer.d.ts +183 -6
- package/lib/transformer.js +209 -14
- package/lib/transformer.js.map +1 -1
- package/lib/transformer.mjs +211 -16
- package/lib/transformer.mjs.map +1 -1
- package/package.json +7 -18
- package/src/core/SnapshotReadOperations.ts +121 -0
- package/src/core/TtscMetroOptions.ts +127 -0
- package/src/core/TtscMetroProjectView.ts +74 -0
- package/src/core/UpstreamTransformer.ts +126 -0
- package/src/core/fingerprint.ts +987 -109
- package/src/core/options.ts +85 -62
- package/src/core/remapAstLocations.ts +219 -0
- package/src/core/upstream.ts +110 -26
- package/src/index.ts +99 -28
- package/src/transformer.ts +214 -17
package/src/index.ts
CHANGED
|
@@ -27,19 +27,22 @@
|
|
|
27
27
|
* module.exports = withTtsc(getDefaultConfig(__dirname));
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
import {
|
|
30
|
+
import { openTtscTransformSession } from "@ttsc/unplugin/api";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
31
32
|
import { createRequire } from "node:module";
|
|
32
33
|
import { basename, dirname, join, resolve } from "node:path";
|
|
33
34
|
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { createFilesystemPathIdentityContext } from "ttsc/path-identity";
|
|
34
36
|
|
|
37
|
+
import type { TtscMetroOptions } from "./core/TtscMetroOptions";
|
|
35
38
|
import { prepareSnapshot } from "./core/fingerprint";
|
|
36
|
-
import type { TtscMetroOptions } from "./core/options";
|
|
37
39
|
import { ENV_KEY, serializeOptions } from "./core/options";
|
|
40
|
+
import { locateProjectUpstreamTransformer } from "./core/upstream";
|
|
38
41
|
|
|
39
42
|
export type {
|
|
40
43
|
ResolvedTtscMetroOptions,
|
|
41
44
|
TtscMetroOptions,
|
|
42
|
-
} from "./core/
|
|
45
|
+
} from "./core/TtscMetroOptions";
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
48
|
* Minimal structural type for a Metro config object, avoids a hard dependency
|
|
@@ -74,6 +77,54 @@ interface MetroConfigLike {
|
|
|
74
77
|
* `.svg` to the auto-detected Expo default instead, with the build still
|
|
75
78
|
* succeeding (samchon/ttsc#1321). An explicit `upstreamTransformer` option
|
|
76
79
|
* still wins, since that is the caller saying it outright.
|
|
80
|
+
*
|
|
81
|
+
* The call also has side effects beyond the returned config, all before Metro
|
|
82
|
+
* forks its workers. It prepares the cache-key snapshot under
|
|
83
|
+
* `<projectRoot>/node_modules/.cache/ttsc-metro` (creating it, merging the
|
|
84
|
+
* previous workers' records into it under a lock, and sweeping stale files),
|
|
85
|
+
* and it opens the shared compile session whose path the workers inherit
|
|
86
|
+
* through the environment. A snapshot that cannot be prepared never fails the
|
|
87
|
+
* call: the run takes a private token that makes its cache key non-reusable.
|
|
88
|
+
*
|
|
89
|
+
* @evidence contracts/common.md#principled-implementation
|
|
90
|
+
* Metro's supported babelTransformerPath boundary installs this adapter by
|
|
91
|
+
* cloning the config. Node project resolution preserves an existing upstream
|
|
92
|
+
* unless explicitly overridden, and realpath/package ownership filters
|
|
93
|
+
* inherited self-selection; explicit upstream choices remain caller-owned.
|
|
94
|
+
* Only the owned option/session environment channels are published before
|
|
95
|
+
* workers start; foreign loaders and methods are not patched.
|
|
96
|
+
*
|
|
97
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
98
|
+
* The entry point prepares run ownership and worker transport, then clones
|
|
99
|
+
* the config's transformer field. Separate helpers own upstream inheritance
|
|
100
|
+
* and module identity instead of repeating those policies in workers.
|
|
101
|
+
*
|
|
102
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
103
|
+
* Decision values come from the documented inputs and product protocol
|
|
104
|
+
* rather than expected test answers. No compensating path is introduced to
|
|
105
|
+
* make a known example pass.
|
|
106
|
+
*
|
|
107
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
108
|
+
* Node paths, createRequire and fileURLToPath resolve native paths and module
|
|
109
|
+
* URLs. The shared filesystem-identity resolver handles links and actual
|
|
110
|
+
* directory case capabilities in the recursion guard, instead of folding
|
|
111
|
+
* all names by OS. Worker options use JSON environment inheritance.
|
|
112
|
+
*
|
|
113
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
114
|
+
* The native JSDoc explains config preservation, upstream precedence, JSON
|
|
115
|
+
* worker transport and preparation side effects. Checked against the
|
|
116
|
+
* documentation skill: separate paragraphs state the contract and why its
|
|
117
|
+
* nonobvious boundary matters; field comments retain their own useful
|
|
118
|
+
* facts.
|
|
119
|
+
*
|
|
120
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
121
|
+
* The compaction lock it takes is released in prepareSnapshot's finally block and the session store is owned by its opener; withTtsc retains no handle itself.
|
|
122
|
+
*
|
|
123
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
124
|
+
* Constant work per call besides the snapshot preparation that prepareSnapshot owns.
|
|
125
|
+
*
|
|
126
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
127
|
+
* Runs once per Metro config load; the snapshot and compile session it opens are what the workers share.
|
|
77
128
|
*/
|
|
78
129
|
export function withTtsc<T extends MetroConfigLike>(
|
|
79
130
|
config: T,
|
|
@@ -81,8 +132,8 @@ export function withTtsc<T extends MetroConfigLike>(
|
|
|
81
132
|
): T {
|
|
82
133
|
// Prepare the reference-graph snapshot backing the transformer's cache-key
|
|
83
134
|
// fingerprint (see `core/fingerprint.ts`). This runs in the single Metro
|
|
84
|
-
// config process before
|
|
85
|
-
//
|
|
135
|
+
// config process before its workers exist. The snapshot owner serializes
|
|
136
|
+
// concurrent config processes while compacting the previous worker records.
|
|
86
137
|
const snapshotRunId = prepareSnapshot(
|
|
87
138
|
typeof config.projectRoot === "string" ? config.projectRoot : undefined,
|
|
88
139
|
);
|
|
@@ -90,6 +141,9 @@ export function withTtsc<T extends MetroConfigLike>(
|
|
|
90
141
|
inheritConfiguredTransformer(config, options),
|
|
91
142
|
snapshotRunId,
|
|
92
143
|
);
|
|
144
|
+
// The workers Metro forks next inherit the session, so the pool compiles
|
|
145
|
+
// each generation once between them (samchon/ttsc#1390).
|
|
146
|
+
openTtscTransformSession();
|
|
93
147
|
return {
|
|
94
148
|
...config,
|
|
95
149
|
transformer: {
|
|
@@ -116,13 +170,31 @@ function inheritConfiguredTransformer(
|
|
|
116
170
|
config: MetroConfigLike,
|
|
117
171
|
options: TtscMetroOptions,
|
|
118
172
|
): TtscMetroOptions {
|
|
119
|
-
|
|
173
|
+
// An explicit option is the caller's module, so it resolves from the project
|
|
174
|
+
// as a declared `babelTransformerPath` does; an empty one asks for detection.
|
|
120
175
|
if (
|
|
121
|
-
options.upstreamTransformer !== undefined
|
|
122
|
-
|
|
123
|
-
declared.length === 0
|
|
176
|
+
options.upstreamTransformer !== undefined &&
|
|
177
|
+
options.upstreamTransformer.length !== 0
|
|
124
178
|
) {
|
|
125
|
-
return
|
|
179
|
+
return {
|
|
180
|
+
...options,
|
|
181
|
+
upstreamTransformer: resolveFromProject(
|
|
182
|
+
options.upstreamTransformer,
|
|
183
|
+
config,
|
|
184
|
+
),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const declared = config.transformer?.babelTransformerPath;
|
|
188
|
+
if (typeof declared !== "string" || declared.length === 0) {
|
|
189
|
+
// Automatic detection belongs here too: the candidates are the app's
|
|
190
|
+
// packages, and only this process knows the project to look in. Nothing
|
|
191
|
+
// found leaves the worker's own probe to report it.
|
|
192
|
+
const located = locateProjectUpstreamTransformer((specifier) =>
|
|
193
|
+
projectRequire(config).resolve(specifier),
|
|
194
|
+
);
|
|
195
|
+
return located === undefined
|
|
196
|
+
? options
|
|
197
|
+
: { ...options, upstreamTransformer: located };
|
|
126
198
|
}
|
|
127
199
|
// Resolve before judging. Ownership is a property of the module, not of the
|
|
128
200
|
// string, and every spelling has to become one absolute path before either
|
|
@@ -161,17 +233,22 @@ function inheritConfiguredTransformer(
|
|
|
161
233
|
* somewhere less legible.
|
|
162
234
|
*/
|
|
163
235
|
function resolveFromProject(declared: string, config: MetroConfigLike): string {
|
|
164
|
-
const base =
|
|
165
|
-
typeof config.projectRoot === "string" && config.projectRoot.length !== 0
|
|
166
|
-
? config.projectRoot
|
|
167
|
-
: process.cwd();
|
|
168
236
|
try {
|
|
169
|
-
return
|
|
237
|
+
return projectRequire(config).resolve(declared);
|
|
170
238
|
} catch {
|
|
171
239
|
return declared;
|
|
172
240
|
}
|
|
173
241
|
}
|
|
174
242
|
|
|
243
|
+
/** A `require` rooted in the Metro project, the owner of its transformers. */
|
|
244
|
+
function projectRequire(config: MetroConfigLike): NodeJS.Require {
|
|
245
|
+
const base =
|
|
246
|
+
typeof config.projectRoot === "string" && config.projectRoot.length !== 0
|
|
247
|
+
? config.projectRoot
|
|
248
|
+
: process.cwd();
|
|
249
|
+
return createRequire(join(resolve(base), "package.json"));
|
|
250
|
+
}
|
|
251
|
+
|
|
175
252
|
/**
|
|
176
253
|
* Whether a `babelTransformerPath` already points at a `@ttsc/metro`
|
|
177
254
|
* transformer — this copy or any other.
|
|
@@ -209,21 +286,15 @@ function isOwnTransformer(declared: string): boolean {
|
|
|
209
286
|
/**
|
|
210
287
|
* Whether two paths name the same file on disk.
|
|
211
288
|
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
289
|
+
* The shared resolver observes physical spelling and directory capabilities
|
|
290
|
+
* within this comparison. Symlinks can identify the same installed file while
|
|
291
|
+
* case-sensitive native names remain distinct.
|
|
215
292
|
*/
|
|
216
293
|
function sameRealPath(left: string, right: string): boolean {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch {
|
|
222
|
-
// Not on disk: the resolved spelling is the best identity available.
|
|
223
|
-
}
|
|
224
|
-
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
225
|
-
};
|
|
226
|
-
return identity(left) === identity(right);
|
|
294
|
+
const identities = createFilesystemPathIdentityContext({
|
|
295
|
+
throwOnRealpathError: false,
|
|
296
|
+
});
|
|
297
|
+
return identities.resolve(left).key === identities.resolve(right).key;
|
|
227
298
|
}
|
|
228
299
|
|
|
229
300
|
/**
|
package/src/transformer.ts
CHANGED
|
@@ -14,25 +14,33 @@
|
|
|
14
14
|
* generation hit. Cross-file invalidation also rides the project fingerprint
|
|
15
15
|
* {@link getCacheKey} folds into Metro's static transformer key (see
|
|
16
16
|
* `core/fingerprint.ts`).
|
|
17
|
+
*
|
|
18
|
+
* The adapter passes no `watching` declaration in its hooks, so a project whose
|
|
19
|
+
* plugin observations are unavailable (a generation the core can only serve
|
|
20
|
+
* fresh) fails the transform with an explicit error instead of caching it: the
|
|
21
|
+
* core refuses fresh-only output unless the host states it is not watching.
|
|
17
22
|
*/
|
|
18
23
|
import {
|
|
19
24
|
createTtscTransformCache,
|
|
20
25
|
isTransformTarget,
|
|
26
|
+
readTtscTransformSession,
|
|
21
27
|
resolveOptions,
|
|
28
|
+
shareTtscTransformCache,
|
|
22
29
|
transformTtsc,
|
|
23
30
|
} from "@ttsc/unplugin/api";
|
|
24
|
-
import { createHash } from "node:crypto";
|
|
31
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
25
32
|
import { createRequire } from "node:module";
|
|
26
33
|
import path from "node:path";
|
|
27
34
|
|
|
35
|
+
import type { ResolvedTtscMetroOptions } from "./core/TtscMetroOptions";
|
|
28
36
|
import {
|
|
29
37
|
computeProjectFingerprint,
|
|
30
38
|
createSnapshotRecorder,
|
|
31
39
|
resolveProjectView,
|
|
32
40
|
stableStringify,
|
|
33
41
|
} from "./core/fingerprint";
|
|
34
|
-
import type { ResolvedTtscMetroOptions } from "./core/options";
|
|
35
42
|
import { resolveOptionsFromEnv } from "./core/options";
|
|
43
|
+
import { remapAstLocations } from "./core/remapAstLocations";
|
|
36
44
|
import { resolveUpstreamTransformer } from "./core/upstream";
|
|
37
45
|
|
|
38
46
|
const nodeRequire = createRequire(import.meta.url);
|
|
@@ -46,6 +54,9 @@ const nodeRequire = createRequire(import.meta.url);
|
|
|
46
54
|
let resolved: ResolvedTtscMetroOptions | undefined;
|
|
47
55
|
let unpluginOptions: ReturnType<typeof resolveOptions> | undefined;
|
|
48
56
|
const cache = createTtscTransformCache();
|
|
57
|
+
// Metro's workers share each compile through the session `withTtsc` opened
|
|
58
|
+
// (samchon/ttsc#1390).
|
|
59
|
+
shareTtscTransformCache(cache, readTtscTransformSession());
|
|
49
60
|
let snapshotRecorder: ReturnType<typeof createSnapshotRecorder> | undefined;
|
|
50
61
|
|
|
51
62
|
/** Lazily resolve the worker-side options (from {@link resolveOptionsFromEnv}). */
|
|
@@ -70,6 +81,39 @@ function recorder(): ReturnType<typeof createSnapshotRecorder> {
|
|
|
70
81
|
* monorepos and when Metro is launched from a parent directory. Getting this
|
|
71
82
|
* wrong makes every file look "outside the project" and silently skips the
|
|
72
83
|
* plugin pass.
|
|
84
|
+
*
|
|
85
|
+
* @evidence contracts/common.md#principled-implementation
|
|
86
|
+
* Node path.isAbsolute/path.resolve implement Metro's filename contract.
|
|
87
|
+
* Absolute input is retained; relative input is anchored at supplied
|
|
88
|
+
* projectRoot, with cwd only for callers lacking that option.
|
|
89
|
+
*
|
|
90
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
91
|
+
* This helper owns the single relative-to-absolute boundary, keeping Metro's
|
|
92
|
+
* original filename separate from the compiler address.
|
|
93
|
+
*
|
|
94
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
95
|
+
* This operation chooses a base and delegates native path resolution;
|
|
96
|
+
* it owns no input collection or processing strategy.
|
|
97
|
+
*
|
|
98
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
99
|
+
* It maps one filename and supplied options without shared computation.
|
|
100
|
+
*
|
|
101
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
102
|
+
* The returned path transfers to its caller; no resource is retained.
|
|
103
|
+
*
|
|
104
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
105
|
+
* This correct owning anchor avoids monorepo misrouting without matching
|
|
106
|
+
* error text or special-casing fixture names.
|
|
107
|
+
*
|
|
108
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
109
|
+
* Node path handles native separators, roots and drive letters. No URLs or
|
|
110
|
+
* shell commands are mixed with filenames and no filesystem is changed.
|
|
111
|
+
*
|
|
112
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
113
|
+
* The native JSDoc explains the project-relative input, absolute result and
|
|
114
|
+
* cwd fallback reason. Checked against the documentation skill: separate
|
|
115
|
+
* paragraphs state the contract and why its nonobvious boundary matters;
|
|
116
|
+
* field comments retain their own useful facts.
|
|
73
117
|
*/
|
|
74
118
|
export function resolveAbsoluteFilename(
|
|
75
119
|
filename: string,
|
|
@@ -92,7 +136,60 @@ export function resolveAbsoluteFilename(
|
|
|
92
136
|
* Expo/React-Native Babel transformer to produce the AST Metro expects. The
|
|
93
137
|
* upstream call receives Metro's original params (notably the project-relative
|
|
94
138
|
* `filename`, which Babel expects); only `src` is replaced with the
|
|
95
|
-
* ttsc-transformed source.
|
|
139
|
+
* ttsc-transformed source. When the adapter returns a source map for that
|
|
140
|
+
* source, the upstream AST's locations are moved back through it to the
|
|
141
|
+
* author's lines, because Metro maps the AST against the file it read
|
|
142
|
+
* (samchon/ttsc#1392).
|
|
143
|
+
*
|
|
144
|
+
* An explicit `project` option selects its tsconfig. Otherwise the adapter
|
|
145
|
+
* discovers the nearest tsconfig and follows its references to the project
|
|
146
|
+
* admitting the file, using Metro's `projectRoot` or the current working
|
|
147
|
+
* directory to resolve a relative file.
|
|
148
|
+
*
|
|
149
|
+
* @evidence contracts/common.md#principled-implementation
|
|
150
|
+
* Metro's transformer callback composes the shared Unplugin transform core
|
|
151
|
+
* with the selected Babel transformer. One resolved project view is frozen
|
|
152
|
+
* into compilation and recorder inputs. Noneligible or out-of-program files
|
|
153
|
+
* follow the shared core contract; genuine compiler/load failures propagate.
|
|
154
|
+
*
|
|
155
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
156
|
+
* The entry point gates delivery, freezes one project view for compilation
|
|
157
|
+
* and recording, then delegates to Babel. Shared core helpers own checking
|
|
158
|
+
* and caching; the adapter owns only Metro transport and AST remapping.
|
|
159
|
+
*
|
|
160
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
161
|
+
* Filtering runs before compilation. Eligible deliveries resolve one view
|
|
162
|
+
* and record their inputs in a single batch; compiler and AST traversal
|
|
163
|
+
* costs belong to their delegated owners rather than repeated per input.
|
|
164
|
+
*
|
|
165
|
+
* @evidence contracts/performance.md#reuse-equivalent-work
|
|
166
|
+
* The worker's shared transform cache validates project state on each hit,
|
|
167
|
+
* while the inherited session shares compilation across workers. The same
|
|
168
|
+
* project view and options feed compilation and generation recording; Babel
|
|
169
|
+
* is invoked for the actual delivery and is not memoized by this adapter.
|
|
170
|
+
*
|
|
171
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources
|
|
172
|
+
* Worker singletons own resolved options, the transform cache and recorder
|
|
173
|
+
* until process termination. The shared core owns cached generations and
|
|
174
|
+
* session storage; the recorder owns its cumulative paths. This entry point
|
|
175
|
+
* exposes no disposal signal from Metro and does not claim a fixed byte bound.
|
|
176
|
+
*
|
|
177
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
178
|
+
* Only the returned AST's owned locations are updated, with no patched
|
|
179
|
+
* loader or test-specific production behavior.
|
|
180
|
+
*
|
|
181
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
182
|
+
* Metro-relative filenames drive substring filters; Node path.resolve
|
|
183
|
+
* produces absolute compiler addresses. The shared core owns native
|
|
184
|
+
* compiler/session access, while Babel retains the original filename.
|
|
185
|
+
* Worker transport is JSON and no shell command is constructed here.
|
|
186
|
+
*
|
|
187
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
188
|
+
* The native JSDoc explains pass order, original parameter preservation,
|
|
189
|
+
* project selection and AST location mutation. Checked against the
|
|
190
|
+
* documentation skill: separate paragraphs state the contract and why its
|
|
191
|
+
* nonobvious boundary matters; field comments retain their own useful
|
|
192
|
+
* facts.
|
|
96
193
|
*/
|
|
97
194
|
export async function transform(params: {
|
|
98
195
|
src: string;
|
|
@@ -112,6 +209,8 @@ export async function transform(params: {
|
|
|
112
209
|
}
|
|
113
210
|
|
|
114
211
|
let transformedSrc = params.src;
|
|
212
|
+
let transformedMap: { mappings: string; sources: string[] } | undefined;
|
|
213
|
+
let absoluteFilename = params.filename;
|
|
115
214
|
{
|
|
116
215
|
unpluginOptions ??= resolveOptions(opts.ttsc);
|
|
117
216
|
const projectRoot =
|
|
@@ -121,6 +220,7 @@ export async function transform(params: {
|
|
|
121
220
|
const explicitProject =
|
|
122
221
|
typeof opts.ttsc.project === "string" ? opts.ttsc.project : undefined;
|
|
123
222
|
const filename = resolveAbsoluteFilename(params.filename, params.options);
|
|
223
|
+
absoluteFilename = filename;
|
|
124
224
|
const project = resolveProjectView({
|
|
125
225
|
compilerOptions: opts.ttsc.compilerOptions,
|
|
126
226
|
explicitProject,
|
|
@@ -174,10 +274,17 @@ export async function transform(params: {
|
|
|
174
274
|
// Genuine compile and type failures still propagate so Metro surfaces them.
|
|
175
275
|
if (result !== undefined && typeof result.code === "string") {
|
|
176
276
|
transformedSrc = result.code;
|
|
277
|
+
transformedMap = result.map;
|
|
177
278
|
}
|
|
178
279
|
}
|
|
179
280
|
|
|
180
|
-
|
|
281
|
+
const output = await upstream.transform({ ...params, src: transformedSrc });
|
|
282
|
+
// Metro maps the returned AST against the file it read, so the upstream's
|
|
283
|
+
// positions in the transformed text are moved back to the author's lines.
|
|
284
|
+
if (transformedMap !== undefined) {
|
|
285
|
+
remapAstLocations(output.ast, transformedMap, absoluteFilename);
|
|
286
|
+
}
|
|
287
|
+
return output;
|
|
181
288
|
}
|
|
182
289
|
|
|
183
290
|
/**
|
|
@@ -199,8 +306,57 @@ export async function transform(params: {
|
|
|
199
306
|
* A change to any fingerprinted input re-keys every transformed file at
|
|
200
307
|
* project-level granularity, forced by Metro's single static key, replacing the
|
|
201
308
|
* former manual `--reset-cache` step. Resolving the upstream is deliberately
|
|
202
|
-
* non-fatal here: a missing peer must not crash cache-key computation
|
|
203
|
-
* README "Caveats"
|
|
309
|
+
* non-fatal here: a missing peer must not crash cache-key computation, but a
|
|
310
|
+
* failed upstream key withdraws reuse with a nonce. See the README "Caveats"
|
|
311
|
+
* and samchon/ttsc#721.
|
|
312
|
+
*
|
|
313
|
+
* @evidence contracts/common.md#principled-implementation
|
|
314
|
+
* Node sha256 combines package identity, stable resolved options, forwarded
|
|
315
|
+
* upstream key and the project fingerprint required by Metro's
|
|
316
|
+
* one-static-key contract. Failed upstream loads or callbacks contribute a
|
|
317
|
+
* nonce; an absent optional callback contributes no additional key.
|
|
318
|
+
* Forwarded compiler and plugin records retain their JSON property order
|
|
319
|
+
* because the compiler or a plugin can give that order semantic meaning.
|
|
320
|
+
* Fingerprint failure disables reuse through a nonce rather
|
|
321
|
+
* than fabricating a proven generation.
|
|
322
|
+
*
|
|
323
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
324
|
+
* One hash assembly combines options, upstream contribution and the project
|
|
325
|
+
* fingerprint. The fingerprint owner handles persistence and validity;
|
|
326
|
+
* cacheKeyProjectRoot extracts Metro's argument without duplicating discovery.
|
|
327
|
+
*
|
|
328
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
329
|
+
* One static key per Metro run hashes encoded options, the upstream key
|
|
330
|
+
* and one project fingerprint. Key assembly is linear in their string bytes;
|
|
331
|
+
* the fingerprint owner accounts for project scans and config traversal.
|
|
332
|
+
*
|
|
333
|
+
* @evidence contracts/performance.md#reuse-equivalent-work
|
|
334
|
+
* Options, adapter version, upstream key and exact project state contribute
|
|
335
|
+
* to the shared key. Project observation failures withdraw reuse through a
|
|
336
|
+
* nonce. A failing upstream load or key uses a nonce too, preserving the
|
|
337
|
+
* nonfatal keying boundary without pretending its external state is known.
|
|
338
|
+
*
|
|
339
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
340
|
+
* The hash is local to this call. Worker singletons are owned by transform
|
|
341
|
+
* and persisted baseline files by prepareSnapshot, not the hash assembler.
|
|
342
|
+
*
|
|
343
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
344
|
+
* Decision values come from the documented inputs and product protocol
|
|
345
|
+
* rather than expected test answers. No compensating path is introduced to
|
|
346
|
+
* make a known example pass.
|
|
347
|
+
*
|
|
348
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
349
|
+
* Native project roots are interpreted by the fingerprint owner and Node
|
|
350
|
+
* module loading reads package identity. Hash input has deterministic
|
|
351
|
+
* string representation across OSes; filesystem content and identity remain
|
|
352
|
+
* deliberately host-specific.
|
|
353
|
+
*
|
|
354
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
355
|
+
* The native JSDoc explains one key per run, all contributions,
|
|
356
|
+
* project-wide invalidation and nonfatal upstream key policy. Checked
|
|
357
|
+
* against the documentation skill: separate paragraphs state the contract
|
|
358
|
+
* and why its nonobvious boundary matters; field comments retain their own
|
|
359
|
+
* useful facts.
|
|
204
360
|
*/
|
|
205
361
|
export function getCacheKey(...args: unknown[]): string {
|
|
206
362
|
const opts = options();
|
|
@@ -208,16 +364,14 @@ export function getCacheKey(...args: unknown[]): string {
|
|
|
208
364
|
hash.update(`@ttsc/metro:${packageVersion()}`);
|
|
209
365
|
hash.update(
|
|
210
366
|
stableStringify({
|
|
211
|
-
ttsc: opts.ttsc,
|
|
367
|
+
ttsc: JSON.stringify(opts.ttsc),
|
|
212
368
|
include: opts.include,
|
|
213
369
|
exclude: opts.exclude,
|
|
214
370
|
upstreamTransformer: opts.upstreamTransformer ?? null,
|
|
215
371
|
}),
|
|
216
372
|
);
|
|
217
373
|
const upstreamKey = upstreamCacheKey(opts.upstreamTransformer, args);
|
|
218
|
-
|
|
219
|
-
hash.update(upstreamKey);
|
|
220
|
-
}
|
|
374
|
+
hash.update(upstreamKey ?? `nonce:${randomBytes(32).toString("hex")}`);
|
|
221
375
|
hash.update(
|
|
222
376
|
computeProjectFingerprint({
|
|
223
377
|
// The same overlay `transform` hands the recorder. Both read these
|
|
@@ -255,18 +409,19 @@ function cacheKeyProjectRoot(args: unknown[]): string | undefined {
|
|
|
255
409
|
* Fold the upstream transformer's cache key in, defensively. Forwards Metro's
|
|
256
410
|
* own `getCacheKey` arguments so the upstream's babelrc-derived key is
|
|
257
411
|
* preserved, and never throws: a missing peer or a throwing upstream
|
|
258
|
-
* `getCacheKey` yields
|
|
259
|
-
* build's cache keying.
|
|
412
|
+
* `getCacheKey` yields `undefined`, withdrawing cross-run reuse without failing
|
|
413
|
+
* the whole build's cache keying. An absent optional callback still contributes
|
|
414
|
+
* the empty string.
|
|
260
415
|
*/
|
|
261
416
|
function upstreamCacheKey(
|
|
262
417
|
upstreamTransformer: string | undefined,
|
|
263
418
|
args: unknown[],
|
|
264
|
-
): string {
|
|
419
|
+
): string | undefined {
|
|
265
420
|
let upstream;
|
|
266
421
|
try {
|
|
267
422
|
upstream = resolveUpstreamTransformer(upstreamTransformer);
|
|
268
423
|
} catch {
|
|
269
|
-
return
|
|
424
|
+
return undefined;
|
|
270
425
|
}
|
|
271
426
|
if (upstream.getCacheKey === undefined) {
|
|
272
427
|
return "";
|
|
@@ -274,15 +429,57 @@ function upstreamCacheKey(
|
|
|
274
429
|
try {
|
|
275
430
|
return String(upstream.getCacheKey(...args) ?? "");
|
|
276
431
|
} catch {
|
|
277
|
-
return
|
|
432
|
+
return undefined;
|
|
278
433
|
}
|
|
279
434
|
}
|
|
280
435
|
|
|
281
436
|
/**
|
|
282
437
|
* Decide whether a file should run through the ttsc pass. Only TypeScript
|
|
283
438
|
* sources (`.ts`/`.tsx`/`.mts`/`.cts`, excluding every declaration form)
|
|
284
|
-
* qualify
|
|
285
|
-
*
|
|
439
|
+
* qualify, and a file below a `node_modules` directory never does, whatever
|
|
440
|
+
* `include` says, because the shared `isTransformTarget` predicate rejects it
|
|
441
|
+
* first. `exclude` substrings win over `include`, and an empty `include` means
|
|
442
|
+
* "all eligible TypeScript". Patterns use the supplied project-relative
|
|
443
|
+
* filename literally; this operation does not normalize separators or
|
|
444
|
+
* filesystem case.
|
|
445
|
+
*
|
|
446
|
+
* @evidence contracts/common.md#principled-implementation
|
|
447
|
+
* The shared isTransformTarget predicate owns supported TypeScript
|
|
448
|
+
* extensions, declaration exclusions, virtual-module and node_modules
|
|
449
|
+
* exclusions. Literal substring filters apply to Metro's project-relative
|
|
450
|
+
* filename, with exclusion taking precedence.
|
|
451
|
+
*
|
|
452
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
453
|
+
* This predicate orders extension eligibility, exclusion and inclusion as
|
|
454
|
+
* early returns. Compiler execution remains outside the filtering policy.
|
|
455
|
+
*
|
|
456
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
457
|
+
* Extension matching precedes substring scans; exclusion returns early and
|
|
458
|
+
* inclusion stops at its first match. Cost follows pattern count and supplied
|
|
459
|
+
* filename length without allocating a normalized copy for literal matching.
|
|
460
|
+
*
|
|
461
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
462
|
+
* This inexpensive predicate coordinates no shared computation and accepts
|
|
463
|
+
* caller-owned, potentially mutable option arrays.
|
|
464
|
+
*
|
|
465
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
466
|
+
* Filtering retains no caller data and opens no native resource.
|
|
467
|
+
*
|
|
468
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
469
|
+
* This pure decision neither invokes compilation nor mutates options, and
|
|
470
|
+
* has no fixture or test-mode branch.
|
|
471
|
+
*
|
|
472
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation
|
|
473
|
+
* This predicate matches TypeScript extensions and caller-supplied literal
|
|
474
|
+
* substrings. It defines no filesystem identity or native path
|
|
475
|
+
* normalization contract.
|
|
476
|
+
*
|
|
477
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
478
|
+
* The native JSDoc explains eligible extensions, declaration and
|
|
479
|
+
* node_modules exclusion, empty include and exclusion precedence. Checked
|
|
480
|
+
* against the documentation skill: separate paragraphs state the contract
|
|
481
|
+
* and why its nonobvious boundary matters; field comments retain their own
|
|
482
|
+
* useful facts.
|
|
286
483
|
*/
|
|
287
484
|
export function shouldTransform(
|
|
288
485
|
filename: string,
|