wesl-packager 0.7.21 → 0.7.23

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/dist/main.mjs CHANGED
@@ -8,7 +8,6 @@ import path from "node:path";
8
8
  import { Biome, Distribution } from "@biomejs/js-api";
9
9
  import { noSuffix, sanitizePackageName } from "wesl";
10
10
  import { weslBundleDeclUrl } from "wesl/bundle-decl";
11
-
12
11
  //#region src/PackageWesl.ts
13
12
  const weslBundleDecl = await readFile(new URL(weslBundleDeclUrl), "utf8");
14
13
  const { biome, projectKey: biomeKey } = await setupBiome();
@@ -99,9 +98,7 @@ async function writeJsBundle(weslBundle, dependencies, outDir) {
99
98
 
100
99
  export default weslBundle;
101
100
  `;
102
- const outPath = path.join(outDir, "weslBundle.js");
103
- const formatted = biome.formatContent(biomeKey, outString, { filePath: "b.js" });
104
- await fs.writeFile(outPath, formatted.content);
101
+ await safeWrite(path.join(outDir, "weslBundle.js"), biome.formatContent(biomeKey, outString, { filePath: "b.js" }).content);
105
102
  }
106
103
  /** Write weslBundle.d.ts containing the type definitions for a WeslBundle */
107
104
  async function writeTypeScriptDts(outDir) {
@@ -110,8 +107,18 @@ async function writeTypeScriptDts(outDir) {
110
107
  export default weslBundle;
111
108
  `;
112
109
  const formatted = biome.formatContent(biomeKey, declText, { filePath: "t.d.ts" });
113
- const outPath = path.join(outDir, "weslBundle.d.ts");
114
- await fs.writeFile(outPath, formatted.content);
110
+ await safeWrite(path.join(outDir, "weslBundle.d.ts"), formatted.content);
111
+ }
112
+ /** Write without truncating the file (atomic via temp+rename).
113
+ * Skips the write entirely if content is unchanged.
114
+ * Avoids a potential race with parallel tsc readers during `--parallel` builds. */
115
+ async function safeWrite(filePath, content) {
116
+ try {
117
+ if (await fs.readFile(filePath, "utf8") === content) return;
118
+ } catch {}
119
+ const tmpPath = filePath + ".tmp";
120
+ await fs.writeFile(tmpPath, content);
121
+ await fs.rename(tmpPath, filePath);
115
122
  }
116
123
  /** @return the bundle plus dependencies as a JavaScript string */
117
124
  function bundleToJsString(bundle, dependencies) {
@@ -161,7 +168,6 @@ async function setupBiome() {
161
168
  projectKey
162
169
  };
163
170
  }
164
-
165
171
  //#endregion
166
172
  //#region src/PackagerCli.ts
167
173
  let cliArgs;
@@ -205,7 +211,6 @@ async function parseArgs(args) {
205
211
  describe: "where to put bundled output files (relative to projectDir)"
206
212
  }).help().parse();
207
213
  }
208
-
209
214
  //#endregion
210
215
  //#region src/main.ts
211
216
  const rawArgs = hideBin(process.argv);
@@ -218,7 +223,7 @@ packagerCli(rawArgs).catch((e) => {
218
223
  console.error(e);
219
224
  exit(1);
220
225
  });
221
-
222
226
  //#endregion
223
- export { };
227
+ export {};
228
+
224
229
  //# sourceMappingURL=main.mjs.map
package/dist/main.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","names":[],"sources":["../src/PackageWesl.ts","../src/PackagerCli.ts","../src/main.ts"],"sourcesContent":["import fs, { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Biome, Distribution } from \"@biomejs/js-api\";\nimport { noSuffix, sanitizePackageName, type WeslBundle } from \"wesl\";\nimport { weslBundleDeclUrl } from \"wesl/bundle-decl\";\nimport { loadModules, parseDependencies, zip } from \"wesl-tooling\";\nimport type { CliArgs } from \"./PackagerCli.ts\";\n\nconst weslBundleDecl = await readFile(new URL(weslBundleDeclUrl), \"utf8\");\n\nconst { biome, projectKey: biomeKey } = await setupBiome();\n\n/** write weslBundle .js and .d.ts files for this shader */\nexport async function packageWgsl(args: CliArgs): Promise<void> {\n const { projectDir, outDir, multiBundle, baseDir, src } = args;\n const modules = await loadModules(projectDir, baseDir, src);\n const pkgJsonPath = path.join(projectDir, \"package.json\");\n const { name } = await loadPackageFields(pkgJsonPath);\n const edition = \"unstable_2025_1\";\n\n if (args.multiBundle) {\n await writeMultiBundle(modules, name, edition, projectDir, outDir);\n } else {\n const allDeps = parseDependencies(modules, projectDir);\n const sanitized = sanitizePackageName(name);\n const deps = filterSelfDeps(allDeps, sanitized);\n const bundle: WeslBundle = { name: sanitized, edition, modules };\n await writeJsBundle(bundle, deps, outDir);\n }\n await writeTypeScriptDts(outDir);\n if (args.updatePackageJson) {\n await updatePackageJson(projectDir, outDir, multiBundle);\n }\n}\n\n/** add an 'exports' entry to package.json for the wesl bundles */\nasync function updatePackageJson(\n projectDir: string,\n outDir: string,\n multiBundle: boolean,\n): Promise<void> {\n const pkgJsonPath = path.join(projectDir, \"package.json\");\n const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: \"utf8\" });\n const pkgJson = JSON.parse(pkgJsonString);\n const exports: Record<string, any> = {};\n\n const distDir = path.relative(projectDir, outDir);\n if (multiBundle) {\n exports[\"./*\"] = {\n types: `./${distDir}/weslBundle.d.ts`,\n import: `./${distDir}/*/weslBundle.js`,\n };\n } else {\n exports[\".\"] = {\n types: `./${distDir}/weslBundle.d.ts`,\n import: `./${distDir}/weslBundle.js`,\n };\n }\n\n const newPkgJson = insertExports(pkgJson, exports);\n const jsonString = JSON.stringify(newPkgJson, null, 2).concat(\"\\n\");\n await fs.writeFile(pkgJsonPath, jsonString);\n}\n\n/** insert the exports field into the package.json */\nfunction insertExports(pkgJson: any, exports: Record<string, any>): any {\n // insert the export entries into the existing package.json, after these fields (for a clean format)\n const exportsAfter = [\n \"name\",\n \"description\",\n \"version\",\n \"private\",\n \"author\",\n \"type\",\n \"bin\",\n \"files\",\n \"repository\",\n \"homepage\",\n \"scripts\",\n ];\n const entries = Object.entries(pkgJson);\n const index = entries.findLastIndex(([key]) => exportsAfter.includes(key));\n\n entries.splice(index + 1, 0, [\"exports\", exports]);\n return Object.fromEntries(entries);\n}\n\n/** create one bundle per source module */\nasync function writeMultiBundle(\n modules: Record<string, string>,\n name: string,\n edition: string,\n projectDir: string,\n outDir: string,\n): Promise<void> {\n const sanitized = sanitizePackageName(name);\n for (const [moduleName, moduleSrc] of Object.entries(modules)) {\n const oneModule = { [moduleName]: moduleSrc };\n const moduleBundle: WeslBundle = {\n name: sanitized,\n edition,\n modules: oneModule,\n };\n const dependencies = parseDependencies(oneModule, projectDir);\n const bundleDirRelative = noSuffix(moduleName);\n const bundleDir = path.join(outDir, bundleDirRelative);\n await writeJsBundle(moduleBundle, dependencies, bundleDir);\n }\n}\n\n/** Write a weslBundle.js containing the bundled shader sources */\nasync function writeJsBundle(\n weslBundle: WeslBundle,\n dependencies: string[],\n outDir: string,\n): Promise<void> {\n await mkdir(outDir, { recursive: true });\n\n const depNames = dependencies.map(dep => dep.replaceAll(\"/\", \"_\"));\n const depsWithNames = zip(dependencies, depNames);\n\n const imports = depsWithNames\n .map(([dep, depName]) => {\n return `import ${depName} from \"${dep}\";`;\n })\n .join(\"\\n\");\n const importsStr = imports ? `${imports}\\n` : \"\";\n\n const bundleString = bundleToJsString(weslBundle, depNames);\n\n const outString = `\n ${importsStr}\n export const weslBundle = ${bundleString}\n\n export default weslBundle;\n `;\n\n const outPath = path.join(outDir, \"weslBundle.js\");\n const formatted = biome.formatContent(biomeKey, outString, {\n filePath: \"b.js\",\n });\n await fs.writeFile(outPath, formatted.content);\n}\n\n/** Write weslBundle.d.ts containing the type definitions for a WeslBundle */\nasync function writeTypeScriptDts(outDir: string): Promise<void> {\n // TODO could we use /// <reference types=\"wesl\"> to get the type of WeslBundle?\n const constDecl = `\n export declare const weslBundle: WeslBundle;\n export default weslBundle;\n `;\n const declText = weslBundleDecl + constDecl;\n const formatted = biome.formatContent(biomeKey, declText, {\n filePath: \"t.d.ts\",\n });\n\n const outPath = path.join(outDir, \"weslBundle.d.ts\");\n await fs.writeFile(outPath, formatted.content);\n}\n\n/** @return the bundle plus dependencies as a JavaScript string */\nfunction bundleToJsString(bundle: WeslBundle, dependencies: string[]): string {\n const { name, edition, modules } = bundle;\n const depsObj = dependencies.length ? { dependencies: 99 } : {};\n const obj = { name, edition, modules, ...depsObj };\n const jsonString = JSON.stringify(obj, null, 2);\n if (dependencies.length) {\n const dependenciesStr = `: [${dependencies.join(\", \")}]`;\n const result = jsonString.replace(\": 99\", dependenciesStr);\n return result;\n } else {\n return jsonString;\n }\n}\n\n/** Filter out self-dependencies from the dependency list */\nfunction filterSelfDeps(deps: string[], packageName: string): string[] {\n return deps.filter(\n dep => dep !== packageName && !dep.startsWith(`${packageName}/`),\n );\n}\n\ninterface PkgFields {\n name: string;\n}\n\n/** parse and extract fields from package.json that we care about\n * (the name of the package) */\nasync function loadPackageFields(pkgJsonPath: string): Promise<PkgFields> {\n const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: \"utf8\" });\n const pkgJson = JSON.parse(pkgJsonString);\n const { name } = pkgJson;\n verifyField(\"name\", name);\n\n function verifyField(field: string, value: any): void {\n if (value === undefined) {\n console.error(`no '${field}' field found in \"${pkgJsonPath}\"`);\n throw new Error(\"package.json incomplete\");\n }\n }\n return { name };\n}\n\n/** setup biome to use as a formatter */\nasync function setupBiome(): Promise<{ biome: Biome; projectKey: number }> {\n const biome = await Biome.create({\n distribution: Distribution.NODE,\n });\n const { projectKey } = biome.openProject();\n biome.applyConfiguration(projectKey, {\n formatter: {\n enabled: true,\n indentStyle: \"space\",\n lineEnding: \"lf\",\n expand: \"auto\",\n },\n });\n return { biome, projectKey };\n}\n","import { versionFromPackageJson } from \"wesl-tooling\";\nimport yargs from \"yargs\";\nimport { packageWgsl } from \"./PackageWesl.ts\";\n\nexport type CliArgs = Awaited<ReturnType<typeof parseArgs>>;\nlet cliArgs: CliArgs;\n\nexport async function packagerCli(rawArgs: string[]): Promise<void> {\n cliArgs = await parseArgs(rawArgs);\n await packageWgsl(cliArgs);\n}\n\nasync function parseArgs(args: string[]) {\n const projectDir = new URL(\"..\", import.meta.url).href;\n const appVersion = await versionFromPackageJson(projectDir);\n return yargs(args)\n .command(\"$0\", \"create an npm package from WGSL/WESL files\")\n .version(appVersion)\n .option(\"src\", {\n type: \"string\",\n describe:\n \"WGSL/WESL files to bundle in the package (glob syntax, defaults to wesl.toml or shaders/**/*.w[eg]sl)\",\n })\n .option(\"rootDir\", {\n deprecated: true,\n type: \"string\",\n describe: \"use --baseDir instead\",\n })\n .option(\"baseDir\", {\n deprecated: true,\n type: \"string\",\n describe: \"root directory for shaders (defaults to wesl.toml or shaders)\",\n })\n .option(\"projectDir\", {\n type: \"string\",\n default: \".\",\n describe: \"directory containing package.json and wesl.toml\",\n })\n .option(\"multiBundle\", {\n type: \"boolean\",\n default: false,\n describe: \"make a shader bundle for each source file\",\n })\n .option(\"exportName\", {\n type: \"string\",\n default: \".\",\n describe:\n \"package.json export name for consolidated bundle (ignored for multiBundle)\",\n })\n .option(\"updatePackageJson\", {\n type: \"boolean\",\n default: false,\n describe: \"add 'exports' entries into package.json\",\n })\n .option(\"outDir\", {\n type: \"string\",\n default: \"dist\",\n describe: \"where to put bundled output files (relative to projectDir)\",\n })\n .help()\n .parse();\n}\n","#!/usr/bin/env node\nimport { exit } from \"node:process\";\nimport { hideBin } from \"yargs/helpers\";\nimport { packagerCli } from \"./PackagerCli.ts\";\n\nconst rawArgs = hideBin(process.argv);\n\nconst [major] = process.versions.node.split(\".\").map(Number);\nif (major < 22) {\n console.error(\n `Please upgrade node to version 22 or higher. (The current node version is ${process.version})`,\n );\n exit(1);\n}\n\npackagerCli(rawArgs).catch(e => {\n console.error(e);\n exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.mjs","names":[],"sources":["../src/PackageWesl.ts","../src/PackagerCli.ts","../src/main.ts"],"sourcesContent":["import fs, { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Biome, Distribution } from \"@biomejs/js-api\";\nimport { noSuffix, sanitizePackageName, type WeslBundle } from \"wesl\";\nimport { weslBundleDeclUrl } from \"wesl/bundle-decl\";\nimport { loadModules, parseDependencies, zip } from \"wesl-tooling\";\nimport type { CliArgs } from \"./PackagerCli.ts\";\n\nconst weslBundleDecl = await readFile(new URL(weslBundleDeclUrl), \"utf8\");\n\nconst { biome, projectKey: biomeKey } = await setupBiome();\n\n/** write weslBundle .js and .d.ts files for this shader */\nexport async function packageWgsl(args: CliArgs): Promise<void> {\n const { projectDir, outDir, multiBundle, baseDir, src } = args;\n const modules = await loadModules(projectDir, baseDir, src);\n const pkgJsonPath = path.join(projectDir, \"package.json\");\n const { name } = await loadPackageFields(pkgJsonPath);\n const edition = \"unstable_2025_1\";\n\n if (args.multiBundle) {\n await writeMultiBundle(modules, name, edition, projectDir, outDir);\n } else {\n const allDeps = parseDependencies(modules, projectDir);\n const sanitized = sanitizePackageName(name);\n const deps = filterSelfDeps(allDeps, sanitized);\n const bundle: WeslBundle = { name: sanitized, edition, modules };\n await writeJsBundle(bundle, deps, outDir);\n }\n await writeTypeScriptDts(outDir);\n if (args.updatePackageJson) {\n await updatePackageJson(projectDir, outDir, multiBundle);\n }\n}\n\n/** add an 'exports' entry to package.json for the wesl bundles */\nasync function updatePackageJson(\n projectDir: string,\n outDir: string,\n multiBundle: boolean,\n): Promise<void> {\n const pkgJsonPath = path.join(projectDir, \"package.json\");\n const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: \"utf8\" });\n const pkgJson = JSON.parse(pkgJsonString);\n const exports: Record<string, any> = {};\n\n const distDir = path.relative(projectDir, outDir);\n if (multiBundle) {\n exports[\"./*\"] = {\n types: `./${distDir}/weslBundle.d.ts`,\n import: `./${distDir}/*/weslBundle.js`,\n };\n } else {\n exports[\".\"] = {\n types: `./${distDir}/weslBundle.d.ts`,\n import: `./${distDir}/weslBundle.js`,\n };\n }\n\n const newPkgJson = insertExports(pkgJson, exports);\n const jsonString = JSON.stringify(newPkgJson, null, 2).concat(\"\\n\");\n await fs.writeFile(pkgJsonPath, jsonString);\n}\n\n/** insert the exports field into the package.json */\nfunction insertExports(pkgJson: any, exports: Record<string, any>): any {\n // insert the export entries into the existing package.json, after these fields (for a clean format)\n const exportsAfter = [\n \"name\",\n \"description\",\n \"version\",\n \"private\",\n \"author\",\n \"type\",\n \"bin\",\n \"files\",\n \"repository\",\n \"homepage\",\n \"scripts\",\n ];\n const entries = Object.entries(pkgJson);\n const index = entries.findLastIndex(([key]) => exportsAfter.includes(key));\n\n entries.splice(index + 1, 0, [\"exports\", exports]);\n return Object.fromEntries(entries);\n}\n\n/** create one bundle per source module */\nasync function writeMultiBundle(\n modules: Record<string, string>,\n name: string,\n edition: string,\n projectDir: string,\n outDir: string,\n): Promise<void> {\n const sanitized = sanitizePackageName(name);\n for (const [moduleName, moduleSrc] of Object.entries(modules)) {\n const oneModule = { [moduleName]: moduleSrc };\n const moduleBundle: WeslBundle = {\n name: sanitized,\n edition,\n modules: oneModule,\n };\n const dependencies = parseDependencies(oneModule, projectDir);\n const bundleDirRelative = noSuffix(moduleName);\n const bundleDir = path.join(outDir, bundleDirRelative);\n await writeJsBundle(moduleBundle, dependencies, bundleDir);\n }\n}\n\n/** Write a weslBundle.js containing the bundled shader sources */\nasync function writeJsBundle(\n weslBundle: WeslBundle,\n dependencies: string[],\n outDir: string,\n): Promise<void> {\n await mkdir(outDir, { recursive: true });\n\n const depNames = dependencies.map(dep => dep.replaceAll(\"/\", \"_\"));\n const depsWithNames = zip(dependencies, depNames);\n\n const imports = depsWithNames\n .map(([dep, depName]) => {\n return `import ${depName} from \"${dep}\";`;\n })\n .join(\"\\n\");\n const importsStr = imports ? `${imports}\\n` : \"\";\n\n const bundleString = bundleToJsString(weslBundle, depNames);\n\n const outString = `\n ${importsStr}\n export const weslBundle = ${bundleString}\n\n export default weslBundle;\n `;\n\n const outPath = path.join(outDir, \"weslBundle.js\");\n const formatted = biome.formatContent(biomeKey, outString, {\n filePath: \"b.js\",\n });\n await safeWrite(outPath, formatted.content);\n}\n\n/** Write weslBundle.d.ts containing the type definitions for a WeslBundle */\nasync function writeTypeScriptDts(outDir: string): Promise<void> {\n // TODO could we use /// <reference types=\"wesl\"> to get the type of WeslBundle?\n const constDecl = `\n export declare const weslBundle: WeslBundle;\n export default weslBundle;\n `;\n const declText = weslBundleDecl + constDecl;\n const formatted = biome.formatContent(biomeKey, declText, {\n filePath: \"t.d.ts\",\n });\n\n const outPath = path.join(outDir, \"weslBundle.d.ts\");\n await safeWrite(outPath, formatted.content);\n}\n\n/** Write without truncating the file (atomic via temp+rename).\n * Skips the write entirely if content is unchanged.\n * Avoids a potential race with parallel tsc readers during `--parallel` builds. */\nasync function safeWrite(filePath: string, content: string): Promise<void> {\n try {\n const existing = await fs.readFile(filePath, \"utf8\");\n if (existing === content) return;\n } catch {}\n const tmpPath = filePath + \".tmp\";\n await fs.writeFile(tmpPath, content);\n await fs.rename(tmpPath, filePath);\n}\n\n/** @return the bundle plus dependencies as a JavaScript string */\nfunction bundleToJsString(bundle: WeslBundle, dependencies: string[]): string {\n const { name, edition, modules } = bundle;\n const depsObj = dependencies.length ? { dependencies: 99 } : {};\n const obj = { name, edition, modules, ...depsObj };\n const jsonString = JSON.stringify(obj, null, 2);\n if (dependencies.length) {\n const dependenciesStr = `: [${dependencies.join(\", \")}]`;\n const result = jsonString.replace(\": 99\", dependenciesStr);\n return result;\n } else {\n return jsonString;\n }\n}\n\n/** Filter out self-dependencies from the dependency list */\nfunction filterSelfDeps(deps: string[], packageName: string): string[] {\n return deps.filter(\n dep => dep !== packageName && !dep.startsWith(`${packageName}/`),\n );\n}\n\ninterface PkgFields {\n name: string;\n}\n\n/** parse and extract fields from package.json that we care about\n * (the name of the package) */\nasync function loadPackageFields(pkgJsonPath: string): Promise<PkgFields> {\n const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: \"utf8\" });\n const pkgJson = JSON.parse(pkgJsonString);\n const { name } = pkgJson;\n verifyField(\"name\", name);\n\n function verifyField(field: string, value: any): void {\n if (value === undefined) {\n console.error(`no '${field}' field found in \"${pkgJsonPath}\"`);\n throw new Error(\"package.json incomplete\");\n }\n }\n return { name };\n}\n\n/** setup biome to use as a formatter */\nasync function setupBiome(): Promise<{ biome: Biome; projectKey: number }> {\n const biome = await Biome.create({\n distribution: Distribution.NODE,\n });\n const { projectKey } = biome.openProject();\n biome.applyConfiguration(projectKey, {\n formatter: {\n enabled: true,\n indentStyle: \"space\",\n lineEnding: \"lf\",\n expand: \"auto\",\n },\n });\n return { biome, projectKey };\n}\n","import { versionFromPackageJson } from \"wesl-tooling\";\nimport yargs from \"yargs\";\nimport { packageWgsl } from \"./PackageWesl.ts\";\n\nexport type CliArgs = Awaited<ReturnType<typeof parseArgs>>;\nlet cliArgs: CliArgs;\n\nexport async function packagerCli(rawArgs: string[]): Promise<void> {\n cliArgs = await parseArgs(rawArgs);\n await packageWgsl(cliArgs);\n}\n\nasync function parseArgs(args: string[]) {\n const projectDir = new URL(\"..\", import.meta.url).href;\n const appVersion = await versionFromPackageJson(projectDir);\n return yargs(args)\n .command(\"$0\", \"create an npm package from WGSL/WESL files\")\n .version(appVersion)\n .option(\"src\", {\n type: \"string\",\n describe:\n \"WGSL/WESL files to bundle in the package (glob syntax, defaults to wesl.toml or shaders/**/*.w[eg]sl)\",\n })\n .option(\"rootDir\", {\n deprecated: true,\n type: \"string\",\n describe: \"use --baseDir instead\",\n })\n .option(\"baseDir\", {\n deprecated: true,\n type: \"string\",\n describe: \"root directory for shaders (defaults to wesl.toml or shaders)\",\n })\n .option(\"projectDir\", {\n type: \"string\",\n default: \".\",\n describe: \"directory containing package.json and wesl.toml\",\n })\n .option(\"multiBundle\", {\n type: \"boolean\",\n default: false,\n describe: \"make a shader bundle for each source file\",\n })\n .option(\"exportName\", {\n type: \"string\",\n default: \".\",\n describe:\n \"package.json export name for consolidated bundle (ignored for multiBundle)\",\n })\n .option(\"updatePackageJson\", {\n type: \"boolean\",\n default: false,\n describe: \"add 'exports' entries into package.json\",\n })\n .option(\"outDir\", {\n type: \"string\",\n default: \"dist\",\n describe: \"where to put bundled output files (relative to projectDir)\",\n })\n .help()\n .parse();\n}\n","#!/usr/bin/env node\nimport { exit } from \"node:process\";\nimport { hideBin } from \"yargs/helpers\";\nimport { packagerCli } from \"./PackagerCli.ts\";\n\nconst rawArgs = hideBin(process.argv);\n\nconst [major] = process.versions.node.split(\".\").map(Number);\nif (major < 22) {\n console.error(\n `Please upgrade node to version 22 or higher. (The current node version is ${process.version})`,\n );\n exit(1);\n}\n\npackagerCli(rawArgs).catch(e => {\n console.error(e);\n exit(1);\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wesl-packager",
3
- "version": "0.7.21",
3
+ "version": "0.7.23",
4
4
  "type": "module",
5
5
  "bin": "dist/main.mjs",
6
6
  "files": [
@@ -13,8 +13,8 @@
13
13
  "@biomejs/js-api": "^1.0.0",
14
14
  "@biomejs/wasm-nodejs": "^2.0.6",
15
15
  "yargs": "^18.0.0",
16
- "wesl": "0.7.23",
17
- "wesl-tooling": "0.6.35"
16
+ "wesl": "0.7.25",
17
+ "wesl-tooling": "0.6.37"
18
18
  },
19
19
  "devDependencies": {
20
20
  "dependent_package": "x",
@@ -139,7 +139,7 @@ async function writeJsBundle(
139
139
  const formatted = biome.formatContent(biomeKey, outString, {
140
140
  filePath: "b.js",
141
141
  });
142
- await fs.writeFile(outPath, formatted.content);
142
+ await safeWrite(outPath, formatted.content);
143
143
  }
144
144
 
145
145
  /** Write weslBundle.d.ts containing the type definitions for a WeslBundle */
@@ -155,7 +155,20 @@ async function writeTypeScriptDts(outDir: string): Promise<void> {
155
155
  });
156
156
 
157
157
  const outPath = path.join(outDir, "weslBundle.d.ts");
158
- await fs.writeFile(outPath, formatted.content);
158
+ await safeWrite(outPath, formatted.content);
159
+ }
160
+
161
+ /** Write without truncating the file (atomic via temp+rename).
162
+ * Skips the write entirely if content is unchanged.
163
+ * Avoids a potential race with parallel tsc readers during `--parallel` builds. */
164
+ async function safeWrite(filePath: string, content: string): Promise<void> {
165
+ try {
166
+ const existing = await fs.readFile(filePath, "utf8");
167
+ if (existing === content) return;
168
+ } catch {}
169
+ const tmpPath = filePath + ".tmp";
170
+ await fs.writeFile(tmpPath, content);
171
+ await fs.rename(tmpPath, filePath);
159
172
  }
160
173
 
161
174
  /** @return the bundle plus dependencies as a JavaScript string */
@@ -3,8 +3,8 @@
3
3
  "private": true,
4
4
  "exports": {
5
5
  ".": {
6
- "import": "./dist/weslBundle.js",
7
- "types": "./dist/weslBundle.d.ts"
6
+ "types": "./dist/weslBundle.d.ts",
7
+ "import": "./dist/weslBundle.js"
8
8
  }
9
9
  }
10
10
  }
@@ -1,20 +0,0 @@
1
- export interface WeslBundle {
2
- /** npm package name sanitized to be a valid WESL identifier
3
- * (@ removed, / ==> __, - ==> _) */
4
- name: string;
5
-
6
- /** WESL edition of the code e.g. unstable_2025_1 */
7
- edition: string;
8
-
9
- /** map of WESL/WGSL modules:
10
- * keys are file paths, relative to package root (e.g. "./lib.wgsl")
11
- * values are WESL/WGSL code strings
12
- */
13
- modules: Record<string, string>;
14
-
15
- /** packages referenced by this package */
16
- dependencies?: WeslBundle[];
17
- }
18
-
19
- export declare const weslBundle: WeslBundle;
20
- export default weslBundle;
@@ -1,10 +0,0 @@
1
- export const weslBundle = {
2
- name: "test_wesl_package",
3
- edition: "unstable_2025_1",
4
- modules: {
5
- "util.wgsl": "fn foo() {}",
6
- "lib.wesl": "import package::util;\n",
7
- },
8
- };
9
-
10
- export default weslBundle;