Skip to content

Commit

Permalink
Merge pull request #1041 from neon-bindings/create-neon-requires-proj…
Browse files Browse the repository at this point in the history
…ect-type

fix(create-neon): Explicit project type
  • Loading branch information
dherman authored May 20, 2024
2 parents f0e8547 + f37c1f9 commit b57ab4f
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 125 deletions.
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
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

0 comments on commit b57ab4f

Please sign in to comment.