@valbuild/cli 0.150.0 → 0.150.2
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 +72 -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
|
@@ -311,6 +311,16 @@ async function* runValidation({
|
|
|
311
311
|
sourcePath
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
|
+
if (result.warning !== undefined) {
|
|
315
|
+
yield {
|
|
316
|
+
type: "validation-warning",
|
|
317
|
+
sourcePath,
|
|
318
|
+
...result.warning,
|
|
319
|
+
...(v.keyError ? {
|
|
320
|
+
keyError: true
|
|
321
|
+
} : {})
|
|
322
|
+
};
|
|
323
|
+
}
|
|
314
324
|
if (result.fixableErrorMessage !== undefined) {
|
|
315
325
|
fileErrors += 1;
|
|
316
326
|
yield {
|
|
@@ -648,10 +658,13 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
648
658
|
return entry;
|
|
649
659
|
}
|
|
650
660
|
|
|
661
|
+
/** How many of a warning group's files are listed before "… N more". */
|
|
662
|
+
const WARNING_FILES_SHOWN = 5;
|
|
651
663
|
async function validate({
|
|
652
664
|
root,
|
|
653
665
|
fix,
|
|
654
|
-
watch
|
|
666
|
+
watch,
|
|
667
|
+
strict
|
|
655
668
|
}) {
|
|
656
669
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
657
670
|
let prettier;
|
|
@@ -663,7 +676,10 @@ async function validate({
|
|
|
663
676
|
const runOnce = () => validateOnce({
|
|
664
677
|
projectRoot,
|
|
665
678
|
fix: !!fix,
|
|
666
|
-
prettier
|
|
679
|
+
prettier,
|
|
680
|
+
...(strict ? {
|
|
681
|
+
strict: true
|
|
682
|
+
} : {})
|
|
667
683
|
});
|
|
668
684
|
if (!watch) {
|
|
669
685
|
const totalErrors = await runOnce();
|
|
@@ -677,7 +693,7 @@ async function validate({
|
|
|
677
693
|
|
|
678
694
|
/**
|
|
679
695
|
* One validation pass over the project, printed as `val validate` prints it,
|
|
680
|
-
* returning the number of errors found
|
|
696
|
+
* returning the number of errors found -- and of warnings, under `strict`.
|
|
681
697
|
*
|
|
682
698
|
* Re-reads config and val files on each call so it always reflects the latest
|
|
683
699
|
* state on disk: `val validate` calls it once or on every change (`--watch`),
|
|
@@ -687,7 +703,8 @@ async function validateOnce({
|
|
|
687
703
|
projectRoot,
|
|
688
704
|
fix,
|
|
689
705
|
prettier,
|
|
690
|
-
blockingOnly = false
|
|
706
|
+
blockingOnly = false,
|
|
707
|
+
strict = false
|
|
691
708
|
}) {
|
|
692
709
|
const valConfigFile = await findAndEvalValConfigFile(projectRoot);
|
|
693
710
|
const resolvedValConfigFile = valConfigFile ? {
|
|
@@ -704,6 +721,7 @@ async function validateOnce({
|
|
|
704
721
|
console.log(pc.greenBright(`Found ${valFiles.length} files...`));
|
|
705
722
|
const fixedFiles = new Set();
|
|
706
723
|
let totalErrors = 0;
|
|
724
|
+
let totalWarnings = 0;
|
|
707
725
|
let repairedOnSave = 0;
|
|
708
726
|
|
|
709
727
|
// Caches each val file's parsed source so files are read/parsed at most once
|
|
@@ -750,10 +768,15 @@ async function validateOnce({
|
|
|
750
768
|
})) {
|
|
751
769
|
switch (event.type) {
|
|
752
770
|
case "file-valid":
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
durationMs
|
|
756
|
-
}
|
|
771
|
+
// No errors, but it may have warnings: then it is shown with them.
|
|
772
|
+
if (reports.has(relFile(event.file))) {
|
|
773
|
+
reportFor(event.file).durationMs = event.durationMs;
|
|
774
|
+
} else {
|
|
775
|
+
valid.push({
|
|
776
|
+
file: relFile(event.file),
|
|
777
|
+
durationMs: event.durationMs
|
|
778
|
+
});
|
|
779
|
+
}
|
|
757
780
|
break;
|
|
758
781
|
case "file-error-count":
|
|
759
782
|
reportFor(event.file).durationMs = event.durationMs;
|
|
@@ -781,6 +804,21 @@ async function validateOnce({
|
|
|
781
804
|
} : {})
|
|
782
805
|
});
|
|
783
806
|
break;
|
|
807
|
+
case "validation-warning":
|
|
808
|
+
totalWarnings += 1;
|
|
809
|
+
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
810
|
+
severity: "warning",
|
|
811
|
+
sourcePath: event.sourcePath,
|
|
812
|
+
message: event.message,
|
|
813
|
+
files: event.files,
|
|
814
|
+
...(event.hint !== undefined ? {
|
|
815
|
+
hint: event.hint
|
|
816
|
+
} : {}),
|
|
817
|
+
...(event.keyError ? {
|
|
818
|
+
keyError: true
|
|
819
|
+
} : {})
|
|
820
|
+
});
|
|
821
|
+
break;
|
|
784
822
|
case "unknown-fix":
|
|
785
823
|
reportFor(moduleOf(event.sourcePath)).diagnostics.push({
|
|
786
824
|
severity: "error",
|
|
@@ -823,28 +861,67 @@ async function validateOnce({
|
|
|
823
861
|
|
|
824
862
|
// Renders a module's diagnostics under a single left "│" gutter bar, with the
|
|
825
863
|
// file name on top and blank gutter lines for air. Output flows least- to
|
|
826
|
-
// most-actionable top-to-bottom
|
|
827
|
-
// bottom, nearest the prompt).
|
|
864
|
+
// most-actionable top-to-bottom: warnings first, then errors, and fixable
|
|
865
|
+
// errors last (at the bottom, nearest the prompt).
|
|
828
866
|
const renderModule = report => {
|
|
829
867
|
const bar = pc.dim("│");
|
|
830
|
-
const
|
|
868
|
+
const rank = {
|
|
869
|
+
warning: 0,
|
|
870
|
+
error: 1,
|
|
871
|
+
fixable: 2
|
|
872
|
+
};
|
|
873
|
+
const diagnostics = [...report.diagnostics].sort((a, b) => rank[a.severity] - rank[b.severity]);
|
|
874
|
+
const warningCount = diagnostics.filter(d => d.severity === "warning").length;
|
|
831
875
|
const fixableCount = diagnostics.filter(d => d.severity === "fixable").length;
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
} else {
|
|
841
|
-
label = `${total} error${total > 1 ? "s" : ""}`;
|
|
876
|
+
const errorCount = diagnostics.length - warningCount;
|
|
877
|
+
const symbol = errorCount > 0 ? pc.red("✘") : pc.yellow("⚠");
|
|
878
|
+
const labels = [];
|
|
879
|
+
if (errorCount > 0) {
|
|
880
|
+
labels.push(`${errorCount} error${errorCount > 1 ? "s" : ""}${fixableCount > 0 ? ` (${fixableCount} fixable)` : ""}`);
|
|
881
|
+
}
|
|
882
|
+
if (warningCount > 0) {
|
|
883
|
+
labels.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
842
884
|
}
|
|
843
|
-
console.log(`${pc.bold(report.file)} ${symbol} ${
|
|
885
|
+
console.log(`${pc.bold(report.file)} ${symbol} ${labels.join(" · ")} ${pc.dim(`(${report.durationMs}ms)`)}`);
|
|
886
|
+
const locationOf = d => sourcePathToLocationParts(d.sourcePath, projectRoot, sourceFileCache, d.keyError ? "key" : "value");
|
|
887
|
+
|
|
888
|
+
// Warnings say the same thing about many values (every local image in a
|
|
889
|
+
// gallery), so each message is printed once, over the places it is about.
|
|
890
|
+
const warningGroups = new Map();
|
|
844
891
|
for (const d of diagnostics) {
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
|
|
892
|
+
if (d.severity !== "warning") continue;
|
|
893
|
+
const group = warningGroups.get(d.message);
|
|
894
|
+
if (group) {
|
|
895
|
+
group.push(d);
|
|
896
|
+
} else {
|
|
897
|
+
warningGroups.set(d.message, [d]);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
for (const [message, group] of warningGroups) {
|
|
901
|
+
var _group$find;
|
|
902
|
+
console.log(bar);
|
|
903
|
+
console.log(`${bar} ${pc.yellow("⚠")} ${message}`);
|
|
904
|
+
console.log(bar);
|
|
905
|
+
for (const d of group.slice(0, WARNING_FILES_SHOWN)) {
|
|
906
|
+
const parts = locationOf(d);
|
|
907
|
+
const files = d.files.join(", ");
|
|
908
|
+
// `file:line:col` first, so VS Code's terminal links it.
|
|
909
|
+
const where = parts ? `${parts.relativeFile}:${parts.line}:${parts.character}` : d.sourcePath;
|
|
910
|
+
console.log(`${bar} ${where} ${pc.dim(files)}`);
|
|
911
|
+
}
|
|
912
|
+
if (group.length > WARNING_FILES_SHOWN) {
|
|
913
|
+
console.log(`${bar} ${pc.dim(`… ${group.length - WARNING_FILES_SHOWN} more`)}`);
|
|
914
|
+
}
|
|
915
|
+
const hint = (_group$find = group.find(d => d.hint !== undefined)) === null || _group$find === void 0 ? void 0 : _group$find.hint;
|
|
916
|
+
if (hint !== undefined) {
|
|
917
|
+
console.log(bar);
|
|
918
|
+
console.log(`${bar} ${pc.dim(`→ ${hint}`)}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
for (const d of diagnostics) {
|
|
922
|
+
if (d.severity === "warning") continue;
|
|
923
|
+
const parts = locationOf(d);
|
|
924
|
+
const dsym = pc.red("✘");
|
|
848
925
|
console.log(bar);
|
|
849
926
|
if (parts) {
|
|
850
927
|
// `file:line:col` (no key/value label) so VS Code's terminal links it.
|
|
@@ -853,7 +930,7 @@ async function validateOnce({
|
|
|
853
930
|
} else {
|
|
854
931
|
console.log(`${bar} ${dsym} ${d.message}`);
|
|
855
932
|
}
|
|
856
|
-
const frame = sourcePathToCodeFrame(d.sourcePath, projectRoot, sourceFileCache,
|
|
933
|
+
const frame = sourcePathToCodeFrame(d.sourcePath, projectRoot, sourceFileCache, d.keyError ? "key" : "value");
|
|
857
934
|
if (frame !== undefined) {
|
|
858
935
|
console.log(bar);
|
|
859
936
|
for (const frameLine of frame.split("\n")) {
|
|
@@ -886,10 +963,13 @@ async function validateOnce({
|
|
|
886
963
|
|
|
887
964
|
// Render the grouped report least- to most-actionable, top-to-bottom, so the
|
|
888
965
|
// most important things end up at the bottom nearest the prompt: valid files
|
|
889
|
-
// and skipped modules first, then
|
|
966
|
+
// and skipped modules first, then warning-only modules, then error-only
|
|
967
|
+
// modules, then fixable modules.
|
|
890
968
|
const allReports = [...reports.values()];
|
|
969
|
+
const hasErrors = r => r.diagnostics.some(d => d.severity !== "warning");
|
|
970
|
+
const warningModules = allReports.filter(r => !hasErrors(r));
|
|
891
971
|
const fixableModules = allReports.filter(r => r.diagnostics.some(d => d.severity === "fixable"));
|
|
892
|
-
const errorModules = allReports.filter(r => !r.diagnostics.some(d => d.severity === "fixable"));
|
|
972
|
+
const errorModules = allReports.filter(r => hasErrors(r) && !r.diagnostics.some(d => d.severity === "fixable"));
|
|
893
973
|
for (const v of valid) {
|
|
894
974
|
console.log(pc.green("✔"), pc.dim(`${v.file} valid (${v.durationMs}ms)`));
|
|
895
975
|
}
|
|
@@ -899,34 +979,34 @@ async function validateOnce({
|
|
|
899
979
|
if (allReports.length > 0) {
|
|
900
980
|
console.log("");
|
|
901
981
|
}
|
|
902
|
-
for (const report of [...errorModules, ...fixableModules]) {
|
|
982
|
+
for (const report of [...warningModules, ...errorModules, ...fixableModules]) {
|
|
903
983
|
renderModule(report);
|
|
904
984
|
}
|
|
905
985
|
const fixableTotal = allReports.reduce((n, r) => n + r.diagnostics.filter(d => d.severity === "fixable").length, 0);
|
|
986
|
+
const errorFiles = allReports.filter(hasErrors).length;
|
|
987
|
+
const warningFiles = allReports.filter(r => r.diagnostics.some(d => d.severity === "warning")).length;
|
|
988
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
989
|
+
const warnings = `${plural(totalWarnings, "warning")} in ${plural(warningFiles, "file")}`;
|
|
990
|
+
const warningPart = totalWarnings > 0 ? `${warnings}${strict ? ", which --strict counts as errors" : ""}` : undefined;
|
|
991
|
+
const tail = [...(valid.length > 0 ? [`${valid.length} valid`] : []), ...(skipped.length > 0 ? [`${skipped.length} skipped`] : [])];
|
|
906
992
|
if (totalErrors > 0) {
|
|
907
|
-
let summary =
|
|
993
|
+
let summary = plural(totalErrors, "error");
|
|
908
994
|
if (fixableTotal > 0) {
|
|
909
995
|
summary += ` (${fixableTotal} fixable)`;
|
|
910
996
|
}
|
|
911
|
-
summary += ` across ${
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
console.log(pc.
|
|
997
|
+
summary += ` across ${plural(errorFiles, "file")}`;
|
|
998
|
+
console.log(pc.red("✘"), [summary, ...(warningPart ? [warningPart] : []), ...tail].join(" · "));
|
|
999
|
+
} else if (warningPart && strict) {
|
|
1000
|
+
// Not "No validation errors" beside a ✘: under --strict this run failed,
|
|
1001
|
+
// and the warnings are why.
|
|
1002
|
+
console.log(pc.red("✘"), [`${warnings}, and --strict fails on warnings`, ...tail].join(" · "));
|
|
1003
|
+
} else if (warningPart) {
|
|
1004
|
+
console.log(pc.green("✔"), ["No validation errors", warningPart, ...tail].join(" · "));
|
|
919
1005
|
} else {
|
|
920
|
-
|
|
921
|
-
if (valid.length > 0) {
|
|
922
|
-
summary += ` · ${valid.length} valid`;
|
|
923
|
-
}
|
|
924
|
-
if (skipped.length > 0) {
|
|
925
|
-
summary += ` · ${skipped.length} skipped`;
|
|
926
|
-
}
|
|
927
|
-
console.log(pc.green("✔"), summary);
|
|
1006
|
+
console.log(pc.green("✔"), ["No validation errors found", ...tail].join(" · "));
|
|
928
1007
|
}
|
|
929
|
-
|
|
1008
|
+
const errors = blockingOnly ? totalErrors - repairedOnSave : totalErrors;
|
|
1009
|
+
return strict ? errors + totalWarnings : errors;
|
|
930
1010
|
}
|
|
931
1011
|
|
|
932
1012
|
// Directory names anywhere in the path that should never be watched.
|
|
@@ -1173,9 +1253,10 @@ const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
|
1173
1253
|
|
|
1174
1254
|
/**
|
|
1175
1255
|
* `val connect`: opens admin's `/new` on "your own hosting", with what it can
|
|
1176
|
-
* tell from here filled in -- the project `val.config.ts` names
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1256
|
+
* tell from here filled in -- the project `val.config.ts` names, the GitHub
|
|
1257
|
+
* repository `origin` points at, and which folder of it this app is in (a
|
|
1258
|
+
* monorepo has several) -- so the person only confirms. The link is printed
|
|
1259
|
+
* too, for when no browser opens (over SSH, in a container).
|
|
1179
1260
|
*/
|
|
1180
1261
|
async function connect(options) {
|
|
1181
1262
|
const {
|
|
@@ -1183,20 +1264,23 @@ async function connect(options) {
|
|
|
1183
1264
|
} = options;
|
|
1184
1265
|
const projectRoot = root ? path.resolve(root) : process.cwd();
|
|
1185
1266
|
const project = await tryGetProject(projectRoot);
|
|
1186
|
-
const
|
|
1267
|
+
const git = findGitDir(projectRoot);
|
|
1268
|
+
const gitRemote = git ? await tryGetGitRemote(git.config) : null;
|
|
1187
1269
|
const repo = gitRemote !== null ? getGitHubOwnerAndRepo(gitRemote) : null;
|
|
1270
|
+
const folder = git ? repoFolder(git.top, projectRoot) : null;
|
|
1188
1271
|
console.log("");
|
|
1189
1272
|
if (project) {
|
|
1190
1273
|
console.log(`Found ${pc.bold(`${project.orgName}/${project.projectName}`)} in val.config.ts`);
|
|
1191
1274
|
}
|
|
1192
1275
|
if (repo) {
|
|
1193
|
-
console.log(`Found ${pc.bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")`);
|
|
1276
|
+
console.log(`Found ${pc.bold(`${repo.owner}/${repo.repo}`)} on GitHub (git remote "origin")${folder ? `, this app in ${pc.bold(folder)}` : ""}`);
|
|
1194
1277
|
} else {
|
|
1195
1278
|
console.log(pc.dim("No GitHub repository found: push this project to GitHub, or pick or create one in the browser."));
|
|
1196
1279
|
}
|
|
1197
1280
|
const url = connectUrl(host, {
|
|
1198
1281
|
project,
|
|
1199
|
-
repo
|
|
1282
|
+
repo,
|
|
1283
|
+
folder
|
|
1200
1284
|
});
|
|
1201
1285
|
const opened = openInBrowser(url);
|
|
1202
1286
|
console.log(`\n${opened ? "Finish in your browser. If it did not open, go to:" : "Finish in your browser:"}\n${pc.cyan(url)}\n`);
|
|
@@ -1213,6 +1297,7 @@ function connectUrl(host, found) {
|
|
|
1213
1297
|
});
|
|
1214
1298
|
if (found.repo) {
|
|
1215
1299
|
params.set("repo", `${found.repo.owner}/${found.repo.repo}`);
|
|
1300
|
+
if (found.folder) params.set("folder", found.folder);
|
|
1216
1301
|
}
|
|
1217
1302
|
if (found.project) {
|
|
1218
1303
|
params.set("org", found.project.orgName);
|
|
@@ -1277,12 +1362,19 @@ function getGitHubOwnerAndRepo(gitRemote) {
|
|
|
1277
1362
|
repo: match[2]
|
|
1278
1363
|
} : null;
|
|
1279
1364
|
}
|
|
1280
|
-
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Where the app is in its repository, with forward slashes: `apps/web`, or ""
|
|
1368
|
+
* when it is at the top. Null when it is not inside the repository at all.
|
|
1369
|
+
*/
|
|
1370
|
+
function repoFolder(top, projectRoot) {
|
|
1371
|
+
const relative = path.relative(top, projectRoot);
|
|
1372
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) return null;
|
|
1373
|
+
return relative.split(path.sep).join("/");
|
|
1374
|
+
}
|
|
1375
|
+
async function tryGetGitRemote(gitConfigPath) {
|
|
1281
1376
|
try {
|
|
1282
|
-
const gitConfig =
|
|
1283
|
-
if (!gitConfig) {
|
|
1284
|
-
return null;
|
|
1285
|
-
}
|
|
1377
|
+
const gitConfig = fs$1.readFileSync(gitConfigPath, "utf-8");
|
|
1286
1378
|
const remoteMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
1287
1379
|
if (remoteMatch && remoteMatch[1]) {
|
|
1288
1380
|
return remoteMatch[1].trim();
|
|
@@ -1293,13 +1385,18 @@ async function tryGetGitRemote(root) {
|
|
|
1293
1385
|
return null;
|
|
1294
1386
|
}
|
|
1295
1387
|
}
|
|
1296
|
-
|
|
1388
|
+
|
|
1389
|
+
/** The repository `root` is in: its top folder and its `.git/config`. */
|
|
1390
|
+
function findGitDir(root) {
|
|
1297
1391
|
let currentDir = root;
|
|
1298
1392
|
let lastDir = null;
|
|
1299
1393
|
while (currentDir !== lastDir) {
|
|
1300
1394
|
const gitConfigPath = path.join(currentDir, ".git", "config");
|
|
1301
1395
|
if (fs$1.existsSync(gitConfigPath)) {
|
|
1302
|
-
return
|
|
1396
|
+
return {
|
|
1397
|
+
top: currentDir,
|
|
1398
|
+
config: gitConfigPath
|
|
1399
|
+
};
|
|
1303
1400
|
}
|
|
1304
1401
|
lastDir = currentDir;
|
|
1305
1402
|
currentDir = path.dirname(currentDir);
|
|
@@ -2469,6 +2566,14 @@ function resolveArtifactsDir(options) {
|
|
|
2469
2566
|
async function loadBuilder() {
|
|
2470
2567
|
try {
|
|
2471
2568
|
const [build, node] = await Promise.all([import('@valbuild/tanstack-build'), import('@valbuild/tanstack-build/node')]);
|
|
2569
|
+
/*
|
|
2570
|
+
* Native rolldown rather than its WebAssembly build, whose glue the site's
|
|
2571
|
+
* lockfile picks and can break (`rolldown.ts` in tanstack-build). Asked
|
|
2572
|
+
* for, because the project's own tanstack-build may predate the choice.
|
|
2573
|
+
*/
|
|
2574
|
+
if ("useRolldown" in build && "nativeRolldown" in node) {
|
|
2575
|
+
build.useRolldown(node.nativeRolldown);
|
|
2576
|
+
}
|
|
2472
2577
|
return {
|
|
2473
2578
|
status: "ok",
|
|
2474
2579
|
build,
|
|
@@ -2482,6 +2587,52 @@ async function loadBuilder() {
|
|
|
2482
2587
|
}
|
|
2483
2588
|
}
|
|
2484
2589
|
|
|
2590
|
+
/**
|
|
2591
|
+
* Whether `val publish` could build here: the builder loaded as a publish
|
|
2592
|
+
* loads it, and its bundler made to parse and bundle a module. Nothing is
|
|
2593
|
+
* read from the project and nothing is asked of content.
|
|
2594
|
+
*
|
|
2595
|
+
* What Val Build runs on an upgrade's install before a connected project
|
|
2596
|
+
* may publish it, since the project's CI is what builds a connected publish:
|
|
2597
|
+
* an upgrade that resolved a bundler which cannot load would otherwise land
|
|
2598
|
+
* on the branch and fail every build after it (architecture/quirks.md,
|
|
2599
|
+
* 2026-10-26).
|
|
2600
|
+
*/
|
|
2601
|
+
async function checkBuildTools$1() {
|
|
2602
|
+
const loaded = await loadBuilder();
|
|
2603
|
+
if (loaded.status === "error") return loaded;
|
|
2604
|
+
try {
|
|
2605
|
+
// A builder older than `loadRolldown` loaded its bundler on import.
|
|
2606
|
+
if (!("loadRolldown" in loaded.build)) return {
|
|
2607
|
+
status: "ok"
|
|
2608
|
+
};
|
|
2609
|
+
const {
|
|
2610
|
+
build,
|
|
2611
|
+
parseAstAsync
|
|
2612
|
+
} = await loaded.build.loadRolldown();
|
|
2613
|
+
await parseAstAsync("export const checked = true;");
|
|
2614
|
+
const ENTRY = "\0val-check-build-tools";
|
|
2615
|
+
await build({
|
|
2616
|
+
input: ENTRY,
|
|
2617
|
+
cwd: "/",
|
|
2618
|
+
write: false,
|
|
2619
|
+
plugins: [{
|
|
2620
|
+
name: "val-check-build-tools",
|
|
2621
|
+
resolveId: id => id === ENTRY ? id : null,
|
|
2622
|
+
load: id => id === ENTRY ? "export default 1;" : null
|
|
2623
|
+
}]
|
|
2624
|
+
});
|
|
2625
|
+
return {
|
|
2626
|
+
status: "ok"
|
|
2627
|
+
};
|
|
2628
|
+
} catch (e) {
|
|
2629
|
+
return {
|
|
2630
|
+
status: "error",
|
|
2631
|
+
message: `rolldown loaded, and could not build: ${e instanceof Error ? e.message : String(e)}`
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2485
2636
|
/**
|
|
2486
2637
|
* The `@valbuild/ui` this checkout runs: the one its `@valbuild/tanstack`
|
|
2487
2638
|
* resolves, which is a transitive dependency pnpm does not hoist -- so it is
|
|
@@ -3974,13 +4125,17 @@ async function publish(options) {
|
|
|
3974
4125
|
// the Studio leaves to the server to repair (a local image in a remote
|
|
3975
4126
|
// project, metadata to fill in) is printed and published, as it would be
|
|
3976
4127
|
// from the Studio. Every site made on /new starts with its template's
|
|
3977
|
-
// images local, so counting those would refuse its first publish
|
|
4128
|
+
// images local, so counting those would refuse its first publish: they are
|
|
4129
|
+
// warnings, and refuse a publish only under --strict.
|
|
3978
4130
|
if (!options.skipValidation) {
|
|
3979
4131
|
const projectRoot = options.root ? path.resolve(options.root) : process.cwd();
|
|
3980
4132
|
const errorCount = await validateOnce({
|
|
3981
4133
|
projectRoot,
|
|
3982
4134
|
fix: false,
|
|
3983
|
-
blockingOnly: true
|
|
4135
|
+
blockingOnly: true,
|
|
4136
|
+
...(options.strict ? {
|
|
4137
|
+
strict: true
|
|
4138
|
+
} : {})
|
|
3984
4139
|
});
|
|
3985
4140
|
if (errorCount > 0) {
|
|
3986
4141
|
error(`Not published: ${errorCount} validation error${errorCount === 1 ? "" : "s"}.`);
|
|
@@ -4199,6 +4354,20 @@ async function ciReport(options) {
|
|
|
4199
4354
|
console.log(pc.dim(`Reported the build of ${read.report.commit.slice(0, 7)} as ${read.report.status}.`));
|
|
4200
4355
|
}
|
|
4201
4356
|
|
|
4357
|
+
/**
|
|
4358
|
+
* `val check-build-tools` -- a thin shell around `checkBuildTools`: says
|
|
4359
|
+
* whether `val publish` could build here, and fails when it could not.
|
|
4360
|
+
*/
|
|
4361
|
+
async function checkBuildTools() {
|
|
4362
|
+
const checked = await checkBuildTools$1();
|
|
4363
|
+
if (checked.status === "error") {
|
|
4364
|
+
console.error(pc.red(checked.message));
|
|
4365
|
+
process.exitCode = 1;
|
|
4366
|
+
return;
|
|
4367
|
+
}
|
|
4368
|
+
console.log("val publish can build here.");
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4202
4371
|
async function main() {
|
|
4203
4372
|
const {
|
|
4204
4373
|
input,
|
|
@@ -4228,6 +4397,7 @@ async function main() {
|
|
|
4228
4397
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
4229
4398
|
--fix [fix] Attempt to fix validation errors
|
|
4230
4399
|
--watch, -w Re-validate on changes to val.config, val.modules and *.val files
|
|
4400
|
+
--strict Fail on warnings too (local files in a project with remote files)
|
|
4231
4401
|
|
|
4232
4402
|
|
|
4233
4403
|
Command: login
|
|
@@ -4285,6 +4455,13 @@ async function main() {
|
|
|
4285
4455
|
--dry-run Verify, and stop before the site changes
|
|
4286
4456
|
--skip-validation Publish without running "val validate" first. It runs by
|
|
4287
4457
|
default, and an error stops the publish
|
|
4458
|
+
--strict Let a validation warning stop the publish too
|
|
4459
|
+
|
|
4460
|
+
Command: check-build-tools
|
|
4461
|
+
Description: load what "val publish" builds with, and build a one-line module with
|
|
4462
|
+
it: whether this install could publish, without asking content anything. Val Build
|
|
4463
|
+
runs it on an upgrade's install before a connected project can publish the upgrade.
|
|
4464
|
+
Fails when it could not.
|
|
4288
4465
|
|
|
4289
4466
|
Command: list-unused-files
|
|
4290
4467
|
Description: EXPERIMENTAL.
|
|
@@ -4379,6 +4556,9 @@ async function main() {
|
|
|
4379
4556
|
skipValidation: {
|
|
4380
4557
|
type: "boolean"
|
|
4381
4558
|
},
|
|
4559
|
+
strict: {
|
|
4560
|
+
type: "boolean"
|
|
4561
|
+
},
|
|
4382
4562
|
yes: {
|
|
4383
4563
|
type: "boolean"
|
|
4384
4564
|
},
|
|
@@ -4433,6 +4613,8 @@ async function main() {
|
|
|
4433
4613
|
yes: flags.yes,
|
|
4434
4614
|
verbose: flags.verbose
|
|
4435
4615
|
});
|
|
4616
|
+
case "check-build-tools":
|
|
4617
|
+
return checkBuildTools();
|
|
4436
4618
|
case "ci-report":
|
|
4437
4619
|
return ciReport({
|
|
4438
4620
|
root: flags.root,
|
|
@@ -4451,7 +4633,8 @@ async function main() {
|
|
|
4451
4633
|
buildHash: flags.buildHash,
|
|
4452
4634
|
linksOwnCss: flags.linksOwnCss,
|
|
4453
4635
|
dryRun: flags.dryRun,
|
|
4454
|
-
skipValidation: flags.skipValidation
|
|
4636
|
+
skipValidation: flags.skipValidation,
|
|
4637
|
+
strict: flags.strict
|
|
4455
4638
|
});
|
|
4456
4639
|
case "login":
|
|
4457
4640
|
return login({
|
|
@@ -4475,7 +4658,8 @@ async function main() {
|
|
|
4475
4658
|
return validate({
|
|
4476
4659
|
root: flags.root,
|
|
4477
4660
|
fix: flags.fix,
|
|
4478
|
-
watch: flags.watch
|
|
4661
|
+
watch: flags.watch,
|
|
4662
|
+
strict: flags.strict
|
|
4479
4663
|
});
|
|
4480
4664
|
default:
|
|
4481
4665
|
return error(`Unknown command "${input.join(" ")}"`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.150.
|
|
4
|
+
"version": "0.150.2",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
28
|
"zod-validation-error": "^5.0.0",
|
|
29
|
-
"@valbuild/
|
|
30
|
-
"@valbuild/server": "0.150.0",
|
|
31
|
-
"@valbuild/shared": "0.149.7",
|
|
29
|
+
"@valbuild/core": "0.150.2",
|
|
32
30
|
"@valbuild/eslint-plugin": "0.125.0",
|
|
33
|
-
"@valbuild/
|
|
31
|
+
"@valbuild/language-server": "0.150.2",
|
|
32
|
+
"@valbuild/shared": "0.150.2",
|
|
33
|
+
"@valbuild/server": "0.150.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"prettier": "*",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"rolldown": ">=1.2.5",
|
|
39
39
|
"@tanstack/router-generator": ">=1.167.0",
|
|
40
40
|
"@tanstack/router-plugin": ">=1.168.0",
|
|
41
|
-
"@valbuild/tanstack-build": "^0.
|
|
41
|
+
"@valbuild/tanstack-build": "^0.150.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@valbuild/tanstack-build": {
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"@tanstack/router-generator": "1.167.32",
|
|
69
69
|
"@tanstack/router-plugin": "1.168.34",
|
|
70
70
|
"prettier": "~3.8.5",
|
|
71
|
-
"rolldown": "
|
|
71
|
+
"rolldown": "1.2.9",
|
|
72
72
|
"tsx": "^4.23.12",
|
|
73
|
-
"@valbuild/tanstack-build": "0.
|
|
73
|
+
"@valbuild/tanstack-build": "0.150.1"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"start": "tsx src/cli.ts --",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { checkBuildTools as check } from "./publish/buildArtifacts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `val check-build-tools` -- a thin shell around `checkBuildTools`: says
|
|
6
|
+
* whether `val publish` could build here, and fails when it could not.
|
|
7
|
+
*/
|
|
8
|
+
export async function checkBuildTools(): Promise<void> {
|
|
9
|
+
const checked = await check();
|
|
10
|
+
if (checked.status === "error") {
|
|
11
|
+
console.error(pc.red(checked.message));
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
console.log("val publish can build here.");
|
|
16
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { debug } from "./debug";
|
|
|
11
11
|
import { deleteUnappliablePatches } from "./deleteUnappliablePatches";
|
|
12
12
|
import { publish } from "./publish";
|
|
13
13
|
import { ciReport } from "./ciReport";
|
|
14
|
+
import { checkBuildTools } from "./checkBuildTools";
|
|
14
15
|
|
|
15
16
|
async function main(): Promise<void> {
|
|
16
17
|
const { input, flags, showHelp } = meow(
|
|
@@ -38,6 +39,7 @@ async function main(): Promise<void> {
|
|
|
38
39
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
39
40
|
--fix [fix] Attempt to fix validation errors
|
|
40
41
|
--watch, -w Re-validate on changes to val.config, val.modules and *.val files
|
|
42
|
+
--strict Fail on warnings too (local files in a project with remote files)
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
Command: login
|
|
@@ -95,6 +97,13 @@ async function main(): Promise<void> {
|
|
|
95
97
|
--dry-run Verify, and stop before the site changes
|
|
96
98
|
--skip-validation Publish without running "val validate" first. It runs by
|
|
97
99
|
default, and an error stops the publish
|
|
100
|
+
--strict Let a validation warning stop the publish too
|
|
101
|
+
|
|
102
|
+
Command: check-build-tools
|
|
103
|
+
Description: load what "val publish" builds with, and build a one-line module with
|
|
104
|
+
it: whether this install could publish, without asking content anything. Val Build
|
|
105
|
+
runs it on an upgrade's install before a connected project can publish the upgrade.
|
|
106
|
+
Fails when it could not.
|
|
98
107
|
|
|
99
108
|
Command: list-unused-files
|
|
100
109
|
Description: EXPERIMENTAL.
|
|
@@ -190,6 +199,9 @@ async function main(): Promise<void> {
|
|
|
190
199
|
skipValidation: {
|
|
191
200
|
type: "boolean",
|
|
192
201
|
},
|
|
202
|
+
strict: {
|
|
203
|
+
type: "boolean",
|
|
204
|
+
},
|
|
193
205
|
yes: {
|
|
194
206
|
type: "boolean",
|
|
195
207
|
},
|
|
@@ -250,6 +262,8 @@ async function main(): Promise<void> {
|
|
|
250
262
|
yes: flags.yes,
|
|
251
263
|
verbose: flags.verbose,
|
|
252
264
|
});
|
|
265
|
+
case "check-build-tools":
|
|
266
|
+
return checkBuildTools();
|
|
253
267
|
case "ci-report":
|
|
254
268
|
return ciReport({
|
|
255
269
|
root: flags.root,
|
|
@@ -269,6 +283,7 @@ async function main(): Promise<void> {
|
|
|
269
283
|
linksOwnCss: flags.linksOwnCss,
|
|
270
284
|
dryRun: flags.dryRun,
|
|
271
285
|
skipValidation: flags.skipValidation,
|
|
286
|
+
strict: flags.strict,
|
|
272
287
|
});
|
|
273
288
|
case "login":
|
|
274
289
|
return login({
|
|
@@ -297,6 +312,7 @@ async function main(): Promise<void> {
|
|
|
297
312
|
root: flags.root,
|
|
298
313
|
fix: flags.fix,
|
|
299
314
|
watch: flags.watch,
|
|
315
|
+
strict: flags.strict,
|
|
300
316
|
});
|
|
301
317
|
default:
|
|
302
318
|
return error(`Unknown command "${input.join(" ")}"`);
|