Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from neon-bindings:main #39

Merged
merged 13 commits into from
May 21, 2024
Merged
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
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions pkgs/create-neon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ You can conveniently use this tool with the [`npm init`](https://docs.npmjs.com/
To create a simple Neon project that consists purely of Rust code:

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

**Note:** The initial `--` is necessary for `npm init` to pass any command-line options to Neon.

#### Global Options

```sh
Expand All @@ -27,9 +29,11 @@ Neon also makes it easy to create **portable, cross-platform libraries** by publ
To create a portable npm library with pre-built binaries:

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

**Note:** The initial `--` is necessary for `npm init` to pass any command-line options to Neon.

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.
Expand Down
4 changes: 2 additions & 2 deletions pkgs/create-neon/data/versions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"neon": "1",
"neonCLI": "0.1.68",
"neonLoad": "0.1.68",
"neonCLI": "0.1.73",
"neonLoad": "0.1.73",
"typescript": "5.3.3",
"typesNode": "20.11.16",
"tsconfigNode": {
Expand Down
17 changes: 2 additions & 15 deletions pkgs/create-neon/dev/expect.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import { ChildProcess } from "child_process";
import { PassThrough, Readable, Writable } from "stream";
import { StringDecoder } from "string_decoder";
import { Readable, Writable } from "stream";
import readStream from "stream-to-string";

function readChunks(input: Readable): Readable {
let output = new PassThrough({ objectMode: true });
let decoder = new StringDecoder("utf8");
input.on("data", (data) => {
output.write(decoder.write(data));
});
input.on("close", () => {
output.write(decoder.end());
output.end();
});
return output;
}
import { readChunks } from "../src/shell.js";

function splitLines(s: string): string[] {
return s.split(/([^\n]*\r?\n)/).filter((x) => x);
Expand Down
5 changes: 3 additions & 2 deletions pkgs/create-neon/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-neon",
"version": "0.4.0",
"version": "0.5.0",
"description": "Create Neon projects with no build configuration.",
"type": "module",
"exports": "./dist/src/bin/create-neon.js",
Expand All @@ -22,6 +22,7 @@
"prepublishOnly": "npm run build",
"pretest": "npm run build",
"test": "mocha",
"manual-interactive-test": "npm run build && rm -rf create-neon-manual-test-project && node ./dist/src/bin/create-neon.js create-neon-manual-test-project",
"manual-test": "npm run build && rm -rf create-neon-manual-test-project && node ./dist/src/bin/create-neon.js --lib --yes create-neon-manual-test-project"
},
"repository": {
Expand All @@ -47,7 +48,7 @@
"typescript": "^5.3.2"
},
"dependencies": {
"@neon-rs/manifest": "^0.0.6",
"@neon-rs/manifest": "^0.1.0",
"chalk": "^5.3.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^7.0.1",
Expand Down
44 changes: 30 additions & 14 deletions pkgs/create-neon/src/bin/create-neon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { CI } from "../ci.js";
import { GitHub } from "../ci/github.js";
import { Lang, ModuleType } from "../package.js";
import {
NodePlatform,
PlatformPreset,
assertIsPlatformPreset,
isNodePlatform,
isPlatformPreset,
} from "@neon-rs/manifest/platform";

Expand All @@ -33,6 +35,7 @@ function tsTemplates(pkg: string): Record<string, string> {
}

const OPTIONS = [
{ name: "app", type: Boolean, defaultValue: false },
{ name: "lib", type: Boolean, defaultValue: false },
{ name: "bins", type: String, defaultValue: "none" },
{ name: "platform", type: String, multiple: true, defaultValue: ["common"] },
Expand All @@ -43,6 +46,10 @@ const OPTIONS = [
try {
const opts = commandLineArgs(OPTIONS, { stopAtFirstUnknown: true });

if (opts.app && opts.lib) {
throw new Error("Cannot choose both --app and --lib");
}

if (!opts._unknown || opts._unknown.length === 0) {
throw new Error("No package name given");
}
Expand All @@ -55,7 +62,10 @@ try {
const platforms = parsePlatforms(opts.platform);
const cache = parseCache(opts.lib, opts.bins, pkg);
const ci = parseCI(opts.ci);
const yes = !!opts.yes;

if (opts.yes) {
process.env["npm_configure_yes"] = "true";
}

createNeon(pkg, {
templates: opts.lib ? tsTemplates(pkg) : JS_TEMPLATES,
Expand All @@ -68,7 +78,7 @@ try {
platforms,
}
: null,
yes,
app: opts.app ? true : null,
});
} catch (e) {
printErrorWithUsage(e);
Expand All @@ -77,17 +87,25 @@ try {

function parsePlatforms(
platforms: string[]
): PlatformPreset | PlatformPreset[] | undefined {
):
| NodePlatform
| PlatformPreset
| (NodePlatform | PlatformPreset)[]
| undefined {
if (platforms.length === 0) {
return undefined;
} else if (platforms.length === 1) {
const preset = platforms[0];
assertIsPlatformPreset(preset);
return preset;
const platform = platforms[0];
if (isNodePlatform(platform) || isPlatformPreset(platform)) {
return platform;
}
throw new TypeError(`expected platform or preset, got ${platform}`);
} else {
return platforms.map((preset) => {
assertIsPlatformPreset(preset);
return preset;
return platforms.map((platform) => {
if (isNodePlatform(platform) || isPlatformPreset(platform)) {
return platform;
}
throw new TypeError(`expected platform or preset, got ${platform}`);
});
}
}
Expand All @@ -110,18 +128,16 @@ function parseCache(
bins: string,
pkg: string
): Cache | undefined {
const defaultOrg = "@" + pkg;

if (bins === "none") {
return lib ? new NPM(defaultOrg) : undefined;
return lib ? new NPM(pkg) : undefined;
}

if (bins === "npm") {
return new NPM(defaultOrg);
return new NPM(pkg);
}

if (bins.startsWith("npm:")) {
return new NPM(bins.substring(4));
return new NPM(pkg, bins.substring(4));
}

throw new Error(
Expand Down
11 changes: 8 additions & 3 deletions pkgs/create-neon/src/cache/npm.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Cache } from "../cache.js";

export class NPM implements Cache {
readonly org: string | null;
readonly org: string;

readonly type: string = "npm";

constructor(org: string | null) {
this.org = org;
constructor(pkg: string, org?: string) {
this.org = org || NPM.inferOrg(pkg);
}

static inferOrg(pkg: string): string {
const m = pkg.match(/^@([^/]+)\/(.*)/);
return `@${m?.[1] ?? pkg}`;
}
}
28 changes: 28 additions & 0 deletions pkgs/create-neon/src/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { existsSync, rmSync } from "node:fs";

export async function assertCanMkdir(dir: string) {
// pretty lightweight way to check both that folder doesn't exist and
// that the user has write permissions.
await fs.mkdir(dir);
await fs.rmdir(dir);
}

export async function mktemp(): Promise<string> {
const tmpFolderName = await fs.mkdtemp("neon-");
const tmpFolderAbsPath = path.join(process.cwd(), tmpFolderName);
function cleanupTmp() {
try {
if (existsSync(tmpFolderAbsPath)) {
rmSync(tmpFolderAbsPath, { recursive: true });
}
} catch (e) {
console.error(`warning: could not delete ${tmpFolderName}: ${e}`);
}
}
process.on("exit", cleanupTmp);
process.on("SIGINT", cleanupTmp);
process.on("uncaughtException", cleanupTmp);
return tmpFolderName;
}
Loading
Loading