@newt-dev/cli 0.1.0
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/src/commands/build.d.ts +4 -0
- package/dist/src/commands/build.js +17 -0
- package/dist/src/commands/check.d.ts +1 -0
- package/dist/src/commands/check.js +12 -0
- package/dist/src/commands/deploy.d.ts +1 -0
- package/dist/src/commands/deploy.js +5 -0
- package/dist/src/commands/new.d.ts +1 -0
- package/dist/src/commands/new.js +51 -0
- package/dist/src/commands/run.d.ts +4 -0
- package/dist/src/commands/run.js +29 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +57 -0
- package/dist/src/util.d.ts +8 -0
- package/dist/src/util.js +26 -0
- package/dist/test/cli.test.d.ts +1 -0
- package/dist/test/cli.test.js +32 -0
- package/package.json +35 -0
- package/src/commands/build.ts +24 -0
- package/src/commands/check.ts +15 -0
- package/src/commands/deploy.ts +5 -0
- package/src/commands/new.ts +54 -0
- package/src/commands/run.ts +38 -0
- package/src/index.ts +55 -0
- package/src/util.ts +31 -0
- package/test/cli.test.ts +32 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { relative, resolve } from "node:path";
|
|
2
|
+
import { compile } from "@newt-dev/compiler";
|
|
3
|
+
import { printErrors, readSource, writeProject } from "../util.js";
|
|
4
|
+
export function buildCommand(file, options = {}) {
|
|
5
|
+
const { filename, source } = readSource(file);
|
|
6
|
+
const result = compile(source, filename);
|
|
7
|
+
if (!result.success) {
|
|
8
|
+
printErrors(result.errors, source);
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
const outDir = options.outDir ?? "dist";
|
|
12
|
+
writeProject(outDir, result.botJs, result.packageJson);
|
|
13
|
+
const display = relative(process.cwd(), resolve(outDir)) || ".";
|
|
14
|
+
console.log(`OK Built to ${display}`);
|
|
15
|
+
console.log(`Next: cd ${display} && npm install && DISCORD_TOKEN=... npm start`);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkCommand(file: string): number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { compile } from "@newt-dev/compiler";
|
|
2
|
+
import { printErrors, readSource } from "../util.js";
|
|
3
|
+
export function checkCommand(file) {
|
|
4
|
+
const { filename, source } = readSource(file);
|
|
5
|
+
const result = compile(source, filename);
|
|
6
|
+
if (!result.success) {
|
|
7
|
+
printErrors(result.errors, source);
|
|
8
|
+
return 1;
|
|
9
|
+
}
|
|
10
|
+
console.log(`OK ${file} - no errors found`);
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deployCommand(_file: string): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function newCommand(name?: string, template?: string): number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { writeTextFile } from "../util.js";
|
|
4
|
+
const templates = {
|
|
5
|
+
hello: `bot name "HelloNewt"
|
|
6
|
+
bot prefix "!"
|
|
7
|
+
bot token from env "DISCORD_TOKEN"
|
|
8
|
+
|
|
9
|
+
on ready:
|
|
10
|
+
say "HelloNewt is online!" in channel "bot-status"
|
|
11
|
+
|
|
12
|
+
on command "hello":
|
|
13
|
+
reply "Hey there, {user.name}!"
|
|
14
|
+
`,
|
|
15
|
+
welcome: `bot name "WelcomeNewt"
|
|
16
|
+
bot prefix "!"
|
|
17
|
+
bot token from env "DISCORD_TOKEN"
|
|
18
|
+
|
|
19
|
+
on join:
|
|
20
|
+
give user role "Member"
|
|
21
|
+
say embed:
|
|
22
|
+
title "Welcome, {user.name}!"
|
|
23
|
+
description "Read #rules first, then say hello."
|
|
24
|
+
color #5865F2
|
|
25
|
+
`,
|
|
26
|
+
points: `bot name "PointsNewt"
|
|
27
|
+
bot prefix "!"
|
|
28
|
+
bot token from env "DISCORD_TOKEN"
|
|
29
|
+
|
|
30
|
+
on command "points":
|
|
31
|
+
let current = load user.id points or 0
|
|
32
|
+
store user.id points = current + 1
|
|
33
|
+
reply "You have {load user.id points} points."
|
|
34
|
+
`,
|
|
35
|
+
blank: `bot name "MyNewtBot"
|
|
36
|
+
bot prefix "!"
|
|
37
|
+
bot token from env "DISCORD_TOKEN"
|
|
38
|
+
`
|
|
39
|
+
};
|
|
40
|
+
export function newCommand(name = "mybot", template = "hello") {
|
|
41
|
+
const safeName = basename(name).replace(/[^A-Za-z0-9_-]/g, "-").replace(/\.newt$/i, "");
|
|
42
|
+
const filename = `${safeName || "mybot"}.newt`;
|
|
43
|
+
if (existsSync(filename)) {
|
|
44
|
+
console.error(`${filename} already exists. Pick another name or move the existing file.`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
const source = templates[template] ?? templates.hello;
|
|
48
|
+
writeTextFile(filename, source);
|
|
49
|
+
console.log(`OK Created ${filename} - run: newt check ${filename}`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { buildCommand } from "./build.js";
|
|
6
|
+
export function runCommand(file, options = {}) {
|
|
7
|
+
if (!process.env.DISCORD_TOKEN) {
|
|
8
|
+
console.error("DISCORD_TOKEN is not set. Newt bots read tokens from the environment by default.");
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
const outDir = mkdtempSync(join(tmpdir(), "newt-run-"));
|
|
12
|
+
const built = buildCommand(file, { outDir });
|
|
13
|
+
if (built !== 0) {
|
|
14
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
15
|
+
return built;
|
|
16
|
+
}
|
|
17
|
+
console.log("Installing generated bot dependencies...");
|
|
18
|
+
const install = spawnSync("npm.cmd", ["install", "--omit=dev"], { cwd: outDir, stdio: "inherit", shell: false });
|
|
19
|
+
if (install.status !== 0) {
|
|
20
|
+
return install.status ?? 1;
|
|
21
|
+
}
|
|
22
|
+
console.log("Starting Newt bot...");
|
|
23
|
+
const child = spawnSync("node", ["bot.js"], {
|
|
24
|
+
cwd: outDir,
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
env: { ...process.env, NEWT_SAFE_MODE: options.safe ? "1" : process.env.NEWT_SAFE_MODE ?? "" }
|
|
27
|
+
});
|
|
28
|
+
return child.status ?? 0;
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildCommand } from "./commands/build.js";
|
|
3
|
+
import { checkCommand } from "./commands/check.js";
|
|
4
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
5
|
+
import { newCommand } from "./commands/new.js";
|
|
6
|
+
import { runCommand } from "./commands/run.js";
|
|
7
|
+
const [, , command, ...args] = process.argv;
|
|
8
|
+
function help() {
|
|
9
|
+
console.log(`Newt CLI
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
newt check <file>
|
|
13
|
+
newt build <file> [--out <dir>]
|
|
14
|
+
newt run <file> [--safe]
|
|
15
|
+
newt new [name] [--template hello|welcome|points|blank]
|
|
16
|
+
newt deploy <file>
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
19
|
+
function optionValue(flag, fallback) {
|
|
20
|
+
const index = args.indexOf(flag);
|
|
21
|
+
return index >= 0 ? args[index + 1] : fallback;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
let exitCode = 0;
|
|
25
|
+
if (!command || command === "--help" || command === "-h") {
|
|
26
|
+
help();
|
|
27
|
+
}
|
|
28
|
+
else if (command === "check") {
|
|
29
|
+
exitCode = args[0] ? checkCommand(args[0]) : missingFile();
|
|
30
|
+
}
|
|
31
|
+
else if (command === "build") {
|
|
32
|
+
exitCode = args[0] ? buildCommand(args[0], { outDir: optionValue("--out") }) : missingFile();
|
|
33
|
+
}
|
|
34
|
+
else if (command === "run") {
|
|
35
|
+
exitCode = args[0] ? runCommand(args[0], { safe: args.includes("--safe") }) : missingFile();
|
|
36
|
+
}
|
|
37
|
+
else if (command === "new") {
|
|
38
|
+
exitCode = newCommand(args[0], optionValue("--template", "hello"));
|
|
39
|
+
}
|
|
40
|
+
else if (command === "deploy") {
|
|
41
|
+
exitCode = args[0] ? deployCommand(args[0]) : missingFile();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error(`Unknown command: ${command}`);
|
|
45
|
+
help();
|
|
46
|
+
exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
process.exitCode = exitCode;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
function missingFile() {
|
|
55
|
+
console.error("Please pass a .newt file.");
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type NewtError } from "@newt-dev/compiler";
|
|
2
|
+
export declare function readSource(file: string): {
|
|
3
|
+
filename: string;
|
|
4
|
+
source: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function printErrors(errors: NewtError[], source: string): void;
|
|
7
|
+
export declare function writeProject(outDir: string, botJs: string, packageJson: string): void;
|
|
8
|
+
export declare function writeTextFile(path: string, text: string): void;
|
package/dist/src/util.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { formatError } from "@newt-dev/compiler";
|
|
4
|
+
export function readSource(file) {
|
|
5
|
+
const filename = resolve(file);
|
|
6
|
+
if (!existsSync(filename)) {
|
|
7
|
+
throw new Error(`I could not find ${file}. Check the path and try again.`);
|
|
8
|
+
}
|
|
9
|
+
return { filename, source: readFileSync(filename, "utf8") };
|
|
10
|
+
}
|
|
11
|
+
export function printErrors(errors, source) {
|
|
12
|
+
for (const error of errors) {
|
|
13
|
+
console.error(formatError(error, source));
|
|
14
|
+
console.error("");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function writeProject(outDir, botJs, packageJson) {
|
|
18
|
+
const target = resolve(outDir);
|
|
19
|
+
mkdirSync(target, { recursive: true });
|
|
20
|
+
writeFileSync(resolve(target, "bot.js"), botJs);
|
|
21
|
+
writeFileSync(resolve(target, "package.json"), packageJson);
|
|
22
|
+
}
|
|
23
|
+
export function writeTextFile(path, text) {
|
|
24
|
+
mkdirSync(dirname(resolve(path)), { recursive: true });
|
|
25
|
+
writeFileSync(resolve(path), text);
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { buildCommand } from "../src/commands/build.js";
|
|
7
|
+
import { checkCommand } from "../src/commands/check.js";
|
|
8
|
+
test("check command accepts a valid file", () => {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "newt-cli-"));
|
|
10
|
+
try {
|
|
11
|
+
const file = join(dir, "bot.newt");
|
|
12
|
+
writeFileSync(file, `bot name "CliBot"\nbot prefix "!"\nbot token from env "DISCORD_TOKEN"\n\non command "hello":\n reply "Hi!"\n`);
|
|
13
|
+
assert.equal(checkCommand(file), 0);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
test("build command writes generated project", () => {
|
|
20
|
+
const dir = mkdtempSync(join(tmpdir(), "newt-cli-"));
|
|
21
|
+
try {
|
|
22
|
+
const file = join(dir, "bot.newt");
|
|
23
|
+
const out = join(dir, "dist");
|
|
24
|
+
writeFileSync(file, `bot name "CliBot"\nbot prefix "!"\nbot token from env "DISCORD_TOKEN"\n\non command "hello":\n reply "Hi!"\n`);
|
|
25
|
+
assert.equal(buildCommand(file, { outDir: out }), 0);
|
|
26
|
+
assert.match(readFileSync(join(out, "bot.js"), "utf8"), /client\.login/);
|
|
27
|
+
assert.match(readFileSync(join(out, "package.json"), "utf8"), /discord\.js/);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@newt-dev/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line tools for the Newt Discord bot DSL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"newt": "dist/src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"test": "node --test dist/test/*.test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"discord",
|
|
16
|
+
"bot",
|
|
17
|
+
"dsl",
|
|
18
|
+
"cli",
|
|
19
|
+
"newt"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/newt-dev-sudo/newt.git",
|
|
24
|
+
"directory": "packages/cli"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/newt-dev-sudo/newt#readme",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@newt-dev/compiler": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.12.0",
|
|
32
|
+
"typescript": "^5.5.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { relative, resolve } from "node:path";
|
|
2
|
+
import { compile } from "@newt-dev/compiler";
|
|
3
|
+
import { printErrors, readSource, writeProject } from "../util.js";
|
|
4
|
+
|
|
5
|
+
export interface BuildOptions {
|
|
6
|
+
outDir?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildCommand(file: string, options: BuildOptions = {}): number {
|
|
10
|
+
const { filename, source } = readSource(file);
|
|
11
|
+
const result = compile(source, filename);
|
|
12
|
+
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
printErrors(result.errors, source);
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const outDir = options.outDir ?? "dist";
|
|
19
|
+
writeProject(outDir, result.botJs, result.packageJson);
|
|
20
|
+
const display = relative(process.cwd(), resolve(outDir)) || ".";
|
|
21
|
+
console.log(`OK Built to ${display}`);
|
|
22
|
+
console.log(`Next: cd ${display} && npm install && DISCORD_TOKEN=... npm start`);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { compile } from "@newt-dev/compiler";
|
|
2
|
+
import { printErrors, readSource } from "../util.js";
|
|
3
|
+
|
|
4
|
+
export function checkCommand(file: string): number {
|
|
5
|
+
const { filename, source } = readSource(file);
|
|
6
|
+
const result = compile(source, filename);
|
|
7
|
+
|
|
8
|
+
if (!result.success) {
|
|
9
|
+
printErrors(result.errors, source);
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log(`OK ${file} - no errors found`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function deployCommand(_file: string): number {
|
|
2
|
+
console.error("newt deploy is not available yet. Closed beta requires Newt Cloud auth, token encryption, and container orchestration first.");
|
|
3
|
+
console.error("For now, use: newt build <file> --out dist");
|
|
4
|
+
return 2;
|
|
5
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { writeTextFile } from "../util.js";
|
|
4
|
+
|
|
5
|
+
const templates: Record<string, string> = {
|
|
6
|
+
hello: `bot name "HelloNewt"
|
|
7
|
+
bot prefix "!"
|
|
8
|
+
bot token from env "DISCORD_TOKEN"
|
|
9
|
+
|
|
10
|
+
on ready:
|
|
11
|
+
say "HelloNewt is online!" in channel "bot-status"
|
|
12
|
+
|
|
13
|
+
on command "hello":
|
|
14
|
+
reply "Hey there, {user.name}!"
|
|
15
|
+
`,
|
|
16
|
+
welcome: `bot name "WelcomeNewt"
|
|
17
|
+
bot prefix "!"
|
|
18
|
+
bot token from env "DISCORD_TOKEN"
|
|
19
|
+
|
|
20
|
+
on join:
|
|
21
|
+
give user role "Member"
|
|
22
|
+
say embed:
|
|
23
|
+
title "Welcome, {user.name}!"
|
|
24
|
+
description "Read #rules first, then say hello."
|
|
25
|
+
color #5865F2
|
|
26
|
+
`,
|
|
27
|
+
points: `bot name "PointsNewt"
|
|
28
|
+
bot prefix "!"
|
|
29
|
+
bot token from env "DISCORD_TOKEN"
|
|
30
|
+
|
|
31
|
+
on command "points":
|
|
32
|
+
let current = load user.id points or 0
|
|
33
|
+
store user.id points = current + 1
|
|
34
|
+
reply "You have {load user.id points} points."
|
|
35
|
+
`,
|
|
36
|
+
blank: `bot name "MyNewtBot"
|
|
37
|
+
bot prefix "!"
|
|
38
|
+
bot token from env "DISCORD_TOKEN"
|
|
39
|
+
`
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function newCommand(name = "mybot", template = "hello"): number {
|
|
43
|
+
const safeName = basename(name).replace(/[^A-Za-z0-9_-]/g, "-").replace(/\.newt$/i, "");
|
|
44
|
+
const filename = `${safeName || "mybot"}.newt`;
|
|
45
|
+
if (existsSync(filename)) {
|
|
46
|
+
console.error(`${filename} already exists. Pick another name or move the existing file.`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const source = templates[template] ?? templates.hello;
|
|
51
|
+
writeTextFile(filename, source);
|
|
52
|
+
console.log(`OK Created ${filename} - run: newt check ${filename}`);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { buildCommand } from "./build.js";
|
|
6
|
+
|
|
7
|
+
export interface RunOptions {
|
|
8
|
+
safe?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function runCommand(file: string, options: RunOptions = {}): number {
|
|
12
|
+
if (!process.env.DISCORD_TOKEN) {
|
|
13
|
+
console.error("DISCORD_TOKEN is not set. Newt bots read tokens from the environment by default.");
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const outDir = mkdtempSync(join(tmpdir(), "newt-run-"));
|
|
18
|
+
const built = buildCommand(file, { outDir });
|
|
19
|
+
if (built !== 0) {
|
|
20
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
21
|
+
return built;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("Installing generated bot dependencies...");
|
|
25
|
+
const install = spawnSync("npm.cmd", ["install", "--omit=dev"], { cwd: outDir, stdio: "inherit", shell: false });
|
|
26
|
+
if (install.status !== 0) {
|
|
27
|
+
return install.status ?? 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log("Starting Newt bot...");
|
|
31
|
+
const child = spawnSync("node", ["bot.js"], {
|
|
32
|
+
cwd: outDir,
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
env: { ...process.env, NEWT_SAFE_MODE: options.safe ? "1" : process.env.NEWT_SAFE_MODE ?? "" }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return child.status ?? 0;
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildCommand } from "./commands/build.js";
|
|
3
|
+
import { checkCommand } from "./commands/check.js";
|
|
4
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
5
|
+
import { newCommand } from "./commands/new.js";
|
|
6
|
+
import { runCommand } from "./commands/run.js";
|
|
7
|
+
|
|
8
|
+
const [, , command, ...args] = process.argv;
|
|
9
|
+
|
|
10
|
+
function help(): void {
|
|
11
|
+
console.log(`Newt CLI
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
newt check <file>
|
|
15
|
+
newt build <file> [--out <dir>]
|
|
16
|
+
newt run <file> [--safe]
|
|
17
|
+
newt new [name] [--template hello|welcome|points|blank]
|
|
18
|
+
newt deploy <file>
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function optionValue(flag: string, fallback?: string): string | undefined {
|
|
23
|
+
const index = args.indexOf(flag);
|
|
24
|
+
return index >= 0 ? args[index + 1] : fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
let exitCode = 0;
|
|
29
|
+
if (!command || command === "--help" || command === "-h") {
|
|
30
|
+
help();
|
|
31
|
+
} else if (command === "check") {
|
|
32
|
+
exitCode = args[0] ? checkCommand(args[0]) : missingFile();
|
|
33
|
+
} else if (command === "build") {
|
|
34
|
+
exitCode = args[0] ? buildCommand(args[0], { outDir: optionValue("--out") }) : missingFile();
|
|
35
|
+
} else if (command === "run") {
|
|
36
|
+
exitCode = args[0] ? runCommand(args[0], { safe: args.includes("--safe") }) : missingFile();
|
|
37
|
+
} else if (command === "new") {
|
|
38
|
+
exitCode = newCommand(args[0], optionValue("--template", "hello"));
|
|
39
|
+
} else if (command === "deploy") {
|
|
40
|
+
exitCode = args[0] ? deployCommand(args[0]) : missingFile();
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`Unknown command: ${command}`);
|
|
43
|
+
help();
|
|
44
|
+
exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
process.exitCode = exitCode;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function missingFile(): number {
|
|
53
|
+
console.error("Please pass a .newt file.");
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { formatError, type NewtError } from "@newt-dev/compiler";
|
|
4
|
+
|
|
5
|
+
export function readSource(file: string): { filename: string; source: string } {
|
|
6
|
+
const filename = resolve(file);
|
|
7
|
+
if (!existsSync(filename)) {
|
|
8
|
+
throw new Error(`I could not find ${file}. Check the path and try again.`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return { filename, source: readFileSync(filename, "utf8") };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function printErrors(errors: NewtError[], source: string): void {
|
|
15
|
+
for (const error of errors) {
|
|
16
|
+
console.error(formatError(error, source));
|
|
17
|
+
console.error("");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writeProject(outDir: string, botJs: string, packageJson: string): void {
|
|
22
|
+
const target = resolve(outDir);
|
|
23
|
+
mkdirSync(target, { recursive: true });
|
|
24
|
+
writeFileSync(resolve(target, "bot.js"), botJs);
|
|
25
|
+
writeFileSync(resolve(target, "package.json"), packageJson);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function writeTextFile(path: string, text: string): void {
|
|
29
|
+
mkdirSync(dirname(resolve(path)), { recursive: true });
|
|
30
|
+
writeFileSync(resolve(path), text);
|
|
31
|
+
}
|
package/test/cli.test.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { buildCommand } from "../src/commands/build.js";
|
|
7
|
+
import { checkCommand } from "../src/commands/check.js";
|
|
8
|
+
|
|
9
|
+
test("check command accepts a valid file", () => {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), "newt-cli-"));
|
|
11
|
+
try {
|
|
12
|
+
const file = join(dir, "bot.newt");
|
|
13
|
+
writeFileSync(file, `bot name "CliBot"\nbot prefix "!"\nbot token from env "DISCORD_TOKEN"\n\non command "hello":\n reply "Hi!"\n`);
|
|
14
|
+
assert.equal(checkCommand(file), 0);
|
|
15
|
+
} finally {
|
|
16
|
+
rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("build command writes generated project", () => {
|
|
21
|
+
const dir = mkdtempSync(join(tmpdir(), "newt-cli-"));
|
|
22
|
+
try {
|
|
23
|
+
const file = join(dir, "bot.newt");
|
|
24
|
+
const out = join(dir, "dist");
|
|
25
|
+
writeFileSync(file, `bot name "CliBot"\nbot prefix "!"\nbot token from env "DISCORD_TOKEN"\n\non command "hello":\n reply "Hi!"\n`);
|
|
26
|
+
assert.equal(buildCommand(file, { outDir: out }), 0);
|
|
27
|
+
assert.match(readFileSync(join(out, "bot.js"), "utf8"), /client\.login/);
|
|
28
|
+
assert.match(readFileSync(join(out, "package.json"), "utf8"), /discord\.js/);
|
|
29
|
+
} finally {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*.ts",
|
|
16
|
+
"test/**/*.ts"
|
|
17
|
+
]
|
|
18
|
+
}
|