Skip to content

Commit c8b7985

Browse files
committed
Build typescript declaration and javascript entrypoint
1 parent 6a0a576 commit c8b7985

File tree

8 files changed

+281
-1800
lines changed

8 files changed

+281
-1800
lines changed

package-lock.json

Lines changed: 127 additions & 1781 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ferric-example/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ Cargo.lock
33

44
/*.xcframework/
55
/*.android.node/
6+
7+
# Generated files
8+
/libferric_example.d.ts
9+
/libferric_example.js

packages/ferric/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"start": "tsx src/run.ts"
1111
},
1212
"dependencies": {
13-
"@napi-rs/cli": "3.0.0-alpha.78",
13+
"@napi-rs/cli": "^2.18.4",
1414
"@commander-js/extra-typings": "^13.1.0",
1515
"bufout": "^0.3.1",
1616
"chalk": "^5.4.1",

packages/ferric/src/build.ts

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from "node:path";
2+
import fs from "node:fs";
23

34
import { Command, Option } from "@commander-js/extra-typings";
45
import chalk from "chalk";
@@ -13,6 +14,8 @@ import {
1314
determineXCFrameworkFilename,
1415
createXCframework,
1516
createUniversalAppleLibrary,
17+
determineLibraryFilename,
18+
prettyPath,
1619
} from "react-native-node-api-modules";
1720

1821
import { UsageError } from "./errors.js";
@@ -26,6 +29,22 @@ import {
2629
ensureInstalledTargets,
2730
filterTargetsByPlatform,
2831
} from "./targets.js";
32+
import { generateTypeScriptDeclarations } from "./napi-rs.js";
33+
34+
type EntrypointOptions = {
35+
outputPath: string;
36+
libraryName: string;
37+
};
38+
async function generateEntrypoint({
39+
outputPath,
40+
libraryName,
41+
}: EntrypointOptions) {
42+
await fs.promises.writeFile(
43+
outputPath,
44+
"module.exports = require('./" + libraryName + ".node');",
45+
"utf8"
46+
);
47+
}
2948

3049
const ANDROID_TRIPLET_PER_TARGET: Record<AndroidTargetName, AndroidTriplet> = {
3150
"aarch64-linux-android": "aarch64-linux-android",
@@ -94,9 +113,9 @@ export const buildCommand = new Command("build")
94113
const androidTargets = filterTargetsByPlatform(targets, "android");
95114

96115
const targetsDescription =
97-
targets.size === 1
98-
? `${targets.size} target`
99-
: `${targets.size} targets`;
116+
targets.size +
117+
(targets.size === 1 ? " target" : " targets") +
118+
chalk.dim(" (" + [...targets].join(", ") + ")");
100119
const [appleLibraries, androidLibraries] = await oraPromise(
101120
Promise.all([
102121
Promise.all(
@@ -127,8 +146,44 @@ export const buildCommand = new Command("build")
127146
}
128147
);
129148

130-
// TODO: Call napi.rs to generate the .d.ts
131-
// TODO: Generate an entrypoint
149+
const libraryName = determineLibraryFilename([
150+
...androidLibraries.map(([, outputPath]) => outputPath),
151+
]);
152+
153+
const declarationsFilename = `${libraryName}.d.ts`;
154+
const declarationsPath = path.join(outputPath, declarationsFilename);
155+
await oraPromise(
156+
generateTypeScriptDeclarations({
157+
outputFilename: declarationsFilename,
158+
createPath: process.cwd(),
159+
outputPath,
160+
}),
161+
{
162+
text: "Generating TypeScript declarations",
163+
successText: `Generated TypeScript declarations ${prettyPath(
164+
declarationsPath
165+
)}`,
166+
failText: (error) =>
167+
`Failed to generate TypeScript declarations: ${error.message}`,
168+
}
169+
);
170+
171+
const entrypointPath = path.join(outputPath, `${libraryName}.js`);
172+
173+
await oraPromise(
174+
generateEntrypoint({
175+
libraryName,
176+
outputPath: entrypointPath,
177+
}),
178+
{
179+
text: `Generating entrypoint`,
180+
successText: `Generated entrypoint into ${prettyPath(
181+
entrypointPath
182+
)}`,
183+
failText: (error) =>
184+
`Failed to generate entrypoint: ${error.message}`,
185+
}
186+
);
132187

133188
if (androidLibraries.length > 0) {
134189
const libraryPathByTriplet = Object.fromEntries(
@@ -154,8 +209,8 @@ export const buildCommand = new Command("build")
154209
}),
155210
{
156211
text: "Assembling Android libs directory",
157-
successText: `Android libs directory assembled into ${chalk.dim(
158-
path.relative(process.cwd(), androidLibsOutputPath)
212+
successText: `Android libs directory assembled into ${prettyPath(
213+
androidLibsOutputPath
159214
)}`,
160215
failText: ({ message }) =>
161216
`Failed to assemble Android libs directory: ${message}`,

packages/ferric/src/napi-rs.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import fs from "node:fs";
2+
import path from "node:path";
3+
4+
import { spawn } from "bufout";
5+
6+
const PACKAGE_ROOT = path.join(import.meta.dirname, "..");
7+
8+
type TypeScriptDeclarationsOptions = {
9+
/**
10+
* Path to the directory containing the Cargo.toml file.
11+
*/
12+
createPath: string;
13+
/**
14+
* Path to the output directory where the TypeScript declarations will be copied into.
15+
*/
16+
outputPath: string;
17+
/**
18+
* File name of the generated TypeScript declarations (including .d.ts).
19+
*/
20+
outputFilename: string;
21+
};
22+
23+
export async function generateTypeScriptDeclarations({
24+
createPath,
25+
outputPath,
26+
outputFilename,
27+
}: TypeScriptDeclarationsOptions) {
28+
// Using a temporary directory to avoid polluting crate with any other side-effects for generating TypeScript declarations
29+
const tempPath = fs.realpathSync(
30+
fs.mkdtempSync(path.join(PACKAGE_ROOT, "dts-tmp-"))
31+
);
32+
try {
33+
// Write a dummy package.json file to avoid errors from napi-rs
34+
await fs.promises.writeFile(
35+
path.join(tempPath, "package.json"),
36+
"{}",
37+
"utf8"
38+
);
39+
// Call into napi.rs to generate TypeScript declarations
40+
const napiCliPath = new URL(
41+
import.meta.resolve("@napi-rs/cli/scripts/index.js")
42+
).pathname;
43+
await spawn(
44+
// TODO: Resolve the CLI path (not using npx because we don't want to npx to mess up the cwd)
45+
napiCliPath,
46+
[
47+
"build",
48+
"--dts",
49+
outputFilename,
50+
"--cargo-cwd",
51+
// This doesn't understand absolute paths
52+
path.relative(tempPath, createPath),
53+
],
54+
{
55+
outputMode: "buffered",
56+
cwd: tempPath,
57+
}
58+
);
59+
// Copy out the generated TypeScript declarations
60+
await fs.promises.copyFile(
61+
path.join(tempPath, outputFilename),
62+
path.join(outputPath, outputFilename)
63+
);
64+
} finally {
65+
await fs.promises.rm(tempPath, { recursive: true, force: true });
66+
}
67+
}

packages/react-native-node-api-modules/src/node/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ export {
2020
createUniversalAppleLibrary,
2121
determineXCFrameworkFilename,
2222
} from "./prebuilds/apple.js";
23+
24+
export { determineLibraryFilename, prettyPath } from "./path-utils.js";

packages/react-native-node-api-modules/src/node/path-utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,20 @@ export function findNodeApiModulePathsByDependency({
301301
);
302302
}
303303

304+
/**
305+
* Determine the library filename based on the library paths.
306+
* Ensuring that all framework paths have the same base name.
307+
*/
308+
export function determineLibraryFilename(libraryPaths: string[]) {
309+
const libraryNames = libraryPaths.map((p) =>
310+
path.basename(p, path.extname(p))
311+
);
312+
const candidates = new Set<string>(libraryNames);
313+
assert(candidates.size === 1, "Expected all libraries to have the same name");
314+
const [name] = candidates;
315+
return name;
316+
}
317+
304318
export function getAutolinkPath(platform: PlatformName) {
305319
const result = path.resolve(__dirname, "../../auto-linked", platform);
306320
fs.mkdirSync(result, { recursive: true });

packages/react-native-node-api-modules/src/node/prebuilds/android.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "node:fs";
33
import path from "node:path";
44

55
import { AndroidTriplet } from "./triplets.js";
6+
import { determineLibraryFilename } from "../path-utils.js";
67

78
export const DEFAULT_ANDROID_TRIPLETS = [
89
"aarch64-linux-android",
@@ -24,17 +25,9 @@ export const ANDROID_ARCHITECTURES = {
2425
* Determine the filename of the Android libs directory based on the framework paths.
2526
* Ensuring that all framework paths have the same base name.
2627
*/
27-
export function determineAndroidLibsFilename(frameworkPaths: string[]) {
28-
const frameworkNames = frameworkPaths.map((p) =>
29-
path.basename(p, path.extname(p))
30-
);
31-
const candidates = new Set<string>(frameworkNames);
32-
assert(
33-
candidates.size === 1,
34-
"Expected all frameworks to have the same name"
35-
);
36-
const [name] = candidates;
37-
return `${name}.android.node`;
28+
export function determineAndroidLibsFilename(libraryPaths: string[]) {
29+
const libraryName = determineLibraryFilename(libraryPaths);
30+
return `${libraryName}.android.node`;
3831
}
3932

4033
type AndroidLibsDirectoryOptions = {

0 commit comments

Comments
 (0)