@valbuild/cli 0.134.0 → 0.135.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 +46 -0
- package/cli/dist/valbuild-cli-cli.cjs.dev.js +89 -29
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +89 -29
- package/cli/dist/valbuild-cli-cli.esm.js +89 -29
- package/package.json +6 -4
- package/src/formatFixedFiles.test.ts +229 -0
- package/src/formatFixedFiles.ts +69 -0
- package/src/validate.ts +18 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @valbuild/cli
|
|
2
2
|
|
|
3
|
+
## 0.135.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#707](https://github.com/valbuild/val/pull/707) [`c9affec`](https://github.com/valbuild/val/commit/c9affece6ae6ba62bd6c1113ed1fdab6317fa112) Thanks [@freekh](https://github.com/freekh)! - `val validate --fix` now formats with your prettier config instead of prettier's defaults
|
|
8
|
+
|
|
9
|
+
`--fix` formatted the files it repaired by calling `prettier.format(code, { filepath })`, which never reads `.prettierrc`: `filepath` picks the parser and nothing else — only `resolveConfig`, `getFileInfo` and prettier's own CLI consult the config. On a project whose style is not prettier's default, a two-line content fix therefore arrived as a whole-file rewrite, and in a repo with a format check in CI it turned a content fix into a red build.
|
|
10
|
+
|
|
11
|
+
There is now one implementation of "format a written file the way this project does", `createPrettierFormatter`, exported from `@valbuild/server` and re-exported by `@valbuild/next/server` and `@valbuild/tanstack/server`. It resolves `.prettierrc` for each file (including any `overrides` that match it), leaves anything in `.prettierignore` untouched, and falls back to prettier's defaults when the project has no config. `val validate --fix` uses it, so the CLI and the Studio can no longer disagree about formatting.
|
|
12
|
+
|
|
13
|
+
Use it for your app's `formatter` too — this is the recommended setup, and it replaces reading `.prettierrc.json` by hand:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import prettier from "prettier";
|
|
17
|
+
import {
|
|
18
|
+
initValServer,
|
|
19
|
+
createPrettierFormatter,
|
|
20
|
+
} from "@valbuild/next/server";
|
|
21
|
+
|
|
22
|
+
const { valNextAppRouter } = initValServer(
|
|
23
|
+
valModules,
|
|
24
|
+
{ ...config },
|
|
25
|
+
{
|
|
26
|
+
draftMode,
|
|
27
|
+
formatter: createPrettierFormatter(prettier, {
|
|
28
|
+
projectRoot: process.cwd(),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Existing `formatter` callbacks keep working unchanged.
|
|
35
|
+
|
|
36
|
+
- Updated dependencies [[`c9affec`](https://github.com/valbuild/val/commit/c9affece6ae6ba62bd6c1113ed1fdab6317fa112), [`72a7ca7`](https://github.com/valbuild/val/commit/72a7ca78354fff00e37a51359b17fba9334dc5e6)]:
|
|
37
|
+
- @valbuild/server@0.135.0
|
|
38
|
+
- @valbuild/language-server@0.135.0
|
|
39
|
+
|
|
40
|
+
## 0.134.1
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- Updated dependencies [[`821e789`](https://github.com/valbuild/val/commit/821e789c1af47510af5d23eb96384ff78ddd7646)]:
|
|
45
|
+
- @valbuild/shared@0.134.1
|
|
46
|
+
- @valbuild/language-server@0.134.1
|
|
47
|
+
- @valbuild/server@0.134.1
|
|
48
|
+
|
|
3
49
|
## 0.134.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
|
@@ -4,12 +4,12 @@ var meow = require('meow');
|
|
|
4
4
|
var chalk = require('chalk');
|
|
5
5
|
var path = require('path');
|
|
6
6
|
var pc = require('picocolors');
|
|
7
|
-
var fs$1 = require('fs/promises');
|
|
8
7
|
var fastGlob = require('fast-glob');
|
|
9
8
|
var core = require('@valbuild/core');
|
|
10
9
|
var server = require('@valbuild/server');
|
|
10
|
+
var fs = require('fs/promises');
|
|
11
11
|
var internal = require('@valbuild/shared/internal');
|
|
12
|
-
var fs = require('fs');
|
|
12
|
+
var fs$1 = require('fs');
|
|
13
13
|
var ts = require('typescript');
|
|
14
14
|
var JSZip = require('jszip');
|
|
15
15
|
var readline = require('readline');
|
|
@@ -42,8 +42,8 @@ var meow__default = /*#__PURE__*/_interopDefault(meow);
|
|
|
42
42
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
43
43
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
44
44
|
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
45
|
-
var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
|
|
46
45
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
46
|
+
var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
|
|
47
47
|
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
48
48
|
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
49
49
|
var readline__default = /*#__PURE__*/_interopDefault(readline);
|
|
@@ -65,6 +65,62 @@ function info(message, opts = {}) {
|
|
|
65
65
|
console.log(chalk__default["default"].blue("️ℹ️ : ") + message);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** A file whose fix landed but which could not be formatted afterwards. */
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format the files `--fix` just wrote, the way the project formats everything.
|
|
72
|
+
*
|
|
73
|
+
* Through `createPrettierFormatter`, which is the same function an app hands to
|
|
74
|
+
* `initValServer` / `initValMcp`, so a fix applied here and an edit saved from
|
|
75
|
+
* the Studio come out identically formatted. Calling
|
|
76
|
+
* `prettier.format(code, { filepath })` directly — which this did — reads no
|
|
77
|
+
* config at all: `filepath` picks the parser, and `.prettierrc` is only ever
|
|
78
|
+
* consulted by `resolveConfig`, `getFileInfo` and prettier's own CLI. A project
|
|
79
|
+
* whose style is not prettier's default got its whole file rewritten by a
|
|
80
|
+
* two-line content fix, and a red `format` job with it.
|
|
81
|
+
*
|
|
82
|
+
* Separate from `validate.ts` so it can be tested against a real project on
|
|
83
|
+
* disk: `runValidation` stops before this step, and `validate()` itself ends in
|
|
84
|
+
* `process.exit`, so neither can reach it from a test.
|
|
85
|
+
*
|
|
86
|
+
* Rendering is the caller's, which is why failures are returned rather than
|
|
87
|
+
* logged. A file that cannot be formatted is not a failed run: the fix itself
|
|
88
|
+
* landed, and stopping here would leave the fix applied and the report unwritten.
|
|
89
|
+
*/
|
|
90
|
+
async function formatFixedFiles(prettier, projectRoot, files) {
|
|
91
|
+
const format = server.createPrettierFormatter(prettier, {
|
|
92
|
+
projectRoot
|
|
93
|
+
});
|
|
94
|
+
const written = [];
|
|
95
|
+
const failures = [];
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const filePath = path__default["default"].join(projectRoot, file);
|
|
98
|
+
const fileContent = await fs__default["default"].readFile(filePath, "utf-8");
|
|
99
|
+
let formattedContent;
|
|
100
|
+
try {
|
|
101
|
+
formattedContent = await format(fileContent, file);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
failures.push({
|
|
104
|
+
file,
|
|
105
|
+
message: err instanceof Error ? err.message : String(err)
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// A file prettier left alone — one the project put in `.prettierignore`,
|
|
110
|
+
// or one already in its final form — is not rewritten. Writing identical
|
|
111
|
+
// bytes still bumps the mtime, and a `--fix` run alongside a dev server
|
|
112
|
+
// makes it rebuild for nothing.
|
|
113
|
+
if (formattedContent !== fileContent) {
|
|
114
|
+
await fs__default["default"].writeFile(filePath, formattedContent);
|
|
115
|
+
written.push(file);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
written,
|
|
120
|
+
failures
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
async function* runValidation({
|
|
69
125
|
root,
|
|
70
126
|
fix,
|
|
@@ -560,7 +616,7 @@ function getCachedJsonFile(jsonFilePath, projectRoot, cache) {
|
|
|
560
616
|
const filePath = path__default["default"].join(projectRoot, jsonFilePath);
|
|
561
617
|
let fileContent;
|
|
562
618
|
try {
|
|
563
|
-
fileContent = fs__default["default"].readFileSync(filePath, "utf-8");
|
|
619
|
+
fileContent = fs__default$1["default"].readFileSync(filePath, "utf-8");
|
|
564
620
|
} catch {
|
|
565
621
|
cache.set(jsonFilePath, null);
|
|
566
622
|
return null;
|
|
@@ -580,7 +636,7 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
580
636
|
const filePath = path__default["default"].join(projectRoot, moduleFilePath);
|
|
581
637
|
let fileContent;
|
|
582
638
|
try {
|
|
583
|
-
fileContent = fs__default["default"].readFileSync(filePath, "utf-8");
|
|
639
|
+
fileContent = fs__default$1["default"].readFileSync(filePath, "utf-8");
|
|
584
640
|
} catch {
|
|
585
641
|
cache.set(moduleFilePath, null);
|
|
586
642
|
return null;
|
|
@@ -788,15 +844,19 @@ async function validate({
|
|
|
788
844
|
console.log("");
|
|
789
845
|
};
|
|
790
846
|
|
|
791
|
-
//
|
|
847
|
+
// Format the files that had fixes applied, the way the project formats
|
|
848
|
+
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
849
|
+
// `prettier.format(code, { filepath })`.
|
|
850
|
+
//
|
|
851
|
+
// A file that could not be formatted is reported and skipped rather than
|
|
852
|
+
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
853
|
+
// applied and the report below unwritten.
|
|
792
854
|
if (prettier) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
});
|
|
799
|
-
await fs__default$1["default"].writeFile(filePath, formattedContent);
|
|
855
|
+
const {
|
|
856
|
+
failures
|
|
857
|
+
} = await formatFixedFiles(prettier, projectRoot, fixedFiles);
|
|
858
|
+
for (const failure of failures) {
|
|
859
|
+
console.log(pc__default["default"].yellow("⚠"), `Could not format ${relFile(failure.file)}: ${failure.message}`);
|
|
800
860
|
}
|
|
801
861
|
}
|
|
802
862
|
|
|
@@ -1084,8 +1144,8 @@ function tryGetGitConfig(root) {
|
|
|
1084
1144
|
let lastDir = null;
|
|
1085
1145
|
while (currentDir !== lastDir) {
|
|
1086
1146
|
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
1087
|
-
if (fs__default["default"].existsSync(gitConfigPath)) {
|
|
1088
|
-
return fs__default["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1147
|
+
if (fs__default$1["default"].existsSync(gitConfigPath)) {
|
|
1148
|
+
return fs__default$1["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1089
1149
|
}
|
|
1090
1150
|
lastDir = currentDir;
|
|
1091
1151
|
currentDir = path__default["default"].dirname(currentDir);
|
|
@@ -1162,7 +1222,7 @@ class DebugContextError extends Error {}
|
|
|
1162
1222
|
async function createDebugContext(options) {
|
|
1163
1223
|
var _config$files;
|
|
1164
1224
|
const projectRoot = options.root ? path__default["default"].resolve(options.root) : process.cwd();
|
|
1165
|
-
if (!fs__default["default"].existsSync(projectRoot)) {
|
|
1225
|
+
if (!fs__default$1["default"].existsSync(projectRoot)) {
|
|
1166
1226
|
throw new DebugContextError(`Project root does not exist: ${projectRoot}`);
|
|
1167
1227
|
}
|
|
1168
1228
|
const config = await server.findAndEvalValConfigFile(projectRoot);
|
|
@@ -1235,8 +1295,8 @@ async function createDebugContext(options) {
|
|
|
1235
1295
|
*/
|
|
1236
1296
|
function readAuth(projectRoot) {
|
|
1237
1297
|
const patFile = server.getPersonalAccessTokenPath(projectRoot);
|
|
1238
|
-
if (fs__default["default"].existsSync(patFile)) {
|
|
1239
|
-
const contents = fs__default["default"].readFileSync(patFile, "utf-8");
|
|
1298
|
+
if (fs__default$1["default"].existsSync(patFile)) {
|
|
1299
|
+
const contents = fs__default$1["default"].readFileSync(patFile, "utf-8");
|
|
1240
1300
|
const parsed = server.parsePersonalAccessTokenFile(contents);
|
|
1241
1301
|
if (parsed.success) {
|
|
1242
1302
|
return {
|
|
@@ -1787,7 +1847,7 @@ function readProjectValVersions(projectRoot) {
|
|
|
1787
1847
|
function readLocalFile(projectRoot, projectRelativePath) {
|
|
1788
1848
|
const absPath = path__default["default"].join(projectRoot, projectRelativePath);
|
|
1789
1849
|
try {
|
|
1790
|
-
return fs__default["default"].readFileSync(absPath, "utf-8");
|
|
1850
|
+
return fs__default$1["default"].readFileSync(absPath, "utf-8");
|
|
1791
1851
|
} catch {
|
|
1792
1852
|
return null;
|
|
1793
1853
|
}
|
|
@@ -1929,10 +1989,10 @@ async function writeZip(outPath, entries) {
|
|
|
1929
1989
|
type: "nodebuffer",
|
|
1930
1990
|
compression: "DEFLATE"
|
|
1931
1991
|
});
|
|
1932
|
-
fs__default["default"].mkdirSync(path__default["default"].dirname(outPath), {
|
|
1992
|
+
fs__default$1["default"].mkdirSync(path__default["default"].dirname(outPath), {
|
|
1933
1993
|
recursive: true
|
|
1934
1994
|
});
|
|
1935
|
-
fs__default["default"].writeFileSync(outPath, buffer);
|
|
1995
|
+
fs__default$1["default"].writeFileSync(outPath, buffer);
|
|
1936
1996
|
}
|
|
1937
1997
|
function timestamp() {
|
|
1938
1998
|
return new Date().toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -2081,7 +2141,7 @@ async function collectArtifacts(dir) {
|
|
|
2081
2141
|
const skipped = [];
|
|
2082
2142
|
let totalBytes = 0;
|
|
2083
2143
|
const walk = async current => {
|
|
2084
|
-
const entries = await fs__default["default"].promises.readdir(current, {
|
|
2144
|
+
const entries = await fs__default$1["default"].promises.readdir(current, {
|
|
2085
2145
|
withFileTypes: true
|
|
2086
2146
|
});
|
|
2087
2147
|
for (const entry of entries) {
|
|
@@ -2095,7 +2155,7 @@ async function collectArtifacts(dir) {
|
|
|
2095
2155
|
// A symlinked file is published as its bytes; a symlinked directory is
|
|
2096
2156
|
// not walked, because one usually comes with a cycle, and a publisher
|
|
2097
2157
|
// that hangs is worse than one that says what it left out.
|
|
2098
|
-
const stat = await fs__default["default"].promises.stat(absolute).catch(() => null);
|
|
2158
|
+
const stat = await fs__default$1["default"].promises.stat(absolute).catch(() => null);
|
|
2099
2159
|
if (stat === null) {
|
|
2100
2160
|
skipped.push(`${key} (broken symlink)`);
|
|
2101
2161
|
continue;
|
|
@@ -2136,7 +2196,7 @@ async function hashFile(absolute) {
|
|
|
2136
2196
|
let bytes = 0;
|
|
2137
2197
|
// Streamed: an artifact may be 128 MB, and reading one into a Buffer to hash
|
|
2138
2198
|
// it is a needless way to run a CI runner out of memory.
|
|
2139
|
-
const stream = fs__default["default"].createReadStream(absolute);
|
|
2199
|
+
const stream = fs__default$1["default"].createReadStream(absolute);
|
|
2140
2200
|
await new Promise((resolve, reject) => {
|
|
2141
2201
|
stream.on("data", chunk => {
|
|
2142
2202
|
hash.update(chunk);
|
|
@@ -2186,7 +2246,7 @@ async function layerRevOf(artifacts) {
|
|
|
2186
2246
|
layerRev: null
|
|
2187
2247
|
};
|
|
2188
2248
|
}
|
|
2189
|
-
const bytes = await fs__default["default"].promises.readFile(layer.file);
|
|
2249
|
+
const bytes = await fs__default$1["default"].promises.readFile(layer.file);
|
|
2190
2250
|
let parsed;
|
|
2191
2251
|
try {
|
|
2192
2252
|
parsed = JSON.parse(zlib__default["default"].gunzipSync(bytes).toString("utf-8"));
|
|
@@ -2214,7 +2274,7 @@ const DEFAULT_ARTIFACTS_DIR = ".val/publish";
|
|
|
2214
2274
|
function resolveArtifactsDir(options) {
|
|
2215
2275
|
const named = options.dir ?? DEFAULT_ARTIFACTS_DIR;
|
|
2216
2276
|
const dir = path__default["default"].resolve(options.root, named);
|
|
2217
|
-
if (!fs__default["default"].existsSync(dir) || !fs__default["default"].statSync(dir).isDirectory()) {
|
|
2277
|
+
if (!fs__default$1["default"].existsSync(dir) || !fs__default$1["default"].statSync(dir).isDirectory()) {
|
|
2218
2278
|
return {
|
|
2219
2279
|
status: "error",
|
|
2220
2280
|
message: `No artifacts to publish: ${dir} is not a directory.\n\n` + "Build the project first, then point at what it produced:\n\n" + " npx val publish --artifacts <directory>\n\n" + "The path of each file under it is its artifact key - `server`, `client`,\n" + "`public/...`, `chunk/client/...`, `layer`."
|
|
@@ -2554,7 +2614,7 @@ async function putArtifact(slot, file, fetchImpl) {
|
|
|
2554
2614
|
* different size as far as the signature is concerned. An artifact is capped
|
|
2555
2615
|
* at 128 MB by the API, which is the bound this trades against.
|
|
2556
2616
|
*/
|
|
2557
|
-
const body = await fs__default["default"].promises.readFile(file);
|
|
2617
|
+
const body = await fs__default$1["default"].promises.readFile(file);
|
|
2558
2618
|
let res;
|
|
2559
2619
|
try {
|
|
2560
2620
|
res = await fetchImpl(slot.url, {
|
|
@@ -2787,12 +2847,12 @@ function looksLikePersonalAccessToken(token) {
|
|
|
2787
2847
|
}
|
|
2788
2848
|
function readPersonalAccessToken(root) {
|
|
2789
2849
|
const patFile = server.getPersonalAccessTokenPath(root);
|
|
2790
|
-
if (!fs__default["default"].existsSync(patFile)) {
|
|
2850
|
+
if (!fs__default$1["default"].existsSync(patFile)) {
|
|
2791
2851
|
return {
|
|
2792
2852
|
status: "none"
|
|
2793
2853
|
};
|
|
2794
2854
|
}
|
|
2795
|
-
const parsed = server.parsePersonalAccessTokenFile(fs__default["default"].readFileSync(patFile, "utf-8"));
|
|
2855
|
+
const parsed = server.parsePersonalAccessTokenFile(fs__default$1["default"].readFileSync(patFile, "utf-8"));
|
|
2796
2856
|
if (!parsed.success) {
|
|
2797
2857
|
return {
|
|
2798
2858
|
status: "error",
|
|
@@ -4,12 +4,12 @@ var meow = require('meow');
|
|
|
4
4
|
var chalk = require('chalk');
|
|
5
5
|
var path = require('path');
|
|
6
6
|
var pc = require('picocolors');
|
|
7
|
-
var fs$1 = require('fs/promises');
|
|
8
7
|
var fastGlob = require('fast-glob');
|
|
9
8
|
var core = require('@valbuild/core');
|
|
10
9
|
var server = require('@valbuild/server');
|
|
10
|
+
var fs = require('fs/promises');
|
|
11
11
|
var internal = require('@valbuild/shared/internal');
|
|
12
|
-
var fs = require('fs');
|
|
12
|
+
var fs$1 = require('fs');
|
|
13
13
|
var ts = require('typescript');
|
|
14
14
|
var JSZip = require('jszip');
|
|
15
15
|
var readline = require('readline');
|
|
@@ -42,8 +42,8 @@ var meow__default = /*#__PURE__*/_interopDefault(meow);
|
|
|
42
42
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
43
43
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
44
44
|
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
45
|
-
var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
|
|
46
45
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
46
|
+
var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
|
|
47
47
|
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
48
48
|
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
49
49
|
var readline__default = /*#__PURE__*/_interopDefault(readline);
|
|
@@ -65,6 +65,62 @@ function info(message, opts = {}) {
|
|
|
65
65
|
console.log(chalk__default["default"].blue("️ℹ️ : ") + message);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** A file whose fix landed but which could not be formatted afterwards. */
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format the files `--fix` just wrote, the way the project formats everything.
|
|
72
|
+
*
|
|
73
|
+
* Through `createPrettierFormatter`, which is the same function an app hands to
|
|
74
|
+
* `initValServer` / `initValMcp`, so a fix applied here and an edit saved from
|
|
75
|
+
* the Studio come out identically formatted. Calling
|
|
76
|
+
* `prettier.format(code, { filepath })` directly — which this did — reads no
|
|
77
|
+
* config at all: `filepath` picks the parser, and `.prettierrc` is only ever
|
|
78
|
+
* consulted by `resolveConfig`, `getFileInfo` and prettier's own CLI. A project
|
|
79
|
+
* whose style is not prettier's default got its whole file rewritten by a
|
|
80
|
+
* two-line content fix, and a red `format` job with it.
|
|
81
|
+
*
|
|
82
|
+
* Separate from `validate.ts` so it can be tested against a real project on
|
|
83
|
+
* disk: `runValidation` stops before this step, and `validate()` itself ends in
|
|
84
|
+
* `process.exit`, so neither can reach it from a test.
|
|
85
|
+
*
|
|
86
|
+
* Rendering is the caller's, which is why failures are returned rather than
|
|
87
|
+
* logged. A file that cannot be formatted is not a failed run: the fix itself
|
|
88
|
+
* landed, and stopping here would leave the fix applied and the report unwritten.
|
|
89
|
+
*/
|
|
90
|
+
async function formatFixedFiles(prettier, projectRoot, files) {
|
|
91
|
+
const format = server.createPrettierFormatter(prettier, {
|
|
92
|
+
projectRoot
|
|
93
|
+
});
|
|
94
|
+
const written = [];
|
|
95
|
+
const failures = [];
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const filePath = path__default["default"].join(projectRoot, file);
|
|
98
|
+
const fileContent = await fs__default["default"].readFile(filePath, "utf-8");
|
|
99
|
+
let formattedContent;
|
|
100
|
+
try {
|
|
101
|
+
formattedContent = await format(fileContent, file);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
failures.push({
|
|
104
|
+
file,
|
|
105
|
+
message: err instanceof Error ? err.message : String(err)
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// A file prettier left alone — one the project put in `.prettierignore`,
|
|
110
|
+
// or one already in its final form — is not rewritten. Writing identical
|
|
111
|
+
// bytes still bumps the mtime, and a `--fix` run alongside a dev server
|
|
112
|
+
// makes it rebuild for nothing.
|
|
113
|
+
if (formattedContent !== fileContent) {
|
|
114
|
+
await fs__default["default"].writeFile(filePath, formattedContent);
|
|
115
|
+
written.push(file);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
written,
|
|
120
|
+
failures
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
async function* runValidation({
|
|
69
125
|
root,
|
|
70
126
|
fix,
|
|
@@ -560,7 +616,7 @@ function getCachedJsonFile(jsonFilePath, projectRoot, cache) {
|
|
|
560
616
|
const filePath = path__default["default"].join(projectRoot, jsonFilePath);
|
|
561
617
|
let fileContent;
|
|
562
618
|
try {
|
|
563
|
-
fileContent = fs__default["default"].readFileSync(filePath, "utf-8");
|
|
619
|
+
fileContent = fs__default$1["default"].readFileSync(filePath, "utf-8");
|
|
564
620
|
} catch {
|
|
565
621
|
cache.set(jsonFilePath, null);
|
|
566
622
|
return null;
|
|
@@ -580,7 +636,7 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
580
636
|
const filePath = path__default["default"].join(projectRoot, moduleFilePath);
|
|
581
637
|
let fileContent;
|
|
582
638
|
try {
|
|
583
|
-
fileContent = fs__default["default"].readFileSync(filePath, "utf-8");
|
|
639
|
+
fileContent = fs__default$1["default"].readFileSync(filePath, "utf-8");
|
|
584
640
|
} catch {
|
|
585
641
|
cache.set(moduleFilePath, null);
|
|
586
642
|
return null;
|
|
@@ -788,15 +844,19 @@ async function validate({
|
|
|
788
844
|
console.log("");
|
|
789
845
|
};
|
|
790
846
|
|
|
791
|
-
//
|
|
847
|
+
// Format the files that had fixes applied, the way the project formats
|
|
848
|
+
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
849
|
+
// `prettier.format(code, { filepath })`.
|
|
850
|
+
//
|
|
851
|
+
// A file that could not be formatted is reported and skipped rather than
|
|
852
|
+
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
853
|
+
// applied and the report below unwritten.
|
|
792
854
|
if (prettier) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
});
|
|
799
|
-
await fs__default$1["default"].writeFile(filePath, formattedContent);
|
|
855
|
+
const {
|
|
856
|
+
failures
|
|
857
|
+
} = await formatFixedFiles(prettier, projectRoot, fixedFiles);
|
|
858
|
+
for (const failure of failures) {
|
|
859
|
+
console.log(pc__default["default"].yellow("⚠"), `Could not format ${relFile(failure.file)}: ${failure.message}`);
|
|
800
860
|
}
|
|
801
861
|
}
|
|
802
862
|
|
|
@@ -1084,8 +1144,8 @@ function tryGetGitConfig(root) {
|
|
|
1084
1144
|
let lastDir = null;
|
|
1085
1145
|
while (currentDir !== lastDir) {
|
|
1086
1146
|
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
1087
|
-
if (fs__default["default"].existsSync(gitConfigPath)) {
|
|
1088
|
-
return fs__default["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1147
|
+
if (fs__default$1["default"].existsSync(gitConfigPath)) {
|
|
1148
|
+
return fs__default$1["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1089
1149
|
}
|
|
1090
1150
|
lastDir = currentDir;
|
|
1091
1151
|
currentDir = path__default["default"].dirname(currentDir);
|
|
@@ -1162,7 +1222,7 @@ class DebugContextError extends Error {}
|
|
|
1162
1222
|
async function createDebugContext(options) {
|
|
1163
1223
|
var _config$files;
|
|
1164
1224
|
const projectRoot = options.root ? path__default["default"].resolve(options.root) : process.cwd();
|
|
1165
|
-
if (!fs__default["default"].existsSync(projectRoot)) {
|
|
1225
|
+
if (!fs__default$1["default"].existsSync(projectRoot)) {
|
|
1166
1226
|
throw new DebugContextError(`Project root does not exist: ${projectRoot}`);
|
|
1167
1227
|
}
|
|
1168
1228
|
const config = await server.findAndEvalValConfigFile(projectRoot);
|
|
@@ -1235,8 +1295,8 @@ async function createDebugContext(options) {
|
|
|
1235
1295
|
*/
|
|
1236
1296
|
function readAuth(projectRoot) {
|
|
1237
1297
|
const patFile = server.getPersonalAccessTokenPath(projectRoot);
|
|
1238
|
-
if (fs__default["default"].existsSync(patFile)) {
|
|
1239
|
-
const contents = fs__default["default"].readFileSync(patFile, "utf-8");
|
|
1298
|
+
if (fs__default$1["default"].existsSync(patFile)) {
|
|
1299
|
+
const contents = fs__default$1["default"].readFileSync(patFile, "utf-8");
|
|
1240
1300
|
const parsed = server.parsePersonalAccessTokenFile(contents);
|
|
1241
1301
|
if (parsed.success) {
|
|
1242
1302
|
return {
|
|
@@ -1787,7 +1847,7 @@ function readProjectValVersions(projectRoot) {
|
|
|
1787
1847
|
function readLocalFile(projectRoot, projectRelativePath) {
|
|
1788
1848
|
const absPath = path__default["default"].join(projectRoot, projectRelativePath);
|
|
1789
1849
|
try {
|
|
1790
|
-
return fs__default["default"].readFileSync(absPath, "utf-8");
|
|
1850
|
+
return fs__default$1["default"].readFileSync(absPath, "utf-8");
|
|
1791
1851
|
} catch {
|
|
1792
1852
|
return null;
|
|
1793
1853
|
}
|
|
@@ -1929,10 +1989,10 @@ async function writeZip(outPath, entries) {
|
|
|
1929
1989
|
type: "nodebuffer",
|
|
1930
1990
|
compression: "DEFLATE"
|
|
1931
1991
|
});
|
|
1932
|
-
fs__default["default"].mkdirSync(path__default["default"].dirname(outPath), {
|
|
1992
|
+
fs__default$1["default"].mkdirSync(path__default["default"].dirname(outPath), {
|
|
1933
1993
|
recursive: true
|
|
1934
1994
|
});
|
|
1935
|
-
fs__default["default"].writeFileSync(outPath, buffer);
|
|
1995
|
+
fs__default$1["default"].writeFileSync(outPath, buffer);
|
|
1936
1996
|
}
|
|
1937
1997
|
function timestamp() {
|
|
1938
1998
|
return new Date().toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -2081,7 +2141,7 @@ async function collectArtifacts(dir) {
|
|
|
2081
2141
|
const skipped = [];
|
|
2082
2142
|
let totalBytes = 0;
|
|
2083
2143
|
const walk = async current => {
|
|
2084
|
-
const entries = await fs__default["default"].promises.readdir(current, {
|
|
2144
|
+
const entries = await fs__default$1["default"].promises.readdir(current, {
|
|
2085
2145
|
withFileTypes: true
|
|
2086
2146
|
});
|
|
2087
2147
|
for (const entry of entries) {
|
|
@@ -2095,7 +2155,7 @@ async function collectArtifacts(dir) {
|
|
|
2095
2155
|
// A symlinked file is published as its bytes; a symlinked directory is
|
|
2096
2156
|
// not walked, because one usually comes with a cycle, and a publisher
|
|
2097
2157
|
// that hangs is worse than one that says what it left out.
|
|
2098
|
-
const stat = await fs__default["default"].promises.stat(absolute).catch(() => null);
|
|
2158
|
+
const stat = await fs__default$1["default"].promises.stat(absolute).catch(() => null);
|
|
2099
2159
|
if (stat === null) {
|
|
2100
2160
|
skipped.push(`${key} (broken symlink)`);
|
|
2101
2161
|
continue;
|
|
@@ -2136,7 +2196,7 @@ async function hashFile(absolute) {
|
|
|
2136
2196
|
let bytes = 0;
|
|
2137
2197
|
// Streamed: an artifact may be 128 MB, and reading one into a Buffer to hash
|
|
2138
2198
|
// it is a needless way to run a CI runner out of memory.
|
|
2139
|
-
const stream = fs__default["default"].createReadStream(absolute);
|
|
2199
|
+
const stream = fs__default$1["default"].createReadStream(absolute);
|
|
2140
2200
|
await new Promise((resolve, reject) => {
|
|
2141
2201
|
stream.on("data", chunk => {
|
|
2142
2202
|
hash.update(chunk);
|
|
@@ -2186,7 +2246,7 @@ async function layerRevOf(artifacts) {
|
|
|
2186
2246
|
layerRev: null
|
|
2187
2247
|
};
|
|
2188
2248
|
}
|
|
2189
|
-
const bytes = await fs__default["default"].promises.readFile(layer.file);
|
|
2249
|
+
const bytes = await fs__default$1["default"].promises.readFile(layer.file);
|
|
2190
2250
|
let parsed;
|
|
2191
2251
|
try {
|
|
2192
2252
|
parsed = JSON.parse(zlib__default["default"].gunzipSync(bytes).toString("utf-8"));
|
|
@@ -2214,7 +2274,7 @@ const DEFAULT_ARTIFACTS_DIR = ".val/publish";
|
|
|
2214
2274
|
function resolveArtifactsDir(options) {
|
|
2215
2275
|
const named = options.dir ?? DEFAULT_ARTIFACTS_DIR;
|
|
2216
2276
|
const dir = path__default["default"].resolve(options.root, named);
|
|
2217
|
-
if (!fs__default["default"].existsSync(dir) || !fs__default["default"].statSync(dir).isDirectory()) {
|
|
2277
|
+
if (!fs__default$1["default"].existsSync(dir) || !fs__default$1["default"].statSync(dir).isDirectory()) {
|
|
2218
2278
|
return {
|
|
2219
2279
|
status: "error",
|
|
2220
2280
|
message: `No artifacts to publish: ${dir} is not a directory.\n\n` + "Build the project first, then point at what it produced:\n\n" + " npx val publish --artifacts <directory>\n\n" + "The path of each file under it is its artifact key - `server`, `client`,\n" + "`public/...`, `chunk/client/...`, `layer`."
|
|
@@ -2554,7 +2614,7 @@ async function putArtifact(slot, file, fetchImpl) {
|
|
|
2554
2614
|
* different size as far as the signature is concerned. An artifact is capped
|
|
2555
2615
|
* at 128 MB by the API, which is the bound this trades against.
|
|
2556
2616
|
*/
|
|
2557
|
-
const body = await fs__default["default"].promises.readFile(file);
|
|
2617
|
+
const body = await fs__default$1["default"].promises.readFile(file);
|
|
2558
2618
|
let res;
|
|
2559
2619
|
try {
|
|
2560
2620
|
res = await fetchImpl(slot.url, {
|
|
@@ -2787,12 +2847,12 @@ function looksLikePersonalAccessToken(token) {
|
|
|
2787
2847
|
}
|
|
2788
2848
|
function readPersonalAccessToken(root) {
|
|
2789
2849
|
const patFile = server.getPersonalAccessTokenPath(root);
|
|
2790
|
-
if (!fs__default["default"].existsSync(patFile)) {
|
|
2850
|
+
if (!fs__default$1["default"].existsSync(patFile)) {
|
|
2791
2851
|
return {
|
|
2792
2852
|
status: "none"
|
|
2793
2853
|
};
|
|
2794
2854
|
}
|
|
2795
|
-
const parsed = server.parsePersonalAccessTokenFile(fs__default["default"].readFileSync(patFile, "utf-8"));
|
|
2855
|
+
const parsed = server.parsePersonalAccessTokenFile(fs__default$1["default"].readFileSync(patFile, "utf-8"));
|
|
2796
2856
|
if (!parsed.success) {
|
|
2797
2857
|
return {
|
|
2798
2858
|
status: "error",
|
|
@@ -2,12 +2,12 @@ import meow from 'meow';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import pc from 'picocolors';
|
|
5
|
-
import fs$1 from 'fs/promises';
|
|
6
5
|
import { glob } from 'fast-glob';
|
|
7
6
|
import { Internal, DEFAULT_VAL_REMOTE_HOST, DEFAULT_CONTENT_HOST } from '@valbuild/core';
|
|
8
|
-
import { createService, createValModuleFileInspector, fixHandlers, createFixPatch, getModulePathRange, findJsonEntryFilePath, createJsonEntryPathMap, createModulePathMap, findAndEvalValConfigFile, getSettings, uploadRemoteFile, createDefaultValFSHost, startValLogin, awaitValLoginConfirmation, persistPersonalAccessToken, ValLoginError, loadValModules, safeReadGit, ValOpsHttp, ValOpsFS, getPersonalAccessTokenPath, parsePersonalAccessTokenFile, formatPatchSourceError } from '@valbuild/server';
|
|
7
|
+
import { createPrettierFormatter, createService, createValModuleFileInspector, fixHandlers, createFixPatch, getModulePathRange, findJsonEntryFilePath, createJsonEntryPathMap, createModulePathMap, findAndEvalValConfigFile, getSettings, uploadRemoteFile, createDefaultValFSHost, startValLogin, awaitValLoginConfirmation, persistPersonalAccessToken, ValLoginError, loadValModules, safeReadGit, ValOpsHttp, ValOpsFS, getPersonalAccessTokenPath, parsePersonalAccessTokenFile, formatPatchSourceError } from '@valbuild/server';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
9
|
import { resolveSchemaSourceFixes } from '@valbuild/shared/internal';
|
|
10
|
-
import fs from 'fs';
|
|
10
|
+
import fs$1 from 'fs';
|
|
11
11
|
import ts from 'typescript';
|
|
12
12
|
import JSZip from 'jszip';
|
|
13
13
|
import readline from 'readline';
|
|
@@ -31,6 +31,62 @@ function info(message, opts = {}) {
|
|
|
31
31
|
console.log(chalk.blue("️ℹ️ : ") + message);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** A file whose fix landed but which could not be formatted afterwards. */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format the files `--fix` just wrote, the way the project formats everything.
|
|
38
|
+
*
|
|
39
|
+
* Through `createPrettierFormatter`, which is the same function an app hands to
|
|
40
|
+
* `initValServer` / `initValMcp`, so a fix applied here and an edit saved from
|
|
41
|
+
* the Studio come out identically formatted. Calling
|
|
42
|
+
* `prettier.format(code, { filepath })` directly — which this did — reads no
|
|
43
|
+
* config at all: `filepath` picks the parser, and `.prettierrc` is only ever
|
|
44
|
+
* consulted by `resolveConfig`, `getFileInfo` and prettier's own CLI. A project
|
|
45
|
+
* whose style is not prettier's default got its whole file rewritten by a
|
|
46
|
+
* two-line content fix, and a red `format` job with it.
|
|
47
|
+
*
|
|
48
|
+
* Separate from `validate.ts` so it can be tested against a real project on
|
|
49
|
+
* disk: `runValidation` stops before this step, and `validate()` itself ends in
|
|
50
|
+
* `process.exit`, so neither can reach it from a test.
|
|
51
|
+
*
|
|
52
|
+
* Rendering is the caller's, which is why failures are returned rather than
|
|
53
|
+
* logged. A file that cannot be formatted is not a failed run: the fix itself
|
|
54
|
+
* landed, and stopping here would leave the fix applied and the report unwritten.
|
|
55
|
+
*/
|
|
56
|
+
async function formatFixedFiles(prettier, projectRoot, files) {
|
|
57
|
+
const format = createPrettierFormatter(prettier, {
|
|
58
|
+
projectRoot
|
|
59
|
+
});
|
|
60
|
+
const written = [];
|
|
61
|
+
const failures = [];
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const filePath = path.join(projectRoot, file);
|
|
64
|
+
const fileContent = await fs.readFile(filePath, "utf-8");
|
|
65
|
+
let formattedContent;
|
|
66
|
+
try {
|
|
67
|
+
formattedContent = await format(fileContent, file);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
failures.push({
|
|
70
|
+
file,
|
|
71
|
+
message: err instanceof Error ? err.message : String(err)
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// A file prettier left alone — one the project put in `.prettierignore`,
|
|
76
|
+
// or one already in its final form — is not rewritten. Writing identical
|
|
77
|
+
// bytes still bumps the mtime, and a `--fix` run alongside a dev server
|
|
78
|
+
// makes it rebuild for nothing.
|
|
79
|
+
if (formattedContent !== fileContent) {
|
|
80
|
+
await fs.writeFile(filePath, formattedContent);
|
|
81
|
+
written.push(file);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
written,
|
|
86
|
+
failures
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
34
90
|
async function* runValidation({
|
|
35
91
|
root,
|
|
36
92
|
fix,
|
|
@@ -526,7 +582,7 @@ function getCachedJsonFile(jsonFilePath, projectRoot, cache) {
|
|
|
526
582
|
const filePath = path.join(projectRoot, jsonFilePath);
|
|
527
583
|
let fileContent;
|
|
528
584
|
try {
|
|
529
|
-
fileContent = fs.readFileSync(filePath, "utf-8");
|
|
585
|
+
fileContent = fs$1.readFileSync(filePath, "utf-8");
|
|
530
586
|
} catch {
|
|
531
587
|
cache.set(jsonFilePath, null);
|
|
532
588
|
return null;
|
|
@@ -546,7 +602,7 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
546
602
|
const filePath = path.join(projectRoot, moduleFilePath);
|
|
547
603
|
let fileContent;
|
|
548
604
|
try {
|
|
549
|
-
fileContent = fs.readFileSync(filePath, "utf-8");
|
|
605
|
+
fileContent = fs$1.readFileSync(filePath, "utf-8");
|
|
550
606
|
} catch {
|
|
551
607
|
cache.set(moduleFilePath, null);
|
|
552
608
|
return null;
|
|
@@ -754,15 +810,19 @@ async function validate({
|
|
|
754
810
|
console.log("");
|
|
755
811
|
};
|
|
756
812
|
|
|
757
|
-
//
|
|
813
|
+
// Format the files that had fixes applied, the way the project formats
|
|
814
|
+
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
815
|
+
// `prettier.format(code, { filepath })`.
|
|
816
|
+
//
|
|
817
|
+
// A file that could not be formatted is reported and skipped rather than
|
|
818
|
+
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
819
|
+
// applied and the report below unwritten.
|
|
758
820
|
if (prettier) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
});
|
|
765
|
-
await fs$1.writeFile(filePath, formattedContent);
|
|
821
|
+
const {
|
|
822
|
+
failures
|
|
823
|
+
} = await formatFixedFiles(prettier, projectRoot, fixedFiles);
|
|
824
|
+
for (const failure of failures) {
|
|
825
|
+
console.log(pc.yellow("⚠"), `Could not format ${relFile(failure.file)}: ${failure.message}`);
|
|
766
826
|
}
|
|
767
827
|
}
|
|
768
828
|
|
|
@@ -1050,8 +1110,8 @@ function tryGetGitConfig(root) {
|
|
|
1050
1110
|
let lastDir = null;
|
|
1051
1111
|
while (currentDir !== lastDir) {
|
|
1052
1112
|
const gitConfigPath = path.join(currentDir, ".git", "config");
|
|
1053
|
-
if (fs.existsSync(gitConfigPath)) {
|
|
1054
|
-
return fs.readFileSync(gitConfigPath, "utf-8");
|
|
1113
|
+
if (fs$1.existsSync(gitConfigPath)) {
|
|
1114
|
+
return fs$1.readFileSync(gitConfigPath, "utf-8");
|
|
1055
1115
|
}
|
|
1056
1116
|
lastDir = currentDir;
|
|
1057
1117
|
currentDir = path.dirname(currentDir);
|
|
@@ -1128,7 +1188,7 @@ class DebugContextError extends Error {}
|
|
|
1128
1188
|
async function createDebugContext(options) {
|
|
1129
1189
|
var _config$files;
|
|
1130
1190
|
const projectRoot = options.root ? path.resolve(options.root) : process.cwd();
|
|
1131
|
-
if (!fs.existsSync(projectRoot)) {
|
|
1191
|
+
if (!fs$1.existsSync(projectRoot)) {
|
|
1132
1192
|
throw new DebugContextError(`Project root does not exist: ${projectRoot}`);
|
|
1133
1193
|
}
|
|
1134
1194
|
const config = await findAndEvalValConfigFile(projectRoot);
|
|
@@ -1201,8 +1261,8 @@ async function createDebugContext(options) {
|
|
|
1201
1261
|
*/
|
|
1202
1262
|
function readAuth(projectRoot) {
|
|
1203
1263
|
const patFile = getPersonalAccessTokenPath(projectRoot);
|
|
1204
|
-
if (fs.existsSync(patFile)) {
|
|
1205
|
-
const contents = fs.readFileSync(patFile, "utf-8");
|
|
1264
|
+
if (fs$1.existsSync(patFile)) {
|
|
1265
|
+
const contents = fs$1.readFileSync(patFile, "utf-8");
|
|
1206
1266
|
const parsed = parsePersonalAccessTokenFile(contents);
|
|
1207
1267
|
if (parsed.success) {
|
|
1208
1268
|
return {
|
|
@@ -1753,7 +1813,7 @@ function readProjectValVersions(projectRoot) {
|
|
|
1753
1813
|
function readLocalFile(projectRoot, projectRelativePath) {
|
|
1754
1814
|
const absPath = path.join(projectRoot, projectRelativePath);
|
|
1755
1815
|
try {
|
|
1756
|
-
return fs.readFileSync(absPath, "utf-8");
|
|
1816
|
+
return fs$1.readFileSync(absPath, "utf-8");
|
|
1757
1817
|
} catch {
|
|
1758
1818
|
return null;
|
|
1759
1819
|
}
|
|
@@ -1895,10 +1955,10 @@ async function writeZip(outPath, entries) {
|
|
|
1895
1955
|
type: "nodebuffer",
|
|
1896
1956
|
compression: "DEFLATE"
|
|
1897
1957
|
});
|
|
1898
|
-
fs.mkdirSync(path.dirname(outPath), {
|
|
1958
|
+
fs$1.mkdirSync(path.dirname(outPath), {
|
|
1899
1959
|
recursive: true
|
|
1900
1960
|
});
|
|
1901
|
-
fs.writeFileSync(outPath, buffer);
|
|
1961
|
+
fs$1.writeFileSync(outPath, buffer);
|
|
1902
1962
|
}
|
|
1903
1963
|
function timestamp() {
|
|
1904
1964
|
return new Date().toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -2047,7 +2107,7 @@ async function collectArtifacts(dir) {
|
|
|
2047
2107
|
const skipped = [];
|
|
2048
2108
|
let totalBytes = 0;
|
|
2049
2109
|
const walk = async current => {
|
|
2050
|
-
const entries = await fs.promises.readdir(current, {
|
|
2110
|
+
const entries = await fs$1.promises.readdir(current, {
|
|
2051
2111
|
withFileTypes: true
|
|
2052
2112
|
});
|
|
2053
2113
|
for (const entry of entries) {
|
|
@@ -2061,7 +2121,7 @@ async function collectArtifacts(dir) {
|
|
|
2061
2121
|
// A symlinked file is published as its bytes; a symlinked directory is
|
|
2062
2122
|
// not walked, because one usually comes with a cycle, and a publisher
|
|
2063
2123
|
// that hangs is worse than one that says what it left out.
|
|
2064
|
-
const stat = await fs.promises.stat(absolute).catch(() => null);
|
|
2124
|
+
const stat = await fs$1.promises.stat(absolute).catch(() => null);
|
|
2065
2125
|
if (stat === null) {
|
|
2066
2126
|
skipped.push(`${key} (broken symlink)`);
|
|
2067
2127
|
continue;
|
|
@@ -2102,7 +2162,7 @@ async function hashFile(absolute) {
|
|
|
2102
2162
|
let bytes = 0;
|
|
2103
2163
|
// Streamed: an artifact may be 128 MB, and reading one into a Buffer to hash
|
|
2104
2164
|
// it is a needless way to run a CI runner out of memory.
|
|
2105
|
-
const stream = fs.createReadStream(absolute);
|
|
2165
|
+
const stream = fs$1.createReadStream(absolute);
|
|
2106
2166
|
await new Promise((resolve, reject) => {
|
|
2107
2167
|
stream.on("data", chunk => {
|
|
2108
2168
|
hash.update(chunk);
|
|
@@ -2152,7 +2212,7 @@ async function layerRevOf(artifacts) {
|
|
|
2152
2212
|
layerRev: null
|
|
2153
2213
|
};
|
|
2154
2214
|
}
|
|
2155
|
-
const bytes = await fs.promises.readFile(layer.file);
|
|
2215
|
+
const bytes = await fs$1.promises.readFile(layer.file);
|
|
2156
2216
|
let parsed;
|
|
2157
2217
|
try {
|
|
2158
2218
|
parsed = JSON.parse(zlib.gunzipSync(bytes).toString("utf-8"));
|
|
@@ -2180,7 +2240,7 @@ const DEFAULT_ARTIFACTS_DIR = ".val/publish";
|
|
|
2180
2240
|
function resolveArtifactsDir(options) {
|
|
2181
2241
|
const named = options.dir ?? DEFAULT_ARTIFACTS_DIR;
|
|
2182
2242
|
const dir = path.resolve(options.root, named);
|
|
2183
|
-
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
2243
|
+
if (!fs$1.existsSync(dir) || !fs$1.statSync(dir).isDirectory()) {
|
|
2184
2244
|
return {
|
|
2185
2245
|
status: "error",
|
|
2186
2246
|
message: `No artifacts to publish: ${dir} is not a directory.\n\n` + "Build the project first, then point at what it produced:\n\n" + " npx val publish --artifacts <directory>\n\n" + "The path of each file under it is its artifact key - `server`, `client`,\n" + "`public/...`, `chunk/client/...`, `layer`."
|
|
@@ -2520,7 +2580,7 @@ async function putArtifact(slot, file, fetchImpl) {
|
|
|
2520
2580
|
* different size as far as the signature is concerned. An artifact is capped
|
|
2521
2581
|
* at 128 MB by the API, which is the bound this trades against.
|
|
2522
2582
|
*/
|
|
2523
|
-
const body = await fs.promises.readFile(file);
|
|
2583
|
+
const body = await fs$1.promises.readFile(file);
|
|
2524
2584
|
let res;
|
|
2525
2585
|
try {
|
|
2526
2586
|
res = await fetchImpl(slot.url, {
|
|
@@ -2753,12 +2813,12 @@ function looksLikePersonalAccessToken(token) {
|
|
|
2753
2813
|
}
|
|
2754
2814
|
function readPersonalAccessToken(root) {
|
|
2755
2815
|
const patFile = getPersonalAccessTokenPath(root);
|
|
2756
|
-
if (!fs.existsSync(patFile)) {
|
|
2816
|
+
if (!fs$1.existsSync(patFile)) {
|
|
2757
2817
|
return {
|
|
2758
2818
|
status: "none"
|
|
2759
2819
|
};
|
|
2760
2820
|
}
|
|
2761
|
-
const parsed = parsePersonalAccessTokenFile(fs.readFileSync(patFile, "utf-8"));
|
|
2821
|
+
const parsed = parsePersonalAccessTokenFile(fs$1.readFileSync(patFile, "utf-8"));
|
|
2762
2822
|
if (!parsed.success) {
|
|
2763
2823
|
return {
|
|
2764
2824
|
status: "error",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.135.0",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"zod-validation-error": "^5.0.0",
|
|
29
29
|
"@valbuild/core": "0.134.0",
|
|
30
30
|
"@valbuild/eslint-plugin": "0.125.0",
|
|
31
|
-
"@valbuild/language-server": "0.
|
|
32
|
-
"@valbuild/
|
|
33
|
-
"@valbuild/
|
|
31
|
+
"@valbuild/language-server": "0.135.0",
|
|
32
|
+
"@valbuild/server": "0.135.0",
|
|
33
|
+
"@valbuild/shared": "0.134.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"prettier": "*",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@jest/globals": "^30.5.0",
|
|
49
|
+
"@prettier/sync": "^0.6.1",
|
|
50
|
+
"prettier": "~3.8.5",
|
|
49
51
|
"tsx": "^4.23.12"
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import synchronizedPrettier from "@prettier/sync";
|
|
5
|
+
import { DEFAULT_VAL_REMOTE_HOST } from "@valbuild/core";
|
|
6
|
+
import {
|
|
7
|
+
createDefaultValFSHost,
|
|
8
|
+
runValidation,
|
|
9
|
+
IValRemote,
|
|
10
|
+
} from "./runValidation";
|
|
11
|
+
import { formatFixedFiles } from "./formatFixedFiles";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The step `val validate --fix` ends on, against a real project on disk.
|
|
15
|
+
*
|
|
16
|
+
* The fixes here are produced by `runValidation` itself rather than written by
|
|
17
|
+
* the test, because the thing being checked is what happens to the TypeScript
|
|
18
|
+
* the fix pipeline emits — which is unformatted, and which used to be handed to
|
|
19
|
+
* `prettier.format(code, { filepath })` and therefore rewritten in prettier's
|
|
20
|
+
* defaults no matter what the project had configured.
|
|
21
|
+
*
|
|
22
|
+
* It stops short of calling `validate()` because that ends in `process.exit`
|
|
23
|
+
* once the fixture's remaining errors are counted, which would take the jest
|
|
24
|
+
* worker with it. `validate()` does nothing between the two but render.
|
|
25
|
+
*
|
|
26
|
+
* `@prettier/sync` rather than `prettier`: prettier 3's CommonJS entry loads its
|
|
27
|
+
* ESM build with a dynamic `import()`, which jest refuses without
|
|
28
|
+
* `--experimental-vm-modules`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const BASIC_FIXTURE = path.resolve(__dirname, "__fixtures__/basic");
|
|
32
|
+
|
|
33
|
+
// The fixture is copied INSIDE the package rather than into the OS temp dir:
|
|
34
|
+
// the project's `val.config.ts` is evaluated with a `require` resolving from
|
|
35
|
+
// its own path, so it has to be somewhere `@valbuild/core` can be walked up to.
|
|
36
|
+
const TMP_BASE = path.join(__dirname, ".tmp");
|
|
37
|
+
|
|
38
|
+
/** A module in neither style, so either pass has something to change. */
|
|
39
|
+
const UNFORMATTED_MODULE = `import {c,s} from "../val.config"
|
|
40
|
+
export default c.define("/content/basic-image.val.ts",s.image(),{path:"/public/val/image.png"})
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const mockRemote: IValRemote = {
|
|
44
|
+
remoteHost: DEFAULT_VAL_REMOTE_HOST,
|
|
45
|
+
getSettings: async () => {
|
|
46
|
+
throw new Error("Not expected to be called");
|
|
47
|
+
},
|
|
48
|
+
uploadFile: async () => {
|
|
49
|
+
throw new Error("Not expected to be called");
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Apply every fix in `valFiles`, and report which files were written. */
|
|
54
|
+
async function applyFixes(
|
|
55
|
+
tmpDir: string,
|
|
56
|
+
valFiles: string[],
|
|
57
|
+
): Promise<string[]> {
|
|
58
|
+
const fixed = new Set<string>();
|
|
59
|
+
for await (const event of runValidation({
|
|
60
|
+
root: tmpDir,
|
|
61
|
+
fix: true,
|
|
62
|
+
valFiles,
|
|
63
|
+
project: undefined,
|
|
64
|
+
remote: mockRemote,
|
|
65
|
+
fs: createDefaultValFSHost(),
|
|
66
|
+
})) {
|
|
67
|
+
if (event.type === "fix-applied") {
|
|
68
|
+
fixed.add(event.file);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [...fixed];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("formatFixedFiles", () => {
|
|
75
|
+
let tmpDir: string;
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
fs.mkdirSync(TMP_BASE, { recursive: true });
|
|
79
|
+
tmpDir = fs.mkdtempSync(path.join(TMP_BASE, "formatFixedFiles-"));
|
|
80
|
+
fs.cpSync(BASIC_FIXTURE, tmpDir, { recursive: true });
|
|
81
|
+
// Every case writes its own config into its own directory, and prettier
|
|
82
|
+
// caches per directory for the life of the process — including the fact
|
|
83
|
+
// that a directory had none.
|
|
84
|
+
synchronizedPrettier.clearConfigCache();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function writeConfig(config: Record<string, unknown>): void {
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
path.join(tmpDir, ".prettierrc.json"),
|
|
94
|
+
JSON.stringify(config),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test("formats a fixed file with the project's config, not prettier's defaults", async () => {
|
|
99
|
+
writeConfig({ singleQuote: true, semi: false });
|
|
100
|
+
const fixed = await applyFixes(tmpDir, ["content/basic-image.val.ts"]);
|
|
101
|
+
expect(fixed).toEqual(["content/basic-image.val.ts"]);
|
|
102
|
+
|
|
103
|
+
const { written, failures } = await formatFixedFiles(
|
|
104
|
+
synchronizedPrettier,
|
|
105
|
+
tmpDir,
|
|
106
|
+
fixed,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(failures).toEqual([]);
|
|
110
|
+
expect(written).toEqual(["content/basic-image.val.ts"]);
|
|
111
|
+
const after = fs.readFileSync(
|
|
112
|
+
path.join(tmpDir, "content/basic-image.val.ts"),
|
|
113
|
+
"utf-8",
|
|
114
|
+
);
|
|
115
|
+
// The fix landed...
|
|
116
|
+
expect(after).toContain("width: 1");
|
|
117
|
+
// ...in the project's style. Prettier's default is double quotes and semis,
|
|
118
|
+
// which is what the old `prettier.format(code, { filepath })` produced.
|
|
119
|
+
expect(after).toContain("'/public/val/image.png'");
|
|
120
|
+
expect(after).not.toContain('"/public/val/image.png"');
|
|
121
|
+
expect(after.trimEnd().endsWith(")")).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("leaves a fixed file the project put in .prettierignore unformatted", async () => {
|
|
125
|
+
writeConfig({ singleQuote: true });
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
path.join(tmpDir, ".prettierignore"),
|
|
128
|
+
"content/basic-image.val.ts\n",
|
|
129
|
+
);
|
|
130
|
+
const fixed = await applyFixes(tmpDir, ["content/basic-image.val.ts"]);
|
|
131
|
+
|
|
132
|
+
const { written, failures } = await formatFixedFiles(
|
|
133
|
+
synchronizedPrettier,
|
|
134
|
+
tmpDir,
|
|
135
|
+
fixed,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(failures).toEqual([]);
|
|
139
|
+
// Nothing rewritten, so the mtime is not bumped either.
|
|
140
|
+
expect(written).toEqual([]);
|
|
141
|
+
const after = fs.readFileSync(
|
|
142
|
+
path.join(tmpDir, "content/basic-image.val.ts"),
|
|
143
|
+
"utf-8",
|
|
144
|
+
);
|
|
145
|
+
// The fix still landed — only the formatting was skipped.
|
|
146
|
+
expect(after).toContain("width: 1");
|
|
147
|
+
expect(after).toContain('"/public/val/image.png"');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("does not rewrite a file that is already formatted", async () => {
|
|
151
|
+
writeConfig({});
|
|
152
|
+
const fixed = await applyFixes(tmpDir, ["content/basic-image.val.ts"]);
|
|
153
|
+
const target = path.join(tmpDir, "content/basic-image.val.ts");
|
|
154
|
+
|
|
155
|
+
// First pass formats it; a second pass has nothing left to do.
|
|
156
|
+
await formatFixedFiles(synchronizedPrettier, tmpDir, fixed);
|
|
157
|
+
const { written, failures } = await formatFixedFiles(
|
|
158
|
+
synchronizedPrettier,
|
|
159
|
+
tmpDir,
|
|
160
|
+
fixed,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(failures).toEqual([]);
|
|
164
|
+
expect(written).toEqual([]);
|
|
165
|
+
expect(fs.readFileSync(target, "utf-8")).toContain("width: 1");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("reports a file it cannot format instead of throwing, and keeps going", async () => {
|
|
169
|
+
writeConfig({});
|
|
170
|
+
const fixed = await applyFixes(tmpDir, ["content/basic-image.val.ts"]);
|
|
171
|
+
// A parser prettier does not have: the same shape as a malformed config or
|
|
172
|
+
// a plugin that fails to load, and the only failure mode reachable here
|
|
173
|
+
// without breaking the file the fix just wrote.
|
|
174
|
+
const brokenPrettier = {
|
|
175
|
+
...synchronizedPrettier,
|
|
176
|
+
format: () => {
|
|
177
|
+
throw new Error("No parser could be inferred");
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const { written, failures } = await formatFixedFiles(
|
|
182
|
+
brokenPrettier,
|
|
183
|
+
tmpDir,
|
|
184
|
+
[...fixed, ...fixed],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(written).toEqual([]);
|
|
188
|
+
// Both entries reported: the loop continued past the first failure.
|
|
189
|
+
expect(failures).toHaveLength(2);
|
|
190
|
+
expect(failures[0]).toEqual({
|
|
191
|
+
file: "content/basic-image.val.ts",
|
|
192
|
+
message: "No parser could be inferred",
|
|
193
|
+
});
|
|
194
|
+
// The fix is still on disk, unformatted rather than lost.
|
|
195
|
+
expect(
|
|
196
|
+
fs.readFileSync(path.join(tmpDir, "content/basic-image.val.ts"), "utf-8"),
|
|
197
|
+
).toContain("width: 1");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("resolves a project-relative path against the project, with or without a leading slash", async () => {
|
|
201
|
+
writeConfig({ singleQuote: true });
|
|
202
|
+
const fixed = await applyFixes(tmpDir, ["content/basic-image.val.ts"]);
|
|
203
|
+
// `runValidation` reports this one without a leading slash, but a
|
|
204
|
+
// `ModuleFilePath` carries one and `validate.ts` strips it for display, so
|
|
205
|
+
// both shapes reach here. Resolving either against the filesystem root
|
|
206
|
+
// rather than the project would find a different `.prettierrc` — or none,
|
|
207
|
+
// which is the old bug wearing a different hat.
|
|
208
|
+
expect(fixed).toEqual(["content/basic-image.val.ts"]);
|
|
209
|
+
const target = path.join(tmpDir, "content/basic-image.val.ts");
|
|
210
|
+
|
|
211
|
+
for (const reported of [
|
|
212
|
+
"content/basic-image.val.ts",
|
|
213
|
+
"/content/basic-image.val.ts",
|
|
214
|
+
]) {
|
|
215
|
+
fs.writeFileSync(target, UNFORMATTED_MODULE);
|
|
216
|
+
const { written, failures } = await formatFixedFiles(
|
|
217
|
+
synchronizedPrettier,
|
|
218
|
+
tmpDir,
|
|
219
|
+
[reported],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
expect(failures).toEqual([]);
|
|
223
|
+
expect(written).toEqual([reported]);
|
|
224
|
+
expect(fs.readFileSync(target, "utf-8")).toContain(
|
|
225
|
+
"'/public/val/image.png'",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { createPrettierFormatter, type PrettierLike } from "@valbuild/server";
|
|
4
|
+
|
|
5
|
+
/** A file whose fix landed but which could not be formatted afterwards. */
|
|
6
|
+
export type FormatFailure = {
|
|
7
|
+
/** Project-relative, as `--fix` reported it. */
|
|
8
|
+
file: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type FormatFixedFilesResult = {
|
|
13
|
+
/** Files actually rewritten — a file already in its final form is not. */
|
|
14
|
+
written: string[];
|
|
15
|
+
failures: FormatFailure[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Format the files `--fix` just wrote, the way the project formats everything.
|
|
20
|
+
*
|
|
21
|
+
* Through `createPrettierFormatter`, which is the same function an app hands to
|
|
22
|
+
* `initValServer` / `initValMcp`, so a fix applied here and an edit saved from
|
|
23
|
+
* the Studio come out identically formatted. Calling
|
|
24
|
+
* `prettier.format(code, { filepath })` directly — which this did — reads no
|
|
25
|
+
* config at all: `filepath` picks the parser, and `.prettierrc` is only ever
|
|
26
|
+
* consulted by `resolveConfig`, `getFileInfo` and prettier's own CLI. A project
|
|
27
|
+
* whose style is not prettier's default got its whole file rewritten by a
|
|
28
|
+
* two-line content fix, and a red `format` job with it.
|
|
29
|
+
*
|
|
30
|
+
* Separate from `validate.ts` so it can be tested against a real project on
|
|
31
|
+
* disk: `runValidation` stops before this step, and `validate()` itself ends in
|
|
32
|
+
* `process.exit`, so neither can reach it from a test.
|
|
33
|
+
*
|
|
34
|
+
* Rendering is the caller's, which is why failures are returned rather than
|
|
35
|
+
* logged. A file that cannot be formatted is not a failed run: the fix itself
|
|
36
|
+
* landed, and stopping here would leave the fix applied and the report unwritten.
|
|
37
|
+
*/
|
|
38
|
+
export async function formatFixedFiles(
|
|
39
|
+
prettier: PrettierLike,
|
|
40
|
+
projectRoot: string,
|
|
41
|
+
files: Iterable<string>,
|
|
42
|
+
): Promise<FormatFixedFilesResult> {
|
|
43
|
+
const format = createPrettierFormatter(prettier, { projectRoot });
|
|
44
|
+
const written: string[] = [];
|
|
45
|
+
const failures: FormatFailure[] = [];
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const filePath = path.join(projectRoot, file);
|
|
48
|
+
const fileContent = await fs.readFile(filePath, "utf-8");
|
|
49
|
+
let formattedContent: string;
|
|
50
|
+
try {
|
|
51
|
+
formattedContent = await format(fileContent, file);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
failures.push({
|
|
54
|
+
file,
|
|
55
|
+
message: err instanceof Error ? err.message : String(err),
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// A file prettier left alone — one the project put in `.prettierignore`,
|
|
60
|
+
// or one already in its final form — is not rewritten. Writing identical
|
|
61
|
+
// bytes still bumps the mtime, and a `--fix` run alongside a dev server
|
|
62
|
+
// makes it rebuild for nothing.
|
|
63
|
+
if (formattedContent !== fileContent) {
|
|
64
|
+
await fs.writeFile(filePath, formattedContent);
|
|
65
|
+
written.push(file);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { written, failures };
|
|
69
|
+
}
|
package/src/validate.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import picocolors from "picocolors";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
3
|
import { glob } from "fast-glob";
|
|
5
4
|
import { DEFAULT_CONTENT_HOST, DEFAULT_VAL_REMOTE_HOST } from "@valbuild/core";
|
|
6
5
|
import { getSettings, uploadRemoteFile } from "@valbuild/server";
|
|
6
|
+
import { formatFixedFiles } from "./formatFixedFiles";
|
|
7
7
|
import { findAndEvalValConfigFile } from "./utils/evalValConfigFile";
|
|
8
8
|
import { createDefaultValFSHost, runValidation } from "./runValidation";
|
|
9
9
|
import {
|
|
@@ -283,15 +283,24 @@ export async function validate({
|
|
|
283
283
|
console.log("");
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
-
//
|
|
286
|
+
// Format the files that had fixes applied, the way the project formats
|
|
287
|
+
// everything else. See `formatFixedFiles` for why this cannot simply be
|
|
288
|
+
// `prettier.format(code, { filepath })`.
|
|
289
|
+
//
|
|
290
|
+
// A file that could not be formatted is reported and skipped rather than
|
|
291
|
+
// thrown: the fix itself landed, and aborting here would leave the fix
|
|
292
|
+
// applied and the report below unwritten.
|
|
287
293
|
if (prettier) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
const { failures } = await formatFixedFiles(
|
|
295
|
+
prettier,
|
|
296
|
+
projectRoot,
|
|
297
|
+
fixedFiles,
|
|
298
|
+
);
|
|
299
|
+
for (const failure of failures) {
|
|
300
|
+
console.log(
|
|
301
|
+
picocolors.yellow("⚠"),
|
|
302
|
+
`Could not format ${relFile(failure.file)}: ${failure.message}`,
|
|
303
|
+
);
|
|
295
304
|
}
|
|
296
305
|
}
|
|
297
306
|
|