@chidchanun/bcp 0.1.4 → 0.1.6
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/CHANGELOG.md +28 -0
- package/README.md +39 -1
- package/docs/application-modules.md +116 -0
- package/docs/development-logging.md +13 -0
- package/package.json +6 -1
- package/packages/bundler/src/application-modules.ts +771 -0
- package/packages/bundler/src/client-boundary.ts +484 -0
- package/packages/cli/src/index.ts +36 -5
- package/packages/client/src/server-only.browser.mjs +7 -0
- package/packages/client/src/server-only.d.ts +1 -0
- package/packages/client/src/server-only.mjs +1 -0
- package/packages/server/src/critical-css-proxy.ts +559 -0
- package/packages/server/src/critical-css.ts +132 -0
- package/packages/server/src/dev-hmr.ts +86 -0
- package/packages/server/src/middleware-dev-server.ts +59 -3
- package/packages/server/src/standalone-production-runtime-v6.ts +205 -0
- package/packages/server/src/standalone-production-server.ts +1 -1
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
registerHooks,
|
|
5
|
+
} from "node:module";
|
|
6
|
+
import {
|
|
7
|
+
pathToFileURL,
|
|
8
|
+
} from "node:url";
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Plugin,
|
|
12
|
+
} from "esbuild";
|
|
13
|
+
|
|
14
|
+
export type HydrationMode =
|
|
15
|
+
| "full"
|
|
16
|
+
| "none";
|
|
17
|
+
|
|
18
|
+
const APPLICATION_ALIAS_PREFIX =
|
|
19
|
+
"@/";
|
|
20
|
+
|
|
21
|
+
const MODULE_EXTENSIONS = [
|
|
22
|
+
".ts",
|
|
23
|
+
".tsx",
|
|
24
|
+
".mts",
|
|
25
|
+
".cts",
|
|
26
|
+
".js",
|
|
27
|
+
".jsx",
|
|
28
|
+
".mjs",
|
|
29
|
+
".cjs",
|
|
30
|
+
".json",
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
const CLIENT_REACT_HOOKS = [
|
|
34
|
+
"useActionState",
|
|
35
|
+
"useCallback",
|
|
36
|
+
"useContext",
|
|
37
|
+
"useDebugValue",
|
|
38
|
+
"useDeferredValue",
|
|
39
|
+
"useEffect",
|
|
40
|
+
"useId",
|
|
41
|
+
"useImperativeHandle",
|
|
42
|
+
"useInsertionEffect",
|
|
43
|
+
"useLayoutEffect",
|
|
44
|
+
"useMemo",
|
|
45
|
+
"useOptimistic",
|
|
46
|
+
"useReducer",
|
|
47
|
+
"useRef",
|
|
48
|
+
"useState",
|
|
49
|
+
"useSyncExternalStore",
|
|
50
|
+
"useTransition",
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
let runtimeAliasRoot:
|
|
54
|
+
string | null =
|
|
55
|
+
null;
|
|
56
|
+
|
|
57
|
+
export function installApplicationModuleAlias(
|
|
58
|
+
rootDirectory: string
|
|
59
|
+
): void {
|
|
60
|
+
const root =
|
|
61
|
+
path.resolve(
|
|
62
|
+
rootDirectory
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (runtimeAliasRoot) {
|
|
66
|
+
if (
|
|
67
|
+
normalizePath(
|
|
68
|
+
runtimeAliasRoot
|
|
69
|
+
) !==
|
|
70
|
+
normalizePath(
|
|
71
|
+
root
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`BCP Framework: application alias is already registered for "${runtimeAliasRoot}".`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
registerHooks({
|
|
83
|
+
resolve(
|
|
84
|
+
specifier,
|
|
85
|
+
context,
|
|
86
|
+
nextResolve
|
|
87
|
+
) {
|
|
88
|
+
if (
|
|
89
|
+
!specifier.startsWith(
|
|
90
|
+
APPLICATION_ALIAS_PREFIX
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
return nextResolve(
|
|
94
|
+
specifier,
|
|
95
|
+
context
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resolved =
|
|
100
|
+
resolveApplicationModuleAlias(
|
|
101
|
+
root,
|
|
102
|
+
specifier
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return nextResolve(
|
|
106
|
+
pathToFileURL(
|
|
107
|
+
resolved
|
|
108
|
+
).href,
|
|
109
|
+
context
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
runtimeAliasRoot =
|
|
115
|
+
root;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function resolveApplicationModuleAlias(
|
|
119
|
+
rootDirectory: string,
|
|
120
|
+
specifier: string
|
|
121
|
+
): string {
|
|
122
|
+
if (
|
|
123
|
+
!specifier.startsWith(
|
|
124
|
+
APPLICATION_ALIAS_PREFIX
|
|
125
|
+
)
|
|
126
|
+
) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`BCP Framework: application alias must start with "${APPLICATION_ALIAS_PREFIX}".`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const aliasPath =
|
|
133
|
+
specifier.slice(
|
|
134
|
+
APPLICATION_ALIAS_PREFIX.length
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
!aliasPath ||
|
|
139
|
+
aliasPath.includes("\\")
|
|
140
|
+
) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`BCP Framework: invalid application alias "${specifier}".`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const root =
|
|
147
|
+
path.resolve(
|
|
148
|
+
rootDirectory
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const unresolvedPath =
|
|
152
|
+
path.resolve(
|
|
153
|
+
root,
|
|
154
|
+
aliasPath.replaceAll(
|
|
155
|
+
"/",
|
|
156
|
+
path.sep
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
!isPathInside(
|
|
162
|
+
root,
|
|
163
|
+
unresolvedPath
|
|
164
|
+
)
|
|
165
|
+
) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`BCP Framework: application alias "${specifier}" cannot resolve outside the project root.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolvedPath =
|
|
172
|
+
resolveExistingModulePath(
|
|
173
|
+
unresolvedPath
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!resolvedPath) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`BCP Framework: application alias "${specifier}" could not be resolved from "${root}".`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const realRoot =
|
|
183
|
+
safeRealPath(
|
|
184
|
+
root
|
|
185
|
+
) ??
|
|
186
|
+
root;
|
|
187
|
+
const realResolved =
|
|
188
|
+
safeRealPath(
|
|
189
|
+
resolvedPath
|
|
190
|
+
) ??
|
|
191
|
+
resolvedPath;
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
!isPathInside(
|
|
195
|
+
realRoot,
|
|
196
|
+
realResolved
|
|
197
|
+
)
|
|
198
|
+
) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`BCP Framework: application alias "${specifier}" resolved outside the project root through a symbolic link.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return realResolved;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createApplicationAliasPlugin(
|
|
208
|
+
rootDirectory: string
|
|
209
|
+
): Plugin {
|
|
210
|
+
return {
|
|
211
|
+
name:
|
|
212
|
+
"bcp-application-alias",
|
|
213
|
+
|
|
214
|
+
setup(buildApi) {
|
|
215
|
+
buildApi.onResolve(
|
|
216
|
+
{
|
|
217
|
+
filter:
|
|
218
|
+
/^@\//,
|
|
219
|
+
},
|
|
220
|
+
(args) => {
|
|
221
|
+
try {
|
|
222
|
+
return {
|
|
223
|
+
path:
|
|
224
|
+
resolveApplicationModuleAlias(
|
|
225
|
+
rootDirectory,
|
|
226
|
+
args.path
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
errors: [
|
|
232
|
+
{
|
|
233
|
+
text:
|
|
234
|
+
error instanceof Error
|
|
235
|
+
? error.message
|
|
236
|
+
: String(error),
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function createClientBoundaryPlugin(
|
|
248
|
+
rootDirectory: string
|
|
249
|
+
): Plugin {
|
|
250
|
+
const root =
|
|
251
|
+
path.resolve(
|
|
252
|
+
rootDirectory
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
name:
|
|
257
|
+
"bcp-client-boundary",
|
|
258
|
+
|
|
259
|
+
setup(buildApi) {
|
|
260
|
+
buildApi.onResolve(
|
|
261
|
+
{
|
|
262
|
+
filter:
|
|
263
|
+
/^bcp\/server-only$/,
|
|
264
|
+
},
|
|
265
|
+
(args) => {
|
|
266
|
+
const importer =
|
|
267
|
+
args.importer
|
|
268
|
+
? formatApplicationPath(
|
|
269
|
+
root,
|
|
270
|
+
args.importer
|
|
271
|
+
)
|
|
272
|
+
: "the current client entry";
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
errors: [
|
|
276
|
+
{
|
|
277
|
+
text:
|
|
278
|
+
`BCP Framework: bcp/server-only cannot be imported into a client bundle from ${importer}. Move the server dependency behind an API route or use export const hydration = "none" for a server-only page route.`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
buildApi.onLoad(
|
|
286
|
+
{
|
|
287
|
+
filter:
|
|
288
|
+
/\.[cm]?[jt]sx?$/,
|
|
289
|
+
},
|
|
290
|
+
async (args) => {
|
|
291
|
+
if (
|
|
292
|
+
!isApplicationSourcePath(
|
|
293
|
+
root,
|
|
294
|
+
args.path
|
|
295
|
+
) ||
|
|
296
|
+
/\.island\.(?:ts|tsx)$/.test(
|
|
297
|
+
args.path
|
|
298
|
+
)
|
|
299
|
+
) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const source =
|
|
304
|
+
await fs.promises
|
|
305
|
+
.readFile(
|
|
306
|
+
args.path,
|
|
307
|
+
"utf8"
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (
|
|
311
|
+
usesReactClientHooks(
|
|
312
|
+
source
|
|
313
|
+
) &&
|
|
314
|
+
!hasUseClientDirective(
|
|
315
|
+
source
|
|
316
|
+
)
|
|
317
|
+
) {
|
|
318
|
+
return {
|
|
319
|
+
errors: [
|
|
320
|
+
{
|
|
321
|
+
text:
|
|
322
|
+
`BCP Framework: ${formatApplicationPath(root, args.path)} uses React client hooks but is missing the "use client" directive. Add "use client" as the first directive in the module.`,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function readRouteHydrationMode(
|
|
336
|
+
filePath: string
|
|
337
|
+
): HydrationMode {
|
|
338
|
+
const source =
|
|
339
|
+
fs.readFileSync(
|
|
340
|
+
filePath,
|
|
341
|
+
"utf8"
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const match =
|
|
345
|
+
/\bexport\s+const\s+hydration\s*(?::[^=;]+)?=\s*["']([^"']+)["']/.exec(
|
|
346
|
+
source
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (!match) {
|
|
350
|
+
return "full";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const value =
|
|
354
|
+
match[1];
|
|
355
|
+
|
|
356
|
+
if (
|
|
357
|
+
value === "full" ||
|
|
358
|
+
value === "none"
|
|
359
|
+
) {
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
throw new Error(
|
|
364
|
+
`BCP Framework: invalid hydration mode "${value}" in "${filePath}". Use "full" or "none".`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function hasUseClientDirective(
|
|
369
|
+
source: string
|
|
370
|
+
): boolean {
|
|
371
|
+
let index =
|
|
372
|
+
source.charCodeAt(0) ===
|
|
373
|
+
0xfeff
|
|
374
|
+
? 1
|
|
375
|
+
: 0;
|
|
376
|
+
|
|
377
|
+
while (
|
|
378
|
+
index <
|
|
379
|
+
source.length
|
|
380
|
+
) {
|
|
381
|
+
index =
|
|
382
|
+
skipTrivia(
|
|
383
|
+
source,
|
|
384
|
+
index
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const remaining =
|
|
388
|
+
source.slice(
|
|
389
|
+
index
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const directive =
|
|
393
|
+
/^(["'])([^"']+)\1\s*;?/.exec(
|
|
394
|
+
remaining
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (!directive) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (
|
|
402
|
+
directive[2] ===
|
|
403
|
+
"use client"
|
|
404
|
+
) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
index +=
|
|
409
|
+
directive[0].length;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function usesReactClientHooks(
|
|
416
|
+
source: string
|
|
417
|
+
): boolean {
|
|
418
|
+
const hookPattern =
|
|
419
|
+
new RegExp(
|
|
420
|
+
`\\b(?:${CLIENT_REACT_HOOKS.join("|")})\\b`
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const importPattern =
|
|
424
|
+
/\bimport\s+([\s\S]*?)\s+from\s+["']react["']/g;
|
|
425
|
+
|
|
426
|
+
let match:
|
|
427
|
+
RegExpExecArray | null;
|
|
428
|
+
|
|
429
|
+
while (
|
|
430
|
+
(
|
|
431
|
+
match =
|
|
432
|
+
importPattern.exec(
|
|
433
|
+
source
|
|
434
|
+
)
|
|
435
|
+
)
|
|
436
|
+
) {
|
|
437
|
+
const clause =
|
|
438
|
+
match[1];
|
|
439
|
+
|
|
440
|
+
if (
|
|
441
|
+
hookPattern.test(
|
|
442
|
+
clause
|
|
443
|
+
)
|
|
444
|
+
) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const namespaceMatch =
|
|
449
|
+
/\*\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)/.exec(
|
|
450
|
+
clause
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const defaultMatch =
|
|
454
|
+
/^\s*([A-Za-z_$][A-Za-z0-9_$]*)/.exec(
|
|
455
|
+
clause
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
for (
|
|
459
|
+
const namespace
|
|
460
|
+
of [
|
|
461
|
+
namespaceMatch?.[1],
|
|
462
|
+
defaultMatch?.[1],
|
|
463
|
+
]
|
|
464
|
+
) {
|
|
465
|
+
if (!namespace) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const namespaceHookPattern =
|
|
470
|
+
new RegExp(
|
|
471
|
+
`\\b${escapeRegExp(namespace)}\\s*\\.\\s*(?:${CLIENT_REACT_HOOKS.join("|")})\\s*\\(`
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
if (
|
|
475
|
+
namespaceHookPattern.test(
|
|
476
|
+
source
|
|
477
|
+
)
|
|
478
|
+
) {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function resolveExistingModulePath(
|
|
488
|
+
unresolvedPath: string
|
|
489
|
+
): string | null {
|
|
490
|
+
if (
|
|
491
|
+
isFile(
|
|
492
|
+
unresolvedPath
|
|
493
|
+
)
|
|
494
|
+
) {
|
|
495
|
+
return unresolvedPath;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
path.extname(
|
|
500
|
+
unresolvedPath
|
|
501
|
+
) === ""
|
|
502
|
+
) {
|
|
503
|
+
for (
|
|
504
|
+
const extension
|
|
505
|
+
of MODULE_EXTENSIONS
|
|
506
|
+
) {
|
|
507
|
+
const candidate =
|
|
508
|
+
`${unresolvedPath}${extension}`;
|
|
509
|
+
|
|
510
|
+
if (
|
|
511
|
+
isFile(
|
|
512
|
+
candidate
|
|
513
|
+
)
|
|
514
|
+
) {
|
|
515
|
+
return candidate;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (
|
|
521
|
+
isDirectory(
|
|
522
|
+
unresolvedPath
|
|
523
|
+
)
|
|
524
|
+
) {
|
|
525
|
+
for (
|
|
526
|
+
const extension
|
|
527
|
+
of MODULE_EXTENSIONS
|
|
528
|
+
) {
|
|
529
|
+
const candidate =
|
|
530
|
+
path.join(
|
|
531
|
+
unresolvedPath,
|
|
532
|
+
`index${extension}`
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (
|
|
536
|
+
isFile(
|
|
537
|
+
candidate
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
540
|
+
return candidate;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function isApplicationSourcePath(
|
|
549
|
+
rootDirectory: string,
|
|
550
|
+
filePath: string
|
|
551
|
+
): boolean {
|
|
552
|
+
const absolute =
|
|
553
|
+
path.resolve(
|
|
554
|
+
filePath
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
if (
|
|
558
|
+
!isPathInside(
|
|
559
|
+
rootDirectory,
|
|
560
|
+
absolute
|
|
561
|
+
)
|
|
562
|
+
) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const relative =
|
|
567
|
+
path.relative(
|
|
568
|
+
rootDirectory,
|
|
569
|
+
absolute
|
|
570
|
+
)
|
|
571
|
+
.replace(
|
|
572
|
+
/\\/g,
|
|
573
|
+
"/"
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
return !(
|
|
577
|
+
relative ===
|
|
578
|
+
"node_modules" ||
|
|
579
|
+
relative.startsWith(
|
|
580
|
+
"node_modules/"
|
|
581
|
+
) ||
|
|
582
|
+
relative ===
|
|
583
|
+
".bcp-framework" ||
|
|
584
|
+
relative.startsWith(
|
|
585
|
+
".bcp-framework/"
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function skipTrivia(
|
|
591
|
+
source: string,
|
|
592
|
+
startIndex: number
|
|
593
|
+
): number {
|
|
594
|
+
let index =
|
|
595
|
+
startIndex;
|
|
596
|
+
|
|
597
|
+
while (
|
|
598
|
+
index <
|
|
599
|
+
source.length
|
|
600
|
+
) {
|
|
601
|
+
const whitespace =
|
|
602
|
+
/^[\s]+/.exec(
|
|
603
|
+
source.slice(
|
|
604
|
+
index
|
|
605
|
+
)
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
if (whitespace) {
|
|
609
|
+
index +=
|
|
610
|
+
whitespace[0].length;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (
|
|
615
|
+
source.startsWith(
|
|
616
|
+
"//",
|
|
617
|
+
index
|
|
618
|
+
)
|
|
619
|
+
) {
|
|
620
|
+
const newline =
|
|
621
|
+
source.indexOf(
|
|
622
|
+
"\n",
|
|
623
|
+
index + 2
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
return newline === -1
|
|
627
|
+
? source.length
|
|
628
|
+
: skipTrivia(
|
|
629
|
+
source,
|
|
630
|
+
newline + 1
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (
|
|
635
|
+
source.startsWith(
|
|
636
|
+
"/*",
|
|
637
|
+
index
|
|
638
|
+
)
|
|
639
|
+
) {
|
|
640
|
+
const end =
|
|
641
|
+
source.indexOf(
|
|
642
|
+
"*/",
|
|
643
|
+
index + 2
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
if (end === -1) {
|
|
647
|
+
return source.length;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
index =
|
|
651
|
+
end + 2;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return index;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function formatApplicationPath(
|
|
662
|
+
rootDirectory: string,
|
|
663
|
+
filePath: string
|
|
664
|
+
): string {
|
|
665
|
+
const relative =
|
|
666
|
+
path.relative(
|
|
667
|
+
rootDirectory,
|
|
668
|
+
filePath
|
|
669
|
+
)
|
|
670
|
+
.replace(
|
|
671
|
+
/\\/g,
|
|
672
|
+
"/"
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
return relative &&
|
|
676
|
+
!relative.startsWith(
|
|
677
|
+
".."
|
|
678
|
+
)
|
|
679
|
+
? `"${relative}"`
|
|
680
|
+
: `"${filePath}"`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function isPathInside(
|
|
684
|
+
parent: string,
|
|
685
|
+
child: string
|
|
686
|
+
): boolean {
|
|
687
|
+
const relative =
|
|
688
|
+
path.relative(
|
|
689
|
+
parent,
|
|
690
|
+
child
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
return (
|
|
694
|
+
relative === "" ||
|
|
695
|
+
(
|
|
696
|
+
relative !== ".." &&
|
|
697
|
+
!relative.startsWith(
|
|
698
|
+
`..${path.sep}`
|
|
699
|
+
) &&
|
|
700
|
+
!path.isAbsolute(
|
|
701
|
+
relative
|
|
702
|
+
)
|
|
703
|
+
)
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function safeRealPath(
|
|
708
|
+
filePath: string
|
|
709
|
+
): string | null {
|
|
710
|
+
try {
|
|
711
|
+
return fs.realpathSync(
|
|
712
|
+
filePath
|
|
713
|
+
);
|
|
714
|
+
} catch {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function isFile(
|
|
720
|
+
filePath: string
|
|
721
|
+
): boolean {
|
|
722
|
+
try {
|
|
723
|
+
return fs.statSync(
|
|
724
|
+
filePath
|
|
725
|
+
).isFile();
|
|
726
|
+
} catch {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function isDirectory(
|
|
732
|
+
filePath: string
|
|
733
|
+
): boolean {
|
|
734
|
+
try {
|
|
735
|
+
return fs.statSync(
|
|
736
|
+
filePath
|
|
737
|
+
).isDirectory();
|
|
738
|
+
} catch {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function normalizePath(
|
|
744
|
+
filePath: string
|
|
745
|
+
): string {
|
|
746
|
+
let normalized =
|
|
747
|
+
path.normalize(
|
|
748
|
+
path.resolve(
|
|
749
|
+
filePath
|
|
750
|
+
)
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
if (
|
|
754
|
+
process.platform ===
|
|
755
|
+
"win32"
|
|
756
|
+
) {
|
|
757
|
+
normalized =
|
|
758
|
+
normalized.toLowerCase();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return normalized;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function escapeRegExp(
|
|
765
|
+
value: string
|
|
766
|
+
): string {
|
|
767
|
+
return value.replace(
|
|
768
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
769
|
+
"\\$&"
|
|
770
|
+
);
|
|
771
|
+
}
|