rsbuild-plugin-react-router 0.3.0 → 0.4.0

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 (64) hide show
  1. package/README.md +88 -35
  2. package/dist/451.js +164 -30
  3. package/dist/build-manifest.d.ts +6 -3
  4. package/dist/build-output-transforms.d.ts +2 -1
  5. package/dist/concurrency.d.ts +2 -1
  6. package/dist/config-imports.d.ts +7 -0
  7. package/dist/dev-background-resources.d.ts +38 -0
  8. package/dist/dev-generation.d.ts +2 -2
  9. package/dist/dev-hmr.d.ts +45 -0
  10. package/dist/dev-runtime-artifacts.d.ts +9 -1
  11. package/dist/dev-runtime-compilation.d.ts +17 -2
  12. package/dist/dev-runtime-controller.d.ts +6 -1
  13. package/dist/dev-server.d.ts +5 -0
  14. package/dist/effect-runtime.d.ts +18 -0
  15. package/dist/export-utils.d.ts +2 -2
  16. package/dist/index.cjs +11852 -848
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +8289 -773
  19. package/dist/lazy-compilation-prewarm.d.ts +25 -0
  20. package/dist/lazy-compilation.d.ts +2 -1
  21. package/dist/manifest.d.ts +19 -4
  22. package/dist/parallel-route-transforms.d.ts +17 -2
  23. package/dist/prerender-build.d.ts +4 -0
  24. package/dist/prerender.d.ts +0 -1
  25. package/dist/react-router-config.d.ts +8 -5
  26. package/dist/route-artifacts.d.ts +10 -2
  27. package/dist/route-transform-tasks.d.ts +3 -0
  28. package/dist/server-build-resolution.d.ts +3 -0
  29. package/dist/ssr-externals.d.ts +1 -0
  30. package/dist/typegen.d.ts +14 -1
  31. package/dist/types.d.ts +15 -6
  32. package/package.json +18 -10
  33. package/src/build-manifest.ts +110 -73
  34. package/src/build-output-transforms.ts +5 -0
  35. package/src/concurrency.ts +3 -22
  36. package/src/config-imports.ts +38 -0
  37. package/src/dev-background-resources.ts +255 -0
  38. package/src/dev-generation.ts +43 -53
  39. package/src/dev-hmr.ts +431 -0
  40. package/src/dev-runtime-artifacts.ts +50 -18
  41. package/src/dev-runtime-compilation.ts +80 -1
  42. package/src/dev-runtime-controller.ts +155 -31
  43. package/src/dev-runtime-session.ts +18 -11
  44. package/src/dev-server.ts +31 -1
  45. package/src/effect-runtime.ts +130 -0
  46. package/src/export-utils.ts +82 -23
  47. package/src/index.ts +166 -153
  48. package/src/lazy-compilation-prewarm.ts +279 -0
  49. package/src/lazy-compilation.ts +12 -5
  50. package/src/manifest.ts +366 -255
  51. package/src/modify-browser-manifest.ts +2 -1
  52. package/src/parallel-route-transforms.ts +195 -69
  53. package/src/prerender-build.ts +147 -63
  54. package/src/prerender.ts +1 -19
  55. package/src/react-router-config.ts +98 -73
  56. package/src/route-artifacts.ts +122 -3
  57. package/src/route-export-resolution.ts +3 -3
  58. package/src/route-transform-tasks.ts +173 -2
  59. package/src/route-watch.ts +119 -84
  60. package/src/server-build-resolution.ts +131 -0
  61. package/src/server-utils.ts +7 -106
  62. package/src/ssr-externals.ts +1 -1
  63. package/src/typegen.ts +162 -33
  64. package/src/types.ts +16 -6
@@ -1,11 +1,20 @@
1
1
  import { watch, type FSWatcher } from 'node:fs';
2
2
  import { access, mkdir, readdir, writeFile } from 'node:fs/promises';
3
3
  import type { RsbuildConfig } from '@rsbuild/core';
4
+ import * as Effect from 'effect/Effect';
4
5
  import { dirname, resolve } from 'pathe';
6
+ import { getCappedPluginConcurrency } from './concurrency.js';
7
+ import {
8
+ createDelayedPluginTask,
9
+ runPluginEffect,
10
+ tryPluginPromise,
11
+ } from './effect-runtime.js';
5
12
  import type { Route } from './types.js';
6
13
 
7
14
  const ROUTE_RESTART_MARKER_ASSET = '.react-router/route-watch';
8
15
  const INITIAL_RESTART_MARKER_CONTENT = 'react-router-route-watch';
16
+ const ROUTE_TOPOLOGY_RESCAN_DEBOUNCE_MS = 100;
17
+ const ROUTE_DIRECTORY_SCAN_CONCURRENCY = getCappedPluginConcurrency();
9
18
 
10
19
  type RouteManifestSnapshotEntry = Pick<
11
20
  Route,
@@ -111,32 +120,32 @@ const areSetsEqual = <T>(left: Set<T>, right: Set<T>): boolean => {
111
120
  return true;
112
121
  };
113
122
 
114
- const readRouteDirectories = async (
123
+ const readRouteDirectories = (watchDirectory: string): Promise<Set<string>> => {
124
+ return runPluginEffect(readRouteDirectoriesEffect(watchDirectory));
125
+ };
126
+
127
+ const readRouteDirectoriesEffect = (
115
128
  watchDirectory: string
116
- ): Promise<Set<string>> => {
129
+ ): Effect.Effect<Set<string>, Error, never> => {
117
130
  const directories = new Set<string>();
118
-
119
- const walkDirectory = async (directory: string): Promise<void> => {
120
- let entries;
121
- try {
122
- entries = await readdir(directory, { withFileTypes: true });
123
- } catch {
124
- return;
125
- }
126
-
127
- directories.add(directory);
128
- await Promise.all(
129
- entries.map(async entry => {
130
- const entryPath = resolve(directory, entry.name);
131
- if (entry.isDirectory()) {
132
- await walkDirectory(entryPath);
133
- }
134
- })
131
+ const walkDirectory = (directory: string): Effect.Effect<void> =>
132
+ tryPluginPromise(() => readdir(directory, { withFileTypes: true })).pipe(
133
+ Effect.catchAll(() => Effect.succeed([])),
134
+ Effect.map(entries => {
135
+ directories.add(directory);
136
+ return entries
137
+ .filter(entry => entry.isDirectory())
138
+ .map(entry => resolve(directory, entry.name));
139
+ }),
140
+ Effect.flatMap(childDirectories =>
141
+ Effect.forEach(childDirectories, walkDirectory, {
142
+ concurrency: ROUTE_DIRECTORY_SCAN_CONCURRENCY,
143
+ discard: true,
144
+ })
145
+ )
135
146
  );
136
- };
137
147
 
138
- await walkDirectory(watchDirectory);
139
- return directories;
148
+ return walkDirectory(watchDirectory).pipe(Effect.as(directories));
140
149
  };
141
150
 
142
151
  export const createRouteTopologyWatcher = async ({
@@ -178,14 +187,17 @@ export const createRouteTopologyWatcher = async ({
178
187
  routeTopology: initialRouteTopology ?? discoveredState.routeTopology,
179
188
  };
180
189
  let closed = false;
181
- let rescanTimer: ReturnType<typeof setTimeout> | undefined;
182
190
  let rescanQueue = Promise.resolve();
183
191
  const directoryWatchers = new Map<string, DirectoryWatcher>();
184
192
 
185
- const touchRestartMarker = async (): Promise<void> => {
186
- await mkdir(dirname(restartMarkerPath), { recursive: true });
187
- await writeFile(restartMarkerPath, String(Date.now()));
188
- };
193
+ const touchRestartMarkerEffect = (): Effect.Effect<void, Error, never> =>
194
+ tryPluginPromise(() =>
195
+ mkdir(dirname(restartMarkerPath), { recursive: true })
196
+ ).pipe(
197
+ Effect.zipRight(
198
+ tryPluginPromise(() => writeFile(restartMarkerPath, String(Date.now())))
199
+ )
200
+ );
189
201
 
190
202
  const closeRemovedDirectoryWatchers = (
191
203
  nextDirectories: Set<string>
@@ -205,13 +217,17 @@ export const createRouteTopologyWatcher = async ({
205
217
  }
206
218
  try {
207
219
  let watcher: DirectoryWatcher;
208
- watcher = watchDirectoryOverride(directory, scheduleRescan, error => {
209
- if (directoryWatchers.get(directory) === watcher) {
210
- watcher.close();
211
- directoryWatchers.delete(directory);
220
+ watcher = watchDirectoryOverride(
221
+ directory,
222
+ () => rescanTask.reschedule(),
223
+ error => {
224
+ if (directoryWatchers.get(directory) === watcher) {
225
+ watcher.close();
226
+ directoryWatchers.delete(directory);
227
+ }
228
+ onError(error);
212
229
  }
213
- onError(error);
214
- });
230
+ );
215
231
  directoryWatchers.set(directory, watcher);
216
232
  } catch (error) {
217
233
  onError(error);
@@ -224,74 +240,94 @@ export const createRouteTopologyWatcher = async ({
224
240
  watchNewDirectories(nextDirectories);
225
241
  };
226
242
 
227
- const applyNextState = async (nextState: RouteDirectoryState) => {
228
- if (closed) {
229
- return;
230
- }
231
- syncDirectoryWatchers(nextState.directories);
232
- if (!areSetsEqual(state.routeTopology, nextState.routeTopology)) {
233
- if (onRouteTopologyChange) {
234
- // This is a notification boundary, not part of the rescan
235
- // transaction. A custom-server callback may close this watcher while
236
- // replacing its compiler, so awaiting it here would deadlock close().
237
- state = nextState;
238
- try {
239
- void Promise.resolve(onRouteTopologyChange()).catch(onError);
240
- } catch (error) {
241
- onError(error);
242
- }
243
- return;
244
- } else {
245
- await touchRestartMarker();
246
- }
243
+ const applyNextStateEffect = (
244
+ nextState: RouteDirectoryState
245
+ ): Effect.Effect<void, Error, never> =>
246
+ Effect.suspend(() => {
247
247
  if (closed) {
248
- return;
248
+ return Effect.void;
249
+ }
250
+ syncDirectoryWatchers(nextState.directories);
251
+ if (!areSetsEqual(state.routeTopology, nextState.routeTopology)) {
252
+ if (onRouteTopologyChange) {
253
+ // This is a notification boundary, not part of the rescan
254
+ // transaction. A custom-server callback may close this watcher while
255
+ // replacing its compiler, so awaiting it here would deadlock close().
256
+ state = nextState;
257
+ return Effect.sync(() => {
258
+ try {
259
+ void Promise.resolve(onRouteTopologyChange()).catch(onError);
260
+ } catch (error) {
261
+ onError(error);
262
+ }
263
+ });
264
+ }
265
+ return touchRestartMarkerEffect().pipe(
266
+ Effect.zipRight(
267
+ Effect.sync(() => {
268
+ if (!closed) {
269
+ state = nextState;
270
+ }
271
+ })
272
+ )
273
+ );
249
274
  }
250
275
  state = nextState;
251
- return;
252
- }
253
- state = nextState;
254
- };
276
+ return Effect.void;
277
+ });
255
278
 
256
- const runRescan = async (): Promise<void> => {
257
- if (closed) {
258
- return;
259
- }
279
+ const runRescanEffect = (): Effect.Effect<void, never, never> => {
260
280
  let nextDirectories: Set<string> | undefined;
261
- try {
262
- nextDirectories = await readRouteDirectories(watchDirectory);
281
+ return Effect.gen(function* () {
282
+ if (closed) {
283
+ return;
284
+ }
285
+ nextDirectories = yield* readRouteDirectoriesEffect(watchDirectory);
263
286
  if (closed) {
264
287
  return;
265
288
  }
266
289
  const nextState = {
267
290
  directories: nextDirectories,
268
- routeTopology: await getRouteTopology(),
291
+ routeTopology: yield* tryPluginPromise(getRouteTopology),
269
292
  };
270
293
  if (closed) {
271
294
  return;
272
295
  }
273
- await applyNextState(nextState);
274
- } catch (error) {
275
- if (nextDirectories && !closed) {
276
- syncDirectoryWatchers(nextDirectories);
277
- }
278
- onError(error);
279
- }
296
+ yield* applyNextStateEffect(nextState);
297
+ }).pipe(
298
+ Effect.catchAll(error =>
299
+ Effect.sync(() => {
300
+ if (nextDirectories && !closed) {
301
+ syncDirectoryWatchers(nextDirectories);
302
+ }
303
+ onError(error);
304
+ })
305
+ )
306
+ );
280
307
  };
281
308
 
282
309
  const rescan = (): Promise<void> => {
283
- rescanQueue = rescanQueue.then(runRescan, runRescan);
310
+ rescanQueue = rescanQueue.then(
311
+ () => runPluginEffect(runRescanEffect()),
312
+ () => runPluginEffect(runRescanEffect())
313
+ );
284
314
  return rescanQueue;
285
315
  };
286
316
 
287
- const scheduleRescan = (): void => {
288
- if (rescanTimer) {
289
- clearTimeout(rescanTimer);
290
- }
291
- rescanTimer = setTimeout(() => {
292
- rescanTimer = undefined;
293
- void rescan();
294
- }, 100);
317
+ const rescanTask = createDelayedPluginTask({
318
+ delayMs: ROUTE_TOPOLOGY_RESCAN_DEBOUNCE_MS,
319
+ run: () =>
320
+ Effect.suspend(() =>
321
+ closed ? Effect.void : tryPluginPromise(rescan).pipe(Effect.asVoid)
322
+ ),
323
+ onError,
324
+ });
325
+
326
+ const cancelScheduledRescan = (): Promise<void> =>
327
+ runPluginEffect(rescanTask.cancelEffect());
328
+
329
+ const applyNextState = async (nextState: RouteDirectoryState) => {
330
+ await runPluginEffect(applyNextStateEffect(nextState));
295
331
  };
296
332
 
297
333
  try {
@@ -302,13 +338,12 @@ export const createRouteTopologyWatcher = async ({
302
338
 
303
339
  return async () => {
304
340
  if (closed) {
341
+ await cancelScheduledRescan();
305
342
  await rescanQueue;
306
343
  return;
307
344
  }
308
345
  closed = true;
309
- if (rescanTimer) {
310
- clearTimeout(rescanTimer);
311
- }
346
+ await cancelScheduledRescan();
312
347
  for (const watcher of directoryWatchers.values()) {
313
348
  watcher.close();
314
349
  }
@@ -0,0 +1,131 @@
1
+ // Internal module: exposes the Effect-based ServerBuild resolution used by
2
+ // dev-runtime code. Not re-exported from the package entry so the public
3
+ // declaration graph stays free of `effect` types; external callers go through
4
+ // the Promise wrappers in server-utils.ts.
5
+ import * as Effect from 'effect/Effect';
6
+ import type { ServerBuild } from 'react-router';
7
+ import { tryPluginPromise, tryPluginSync } from './effect-runtime.js';
8
+
9
+ const RESOLVABLE_BUILD_EXPORTS = new Set([
10
+ 'allowedActionOrigins',
11
+ 'assets',
12
+ 'assetsBuildDirectory',
13
+ 'basename',
14
+ 'entry',
15
+ 'future',
16
+ 'isSpaMode',
17
+ 'prerender',
18
+ 'publicPath',
19
+ 'routeDiscovery',
20
+ 'routes',
21
+ 'ssr',
22
+ ]);
23
+
24
+ function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+
28
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
29
+ return isRecord(value) && typeof value.then === 'function';
30
+ }
31
+
32
+ function isRouteDiscovery(value: unknown): boolean {
33
+ return (
34
+ value === undefined ||
35
+ (isRecord(value) &&
36
+ (value.mode === 'initial' ||
37
+ (value.mode === 'lazy' &&
38
+ (value.manifestPath === undefined ||
39
+ typeof value.manifestPath === 'string'))))
40
+ );
41
+ }
42
+
43
+ function resolveBuildExportsEffect(
44
+ build: Record<string, unknown>
45
+ ): Effect.Effect<Record<string, unknown>, Error, never> {
46
+ const resolved = { ...build };
47
+ return Effect.forEach(
48
+ Object.keys(build),
49
+ key =>
50
+ Effect.gen(function* () {
51
+ if (!RESOLVABLE_BUILD_EXPORTS.has(key)) {
52
+ return;
53
+ }
54
+ const value = build[key];
55
+ if (typeof value === 'function' && value.length === 0) {
56
+ const result = yield* tryPluginSync(() => value());
57
+ resolved[key] = isPromiseLike(result)
58
+ ? yield* tryPluginPromise(() => result)
59
+ : result;
60
+ return;
61
+ }
62
+ if (isPromiseLike(value)) {
63
+ resolved[key] = yield* tryPluginPromise(() => value);
64
+ }
65
+ }),
66
+ { discard: true }
67
+ ).pipe(Effect.as(resolved));
68
+ }
69
+
70
+ function isServerBuild(value: unknown): value is ServerBuild {
71
+ return Boolean(
72
+ isRecord(value) &&
73
+ isRecord(value.entry) &&
74
+ isRecord(value.entry.module) &&
75
+ typeof value.entry.module.default === 'function' &&
76
+ isRecord(value.routes) &&
77
+ isRecord(value.assets) &&
78
+ typeof value.assetsBuildDirectory === 'string' &&
79
+ (value.basename === undefined || typeof value.basename === 'string') &&
80
+ isRecord(value.future) &&
81
+ typeof value.isSpaMode === 'boolean' &&
82
+ Array.isArray(value.prerender) &&
83
+ typeof value.publicPath === 'string' &&
84
+ isRouteDiscovery(value.routeDiscovery) &&
85
+ typeof value.ssr === 'boolean'
86
+ );
87
+ }
88
+
89
+ function resolveServerBuildCandidateEffect(
90
+ candidate: unknown
91
+ ): Effect.Effect<ServerBuild | undefined, Error, never> {
92
+ if (!isRecord(candidate)) {
93
+ return Effect.succeed(undefined);
94
+ }
95
+ return resolveBuildExportsEffect(candidate).pipe(
96
+ Effect.map(resolved => (isServerBuild(resolved) ? resolved : undefined))
97
+ );
98
+ }
99
+
100
+ export function resolveServerBuildModuleEffect(
101
+ buildModule: unknown,
102
+ source: string
103
+ ): Effect.Effect<ServerBuild, Error, never> {
104
+ return Effect.gen(function* () {
105
+ const moduleValue = isPromiseLike(buildModule)
106
+ ? yield* tryPluginPromise(() => buildModule)
107
+ : buildModule;
108
+ const candidates = [() => moduleValue];
109
+ if (isRecord(moduleValue)) {
110
+ if ('default' in moduleValue) {
111
+ candidates.push(() => moduleValue.default);
112
+ }
113
+ if ('module.exports' in moduleValue) {
114
+ candidates.push(() => moduleValue['module.exports']);
115
+ }
116
+ }
117
+
118
+ for (const getCandidate of candidates) {
119
+ const candidate = yield* tryPluginPromise(() => getCandidate());
120
+ const serverBuild = yield* resolveServerBuildCandidateEffect(candidate);
121
+ if (serverBuild) {
122
+ return serverBuild;
123
+ }
124
+ }
125
+ return yield* Effect.fail(
126
+ new Error(
127
+ `[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.`
128
+ )
129
+ );
130
+ });
131
+ }
@@ -1,5 +1,7 @@
1
1
  import { resolve } from 'pathe';
2
2
  import type { ServerBuild } from 'react-router';
3
+ import { runPluginEffect } from './effect-runtime.js';
4
+ import { resolveServerBuildModuleEffect } from './server-build-resolution.js';
3
5
  import type { Route } from './types.js';
4
6
 
5
7
  /**
@@ -91,120 +93,19 @@ function generateServerBuild(
91
93
  return generateStaticTemplate(routes, options);
92
94
  }
93
95
 
94
- const RESOLVABLE_BUILD_EXPORTS = new Set([
95
- 'allowedActionOrigins',
96
- 'assets',
97
- 'assetsBuildDirectory',
98
- 'basename',
99
- 'entry',
100
- 'future',
101
- 'isSpaMode',
102
- 'prerender',
103
- 'publicPath',
104
- 'routeDiscovery',
105
- 'routes',
106
- 'ssr',
107
- ]);
108
-
109
- function isRecord(value: unknown): value is Record<string, unknown> {
110
- return typeof value === 'object' && value !== null && !Array.isArray(value);
111
- }
112
-
113
- function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
114
- return isRecord(value) && typeof value.then === 'function';
115
- }
116
-
117
- function isRouteDiscovery(value: unknown): boolean {
118
- return (
119
- isRecord(value) &&
120
- (value.mode === 'initial' ||
121
- (value.mode === 'lazy' &&
122
- (value.manifestPath === undefined ||
123
- typeof value.manifestPath === 'string')))
124
- );
125
- }
126
-
127
- async function resolveBuildExports(
128
- build: Record<string, unknown>
129
- ): Promise<Record<string, unknown>> {
130
- const resolved = { ...build };
131
- for (const key of Object.keys(build)) {
132
- if (!RESOLVABLE_BUILD_EXPORTS.has(key)) {
133
- continue;
134
- }
135
- const value = build[key];
136
- if (typeof value === 'function' && value.length === 0) {
137
- const result = value();
138
- resolved[key] = isPromiseLike(result) ? await result : result;
139
- continue;
140
- }
141
- if (isPromiseLike(value)) {
142
- resolved[key] = await value;
143
- }
144
- }
145
- return resolved;
146
- }
147
-
148
- function isServerBuild(value: unknown): value is ServerBuild {
149
- return Boolean(
150
- isRecord(value) &&
151
- isRecord(value.entry) &&
152
- isRecord(value.entry.module) &&
153
- typeof value.entry.module.default === 'function' &&
154
- isRecord(value.routes) &&
155
- isRecord(value.assets) &&
156
- typeof value.assetsBuildDirectory === 'string' &&
157
- (value.basename === undefined || typeof value.basename === 'string') &&
158
- isRecord(value.future) &&
159
- typeof value.isSpaMode === 'boolean' &&
160
- Array.isArray(value.prerender) &&
161
- typeof value.publicPath === 'string' &&
162
- isRouteDiscovery(value.routeDiscovery) &&
163
- typeof value.ssr === 'boolean'
164
- );
165
- }
166
-
167
- async function resolveServerBuildCandidate(
168
- candidate: unknown
169
- ): Promise<ServerBuild | undefined> {
170
- if (!isRecord(candidate)) {
171
- return undefined;
172
- }
173
- const resolved = await resolveBuildExports(candidate);
174
- return isServerBuild(resolved) ? resolved : undefined;
175
- }
176
-
177
- export async function resolveServerBuildModule(
96
+ export function resolveServerBuildModule(
178
97
  buildModule: unknown,
179
98
  source: string
180
99
  ): Promise<ServerBuild> {
181
- const moduleValue = await buildModule;
182
- const candidates = [() => moduleValue];
183
- if (isRecord(moduleValue)) {
184
- if ('default' in moduleValue) {
185
- candidates.push(() => moduleValue.default);
186
- }
187
- if ('module.exports' in moduleValue) {
188
- candidates.push(() => moduleValue['module.exports']);
189
- }
190
- }
191
-
192
- for (const getCandidate of candidates) {
193
- const candidate = await getCandidate();
194
- const serverBuild = await resolveServerBuildCandidate(candidate);
195
- if (serverBuild) {
196
- return serverBuild;
197
- }
198
- }
199
- throw new Error(
200
- `[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.`
201
- );
100
+ return runPluginEffect(resolveServerBuildModuleEffect(buildModule, source));
202
101
  }
203
102
 
204
103
  export function resolveReactRouterServerBuild(
205
104
  buildModule: unknown
206
105
  ): Promise<ServerBuild> {
207
- return resolveServerBuildModule(buildModule, 'Imported module');
106
+ return runPluginEffect(
107
+ resolveServerBuildModuleEffect(buildModule, 'Imported module')
108
+ );
208
109
  }
209
110
 
210
111
  export { generateServerBuild };
@@ -15,7 +15,7 @@ const REACT_ROUTER_EXTERNALS = [
15
15
 
16
16
  const requireFromHere = createRequire(import.meta.url);
17
17
 
18
- function resolvePackageJson(
18
+ export function resolvePackageJson(
19
19
  name: string,
20
20
  rootDirectory: string
21
21
  ): string | null {