@valbuild/cli 0.149.7 → 0.150.1
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 +70 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +248 -64
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +248 -64
- package/cli/dist/valbuild-cli-cli.esm.js +248 -64
- package/package.json +8 -8
- package/src/checkBuildTools.ts +16 -0
- package/src/cli.ts +16 -0
- package/src/connect.test.ts +32 -1
- package/src/connect.ts +27 -13
- package/src/publish/buildArtifacts.ts +51 -0
- package/src/publish.ts +5 -1
- package/src/runValidation.imageset.test.ts +23 -19
- package/src/runValidation.test.ts +64 -23
- package/src/runValidation.ts +9 -0
- package/src/validate.test.ts +4 -4
- package/src/validate.ts +186 -58
- package/src/validate.warnings.test.ts +170 -0
|
@@ -346,6 +346,16 @@ async function* runValidation({
|
|
|
346
346
|
sourcePath
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
|
+
if (result.warning !== undefined) {
|
|
350
|
+
yield {
|
|
351
|
+
type: "validation-warning",
|
|
352
|
+
sourcePath,
|
|
353
|
+
...result.warning,
|
|
354
|
+
...(v.keyError ? {
|
|
355
|
+
keyError: true
|
|
356
|
+
} : {})
|
|
357
|
+
};
|
|
358
|
+
}
|
|
349
359
|
if (result.fixableErrorMessage !== undefined) {
|
|
350
360
|
fileErrors += 1;
|
|
351
361
|
yield {
|
|
@@ -683,10 +693,13 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
683
693
|
return entry;
|
|
684
694
|
}
|
|
685
695
|
|
|
696
|
+
/** How many of a warning group's files are listed before "… N more". */
|
|
697
|
+
const WARNING_FILES_SHOWN = 5;
|
|
686
698
|
async function validate({
|
|
687
699
|
root,
|
|
688
700
|
fix,
|
|
689
|
-
watch
|
|
701
|
+
watch,
|
|
702
|
+
strict
|
|
690
703
|
}) {
|
|
691
704
|
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
692
705
|
let prettier;
|
|
@@ -698,7 +711,10 @@ async function validate({
|
|
|
698
711
|
const runOnce = () => validateOnce({
|
|
699
712
|
projectRoot,
|
|
700
713
|
fix: !!fix,
|
|
701
|
-
prettier
|
|
714
|
+
prettier,
|
|
715
|
+
...(strict ? {
|
|
716
|
+
strict: true
|
|
717
|
+
} : {})
|
|
702
718
|
});
|
|
703
719
|
if (!watch) {
|
|
704
720
|
const totalErrors = await runOnce();
|
|
@@ -712,7 +728,7 @@ async function validate({
|
|
|
712
728
|
|
|
713
729
|
/**
|
|
714
730
|
* One validation pass over the project, printed as `val validate` prints it,
|
|
715
|
-
* returning the number of errors found
|
|
731
|
+
* returning the number of errors found -- and of warnings, under `strict`.
|
|
716
732
|
*
|
|
717
733
|
* Re-reads config and val files on each call so it always reflects the latest
|
|
718
734
|
* state on disk: `val validate` calls it once or on every change (`--watch`),
|
|
@@ -722,7 +738,8 @@ async function validateOnce({
|
|
|
722
738
|
projectRoot,
|
|
723
739
|
fix,
|
|
724
740
|
prettier,
|
|
725
|
-
blockingOnly = false
|
|
741
|
+
blockingOnly = false,
|
|
742
|
+
strict = false
|
|
726
743
|
}) {
|
|
727
744
|
const valConfigFile = await server.findAndEvalValConfigFile(projectRoot);
|
|
728
745
|
const resolvedValConfigFile = valConfigFile ? {
|
|
@@ -739,6 +756,7 @@ async function validateOnce({
|
|
|
739
756
|
console.log(pc__default["default"].greenBright(`Found ${valFiles.length} files...`));
|
|
740
757
|
const fixedFiles = new Set();
|
|
741
758
|
let totalErrors = 0;
|
|
759
|
+
let totalWarnings = 0;
|
|
742
760
|
let repairedOnSave = 0;
|
|
743
761
|
|
|
744
762
|
// Caches each val file's parsed source so files are read/parsed at most once
|
|
@@ -785,10 +803,15 @@ async function validateOnce({
|
|
|
785
803
|
})) {
|
|
786
804
|
switch (event.type) {
|
|
787
805
|
case "file-valid":
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
durationMs
|
|
791
|
-
}
|
|
806
|
+
// No errors, but it may have warnings: then it is shown with them.
|
|
807
|
+
if (reports.has(relFile(event.file))) {
|
|
808
|
+
reportFor(event.file).durationMs = event.durationMs;
|
|
809
|
+
} else {
|
|
810
|
+
valid.push({
|
|
811
|
+
file: relFile(event.file),
|
|
812
|
+
durationMs: event.durationMs
|
|
813
|
+
});
|
|
814
|
+
}
|
|
792
815
|
break;
|
|
793
816
|
case "file-error-count":
|
|
794
817
|
reportFor(event.file).durationMs = event.durationMs;
|
|
@@ -816,6 +839,21 @@ async function validateOnce({
|
|
|
816
839
|
} : {})
|
|
817
840
|
});
|
|
818
841
|
break;
|
|
842
|
+
case "validation-warning":
|
|
843
|
+
totalWarnings += 1;
|
|
844
|
+
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
845
|
+
severity: "warning",
|
|
846
|
+
sourcePath: event.sourcePath,
|
|
847
|
+
message: event.message,
|
|
848
|
+
files: event.files,
|
|
849
|
+
...(event.hint !== undefined ? {
|
|
850
|
+
hint: event.hint
|
|
851
|
+
} : {}),
|
|
852
|
+
...(event.keyError ? {
|
|
853
|
+
keyError: true
|
|
854
|
+
} : {})
|
|
855
|
+
});
|
|
856
|
+
break;
|
|
819
857
|
case "unknown-fix":
|
|
820
858
|
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
821
859
|
severity: "error",
|
|
@@ -858,28 +896,67 @@ async function validateOnce({
|
|
|
858
896
|
|
|
859
897
|
// Renders a module's diagnostics under a single left "│" gutter bar, with the
|
|
860
898
|
// file name on top and blank gutter lines for air. Output flows least- to
|
|
861
|
-
// most-actionable top-to-bottom
|
|
862
|
-
// bottom, nearest the prompt).
|
|
899
|
+
// most-actionable top-to-bottom: warnings first, then errors, and fixable
|
|
900
|
+
// errors last (at the bottom, nearest the prompt).
|
|
863
901
|
const renderModule = report => {
|
|
864
902
|
const bar = pc__default["default"].dim("│");
|
|
865
|
-
const
|
|
903
|
+
const rank = {
|
|
904
|
+
warning: 0,
|
|
905
|
+
error: 1,
|
|
906
|
+
fixable: 2
|
|
907
|
+
};
|
|
908
|
+
const diagnostics = [...report.diagnostics].sort((a, b) => rank[a.severity] - rank[b.severity]);
|
|
909
|
+
const warningCount = diagnostics.filter(d => d.severity === "warning").length;
|
|
866
910
|
const fixableCount = diagnostics.filter(d => d.severity === "fixable").length;
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
} else {
|
|
876
|
-
label = `${total} error${total > 1 ? "s" : ""}`;
|
|
911
|
+
const errorCount = diagnostics.length - warningCount;
|
|
912
|
+
const symbol = errorCount > 0 ? pc__default["default"].red("✘") : pc__default["default"].yellow("⚠");
|
|
913
|
+
const labels = [];
|
|
914
|
+
if (errorCount > 0) {
|
|
915
|
+
labels.push(`${errorCount} error${errorCount > 1 ? "s" : ""}${fixableCount > 0 ? ` (${fixableCount} fixable)` : ""}`);
|
|
916
|
+
}
|
|
917
|
+
if (warningCount > 0) {
|
|
918
|
+
labels.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
877
919
|
}
|
|
878
|
-
console.log(`${pc__default["default"].bold(report.file)} ${symbol} ${
|
|
920
|
+
console.log(`${pc__default["default"].bold(report.file)} ${symbol} ${labels.join(" · ")} ${pc__default["default"].dim(`(${report.durationMs}ms)`)}`);
|
|
921
|
+
const locationOf = d => sourcePathToLocationParts(d.sourcePath, projectRoot, sourceFileCache, d.keyError ? "key" : "value");
|
|
922
|
+
|
|
923
|
+
// Warnings say the same thing about many values (every local image in a
|
|
924
|
+
// gallery), so each message is printed once, over the places it is about.
|
|
925
|
+
const warningGroups = new Map();
|
|
879
926
|
for (const d of diagnostics) {
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
927
|
+
if (d.severity !== "warning") continue;
|
|
928
|
+
const group = warningGroups.get(d.message);
|
|
929
|
+
if (group) {
|
|
930
|
+
group.push(d);
|
|
931
|
+
} else {
|
|
932
|
+
warningGroups.set(d.message, [d]);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
for (const [message, group] of warningGroups) {
|
|
936
|
+
var _group$find;
|
|
937
|
+
console.log(bar);
|
|
938
|
+
console.log(`${bar} ${pc__default["default"].yellow("⚠")} ${message}`);
|
|
939
|
+
console.log(bar);
|
|
940
|
+
for (const d of group.slice(0, WARNING_FILES_SHOWN)) {
|
|
941
|
+
const parts = locationOf(d);
|
|
942
|
+
const files = d.files.join(", ");
|
|
943
|
+
// `file:line:col` first, so VS Code's terminal links it.
|
|
944
|
+
const where = parts ? `${parts.relativeFile}:${parts.line}:${parts.character}` : d.sourcePath;
|
|
945
|
+
console.log(`${bar} ${where} ${pc__default["default"].dim(files)}`);
|
|
946
|
+
}
|
|
947
|
+
if (group.length > WARNING_FILES_SHOWN) {
|
|
948
|
+
console.log(`${bar} ${pc__default["default"].dim(`… ${group.length - WARNING_FILES_SHOWN} more`)}`);
|
|
949
|
+
}
|
|
950
|
+
const hint = (_group$find = group.find(d => d.hint !== undefined)) === null || _group$find === void 0 ? void 0 : _group$find.hint;
|
|
951
|
+
if (hint !== undefined) {
|
|
952
|
+
console.log(bar);
|
|
953
|
+
console.log(`${bar} ${pc__default["default"].dim(`→ ${hint}`)}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
for (const d of diagnostics) {
|
|
957
|
+
if (d.severity === "warning") continue;
|
|
958
|
+
const parts = locationOf(d);
|
|
959
|
+
const dsym = pc__default["default"].red("✘");
|
|
883
960
|
console.log(bar);
|
|
884
961
|
if (parts) {
|
|
885
962
|
// `file:line:col` (no key/value label) so VS Code's terminal links it.
|
|
@@ -888,7 +965,7 @@ async function validateOnce({
|
|
|
888
965
|
} else {
|
|
889
966
|
console.log(`${bar} ${dsym} ${d.message}`);
|
|
890
967
|
}
|
|
891
|
-
const frame = sourcePathToCodeFrame(d.sourcePath, projectRoot, sourceFileCache,
|
|
968
|
+
const frame = sourcePathToCodeFrame(d.sourcePath, projectRoot, sourceFileCache, d.keyError ? "key" : "value");
|
|
892
969
|
if (frame !== undefined) {
|
|
893
970
|
console.log(bar);
|
|
894
971
|
for (const frameLine of frame.split("\n")) {
|
|
@@ -921,10 +998,13 @@ async function validateOnce({
|
|
|
921
998
|
|
|
922
999
|
// Render the grouped report least- to most-actionable, top-to-bottom, so the
|
|
923
1000
|
// most important things end up at the bottom nearest the prompt: valid files
|
|
924
|
-
// and skipped modules first, then
|
|
1001
|
+
// and skipped modules first, then warning-only modules, then error-only
|
|
1002
|
+
// modules, then fixable modules.
|
|
925
1003
|
const allReports = [...reports.values()];
|
|
1004
|
+
const hasErrors = r => r.diagnostics.some(d => d.severity !== "warning");
|
|
1005
|
+
const warningModules = allReports.filter(r => !hasErrors(r));
|
|
926
1006
|
const fixableModules = allReports.filter(r => r.diagnostics.some(d => d.severity === "fixable"));
|
|
927
|
-
const errorModules = allReports.filter(r => !r.diagnostics.some(d => d.severity === "fixable"));
|
|
1007
|
+
const errorModules = allReports.filter(r => hasErrors(r) && !r.diagnostics.some(d => d.severity === "fixable"));
|
|
928
1008
|
for (const v of valid) {
|
|
929
1009
|
console.log(pc__default["default"].green("✔"), pc__default["default"].dim(`${v.file} valid (${v.durationMs}ms)`));
|
|
930
1010
|
}
|
|
@@ -934,34 +1014,34 @@ async function validateOnce({
|
|
|
934
1014
|
if (allReports.length > 0) {
|
|
935
1015
|
console.log("");
|
|
936
1016
|
}
|
|
937
|
-
for (const report of [...errorModules, ...fixableModules]) {
|
|
1017
|
+
for (const report of [...warningModules, ...errorModules, ...fixableModules]) {
|
|
938
1018
|
renderModule(report);
|
|
939
1019
|
}
|
|
940
1020
|
const fixableTotal = allReports.reduce((n, r) => n + r.diagnostics.filter(d => d.severity === "fixable").length, 0);
|
|
1021
|
+
const errorFiles = allReports.filter(hasErrors).length;
|
|
1022
|
+
const warningFiles = allReports.filter(r => r.diagnostics.some(d => d.severity === "warning")).length;
|
|
1023
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
1024
|
+
const warnings = `${plural(totalWarnings, "warning")} in ${plural(warningFiles, "file")}`;
|
|
1025
|
+
const warningPart = totalWarnings > 0 ? `${warnings}${strict ? ", which --strict counts as errors" : ""}` : undefined;
|
|
1026
|
+
const tail = [...(valid.length > 0 ? [`${valid.length} valid`] : []), ...(skipped.length > 0 ? [`${skipped.length} skipped`] : [])];
|
|
941
1027
|
if (totalErrors > 0) {
|
|
942
|
-
let summary =
|
|
1028
|
+
let summary = plural(totalErrors, "error");
|
|
943
1029
|
if (fixableTotal > 0) {
|
|
944
1030
|
summary += ` (${fixableTotal} fixable)`;
|
|
945
1031
|
}
|
|
946
|
-
summary += ` across ${
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
console.log(pc__default["default"].
|
|
1032
|
+
summary += ` across ${plural(errorFiles, "file")}`;
|
|
1033
|
+
console.log(pc__default["default"].red("✘"), [summary, ...(warningPart ? [warningPart] : []), ...tail].join(" · "));
|
|
1034
|
+
} else if (warningPart && strict) {
|
|
1035
|
+
// Not "No validation errors" beside a ✘: under --strict this run failed,
|
|
1036
|
+
// and the warnings are why.
|
|
1037
|
+
console.log(pc__default["default"].red("✘"), [`${warnings}, and --strict fails on warnings`, ...tail].join(" · "));
|
|
1038
|
+
} else if (warningPart) {
|
|
1039
|
+
console.log(pc__default["default"].green("✔"), ["No validation errors", warningPart, ...tail].join(" · "));
|
|
954
1040
|
} else {
|
|
955
|
-
|
|
956
|
-
if (valid.length > 0) {
|
|
957
|
-
summary += ` · ${valid.length} valid`;
|
|
958
|
-
}
|
|
959
|
-
if (skipped.length > 0) {
|
|
960
|
-
summary += ` · ${skipped.length} skipped`;
|
|
961
|
-
}
|
|
962
|
-
console.log(pc__default["default"].green("✔"), summary);
|
|
1041
|
+
console.log(pc__default["default"].green("✔"), ["No validation errors found", ...tail].join(" · "));
|
|
963
1042
|
}
|
|
964
|
-
|
|
1043
|
+
const errors = blockingOnly ? totalErrors - repairedOnSave : totalErrors;
|
|
1044
|
+
return strict ? errors + totalWarnings : errors;
|
|
965
1045
|
}
|
|
966
1046
|
|
|
967
1047
|
// Directory names anywhere in the path that should never be watched.
|
|
@@ -1208,9 +1288,10 @@ const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
|
1208
1288
|
|
|
1209
1289
|
/**
|
|
1210
1290
|
* `val connect`: opens admin's `/new` on "your own hosting", with what it can
|
|
1211
|
-
* tell from here filled in -- the project `val.config.ts` names
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1291
|
+
* tell from here filled in -- the project `val.config.ts` names, the GitHub
|
|
1292
|
+
* repository `origin` points at, and which folder of it this app is in (a
|
|
1293
|
+
* monorepo has several) -- so the person only confirms. The link is printed
|
|
1294
|
+
* too, for when no browser opens (over SSH, in a container).
|
|
1214
1295
|
*/
|
|
1215
1296
|
async function connect(options) {
|
|
1216
1297
|
const {
|
|
@@ -1218,20 +1299,23 @@ async function connect(options) {
|
|
|
1218
1299
|
} = options;
|
|
1219
1300
|
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
1220
1301
|
const project = await tryGetProject(projectRoot);
|
|
1221
|
-
const
|
|
1302
|
+
const git = findGitDir(projectRoot);
|
|
1303
|
+
const gitRemote = git ? await tryGetGitRemote(git.config) : null;
|
|
1222
1304
|
const repo = gitRemote !== null ? getGitHubOwnerAndRepo(gitRemote) : null;
|
|
1305
|
+
const folder = git ? repoFolder(git.top, projectRoot) : null;
|
|
1223
1306
|
console.log("");
|
|
1224
1307
|
if (project) {
|
|
1225
1308
|
console.log(`Found ${pc__default["default"].bold(`${project.orgName}/${project.projectName}`)} in val.config.ts`);
|
|
1226
1309
|
}
|
|
1227
1310
|
if (repo) {
|
|
1228
|
-
console.log(`Found ${pc__default["default"].bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")`);
|
|
1311
|
+
console.log(`Found ${pc__default["default"].bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")${folder ? `, this app in ${pc__default["default"].bold(folder)}` : ""}`);
|
|
1229
1312
|
} else {
|
|
1230
1313
|
console.log(pc__default["default"].dim("No GitHub repository found: push this project to GitHub, or pick or create one in the browser."));
|
|
1231
1314
|
}
|
|
1232
1315
|
const url = connectUrl(host, {
|
|
1233
1316
|
project,
|
|
1234
|
-
repo
|
|
1317
|
+
repo,
|
|
1318
|
+
folder
|
|
1235
1319
|
});
|
|
1236
1320
|
const opened = openInBrowser(url);
|
|
1237
1321
|
console.log(`\n${opened ? "Finish in your browser. If it did not open, go to:" : "Finish in your browser:"}\n${pc__default["default"].cyan(url)}\n`);
|
|
@@ -1248,6 +1332,7 @@ function connectUrl(host, found) {
|
|
|
1248
1332
|
});
|
|
1249
1333
|
if (found.repo) {
|
|
1250
1334
|
params.set("repo", `${found.repo.owner}/${found.repo.repo}`);
|
|
1335
|
+
if (found.folder) params.set("folder", found.folder);
|
|
1251
1336
|
}
|
|
1252
1337
|
if (found.project) {
|
|
1253
1338
|
params.set("org", found.project.orgName);
|
|
@@ -1312,12 +1397,19 @@ function getGitHubOwnerAndRepo(gitRemote) {
|
|
|
1312
1397
|
repo: match[2]
|
|
1313
1398
|
} : null;
|
|
1314
1399
|
}
|
|
1315
|
-
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Where the app is in its repository, with forward slashes: `apps/web`, or ""
|
|
1403
|
+
* when it is at the top. Null when it is not inside the repository at all.
|
|
1404
|
+
*/
|
|
1405
|
+
function repoFolder(top, projectRoot) {
|
|
1406
|
+
const relative = path__default["default"].relative(top, projectRoot);
|
|
1407
|
+
if (relative.startsWith("..") || path__default["default"].isAbsolute(relative)) return null;
|
|
1408
|
+
return relative.split(path__default["default"].sep).join("/");
|
|
1409
|
+
}
|
|
1410
|
+
async function tryGetGitRemote(gitConfigPath) {
|
|
1316
1411
|
try {
|
|
1317
|
-
const gitConfig =
|
|
1318
|
-
if (!gitConfig) {
|
|
1319
|
-
return null;
|
|
1320
|
-
}
|
|
1412
|
+
const gitConfig = fs__default$1["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1321
1413
|
const remoteMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
1322
1414
|
if (remoteMatch && remoteMatch[1]) {
|
|
1323
1415
|
return remoteMatch[1].trim();
|
|
@@ -1328,13 +1420,18 @@ async function tryGetGitRemote(root) {
|
|
|
1328
1420
|
return null;
|
|
1329
1421
|
}
|
|
1330
1422
|
}
|
|
1331
|
-
|
|
1423
|
+
|
|
1424
|
+
/** The repository `root` is in: its top folder and its `.git/config`. */
|
|
1425
|
+
function findGitDir(root) {
|
|
1332
1426
|
let currentDir = root;
|
|
1333
1427
|
let lastDir = null;
|
|
1334
1428
|
while (currentDir !== lastDir) {
|
|
1335
1429
|
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
1336
1430
|
if (fs__default$1["default"].existsSync(gitConfigPath)) {
|
|
1337
|
-
return
|
|
1431
|
+
return {
|
|
1432
|
+
top: currentDir,
|
|
1433
|
+
config: gitConfigPath
|
|
1434
|
+
};
|
|
1338
1435
|
}
|
|
1339
1436
|
lastDir = currentDir;
|
|
1340
1437
|
currentDir = path__default["default"].dirname(currentDir);
|
|
@@ -2504,6 +2601,14 @@ function resolveArtifactsDir(options) {
|
|
|
2504
2601
|
async function loadBuilder() {
|
|
2505
2602
|
try {
|
|
2506
2603
|
const [build, node] = await Promise.all([Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@valbuild/tanstack-build')); }), Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@valbuild/tanstack-build/node')); })]);
|
|
2604
|
+
/*
|
|
2605
|
+
* Native rolldown rather than its WebAssembly build, whose glue the site's
|
|
2606
|
+
* lockfile picks and can break (`rolldown.ts` in tanstack-build). Asked
|
|
2607
|
+
* for, because the project's own tanstack-build may predate the choice.
|
|
2608
|
+
*/
|
|
2609
|
+
if ("useRolldown" in build && "nativeRolldown" in node) {
|
|
2610
|
+
build.useRolldown(node.nativeRolldown);
|
|
2611
|
+
}
|
|
2507
2612
|
return {
|
|
2508
2613
|
status: "ok",
|
|
2509
2614
|
build,
|
|
@@ -2517,6 +2622,52 @@ async function loadBuilder() {
|
|
|
2517
2622
|
}
|
|
2518
2623
|
}
|
|
2519
2624
|
|
|
2625
|
+
/**
|
|
2626
|
+
* Whether `val publish` could build here: the builder loaded as a publish
|
|
2627
|
+
* loads it, and its bundler made to parse and bundle a module. Nothing is
|
|
2628
|
+
* read from the project and nothing is asked of content.
|
|
2629
|
+
*
|
|
2630
|
+
* What Val Build runs on an upgrade's install before a connected project
|
|
2631
|
+
* may publish it, since the project's CI is what builds a connected publish:
|
|
2632
|
+
* an upgrade that resolved a bundler which cannot load would otherwise land
|
|
2633
|
+
* on the branch and fail every build after it (architecture/quirks.md,
|
|
2634
|
+
* 2026-10-26).
|
|
2635
|
+
*/
|
|
2636
|
+
async function checkBuildTools$1() {
|
|
2637
|
+
const loaded = await loadBuilder();
|
|
2638
|
+
if (loaded.status === "error") return loaded;
|
|
2639
|
+
try {
|
|
2640
|
+
// A builder older than `loadRolldown` loaded its bundler on import.
|
|
2641
|
+
if (!("loadRolldown" in loaded.build)) return {
|
|
2642
|
+
status: "ok"
|
|
2643
|
+
};
|
|
2644
|
+
const {
|
|
2645
|
+
build,
|
|
2646
|
+
parseAstAsync
|
|
2647
|
+
} = await loaded.build.loadRolldown();
|
|
2648
|
+
await parseAstAsync("export const checked = true;");
|
|
2649
|
+
const ENTRY = "\0val-check-build-tools";
|
|
2650
|
+
await build({
|
|
2651
|
+
input: ENTRY,
|
|
2652
|
+
cwd: "/",
|
|
2653
|
+
write: false,
|
|
2654
|
+
plugins: [{
|
|
2655
|
+
name: "val-check-build-tools",
|
|
2656
|
+
resolveId: id => id === ENTRY ? id : null,
|
|
2657
|
+
load: id => id === ENTRY ? "export default 1;" : null
|
|
2658
|
+
}]
|
|
2659
|
+
});
|
|
2660
|
+
return {
|
|
2661
|
+
status: "ok"
|
|
2662
|
+
};
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
return {
|
|
2665
|
+
status: "error",
|
|
2666
|
+
message: `rolldown loaded, and could not build: ${e instanceof Error ? e.message : String(e)}`
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2520
2671
|
/**
|
|
2521
2672
|
* The `@valbuild/ui` this checkout runs: the one its `@valbuild/tanstack`
|
|
2522
2673
|
* resolves, which is a transitive dependency pnpm does not hoist -- so it is
|
|
@@ -4009,13 +4160,17 @@ async function publish(options) {
|
|
|
4009
4160
|
// the Studio leaves to the server to repair (a local image in a remote
|
|
4010
4161
|
// project, metadata to fill in) is printed and published, as it would be
|
|
4011
4162
|
// from the Studio. Every site made on /new starts with its template's
|
|
4012
|
-
// images local, so counting those would refuse its first publish
|
|
4163
|
+
// images local, so counting those would refuse its first publish: they are
|
|
4164
|
+
// warnings, and refuse a publish only under --strict.
|
|
4013
4165
|
if (!options.skipValidation) {
|
|
4014
4166
|
const projectRoot = options.root ? path__default["default"].resolve(options.root) : process.cwd();
|
|
4015
4167
|
const errorCount = await validateOnce({
|
|
4016
4168
|
projectRoot,
|
|
4017
4169
|
fix: false,
|
|
4018
|
-
blockingOnly: true
|
|
4170
|
+
blockingOnly: true,
|
|
4171
|
+
...(options.strict ? {
|
|
4172
|
+
strict: true
|
|
4173
|
+
} : {})
|
|
4019
4174
|
});
|
|
4020
4175
|
if (errorCount > 0) {
|
|
4021
4176
|
error(`Not published: ${errorCount} validation error${errorCount === 1 ? "" : "s"}.`);
|
|
@@ -4234,6 +4389,20 @@ async function ciReport(options) {
|
|
|
4234
4389
|
console.log(pc__default["default"].dim(`Reported the build of ${read.report.commit.slice(0, 7)} as ${read.report.status}.`));
|
|
4235
4390
|
}
|
|
4236
4391
|
|
|
4392
|
+
/**
|
|
4393
|
+
* `val check-build-tools` -- a thin shell around `checkBuildTools`: says
|
|
4394
|
+
* whether `val publish` could build here, and fails when it could not.
|
|
4395
|
+
*/
|
|
4396
|
+
async function checkBuildTools() {
|
|
4397
|
+
const checked = await checkBuildTools$1();
|
|
4398
|
+
if (checked.status === "error") {
|
|
4399
|
+
console.error(pc__default["default"].red(checked.message));
|
|
4400
|
+
process.exitCode = 1;
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4403
|
+
console.log("val publish can build here.");
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4237
4406
|
async function main() {
|
|
4238
4407
|
const {
|
|
4239
4408
|
input,
|
|
@@ -4263,6 +4432,7 @@ async function main() {
|
|
|
4263
4432
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
4264
4433
|
--fix [fix] Attempt to fix validation errors
|
|
4265
4434
|
--watch, -w Re-validate on changes to val.config, val.modules and *.val files
|
|
4435
|
+
--strict Fail on warnings too (local files in a project with remote files)
|
|
4266
4436
|
|
|
4267
4437
|
|
|
4268
4438
|
Command: login
|
|
@@ -4320,6 +4490,13 @@ async function main() {
|
|
|
4320
4490
|
--dry-run Verify, and stop before the site changes
|
|
4321
4491
|
--skip-validation Publish without running "val validate" first. It runs by
|
|
4322
4492
|
default, and an error stops the publish
|
|
4493
|
+
--strict Let a validation warning stop the publish too
|
|
4494
|
+
|
|
4495
|
+
Command: check-build-tools
|
|
4496
|
+
Description: load what "val publish" builds with, and build a one-line module with
|
|
4497
|
+
it: whether this install could publish, without asking content anything. Val Build
|
|
4498
|
+
runs it on an upgrade's install before a connected project can publish the upgrade.
|
|
4499
|
+
Fails when it could not.
|
|
4323
4500
|
|
|
4324
4501
|
Command: list-unused-files
|
|
4325
4502
|
Description: EXPERIMENTAL.
|
|
@@ -4414,6 +4591,9 @@ async function main() {
|
|
|
4414
4591
|
skipValidation: {
|
|
4415
4592
|
type: "boolean"
|
|
4416
4593
|
},
|
|
4594
|
+
strict: {
|
|
4595
|
+
type: "boolean"
|
|
4596
|
+
},
|
|
4417
4597
|
yes: {
|
|
4418
4598
|
type: "boolean"
|
|
4419
4599
|
},
|
|
@@ -4468,6 +4648,8 @@ async function main() {
|
|
|
4468
4648
|
yes: flags.yes,
|
|
4469
4649
|
verbose: flags.verbose
|
|
4470
4650
|
});
|
|
4651
|
+
case "check-build-tools":
|
|
4652
|
+
return checkBuildTools();
|
|
4471
4653
|
case "ci-report":
|
|
4472
4654
|
return ciReport({
|
|
4473
4655
|
root: flags.root,
|
|
@@ -4486,7 +4668,8 @@ async function main() {
|
|
|
4486
4668
|
buildHash: flags.buildHash,
|
|
4487
4669
|
linksOwnCss: flags.linksOwnCss,
|
|
4488
4670
|
dryRun: flags.dryRun,
|
|
4489
|
-
skipValidation: flags.skipValidation
|
|
4671
|
+
skipValidation: flags.skipValidation,
|
|
4672
|
+
strict: flags.strict
|
|
4490
4673
|
});
|
|
4491
4674
|
case "login":
|
|
4492
4675
|
return login({
|
|
@@ -4510,7 +4693,8 @@ async function main() {
|
|
|
4510
4693
|
return validate({
|
|
4511
4694
|
root: flags.root,
|
|
4512
4695
|
fix: flags.fix,
|
|
4513
|
-
watch: flags.watch
|
|
4696
|
+
watch: flags.watch,
|
|
4697
|
+
strict: flags.strict
|
|
4514
4698
|
});
|
|
4515
4699
|
default:
|
|
4516
4700
|
return error(`Unknown command "${input.join(" ")}"`);
|