@vzn/vx 0.0.597 → 0.0.598

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.
@@ -606,8 +606,12 @@ export function configEvalKeyFromIdentities(a: {
606
606
  * `node_modules`, the config itself excluded. `vx watch` watches these: a
607
607
  * shared preset outside the project (`../../shared/preset.mjs`, the way
608
608
  * configs compose) changed what a run evaluates and no watcher saw it
609
- * (item 949). A file that cannot be read or resolved ends its branch.
609
+ * (item 949). A file that cannot be read or resolved ends its branch; one
610
+ * that cannot be resolved is still listed, by the name it would have.
610
611
  */
612
+ // The names an extensionless relative import may resolve to, as Bun tries them.
613
+ const IMPORT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json']
614
+
611
615
  export async function configImports(configPath: string): Promise<string[]> {
612
616
  const seen = new Set<string>([configPath])
613
617
  const out: string[] = []
@@ -635,6 +639,16 @@ export async function configImports(configPath: string): Promise<string[]> {
635
639
  dir ??= realDirOf(file)
636
640
  resolved = realPath(Bun.resolveSync(spec, dir))
637
641
  } catch {
642
+ // Missing for now, still imported: the config that names it fails
643
+ // to load until it is back, and a list without it left `vx watch`
644
+ // deaf to its return. Named as written, and not walked.
645
+ if (dir === undefined) continue
646
+ for (const ext of path.extname(spec) === '' ? IMPORT_EXTENSIONS : ['']) {
647
+ const missing = path.resolve(dir, spec + ext)
648
+ if (seen.has(missing)) continue
649
+ seen.add(missing)
650
+ out.push(missing)
651
+ }
638
652
  continue
639
653
  }
640
654
  if (seen.has(resolved) || resolved.split(path.sep).includes('node_modules')) continue
@@ -103,6 +103,23 @@ const nonJsonPaths = ${nonJsonPaths.toString()}
103
103
  throw new Error('process.exit(' + (code ?? '') + ') in a config: ' + ${JSON.stringify(CONFIG_EXIT)})
104
104
  }
105
105
  }
106
+ // Bun's resolver reads \\ as a separator, so a config under a\\b is served
107
+ // from the path as written (project-loader.ts's LITERAL_QUERY), read now.
108
+ let literal = false
109
+ const serveLiteral = () => {
110
+ if (literal) return
111
+ literal = true
112
+ globalThis.Bun.plugin({
113
+ name: 'vx-config-literal',
114
+ setup(build) {
115
+ build.onResolve({ filter: /\\?vx-literal$/ }, (args) => ({ path: args.path, namespace: 'file' }))
116
+ build.onLoad({ filter: /\\?vx-literal$/ }, async (args) => {
117
+ const file = args.path.slice(0, -'?vx-literal'.length)
118
+ return { contents: await globalThis.Bun.file(file).text(), loader: /\\.[cm]?ts$/.test(file) ? 'ts' : 'js' }
119
+ })
120
+ },
121
+ })
122
+ }
106
123
  self.onmessage = async (e) => {
107
124
  const { id, path, env } = e.data
108
125
  // A Worker starts with the process's STARTUP environment, not the
@@ -155,7 +172,9 @@ self.onmessage = async (e) => {
155
172
  if (umaskIn !== null && globalThis.process.umask() !== umaskIn) globalThis.process.umask(umaskIn)
156
173
  }
157
174
  try {
158
- const ns = await import(path)
175
+ const literalPath = path.includes('\\\\')
176
+ if (literalPath) serveLiteral()
177
+ const ns = await import(literalPath ? path + '?vx-literal' : path)
159
178
  // Awaited as the in-process load's async return flattens it: a Promise
160
179
  // default loaded on a run and was refused as "an instance of Promise"
161
180
  // on every later evaluation in the process (D-5).
@@ -87,7 +87,12 @@ function assertDefaultObject(mod: unknown, kind: string, configPath: string): vo
87
87
  const heldSources = new Map<string, { source: string; uses: number }>()
88
88
  let serving = false
89
89
 
90
- const HELD_QUERY = /\?vx-held=([0-9a-f]+)$/
90
+ const HELD_QUERY = /\?vx-(?:held|literal)=([0-9a-f]+)$/
91
+
92
+ // Bun's resolver reads `\` as a separator, even in a file: URL, so a
93
+ // config under `a\b` was looked for under `a/b`. Resolving it here keeps
94
+ // the path as written; only the served load can take it.
95
+ const LITERAL_QUERY = /\?vx-literal=[0-9a-f]+$/
91
96
 
92
97
  function serveHeldSources(): void {
93
98
  if (serving) return
@@ -95,6 +100,7 @@ function serveHeldSources(): void {
95
100
  Bun.plugin({
96
101
  name: 'vx-config-bytes',
97
102
  setup(build) {
103
+ build.onResolve({ filter: LITERAL_QUERY }, (args) => ({ path: args.path, namespace: 'file' }))
98
104
  build.onLoad({ filter: HELD_QUERY }, (args) => ({
99
105
  contents: heldSources.get(HELD_QUERY.exec(args.path)![1]!)!.source,
100
106
  loader: /\.[cm]?ts\?/.test(args.path) ? 'ts' : 'js',
@@ -136,7 +142,7 @@ const COMMONJS_HINT =
136
142
  /\b(?:module|exports|require|this|__dirname|__filename)\b|\\|\bexport\s*=(?!=)/
137
143
 
138
144
  /** vx's module-cache query, which no user wrote: stripped from anything shown to them. */
139
- const BUST_QUERY = /\?vx-(?:bust|held)=[^'"\s]*/g
145
+ const BUST_QUERY = /\?vx-(?:bust|held|literal)=[^'"\s]*/g
140
146
 
141
147
  // Bun has native TS / ESM execution — no transpiler dep needed. We fold
142
148
  // a short content hash into the import URL as a cache-bust key so that:
@@ -157,10 +163,18 @@ async function loadDefaultExport(
157
163
  // URL yet, and a repeat load (the one that could replay an evaluation
158
164
  // made under earlier env values) re-evaluates in a worker instead
159
165
  // (`loadedConfigs`, item 678).
166
+ const literal = configPath.includes('\\')
160
167
  const source =
161
- kind === 'Project' ? servableSource(bytes, /\.[cm]?ts$/.test(configPath) ? 'ts' : 'js') : null
168
+ kind === 'Project' || literal
169
+ ? servableSource(bytes, /\.[cm]?ts$/.test(configPath) ? 'ts' : 'js')
170
+ : null
171
+ if (literal && source === null) {
172
+ throw new UserError(
173
+ `${kind} config ${configPath} sits under a path that holds a backslash, which Bun loads only as an ES module in UTF-8`,
174
+ )
175
+ }
162
176
  const hash = xxh3hex(bytes)
163
- const specifier = `${configPath}?vx-${source !== null ? 'held' : 'bust'}=${hash}`
177
+ const specifier = `${configPath}?vx-${literal ? 'literal' : source !== null ? 'held' : 'bust'}=${hash}`
164
178
  if (source !== null) {
165
179
  serveHeldSources()
166
180
  holdSource(hash, source)