@moonsjs/vite 0.1.13 → 0.1.15
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/dist/{index-qr2w9b24.js → index-snbxks72.js} +162 -1
- package/dist/{index-qr2w9b24.js.map → index-snbxks72.js.map} +3 -3
- package/dist/index.js +1 -1
- package/dist/server/index.js +57 -3
- package/dist/server/index.js.map +3 -3
- package/dist/server/node-server.d.ts.map +1 -1
- package/dist/transform.d.ts +0 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +1 -1
- package/package.json +1 -1
- package/src/server/node-server.ts +82 -3
- package/src/transform.ts +199 -0
package/src/transform.ts
CHANGED
|
@@ -758,11 +758,151 @@ export function nonContractServerFileError(file: string, projectRoot: string): M
|
|
|
758
758
|
}
|
|
759
759
|
|
|
760
760
|
/** Periksa import modul client terhadap §12.4/§12.7/§12.8 — berbasis hasil resolve. */
|
|
761
|
+
interface TraceFileRecord {
|
|
762
|
+
ast: AstNode | null;
|
|
763
|
+
mentionsServer: boolean;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Addendum K.1: telusuri apakah `name` dari sebuah modul client pada akhirnya
|
|
768
|
+
* berasal dari `*.server.ts` (langsung atau lewat rantai re-export/barrel),
|
|
769
|
+
* dan bukan serverFn. Mengembalikan file server yang bermasalah, atau null.
|
|
770
|
+
*/
|
|
771
|
+
async function traceValueToServer(input: {
|
|
772
|
+
file: string;
|
|
773
|
+
name: string;
|
|
774
|
+
resolveSafe: (specifier: string, importer: string) => Promise<string | null>;
|
|
775
|
+
analyze: (absoluteFile: string) => ServerFileAnalysis | null;
|
|
776
|
+
cache: Map<string, TraceFileRecord>;
|
|
777
|
+
seen: Set<string>;
|
|
778
|
+
}): Promise<string | null> {
|
|
779
|
+
const key = `${input.file}#${input.name}`;
|
|
780
|
+
if (input.seen.has(key) || input.seen.size > 64) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
input.seen.add(key);
|
|
784
|
+
|
|
785
|
+
const record = loadTraceFile(input.file, input.cache);
|
|
786
|
+
if (record === null || record.ast === null || !record.mentionsServer) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
const ast = record.ast;
|
|
790
|
+
if (localTopLevelNames(ast).has(input.name)) {
|
|
791
|
+
return null; // didefinisikan lokal = kode client
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const importBindings = new Map<string, { specifier: string; imported: string }>();
|
|
795
|
+
const edges: Array<{ specifier: string; local: string }> = [];
|
|
796
|
+
|
|
797
|
+
for (const statement of topLevelStatements(ast)) {
|
|
798
|
+
if (statement.type === "ImportDeclaration") {
|
|
799
|
+
const source = (statement["source"] as AstNode | null | undefined)?.["value"];
|
|
800
|
+
if (typeof source !== "string") {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
for (const item of (statement["specifiers"] ?? []) as AstNode[]) {
|
|
804
|
+
if (item.type !== "ImportSpecifier") {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const local = (item["local"] as AstNode | undefined)?.["name"] as string | undefined;
|
|
808
|
+
const imported = ((item["imported"] as AstNode | undefined)?.["name"] as string | undefined) ?? local;
|
|
809
|
+
if (local !== undefined && imported !== undefined) {
|
|
810
|
+
importBindings.set(local, { specifier: source, imported });
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (statement.type === "ExportNamedDeclaration") {
|
|
817
|
+
const source = (statement["source"] as AstNode | null | undefined)?.["value"];
|
|
818
|
+
for (const item of (statement["specifiers"] ?? []) as AstNode[]) {
|
|
819
|
+
if (item.type !== "ExportSpecifier") {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const local = (item["local"] as AstNode | undefined)?.["name"] as string | undefined;
|
|
823
|
+
const exported = ((item["exported"] as AstNode | undefined)?.["name"] as string | undefined) ?? local;
|
|
824
|
+
if (local === undefined || exported !== input.name) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (typeof source === "string") {
|
|
828
|
+
edges.push({ specifier: source, local });
|
|
829
|
+
} else {
|
|
830
|
+
const binding = importBindings.get(local);
|
|
831
|
+
if (binding !== undefined) {
|
|
832
|
+
edges.push({ specifier: binding.specifier, local: binding.imported });
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const exportedAll = statement["exported"];
|
|
840
|
+
if (
|
|
841
|
+
statement.type === "ExportAllDeclaration" &&
|
|
842
|
+
(exportedAll === null || exportedAll === undefined)
|
|
843
|
+
) {
|
|
844
|
+
const source = (statement["source"] as AstNode | null | undefined)?.["value"];
|
|
845
|
+
if (typeof source === "string") {
|
|
846
|
+
edges.push({ specifier: source, local: input.name });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
for (const edge of edges) {
|
|
852
|
+
const next = await input.resolveSafe(edge.specifier, input.file);
|
|
853
|
+
if (next === null) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (isServerFilePath(next)) {
|
|
857
|
+
const analysis = input.analyze(next);
|
|
858
|
+
const isServerFn =
|
|
859
|
+
analysis !== null && analysis.serverFns.some((fn) => fn.name === edge.local);
|
|
860
|
+
if (!isServerFn) {
|
|
861
|
+
return next;
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const deeper = await traceValueToServer({
|
|
866
|
+
...input,
|
|
867
|
+
file: next,
|
|
868
|
+
name: edge.local,
|
|
869
|
+
});
|
|
870
|
+
if (deeper !== null) {
|
|
871
|
+
return deeper;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function loadTraceFile(file: string, cache: Map<string, TraceFileRecord>): TraceFileRecord | null {
|
|
878
|
+
const cached = cache.get(file);
|
|
879
|
+
if (cached !== undefined) {
|
|
880
|
+
return cached;
|
|
881
|
+
}
|
|
882
|
+
let record: TraceFileRecord | null = null;
|
|
883
|
+
try {
|
|
884
|
+
const source = readFileSync(file, "utf8");
|
|
885
|
+
// Pra-filter murah: hanya modul yang (a) menyebut `.server` atau (b)
|
|
886
|
+
// punya re-export (`export … from`) yang bisa menjadi mata rantai barrel.
|
|
887
|
+
const isBarrel = /(^|\n)\s*export\s+(\*|\{[^}]*\})\s+from\s/.test(source);
|
|
888
|
+
const mentionsServer = source.includes(".server") || isBarrel;
|
|
889
|
+
record = {
|
|
890
|
+
ast: mentionsServer ? parseModule(source, file) : null,
|
|
891
|
+
mentionsServer,
|
|
892
|
+
};
|
|
893
|
+
} catch {
|
|
894
|
+
record = null;
|
|
895
|
+
}
|
|
896
|
+
cache.set(file, record ?? { ast: null, mentionsServer: false });
|
|
897
|
+
return record;
|
|
898
|
+
}
|
|
899
|
+
|
|
761
900
|
export async function checkClientModuleImports(
|
|
762
901
|
context: ClientImportCheckContext,
|
|
763
902
|
): Promise<void> {
|
|
764
903
|
const { file, code, projectRoot, resolve, analyze } = context;
|
|
765
904
|
const ast = parseModule(code, file);
|
|
905
|
+
const traceCache = new Map<string, TraceFileRecord>();
|
|
766
906
|
const relativeFile = toRelative(projectRoot, file);
|
|
767
907
|
// Addendum C: gate paket server-only (daftar bawaan ∪ security.serverOnlyPackages ∪ marker).
|
|
768
908
|
const serverOnly = createServerOnlyClassifier({
|
|
@@ -990,6 +1130,65 @@ export async function checkClientModuleImports(
|
|
|
990
1130
|
const serverBySpecifier = isServerSpecifier(specifier);
|
|
991
1131
|
const serverByResolve = resolvedTarget !== null && isServerFilePath(resolvedTarget);
|
|
992
1132
|
if (!serverBySpecifier && !serverByResolve) {
|
|
1133
|
+
// Addendum K.1: rantai re-export ke `*.server.ts` (barrel) — pesan Moons
|
|
1134
|
+
// menggantikan `MISSING_EXPORT` bundler. Hanya nilai (bukan type-only).
|
|
1135
|
+
if (!typeOnly && resolvedTarget !== null && /[.](ts|tsx)$/.test(resolvedTarget)) {
|
|
1136
|
+
const requestedNames: string[] = [];
|
|
1137
|
+
if (statement.type === "ImportDeclaration") {
|
|
1138
|
+
for (const item of (statement["specifiers"] ?? []) as AstNode[]) {
|
|
1139
|
+
if (item.type !== "ImportSpecifier") {
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
const kind = (item["importKind"] ?? statement["importKind"]) as string | undefined;
|
|
1143
|
+
if (kind === "type") {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
const imported = (item["imported"] as AstNode | undefined)?.["name"] as string | undefined;
|
|
1147
|
+
if (imported !== undefined) {
|
|
1148
|
+
requestedNames.push(imported);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
} else if (statement.type === "ExportNamedDeclaration") {
|
|
1152
|
+
for (const item of (statement["specifiers"] ?? []) as AstNode[]) {
|
|
1153
|
+
const kind = (item["importKind"] ??
|
|
1154
|
+
statement["importKind"] ??
|
|
1155
|
+
item["exportKind"] ??
|
|
1156
|
+
statement["exportKind"]) as string | undefined;
|
|
1157
|
+
if (kind === "type") {
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
const local = (item["local"] as AstNode | undefined)?.["name"] as string | undefined;
|
|
1161
|
+
if (local !== undefined) {
|
|
1162
|
+
requestedNames.push(local);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
for (const name of requestedNames) {
|
|
1167
|
+
const serverFile = await traceValueToServer({
|
|
1168
|
+
file: resolvedTarget,
|
|
1169
|
+
name,
|
|
1170
|
+
resolveSafe,
|
|
1171
|
+
analyze,
|
|
1172
|
+
cache: traceCache,
|
|
1173
|
+
seen: new Set<string>(),
|
|
1174
|
+
});
|
|
1175
|
+
if (serverFile !== null) {
|
|
1176
|
+
throw new MoonsError({
|
|
1177
|
+
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
1178
|
+
file: relativeFile,
|
|
1179
|
+
line,
|
|
1180
|
+
message: `mengimpor \`${name}\` — berasal dari \`${toRelative(projectRoot, serverFile)}\` (via rantai re-export)`,
|
|
1181
|
+
details:
|
|
1182
|
+
`\`${toRelative(projectRoot, resolvedTarget)}\` me-re-export nilai dari file server; ` +
|
|
1183
|
+
"stub client tidak mengekspornya (dulu gagal dengan error mentah bundler `MISSING_EXPORT`).",
|
|
1184
|
+
suggestions: [
|
|
1185
|
+
"Buat server function yang mengembalikan data yang dibutuhkan, atau",
|
|
1186
|
+
"Definisikan ulang konstanta publik itu di modul client-safe (jangan pindahkan nilai rahasia)",
|
|
1187
|
+
],
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
993
1192
|
continue;
|
|
994
1193
|
}
|
|
995
1194
|
|