@valbuild/server 0.105.1 → 0.106.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.
- package/dist/declarations/src/fixHandlers.d.ts +8 -1
- package/dist/declarations/src/index.d.ts +2 -1
- package/dist/declarations/src/loadValModules.d.ts +41 -1
- package/dist/valbuild-server.cjs.dev.js +145 -0
- package/dist/valbuild-server.cjs.prod.js +145 -0
- package/dist/valbuild-server.esm.js +146 -2
- package/package.json +4 -4
|
@@ -120,7 +120,14 @@ export type ValidationEvent = {
|
|
|
120
120
|
sourcePath: string;
|
|
121
121
|
fixes: string[];
|
|
122
122
|
keyError?: boolean;
|
|
123
|
-
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A file that default exports a Val module, but that val.modules does not
|
|
126
|
+
* register — so Val will never serve it. Files that do NOT default export a
|
|
127
|
+
* module are not reported at all, and one whose default export is not a
|
|
128
|
+
* module is a `fatal-error`; see `reportUnregistered` in the CLI.
|
|
129
|
+
*/
|
|
130
|
+
| {
|
|
124
131
|
type: "unregistered-module";
|
|
125
132
|
file: string;
|
|
126
133
|
} | {
|
|
@@ -33,7 +33,8 @@ export { extractJsonValuesEntry } from "./extractJsonValuesEntry.js";
|
|
|
33
33
|
export type { ModulePathMap } from "./modulePathMap.js";
|
|
34
34
|
export { ValOpsFS } from "./ValOpsFS.js";
|
|
35
35
|
export { ValOpsHttp } from "./ValOpsHttp.js";
|
|
36
|
-
export { loadValModules } from "./loadValModules.js";
|
|
36
|
+
export { loadValModules, createValModuleFileInspector } from "./loadValModules.js";
|
|
37
|
+
export type { ValModuleFileInspection } from "./loadValModules.js";
|
|
37
38
|
export { formatPatchSourceError } from "./ValOps.js";
|
|
38
39
|
export { compareWithCapturedReport, readCapturedReport, replaySnapshot, } from "./debug/replaySnapshot.js";
|
|
39
40
|
export type { ReplayComparison, ReplayResult } from "./debug/replaySnapshot.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import type
|
|
2
|
+
import { type ValModules } from "@valbuild/core";
|
|
3
3
|
/**
|
|
4
4
|
* The filesystem seam `loadValModules` reads through.
|
|
5
5
|
*
|
|
@@ -31,3 +31,43 @@ export type ValModulesHost = ts.ParseConfigHost & ts.ModuleResolutionHost;
|
|
|
31
31
|
* evaluate untrusted or third-party modules.
|
|
32
32
|
*/
|
|
33
33
|
export declare function loadValModules(projectRoot: string, host?: ValModulesHost): ValModules;
|
|
34
|
+
/**
|
|
35
|
+
* What a `*.val.ts` file that is NOT registered in `val.modules` turns out to
|
|
36
|
+
* be.
|
|
37
|
+
*
|
|
38
|
+
* A file matching `*.val.ts` is not necessarily a Val module: the same
|
|
39
|
+
* convention is used for shared schemas and other content-adjacent helpers, and
|
|
40
|
+
* those are not meant to be registered. Only a file that actually default
|
|
41
|
+
* exports a module is worth warning about; one that default exports something
|
|
42
|
+
* else is a mistake, because nothing will ever load it.
|
|
43
|
+
*
|
|
44
|
+
* See {@link createValModuleFileInspector}.
|
|
45
|
+
*/
|
|
46
|
+
export type ValModuleFileInspection =
|
|
47
|
+
/** No default export at all: not a Val module, and not trying to be one. */
|
|
48
|
+
{
|
|
49
|
+
status: "no-default-export";
|
|
50
|
+
}
|
|
51
|
+
/** A real Val module — `export default c.define(...)`. */
|
|
52
|
+
| {
|
|
53
|
+
status: "val-module";
|
|
54
|
+
}
|
|
55
|
+
/** A default export that is not a Val module, or that would not evaluate. */
|
|
56
|
+
| {
|
|
57
|
+
status: "invalid";
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Inspects individual `*.val.{ts,js}` files, sharing one module cache and one
|
|
62
|
+
* parsed tsconfig across every call.
|
|
63
|
+
*
|
|
64
|
+
* The default export is checked SYNTACTICALLY first and only evaluated if it is
|
|
65
|
+
* there. That ordering is the point: a `.val.ts` with no default export is a
|
|
66
|
+
* helper file, and evaluating it to learn that would be both wasted work and a
|
|
67
|
+
* way to turn an unrelated top-level throw into a reported error.
|
|
68
|
+
*
|
|
69
|
+
* SECURITY: evaluation goes through the same `vm` loader as
|
|
70
|
+
* {@link loadValModules} — see the warning there. Only ever point this at the
|
|
71
|
+
* project's own first-party files.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createValModuleFileInspector(projectRoot: string, host?: ValModulesHost): (absPath: string) => ValModuleFileInspection;
|
|
@@ -896,6 +896,150 @@ function resolveRelative(dirName, spec, host) {
|
|
|
896
896
|
return null;
|
|
897
897
|
}
|
|
898
898
|
|
|
899
|
+
/**
|
|
900
|
+
* What a `*.val.ts` file that is NOT registered in `val.modules` turns out to
|
|
901
|
+
* be.
|
|
902
|
+
*
|
|
903
|
+
* A file matching `*.val.ts` is not necessarily a Val module: the same
|
|
904
|
+
* convention is used for shared schemas and other content-adjacent helpers, and
|
|
905
|
+
* those are not meant to be registered. Only a file that actually default
|
|
906
|
+
* exports a module is worth warning about; one that default exports something
|
|
907
|
+
* else is a mistake, because nothing will ever load it.
|
|
908
|
+
*
|
|
909
|
+
* See {@link createValModuleFileInspector}.
|
|
910
|
+
*/
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Inspects individual `*.val.{ts,js}` files, sharing one module cache and one
|
|
914
|
+
* parsed tsconfig across every call.
|
|
915
|
+
*
|
|
916
|
+
* The default export is checked SYNTACTICALLY first and only evaluated if it is
|
|
917
|
+
* there. That ordering is the point: a `.val.ts` with no default export is a
|
|
918
|
+
* helper file, and evaluating it to learn that would be both wasted work and a
|
|
919
|
+
* way to turn an unrelated top-level throw into a reported error.
|
|
920
|
+
*
|
|
921
|
+
* SECURITY: evaluation goes through the same `vm` loader as
|
|
922
|
+
* {@link loadValModules} — see the warning there. Only ever point this at the
|
|
923
|
+
* project's own first-party files.
|
|
924
|
+
*/
|
|
925
|
+
function createValModuleFileInspector(projectRoot, host = ts__default["default"].sys) {
|
|
926
|
+
const compilerOptions = getCompilerOptions(projectRoot, host);
|
|
927
|
+
const cache = {};
|
|
928
|
+
return absPath => {
|
|
929
|
+
const code = host.readFile(absPath);
|
|
930
|
+
if (code === undefined) {
|
|
931
|
+
return {
|
|
932
|
+
status: "invalid",
|
|
933
|
+
message: `Could not read file: '${absPath}'`
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
const sourceFile = ts__default["default"].createSourceFile(absPath, code, ts__default["default"].ScriptTarget.ES2020, true);
|
|
937
|
+
if (!hasDefaultExport(sourceFile)) {
|
|
938
|
+
return {
|
|
939
|
+
status: "no-default-export"
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
let exports;
|
|
943
|
+
// `loadModule` inserts a module into the cache BEFORE evaluating it, so
|
|
944
|
+
// that a cycle resolves. One that throws therefore leaves a half-built
|
|
945
|
+
// entry behind - and unlike `loadValModules`, which builds a cache per
|
|
946
|
+
// call and lets the throw escape, this cache outlives the failure. A later
|
|
947
|
+
// inspection of the same file (or of the helper that actually threw) would
|
|
948
|
+
// hit that entry, see empty exports, and report "default export is
|
|
949
|
+
// undefined" instead of the real error - or, worse, quietly downgrade it to
|
|
950
|
+
// a warning. So roll the cache back to what it was before this attempt.
|
|
951
|
+
const before = new Set(Object.keys(cache));
|
|
952
|
+
try {
|
|
953
|
+
exports = loadModule(absPath, cache, compilerOptions, host).exports;
|
|
954
|
+
} catch (e) {
|
|
955
|
+
for (const key of Object.keys(cache)) {
|
|
956
|
+
if (!before.has(key)) {
|
|
957
|
+
delete cache[key];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
status: "invalid",
|
|
962
|
+
message: `Could not be loaded. Error: ${errorMessage(e)}`
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (core.Internal.isValModule(exports.default)) {
|
|
966
|
+
return {
|
|
967
|
+
status: "val-module"
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
// NOTE: do NOT suggest wrapping this in `c.define`. A shared schema turned
|
|
971
|
+
// into a module is an UNREGISTERED module, i.e. straight back to a warning.
|
|
972
|
+
// The fix is to move it out of the default export slot, which is the one
|
|
973
|
+
// thing about a `.val.ts` that Val reserves for itself.
|
|
974
|
+
return {
|
|
975
|
+
status: "invalid",
|
|
976
|
+
message: `Default export is ${describeDefaultExport(exports.default)}, not a Val module. Only 'c.define(...)' may be the default export of a '*.val.ts' file: use a named export for a shared schema or helper`
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Whether the file exports a RUNTIME value as `default`, without evaluating it.
|
|
983
|
+
*
|
|
984
|
+
* Two things deliberately do not count, because neither exists once the file is
|
|
985
|
+
* transpiled — and treating either as a default export would send a pure helper
|
|
986
|
+
* off to be evaluated and reported:
|
|
987
|
+
*
|
|
988
|
+
* - `export * from "./x"`, since a star re-export never carries the default;
|
|
989
|
+
* - a type-only export, in either of its spellings
|
|
990
|
+
* (`export type { T as default }` and `export { type T as default }`).
|
|
991
|
+
*/
|
|
992
|
+
function hasDefaultExport(sourceFile) {
|
|
993
|
+
return sourceFile.statements.some(statement => {
|
|
994
|
+
// `export default <expr>` — but not `export = x`, which shares this node.
|
|
995
|
+
if (ts__default["default"].isExportAssignment(statement)) {
|
|
996
|
+
return !statement.isExportEquals;
|
|
997
|
+
}
|
|
998
|
+
// `export { x as default }` / `export { default } from "./x"`
|
|
999
|
+
if (ts__default["default"].isExportDeclaration(statement) && !statement.isTypeOnly && statement.exportClause && ts__default["default"].isNamedExports(statement.exportClause)) {
|
|
1000
|
+
return statement.exportClause.elements.some(element => !element.isTypeOnly && element.name.text === "default");
|
|
1001
|
+
}
|
|
1002
|
+
// `export default function f() {}` / `export default class C {}`, which are
|
|
1003
|
+
// declarations carrying a `default` modifier rather than export assignments.
|
|
1004
|
+
return ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/** A short, human-readable "what you exported instead" for the error message. */
|
|
1009
|
+
function describeDefaultExport(value) {
|
|
1010
|
+
if (value === null) {
|
|
1011
|
+
return "null";
|
|
1012
|
+
}
|
|
1013
|
+
if (Array.isArray(value)) {
|
|
1014
|
+
return "an array";
|
|
1015
|
+
}
|
|
1016
|
+
if (typeof value === "object") {
|
|
1017
|
+
// Duck-typed rather than `instanceof Schema`, for the same cross-realm
|
|
1018
|
+
// reason `isValModule` avoids a constructor check.
|
|
1019
|
+
if ("executeSerialize" in value && typeof value["executeSerialize"] === "function") {
|
|
1020
|
+
return "a schema";
|
|
1021
|
+
}
|
|
1022
|
+
return "an object";
|
|
1023
|
+
}
|
|
1024
|
+
if (typeof value === "undefined") {
|
|
1025
|
+
return "undefined";
|
|
1026
|
+
}
|
|
1027
|
+
return `a ${typeof value}`;
|
|
1028
|
+
}
|
|
1029
|
+
function errorMessage(e) {
|
|
1030
|
+
// NOT `e instanceof Error`: an error thrown from inside the `vm` context is
|
|
1031
|
+
// built from that realm's Error constructor. Duck-type the message instead.
|
|
1032
|
+
if (typeof e === "object" && e !== null && "message" in e) {
|
|
1033
|
+
const {
|
|
1034
|
+
message
|
|
1035
|
+
} = e;
|
|
1036
|
+
if (typeof message === "string") {
|
|
1037
|
+
return message;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return String(e);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
899
1043
|
const jsonOps$1 = new patch.JSONOps();
|
|
900
1044
|
|
|
901
1045
|
/**
|
|
@@ -12531,6 +12675,7 @@ exports.createJsonEntryPathMap = createJsonEntryPathMap;
|
|
|
12531
12675
|
exports.createModulePathMap = createModulePathMap;
|
|
12532
12676
|
exports.createService = createService;
|
|
12533
12677
|
exports.createValApiRouter = createValApiRouter;
|
|
12678
|
+
exports.createValModuleFileInspector = createValModuleFileInspector;
|
|
12534
12679
|
exports.createValServer = createValServer;
|
|
12535
12680
|
exports.currentFixHandlers = currentFixHandlers;
|
|
12536
12681
|
exports.decodeJwt = decodeJwt;
|
|
@@ -896,6 +896,150 @@ function resolveRelative(dirName, spec, host) {
|
|
|
896
896
|
return null;
|
|
897
897
|
}
|
|
898
898
|
|
|
899
|
+
/**
|
|
900
|
+
* What a `*.val.ts` file that is NOT registered in `val.modules` turns out to
|
|
901
|
+
* be.
|
|
902
|
+
*
|
|
903
|
+
* A file matching `*.val.ts` is not necessarily a Val module: the same
|
|
904
|
+
* convention is used for shared schemas and other content-adjacent helpers, and
|
|
905
|
+
* those are not meant to be registered. Only a file that actually default
|
|
906
|
+
* exports a module is worth warning about; one that default exports something
|
|
907
|
+
* else is a mistake, because nothing will ever load it.
|
|
908
|
+
*
|
|
909
|
+
* See {@link createValModuleFileInspector}.
|
|
910
|
+
*/
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Inspects individual `*.val.{ts,js}` files, sharing one module cache and one
|
|
914
|
+
* parsed tsconfig across every call.
|
|
915
|
+
*
|
|
916
|
+
* The default export is checked SYNTACTICALLY first and only evaluated if it is
|
|
917
|
+
* there. That ordering is the point: a `.val.ts` with no default export is a
|
|
918
|
+
* helper file, and evaluating it to learn that would be both wasted work and a
|
|
919
|
+
* way to turn an unrelated top-level throw into a reported error.
|
|
920
|
+
*
|
|
921
|
+
* SECURITY: evaluation goes through the same `vm` loader as
|
|
922
|
+
* {@link loadValModules} — see the warning there. Only ever point this at the
|
|
923
|
+
* project's own first-party files.
|
|
924
|
+
*/
|
|
925
|
+
function createValModuleFileInspector(projectRoot, host = ts__default["default"].sys) {
|
|
926
|
+
const compilerOptions = getCompilerOptions(projectRoot, host);
|
|
927
|
+
const cache = {};
|
|
928
|
+
return absPath => {
|
|
929
|
+
const code = host.readFile(absPath);
|
|
930
|
+
if (code === undefined) {
|
|
931
|
+
return {
|
|
932
|
+
status: "invalid",
|
|
933
|
+
message: `Could not read file: '${absPath}'`
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
const sourceFile = ts__default["default"].createSourceFile(absPath, code, ts__default["default"].ScriptTarget.ES2020, true);
|
|
937
|
+
if (!hasDefaultExport(sourceFile)) {
|
|
938
|
+
return {
|
|
939
|
+
status: "no-default-export"
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
let exports;
|
|
943
|
+
// `loadModule` inserts a module into the cache BEFORE evaluating it, so
|
|
944
|
+
// that a cycle resolves. One that throws therefore leaves a half-built
|
|
945
|
+
// entry behind - and unlike `loadValModules`, which builds a cache per
|
|
946
|
+
// call and lets the throw escape, this cache outlives the failure. A later
|
|
947
|
+
// inspection of the same file (or of the helper that actually threw) would
|
|
948
|
+
// hit that entry, see empty exports, and report "default export is
|
|
949
|
+
// undefined" instead of the real error - or, worse, quietly downgrade it to
|
|
950
|
+
// a warning. So roll the cache back to what it was before this attempt.
|
|
951
|
+
const before = new Set(Object.keys(cache));
|
|
952
|
+
try {
|
|
953
|
+
exports = loadModule(absPath, cache, compilerOptions, host).exports;
|
|
954
|
+
} catch (e) {
|
|
955
|
+
for (const key of Object.keys(cache)) {
|
|
956
|
+
if (!before.has(key)) {
|
|
957
|
+
delete cache[key];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
status: "invalid",
|
|
962
|
+
message: `Could not be loaded. Error: ${errorMessage(e)}`
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (core.Internal.isValModule(exports.default)) {
|
|
966
|
+
return {
|
|
967
|
+
status: "val-module"
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
// NOTE: do NOT suggest wrapping this in `c.define`. A shared schema turned
|
|
971
|
+
// into a module is an UNREGISTERED module, i.e. straight back to a warning.
|
|
972
|
+
// The fix is to move it out of the default export slot, which is the one
|
|
973
|
+
// thing about a `.val.ts` that Val reserves for itself.
|
|
974
|
+
return {
|
|
975
|
+
status: "invalid",
|
|
976
|
+
message: `Default export is ${describeDefaultExport(exports.default)}, not a Val module. Only 'c.define(...)' may be the default export of a '*.val.ts' file: use a named export for a shared schema or helper`
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Whether the file exports a RUNTIME value as `default`, without evaluating it.
|
|
983
|
+
*
|
|
984
|
+
* Two things deliberately do not count, because neither exists once the file is
|
|
985
|
+
* transpiled — and treating either as a default export would send a pure helper
|
|
986
|
+
* off to be evaluated and reported:
|
|
987
|
+
*
|
|
988
|
+
* - `export * from "./x"`, since a star re-export never carries the default;
|
|
989
|
+
* - a type-only export, in either of its spellings
|
|
990
|
+
* (`export type { T as default }` and `export { type T as default }`).
|
|
991
|
+
*/
|
|
992
|
+
function hasDefaultExport(sourceFile) {
|
|
993
|
+
return sourceFile.statements.some(statement => {
|
|
994
|
+
// `export default <expr>` — but not `export = x`, which shares this node.
|
|
995
|
+
if (ts__default["default"].isExportAssignment(statement)) {
|
|
996
|
+
return !statement.isExportEquals;
|
|
997
|
+
}
|
|
998
|
+
// `export { x as default }` / `export { default } from "./x"`
|
|
999
|
+
if (ts__default["default"].isExportDeclaration(statement) && !statement.isTypeOnly && statement.exportClause && ts__default["default"].isNamedExports(statement.exportClause)) {
|
|
1000
|
+
return statement.exportClause.elements.some(element => !element.isTypeOnly && element.name.text === "default");
|
|
1001
|
+
}
|
|
1002
|
+
// `export default function f() {}` / `export default class C {}`, which are
|
|
1003
|
+
// declarations carrying a `default` modifier rather than export assignments.
|
|
1004
|
+
return ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/** A short, human-readable "what you exported instead" for the error message. */
|
|
1009
|
+
function describeDefaultExport(value) {
|
|
1010
|
+
if (value === null) {
|
|
1011
|
+
return "null";
|
|
1012
|
+
}
|
|
1013
|
+
if (Array.isArray(value)) {
|
|
1014
|
+
return "an array";
|
|
1015
|
+
}
|
|
1016
|
+
if (typeof value === "object") {
|
|
1017
|
+
// Duck-typed rather than `instanceof Schema`, for the same cross-realm
|
|
1018
|
+
// reason `isValModule` avoids a constructor check.
|
|
1019
|
+
if ("executeSerialize" in value && typeof value["executeSerialize"] === "function") {
|
|
1020
|
+
return "a schema";
|
|
1021
|
+
}
|
|
1022
|
+
return "an object";
|
|
1023
|
+
}
|
|
1024
|
+
if (typeof value === "undefined") {
|
|
1025
|
+
return "undefined";
|
|
1026
|
+
}
|
|
1027
|
+
return `a ${typeof value}`;
|
|
1028
|
+
}
|
|
1029
|
+
function errorMessage(e) {
|
|
1030
|
+
// NOT `e instanceof Error`: an error thrown from inside the `vm` context is
|
|
1031
|
+
// built from that realm's Error constructor. Duck-type the message instead.
|
|
1032
|
+
if (typeof e === "object" && e !== null && "message" in e) {
|
|
1033
|
+
const {
|
|
1034
|
+
message
|
|
1035
|
+
} = e;
|
|
1036
|
+
if (typeof message === "string") {
|
|
1037
|
+
return message;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return String(e);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
899
1043
|
const jsonOps$1 = new patch.JSONOps();
|
|
900
1044
|
|
|
901
1045
|
/**
|
|
@@ -12531,6 +12675,7 @@ exports.createJsonEntryPathMap = createJsonEntryPathMap;
|
|
|
12531
12675
|
exports.createModulePathMap = createModulePathMap;
|
|
12532
12676
|
exports.createService = createService;
|
|
12533
12677
|
exports.createValApiRouter = createValApiRouter;
|
|
12678
|
+
exports.createValModuleFileInspector = createValModuleFileInspector;
|
|
12534
12679
|
exports.createValServer = createValServer;
|
|
12535
12680
|
exports.currentFixHandlers = currentFixHandlers;
|
|
12536
12681
|
exports.decodeJwt = decodeJwt;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { pipe, result, array } from '@valbuild/core/fp';
|
|
3
3
|
import { PatchError, deepEqual, parseAndValidateArrayIndex, isNotRoot, applyPatch, JSONOps, deepClone, sourceToPatchPath } from '@valbuild/core/patch';
|
|
4
|
-
import { derefPatch,
|
|
4
|
+
import { derefPatch, Internal, RecordSchema, extractValModules, computeValModuleShas, VAL_EXTENSION, ImageSchema, DEFAULT_CONTENT_HOST, hasRemoteFileSchema } from '@valbuild/core';
|
|
5
5
|
export { hasRemoteFileSchema } from '@valbuild/core';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import path__default from 'path';
|
|
@@ -862,6 +862,150 @@ function resolveRelative(dirName, spec, host) {
|
|
|
862
862
|
return null;
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
+
/**
|
|
866
|
+
* What a `*.val.ts` file that is NOT registered in `val.modules` turns out to
|
|
867
|
+
* be.
|
|
868
|
+
*
|
|
869
|
+
* A file matching `*.val.ts` is not necessarily a Val module: the same
|
|
870
|
+
* convention is used for shared schemas and other content-adjacent helpers, and
|
|
871
|
+
* those are not meant to be registered. Only a file that actually default
|
|
872
|
+
* exports a module is worth warning about; one that default exports something
|
|
873
|
+
* else is a mistake, because nothing will ever load it.
|
|
874
|
+
*
|
|
875
|
+
* See {@link createValModuleFileInspector}.
|
|
876
|
+
*/
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Inspects individual `*.val.{ts,js}` files, sharing one module cache and one
|
|
880
|
+
* parsed tsconfig across every call.
|
|
881
|
+
*
|
|
882
|
+
* The default export is checked SYNTACTICALLY first and only evaluated if it is
|
|
883
|
+
* there. That ordering is the point: a `.val.ts` with no default export is a
|
|
884
|
+
* helper file, and evaluating it to learn that would be both wasted work and a
|
|
885
|
+
* way to turn an unrelated top-level throw into a reported error.
|
|
886
|
+
*
|
|
887
|
+
* SECURITY: evaluation goes through the same `vm` loader as
|
|
888
|
+
* {@link loadValModules} — see the warning there. Only ever point this at the
|
|
889
|
+
* project's own first-party files.
|
|
890
|
+
*/
|
|
891
|
+
function createValModuleFileInspector(projectRoot, host = ts.sys) {
|
|
892
|
+
const compilerOptions = getCompilerOptions(projectRoot, host);
|
|
893
|
+
const cache = {};
|
|
894
|
+
return absPath => {
|
|
895
|
+
const code = host.readFile(absPath);
|
|
896
|
+
if (code === undefined) {
|
|
897
|
+
return {
|
|
898
|
+
status: "invalid",
|
|
899
|
+
message: `Could not read file: '${absPath}'`
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const sourceFile = ts.createSourceFile(absPath, code, ts.ScriptTarget.ES2020, true);
|
|
903
|
+
if (!hasDefaultExport(sourceFile)) {
|
|
904
|
+
return {
|
|
905
|
+
status: "no-default-export"
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
let exports;
|
|
909
|
+
// `loadModule` inserts a module into the cache BEFORE evaluating it, so
|
|
910
|
+
// that a cycle resolves. One that throws therefore leaves a half-built
|
|
911
|
+
// entry behind - and unlike `loadValModules`, which builds a cache per
|
|
912
|
+
// call and lets the throw escape, this cache outlives the failure. A later
|
|
913
|
+
// inspection of the same file (or of the helper that actually threw) would
|
|
914
|
+
// hit that entry, see empty exports, and report "default export is
|
|
915
|
+
// undefined" instead of the real error - or, worse, quietly downgrade it to
|
|
916
|
+
// a warning. So roll the cache back to what it was before this attempt.
|
|
917
|
+
const before = new Set(Object.keys(cache));
|
|
918
|
+
try {
|
|
919
|
+
exports = loadModule(absPath, cache, compilerOptions, host).exports;
|
|
920
|
+
} catch (e) {
|
|
921
|
+
for (const key of Object.keys(cache)) {
|
|
922
|
+
if (!before.has(key)) {
|
|
923
|
+
delete cache[key];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
status: "invalid",
|
|
928
|
+
message: `Could not be loaded. Error: ${errorMessage(e)}`
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
if (Internal.isValModule(exports.default)) {
|
|
932
|
+
return {
|
|
933
|
+
status: "val-module"
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
// NOTE: do NOT suggest wrapping this in `c.define`. A shared schema turned
|
|
937
|
+
// into a module is an UNREGISTERED module, i.e. straight back to a warning.
|
|
938
|
+
// The fix is to move it out of the default export slot, which is the one
|
|
939
|
+
// thing about a `.val.ts` that Val reserves for itself.
|
|
940
|
+
return {
|
|
941
|
+
status: "invalid",
|
|
942
|
+
message: `Default export is ${describeDefaultExport(exports.default)}, not a Val module. Only 'c.define(...)' may be the default export of a '*.val.ts' file: use a named export for a shared schema or helper`
|
|
943
|
+
};
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Whether the file exports a RUNTIME value as `default`, without evaluating it.
|
|
949
|
+
*
|
|
950
|
+
* Two things deliberately do not count, because neither exists once the file is
|
|
951
|
+
* transpiled — and treating either as a default export would send a pure helper
|
|
952
|
+
* off to be evaluated and reported:
|
|
953
|
+
*
|
|
954
|
+
* - `export * from "./x"`, since a star re-export never carries the default;
|
|
955
|
+
* - a type-only export, in either of its spellings
|
|
956
|
+
* (`export type { T as default }` and `export { type T as default }`).
|
|
957
|
+
*/
|
|
958
|
+
function hasDefaultExport(sourceFile) {
|
|
959
|
+
return sourceFile.statements.some(statement => {
|
|
960
|
+
// `export default <expr>` — but not `export = x`, which shares this node.
|
|
961
|
+
if (ts.isExportAssignment(statement)) {
|
|
962
|
+
return !statement.isExportEquals;
|
|
963
|
+
}
|
|
964
|
+
// `export { x as default }` / `export { default } from "./x"`
|
|
965
|
+
if (ts.isExportDeclaration(statement) && !statement.isTypeOnly && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
966
|
+
return statement.exportClause.elements.some(element => !element.isTypeOnly && element.name.text === "default");
|
|
967
|
+
}
|
|
968
|
+
// `export default function f() {}` / `export default class C {}`, which are
|
|
969
|
+
// declarations carrying a `default` modifier rather than export assignments.
|
|
970
|
+
return ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** A short, human-readable "what you exported instead" for the error message. */
|
|
975
|
+
function describeDefaultExport(value) {
|
|
976
|
+
if (value === null) {
|
|
977
|
+
return "null";
|
|
978
|
+
}
|
|
979
|
+
if (Array.isArray(value)) {
|
|
980
|
+
return "an array";
|
|
981
|
+
}
|
|
982
|
+
if (typeof value === "object") {
|
|
983
|
+
// Duck-typed rather than `instanceof Schema`, for the same cross-realm
|
|
984
|
+
// reason `isValModule` avoids a constructor check.
|
|
985
|
+
if ("executeSerialize" in value && typeof value["executeSerialize"] === "function") {
|
|
986
|
+
return "a schema";
|
|
987
|
+
}
|
|
988
|
+
return "an object";
|
|
989
|
+
}
|
|
990
|
+
if (typeof value === "undefined") {
|
|
991
|
+
return "undefined";
|
|
992
|
+
}
|
|
993
|
+
return `a ${typeof value}`;
|
|
994
|
+
}
|
|
995
|
+
function errorMessage(e) {
|
|
996
|
+
// NOT `e instanceof Error`: an error thrown from inside the `vm` context is
|
|
997
|
+
// built from that realm's Error constructor. Duck-type the message instead.
|
|
998
|
+
if (typeof e === "object" && e !== null && "message" in e) {
|
|
999
|
+
const {
|
|
1000
|
+
message
|
|
1001
|
+
} = e;
|
|
1002
|
+
if (typeof message === "string") {
|
|
1003
|
+
return message;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return String(e);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
865
1009
|
const jsonOps$1 = new JSONOps();
|
|
866
1010
|
|
|
867
1011
|
/**
|
|
@@ -12473,4 +12617,4 @@ function readCapturedReport(snapshotDir) {
|
|
|
12473
12617
|
return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
|
|
12474
12618
|
}
|
|
12475
12619
|
|
|
12476
|
-
export { DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_MAX_DURATION, DEFAULT_LOGIN_POLL_INTERVAL, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValServer, currentFixHandlers, decodeJwt, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata };
|
|
12620
|
+
export { DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_MAX_DURATION, DEFAULT_LOGIN_POLL_INTERVAL, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValServer, currentFixHandlers, decodeJwt, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata };
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"types": "dist/valbuild-server.cjs.d.ts",
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.106.0",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@prettier/sync": "^0.6.1",
|
|
22
22
|
"@types/jest": "^30.0.0"
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"chokidar": "^5.0.0",
|
|
26
26
|
"image-size": "^2.0.2",
|
|
27
|
-
"minimatch": "^10.
|
|
27
|
+
"minimatch": "^10.2.3",
|
|
28
28
|
"sucrase": "^3.35.1",
|
|
29
29
|
"typescript": "^5.9.3",
|
|
30
30
|
"zod": "^4.3.5",
|
|
31
31
|
"zod-validation-error": "^5.0.0",
|
|
32
|
-
"@valbuild/core": "0.
|
|
33
|
-
"@valbuild/shared": "0.
|
|
32
|
+
"@valbuild/core": "0.106.0",
|
|
33
|
+
"@valbuild/shared": "0.106.0",
|
|
34
34
|
"@valbuild/ui": "0.105.1"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|