Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 21982a1

Browse filesBrowse files
authored
feat(napi-derive): auto invalid type gen (#2638)
1 parent 46bd338 commit 21982a1
Copy full SHA for 21982a1

File tree

Expand file treeCollapse file tree

11 files changed

+165
-33
lines changed
Filter options
Expand file treeCollapse file tree

11 files changed

+165
-33
lines changed

‎cli/package.json

Copy file name to clipboardExpand all lines: cli/package.json
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"devDependencies": {
8686
"@emnapi/core": "^1.4.0",
8787
"@emnapi/runtime": "^1.4.0",
88+
"@oxc-node/core": "^0.0.27",
8889
"@types/debug": "^4.1.12",
8990
"@types/inquirer": "^9.0.7",
9091
"@types/js-yaml": "^4.0.9",
@@ -95,7 +96,6 @@
9596
"env-paths": "^3.0.0",
9697
"esbuild": "^0.25.2",
9798
"prettier": "^3.5.3",
98-
"ts-node": "^10.9.2",
9999
"tslib": "^2.8.1",
100100
"typescript": "^5.8.2"
101101
},
@@ -116,10 +116,10 @@
116116
"url": "https://github.com/sponsors/Brooooooklyn"
117117
},
118118
"scripts": {
119-
"codegen": "node --loader ts-node/esm/transpile-only ./codegen/index.ts",
119+
"codegen": "node --import @oxc-node/core/register ./codegen/index.ts",
120120
"build": "tsc && yarn build:cjs",
121121
"build:cjs": "node ./esbuild.mjs",
122-
"test": "node --loader ts-node/esm/transpile-only ../node_modules/ava/entrypoints/cli.mjs"
122+
"test": "node --import @oxc-node/core/register ../node_modules/ava/entrypoints/cli.mjs"
123123
},
124124
"ava": {
125125
"extensions": {

‎cli/src/api/build.ts

Copy file name to clipboardExpand all lines: cli/src/api/build.ts
+85-4Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { spawn } from 'node:child_process'
22
import { createHash } from 'node:crypto'
3-
import { existsSync, mkdirSync, unlinkSync } from 'node:fs'
3+
import {
4+
existsSync,
5+
mkdirSync,
6+
unlinkSync,
7+
readFileSync,
8+
writeFileSync,
9+
} from 'node:fs'
410
import { createRequire } from 'node:module'
511
import { tmpdir, homedir } from 'node:os'
612
import { parse, join, resolve } from 'node:path'
713

814
import * as colors from 'colorette'
15+
import { groupBy, split } from 'lodash-es'
916
import { include as setjmpInclude, lib as setjmpLib } from 'wasm-sjlj'
1017

1118
import { BuildOptions as RawBuildOptions } from '../def/build.js'
@@ -85,6 +92,13 @@ export async function buildProject(options: BuildOptions) {
8592
)
8693
}
8794

95+
const depsNapiPackages = pkg.dependencies
96+
.map((p) => metadata.packages.find((c) => c.name === p.name))
97+
.filter(
98+
(c): c is Crate =>
99+
c != null && c.dependencies.some((d) => d.name == 'napi-derive'),
100+
)
101+
88102
const crateDir = parse(pkg.manifest_path).dir
89103

90104
const builder = new Builder(
@@ -107,6 +121,7 @@ export async function buildProject(options: BuildOptions) {
107121
),
108122
options.configPath ? resolvePath(options.configPath) : undefined,
109123
),
124+
depsNapiPackages,
110125
)
111126

112127
return builder.build()
@@ -126,6 +141,7 @@ class Builder {
126141
private readonly outputDir: string,
127142
private readonly targetDir: string,
128143
private readonly config: NapiConfig,
144+
private readonly depsNapiPackages: Crate[],
129145
) {}
130146

131147
get cdyLibName() {
@@ -463,9 +479,74 @@ class Builder {
463479
private setEnvs() {
464480
// type definition intermediate file
465481
this.envs.TYPE_DEF_TMP_PATH = this.getIntermediateTypeFile()
466-
// TODO:
467-
// remove after napi-derive@v3 release
468-
this.envs.CARGO_CFG_NAPI_RS_CLI_VERSION = CLI_VERSION
482+
// Validate the packages and dependencies
483+
if (existsSync(this.envs.TYPE_DEF_TMP_PATH)) {
484+
const { depsNapiPackages, crate } = this
485+
let shouldInvalidateSelf = false
486+
const content = readFileSync(this.envs.TYPE_DEF_TMP_PATH, 'utf-8')
487+
const typedefRaw = content
488+
.split('\n')
489+
.filter((line) => line.trim().length)
490+
.map((line) => {
491+
const [pkgName, ...jsonContents] = split(line, ':')
492+
return {
493+
pkgName,
494+
jsonContent: jsonContents.join(':'),
495+
}
496+
})
497+
const groupedTypedefRaw = groupBy(typedefRaw, ({ pkgName }) => pkgName)
498+
const invalidPackages = new Set<string>()
499+
for (const [pkgName, value] of Object.entries(groupedTypedefRaw)) {
500+
const packageInvalidKey = `NAPI_PACKAGE_${pkgName.toUpperCase().replaceAll('-', '_')}_INVALID`
501+
let done = false
502+
for (const { jsonContent } of value) {
503+
try {
504+
// package_name: { "done": true } was written by napi-build in project build.rs
505+
// if it exists, the package was successfully built and typedef was written
506+
const json = JSON.parse(jsonContent)
507+
if (json.done === true) {
508+
done = true
509+
break
510+
}
511+
} catch {
512+
done = false
513+
break
514+
}
515+
}
516+
if (!done) {
517+
process.env[packageInvalidKey] = `${Date.now()}`
518+
shouldInvalidateSelf = true
519+
invalidPackages.add(pkgName)
520+
}
521+
}
522+
const typedefPackages = new Set(Object.keys(groupedTypedefRaw))
523+
for (const crate of depsNapiPackages) {
524+
if (!typedefPackages.has(crate.name)) {
525+
debug('Set invalid package typedef: %i', crate.name)
526+
shouldInvalidateSelf = true
527+
process.env[
528+
`NAPI_PACKAGE_${crate.name.toUpperCase().replaceAll('-', '_')}_INVALID`
529+
] = `${Date.now()}`
530+
}
531+
}
532+
if (shouldInvalidateSelf) {
533+
debug('Set invalid package typedef: %i', crate.name)
534+
process.env[
535+
`NAPI_PACKAGE_${crate.name.toUpperCase().replaceAll('-', '_')}_INVALID`
536+
] = `${Date.now()}`
537+
}
538+
// clean invalid packages typedef
539+
if (invalidPackages.size) {
540+
debug('Clean invalid packages typedef: %O', invalidPackages)
541+
writeFileSync(
542+
this.envs.TYPE_DEF_TMP_PATH,
543+
typedefRaw
544+
.filter(({ pkgName }) => !invalidPackages.has(pkgName))
545+
.map(({ pkgName, jsonContent }) => `${pkgName}:${jsonContent}`)
546+
.join('\n'),
547+
)
548+
}
549+
}
469550

470551
// RUSTFLAGS
471552
let rustflags =

‎cli/src/utils/metadata.ts

Copy file name to clipboardExpand all lines: cli/src/utils/metadata.ts
+16-7Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export interface Crate {
2626
targets: CrateTarget[]
2727
features: Record<string, string[]>
2828
manifest_path: string
29+
dependencies: Array<{
30+
name: string
31+
source: string
32+
req: string
33+
kind: string | null
34+
rename: string | null
35+
optional: boolean
36+
uses_default_features: boolean
37+
features: string[]
38+
target: string | null
39+
registry: string | null
40+
}>
2941
}
3042

3143
export interface CargoWorkspaceMetadata {
@@ -51,20 +63,17 @@ export function parseMetadata(manifestPath: string) {
5163
'1',
5264
'--no-deps',
5365
],
54-
{ encoding: 'utf-8' },
66+
{ encoding: 'utf-8' },
5567
)
5668

5769
if (error) {
5870
throw new Error('cargo metadata failed to run', { cause: error })
5971
}
6072
if (status !== 0) {
6173
const simpleMessage = `cargo metadata exited with code ${status}`
62-
throw new Error(
63-
`${simpleMessage} and error message:\n\n${stderr}`,
64-
{
65-
cause: new Error(simpleMessage),
66-
},
67-
)
74+
throw new Error(`${simpleMessage} and error message:\n\n${stderr}`, {
75+
cause: new Error(simpleMessage),
76+
})
6877
}
6978

7079
try {

‎cli/src/utils/typegen.ts

Copy file name to clipboardExpand all lines: cli/src/utils/typegen.ts
+17-13Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sortBy } from 'lodash-es'
1+
import { sortBy, unionWith, isEqual } from 'lodash-es'
22

33
import { readFileAsync } from './misc.js'
44

@@ -149,18 +149,22 @@ export declare class ExternalObject<T> {
149149

150150
async function readIntermediateTypeFile(file: string) {
151151
const content = await readFileAsync(file, 'utf8')
152-
const defs = content
153-
.split('\n')
154-
.filter(Boolean)
155-
.map((line) => {
156-
line = line.trim()
157-
if (!line.startsWith('{')) {
158-
// crateName:{ "def": "", ... }
159-
const start = line.indexOf(':') + 1
160-
line = line.slice(start)
161-
}
162-
return JSON.parse(line) as TypeDefLine
163-
})
152+
const defs = unionWith(
153+
content
154+
.split('\n')
155+
.filter(Boolean)
156+
.map((line) => {
157+
line = line.trim()
158+
if (!line.startsWith('{')) {
159+
// crateName:{ "def": "", ... }
160+
const start = line.indexOf(':') + 1
161+
line = line.slice(start)
162+
}
163+
return JSON.parse(line) as TypeDefLine
164+
})
165+
.filter((def) => !!def.kind),
166+
(a, b) => isEqual(a, b),
167+
)
164168

165169
// move all `struct` def to the very top
166170
// and order the rest alphabetically.

‎crates/build/src/lib.rs

Copy file name to clipboardExpand all lines: crates/build/src/lib.rs
+9-3Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
use std::env;
2+
13
mod android;
24
mod macos;
35
mod wasi;
46
mod windows;
57

6-
use std::env;
7-
88
pub fn setup() {
9+
let package_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
10+
911
println!("cargo:rerun-if-env-changed=DEBUG_GENERATED_CODE");
1012
println!("cargo:rerun-if-env-changed=TYPE_DEF_TMP_PATH");
1113
println!("cargo:rerun-if-env-changed=CARGO_CFG_NAPI_RS_CLI_VERSION");
14+
println!(
15+
"cargo:rerun-if-env-changed=NAPI_PACKAGE_{}_INVALID",
16+
package_name.to_uppercase().replace("-", "_")
17+
);
1218

13-
match std::env::var("CARGO_CFG_TARGET_OS").as_deref() {
19+
match env::var("CARGO_CFG_TARGET_OS").as_deref() {
1420
Ok("macos") => {
1521
macos::setup();
1622
}

‎crates/build/src/wasi.rs

Copy file name to clipboardExpand all lines: crates/build/src/wasi.rs
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::{env, path::Path};
33
pub fn setup() {
44
let link_dir = env::var("EMNAPI_LINK_DIR").expect("EMNAPI_LINK_DIR must be set");
55
println!("cargo:rerun-if-env-changed=EMNAPI_LINK_DIR");
6-
println!("cargo:rerun-if-env-changed=WASI_REGISTER_TMP_PATH");
76
println!("cargo:rustc-link-search={link_dir}");
87
println!("cargo:rustc-link-lib=static=emnapi-basic-mt");
98
println!("cargo:rustc-link-arg=--export=malloc");

‎crates/macro/Cargo.toml

Copy file name to clipboardExpand all lines: crates/macro/Cargo.toml
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ default = ["type-def", "strict"]
1919
full = ["type-def", "strict", "compat-mode"]
2020
noop = ["napi-derive-backend/noop"]
2121
strict = ["napi-derive-backend/strict"]
22-
type-def = ["napi-derive-backend/type-def"]
22+
type-def = ["napi-derive-backend/type-def", "ctor"]
2323

2424
[dependencies]
2525
convert_case = "0.8"
26+
ctor = { version = "0.4", features = ["dtor"], optional = true }
2627
napi-derive-backend = { version = "2.0.0-alpha.28", path = "../backend" }
2728
proc-macro2 = "1"
2829
quote = "1"

‎crates/macro/src/expand/napi.rs

Copy file name to clipboardExpand all lines: crates/macro/src/expand/napi.rs
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@ use syn::{Attribute, Item};
2727
#[cfg(feature = "type-def")]
2828
static BUILT_FLAG: AtomicBool = AtomicBool::new(false);
2929

30+
#[cfg(feature = "type-def")]
31+
#[ctor::dtor]
32+
fn dtor() {
33+
if let Ok(ref type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
34+
let package_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
35+
println!("type_def_file {type_def_file} {package_name}");
36+
if let Ok(f) = fs::OpenOptions::new()
37+
.read(true)
38+
.append(true)
39+
.write(true)
40+
.open(type_def_file)
41+
{
42+
let mut writer = BufWriter::<fs::File>::new(f);
43+
if let Err(err) = writer
44+
.write_all(format!("{package_name}:{{\"done\": true}}\n").as_bytes())
45+
.and_then(|_| writer.flush())
46+
{
47+
eprintln!(
48+
"Failed to write type def file for `{package_name}`: {:?}",
49+
err
50+
);
51+
}
52+
}
53+
}
54+
}
55+
3056
pub fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> {
3157
#[cfg(feature = "type-def")]
3258
if BUILT_FLAG

‎examples/napi-shared/Cargo.toml

Copy file name to clipboardExpand all lines: examples/napi-shared/Cargo.toml
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ name = "napi-shared"
55
publish = false
66
version = "0.1.0"
77

8+
[lib]
9+
crate-type = ["cdylib", "lib"]
10+
811
[dependencies]
912
napi = { path = "../../crates/napi", default-features = false, features = [
1013
"napi8",

‎examples/napi-shared/build.rs

Copy file name to clipboard
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
napi_build::setup();
3+
}

‎yarn.lock

Copy file name to clipboardExpand all lines: yarn.lock
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ __metadata:
794794
"@napi-rs/cross-toolchain": "npm:^0.0.19"
795795
"@napi-rs/wasm-tools": "npm:^0.0.3"
796796
"@octokit/rest": "npm:^21.1.1"
797+
"@oxc-node/core": "npm:^0.0.27"
797798
"@types/debug": "npm:^4.1.12"
798799
"@types/inquirer": "npm:^9.0.7"
799800
"@types/js-yaml": "npm:^4.0.9"
@@ -812,7 +813,6 @@ __metadata:
812813
prettier: "npm:^3.5.3"
813814
semver: "npm:^7.7.1"
814815
toml: "npm:^3.0.0"
815-
ts-node: "npm:^10.9.2"
816816
tslib: "npm:^2.8.1"
817817
typanion: "npm:^3.14.0"
818818
typescript: "npm:^5.8.2"

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.