@nectar-js/create 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/LICENSE +21 -0
- package/README.md +22 -0
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -0
- package/package.json +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tiago Mouta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @nectar-js/create
|
|
2
|
+
|
|
3
|
+
Scaffolds a new [Nectar](https://github.com/nectar-js/nectar) project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create @nectar-js
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Asks for a directory, TypeScript or JavaScript, and a package manager, then prints the steps to get the bot running.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm create @nectar-js [directory] [options]
|
|
13
|
+
|
|
14
|
+
--ts, --js Language. Asked when omitted.
|
|
15
|
+
--pm <name> Package manager: npm, pnpm, yarn, or bun. Detected from the one running this.
|
|
16
|
+
--yes, -y Take the defaults instead of asking.
|
|
17
|
+
--help, -h Show this help.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## License
|
|
21
|
+
|
|
22
|
+
MIT. See [LICENSE](https://github.com/nectar-js/nectar/blob/main/LICENSE).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { parseArgs, styleText } from "node:util";
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
//#region src/scaffold.ts
|
|
8
|
+
const PACKAGE_MANAGERS = [
|
|
9
|
+
"npm",
|
|
10
|
+
"pnpm",
|
|
11
|
+
"yarn",
|
|
12
|
+
"bun"
|
|
13
|
+
];
|
|
14
|
+
/** The package manager that launched us, from `npm_config_user_agent`. npm when unknown. */
|
|
15
|
+
function detectPackageManager(userAgent) {
|
|
16
|
+
const name = userAgent?.split("/")[0];
|
|
17
|
+
return PACKAGE_MANAGERS.includes(name) ? name : "npm";
|
|
18
|
+
}
|
|
19
|
+
function nextSteps(directory, { packageManager }, style = (_format, text) => text) {
|
|
20
|
+
const run = packageManager === "npm" ? "npm run" : packageManager;
|
|
21
|
+
const step = (command, note = "") => ` ${style("cyan", command)}${note === "" ? "" : ` ${style("dim", note)}`}`;
|
|
22
|
+
return [
|
|
23
|
+
style("bold", "Next:"),
|
|
24
|
+
step(`cd ${directory}`),
|
|
25
|
+
step(`${packageManager} install`),
|
|
26
|
+
step("cp .env.example .env", "then fill in DISCORD_TOKEN and DISCORD_APPLICATION_ID"),
|
|
27
|
+
` ${style("dim", "add your test server's ID to dev.guilds in the config")}`,
|
|
28
|
+
step(`${run} dev`),
|
|
29
|
+
"",
|
|
30
|
+
`${style("dim", "Tokens and IDs live in the Developer Portal:")} https://discord.com/developers/applications`
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
34
|
+
const DISCORD_JS = "^14.27.0";
|
|
35
|
+
/** Everything a new project needs, as file paths relative to the project root. */
|
|
36
|
+
function templateFiles(options) {
|
|
37
|
+
const ts = options.language === "ts";
|
|
38
|
+
const ext = ts ? "ts" : "js";
|
|
39
|
+
const files = {
|
|
40
|
+
"package.json": packageJson(options),
|
|
41
|
+
[`nectar.config.${ext}`]: config(),
|
|
42
|
+
".env.example": "DISCORD_TOKEN=\nDISCORD_APPLICATION_ID=\n",
|
|
43
|
+
".gitignore": "node_modules/\n.nectar/\n.env\n",
|
|
44
|
+
[`app/middleware.${ext}`]: middleware(ts),
|
|
45
|
+
[`app/commands/ping/command.${ext}`]: pingCommand(ts),
|
|
46
|
+
[`app/components/counter/[count]/button.${ext}`]: counterButton(ts),
|
|
47
|
+
[`app/events/clientReady/event.${ext}`]: readyEvent()
|
|
48
|
+
};
|
|
49
|
+
if (ts) files["tsconfig.json"] = tsconfig();
|
|
50
|
+
return files;
|
|
51
|
+
}
|
|
52
|
+
/** Writes the template into `dir`, which must not exist or be empty. Returns the written paths. */
|
|
53
|
+
function scaffold(dir, options) {
|
|
54
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) throw new Error(`${dir} already exists and is not empty.`);
|
|
55
|
+
const files = templateFiles(options);
|
|
56
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
57
|
+
const file = path.join(dir, ...relative.split("/"));
|
|
58
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
+
writeFileSync(file, content);
|
|
60
|
+
}
|
|
61
|
+
return Object.keys(files);
|
|
62
|
+
}
|
|
63
|
+
function packageJson({ name, language }) {
|
|
64
|
+
const scripts = {
|
|
65
|
+
dev: "nectar dev",
|
|
66
|
+
build: "nectar build",
|
|
67
|
+
start: "nectar start",
|
|
68
|
+
check: "nectar check",
|
|
69
|
+
sync: "nectar sync"
|
|
70
|
+
};
|
|
71
|
+
if (language === "ts") scripts.typecheck = "nectar build && tsc --noEmit";
|
|
72
|
+
const pkg = {
|
|
73
|
+
name,
|
|
74
|
+
private: true,
|
|
75
|
+
type: "module",
|
|
76
|
+
engines: { node: ">=22.18" },
|
|
77
|
+
scripts,
|
|
78
|
+
dependencies: {
|
|
79
|
+
"@nectar-js/nectar": `^${version}`,
|
|
80
|
+
"discord.js": DISCORD_JS
|
|
81
|
+
},
|
|
82
|
+
...language === "ts" ? { devDependencies: {
|
|
83
|
+
"@types/node": "^22.20.1",
|
|
84
|
+
typescript: "^5.9.0"
|
|
85
|
+
} } : {}
|
|
86
|
+
};
|
|
87
|
+
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
88
|
+
}
|
|
89
|
+
function config() {
|
|
90
|
+
return `import { defineConfig } from "@nectar-js/nectar";
|
|
91
|
+
|
|
92
|
+
export default defineConfig({
|
|
93
|
+
token: process.env.DISCORD_TOKEN,
|
|
94
|
+
applicationId: process.env.DISCORD_APPLICATION_ID,
|
|
95
|
+
intents: ["Guilds"],
|
|
96
|
+
dev: {
|
|
97
|
+
// Commands register here instantly while you develop. Paste your test server's ID,
|
|
98
|
+
// or read it from .env the same way as the token above.
|
|
99
|
+
guilds: [],
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
function tsconfig() {
|
|
105
|
+
return `${JSON.stringify({
|
|
106
|
+
compilerOptions: {
|
|
107
|
+
target: "ES2023",
|
|
108
|
+
module: "NodeNext",
|
|
109
|
+
moduleResolution: "NodeNext",
|
|
110
|
+
lib: ["ES2023"],
|
|
111
|
+
types: ["node"],
|
|
112
|
+
strict: true,
|
|
113
|
+
noEmit: true,
|
|
114
|
+
allowImportingTsExtensions: true,
|
|
115
|
+
skipLibCheck: true,
|
|
116
|
+
verbatimModuleSyntax: true
|
|
117
|
+
},
|
|
118
|
+
include: [
|
|
119
|
+
"app",
|
|
120
|
+
"nectar.config.ts",
|
|
121
|
+
".nectar/types.d.ts"
|
|
122
|
+
]
|
|
123
|
+
}, null, 2)}\n`;
|
|
124
|
+
}
|
|
125
|
+
function middleware(ts) {
|
|
126
|
+
return `import { defineMiddleware } from "@nectar-js/nectar";
|
|
127
|
+
|
|
128
|
+
// Runs before every interaction. Whatever you pass to next() is on ctx downstream${ts ? ", typed" : ""}.
|
|
129
|
+
export default defineMiddleware(async (_ctx, next) => {
|
|
130
|
+
return next({ startedAt: Date.now() });
|
|
131
|
+
});
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
function pingCommand(ts) {
|
|
135
|
+
return `${ts ? `import { type CommandMeta, customId, defineCommand } from "@nectar-js/nectar";` : `import { customId, defineCommand } from "@nectar-js/nectar";`}
|
|
136
|
+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
|
137
|
+
|
|
138
|
+
${ts ? `export const meta: CommandMeta = {` : `/** @type {import("@nectar-js/nectar").CommandMeta} */
|
|
139
|
+
export const meta = {`}
|
|
140
|
+
description: "Check that the bot is alive",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default defineCommand("ping", async (ctx) => {
|
|
144
|
+
const button = new ButtonBuilder()
|
|
145
|
+
.setCustomId(customId("counter/[count]", { count: "0" }))
|
|
146
|
+
.setLabel("Clicked 0 times")
|
|
147
|
+
.setStyle(ButtonStyle.Primary);
|
|
148
|
+
await ctx.interaction.reply({
|
|
149
|
+
content: \`Pong in \${Date.now() - ctx.startedAt}ms\`,
|
|
150
|
+
components: [new ActionRowBuilder${ts ? "<ButtonBuilder>" : ""}().addComponents(button)],
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
function counterButton(ts) {
|
|
156
|
+
return `import { customId, defineComponent } from "@nectar-js/nectar";
|
|
157
|
+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
|
158
|
+
|
|
159
|
+
// The directory name [count] makes ctx.params.count a string decoded from the custom ID.
|
|
160
|
+
export default defineComponent("counter/[count]", async (ctx) => {
|
|
161
|
+
const count = Number(ctx.params.count) + 1;
|
|
162
|
+
const button = new ButtonBuilder()
|
|
163
|
+
.setCustomId(customId("counter/[count]", { count: String(count) }))
|
|
164
|
+
.setLabel(\`Clicked \${count} time\${count === 1 ? "" : "s"}\`)
|
|
165
|
+
.setStyle(ButtonStyle.Primary);
|
|
166
|
+
await ctx.interaction.update({
|
|
167
|
+
components: [new ActionRowBuilder${ts ? "<ButtonBuilder>" : ""}().addComponents(button)],
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
function readyEvent() {
|
|
173
|
+
return `import { defineEvent } from "@nectar-js/nectar";
|
|
174
|
+
|
|
175
|
+
export default defineEvent("clientReady", async (client) => {
|
|
176
|
+
console.log(\`Logged in as \${client.user.tag}\`);
|
|
177
|
+
});
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/index.ts
|
|
182
|
+
const USAGE = `Usage: npm create @nectar-js [directory] [options]
|
|
183
|
+
|
|
184
|
+
Options:
|
|
185
|
+
--ts, --js Language. Asked when omitted.
|
|
186
|
+
--pm <name> Package manager: npm, pnpm, yarn, or bun. Detected from the one running this.
|
|
187
|
+
--yes, -y Take the defaults instead of asking.
|
|
188
|
+
--help, -h Show this help.`;
|
|
189
|
+
const colors = process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
190
|
+
const style = (format, text) => colors ? styleText(format, text) : text;
|
|
191
|
+
async function main(argv) {
|
|
192
|
+
let values;
|
|
193
|
+
let positionals;
|
|
194
|
+
try {
|
|
195
|
+
({values, positionals} = parseArgs({
|
|
196
|
+
args: argv,
|
|
197
|
+
options: {
|
|
198
|
+
ts: { type: "boolean" },
|
|
199
|
+
js: { type: "boolean" },
|
|
200
|
+
pm: { type: "string" },
|
|
201
|
+
yes: {
|
|
202
|
+
type: "boolean",
|
|
203
|
+
short: "y"
|
|
204
|
+
},
|
|
205
|
+
help: {
|
|
206
|
+
type: "boolean",
|
|
207
|
+
short: "h"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
allowPositionals: true
|
|
211
|
+
}));
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
214
|
+
console.error(USAGE);
|
|
215
|
+
return 2;
|
|
216
|
+
}
|
|
217
|
+
if (values.help === true) {
|
|
218
|
+
console.log(USAGE);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
if (values.ts === true && values.js === true) {
|
|
222
|
+
console.error("Pass --ts or --js, not both.");
|
|
223
|
+
return 2;
|
|
224
|
+
}
|
|
225
|
+
if (values.pm !== void 0 && !PACKAGE_MANAGERS.includes(values.pm)) {
|
|
226
|
+
console.error(`Unknown package manager "${values.pm}". Use ${PACKAGE_MANAGERS.join(", ")}.`);
|
|
227
|
+
return 2;
|
|
228
|
+
}
|
|
229
|
+
const rl = values.yes !== true && process.stdin.isTTY === true ? createInterface({
|
|
230
|
+
input: process.stdin,
|
|
231
|
+
output: process.stdout
|
|
232
|
+
}) : null;
|
|
233
|
+
const ask = async (question, fallback) => {
|
|
234
|
+
if (rl === null) return fallback;
|
|
235
|
+
const answer = (await rl.question(`${question} (${fallback}) `)).trim();
|
|
236
|
+
return answer === "" ? fallback : answer;
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
const directory = positionals[0] ?? await ask("Project directory:", "my-bot");
|
|
240
|
+
const name = path.basename(path.resolve(directory));
|
|
241
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(name)) {
|
|
242
|
+
console.error(`"${name}" is not a valid package name. Use lowercase letters, digits, ".", "_", and "-".`);
|
|
243
|
+
return 2;
|
|
244
|
+
}
|
|
245
|
+
let language = values.js === true ? "js" : "ts";
|
|
246
|
+
if (values.ts !== true && values.js !== true) {
|
|
247
|
+
const answer = await ask("TypeScript or JavaScript? [ts/js]", "ts");
|
|
248
|
+
if (answer !== "ts" && answer !== "js") {
|
|
249
|
+
console.error(`Answer ts or js, not "${answer}".`);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
language = answer;
|
|
253
|
+
}
|
|
254
|
+
const detected = detectPackageManager(process.env.npm_config_user_agent);
|
|
255
|
+
let packageManager = values.pm ?? detected;
|
|
256
|
+
if (values.pm === void 0) {
|
|
257
|
+
const answer = await ask(`Package manager? [${PACKAGE_MANAGERS.join("/")}]`, detected);
|
|
258
|
+
if (!PACKAGE_MANAGERS.includes(answer)) {
|
|
259
|
+
console.error(`Unknown package manager "${answer}".`);
|
|
260
|
+
return 2;
|
|
261
|
+
}
|
|
262
|
+
packageManager = answer;
|
|
263
|
+
}
|
|
264
|
+
const options = {
|
|
265
|
+
name,
|
|
266
|
+
language,
|
|
267
|
+
packageManager
|
|
268
|
+
};
|
|
269
|
+
const target = path.resolve(directory);
|
|
270
|
+
let files;
|
|
271
|
+
try {
|
|
272
|
+
files = scaffold(target, options);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
console.log(`\n${style("green", "✔")} Created ${style("bold", name)} with ${files.length} files.\n`);
|
|
278
|
+
console.log(nextSteps(directory, options, style));
|
|
279
|
+
return 0;
|
|
280
|
+
} finally {
|
|
281
|
+
rl?.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
285
|
+
//#endregion
|
|
286
|
+
export {};
|
|
287
|
+
|
|
288
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\n\nexport type Language = \"ts\" | \"js\";\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport interface ScaffoldOptions {\n name: string;\n language: Language;\n packageManager: PackageManager;\n}\n\nexport const PACKAGE_MANAGERS: PackageManager[] = [\"npm\", \"pnpm\", \"yarn\", \"bun\"];\n\n/** The package manager that launched us, from `npm_config_user_agent`. npm when unknown. */\nexport function detectPackageManager(userAgent: string | undefined): PackageManager {\n const name = userAgent?.split(\"/\")[0];\n return PACKAGE_MANAGERS.includes(name as PackageManager) ? (name as PackageManager) : \"npm\";\n}\n\ntype Style = (format: \"bold\" | \"green\" | \"dim\" | \"cyan\", text: string) => string;\n\nexport function nextSteps(\n directory: string,\n { packageManager }: ScaffoldOptions,\n style: Style = (_format, text) => text,\n): string {\n const run = packageManager === \"npm\" ? \"npm run\" : packageManager;\n const step = (command: string, note = \"\") =>\n ` ${style(\"cyan\", command)}${note === \"\" ? \"\" : ` ${style(\"dim\", note)}`}`;\n return [\n style(\"bold\", \"Next:\"),\n step(`cd ${directory}`),\n step(`${packageManager} install`),\n step(\"cp .env.example .env\", \"then fill in DISCORD_TOKEN and DISCORD_APPLICATION_ID\"),\n ` ${style(\"dim\", \"add your test server's ID to dev.guilds in the config\")}`,\n step(`${run} dev`),\n \"\",\n `${style(\"dim\", \"Tokens and IDs live in the Developer Portal:\")} https://discord.com/developers/applications`,\n ].join(\"\\n\");\n}\n\nconst { version } = createRequire(import.meta.url)(\"../package.json\") as { version: string };\nconst DISCORD_JS = \"^14.27.0\";\n\n/** Everything a new project needs, as file paths relative to the project root. */\nexport function templateFiles(options: ScaffoldOptions): Record<string, string> {\n const ts = options.language === \"ts\";\n const ext = ts ? \"ts\" : \"js\";\n const files: Record<string, string> = {\n \"package.json\": packageJson(options),\n [`nectar.config.${ext}`]: config(),\n \".env.example\": \"DISCORD_TOKEN=\\nDISCORD_APPLICATION_ID=\\n\",\n \".gitignore\": \"node_modules/\\n.nectar/\\n.env\\n\",\n [`app/middleware.${ext}`]: middleware(ts),\n [`app/commands/ping/command.${ext}`]: pingCommand(ts),\n [`app/components/counter/[count]/button.${ext}`]: counterButton(ts),\n [`app/events/clientReady/event.${ext}`]: readyEvent(),\n };\n if (ts) files[\"tsconfig.json\"] = tsconfig();\n return files;\n}\n\n/** Writes the template into `dir`, which must not exist or be empty. Returns the written paths. */\nexport function scaffold(dir: string, options: ScaffoldOptions): string[] {\n if (existsSync(dir) && readdirSync(dir).length > 0) {\n throw new Error(`${dir} already exists and is not empty.`);\n }\n const files = templateFiles(options);\n for (const [relative, content] of Object.entries(files)) {\n const file = path.join(dir, ...relative.split(\"/\"));\n mkdirSync(path.dirname(file), { recursive: true });\n writeFileSync(file, content);\n }\n return Object.keys(files);\n}\n\nfunction packageJson({ name, language }: ScaffoldOptions): string {\n const scripts: Record<string, string> = {\n dev: \"nectar dev\",\n build: \"nectar build\",\n start: \"nectar start\",\n check: \"nectar check\",\n sync: \"nectar sync\",\n };\n if (language === \"ts\") scripts.typecheck = \"nectar build && tsc --noEmit\";\n const pkg = {\n name,\n private: true,\n type: \"module\",\n engines: { node: \">=22.18\" },\n scripts,\n dependencies: { \"@nectar-js/nectar\": `^${version}`, \"discord.js\": DISCORD_JS },\n ...(language === \"ts\"\n ? { devDependencies: { \"@types/node\": \"^22.20.1\", typescript: \"^5.9.0\" } }\n : {}),\n };\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n\nfunction config(): string {\n return `import { defineConfig } from \"@nectar-js/nectar\";\n\nexport default defineConfig({\n token: process.env.DISCORD_TOKEN,\n applicationId: process.env.DISCORD_APPLICATION_ID,\n intents: [\"Guilds\"],\n dev: {\n // Commands register here instantly while you develop. Paste your test server's ID,\n // or read it from .env the same way as the token above.\n guilds: [],\n },\n});\n`;\n}\n\nfunction tsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2023\",\n module: \"NodeNext\",\n moduleResolution: \"NodeNext\",\n lib: [\"ES2023\"],\n types: [\"node\"],\n strict: true,\n noEmit: true,\n allowImportingTsExtensions: true,\n skipLibCheck: true,\n verbatimModuleSyntax: true,\n },\n include: [\"app\", \"nectar.config.ts\", \".nectar/types.d.ts\"],\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction middleware(ts: boolean): string {\n return `import { defineMiddleware } from \"@nectar-js/nectar\";\n\n// Runs before every interaction. Whatever you pass to next() is on ctx downstream${ts ? \", typed\" : \"\"}.\nexport default defineMiddleware(async (_ctx, next) => {\n return next({ startedAt: Date.now() });\n});\n`;\n}\n\nfunction pingCommand(ts: boolean): string {\n const meta = ts\n ? `export const meta: CommandMeta = {`\n : `/** @type {import(\"@nectar-js/nectar\").CommandMeta} */\nexport const meta = {`;\n const imports = ts\n ? `import { type CommandMeta, customId, defineCommand } from \"@nectar-js/nectar\";`\n : `import { customId, defineCommand } from \"@nectar-js/nectar\";`;\n return `${imports}\nimport { ActionRowBuilder, ButtonBuilder, ButtonStyle } from \"discord.js\";\n\n${meta}\n description: \"Check that the bot is alive\",\n};\n\nexport default defineCommand(\"ping\", async (ctx) => {\n const button = new ButtonBuilder()\n .setCustomId(customId(\"counter/[count]\", { count: \"0\" }))\n .setLabel(\"Clicked 0 times\")\n .setStyle(ButtonStyle.Primary);\n await ctx.interaction.reply({\n content: \\`Pong in \\${Date.now() - ctx.startedAt}ms\\`,\n components: [new ActionRowBuilder${ts ? \"<ButtonBuilder>\" : \"\"}().addComponents(button)],\n });\n});\n`;\n}\n\nfunction counterButton(ts: boolean): string {\n return `import { customId, defineComponent } from \"@nectar-js/nectar\";\nimport { ActionRowBuilder, ButtonBuilder, ButtonStyle } from \"discord.js\";\n\n// The directory name [count] makes ctx.params.count a string decoded from the custom ID.\nexport default defineComponent(\"counter/[count]\", async (ctx) => {\n const count = Number(ctx.params.count) + 1;\n const button = new ButtonBuilder()\n .setCustomId(customId(\"counter/[count]\", { count: String(count) }))\n .setLabel(\\`Clicked \\${count} time\\${count === 1 ? \"\" : \"s\"}\\`)\n .setStyle(ButtonStyle.Primary);\n await ctx.interaction.update({\n components: [new ActionRowBuilder${ts ? \"<ButtonBuilder>\" : \"\"}().addComponents(button)],\n });\n});\n`;\n}\n\nfunction readyEvent(): string {\n return `import { defineEvent } from \"@nectar-js/nectar\";\n\nexport default defineEvent(\"clientReady\", async (client) => {\n console.log(\\`Logged in as \\${client.user.tag}\\`);\n});\n`;\n}\n","#!/usr/bin/env node\nimport path from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parseArgs, styleText } from \"node:util\";\nimport {\n detectPackageManager,\n type Language,\n nextSteps,\n PACKAGE_MANAGERS,\n type PackageManager,\n type ScaffoldOptions,\n scaffold,\n} from \"./scaffold.js\";\n\nconst USAGE = `Usage: npm create @nectar-js [directory] [options]\n\nOptions:\n --ts, --js Language. Asked when omitted.\n --pm <name> Package manager: npm, pnpm, yarn, or bun. Detected from the one running this.\n --yes, -y Take the defaults instead of asking.\n --help, -h Show this help.`;\n\nconst colors = process.stdout.isTTY === true && !process.env.NO_COLOR;\nconst style = (format: \"bold\" | \"green\" | \"dim\" | \"cyan\", text: string): string =>\n colors ? styleText(format, text) : text;\n\nasync function main(argv: string[]): Promise<number> {\n let values: Record<string, string | boolean | undefined>;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: {\n ts: { type: \"boolean\" },\n js: { type: \"boolean\" },\n pm: { type: \"string\" },\n yes: { type: \"boolean\", short: \"y\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n allowPositionals: true,\n }));\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n console.error(USAGE);\n return 2;\n }\n if (values.help === true) {\n console.log(USAGE);\n return 0;\n }\n if (values.ts === true && values.js === true) {\n console.error(\"Pass --ts or --js, not both.\");\n return 2;\n }\n if (values.pm !== undefined && !PACKAGE_MANAGERS.includes(values.pm as PackageManager)) {\n console.error(`Unknown package manager \"${values.pm}\". Use ${PACKAGE_MANAGERS.join(\", \")}.`);\n return 2;\n }\n\n const interactive = values.yes !== true && process.stdin.isTTY === true;\n const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : null;\n const ask = async (question: string, fallback: string): Promise<string> => {\n if (rl === null) return fallback;\n const answer = (await rl.question(`${question} (${fallback}) `)).trim();\n return answer === \"\" ? fallback : answer;\n };\n\n try {\n const directory = positionals[0] ?? (await ask(\"Project directory:\", \"my-bot\"));\n const name = path.basename(path.resolve(directory));\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(name)) {\n console.error(\n `\"${name}\" is not a valid package name. Use lowercase letters, digits, \".\", \"_\", and \"-\".`,\n );\n return 2;\n }\n\n let language: Language = values.js === true ? \"js\" : \"ts\";\n if (values.ts !== true && values.js !== true) {\n const answer = await ask(\"TypeScript or JavaScript? [ts/js]\", \"ts\");\n if (answer !== \"ts\" && answer !== \"js\") {\n console.error(`Answer ts or js, not \"${answer}\".`);\n return 2;\n }\n language = answer;\n }\n\n const detected = detectPackageManager(process.env.npm_config_user_agent);\n let packageManager = (values.pm as PackageManager | undefined) ?? detected;\n if (values.pm === undefined) {\n const answer = await ask(`Package manager? [${PACKAGE_MANAGERS.join(\"/\")}]`, detected);\n if (!PACKAGE_MANAGERS.includes(answer as PackageManager)) {\n console.error(`Unknown package manager \"${answer}\".`);\n return 2;\n }\n packageManager = answer as PackageManager;\n }\n\n const options: ScaffoldOptions = { name, language, packageManager };\n const target = path.resolve(directory);\n let files: string[];\n try {\n files = scaffold(target, options);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n return 1;\n }\n\n console.log(\n `\\n${style(\"green\", \"✔\")} Created ${style(\"bold\", name)} with ${files.length} files.\\n`,\n );\n console.log(nextSteps(directory, options, style));\n return 0;\n } finally {\n rl?.close();\n }\n}\n\nprocess.exitCode = await main(process.argv.slice(2));\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nectar-js/create",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new Nectar project.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.18"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-nectar": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsdown",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
}
|
|
20
|
+
}
|