wesl-packager 0.6.46 → 0.6.49
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/bin/wesl-packager.js +2 -0
- package/package.json +7 -9
- package/src/PackageWesl.ts +219 -0
- package/src/PackagerCli.ts +62 -0
- package/src/main.ts +19 -0
- package/src/test/ExpectDirMatch.ts +119 -0
- package/src/test/PackageWesl.test.ts +103 -0
- package/src/test/multi_package/package.json +8 -0
- package/src/test/multi_package/shaders/dir/nested.wesl +1 -0
- package/src/test/multi_package/shaders/second.wesl +1 -0
- package/src/test/multi_package/shaders/transitive.wesl +3 -0
- package/src/test/wesl-package/dist/weslBundle.d.ts +20 -0
- package/src/test/wesl-package/dist/weslBundle.js +10 -0
- package/src/test/wesl-package/package.json +10 -0
- package/src/test/wesl-package/src/lib.wesl +1 -0
- package/src/test/wesl-package/src/util.wgsl +1 -0
- package/bin/wesl-packager +0 -11385
package/package.json
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wesl-packager",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.49",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"bin": "bin/wesl-packager",
|
|
5
|
+
"bin": "bin/wesl-packager.js",
|
|
6
6
|
"files": [
|
|
7
|
-
"bin"
|
|
7
|
+
"bin",
|
|
8
|
+
"src"
|
|
8
9
|
],
|
|
9
10
|
"repository": "github:wgsl-tooling-wg/wesl-js",
|
|
10
11
|
"dependencies": {
|
|
11
12
|
"@biomejs/js-api": "^1.0.0",
|
|
12
13
|
"@biomejs/wasm-nodejs": "^2.0.6",
|
|
13
14
|
"yargs": "^18.0.0",
|
|
14
|
-
"wesl": "0.6.
|
|
15
|
+
"wesl": "0.6.49",
|
|
16
|
+
"wesl-tooling": "0.6.12"
|
|
15
17
|
},
|
|
16
18
|
"devDependencies": {
|
|
17
19
|
"dependent_package": "x",
|
|
18
20
|
"dir-compare": "^5.0.0",
|
|
19
|
-
"picocolors": "^1.1.1"
|
|
20
|
-
"rollup": "^4.44.1",
|
|
21
|
-
"wesl-tooling": "x"
|
|
21
|
+
"picocolors": "^1.1.1"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsdown",
|
|
25
|
-
"build:watch": "tsdown --watch",
|
|
26
24
|
"test": "cross-env FORCE_COLOR=1 vitest",
|
|
27
25
|
"test:once": "vitest run",
|
|
28
26
|
"typecheck": "tsgo"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import fs, { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Biome, Distribution } from "@biomejs/js-api";
|
|
4
|
+
import { noSuffix, sanitizePackageName, type WeslBundle } from "wesl";
|
|
5
|
+
import { weslBundleDeclUrl } from "wesl/bundle-decl";
|
|
6
|
+
import { loadModules, parseDependencies, zip } from "wesl-tooling";
|
|
7
|
+
import type { CliArgs } from "./PackagerCli.ts";
|
|
8
|
+
|
|
9
|
+
const weslBundleDecl = await readFile(new URL(weslBundleDeclUrl), "utf8");
|
|
10
|
+
|
|
11
|
+
const { biome, projectKey: biomeKey } = await setupBiome();
|
|
12
|
+
|
|
13
|
+
/** write weslBundle .js and .d.ts files for this shader */
|
|
14
|
+
export async function packageWgsl(args: CliArgs): Promise<void> {
|
|
15
|
+
const { projectDir, outDir, multiBundle, baseDir, src } = args;
|
|
16
|
+
const modules = await loadModules(projectDir, baseDir, src);
|
|
17
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
18
|
+
const { name } = await loadPackageFields(pkgJsonPath);
|
|
19
|
+
const edition = "unstable_2025_1";
|
|
20
|
+
|
|
21
|
+
if (args.multiBundle) {
|
|
22
|
+
await writeMultiBundle(modules, name, edition, projectDir, outDir);
|
|
23
|
+
} else {
|
|
24
|
+
const allDeps = parseDependencies(modules, projectDir);
|
|
25
|
+
const sanitized = sanitizePackageName(name);
|
|
26
|
+
const deps = filterSelfDeps(allDeps, sanitized);
|
|
27
|
+
const bundle: WeslBundle = { name: sanitized, edition, modules };
|
|
28
|
+
await writeJsBundle(bundle, deps, outDir);
|
|
29
|
+
}
|
|
30
|
+
await writeTypeScriptDts(outDir);
|
|
31
|
+
if (args.updatePackageJson) {
|
|
32
|
+
await updatePackageJson(projectDir, outDir, multiBundle);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** add an 'exports' entry to package.json for the wesl bundles */
|
|
37
|
+
async function updatePackageJson(
|
|
38
|
+
projectDir: string,
|
|
39
|
+
outDir: string,
|
|
40
|
+
multiBundle: boolean,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
43
|
+
const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: "utf8" });
|
|
44
|
+
const pkgJson = JSON.parse(pkgJsonString);
|
|
45
|
+
const exports: Record<string, any> = {};
|
|
46
|
+
|
|
47
|
+
const distDir = path.relative(projectDir, outDir);
|
|
48
|
+
if (multiBundle) {
|
|
49
|
+
exports["./*"] = {
|
|
50
|
+
types: `./${distDir}/weslBundle.d.ts`,
|
|
51
|
+
import: `./${distDir}/*/weslBundle.js`,
|
|
52
|
+
};
|
|
53
|
+
} else {
|
|
54
|
+
exports["."] = {
|
|
55
|
+
types: `./${distDir}/weslBundle.d.ts`,
|
|
56
|
+
import: `./${distDir}/weslBundle.js`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const newPkgJson = insertExports(pkgJson, exports);
|
|
61
|
+
const jsonString = JSON.stringify(newPkgJson, null, 2).concat("\n");
|
|
62
|
+
await fs.writeFile(pkgJsonPath, jsonString);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** insert the exports field into the package.json */
|
|
66
|
+
function insertExports(pkgJson: any, exports: Record<string, any>): any {
|
|
67
|
+
// insert the export entries into the existing package.json, after these fields (for a clean format)
|
|
68
|
+
const exportsAfter = [
|
|
69
|
+
"name",
|
|
70
|
+
"description",
|
|
71
|
+
"version",
|
|
72
|
+
"private",
|
|
73
|
+
"author",
|
|
74
|
+
"type",
|
|
75
|
+
"bin",
|
|
76
|
+
"files",
|
|
77
|
+
"repository",
|
|
78
|
+
"homepage",
|
|
79
|
+
"scripts",
|
|
80
|
+
];
|
|
81
|
+
const entries = Object.entries(pkgJson);
|
|
82
|
+
const index = entries.findLastIndex(([key]) => exportsAfter.includes(key));
|
|
83
|
+
|
|
84
|
+
entries.splice(index + 1, 0, ["exports", exports]);
|
|
85
|
+
return Object.fromEntries(entries);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** create one bundle per source module */
|
|
89
|
+
async function writeMultiBundle(
|
|
90
|
+
modules: Record<string, string>,
|
|
91
|
+
name: string,
|
|
92
|
+
edition: string,
|
|
93
|
+
projectDir: string,
|
|
94
|
+
outDir: string,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const sanitized = sanitizePackageName(name);
|
|
97
|
+
for (const [moduleName, moduleSrc] of Object.entries(modules)) {
|
|
98
|
+
const oneModule = { [moduleName]: moduleSrc };
|
|
99
|
+
const moduleBundle: WeslBundle = {
|
|
100
|
+
name: sanitized,
|
|
101
|
+
edition,
|
|
102
|
+
modules: oneModule,
|
|
103
|
+
};
|
|
104
|
+
const dependencies = parseDependencies(oneModule, projectDir);
|
|
105
|
+
const bundleDirRelative = noSuffix(moduleName);
|
|
106
|
+
const bundleDir = path.join(outDir, bundleDirRelative);
|
|
107
|
+
await writeJsBundle(moduleBundle, dependencies, bundleDir);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Write a weslBundle.js containing the bundled shader sources */
|
|
112
|
+
async function writeJsBundle(
|
|
113
|
+
weslBundle: WeslBundle,
|
|
114
|
+
dependencies: string[],
|
|
115
|
+
outDir: string,
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
await mkdir(outDir, { recursive: true });
|
|
118
|
+
|
|
119
|
+
const depNames = dependencies.map(dep => dep.replaceAll("/", "_"));
|
|
120
|
+
const depsWithNames = zip(dependencies, depNames);
|
|
121
|
+
|
|
122
|
+
const imports = depsWithNames
|
|
123
|
+
.map(([dep, depName]) => {
|
|
124
|
+
return `import ${depName} from "${dep}";`;
|
|
125
|
+
})
|
|
126
|
+
.join("\n");
|
|
127
|
+
const importsStr = imports ? `${imports}\n` : "";
|
|
128
|
+
|
|
129
|
+
const bundleString = bundleToJsString(weslBundle, depNames);
|
|
130
|
+
|
|
131
|
+
const outString = `
|
|
132
|
+
${importsStr}
|
|
133
|
+
export const weslBundle = ${bundleString}
|
|
134
|
+
|
|
135
|
+
export default weslBundle;
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const outPath = path.join(outDir, "weslBundle.js");
|
|
139
|
+
const formatted = biome.formatContent(biomeKey, outString, {
|
|
140
|
+
filePath: "b.js",
|
|
141
|
+
});
|
|
142
|
+
await fs.writeFile(outPath, formatted.content);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Write weslBundle.d.ts containing the type definitions for a WeslBundle */
|
|
146
|
+
async function writeTypeScriptDts(outDir: string): Promise<void> {
|
|
147
|
+
// TODO could we use /// <reference types="wesl"> to get the type of WeslBundle?
|
|
148
|
+
const constDecl = `
|
|
149
|
+
export declare const weslBundle: WeslBundle;
|
|
150
|
+
export default weslBundle;
|
|
151
|
+
`;
|
|
152
|
+
const declText = weslBundleDecl + constDecl;
|
|
153
|
+
const formatted = biome.formatContent(biomeKey, declText, {
|
|
154
|
+
filePath: "t.d.ts",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const outPath = path.join(outDir, "weslBundle.d.ts");
|
|
158
|
+
await fs.writeFile(outPath, formatted.content);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @return the bundle plus dependencies as a JavaScript string */
|
|
162
|
+
function bundleToJsString(bundle: WeslBundle, dependencies: string[]): string {
|
|
163
|
+
const { name, edition, modules } = bundle;
|
|
164
|
+
const depsObj = dependencies.length ? { dependencies: 99 } : {};
|
|
165
|
+
const obj = { name, edition, modules, ...depsObj };
|
|
166
|
+
const jsonString = JSON.stringify(obj, null, 2);
|
|
167
|
+
if (dependencies.length) {
|
|
168
|
+
const dependenciesStr = `: [${dependencies.join(", ")}]`;
|
|
169
|
+
const result = jsonString.replace(": 99", dependenciesStr);
|
|
170
|
+
return result;
|
|
171
|
+
} else {
|
|
172
|
+
return jsonString;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Filter out self-dependencies from the dependency list */
|
|
177
|
+
function filterSelfDeps(deps: string[], packageName: string): string[] {
|
|
178
|
+
return deps.filter(
|
|
179
|
+
dep => dep !== packageName && !dep.startsWith(`${packageName}/`),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface PkgFields {
|
|
184
|
+
name: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** parse and extract fields from package.json that we care about
|
|
188
|
+
* (the name of the package) */
|
|
189
|
+
async function loadPackageFields(pkgJsonPath: string): Promise<PkgFields> {
|
|
190
|
+
const pkgJsonString = await fs.readFile(pkgJsonPath, { encoding: "utf8" });
|
|
191
|
+
const pkgJson = JSON.parse(pkgJsonString);
|
|
192
|
+
const { name } = pkgJson;
|
|
193
|
+
verifyField("name", name);
|
|
194
|
+
|
|
195
|
+
function verifyField(field: string, value: any): void {
|
|
196
|
+
if (value === undefined) {
|
|
197
|
+
console.error(`no '${field}' field found in "${pkgJsonPath}"`);
|
|
198
|
+
throw new Error("package.json incomplete");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { name };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** setup biome to use as a formatter */
|
|
205
|
+
async function setupBiome(): Promise<{ biome: Biome; projectKey: number }> {
|
|
206
|
+
const biome = await Biome.create({
|
|
207
|
+
distribution: Distribution.NODE,
|
|
208
|
+
});
|
|
209
|
+
const { projectKey } = biome.openProject();
|
|
210
|
+
biome.applyConfiguration(projectKey, {
|
|
211
|
+
formatter: {
|
|
212
|
+
enabled: true,
|
|
213
|
+
indentStyle: "space",
|
|
214
|
+
lineEnding: "lf",
|
|
215
|
+
expand: "auto",
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return { biome, projectKey };
|
|
219
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { versionFromPackageJson } from "wesl-tooling";
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { packageWgsl } from "./PackageWesl.ts";
|
|
4
|
+
|
|
5
|
+
export type CliArgs = Awaited<ReturnType<typeof parseArgs>>;
|
|
6
|
+
let cliArgs: CliArgs;
|
|
7
|
+
|
|
8
|
+
export async function packagerCli(rawArgs: string[]): Promise<void> {
|
|
9
|
+
cliArgs = await parseArgs(rawArgs);
|
|
10
|
+
await packageWgsl(cliArgs);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function parseArgs(args: string[]) {
|
|
14
|
+
const projectDir = new URL("..", import.meta.url).href;
|
|
15
|
+
const appVersion = await versionFromPackageJson(projectDir);
|
|
16
|
+
return yargs(args)
|
|
17
|
+
.command("$0", "create an npm package from WGSL/WESL files")
|
|
18
|
+
.version(appVersion)
|
|
19
|
+
.option("src", {
|
|
20
|
+
type: "string",
|
|
21
|
+
describe:
|
|
22
|
+
"WGSL/WESL files to bundle in the package (glob syntax, defaults to wesl.toml or shaders/**/*.w[eg]sl)",
|
|
23
|
+
})
|
|
24
|
+
.option("rootDir", {
|
|
25
|
+
deprecated: true,
|
|
26
|
+
type: "string",
|
|
27
|
+
describe: "use --baseDir instead",
|
|
28
|
+
})
|
|
29
|
+
.option("baseDir", {
|
|
30
|
+
deprecated: true,
|
|
31
|
+
type: "string",
|
|
32
|
+
describe: "root directory for shaders (defaults to wesl.toml or shaders)",
|
|
33
|
+
})
|
|
34
|
+
.option("projectDir", {
|
|
35
|
+
type: "string",
|
|
36
|
+
default: ".",
|
|
37
|
+
describe: "directory containing package.json and wesl.toml",
|
|
38
|
+
})
|
|
39
|
+
.option("multiBundle", {
|
|
40
|
+
type: "boolean",
|
|
41
|
+
default: false,
|
|
42
|
+
describe: "make a shader bundle for each source file",
|
|
43
|
+
})
|
|
44
|
+
.option("exportName", {
|
|
45
|
+
type: "string",
|
|
46
|
+
default: ".",
|
|
47
|
+
describe:
|
|
48
|
+
"package.json export name for consolidated bundle (ignored for multiBundle)",
|
|
49
|
+
})
|
|
50
|
+
.option("updatePackageJson", {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
default: false,
|
|
53
|
+
describe: "add 'exports' entries into package.json",
|
|
54
|
+
})
|
|
55
|
+
.option("outDir", {
|
|
56
|
+
type: "string",
|
|
57
|
+
default: "dist",
|
|
58
|
+
describe: "where to put bundled output files (relative to projectDir)",
|
|
59
|
+
})
|
|
60
|
+
.help()
|
|
61
|
+
.parse();
|
|
62
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exit } from "node:process";
|
|
3
|
+
import { hideBin } from "yargs/helpers";
|
|
4
|
+
import { packagerCli } from "./PackagerCli.ts";
|
|
5
|
+
|
|
6
|
+
const rawArgs = hideBin(process.argv);
|
|
7
|
+
|
|
8
|
+
const [major] = process.versions.node.split(".").map(Number);
|
|
9
|
+
if (major < 22) {
|
|
10
|
+
console.error(
|
|
11
|
+
`Please upgrade node to version 22 or higher. (The current node version is ${process.version})`,
|
|
12
|
+
);
|
|
13
|
+
exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
packagerCli(rawArgs).catch(e => {
|
|
17
|
+
console.error(e);
|
|
18
|
+
exit(1);
|
|
19
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { diffChars, diffLines } from "diff";
|
|
4
|
+
import {
|
|
5
|
+
compareSync,
|
|
6
|
+
type Difference,
|
|
7
|
+
type Options,
|
|
8
|
+
type Result,
|
|
9
|
+
} from "dir-compare";
|
|
10
|
+
import pico from "picocolors";
|
|
11
|
+
import { expect } from "vitest";
|
|
12
|
+
|
|
13
|
+
export const defaultOptions: Options = {
|
|
14
|
+
compareContent: true,
|
|
15
|
+
excludeFilter: "node_modules, .gitignore",
|
|
16
|
+
compareFileSync: normalizeCRLFCompare,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compare two directories recursively and asserts that their contents match.
|
|
21
|
+
* Logs any differences found (extra, missing, or changed files).
|
|
22
|
+
* Fails the test if any differences are detected.
|
|
23
|
+
*
|
|
24
|
+
* @param resultDir - Path to the directory containing actual results.
|
|
25
|
+
* @param expectDir - Path to the directory containing expected results.
|
|
26
|
+
* @param options - Optional dir-compare options to customize comparison.
|
|
27
|
+
*/
|
|
28
|
+
export function expectDirMatch(
|
|
29
|
+
expectDir: string,
|
|
30
|
+
resultDir: string,
|
|
31
|
+
options?: Options,
|
|
32
|
+
): void {
|
|
33
|
+
const compareOpts = { ...defaultOptions, ...options };
|
|
34
|
+
const compareResult: Result = compareSync(resultDir, expectDir, compareOpts);
|
|
35
|
+
const diffs = compareResult.diffSet!.filter(r => r.state !== "equal");
|
|
36
|
+
diffs.forEach(logDiff);
|
|
37
|
+
if (diffs.length > 0) {
|
|
38
|
+
expect.fail(`${resultDir} and ${expectDir} do not match`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** print a difference between two files or directories to the error log */
|
|
43
|
+
function logDiff(diff: Difference): void {
|
|
44
|
+
const { name1, name2, path1, path2, state, relativePath } = diff;
|
|
45
|
+
const relative = relativePath.endsWith("/")
|
|
46
|
+
? relativePath
|
|
47
|
+
: `${relativePath}/`;
|
|
48
|
+
if (state === "left") {
|
|
49
|
+
console.error(`Extra in result: ${relative}${name1}`);
|
|
50
|
+
} else if (state === "right") {
|
|
51
|
+
console.error(`Missing in result: ${relative}${name2}`);
|
|
52
|
+
} else {
|
|
53
|
+
const resultPath = path.join(path1!, name1!);
|
|
54
|
+
const expectPath = path.join(path2!, name2!);
|
|
55
|
+
console.error(`\n--- File ${relative}${name1} is different: ---`);
|
|
56
|
+
logFileDiff(resultPath, expectPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** error log a difference between two files */
|
|
61
|
+
function logFileDiff(resultPath: string, expectPath: string): void {
|
|
62
|
+
const resultStr = fs.readFileSync(resultPath, "utf8");
|
|
63
|
+
const expectStr = fs.readFileSync(expectPath, "utf8");
|
|
64
|
+
const diffResult = diffChars(resultStr, expectStr);
|
|
65
|
+
if (diffResult) {
|
|
66
|
+
if (process.env.FORCE_COLOR) {
|
|
67
|
+
const messages: string[] = [];
|
|
68
|
+
diffResult.forEach(part => {
|
|
69
|
+
const { value, added, removed } = part;
|
|
70
|
+
let spaced = value;
|
|
71
|
+
if (value.trim() === "") {
|
|
72
|
+
spaced = value
|
|
73
|
+
.replaceAll(" ", "█")
|
|
74
|
+
.replaceAll("\n", "\\n")
|
|
75
|
+
.replaceAll("\t", "→");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let colored = spaced;
|
|
79
|
+
if (added) {
|
|
80
|
+
colored = pico.bold(pico.green(spaced));
|
|
81
|
+
} else if (removed) {
|
|
82
|
+
colored = pico.bold(pico.red(spaced));
|
|
83
|
+
}
|
|
84
|
+
messages.push(colored);
|
|
85
|
+
});
|
|
86
|
+
console.error(messages.join(""));
|
|
87
|
+
} else {
|
|
88
|
+
const diffResult = diffLines(resultStr, expectStr);
|
|
89
|
+
diffResult.forEach(part => {
|
|
90
|
+
const { value, added, removed } = part;
|
|
91
|
+
let prefix = "";
|
|
92
|
+
if (added) {
|
|
93
|
+
prefix = "+ ";
|
|
94
|
+
} else if (removed) {
|
|
95
|
+
prefix = "- ";
|
|
96
|
+
}
|
|
97
|
+
console.error(prefix + value);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
console.log(`'${resultPath}' and '${expectPath}' match`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** compare but convert \r\n to \n on both path1 and path2 */
|
|
106
|
+
function normalizeCRLFCompare(
|
|
107
|
+
path1: string,
|
|
108
|
+
stat1: fs.Stats,
|
|
109
|
+
path2: string,
|
|
110
|
+
stat2: fs.Stats,
|
|
111
|
+
_options: Options,
|
|
112
|
+
): boolean {
|
|
113
|
+
if (stat1.isFile() && stat2.isFile()) {
|
|
114
|
+
const content1 = fs.readFileSync(path1, "utf8").replace(/\r\n/g, "\n");
|
|
115
|
+
const content2 = fs.readFileSync(path2, "utf8").replace(/\r\n/g, "\n");
|
|
116
|
+
return content1 === content2;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs, { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path, { dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { rimraf } from "rimraf";
|
|
6
|
+
import { expect, test } from "vitest";
|
|
7
|
+
import { packagerCli } from "../PackagerCli.ts";
|
|
8
|
+
import { expectDirMatch } from "./ExpectDirMatch.ts";
|
|
9
|
+
|
|
10
|
+
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
test("package two wgsl files into one bundle", async () => {
|
|
13
|
+
const projectDir = path.join(testDir, "wesl-package");
|
|
14
|
+
const distDir = path.join(projectDir, "dist");
|
|
15
|
+
const srcDir = path.join(projectDir, "src");
|
|
16
|
+
await rimraf(distDir);
|
|
17
|
+
await mkdir(distDir);
|
|
18
|
+
await packageCli(
|
|
19
|
+
`--projectDir ${projectDir}
|
|
20
|
+
--baseDir ${srcDir}
|
|
21
|
+
--updatePackageJson false
|
|
22
|
+
--src ${srcDir}/*.w[eg]sl
|
|
23
|
+
--outDir ${distDir}`,
|
|
24
|
+
);
|
|
25
|
+
const contents = await readFile(path.join(distDir, "weslBundle.js"), "utf8");
|
|
26
|
+
const normalized = contents.replace(/\r\n/g, "\n"); // normalize line endings to LF
|
|
27
|
+
expect(normalized).toMatchInlineSnapshot(`
|
|
28
|
+
"export const weslBundle = {
|
|
29
|
+
name: "test_wesl_package",
|
|
30
|
+
edition: "unstable_2025_1",
|
|
31
|
+
modules: {
|
|
32
|
+
"util.wgsl": "fn foo() {}",
|
|
33
|
+
"lib.wesl": "import package::util;\\n",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default weslBundle;
|
|
38
|
+
"
|
|
39
|
+
`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** set to true for manual review of test results */
|
|
43
|
+
const saveTestDir = false;
|
|
44
|
+
|
|
45
|
+
test("package multi ", async () => {
|
|
46
|
+
// create a copy of multi_package in a temporary directory so we can mutate it
|
|
47
|
+
let workDir: string;
|
|
48
|
+
if (saveTestDir) {
|
|
49
|
+
workDir = "/tmp/testing_multi";
|
|
50
|
+
await rimraf(workDir);
|
|
51
|
+
await mkdir(workDir);
|
|
52
|
+
} else {
|
|
53
|
+
workDir = await fs.mkdtemp(
|
|
54
|
+
path.join(tmpdir(), "wesl-packager-test-multi-"),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const multiDir = path.join(testDir, "multi_package");
|
|
60
|
+
await fs.cp(multiDir, workDir, {
|
|
61
|
+
recursive: true,
|
|
62
|
+
preserveTimestamps: true,
|
|
63
|
+
});
|
|
64
|
+
replaceInFile(path.join(workDir, "package.json"), {
|
|
65
|
+
multi_pkg_src: "multi_pkg",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const distDir = path.join(workDir, "dist");
|
|
69
|
+
// run wesl-packager in temporary directory
|
|
70
|
+
await packageCli(
|
|
71
|
+
`--projectDir ${workDir}
|
|
72
|
+
--src ${workDir}/shaders/**/*.wesl
|
|
73
|
+
--updatePackageJson
|
|
74
|
+
--multiBundle
|
|
75
|
+
--baseDir ${workDir}/shaders
|
|
76
|
+
--outDir ${distDir}`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const expectDir = path.join(testDir, "../../../test_pkg/multi_pkg");
|
|
80
|
+
expectDirMatch(workDir, expectDir);
|
|
81
|
+
} finally {
|
|
82
|
+
if (!saveTestDir) {
|
|
83
|
+
await rimraf(workDir);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function packageCli(argsLine: string): Promise<void> {
|
|
89
|
+
return packagerCli(argsLine.split(/\s+/));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Rewrite a file, replacing specified strings. */
|
|
93
|
+
async function replaceInFile(
|
|
94
|
+
filePath: string,
|
|
95
|
+
replacements: Record<string, string>,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
let content = await readFile(filePath, "utf8");
|
|
98
|
+
// content = content.replace(/\r\n/g, "\n"); // normalize line endings to LF
|
|
99
|
+
for (const [search, replace] of Object.entries(replacements)) {
|
|
100
|
+
content = content.replaceAll(search, replace);
|
|
101
|
+
}
|
|
102
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fn nest() {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fn two() { }
|
|
@@ -0,0 +1,20 @@
|
|
|
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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import package::util;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fn foo() {}
|