Skip to content

Commit a16551b

Browse files
committed
heft-napi-rs-plugin
1 parent 3f7b8f9 commit a16551b

File tree

15 files changed

+664
-0
lines changed

15 files changed

+664
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO.
2+
3+
# Ignore all files by default, to avoid accidentally publishing unintended files.
4+
*
5+
6+
# Use negative patterns to bring back the specific things we want to publish.
7+
!/bin/**
8+
!/lib/**
9+
!/lib-*/**
10+
!/dist/**
11+
12+
!CHANGELOG.md
13+
!CHANGELOG.json
14+
!heft-plugin.json
15+
!rush-plugin-manifest.json
16+
!ThirdPartyNotice.txt
17+
18+
# Ignore certain patterns that should not get published.
19+
/dist/*.stats.*
20+
/lib/**/test/
21+
/lib-*/**/test/
22+
*.test.js
23+
24+
# NOTE: These don't need to be specified, because NPM includes them automatically.
25+
#
26+
# package.json
27+
# README.md
28+
# LICENSE
29+
30+
# ---------------------------------------------------------------------------
31+
# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below.
32+
# ---------------------------------------------------------------------------
33+
34+
!/includes/**
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@rushstack/heft-napi-rs-plugin
2+
3+
Copyright (c) Microsoft Corporation. All rights reserved.
4+
5+
MIT License
6+
7+
Permission is hereby granted, free of charge, to any person obtaining
8+
a copy of this software and associated documentation files (the
9+
"Software"), to deal in the Software without restriction, including
10+
without limitation the rights to use, copy, modify, merge, publish,
11+
distribute, sublicense, and/or sell copies of the Software, and to
12+
permit persons to whom the Software is furnished to do so, subject to
13+
the following conditions:
14+
15+
The above copyright notice and this permission notice shall be
16+
included in all copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# @rushstack/heft-napi-rs-plugin
2+
3+
This is a Heft plugin for using NAPI-RS.
4+
5+
## Links
6+
7+
- [CHANGELOG.md](
8+
https://github.com/microsoft/rushstack/blob/main/heft-plugins/heft-napi-rs-plugin/CHANGELOG.md) - Find
9+
out what's new in the latest version
10+
- [@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.
11+
12+
Heft is part of the [Rush Stack](https://rushstack.io/) family of projects.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3+
4+
"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts",
5+
"apiReport": {
6+
"enabled": true,
7+
"reportFolder": "../../../common/reviews/api"
8+
},
9+
"docModel": {
10+
"enabled": false
11+
},
12+
"dtsRollup": {
13+
"enabled": true,
14+
"betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts"
15+
}
16+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@rushstack/heft-node-rig/profiles/default/config/jest.config.json"
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
// The "rig.json" file directs tools to look for their config files in an external package.
3+
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
4+
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
5+
6+
"rigPackageName": "@rushstack/heft-node-rig"
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-plugin.schema.json",
3+
4+
"taskPlugins": [
5+
{
6+
"pluginName": "napi-rs-plugin",
7+
"entryPoint": "./lib/NapiRsPlugin",
8+
"optionsSchema": "./lib/schemas/heft-napi-rs-plugin.schema.json",
9+
10+
"parameterScope": "napirs"
11+
}
12+
]
13+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@rushstack/heft-napi-rs-plugin",
3+
"version": "0.0.0",
4+
"description": "Heft plugin for NAPI-RS",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/microsoft/rushstack.git",
8+
"directory": "heft-plugins/heft-napi-rs-plugin"
9+
},
10+
"homepage": "https://rushstack.io/pages/heft/overview/",
11+
"main": "lib/index.js",
12+
"types": "dist/heft-napi-rs-plugin.d.ts",
13+
"license": "MIT",
14+
"scripts": {
15+
"build": "heft build --clean",
16+
"start": "heft test --clean --watch",
17+
"_phase:build": "heft run --only build -- --clean",
18+
"_phase:test": "heft run --only test -- --clean"
19+
},
20+
"peerDependencies": {
21+
"@rushstack/heft": "^1.1.4",
22+
"@napi-rs/cli": "^3.3.0"
23+
},
24+
"dependencies": {
25+
"@rushstack/node-core-library": "workspace:*",
26+
"tapable": "2.3.0"
27+
},
28+
"devDependencies": {
29+
"@types/heft-jest": "1.0.2",
30+
"@rushstack/heft": "workspace:*",
31+
"eslint": "9.25.1",
32+
"@rushstack/heft-node-rig": "workspace:*",
33+
"@napi-rs/cli": "3.3.0"
34+
}
35+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import * as path from 'node:path';
5+
6+
import type { HeftConfiguration, IHeftTaskSession } from '@rushstack/heft';
7+
import { FileSystem } from '@rushstack/node-core-library';
8+
9+
import type { INapiRsPluginOptions } from './NapiRsPlugin';
10+
import {
11+
type INapiRsConfiguration,
12+
type INapiRsPluginAccessorHooks,
13+
STAGE_LOAD_LOCAL_CONFIG,
14+
PLUGIN_NAME,
15+
INapiRsConfigurationFnOptions,
16+
NapiRsCliImport
17+
} from './shared';
18+
19+
type INapiRsConfigJsExport =
20+
| INapiRsConfiguration
21+
| Promise<INapiRsConfiguration>
22+
| ((options: INapiRsConfigurationFnOptions) => INapiRsConfiguration)
23+
| ((options: INapiRsConfigurationFnOptions) => Promise<INapiRsConfiguration>);
24+
type INapiRsConfigJs = INapiRsConfigJsExport | { default: INapiRsConfigJsExport };
25+
26+
/**
27+
* @internal
28+
*/
29+
export interface ILoadNapiRsConfigurationOptions {
30+
taskSession: IHeftTaskSession;
31+
heftConfiguration: HeftConfiguration;
32+
loadNapiRsAsyncFn: () => Promise<NapiRsCliImport>;
33+
hooks: Pick<INapiRsPluginAccessorHooks, 'onLoadConfiguration' | 'onConfigure' | 'onAfterConfigure'>;
34+
35+
_tryLoadConfigFileAsync?: typeof tryLoadNapiRsConfigurationFileAsync;
36+
}
37+
38+
const DEFAULT_NAPI_RS_CONFIG_PATH: './napi-rs.config.mjs' = './napi-rs.config.mjs';
39+
40+
/**
41+
* @internal
42+
*/
43+
export async function tryLoadNapiRsConfigurationAsync(
44+
options: ILoadNapiRsConfigurationOptions,
45+
pluginOptions: INapiRsPluginOptions
46+
): Promise<INapiRsConfiguration | undefined> {
47+
const { taskSession, hooks, _tryLoadConfigFileAsync = tryLoadNapiRsConfigurationFileAsync } = options;
48+
const { logger } = taskSession;
49+
const { terminal } = logger;
50+
51+
// Apply default behavior. Due to the state of `this._napiRsConfiguration`, this code
52+
// will execute exactly once.
53+
hooks.onLoadConfiguration.tapPromise(
54+
{
55+
name: PLUGIN_NAME,
56+
stage: STAGE_LOAD_LOCAL_CONFIG
57+
},
58+
async () => {
59+
terminal.writeVerboseLine(`Attempting to load NAPI-RS configuration from local file`);
60+
const napiRsConfiguration: INapiRsConfiguration | undefined = await _tryLoadConfigFileAsync(
61+
options,
62+
pluginOptions
63+
);
64+
65+
if (napiRsConfiguration) {
66+
terminal.writeVerboseLine(`Loaded NAPI-RS configuration from local file.`);
67+
}
68+
69+
return napiRsConfiguration;
70+
}
71+
);
72+
73+
// Obtain the NAPI-RS configuration by calling into the hook.
74+
// The local configuration is loaded at STAGE_LOAD_LOCAL_CONFIG
75+
terminal.writeVerboseLine('Attempting to load NAPI-RS configuration');
76+
let napiRsConfiguration: INapiRsConfiguration | false | undefined =
77+
await hooks.onLoadConfiguration.promise();
78+
79+
if (napiRsConfiguration === false) {
80+
terminal.writeLine('NAPI-RS disabled by external plugin');
81+
napiRsConfiguration = undefined;
82+
} else if (
83+
napiRsConfiguration === undefined ||
84+
(Array.isArray(napiRsConfiguration) && napiRsConfiguration.length === 0)
85+
) {
86+
terminal.writeLine('No NAPI-RS configuration found');
87+
napiRsConfiguration = undefined;
88+
} else {
89+
if (hooks.onConfigure.isUsed()) {
90+
// Allow for plugins to customize the configuration
91+
await hooks.onConfigure.promise(napiRsConfiguration);
92+
}
93+
if (hooks.onAfterConfigure.isUsed()) {
94+
// Provide the finalized configuration
95+
await hooks.onAfterConfigure.promise(napiRsConfiguration);
96+
}
97+
}
98+
return napiRsConfiguration as INapiRsConfiguration | undefined;
99+
}
100+
101+
/**
102+
* @internal
103+
*/
104+
export async function tryLoadNapiRsConfigurationFileAsync(
105+
options: ILoadNapiRsConfigurationOptions,
106+
pluginOptions: INapiRsPluginOptions
107+
): Promise<INapiRsConfiguration | undefined> {
108+
const { taskSession, heftConfiguration, loadNapiRsAsyncFn } = options;
109+
const { logger } = taskSession;
110+
const { terminal } = logger;
111+
const { configurationPath } = pluginOptions;
112+
let napiRsConfigJs: INapiRsConfigJs | undefined;
113+
114+
try {
115+
const buildFolderPath: string = heftConfiguration.buildFolderPath;
116+
const configPath: string = path.resolve(
117+
buildFolderPath,
118+
configurationPath || DEFAULT_NAPI_RS_CONFIG_PATH
119+
);
120+
terminal.writeVerboseLine(`Attempting to load NAPI-RS configuration from "${configPath}".`);
121+
napiRsConfigJs = await _tryLoadNapiRsConfigurationFileInnerAsync(configPath);
122+
} catch (error) {
123+
logger.emitError(error as Error);
124+
}
125+
126+
if (napiRsConfigJs) {
127+
const napiRsConfig: INapiRsConfigJsExport =
128+
(napiRsConfigJs as { default: INapiRsConfigJsExport }).default ||
129+
(napiRsConfigJs as INapiRsConfigJsExport);
130+
131+
if (typeof napiRsConfig === 'function') {
132+
// Defer loading of napiRs until we know for sure that we will need it
133+
return napiRsConfig({
134+
taskSession,
135+
heftConfiguration,
136+
napiRs: await loadNapiRsAsyncFn()
137+
});
138+
} else {
139+
return napiRsConfig;
140+
}
141+
} else {
142+
return undefined;
143+
}
144+
}
145+
146+
/**
147+
* @internal
148+
*/
149+
export async function _tryLoadNapiRsConfigurationFileInnerAsync(
150+
configurationPath: string
151+
): Promise<INapiRsConfigJs | undefined> {
152+
const configExists: boolean = await FileSystem.existsAsync(configurationPath);
153+
if (configExists) {
154+
try {
155+
return await import(configurationPath);
156+
} catch (e) {
157+
const error: NodeJS.ErrnoException = e as NodeJS.ErrnoException;
158+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
159+
// No configuration found, return undefined.
160+
return undefined;
161+
}
162+
throw new Error(`Error loading NAPI-RS configuration at "${configurationPath}": ${e}`);
163+
}
164+
} else {
165+
return undefined;
166+
}
167+
}

0 commit comments

Comments
 (0)