vize 0.41.0 → 0.42.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/src/config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { pathToFileURL } from "node:url";
3
+ import { execFileSync } from "node:child_process";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
5
  import { transform } from "oxc-transform";
5
6
  import type {
6
7
  VizeConfig,
@@ -11,18 +12,29 @@ import type {
11
12
  GlobalTypeDeclaration,
12
13
  } from "./types/index.js";
13
14
 
14
- const CONFIG_FILE_NAMES = [
15
+ export const CONFIG_FILE_NAMES = [
15
16
  "vize.config.ts",
16
17
  "vize.config.js",
17
18
  "vize.config.mjs",
19
+ "vize.config.pkl",
18
20
  "vize.config.json",
19
- ];
21
+ ] as const;
20
22
 
21
23
  const DEFAULT_CONFIG_ENV: ConfigEnv = {
22
24
  mode: "development",
23
25
  command: "serve",
24
26
  };
25
27
 
28
+ const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
29
+
30
+ export const VIZE_CONFIG_JSON_SCHEMA_PATH = path.join(
31
+ PACKAGE_ROOT,
32
+ "schemas",
33
+ "vize.config.schema.json",
34
+ );
35
+
36
+ export const VIZE_CONFIG_PKL_SCHEMA_PATH = path.join(PACKAGE_ROOT, "pkl", "vize.pkl");
37
+
26
38
  /**
27
39
  * Define a Vize configuration with type checking.
28
40
  * Accepts a plain object or a function that receives ConfigEnv.
@@ -32,7 +44,7 @@ export function defineConfig(config: UserConfigExport): UserConfigExport {
32
44
  }
33
45
 
34
46
  /**
35
- * Load vize.config file from the specified directory
47
+ * Load `vize.config.*` from the specified directory.
36
48
  */
37
49
  export async function loadConfig(
38
50
  root: string,
@@ -44,7 +56,6 @@ export async function loadConfig(
44
56
  return null;
45
57
  }
46
58
 
47
- // Custom config file path
48
59
  if (configFile) {
49
60
  const absolutePath = path.isAbsolute(configFile) ? configFile : path.resolve(root, configFile);
50
61
  if (fs.existsSync(absolutePath)) {
@@ -53,7 +64,6 @@ export async function loadConfig(
53
64
  return null;
54
65
  }
55
66
 
56
- // Search for config file
57
67
  if (mode === "auto") {
58
68
  const configPath = findConfigFileAuto(root);
59
69
  if (!configPath) {
@@ -62,17 +72,14 @@ export async function loadConfig(
62
72
  return loadConfigFile(configPath, env);
63
73
  }
64
74
 
65
- // mode === "root"
66
75
  const configPath = findConfigFileInDir(root);
67
76
  if (!configPath) {
68
77
  return null;
69
78
  }
79
+
70
80
  return loadConfigFile(configPath, env);
71
81
  }
72
82
 
73
- /**
74
- * Find config file in a specific directory
75
- */
76
83
  function findConfigFileInDir(dir: string): string | null {
77
84
  for (const name of CONFIG_FILE_NAMES) {
78
85
  const filePath = path.join(dir, name);
@@ -83,27 +90,24 @@ function findConfigFileInDir(dir: string): string | null {
83
90
  return null;
84
91
  }
85
92
 
86
- /**
87
- * Find config file by searching from cwd upward
88
- */
89
93
  function findConfigFileAuto(startDir: string): string | null {
90
94
  let currentDir = path.resolve(startDir);
91
- const root = path.parse(currentDir).root;
92
95
 
93
- while (currentDir !== root) {
96
+ while (true) {
94
97
  const configPath = findConfigFileInDir(currentDir);
95
98
  if (configPath) {
96
99
  return configPath;
97
100
  }
98
- currentDir = path.dirname(currentDir);
99
- }
100
101
 
101
- return null;
102
+ const parentDir = path.dirname(currentDir);
103
+ if (parentDir === currentDir) {
104
+ return null;
105
+ }
106
+
107
+ currentDir = parentDir;
108
+ }
102
109
  }
103
110
 
104
- /**
105
- * Load and evaluate a config file
106
- */
107
111
  async function loadConfigFile(filePath: string, env?: ConfigEnv): Promise<VizeConfig | null> {
108
112
  if (!fs.existsSync(filePath)) {
109
113
  return null;
@@ -113,20 +117,20 @@ async function loadConfigFile(filePath: string, env?: ConfigEnv): Promise<VizeCo
113
117
 
114
118
  if (ext === ".json") {
115
119
  const content = fs.readFileSync(filePath, "utf-8");
116
- return JSON.parse(content);
120
+ return parseJsonConfig(content, filePath);
121
+ }
122
+
123
+ if (ext === ".pkl") {
124
+ return loadPklConfig(filePath);
117
125
  }
118
126
 
119
127
  if (ext === ".ts") {
120
128
  return loadTypeScriptConfig(filePath, env);
121
129
  }
122
130
 
123
- // .js, .mjs - ESM
124
131
  return loadESMConfig(filePath, env);
125
132
  }
126
133
 
127
- /**
128
- * Resolve a UserConfigExport to a VizeConfig
129
- */
130
134
  async function resolveConfigExport(
131
135
  exported: UserConfigExport,
132
136
  env?: ConfigEnv,
@@ -134,12 +138,10 @@ async function resolveConfigExport(
134
138
  if (typeof exported === "function") {
135
139
  return exported(env ?? DEFAULT_CONFIG_ENV);
136
140
  }
141
+
137
142
  return exported;
138
143
  }
139
144
 
140
- /**
141
- * Load TypeScript config file using oxc-transform
142
- */
143
145
  async function loadTypeScriptConfig(filePath: string, env?: ConfigEnv): Promise<VizeConfig> {
144
146
  const source = fs.readFileSync(filePath, "utf-8");
145
147
  const result = transform(filePath, source, {
@@ -148,32 +150,89 @@ async function loadTypeScriptConfig(filePath: string, env?: ConfigEnv): Promise<
148
150
  },
149
151
  });
150
152
 
151
- const code = result.code;
152
-
153
- // Write to temp file and import (use Date.now() to avoid race conditions)
154
153
  const tempFile = filePath.replace(/\.ts$/, `.temp.${Date.now()}.mjs`);
155
- fs.writeFileSync(tempFile, code);
154
+ fs.writeFileSync(tempFile, result.code);
156
155
 
157
156
  try {
158
- const fileUrl = pathToFileURL(tempFile).href;
159
- const module = await import(fileUrl);
157
+ const module = await importFresh(tempFile);
160
158
  const exported: UserConfigExport = module.default || module;
161
159
  return resolveConfigExport(exported, env);
162
160
  } finally {
163
- fs.unlinkSync(tempFile);
161
+ fs.rmSync(tempFile, { force: true });
164
162
  }
165
163
  }
166
164
 
167
- /**
168
- * Load ESM config file
169
- */
170
165
  async function loadESMConfig(filePath: string, env?: ConfigEnv): Promise<VizeConfig> {
171
- const fileUrl = pathToFileURL(filePath).href;
172
- const module = await import(fileUrl);
166
+ const module = await importFresh(filePath);
173
167
  const exported: UserConfigExport = module.default || module;
174
168
  return resolveConfigExport(exported, env);
175
169
  }
176
170
 
171
+ function loadPklConfig(filePath: string): VizeConfig {
172
+ try {
173
+ const output = execFileSync("pkl", ["eval", "--format", "json", filePath], {
174
+ cwd: path.dirname(filePath),
175
+ encoding: "utf-8",
176
+ stdio: ["ignore", "pipe", "pipe"],
177
+ });
178
+ return parseJsonConfig(output, filePath);
179
+ } catch (error) {
180
+ throw new Error(
181
+ `Failed to evaluate PKL config at ${filePath}. Make sure the 'pkl' CLI is installed and on PATH. ${getErrorMessage(error)}`,
182
+ );
183
+ }
184
+ }
185
+
186
+ async function importFresh(filePath: string): Promise<Record<string, unknown>> {
187
+ const fileUrl = pathToFileURL(filePath);
188
+ fileUrl.searchParams.set("t", String(fs.statSync(filePath).mtimeMs));
189
+ return import(fileUrl.href);
190
+ }
191
+
192
+ function parseJsonConfig(content: string, filePath: string): VizeConfig {
193
+ try {
194
+ return normalizeLoadedConfig(JSON.parse(content));
195
+ } catch (error) {
196
+ throw new Error(`Failed to parse vize config JSON at ${filePath}: ${getErrorMessage(error)}`);
197
+ }
198
+ }
199
+
200
+ function normalizeLoadedConfig(config: unknown): VizeConfig {
201
+ const normalized = stripNullish(config);
202
+ return (normalized ?? {}) as VizeConfig;
203
+ }
204
+
205
+ function stripNullish(value: unknown): unknown {
206
+ if (value === null) {
207
+ return undefined;
208
+ }
209
+
210
+ if (Array.isArray(value)) {
211
+ return value.map((entry) => stripNullish(entry)).filter((entry) => entry !== undefined);
212
+ }
213
+
214
+ if (typeof value === "object" && value !== null) {
215
+ const result: Record<string, unknown> = {};
216
+ for (const [key, entry] of Object.entries(value)) {
217
+ const normalizedEntry = stripNullish(entry);
218
+ if (normalizedEntry !== undefined) {
219
+ result[key] = normalizedEntry;
220
+ }
221
+ }
222
+ return result;
223
+ }
224
+
225
+ return value;
226
+ }
227
+
228
+ function getErrorMessage(error: unknown): string {
229
+ if (error instanceof Error) {
230
+ return error.message;
231
+ }
232
+
233
+ return String(error);
234
+ }
235
+
177
236
  /**
178
237
  * Normalize GlobalTypesConfig shorthand strings to GlobalTypeDeclaration objects
179
238
  */
package/src/index.ts CHANGED
@@ -31,4 +31,11 @@ export type {
31
31
  } from "./types/index.js";
32
32
 
33
33
  // Config utilities
34
- export { defineConfig, loadConfig, normalizeGlobalTypes } from "./config.js";
34
+ export {
35
+ CONFIG_FILE_NAMES,
36
+ VIZE_CONFIG_JSON_SCHEMA_PATH,
37
+ VIZE_CONFIG_PKL_SCHEMA_PATH,
38
+ defineConfig,
39
+ loadConfig,
40
+ normalizeGlobalTypes,
41
+ } from "./config.js";
package/src/types/core.ts CHANGED
@@ -36,6 +36,11 @@ export type LintPreset = "happy-path" | "opinionated" | "essential" | "nuxt";
36
36
  * Vize configuration options
37
37
  */
38
38
  export interface VizeConfig {
39
+ /**
40
+ * JSON Schema reference for editor autocompletion.
41
+ */
42
+ $schema?: string;
43
+
39
44
  /**
40
45
  * Vue compiler options
41
46
  */