Skip to content

Commit

Permalink
Merge pull request #1014 from neon-bindings/create-neon-lib
Browse files Browse the repository at this point in the history
`npm init neon --lib`
  • Loading branch information
dherman authored May 6, 2024
2 parents 2e0a0f1 + fd212c1 commit 62440e2
Show file tree
Hide file tree
Showing 33 changed files with 5,238 additions and 264 deletions.
4,026 changes: 3,940 additions & 86 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 39 additions & 1 deletion pkgs/create-neon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,44 @@ The `create-neon` tool bootstraps [Neon](https://neon-bindings.com) projects, wh

You can conveniently use this tool with the [`npm init`](https://docs.npmjs.com/cli/v7/commands/npm-init) syntax:

### Creating a Simple Project

To create a simple Neon project that consists purely of Rust code:

```sh
$ npm init neon [<opts> ...] my-project
```

#### Global Options

```sh
-y|--yes Skip interactive `npm init` questionnaire.
```

### Creating a Portable Library

Neon also makes it easy to create **portable, cross-platform libraries** by publishing pre-built binaries. This means you can implement your Node.js library in Rust and publish the binaries so that users of your library (and any downstream users of theirs!) on all major hardware and operating systems can take a dependency on your library---_without having to install Rust or run any builds_.

To create a portable npm library with pre-built binaries:

```sh
$ npm init neon [<opts> ...] --lib [<lib-opts> ...] my-project
```

This will generate a project that can be used by pure JavaScript or TypeScript consumers without them even being aware of the use of Rust under the hood. It achieves this by publishing pre-built binaries for common Node platform architectures that are loaded just-in-time by a JS wrapper module.

This command generates the necessary npm and CI/CD configuration boilerplate to require nearly zero manual installation on typical GitHub-hosted repos. The only manual step required is to configure GitHub Actions with the necessary npm access token to enable automated publishing.

This command chooses the most common setup by default, but allows customization with fine-grained configuration options. These configuration options can also be modified later with the [Neon CLI](https://www.npmjs.com/package/@neon-rs/cli).

#### Library Options

```sh
$ npm init neon my-project
--ci none|github CI/CD provider to generate config for.
(Default: github)
--bins none|npm[:org] Cache provider to publish pre-built binaries.
(Default: npm, with org inferred from package)
--platform <platform> Binary platform to add support to this library for.
This option can be specified multiple times.
(Default: macos, linux, windows)
```
6 changes: 5 additions & 1 deletion pkgs/create-neon/data/templates/.gitignore.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ target
index.node
**/node_modules
**/.DS_Store
npm-debug.log*
npm-debug.log*{{#eq packageSpec.library.lang compare="ts"}}
lib
{{/eq}}
cargo.log
cross.log
141 changes: 81 additions & 60 deletions pkgs/create-neon/data/templates/README.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,79 @@
{{/if}}
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).

## Installing {{package.name}}
## Building {{package.name}}

Installing {{package.name}} requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
Building {{package.name}} requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).

You can install the project with npm. In the project directory, run:
To run the build, run:

```sh
$ npm install
$ npm run build
```

This fully installs the project, including installing any dependencies and running the build.
This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli) utility to assemble the binary Node addon from the output of `cargo`.

## Building {{package.name}}
## Exploring {{package.name}}

If you have already installed the project and only want to run the build, run:
After building {{package.name}}, you can explore its exports at the Node console:

{{#if packageSpec.library}}
```sh
$ npm i
$ npm run build
$ node
> require('.').greeting()
{ message: 'hello node' }
```

This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust build and copy the built library into `./index.node`.

## Exploring {{package.name}}

After building {{package.name}}, you can explore its exports at the Node REPL:

{{else}}
```sh
$ npm install
$ npm i
$ npm run build
$ node
> require('.').hello()
"hello node"
'hello node'
```
{{/if}}

## Available Scripts

In the project directory, you can run:

### `npm install`
{{#unless packageSpec.library}}
#### `npm install`

Installs the project, including running `npm run build`.

### `npm build`
{{/unless}}
#### `npm run build`

Builds the Node addon (`index.node`) from source.
Builds the Node addon (`index.node`) from source, generating a release build with `cargo --release`.

Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm run build` and similar commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):

```
npm run build -- --feature=beetle
```

#### `npm build-debug`
#### `npm run debug`

Similar to `npm run build` but generates a debug build with `cargo`.

#### `npm run cross`

Similar to `npm run build` but uses [cross-rs](https://github.com/cross-rs/cross) to cross-compile for another platform. Use the [`CARGO_BUILD_TARGET`](https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) environment variable to select the build target.

Alias for `npm build`.
{{#eq packageSpec.library.ci.type compare="github"}}
#### `npm run release`

#### `npm build-release`
Initiate a full build and publication of a new patch release of this library via GitHub Actions.

Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
#### `npm run dryrun`

### `npm test`
Initiate a dry run of a patch release of this library via GitHub Actions. This performs a full build but does not publish the final result.

{{/eq}}
#### `npm test`

Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).

Expand All @@ -77,47 +90,55 @@ The directory structure of this project is:
{{package.name}}/
├── Cargo.toml
├── README.md
├── index.node
├── package.json
{{#if packageSpec.library}}
├── lib/
{{#eq packageSpec.library.lang compare="ts"}}
├── src/
| ├── index.mts
| └── index.cts
├── crates/
| └── {{package.name}}/
| └── src/
| └── lib.rs
{{/eq}}
├── platforms/
{{else}}
├── src/
| └── lib.rs
├── index.node
{{/if}}
├── package.json
└── target/
```

### Cargo.toml

The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.

### README.md

This file.

### index.node

The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.

Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.

### package.json

The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.

### src/

The directory tree containing the Rust source code for the project.

### src/lib.rs

The Rust library's main module.

### target/

Binary artifacts generated by the Rust build.
| Entry | Purpose |
|----------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `Cargo.toml` | The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command. |
| `README.md` | This file. |
{{#if packageSpec.library}}
{{#eq packageSpec.library.lang compare="ts"}}
| `lib/` | The directory containing the generated output from [tsc](https://typescriptlang.org). |
| `src/` | The directory containing the TypeScript source files. |
| `index.mts` | Entry point for when this library is loaded via [ESM `import`](https://nodejs.org/api/esm.html#modules-ecmascript-modules) syntax. |
| `index.cts` | Entry point for when this library is loaded via [CJS `require`](https://nodejs.org/api/modules.html#requireid). |
| `crates/` | The directory tree containing the Rust source code for the project. |
| `lib.rs` | Entry point for the Rust source code. |
{{else}}
| `lib/` | The directory containing The directory containing the JavaScript source files. |
{{/eq}}
| `platforms/` | The directory containing distributions of the binary addon backend for each platform supported by this library. |
{{else}}
| `src/` | The directory tree containing the Rust source code for the project. |
| `lib.rs` | Entry point for the Rust source code. |
| `index.node` | The main module, a [Node addon](https://nodejs.org/api/addons.html) generated by the build and pointed to by `"main"` in `package.json`. |
{{/if}}
| `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. |
| `target/` | Binary artifacts generated by the Rust build. |

## Learn More

To learn more about Neon, see the [Neon documentation](https://neon-bindings.com).

To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
Learn more about:

To learn more about Node, see the [Node documentation](https://nodejs.org).
- [Neon](https://neon-bindings.com).
- [Rust](https://www.rust-lang.org).
- [Node](https://nodejs.org).
3 changes: 3 additions & 0 deletions pkgs/create-neon/data/templates/Workspace.toml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[workspace]
members = ["crates/{{package.name}}"]
resolver = "2"
5 changes: 5 additions & 0 deletions pkgs/create-neon/data/templates/ci/github/.env.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NODE_VERSION={{versions.node}}.x
NPM_REGISTRY=https://registry.npmjs.org
RUST_VERSION=stable
ACTIONS_USER=github-actions
[email protected]
137 changes: 137 additions & 0 deletions pkgs/create-neon/data/templates/ci/github/build.yml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Build

on:
workflow_call:
inputs:
ref:
description: 'The branch, tag, or SHA to check out'
required: true
type: string
update-version:
description: 'Update version before building?'
required: false
type: boolean
default: false
version:
description: 'Version update (ignored if update-version is false)'
required: false
type: string
default: 'patch'
github-release:
description: 'Publish GitHub release?'
required: false
type: boolean
default: false
tag:
description: 'The release tag (ignored if github-release is false)'
required: false
type: string
default: ''

jobs:
matrix:
name: Matrix
runs-on: ubuntu-latest
outputs:
matrix: {{#$}} steps.matrix.outputs.result {{/$}}
steps:
- name: Checkout Code
uses: actions/checkout@{{versions.actions.verified.checkout}}
with:
ref: {{#$}} inputs.ref {{/$}}
- name: Setup Neon Environment
uses: ./.github/actions/setup
with:
use-rust: false
- name: Look Up Matrix Data
id: matrixData
shell: bash
run: echo "json=$(npx neon show ci github | jq -rc)" | tee -a $GITHUB_OUTPUT
- name: Compute Matrix
id: matrix
uses: actions/github-script@{{versions.actions.verified.githubScript}}
with:
script: |
const platforms = {{#$}} steps.matrixData.outputs.json {{/$}};
const macOS = platforms.macOS.map(platform => {
return { os: "macos-latest", platform, script: "build" };
});
const windows = platforms.Windows.map(platform => {
return { os: "windows-latest", platform, script: "build" };
});
const linux = platforms.Linux.map(platform => {
return { os: "ubuntu-latest", platform, script: "cross" };
});
return [...macOS, ...windows, ...linux];

binaries:
name: Binaries
needs: [matrix]
strategy:
matrix:
cfg: {{#$}} fromJSON(needs.matrix.outputs.matrix) {{/$}}
runs-on: {{#$}} matrix.cfg.os {{/$}}
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@{{versions.actions.verified.checkout}}
with:
ref: {{#$}} inputs.ref {{/$}}
- name: Setup Neon Environment
id: neon
uses: ./.github/actions/setup
with:
use-cross: {{#$}} matrix.cfg.script == 'cross' {{/$}}
platform: {{#$}} matrix.cfg.platform {{/$}}
- name: Update Version
if: {{#$}} inputs.update-version {{/$}}
shell: bash
run: |
git config --global user.name $ACTIONS_USER
git config --global user.email $ACTIONS_EMAIL
npm version {{#$}} inputs.version {{/$}} -m "v%s"
- name: Build
shell: bash
env:
CARGO_BUILD_TARGET: {{#$}} steps.neon.outputs.target {{/$}}
NEON_BUILD_PLATFORM: {{#$}} matrix.cfg.platform {{/$}}
run: npm run {{#$}} matrix.cfg.script {{/$}}
- name: Pack
id: pack
shell: bash
run: |
mkdir -p dist
echo filename=$(basename $(npm pack ./platforms/{{#$}} matrix.cfg.platform {{/$}} --silent --pack-destination=./dist --json | jq -r '.[0].filename')) | tee -a $GITHUB_OUTPUT
- name: Release
if: {{#$}} inputs.github-release {{/$}}
uses: softprops/action-gh-release@{{versions.actions.unverified.ghRelease.sha}} # {{versions.actions.unverified.ghRelease.tag}}
with:
files: ./dist/{{#$}} steps.pack.outputs.filename {{/$}}
tag_name: {{#$}} inputs.tag {{/$}}

main:
name: Main
needs: [matrix]
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@{{versions.actions.verified.checkout}}
with:
ref: {{#$}} inputs.ref {{/$}}
- name: Setup Neon Environment
uses: ./.github/actions/setup
with:
use-rust: false
- name: Pack
id: pack
shell: bash
run: |
mkdir -p dist
echo "filename=$(npm pack --silent --pack-destination=./dist)" | tee -a $GITHUB_OUTPUT
- name: Release
if: {{#$}} inputs.github-release {{/$}}
uses: softprops/action-gh-release@{{versions.actions.unverified.ghRelease.sha}} # {{versions.actions.unverified.ghRelease.tag}}
with:
files: ./dist/{{#$}} steps.pack.outputs.filename {{/$}}
tag_name: {{#$}} inputs.tag {{/$}}
Loading

0 comments on commit 62440e2

Please sign in to comment.