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

create-neon #690

Merged
merged 25 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e40c612
WIP
dherman Mar 1, 2021
e548f23
Add shelling to `npm init` and reading from package.json.
dherman Mar 3, 2021
5cffd02
First working version, with a simple manual test. Next up we need aut…
dherman Mar 4, 2021
fe9e588
Address @kjvalencik's review, plus some general cleanup and refactoring:
dherman Mar 5, 2021
96f3f86
Abstract out `die` helper.
dherman Mar 5, 2021
01c0526
Just call `npm` directly since `npm init create-neon` doesn't pass th…
dherman Mar 5, 2021
d7f1c7f
- remove dead import
dherman Mar 5, 2021
73bbe42
Add shebang
dherman Mar 5, 2021
f7ee6b6
Updates the generated package.json with Neon-specific default configu…
dherman Mar 5, 2021
70fe6e4
Avoids asking the "main" or "test" package.json entries by creating t…
dherman Mar 6, 2021
d79f9ee
Simplify the datatypes for the template expansion.
dherman Mar 6, 2021
7840161
Initial test suite.
dherman Mar 8, 2021
e8803c6
- Move test helpers into dev/ directory
dherman Mar 9, 2021
ddcbcbc
Add create-neon tests to CI.
dherman Mar 9, 2021
7c3c97b
Ensure we run npm install before running the tests, for CI.
dherman Mar 9, 2021
7b13678
Remove `fs/promises` since some supported Node versions don't support…
dherman Mar 9, 2021
9d97541
Add 3 retries to `rmdir` after each test.
dherman Mar 9, 2021
6b21eb1
Longer retry delay for rmdir?
dherman Mar 9, 2021
8400efa
Add rimraf shim since `{ recursive: true }` isn't supported in all ou…
dherman Mar 9, 2021
20727be
Debugging CI: print output on failure of child process
dherman Mar 10, 2021
993d106
More CI debugging: print stderr on failure
dherman Mar 10, 2021
208346f
Call npm.cmd on Windows
dherman Mar 10, 2021
af4a264
Tweaking diagnostic output...
dherman Mar 10, 2021
e9a82cb
Relax test timeout from 2s to 5s, since GH Actions test runners are f…
dherman Mar 10, 2021
6165246
Shell to `npm` using `shell: true` instead of tacking on the extensio…
dherman Mar 10, 2021
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Cargo.lock
**/index.node
**/artifacts.json
cli/lib
create-neon/dist
test/cli/lib
npm-debug.log
rls*.log
4 changes: 4 additions & 0 deletions create-neon/data/templates/.gitignore.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
index.node
**/node_modules
**/.DS_Store
dherman marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions create-neon/data/templates/Cargo.toml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "{{project.name}}"
version = "{{project.version}}"
{{#if project.description}}
description = {{project.description.quoted}}
{{/if}}
{{#if project.author}}
authors = [{{project.author.quoted}}]
{{/if}}
{{#if project.license}}
license = "{{project.license}}"
{{/if}}
edition = "2018"
exclude = ["index.node"]

[lib]
crate-type = ["cdylib"]

[dependencies.neon]
version = "{{versions.neon}}"
default-features = false
features = ["napi-{{versions.napi}}"]
5 changes: 5 additions & 0 deletions create-neon/data/templates/README.md.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# {{project.name}}

{{#if project.description}}
{{project.description.raw}}
{{/if}}
11 changes: 11 additions & 0 deletions create-neon/data/templates/lib.rs.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use neon::prelude::*;

fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
Ok(cx.string("hello node"))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("hello", hello)?;
Ok(())
}
4 changes: 4 additions & 0 deletions create-neon/data/versions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"neon": "0.7",
"napi": "6"
}
58 changes: 58 additions & 0 deletions create-neon/package-lock.json

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

37 changes: 37 additions & 0 deletions create-neon/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "create-neon",
"version": "0.1.0",
"description": "Create Neon projects with no build configuration.",
"author": "Dave Herman <david.herman@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/neon-bindings/neon/issues"
},
"homepage": "https://github.com/neon-bindings/neon#readme",
"bin": {
"create-neon": "dist/src/bin/create-neon.js"
},
"files": [
"dist/**/*"
],
"scripts": {
"build": "tsc && cp -r data/templates dist/data",
"prepublishOnly": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1",
"manual-test": "npm run build && rm -rf throwaway-test && mkdir throwaway-test && cd throwaway-test && node ../dist/src/bin/create-neon.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/neon-bindings/neon.git"
},
"keywords": [
"neon"
],
"devDependencies": {
dherman marked this conversation as resolved.
Show resolved Hide resolved
"@types/node": "^14.14.31",
"typescript": "^4.2.2"
},
"dependencies": {
"handlebars": "^4.7.7"
}
}
54 changes: 54 additions & 0 deletions create-neon/src/bin/create-neon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { mkdir } from 'fs/promises';
dherman marked this conversation as resolved.
Show resolved Hide resolved
import npmInit from '../npm-init';
import versions from '../../data/versions.json';
import { Project, Metadata } from '../metadata';
import * as path from 'path';
import Template from '../template';
import die from '../die';

async function main() {
await npmInit();

let project: Project;

try {
project = await Project.load('package.json');
} catch (err) {
die("Could not read `package.json`: " + err.message);
}

// Select the N-API version associated with the current
// running Node process.
let inferred = process.versions.napi;

let napi = inferred
? Math.min(+versions.napi, +inferred)
: +versions.napi;

let metadata: Metadata = {
project,
versions: {
neon: versions.neon,
napi: napi
}
};

await mkdir('src');

let gitignore = new Template('.gitignore.hbs', '.gitignore');
let manifest = new Template('Cargo.toml.hbs', 'Cargo.toml');
let readme = new Template('README.md.hbs', 'README.md');
let lib = new Template('lib.rs.hbs', path.join('src', 'lib.rs'));

for (let template of [gitignore, manifest, readme, lib]) {
try {
await template.expand(metadata);
} catch (err) {
die(`Could not save ${template.target}: ${err.message}`);
}
}

console.log(`✨ Initialized Neon project \`${metadata.project.name}\`. Happy 🦀 hacking! ✨`);
}

main();
4 changes: 4 additions & 0 deletions create-neon/src/die.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function die(message: string): never {
console.error(`❌ ${message}`);
process.exit(1);
}
48 changes: 48 additions & 0 deletions create-neon/src/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { readFile } from "fs/promises";

export interface Metadata {
project: Project,
versions: Versions
}

export interface Project {
name: string;
version: string;
author?: FreeText;
license: string;
description?: FreeText;
}

export interface FreeText {
raw: string;
quoted: string;
}

function quote(text: string): FreeText | undefined {
if (!text) {
return undefined;
}

return {
raw: text,
quoted: JSON.stringify(text)
};
}

export namespace Project {
export async function load(source: string): Promise<Project> {
let json = JSON.parse(await readFile(source, 'utf8'));
return {
name: json.name || "",
version: json.version || "",
author: quote(json.author),
license: json.license || "",
description: quote(json.description)
};
}
}

export interface Versions {
neon: string,
napi: number
}
19 changes: 19 additions & 0 deletions create-neon/src/npm-init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import die from './die';
import shell from './shell';

const NPM: string = process.env.npm_execpath || die('create-neon must be run from `npm init`');
const NODE: string = process.env.npm_node_execpath || die('create-neon must be run from `npm init`');

export default async function npmInit(): Promise<number> {
let code = await shell(NODE, [NPM, 'init']);

if (code == null) {
process.exit(1);
}

if (code !== 0) {
process.exit(code);
}

return 0;
}
27 changes: 27 additions & 0 deletions create-neon/src/shell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { spawn } from 'child_process';

/**
* 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[]): Promise<number | null> {
let child = spawn(cmd, args, { stdio: 'inherit' });

let resolve: (result: number | null) => void;
let reject: (error: Error) => void;

let result: Promise<number | null> = new Promise((res, rej) => {
resolve = res;
reject = rej;
});

child.on('exit', (code) => {
resolve(code);
});

child.on('error', (error) => {
reject(error);
});

return result;
}
27 changes: 27 additions & 0 deletions create-neon/src/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { readFile, writeFile } from 'fs/promises';
import handlebars, { TemplateDelegate } from 'handlebars';
import * as path from 'path';
import { Metadata } from './metadata';

const TEMPLATES_DIR = path.join(__dirname, '..', 'data', 'templates');

export default class Template {
source: string;
target: string;
private compiled: Promise<TemplateDelegate<Metadata>>;

constructor(source: string, target: string) {
this.source = source;
this.target = target;
this.compiled = readFile(path.join(TEMPLATES_DIR, source), {
encoding: 'utf8'
}).then(source => handlebars.compile(source, { noEscape: true }));
}

async expand(ctx: Metadata): Promise<null> {
let expanded = (await this.compiled)(ctx);
// The 'wx' flag creates the file but fails if it already exists.
await writeFile(this.target, expanded, { flag: 'wx' });
return null;
}
}
28 changes: 28 additions & 0 deletions create-neon/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"lib": [
"es6",
"es7"
],
"allowJs": false,

"sourceMap": false,
"outDir": "dist",

"esModuleInterop": true,
"strict": true,
"noImplicitReturns": true,
"allowUnreachableCode": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}