@valbuild/cli 0.97.7 → 0.99.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/cli/dist/valbuild-cli-cli.cjs.dev.js +222 -222
- package/cli/dist/valbuild-cli-cli.cjs.prod.js +222 -222
- package/cli/dist/valbuild-cli-cli.esm.js +221 -219
- package/package.json +5 -4
- package/src/__fixtures__/basic/content/basic-gallery-remote-existing.val.ts +20 -0
- package/src/__fixtures__/basic/content/basic-gallery-remote.val.ts +19 -0
- package/src/__fixtures__/basic/public/val/images-remote/image.png +0 -0
- package/src/__fixtures__/basic/public/val/images-remote2/image.png +0 -0
- package/src/__fixtures__/basic/val.config.ts +3 -1
- package/src/__fixtures__/basic/val.modules.ts +2 -0
- package/src/cli.ts +11 -5
- package/src/connect.ts +2 -4
- package/src/debug/context.ts +2 -4
- package/src/listUnusedFiles.ts +2 -4
- package/src/login.ts +29 -94
- package/src/lsp.ts +19 -0
- package/src/runValidation.test.ts +101 -0
- package/src/runValidation.ts +200 -73
- package/src/utils/evalValConfigFile.ts +6 -100
- package/src/validate.ts +2 -4
|
@@ -4,16 +4,13 @@ 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 = require('fs/promises');
|
|
7
|
+
var fs$1 = require('fs/promises');
|
|
8
8
|
var fastGlob = require('fast-glob');
|
|
9
9
|
var core = require('@valbuild/core');
|
|
10
10
|
var server = require('@valbuild/server');
|
|
11
|
-
var vm = require('node:vm');
|
|
12
|
-
var ts = require('typescript');
|
|
13
|
-
var z = require('zod');
|
|
14
|
-
var node_module = require('node:module');
|
|
15
11
|
var internal = require('@valbuild/shared/internal');
|
|
16
|
-
var
|
|
12
|
+
var ts = require('typescript');
|
|
13
|
+
var fs = require('fs');
|
|
17
14
|
var JSZip = require('jszip');
|
|
18
15
|
var readline = require('readline');
|
|
19
16
|
|
|
@@ -41,11 +38,9 @@ var meow__default = /*#__PURE__*/_interopDefault(meow);
|
|
|
41
38
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
42
39
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
43
40
|
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
44
|
-
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
45
|
-
var vm__default = /*#__PURE__*/_interopDefault(vm);
|
|
46
|
-
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
47
|
-
var z__default = /*#__PURE__*/_interopDefault(z);
|
|
48
41
|
var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
|
|
42
|
+
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
43
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
49
44
|
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
50
45
|
var readline__default = /*#__PURE__*/_interopDefault(readline);
|
|
51
46
|
|
|
@@ -64,76 +59,6 @@ function info(message, opts = {}) {
|
|
|
64
59
|
console.log(chalk__default["default"].blue("️ℹ️ : ") + message);
|
|
65
60
|
}
|
|
66
61
|
|
|
67
|
-
const ValConfigSchema = z__default["default"].object({
|
|
68
|
-
project: z__default["default"].string().optional(),
|
|
69
|
-
root: z__default["default"].string().optional(),
|
|
70
|
-
files: z__default["default"].object({
|
|
71
|
-
directory: z__default["default"].string().refine(val => (val === "/public" || val.startsWith("/public/") && !val.endsWith("/")) &&
|
|
72
|
-
// Reject path traversal so the directory cannot escape /public
|
|
73
|
-
!val.split("/").some(segment => segment === "." || segment === ".."), {
|
|
74
|
-
message: "files.directory must start with '/public', must not end with '/' and must not contain '.' or '..' segments"
|
|
75
|
-
})
|
|
76
|
-
}).optional(),
|
|
77
|
-
gitCommit: z__default["default"].string().optional(),
|
|
78
|
-
gitBranch: z__default["default"].string().optional(),
|
|
79
|
-
defaultTheme: z__default["default"].union([z__default["default"].literal("light"), z__default["default"].literal("dark")]).optional(),
|
|
80
|
-
ai: z__default["default"].object({
|
|
81
|
-
commitMessages: z__default["default"].object({
|
|
82
|
-
disabled: z__default["default"].boolean().optional()
|
|
83
|
-
}).optional()
|
|
84
|
-
}).optional()
|
|
85
|
-
});
|
|
86
|
-
async function evalValConfigFile(projectRoot, configFileName) {
|
|
87
|
-
const valConfigPath = path__default["default"].join(projectRoot, configFileName);
|
|
88
|
-
let code = null;
|
|
89
|
-
try {
|
|
90
|
-
code = await fs__default["default"].readFile(valConfigPath, "utf-8");
|
|
91
|
-
} catch {
|
|
92
|
-
//
|
|
93
|
-
}
|
|
94
|
-
if (!code) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
const transpiled = ts__default["default"].transpileModule(code, {
|
|
98
|
-
compilerOptions: {
|
|
99
|
-
target: ts__default["default"].ScriptTarget.ES2020,
|
|
100
|
-
module: ts__default["default"].ModuleKind.CommonJS,
|
|
101
|
-
esModuleInterop: true
|
|
102
|
-
},
|
|
103
|
-
fileName: valConfigPath
|
|
104
|
-
});
|
|
105
|
-
const projectRootRequire = node_module.createRequire(valConfigPath);
|
|
106
|
-
const exportsObj = {};
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
-
const sandbox = {
|
|
109
|
-
exports: exportsObj,
|
|
110
|
-
module: {
|
|
111
|
-
exports: exportsObj
|
|
112
|
-
},
|
|
113
|
-
require: projectRootRequire,
|
|
114
|
-
// NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
|
|
115
|
-
__filename: valConfigPath,
|
|
116
|
-
__dirname: projectRoot,
|
|
117
|
-
console,
|
|
118
|
-
process
|
|
119
|
-
};
|
|
120
|
-
sandbox.global = sandbox;
|
|
121
|
-
const context = vm__default["default"].createContext(sandbox);
|
|
122
|
-
const script = new vm__default["default"].Script(transpiled.outputText, {
|
|
123
|
-
filename: valConfigPath
|
|
124
|
-
});
|
|
125
|
-
script.runInContext(context);
|
|
126
|
-
const valConfig = sandbox.module.exports.config;
|
|
127
|
-
if (!valConfig) {
|
|
128
|
-
throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
|
|
129
|
-
}
|
|
130
|
-
const result = ValConfigSchema.safeParse(valConfig);
|
|
131
|
-
if (!result.success) {
|
|
132
|
-
throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
|
|
133
|
-
}
|
|
134
|
-
return result.data;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
62
|
function getFileExt(filePath) {
|
|
138
63
|
// NOTE: We do not import the path module. This code is copied in different projects. We want the same implementation and which means that this might running in browser where path is not available).
|
|
139
64
|
return filePath.split(".").pop() || "";
|
|
@@ -175,32 +100,13 @@ async function handleFileMetadata(ctx) {
|
|
|
175
100
|
shouldApplyPatch: true
|
|
176
101
|
};
|
|
177
102
|
}
|
|
178
|
-
async function handleRemoteFileUpload(ctx) {
|
|
179
|
-
var _resolvedRemoteFileAt;
|
|
180
|
-
if (!ctx.fix) {
|
|
181
|
-
return {
|
|
182
|
-
success: false,
|
|
183
|
-
errorMessage: `Remote file ${ctx.sourcePath} needs to be uploaded (use --fix to upload)`
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
187
|
-
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
188
|
-
return {
|
|
189
|
-
success: false,
|
|
190
|
-
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
194
103
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
const filePath = path__default["default"].join(ctx.projectRoot, fileRefProp);
|
|
104
|
+
// Shared upload core used by both the single-field (handleRemoteFileUpload)
|
|
105
|
+
// and gallery (handleRemoteGalleryFileUpload) handlers. The two differ only in
|
|
106
|
+
// how they derive the local file ref, metadata and serialized image/file
|
|
107
|
+
// schema; everything from auth through upload is identical.
|
|
108
|
+
async function uploadRemoteFileCore(ctx, fileRef, metadata, schema) {
|
|
109
|
+
const filePath = path__default["default"].join(ctx.projectRoot, fileRef);
|
|
204
110
|
if (!ctx.fs.fileExists(filePath)) {
|
|
205
111
|
return {
|
|
206
112
|
success: false,
|
|
@@ -240,21 +146,6 @@ async function handleRemoteFileUpload(ctx) {
|
|
|
240
146
|
}]
|
|
241
147
|
};
|
|
242
148
|
}
|
|
243
|
-
if (!resolvedRemoteFileAtSourcePath.schema) {
|
|
244
|
-
return {
|
|
245
|
-
success: false,
|
|
246
|
-
errorMessage: `Cannot upload remote file: schema not found for ${ctx.sourcePath}`
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
|
|
250
|
-
const fileSourceMetadata = core.Internal.isFile(actualRemoteFileSource) ? actualRemoteFileSource.metadata : undefined;
|
|
251
|
-
const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
|
|
252
|
-
if (!resolveRemoteFileSchema) {
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
errorMessage: `Could not resolve schema for remote file: ${ctx.sourcePath}`
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
149
|
const projectName = ctx.project;
|
|
259
150
|
let publicProjectId = ctx.publicProjectId;
|
|
260
151
|
let remoteFileBuckets = ctx.remoteFileBuckets;
|
|
@@ -290,12 +181,6 @@ async function handleRemoteFileUpload(ctx) {
|
|
|
290
181
|
errorMessage: `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`
|
|
291
182
|
};
|
|
292
183
|
}
|
|
293
|
-
if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
|
|
294
|
-
return {
|
|
295
|
-
success: false,
|
|
296
|
-
errorMessage: `The schema is the remote is neither image nor file: ${ctx.sourcePath}`
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
184
|
remoteFilesCounter += 1;
|
|
300
185
|
const bucket = remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length];
|
|
301
186
|
if (!bucket) {
|
|
@@ -321,8 +206,6 @@ async function handleRemoteFileUpload(ctx) {
|
|
|
321
206
|
const fileHash = core.Internal.remote.getFileHash(fileBuffer);
|
|
322
207
|
const coreVersion = core.Internal.VERSION.core || "unknown";
|
|
323
208
|
const fileExt = getFileExt(filePath);
|
|
324
|
-
const schema = resolveRemoteFileSchema;
|
|
325
|
-
const metadata = fileSourceMetadata;
|
|
326
209
|
const ref = core.Internal.remote.createRemoteRef(ctx.remote.remoteHost, {
|
|
327
210
|
publicProjectId,
|
|
328
211
|
coreVersion,
|
|
@@ -342,7 +225,7 @@ async function handleRemoteFileUpload(ctx) {
|
|
|
342
225
|
}
|
|
343
226
|
ctx.remoteFiles[ctx.sourcePath] = {
|
|
344
227
|
ref,
|
|
345
|
-
metadata
|
|
228
|
+
metadata
|
|
346
229
|
};
|
|
347
230
|
return {
|
|
348
231
|
success: true,
|
|
@@ -359,6 +242,131 @@ async function handleRemoteFileUpload(ctx) {
|
|
|
359
242
|
}]
|
|
360
243
|
};
|
|
361
244
|
}
|
|
245
|
+
async function handleRemoteFileUpload(ctx) {
|
|
246
|
+
var _resolvedRemoteFileAt;
|
|
247
|
+
if (!ctx.fix) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
// No sourcePath in the message: the reported location already points at it.
|
|
251
|
+
errorMessage: `Remote file needs to be uploaded (use --fix to upload)`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
255
|
+
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
256
|
+
return {
|
|
257
|
+
success: false,
|
|
258
|
+
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
262
|
+
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
|
+
const fileRefProp = (_resolvedRemoteFileAt = resolvedRemoteFileAtSourcePath.source) === null || _resolvedRemoteFileAt === void 0 ? void 0 : _resolvedRemoteFileAt[core.FILE_REF_PROP];
|
|
265
|
+
if (!fileRefProp) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
errorMessage: `Expected file to be defined at: ${ctx.sourcePath} but no file was found`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema;
|
|
272
|
+
if (!resolveRemoteFileSchema) {
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
errorMessage: `Cannot upload remote file: schema not found for ${ctx.sourcePath}`
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
errorMessage: `The schema is the remote is neither image nor file: ${ctx.sourcePath}`
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source;
|
|
285
|
+
const fileSourceMetadata = core.Internal.isFile(actualRemoteFileSource) ? actualRemoteFileSource.metadata : undefined;
|
|
286
|
+
return uploadRemoteFileCore(ctx, fileRefProp, fileSourceMetadata, resolveRemoteFileSchema);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Gallery (s.images({ remote: true }) / s.files({ remote: true })) upload.
|
|
290
|
+
// Unlike a single image/file field, a gallery entry is keyed by its local file
|
|
291
|
+
// path and the value is bare metadata (no FileSource), so we derive the file
|
|
292
|
+
// ref from the key and synthesize the image/file schema from the record's
|
|
293
|
+
// media options.
|
|
294
|
+
async function handleRemoteGalleryFileUpload(ctx) {
|
|
295
|
+
var _ctx$validationError$;
|
|
296
|
+
if (!ctx.fix) {
|
|
297
|
+
return {
|
|
298
|
+
success: false,
|
|
299
|
+
// No sourcePath in the message: the reported location already points at it.
|
|
300
|
+
errorMessage: `Remote file needs to be uploaded (use --fix to upload)`
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const fix = (_ctx$validationError$ = ctx.validationError.fixes) === null || _ctx$validationError$ === void 0 ? void 0 : _ctx$validationError$[0];
|
|
304
|
+
const mediaType = fix === "files:upload-remote" ? "file" : "image";
|
|
305
|
+
|
|
306
|
+
// The gallery entry is keyed by its local file path; validateMediaKey carries
|
|
307
|
+
// that key as the error value.
|
|
308
|
+
const fileRef = ctx.validationError.value;
|
|
309
|
+
if (typeof fileRef !== "string") {
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
errorMessage: `Expected a local file path for gallery entry at ${ctx.sourcePath}`
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(ctx.sourcePath);
|
|
316
|
+
if (!ctx.valModule.source || !ctx.valModule.schema) {
|
|
317
|
+
return {
|
|
318
|
+
success: false,
|
|
319
|
+
errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const resolved = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
|
|
323
|
+
const entrySource = resolved.source;
|
|
324
|
+
const metadata = entrySource && typeof entrySource === "object" && !Array.isArray(entrySource) ? entrySource : undefined;
|
|
325
|
+
|
|
326
|
+
// The gallery item schema is an ObjectSchema, not an image/file schema, so
|
|
327
|
+
// synthesize the serialized image/file schema (matching how single fields
|
|
328
|
+
// serialize) for the remote ref's validation hash.
|
|
329
|
+
//
|
|
330
|
+
// `accept`/`directory` come from the RECORD that holds the entry, resolved
|
|
331
|
+
// from the entry's PARENT path - not from the module root, which is only the
|
|
332
|
+
// record when the gallery is the whole module. A nested gallery
|
|
333
|
+
// (s.object({ gallery: s.images(...) })) would otherwise synthesize a schema
|
|
334
|
+
// with no options and bake a validation hash into the remote ref that can
|
|
335
|
+
// never validate, so a mismatch fails fast instead of uploading.
|
|
336
|
+
const [, parentModulePath] = core.Internal.splitModuleFilePathAndModulePath(core.Internal.parentOfSourcePath(ctx.sourcePath));
|
|
337
|
+
const recordSchema = core.Internal.resolvePath(parentModulePath, ctx.valModule.source, ctx.valModule.schema).schema;
|
|
338
|
+
if ((recordSchema === null || recordSchema === void 0 ? void 0 : recordSchema.type) !== "record") {
|
|
339
|
+
return {
|
|
340
|
+
success: false,
|
|
341
|
+
errorMessage: `Expected a gallery record at the parent of ${ctx.sourcePath}, got ${(recordSchema === null || recordSchema === void 0 ? void 0 : recordSchema.type) ?? "nothing"}`
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const {
|
|
345
|
+
accept,
|
|
346
|
+
directory
|
|
347
|
+
} = recordSchema;
|
|
348
|
+
const schema = mediaType === "image" ? {
|
|
349
|
+
type: "image",
|
|
350
|
+
opt: false,
|
|
351
|
+
options: {
|
|
352
|
+
...(accept ? {
|
|
353
|
+
accept
|
|
354
|
+
} : {}),
|
|
355
|
+
...(directory ? {
|
|
356
|
+
directory
|
|
357
|
+
} : {})
|
|
358
|
+
}
|
|
359
|
+
} : {
|
|
360
|
+
type: "file",
|
|
361
|
+
opt: false,
|
|
362
|
+
options: {
|
|
363
|
+
...(accept ? {
|
|
364
|
+
accept
|
|
365
|
+
} : {})
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
return uploadRemoteFileCore(ctx, fileRef, metadata, schema);
|
|
369
|
+
}
|
|
362
370
|
async function handleRemoteFileDownload(ctx) {
|
|
363
371
|
if (ctx.fix) {
|
|
364
372
|
return {
|
|
@@ -416,6 +424,17 @@ async function handleUniqueFolderCheck(ctx) {
|
|
|
416
424
|
success: true
|
|
417
425
|
};
|
|
418
426
|
}
|
|
427
|
+
|
|
428
|
+
// Maps a gallery key to its on-disk local path. Remote galleries key uploaded
|
|
429
|
+
// entries by a remote URL while keeping the file on disk; everything else is
|
|
430
|
+
// already a local path and is returned unchanged.
|
|
431
|
+
function remoteKeyToLocalPath(key) {
|
|
432
|
+
const remoteRefRes = core.Internal.remote.splitRemoteRef(key);
|
|
433
|
+
if (remoteRefRes.status === "success") {
|
|
434
|
+
return `/${remoteRefRes.filePath}`;
|
|
435
|
+
}
|
|
436
|
+
return key;
|
|
437
|
+
}
|
|
419
438
|
async function handleCheckAllFiles(ctx) {
|
|
420
439
|
const value = ctx.validationError.value;
|
|
421
440
|
if (!value || typeof value.directory !== "string") {
|
|
@@ -434,7 +453,10 @@ async function handleCheckAllFiles(ctx) {
|
|
|
434
453
|
errorMessage: `Could not get source for ${ctx.sourcePath}`
|
|
435
454
|
};
|
|
436
455
|
}
|
|
437
|
-
|
|
456
|
+
// Gallery entries are keyed by their file path. Remote galleries key uploaded
|
|
457
|
+
// entries by a remote URL, but the file is kept on disk at its local path, so
|
|
458
|
+
// normalize remote-URL keys back to that local path for the on-disk checks.
|
|
459
|
+
const trackedFiles = new Set(Object.keys(source).map(remoteKeyToLocalPath));
|
|
438
460
|
|
|
439
461
|
// Check that all tracked files exist on disk
|
|
440
462
|
const missingTrackedFiles = [...trackedFiles].filter(f => {
|
|
@@ -489,6 +511,8 @@ const currentFixHandlers = {
|
|
|
489
511
|
"file:add-metadata": handleFileMetadata,
|
|
490
512
|
"image:upload-remote": handleRemoteFileUpload,
|
|
491
513
|
"file:upload-remote": handleRemoteFileUpload,
|
|
514
|
+
"images:upload-remote": handleRemoteGalleryFileUpload,
|
|
515
|
+
"files:upload-remote": handleRemoteGalleryFileUpload,
|
|
492
516
|
"image:download-remote": handleRemoteFileDownload,
|
|
493
517
|
"file:download-remote": handleRemoteFileDownload,
|
|
494
518
|
"image:check-remote": handleRemoteFileCheck,
|
|
@@ -511,15 +535,15 @@ function createDefaultValFSHost() {
|
|
|
511
535
|
return {
|
|
512
536
|
...ts__default["default"].sys,
|
|
513
537
|
writeFile: (fileName, data, encoding) => {
|
|
514
|
-
fs__default
|
|
538
|
+
fs__default["default"].mkdirSync(path__default["default"].dirname(fileName), {
|
|
515
539
|
recursive: true
|
|
516
540
|
});
|
|
517
|
-
fs__default
|
|
541
|
+
fs__default["default"].writeFileSync(fileName, typeof data === "string" ? data : new Uint8Array(data), encoding);
|
|
518
542
|
},
|
|
519
|
-
rmFile: fs__default
|
|
543
|
+
rmFile: fs__default["default"].rmSync,
|
|
520
544
|
readBuffer: fileName => {
|
|
521
545
|
try {
|
|
522
|
-
return fs__default
|
|
546
|
+
return fs__default["default"].readFileSync(fileName);
|
|
523
547
|
} catch {
|
|
524
548
|
return undefined;
|
|
525
549
|
}
|
|
@@ -860,7 +884,7 @@ function getCachedFile(moduleFilePath, projectRoot, cache) {
|
|
|
860
884
|
const filePath = path__default["default"].join(projectRoot, moduleFilePath);
|
|
861
885
|
let fileContent;
|
|
862
886
|
try {
|
|
863
|
-
fileContent = fs__default
|
|
887
|
+
fileContent = fs__default["default"].readFileSync(filePath, "utf-8");
|
|
864
888
|
} catch {
|
|
865
889
|
cache.set(moduleFilePath, null);
|
|
866
890
|
return null;
|
|
@@ -891,7 +915,7 @@ async function validate({
|
|
|
891
915
|
// errors found. Re-reads config and val files each call so it always reflects
|
|
892
916
|
// the latest state on disk (used both for one-shot and watch mode).
|
|
893
917
|
async function runOnce() {
|
|
894
|
-
const valConfigFile =
|
|
918
|
+
const valConfigFile = await server.findAndEvalValConfigFile(projectRoot);
|
|
895
919
|
const resolvedValConfigFile = valConfigFile ? {
|
|
896
920
|
...valConfigFile,
|
|
897
921
|
project: process.env.VAL_PROJECT || valConfigFile.project
|
|
@@ -1071,11 +1095,11 @@ async function validate({
|
|
|
1071
1095
|
if (prettier) {
|
|
1072
1096
|
for (const file of fixedFiles) {
|
|
1073
1097
|
const filePath = path__default["default"].join(projectRoot, file);
|
|
1074
|
-
const fileContent = await fs__default["default"].readFile(filePath, "utf-8");
|
|
1098
|
+
const fileContent = await fs__default$1["default"].readFile(filePath, "utf-8");
|
|
1075
1099
|
const formattedContent = await prettier.format(fileContent, {
|
|
1076
1100
|
filepath: filePath
|
|
1077
1101
|
});
|
|
1078
|
-
await fs__default["default"].writeFile(filePath, formattedContent);
|
|
1102
|
+
await fs__default$1["default"].writeFile(filePath, formattedContent);
|
|
1079
1103
|
}
|
|
1080
1104
|
}
|
|
1081
1105
|
|
|
@@ -1196,7 +1220,7 @@ async function listUnusedFiles({
|
|
|
1196
1220
|
}) {
|
|
1197
1221
|
var _valConfigFile$files;
|
|
1198
1222
|
const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
|
|
1199
|
-
const valConfigFile =
|
|
1223
|
+
const valConfigFile = await server.findAndEvalValConfigFile(projectRoot);
|
|
1200
1224
|
// Strip the leading "/" so it is relative to the project root (e.g. "public/val").
|
|
1201
1225
|
const managedDir = ((valConfigFile === null || valConfigFile === void 0 || (_valConfigFile$files = valConfigFile.files) === null || _valConfigFile$files === void 0 ? void 0 : _valConfigFile$files.directory) ?? "/public/val").replace(/^\//, "");
|
|
1202
1226
|
const service = await server.createService(projectRoot);
|
|
@@ -1285,7 +1309,7 @@ const getVersions = () => {
|
|
|
1285
1309
|
};
|
|
1286
1310
|
};
|
|
1287
1311
|
|
|
1288
|
-
const host
|
|
1312
|
+
const host = process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
1289
1313
|
async function connect(options) {
|
|
1290
1314
|
const {
|
|
1291
1315
|
root
|
|
@@ -1303,11 +1327,11 @@ async function connect(options) {
|
|
|
1303
1327
|
if (maybeGitOwnerAndRepo) {
|
|
1304
1328
|
params.set("github_repo", [maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"));
|
|
1305
1329
|
}
|
|
1306
|
-
const url = `${host
|
|
1330
|
+
const url = `${host}/connect?${params.toString()}`;
|
|
1307
1331
|
console.log(pc__default["default"].dim(`\nFollow the instructions in your browser to complete the setup:\n${pc__default["default"].cyan(url)}\n`));
|
|
1308
1332
|
}
|
|
1309
1333
|
async function tryGetProject(projectRoot) {
|
|
1310
|
-
const valConfigFile =
|
|
1334
|
+
const valConfigFile = await server.findAndEvalValConfigFile(projectRoot);
|
|
1311
1335
|
if (valConfigFile && valConfigFile.project) {
|
|
1312
1336
|
const parts = valConfigFile.project.split("/");
|
|
1313
1337
|
if (parts.length === 2) {
|
|
@@ -1360,8 +1384,8 @@ function tryGetGitConfig(root) {
|
|
|
1360
1384
|
let lastDir = null;
|
|
1361
1385
|
while (currentDir !== lastDir) {
|
|
1362
1386
|
const gitConfigPath = path__default["default"].join(currentDir, ".git", "config");
|
|
1363
|
-
if (fs__default
|
|
1364
|
-
return fs__default
|
|
1387
|
+
if (fs__default["default"].existsSync(gitConfigPath)) {
|
|
1388
|
+
return fs__default["default"].readFileSync(gitConfigPath, "utf-8");
|
|
1365
1389
|
}
|
|
1366
1390
|
lastDir = currentDir;
|
|
1367
1391
|
currentDir = path__default["default"].dirname(currentDir);
|
|
@@ -1372,83 +1396,54 @@ function tryGetGitConfig(root) {
|
|
|
1372
1396
|
return null;
|
|
1373
1397
|
}
|
|
1374
1398
|
|
|
1375
|
-
|
|
1399
|
+
/**
|
|
1400
|
+
* Terminal wrapper around the login device flow in `@valbuild/server`.
|
|
1401
|
+
* The flow itself (polling, token persistence) is shared with
|
|
1402
|
+
* `@valbuild/language-server`.
|
|
1403
|
+
*/
|
|
1376
1404
|
async function login(options) {
|
|
1377
1405
|
try {
|
|
1378
|
-
var _response$headers$get;
|
|
1379
1406
|
console.log(pc__default["default"].cyan("\nStarting login process...\n"));
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
headers: {
|
|
1385
|
-
"Content-Type": "application/json"
|
|
1386
|
-
}
|
|
1387
|
-
});
|
|
1388
|
-
let token;
|
|
1389
|
-
let url;
|
|
1390
|
-
if (!((_response$headers$get = response.headers.get("content-type")) !== null && _response$headers$get !== void 0 && _response$headers$get.includes("application/json"))) {
|
|
1391
|
-
const text = await response.text();
|
|
1392
|
-
console.error(pc__default["default"].red("Unexpected failure while trying to login (content type was not JSON). "), text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
1393
|
-
process.exit(1);
|
|
1394
|
-
}
|
|
1395
|
-
const json = await response.json();
|
|
1396
|
-
if (json) {
|
|
1397
|
-
token = json.nonce;
|
|
1398
|
-
url = json.url;
|
|
1399
|
-
}
|
|
1400
|
-
if (!token || !url) {
|
|
1401
|
-
console.error(pc__default["default"].red("Unexpected response from the server."), json);
|
|
1402
|
-
process.exit(1);
|
|
1403
|
-
}
|
|
1407
|
+
const {
|
|
1408
|
+
nonce,
|
|
1409
|
+
url
|
|
1410
|
+
} = await server.startValLogin();
|
|
1404
1411
|
console.log(pc__default["default"].green("Open the following URL in your browser to log in:"));
|
|
1405
1412
|
console.log(pc__default["default"].underline(pc__default["default"].blue(url)));
|
|
1406
1413
|
console.log(pc__default["default"].dim("\nWaiting for login confirmation...\n"));
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
// Step 3: Save the token
|
|
1412
|
-
const filePath = server.getPersonalAccessTokenPath(options.root || process.cwd());
|
|
1413
|
-
saveToken(result, filePath);
|
|
1414
|
+
const result = await server.awaitValLoginConfirmation(nonce);
|
|
1415
|
+
const filePath = server.persistPersonalAccessToken(options.root || process.cwd(), result);
|
|
1416
|
+
console.log(pc__default["default"].green(`Token for ${pc__default["default"].cyan(result.profile.email)} saved to ${pc__default["default"].cyan(filePath)}`));
|
|
1414
1417
|
} catch (error) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
const MAX_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
1420
|
-
async function pollForConfirmation(token) {
|
|
1421
|
-
const start = Date.now();
|
|
1422
|
-
while (Date.now() - start < MAX_DURATION) {
|
|
1423
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1424
|
-
const response = await fetch(`${host}/api/login?token=${token}&consume=true`, {
|
|
1425
|
-
method: "POST"
|
|
1426
|
-
});
|
|
1427
|
-
if (response.status === 500) {
|
|
1428
|
-
console.error(pc__default["default"].red("An error occurred on the server."));
|
|
1418
|
+
if (error instanceof server.ValLoginError) {
|
|
1419
|
+
console.error(pc__default["default"].red(error.message), error.details ?? "");
|
|
1429
1420
|
process.exit(1);
|
|
1430
1421
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if (json) {
|
|
1434
|
-
if (typeof json.profile.email === "string" && typeof json.pat === "string") {
|
|
1435
|
-
return json;
|
|
1436
|
-
} else {
|
|
1437
|
-
console.error(pc__default["default"].red("Unexpected response from the server."));
|
|
1438
|
-
process.exit(1);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1422
|
+
console.error(pc__default["default"].red("An error occurred during the login process. Check your internet connection. Details:"), error instanceof Error ? error.message : JSON.stringify(error, null, 2));
|
|
1423
|
+
process.exit(1);
|
|
1442
1424
|
}
|
|
1443
|
-
console.error(pc__default["default"].red("Login confirmation timed out."));
|
|
1444
|
-
process.exit(1);
|
|
1445
1425
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Start the Val language server.
|
|
1429
|
+
*
|
|
1430
|
+
* Editors normally resolve `@valbuild/language-server` from the project's
|
|
1431
|
+
* `node_modules` and launch its `val-language-server` binary directly. This
|
|
1432
|
+
* subcommand exists so the server is discoverable from the CLI and easy to
|
|
1433
|
+
* start by hand when debugging an editor integration:
|
|
1434
|
+
*
|
|
1435
|
+
* npx val lsp --stdio
|
|
1436
|
+
*
|
|
1437
|
+
* The import is dynamic so that `vscode-languageserver` is not loaded on every
|
|
1438
|
+
* `val` invocation.
|
|
1439
|
+
*/
|
|
1440
|
+
async function lsp() {
|
|
1441
|
+
const {
|
|
1442
|
+
main
|
|
1443
|
+
} = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@valbuild/language-server')); });
|
|
1444
|
+
// Transport (`--stdio`, `--node-ipc`, `--socket=`) is read from argv by the
|
|
1445
|
+
// server itself, so the flags pass straight through.
|
|
1446
|
+
main();
|
|
1452
1447
|
}
|
|
1453
1448
|
|
|
1454
1449
|
/**
|
|
@@ -1465,10 +1460,10 @@ class DebugContextError extends Error {}
|
|
|
1465
1460
|
async function createDebugContext(options) {
|
|
1466
1461
|
var _config$files;
|
|
1467
1462
|
const projectRoot = options.root ? path__default["default"].resolve(options.root) : process.cwd();
|
|
1468
|
-
if (!fs__default
|
|
1463
|
+
if (!fs__default["default"].existsSync(projectRoot)) {
|
|
1469
1464
|
throw new DebugContextError(`Project root does not exist: ${projectRoot}`);
|
|
1470
1465
|
}
|
|
1471
|
-
const config =
|
|
1466
|
+
const config = await server.findAndEvalValConfigFile(projectRoot);
|
|
1472
1467
|
if (!config) {
|
|
1473
1468
|
throw new DebugContextError(`Could not find val.config.ts nor val.config.js in: ${projectRoot}`);
|
|
1474
1469
|
}
|
|
@@ -1535,8 +1530,8 @@ async function createDebugContext(options) {
|
|
|
1535
1530
|
*/
|
|
1536
1531
|
function readAuth(projectRoot) {
|
|
1537
1532
|
const patFile = server.getPersonalAccessTokenPath(projectRoot);
|
|
1538
|
-
if (fs__default
|
|
1539
|
-
const contents = fs__default
|
|
1533
|
+
if (fs__default["default"].existsSync(patFile)) {
|
|
1534
|
+
const contents = fs__default["default"].readFileSync(patFile, "utf-8");
|
|
1540
1535
|
const parsed = server.parsePersonalAccessTokenFile(contents);
|
|
1541
1536
|
if (parsed.success) {
|
|
1542
1537
|
return {
|
|
@@ -2083,7 +2078,7 @@ function readProjectValVersions(projectRoot) {
|
|
|
2083
2078
|
function readLocalFile(projectRoot, projectRelativePath) {
|
|
2084
2079
|
const absPath = path__default["default"].join(projectRoot, projectRelativePath);
|
|
2085
2080
|
try {
|
|
2086
|
-
return fs__default
|
|
2081
|
+
return fs__default["default"].readFileSync(absPath, "utf-8");
|
|
2087
2082
|
} catch {
|
|
2088
2083
|
return null;
|
|
2089
2084
|
}
|
|
@@ -2225,10 +2220,10 @@ async function writeZip(outPath, entries) {
|
|
|
2225
2220
|
type: "nodebuffer",
|
|
2226
2221
|
compression: "DEFLATE"
|
|
2227
2222
|
});
|
|
2228
|
-
fs__default
|
|
2223
|
+
fs__default["default"].mkdirSync(path__default["default"].dirname(outPath), {
|
|
2229
2224
|
recursive: true
|
|
2230
2225
|
});
|
|
2231
|
-
fs__default
|
|
2226
|
+
fs__default["default"].writeFileSync(outPath, buffer);
|
|
2232
2227
|
}
|
|
2233
2228
|
function timestamp() {
|
|
2234
2229
|
return new Date().toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -2365,6 +2360,7 @@ async function main() {
|
|
|
2365
2360
|
files
|
|
2366
2361
|
connect
|
|
2367
2362
|
versions
|
|
2363
|
+
lsp
|
|
2368
2364
|
debug
|
|
2369
2365
|
delete-unappliable-patches
|
|
2370
2366
|
|
|
@@ -2394,6 +2390,13 @@ async function main() {
|
|
|
2394
2390
|
Options:
|
|
2395
2391
|
--root [root], -r [root] Set project root directory (default process.cwd())
|
|
2396
2392
|
|
|
2393
|
+
Command: lsp
|
|
2394
|
+
Description: start the Val language server (used by editor integrations)
|
|
2395
|
+
Options:
|
|
2396
|
+
--stdio Communicate over stdin/stdout
|
|
2397
|
+
--node-ipc Communicate over Node IPC
|
|
2398
|
+
--socket=[port] Communicate over a socket
|
|
2399
|
+
|
|
2397
2400
|
Command: debug
|
|
2398
2401
|
Description: Create a self-contained snapshot (the pending patches plus the modules they
|
|
2399
2402
|
touch or reference) that Val developers can replay to reproduce validation and save
|
|
@@ -2423,11 +2426,6 @@ async function main() {
|
|
|
2423
2426
|
--verbose List every pending patch, not just the failing ones
|
|
2424
2427
|
`, {
|
|
2425
2428
|
flags: {
|
|
2426
|
-
port: {
|
|
2427
|
-
type: "number",
|
|
2428
|
-
alias: "p",
|
|
2429
|
-
default: 4123
|
|
2430
|
-
},
|
|
2431
2429
|
root: {
|
|
2432
2430
|
type: "string",
|
|
2433
2431
|
alias: "r"
|
|
@@ -2489,6 +2487,8 @@ async function main() {
|
|
|
2489
2487
|
});
|
|
2490
2488
|
case "versions":
|
|
2491
2489
|
return versions();
|
|
2490
|
+
case "lsp":
|
|
2491
|
+
return lsp();
|
|
2492
2492
|
case "debug":
|
|
2493
2493
|
return debug({
|
|
2494
2494
|
root: flags.root,
|