@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.
@@ -25,8 +25,7 @@ const parsed = parseArgs({
25
25
  node: { type: "string" },
26
26
  python: { type: "string" },
27
27
  "node-package": { type: "string" },
28
- "node-generator": { type: "string" },
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
- rmSync(join(facadeDirectory, "src", libraryFile), { force: true });
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
- typeof dependency === "string" && dependency.startsWith("workspace:") ? version : dependency,
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
- run("bun", [
142
- resolve(root, nodeGenerator),
143
- "--root",
144
- root,
145
- "--crate",
146
- crate,
147
- "--node",
148
- facadeDirectory,
149
- "--cargo-target",
150
- required("cargo-target"),
151
- "--node-package-base",
152
- `${nodePackage}-`,
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
- `uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
377
+ `${crate}-uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
185
378
  );
186
379
  if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
187
- run(generator, ["generate", "--language", "python", "--out-dir", generatedDirectory, library]);
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] !== "build") throw new Error("Expected build command");
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 linkedComponents = generatedModules.length > 2;
130
- const nodeBindings = join(nodeSource, "bindings.ts");
131
- const linkedNames = new Map(generatedModules.map((file) => [file, `_bindings-${file}`]));
132
- const generatedFiles = linkedComponents
133
- ? generatedModules.map((file) => join(nodeSource, linkedNames.get(file)!))
134
- : [join(nodeSource, "_bindings.ts"), join(nodeSource, "_bindings-ffi.ts")];
135
- replaceGenerated(join(nodeOutput, "index.ts"), nodeBindings);
136
- if (linkedComponents) {
137
- for (const file of generatedModules) {
138
- replaceGenerated(join(nodeOutput, file), join(nodeSource, linkedNames.get(file)!));
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
- } else {
141
- replaceGenerated(join(nodeOutput, `${libraryName}.ts`), generatedFiles[0]);
142
- replaceGenerated(join(nodeOutput, `${libraryName}-ffi.ts`), generatedFiles[1]);
143
- writeFileSync(
144
- nodeBindings,
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("cargo", [
210
- "run",
211
- "--release",
212
- "--package",
213
- crate,
214
- "--bin",
215
- "uniffi-bindgen",
216
- "--",
217
- "generate",
218
- "--language",
219
- "python",
220
- "--out-dir",
221
- pythonOutput,
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
- if (!existsSync(pythonInit)) {
230
- writeFileSync(
231
- pythonInit,
232
- `\"\"\"Python bindings for ${crate}.\"\"\"\n\nfrom .bindings import * # noqa: F403\n`,
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);