@valbuild/cli 0.73.0 → 0.73.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,9 @@ var fastGlob = require('fast-glob');
9
9
  var picocolors = require('picocolors');
10
10
  var eslint = require('eslint');
11
11
  var fs = require('fs/promises');
12
+ var vm = require('node:vm');
13
+ var ts = require('typescript');
14
+ var z = require('zod');
12
15
  var fs$1 = require('fs');
13
16
 
14
17
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -36,23 +39,102 @@ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
36
39
  var path__default = /*#__PURE__*/_interopDefault(path);
37
40
  var picocolors__default = /*#__PURE__*/_interopDefault(picocolors);
38
41
  var fs__default = /*#__PURE__*/_interopDefault(fs);
42
+ var vm__default = /*#__PURE__*/_interopDefault(vm);
43
+ var ts__default = /*#__PURE__*/_interopDefault(ts);
44
+ var z__default = /*#__PURE__*/_interopDefault(z);
39
45
  var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
40
46
 
41
47
  function error(message) {
42
48
  console.error(chalk__default["default"].red("❌Error: ") + message);
43
49
  }
44
50
 
51
+ const ValConfigSchema = z__default["default"].object({
52
+ project: z__default["default"].string().optional(),
53
+ root: z__default["default"].string().optional(),
54
+ files: z__default["default"].object({
55
+ directory: z__default["default"].string().refine(val => val.startsWith("/public/val"), {
56
+ message: "files.directory must start with '/public/val'"
57
+ })
58
+ }).optional(),
59
+ gitCommit: z__default["default"].string().optional(),
60
+ gitBranch: z__default["default"].string().optional(),
61
+ defaultTheme: z__default["default"].union([z__default["default"].literal("light"), z__default["default"].literal("dark")]).optional(),
62
+ ai: z__default["default"].object({
63
+ commitMessages: z__default["default"].object({
64
+ disabled: z__default["default"].boolean().optional()
65
+ }).optional()
66
+ }).optional()
67
+ });
68
+ async function evalValConfigFile(projectRoot, configFileName) {
69
+ const valConfigPath = path__default["default"].join(projectRoot, configFileName);
70
+ let code = null;
71
+ try {
72
+ code = await fs__default["default"].readFile(valConfigPath, "utf-8");
73
+ } catch (err) {
74
+ //
75
+ }
76
+ if (!code) {
77
+ return null;
78
+ }
79
+ const transpiled = ts__default["default"].transpileModule(code, {
80
+ compilerOptions: {
81
+ target: ts__default["default"].ScriptTarget.ES2020,
82
+ module: ts__default["default"].ModuleKind.CommonJS,
83
+ esModuleInterop: true
84
+ },
85
+ fileName: valConfigPath
86
+ });
87
+ const exportsObj = {};
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const sandbox = {
90
+ exports: exportsObj,
91
+ module: {
92
+ exports: exportsObj
93
+ },
94
+ require,
95
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
96
+ __filename: valConfigPath,
97
+ __dirname: path__default["default"].dirname(valConfigPath),
98
+ console,
99
+ process
100
+ };
101
+ sandbox.global = sandbox;
102
+ const context = vm__default["default"].createContext(sandbox);
103
+ const script = new vm__default["default"].Script(transpiled.outputText, {
104
+ filename: valConfigPath
105
+ });
106
+ script.runInContext(context);
107
+ const valConfig = sandbox.module.exports.config;
108
+ if (!valConfig) {
109
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
110
+ }
111
+ const result = ValConfigSchema.safeParse(valConfig);
112
+ if (!result.success) {
113
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
114
+ }
115
+ return result.data;
116
+ }
117
+
118
+ function getFileExt(filePath) {
119
+ // 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).
120
+ return filePath.split(".").pop() || "";
121
+ }
122
+
123
+ const textEncoder = new TextEncoder();
45
124
  async function validate({
46
125
  root,
47
126
  fix,
48
127
  noEslint
49
128
  }) {
50
129
  const valRemoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST;
130
+ const contentHostUrl = process.env.VAL_CONTENT_URL || "https://content.val.build";
51
131
  const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
52
132
  const eslint$1 = new eslint.ESLint({
53
133
  cwd: projectRoot,
54
134
  ignore: false
55
135
  });
136
+ const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
137
+ console.log(picocolors__default["default"].greenBright(`Validating project${valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project ? ` '${picocolors__default["default"].inverse(valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project)}'` : ""}...`));
56
138
  const service = await server.createService(projectRoot, {});
57
139
  const checkKeyIsValid = async (key, sourcePath) => {
58
140
  const [moduleFilePath, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
@@ -120,7 +202,7 @@ async function validate({
120
202
  });
121
203
  console.log(errors === 0 ? picocolors__default["default"].green("✔") : picocolors__default["default"].red("✘"), "ESlint complete", lintFiles.length, "files");
122
204
  }
123
- console.log("Validating...", valFiles.length, "files");
205
+ console.log(picocolors__default["default"].greenBright(`Found ${valFiles.length} files...`));
124
206
  let publicProjectId;
125
207
  let didFix = false; // TODO: ugly
126
208
  async function validateFile(file) {
@@ -208,7 +290,6 @@ async function validate({
208
290
  if (valModule.source && valModule.schema) {
209
291
  const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
210
292
  let filePath = null;
211
- console.log(sourcePath, resolvedRemoteFileAtSourcePath.source);
212
293
  try {
213
294
  var _resolvedRemoteFileAt;
214
295
  filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -245,8 +326,7 @@ async function validate({
245
326
  console.log(picocolors__default["default"].yellow("⚠"), `Remote file ${filePath} already uploaded`);
246
327
  continue;
247
328
  }
248
- // TODO: parallelize this:
249
- console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file ${filePath}...`);
329
+ // TODO: parallelize uploading files
250
330
  if (!resolvedRemoteFileAtSourcePath.schema) {
251
331
  console.log(picocolors__default["default"].red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
252
332
  errors += 1;
@@ -263,31 +343,7 @@ async function validate({
263
343
  if (!publicProjectId || !remoteFileBuckets) {
264
344
  let projectName = process.env.VAL_PROJECT;
265
345
  if (!projectName) {
266
- try {
267
- var _require;
268
- // eslint-disable-next-line @typescript-eslint/no-var-requires
269
- projectName = (_require = require(`${root}/val.config`)) === null || _require === void 0 || (_require = _require.config) === null || _require === void 0 ? void 0 : _require.project;
270
- } catch {
271
- // ignore
272
- }
273
- }
274
- if (!projectName) {
275
- try {
276
- var _require2;
277
- // eslint-disable-next-line @typescript-eslint/no-var-requires
278
- projectName = (_require2 = require(`${root}/val.config.ts`)) === null || _require2 === void 0 || (_require2 = _require2.config) === null || _require2 === void 0 ? void 0 : _require2.project;
279
- } catch {
280
- // ignore
281
- }
282
- }
283
- if (!projectName) {
284
- try {
285
- var _require3;
286
- // eslint-disable-next-line @typescript-eslint/no-var-requires
287
- projectName = (_require3 = require(`${root}/val.config.js`)) === null || _require3 === void 0 || (_require3 = _require3.config) === null || _require3 === void 0 ? void 0 : _require3.project;
288
- } catch {
289
- // ignore
290
- }
346
+ projectName = valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project;
291
347
  }
292
348
  if (!projectName) {
293
349
  console.log(picocolors__default["default"].red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
@@ -310,6 +366,11 @@ async function validate({
310
366
  errors += 1;
311
367
  continue;
312
368
  }
369
+ if (!(valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project)) {
370
+ console.log(picocolors__default["default"].red("✘"), `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`);
371
+ errors += 1;
372
+ continue;
373
+ }
313
374
  if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
314
375
  console.log(picocolors__default["default"].red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
315
376
  }
@@ -334,17 +395,31 @@ async function validate({
334
395
  errors += 1;
335
396
  continue;
336
397
  }
337
- const remoteFileUpload = await server.uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
398
+ const fileHash = core.Internal.remote.getFileHash(fileBuffer);
399
+ const coreVersion = core.Internal.VERSION.core || "unknown";
400
+ const fileExt = getFileExt(filePath);
401
+ const schema = resolveRemoteFileSchema;
402
+ const metadata = fileSourceMetadata;
403
+ const ref = core.Internal.remote.createRemoteRef(valRemoteHost, {
404
+ publicProjectId,
405
+ coreVersion,
406
+ bucket,
407
+ validationHash: core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, metadata, fileHash, textEncoder),
408
+ fileHash,
409
+ filePath: relativeFilePath
410
+ });
411
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file: '${ref}'...`);
412
+ const remoteFileUpload = await server.uploadRemoteFile(contentHostUrl, valConfigFile.project, bucket, fileHash, fileExt, fileBuffer, {
338
413
  pat
339
414
  });
340
415
  if (!remoteFileUpload.success) {
341
- console.log(picocolors__default["default"].red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
416
+ console.log(picocolors__default["default"].red("✘"), `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`);
342
417
  errors += 1;
343
418
  continue;
344
419
  }
345
- console.log(picocolors__default["default"].yellow(""), `Uploaded remote file ${filePath}`);
420
+ console.log(picocolors__default["default"].green(""), `Completed upload of remote file: '${ref}'`);
346
421
  remoteFiles[sourcePath] = {
347
- ref: remoteFileUpload.ref,
422
+ ref,
348
423
  metadata: fileSourceMetadata
349
424
  };
350
425
  }
@@ -9,6 +9,9 @@ var fastGlob = require('fast-glob');
9
9
  var picocolors = require('picocolors');
10
10
  var eslint = require('eslint');
11
11
  var fs = require('fs/promises');
12
+ var vm = require('node:vm');
13
+ var ts = require('typescript');
14
+ var z = require('zod');
12
15
  var fs$1 = require('fs');
13
16
 
14
17
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -36,23 +39,102 @@ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
36
39
  var path__default = /*#__PURE__*/_interopDefault(path);
37
40
  var picocolors__default = /*#__PURE__*/_interopDefault(picocolors);
38
41
  var fs__default = /*#__PURE__*/_interopDefault(fs);
42
+ var vm__default = /*#__PURE__*/_interopDefault(vm);
43
+ var ts__default = /*#__PURE__*/_interopDefault(ts);
44
+ var z__default = /*#__PURE__*/_interopDefault(z);
39
45
  var fs__default$1 = /*#__PURE__*/_interopDefault(fs$1);
40
46
 
41
47
  function error(message) {
42
48
  console.error(chalk__default["default"].red("❌Error: ") + message);
43
49
  }
44
50
 
51
+ const ValConfigSchema = z__default["default"].object({
52
+ project: z__default["default"].string().optional(),
53
+ root: z__default["default"].string().optional(),
54
+ files: z__default["default"].object({
55
+ directory: z__default["default"].string().refine(val => val.startsWith("/public/val"), {
56
+ message: "files.directory must start with '/public/val'"
57
+ })
58
+ }).optional(),
59
+ gitCommit: z__default["default"].string().optional(),
60
+ gitBranch: z__default["default"].string().optional(),
61
+ defaultTheme: z__default["default"].union([z__default["default"].literal("light"), z__default["default"].literal("dark")]).optional(),
62
+ ai: z__default["default"].object({
63
+ commitMessages: z__default["default"].object({
64
+ disabled: z__default["default"].boolean().optional()
65
+ }).optional()
66
+ }).optional()
67
+ });
68
+ async function evalValConfigFile(projectRoot, configFileName) {
69
+ const valConfigPath = path__default["default"].join(projectRoot, configFileName);
70
+ let code = null;
71
+ try {
72
+ code = await fs__default["default"].readFile(valConfigPath, "utf-8");
73
+ } catch (err) {
74
+ //
75
+ }
76
+ if (!code) {
77
+ return null;
78
+ }
79
+ const transpiled = ts__default["default"].transpileModule(code, {
80
+ compilerOptions: {
81
+ target: ts__default["default"].ScriptTarget.ES2020,
82
+ module: ts__default["default"].ModuleKind.CommonJS,
83
+ esModuleInterop: true
84
+ },
85
+ fileName: valConfigPath
86
+ });
87
+ const exportsObj = {};
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const sandbox = {
90
+ exports: exportsObj,
91
+ module: {
92
+ exports: exportsObj
93
+ },
94
+ require,
95
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
96
+ __filename: valConfigPath,
97
+ __dirname: path__default["default"].dirname(valConfigPath),
98
+ console,
99
+ process
100
+ };
101
+ sandbox.global = sandbox;
102
+ const context = vm__default["default"].createContext(sandbox);
103
+ const script = new vm__default["default"].Script(transpiled.outputText, {
104
+ filename: valConfigPath
105
+ });
106
+ script.runInContext(context);
107
+ const valConfig = sandbox.module.exports.config;
108
+ if (!valConfig) {
109
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
110
+ }
111
+ const result = ValConfigSchema.safeParse(valConfig);
112
+ if (!result.success) {
113
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
114
+ }
115
+ return result.data;
116
+ }
117
+
118
+ function getFileExt(filePath) {
119
+ // 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).
120
+ return filePath.split(".").pop() || "";
121
+ }
122
+
123
+ const textEncoder = new TextEncoder();
45
124
  async function validate({
46
125
  root,
47
126
  fix,
48
127
  noEslint
49
128
  }) {
50
129
  const valRemoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST;
130
+ const contentHostUrl = process.env.VAL_CONTENT_URL || "https://content.val.build";
51
131
  const projectRoot = root ? path__default["default"].resolve(root) : process.cwd();
52
132
  const eslint$1 = new eslint.ESLint({
53
133
  cwd: projectRoot,
54
134
  ignore: false
55
135
  });
136
+ const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
137
+ console.log(picocolors__default["default"].greenBright(`Validating project${valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project ? ` '${picocolors__default["default"].inverse(valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project)}'` : ""}...`));
56
138
  const service = await server.createService(projectRoot, {});
57
139
  const checkKeyIsValid = async (key, sourcePath) => {
58
140
  const [moduleFilePath, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
@@ -120,7 +202,7 @@ async function validate({
120
202
  });
121
203
  console.log(errors === 0 ? picocolors__default["default"].green("✔") : picocolors__default["default"].red("✘"), "ESlint complete", lintFiles.length, "files");
122
204
  }
123
- console.log("Validating...", valFiles.length, "files");
205
+ console.log(picocolors__default["default"].greenBright(`Found ${valFiles.length} files...`));
124
206
  let publicProjectId;
125
207
  let didFix = false; // TODO: ugly
126
208
  async function validateFile(file) {
@@ -208,7 +290,6 @@ async function validate({
208
290
  if (valModule.source && valModule.schema) {
209
291
  const resolvedRemoteFileAtSourcePath = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
210
292
  let filePath = null;
211
- console.log(sourcePath, resolvedRemoteFileAtSourcePath.source);
212
293
  try {
213
294
  var _resolvedRemoteFileAt;
214
295
  filePath = path__default["default"].join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -245,8 +326,7 @@ async function validate({
245
326
  console.log(picocolors__default["default"].yellow("⚠"), `Remote file ${filePath} already uploaded`);
246
327
  continue;
247
328
  }
248
- // TODO: parallelize this:
249
- console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file ${filePath}...`);
329
+ // TODO: parallelize uploading files
250
330
  if (!resolvedRemoteFileAtSourcePath.schema) {
251
331
  console.log(picocolors__default["default"].red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
252
332
  errors += 1;
@@ -263,31 +343,7 @@ async function validate({
263
343
  if (!publicProjectId || !remoteFileBuckets) {
264
344
  let projectName = process.env.VAL_PROJECT;
265
345
  if (!projectName) {
266
- try {
267
- var _require;
268
- // eslint-disable-next-line @typescript-eslint/no-var-requires
269
- projectName = (_require = require(`${root}/val.config`)) === null || _require === void 0 || (_require = _require.config) === null || _require === void 0 ? void 0 : _require.project;
270
- } catch {
271
- // ignore
272
- }
273
- }
274
- if (!projectName) {
275
- try {
276
- var _require2;
277
- // eslint-disable-next-line @typescript-eslint/no-var-requires
278
- projectName = (_require2 = require(`${root}/val.config.ts`)) === null || _require2 === void 0 || (_require2 = _require2.config) === null || _require2 === void 0 ? void 0 : _require2.project;
279
- } catch {
280
- // ignore
281
- }
282
- }
283
- if (!projectName) {
284
- try {
285
- var _require3;
286
- // eslint-disable-next-line @typescript-eslint/no-var-requires
287
- projectName = (_require3 = require(`${root}/val.config.js`)) === null || _require3 === void 0 || (_require3 = _require3.config) === null || _require3 === void 0 ? void 0 : _require3.project;
288
- } catch {
289
- // ignore
290
- }
346
+ projectName = valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project;
291
347
  }
292
348
  if (!projectName) {
293
349
  console.log(picocolors__default["default"].red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
@@ -310,6 +366,11 @@ async function validate({
310
366
  errors += 1;
311
367
  continue;
312
368
  }
369
+ if (!(valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project)) {
370
+ console.log(picocolors__default["default"].red("✘"), `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`);
371
+ errors += 1;
372
+ continue;
373
+ }
313
374
  if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
314
375
  console.log(picocolors__default["default"].red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
315
376
  }
@@ -334,17 +395,31 @@ async function validate({
334
395
  errors += 1;
335
396
  continue;
336
397
  }
337
- const remoteFileUpload = await server.uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
398
+ const fileHash = core.Internal.remote.getFileHash(fileBuffer);
399
+ const coreVersion = core.Internal.VERSION.core || "unknown";
400
+ const fileExt = getFileExt(filePath);
401
+ const schema = resolveRemoteFileSchema;
402
+ const metadata = fileSourceMetadata;
403
+ const ref = core.Internal.remote.createRemoteRef(valRemoteHost, {
404
+ publicProjectId,
405
+ coreVersion,
406
+ bucket,
407
+ validationHash: core.Internal.remote.getValidationHash(coreVersion, schema, fileExt, metadata, fileHash, textEncoder),
408
+ fileHash,
409
+ filePath: relativeFilePath
410
+ });
411
+ console.log(picocolors__default["default"].yellow("⚠"), `Uploading remote file: '${ref}'...`);
412
+ const remoteFileUpload = await server.uploadRemoteFile(contentHostUrl, valConfigFile.project, bucket, fileHash, fileExt, fileBuffer, {
338
413
  pat
339
414
  });
340
415
  if (!remoteFileUpload.success) {
341
- console.log(picocolors__default["default"].red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
416
+ console.log(picocolors__default["default"].red("✘"), `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`);
342
417
  errors += 1;
343
418
  continue;
344
419
  }
345
- console.log(picocolors__default["default"].yellow(""), `Uploaded remote file ${filePath}`);
420
+ console.log(picocolors__default["default"].green(""), `Completed upload of remote file: '${ref}'`);
346
421
  remoteFiles[sourcePath] = {
347
- ref: remoteFileUpload.ref,
422
+ ref,
348
423
  metadata: fileSourceMetadata
349
424
  };
350
425
  }
@@ -7,23 +7,102 @@ import { glob } from 'fast-glob';
7
7
  import picocolors from 'picocolors';
8
8
  import { ESLint } from 'eslint';
9
9
  import fs from 'fs/promises';
10
+ import vm from 'node:vm';
11
+ import ts from 'typescript';
12
+ import z from 'zod';
10
13
  import fs$1 from 'fs';
11
14
 
12
15
  function error(message) {
13
16
  console.error(chalk.red("❌Error: ") + message);
14
17
  }
15
18
 
19
+ const ValConfigSchema = z.object({
20
+ project: z.string().optional(),
21
+ root: z.string().optional(),
22
+ files: z.object({
23
+ directory: z.string().refine(val => val.startsWith("/public/val"), {
24
+ message: "files.directory must start with '/public/val'"
25
+ })
26
+ }).optional(),
27
+ gitCommit: z.string().optional(),
28
+ gitBranch: z.string().optional(),
29
+ defaultTheme: z.union([z.literal("light"), z.literal("dark")]).optional(),
30
+ ai: z.object({
31
+ commitMessages: z.object({
32
+ disabled: z.boolean().optional()
33
+ }).optional()
34
+ }).optional()
35
+ });
36
+ async function evalValConfigFile(projectRoot, configFileName) {
37
+ const valConfigPath = path.join(projectRoot, configFileName);
38
+ let code = null;
39
+ try {
40
+ code = await fs.readFile(valConfigPath, "utf-8");
41
+ } catch (err) {
42
+ //
43
+ }
44
+ if (!code) {
45
+ return null;
46
+ }
47
+ const transpiled = ts.transpileModule(code, {
48
+ compilerOptions: {
49
+ target: ts.ScriptTarget.ES2020,
50
+ module: ts.ModuleKind.CommonJS,
51
+ esModuleInterop: true
52
+ },
53
+ fileName: valConfigPath
54
+ });
55
+ const exportsObj = {};
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const sandbox = {
58
+ exports: exportsObj,
59
+ module: {
60
+ exports: exportsObj
61
+ },
62
+ require,
63
+ // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
64
+ __filename: valConfigPath,
65
+ __dirname: path.dirname(valConfigPath),
66
+ console,
67
+ process
68
+ };
69
+ sandbox.global = sandbox;
70
+ const context = vm.createContext(sandbox);
71
+ const script = new vm.Script(transpiled.outputText, {
72
+ filename: valConfigPath
73
+ });
74
+ script.runInContext(context);
75
+ const valConfig = sandbox.module.exports.config;
76
+ if (!valConfig) {
77
+ throw Error(`Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`);
78
+ }
79
+ const result = ValConfigSchema.safeParse(valConfig);
80
+ if (!result.success) {
81
+ throw Error(`Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`);
82
+ }
83
+ return result.data;
84
+ }
85
+
86
+ function getFileExt(filePath) {
87
+ // 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).
88
+ return filePath.split(".").pop() || "";
89
+ }
90
+
91
+ const textEncoder = new TextEncoder();
16
92
  async function validate({
17
93
  root,
18
94
  fix,
19
95
  noEslint
20
96
  }) {
21
97
  const valRemoteHost = process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST;
98
+ const contentHostUrl = process.env.VAL_CONTENT_URL || "https://content.val.build";
22
99
  const projectRoot = root ? path.resolve(root) : process.cwd();
23
100
  const eslint = new ESLint({
24
101
  cwd: projectRoot,
25
102
  ignore: false
26
103
  });
104
+ const valConfigFile = (await evalValConfigFile(projectRoot, "val.config.ts")) || (await evalValConfigFile(projectRoot, "val.config.js"));
105
+ console.log(picocolors.greenBright(`Validating project${valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project ? ` '${picocolors.inverse(valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project)}'` : ""}...`));
27
106
  const service = await createService(projectRoot, {});
28
107
  const checkKeyIsValid = async (key, sourcePath) => {
29
108
  const [moduleFilePath, modulePath] = Internal.splitModuleFilePathAndModulePath(sourcePath);
@@ -91,7 +170,7 @@ async function validate({
91
170
  });
92
171
  console.log(errors === 0 ? picocolors.green("✔") : picocolors.red("✘"), "ESlint complete", lintFiles.length, "files");
93
172
  }
94
- console.log("Validating...", valFiles.length, "files");
173
+ console.log(picocolors.greenBright(`Found ${valFiles.length} files...`));
95
174
  let publicProjectId;
96
175
  let didFix = false; // TODO: ugly
97
176
  async function validateFile(file) {
@@ -179,7 +258,6 @@ async function validate({
179
258
  if (valModule.source && valModule.schema) {
180
259
  const resolvedRemoteFileAtSourcePath = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
181
260
  let filePath = null;
182
- console.log(sourcePath, resolvedRemoteFileAtSourcePath.source);
183
261
  try {
184
262
  var _resolvedRemoteFileAt;
185
263
  filePath = path.join(projectRoot, // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -216,8 +294,7 @@ async function validate({
216
294
  console.log(picocolors.yellow("⚠"), `Remote file ${filePath} already uploaded`);
217
295
  continue;
218
296
  }
219
- // TODO: parallelize this:
220
- console.log(picocolors.yellow("⚠"), `Uploading remote file ${filePath}...`);
297
+ // TODO: parallelize uploading files
221
298
  if (!resolvedRemoteFileAtSourcePath.schema) {
222
299
  console.log(picocolors.red("✘"), `Cannot upload remote file: schema not found for ${sourcePath}`);
223
300
  errors += 1;
@@ -234,31 +311,7 @@ async function validate({
234
311
  if (!publicProjectId || !remoteFileBuckets) {
235
312
  let projectName = process.env.VAL_PROJECT;
236
313
  if (!projectName) {
237
- try {
238
- var _require;
239
- // eslint-disable-next-line @typescript-eslint/no-var-requires
240
- projectName = (_require = require(`${root}/val.config`)) === null || _require === void 0 || (_require = _require.config) === null || _require === void 0 ? void 0 : _require.project;
241
- } catch {
242
- // ignore
243
- }
244
- }
245
- if (!projectName) {
246
- try {
247
- var _require2;
248
- // eslint-disable-next-line @typescript-eslint/no-var-requires
249
- projectName = (_require2 = require(`${root}/val.config.ts`)) === null || _require2 === void 0 || (_require2 = _require2.config) === null || _require2 === void 0 ? void 0 : _require2.project;
250
- } catch {
251
- // ignore
252
- }
253
- }
254
- if (!projectName) {
255
- try {
256
- var _require3;
257
- // eslint-disable-next-line @typescript-eslint/no-var-requires
258
- projectName = (_require3 = require(`${root}/val.config.js`)) === null || _require3 === void 0 || (_require3 = _require3.config) === null || _require3 === void 0 ? void 0 : _require3.project;
259
- } catch {
260
- // ignore
261
- }
314
+ projectName = valConfigFile === null || valConfigFile === void 0 ? void 0 : valConfigFile.project;
262
315
  }
263
316
  if (!projectName) {
264
317
  console.log(picocolors.red("✘"), "Project name not found. Set VAL_PROJECT environment variable or add project name to val.config");
@@ -281,6 +334,11 @@ async function validate({
281
334
  errors += 1;
282
335
  continue;
283
336
  }
337
+ if (!(valConfigFile !== null && valConfigFile !== void 0 && valConfigFile.project)) {
338
+ console.log(picocolors.red("✘"), `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`);
339
+ errors += 1;
340
+ continue;
341
+ }
284
342
  if (resolveRemoteFileSchema.type !== "image" && resolveRemoteFileSchema.type !== "file") {
285
343
  console.log(picocolors.red("✘"), `The schema is the remote is neither image nor file: ${sourcePath}`);
286
344
  }
@@ -305,17 +363,31 @@ async function validate({
305
363
  errors += 1;
306
364
  continue;
307
365
  }
308
- const remoteFileUpload = await uploadRemoteFile(valRemoteHost, fileBuffer, publicProjectId, bucket, relativeFilePath, resolveRemoteFileSchema, fileSourceMetadata, {
366
+ const fileHash = Internal.remote.getFileHash(fileBuffer);
367
+ const coreVersion = Internal.VERSION.core || "unknown";
368
+ const fileExt = getFileExt(filePath);
369
+ const schema = resolveRemoteFileSchema;
370
+ const metadata = fileSourceMetadata;
371
+ const ref = Internal.remote.createRemoteRef(valRemoteHost, {
372
+ publicProjectId,
373
+ coreVersion,
374
+ bucket,
375
+ validationHash: Internal.remote.getValidationHash(coreVersion, schema, fileExt, metadata, fileHash, textEncoder),
376
+ fileHash,
377
+ filePath: relativeFilePath
378
+ });
379
+ console.log(picocolors.yellow("⚠"), `Uploading remote file: '${ref}'...`);
380
+ const remoteFileUpload = await uploadRemoteFile(contentHostUrl, valConfigFile.project, bucket, fileHash, fileExt, fileBuffer, {
309
381
  pat
310
382
  });
311
383
  if (!remoteFileUpload.success) {
312
- console.log(picocolors.red("✘"), `Error uploading remote file: ${remoteFileUpload.error}`);
384
+ console.log(picocolors.red("✘"), `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`);
313
385
  errors += 1;
314
386
  continue;
315
387
  }
316
- console.log(picocolors.yellow(""), `Uploaded remote file ${filePath}`);
388
+ console.log(picocolors.green(""), `Completed upload of remote file: '${ref}'`);
317
389
  remoteFiles[sourcePath] = {
318
- ref: remoteFileUpload.ref,
390
+ ref,
319
391
  metadata: fileSourceMetadata
320
392
  };
321
393
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@valbuild/cli",
3
3
  "private": false,
4
- "version": "0.73.0",
4
+ "version": "0.73.2",
5
5
  "description": "Val CLI tools",
6
6
  "bin": {
7
7
  "val": "./bin.js"
@@ -18,9 +18,9 @@
18
18
  "typecheck": "tsc --noEmit"
19
19
  },
20
20
  "dependencies": {
21
- "@valbuild/core": "~0.73.0",
22
- "@valbuild/server": "~0.73.0",
23
- "@valbuild/eslint-plugin": "~0.73.0",
21
+ "@valbuild/core": "~0.73.2",
22
+ "@valbuild/server": "~0.73.2",
23
+ "@valbuild/eslint-plugin": "~0.73.2",
24
24
  "eslint": "^8.31.0",
25
25
  "@inquirer/confirm": "^2.0.15",
26
26
  "@inquirer/prompts": "^3.0.2",
@@ -34,7 +34,8 @@
34
34
  "zod": "^3.22.4"
35
35
  },
36
36
  "peerDependencies": {
37
- "prettier": "*"
37
+ "prettier": "*",
38
+ "typescript": ">=5.0.0"
38
39
  },
39
40
  "preconstruct": {
40
41
  "entrypoints": [
@@ -0,0 +1,90 @@
1
+ import path from "path";
2
+ import fs from "fs/promises";
3
+ import vm from "node:vm";
4
+ import ts from "typescript"; // TODO: make this dependency optional (only required if the file is val.config.ts not val.config.js)
5
+ import z from "zod";
6
+ import { ValConfig } from "@valbuild/core";
7
+
8
+ const ValConfigSchema = z.object({
9
+ project: z.string().optional(),
10
+ root: z.string().optional(),
11
+ files: z
12
+ .object({
13
+ directory: z
14
+ .string()
15
+ .refine((val): val is `/public/val` => val.startsWith("/public/val"), {
16
+ message: "files.directory must start with '/public/val'",
17
+ }),
18
+ })
19
+ .optional(),
20
+ gitCommit: z.string().optional(),
21
+ gitBranch: z.string().optional(),
22
+ defaultTheme: z.union([z.literal("light"), z.literal("dark")]).optional(),
23
+ ai: z
24
+ .object({
25
+ commitMessages: z
26
+ .object({
27
+ disabled: z.boolean().optional(),
28
+ })
29
+ .optional(),
30
+ })
31
+ .optional(),
32
+ });
33
+
34
+ export async function evalValConfigFile(
35
+ projectRoot: string,
36
+ configFileName: string,
37
+ ): Promise<ValConfig | null> {
38
+ const valConfigPath = path.join(projectRoot, configFileName);
39
+
40
+ let code: string | null = null;
41
+ try {
42
+ code = await fs.readFile(valConfigPath, "utf-8");
43
+ } catch (err) {
44
+ //
45
+ }
46
+ if (!code) {
47
+ return null;
48
+ }
49
+
50
+ const transpiled = ts.transpileModule(code, {
51
+ compilerOptions: {
52
+ target: ts.ScriptTarget.ES2020,
53
+ module: ts.ModuleKind.CommonJS,
54
+ esModuleInterop: true,
55
+ },
56
+ fileName: valConfigPath,
57
+ });
58
+
59
+ const exportsObj = {};
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ const sandbox: Record<string, any> = {
62
+ exports: exportsObj,
63
+ module: { exports: exportsObj },
64
+ require, // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level
65
+ __filename: valConfigPath,
66
+ __dirname: path.dirname(valConfigPath),
67
+ console,
68
+ process,
69
+ };
70
+ sandbox.global = sandbox;
71
+
72
+ const context = vm.createContext(sandbox);
73
+ const script = new vm.Script(transpiled.outputText, {
74
+ filename: valConfigPath,
75
+ });
76
+ script.runInContext(context);
77
+ const valConfig = sandbox.module.exports.config;
78
+ if (!valConfig) {
79
+ throw Error(
80
+ `Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`,
81
+ );
82
+ }
83
+ const result = ValConfigSchema.safeParse(valConfig);
84
+ if (!result.success) {
85
+ throw Error(
86
+ `Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`,
87
+ );
88
+ }
89
+ return result.data;
90
+ }
package/src/validate.ts CHANGED
@@ -22,7 +22,10 @@ import { glob } from "fast-glob";
22
22
  import picocolors from "picocolors";
23
23
  import { ESLint } from "eslint";
24
24
  import fs from "fs/promises";
25
+ import { evalValConfigFile } from "./utils/evalValConfigFile";
26
+ import { getFileExt } from "./utils/getFileExt";
25
27
 
28
+ const textEncoder = new TextEncoder();
26
29
  export async function validate({
27
30
  root,
28
31
  fix,
@@ -33,11 +36,21 @@ export async function validate({
33
36
  noEslint?: boolean;
34
37
  }) {
35
38
  const valRemoteHost = process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST;
39
+ const contentHostUrl =
40
+ process.env.VAL_CONTENT_URL || "https://content.val.build";
36
41
  const projectRoot = root ? path.resolve(root) : process.cwd();
37
42
  const eslint = new ESLint({
38
43
  cwd: projectRoot,
39
44
  ignore: false,
40
45
  });
46
+ const valConfigFile =
47
+ (await evalValConfigFile(projectRoot, "val.config.ts")) ||
48
+ (await evalValConfigFile(projectRoot, "val.config.js"));
49
+ console.log(
50
+ picocolors.greenBright(
51
+ `Validating project${valConfigFile?.project ? ` '${picocolors.inverse(valConfigFile?.project)}'` : ""}...`,
52
+ ),
53
+ );
41
54
  const service = await createService(projectRoot, {});
42
55
  const checkKeyIsValid = async (
43
56
  key: string,
@@ -130,8 +143,7 @@ export async function validate({
130
143
  "files",
131
144
  );
132
145
  }
133
- console.log("Validating...", valFiles.length, "files");
134
-
146
+ console.log(picocolors.greenBright(`Found ${valFiles.length} files...`));
135
147
  let publicProjectId: string | undefined;
136
148
  let didFix = false; // TODO: ugly
137
149
  async function validateFile(file: string): Promise<number> {
@@ -287,10 +299,6 @@ export async function validate({
287
299
  valModule.schema,
288
300
  );
289
301
  let filePath: string | null = null;
290
- console.log(
291
- sourcePath,
292
- resolvedRemoteFileAtSourcePath.source,
293
- );
294
302
  try {
295
303
  filePath = path.join(
296
304
  projectRoot,
@@ -348,12 +356,7 @@ export async function validate({
348
356
  );
349
357
  continue;
350
358
  }
351
- // TODO: parallelize this:
352
- console.log(
353
- picocolors.yellow("⚠"),
354
- `Uploading remote file ${filePath}...`,
355
- );
356
-
359
+ // TODO: parallelize uploading files
357
360
  if (!resolvedRemoteFileAtSourcePath.schema) {
358
361
  console.log(
359
362
  picocolors.red("✘"),
@@ -383,31 +386,7 @@ export async function validate({
383
386
  if (!publicProjectId || !remoteFileBuckets) {
384
387
  let projectName = process.env.VAL_PROJECT;
385
388
  if (!projectName) {
386
- try {
387
- // eslint-disable-next-line @typescript-eslint/no-var-requires
388
- projectName = require(`${root}/val.config`)?.config
389
- ?.project;
390
- } catch {
391
- // ignore
392
- }
393
- }
394
- if (!projectName) {
395
- try {
396
- // eslint-disable-next-line @typescript-eslint/no-var-requires
397
- projectName = require(`${root}/val.config.ts`)?.config
398
- ?.project;
399
- } catch {
400
- // ignore
401
- }
402
- }
403
- if (!projectName) {
404
- try {
405
- // eslint-disable-next-line @typescript-eslint/no-var-requires
406
- projectName = require(`${root}/val.config.js`)?.config
407
- ?.project;
408
- } catch {
409
- // ignore
410
- }
389
+ projectName = valConfigFile?.project;
411
390
  }
412
391
  if (!projectName) {
413
392
  console.log(
@@ -440,6 +419,14 @@ export async function validate({
440
419
  errors += 1;
441
420
  continue;
442
421
  }
422
+ if (!valConfigFile?.project) {
423
+ console.log(
424
+ picocolors.red("✘"),
425
+ `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`,
426
+ );
427
+ errors += 1;
428
+ continue;
429
+ }
443
430
  if (
444
431
  resolveRemoteFileSchema.type !== "image" &&
445
432
  resolveRemoteFileSchema.type !== "file"
@@ -485,32 +472,57 @@ export async function validate({
485
472
  errors += 1;
486
473
  continue;
487
474
  }
488
- const remoteFileUpload = await uploadRemoteFile(
489
- valRemoteHost,
490
- fileBuffer,
475
+
476
+ const fileHash = Internal.remote.getFileHash(fileBuffer);
477
+ const coreVersion = Internal.VERSION.core || "unknown";
478
+ const fileExt = getFileExt(filePath);
479
+ const schema = resolveRemoteFileSchema as
480
+ | SerializedImageSchema
481
+ | SerializedFileSchema;
482
+ const metadata = fileSourceMetadata;
483
+ const ref = Internal.remote.createRemoteRef(valRemoteHost, {
491
484
  publicProjectId,
485
+ coreVersion,
492
486
  bucket,
493
- relativeFilePath,
494
- resolveRemoteFileSchema as
495
- | SerializedFileSchema
496
- | SerializedImageSchema,
497
- fileSourceMetadata,
487
+ validationHash: Internal.remote.getValidationHash(
488
+ coreVersion,
489
+ schema,
490
+ fileExt,
491
+ metadata,
492
+ fileHash,
493
+ textEncoder,
494
+ ),
495
+ fileHash,
496
+ filePath: relativeFilePath,
497
+ });
498
+ console.log(
499
+ picocolors.yellow("⚠"),
500
+ `Uploading remote file: '${ref}'...`,
501
+ );
502
+
503
+ const remoteFileUpload = await uploadRemoteFile(
504
+ contentHostUrl,
505
+ valConfigFile.project,
506
+ bucket,
507
+ fileHash,
508
+ fileExt,
509
+ fileBuffer,
498
510
  { pat },
499
511
  );
500
512
  if (!remoteFileUpload.success) {
501
513
  console.log(
502
514
  picocolors.red("✘"),
503
- `Error uploading remote file: ${remoteFileUpload.error}`,
515
+ `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`,
504
516
  );
505
517
  errors += 1;
506
518
  continue;
507
519
  }
508
520
  console.log(
509
- picocolors.yellow(""),
510
- `Uploaded remote file ${filePath}`,
521
+ picocolors.green(""),
522
+ `Completed upload of remote file: '${ref}'`,
511
523
  );
512
524
  remoteFiles[sourcePath as SourcePath] = {
513
- ref: remoteFileUpload.ref,
525
+ ref,
514
526
  metadata: fileSourceMetadata,
515
527
  };
516
528
  }