diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3784218a..e96aaa3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: neon-actions/build@v0.4 with: working-directory: ./pkgs/cargo-messages - target: linux-x64-gnu + input-directory: npm/linux-x64-gnu node-version: ${{ env.NODE_VERSION }} use-cross: false npm-publish: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cca88eed..a8422d6c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -65,7 +65,7 @@ jobs: uses: neon-actions/build@v0.4 with: working-directory: ./pkgs/cargo-messages - target: ${{ matrix.target }} + input-directory: npm/${{ matrix.target }} node-version: ${{ env.NODE_VERSION }} npm-publish: false github-release: ${{ needs.setup.outputs.action == 'publish' }} @@ -102,7 +102,7 @@ jobs: uses: neon-actions/build@v0.4 with: working-directory: ./pkgs/cargo-messages - target: ${{ matrix.target }} + input-directory: npm/${{ matrix.target }} node-version: ${{ env.NODE_VERSION }} npm-publish: false github-release: ${{ needs.setup.outputs.action == 'publish' }} @@ -139,7 +139,7 @@ jobs: uses: neon-actions/build@v0.4 with: working-directory: ./pkgs/cargo-messages - target: ${{ matrix.target }} + input-directory: npm/${{ matrix.target }} node-version: ${{ env.NODE_VERSION }} use-cross: true npm-publish: false diff --git a/pkgs/cli/index.js b/pkgs/cli/index.js index 4a56e9fd..03f94598 100755 --- a/pkgs/cli/index.js +++ b/pkgs/cli/index.js @@ -2,6 +2,13 @@ import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "module"; /******/ var __webpack_modules__ = ({ +/***/ 9074: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = require(__nccwpck_require__.ab + "index.node") + +/***/ }), + /***/ 1869: /***/ ((__unused_webpack_module, exports) => { @@ -10245,7 +10252,7 @@ function wrappy (fn, cb) { /***/ }), -/***/ 1217: +/***/ 3235: /***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => { @@ -10314,6 +10321,7 @@ class Dist { { name: 'cross-rs', summary: '' } ]; } + static extraSection() { } _log; _file; _mount; @@ -11956,6 +11964,7 @@ class Bump { { name: 'npm version', summary: '' } ]; } + static extraSection() { } _verbose; _dir; _workspaces; @@ -12027,10 +12036,13 @@ var temp = __nccwpck_require__(591); const rust_namespaceObject = JSON.parse('{"aarch64-apple-darwin":"darwin-arm64","x86_64-apple-darwin":"darwin-x64","aarch64-apple-ios":"ios-arm64","x86_64-apple-ios":"ios-x64","aarch64-linux-android":"android-arm64","armv7-linux-androideabi":"android-arm-eabi","i686-linux-android":"android-ia32","x86_64-linux-android":"android-x64","aarch64-pc-windows-msvc":"win32-arm64-msvc","i686-pc-windows-gnu":"win32-ia32-gnu","i686-pc-windows-msvc":"win32-ia32-msvc","x86_64-pc-windows-gnu":"win32-x64-gnu","x86_64-pc-windows-msvc":"win32-x64-msvc","aarch64-unknown-linux-gnu":"linux-arm64-gnu","aarch64-unknown-linux-musl":"linux-arm64-musl","arm-unknown-linux-gnueabihf":"linux-arm-gnueabihf","arm-unknown-linux-musleabihf":"linux-arm-musleabihf","armv7-unknown-linux-gnueabihf":"linux-arm-gnueabihf","armv7-unknown-linux-musleabihf":"linux-arm-musleabihf","i686-unknown-linux-gnu":"linux-ia32-gnu","i686-unknown-linux-musl":"linux-ia32-musl","mips-unknown-linux-gnu":"linux-mips-gnu","mips-unknown-linux-musl":"linux-mips-musl","mips64-unknown-linux-gnuabi64":"linux-mips64-gnuabi64","mips64-unknown-linux-muslabi64":"linux-mips64-muslabi64","mips64el-unknown-linux-gnuabi64":"linux-mips64el-gnuabi64","mips64el-unknown-linux-muslabi64":"linux-mips64el-muslabi64","mipsel-unknown-linux-gnu":"linux-mipsel-gnu","mipsel-unknown-linux-musl":"linux-mipsel-musl","powerpc-unknown-linux-gnu":"linux-powerpc-gnu","powerpc64-unknown-linux-gnu":"linux-powerpc64-gnu","powerpc64le-unknown-linux-gnu":"linux-powerpc64le-gnu","riscv64gc-unknown-linux-gnu":"linux-riscv64gc-gnu","s390x-unknown-linux-gnu":"linux-s390x-gnu","sparc64-unknown-linux-gnu":"linux-sparc64-gnu","x86_64-unknown-linux-gnu":"linux-x64-gnu","x86_64-unknown-linux-gnux32":"linux-x64-gnux32","x86_64-unknown-linux-musl":"linux-x64-musl","i686-unknown-freebsd":"freebsd-ia32","x86_64-unknown-freebsd":"freebsd-x64"}'); ;// CONCATENATED MODULE: ./data/node.json const node_namespaceObject = JSON.parse('{"darwin-arm64":{"platform":"darwin","arch":"arm64","abi":null,"llvm":["aarch64-apple-darwin"]},"darwin-x64":{"platform":"darwin","arch":"x64","abi":null,"llvm":["x86_64-apple-darwin"]},"ios-arm64":{"platform":"ios","arch":"arm64","abi":null,"llvm":["aarch64-apple-ios"]},"ios-x64":{"platform":"ios","arch":"x64","abi":null,"llvm":["x86_64-apple-ios"]},"android-arm64":{"platform":"android","arch":"arm64","abi":null,"llvm":["aarch64-linux-android"]},"android-arm-eabi":{"platform":"android","arch":"arm","abi":"eabi","llvm":["armv7-linux-androideabi"]},"android-ia32":{"platform":"android","arch":"ia32","abi":null,"llvm":["i686-linux-android"]},"android-x64":{"platform":"android","arch":"x64","abi":null,"llvm":["x86_64-linux-android"]},"win32-arm64-msvc":{"platform":"win32","arch":"arm64","abi":"msvc","llvm":["aarch64-pc-windows-msvc"]},"win32-ia32-gnu":{"platform":"win32","arch":"ia32","abi":"gnu","llvm":["i686-pc-windows-gnu"]},"win32-ia32-msvc":{"platform":"win32","arch":"ia32","abi":"msvc","llvm":["i686-pc-windows-msvc"]},"win32-x64-gnu":{"platform":"win32","arch":"x64","abi":"gnu","llvm":["x86_64-pc-windows-gnu"]},"win32-x64-msvc":{"platform":"win32","arch":"x64","abi":"msvc","llvm":["x86_64-pc-windows-msvc"]},"linux-arm64-gnu":{"platform":"linux","arch":"arm64","abi":"gnu","llvm":["aarch64-unknown-linux-gnu"]},"linux-arm64-musl":{"platform":"linux","arch":"arm64","abi":"musl","llvm":["aarch64-unknown-linux-musl"]},"linux-arm-gnueabihf":{"platform":"linux","arch":"arm","abi":"gnueabihf","llvm":["arm-unknown-linux-gnueabihf","armv7-unknown-linux-gnueabihf"]},"linux-arm-musleabihf":{"platform":"linux","arch":"arm","abi":"musleabihf","llvm":["arm-unknown-linux-musleabihf","armv7-unknown-linux-musleabihf"]},"linux-ia32-gnu":{"platform":"linux","arch":"ia32","abi":"gnu","llvm":["i686-unknown-linux-gnu"]},"linux-ia32-musl":{"platform":"linux","arch":"ia32","abi":"musl","llvm":["i686-unknown-linux-musl"]},"linux-mips-gnu":{"platform":"linux","arch":"mips","abi":"gnu","llvm":["mips-unknown-linux-gnu"]},"linux-mips-musl":{"platform":"linux","arch":"mips","abi":"musl","llvm":["mips-unknown-linux-musl"]},"linux-mips64-gnuabi64":{"platform":"linux","arch":"mips64","abi":"gnuabi64","llvm":["mips64-unknown-linux-gnuabi64"]},"linux-mips64-muslabi64":{"platform":"linux","arch":"mips64","abi":"muslabi64","llvm":["mips64-unknown-linux-muslabi64"]},"linux-mips64el-gnuabi64":{"platform":"linux","arch":"mips64el","abi":"gnuabi64","llvm":["mips64el-unknown-linux-gnuabi64"]},"linux-mips64el-muslabi64":{"platform":"linux","arch":"mips64el","abi":"muslabi64","llvm":["mips64el-unknown-linux-muslabi64"]},"linux-mipsel-gnu":{"platform":"linux","arch":"mipsel","abi":"gnu","llvm":["mipsel-unknown-linux-gnu"]},"linux-mipsel-musl":{"platform":"linux","arch":"mipsel","abi":"musl","llvm":["mipsel-unknown-linux-musl"]},"linux-powerpc-gnu":{"platform":"linux","arch":"powerpc","abi":"gnu","llvm":["powerpc-unknown-linux-gnu"]},"linux-powerpc64-gnu":{"platform":"linux","arch":"powerpc64","abi":"gnu","llvm":["powerpc64-unknown-linux-gnu"]},"linux-powerpc64le-gnu":{"platform":"linux","arch":"powerpc64le","abi":"gnu","llvm":["powerpc64le-unknown-linux-gnu"]},"linux-riscv64gc-gnu":{"platform":"linux","arch":"riscv64gc","abi":"gnu","llvm":["riscv64gc-unknown-linux-gnu"]},"linux-s390x-gnu":{"platform":"linux","arch":"s390x","abi":"gnu","llvm":["s390x-unknown-linux-gnu"]},"linux-sparc64-gnu":{"platform":"linux","arch":"sparc64","abi":"gnu","llvm":["sparc64-unknown-linux-gnu"]},"linux-x64-gnu":{"platform":"linux","arch":"x64","abi":"gnu","llvm":["x86_64-unknown-linux-gnu"]},"linux-x64-gnux32":{"platform":"linux","arch":"x64","abi":"gnux32","llvm":["x86_64-unknown-linux-gnux32"]},"linux-x64-musl":{"platform":"linux","arch":"x64","abi":"musl","llvm":["x86_64-unknown-linux-musl"]},"freebsd-ia32":{"platform":"freebsd","arch":"ia32","abi":null,"llvm":["i686-unknown-freebsd"]},"freebsd-x64":{"platform":"freebsd","arch":"x64","abi":null,"llvm":["x86_64-unknown-freebsd"]}}'); +;// CONCATENATED MODULE: ./data/family.json +const family_namespaceObject = JSON.parse('{"windows":{"win32-x64-msvc":"x86_64-pc-windows-msvc"},"macos":{"darwin-x64":"x86_64-apple-darwin","darwin-arm64":"aarch64-apple-darwin"},"linux":{"linux-x64-gnu":"x86_64-unknown-linux-gnu","linux-arm-gnueabihf":"armv7-unknown-linux-gnueabihf"},"desktop":{"win32-x64-msvc":"x86_64-pc-windows-msvc","darwin-x64":"x86_64-apple-darwin","darwin-arm64":"aarch64-apple-darwin","linux-x64-gnu":"x86_64-unknown-linux-gnu"},"mobile":{"win32-arm64-msvc":"aarch64-pc-windows-msvc","linux-arm-gnueabihf":"armv7-unknown-linux-gnueabihf","android-arm-eabi":"armv7-linux-androideabi"},"common":["desktop"],"extended":["desktop","mobile"]}'); ;// CONCATENATED MODULE: ./src/target.ts + function isRustTarget(x) { return (typeof x === 'string') && (x in rust_namespaceObject); } @@ -12047,6 +12059,31 @@ function assertIsNodeTarget(x) { throw new RangeError(`invalid Node target: ${x}`); } } +function isTargetFamilyKey(x) { + return (typeof x === 'string') && (x in family_namespaceObject); +} +function assertIsTargetFamilyKey(x) { + if (!isTargetFamilyKey(x)) { + throw new RangeError(`invalid target family name: ${x}`); + } +} +function lookupTargetFamily(key) { + return family_namespaceObject[key]; +} +function merge(maps) { + const merged = Object.create(null); + for (const map of maps) { + Object.assign(merged, map); + } + return merged; +} +function expandTargetFamily(family) { + return isTargetFamilyKey(family) + ? expandTargetFamily(lookupTargetFamily(family)) + : Array.isArray(family) + ? merge(family.map(expandTargetFamily)) + : family; +} function getTargetDescriptor(target) { const node = rust_namespaceObject[target]; if (!isNodeTarget(node)) { @@ -12384,24 +12421,42 @@ class SourceManifest extends AbstractManifest { } return new BinaryManifest(json); } - async addTargetPair(node, rust) { + async addTargetPair(pair) { + const { node, rust } = pair; const targets = this.cfg().targets; if (targets[node] === rust) { - return false; + return null; } targets[node] = rust; await this.save(); - return true; + return pair; } async addNodeTarget(target) { const rt = node2Rust(target); if (rt.length > 1) { throw new Error(`multiple Rust targets found for Node target ${target}; please specify one of ${rt.join(', ')}`); } - return await this.addTargetPair(target, rt[0]); + return await this.addTargetPair({ node: target, rust: rt[0] }); } async addRustTarget(target) { - return await this.addTargetPair(rust2Node(target), target); + return await this.addTargetPair({ node: rust2Node(target), rust: target }); + } + async addTargets(family) { + const targets = this.cfg().targets; + let modified = []; + for (const [key, value] of Object.entries(family)) { + const node = key; + const rust = value; + if (targets[node] === rust) { + continue; + } + targets[node] = rust; + modified.push({ node, rust }); + } + if (modified.length) { + await this.save(); + } + return modified; } async updateTargets(log, bundle) { const packages = this.packageNames(); @@ -12503,6 +12558,7 @@ class Tarball { { name: 'cross-rs', summary: '' } ]; } + static extraSection() { } _target; _addon; _inDir; @@ -12598,23 +12654,34 @@ class Tarball { + + +function optionArray(option) { + return option == null ? [] : [option]; +} const add_target_OPTIONS = [ { name: 'bundle', alias: 'b', type: String, defaultValue: null }, { name: 'platform', alias: 'p', type: String, defaultValue: null }, { name: 'arch', alias: 'a', type: String, defaultValue: null }, { name: 'abi', type: String, defaultValue: null }, + { name: 'out-dir', alias: 'o', type: String, defaultValue: 'npm' }, { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false } ]; class AddTarget { static summary() { return 'Add a new build target to package.json.'; } - static syntax() { return 'neon add-target [ | -p -a [--abi ]] [-b ]'; } + static syntax() { return 'neon add-target [ | -p

-a [--abi ]] [-o ] [-b ]'; } static options() { return [ - { name: '', summary: 'Full target name, in either Node or Rust convention. (Default: current target)' }, - { name: '-p, --platform ', summary: 'Target platform name. (Default: current platform)' }, + { name: '', summary: 'Full target name, in either Node or Rust convention.' }, + { + name: '', + summary: 'This may be a target name in either Node or Rust convention, or one of the Neon target family presets described below. (Default: current target)' + }, + { name: '-p, --platform

', summary: 'Target platform name. (Default: current platform)' }, { name: '-a, --arch ', summary: 'Target architecture name. (Default: current arch)' }, { name: '--abi ', summary: 'Target ABI name. (Default: current ABI)' }, - { name: '-b, --bundle ', summary: 'File to generate bundling metadata.' }, + { name: '-o, --out-dir ', summary: 'Output directory for target template tree. (Default: npm)' }, + { name: '-b, --bundle ', summary: 'File to generate bundling metadata.' }, { name: '', summary: 'This generated file ensures support for bundlers (e.g. @vercel/ncc), which rely on static analysis to detect and enable any addons used by the library.' @@ -12622,13 +12689,26 @@ class AddTarget { { name: '-v, --verbose', summary: 'Enable verbose logging. (Default: false)' } ]; } - static seeAlso() { - return []; + static seeAlso() { } + static extraSection() { + return { + title: 'Target Family Presets', + details: [ + { name: 'linux', summary: 'Common desktop Linux targets.' }, + { name: 'macos', summary: 'Common desktop macOS targets.' }, + { name: 'windows', summary: 'Common desktop Windows targets.' }, + { name: 'mobile', summary: 'Common mobile and tablet targets.' }, + { name: 'desktop', summary: 'All common desktop targets.' }, + { name: 'common', summary: 'All common targets.' }, + { name: 'extended', summary: 'All supported targets.' } + ] + }; } _platform; _arch; _abi; _target; + _outDir; _bundle; _verbose; constructor(argv) { @@ -12636,6 +12716,7 @@ class AddTarget { this._platform = options.platform || null; this._arch = options.arch || null; this._abi = options.abi || null; + this._outDir = options['out-dir'] || external_node_path_namespaceObject.join(process.cwd(), 'dist'); this._bundle = options.bundle || null; this._verbose = !!options.verbose; if (options.platform && !options.arch) { @@ -12654,7 +12735,10 @@ class AddTarget { this._target = options._unknown[0]; } else { - this._target = null; + this._target = `${options.platform}-${options.arch}`; + if (!!options.abi) { + this._target = `${this._target}-${options.abi}`; + } } } log(msg) { @@ -12665,26 +12749,46 @@ class AddTarget { async addTarget(sourceManifest) { if (!this._target) { this.log('adding default system target'); - return sourceManifest.addRustTarget(await getCurrentTarget(msg => this.log(msg))); + return optionArray(await sourceManifest.addRustTarget(await getCurrentTarget(msg => this.log(msg)))); } else if (isRustTarget(this._target)) { this.log(`adding Rust target ${this._target}`); - return sourceManifest.addRustTarget(this._target); + return optionArray(await sourceManifest.addRustTarget(this._target)); } else if (isNodeTarget(this._target)) { this.log(`adding Node target ${this._target}`); - return sourceManifest.addNodeTarget(this._target); + return optionArray(await sourceManifest.addNodeTarget(this._target)); + } + else if (isTargetFamilyKey(this._target)) { + return sourceManifest.addTargets(expandTargetFamily(this._target)); } else { throw new Error(`unrecognized target ${this._target}`); } } + async createTemplateTree(sourceManifest, pair) { + const { node, rust } = pair; + const binaryManifest = sourceManifest.manifestFor(rust); + this.log(`prebuild manifest: ${binaryManifest.stringify()}`); + const treeDir = external_node_path_namespaceObject.join(this._outDir, node); + this.log(`creating ${treeDir}`); + await promises_namespaceObject.mkdir(treeDir, { recursive: true }); + this.log(`created ${treeDir}`); + this.log(`creating ${treeDir}/package.json`); + await binaryManifest.save(treeDir); + this.log(`creating ${treeDir}/README.md`); + await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(treeDir, "README.md"), `# \`${binaryManifest.name}\`\n\n${binaryManifest.description}\n`); + } async run() { this.log(`reading package.json`); const sourceManifest = await SourceManifest.load(); this.log(`manifest: ${sourceManifest.stringify()}`); - if (await this.addTarget(sourceManifest)) { + const modified = await this.addTarget(sourceManifest); + if (modified.length) { sourceManifest.updateTargets(msg => this.log(msg), this._bundle); + for (const pair of modified) { + await this.createTemplateTree(sourceManifest, pair); + } } } } @@ -12714,6 +12818,7 @@ class UpdateTargets { { name: 'ncc', summary: '' } ]; } + static extraSection() { } _bundle; _verbose; constructor(argv) { @@ -12740,6 +12845,86 @@ class UpdateTargets { } } +;// CONCATENATED MODULE: ./src/commands/rust-target.ts + + + +const rust_target_OPTIONS = [ + { name: 'platform', alias: 'p', type: String, defaultValue: null }, + { name: 'arch', alias: 'a', type: String, defaultValue: null }, + { name: 'abi', type: String, defaultValue: null }, + { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false } +]; +class RustTarget { + static summary() { return 'Look up the Rust target triple for a given build target.'; } + static syntax() { return 'neon rust-target | (-p -a [--abi ])'; } + static options() { + return [ + { name: '', summary: 'Full target name in Node convention.' }, + { name: '-p, --platform ', summary: 'Target platform name.' }, + { name: '-a, --arch ', summary: 'Target architecture name.' }, + { name: '--abi ', summary: 'Target ABI name. (Default: null)' }, + { name: '-v, --verbose', summary: 'Enable verbose logging. (Default: false)' } + ]; + } + static seeAlso() { } + static extraSection() { } + _platform; + _arch; + _abi; + _target; + _verbose; + constructor(argv) { + const options = dist_default()(rust_target_OPTIONS, { argv, partial: true }); + this._platform = options.platform || null; + this._arch = options.arch || null; + this._abi = options.abi || null; + this._verbose = !!options.verbose; + if (options.platform && !options.arch) { + throw new Error("Option --platform requires option --arch to be specified as well."); + } + if (!options.platform && options.arch) { + throw new Error("Option --arch requires option --platform to be specified as well."); + } + if (options.abi && (!options.platform || !options.arch)) { + throw new Error("Option --abi requires both options --platform and --arch to be specified as well."); + } + let target; + if (!options.platform && !options.arch && !options.abi) { + if (!options._unknown || options._unknown.length === 0) { + throw new Error("No arguments found, expected or -p and -a options."); + } + target = options._unknown[0]; + } + else { + target = `${options.platform}-${options.arch}`; + if (!!options.abi) { + target = `${target}-${options.abi}`; + } + } + if (!isNodeTarget(target)) { + throw new Error(`${target} is not a valid Node target.`); + } + this._target = target; + } + log(msg) { + if (this._verbose) { + console.error("[neon rust-target] " + msg); + } + } + async run() { + this.log(`reading package.json`); + const sourceManifest = await SourceManifest.load(); + this.log(`manifest: ${sourceManifest.stringify()}`); + const targets = sourceManifest.cfg().targets; + const rust = targets[this._target]; + if (!rust) { + throw new Error(`no Rust target found for ${this._target}`); + } + console.log(rust); + } +} + // EXTERNAL MODULE: ./src/print.ts + 26 modules var print = __nccwpck_require__(9050); ;// CONCATENATED MODULE: ./src/commands/help.ts @@ -12754,6 +12939,7 @@ class Help { ]; } static seeAlso() { } + static extraSection() { } _name; constructor(argv) { this._name = argv.length > 0 ? asCommandName(argv[0]) : undefined; @@ -12778,6 +12964,7 @@ class Help { + var CommandName; (function (CommandName) { CommandName["Help"] = "help"; @@ -12788,6 +12975,7 @@ var CommandName; CommandName["AddTarget"] = "add-target"; CommandName["InstallBuilds"] = "install-builds"; CommandName["UpdateTargets"] = "update-targets"; + CommandName["RustTarget"] = "rust-target"; })(CommandName || (CommandName = {})); ; function isCommandName(s) { @@ -12808,7 +12996,8 @@ const COMMANDS = { [CommandName.Tarball]: Tarball, [CommandName.AddTarget]: AddTarget, [CommandName.InstallBuilds]: UpdateTargets, - [CommandName.UpdateTargets]: UpdateTargets + [CommandName.UpdateTargets]: UpdateTargets, + [CommandName.RustTarget]: RustTarget }; function commandFor(name) { return COMMANDS[name]; @@ -12820,7 +13009,8 @@ function summaries() { { name: CommandName.Bump, summary: Bump.summary() }, { name: CommandName.Tarball, summary: Tarball.summary() }, { name: CommandName.AddTarget, summary: AddTarget.summary() }, - { name: CommandName.UpdateTargets, summary: UpdateTargets.summary() } + { name: CommandName.UpdateTargets, summary: UpdateTargets.summary() }, + { name: CommandName.RustTarget, summary: RustTarget.summary() } ]; } @@ -12834,7 +13024,7 @@ __nccwpck_require__.a(module, async (__webpack_handle_async_dependencies__, __we /* harmony import */ var command_line_commands__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(5046); /* harmony import */ var command_line_commands__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(command_line_commands__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _print_js__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(9050); -/* harmony import */ var _command_js__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(1217); +/* harmony import */ var _command_js__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(3235); /* harmony import */ var node_module__WEBPACK_IMPORTED_MODULE_3__ = __nccwpck_require__(2033); /* harmony import */ var node_module__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__nccwpck_require__.n(node_module__WEBPACK_IMPORTED_MODULE_3__); @@ -15857,8 +16047,8 @@ const chalkStderr = createChalk({level: stderrColor ? stderrColor.level : 0}); /* harmony default export */ const chalk_source = (chalk); -// EXTERNAL MODULE: ./src/command.ts + 37 modules -var command = __nccwpck_require__(1217); +// EXTERNAL MODULE: ./src/command.ts + 39 modules +var command = __nccwpck_require__(3235); ;// CONCATENATED MODULE: ./src/print.ts @@ -15875,6 +16065,9 @@ function yellow(text) { function green(text) { return chalk_source.bold.greenBright(text); } +function purple(text) { + return chalk_source.bold.magentaBright(text); +} function commandUsage(name, command) { const sections = [ { @@ -15894,6 +16087,10 @@ function commandUsage(name, command) { if (seeAlso) { sections.push({ header: green('See Also:'), content: seeAlso }); } + const extraSection = command.extraSection(); + if (extraSection) { + sections.push({ header: purple(extraSection.title + ':'), content: extraSection.details }); + } return command_line_usage(sections).trimStart(); } function mainUsage() { @@ -15939,14 +16136,6 @@ function printError(e) { module.exports = eval("require")("@cargo-messages/android-arm-eabi"); -/***/ }), - -/***/ 4404: -/***/ ((module) => { - -module.exports = eval("require")("@cargo-messages/darwin-arm64"); - - /***/ }), /***/ 2990: @@ -16263,7 +16452,7 @@ module.exports = (__nccwpck_require__(1869)/* .lazy */ .Vo)({ 'win32-x64-msvc': () => __nccwpck_require__(1324), 'aarch64-pc-windows-msvc': () => __nccwpck_require__(7894), 'darwin-x64': () => __nccwpck_require__(2990), - 'darwin-arm64': () => __nccwpck_require__(4404), + 'darwin-arm64': () => __nccwpck_require__(9074), 'linux-x64-gnu': () => __nccwpck_require__(1316), 'linux-arm-gnueabihf': () => __nccwpck_require__(5379), 'android-arm-eabi': () => __nccwpck_require__(1738) diff --git a/src/cli/data/family.json b/src/cli/data/family.json new file mode 100644 index 00000000..f8773713 --- /dev/null +++ b/src/cli/data/family.json @@ -0,0 +1,26 @@ +{ + "windows": { + "win32-x64-msvc": "x86_64-pc-windows-msvc" + }, + "macos": { + "darwin-x64": "x86_64-apple-darwin", + "darwin-arm64": "aarch64-apple-darwin" + }, + "linux": { + "linux-x64-gnu": "x86_64-unknown-linux-gnu", + "linux-arm-gnueabihf": "armv7-unknown-linux-gnueabihf" + }, + "desktop": { + "win32-x64-msvc": "x86_64-pc-windows-msvc", + "darwin-x64": "x86_64-apple-darwin", + "darwin-arm64": "aarch64-apple-darwin", + "linux-x64-gnu": "x86_64-unknown-linux-gnu" + }, + "mobile": { + "win32-arm64-msvc": "aarch64-pc-windows-msvc", + "linux-arm-gnueabihf": "armv7-unknown-linux-gnueabihf", + "android-arm-eabi": "armv7-linux-androideabi" + }, + "common": ["desktop"], + "extended": ["desktop", "mobile"] +} diff --git a/src/cli/src/command.ts b/src/cli/src/command.ts index ad652585..1bc164c6 100644 --- a/src/cli/src/command.ts +++ b/src/cli/src/command.ts @@ -3,17 +3,21 @@ import Bump from './commands/bump.js'; import Tarball from './commands/tarball.js'; import AddTarget from './commands/add-target.js'; import UpdateTargets from './commands/update-targets.js'; +import RustTarget from './commands/rust-target.js'; import Help from './commands/help.js'; export interface Command { run(): Promise; } +export type CommandSection = { title: string, details: CommandDetail[] }; + export interface CommandStatics { summary(): string; syntax(): string; options(): CommandDetail[]; seeAlso(): CommandDetail[] | void; + extraSection(): CommandSection | void; } export type CommandClass = (new (argv: string[]) => Command) & CommandStatics; @@ -31,7 +35,8 @@ export enum CommandName { Tarball = 'tarball', AddTarget = 'add-target', InstallBuilds = 'install-builds', // deprecated but retained for compat - UpdateTargets = 'update-targets' + UpdateTargets = 'update-targets', + RustTarget = 'rust-target' }; export function isCommandName(s: string): s is CommandName { @@ -54,7 +59,8 @@ const COMMANDS: Record = { [CommandName.Tarball]: Tarball, [CommandName.AddTarget]: AddTarget, [CommandName.InstallBuilds]: UpdateTargets, // deprecated but retained for compat - [CommandName.UpdateTargets]: UpdateTargets + [CommandName.UpdateTargets]: UpdateTargets, + [CommandName.RustTarget]: RustTarget }; export function commandFor(name: CommandName): CommandClass { @@ -68,6 +74,7 @@ export function summaries(): CommandDetail[] { { name: CommandName.Bump, summary: Bump.summary() }, { name: CommandName.Tarball, summary: Tarball.summary() }, { name: CommandName.AddTarget, summary: AddTarget.summary() }, - { name: CommandName.UpdateTargets, summary: UpdateTargets.summary() } + { name: CommandName.UpdateTargets, summary: UpdateTargets.summary() }, + { name: CommandName.RustTarget, summary: RustTarget.summary() } ]; } diff --git a/src/cli/src/commands/add-target.ts b/src/cli/src/commands/add-target.ts index 62ccc79c..a27014a3 100644 --- a/src/cli/src/commands/add-target.ts +++ b/src/cli/src/commands/add-target.ts @@ -1,26 +1,38 @@ +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; import commandLineArgs from 'command-line-args'; -import { Command, CommandDetail } from '../command.js'; -import { getCurrentTarget, isNodeTarget, isRustTarget } from '../target.js'; +import { Command, CommandDetail, CommandSection } from '../command.js'; +import { expandTargetFamily, getCurrentTarget, isNodeTarget, isRustTarget, isTargetFamilyKey, NodeTarget, RustTarget, TargetPair } from '../target.js'; import { SourceManifest } from '../manifest.js'; +function optionArray(option: T | undefined | null): T[] { + return option == null ? [] : [option]; +} + const OPTIONS = [ { name: 'bundle', alias: 'b', type: String, defaultValue: null }, { name: 'platform', alias: 'p', type: String, defaultValue: null }, { name: 'arch', alias: 'a', type: String, defaultValue: null }, { name: 'abi', type: String, defaultValue: null }, + { name: 'out-dir', alias: 'o', type: String, defaultValue: 'npm' }, { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false } ]; export default class AddTarget implements Command { static summary(): string { return 'Add a new build target to package.json.'; } - static syntax(): string { return 'neon add-target [ | -p -a [--abi ]] [-b ]'; } + static syntax(): string { return 'neon add-target [ | -p

-a [--abi ]] [-o ] [-b ]'; } static options(): CommandDetail[] { return [ - { name: '', summary: 'Full target name, in either Node or Rust convention. (Default: current target)' }, - { name: '-p, --platform ', summary: 'Target platform name. (Default: current platform)' }, + { name: '', summary: 'Full target name, in either Node or Rust convention.' }, + { + name: '', + summary: 'This may be a target name in either Node or Rust convention, or one of the Neon target family presets described below. (Default: current target)' + }, + { name: '-p, --platform

', summary: 'Target platform name. (Default: current platform)' }, { name: '-a, --arch ', summary: 'Target architecture name. (Default: current arch)' }, { name: '--abi ', summary: 'Target ABI name. (Default: current ABI)' }, - { name: '-b, --bundle ', summary: 'File to generate bundling metadata.' }, + { name: '-o, --out-dir ', summary: 'Output directory for target template tree. (Default: npm)' }, + { name: '-b, --bundle ', summary: 'File to generate bundling metadata.' }, { name: '', summary: 'This generated file ensures support for bundlers (e.g. @vercel/ncc), which rely on static analysis to detect and enable any addons used by the library.' @@ -28,15 +40,27 @@ export default class AddTarget implements Command { { name: '-v, --verbose', summary: 'Enable verbose logging. (Default: false)' } ]; } - static seeAlso(): CommandDetail[] | void { - return [ - ]; + static seeAlso(): CommandDetail[] | void { } + static extraSection(): CommandSection | void { + return { + title: 'Target Family Presets', + details: [ + { name: 'linux', summary: 'Common desktop Linux targets.' }, + { name: 'macos', summary: 'Common desktop macOS targets.' }, + { name: 'windows', summary: 'Common desktop Windows targets.' }, + { name: 'mobile', summary: 'Common mobile and tablet targets.' }, + { name: 'desktop', summary: 'All common desktop targets.' }, + { name: 'common', summary: 'All common targets.' }, + { name: 'extended', summary: 'All supported targets.' } + ] + }; } private _platform: string | null; private _arch: string | null; private _abi: string | null; private _target: string | null; + private _outDir: string; private _bundle: string | null; private _verbose: boolean; @@ -46,6 +70,7 @@ export default class AddTarget implements Command { this._platform = options.platform || null; this._arch = options.arch || null; this._abi = options.abi || null; + this._outDir = options['out-dir'] || path.join(process.cwd(), 'dist'); this._bundle = options.bundle || null; this._verbose = !!options.verbose; @@ -67,7 +92,11 @@ export default class AddTarget implements Command { } this._target = options._unknown[0]; } else { - this._target = null; + this._target = `${options.platform}-${options.arch}`; + + if (!!options.abi) { + this._target = `${this._target}-${options.abi}`; + } } } @@ -77,28 +106,52 @@ export default class AddTarget implements Command { } } - async addTarget(sourceManifest: SourceManifest): Promise { + async addTarget(sourceManifest: SourceManifest): Promise { if (!this._target) { this.log('adding default system target'); - return sourceManifest.addRustTarget(await getCurrentTarget(msg => this.log(msg))); + return optionArray(await sourceManifest.addRustTarget(await getCurrentTarget(msg => this.log(msg)))); } else if (isRustTarget(this._target)) { this.log(`adding Rust target ${this._target}`); - return sourceManifest.addRustTarget(this._target); + return optionArray(await sourceManifest.addRustTarget(this._target)); } else if (isNodeTarget(this._target)) { this.log(`adding Node target ${this._target}`); - return sourceManifest.addNodeTarget(this._target); + return optionArray(await sourceManifest.addNodeTarget(this._target)); + } else if (isTargetFamilyKey(this._target)) { + return sourceManifest.addTargets(expandTargetFamily(this._target)); } else { throw new Error(`unrecognized target ${this._target}`); } } + async createTemplateTree(sourceManifest: SourceManifest, pair: TargetPair): Promise { + const { node, rust } = pair; + const binaryManifest = sourceManifest.manifestFor(rust); + this.log(`prebuild manifest: ${binaryManifest.stringify()}`); + + const treeDir = path.join(this._outDir, node); + + this.log(`creating ${treeDir}`); + await fs.mkdir(treeDir, { recursive: true }); + this.log(`created ${treeDir}`); + + this.log(`creating ${treeDir}/package.json`); + await binaryManifest.save(treeDir); + + this.log(`creating ${treeDir}/README.md`); + await fs.writeFile(path.join(treeDir, "README.md"), `# \`${binaryManifest.name}\`\n\n${binaryManifest.description}\n`); + } + async run() { this.log(`reading package.json`); const sourceManifest = await SourceManifest.load(); this.log(`manifest: ${sourceManifest.stringify()}`); - if (await this.addTarget(sourceManifest)) { + const modified = await this.addTarget(sourceManifest); + if (modified.length) { sourceManifest.updateTargets(msg => this.log(msg), this._bundle); + for (const pair of modified) { + await this.createTemplateTree(sourceManifest, pair); + } } } } diff --git a/src/cli/src/commands/bump.ts b/src/cli/src/commands/bump.ts index d630d577..88b0df46 100644 --- a/src/cli/src/commands/bump.ts +++ b/src/cli/src/commands/bump.ts @@ -2,7 +2,7 @@ import { execa } from 'execa'; import commandLineArgs from 'command-line-args'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { Command, CommandDetail } from '../command.js'; +import { Command, CommandDetail, CommandSection } from '../command.js'; const OPTIONS = [ { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false }, @@ -43,6 +43,7 @@ export default class Bump implements Command { { name: 'npm version', summary: '' } ]; } + static extraSection(): CommandSection | void { } private _verbose: boolean; private _dir: string | null; diff --git a/src/cli/src/commands/dist.ts b/src/cli/src/commands/dist.ts index f3539cfb..b3dd115f 100644 --- a/src/cli/src/commands/dist.ts +++ b/src/cli/src/commands/dist.ts @@ -1,7 +1,7 @@ import { createReadStream } from 'node:fs'; import { copyFile } from 'node:fs/promises'; import commandLineArgs from 'command-line-args'; -import { Command, CommandDetail } from '../command.js'; +import { Command, CommandDetail, CommandSection } from '../command.js'; import { CargoMessages, CargoReader } from 'cargo-messages'; // FIXME: add options to infer crate name from manifests @@ -44,6 +44,7 @@ export default class Dist implements Command { { name: 'cross-rs', summary: '' } ]; } + static extraSection(): CommandSection | void { } private _log: string | null; private _file: string | null; diff --git a/src/cli/src/commands/help.ts b/src/cli/src/commands/help.ts index 01076c15..4006552b 100644 --- a/src/cli/src/commands/help.ts +++ b/src/cli/src/commands/help.ts @@ -1,5 +1,5 @@ import { printMainUsage, printCommandUsage } from '../print.js'; -import { Command, CommandName, CommandDetail, asCommandName } from '../command.js'; +import { Command, CommandName, CommandDetail, CommandSection, asCommandName } from '../command.js'; export default class Help implements Command { static summary(): string { return 'Display help information about Neon.'; } @@ -10,6 +10,7 @@ export default class Help implements Command { ]; } static seeAlso(): CommandDetail[] | void { } + static extraSection(): CommandSection | void { } private _name?: CommandName; diff --git a/src/cli/src/commands/rust-target.ts b/src/cli/src/commands/rust-target.ts new file mode 100644 index 00000000..0ab811c1 --- /dev/null +++ b/src/cli/src/commands/rust-target.ts @@ -0,0 +1,94 @@ +import commandLineArgs from 'command-line-args'; +import { Command, CommandDetail, CommandSection } from '../command.js'; +import { isNodeTarget, NodeTarget } from '../target.js'; +import { SourceManifest } from '../manifest.js'; + +const OPTIONS = [ + { name: 'platform', alias: 'p', type: String, defaultValue: null }, + { name: 'arch', alias: 'a', type: String, defaultValue: null }, + { name: 'abi', type: String, defaultValue: null }, + { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false } +]; + +export default class RustTarget implements Command { + static summary(): string { return 'Look up the Rust target triple for a given build target.'; } + static syntax(): string { return 'neon rust-target | (-p -a [--abi ])'; } + static options(): CommandDetail[] { + return [ + { name: '', summary: 'Full target name in Node convention.' }, + { name: '-p, --platform ', summary: 'Target platform name.' }, + { name: '-a, --arch ', summary: 'Target architecture name.' }, + { name: '--abi ', summary: 'Target ABI name. (Default: null)' }, + { name: '-v, --verbose', summary: 'Enable verbose logging. (Default: false)' } + ]; + } + static seeAlso(): CommandDetail[] | void { } + static extraSection(): CommandSection | void { } + + private _platform: string | null; + private _arch: string | null; + private _abi: string | null; + private _target: NodeTarget; + private _verbose: boolean; + + constructor(argv: string[]) { + const options = commandLineArgs(OPTIONS, { argv, partial: true }); + + this._platform = options.platform || null; + this._arch = options.arch || null; + this._abi = options.abi || null; + this._verbose = !!options.verbose; + + if (options.platform && !options.arch) { + throw new Error("Option --platform requires option --arch to be specified as well."); + } + + if (!options.platform && options.arch) { + throw new Error("Option --arch requires option --platform to be specified as well."); + } + + if (options.abi && (!options.platform || !options.arch)) { + throw new Error("Option --abi requires both options --platform and --arch to be specified as well."); + } + + let target: string; + + if (!options.platform && !options.arch && !options.abi) { + if (!options._unknown || options._unknown.length === 0) { + throw new Error("No arguments found, expected or -p and -a options."); + } + target = options._unknown[0]; + } else { + target = `${options.platform}-${options.arch}`; + + if (!!options.abi) { + target = `${target}-${options.abi}`; + } + } + + if (!isNodeTarget(target)) { + throw new Error(`${target} is not a valid Node target.`); + } + + this._target = target; + } + + log(msg: string) { + if (this._verbose) { + console.error("[neon rust-target] " + msg); + } + } + + async run() { + this.log(`reading package.json`); + const sourceManifest = await SourceManifest.load(); + this.log(`manifest: ${sourceManifest.stringify()}`); + + const targets = sourceManifest.cfg().targets; + const rust = targets[this._target]; + if (!rust) { + throw new Error(`no Rust target found for ${this._target}`); + } + console.log(rust); + } +} diff --git a/src/cli/src/commands/tarball.ts b/src/cli/src/commands/tarball.ts index fba94054..88d775c9 100644 --- a/src/cli/src/commands/tarball.ts +++ b/src/cli/src/commands/tarball.ts @@ -3,7 +3,7 @@ import * as path from 'node:path'; import * as temp from 'temp'; import commandLineArgs from 'command-line-args'; import { execa } from 'execa'; -import { Command, CommandDetail } from '../command.js'; +import { Command, CommandDetail, CommandSection } from '../command.js'; import { getCurrentTarget, getTargetDescriptor, isRustTarget } from '../target.js'; import { SourceManifest, BinaryManifest } from '../manifest.js'; @@ -36,6 +36,7 @@ export default class Tarball implements Command { { name: 'cross-rs', summary: '' } ]; } + static extraSection(): CommandSection | void { } private _target: string | null; private _addon: string; diff --git a/src/cli/src/commands/update-targets.ts b/src/cli/src/commands/update-targets.ts index af9234a4..e326e2af 100644 --- a/src/cli/src/commands/update-targets.ts +++ b/src/cli/src/commands/update-targets.ts @@ -2,7 +2,7 @@ import { execa } from 'execa'; import commandLineArgs from 'command-line-args'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { Command, CommandDetail } from '../command.js'; +import { Command, CommandDetail, CommandSection } from '../command.js'; import { SourceManifest } from '../manifest.js'; const OPTIONS = [ @@ -28,6 +28,7 @@ export default class UpdateTargets implements Command { { name: 'ncc', summary: '' } ]; } + static extraSection(): CommandSection | void { } private _bundle: string | null; private _verbose: boolean; diff --git a/src/cli/src/manifest.ts b/src/cli/src/manifest.ts index ed417374..0934438d 100644 --- a/src/cli/src/manifest.ts +++ b/src/cli/src/manifest.ts @@ -1,7 +1,7 @@ import { execa } from 'execa'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { RustTarget, NodeTarget, isRustTarget, isNodeTarget, assertIsRustTarget, assertIsNodeTarget, getCurrentTarget, getTargetDescriptor, node2Rust, rust2Node } from './target.js'; +import { RustTarget, NodeTarget, isRustTarget, isNodeTarget, assertIsRustTarget, assertIsNodeTarget, getTargetDescriptor, node2Rust, rust2Node, TargetMap, TargetPair } from './target.js'; export interface BinaryCfg { type: "binary", @@ -62,8 +62,6 @@ function assertIsBinaryCfg(json: unknown): asserts json is BinaryCfg { } } -export type TargetMap = {[key in NodeTarget]?: RustTarget}; - function assertIsTargetMap(json: unknown, path: string): asserts json is TargetMap { assertIsObject(json, path); for (const key in json) { @@ -374,28 +372,52 @@ export class SourceManifest extends AbstractManifest { return new BinaryManifest(json); } - async addTargetPair(node: NodeTarget, rust: RustTarget): Promise { + async addTargetPair(pair: TargetPair): Promise { + const { node, rust } = pair; const targets = this.cfg().targets; if (targets[node] === rust) { - return false; + return null; } targets[node] = rust; await this.save(); - return true; + return pair; } - async addNodeTarget(target: NodeTarget): Promise { + async addNodeTarget(target: NodeTarget): Promise { const rt = node2Rust(target); if (rt.length > 1) { throw new Error(`multiple Rust targets found for Node target ${target}; please specify one of ${rt.join(', ')}`); } - return await this.addTargetPair(target, rt[0]); + return await this.addTargetPair({ node: target, rust: rt[0] }); + } + + async addRustTarget(target: RustTarget): Promise { + return await this.addTargetPair({ node: rust2Node(target), rust: target }); } - async addRustTarget(target: RustTarget): Promise { - return await this.addTargetPair(rust2Node(target), target); + async addTargets(family: TargetMap): Promise { + const targets = this.cfg().targets; + let modified = []; + + for (const [key, value] of Object.entries(family)) { + const node: NodeTarget = key as NodeTarget; + const rust: RustTarget = value; + + if (targets[node] === rust) { + continue; + } + + targets[node] = rust; + modified.push({ node, rust }); + } + + if (modified.length) { + await this.save(); + } + + return modified; } async updateTargets(log: (msg: string) => void, bundle: string | null) { diff --git a/src/cli/src/print.ts b/src/cli/src/print.ts index cde7ac23..086b2c0c 100644 --- a/src/cli/src/print.ts +++ b/src/cli/src/print.ts @@ -18,6 +18,10 @@ function green(text: string): string { return chalk.bold.greenBright(text); } +function purple(text: string): string { + return chalk.bold.magentaBright(text); +} + function commandUsage(name: CommandName, command: CommandStatics): string { const sections = [ { @@ -39,6 +43,11 @@ function commandUsage(name: CommandName, command: CommandStatics): string { sections.push({ header: green('See Also:'), content: seeAlso }); } + const extraSection = command.extraSection(); + if (extraSection) { + sections.push({ header: purple(extraSection.title + ':'), content: extraSection.details }); + } + return commandLineUsage(sections).trimStart(); } diff --git a/src/cli/src/target.ts b/src/cli/src/target.ts index 33676b89..0032fcef 100644 --- a/src/cli/src/target.ts +++ b/src/cli/src/target.ts @@ -2,6 +2,7 @@ import { execa } from 'execa'; import RUST from '../data/rust.json'; import NODE from '../data/node.json'; +import FAMILY from '../data/family.json'; export type RustTarget = keyof(typeof RUST); @@ -27,6 +28,46 @@ export function assertIsNodeTarget(x: unknown): asserts x is NodeTarget { } } +export type TargetFamilyKey = keyof(typeof FAMILY); + +export function isTargetFamilyKey(x: unknown): x is TargetFamilyKey { + return (typeof x === 'string') && (x in FAMILY); +} + +export function assertIsTargetFamilyKey(x: unknown): asserts x is TargetFamilyKey { + if (!isTargetFamilyKey(x)) { + throw new RangeError(`invalid target family name: ${x}`); + } +} + +export type TargetPair = { node: NodeTarget, rust: RustTarget }; +export type TargetMap = { [key in NodeTarget]?: RustTarget }; + +export type TargetFamily = + TargetFamilyKey + | TargetFamilyKey[] + | TargetMap; + +function lookupTargetFamily(key: TargetFamilyKey): TargetFamily { + return FAMILY[key] as TargetFamily; +} + +function merge(maps: TargetMap[]): TargetMap { + const merged = Object.create(null); + for (const map of maps) { + Object.assign(merged, map); + } + return merged; +} + +export function expandTargetFamily(family: TargetFamily): TargetMap { + return isTargetFamilyKey(family) + ? expandTargetFamily(lookupTargetFamily(family)) + : Array.isArray(family) + ? merge(family.map(expandTargetFamily)) + : family; +} + export type TargetDescriptor = { node: NodeTarget, platform: string,