Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/.npmignore
Original file line number Diff line number Diff line change
@@ -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/**
24 changes: 24 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/README.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/config/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",

"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "../../../common/reviews/api"
},
"docModel": {
"enabled": false
},
"dtsRollup": {
"enabled": true,
"betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts"
}
}
3 changes: 3 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/config/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@rushstack/heft-node-rig/profiles/default/config/jest.config.json"
}
7 changes: 7 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/config/rig.json
Original file line number Diff line number Diff line change
@@ -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"
}
13 changes: 13 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/heft-plugin.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
35 changes: 35 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
167 changes: 167 additions & 0 deletions heft-plugins/heft-napi-rs-plugin/src/NapiRsConfigurationLoader.ts
Original file line number Diff line number Diff line change
@@ -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<INapiRsConfiguration>
| ((options: INapiRsConfigurationFnOptions) => INapiRsConfiguration)
| ((options: INapiRsConfigurationFnOptions) => Promise<INapiRsConfiguration>);
type INapiRsConfigJs = INapiRsConfigJsExport | { default: INapiRsConfigJsExport };

/**
* @internal
*/
export interface ILoadNapiRsConfigurationOptions {
taskSession: IHeftTaskSession;
heftConfiguration: HeftConfiguration;
loadNapiRsAsyncFn: () => Promise<NapiRsCliImport>;
hooks: Pick<INapiRsPluginAccessorHooks, 'onLoadConfiguration' | 'onConfigure' | 'onAfterConfigure'>;

_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<INapiRsConfiguration | undefined> {
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<INapiRsConfiguration | undefined> {
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<INapiRsConfigJs | undefined> {
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;
}
}
Loading