@reckona/mreact-router 0.0.73 → 0.0.75
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 +3 -1
- package/dist/actions.d.ts +14 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +55 -44
- package/dist/actions.js.map +1 -1
- package/dist/build.d.ts +12 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +151 -52
- package/dist/build.js.map +1 -1
- package/dist/cli-options.d.ts +13 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +73 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +17 -8
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +2 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -0
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +8 -0
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +15 -1
- package/dist/route-source.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +6 -0
- package/dist/serve.js.map +1 -1
- package/dist/server-action-inference.d.ts +50 -0
- package/dist/server-action-inference.d.ts.map +1 -0
- package/dist/server-action-inference.js +488 -0
- package/dist/server-action-inference.js.map +1 -0
- package/package.json +12 -11
- package/src/actions.ts +92 -62
- package/src/build.ts +231 -71
- package/src/cli-options.ts +103 -0
- package/src/cli.ts +6 -0
- package/src/client.ts +14 -11
- package/src/index.ts +1 -0
- package/src/render.ts +9 -0
- package/src/route-source.ts +25 -0
- package/src/serve.ts +22 -0
- package/src/server-action-inference.ts +804 -0
package/src/build.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "nod
|
|
|
4
4
|
import { builtinModules } from "node:module";
|
|
5
5
|
import { dirname, join, relative, sep } from "node:path";
|
|
6
6
|
import {
|
|
7
|
-
collectFormActionReferenceNames,
|
|
8
7
|
collectStaticImportReferences,
|
|
9
8
|
collectTopLevelValueExportNames,
|
|
10
9
|
formatDiagnostic,
|
|
@@ -56,6 +55,7 @@ import {
|
|
|
56
55
|
routeClosureMayUseAwaitBoundary,
|
|
57
56
|
stripRouteBuildExports,
|
|
58
57
|
stripRouteClientOnlyExports,
|
|
58
|
+
stripRouteClientSource,
|
|
59
59
|
stripRouteLoaderOnlyExports,
|
|
60
60
|
stripRouteMetadataOnlyExports,
|
|
61
61
|
stripRouteRequestOnlyExports,
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
import { collectRouteCssFiles } from "./route-styles.js";
|
|
68
68
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
69
69
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
70
|
+
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
70
71
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
71
72
|
import type { PluginOption, UserConfig } from "vite";
|
|
72
73
|
|
|
@@ -112,6 +113,7 @@ export interface BuiltServerManifest {
|
|
|
112
113
|
prerenderedRoutes?: Record<string, BuiltPrerenderedRoute>;
|
|
113
114
|
publicAssetBaseUrl?: string;
|
|
114
115
|
routesDir?: string;
|
|
116
|
+
routeServerActionReferences?: Record<string, BuiltServerActionExpressionReference[]>;
|
|
115
117
|
serverActionManifest?: BuiltServerActionReference[];
|
|
116
118
|
serverModuleFiles?: Record<string, string>;
|
|
117
119
|
serverModuleRenderFiles?: Record<string, string>;
|
|
@@ -126,6 +128,18 @@ export interface BuiltServerActionReference {
|
|
|
126
128
|
moduleId: string;
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
export interface BuiltServerActionExpressionReference {
|
|
132
|
+
end: number;
|
|
133
|
+
exportName: string;
|
|
134
|
+
expression: string;
|
|
135
|
+
expressionEnd: number;
|
|
136
|
+
expressionStart: number;
|
|
137
|
+
inferred: boolean;
|
|
138
|
+
moduleId: string;
|
|
139
|
+
sourceHash: string;
|
|
140
|
+
start: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
export interface BuiltServerModuleArtifact {
|
|
130
144
|
analysis?: BuiltRouteSourceAnalysisSummary;
|
|
131
145
|
loader?: BuiltServerModuleOutput;
|
|
@@ -200,7 +214,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
200
214
|
...new Set([...(await collectPublicAssetPaths(project.publicDir)), ...appConventionPublicAssets]),
|
|
201
215
|
].sort();
|
|
202
216
|
|
|
203
|
-
const serverActionManifest = collectBuildServerActionManifest({
|
|
217
|
+
const serverActionManifest = await collectBuildServerActionManifest({
|
|
204
218
|
files,
|
|
205
219
|
projectRoot: project.projectRoot,
|
|
206
220
|
routes,
|
|
@@ -216,7 +230,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
216
230
|
routes,
|
|
217
231
|
vitePlugins,
|
|
218
232
|
});
|
|
219
|
-
const generatedImportPolicy = buildGeneratedImportPolicy({
|
|
233
|
+
const generatedImportPolicy = await buildGeneratedImportPolicy({
|
|
220
234
|
files,
|
|
221
235
|
projectRoot: project.projectRoot,
|
|
222
236
|
routes,
|
|
@@ -274,6 +288,11 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
274
288
|
});
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
const routeServerActionReferences = Object.fromEntries(
|
|
292
|
+
[...serverActionManifest.routeReferences.entries()].sort(([left], [right]) =>
|
|
293
|
+
left.localeCompare(right),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
277
296
|
const serverManifest = {
|
|
278
297
|
allowedSourceDirs: project.allowedSourceDirs.map((directory) =>
|
|
279
298
|
relative(project.projectRoot, directory),
|
|
@@ -287,7 +306,12 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
287
306
|
...(project.publicAssetBaseUrl === undefined
|
|
288
307
|
? {}
|
|
289
308
|
: { publicAssetBaseUrl: project.publicAssetBaseUrl }),
|
|
290
|
-
...(
|
|
309
|
+
...(Object.keys(routeServerActionReferences).length === 0
|
|
310
|
+
? {}
|
|
311
|
+
: { routeServerActionReferences }),
|
|
312
|
+
...(serverActionManifest.allowedActions.length === 0
|
|
313
|
+
? {}
|
|
314
|
+
: { serverActionManifest: serverActionManifest.allowedActions }),
|
|
291
315
|
...(Object.keys(serverModuleArtifacts.files).length === 0
|
|
292
316
|
? {}
|
|
293
317
|
: { serverModuleFiles: serverModuleArtifacts.files }),
|
|
@@ -461,22 +485,28 @@ const frameworkRuntimePackages = new Set([
|
|
|
461
485
|
"@reckona/mreact-router",
|
|
462
486
|
"@reckona/mreact-server",
|
|
463
487
|
]);
|
|
488
|
+
const maxRuntimePackageManifestReads = 1000;
|
|
464
489
|
|
|
465
|
-
function buildGeneratedImportPolicy(options: {
|
|
490
|
+
async function buildGeneratedImportPolicy(options: {
|
|
466
491
|
files: Record<string, string>;
|
|
467
492
|
projectRoot: string;
|
|
468
493
|
routes: readonly AppRoute[];
|
|
469
494
|
routesDir: string;
|
|
470
|
-
}): BuiltImportPolicyArtifact {
|
|
495
|
+
}): Promise<BuiltImportPolicyArtifact> {
|
|
471
496
|
const routePackages = new Map<string, string[]>();
|
|
472
497
|
const allPackages = new Set<string>();
|
|
473
498
|
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
499
|
+
const packageJsonLookupCache = new Map<
|
|
500
|
+
string,
|
|
501
|
+
Promise<(RuntimePackageManifest & { packageJsonPath: string }) | undefined>
|
|
502
|
+
>();
|
|
474
503
|
|
|
475
504
|
for (const route of options.routes) {
|
|
476
505
|
const file = relative(options.projectRoot, route.file);
|
|
477
|
-
const packages = collectRuntimePackagesForFile({
|
|
506
|
+
const packages = await collectRuntimePackagesForFile({
|
|
478
507
|
file,
|
|
479
508
|
files: options.files,
|
|
509
|
+
packageJsonLookupCache,
|
|
480
510
|
projectRoot: options.projectRoot,
|
|
481
511
|
seen: new Set(),
|
|
482
512
|
});
|
|
@@ -494,9 +524,10 @@ function buildGeneratedImportPolicy(options: {
|
|
|
494
524
|
.find((file) => options.files[file] !== undefined);
|
|
495
525
|
|
|
496
526
|
if (middlewareFile !== undefined) {
|
|
497
|
-
const packages = collectRuntimePackagesForFile({
|
|
527
|
+
const packages = await collectRuntimePackagesForFile({
|
|
498
528
|
file: middlewareFile,
|
|
499
529
|
files: options.files,
|
|
530
|
+
packageJsonLookupCache,
|
|
500
531
|
projectRoot: options.projectRoot,
|
|
501
532
|
seen: new Set(),
|
|
502
533
|
});
|
|
@@ -518,12 +549,13 @@ function buildGeneratedImportPolicy(options: {
|
|
|
518
549
|
};
|
|
519
550
|
}
|
|
520
551
|
|
|
521
|
-
function collectRuntimePackagesForFile(options: {
|
|
552
|
+
async function collectRuntimePackagesForFile(options: {
|
|
522
553
|
file: string;
|
|
523
554
|
files: Record<string, string>;
|
|
555
|
+
packageJsonLookupCache: RuntimePackageManifestCache;
|
|
524
556
|
projectRoot: string;
|
|
525
557
|
seen: Set<string>;
|
|
526
|
-
}): string[] {
|
|
558
|
+
}): Promise<string[]> {
|
|
527
559
|
if (options.seen.has(options.file)) {
|
|
528
560
|
return [];
|
|
529
561
|
}
|
|
@@ -548,7 +580,15 @@ function collectRuntimePackagesForFile(options: {
|
|
|
548
580
|
filename: join(options.projectRoot, options.file),
|
|
549
581
|
})) {
|
|
550
582
|
if (isRuntimePackageSpecifier(reference.source)) {
|
|
551
|
-
|
|
583
|
+
const packageName = runtimePackageNameForSpecifier(reference.source);
|
|
584
|
+
packages.add(packageName);
|
|
585
|
+
for (const optionalPackageName of await collectRuntimeOptionalPackages({
|
|
586
|
+
packageJsonLookupCache: options.packageJsonLookupCache,
|
|
587
|
+
packageName,
|
|
588
|
+
projectRoot: options.projectRoot,
|
|
589
|
+
})) {
|
|
590
|
+
packages.add(optionalPackageName);
|
|
591
|
+
}
|
|
552
592
|
continue;
|
|
553
593
|
}
|
|
554
594
|
|
|
@@ -557,7 +597,7 @@ function collectRuntimePackagesForFile(options: {
|
|
|
557
597
|
continue;
|
|
558
598
|
}
|
|
559
599
|
|
|
560
|
-
for (const packageName of collectRuntimePackagesForFile({
|
|
600
|
+
for (const packageName of await collectRuntimePackagesForFile({
|
|
561
601
|
...options,
|
|
562
602
|
file: localFile,
|
|
563
603
|
})) {
|
|
@@ -570,13 +610,140 @@ function collectRuntimePackagesForFile(options: {
|
|
|
570
610
|
return [...packages].sort();
|
|
571
611
|
}
|
|
572
612
|
|
|
613
|
+
interface RuntimePackageManifest {
|
|
614
|
+
dependencies?: Record<string, unknown> | undefined;
|
|
615
|
+
optionalDependencies?: Record<string, unknown> | undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
type RuntimePackageManifestCache = Map<
|
|
619
|
+
string,
|
|
620
|
+
Promise<(RuntimePackageManifest & { packageJsonPath: string }) | undefined>
|
|
621
|
+
>;
|
|
622
|
+
|
|
623
|
+
async function collectRuntimeOptionalPackages(options: {
|
|
624
|
+
packageJsonLookupCache: RuntimePackageManifestCache;
|
|
625
|
+
packageName: string;
|
|
626
|
+
projectRoot: string;
|
|
627
|
+
}): Promise<string[]> {
|
|
628
|
+
const optionalPackages = new Set<string>();
|
|
629
|
+
const seenPackageJson = new Set<string>();
|
|
630
|
+
const queue: Array<{ packageName: string; optional: boolean; startDir: string }> = [
|
|
631
|
+
{ packageName: options.packageName, optional: false, startDir: options.projectRoot },
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
for (let index = 0; index < queue.length && seenPackageJson.size < maxRuntimePackageManifestReads; index += 1) {
|
|
635
|
+
const item = queue[index];
|
|
636
|
+
if (item === undefined || !isValidRuntimePackageName(item.packageName)) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const manifest = await readRuntimePackageManifest({
|
|
641
|
+
cache: options.packageJsonLookupCache,
|
|
642
|
+
packageName: item.packageName,
|
|
643
|
+
startDir: item.startDir,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
if (manifest === undefined || seenPackageJson.has(manifest.packageJsonPath)) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
seenPackageJson.add(manifest.packageJsonPath);
|
|
651
|
+
if (item.optional && isRuntimePackageName(item.packageName)) {
|
|
652
|
+
optionalPackages.add(item.packageName);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const packageDir = dirname(manifest.packageJsonPath);
|
|
656
|
+
for (const dependencyName of Object.keys(manifest.dependencies ?? {})) {
|
|
657
|
+
if (isValidRuntimePackageName(dependencyName)) {
|
|
658
|
+
queue.push({ packageName: dependencyName, optional: false, startDir: packageDir });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
for (const optionalDependencyName of Object.keys(manifest.optionalDependencies ?? {})) {
|
|
663
|
+
if (isValidRuntimePackageName(optionalDependencyName)) {
|
|
664
|
+
queue.push({ packageName: optionalDependencyName, optional: true, startDir: packageDir });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return [...optionalPackages].sort();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function readRuntimePackageManifest(options: {
|
|
673
|
+
cache: RuntimePackageManifestCache;
|
|
674
|
+
packageName: string;
|
|
675
|
+
startDir: string;
|
|
676
|
+
}): Promise<(RuntimePackageManifest & { packageJsonPath: string }) | undefined> {
|
|
677
|
+
const key = `${options.startDir}\0${options.packageName}`;
|
|
678
|
+
const cached = options.cache.get(key);
|
|
679
|
+
const manifest =
|
|
680
|
+
cached ??
|
|
681
|
+
findRuntimePackageJson(options.packageName, options.startDir).then(async (packageJsonPath) => {
|
|
682
|
+
if (packageJsonPath === undefined) {
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
const json = JSON.parse(await readFile(packageJsonPath, "utf8")) as RuntimePackageManifest;
|
|
688
|
+
return { ...json, packageJsonPath };
|
|
689
|
+
} catch {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
if (cached === undefined) {
|
|
695
|
+
options.cache.set(key, manifest);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return (await manifest) as (RuntimePackageManifest & { packageJsonPath: string }) | undefined;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function findRuntimePackageJson(
|
|
702
|
+
packageName: string,
|
|
703
|
+
startDir: string,
|
|
704
|
+
): Promise<string | undefined> {
|
|
705
|
+
if (!isValidRuntimePackageName(packageName)) {
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
let current = startDir;
|
|
710
|
+
|
|
711
|
+
while (true) {
|
|
712
|
+
const candidate = join(current, "node_modules", ...packageName.split("/"), "package.json");
|
|
713
|
+
try {
|
|
714
|
+
if ((await stat(candidate)).isFile()) {
|
|
715
|
+
return candidate;
|
|
716
|
+
}
|
|
717
|
+
} catch {
|
|
718
|
+
// Keep walking toward the filesystem root.
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const parent = dirname(current);
|
|
722
|
+
if (parent === current) {
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
current = parent;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
573
729
|
function isRuntimePackageSpecifier(specifier: string): boolean {
|
|
574
730
|
if (specifier.startsWith(".") || specifier.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)) {
|
|
575
731
|
return false;
|
|
576
732
|
}
|
|
577
733
|
|
|
578
734
|
const packageName = runtimePackageNameForSpecifier(specifier);
|
|
579
|
-
return !nodeBuiltinPackages.has(specifier) &&
|
|
735
|
+
return !nodeBuiltinPackages.has(specifier) && isRuntimePackageName(packageName);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function isRuntimePackageName(packageName: string): boolean {
|
|
739
|
+
return !frameworkRuntimePackages.has(packageName);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function isValidRuntimePackageName(packageName: string): boolean {
|
|
743
|
+
return (
|
|
744
|
+
/^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u.test(packageName) &&
|
|
745
|
+
!packageName.includes("..")
|
|
746
|
+
);
|
|
580
747
|
}
|
|
581
748
|
|
|
582
749
|
function runtimePackageNameForSpecifier(specifier: string): string {
|
|
@@ -588,13 +755,17 @@ function runtimePackageNameForSpecifier(specifier: string): string {
|
|
|
588
755
|
return scope !== undefined && name !== undefined ? `${scope}/${name}` : specifier;
|
|
589
756
|
}
|
|
590
757
|
|
|
591
|
-
function collectBuildServerActionManifest(options: {
|
|
758
|
+
async function collectBuildServerActionManifest(options: {
|
|
592
759
|
files: Record<string, string>;
|
|
593
760
|
projectRoot: string;
|
|
594
761
|
routes: readonly AppRoute[];
|
|
595
762
|
routesDir: string;
|
|
596
|
-
}):
|
|
763
|
+
}): Promise<{
|
|
764
|
+
allowedActions: BuiltServerActionReference[];
|
|
765
|
+
routeReferences: Map<string, BuiltServerActionExpressionReference[]>;
|
|
766
|
+
}> {
|
|
597
767
|
const entries = new Map<string, BuiltServerActionReference>();
|
|
768
|
+
const routeReferences = new Map<string, BuiltServerActionExpressionReference[]>();
|
|
598
769
|
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
599
770
|
const routeSourceFiles = new Set(
|
|
600
771
|
options.routes
|
|
@@ -616,26 +787,41 @@ function collectBuildServerActionManifest(options: {
|
|
|
616
787
|
}
|
|
617
788
|
|
|
618
789
|
if (routeSourceFiles.has(file)) {
|
|
619
|
-
|
|
790
|
+
const inference = await collectBuildInferredServerActionReferences({
|
|
620
791
|
file,
|
|
621
792
|
files: options.files,
|
|
622
793
|
relativeRoutesDir,
|
|
623
794
|
source: code,
|
|
624
|
-
})
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
for (const diagnostic of inference.diagnostics) {
|
|
798
|
+
console.warn(formatServerActionInferenceDiagnostic(diagnostic));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
routeReferences.set(file, inference.references);
|
|
802
|
+
|
|
803
|
+
for (const reference of inference.references) {
|
|
625
804
|
const key = `${reference.moduleId}#${reference.exportName}`;
|
|
626
805
|
|
|
627
806
|
if (!entries.has(key)) {
|
|
628
|
-
entries.set(key,
|
|
807
|
+
entries.set(key, {
|
|
808
|
+
exportName: reference.exportName,
|
|
809
|
+
inferred: reference.inferred,
|
|
810
|
+
moduleId: reference.moduleId,
|
|
811
|
+
});
|
|
629
812
|
}
|
|
630
813
|
}
|
|
631
814
|
}
|
|
632
815
|
}
|
|
633
816
|
|
|
634
|
-
return
|
|
635
|
-
left
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
817
|
+
return {
|
|
818
|
+
allowedActions: [...entries.values()].sort((left, right) =>
|
|
819
|
+
left.moduleId === right.moduleId
|
|
820
|
+
? left.exportName.localeCompare(right.exportName)
|
|
821
|
+
: left.moduleId.localeCompare(right.moduleId),
|
|
822
|
+
),
|
|
823
|
+
routeReferences,
|
|
824
|
+
};
|
|
639
825
|
}
|
|
640
826
|
|
|
641
827
|
function collectBuildInferredServerActionReferences(options: {
|
|
@@ -643,54 +829,25 @@ function collectBuildInferredServerActionReferences(options: {
|
|
|
643
829
|
files: Record<string, string>;
|
|
644
830
|
relativeRoutesDir: string;
|
|
645
831
|
source: string;
|
|
646
|
-
}):
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
)
|
|
658
|
-
const source = match.groups?.source;
|
|
659
|
-
const specifiers = match.groups?.specifiers;
|
|
660
|
-
|
|
661
|
-
if (source === undefined || specifiers === undefined || !source.startsWith(".")) {
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
const localFile = resolveBuildLocalSourceImport(options.files, options.file, source);
|
|
666
|
-
|
|
667
|
-
if (localFile === undefined) {
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const moduleId = moduleIdForBuildFile(localFile, options.relativeRoutesDir);
|
|
672
|
-
|
|
673
|
-
for (const specifier of specifiers.split(",")) {
|
|
674
|
-
const [exportName, localName] = specifier.trim().split(/\s+as\s+/);
|
|
675
|
-
const imported = exportName?.trim();
|
|
676
|
-
const local = localName?.trim() ?? imported;
|
|
677
|
-
|
|
678
|
-
if (
|
|
679
|
-
imported !== undefined &&
|
|
680
|
-
imported.length > 0 &&
|
|
681
|
-
local !== undefined &&
|
|
682
|
-
actionNames.has(local)
|
|
683
|
-
) {
|
|
684
|
-
references.push({ moduleId, exportName: imported, inferred: true });
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
return references;
|
|
832
|
+
}): Promise<{
|
|
833
|
+
diagnostics: { code: string; message: string }[];
|
|
834
|
+
references: BuiltServerActionExpressionReference[];
|
|
835
|
+
}> {
|
|
836
|
+
return collectBuildInferredServerActions({
|
|
837
|
+
file: options.file,
|
|
838
|
+
files: options.files,
|
|
839
|
+
relativeRoutesDir: options.relativeRoutesDir,
|
|
840
|
+
resolveSourceImport: (importer, source) =>
|
|
841
|
+
resolveBuildLocalSourceImport(options.files, importer, source),
|
|
842
|
+
source: options.source,
|
|
843
|
+
});
|
|
690
844
|
}
|
|
691
845
|
|
|
692
|
-
function
|
|
693
|
-
|
|
846
|
+
function formatServerActionInferenceDiagnostic(diagnostic: {
|
|
847
|
+
code: string;
|
|
848
|
+
message: string;
|
|
849
|
+
}): string {
|
|
850
|
+
return `${diagnostic.code}: ${diagnostic.message}`;
|
|
694
851
|
}
|
|
695
852
|
|
|
696
853
|
function isSourceModuleFile(file: string): boolean {
|
|
@@ -1038,7 +1195,10 @@ async function buildServerModuleArtifacts(options: {
|
|
|
1038
1195
|
const clientInference = await inferClientRouteModule({
|
|
1039
1196
|
...(route === undefined ? {} : { appDir: options.project.routesDir }),
|
|
1040
1197
|
cache: options.clientRouteInferenceCache,
|
|
1041
|
-
code:
|
|
1198
|
+
code:
|
|
1199
|
+
route === undefined
|
|
1200
|
+
? stripRouteClientOnlyExports(source)
|
|
1201
|
+
: stripRouteClientSource({ code: source, filename: route.file }),
|
|
1042
1202
|
filename: join(options.projectRoot, file),
|
|
1043
1203
|
...(route === undefined ? {} : { routePath: route.path }),
|
|
1044
1204
|
vitePlugins: options.vitePlugins,
|
|
@@ -2467,7 +2627,7 @@ async function writeClientRouteBundles(options: {
|
|
|
2467
2627
|
vitePlugins: options.vitePlugins,
|
|
2468
2628
|
});
|
|
2469
2629
|
const source = await readFile(route.file, "utf8");
|
|
2470
|
-
const clientSource =
|
|
2630
|
+
const clientSource = stripRouteClientSource({ code: source, filename: route.file });
|
|
2471
2631
|
const navigation = detectNavigationRuntimeHint(source);
|
|
2472
2632
|
const references = await collectClientRouteReferences({
|
|
2473
2633
|
appDir: options.appDir,
|
package/src/cli-options.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
AppRouterBuildTarget,
|
|
4
4
|
AppRouterClientSourceMapMode,
|
|
5
5
|
} from "./config.js";
|
|
6
|
+
import type { RequestHostPolicy } from "./serve.js";
|
|
6
7
|
|
|
7
8
|
export type CliRequestLogMode = "requests";
|
|
8
9
|
export type CliBuildTarget = AppRouterBuildTarget | "all";
|
|
@@ -10,8 +11,11 @@ export type CliBuildTarget = AppRouterBuildTarget | "all";
|
|
|
10
11
|
export interface ParsedCliArguments {
|
|
11
12
|
clientSourceMaps?: AppRouterClientSourceMapMode | undefined;
|
|
12
13
|
command: string;
|
|
14
|
+
allowedHosts?: readonly string[] | undefined;
|
|
13
15
|
from?: string | undefined;
|
|
14
16
|
help?: boolean | undefined;
|
|
17
|
+
host?: string | undefined;
|
|
18
|
+
hostPolicy?: RequestHostPolicy | undefined;
|
|
15
19
|
log?: CliRequestLogMode | undefined;
|
|
16
20
|
out?: string | undefined;
|
|
17
21
|
routeArg?: string | undefined;
|
|
@@ -43,6 +47,39 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
|
|
|
43
47
|
continue;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
if (value === "--host") {
|
|
51
|
+
parsed.host = readOptionValue(argv, index, "host");
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (value.startsWith("--host=")) {
|
|
57
|
+
parsed.host = value.slice("--host=".length);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (value === "--host-policy") {
|
|
62
|
+
parsed.hostPolicy = parseCliHostPolicy(readOptionValue(argv, index, "host-policy"));
|
|
63
|
+
index += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (value.startsWith("--host-policy=")) {
|
|
68
|
+
parsed.hostPolicy = parseCliHostPolicy(value.slice("--host-policy=".length));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (value === "--allowed-hosts") {
|
|
73
|
+
parsed.allowedHosts = parseCliAllowedHosts(readOptionValue(argv, index, "allowed-hosts"));
|
|
74
|
+
index += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (value.startsWith("--allowed-hosts=")) {
|
|
79
|
+
parsed.allowedHosts = parseCliAllowedHosts(value.slice("--allowed-hosts=".length));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
46
83
|
if (value === "--from") {
|
|
47
84
|
parsed.from = readOptionValue(argv, index, "from");
|
|
48
85
|
index += 1;
|
|
@@ -155,8 +192,21 @@ export function formatCliHelp(command?: string | undefined): string {
|
|
|
155
192
|
"Serve built mreact app router output with the Node adapter.",
|
|
156
193
|
"",
|
|
157
194
|
"Options:",
|
|
195
|
+
" --host <host> Bind address. Default: 127.0.0.1. Use 0.0.0.0 inside containers behind explicit port publishing or a reverse proxy.",
|
|
196
|
+
" --host-policy=strict|trusted-proxy",
|
|
197
|
+
" Control Host header trust for request origin reconstruction.",
|
|
198
|
+
" --allowed-hosts <host[,host...]>",
|
|
199
|
+
" Exact Host header allow-list for public deployments.",
|
|
158
200
|
" --log=requests Print request summaries.",
|
|
159
201
|
" -h, --help Show this help message.",
|
|
202
|
+
"",
|
|
203
|
+
"Environment:",
|
|
204
|
+
" HOST Bind address when --host is not set.",
|
|
205
|
+
" MREACT_ROUTER_HOST_POLICY",
|
|
206
|
+
" Host header trust policy when --host-policy is not set.",
|
|
207
|
+
" MREACT_ROUTER_ALLOWED_HOSTS",
|
|
208
|
+
" Comma-separated Host header allow-list when --allowed-hosts is not set.",
|
|
209
|
+
" PORT TCP port. Default: 3001.",
|
|
160
210
|
].join("\n");
|
|
161
211
|
}
|
|
162
212
|
|
|
@@ -222,6 +272,42 @@ export function resolveCliRequestLogMode(
|
|
|
222
272
|
return parseCliRequestLogMode(envValue);
|
|
223
273
|
}
|
|
224
274
|
|
|
275
|
+
export function resolveCliHost(
|
|
276
|
+
flagValue: string | undefined,
|
|
277
|
+
env: { HOST?: string | undefined },
|
|
278
|
+
): string {
|
|
279
|
+
if (flagValue !== undefined && flagValue !== "") {
|
|
280
|
+
return flagValue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const envValue = env.HOST;
|
|
284
|
+
return envValue === undefined || envValue === "" ? "127.0.0.1" : envValue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function resolveCliHostPolicy(
|
|
288
|
+
flagValue: RequestHostPolicy | undefined,
|
|
289
|
+
env: { MREACT_ROUTER_HOST_POLICY?: string | undefined },
|
|
290
|
+
): RequestHostPolicy | undefined {
|
|
291
|
+
if (flagValue !== undefined) {
|
|
292
|
+
return flagValue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const envValue = env.MREACT_ROUTER_HOST_POLICY;
|
|
296
|
+
return envValue === undefined || envValue === "" ? undefined : parseCliHostPolicy(envValue);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function resolveCliAllowedHosts(
|
|
300
|
+
flagValue: readonly string[] | undefined,
|
|
301
|
+
env: { MREACT_ROUTER_ALLOWED_HOSTS?: string | undefined },
|
|
302
|
+
): readonly string[] | undefined {
|
|
303
|
+
if (flagValue !== undefined) {
|
|
304
|
+
return flagValue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const envValue = env.MREACT_ROUTER_ALLOWED_HOSTS;
|
|
308
|
+
return envValue === undefined || envValue === "" ? undefined : parseCliAllowedHosts(envValue);
|
|
309
|
+
}
|
|
310
|
+
|
|
225
311
|
export function createCliRequestLogger(): AppRouterLogger {
|
|
226
312
|
return {
|
|
227
313
|
error(event) {
|
|
@@ -245,6 +331,23 @@ function parseCliRequestLogMode(value: string): CliRequestLogMode {
|
|
|
245
331
|
throw new Error(`Unsupported log mode ${JSON.stringify(value)}. Expected "requests".`);
|
|
246
332
|
}
|
|
247
333
|
|
|
334
|
+
function parseCliHostPolicy(value: string): RequestHostPolicy {
|
|
335
|
+
if (value === "strict" || value === "trusted-proxy") {
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Unsupported host policy ${JSON.stringify(value)}. Expected "strict" or "trusted-proxy".`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function parseCliAllowedHosts(value: string): readonly string[] {
|
|
345
|
+
return value
|
|
346
|
+
.split(",")
|
|
347
|
+
.map((entry) => entry.trim())
|
|
348
|
+
.filter((entry) => entry !== "");
|
|
349
|
+
}
|
|
350
|
+
|
|
248
351
|
function parseCliBuildTarget(value: string): CliBuildTarget {
|
|
249
352
|
if (value === "node" || value === "cloudflare" || value === "aws-lambda" || value === "all") {
|
|
250
353
|
return value;
|
package/src/cli.ts
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
createCliRequestLogger,
|
|
8
8
|
formatCliHelp,
|
|
9
9
|
parseCliArguments,
|
|
10
|
+
resolveCliAllowedHosts,
|
|
11
|
+
resolveCliHost,
|
|
12
|
+
resolveCliHostPolicy,
|
|
10
13
|
resolveCliRequestLogMode,
|
|
11
14
|
} from "./cli-options.js";
|
|
12
15
|
import { startDevServer } from "./dev-server.js";
|
|
@@ -77,6 +80,9 @@ if (parsed !== undefined) {
|
|
|
77
80
|
console.log(`mreact app router ready at ${server.url}`);
|
|
78
81
|
} else if (command === "start") {
|
|
79
82
|
const server = await startServer({
|
|
83
|
+
allowedHosts: resolveCliAllowedHosts(parsed.allowedHosts, process.env),
|
|
84
|
+
hostPolicy: resolveCliHostPolicy(parsed.hostPolicy, process.env),
|
|
85
|
+
hostname: resolveCliHost(parsed.host, process.env),
|
|
80
86
|
logger,
|
|
81
87
|
outDir: resolve(routeArg ?? ".mreact"),
|
|
82
88
|
port: Number(process.env.PORT ?? 3001),
|