From 7661e45e3c5f9fe62f93b48cd3eb5dd886f76937 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 10:44:49 -0700 Subject: [PATCH 01/11] require user to specify the Neon project type: - either by passing --app or --lib - or by interactive dialog via stdin --- pkgs/create-neon/dev/expect.ts | 17 +-- pkgs/create-neon/package.json | 1 + pkgs/create-neon/src/bin/create-neon.ts | 40 ++++-- pkgs/create-neon/src/cache/npm.ts | 11 +- pkgs/create-neon/src/fs.ts | 28 ++++ pkgs/create-neon/src/index.ts | 155 ++++++++++++++++----- pkgs/create-neon/src/package.ts | 18 +-- pkgs/create-neon/src/shell.ts | 171 ++++++++++++++++++------ pkgs/create-neon/test/create-neon.ts | 69 +++++++++- 9 files changed, 394 insertions(+), 116 deletions(-) create mode 100644 pkgs/create-neon/src/fs.ts diff --git a/pkgs/create-neon/dev/expect.ts b/pkgs/create-neon/dev/expect.ts index 9cd96b572..6f928f1a2 100644 --- a/pkgs/create-neon/dev/expect.ts +++ b/pkgs/create-neon/dev/expect.ts @@ -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); diff --git a/pkgs/create-neon/package.json b/pkgs/create-neon/package.json index 5815167f4..f342d4bbc 100644 --- a/pkgs/create-neon/package.json +++ b/pkgs/create-neon/package.json @@ -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": { diff --git a/pkgs/create-neon/src/bin/create-neon.ts b/pkgs/create-neon/src/bin/create-neon.ts index 0c27bd59f..2e1d9a31b 100644 --- a/pkgs/create-neon/src/bin/create-neon.ts +++ b/pkgs/create-neon/src/bin/create-neon.ts @@ -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"; @@ -33,6 +35,7 @@ function tsTemplates(pkg: string): Record { } 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"] }, @@ -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"); } @@ -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, @@ -68,7 +78,7 @@ try { platforms, } : null, - yes, + app: opts.app ? true : null }); } catch (e) { printErrorWithUsage(e); @@ -77,17 +87,21 @@ 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}`); }); } } @@ -110,18 +124,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( diff --git a/pkgs/create-neon/src/cache/npm.ts b/pkgs/create-neon/src/cache/npm.ts index fe69c65dc..e93e08c5f 100644 --- a/pkgs/create-neon/src/cache/npm.ts +++ b/pkgs/create-neon/src/cache/npm.ts @@ -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 ? m[1] : pkg); } } diff --git a/pkgs/create-neon/src/fs.ts b/pkgs/create-neon/src/fs.ts new file mode 100644 index 000000000..349ef2f2f --- /dev/null +++ b/pkgs/create-neon/src/fs.ts @@ -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 { + 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; +} diff --git a/pkgs/create-neon/src/index.ts b/pkgs/create-neon/src/index.ts index 853733715..54a7a9f06 100644 --- a/pkgs/create-neon/src/index.ts +++ b/pkgs/create-neon/src/index.ts @@ -1,6 +1,6 @@ -import { promises as fs } from "fs"; -import * as path from "path"; -import die from "./die.js"; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import die from './die.js'; import Package, { PackageSpec, LibrarySpec, @@ -11,12 +11,91 @@ import Package, { import { VERSIONS } from "./versions.js"; import { Metadata, expandTo } from "./expand.js"; import { LibraryManifest } from "@neon-rs/manifest"; -import { PlatformPreset } from "@neon-rs/manifest/platform"; +import { NodePlatform, PlatformPreset, isNodePlatform, isPlatformPreset } from "@neon-rs/manifest/platform"; +import { assertCanMkdir, mktemp } from './fs.js'; +import { Dialog, oneOf } from './shell.js'; +import { NPM } from './cache/npm.js'; +import { GitHub } from './ci/github.js'; + +const CREATE_NEON_PRELUDE: string = ` +This utility will walk you through creating a Neon project. +It only covers the most common items, and tries to guess sensible defaults. + +Use \`npm install \` afterwards to install a package and +save it as a dependency in the package.json file. + +Use \`npm run build\` to build the Neon project from source. + +Press ^C at any time to quit. +`.trim(); + +async function askProjectType(packageSpec: PackageSpec) { + // If non-interactive, use the default (--app). + if (packageSpec.yes) { + packageSpec.app = true; + return; + } + + // Otherwise, find out interactively. + const dialog = new Dialog(); + const ty = await dialog.ask({ + prompt: 'project type', + parse: oneOf({ app: 'app' as const, lib: 'lib' as const }), + default: 'app' as const, + error: "type should be a valid Neon project type (\"app\" or \"lib\")." + }); + + if (ty === 'lib') { + const platforms: (NodePlatform | PlatformPreset)[] = await dialog.ask({ + prompt: 'target platforms', + parse: (v: string): (NodePlatform | PlatformPreset)[] => { + const a = v.split(',').map(s => s.trim()); + if (a.some(elt => !isNodePlatform(elt) && !isPlatformPreset(elt))) { + throw new Error("parse error"); + } + return a as (NodePlatform | PlatformPreset)[]; + }, + default: ['common'], + error: "platforms should be a comma-separated list of platforms or platform presets." + }); + + const cache = await dialog.ask({ + prompt: 'binary cache', + parse: oneOf({ npm: 'npm' as const, none: undefined }), + default: 'npm' as const, + error: "cache should be a supported Neon binary cache type (\"npm\" or \"none\")." + }); + + const org = cache === 'npm' ? await dialog.ask({ + prompt: 'cache org', + parse: (v: string): string => v, + default: NPM.inferOrg(packageSpec.name) + }) : null; + + const ci = await dialog.ask({ + prompt: 'ci provider', + parse: oneOf({ npm: 'github' as const, none: undefined }), + default: 'github' as const, + error: "provider should be a supported Neon CI provider (\"github\" or \"none\")." + }); + + packageSpec.library = { + lang: Lang.TS, + module: ModuleType.ESM, + cache: cache === 'npm' ? new NPM(packageSpec.name, org!) : undefined, + ci: ci === 'github' ? new GitHub() : undefined, + platforms: (platforms.length === 1) ? platforms[0] : platforms + }; + } else { + packageSpec.app = true; + } + dialog.end(); +} export type CreateNeonOptions = { templates: Record; library: LibrarySpec | null; - yes: boolean | undefined; + app: boolean | null; }; export async function createNeon(name: string, options: CreateNeonOptions) { @@ -24,7 +103,11 @@ export async function createNeon(name: string, options: CreateNeonOptions) { name, version: "0.1.0", library: options.library, - yes: options.yes, + app: options.app, + // Even if the user specifies this with a flag (e.g. `npm init -y neon`), + // `npm init` sets this env var to 'true' before invoking create-neon. + // So this is the most general way to check this configuration option. + yes: process.env['npm_configure_yes'] === 'true', }; const metadata: Metadata = { @@ -36,32 +119,38 @@ export async function createNeon(name: string, options: CreateNeonOptions) { let tmpPackagePath: string = ""; try { - // pretty lightweight way to check both that folder doesn't exist and - // that the user has write permissions. - await fs.mkdir(name); - await fs.rmdir(name); + await assertCanMkdir(name); - tmpFolderName = await fs.mkdtemp(`neon-`); + tmpFolderName = await mktemp(); tmpPackagePath = path.join(tmpFolderName, name); + await fs.mkdir(tmpPackagePath); } catch (err: any) { await die(`Could not create \`${name}\`: ${err.message}`, tmpFolderName); } - let pkg: Package | undefined; + // Print a Neon variation of the `npm init` prelude text. + if (!packageSpec.yes) { + console.log(CREATE_NEON_PRELUDE); + } + + // If neither --lib nor --app was specified, find out. + if (packageSpec.library === null && packageSpec.app === null) { + await askProjectType(packageSpec); + } try { - pkg = await Package.create(metadata, tmpPackagePath); - metadata.package = pkg; + metadata.package = await Package.create(metadata, tmpFolderName, tmpPackagePath); } catch (err: any) { await die( "Could not create `package.json`: " + err.message, tmpPackagePath ); } - if (pkg) { - if (options.library && options.library.ci) { - options.library.ci.setup(); + + if (metadata.package) { + if (packageSpec.library && packageSpec.library.ci) { + packageSpec.library.ci.setup(); } for (const source of Object.keys(options.templates)) { @@ -70,19 +159,19 @@ export async function createNeon(name: string, options: CreateNeonOptions) { } } - if (options.library) { - const templates = LANG_TEMPLATES[options.library.lang]; + if (packageSpec.library) { + const templates = LANG_TEMPLATES[packageSpec.library.lang]; for (const source of Object.keys(templates)) { const target = path.join(tmpPackagePath, templates[source]); await expandTo(source, target, metadata); } - if (options.library.ci) { - const templates = options.library.ci.templates(); + if (packageSpec.library.ci) { + const templates = packageSpec.library.ci.templates(); for (const source of Object.keys(templates)) { const target = path.join(tmpPackagePath, templates[source]); await expandTo( - `ci/${options.library.ci.type}/${source}`, + `ci/${packageSpec.library.ci.type}/${source}`, target, metadata ); @@ -91,16 +180,20 @@ export async function createNeon(name: string, options: CreateNeonOptions) { const manifest = await LibraryManifest.load(tmpPackagePath); - const platformPresets: PlatformPreset[] = Array.isArray( - options.library.platforms + const platforms: (NodePlatform | PlatformPreset)[] = Array.isArray( + packageSpec.library.platforms ) - ? options.library.platforms - : !options.library.platforms - ? ["common"] - : [options.library.platforms]; - - for (const preset of platformPresets) { - await manifest.addPlatformPreset(preset); + ? packageSpec.library.platforms + : !packageSpec.library.platforms + ? ['common'] + : [packageSpec.library.platforms]; + + for (const platform of platforms) { + if (isNodePlatform(platform)) { + await manifest.addNodePlatform(platform); + } else { + await manifest.addPlatformPreset(platform); + } } await manifest.saveChanges((msg) => {}); diff --git a/pkgs/create-neon/src/package.ts b/pkgs/create-neon/src/package.ts index 55fef31fa..bb63903e4 100644 --- a/pkgs/create-neon/src/package.ts +++ b/pkgs/create-neon/src/package.ts @@ -1,11 +1,11 @@ import { promises as fs } from "fs"; import * as path from "path"; -import shell from "./shell.js"; +import { npmInit } from './shell.js'; import { VERSIONS } from "./versions.js"; import { Cache } from "./cache.js"; import { CI } from "./ci.js"; import { Metadata, expand, expandTo } from "./expand.js"; -import { PlatformPreset } from "@neon-rs/manifest/platform"; +import { NodePlatform, PlatformPreset } from "@neon-rs/manifest/platform"; export enum Lang { JS = "js", @@ -34,13 +34,14 @@ export type LibrarySpec = { module: ModuleType; cache?: Cache; ci?: CI | undefined; - platforms?: PlatformPreset | PlatformPreset[]; + platforms?: NodePlatform | PlatformPreset | (NodePlatform | PlatformPreset)[]; }; export type PackageSpec = { name: string; version: string; library: LibrarySpec | null; + app: boolean | null; cache?: Cache | undefined; ci?: CI | undefined; yes: boolean | undefined; @@ -75,7 +76,7 @@ export default class Package { description: string; quotedDescription: string; - static async create(metadata: Metadata, dir: string): Promise { + static async create(metadata: Metadata, tmp: string, dir: string): Promise { const baseTemplate = metadata.packageSpec.library ? "manifest/base/library.json.hbs" : "manifest/base/default.json.hbs"; @@ -103,10 +104,11 @@ export default class Package { await fs.writeFile(filename, JSON.stringify(seed)); // 2. Call `npm init` to ask the user remaining questions. - await shell( - "npm", - ["init", ...(metadata.packageSpec.yes ? ["--yes"] : [])], - dir + await npmInit( + !metadata.packageSpec.yes, + metadata.packageSpec.yes ? ["--yes"] : [], + dir, + tmp ); // 3. Sort the values in idiomatic `npm init` order. diff --git a/pkgs/create-neon/src/shell.ts b/pkgs/create-neon/src/shell.ts index ec061ea44..999285630 100644 --- a/pkgs/create-neon/src/shell.ts +++ b/pkgs/create-neon/src/shell.ts @@ -1,54 +1,139 @@ -import { spawn } from "child_process"; -import { promises as fs } from "fs"; -import path from "path"; - -/** - * Transparently shell out to an executable with a list of arguments. - * All stdio is inherited directly from the current process. - */ -export default function shell( - cmd: string, - args: string[], - cwd: string -): Promise { - let child = spawn(cmd, args, { stdio: "inherit", shell: true, cwd }); - - let resolve: (result: undefined) => void; - let reject: (error: Error) => void; +import { ChildProcess, spawn } from 'node:child_process'; +import { PassThrough, Readable, Writable } from 'node:stream'; +import { StringDecoder } from 'node:string_decoder'; +import readline from 'node:readline/promises'; - let result: Promise = new Promise((res, rej) => { - resolve = res; - reject = rej; +export 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; +} + +class NpmInit { + private _tmp: string; + private _regexp: RegExp; + private _child: ChildProcess; + + constructor(interactive: boolean, args: string[], cwd: string, tmp: string) { + this._tmp = tmp; + this._regexp = new RegExp(tmp + "."); + this._child = spawn('npm', ['init', ...args], { + stdio: ['inherit', 'pipe', 'inherit'], + shell: true, + cwd + }); + this.filterStdout({ interactive }).then(() => {}); + } + + exit(): Promise { + let resolve: (code: number | null) => void; + const result: Promise = new Promise((res) => { + resolve = res; + }); + this._child.on('exit', (code) => { + resolve(code); + }); + return result; + } + + async filterStdout(opts: { interactive: boolean }) { + // We'll suppress the `npm init` interactive prelude text, + // in favor of printing our own create-neon version of the text. + let inPrelude = opts.interactive; + + for await (const chunk of readChunks(this._child.stdout!)) { + const lines = (chunk as string).split(/\r?\n/); + if (opts.interactive && inPrelude) { + // The first interactive prompt marks the end of the prelude. + const i = lines.findIndex(line => line.match(/^[a-z ]+:/)); + + // No prompt? We're still in the prelude so ignore and continue. + if (i === -1) { + continue; + } + + // Ignore the prelude lines up to the first interactive prompt. + lines.splice(0, i); + inPrelude = false; + } + + // Print out all the lines. + lines.forEach((line, i) => { + // Remove the temp dir from any paths printed out by `npm init`. + process.stdout.write(line.replace(this._regexp, "")); + if (i < lines.length - 1) { + process.stdout.write('\n'); + } + }); + } + } +} + +export function npmInit( + interactive: boolean, + args: string[], + cwd: string, + tmp: string +): Promise { + return (new NpmInit(interactive, args, cwd, tmp)).exit(); +} - child.on("exit", async (code) => { - if (code == null) { - reject(Error(`error code: ${code}`)); +export type Parser = (v: string) => T; + +export function oneOf(opts: T): Parser { + return (v: string) => { + for (const key in opts) { + if (v === key) { + return opts[key]; + } } - if (code !== 0) { - //This will catch answering no and many other failures - reject(Error(`error code: ${code}`)); + throw new Error('parse error'); + }; +} + +export interface Question { + prompt: string, + parse: Parser, + default: T, + error?: string +}; + +export class Dialog { + private _rl: readline.Interface | undefined; + + constructor() { + this._rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + } + + private rl(): readline.Interface { + if (!this._rl) { + throw new Error("dialog already ended"); } + return this._rl; + } + + end() { + this.rl().close(); + this._rl = undefined; + } - if (code === 0) { + async ask(opts: Question): Promise { + while (true) { try { - let data = await fs.readFile(path.join(cwd, "package.json"), "utf8"); - //Testing whether npm init was successful. - //It will catch Ctrl+C and many other failures - let { description, author, license } = JSON.parse(data); - if ([description, author, license].includes(undefined)) { - reject(Error(`error code: ${code}`)); + const answer = (await this.rl().question(`neon ${opts.prompt}: (${String(opts.default)}) `)).trim(); + return answer === "" ? opts.default : opts.parse(answer); + } catch (_ignored) { + if (opts.error) { + console.log(`Sorry, ${opts.error}`); } - } catch (e: any) { - reject(e); } } - - resolve(undefined); - }); - - child.on("error", async (error) => { - reject(error); - }); - return result; + } } diff --git a/pkgs/create-neon/test/create-neon.ts b/pkgs/create-neon/test/create-neon.ts index 71d8d8f5c..d587000d2 100644 --- a/pkgs/create-neon/test/create-neon.ts +++ b/pkgs/create-neon/test/create-neon.ts @@ -49,7 +49,7 @@ describe("Project creation", () => { it("succeeds with all default answers", async () => { try { - await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { + await expect(spawn(NODE, [CREATE_NEON, "--app", PROJECT]), { "package name:": "", "version:": "", "description:": "", @@ -89,7 +89,7 @@ describe("Project creation", () => { it("handles quotation marks in author and description", async () => { try { - await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { + await expect(spawn(NODE, [CREATE_NEON, "--app", PROJECT]), { "package name:": "", "version:": "", "description:": 'the "hello world" of examples', @@ -124,4 +124,69 @@ describe("Project creation", () => { '"Dave Herman" ', ]); }); + + it("asks Neon project type if not specified", async () => { + try { + await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { + "neon project type": "", + "package name:": "", + "version:": "", + "description:": "", + "git repository:": "", + "keywords:": "", + "author:": "", + "license:": "", + "Is this OK?": "", + }); + } catch (error: any) { + assert.fail("create-neon unexpectedly failed: " + error.message); + } + + JSON.parse( + await fs.readFile(path.join(PROJECT, "package.json"), { + encoding: "utf8", + }) + ); + + TOML.parse( + await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) + ); + }); + + it("asks Neon lib questions interactively", async () => { + try { + await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { + "neon project type": "lib", + "neon target platforms": "", + "neon binary cache": "", + "neon cache org": "", + "neon ci provider": "", + "package name:": "", + "version:": "", + "description:": "", + "git repository:": "", + "keywords:": "", + "author:": "", + "license:": "", + "Is this OK?": "", + }); + } catch (error: any) { + assert.fail("create-neon unexpectedly failed: " + error.message); + } + + let json = JSON.parse( + await fs.readFile(path.join(PROJECT, "package.json"), { + encoding: "utf8", + }) + ); + + assert.strictEqual(json.neon.type, "library"); + assert.strictEqual(json.neon.org, "@create-neon-test-project"); + assert.deepEqual(json.neon.platforms, ["common"]); + assert.strictEqual(json.neon.load, "./src/load.cts"); + + TOML.parse( + await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) + ); + }); }); From ab6147273b0fd1c39e41fcaa23382ff73f07759a Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 11:04:55 -0700 Subject: [PATCH 02/11] update README to include the mandatory `--` --- pkgs/create-neon/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/create-neon/README.md b/pkgs/create-neon/README.md index 1630efe7b..04c306a4b 100644 --- a/pkgs/create-neon/README.md +++ b/pkgs/create-neon/README.md @@ -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 [ ...] my-project +$ npm init neon -- [ ...] my-project ``` +**Note:** The initial `--` is necessary for `npm init` to pass any command-line options to Neon. + #### Global Options ```sh @@ -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 [ ...] --lib [ ...] my-project +$ npm init neon -- [ ...] --lib [ ...] 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. From 34159d66149ad1ec15952fd4c41f1ccbde4a5a43 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 11:23:58 -0700 Subject: [PATCH 03/11] simplify NpmInit logic -- we know the prompt will always be at the end of a chunk --- pkgs/create-neon/src/shell.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pkgs/create-neon/src/shell.ts b/pkgs/create-neon/src/shell.ts index 999285630..b3b4049b7 100644 --- a/pkgs/create-neon/src/shell.ts +++ b/pkgs/create-neon/src/shell.ts @@ -16,13 +16,16 @@ export function readChunks(input: Readable): Readable { return output; } +// A child process representing a modified `npm init` invocation: +// - If interactive, the initial prelude of stdout text is suppressed +// so we can present a modified prelude for create-neon. +// - The process is being run in a temp subdirectory, so any output that +// includes the temp directory in a path is transformed to remove it. class NpmInit { - private _tmp: string; private _regexp: RegExp; private _child: ChildProcess; constructor(interactive: boolean, args: string[], cwd: string, tmp: string) { - this._tmp = tmp; this._regexp = new RegExp(tmp + "."); this._child = spawn('npm', ['init', ...args], { stdio: ['inherit', 'pipe', 'inherit'], @@ -51,16 +54,16 @@ class NpmInit { for await (const chunk of readChunks(this._child.stdout!)) { const lines = (chunk as string).split(/\r?\n/); if (opts.interactive && inPrelude) { - // The first interactive prompt marks the end of the prelude. - const i = lines.findIndex(line => line.match(/^[a-z ]+:/)); - - // No prompt? We're still in the prelude so ignore and continue. - if (i === -1) { + // If there's a prompt, it'll be at the end of the data chunk + // since npm init will have flushed stdout to block on stdin. + if (!lines[lines.length - 1].match(/^[a-z ]+:/)) { + // We're still in the prelude so suppress all the lines and + // wait for the next chunk of stdout data. continue; } - // Ignore the prelude lines up to the first interactive prompt. - lines.splice(0, i); + // Suppress the prelude lines up to the prompt. + lines.splice(0, lines.length - 1); inPrelude = false; } From 97e1a5e0c619c3030b5a8ad60e57ce500383ac2b Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 11:25:51 -0700 Subject: [PATCH 04/11] bump create-neon version to v0.5 --- pkgs/create-neon/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/create-neon/package.json b/pkgs/create-neon/package.json index f342d4bbc..5a316e411 100644 --- a/pkgs/create-neon/package.json +++ b/pkgs/create-neon/package.json @@ -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", From 662f1e0ac1778eced895f1a11176289ce207f9b0 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 11:29:14 -0700 Subject: [PATCH 05/11] prettier --- pkgs/create-neon/src/bin/create-neon.ts | 10 ++- pkgs/create-neon/src/fs.ts | 12 ++-- pkgs/create-neon/src/index.ts | 89 +++++++++++++++---------- pkgs/create-neon/src/package.ts | 8 ++- pkgs/create-neon/src/shell.ts | 49 ++++++++------ 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/pkgs/create-neon/src/bin/create-neon.ts b/pkgs/create-neon/src/bin/create-neon.ts index 2e1d9a31b..3c7eee173 100644 --- a/pkgs/create-neon/src/bin/create-neon.ts +++ b/pkgs/create-neon/src/bin/create-neon.ts @@ -64,7 +64,7 @@ try { const ci = parseCI(opts.ci); if (opts.yes) { - process.env['npm_configure_yes'] = 'true'; + process.env["npm_configure_yes"] = "true"; } createNeon(pkg, { @@ -78,7 +78,7 @@ try { platforms, } : null, - app: opts.app ? true : null + app: opts.app ? true : null, }); } catch (e) { printErrorWithUsage(e); @@ -87,7 +87,11 @@ try { function parsePlatforms( platforms: string[] -): NodePlatform | PlatformPreset | (NodePlatform | PlatformPreset)[] | undefined { +): + | NodePlatform + | PlatformPreset + | (NodePlatform | PlatformPreset)[] + | undefined { if (platforms.length === 0) { return undefined; } else if (platforms.length === 1) { diff --git a/pkgs/create-neon/src/fs.ts b/pkgs/create-neon/src/fs.ts index 349ef2f2f..df8f32186 100644 --- a/pkgs/create-neon/src/fs.ts +++ b/pkgs/create-neon/src/fs.ts @@ -1,6 +1,6 @@ -import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; -import { existsSync, rmSync } from 'node:fs'; +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 @@ -21,8 +21,8 @@ export async function mktemp(): Promise { console.error(`warning: could not delete ${tmpFolderName}: ${e}`); } } - process.on('exit', cleanupTmp); - process.on('SIGINT', cleanupTmp); - process.on('uncaughtException', cleanupTmp); + process.on("exit", cleanupTmp); + process.on("SIGINT", cleanupTmp); + process.on("uncaughtException", cleanupTmp); return tmpFolderName; } diff --git a/pkgs/create-neon/src/index.ts b/pkgs/create-neon/src/index.ts index 54a7a9f06..4d9c65859 100644 --- a/pkgs/create-neon/src/index.ts +++ b/pkgs/create-neon/src/index.ts @@ -1,6 +1,6 @@ -import { promises as fs } from 'fs'; -import * as path from 'path'; -import die from './die.js'; +import { promises as fs } from "fs"; +import * as path from "path"; +import die from "./die.js"; import Package, { PackageSpec, LibrarySpec, @@ -11,11 +11,16 @@ import Package, { import { VERSIONS } from "./versions.js"; import { Metadata, expandTo } from "./expand.js"; import { LibraryManifest } from "@neon-rs/manifest"; -import { NodePlatform, PlatformPreset, isNodePlatform, isPlatformPreset } from "@neon-rs/manifest/platform"; -import { assertCanMkdir, mktemp } from './fs.js'; -import { Dialog, oneOf } from './shell.js'; -import { NPM } from './cache/npm.js'; -import { GitHub } from './ci/github.js'; +import { + NodePlatform, + PlatformPreset, + isNodePlatform, + isPlatformPreset, +} from "@neon-rs/manifest/platform"; +import { assertCanMkdir, mktemp } from "./fs.js"; +import { Dialog, oneOf } from "./shell.js"; +import { NPM } from "./cache/npm.js"; +import { GitHub } from "./ci/github.js"; const CREATE_NEON_PRELUDE: string = ` This utility will walk you through creating a Neon project. @@ -39,52 +44,58 @@ async function askProjectType(packageSpec: PackageSpec) { // Otherwise, find out interactively. const dialog = new Dialog(); const ty = await dialog.ask({ - prompt: 'project type', - parse: oneOf({ app: 'app' as const, lib: 'lib' as const }), - default: 'app' as const, - error: "type should be a valid Neon project type (\"app\" or \"lib\")." + prompt: "project type", + parse: oneOf({ app: "app" as const, lib: "lib" as const }), + default: "app" as const, + error: 'type should be a valid Neon project type ("app" or "lib").', }); - if (ty === 'lib') { + if (ty === "lib") { const platforms: (NodePlatform | PlatformPreset)[] = await dialog.ask({ - prompt: 'target platforms', + prompt: "target platforms", parse: (v: string): (NodePlatform | PlatformPreset)[] => { - const a = v.split(',').map(s => s.trim()); - if (a.some(elt => !isNodePlatform(elt) && !isPlatformPreset(elt))) { + const a = v.split(",").map((s) => s.trim()); + if (a.some((elt) => !isNodePlatform(elt) && !isPlatformPreset(elt))) { throw new Error("parse error"); } return a as (NodePlatform | PlatformPreset)[]; }, - default: ['common'], - error: "platforms should be a comma-separated list of platforms or platform presets." + default: ["common"], + error: + "platforms should be a comma-separated list of platforms or platform presets.", }); const cache = await dialog.ask({ - prompt: 'binary cache', - parse: oneOf({ npm: 'npm' as const, none: undefined }), - default: 'npm' as const, - error: "cache should be a supported Neon binary cache type (\"npm\" or \"none\")." + prompt: "binary cache", + parse: oneOf({ npm: "npm" as const, none: undefined }), + default: "npm" as const, + error: + 'cache should be a supported Neon binary cache type ("npm" or "none").', }); - const org = cache === 'npm' ? await dialog.ask({ - prompt: 'cache org', - parse: (v: string): string => v, - default: NPM.inferOrg(packageSpec.name) - }) : null; + const org = + cache === "npm" + ? await dialog.ask({ + prompt: "cache org", + parse: (v: string): string => v, + default: NPM.inferOrg(packageSpec.name), + }) + : null; const ci = await dialog.ask({ - prompt: 'ci provider', - parse: oneOf({ npm: 'github' as const, none: undefined }), - default: 'github' as const, - error: "provider should be a supported Neon CI provider (\"github\" or \"none\")." + prompt: "ci provider", + parse: oneOf({ npm: "github" as const, none: undefined }), + default: "github" as const, + error: + 'provider should be a supported Neon CI provider ("github" or "none").', }); packageSpec.library = { lang: Lang.TS, module: ModuleType.ESM, - cache: cache === 'npm' ? new NPM(packageSpec.name, org!) : undefined, - ci: ci === 'github' ? new GitHub() : undefined, - platforms: (platforms.length === 1) ? platforms[0] : platforms + cache: cache === "npm" ? new NPM(packageSpec.name, org!) : undefined, + ci: ci === "github" ? new GitHub() : undefined, + platforms: platforms.length === 1 ? platforms[0] : platforms, }; } else { packageSpec.app = true; @@ -107,7 +118,7 @@ export async function createNeon(name: string, options: CreateNeonOptions) { // Even if the user specifies this with a flag (e.g. `npm init -y neon`), // `npm init` sets this env var to 'true' before invoking create-neon. // So this is the most general way to check this configuration option. - yes: process.env['npm_configure_yes'] === 'true', + yes: process.env["npm_configure_yes"] === "true", }; const metadata: Metadata = { @@ -140,7 +151,11 @@ export async function createNeon(name: string, options: CreateNeonOptions) { } try { - metadata.package = await Package.create(metadata, tmpFolderName, tmpPackagePath); + metadata.package = await Package.create( + metadata, + tmpFolderName, + tmpPackagePath + ); } catch (err: any) { await die( "Could not create `package.json`: " + err.message, @@ -185,7 +200,7 @@ export async function createNeon(name: string, options: CreateNeonOptions) { ) ? packageSpec.library.platforms : !packageSpec.library.platforms - ? ['common'] + ? ["common"] : [packageSpec.library.platforms]; for (const platform of platforms) { diff --git a/pkgs/create-neon/src/package.ts b/pkgs/create-neon/src/package.ts index bb63903e4..43f77102c 100644 --- a/pkgs/create-neon/src/package.ts +++ b/pkgs/create-neon/src/package.ts @@ -1,6 +1,6 @@ import { promises as fs } from "fs"; import * as path from "path"; -import { npmInit } from './shell.js'; +import { npmInit } from "./shell.js"; import { VERSIONS } from "./versions.js"; import { Cache } from "./cache.js"; import { CI } from "./ci.js"; @@ -76,7 +76,11 @@ export default class Package { description: string; quotedDescription: string; - static async create(metadata: Metadata, tmp: string, dir: string): Promise { + static async create( + metadata: Metadata, + tmp: string, + dir: string + ): Promise { const baseTemplate = metadata.packageSpec.library ? "manifest/base/library.json.hbs" : "manifest/base/default.json.hbs"; diff --git a/pkgs/create-neon/src/shell.ts b/pkgs/create-neon/src/shell.ts index b3b4049b7..6ea40cad2 100644 --- a/pkgs/create-neon/src/shell.ts +++ b/pkgs/create-neon/src/shell.ts @@ -1,15 +1,15 @@ -import { ChildProcess, spawn } from 'node:child_process'; -import { PassThrough, Readable, Writable } from 'node:stream'; -import { StringDecoder } from 'node:string_decoder'; -import readline from 'node:readline/promises'; +import { ChildProcess, spawn } from "node:child_process"; +import { PassThrough, Readable, Writable } from "node:stream"; +import { StringDecoder } from "node:string_decoder"; +import readline from "node:readline/promises"; export function readChunks(input: Readable): Readable { let output = new PassThrough({ objectMode: true }); - let decoder = new StringDecoder('utf8'); - input.on('data', (data) => { + let decoder = new StringDecoder("utf8"); + input.on("data", (data) => { output.write(decoder.write(data)); }); - input.on('close', () => { + input.on("close", () => { output.write(decoder.end()); output.end(); }); @@ -27,10 +27,10 @@ class NpmInit { constructor(interactive: boolean, args: string[], cwd: string, tmp: string) { this._regexp = new RegExp(tmp + "."); - this._child = spawn('npm', ['init', ...args], { - stdio: ['inherit', 'pipe', 'inherit'], + this._child = spawn("npm", ["init", ...args], { + stdio: ["inherit", "pipe", "inherit"], shell: true, - cwd + cwd, }); this.filterStdout({ interactive }).then(() => {}); } @@ -40,7 +40,7 @@ class NpmInit { const result: Promise = new Promise((res) => { resolve = res; }); - this._child.on('exit', (code) => { + this._child.on("exit", (code) => { resolve(code); }); return result; @@ -72,7 +72,7 @@ class NpmInit { // Remove the temp dir from any paths printed out by `npm init`. process.stdout.write(line.replace(this._regexp, "")); if (i < lines.length - 1) { - process.stdout.write('\n'); + process.stdout.write("\n"); } }); } @@ -85,7 +85,7 @@ export function npmInit( cwd: string, tmp: string ): Promise { - return (new NpmInit(interactive, args, cwd, tmp)).exit(); + return new NpmInit(interactive, args, cwd, tmp).exit(); } export type Parser = (v: string) => T; @@ -97,22 +97,25 @@ export function oneOf(opts: T): Parser { return opts[key]; } } - throw new Error('parse error'); + throw new Error("parse error"); }; } export interface Question { - prompt: string, - parse: Parser, - default: T, - error?: string -}; + prompt: string; + parse: Parser; + default: T; + error?: string; +} export class Dialog { private _rl: readline.Interface | undefined; constructor() { - this._rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + this._rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); } private rl(): readline.Interface { @@ -130,7 +133,11 @@ export class Dialog { async ask(opts: Question): Promise { while (true) { try { - const answer = (await this.rl().question(`neon ${opts.prompt}: (${String(opts.default)}) `)).trim(); + const answer = ( + await this.rl().question( + `neon ${opts.prompt}: (${String(opts.default)}) ` + ) + ).trim(); return answer === "" ? opts.default : opts.parse(answer); } catch (_ignored) { if (opts.error) { From 928af6956f1ebd7dfa898e786277d07bdfb46fe7 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 12 May 2024 16:52:19 -0700 Subject: [PATCH 06/11] update @neon-rs/manifest to 0.1.0 for https://github.com/neon-bindings/neon-rs/pull/52 --- package-lock.json | 10 +++++----- pkgs/create-neon/package.json | 2 +- pkgs/create-neon/test/create-neon.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index fac55a862..2324603d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -833,9 +833,9 @@ } }, "node_modules/@neon-rs/manifest": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@neon-rs/manifest/-/manifest-0.0.6.tgz", - "integrity": "sha512-Hg4gbbyAl7h96WNTu/IaLIBI5w82yr2Mjs/fvYohTYDE4wS+g+Fv7B2ahfGzLHaxLJpljTMgfMwQJuan0w7o5Q==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@neon-rs/manifest/-/manifest-0.1.0.tgz", + "integrity": "sha512-CtmcJbqczgEjiU169n+ZdmWwKLDFz5PSybmD6JbzzzWIi9JaLh5Jpk67vipN5AnEwYUEtXVSUmmkJGwzRBu5Ug==", "dependencies": { "jscodeshift": "^0.15.1" } @@ -6066,10 +6066,10 @@ } }, "pkgs/create-neon": { - "version": "0.3.0", + "version": "0.5.0", "license": "MIT", "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", diff --git a/pkgs/create-neon/package.json b/pkgs/create-neon/package.json index 5a316e411..21cea275f 100644 --- a/pkgs/create-neon/package.json +++ b/pkgs/create-neon/package.json @@ -48,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", diff --git a/pkgs/create-neon/test/create-neon.ts b/pkgs/create-neon/test/create-neon.ts index d587000d2..ef747b25c 100644 --- a/pkgs/create-neon/test/create-neon.ts +++ b/pkgs/create-neon/test/create-neon.ts @@ -182,7 +182,7 @@ describe("Project creation", () => { assert.strictEqual(json.neon.type, "library"); assert.strictEqual(json.neon.org, "@create-neon-test-project"); - assert.deepEqual(json.neon.platforms, ["common"]); + assert.strictEqual(json.neon.platforms, "common"); assert.strictEqual(json.neon.load, "./src/load.cts"); TOML.parse( From 78c14ce867b33153dfb91e7faae09eda2c326e88 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 13 May 2024 09:15:55 -0700 Subject: [PATCH 07/11] remove useless check --- pkgs/create-neon/src/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pkgs/create-neon/src/index.ts b/pkgs/create-neon/src/index.ts index 4d9c65859..379b0c26f 100644 --- a/pkgs/create-neon/src/index.ts +++ b/pkgs/create-neon/src/index.ts @@ -163,15 +163,13 @@ export async function createNeon(name: string, options: CreateNeonOptions) { ); } - if (metadata.package) { - if (packageSpec.library && packageSpec.library.ci) { - packageSpec.library.ci.setup(); - } + if (packageSpec.library && packageSpec.library.ci) { + packageSpec.library.ci.setup(); + } - for (const source of Object.keys(options.templates)) { - const target = path.join(tmpPackagePath, options.templates[source]); - await expandTo(source, target, metadata); - } + for (const source of Object.keys(options.templates)) { + const target = path.join(tmpPackagePath, options.templates[source]); + await expandTo(source, target, metadata); } if (packageSpec.library) { From 41b808c3194fcc34eadbbc7c6292e5f9f2feb047 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 18 May 2024 14:34:02 -0700 Subject: [PATCH 08/11] update create-neon to bump the CLI version to 0.1.72. Fixes #1043. --- pkgs/create-neon/data/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/create-neon/data/versions.json b/pkgs/create-neon/data/versions.json index 1d3b37a1b..1321a9076 100644 --- a/pkgs/create-neon/data/versions.json +++ b/pkgs/create-neon/data/versions.json @@ -1,7 +1,7 @@ { "neon": "1", - "neonCLI": "0.1.68", - "neonLoad": "0.1.68", + "neonCLI": "0.1.72", + "neonLoad": "0.1.72", "typescript": "5.3.3", "typesNode": "20.11.16", "tsconfigNode": { From 56d07f9a0a9476795f733ebf6284ece88195e27e Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 19 May 2024 11:52:13 -0700 Subject: [PATCH 09/11] Add `"prepare"` hook for `--app` projects so they'll build on `npm install`. Closes #1042. --- pkgs/create-neon/data/templates/manifest/scripts.json.hbs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/create-neon/data/templates/manifest/scripts.json.hbs b/pkgs/create-neon/data/templates/manifest/scripts.json.hbs index 9dc21fb6e..b43497366 100644 --- a/pkgs/create-neon/data/templates/manifest/scripts.json.hbs +++ b/pkgs/create-neon/data/templates/manifest/scripts.json.hbs @@ -6,7 +6,8 @@ "postcross-build": "neon dist -m /target < cross.log", "debug": "npm run cargo-build --", "build": "npm run cargo-build -- --release", - "cross": "npm run cross-build -- --release"{{#if packageSpec.library}}, + "cross": "npm run cross-build -- --release",{{#if packageSpec.library}} "prepack": "{{#eq packageSpec.library.lang compare="ts"}}tsc && {{/eq}}neon update", - "version": "neon bump --binaries platforms && git add ."{{/if}} + "version": "neon bump --binaries platforms && git add ."{{else}} + "prepare": "npm run build"{{/if}} } From 6d21027ae1f80608e4a7c8c8778fb1bb4b546fd5 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 19 May 2024 13:14:23 -0700 Subject: [PATCH 10/11] bump 0.1.72 -> 0.1.73 --- pkgs/create-neon/data/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/create-neon/data/versions.json b/pkgs/create-neon/data/versions.json index 1321a9076..46f2b4999 100644 --- a/pkgs/create-neon/data/versions.json +++ b/pkgs/create-neon/data/versions.json @@ -1,7 +1,7 @@ { "neon": "1", - "neonCLI": "0.1.72", - "neonLoad": "0.1.72", + "neonCLI": "0.1.73", + "neonLoad": "0.1.73", "typescript": "5.3.3", "typesNode": "20.11.16", "tsconfigNode": { From f37c1f9320c67866979c56ddae48347d73ba1ba3 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 20 May 2024 11:52:44 -0700 Subject: [PATCH 11/11] improvements based on @kjvalencik's review: - small cleanups to `inferOrg` regexp logic - revert the `"prepare"` hook for --app (see https://github.com/neon-bindings/neon/issues/1042#issuecomment-2121020880 ) --- pkgs/create-neon/data/templates/manifest/scripts.json.hbs | 5 ++--- pkgs/create-neon/src/cache/npm.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkgs/create-neon/data/templates/manifest/scripts.json.hbs b/pkgs/create-neon/data/templates/manifest/scripts.json.hbs index b43497366..9dc21fb6e 100644 --- a/pkgs/create-neon/data/templates/manifest/scripts.json.hbs +++ b/pkgs/create-neon/data/templates/manifest/scripts.json.hbs @@ -6,8 +6,7 @@ "postcross-build": "neon dist -m /target < cross.log", "debug": "npm run cargo-build --", "build": "npm run cargo-build -- --release", - "cross": "npm run cross-build -- --release",{{#if packageSpec.library}} + "cross": "npm run cross-build -- --release"{{#if packageSpec.library}}, "prepack": "{{#eq packageSpec.library.lang compare="ts"}}tsc && {{/eq}}neon update", - "version": "neon bump --binaries platforms && git add ."{{else}} - "prepare": "npm run build"{{/if}} + "version": "neon bump --binaries platforms && git add ."{{/if}} } diff --git a/pkgs/create-neon/src/cache/npm.ts b/pkgs/create-neon/src/cache/npm.ts index e93e08c5f..4417c096d 100644 --- a/pkgs/create-neon/src/cache/npm.ts +++ b/pkgs/create-neon/src/cache/npm.ts @@ -10,7 +10,7 @@ export class NPM implements Cache { } static inferOrg(pkg: string): string { - const m = pkg.match(/^@([^/]+)\/([^/]+)/); - return "@" + (m ? m[1] : pkg); + const m = pkg.match(/^@([^/]+)\/(.*)/); + return `@${m?.[1] ?? pkg}`; } }