@neon-rs/cli 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cargo.js CHANGED
@@ -119,33 +119,28 @@ export class MessageStream {
119
119
  }
120
120
  }
121
121
  }
122
- export class CrossMessageStream extends MessageStream {
123
- constructor(dir, file) {
122
+ export class UnmountMessageStream extends MessageStream {
123
+ constructor(mount, manifestPath, file) {
124
124
  super(file);
125
- this._dir = dir;
125
+ this._mount = mount;
126
+ this._manifestPath = manifestPath;
126
127
  }
127
128
  async findPath(pred) {
128
129
  // The base class's version reports paths as absolute paths from within
129
- // the cross-rs Docker image's virtual filesystem.
130
- const dockerPath = await super.findPath(pred);
131
- if (!dockerPath) {
130
+ // a mount in a virtual filesystem.
131
+ const mountedPath = await super.findPath(pred);
132
+ if (!mountedPath) {
132
133
  return null;
133
134
  }
134
- // Convert the absolute path into a relative path from within the
135
- // workspace's target directory.
136
- const cross = await execa('cross', ['metadata', '--format-version=1', '--no-deps'], {
137
- cwd: this._dir,
138
- shell: true
139
- });
140
- if (cross.exitCode !== 0) {
141
- throw new Error(`Invoking \`cross metadata\` failed: ${cross.stderr}`);
142
- }
143
- const crossMetadata = JSON.parse(cross.stdout);
144
135
  // The path relative to the workspace's target directory.
145
- const relPath = path.relative(crossMetadata.target_directory, dockerPath);
136
+ const relPath = path.relative(this._mount, mountedPath);
146
137
  // Now find the true absolute path of the target directory on the host system.
147
- const cargo = await execa('cargo', ['metadata', '--format-version=1', '--no-deps'], {
148
- cwd: this._dir,
138
+ const cargo = await execa('cargo', [
139
+ 'metadata',
140
+ '--format-version=1',
141
+ '--no-deps',
142
+ ...(this._manifestPath ? ['--manifest-path', this._manifestPath] : [])
143
+ ], {
149
144
  shell: true
150
145
  });
151
146
  if (cargo.exitCode !== 0) {
@@ -0,0 +1,39 @@
1
+ import Dist from './commands/dist.js';
2
+ import PackBuild from './commands/pack-build.js';
3
+ import InstallBuilds from './commands/install-builds.js';
4
+ import Help from './commands/help.js';
5
+ export var CommandName;
6
+ (function (CommandName) {
7
+ CommandName["Help"] = "help";
8
+ CommandName["Dist"] = "dist";
9
+ CommandName["PackBuild"] = "pack-build";
10
+ CommandName["InstallBuilds"] = "install-builds";
11
+ })(CommandName = CommandName || (CommandName = {}));
12
+ ;
13
+ export function isCommandName(s) {
14
+ const keys = Object.values(CommandName);
15
+ return keys.includes(s);
16
+ }
17
+ export function asCommandName(name) {
18
+ if (!isCommandName(name)) {
19
+ throw new RangeError(`Command not recognized: ${name}`);
20
+ }
21
+ return name;
22
+ }
23
+ const COMMANDS = {
24
+ [CommandName.Help]: Help,
25
+ [CommandName.Dist]: Dist,
26
+ [CommandName.PackBuild]: PackBuild,
27
+ [CommandName.InstallBuilds]: InstallBuilds
28
+ };
29
+ export function commandFor(name) {
30
+ return COMMANDS[name];
31
+ }
32
+ export function summaries() {
33
+ return [
34
+ { name: CommandName.Help, summary: Help.summary() },
35
+ { name: CommandName.Dist, summary: Dist.summary() },
36
+ { name: CommandName.PackBuild, summary: PackBuild.summary() },
37
+ { name: CommandName.InstallBuilds, summary: InstallBuilds.summary() }
38
+ ];
39
+ }
@@ -1,38 +1,75 @@
1
1
  import { copyFile } from 'node:fs/promises';
2
2
  import commandLineArgs from 'command-line-args';
3
- import { MessageStream, isCompilerArtifact } from '../cargo.js';
3
+ import { MessageStream, UnmountMessageStream, isCompilerArtifact } from '../cargo.js';
4
4
  // FIXME: add options to infer crate name from manifests
5
5
  // --package <path/to/package.json>
6
6
  // --crate <path/to/Cargo.toml>
7
7
  const OPTIONS = [
8
8
  { name: 'name', alias: 'n', type: String, defaultValue: null },
9
- { name: 'log', alias: 'l', type: String, defaultValue: null },
10
9
  { name: 'file', alias: 'f', type: String, defaultValue: null },
10
+ { name: 'log', alias: 'l', type: String, defaultValue: null },
11
+ { name: 'mount', alias: 'm', type: String, defaultValue: null },
12
+ { name: 'manifest-path', type: String, defaultValue: null },
11
13
  { name: 'out', alias: 'o', type: String, defaultValue: 'index.node' }
12
14
  ];
13
- async function findArtifact(log, crateName) {
14
- const stream = new MessageStream(log);
15
- return await stream.findPath((msg) => {
16
- if (!isCompilerArtifact(msg) || (msg.target.name !== crateName)) {
17
- return null;
15
+ export default class Dist {
16
+ static summary() { return 'Generate a .node file from a build.'; }
17
+ static syntax() { return 'neon dist [-n <name>] [-f <dylib>|[-l <log>] [-m <path>]] [-o <dist>]'; }
18
+ static options() {
19
+ return [
20
+ { name: '-n, --name', summary: 'Crate name. (Default: $npm_package_name)' },
21
+ { name: '-f, --file <dylib>', summary: 'Build .node from dylib file <dylib>.' },
22
+ { name: '-l, --log <log>', summary: 'Find dylib path from cargo messages <log>. (Default: stdin)' },
23
+ {
24
+ name: '-m, --mount <path>',
25
+ summary: 'Mounted path of target directory in virtual filesystem. This is used to map paths from the log data back to their real paths, needed when tools such as cross-rs report messages from within a mounted Docker filesystem.'
26
+ },
27
+ { name: '--manifest-path <path>', summary: 'Real path to Cargo.toml. (Default: cargo behavior)' },
28
+ { name: '-o, --out <dist>', summary: 'Copy output to file <dist>. (Default: index.node)' }
29
+ ];
30
+ }
31
+ static seeAlso() {
32
+ return [
33
+ { name: 'cargo messages', summary: '<https://doc.rust-lang.org/cargo/reference/external-tools.html>' },
34
+ { name: 'cross-rs', summary: '<https://github.com/cross-rs/cross>' }
35
+ ];
36
+ }
37
+ constructor(argv) {
38
+ const options = commandLineArgs(OPTIONS, { argv });
39
+ if (options.log && options.file) {
40
+ throw new Error("Options --log and --file cannot both be enabled.");
18
41
  }
19
- const index = msg.target.crate_types.indexOf('cdylib');
20
- return (index < 0) ? null : msg.filenames[index];
21
- });
22
- }
23
- export default async function main(argv) {
24
- const options = commandLineArgs(OPTIONS, { argv });
25
- if (options.log && options.file) {
26
- throw new Error("Options --log and --file cannot both be enabled.");
42
+ if (options.file && options.mount) {
43
+ throw new Error("Options --mount and --file cannot both be enabled.");
44
+ }
45
+ if (options['manifest-path'] && !options.mount) {
46
+ throw new Error("Option --manifest-path requires option --mount to be provided.");
47
+ }
48
+ this._log = options.log ?? null;
49
+ this._file = options.file ?? null;
50
+ this._mount = options.mount;
51
+ this._manifestPath = options['manifest-path'];
52
+ this._crateName = options.name || process.env['npm_package_name'];
53
+ this._out = options.out;
27
54
  }
28
- const crateName = options.name || process.env['npm_package_name'];
29
- if (!crateName) {
30
- throw new Error("No crate name provided.");
55
+ async findArtifact() {
56
+ const stream = this._mount
57
+ ? new UnmountMessageStream(this._mount, this._manifestPath, this._log)
58
+ : new MessageStream(this._log);
59
+ return await stream.findPath((msg) => {
60
+ if (!isCompilerArtifact(msg) || (msg.target.name !== this._crateName)) {
61
+ return null;
62
+ }
63
+ const index = msg.target.crate_types.indexOf('cdylib');
64
+ return (index < 0) ? null : msg.filenames[index];
65
+ });
31
66
  }
32
- const file = options.file ?? await findArtifact(options.log, crateName);
33
- if (!file) {
34
- throw new Error(`No library found for crate ${crateName}`);
67
+ async run() {
68
+ const file = this._file || await this.findArtifact();
69
+ if (!file) {
70
+ throw new Error(`No library found for crate ${this._crateName}`);
71
+ }
72
+ // FIXME: needs all the logic of cargo-cp-artifact (timestamp check, M1 workaround, async, errors)
73
+ await copyFile(file, this._out);
35
74
  }
36
- // FIXME: needs all the logic of cargo-cp-artifact (timestamp check, M1 workaround, async, errors)
37
- await copyFile(file, options.out);
38
75
  }
@@ -0,0 +1,26 @@
1
+ import { printMainUsage, printCommandUsage } from '../print.js';
2
+ import { asCommandName } from '../command.js';
3
+ export default class Help {
4
+ static summary() { return 'Display help information about Neon.'; }
5
+ static syntax() { return 'neon help <command>'; }
6
+ static options() {
7
+ return [
8
+ { name: '<command>', summary: 'Command to display help information about.' }
9
+ ];
10
+ }
11
+ static seeAlso() { }
12
+ constructor(argv) {
13
+ this._name = argv.length > 0 ? asCommandName(argv[0]) : undefined;
14
+ if (argv.length > 1) {
15
+ throw new Error(`Unexpected argument: ${argv[1]}`);
16
+ }
17
+ }
18
+ async run() {
19
+ if (this._name) {
20
+ printCommandUsage(this._name);
21
+ }
22
+ else {
23
+ printMainUsage();
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,65 @@
1
+ import { execa } from 'execa';
2
+ import commandLineArgs from 'command-line-args';
3
+ import * as fs from 'node:fs/promises';
4
+ import * as path from 'node:path';
5
+ const OPTIONS = [
6
+ { name: 'bundle', alias: 'b', type: String, defaultValue: null },
7
+ { name: 'no-bundle', alias: 'B', type: String, defaultValue: null }
8
+ ];
9
+ export default class InstallBuilds {
10
+ static summary() { return 'Install dependencies on prebuilds in package.json.'; }
11
+ static syntax() { return 'neon install-builds [-b <file>|-B]'; }
12
+ static options() {
13
+ return [
14
+ { name: '-b, --bundle <file>', summary: 'File to generate bundling metadata. (Default: .targets)' },
15
+ {
16
+ name: '',
17
+ summary: 'This generated file ensures support for bundlers (e.g. @vercel/ncc), which rely on static analysis to detect and enable any addons used by the library.'
18
+ },
19
+ { name: '-B, --no-bundle', summary: 'Do not generate bundling metadata.' }
20
+ ];
21
+ }
22
+ static seeAlso() {
23
+ return [
24
+ { name: 'ncc', summary: '<https://github.com/vercel/ncc>' }
25
+ ];
26
+ }
27
+ constructor(argv) {
28
+ const options = commandLineArgs(OPTIONS, { argv });
29
+ if (options.bundle && options['no-bundle']) {
30
+ throw new Error("Options --bundle and --no-bundle cannot both be enabled.");
31
+ }
32
+ this._bundle = options['no-bundle']
33
+ ? null
34
+ : !options.bundle
35
+ ? '.targets'
36
+ : options.bundle;
37
+ }
38
+ async run() {
39
+ const manifest = JSON.parse(await fs.readFile(path.join(process.cwd(), 'package.json'), { encoding: 'utf8' }));
40
+ const version = manifest.version;
41
+ const targets = Object.values(manifest.neon.targets);
42
+ const specs = targets.map(name => `${name}@${version}`);
43
+ const result = await execa('npm', ['install', '--save-exact', '-O', ...specs], { shell: true });
44
+ if (result.exitCode !== 0) {
45
+ console.error(result.stderr);
46
+ process.exit(result.exitCode);
47
+ }
48
+ if (!this._bundle) {
49
+ return;
50
+ }
51
+ const PREAMBLE = `// AUTOMATICALLY GENERATED FILE. DO NOT EDIT.
52
+ //
53
+ // This code is never executed but is detected by the static analysis of
54
+ // bundlers such as \`@vercel/ncc\`. The require() expression that selects
55
+ // the right binary module for the current platform is too dynamic to be
56
+ // analyzable by bundler analyses, so this module provides an exhaustive
57
+ // static list for those analyses.
58
+
59
+ return;
60
+
61
+ `;
62
+ const requires = targets.map(name => `require('${name}');`).join('\n');
63
+ await fs.writeFile(this._bundle, PREAMBLE + requires + '\n');
64
+ }
65
+ }
@@ -0,0 +1,106 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ import * as temp from 'temp';
5
+ import commandLineArgs from 'command-line-args';
6
+ import { execa } from 'execa';
7
+ const mktemp = temp.track().mkdir;
8
+ const OPTIONS = [
9
+ { name: 'file', alias: 'f', type: String, defaultValue: 'index.node' },
10
+ { name: 'out-dir', alias: 'd', type: String, defaultValue: null }
11
+ ];
12
+ const require = createRequire(import.meta.url);
13
+ const LLVM = require('../../data/llvm.json');
14
+ const NODE = require('../../data/node.json');
15
+ function lookup(target) {
16
+ const path = LLVM[target];
17
+ if (!path) {
18
+ throw new Error(`Rust target ${target} not supported`);
19
+ }
20
+ const [platform, arch, abi] = path;
21
+ return NODE[platform][arch][abi];
22
+ }
23
+ export default class PackBuild {
24
+ static summary() { return 'Create an npm tarball from a prebuild.'; }
25
+ static syntax() { return 'neon pack-build [-f <addon>] [-t <target>]'; }
26
+ static options() {
27
+ return [
28
+ { name: '-f, --file <addon>', summary: 'Prebuilt .node file to pack. (Default: index.node)' },
29
+ { name: '-t, --target <target>', summary: 'Rust target triple the addon was built for. (Default: rustc default host)' }
30
+ ];
31
+ }
32
+ static seeAlso() {
33
+ return [
34
+ { name: 'Rust platform support', summary: '<https://doc.rust-lang.org/rustc/platform-support.html>' },
35
+ { name: 'npm pack', summary: '<https://docs.npmjs.com/cli/commands/npm-pack>' },
36
+ { name: 'cross-rs', summary: '<https://github.com/cross-rs/cross>' }
37
+ ];
38
+ }
39
+ constructor(argv) {
40
+ const options = commandLineArgs(OPTIONS, { argv });
41
+ this._target = options.target || null;
42
+ this._addon = options.file;
43
+ this._outDir = options['out-dir'] || path.join(process.cwd(), 'dist');
44
+ }
45
+ async currentTarget() {
46
+ const result = await execa("rustc", ["-vV"], { shell: true });
47
+ if (result.exitCode !== 0) {
48
+ throw new Error(`Could not determine current Rust target: ${result.stderr}`);
49
+ }
50
+ const hostLine = result.stdout.split(/\n/).find(line => line.startsWith('host:'));
51
+ if (!hostLine) {
52
+ throw new Error("Could not determine current Rust target (unexpected rustc output)");
53
+ }
54
+ return hostLine.replace(/^host:\s+/, '');
55
+ }
56
+ async run() {
57
+ await fs.mkdir(this._outDir, { recursive: true });
58
+ const manifest = JSON.parse(await fs.readFile('package.json', { encoding: 'utf8' }));
59
+ const version = manifest.version;
60
+ const targets = manifest.neon.targets;
61
+ const target = this._target || await this.currentTarget();
62
+ const name = targets[target];
63
+ if (!name) {
64
+ throw new Error(`Rust target ${target} not found in package.json.`);
65
+ }
66
+ const targetInfo = lookup(target);
67
+ const description = `Prebuilt binary package for \`${manifest.name}\` on \`${targetInfo.node}\`.`;
68
+ let prebuildManifest = {
69
+ name,
70
+ description,
71
+ version,
72
+ os: [targetInfo.platform],
73
+ cpu: [targetInfo.arch],
74
+ main: "index.node",
75
+ files: ["README.md", "index.node"]
76
+ };
77
+ const OPTIONAL_KEYS = [
78
+ 'author', 'repository', 'keywords', 'bugs', 'homepage', 'license', 'engines'
79
+ ];
80
+ for (const key of OPTIONAL_KEYS) {
81
+ if (manifest[key]) {
82
+ prebuildManifest[key] = manifest[key];
83
+ }
84
+ }
85
+ const tmpdir = await mktemp('neon-');
86
+ await fs.writeFile(path.join(tmpdir, "package.json"), JSON.stringify(prebuildManifest, null, 2));
87
+ await fs.copyFile(this._addon, path.join(tmpdir, "index.node"));
88
+ await fs.writeFile(path.join(tmpdir, "README.md"), `# \`${name}\`\n\n${description}\n`);
89
+ const result = await execa("npm", ["pack", "--json"], {
90
+ shell: true,
91
+ cwd: tmpdir,
92
+ stdio: ['pipe', 'pipe', 'inherit']
93
+ });
94
+ if (result.exitCode !== 0) {
95
+ process.exit(result.exitCode);
96
+ }
97
+ // FIXME: comment linking to the npm issue this fixes
98
+ const tarball = JSON.parse(result.stdout)[0].filename.replace('@', '').replace('/', '-');
99
+ const dest = path.join(this._outDir, tarball);
100
+ // Copy instead of move since e.g. GitHub Actions Windows runners host temp directories
101
+ // on a different device (which causes fs.renameSync to fail).
102
+ await fs.copyFile(path.join(tmpdir, tarball), dest);
103
+ console.log(dest);
104
+ }
105
+ ;
106
+ }
package/dist/index.js CHANGED
@@ -1,52 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import commandLineCommands from 'command-line-commands';
3
- import { printUsage, die } from './usage.js';
4
- import dist from './commands/dist.js';
5
- import crossDist from './commands/cross-dist.js';
6
- import crossPack from './commands/cross-pack.js';
7
- import crossInstall from './commands/cross-install.js';
8
- const VALID_COMMANDS = [null, 'help', 'dist', 'cross-dist', 'cross-pack', 'cross-install'];
9
- export function expectCommandName(name) {
10
- switch (name) {
11
- case 'help':
12
- case 'dist':
13
- case 'cross-dist':
14
- case 'cross-pack':
15
- case 'cross-install':
16
- return name;
17
- default:
18
- throw new RangeError(`Unrecognized command: ${name}`);
3
+ import { printErrorWithUsage, printError, printMainUsage } from './print.js';
4
+ import { CommandName, asCommandName, commandFor } from './command.js';
5
+ class Cli {
6
+ parse() {
7
+ try {
8
+ const { command, argv } = commandLineCommands([null, ...Object.values(CommandName)]);
9
+ if (!command) {
10
+ printMainUsage();
11
+ process.exit(0);
12
+ }
13
+ const ctor = commandFor(asCommandName(command));
14
+ return new ctor(argv);
15
+ }
16
+ catch (e) {
17
+ printErrorWithUsage(e);
18
+ process.exit(1);
19
+ }
19
20
  }
20
21
  }
21
22
  async function main() {
23
+ const cli = new Cli();
24
+ const command = cli.parse();
22
25
  try {
23
- const { command, argv } = commandLineCommands(VALID_COMMANDS);
24
- switch (command) {
25
- case 'dist':
26
- await dist(argv);
27
- break;
28
- case 'cross-dist':
29
- await crossDist(argv);
30
- break;
31
- case 'cross-pack':
32
- await crossPack(argv);
33
- break;
34
- case 'cross-install':
35
- await crossInstall(argv);
36
- break;
37
- case 'help':
38
- if (argv.length > 0) {
39
- printUsage(expectCommandName(argv[0]));
40
- process.exit(0);
41
- }
42
- // FALL THROUGH
43
- case null:
44
- printUsage();
45
- }
46
- process.exit(0);
26
+ await command.run();
47
27
  }
48
28
  catch (e) {
49
- die((e instanceof Error) ? e.message : null);
29
+ printError(e);
30
+ process.exit(1);
50
31
  }
51
32
  }
52
33
  main();
package/dist/print.js ADDED
@@ -0,0 +1,69 @@
1
+ import commandLineUsage from 'command-line-usage';
2
+ import chalk from 'chalk';
3
+ import { commandFor, summaries } from './command.js';
4
+ function pink(text) {
5
+ return chalk.bold.hex('#e75480')(text);
6
+ }
7
+ function blue(text) {
8
+ return chalk.bold.cyanBright(text);
9
+ }
10
+ function yellow(text) {
11
+ return chalk.bold.yellowBright(text);
12
+ }
13
+ function green(text) {
14
+ return chalk.bold.greenBright(text);
15
+ }
16
+ function commandUsage(name, command) {
17
+ const sections = [
18
+ {
19
+ content: `${pink('Neon:')} ${name} - ${command.summary()}`,
20
+ raw: true
21
+ },
22
+ {
23
+ header: blue('Usage:'),
24
+ content: `${blue('$')} ${command.syntax()}`
25
+ },
26
+ {
27
+ header: yellow('Options:'),
28
+ content: command.options()
29
+ }
30
+ ];
31
+ const seeAlso = command.seeAlso();
32
+ if (seeAlso) {
33
+ sections.push({ header: green('See Also:'), content: seeAlso });
34
+ }
35
+ return commandLineUsage(sections).trimStart();
36
+ }
37
+ function mainUsage() {
38
+ const sections = [
39
+ {
40
+ content: `${pink('Neon:')} the npm packaging tool for Rust addons`,
41
+ raw: true
42
+ },
43
+ {
44
+ header: blue('Usage:'),
45
+ content: `${blue('$')} neon <command> <options>`
46
+ },
47
+ {
48
+ header: yellow('Commands:'),
49
+ content: summaries()
50
+ }
51
+ ];
52
+ return commandLineUsage(sections).trim();
53
+ }
54
+ export function printCommandUsage(name) {
55
+ console.error(commandUsage(name, commandFor(name)));
56
+ }
57
+ export function printMainUsage() {
58
+ console.error(mainUsage());
59
+ console.error();
60
+ console.error("See 'neon help <command>' for more information on a specific command.");
61
+ }
62
+ export function printErrorWithUsage(e) {
63
+ console.error(mainUsage());
64
+ console.error();
65
+ printError(e);
66
+ }
67
+ export function printError(e) {
68
+ console.error(chalk.bold.red("error:") + " " + ((e instanceof Error) ? e.message : String(e)));
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neon-rs/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Command-line build tool for Neon modules.",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",
package/types/cargo.d.ts CHANGED
@@ -35,8 +35,9 @@ export declare class MessageStream {
35
35
  constructor(file?: string | null);
36
36
  findPath(pred: MessageFilter<string>): Promise<string | null>;
37
37
  }
38
- export declare class CrossMessageStream extends MessageStream {
39
- private _dir;
40
- constructor(dir: string, file?: string | null);
38
+ export declare class UnmountMessageStream extends MessageStream {
39
+ private _mount;
40
+ private _manifestPath;
41
+ constructor(mount: string, manifestPath: string | null, file?: string | null);
41
42
  findPath(pred: MessageFilter<string>): Promise<string | null>;
42
43
  }
@@ -0,0 +1,24 @@
1
+ export interface Command {
2
+ run(): Promise<void>;
3
+ }
4
+ export interface CommandStatics {
5
+ summary(): string;
6
+ syntax(): string;
7
+ options(): CommandDetail[];
8
+ seeAlso(): CommandDetail[] | void;
9
+ }
10
+ export type CommandClass = (new (argv: string[]) => Command) & CommandStatics;
11
+ export type CommandDetail = {
12
+ name: string;
13
+ summary: string;
14
+ };
15
+ export declare enum CommandName {
16
+ Help = "help",
17
+ Dist = "dist",
18
+ PackBuild = "pack-build",
19
+ InstallBuilds = "install-builds"
20
+ }
21
+ export declare function isCommandName(s: string): s is CommandName;
22
+ export declare function asCommandName(name: string): CommandName;
23
+ export declare function commandFor(name: CommandName): CommandClass;
24
+ export declare function summaries(): CommandDetail[];
@@ -1 +1,14 @@
1
- export default function main(argv: string[]): Promise<void>;
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class CrossDist implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _file;
8
+ private _log;
9
+ private _crateName;
10
+ private _dir;
11
+ private _out;
12
+ constructor(argv: string[]);
13
+ run(): Promise<void>;
14
+ }
@@ -1 +1,10 @@
1
- export default function main(argv: string[]): Promise<void>;
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class CrossInstall implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _bundle;
8
+ constructor(argv: string[]);
9
+ run(): Promise<void>;
10
+ }
@@ -1 +1,12 @@
1
- export default function main(argv: string[]): Promise<void>;
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class CrossPack implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _target;
8
+ private _addon;
9
+ private _outDir;
10
+ constructor(argv: string[]);
11
+ run(): Promise<void>;
12
+ }
@@ -1 +1,16 @@
1
- export default function main(argv: string[]): Promise<void>;
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class Dist implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _log;
8
+ private _file;
9
+ private _mount;
10
+ private _manifestPath;
11
+ private _crateName;
12
+ private _out;
13
+ constructor(argv: string[]);
14
+ findArtifact(): Promise<string | null>;
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,10 @@
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class Help implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _name?;
8
+ constructor(argv: string[]);
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,10 @@
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class InstallBuilds implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _bundle;
8
+ constructor(argv: string[]);
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,13 @@
1
+ import { Command, CommandDetail } from '../command.js';
2
+ export default class PackBuild implements Command {
3
+ static summary(): string;
4
+ static syntax(): string;
5
+ static options(): CommandDetail[];
6
+ static seeAlso(): CommandDetail[] | void;
7
+ private _target;
8
+ private _addon;
9
+ private _outDir;
10
+ constructor(argv: string[]);
11
+ currentTarget(): Promise<string>;
12
+ run(): Promise<void>;
13
+ }
package/types/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export type CommandName = 'help' | 'dist' | 'cross-dist' | 'cross-pack' | 'cross-install';
3
- export declare function expectCommandName(name: string): CommandName;
2
+ export {};
@@ -0,0 +1,5 @@
1
+ import { CommandName } from './command.js';
2
+ export declare function printCommandUsage(name: CommandName): void;
3
+ export declare function printMainUsage(): void;
4
+ export declare function printErrorWithUsage(e: any): void;
5
+ export declare function printError(e: any): void;
package/types/usage.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  import type { CommandName } from './index.js';
2
2
  export declare function printUsage(command?: CommandName): void;
3
- export declare function die(msg?: string | null): void;
@@ -1,40 +0,0 @@
1
- import { copyFile } from 'node:fs/promises';
2
- import commandLineArgs from 'command-line-args';
3
- import { CrossMessageStream, isCompilerArtifact } from '../cargo.js';
4
- // FIXME: add options to infer crate name from manifests
5
- // --package <path/to/package.json>
6
- // --crate <path/to/Cargo.toml>
7
- const OPTIONS = [
8
- { name: 'name', alias: 'n', type: String, defaultValue: null },
9
- { name: 'log', alias: 'l', type: String, defaultValue: null },
10
- { name: 'dir', alias: 'd', type: String, defaultValue: null },
11
- { name: 'file', alias: 'f', type: String, defaultValue: null },
12
- { name: 'out', alias: 'o', type: String, defaultValue: 'index.node' }
13
- ];
14
- async function findArtifact(log, crateName, dir) {
15
- const stream = new CrossMessageStream(dir, log);
16
- return await stream.findPath((msg) => {
17
- if (!isCompilerArtifact(msg) || (msg.target.name !== crateName)) {
18
- return null;
19
- }
20
- const index = msg.target.crate_types.indexOf('cdylib');
21
- return (index < 0) ? null : msg.filenames[index];
22
- });
23
- }
24
- export default async function main(argv) {
25
- const options = commandLineArgs(OPTIONS, { argv });
26
- if (options.log && options.file) {
27
- throw new Error("Options --log and --file cannot both be enabled.");
28
- }
29
- const crateName = options.name || process.env['npm_package_name'];
30
- if (!crateName) {
31
- throw new Error("No crate name provided.");
32
- }
33
- const dir = options.dir || process.cwd();
34
- const file = options.file ?? await findArtifact(options.log, crateName, dir);
35
- if (!file) {
36
- throw new Error(`No library found for crate ${crateName}`);
37
- }
38
- // FIXME: needs all the logic of cargo-cp-artifact (timestamp check, M1 workaround, async, errors)
39
- await copyFile(file, options.out);
40
- }
@@ -1,42 +0,0 @@
1
- import { execa } from 'execa';
2
- import commandLineArgs from 'command-line-args';
3
- import * as fs from 'node:fs/promises';
4
- import * as path from 'node:path';
5
- const OPTIONS = [
6
- { name: 'bundle', alias: 'b', type: String, defaultValue: null },
7
- { name: 'no-bundle', alias: 'B', type: String, defaultValue: null }
8
- ];
9
- export default async function main(argv) {
10
- const options = commandLineArgs(OPTIONS, { argv });
11
- if (options.bundle && options['no-bundle']) {
12
- throw new Error("Options --bundle and --no-bundle cannot both be enabled.");
13
- }
14
- if (!options.bundle && !options['no-bundle']) {
15
- options.bundle = '.targets';
16
- }
17
- const manifest = JSON.parse(await fs.readFile(path.join(process.cwd(), 'package.json'), { encoding: 'utf8' }));
18
- const version = manifest.version;
19
- const targets = Object.values(manifest.neon.targets);
20
- const specs = targets.map(name => `${name}@${version}`);
21
- const result = await execa('npm', ['install', '--save-exact', '-O', ...specs], { shell: true });
22
- if (result.exitCode !== 0) {
23
- console.error(result.stderr);
24
- process.exit(result.exitCode);
25
- }
26
- if (!options.bundle) {
27
- return;
28
- }
29
- const PREAMBLE = `// AUTOMATICALLY GENERATED FILE. DO NOT EDIT.
30
- //
31
- // This code is never executed but is detected by the static analysis of
32
- // bundlers such as \`@vercel/ncc\`. The require() expression that selects
33
- // the right binary module for the current platform is too dynamic to be
34
- // analyzable by bundler analyses, so this module provides an exhaustive
35
- // static list for those analyses.
36
-
37
- return;
38
-
39
- `;
40
- const requires = targets.map(name => `require('${name}');`).join('\n');
41
- await fs.writeFile(options.bundle, PREAMBLE + requires + '\n');
42
- }
@@ -1,81 +0,0 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as path from 'node:path';
3
- import { createRequire } from 'node:module';
4
- import * as temp from 'temp';
5
- import commandLineArgs from 'command-line-args';
6
- import { execa } from 'execa';
7
- const mktemp = temp.track().mkdir;
8
- const OPTIONS = [
9
- { name: 'file', alias: 'f', type: String, defaultValue: 'index.node' },
10
- { name: 'out-dir', alias: 'd', type: String, defaultValue: null }
11
- ];
12
- const require = createRequire(import.meta.url);
13
- const LLVM = require('../../data/llvm.json');
14
- const NODE = require('../../data/node.json');
15
- function lookup(target) {
16
- const path = LLVM[target];
17
- if (!path) {
18
- throw new Error(`Rust target ${target} not supported`);
19
- }
20
- const [platform, arch, abi] = path;
21
- return NODE[platform][arch][abi];
22
- }
23
- export default async function main(argv) {
24
- const options = commandLineArgs(OPTIONS, { argv, stopAtFirstUnknown: true });
25
- argv = options._unknown || [];
26
- if (argv.length === 0) {
27
- throw new Error("Expected <target>.");
28
- }
29
- if (argv.length > 1) {
30
- throw new Error(`Unexpected argument \`${argv[1]}\`.`);
31
- }
32
- const target = argv[0];
33
- const addon = options.file;
34
- const outDir = options['out-dir'] || path.join(process.cwd(), 'dist');
35
- await fs.mkdir(outDir, { recursive: true });
36
- const manifest = JSON.parse(await fs.readFile('package.json', { encoding: 'utf8' }));
37
- const version = manifest.version;
38
- const targets = manifest.neon.targets;
39
- const name = targets[target];
40
- if (!name) {
41
- throw new Error(`Rust target ${target} not found in package.json.`);
42
- }
43
- const targetInfo = lookup(target);
44
- const description = `Prebuilt binary package for \`${manifest.name}\` on \`${targetInfo.node}\`.`;
45
- let prebuildManifest = {
46
- name,
47
- description,
48
- version,
49
- os: [targetInfo.platform],
50
- cpu: [targetInfo.arch],
51
- main: "index.node",
52
- files: ["README.md", "index.node"]
53
- };
54
- const OPTIONAL_KEYS = [
55
- 'author', 'repository', 'keywords', 'bugs', 'homepage', 'license', 'engines'
56
- ];
57
- for (const key of OPTIONAL_KEYS) {
58
- if (manifest[key]) {
59
- prebuildManifest[key] = manifest[key];
60
- }
61
- }
62
- const tmpdir = await mktemp('neon-');
63
- await fs.writeFile(path.join(tmpdir, "package.json"), JSON.stringify(prebuildManifest, null, 2));
64
- await fs.copyFile(addon, path.join(tmpdir, "index.node"));
65
- await fs.writeFile(path.join(tmpdir, "README.md"), `# \`${name}\`\n\n${description}\n`);
66
- const result = await execa("npm", ["pack", "--json"], {
67
- shell: true,
68
- cwd: tmpdir,
69
- stdio: ['pipe', 'pipe', 'inherit']
70
- });
71
- if (result.exitCode !== 0) {
72
- process.exit(result.exitCode);
73
- }
74
- // FIXME: comment linking to the npm issue this fixes
75
- const tarball = JSON.parse(result.stdout)[0].filename.replace('@', '').replace('/', '-');
76
- const dest = path.join(outDir, tarball);
77
- // Copy instead of move since e.g. GitHub Actions Windows runners host temp directories
78
- // on a different device (which causes fs.renameSync to fail).
79
- await fs.copyFile(path.join(tmpdir, tarball), dest);
80
- console.log(dest);
81
- }
package/dist/usage.js DELETED
@@ -1,150 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import commandLineUsage from 'command-line-usage';
3
- import chalk from 'chalk';
4
- function pink(text) {
5
- return chalk.bold.hex('#e75480')(text);
6
- }
7
- function blue(text) {
8
- return chalk.bold.cyanBright(text);
9
- }
10
- function yellow(text) {
11
- return chalk.bold.yellowBright(text);
12
- }
13
- function green(text) {
14
- return chalk.bold.greenBright(text);
15
- }
16
- function commandUsage(name, command) {
17
- const sections = [
18
- {
19
- content: `${pink('Neon:')} ${name} - ${COMMAND_SUMMARIES[name]}`,
20
- raw: true
21
- },
22
- {
23
- header: blue('Usage:'),
24
- content: `${blue('$')} ${command.syntax}`
25
- },
26
- {
27
- header: yellow('Options:'),
28
- content: command.options
29
- }
30
- ];
31
- if (command.seeAlso) {
32
- sections.push({
33
- header: green('See Also:'),
34
- content: command.seeAlso
35
- });
36
- }
37
- return commandLineUsage(sections);
38
- }
39
- function mainUsage() {
40
- const require = createRequire(import.meta.url);
41
- const version = require('../package.json').version;
42
- const sections = [
43
- {
44
- content: `${pink('Neon:')} CLI v${version}`,
45
- raw: true
46
- },
47
- {
48
- header: blue('Usage:'),
49
- content: `${blue('$')} neon <command> <options>`
50
- },
51
- {
52
- header: yellow('Commands:'),
53
- content: [
54
- { name: 'help', summary: COMMAND_SUMMARIES['help'] + '.' },
55
- { name: 'dist', summary: COMMAND_SUMMARIES['dist'] + '.' },
56
- { name: 'cross-dist', summary: COMMAND_SUMMARIES['cross-dist'] + '.' },
57
- { name: 'cross-pack', summary: COMMAND_SUMMARIES['cross-pack'] + '.' },
58
- { name: 'cross-install', summary: COMMAND_SUMMARIES['cross-install'] + '.' }
59
- ]
60
- }
61
- ];
62
- return commandLineUsage(sections);
63
- }
64
- const COMMAND_SUMMARIES = {
65
- 'help': 'Display help information about Neon',
66
- 'dist': 'Generate a .node file from a build',
67
- 'cross-dist': 'Generate a .node file from a cross-compiled prebuild',
68
- 'cross-pack': 'Create an npm tarball from a cross-compiled prebuild',
69
- 'cross-install': 'Install dependencies on cross-compiled prebuilds'
70
- };
71
- const COMMAND_USAGES = {
72
- 'help': {
73
- syntax: 'neon help <command>',
74
- options: [
75
- { name: '<command>', summary: 'Command to display help information about.' }
76
- ]
77
- },
78
- 'dist': {
79
- syntax: 'neon dist [-n <name>] [-l <log>|-f <dylib>] [-o <dist>]',
80
- options: [
81
- { name: '-n, --name', summary: 'Crate name. (Default: $npm_package_name)' },
82
- { name: '-l, --log <log>', summary: 'Find dylib path from cargo messages <log>. (Default: stdin)' },
83
- { name: '-f, --file <dylib>', summary: 'Build .node from dylib file <dylib>.' },
84
- { name: '-o, --out <dist>', summary: 'Copy output to file <dist>. (Default: index.node)' }
85
- ],
86
- seeAlso: [
87
- { name: 'cargo messages', summary: '<https://doc.rust-lang.org/cargo/reference/external-tools.html>' }
88
- ]
89
- },
90
- 'cross-dist': {
91
- syntax: 'neon cross-dist [-n <name>] [-l <log>|-f <dylib>] [-d <dir>] [-o <dist>]',
92
- options: [
93
- { name: '-n, --name', summary: 'Crate name. (Default: $npm_package_name)' },
94
- { name: '-l, --log <log>', summary: 'Find dylib path from cargo messages <log>. (Default: stdin)' },
95
- { name: '-d, --dir <dir>', summary: 'Crate workspace root directory. (Default: .)' },
96
- {
97
- name: '',
98
- summary: 'This is needed to normalize paths from the log data, which cross-rs provides from within the mounted Docker filesystem, back to the host filesystem.'
99
- },
100
- { name: '-f, --file <dylib>', summary: 'Build .node from dylib file <dylib>.' },
101
- { name: '-o, --out <dist>', summary: 'Copy output to file <dist>. (Default: index.node)' }
102
- ],
103
- seeAlso: [
104
- { name: 'cargo messages', summary: '<https://doc.rust-lang.org/cargo/reference/external-tools.html>' },
105
- { name: 'cross-rs', summary: '<https://github.com/cross-rs/cross>' }
106
- ]
107
- },
108
- 'cross-pack': {
109
- syntax: 'neon cross-pack [-f <addon>] <target>',
110
- options: [
111
- { name: '-f, --file <addon>', summary: 'Prebuilt .node file to pack. (Default: index.node)' },
112
- { name: '<target>', summary: 'Rust target triple the addon was built for.' }
113
- ],
114
- seeAlso: [
115
- { name: 'Rust platform support', summary: '<https://doc.rust-lang.org/rustc/platform-support.html>' },
116
- { name: 'npm pack', summary: '<https://docs.npmjs.com/cli/commands/npm-pack>' },
117
- { name: 'cross-rs', summary: '<https://github.com/cross-rs/cross>' }
118
- ]
119
- },
120
- 'cross-install': {
121
- syntax: 'neon cross-install [-b <file>|-B]',
122
- options: [
123
- { name: '-b, --bundle <file>', summary: 'File to generate bundling metadata. (Default: .targets)' },
124
- {
125
- name: '',
126
- summary: 'This generated file ensures support for bundlers (e.g. @vercel/ncc), which rely on static analysis to detect and enable any addons used by the library.'
127
- },
128
- { name: '-B, --no-bundle', summary: 'Do not generate bundling metadata.' }
129
- ],
130
- seeAlso: [
131
- { name: 'ncc', summary: '<https://github.com/vercel/ncc>' }
132
- ]
133
- }
134
- };
135
- export function printUsage(command) {
136
- if (command) {
137
- console.error(commandUsage(command, COMMAND_USAGES[command]));
138
- }
139
- else {
140
- console.error(mainUsage());
141
- }
142
- }
143
- export function die(msg) {
144
- printUsage();
145
- if (msg) {
146
- console.error();
147
- console.error(msg);
148
- }
149
- process.exit(1);
150
- }