|
1 | 1 | #!/usr/bin/env node |
2 | | -import { copyFileSync, existsSync, mkdirSync } from 'fs'; |
| 2 | +/** |
| 3 | + * Stage Native AOT Node module into `motely-node/` for npm. |
| 4 | + * |
| 5 | + * `dotnet publish Motely/Motely.csproj` with `-p:PublishAot=true` and a RID produces: |
| 6 | + * Motely.node, Motely.js, import.cjs, Motely.d.ts (from Microsoft.JavaScript.NodeApi.Generator) |
| 7 | + * |
| 8 | + * There is **no** `index.cjs` from the generator — CommonJS consumers expect a small loader that |
| 9 | + * `require()`s the native addon under `bin/<rid>/motely.node`. |
| 10 | + */ |
| 11 | +import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'fs'; |
3 | 12 | import { dirname, join, resolve } from 'path'; |
4 | 13 | import { fileURLToPath } from 'url'; |
5 | 14 |
|
6 | 15 | const __dirname = dirname(fileURLToPath(import.meta.url)); |
7 | 16 | const repoRoot = resolve(__dirname, '..', '..'); |
8 | 17 | const pkgRoot = join(repoRoot, 'motely-node'); |
9 | | -const binDir = join(pkgRoot, 'bin', 'linux-x64'); |
10 | 18 |
|
11 | | -const publishDir = |
12 | | - process.env.MOTELY_NODE_PUBLISH_DIR || |
13 | | - join(repoRoot, 'Motely', 'bin', 'Release', 'net10.0', 'publish'); |
14 | | -// NativeAOT produces Motely.so on linux — rename to .node for Node.js |
15 | | -const addonSrc = existsSync(join(publishDir, 'Motely.so')) |
16 | | - ? join(publishDir, 'Motely.so') |
17 | | - : join(publishDir, 'motely.node'); |
| 19 | +const ridFromEnv = process.env.MOTELY_NODE_RID; |
| 20 | +const CANDIDATE_PUBLISH_DIRS = [ |
| 21 | + process.env.MOTELY_NODE_PUBLISH_DIR, |
| 22 | + ridFromEnv && join(repoRoot, 'Motely', 'bin', 'Release', 'net10.0', ridFromEnv, 'publish'), |
| 23 | + join(repoRoot, 'Motely', 'bin', 'Release', 'net10.0', 'linux-x64', 'publish'), |
| 24 | + join(repoRoot, 'Motely', 'bin', 'Release', 'net10.0', 'win-x64', 'publish'), |
| 25 | + join(repoRoot, 'Motely', 'bin', 'Release', 'net10.0', 'publish'), |
| 26 | +].filter(Boolean); |
18 | 27 |
|
19 | | -mkdirSync(binDir, { recursive: true }); |
| 28 | +function findPublishDir() { |
| 29 | + for (const dir of CANDIDATE_PUBLISH_DIRS) { |
| 30 | + const nodeFile = join(dir, 'Motely.node'); |
| 31 | + if (existsSync(nodeFile)) return dir; |
| 32 | + } |
| 33 | + return null; |
| 34 | +} |
| 35 | + |
| 36 | +const publishDir = findPublishDir(); |
| 37 | +if (!publishDir) { |
| 38 | + console.error('Native AOT publish output not found (expected Motely.node). Tried:'); |
| 39 | + for (const dir of CANDIDATE_PUBLISH_DIRS) console.error(` - ${dir}`); |
| 40 | + console.error('Build first, e.g. docker + dotnet publish Motely/Motely.csproj -c Release -f net10.0 -r linux-x64 -p:PublishAot=true'); |
| 41 | + console.error('On Windows after a local win-x64 AOT publish: MOTELY_NODE_RID=win-x64 node Motely/build/stage-node.mjs'); |
| 42 | + process.exit(1); |
| 43 | +} |
20 | 44 |
|
21 | | -if (!existsSync(addonSrc)) { |
22 | | - console.error(`Native addon not found at ${publishDir} (looked for Motely.so and motely.node)`); |
23 | | - console.error('Build linux-x64 NativeAOT first: docker run ... dotnet publish -r linux-x64 -p:PublishAot=true'); |
24 | | - process.exit(1); |
| 45 | +function runtimeIdFromPath(dir) { |
| 46 | + if (dir.includes('linux-x64')) return 'linux-x64'; |
| 47 | + if (dir.includes('win-x64')) return 'win-x64'; |
| 48 | + if (dir.includes('osx-x64')) return 'osx-x64'; |
| 49 | + if (dir.includes('osx-arm64')) return 'osx-arm64'; |
| 50 | + return 'linux-x64'; |
25 | 51 | } |
26 | 52 |
|
| 53 | +const rid = runtimeIdFromPath(publishDir); |
| 54 | + |
| 55 | +const binDir = join(pkgRoot, 'bin', rid); |
| 56 | +mkdirSync(binDir, { recursive: true }); |
| 57 | + |
| 58 | +const addonSrc = join(publishDir, 'Motely.node'); |
27 | 59 | copyFileSync(addonSrc, join(binDir, 'motely.node')); |
28 | | -console.log(`→ motely-node/bin/linux-x64/motely.node`); |
| 60 | +console.log(`→ motely-node/bin/${rid}/motely.node`); |
| 61 | + |
| 62 | +const copyIfPresent = (name) => { |
| 63 | + const src = join(publishDir, name); |
| 64 | + if (existsSync(src) && statSync(src).isFile()) { |
| 65 | + copyFileSync(src, join(pkgRoot, name)); |
| 66 | + console.log(`→ motely-node/${name}`); |
| 67 | + } |
| 68 | +}; |
| 69 | + |
| 70 | +for (const name of readdirSync(publishDir)) { |
| 71 | + if (name === 'Motely.node' || name === 'Motely.node.pdb') continue; |
| 72 | + const lower = name.toLowerCase(); |
| 73 | + if (lower.endsWith('.dll') || lower.endsWith('.deps.json') || lower.endsWith('.pdb')) { |
| 74 | + copyIfPresent(name); |
| 75 | + continue; |
| 76 | + } |
| 77 | + if (name.endsWith('.js') || name.endsWith('.cjs') || name.endsWith('.mjs') || name.endsWith('.d.ts')) { |
| 78 | + copyIfPresent(name); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +const indexCjs = `'use strict'; |
| 83 | +const path = require('node:path'); |
| 84 | +const addonPath = path.join(__dirname, 'bin', '${rid}', 'motely.node'); |
| 85 | +const addon = require(addonPath); |
| 86 | +module.exports = addon; |
| 87 | +module.exports.default = addon; |
| 88 | +`; |
| 89 | +writeFileSync(join(pkgRoot, 'index.cjs'), indexCjs); |
| 90 | +console.log('→ motely-node/index.cjs (CommonJS loader → native addon)'); |
| 91 | + |
| 92 | +const schemaCandidates = [join(repoRoot, 'jaml.schema.json'), join(repoRoot, 'Motely', 'jaml.schema.json')]; |
| 93 | +for (const p of schemaCandidates) { |
| 94 | + if (existsSync(p)) { |
| 95 | + copyFileSync(p, join(pkgRoot, 'jaml.schema.json')); |
| 96 | + console.log('→ motely-node/jaml.schema.json'); |
| 97 | + break; |
| 98 | + } |
| 99 | +} |
29 | 100 |
|
30 | | -const schemaJson = join(repoRoot, 'Motely', 'jaml.schema.json'); |
31 | | -if (existsSync(schemaJson)) { |
32 | | - copyFileSync(schemaJson, join(pkgRoot, 'jaml.schema.json')); |
33 | | - console.log('→ motely-node/jaml.schema.json'); |
| 101 | +/** Motely.d.ts imports these paths; publish output does not ship them — empty modules satisfy tsc. */ |
| 102 | +const TYPEDEF_STUBS = [ |
| 103 | + 'System.Runtime', |
| 104 | + 'System.Runtime.Intrinsics', |
| 105 | + 'System.Text.Json', |
| 106 | + 'System.Collections', |
| 107 | + 'YamlDotNet', |
| 108 | +]; |
| 109 | +for (const name of TYPEDEF_STUBS) { |
| 110 | + const stubPath = join(pkgRoot, `${name}.d.ts`); |
| 111 | + if (!existsSync(stubPath)) { |
| 112 | + writeFileSync(stubPath, 'export {};\n'); |
| 113 | + console.log(`→ motely-node/${name}.d.ts (stub for Motely.d.ts imports)`); |
| 114 | + } |
34 | 115 | } |
0 commit comments