@dbx-tools/projen 0.6.163 → 0.6.174
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/README.md +56 -41
- package/index.ts +2 -1
- package/package.json +4 -5
- package/src/barrels.ts +134 -34
- package/src/project-js.ts +19 -22
- package/src/project-py.ts +83 -21
- package/src/project-rs.ts +175 -309
- package/src/release.ts +109 -4
- package/src/uniffi.ts +45 -0
- package/tasks/bump.ts +12 -2
- package/tasks/publish-uniffi-local.ts +3 -4
- package/tasks/publish.ts +22 -13
- package/tasks/rust.ts +17 -3
- package/tasks/uniffi-release.mjs +242 -29
- package/tasks/uniffi.ts +101 -55
package/tasks/uniffi-release.mjs
CHANGED
|
@@ -25,8 +25,7 @@ const parsed = parseArgs({
|
|
|
25
25
|
node: { type: "string" },
|
|
26
26
|
python: { type: "string" },
|
|
27
27
|
"node-package": { type: "string" },
|
|
28
|
-
"
|
|
29
|
-
ubrn: { type: "string" },
|
|
28
|
+
"native-package": { type: "string" },
|
|
30
29
|
"python-package": { type: "string" },
|
|
31
30
|
"cargo-target": { type: "string" },
|
|
32
31
|
"node-triple": { type: "string" },
|
|
@@ -65,9 +64,139 @@ const run = (command, args, cwd = root) => {
|
|
|
65
64
|
if (result.status !== 0) throw new Error(`${invocation.command} exited with ${result.status}`);
|
|
66
65
|
};
|
|
67
66
|
|
|
67
|
+
const singlePackage = (directory) => {
|
|
68
|
+
const packages = readdirSync(directory)
|
|
69
|
+
.filter((file) => file.endsWith(".tgz"))
|
|
70
|
+
.map((file) => resolve(directory, file));
|
|
71
|
+
if (packages.length !== 1) {
|
|
72
|
+
throw new Error(`Expected one npm package in ${directory}, found ${packages.length}`);
|
|
73
|
+
}
|
|
74
|
+
return packages[0];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const localWorkspacePackages = () => {
|
|
78
|
+
const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
|
|
79
|
+
const workspaces = Array.isArray(manifest.workspaces) ? manifest.workspaces : [];
|
|
80
|
+
return new Map(
|
|
81
|
+
workspaces.flatMap((directory) => {
|
|
82
|
+
const manifestPath = resolve(root, directory, "package.json");
|
|
83
|
+
if (!existsSync(manifestPath)) return [];
|
|
84
|
+
const workspaceManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
85
|
+
return workspaceManifest.name ? [[workspaceManifest.name, workspaceManifest.version]] : [];
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read one bundled facade's static imports so local workspace dependency stubs
|
|
92
|
+
* expose every requested ESM name. Callable proxy values support module helpers
|
|
93
|
+
* and base classes that execute while the facade is imported.
|
|
94
|
+
*/
|
|
95
|
+
const importedNames = (source, packageName) => {
|
|
96
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
97
|
+
const pattern = new RegExp(`(?:^|\\n)import\\s+([^;]+?)\\s+from\\s+["']${escaped}["'];`, "g");
|
|
98
|
+
const names = new Set();
|
|
99
|
+
let hasDefault = false;
|
|
100
|
+
for (const match of source.matchAll(pattern)) {
|
|
101
|
+
const clause = match[1].trim();
|
|
102
|
+
const named = /\{([\s\S]*?)\}/.exec(clause)?.[1];
|
|
103
|
+
if (named) {
|
|
104
|
+
for (const specifier of named.split(",")) {
|
|
105
|
+
const name = specifier.trim().split(/\s+as\s+/)[0];
|
|
106
|
+
if (name) names.add(name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const beforeNamed = clause.split(/[,{\s]/, 1)[0];
|
|
110
|
+
if (beforeNamed && beforeNamed !== "*" && !clause.startsWith("{")) hasDefault = true;
|
|
111
|
+
}
|
|
112
|
+
return { names: [...names].sort(), hasDefault };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const testNodeFacade = ({
|
|
116
|
+
facadeDirectory,
|
|
117
|
+
facadePackage,
|
|
118
|
+
manifest,
|
|
119
|
+
nativePackage,
|
|
120
|
+
nodePackage,
|
|
121
|
+
}) => {
|
|
122
|
+
const installDirectory = mkdtempSync(join(tmpdir(), "uniffi-facade-install-"));
|
|
123
|
+
try {
|
|
124
|
+
const workspacePackages = localWorkspacePackages();
|
|
125
|
+
const facadeSource = readFileSync(join(facadeDirectory, "lib", "index.js"), "utf8");
|
|
126
|
+
const bindings =
|
|
127
|
+
JSON.parse(readFileSync(join(root, "package.json"), "utf8")).dbxToolsConfig?.rust?.bindings ??
|
|
128
|
+
[];
|
|
129
|
+
const localDependencies = Object.keys(manifest.dependencies ?? {}).flatMap((name, index) => {
|
|
130
|
+
const workspaceVersion = workspacePackages.get(name);
|
|
131
|
+
if (!workspaceVersion) return [];
|
|
132
|
+
const binding = bindings.find((binding) => binding.nodePackage === name);
|
|
133
|
+
if (binding) {
|
|
134
|
+
const output = resolve(root, "dist/release", binding.crate, required("node-triple"));
|
|
135
|
+
if (existsSync(join(output, "npm-facade")) && existsSync(join(output, "npm"))) {
|
|
136
|
+
return [singlePackage(join(output, "npm-facade")), singlePackage(join(output, "npm"))];
|
|
137
|
+
}
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const directory = join(installDirectory, "local-dependencies", String(index));
|
|
141
|
+
mkdirSync(directory, { recursive: true });
|
|
142
|
+
writeFileSync(
|
|
143
|
+
join(directory, "package.json"),
|
|
144
|
+
`${JSON.stringify({
|
|
145
|
+
name,
|
|
146
|
+
version: workspaceVersion,
|
|
147
|
+
type: "module",
|
|
148
|
+
exports: "./index.js",
|
|
149
|
+
})}\n`,
|
|
150
|
+
);
|
|
151
|
+
const imports = importedNames(facadeSource, name);
|
|
152
|
+
writeFileSync(
|
|
153
|
+
join(directory, "index.js"),
|
|
154
|
+
[
|
|
155
|
+
"const stub = new Proxy(function () {}, { get: () => stub, apply: () => stub });",
|
|
156
|
+
...imports.names.map((name) => `export const ${name} = stub;`),
|
|
157
|
+
...(imports.hasDefault ? ["export default stub;"] : []),
|
|
158
|
+
"",
|
|
159
|
+
].join("\n"),
|
|
160
|
+
);
|
|
161
|
+
return [directory];
|
|
162
|
+
});
|
|
163
|
+
writeFileSync(
|
|
164
|
+
join(installDirectory, "package.json"),
|
|
165
|
+
`${JSON.stringify({ private: true, type: "module" })}\n`,
|
|
166
|
+
);
|
|
167
|
+
run(
|
|
168
|
+
"npm",
|
|
169
|
+
[
|
|
170
|
+
"install",
|
|
171
|
+
"--ignore-scripts",
|
|
172
|
+
"--no-audit",
|
|
173
|
+
"--no-fund",
|
|
174
|
+
"--package-lock=false",
|
|
175
|
+
facadePackage,
|
|
176
|
+
nativePackage,
|
|
177
|
+
...localDependencies,
|
|
178
|
+
],
|
|
179
|
+
installDirectory,
|
|
180
|
+
);
|
|
181
|
+
run("node", ["-e", `import(${JSON.stringify(nodePackage)})`], installDirectory);
|
|
182
|
+
} finally {
|
|
183
|
+
rmSync(installDirectory, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
68
187
|
const replaceVersion = (source, version) =>
|
|
69
188
|
source.replace(/^version = "[^"]+"$/m, `version = "${version}"`);
|
|
70
189
|
|
|
190
|
+
/** Resolve workspace and generated catalog protocols for a publishable facade. */
|
|
191
|
+
const facadeDependency = (name, dependency, version, catalog) => {
|
|
192
|
+
if (typeof dependency !== "string") return dependency;
|
|
193
|
+
if (dependency.startsWith("workspace:")) return version;
|
|
194
|
+
if (!dependency.startsWith("catalog:")) return dependency;
|
|
195
|
+
const resolved = catalog[name];
|
|
196
|
+
if (!resolved) throw new Error(`Missing root catalog entry for ${name}`);
|
|
197
|
+
return resolved;
|
|
198
|
+
};
|
|
199
|
+
|
|
71
200
|
const writable = (path) => {
|
|
72
201
|
if (existsSync(path)) chmodSync(path, statSync(path).mode | 0o200);
|
|
73
202
|
};
|
|
@@ -80,7 +209,6 @@ const libraryPath = (crate, cargoTarget, os) => {
|
|
|
80
209
|
};
|
|
81
210
|
|
|
82
211
|
const packageNode = ({
|
|
83
|
-
crate,
|
|
84
212
|
library,
|
|
85
213
|
output,
|
|
86
214
|
nodeDirectory,
|
|
@@ -90,9 +218,11 @@ const packageNode = ({
|
|
|
90
218
|
cpu,
|
|
91
219
|
version,
|
|
92
220
|
facade,
|
|
93
|
-
nodeGenerator,
|
|
94
221
|
}) => {
|
|
95
222
|
const libraryFile = basename(library);
|
|
223
|
+
const sourceManifest = JSON.parse(
|
|
224
|
+
readFileSync(resolve(root, nodeDirectory, "package.json"), "utf8"),
|
|
225
|
+
);
|
|
96
226
|
const nativePackage = resolve(output, "native-node");
|
|
97
227
|
mkdirSync(nativePackage, { recursive: true });
|
|
98
228
|
cpSync(library, join(nativePackage, libraryFile));
|
|
@@ -103,6 +233,7 @@ const packageNode = ({
|
|
|
103
233
|
name: `${nodePackage}-${nodeTriple}`,
|
|
104
234
|
version,
|
|
105
235
|
description: `Native ${nodeTriple} library for ${nodePackage}`,
|
|
236
|
+
repository: sourceManifest.repository,
|
|
106
237
|
license: "Apache-2.0",
|
|
107
238
|
os: [os],
|
|
108
239
|
cpu: [cpu],
|
|
@@ -117,12 +248,40 @@ const packageNode = ({
|
|
|
117
248
|
run("npm", ["pack", "--pack-destination", resolve(output, "npm")], nativePackage);
|
|
118
249
|
|
|
119
250
|
if (!facade) return;
|
|
251
|
+
packageNodeFacade({
|
|
252
|
+
output,
|
|
253
|
+
nodeDirectory,
|
|
254
|
+
nodePackage,
|
|
255
|
+
version,
|
|
256
|
+
nativePackage: singlePackage(resolve(output, "npm")),
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const packageNodeFacade = ({ output, nodeDirectory, nodePackage, version, nativePackage }) => {
|
|
120
261
|
const facadeDirectory = resolve(output, "facade-node");
|
|
262
|
+
rmSync(facadeDirectory, { recursive: true, force: true });
|
|
263
|
+
rmSync(resolve(output, "npm-facade"), { recursive: true, force: true });
|
|
264
|
+
mkdirSync(output, { recursive: true });
|
|
121
265
|
cpSync(resolve(root, nodeDirectory), facadeDirectory, { recursive: true });
|
|
122
|
-
|
|
266
|
+
const barrel = join(facadeDirectory, "index.ts");
|
|
267
|
+
writable(barrel);
|
|
268
|
+
writeFileSync(
|
|
269
|
+
barrel,
|
|
270
|
+
readFileSync(barrel, "utf8").replace(
|
|
271
|
+
/^export const PACKAGE_VERSION = .*;$/m,
|
|
272
|
+
`export const PACKAGE_VERSION = ${JSON.stringify(version)};`,
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
for (const file of readdirSync(join(facadeDirectory, "src"))) {
|
|
276
|
+
if (/\.(dll|dylib|so)$/.test(file)) {
|
|
277
|
+
rmSync(join(facadeDirectory, "src", file), { force: true });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
123
280
|
const manifestPath = join(facadeDirectory, "package.json");
|
|
124
281
|
writable(manifestPath);
|
|
125
282
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
283
|
+
const workspaceManifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
284
|
+
const catalog = workspaceManifest.catalog ?? {};
|
|
126
285
|
manifest.version = version;
|
|
127
286
|
manifest.private = false;
|
|
128
287
|
manifest.license = manifest.license === "UNLICENSED" ? "Apache-2.0" : manifest.license;
|
|
@@ -132,29 +291,50 @@ const packageNode = ({
|
|
|
132
291
|
manifest.dependencies = Object.fromEntries(
|
|
133
292
|
Object.entries(manifest.dependencies ?? {}).map(([name, dependency]) => [
|
|
134
293
|
name,
|
|
135
|
-
|
|
294
|
+
facadeDependency(name, dependency, version, catalog),
|
|
136
295
|
]),
|
|
137
296
|
);
|
|
297
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
298
|
+
// Node loads the facade from node_modules, so every TypeScript entry point
|
|
299
|
+
// must be emitted and advertised as JavaScript.
|
|
300
|
+
rmSync(join(facadeDirectory, "lib"), { recursive: true, force: true });
|
|
301
|
+
run(
|
|
302
|
+
"bun",
|
|
303
|
+
[
|
|
304
|
+
"build",
|
|
305
|
+
"index.ts",
|
|
306
|
+
"--outdir",
|
|
307
|
+
"lib",
|
|
308
|
+
"--target",
|
|
309
|
+
"node",
|
|
310
|
+
"--format",
|
|
311
|
+
"esm",
|
|
312
|
+
"--packages",
|
|
313
|
+
"external",
|
|
314
|
+
],
|
|
315
|
+
facadeDirectory,
|
|
316
|
+
);
|
|
317
|
+
const compiledPublish = { ...(manifest.publishConfig ?? {}) };
|
|
318
|
+
delete compiledPublish.access;
|
|
319
|
+
Object.assign(manifest, compiledPublish);
|
|
320
|
+
manifest.types = "./index.ts";
|
|
321
|
+
manifest.exports["."].types = "./index.ts";
|
|
322
|
+
delete manifest.publishConfig;
|
|
138
323
|
delete manifest.scripts;
|
|
139
324
|
delete manifest.devDependencies;
|
|
140
325
|
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
...(parsed.values.ubrn ? ["--ubrn", parsed.values.ubrn, "--skip-barrels"] : []),
|
|
154
|
-
"--skip-build",
|
|
155
|
-
]);
|
|
156
|
-
mkdirSync(resolve(output, "npm-facade"), { recursive: true });
|
|
157
|
-
run("npm", ["pack", "--pack-destination", resolve(output, "npm-facade")], facadeDirectory);
|
|
326
|
+
const facadeOutput = resolve(output, "npm-facade");
|
|
327
|
+
mkdirSync(facadeOutput, { recursive: true });
|
|
328
|
+
run("npm", ["pack", "--pack-destination", facadeOutput], facadeDirectory);
|
|
329
|
+
if (nativePackage) {
|
|
330
|
+
testNodeFacade({
|
|
331
|
+
facadeDirectory,
|
|
332
|
+
facadePackage: singlePackage(facadeOutput),
|
|
333
|
+
manifest,
|
|
334
|
+
nativePackage,
|
|
335
|
+
nodePackage,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
158
338
|
};
|
|
159
339
|
|
|
160
340
|
const packagePython = ({
|
|
@@ -172,6 +352,19 @@ const packagePython = ({
|
|
|
172
352
|
const pyproject = join(pythonRoot, "pyproject.toml");
|
|
173
353
|
writable(pyproject);
|
|
174
354
|
writeFileSync(pyproject, replaceVersion(readFileSync(pyproject, "utf8"), version));
|
|
355
|
+
const workspaceBindings =
|
|
356
|
+
JSON.parse(readFileSync(join(root, "package.json"), "utf8")).dbxToolsConfig?.rust?.bindings ??
|
|
357
|
+
[];
|
|
358
|
+
let metadata = readFileSync(pyproject, "utf8");
|
|
359
|
+
for (const binding of workspaceBindings) {
|
|
360
|
+
if (!binding.pythonPackage) continue;
|
|
361
|
+
const escaped = binding.pythonPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
362
|
+
metadata = metadata.replace(
|
|
363
|
+
new RegExp(`"${escaped} @ git\\+[^"\\n]+"`, "g"),
|
|
364
|
+
JSON.stringify(`${binding.pythonPackage}==${version}`),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
writeFileSync(pyproject, metadata);
|
|
175
368
|
|
|
176
369
|
const packageName = crate.replace(/^dbx-tools-/, "").replaceAll("-", "_");
|
|
177
370
|
const packageDirectory = resolve(pythonRoot, "src", "dbx_tools", packageName);
|
|
@@ -181,10 +374,19 @@ const packagePython = ({
|
|
|
181
374
|
"target",
|
|
182
375
|
cargoTarget,
|
|
183
376
|
"release",
|
|
184
|
-
|
|
377
|
+
`${crate}-uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
|
|
185
378
|
);
|
|
186
379
|
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
187
|
-
run(generator, [
|
|
380
|
+
run(generator, [
|
|
381
|
+
"generate",
|
|
382
|
+
"--language",
|
|
383
|
+
"python",
|
|
384
|
+
"--crate",
|
|
385
|
+
crate.replaceAll("-", "_"),
|
|
386
|
+
"--out-dir",
|
|
387
|
+
generatedDirectory,
|
|
388
|
+
library,
|
|
389
|
+
]);
|
|
188
390
|
const bindings = join(packageDirectory, "bindings.py");
|
|
189
391
|
writable(bindings);
|
|
190
392
|
const body = readFileSync(join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`), "utf8");
|
|
@@ -240,7 +442,6 @@ const build = () => {
|
|
|
240
442
|
const nodePackage = parsed.values["node-package"];
|
|
241
443
|
if (nodeDirectory && nodePackage) {
|
|
242
444
|
packageNode({
|
|
243
|
-
crate,
|
|
244
445
|
library,
|
|
245
446
|
output,
|
|
246
447
|
nodeDirectory,
|
|
@@ -250,7 +451,6 @@ const build = () => {
|
|
|
250
451
|
cpu,
|
|
251
452
|
version,
|
|
252
453
|
facade: parsed.values.facade === "true",
|
|
253
|
-
nodeGenerator: required("node-generator"),
|
|
254
454
|
});
|
|
255
455
|
}
|
|
256
456
|
|
|
@@ -269,5 +469,18 @@ const build = () => {
|
|
|
269
469
|
}
|
|
270
470
|
};
|
|
271
471
|
|
|
272
|
-
if (parsed.positionals[0]
|
|
273
|
-
build();
|
|
472
|
+
if (parsed.positionals[0] === "build") {
|
|
473
|
+
build();
|
|
474
|
+
} else if (parsed.positionals[0] === "facade") {
|
|
475
|
+
packageNodeFacade({
|
|
476
|
+
output: resolve(root, required("output")),
|
|
477
|
+
nodeDirectory: required("node"),
|
|
478
|
+
nodePackage: required("node-package"),
|
|
479
|
+
version: required("version"),
|
|
480
|
+
nativePackage: parsed.values["native-package"]
|
|
481
|
+
? resolve(root, parsed.values["native-package"])
|
|
482
|
+
: undefined,
|
|
483
|
+
});
|
|
484
|
+
} else {
|
|
485
|
+
throw new Error("Expected build or facade command");
|
|
486
|
+
}
|
package/tasks/uniffi.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import {
|
|
3
4
|
cpSync,
|
|
4
5
|
existsSync,
|
|
@@ -14,11 +15,13 @@ import { tmpdir } from "node:os";
|
|
|
14
15
|
import { basename, dirname, join, resolve } from "node:path";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { parseArgs } from "node:util";
|
|
17
|
-
import { spawnSync } from "node:child_process";
|
|
18
18
|
import { makeReadonly, makeWritable, stampGenerated } from "../src/generated.ts";
|
|
19
|
+
import type { RustWorkspaceMapping } from "../src/project-rs.ts";
|
|
19
20
|
import {
|
|
20
21
|
addExplicitInterfaceReexports,
|
|
22
|
+
addTypeScriptExtensionsToBindingImports,
|
|
21
23
|
makeDefaultedInterfaceParametersOptional,
|
|
24
|
+
mergePythonBindingExports,
|
|
22
25
|
} from "../src/uniffi.ts";
|
|
23
26
|
|
|
24
27
|
const { values } = parseArgs({
|
|
@@ -43,6 +46,11 @@ const root = values.root
|
|
|
43
46
|
: resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
44
47
|
const crate = values.crate;
|
|
45
48
|
const libraryName = crate.replaceAll("-", "_");
|
|
49
|
+
const workspace = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).dbxToolsConfig
|
|
50
|
+
?.rust as RustWorkspaceMapping | undefined;
|
|
51
|
+
const binding = workspace?.bindings.find((binding) => binding.crate === crate);
|
|
52
|
+
const dependencies =
|
|
53
|
+
workspace?.bindings.filter((candidate) => binding?.dependencies?.includes(candidate.crate)) ?? [];
|
|
46
54
|
const extension =
|
|
47
55
|
process.platform === "darwin" ? "dylib" : process.platform === "win32" ? "dll" : "so";
|
|
48
56
|
const prefix = process.platform === "win32" ? "" : "lib";
|
|
@@ -61,6 +69,9 @@ const run = (command: string, args: string[]) => {
|
|
|
61
69
|
const stderr = normalizedOutput(result.stderr ?? "");
|
|
62
70
|
if (stdout) process.stdout.write(`${stdout}\n`);
|
|
63
71
|
if (stderr) process.stderr.write(`${stderr}\n`);
|
|
72
|
+
if (result.error) {
|
|
73
|
+
throw new Error(`${command} failed: ${result.error.message}`, { cause: result.error });
|
|
74
|
+
}
|
|
64
75
|
if (result.status !== 0) throw new Error(`${command} exited with ${result.status}`);
|
|
65
76
|
};
|
|
66
77
|
|
|
@@ -126,42 +137,68 @@ if (values.node) {
|
|
|
126
137
|
const generatedModules = readdirSync(nodeOutput).filter(
|
|
127
138
|
(file) => file.endsWith(".ts") && file !== "index.ts",
|
|
128
139
|
);
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
for (const dependency of dependencies) {
|
|
141
|
+
if (!dependency.nodePackage) throw new Error(`Missing Node binding for ${dependency.crate}`);
|
|
142
|
+
const namespace = dependency.crate.replaceAll("-", "_");
|
|
143
|
+
for (const file of readdirSync(nodeOutput).filter((file) => file.endsWith(".ts"))) {
|
|
144
|
+
const path = join(nodeOutput, file);
|
|
145
|
+
const source = readFileSync(path, "utf8")
|
|
146
|
+
.replace(
|
|
147
|
+
new RegExp(`import (\\w+) from ["']\\./${namespace}["'];`, "g"),
|
|
148
|
+
`import { uniffiModule as $1 } from "${dependency.nodePackage}";`,
|
|
149
|
+
)
|
|
150
|
+
.replaceAll(`'./${namespace}'`, `'${dependency.nodePackage}'`)
|
|
151
|
+
.replaceAll(`"./${namespace}"`, `"${dependency.nodePackage}"`);
|
|
152
|
+
writeFileSync(path, source);
|
|
139
153
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
readFileSync(nodeBindings, "utf8").replaceAll(`./${libraryName}`, "./_bindings"),
|
|
146
|
-
);
|
|
147
|
-
writeFileSync(
|
|
148
|
-
generatedFiles[0],
|
|
149
|
-
readFileSync(generatedFiles[0], "utf8").replaceAll(`./${libraryName}-ffi`, "./_bindings-ffi"),
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
if (linkedComponents) {
|
|
153
|
-
for (const file of [nodeBindings, ...generatedFiles]) {
|
|
154
|
-
let source = readFileSync(file, "utf8");
|
|
155
|
-
for (const [generated, destination] of linkedNames) {
|
|
156
|
-
const from = generated.slice(0, -3);
|
|
157
|
-
const to = destination.slice(0, -3);
|
|
158
|
-
source = source
|
|
159
|
-
.replaceAll(`'./${from}'`, `'./${to}'`)
|
|
160
|
-
.replaceAll(`"./${from}"`, `"./${to}"`);
|
|
161
|
-
}
|
|
162
|
-
writeFileSync(file, source);
|
|
154
|
+
for (const suffix of [".ts", "-ffi.ts"]) {
|
|
155
|
+
const file = `${namespace}${suffix}`;
|
|
156
|
+
rmSync(join(nodeOutput, file), { force: true });
|
|
157
|
+
const index = generatedModules.indexOf(file);
|
|
158
|
+
if (index >= 0) generatedModules.splice(index, 1);
|
|
163
159
|
}
|
|
164
160
|
}
|
|
161
|
+
const ownModules = generatedModules.filter(
|
|
162
|
+
(file) => file === `${libraryName}.ts` || file === `${libraryName}-ffi.ts`,
|
|
163
|
+
);
|
|
164
|
+
if (ownModules.length !== 2 || generatedModules.length !== 2) {
|
|
165
|
+
throw new Error(`Unmapped UniFFI components in ${crate}: ${generatedModules.join(", ")}`);
|
|
166
|
+
}
|
|
167
|
+
writeFileSync(
|
|
168
|
+
join(nodeOutput, "index.ts"),
|
|
169
|
+
[
|
|
170
|
+
`export * from './${libraryName}';`,
|
|
171
|
+
`import uniffiModule from './${libraryName}';`,
|
|
172
|
+
"uniffiModule.initialize();",
|
|
173
|
+
"export { uniffiModule };",
|
|
174
|
+
"export async function uniffiInitAsync() {}",
|
|
175
|
+
"",
|
|
176
|
+
].join("\n"),
|
|
177
|
+
);
|
|
178
|
+
const nodeBindings = join(nodeSource, "bindings.ts");
|
|
179
|
+
const generatedFiles = [join(nodeSource, "_bindings.ts"), join(nodeSource, "_bindings-ffi.ts")];
|
|
180
|
+
const legacyExports = resolve(root, values.node, "exports.ts");
|
|
181
|
+
if (
|
|
182
|
+
existsSync(legacyExports) &&
|
|
183
|
+
readFileSync(legacyExports, "utf8").trim() === 'export * from "./src/bindings.ts";'
|
|
184
|
+
) {
|
|
185
|
+
makeWritable(legacyExports);
|
|
186
|
+
rmSync(legacyExports, { force: true });
|
|
187
|
+
}
|
|
188
|
+
replaceGenerated(join(nodeOutput, "index.ts"), nodeBindings);
|
|
189
|
+
replaceGenerated(join(nodeOutput, libraryName + ".ts"), generatedFiles[0]);
|
|
190
|
+
replaceGenerated(join(nodeOutput, libraryName + "-ffi.ts"), generatedFiles[1]);
|
|
191
|
+
writeFileSync(
|
|
192
|
+
nodeBindings,
|
|
193
|
+
readFileSync(nodeBindings, "utf8").replaceAll("./" + libraryName, "./_bindings"),
|
|
194
|
+
);
|
|
195
|
+
writeFileSync(
|
|
196
|
+
generatedFiles[0],
|
|
197
|
+
readFileSync(generatedFiles[0], "utf8").replaceAll(
|
|
198
|
+
"./" + libraryName + "-ffi",
|
|
199
|
+
"./_bindings-ffi",
|
|
200
|
+
),
|
|
201
|
+
);
|
|
165
202
|
for (const file of generatedFiles) {
|
|
166
203
|
writeFileSync(file, makeDefaultedInterfaceParametersOptional(readFileSync(file, "utf8")));
|
|
167
204
|
}
|
|
@@ -176,6 +213,13 @@ if (values.node) {
|
|
|
176
213
|
),
|
|
177
214
|
);
|
|
178
215
|
for (const file of [nodeBindings, ...generatedFiles]) {
|
|
216
|
+
// Consumers type-check these files through workspace source exports, while
|
|
217
|
+
// the generated runtime internals are validated by UniFFI's own build.
|
|
218
|
+
const source = addTypeScriptExtensionsToBindingImports(readFileSync(file, "utf8")).replace(
|
|
219
|
+
/[ \t]+$/gm,
|
|
220
|
+
"",
|
|
221
|
+
);
|
|
222
|
+
writeFileSync(file, `/* eslint-disable */\n// @ts-nocheck\n${source}`);
|
|
179
223
|
stampGenerated(file, {
|
|
180
224
|
tool: "UniFFI binding generation",
|
|
181
225
|
source: `the ${crate} Rust exports`,
|
|
@@ -206,32 +250,34 @@ if (values.python) {
|
|
|
206
250
|
crate.replace(/^dbx-tools-/, "").replaceAll("-", "_"),
|
|
207
251
|
);
|
|
208
252
|
const pythonOutput = mkdtempSync(join(tmpdir(), `${libraryName}-python-`));
|
|
209
|
-
run(
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
library,
|
|
223
|
-
]);
|
|
253
|
+
run(
|
|
254
|
+
join(targetDirectory, `${crate}-uniffi-bindgen${process.platform === "win32" ? ".exe" : ""}`),
|
|
255
|
+
[
|
|
256
|
+
"generate",
|
|
257
|
+
"--language",
|
|
258
|
+
"python",
|
|
259
|
+
"--crate",
|
|
260
|
+
libraryName,
|
|
261
|
+
"--out-dir",
|
|
262
|
+
pythonOutput,
|
|
263
|
+
library,
|
|
264
|
+
],
|
|
265
|
+
);
|
|
224
266
|
const generated = join(pythonOutput, `${libraryName}.py`);
|
|
225
267
|
const pythonBindings = join(pythonPackage, "bindings.py");
|
|
226
268
|
replaceGenerated(generated, pythonBindings);
|
|
227
269
|
stampGeneratedPython(pythonBindings);
|
|
228
270
|
const pythonInit = join(pythonPackage, "__init__.py");
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
271
|
+
makeWritable(pythonInit);
|
|
272
|
+
writeFileSync(
|
|
273
|
+
pythonInit,
|
|
274
|
+
mergePythonBindingExports(
|
|
275
|
+
existsSync(pythonInit) ? readFileSync(pythonInit, "utf8") : "",
|
|
276
|
+
readFileSync(pythonBindings, "utf8"),
|
|
277
|
+
{ crate, file: pythonInit },
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
makeReadonly(pythonInit);
|
|
235
281
|
const pythonLibrary = join(pythonPackage, basename(library));
|
|
236
282
|
makeWritable(pythonLibrary);
|
|
237
283
|
cpSync(library, pythonLibrary);
|