@valbuild/server 0.134.1 → 0.136.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/CHANGELOG.md +146 -0
- package/dist/declarations/src/ValOps.d.ts +16 -0
- package/dist/declarations/src/ValOpsHttp.d.ts +15 -0
- package/dist/declarations/src/createPrettierFormatter.d.ts +87 -0
- package/dist/declarations/src/index.d.ts +3 -1
- package/dist/declarations/src/loadValModules.d.ts +23 -0
- package/dist/valbuild-server.cjs.dev.js +250 -12
- package/dist/valbuild-server.cjs.prod.js +250 -12
- package/dist/valbuild-server.esm.js +249 -13
- package/package.json +6 -5
|
@@ -1007,18 +1007,29 @@ function createValModuleFileInspector(projectRoot, host = ts__default["default"]
|
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
1009
|
/**
|
|
1010
|
-
*
|
|
1010
|
+
* The statement that exports a RUNTIME value as `default`, without evaluating
|
|
1011
|
+
* it — or `undefined` when the file has no such export.
|
|
1012
|
+
*
|
|
1013
|
+
* This is the one rule that separates a Val module from a `*.val.ts` that
|
|
1014
|
+
* merely wears the naming convention: a shared schema or helper is exported by
|
|
1015
|
+
* name, a module is exported by default. Every caller that needs to tell those
|
|
1016
|
+
* apart goes through this — `val validate` to decide whether an unregistered
|
|
1017
|
+
* file is worth reporting, and the language server to decide the same thing and
|
|
1018
|
+
* to put its diagnostic on the export rather than on line 1.
|
|
1011
1019
|
*
|
|
1012
1020
|
* Two things deliberately do not count, because neither exists once the file is
|
|
1013
1021
|
* transpiled — and treating either as a default export would send a pure helper
|
|
1014
1022
|
* off to be evaluated and reported:
|
|
1015
1023
|
*
|
|
1016
1024
|
* - `export * from "./x"`, since a star re-export never carries the default;
|
|
1017
|
-
* - a type-only export, in
|
|
1018
|
-
* (`export type { T as default }
|
|
1025
|
+
* - a type-only export, in any of its three spellings
|
|
1026
|
+
* (`export type { T as default }`, `export { type T as default }` and
|
|
1027
|
+
* `export default interface T {}` — the last one parses as a declaration
|
|
1028
|
+
* carrying a `default` modifier, exactly like `export default class`, and is
|
|
1029
|
+
* the one that looks like a runtime export and is not).
|
|
1019
1030
|
*/
|
|
1020
|
-
function
|
|
1021
|
-
return sourceFile.statements.
|
|
1031
|
+
function findDefaultExport(sourceFile) {
|
|
1032
|
+
return sourceFile.statements.find(statement => {
|
|
1022
1033
|
// `export default <expr>` — but not `export = x`, which shares this node.
|
|
1023
1034
|
if (ts__default["default"].isExportAssignment(statement)) {
|
|
1024
1035
|
return !statement.isExportEquals;
|
|
@@ -1029,10 +1040,17 @@ function hasDefaultExport(sourceFile) {
|
|
|
1029
1040
|
}
|
|
1030
1041
|
// `export default function f() {}` / `export default class C {}`, which are
|
|
1031
1042
|
// declarations carrying a `default` modifier rather than export assignments.
|
|
1032
|
-
|
|
1043
|
+
// `export default interface T {}` is spelled the same way and is a type, so
|
|
1044
|
+
// it is excluded here rather than by the `isTypeOnly` checks above.
|
|
1045
|
+
return !ts__default["default"].isInterfaceDeclaration(statement) && ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
|
|
1033
1046
|
});
|
|
1034
1047
|
}
|
|
1035
1048
|
|
|
1049
|
+
/** Whether the file exports a runtime value as `default`. */
|
|
1050
|
+
function hasDefaultExport(sourceFile) {
|
|
1051
|
+
return findDefaultExport(sourceFile) !== undefined;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1036
1054
|
/** A short, human-readable "what you exported instead" for the error message. */
|
|
1037
1055
|
function describeDefaultExport(value) {
|
|
1038
1056
|
if (value === null) {
|
|
@@ -4558,6 +4576,25 @@ class ValOps {
|
|
|
4558
4576
|
return null;
|
|
4559
4577
|
}
|
|
4560
4578
|
|
|
4579
|
+
/**
|
|
4580
|
+
* How this project's SOURCE is kept, or `null` when it is nobody's question.
|
|
4581
|
+
*
|
|
4582
|
+
* `"managed"` -- the content service is the store of record, there is no
|
|
4583
|
+
* repository, and nothing outside the browser will ever pick a commit up.
|
|
4584
|
+
* `"connected"` -- commits are mirrored into a repository a host watches.
|
|
4585
|
+
*
|
|
4586
|
+
* `null` is the honest answer for `fs` and memory mode, where publishing is
|
|
4587
|
+
* writing to disk and there is no project to have a mode, and for an `http`
|
|
4588
|
+
* project whose content service predates the field. The Studio treats it as
|
|
4589
|
+
* "the story I have always told", which is the connected one -- the feed, and
|
|
4590
|
+
* a `building` state that something outside resolves. Guessing `managed`
|
|
4591
|
+
* instead would take the deploy feed away from every project running against
|
|
4592
|
+
* an older service.
|
|
4593
|
+
*/
|
|
4594
|
+
sourceMode() {
|
|
4595
|
+
return null;
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4561
4598
|
/**
|
|
4562
4599
|
* Whether a commit here produces `.val.ts` TEXT as well as data.
|
|
4563
4600
|
*
|
|
@@ -7917,6 +7954,25 @@ class ValOpsHttp extends ValOps {
|
|
|
7917
7954
|
message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
|
|
7918
7955
|
};
|
|
7919
7956
|
}
|
|
7957
|
+
|
|
7958
|
+
/**
|
|
7959
|
+
* What the content service last said this project's source mode is.
|
|
7960
|
+
*
|
|
7961
|
+
* Read off the same remembered expectation {@link publishRefusal} uses. It is
|
|
7962
|
+
* CURRENT wherever it matters rather than a poll behind, and by construction
|
|
7963
|
+
* rather than by luck: the only caller is `/stat`, which awaits `getStat`
|
|
7964
|
+
* first, and that fetches the patches -- which is the response the expectation
|
|
7965
|
+
* is recorded from.
|
|
7966
|
+
*
|
|
7967
|
+
* `null` before anything has fetched patches, which is the same "not
|
|
7968
|
+
* reported" the wire field means, and reads as connected. The alternative
|
|
7969
|
+
* would be to ask for it separately, which is a round trip for a field that
|
|
7970
|
+
* has just arrived.
|
|
7971
|
+
*/
|
|
7972
|
+
sourceMode() {
|
|
7973
|
+
var _this$projectExpectat2;
|
|
7974
|
+
return ((_this$projectExpectat2 = this.projectExpectation) === null || _this$projectExpectat2 === void 0 ? void 0 : _this$projectExpectat2.sourceMode) ?? null;
|
|
7975
|
+
}
|
|
7920
7976
|
async onInit() {
|
|
7921
7977
|
// TODO: unused for now. Implement or remove
|
|
7922
7978
|
}
|
|
@@ -11898,6 +11954,16 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11898
11954
|
* it failed to compute.
|
|
11899
11955
|
*/
|
|
11900
11956
|
const publishRefusal = serverOps.publishRefusal();
|
|
11957
|
+
/*
|
|
11958
|
+
* How this project's source is kept, when the store knows.
|
|
11959
|
+
*
|
|
11960
|
+
* Spread for the same reason `publishRefusal` is: absent has to keep
|
|
11961
|
+
* meaning "not reported" rather than becoming a mode. The Studio
|
|
11962
|
+
* narrates a publish differently for a managed project -- there is
|
|
11963
|
+
* nothing outside the browser to finish it -- so a wrong default here
|
|
11964
|
+
* shows one story to a project that lives by the other.
|
|
11965
|
+
*/
|
|
11966
|
+
const sourceMode = serverOps.sourceMode();
|
|
11901
11967
|
return {
|
|
11902
11968
|
status: 200,
|
|
11903
11969
|
json: {
|
|
@@ -11907,6 +11973,9 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11907
11973
|
...(publishRefusal ? {
|
|
11908
11974
|
publishRefusal
|
|
11909
11975
|
} : {}),
|
|
11976
|
+
...(sourceMode ? {
|
|
11977
|
+
sourceMode
|
|
11978
|
+
} : {}),
|
|
11910
11979
|
config: options.config
|
|
11911
11980
|
}
|
|
11912
11981
|
};
|
|
@@ -15311,12 +15380,62 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
|
15311
15380
|
}
|
|
15312
15381
|
query = queryRes.data;
|
|
15313
15382
|
}
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15383
|
+
|
|
15384
|
+
/*
|
|
15385
|
+
* A throw from an endpoint is Val's to report, not the framework's.
|
|
15386
|
+
*
|
|
15387
|
+
* Nothing here used to catch, so an endpoint that threw left the router
|
|
15388
|
+
* entirely and became whatever the host does with an unhandled error --
|
|
15389
|
+
* and on TanStack Start that is h3, which replaces the message with the
|
|
15390
|
+
* literal string "HTTPError" and drops the stack. Its `debug` and
|
|
15391
|
+
* `silent` options are not reachable from out here: `requestHandler`
|
|
15392
|
+
* calls `toResponse(value, event)` with no config, and the default is
|
|
15393
|
+
* `{}`. So the body a caller got was
|
|
15394
|
+
* `{"status":500,"unhandled":true,"message":"HTTPError"}` -- the same
|
|
15395
|
+
* five words for a missing project, a bad cookie secret and a module
|
|
15396
|
+
* that failed to link -- with the real cause only on the server's
|
|
15397
|
+
* console.
|
|
15398
|
+
*
|
|
15399
|
+
* That is survivable where the console is readable. It is not in a
|
|
15400
|
+
* Cloudflare Dynamic Worker, whose `console` reaches no tail stream: a
|
|
15401
|
+
* link error in a lazily imported chunk (`does not provide an export
|
|
15402
|
+
* named 'getSerovalPlugins'`) showed up as an unreadable 500 on
|
|
15403
|
+
* `/authorize` and nowhere else at all.
|
|
15404
|
+
*
|
|
15405
|
+
* So every endpoint's throw becomes Val's own 500 envelope, which the
|
|
15406
|
+
* Studio already renders and `curl` already prints. The route and method
|
|
15407
|
+
* are named because a message rarely says which endpoint it came from.
|
|
15408
|
+
*
|
|
15409
|
+
* The MESSAGE travels and the STACK does not. The message is the whole
|
|
15410
|
+
* diagnostic value -- the line above names the package, the chunk and
|
|
15411
|
+
* the export -- while a stack is a walk through somebody's bundle, and
|
|
15412
|
+
* `/authorize` and `/enable` are reachable without a session. So the
|
|
15413
|
+
* stack goes to the console, where a host that can read one will find
|
|
15414
|
+
* it, and the body carries what a caller can act on.
|
|
15415
|
+
*/
|
|
15416
|
+
let res;
|
|
15417
|
+
try {
|
|
15418
|
+
res = await endpointImpl({
|
|
15419
|
+
body: bodyRes.data,
|
|
15420
|
+
cookies: cookiesRes.data,
|
|
15421
|
+
query,
|
|
15422
|
+
path
|
|
15423
|
+
});
|
|
15424
|
+
} catch (err) {
|
|
15425
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
15426
|
+
console.error(`Val: ${method} ${route} threw: ${error.message}`, error.stack);
|
|
15427
|
+
return {
|
|
15428
|
+
status: 500,
|
|
15429
|
+
json: {
|
|
15430
|
+
message: `Val: ${method} ${route} failed: ${error.message}`,
|
|
15431
|
+
details: {
|
|
15432
|
+
route,
|
|
15433
|
+
method,
|
|
15434
|
+
error: error.message
|
|
15435
|
+
}
|
|
15436
|
+
}
|
|
15437
|
+
};
|
|
15438
|
+
}
|
|
15320
15439
|
if (res.status === 500) {
|
|
15321
15440
|
var _res$json;
|
|
15322
15441
|
return {
|
|
@@ -17246,6 +17365,123 @@ function validateMetadata(actualMetadata, expectedMetadata) {
|
|
|
17246
17365
|
};
|
|
17247
17366
|
}
|
|
17248
17367
|
|
|
17368
|
+
/**
|
|
17369
|
+
* What Val calls to format a file it has just written.
|
|
17370
|
+
*
|
|
17371
|
+
* The same shape `initValServer`, `initValMcp` and `ValOps` take as their
|
|
17372
|
+
* `formatter` option. The path is the file's path INSIDE the project — a
|
|
17373
|
+
* `ModuleFilePath` such as `/content/page.val.ts`, or a `*.val.json` entry
|
|
17374
|
+
* beside it — never an absolute path on disk, because the writers that call it
|
|
17375
|
+
* (`ValOps.prepare`, the CLI's `--fix`) only ever know a module by that path.
|
|
17376
|
+
*/
|
|
17377
|
+
|
|
17378
|
+
/**
|
|
17379
|
+
* Prettier's options, as far as this file is concerned.
|
|
17380
|
+
*
|
|
17381
|
+
* Deliberately opaque: this file never reads an option, it only carries the
|
|
17382
|
+
* bag from `resolveConfig` to `format`. Naming the real `Options` here would
|
|
17383
|
+
* make it a compile error to build `@valbuild/server` in a project that has no
|
|
17384
|
+
* prettier installed, which is most of them — prettier is a dependency the app
|
|
17385
|
+
* opts into, so it is passed IN rather than imported.
|
|
17386
|
+
*/
|
|
17387
|
+
|
|
17388
|
+
/**
|
|
17389
|
+
* `prettier`, as this file wants it.
|
|
17390
|
+
*
|
|
17391
|
+
* Structural for the same reason `SharpLike` in `@valbuild/mcp` is: a type-only
|
|
17392
|
+
* import of a package that is not installed is still a compile error, and this
|
|
17393
|
+
* package has to typecheck without prettier. `createPrettierFormatter.test.ts`
|
|
17394
|
+
* asserts the real library against this type so the structural description
|
|
17395
|
+
* cannot drift away from it unnoticed.
|
|
17396
|
+
*
|
|
17397
|
+
* Every result is `T | Promise<T>` so that `@prettier/sync` — the same API with
|
|
17398
|
+
* the awaits taken out — satisfies it too. Everything here is awaited either
|
|
17399
|
+
* way, and a formatter is allowed to be synchronous: `ValFormatter` says so.
|
|
17400
|
+
*/
|
|
17401
|
+
|
|
17402
|
+
/**
|
|
17403
|
+
* Resolve a path Val handed us against the project.
|
|
17404
|
+
*
|
|
17405
|
+
* Val's writers pass a project-relative path (`/content/page.val.ts`), and
|
|
17406
|
+
* prettier needs a real one — both to find the `.prettierrc` that governs the
|
|
17407
|
+
* file and to match it against `.prettierignore`. An absolute path that is
|
|
17408
|
+
* already inside the project is taken as it comes, so a caller that happens to
|
|
17409
|
+
* hold a real path is not mangled into `<root>/<root>/...`.
|
|
17410
|
+
*/
|
|
17411
|
+
function resolveAgainstProject(projectRoot, filePath) {
|
|
17412
|
+
if (path__namespace["default"].isAbsolute(filePath)) {
|
|
17413
|
+
const relative = path__namespace["default"].relative(projectRoot, filePath);
|
|
17414
|
+
if (relative && !relative.startsWith("..") && !path__namespace["default"].isAbsolute(relative)) {
|
|
17415
|
+
return filePath;
|
|
17416
|
+
}
|
|
17417
|
+
}
|
|
17418
|
+
return path__namespace["default"].join(projectRoot, filePath);
|
|
17419
|
+
}
|
|
17420
|
+
|
|
17421
|
+
/**
|
|
17422
|
+
* A {@link ValFormatter} that formats the way the project itself does.
|
|
17423
|
+
*
|
|
17424
|
+
* ONE implementation, for every writer of source files Val has: the Studio and
|
|
17425
|
+
* the dev server (`initValServer`), an agent (`initValMcp`) and
|
|
17426
|
+
* `val validate --fix`. They used to disagree — the CLI called
|
|
17427
|
+
* `prettier.format(code, { filepath })` on its own behalf, and `format` does
|
|
17428
|
+
* not read `.prettierrc`; only `resolveConfig`, `getFileInfo` and the prettier
|
|
17429
|
+
* CLI do. `filepath` there selects the PARSER and nothing else, so the output
|
|
17430
|
+
* was valid TypeScript in prettier's default style rather than the project's:
|
|
17431
|
+
* a two-line content fix arriving as a whole-file rewrite, and a red `format`
|
|
17432
|
+
* job in any repo that checks formatting in CI.
|
|
17433
|
+
*
|
|
17434
|
+
* ```ts
|
|
17435
|
+
* import prettier from "prettier";
|
|
17436
|
+
* import { createPrettierFormatter } from "@valbuild/next/server";
|
|
17437
|
+
*
|
|
17438
|
+
* initValServer(valModules, config, {
|
|
17439
|
+
* draftMode,
|
|
17440
|
+
* formatter: createPrettierFormatter(prettier, { projectRoot: process.cwd() }),
|
|
17441
|
+
* });
|
|
17442
|
+
* ```
|
|
17443
|
+
*
|
|
17444
|
+
* `projectRoot` is what a project-relative path is resolved against, and is
|
|
17445
|
+
* therefore also where `.prettierignore` is looked for. Prettier's own config
|
|
17446
|
+
* search walks UP from the resolved file, so a monorepo package with its own
|
|
17447
|
+
* `.prettierrc` is found without being named here.
|
|
17448
|
+
*
|
|
17449
|
+
* Two decisions worth knowing:
|
|
17450
|
+
*
|
|
17451
|
+
* - **`.prettierignore` is honoured; `.gitignore` is not.** The prettier CLI
|
|
17452
|
+
* consults both, but only one of them is a statement about formatting — a
|
|
17453
|
+
* file is gitignored for reasons that have nothing to do with style, and the
|
|
17454
|
+
* only files reaching this function are ones Val itself just wrote.
|
|
17455
|
+
* - **No config found is not an error.** `resolveConfig` returns `null` and
|
|
17456
|
+
* the file is formatted with prettier's defaults, which is what a project
|
|
17457
|
+
* without a config has asked for.
|
|
17458
|
+
*/
|
|
17459
|
+
function createPrettierFormatter(prettier, options) {
|
|
17460
|
+
const projectRoot = path__namespace["default"].resolve(options.projectRoot);
|
|
17461
|
+
// One array, built once: prettier reads and caches the ignore file itself, so
|
|
17462
|
+
// this is only here to avoid rebuilding the path per file.
|
|
17463
|
+
const ignorePath = [path__namespace["default"].join(projectRoot, ".prettierignore")];
|
|
17464
|
+
return async (code, filePath) => {
|
|
17465
|
+
const absoluteFilePath = resolveAgainstProject(projectRoot, filePath);
|
|
17466
|
+
const {
|
|
17467
|
+
ignored
|
|
17468
|
+
} = await prettier.getFileInfo(absoluteFilePath, {
|
|
17469
|
+
ignorePath
|
|
17470
|
+
});
|
|
17471
|
+
if (ignored) {
|
|
17472
|
+
return code;
|
|
17473
|
+
}
|
|
17474
|
+
// `resolveConfig` applies the `overrides` that match this file and caches
|
|
17475
|
+
// per directory, so calling it per file is cheap even on a project-wide
|
|
17476
|
+
// `--fix`. `filepath` is still required: it is what picks the parser.
|
|
17477
|
+
const config = await prettier.resolveConfig(absoluteFilePath);
|
|
17478
|
+
return prettier.format(code, {
|
|
17479
|
+
...config,
|
|
17480
|
+
filepath: absoluteFilePath
|
|
17481
|
+
});
|
|
17482
|
+
};
|
|
17483
|
+
}
|
|
17484
|
+
|
|
17249
17485
|
/**
|
|
17250
17486
|
* NOTE: this is intentionally NOT `SharedValConfig` from `@valbuild/shared`.
|
|
17251
17487
|
* That schema requires `files.directory` to be exactly `/public/val`, whereas
|
|
@@ -17854,6 +18090,7 @@ exports.createDefaultValFSHost = createDefaultValFSHost;
|
|
|
17854
18090
|
exports.createFixPatch = createFixPatch;
|
|
17855
18091
|
exports.createJsonEntryPathMap = createJsonEntryPathMap;
|
|
17856
18092
|
exports.createModulePathMap = createModulePathMap;
|
|
18093
|
+
exports.createPrettierFormatter = createPrettierFormatter;
|
|
17857
18094
|
exports.createService = createService;
|
|
17858
18095
|
exports.createValApiRouter = createValApiRouter;
|
|
17859
18096
|
exports.createValModuleFileInspector = createValModuleFileInspector;
|
|
@@ -17871,6 +18108,7 @@ exports.extractFileMetadata = extractFileMetadata;
|
|
|
17871
18108
|
exports.extractImageMetadata = extractImageMetadata;
|
|
17872
18109
|
exports.extractJsonValuesEntry = extractJsonValuesEntry;
|
|
17873
18110
|
exports.findAndEvalValConfigFile = findAndEvalValConfigFile;
|
|
18111
|
+
exports.findDefaultExport = findDefaultExport;
|
|
17874
18112
|
exports.findJsonEntryFilePath = findJsonEntryFilePath;
|
|
17875
18113
|
exports.fixHandlers = fixHandlers;
|
|
17876
18114
|
exports.formatPatchSourceError = formatPatchSourceError;
|
|
@@ -973,18 +973,29 @@ function createValModuleFileInspector(projectRoot, host = ts.sys) {
|
|
|
973
973
|
}
|
|
974
974
|
|
|
975
975
|
/**
|
|
976
|
-
*
|
|
976
|
+
* The statement that exports a RUNTIME value as `default`, without evaluating
|
|
977
|
+
* it — or `undefined` when the file has no such export.
|
|
978
|
+
*
|
|
979
|
+
* This is the one rule that separates a Val module from a `*.val.ts` that
|
|
980
|
+
* merely wears the naming convention: a shared schema or helper is exported by
|
|
981
|
+
* name, a module is exported by default. Every caller that needs to tell those
|
|
982
|
+
* apart goes through this — `val validate` to decide whether an unregistered
|
|
983
|
+
* file is worth reporting, and the language server to decide the same thing and
|
|
984
|
+
* to put its diagnostic on the export rather than on line 1.
|
|
977
985
|
*
|
|
978
986
|
* Two things deliberately do not count, because neither exists once the file is
|
|
979
987
|
* transpiled — and treating either as a default export would send a pure helper
|
|
980
988
|
* off to be evaluated and reported:
|
|
981
989
|
*
|
|
982
990
|
* - `export * from "./x"`, since a star re-export never carries the default;
|
|
983
|
-
* - a type-only export, in
|
|
984
|
-
* (`export type { T as default }
|
|
991
|
+
* - a type-only export, in any of its three spellings
|
|
992
|
+
* (`export type { T as default }`, `export { type T as default }` and
|
|
993
|
+
* `export default interface T {}` — the last one parses as a declaration
|
|
994
|
+
* carrying a `default` modifier, exactly like `export default class`, and is
|
|
995
|
+
* the one that looks like a runtime export and is not).
|
|
985
996
|
*/
|
|
986
|
-
function
|
|
987
|
-
return sourceFile.statements.
|
|
997
|
+
function findDefaultExport(sourceFile) {
|
|
998
|
+
return sourceFile.statements.find(statement => {
|
|
988
999
|
// `export default <expr>` — but not `export = x`, which shares this node.
|
|
989
1000
|
if (ts.isExportAssignment(statement)) {
|
|
990
1001
|
return !statement.isExportEquals;
|
|
@@ -995,10 +1006,17 @@ function hasDefaultExport(sourceFile) {
|
|
|
995
1006
|
}
|
|
996
1007
|
// `export default function f() {}` / `export default class C {}`, which are
|
|
997
1008
|
// declarations carrying a `default` modifier rather than export assignments.
|
|
998
|
-
|
|
1009
|
+
// `export default interface T {}` is spelled the same way and is a type, so
|
|
1010
|
+
// it is excluded here rather than by the `isTypeOnly` checks above.
|
|
1011
|
+
return !ts.isInterfaceDeclaration(statement) && ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword);
|
|
999
1012
|
});
|
|
1000
1013
|
}
|
|
1001
1014
|
|
|
1015
|
+
/** Whether the file exports a runtime value as `default`. */
|
|
1016
|
+
function hasDefaultExport(sourceFile) {
|
|
1017
|
+
return findDefaultExport(sourceFile) !== undefined;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1002
1020
|
/** A short, human-readable "what you exported instead" for the error message. */
|
|
1003
1021
|
function describeDefaultExport(value) {
|
|
1004
1022
|
if (value === null) {
|
|
@@ -4524,6 +4542,25 @@ class ValOps {
|
|
|
4524
4542
|
return null;
|
|
4525
4543
|
}
|
|
4526
4544
|
|
|
4545
|
+
/**
|
|
4546
|
+
* How this project's SOURCE is kept, or `null` when it is nobody's question.
|
|
4547
|
+
*
|
|
4548
|
+
* `"managed"` -- the content service is the store of record, there is no
|
|
4549
|
+
* repository, and nothing outside the browser will ever pick a commit up.
|
|
4550
|
+
* `"connected"` -- commits are mirrored into a repository a host watches.
|
|
4551
|
+
*
|
|
4552
|
+
* `null` is the honest answer for `fs` and memory mode, where publishing is
|
|
4553
|
+
* writing to disk and there is no project to have a mode, and for an `http`
|
|
4554
|
+
* project whose content service predates the field. The Studio treats it as
|
|
4555
|
+
* "the story I have always told", which is the connected one -- the feed, and
|
|
4556
|
+
* a `building` state that something outside resolves. Guessing `managed`
|
|
4557
|
+
* instead would take the deploy feed away from every project running against
|
|
4558
|
+
* an older service.
|
|
4559
|
+
*/
|
|
4560
|
+
sourceMode() {
|
|
4561
|
+
return null;
|
|
4562
|
+
}
|
|
4563
|
+
|
|
4527
4564
|
/**
|
|
4528
4565
|
* Whether a commit here produces `.val.ts` TEXT as well as data.
|
|
4529
4566
|
*
|
|
@@ -7883,6 +7920,25 @@ class ValOpsHttp extends ValOps {
|
|
|
7883
7920
|
message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
|
|
7884
7921
|
};
|
|
7885
7922
|
}
|
|
7923
|
+
|
|
7924
|
+
/**
|
|
7925
|
+
* What the content service last said this project's source mode is.
|
|
7926
|
+
*
|
|
7927
|
+
* Read off the same remembered expectation {@link publishRefusal} uses. It is
|
|
7928
|
+
* CURRENT wherever it matters rather than a poll behind, and by construction
|
|
7929
|
+
* rather than by luck: the only caller is `/stat`, which awaits `getStat`
|
|
7930
|
+
* first, and that fetches the patches -- which is the response the expectation
|
|
7931
|
+
* is recorded from.
|
|
7932
|
+
*
|
|
7933
|
+
* `null` before anything has fetched patches, which is the same "not
|
|
7934
|
+
* reported" the wire field means, and reads as connected. The alternative
|
|
7935
|
+
* would be to ask for it separately, which is a round trip for a field that
|
|
7936
|
+
* has just arrived.
|
|
7937
|
+
*/
|
|
7938
|
+
sourceMode() {
|
|
7939
|
+
var _this$projectExpectat2;
|
|
7940
|
+
return ((_this$projectExpectat2 = this.projectExpectation) === null || _this$projectExpectat2 === void 0 ? void 0 : _this$projectExpectat2.sourceMode) ?? null;
|
|
7941
|
+
}
|
|
7886
7942
|
async onInit() {
|
|
7887
7943
|
// TODO: unused for now. Implement or remove
|
|
7888
7944
|
}
|
|
@@ -11864,6 +11920,16 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11864
11920
|
* it failed to compute.
|
|
11865
11921
|
*/
|
|
11866
11922
|
const publishRefusal = serverOps.publishRefusal();
|
|
11923
|
+
/*
|
|
11924
|
+
* How this project's source is kept, when the store knows.
|
|
11925
|
+
*
|
|
11926
|
+
* Spread for the same reason `publishRefusal` is: absent has to keep
|
|
11927
|
+
* meaning "not reported" rather than becoming a mode. The Studio
|
|
11928
|
+
* narrates a publish differently for a managed project -- there is
|
|
11929
|
+
* nothing outside the browser to finish it -- so a wrong default here
|
|
11930
|
+
* shows one story to a project that lives by the other.
|
|
11931
|
+
*/
|
|
11932
|
+
const sourceMode = serverOps.sourceMode();
|
|
11867
11933
|
return {
|
|
11868
11934
|
status: 200,
|
|
11869
11935
|
json: {
|
|
@@ -11873,6 +11939,9 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
11873
11939
|
...(publishRefusal ? {
|
|
11874
11940
|
publishRefusal
|
|
11875
11941
|
} : {}),
|
|
11942
|
+
...(sourceMode ? {
|
|
11943
|
+
sourceMode
|
|
11944
|
+
} : {}),
|
|
11876
11945
|
config: options.config
|
|
11877
11946
|
}
|
|
11878
11947
|
};
|
|
@@ -15277,12 +15346,62 @@ function createValApiRouter(route, valServerPromise, convert) {
|
|
|
15277
15346
|
}
|
|
15278
15347
|
query = queryRes.data;
|
|
15279
15348
|
}
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15349
|
+
|
|
15350
|
+
/*
|
|
15351
|
+
* A throw from an endpoint is Val's to report, not the framework's.
|
|
15352
|
+
*
|
|
15353
|
+
* Nothing here used to catch, so an endpoint that threw left the router
|
|
15354
|
+
* entirely and became whatever the host does with an unhandled error --
|
|
15355
|
+
* and on TanStack Start that is h3, which replaces the message with the
|
|
15356
|
+
* literal string "HTTPError" and drops the stack. Its `debug` and
|
|
15357
|
+
* `silent` options are not reachable from out here: `requestHandler`
|
|
15358
|
+
* calls `toResponse(value, event)` with no config, and the default is
|
|
15359
|
+
* `{}`. So the body a caller got was
|
|
15360
|
+
* `{"status":500,"unhandled":true,"message":"HTTPError"}` -- the same
|
|
15361
|
+
* five words for a missing project, a bad cookie secret and a module
|
|
15362
|
+
* that failed to link -- with the real cause only on the server's
|
|
15363
|
+
* console.
|
|
15364
|
+
*
|
|
15365
|
+
* That is survivable where the console is readable. It is not in a
|
|
15366
|
+
* Cloudflare Dynamic Worker, whose `console` reaches no tail stream: a
|
|
15367
|
+
* link error in a lazily imported chunk (`does not provide an export
|
|
15368
|
+
* named 'getSerovalPlugins'`) showed up as an unreadable 500 on
|
|
15369
|
+
* `/authorize` and nowhere else at all.
|
|
15370
|
+
*
|
|
15371
|
+
* So every endpoint's throw becomes Val's own 500 envelope, which the
|
|
15372
|
+
* Studio already renders and `curl` already prints. The route and method
|
|
15373
|
+
* are named because a message rarely says which endpoint it came from.
|
|
15374
|
+
*
|
|
15375
|
+
* The MESSAGE travels and the STACK does not. The message is the whole
|
|
15376
|
+
* diagnostic value -- the line above names the package, the chunk and
|
|
15377
|
+
* the export -- while a stack is a walk through somebody's bundle, and
|
|
15378
|
+
* `/authorize` and `/enable` are reachable without a session. So the
|
|
15379
|
+
* stack goes to the console, where a host that can read one will find
|
|
15380
|
+
* it, and the body carries what a caller can act on.
|
|
15381
|
+
*/
|
|
15382
|
+
let res;
|
|
15383
|
+
try {
|
|
15384
|
+
res = await endpointImpl({
|
|
15385
|
+
body: bodyRes.data,
|
|
15386
|
+
cookies: cookiesRes.data,
|
|
15387
|
+
query,
|
|
15388
|
+
path
|
|
15389
|
+
});
|
|
15390
|
+
} catch (err) {
|
|
15391
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
15392
|
+
console.error(`Val: ${method} ${route} threw: ${error.message}`, error.stack);
|
|
15393
|
+
return {
|
|
15394
|
+
status: 500,
|
|
15395
|
+
json: {
|
|
15396
|
+
message: `Val: ${method} ${route} failed: ${error.message}`,
|
|
15397
|
+
details: {
|
|
15398
|
+
route,
|
|
15399
|
+
method,
|
|
15400
|
+
error: error.message
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15403
|
+
};
|
|
15404
|
+
}
|
|
15286
15405
|
if (res.status === 500) {
|
|
15287
15406
|
var _res$json;
|
|
15288
15407
|
return {
|
|
@@ -17212,6 +17331,123 @@ function validateMetadata(actualMetadata, expectedMetadata) {
|
|
|
17212
17331
|
};
|
|
17213
17332
|
}
|
|
17214
17333
|
|
|
17334
|
+
/**
|
|
17335
|
+
* What Val calls to format a file it has just written.
|
|
17336
|
+
*
|
|
17337
|
+
* The same shape `initValServer`, `initValMcp` and `ValOps` take as their
|
|
17338
|
+
* `formatter` option. The path is the file's path INSIDE the project — a
|
|
17339
|
+
* `ModuleFilePath` such as `/content/page.val.ts`, or a `*.val.json` entry
|
|
17340
|
+
* beside it — never an absolute path on disk, because the writers that call it
|
|
17341
|
+
* (`ValOps.prepare`, the CLI's `--fix`) only ever know a module by that path.
|
|
17342
|
+
*/
|
|
17343
|
+
|
|
17344
|
+
/**
|
|
17345
|
+
* Prettier's options, as far as this file is concerned.
|
|
17346
|
+
*
|
|
17347
|
+
* Deliberately opaque: this file never reads an option, it only carries the
|
|
17348
|
+
* bag from `resolveConfig` to `format`. Naming the real `Options` here would
|
|
17349
|
+
* make it a compile error to build `@valbuild/server` in a project that has no
|
|
17350
|
+
* prettier installed, which is most of them — prettier is a dependency the app
|
|
17351
|
+
* opts into, so it is passed IN rather than imported.
|
|
17352
|
+
*/
|
|
17353
|
+
|
|
17354
|
+
/**
|
|
17355
|
+
* `prettier`, as this file wants it.
|
|
17356
|
+
*
|
|
17357
|
+
* Structural for the same reason `SharpLike` in `@valbuild/mcp` is: a type-only
|
|
17358
|
+
* import of a package that is not installed is still a compile error, and this
|
|
17359
|
+
* package has to typecheck without prettier. `createPrettierFormatter.test.ts`
|
|
17360
|
+
* asserts the real library against this type so the structural description
|
|
17361
|
+
* cannot drift away from it unnoticed.
|
|
17362
|
+
*
|
|
17363
|
+
* Every result is `T | Promise<T>` so that `@prettier/sync` — the same API with
|
|
17364
|
+
* the awaits taken out — satisfies it too. Everything here is awaited either
|
|
17365
|
+
* way, and a formatter is allowed to be synchronous: `ValFormatter` says so.
|
|
17366
|
+
*/
|
|
17367
|
+
|
|
17368
|
+
/**
|
|
17369
|
+
* Resolve a path Val handed us against the project.
|
|
17370
|
+
*
|
|
17371
|
+
* Val's writers pass a project-relative path (`/content/page.val.ts`), and
|
|
17372
|
+
* prettier needs a real one — both to find the `.prettierrc` that governs the
|
|
17373
|
+
* file and to match it against `.prettierignore`. An absolute path that is
|
|
17374
|
+
* already inside the project is taken as it comes, so a caller that happens to
|
|
17375
|
+
* hold a real path is not mangled into `<root>/<root>/...`.
|
|
17376
|
+
*/
|
|
17377
|
+
function resolveAgainstProject(projectRoot, filePath) {
|
|
17378
|
+
if (path__default.isAbsolute(filePath)) {
|
|
17379
|
+
const relative = path__default.relative(projectRoot, filePath);
|
|
17380
|
+
if (relative && !relative.startsWith("..") && !path__default.isAbsolute(relative)) {
|
|
17381
|
+
return filePath;
|
|
17382
|
+
}
|
|
17383
|
+
}
|
|
17384
|
+
return path__default.join(projectRoot, filePath);
|
|
17385
|
+
}
|
|
17386
|
+
|
|
17387
|
+
/**
|
|
17388
|
+
* A {@link ValFormatter} that formats the way the project itself does.
|
|
17389
|
+
*
|
|
17390
|
+
* ONE implementation, for every writer of source files Val has: the Studio and
|
|
17391
|
+
* the dev server (`initValServer`), an agent (`initValMcp`) and
|
|
17392
|
+
* `val validate --fix`. They used to disagree — the CLI called
|
|
17393
|
+
* `prettier.format(code, { filepath })` on its own behalf, and `format` does
|
|
17394
|
+
* not read `.prettierrc`; only `resolveConfig`, `getFileInfo` and the prettier
|
|
17395
|
+
* CLI do. `filepath` there selects the PARSER and nothing else, so the output
|
|
17396
|
+
* was valid TypeScript in prettier's default style rather than the project's:
|
|
17397
|
+
* a two-line content fix arriving as a whole-file rewrite, and a red `format`
|
|
17398
|
+
* job in any repo that checks formatting in CI.
|
|
17399
|
+
*
|
|
17400
|
+
* ```ts
|
|
17401
|
+
* import prettier from "prettier";
|
|
17402
|
+
* import { createPrettierFormatter } from "@valbuild/next/server";
|
|
17403
|
+
*
|
|
17404
|
+
* initValServer(valModules, config, {
|
|
17405
|
+
* draftMode,
|
|
17406
|
+
* formatter: createPrettierFormatter(prettier, { projectRoot: process.cwd() }),
|
|
17407
|
+
* });
|
|
17408
|
+
* ```
|
|
17409
|
+
*
|
|
17410
|
+
* `projectRoot` is what a project-relative path is resolved against, and is
|
|
17411
|
+
* therefore also where `.prettierignore` is looked for. Prettier's own config
|
|
17412
|
+
* search walks UP from the resolved file, so a monorepo package with its own
|
|
17413
|
+
* `.prettierrc` is found without being named here.
|
|
17414
|
+
*
|
|
17415
|
+
* Two decisions worth knowing:
|
|
17416
|
+
*
|
|
17417
|
+
* - **`.prettierignore` is honoured; `.gitignore` is not.** The prettier CLI
|
|
17418
|
+
* consults both, but only one of them is a statement about formatting — a
|
|
17419
|
+
* file is gitignored for reasons that have nothing to do with style, and the
|
|
17420
|
+
* only files reaching this function are ones Val itself just wrote.
|
|
17421
|
+
* - **No config found is not an error.** `resolveConfig` returns `null` and
|
|
17422
|
+
* the file is formatted with prettier's defaults, which is what a project
|
|
17423
|
+
* without a config has asked for.
|
|
17424
|
+
*/
|
|
17425
|
+
function createPrettierFormatter(prettier, options) {
|
|
17426
|
+
const projectRoot = path__default.resolve(options.projectRoot);
|
|
17427
|
+
// One array, built once: prettier reads and caches the ignore file itself, so
|
|
17428
|
+
// this is only here to avoid rebuilding the path per file.
|
|
17429
|
+
const ignorePath = [path__default.join(projectRoot, ".prettierignore")];
|
|
17430
|
+
return async (code, filePath) => {
|
|
17431
|
+
const absoluteFilePath = resolveAgainstProject(projectRoot, filePath);
|
|
17432
|
+
const {
|
|
17433
|
+
ignored
|
|
17434
|
+
} = await prettier.getFileInfo(absoluteFilePath, {
|
|
17435
|
+
ignorePath
|
|
17436
|
+
});
|
|
17437
|
+
if (ignored) {
|
|
17438
|
+
return code;
|
|
17439
|
+
}
|
|
17440
|
+
// `resolveConfig` applies the `overrides` that match this file and caches
|
|
17441
|
+
// per directory, so calling it per file is cheap even on a project-wide
|
|
17442
|
+
// `--fix`. `filepath` is still required: it is what picks the parser.
|
|
17443
|
+
const config = await prettier.resolveConfig(absoluteFilePath);
|
|
17444
|
+
return prettier.format(code, {
|
|
17445
|
+
...config,
|
|
17446
|
+
filepath: absoluteFilePath
|
|
17447
|
+
});
|
|
17448
|
+
};
|
|
17449
|
+
}
|
|
17450
|
+
|
|
17215
17451
|
/**
|
|
17216
17452
|
* NOTE: this is intentionally NOT `SharedValConfig` from `@valbuild/shared`.
|
|
17217
17453
|
* That schema requires `files.directory` to be exactly `/public/val`, whereas
|
|
@@ -17794,4 +18030,4 @@ function readCapturedReport(snapshotDir) {
|
|
|
17794
18030
|
return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
|
|
17795
18031
|
}
|
|
17796
18032
|
|
|
17797
|
-
export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
|
|
18033
|
+
export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createPrettierFormatter, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findDefaultExport, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
|