diff --git a/heft-plugins/heft-napi-rs-plugin/.npmignore b/heft-plugins/heft-napi-rs-plugin/.npmignore new file mode 100644 index 00000000000..ffb155d74e6 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/.npmignore @@ -0,0 +1,34 @@ +# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO. + +# Ignore all files by default, to avoid accidentally publishing unintended files. +* + +# Use negative patterns to bring back the specific things we want to publish. +!/bin/** +!/lib/** +!/lib-*/** +!/dist/** + +!CHANGELOG.md +!CHANGELOG.json +!heft-plugin.json +!rush-plugin-manifest.json +!ThirdPartyNotice.txt + +# Ignore certain patterns that should not get published. +/dist/*.stats.* +/lib/**/test/ +/lib-*/**/test/ +*.test.js + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README.md +# LICENSE + +# --------------------------------------------------------------------------- +# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. +# --------------------------------------------------------------------------- + +!/includes/** diff --git a/heft-plugins/heft-napi-rs-plugin/LICENSE b/heft-plugins/heft-napi-rs-plugin/LICENSE new file mode 100644 index 00000000000..b4b981d4502 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/LICENSE @@ -0,0 +1,24 @@ +@rushstack/heft-napi-rs-plugin + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/heft-plugins/heft-napi-rs-plugin/README.md b/heft-plugins/heft-napi-rs-plugin/README.md new file mode 100644 index 00000000000..758e13765a4 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/README.md @@ -0,0 +1,12 @@ +# @rushstack/heft-napi-rs-plugin + +This is a Heft plugin for using NAPI-RS. + +## Links + +- [CHANGELOG.md]( + https://github.com/microsoft/rushstack/blob/main/heft-plugins/heft-napi-rs-plugin/CHANGELOG.md) - Find + out what's new in the latest version +- [@rushstack/heft](https://www.npmjs.com/package/@rushstack/heft) - Heft is a config-driven toolchain that invokes popular tools such as TypeScript, ESLint, Jest, Webpack, and API Extractor. + +Heft is part of the [Rush Stack](https://rushstack.io/) family of projects. diff --git a/heft-plugins/heft-napi-rs-plugin/config/api-extractor.json b/heft-plugins/heft-napi-rs-plugin/config/api-extractor.json new file mode 100644 index 00000000000..74590d3c4f8 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/config/api-extractor.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + "mainEntryPointFilePath": "/lib/index.d.ts", + "apiReport": { + "enabled": true, + "reportFolder": "../../../common/reviews/api" + }, + "docModel": { + "enabled": false + }, + "dtsRollup": { + "enabled": true, + "betaTrimmedFilePath": "/dist/.d.ts" + } +} diff --git a/heft-plugins/heft-napi-rs-plugin/config/jest.config.json b/heft-plugins/heft-napi-rs-plugin/config/jest.config.json new file mode 100644 index 00000000000..4bb17bde3ee --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/config/jest.config.json @@ -0,0 +1,3 @@ +{ + "extends": "@rushstack/heft-node-rig/profiles/default/config/jest.config.json" +} diff --git a/heft-plugins/heft-napi-rs-plugin/config/rig.json b/heft-plugins/heft-napi-rs-plugin/config/rig.json new file mode 100644 index 00000000000..6ac88a96368 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "@rushstack/heft-node-rig" +} diff --git a/heft-plugins/heft-napi-rs-plugin/heft-plugin.json b/heft-plugins/heft-napi-rs-plugin/heft-plugin.json new file mode 100644 index 00000000000..40b6baba597 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/heft-plugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-plugin.schema.json", + + "taskPlugins": [ + { + "pluginName": "napi-rs-plugin", + "entryPoint": "./lib/NapiRsPlugin", + "optionsSchema": "./lib/schemas/heft-napi-rs-plugin.schema.json", + + "parameterScope": "napirs" + } + ] +} diff --git a/heft-plugins/heft-napi-rs-plugin/package.json b/heft-plugins/heft-napi-rs-plugin/package.json new file mode 100644 index 00000000000..ebe85cc8700 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/package.json @@ -0,0 +1,35 @@ +{ + "name": "@rushstack/heft-napi-rs-plugin", + "version": "0.0.0", + "description": "Heft plugin for NAPI-RS", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/rushstack.git", + "directory": "heft-plugins/heft-napi-rs-plugin" + }, + "homepage": "https://rushstack.io/pages/heft/overview/", + "main": "lib/index.js", + "types": "dist/heft-napi-rs-plugin.d.ts", + "license": "MIT", + "scripts": { + "build": "heft build --clean", + "start": "heft test --clean --watch", + "_phase:build": "heft run --only build -- --clean", + "_phase:test": "heft run --only test -- --clean" + }, + "peerDependencies": { + "@rushstack/heft": "^1.1.4", + "@napi-rs/cli": "^3.3.0" + }, + "dependencies": { + "@rushstack/node-core-library": "workspace:*", + "tapable": "2.3.0" + }, + "devDependencies": { + "@types/heft-jest": "1.0.2", + "@rushstack/heft": "workspace:*", + "eslint": "9.25.1", + "@rushstack/heft-node-rig": "workspace:*", + "@napi-rs/cli": "3.3.0" + } +} diff --git a/heft-plugins/heft-napi-rs-plugin/src/NapiRsConfigurationLoader.ts b/heft-plugins/heft-napi-rs-plugin/src/NapiRsConfigurationLoader.ts new file mode 100644 index 00000000000..3600f121e3c --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/NapiRsConfigurationLoader.ts @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'node:path'; + +import type { HeftConfiguration, IHeftTaskSession } from '@rushstack/heft'; +import { FileSystem } from '@rushstack/node-core-library'; + +import type { INapiRsPluginOptions } from './NapiRsPlugin'; +import { + type INapiRsConfiguration, + type INapiRsPluginAccessorHooks, + STAGE_LOAD_LOCAL_CONFIG, + PLUGIN_NAME, + INapiRsConfigurationFnOptions, + NapiRsCliImport +} from './shared'; + +type INapiRsConfigJsExport = + | INapiRsConfiguration + | Promise + | ((options: INapiRsConfigurationFnOptions) => INapiRsConfiguration) + | ((options: INapiRsConfigurationFnOptions) => Promise); +type INapiRsConfigJs = INapiRsConfigJsExport | { default: INapiRsConfigJsExport }; + +/** + * @internal + */ +export interface ILoadNapiRsConfigurationOptions { + taskSession: IHeftTaskSession; + heftConfiguration: HeftConfiguration; + loadNapiRsAsyncFn: () => Promise; + hooks: Pick; + + _tryLoadConfigFileAsync?: typeof tryLoadNapiRsConfigurationFileAsync; +} + +const DEFAULT_NAPI_RS_CONFIG_PATH: './napi-rs.config.mjs' = './napi-rs.config.mjs'; + +/** + * @internal + */ +export async function tryLoadNapiRsConfigurationAsync( + options: ILoadNapiRsConfigurationOptions, + pluginOptions: INapiRsPluginOptions +): Promise { + const { taskSession, hooks, _tryLoadConfigFileAsync = tryLoadNapiRsConfigurationFileAsync } = options; + const { logger } = taskSession; + const { terminal } = logger; + + // Apply default behavior. Due to the state of `this._napiRsConfiguration`, this code + // will execute exactly once. + hooks.onLoadConfiguration.tapPromise( + { + name: PLUGIN_NAME, + stage: STAGE_LOAD_LOCAL_CONFIG + }, + async () => { + terminal.writeVerboseLine(`Attempting to load NAPI-RS configuration from local file`); + const napiRsConfiguration: INapiRsConfiguration | undefined = await _tryLoadConfigFileAsync( + options, + pluginOptions + ); + + if (napiRsConfiguration) { + terminal.writeVerboseLine(`Loaded NAPI-RS configuration from local file.`); + } + + return napiRsConfiguration; + } + ); + + // Obtain the NAPI-RS configuration by calling into the hook. + // The local configuration is loaded at STAGE_LOAD_LOCAL_CONFIG + terminal.writeVerboseLine('Attempting to load NAPI-RS configuration'); + let napiRsConfiguration: INapiRsConfiguration | false | undefined = + await hooks.onLoadConfiguration.promise(); + + if (napiRsConfiguration === false) { + terminal.writeLine('NAPI-RS disabled by external plugin'); + napiRsConfiguration = undefined; + } else if ( + napiRsConfiguration === undefined || + (Array.isArray(napiRsConfiguration) && napiRsConfiguration.length === 0) + ) { + terminal.writeLine('No NAPI-RS configuration found'); + napiRsConfiguration = undefined; + } else { + if (hooks.onConfigure.isUsed()) { + // Allow for plugins to customize the configuration + await hooks.onConfigure.promise(napiRsConfiguration); + } + if (hooks.onAfterConfigure.isUsed()) { + // Provide the finalized configuration + await hooks.onAfterConfigure.promise(napiRsConfiguration); + } + } + return napiRsConfiguration as INapiRsConfiguration | undefined; +} + +/** + * @internal + */ +export async function tryLoadNapiRsConfigurationFileAsync( + options: ILoadNapiRsConfigurationOptions, + pluginOptions: INapiRsPluginOptions +): Promise { + const { taskSession, heftConfiguration, loadNapiRsAsyncFn } = options; + const { logger } = taskSession; + const { terminal } = logger; + const { configurationPath } = pluginOptions; + let napiRsConfigJs: INapiRsConfigJs | undefined; + + try { + const buildFolderPath: string = heftConfiguration.buildFolderPath; + const configPath: string = path.resolve( + buildFolderPath, + configurationPath || DEFAULT_NAPI_RS_CONFIG_PATH + ); + terminal.writeVerboseLine(`Attempting to load NAPI-RS configuration from "${configPath}".`); + napiRsConfigJs = await _tryLoadNapiRsConfigurationFileInnerAsync(configPath); + } catch (error) { + logger.emitError(error as Error); + } + + if (napiRsConfigJs) { + const napiRsConfig: INapiRsConfigJsExport = + (napiRsConfigJs as { default: INapiRsConfigJsExport }).default || + (napiRsConfigJs as INapiRsConfigJsExport); + + if (typeof napiRsConfig === 'function') { + // Defer loading of napiRs until we know for sure that we will need it + return napiRsConfig({ + taskSession, + heftConfiguration, + napiRs: await loadNapiRsAsyncFn() + }); + } else { + return napiRsConfig; + } + } else { + return undefined; + } +} + +/** + * @internal + */ +export async function _tryLoadNapiRsConfigurationFileInnerAsync( + configurationPath: string +): Promise { + const configExists: boolean = await FileSystem.existsAsync(configurationPath); + if (configExists) { + try { + return await import(configurationPath); + } catch (e) { + const error: NodeJS.ErrnoException = e as NodeJS.ErrnoException; + if (error.code === 'ERR_MODULE_NOT_FOUND') { + // No configuration found, return undefined. + return undefined; + } + throw new Error(`Error loading NAPI-RS configuration at "${configurationPath}": ${e}`); + } + } else { + return undefined; + } +} diff --git a/heft-plugins/heft-napi-rs-plugin/src/NapiRsPlugin.ts b/heft-plugins/heft-napi-rs-plugin/src/NapiRsPlugin.ts new file mode 100644 index 00000000000..bfdf3b57bee --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/NapiRsPlugin.ts @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import child_process from 'node:child_process'; +import path from 'node:path'; +import type * as TNapiRsCli from '@napi-rs/cli'; +import type { + HeftConfiguration, + IHeftTaskPlugin, + IHeftTaskRunHookOptions, + IHeftTaskSession +} from '@rushstack/heft'; +import { AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook } from 'tapable'; +import { tryLoadNapiRsConfigurationAsync } from './NapiRsConfigurationLoader'; +import { + INapiRsPluginAccessor, + INapiRsConfiguration, + PLUGIN_NAME, + INapiRsPluginAccessorHooks, + NapiCli +} from './shared'; + +export interface INapiRsPluginOptions { + configurationPath?: string | undefined; +} + +const NAPI_RS_CLI_PACKAGE_NAME: '@napi-rs/cli' = '@napi-rs/cli'; + +/** + * @internal + */ +export default class NapiRsPlugin implements IHeftTaskPlugin { + private _accessor: INapiRsPluginAccessor | undefined; + private _napiRs: typeof TNapiRsCli | undefined; + private _napiRsCli: NapiCli | undefined; + private _napiRsConfiguration: INapiRsConfiguration | undefined | false = false; + + public get accessor(): INapiRsPluginAccessor { + if (!this._accessor) { + this._accessor = { + hooks: _createAccessorHooks(), + parameters: {} + }; + } + return this._accessor; + } + + public apply( + taskSession: IHeftTaskSession, + heftConfiguration: HeftConfiguration, + options: INapiRsPluginOptions = {} + ): void { + taskSession.hooks.run.tapPromise(PLUGIN_NAME, async ({ abortSignal }: IHeftTaskRunHookOptions) => { + // Load the config and compiler, and return if there is no config found + const napiRsConfiguration: INapiRsConfiguration | undefined = await this._getNapiRsConfigurationAsync( + taskSession, + heftConfiguration, + options + ); + if (!napiRsConfiguration) { + return; + } + taskSession.logger.terminal.writeLine('Running NAPI-RS compilation'); + + // NAPI-RS CLI writes its status messages to stderr, which Rush treats as warnings. + // Fork the process to run cli.build to prevent stderr from being seen by Rush + await this._runNapiRsBuildInFork(napiRsConfiguration, abortSignal, taskSession); + taskSession.logger.terminal.writeLine(`NAPI-RS compilation complete.`); + }); + } + + private async _getNapiRsConfigurationAsync( + taskSession: IHeftTaskSession, + heftConfiguration: HeftConfiguration, + options: INapiRsPluginOptions, + _requestRun?: () => void + ): Promise { + if (this._napiRsConfiguration === false) { + const napiRsConfiguration: INapiRsConfiguration | undefined = await tryLoadNapiRsConfigurationAsync( + { + taskSession, + heftConfiguration, + hooks: this.accessor.hooks, + loadNapiRsAsyncFn: this._loadNapiRsAsync.bind(this, taskSession, heftConfiguration) + }, + options + ); + + this._napiRsConfiguration = napiRsConfiguration; + } + + return this._napiRsConfiguration; + } + + private async _loadNapiRsAsync( + taskSession: IHeftTaskSession, + heftConfiguration: HeftConfiguration + ): Promise { + if (!this._napiRs) { + try { + const napiRsPackagePath: string = await heftConfiguration.rigPackageResolver.resolvePackageAsync( + NAPI_RS_CLI_PACKAGE_NAME, + taskSession.logger.terminal + ); + this._napiRs = await import(napiRsPackagePath); + } catch (e) { + // Fallback to bundled version if not found in rig. + this._napiRs = await import(NAPI_RS_CLI_PACKAGE_NAME); + taskSession.logger.terminal.writeDebugLine( + `Using NAPI-RS from built-in "${NAPI_RS_CLI_PACKAGE_NAME}"` + ); + } + } + return this._napiRs!; + } + + private async _runNapiRsBuildInFork( + napiRsConfiguration: INapiRsConfiguration, + abortSignal: AbortSignal, + taskSession: IHeftTaskSession + ): Promise { + return new Promise((resolve, reject) => { + // Path to the worker script + const workerPath = path.resolve(__dirname, 'napiRsBuildWorker.js'); + const configJson = JSON.stringify(napiRsConfiguration.build); + + const child = child_process.spawn('node', [workerPath, configJson], { + stdio: ['inherit', 'inherit', 1], // Redirect stderr to stdout + cwd: process.cwd(), + env: process.env + }); + + // Handle abort signal + const abortHandler = (): void => { + child.kill('SIGTERM'); + }; + + if (abortSignal.aborted) { + child.kill('SIGTERM'); + reject(new Error('Operation aborted')); + return; + } + + abortSignal.addEventListener('abort', abortHandler); + + child.on('error', (error) => { + abortSignal.removeEventListener('abort', abortHandler); + reject(new Error(`Failed to spawn NAPI-RS build process: ${error.message}`)); + }); + + child.on('exit', (code, signal) => { + abortSignal.removeEventListener('abort', abortHandler); + if (signal === 'SIGTERM' && abortSignal.aborted) { + reject(new Error('Operation aborted')); + } else if (code === 0) { + resolve(); + } else { + reject(new Error(`NAPI-RS build process exited with code ${code}`)); + } + }); + }); + } +} + +/** + * @internal + */ +export function _createAccessorHooks(): INapiRsPluginAccessorHooks { + return { + onLoadConfiguration: new AsyncSeriesBailHook(), + onConfigure: new AsyncSeriesHook(['napiRsConfiguration']), + onAfterConfigure: new AsyncParallelHook(['napiRsConfiguration']) + }; +} diff --git a/heft-plugins/heft-napi-rs-plugin/src/index.ts b/heft-plugins/heft-napi-rs-plugin/src/index.ts new file mode 100644 index 00000000000..f8b584852c9 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export { PLUGIN_NAME as PluginName, STAGE_LOAD_LOCAL_CONFIG } from './shared'; +export type { INapiRsConfiguration, NapiCliBuildOptions } from './shared'; diff --git a/heft-plugins/heft-napi-rs-plugin/src/napiRsBuildWorker.ts b/heft-plugins/heft-napi-rs-plugin/src/napiRsBuildWorker.ts new file mode 100644 index 00000000000..7346745f91c --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/napiRsBuildWorker.ts @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { NapiCliBuildOptions } from './shared'; +import napiRsPackageJson from '@napi-rs/cli/package.json'; + +/** + * Worker script that runs NAPI-RS build in a separate process to prevent stderr + * from being seen by Rush as warnings. + */ +async function runNapiRsBuild(): Promise { + try { + console.log(`Using NAPI-RS version ${napiRsPackageJson.version}`); + // Get the configuration from command line arguments + const configJson = process.argv[2]; + if (!configJson) { + throw new Error('Configuration not provided'); + } + + const config: NapiCliBuildOptions = JSON.parse(configJson); + + const { NapiCli } = await import('@napi-rs/cli'); + const cli = new NapiCli(); + const { task, abort } = await cli.build(config); + + // Handle termination signals + const handleSignal = (): void => { + abort(); + process.exit(1); + }; + process.on('SIGTERM', handleSignal); + process.on('SIGINT', handleSignal); + + await task; + process.exit(0); + } catch (error) { + console.error('NAPI-RS build failed:', error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +runNapiRsBuild().catch((error) => { + console.error('Unexpected error in NAPI-RS build worker:', error); + process.exit(1); +}); diff --git a/heft-plugins/heft-napi-rs-plugin/src/schemas/heft-napi-rs-plugin.schema.json b/heft-plugins/heft-napi-rs-plugin/src/schemas/heft-napi-rs-plugin.schema.json new file mode 100644 index 00000000000..fe035508c7b --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/schemas/heft-napi-rs-plugin.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "NAPI-RS Plugin Configuration", + "description": "Defines options for NAPI-RS plugin execution.", + "type": "object", + + "additionalProperties": false, + + "properties": { + "$schema": { + "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.", + "type": "string" + }, + + "configurationPath": { + "description": "Specifies a relative path to the NAPI-RS configuration. The default value is \"./napi-rs.config.mjs\".", + "type": "string" + } + } +} diff --git a/heft-plugins/heft-napi-rs-plugin/src/shared.ts b/heft-plugins/heft-napi-rs-plugin/src/shared.ts new file mode 100644 index 00000000000..6e24f70e612 --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/src/shared.ts @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type * as TNapiRsCli from '@napi-rs/cli'; +import { IHeftTaskSession, HeftConfiguration } from '@rushstack/heft'; +import type { AsyncParallelHook, AsyncSeriesBailHook, AsyncSeriesHook } from 'tapable'; + +/** + * @beta + */ +export const PLUGIN_NAME: 'heft-napi-rs-plugin' = 'heft-napi-rs-plugin'; + +export type NapiRsCliImport = typeof import('@napi-rs/cli'); + +export type NapiCli = TNapiRsCli.NapiCli; + +/** + * @beta + */ +export type NapiCliBuildOptions = Parameters[0]; + +/** + * @beta + */ +export interface INapiRsConfiguration { + build: NapiCliBuildOptions; +} + +/** + * The environment passed into the NAPI-RS configuration function. Loosely based + * on the default NAPI-RS environment options, specified here: + * + * @beta + */ +export interface INapiRsConfigurationFnOptions { + /** + * The task session provided to the plugin. + */ + taskSession: IHeftTaskSession; + /** + * The Heft configuration provided to the plugin. + */ + heftConfiguration: HeftConfiguration; + /** + * The resolved NAPI-RS package. + */ + napiRs: NapiRsCliImport; +} + +/** + * @beta + */ +export interface INapiRsPluginAccessorHooks { + /** + * A hook that allows for loading custom configurations used by the NAPI-RS + * plugin. If a tap returns a value other than `undefined` before stage {@link STAGE_LOAD_LOCAL_CONFIG}, + * it will suppress loading from the NAPI-RS config file. To provide a fallback behavior in the + * absence of a local config file, tap this hook with a `stage` value greater than {@link STAGE_LOAD_LOCAL_CONFIG}. + * + * @remarks + * Tapable event handlers can return `false` instead of `undefined` to suppress + * other handlers from creating a configuration object, and prevent NAPI-RS from running. + */ + readonly onLoadConfiguration: AsyncSeriesBailHook<[], INapiRsConfiguration | undefined | false>; + /** + * A hook that allows for modification of the loaded configuration used by the NAPI-RS + * plugin. If no configuration was loaded, this hook will not be called. + */ + readonly onConfigure: AsyncSeriesHook<[INapiRsConfiguration], never>; + /** + * A hook that provides the finalized configuration that will be used by NAPI-RS. + * If no configuration was loaded, this hook will not be called. + */ + readonly onAfterConfigure: AsyncParallelHook<[INapiRsConfiguration], never>; +} + +/** + * @beta + */ +export interface INapiRsPluginAccessorParameters {} + +/** + * @beta + */ +export interface INapiRsPluginAccessor { + /** + * Hooks that are called at various points in the NAPI-RS plugin lifecycle. + */ + readonly hooks: INapiRsPluginAccessorHooks; + /** + * Parameters that are provided by the NAPI-RS plugin. + */ + readonly parameters: INapiRsPluginAccessorParameters; +} + +/** + * The stage in the `onLoadConfiguration` hook at which the config will be loaded from the local + * NAPI-RS config file. + * @beta + */ +export const STAGE_LOAD_LOCAL_CONFIG: 1000 = 1000; diff --git a/heft-plugins/heft-napi-rs-plugin/tsconfig.json b/heft-plugins/heft-napi-rs-plugin/tsconfig.json new file mode 100644 index 00000000000..626d668d6ac --- /dev/null +++ b/heft-plugins/heft-napi-rs-plugin/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json", + "compilerOptions": { + "lib": ["DOM"], + "skipLibCheck": true, + "resolveJsonModule": true + } +}