ttsc 0.4.4 → 0.5.0-dev.20260429

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +35 -3
  2. package/cmd/ttsc/main.go +2 -2
  3. package/driver/program.go +120 -15
  4. package/go.mod +3 -3
  5. package/go.sum +6 -6
  6. package/lib/api.d.ts +3 -3
  7. package/lib/api.js +275 -624
  8. package/lib/api.js.map +1 -1
  9. package/lib/cli.js +78 -2
  10. package/lib/cli.js.map +1 -1
  11. package/lib/index.d.ts +3 -3
  12. package/lib/index.js +3 -3
  13. package/lib/native.d.ts +5 -0
  14. package/lib/native.js +8 -0
  15. package/lib/native.js.map +1 -1
  16. package/lib/plugin.d.ts +8 -48
  17. package/lib/plugin.js +79 -132
  18. package/lib/plugin.js.map +1 -1
  19. package/lib/runner/cli.js +1 -0
  20. package/lib/runner/cli.js.map +1 -1
  21. package/lib/source-build.d.ts +30 -0
  22. package/lib/source-build.js +365 -0
  23. package/lib/source-build.js.map +1 -0
  24. package/package.json +9 -9
  25. package/shim/ast/go.mod +1 -1
  26. package/shim/ast/lint.go +328 -0
  27. package/shim/bundled/go.mod +1 -1
  28. package/shim/checker/go.mod +1 -1
  29. package/shim/compiler/go.mod +1 -1
  30. package/shim/core/go.mod +1 -1
  31. package/shim/core/shim.go +15 -0
  32. package/shim/diagnosticwriter/go.mod +1 -1
  33. package/shim/diagnosticwriter/lint.go +137 -0
  34. package/shim/parser/go.mod +1 -1
  35. package/shim/scanner/go.mod +1 -1
  36. package/shim/tsoptions/go.mod +1 -1
  37. package/shim/tspath/go.mod +1 -1
  38. package/shim/vfs/cachedvfs/go.mod +1 -1
  39. package/shim/vfs/go.mod +1 -1
  40. package/shim/vfs/osvfs/go.mod +1 -1
  41. package/src/api.ts +413 -771
  42. package/src/cli.ts +82 -2
  43. package/src/index.ts +3 -3
  44. package/src/native.ts +18 -0
  45. package/src/plugin.ts +107 -213
  46. package/src/runner/cli.ts +5 -0
  47. package/src/source-build.ts +383 -0
  48. package/test/native.test.ts +43 -0
  49. package/test/source-build.test.ts +219 -0
package/src/cli.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  } from "./api";
13
13
  import { installHint, resolveBinary } from "./platform";
14
14
  import { resolveProjectConfig, resolveProjectRoot } from "./project";
15
+ import { pluginCacheCleanupTargets } from "./source-build";
15
16
 
16
17
  export function main(argv: readonly string[] = process.argv.slice(2)): number {
17
18
  try {
@@ -35,6 +36,8 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
35
36
  return runCompatibleBuild(rest, false);
36
37
  case "check":
37
38
  return runCompatibleBuild(rest, true);
39
+ case "clean":
40
+ return runClean(rest);
38
41
  case "transform":
39
42
  return runTransform(rest);
40
43
  case "demo":
@@ -99,6 +102,49 @@ function runTransform(argv: readonly string[]): number {
99
102
  return 0;
100
103
  }
101
104
 
105
+ interface CleanInvocation {
106
+ cwd?: string;
107
+ tsconfig?: string;
108
+ }
109
+
110
+ function runClean(argv: readonly string[]): number {
111
+ const options = parseCleanArgs(argv);
112
+ const cwd = path.resolve(options.cwd ?? process.cwd());
113
+ const projectRoot = resolveCleanProjectRoot(cwd, options.tsconfig);
114
+ const targets = pluginCacheCleanupTargets(projectRoot);
115
+ const removed: string[] = [];
116
+ for (const target of targets) {
117
+ if (!fs.existsSync(target)) continue;
118
+ fs.rmSync(target, { recursive: true, force: true });
119
+ removed.push(target);
120
+ }
121
+ if (removed.length === 0) {
122
+ process.stdout.write(`ttsc: no cache directories found under ${projectRoot}\n`);
123
+ return 0;
124
+ }
125
+ for (const target of removed) {
126
+ process.stdout.write(`ttsc: removed ${formatCleanPath(cwd, target)}\n`);
127
+ }
128
+ return 0;
129
+ }
130
+
131
+ function resolveCleanProjectRoot(cwd: string, tsconfig?: string): string {
132
+ try {
133
+ return path.dirname(resolveProjectConfig({ cwd, tsconfig }));
134
+ } catch (error) {
135
+ if (tsconfig) throw error;
136
+ return cwd;
137
+ }
138
+ }
139
+
140
+ function formatCleanPath(cwd: string, target: string): string {
141
+ const relative = path.relative(cwd, target);
142
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
143
+ return target;
144
+ }
145
+ return relative;
146
+ }
147
+
102
148
  function delegateToNative(argv: readonly string[]): number {
103
149
  const bin = resolveBinary();
104
150
  if (!bin) {
@@ -122,6 +168,38 @@ function delegateToNative(argv: readonly string[]): number {
122
168
  return result.status ?? 1;
123
169
  }
124
170
 
171
+ function parseCleanArgs(argv: readonly string[]): CleanInvocation {
172
+ let cwd: string | undefined;
173
+ let tsconfig: string | undefined;
174
+
175
+ const rest = [...argv];
176
+ while (rest.length !== 0) {
177
+ const current = rest.shift()!;
178
+ switch (current) {
179
+ case "--cwd":
180
+ cwd = takeValue(current, rest);
181
+ break;
182
+ case "-p":
183
+ case "--tsconfig":
184
+ case "--project":
185
+ tsconfig = takeValue(current, rest);
186
+ break;
187
+ default:
188
+ if (current.startsWith("--cwd=")) {
189
+ cwd = current.slice("--cwd=".length);
190
+ } else if (current.startsWith("--tsconfig=")) {
191
+ tsconfig = current.slice("--tsconfig=".length);
192
+ } else if (current.startsWith("--project=")) {
193
+ tsconfig = current.slice("--project=".length);
194
+ } else {
195
+ throw new Error(`ttsc: unknown option ${current}`);
196
+ }
197
+ break;
198
+ }
199
+ }
200
+ return { cwd, tsconfig };
201
+ }
202
+
125
203
  function parseBuildArgs(
126
204
  argv: readonly string[],
127
205
  checkOnly: boolean,
@@ -298,6 +376,7 @@ function printHelp(): void {
298
376
  " ttsc -p tsconfig.json",
299
377
  " ttsc --watch",
300
378
  " ttsc --noEmit",
379
+ " ttsc clean [options]",
301
380
  " ttsc transform --file <path> [options]",
302
381
  " ttsc version",
303
382
  " ttsc --help",
@@ -319,12 +398,13 @@ function printHelp(): void {
319
398
  "",
320
399
  "Plugin contract:",
321
400
  " ttsc reads compilerOptions.plugins from tsconfig.json.",
322
- " TS plugins may export `definePlugin(...)` from ttsc.",
323
- " Native consumer plugins may also provide their own binary path.",
401
+ " Plugin modules are descriptors for ordered native transformer backends.",
402
+ " JS transformOutput/transformSource hooks are not part of the public contract.",
324
403
  "",
325
404
  "Compatibility aliases:",
326
405
  " ttsc build [options] Same project build lane as `ttsc [options]`.",
327
406
  " ttsc check [options] Same as `ttsc --noEmit [options]`.",
407
+ " ttsc clean [options] Delete local source-plugin cache directories.",
328
408
  ].join("\n"),
329
409
  );
330
410
  process.stdout.write("\n");
package/src/index.ts CHANGED
@@ -7,9 +7,9 @@
7
7
  * - platform helpers (`resolveBinary`, `installHint`, …) — compatibility
8
8
  * helper resolution for commands such as `ttsc demo`.
9
9
  * - programmatic API (`transform`, `build`, `check`, `version`) — a thin
10
- * TS wrapper around the consumer `tsgo` binary plus JS output-plugin
11
- * hooks. Adapters never have to shell out themselves; they call these
12
- * helpers and get back a string or a result record.
10
+ * TS wrapper around the consumer `tsgo` binary and native plugin
11
+ * descriptors. Adapters never have to shell out themselves; they call
12
+ * these helpers and get back a string or a result record.
13
13
  */
14
14
 
15
15
  export * from "./platform";
package/src/native.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  export type NativeRewriteMode = string;
2
2
  export type NativePluginContractVersion = 1;
3
3
 
4
+ export interface TtscNativeSource {
5
+ dir: string;
6
+ entry?: string;
7
+ }
8
+
4
9
  export interface TtscNativeBackend {
5
10
  mode: NativeRewriteMode;
6
11
  binary?: string;
12
+ source?: TtscNativeSource;
7
13
  contractVersion?: NativePluginContractVersion;
8
14
  capabilities?: readonly string[];
9
15
  }
@@ -53,6 +59,18 @@ export function resolveNativeBackend(
53
59
  `ttsc: plugin "${plugin.name}" requested unsupported native contract version ${backend.contractVersion}`,
54
60
  );
55
61
  }
62
+ if (backend.source) {
63
+ if (backend.binary) {
64
+ throw new Error(
65
+ `ttsc: plugin "${plugin.name}" must use either native.binary or native.source, not both`,
66
+ );
67
+ }
68
+ if (typeof backend.source.dir !== "string" || backend.source.dir.length === 0) {
69
+ throw new Error(
70
+ `ttsc: plugin "${plugin.name}" native.source.dir must be a non-empty string`,
71
+ );
72
+ }
73
+ }
56
74
  return {
57
75
  ...backend,
58
76
  contractVersion: backend.contractVersion ?? 1,
package/src/plugin.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
 
@@ -11,11 +12,13 @@ import {
11
12
  type ProjectPluginConfig,
12
13
  readProjectConfig,
13
14
  } from "./project";
15
+ import { buildSourcePlugin } from "./source-build";
14
16
 
15
17
  export type {
16
18
  NativePluginContractVersion,
17
19
  NativeRewriteMode,
18
20
  TtscNativeBackend,
21
+ TtscNativeSource,
19
22
  } from "./native";
20
23
 
21
24
  export interface TtscPluginFactoryContext {
@@ -25,53 +28,6 @@ export interface TtscPluginFactoryContext {
25
28
  tsconfig: string;
26
29
  }
27
30
 
28
- export interface TtscTransformContext {
29
- code: string;
30
- command: "build" | "transform";
31
- cwd: string;
32
- compilerOptions: Record<string, unknown>;
33
- outputFile?: string;
34
- projectRoot: string;
35
- sourceFile?: string;
36
- tsconfig: string;
37
- }
38
-
39
- export interface TtscSourceTransformContext {
40
- code: string;
41
- command: "build" | "transform";
42
- compilerOptions: Record<string, unknown>;
43
- cwd: string;
44
- projectRoot: string;
45
- sourceFile: string;
46
- tsconfig: string;
47
- }
48
-
49
- export interface TtscSourceEdit {
50
- code: string;
51
- end: number;
52
- start: number;
53
- }
54
-
55
- export interface TtscSourceTransformResult {
56
- code?: string;
57
- edits?: readonly TtscSourceEdit[];
58
- }
59
-
60
- export interface AppliedTtscSourceEdit extends TtscSourceEdit {
61
- newEnd: number;
62
- newStart: number;
63
- }
64
-
65
- export interface TtscSourceTransformStage {
66
- before: string;
67
- edits: readonly AppliedTtscSourceEdit[];
68
- }
69
-
70
- export interface TtscSourceTransformOutput {
71
- code: string;
72
- stages: readonly TtscSourceTransformStage[];
73
- }
74
-
75
31
  export interface TtscPlugin {
76
32
  name: string;
77
33
  native?: TtscNativeBackend;
@@ -79,10 +35,6 @@ export interface TtscPlugin {
79
35
  nativeMode?: NativeRewriteMode;
80
36
  /** @deprecated Use `native.binary` instead. */
81
37
  nativeBinary?: string;
82
- transformSource?(
83
- context: TtscSourceTransformContext,
84
- ): string | TtscSourceTransformResult | readonly TtscSourceEdit[] | undefined;
85
- transformOutput?(context: TtscTransformContext): string;
86
38
  }
87
39
 
88
40
  export type TtscPluginFactory = (
@@ -92,11 +44,17 @@ export type TtscPluginFactory = (
92
44
 
93
45
  export type TtscPluginModule = TtscPlugin | TtscPluginFactory;
94
46
 
47
+ export interface LoadedNativePlugin {
48
+ backend: TtscNativeBackend;
49
+ config: ProjectPluginConfig;
50
+ name: string;
51
+ }
52
+
95
53
  export interface LoadedPlugins {
96
54
  compatibilityFallback: boolean;
97
- nativeBackend: TtscNativeBackend | null;
98
55
  nativeBinary: string | null;
99
- nativeMode: NativeRewriteMode;
56
+ nativeBinaries: string[];
57
+ nativePlugins: LoadedNativePlugin[];
100
58
  plugins: TtscPlugin[];
101
59
  project: ParsedProjectConfig;
102
60
  }
@@ -128,9 +86,9 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins {
128
86
  if (entries.length === 0) {
129
87
  return {
130
88
  compatibilityFallback: false,
131
- nativeBackend: null,
132
89
  nativeBinary: null,
133
- nativeMode: "none",
90
+ nativeBinaries: [],
91
+ nativePlugins: [],
134
92
  plugins: [],
135
93
  project,
136
94
  };
@@ -144,169 +102,51 @@ export function loadProjectPlugins(options: LoadPluginsOptions): LoadedPlugins {
144
102
  };
145
103
  const plugins = entries.map((entry) => loadPluginEntry(entry, context));
146
104
 
147
- let nativeBinary: string | null = null;
148
- let nativeBackend: TtscNativeBackend | null = null;
149
- let nativeMode: NativeRewriteMode = "none";
150
- for (const plugin of plugins) {
151
- const backend = resolveNativeBackend(plugin);
105
+ const nativePlugins: LoadedNativePlugin[] = [];
106
+ const ttscVersion = readTtscVersion();
107
+ const tsgoVersion = readTsgoVersion(context.projectRoot);
108
+ plugins.forEach((plugin, index) => {
109
+ let backend = resolveNativeBackend(plugin);
152
110
  if (!backend) {
153
- continue;
154
- }
155
- if (nativeMode !== "none" && nativeMode !== backend.mode) {
156
- throw new Error(
157
- `ttsc: multiple native plugin modes requested (${nativeMode}, ${backend.mode})`,
158
- );
111
+ return;
159
112
  }
160
- nativeMode = backend.mode;
161
- nativeBackend = backend;
162
- if (backend.binary) {
163
- if (nativeBinary !== null && nativeBinary !== backend.binary) {
164
- throw new Error(
165
- `ttsc: multiple native plugin binaries requested (${nativeBinary}, ${backend.binary})`,
166
- );
167
- }
168
- nativeBinary = backend.binary;
113
+ if (backend.source && !backend.binary) {
114
+ const built = buildSourcePlugin({
115
+ baseDir: context.projectRoot,
116
+ pluginName: plugin.name,
117
+ source: backend.source,
118
+ ttscVersion,
119
+ tsgoVersion,
120
+ });
121
+ backend = { ...backend, binary: built };
169
122
  }
123
+ nativePlugins.push({ backend, config: entries[index]!, name: plugin.name });
124
+ });
125
+ if (nativePlugins.length !== plugins.length) {
126
+ const missing = plugins
127
+ .filter((plugin) => !resolveNativeBackend(plugin))
128
+ .map((plugin) => plugin.name)
129
+ .join(", ");
130
+ throw new Error(
131
+ `ttsc: every plugin must declare a native backend; missing native for ${missing}`,
132
+ );
170
133
  }
171
134
  return {
172
135
  compatibilityFallback: false,
173
- nativeBackend,
174
- nativeBinary,
175
- nativeMode,
136
+ nativeBinary: nativePlugins[0]?.backend.binary ?? null,
137
+ nativeBinaries: [
138
+ ...new Set(
139
+ nativePlugins
140
+ .map((plugin) => plugin.backend.binary)
141
+ .filter((binary): binary is string => typeof binary === "string"),
142
+ ),
143
+ ],
144
+ nativePlugins,
176
145
  plugins,
177
146
  project,
178
147
  };
179
148
  }
180
149
 
181
- export function applyPluginTransforms(
182
- plugins: readonly TtscPlugin[],
183
- context: TtscTransformContext,
184
- ): string {
185
- let code = context.code;
186
- for (const plugin of plugins) {
187
- if (!plugin.transformOutput) {
188
- continue;
189
- }
190
- code = plugin.transformOutput({ ...context, code });
191
- }
192
- return code;
193
- }
194
-
195
- export function applyPluginSourceTransforms(
196
- plugins: readonly TtscPlugin[],
197
- context: TtscSourceTransformContext,
198
- ): string {
199
- return applyPluginSourceTransformsWithMap(plugins, context).code;
200
- }
201
-
202
- export function applyPluginSourceTransformsWithMap(
203
- plugins: readonly TtscPlugin[],
204
- context: TtscSourceTransformContext,
205
- ): TtscSourceTransformOutput {
206
- let code = context.code;
207
- const stages: TtscSourceTransformStage[] = [];
208
- for (const plugin of plugins) {
209
- if (!plugin.transformSource) {
210
- continue;
211
- }
212
- const before = code;
213
- const result = plugin.transformSource({ ...context, code });
214
- if (typeof result === "string") {
215
- code = result;
216
- if (code !== before) {
217
- stages.push({
218
- before,
219
- edits: createAppliedSourceEdits(before, [
220
- { code, end: before.length, start: 0 },
221
- ]),
222
- });
223
- }
224
- continue;
225
- }
226
- if (isSourceEditArray(result)) {
227
- const edits = createAppliedSourceEdits(before, result);
228
- code = applySourceEdits(before, edits);
229
- if (edits.length > 0) {
230
- stages.push({ before, edits });
231
- }
232
- continue;
233
- }
234
- if (result?.code !== undefined) {
235
- code = result.code;
236
- if (code !== before) {
237
- stages.push({
238
- before,
239
- edits: createAppliedSourceEdits(before, [
240
- { code, end: before.length, start: 0 },
241
- ]),
242
- });
243
- }
244
- continue;
245
- }
246
- if (result?.edits) {
247
- const edits = createAppliedSourceEdits(before, result.edits);
248
- code = applySourceEdits(before, edits);
249
- if (edits.length > 0) {
250
- stages.push({ before, edits });
251
- }
252
- }
253
- }
254
- return { code, stages };
255
- }
256
-
257
- function isSourceEditArray(value: unknown): value is readonly TtscSourceEdit[] {
258
- return Array.isArray(value);
259
- }
260
-
261
- function applySourceEdits(
262
- source: string,
263
- edits: readonly TtscSourceEdit[],
264
- ): string {
265
- if (edits.length === 0) {
266
- return source;
267
- }
268
- const ordered = [...edits].sort((a, b) => b.start - a.start);
269
- let output = source;
270
- for (const edit of ordered) {
271
- output = output.slice(0, edit.start) + edit.code + output.slice(edit.end);
272
- }
273
- return output;
274
- }
275
-
276
- function createAppliedSourceEdits(
277
- source: string,
278
- edits: readonly TtscSourceEdit[],
279
- ): AppliedTtscSourceEdit[] {
280
- if (edits.length === 0) {
281
- return [];
282
- }
283
- const ordered = [...edits].sort((a, b) => a.start - b.start);
284
- let previousEnd = 0;
285
- let delta = 0;
286
- const applied: AppliedTtscSourceEdit[] = [];
287
- for (const edit of ordered) {
288
- if (!Number.isInteger(edit.start) || !Number.isInteger(edit.end)) {
289
- throw new Error("ttsc: transformSource edit offsets must be integers");
290
- }
291
- if (
292
- edit.start < 0 ||
293
- edit.end < edit.start ||
294
- edit.end > source.length ||
295
- edit.start < previousEnd
296
- ) {
297
- throw new Error(
298
- `ttsc: invalid or overlapping transformSource edit ${edit.start}:${edit.end}`,
299
- );
300
- }
301
- const newStart = edit.start + delta;
302
- const newEnd = newStart + edit.code.length;
303
- applied.push({ ...edit, newEnd, newStart });
304
- delta += edit.code.length - (edit.end - edit.start);
305
- previousEnd = edit.end;
306
- }
307
- return applied;
308
- }
309
-
310
150
  function loadPluginEntry(
311
151
  entry: ProjectPluginConfig,
312
152
  context: TtscPluginFactoryContext,
@@ -327,13 +167,17 @@ function loadPluginEntry(
327
167
  mod.plugin ??
328
168
  (mod as unknown as TtscPluginModule);
329
169
  if (typeof candidate === "function") {
330
- return candidate(entry, context);
170
+ const plugin = candidate(entry, context);
171
+ if (!isTtscPlugin(plugin)) {
172
+ throw new Error(
173
+ `ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
174
+ );
175
+ }
176
+ rejectJsTransformHooks(specifier, plugin);
177
+ return plugin;
331
178
  }
332
- if (
333
- candidate &&
334
- typeof candidate === "object" &&
335
- typeof candidate.name === "string"
336
- ) {
179
+ if (isTtscPlugin(candidate)) {
180
+ rejectJsTransformHooks(specifier, candidate);
337
181
  return candidate;
338
182
  }
339
183
  throw new Error(
@@ -341,6 +185,23 @@ function loadPluginEntry(
341
185
  );
342
186
  }
343
187
 
188
+ function isTtscPlugin(value: unknown): value is TtscPlugin {
189
+ return (
190
+ typeof value === "object" &&
191
+ value !== null &&
192
+ typeof (value as { name?: unknown }).name === "string"
193
+ );
194
+ }
195
+
196
+ function rejectJsTransformHooks(specifier: string, candidate: object): void {
197
+ if ("transformSource" in candidate || "transformOutput" in candidate) {
198
+ throw new Error(
199
+ `ttsc: plugin "${specifier}" declares unsupported JS transform hooks; ` +
200
+ "declare a native backend instead",
201
+ );
202
+ }
203
+ }
204
+
344
205
  function resolvePluginRequest(specifier: string, projectRoot: string): string {
345
206
  if (path.isAbsolute(specifier)) {
346
207
  return specifier;
@@ -394,3 +255,36 @@ function resolveSourceCheckoutPlugin(
394
255
  return null;
395
256
  }
396
257
  }
258
+
259
+ let cachedTtscVersion: string | null = null;
260
+
261
+ function readTtscVersion(): string {
262
+ if (cachedTtscVersion !== null) {
263
+ return cachedTtscVersion;
264
+ }
265
+ try {
266
+ const file = path.resolve(__dirname, "..", "package.json");
267
+ const pkg = JSON.parse(fs.readFileSync(file, "utf8")) as {
268
+ version?: string;
269
+ };
270
+ cachedTtscVersion = pkg.version ?? "0.0.0";
271
+ } catch {
272
+ cachedTtscVersion = "0.0.0";
273
+ }
274
+ return cachedTtscVersion;
275
+ }
276
+
277
+ function readTsgoVersion(projectRoot: string): string {
278
+ try {
279
+ const projectRequire = createRequire(path.join(projectRoot, "package.json"));
280
+ const pkgPath = projectRequire.resolve(
281
+ "@typescript/native-preview/package.json",
282
+ );
283
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as {
284
+ version?: string;
285
+ };
286
+ return pkg.version ?? "unknown";
287
+ } catch {
288
+ return "unknown";
289
+ }
290
+ }
package/src/runner/cli.ts CHANGED
@@ -244,6 +244,11 @@ function rewriteEsmSpecifiers(root: string): void {
244
244
  (_, quote: string, specifier: string) =>
245
245
  `from ${quote}${withResolvableExtension(next, specifier)}${quote}`,
246
246
  )
247
+ .replace(
248
+ /\bimport\s+(['"])(\.[^'"]+)\1/g,
249
+ (_, quote: string, specifier: string) =>
250
+ `import ${quote}${withResolvableExtension(next, specifier)}${quote}`,
251
+ )
247
252
  .replace(
248
253
  /\bimport\(\s*(['"])(\.[^'"]+)\1\s*\)/g,
249
254
  (_, quote: string, specifier: string) =>