create-napukettoqq 0.2.0 → 0.2.2
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/index.mjs +8 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/napuketto.toml.tmpl +35 -19
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import path, { relative } from "node:path";
|
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { cancel, confirm, intro, isCancel, log, outro, spinner, text } from "@clack/prompts";
|
|
7
7
|
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
8
|
-
import { homedir } from "node:os";
|
|
9
8
|
//#region src/scaffold.ts
|
|
10
9
|
/**
|
|
11
10
|
* create-napukettoqq 脚手架核心:模板渲染 + 目标目录创建 + 文件写入。
|
|
@@ -110,8 +109,7 @@ async function scaffoldProject(dirName, overwrite = false) {
|
|
|
110
109
|
if (status === "nonempty") await emptyDir(dir);
|
|
111
110
|
const vars = {
|
|
112
111
|
packageName,
|
|
113
|
-
cliVersion: cliVersionRange()
|
|
114
|
-
dataDir: JSON.stringify(path.join(homedir(), ".napuketto"))
|
|
112
|
+
cliVersion: cliVersionRange()
|
|
115
113
|
};
|
|
116
114
|
const files = ["package.json", "napuketto.toml"];
|
|
117
115
|
await mkdir(dir, { recursive: true });
|
|
@@ -219,14 +217,19 @@ async function askStartNow(yes) {
|
|
|
219
217
|
}
|
|
220
218
|
return ok;
|
|
221
219
|
}
|
|
220
|
+
/** 拼命令行时最小转义:含空白/引号则加双引号包裹(参数均为内部字面量,仅兜底)。 */
|
|
221
|
+
function quoteShellArg(arg) {
|
|
222
|
+
return /[\s"]/.test(arg) ? `"${arg.replace(/"/g, "\\\"")}"` : arg;
|
|
223
|
+
}
|
|
222
224
|
/** 前台执行命令(stdio 继承,用户可见进度/交互),返回退出码。 */
|
|
223
225
|
function runInteractive(bin, args, cwd) {
|
|
224
226
|
return new Promise((resolve) => {
|
|
225
|
-
|
|
227
|
+
const shell = process.platform === "win32";
|
|
228
|
+
spawn(shell ? [bin, ...args.map(quoteShellArg)].join(" ") : bin, shell ? [] : args, {
|
|
226
229
|
cwd,
|
|
227
230
|
stdio: "inherit",
|
|
228
231
|
windowsHide: false,
|
|
229
|
-
shell
|
|
232
|
+
shell
|
|
230
233
|
}).on("exit", (code) => {
|
|
231
234
|
resolve(code ?? 0);
|
|
232
235
|
});
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["/**\n * create-napukettoqq 脚手架核心:模板渲染 + 目标目录创建 + 文件写入。\n *\n * 模板为独立文件(templates/ 目录,运行时读取),不硬编码在源码里;\n * 需要插值的占位符用 {{key}} 标记(见 renderTemplate)。\n * 依赖极轻(node:fs/promises / node:os / node:path / node:process)。\n * 生成的用户项目依赖发布版的 @napuketto/cli,用户 install 后经 cli → loader\n * 自建宿主启动。\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\n/** 模板目录(相对本文件:src 与 dist 都在包根下,../templates 均指向包根/templates)。 */\nconst TEMPLATES = new URL(\"../templates/\", import.meta.url);\n\n/**\n * 生成的用户项目所依赖的 @napuketto/cli 版本范围。\n * 运行时从脚手架自身 package.json 读取(dependencies[\"@napuketto/cli\"])——\n * 发版时 changesets 升版本 + pnpm publish 把 workspace:* 替换为实际版本,\n * 模板版本自动跟随,无需手动同步(比硬编码常量可靠)。\n */\nexport function cliVersionRange(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { dependencies?: Record<string, string> };\n const range = ownPkg.dependencies?.[\"@napuketto/cli\"];\n // workspace:* 仅在本地 monorepo 直跑脚手架时出现(发布后 pnpm 替换为实际版本),\n // 此时兜底默认范围,避免生成的用户项目 install 失败\n if (range !== undefined && range !== \"workspace:*\") {\n return range;\n }\n } catch {\n // package.json 缺失(异常环境)→ 兜底默认范围\n }\n return \"^0.0.1\";\n}\n\n/** 默认部署文件夹名(用户交互回车缺省值)。 */\nexport const DEFAULT_PROJECT_NAME = \"NapukettoQQ\";\n\n/** 用户可用的包管理器(npm_config_user_agent 检测)。 */\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\n/**\n * 由 npm_config_user_agent 检测调用方包管理器(yarn create / npm create /\n * pnpm create 都会注入该变量)。兜底 pnpm(项目生态,或直接 node 运行)。\n */\nexport function detectPackageManager(): PackageManager {\n // noPropertyAccessFromIndexSignature:process.env 是索引签名,必须括号访问\n const ua = process.env[\"npm_config_user_agent\"] ?? \"\";\n if (ua.startsWith(\"yarn/\")) {\n return \"yarn\";\n }\n if (ua.startsWith(\"npm/\")) {\n return \"npm\";\n }\n return \"pnpm\";\n}\n\n/** Windows 下包管理器 bin 需带 .cmd 后缀(CreateProcess 不认无扩展名 shim)。 */\nexport function pmBin(pm: PackageManager): string {\n return process.platform === \"win32\" ? `${pm}.cmd` : pm;\n}\n\n/** Windows 路径非法字符(项目文件夹名校验用)。 */\nconst ILLEGAL_NAME = /[\\\\/:*?\"<>|]/;\n\n/** 脚手架生成结果。 */\nexport interface ScaffoldResult {\n /** 目标目录绝对路径。 */\n dir: string;\n /** 派生出的 npm 包名(小写、空格 → -)。 */\n packageName: string;\n}\n\n/** 校验文件夹名:非空、非 . / ..、不含 Windows 非法字符。 */\nexport function validateProjectName(raw: string): string {\n const name = raw.trim();\n if (name === \"\" || name === \".\" || name === \"..\") {\n throw new Error(\"文件夹名不能为空或为 . / ..\");\n }\n if (ILLEGAL_NAME.test(name)) {\n throw new Error(`文件夹名含非法字符:${name}(不允许 \\\\ / : * ? \" < > |)`);\n }\n return name;\n}\n\n/** 由文件夹名派生 npm 包名(小写、空格 → -)。 */\nexport function derivePackageName(dirName: string): string {\n return dirName.toLowerCase().replace(/\\s+/g, \"-\");\n}\n\n/** 解析目标目录绝对路径(校验名称后,基于 cwd)。 */\nexport function resolveTargetDir(dirName: string): string {\n return path.resolve(process.cwd(), validateProjectName(dirName));\n}\n\n/** 目标目录状态:不存在 / 空 / 非空。 */\nexport type DirStatus = \"missing\" | \"empty\" | \"nonempty\";\n\n/** 检测目标目录状态(供交互层决定是否询问清空)。 */\nexport async function checkDirStatus(dir: string): Promise<DirStatus> {\n let entries: string[] = [];\n try {\n entries = await readdir(dir);\n } catch {\n return \"missing\";\n }\n return entries.length === 0 ? \"empty\" : \"nonempty\";\n}\n\n/** 清空目录内容(保留目录本身)。 */\nexport async function emptyDir(dir: string): Promise<void> {\n for (const entry of await readdir(dir)) {\n await rm(path.join(dir, entry), { recursive: true, force: true });\n }\n}\n\n/** 读取模板文件(templates/ 目录,UTF-8)。 */\nfunction readTemplate(file: string): Promise<string> {\n return readFile(new URL(file, TEMPLATES), \"utf8\");\n}\n\n/**\n * 占位符渲染:把 {{key}} 替换为 vars[key]。\n * 模板中出现未提供的占位符 → 抛错(防漏插值写出半成品)。\n */\nfunction renderTemplate(content: string, vars: Record<string, string>): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value = vars[key];\n if (value === undefined) {\n throw new Error(`模板占位符未提供:{{${key}}}(模板 ${match} 无法渲染)`);\n }\n return value;\n });\n}\n\n/**\n * 生成 NapukettoQQ 项目骨架到当前目录下的 <dirName>/。\n * 目标目录已存在且非空:overwrite=false → 抛错;overwrite=true → 清空后生成。\n * 模板与写入目标一一对应(同文件名)。\n */\nexport async function scaffoldProject(dirName: string, overwrite = false): Promise<ScaffoldResult> {\n const name = validateProjectName(dirName);\n const packageName = derivePackageName(name);\n const dir = resolveTargetDir(name);\n\n const status = await checkDirStatus(dir);\n if (status === \"nonempty\" && !overwrite) {\n throw new Error(`目录已存在且非空:${dir}\\n请换一个文件夹名,或先清空该目录。`);\n }\n if (status === \"nonempty\") {\n await emptyDir(dir);\n }\n\n // 渲染变量:dataDir 用 JSON.stringify 转义反斜杠 → TOML 基本字符串合法写法\n const vars: Record<string, string> = {\n packageName,\n cliVersion: cliVersionRange(),\n dataDir: JSON.stringify(path.join(homedir(), \".napuketto\")),\n };\n // 用户项目是「运行壳」:不开发代码,只需 package.json(依赖 + start)+ napuketto.toml(配置)\n // 模板文件为 templates/<name>.tmpl(.tmpl 后缀避免 TOML/JSON 语言服务把占位符当非法语法)\n const files = [\"package.json\", \"napuketto.toml\"];\n\n await mkdir(dir, { recursive: true });\n for (const file of files) {\n const rendered = renderTemplate(await readTemplate(`${file}.tmpl`), vars);\n await writeFile(path.join(dir, file), rendered, \"utf8\");\n }\n\n return { dir, packageName };\n}\n","#!/usr/bin/env node\n\n/**\n * create-napukettoqq 入口(2026-08-07 美化 v3,交互层用 @clack/prompts——\n * 现代脚手架交互标准,create-vue / create-turbo 同款)。\n *\n * 用法:\n * create-napukettoqq # intro + 交互问部署文件夹名(默认 NapukettoQQ)\n * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过命名交互\n * create-napukettoqq my-bot -f # 目标目录非空时强制清空覆盖\n * create-napukettoqq -y # 全默认(命名用默认值、启动询问用默认 Y)\n * create-napukettoqq -h # 打印帮助\n *\n * 流程:intro → 问文件夹名 → 目标目录准备(非空需确认清空)→ spinner 生成骨架\n * (package.json / napuketto.toml / readme.md / .gitignore)→ 用调用方包管理器\n * 自动 install → 询问是否现在启动(默认 Y)→ 前台运行 → outro 收尾。\n */\n\nimport { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, confirm, intro, isCancel, log, outro, spinner, text } from \"@clack/prompts\";\nimport {\n checkDirStatus,\n DEFAULT_PROJECT_NAME,\n detectPackageManager,\n pmBin,\n resolveTargetDir,\n scaffoldProject,\n validateProjectName,\n} from \"./scaffold.js\";\n\n/** CLI 参数解析结果。 */\ninterface CliOptions {\n /** 位置参数:部署文件夹名(可选)。 */\n name?: string;\n /** -f/--forced:目标目录非空时强制清空覆盖。 */\n forced: boolean;\n /** -y/--yes:跳过所有交互提示(使用默认值)。 */\n yes: boolean;\n /** -h/--help:显示帮助。 */\n help: boolean;\n}\n\n/** 帮助文本(-h/--help)。 */\nconst HELP = ` 用法: create-napukettoqq [name] [选项]\n\n 选项:\n -f, --forced 目标目录非空时强制清空并覆盖\n -y, --yes 跳过所有交互提示(使用默认值)\n -h, --help 显示本帮助\n`;\n\n/** 解析命令行参数(flag 仅三个,手写保持自包含,不引 yargs-parser)。 */\nfunction parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = { forced: false, yes: false, help: false };\n for (const arg of argv) {\n if (arg === \"-f\" || arg === \"--forced\") {\n opts.forced = true;\n } else if (arg === \"-y\" || arg === \"--yes\") {\n opts.yes = true;\n } else if (arg === \"-h\" || arg === \"--help\") {\n opts.help = true;\n } else if (arg.startsWith(\"-\")) {\n throw new Error(`未知参数:${arg}`);\n } else if (opts.name === undefined) {\n opts.name = arg;\n } else {\n throw new Error(`多余的位置参数:${arg}`);\n }\n }\n return opts;\n}\n\n/** 读取脚手架自身版本号(intro 显示;dist 场景 import.meta.url 指向包内)。 */\nfunction readVersion(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { version?: string };\n return ownPkg.version ?? \"0.0.0\";\n } catch {\n // package.json 缺失(异常环境)→ 兜底\n return \"0.0.0\";\n }\n}\n\n/** 交互询问部署文件夹名(回车取默认值;-y 直接返回默认值)。 */\nasync function askProjectName(yes: boolean): Promise<string> {\n if (yes) {\n return DEFAULT_PROJECT_NAME;\n }\n const name = await text({\n message: \"部署文件夹名\",\n placeholder: DEFAULT_PROJECT_NAME,\n initialValue: DEFAULT_PROJECT_NAME,\n validate: (input: string | undefined) => {\n try {\n validateProjectName(input ?? \"\");\n return undefined;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n },\n });\n if (isCancel(name)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return validateProjectName(name);\n}\n\n/** 询问是否移除现有文件并继续(目标目录非空时)。 */\nasync function confirmRemove(dirName: string): Promise<boolean> {\n const ok = await confirm({\n message: `目录 \"${dirName}\" 已存在且非空,移除现有文件并继续?`,\n initialValue: false,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 询问是否现在启动(默认 Y;-y 直接返回 true)。 */\nasync function askStartNow(yes: boolean): Promise<boolean> {\n if (yes) {\n return true;\n }\n const ok = await confirm({\n message: \"是否现在启动?\",\n initialValue: true,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 前台执行命令(stdio 继承,用户可见进度/交互),返回退出码。 */\nfunction runInteractive(bin: string, args: string[], cwd: string): Promise<number> {\n return new Promise((resolve) => {\n // Windows 下 .cmd shim 需 shell:true(CreateProcess 不能直接执行 .cmd)\n const child = spawn(bin, args, {\n cwd,\n stdio: \"inherit\",\n windowsHide: false,\n shell: process.platform === \"win32\",\n });\n child.on(\"exit\", (code) => {\n resolve(code ?? 0);\n });\n });\n}\n\nasync function main(): Promise<void> {\n const opts = parseArgs(process.argv.slice(2));\n if (opts.help) {\n console.log(HELP);\n return;\n }\n\n // intro:顶边框 + 名称 + 版本\n intro(`create-napukettoqq v${readVersion()}`);\n\n const dirName = opts.name ?? (await askProjectName(opts.yes));\n const targetDir = resolveTargetDir(dirName);\n\n // 目标目录准备:非空需确认清空(-f 跳过确认;-y 不隐式清空,保持默认安全语义)\n let overwrite = false;\n if ((await checkDirStatus(targetDir)) === \"nonempty\") {\n if (!opts.forced) {\n log.warn(`目标目录 \"${dirName}\" 已存在且非空。`);\n overwrite = await confirmRemove(dirName);\n if (!overwrite) {\n outro(\"已取消,未做任何修改。\");\n return;\n }\n } else {\n overwrite = true;\n }\n }\n\n // 生成骨架(spinner 进度)\n const s = spinner();\n s.start(`正在生成项目骨架 ${dirName} ...`);\n const result = await scaffoldProject(dirName, overwrite);\n s.stop(`项目骨架已生成:${dirName}`);\n log.success(`位置:${result.dir}`);\n\n // 自动安装依赖(用调用方包管理器)\n const pm = detectPackageManager();\n log.info(`包管理器:${pm}(自动检测)`);\n log.info(\"前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。\");\n log.info(`正在安装依赖(${pm} install)...`);\n const installCode = await runInteractive(pmBin(pm), [\"install\"], result.dir);\n if (installCode !== 0) {\n log.error(`依赖安装失败(退出码 ${installCode})。请手动执行:`);\n log.step(`cd ${result.dir}`);\n log.step(`${pm} install`);\n outro(\"创建失败,请手动安装后重试。\");\n process.exitCode = 1;\n return;\n }\n\n // 询问是否现在启动(默认 Y → 前台运行)\n if (await askStartNow(opts.yes)) {\n log.info(\"正在启动 NapukettoQQ(Ctrl+C 停止)...\\n\");\n const startCode = await runInteractive(pmBin(pm), [\"start\"], result.dir);\n if (startCode !== 0) {\n process.exitCode = startCode;\n }\n } else {\n log.message(\"你可以稍后这样启动:\");\n if (targetDir !== process.cwd()) {\n const related = relative(process.cwd(), targetDir);\n log.step(`cd ${related}`);\n }\n log.step(`${pm} start -q <你的QQ号>`);\n log.message(\"配置说明见项目内 napuketto.toml。\");\n }\n\n outro(\"完成。\");\n}\n\nmain().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.error(`创建失败:${message}`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["/**\n * create-napukettoqq 脚手架核心:模板渲染 + 目标目录创建 + 文件写入。\n *\n * 模板为独立文件(templates/ 目录,运行时读取),不硬编码在源码里;\n * 需要插值的占位符用 {{key}} 标记(见 renderTemplate)。\n * 依赖极轻(node:fs/promises / node:os / node:path / node:process)。\n * 生成的用户项目依赖发布版的 @napuketto/cli,用户 install 后经 cli → loader\n * 自建宿主启动。\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\n\nimport path from \"node:path\";\nimport process from \"node:process\";\n\n/** 模板目录(相对本文件:src 与 dist 都在包根下,../templates 均指向包根/templates)。 */\nconst TEMPLATES = new URL(\"../templates/\", import.meta.url);\n\n/**\n * 生成的用户项目所依赖的 @napuketto/cli 版本范围。\n * 运行时从脚手架自身 package.json 读取(dependencies[\"@napuketto/cli\"])——\n * 发版时 changesets 升版本 + pnpm publish 把 workspace:* 替换为实际版本,\n * 模板版本自动跟随,无需手动同步(比硬编码常量可靠)。\n */\nfunction cliVersionRange(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { dependencies?: Record<string, string> };\n const range = ownPkg.dependencies?.[\"@napuketto/cli\"];\n // workspace:* 仅在本地 monorepo 直跑脚手架时出现(发布后 pnpm 替换为实际版本),\n // 此时兜底默认范围,避免生成的用户项目 install 失败\n if (range !== undefined && range !== \"workspace:*\") {\n return range;\n }\n } catch {\n // package.json 缺失(异常环境)→ 兜底默认范围\n }\n return \"^0.0.1\";\n}\n\n/** 默认部署文件夹名(用户交互回车缺省值)。 */\nexport const DEFAULT_PROJECT_NAME = \"NapukettoQQ\";\n\n/** 用户可用的包管理器(npm_config_user_agent 检测)。 */\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\n/**\n * 由 npm_config_user_agent 检测调用方包管理器(yarn create / npm create /\n * pnpm create 都会注入该变量)。兜底 pnpm(项目生态,或直接 node 运行)。\n */\nexport function detectPackageManager(): PackageManager {\n // noPropertyAccessFromIndexSignature:process.env 是索引签名,必须括号访问\n const ua = process.env[\"npm_config_user_agent\"] ?? \"\";\n if (ua.startsWith(\"yarn/\")) {\n return \"yarn\";\n }\n if (ua.startsWith(\"npm/\")) {\n return \"npm\";\n }\n return \"pnpm\";\n}\n\n/** Windows 下包管理器 bin 需带 .cmd 后缀(CreateProcess 不认无扩展名 shim)。 */\nexport function pmBin(pm: PackageManager): string {\n return process.platform === \"win32\" ? `${pm}.cmd` : pm;\n}\n\n/** Windows 路径非法字符(项目文件夹名校验用)。 */\nconst ILLEGAL_NAME = /[\\\\/:*?\"<>|]/;\n\n/** 脚手架生成结果。 */\nexport interface ScaffoldResult {\n /** 目标目录绝对路径。 */\n dir: string;\n /** 派生出的 npm 包名(小写、空格 → -)。 */\n packageName: string;\n}\n\n/** 校验文件夹名:非空、非 . / ..、不含 Windows 非法字符。 */\nexport function validateProjectName(raw: string): string {\n const name = raw.trim();\n if (name === \"\" || name === \".\" || name === \"..\") {\n throw new Error(\"文件夹名不能为空或为 . / ..\");\n }\n if (ILLEGAL_NAME.test(name)) {\n throw new Error(`文件夹名含非法字符:${name}(不允许 \\\\ / : * ? \" < > |)`);\n }\n return name;\n}\n\n/** 由文件夹名派生 npm 包名(小写、空格 → -)。 */\nfunction derivePackageName(dirName: string): string {\n return dirName.toLowerCase().replace(/\\s+/g, \"-\");\n}\n\n/** 解析目标目录绝对路径(校验名称后,基于 cwd)。 */\nexport function resolveTargetDir(dirName: string): string {\n return path.resolve(process.cwd(), validateProjectName(dirName));\n}\n\n/** 目标目录状态:不存在 / 空 / 非空。 */\nexport type DirStatus = \"missing\" | \"empty\" | \"nonempty\";\n\n/** 检测目标目录状态(供交互层决定是否询问清空)。 */\nexport async function checkDirStatus(dir: string): Promise<DirStatus> {\n let entries: string[] = [];\n try {\n entries = await readdir(dir);\n } catch {\n return \"missing\";\n }\n return entries.length === 0 ? \"empty\" : \"nonempty\";\n}\n\n/** 清空目录内容(保留目录本身)。 */\nasync function emptyDir(dir: string): Promise<void> {\n for (const entry of await readdir(dir)) {\n await rm(path.join(dir, entry), { recursive: true, force: true });\n }\n}\n\n/** 读取模板文件(templates/ 目录,UTF-8)。 */\nfunction readTemplate(file: string): Promise<string> {\n return readFile(new URL(file, TEMPLATES), \"utf8\");\n}\n\n/**\n * 占位符渲染:把 {{key}} 替换为 vars[key]。\n * 模板中出现未提供的占位符 → 抛错(防漏插值写出半成品)。\n */\nfunction renderTemplate(content: string, vars: Record<string, string>): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value = vars[key];\n if (value === undefined) {\n throw new Error(`模板占位符未提供:{{${key}}}(模板 ${match} 无法渲染)`);\n }\n return value;\n });\n}\n\n/**\n * 生成 NapukettoQQ 项目骨架到当前目录下的 <dirName>/。\n * 目标目录已存在且非空:overwrite=false → 抛错;overwrite=true → 清空后生成。\n * 模板与写入目标一一对应(同文件名)。\n */\nexport async function scaffoldProject(dirName: string, overwrite = false): Promise<ScaffoldResult> {\n const name = validateProjectName(dirName);\n const packageName = derivePackageName(name);\n const dir = resolveTargetDir(name);\n\n const status = await checkDirStatus(dir);\n if (status === \"nonempty\" && !overwrite) {\n throw new Error(`目录已存在且非空:${dir}\\n请换一个文件夹名,或先清空该目录。`);\n }\n if (status === \"nonempty\") {\n await emptyDir(dir);\n }\n\n // 渲染变量(napuketto.toml 模板不再插值 dataDir——缺省 = 项目根/.napuketto,跨平台)\n const vars: Record<string, string> = {\n packageName,\n cliVersion: cliVersionRange(),\n };\n // 用户项目是「运行壳」:不开发代码,只需 package.json(依赖 + start)+ napuketto.toml(配置)\n // 模板文件为 templates/<name>.tmpl(.tmpl 后缀避免 TOML/JSON 语言服务把占位符当非法语法)\n const files = [\"package.json\", \"napuketto.toml\"];\n\n await mkdir(dir, { recursive: true });\n for (const file of files) {\n const rendered = renderTemplate(await readTemplate(`${file}.tmpl`), vars);\n await writeFile(path.join(dir, file), rendered, \"utf8\");\n }\n\n return { dir, packageName };\n}\n","#!/usr/bin/env node\n\n/**\n * create-napukettoqq 入口(2026-08-07 美化 v3,交互层用 @clack/prompts——\n * 现代脚手架交互标准,create-vue / create-turbo 同款)。\n *\n * 用法:\n * create-napukettoqq # intro + 交互问部署文件夹名(默认 NapukettoQQ)\n * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过命名交互\n * create-napukettoqq my-bot -f # 目标目录非空时强制清空覆盖\n * create-napukettoqq -y # 全默认(命名用默认值、启动询问用默认 Y)\n * create-napukettoqq -h # 打印帮助\n *\n * 流程:intro → 问文件夹名 → 目标目录准备(非空需确认清空)→ spinner 生成骨架\n * (package.json / napuketto.toml / readme.md / .gitignore)→ 用调用方包管理器\n * 自动 install → 询问是否现在启动(默认 Y)→ 前台运行 → outro 收尾。\n */\n\nimport { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, confirm, intro, isCancel, log, outro, spinner, text } from \"@clack/prompts\";\nimport {\n checkDirStatus,\n DEFAULT_PROJECT_NAME,\n detectPackageManager,\n pmBin,\n resolveTargetDir,\n scaffoldProject,\n validateProjectName,\n} from \"./scaffold.js\";\n\n/** CLI 参数解析结果。 */\ninterface CliOptions {\n /** 位置参数:部署文件夹名(可选)。 */\n name?: string;\n /** -f/--forced:目标目录非空时强制清空覆盖。 */\n forced: boolean;\n /** -y/--yes:跳过所有交互提示(使用默认值)。 */\n yes: boolean;\n /** -h/--help:显示帮助。 */\n help: boolean;\n}\n\n/** 帮助文本(-h/--help)。 */\nconst HELP = ` 用法: create-napukettoqq [name] [选项]\n\n 选项:\n -f, --forced 目标目录非空时强制清空并覆盖\n -y, --yes 跳过所有交互提示(使用默认值)\n -h, --help 显示本帮助\n`;\n\n/** 解析命令行参数(flag 仅三个,手写保持自包含,不引 yargs-parser)。 */\nfunction parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = { forced: false, yes: false, help: false };\n for (const arg of argv) {\n if (arg === \"-f\" || arg === \"--forced\") {\n opts.forced = true;\n } else if (arg === \"-y\" || arg === \"--yes\") {\n opts.yes = true;\n } else if (arg === \"-h\" || arg === \"--help\") {\n opts.help = true;\n } else if (arg.startsWith(\"-\")) {\n throw new Error(`未知参数:${arg}`);\n } else if (opts.name === undefined) {\n opts.name = arg;\n } else {\n throw new Error(`多余的位置参数:${arg}`);\n }\n }\n return opts;\n}\n\n/** 读取脚手架自身版本号(intro 显示;dist 场景 import.meta.url 指向包内)。 */\nfunction readVersion(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { version?: string };\n return ownPkg.version ?? \"0.0.0\";\n } catch {\n // package.json 缺失(异常环境)→ 兜底\n return \"0.0.0\";\n }\n}\n\n/** 交互询问部署文件夹名(回车取默认值;-y 直接返回默认值)。 */\nasync function askProjectName(yes: boolean): Promise<string> {\n if (yes) {\n return DEFAULT_PROJECT_NAME;\n }\n const name = await text({\n message: \"部署文件夹名\",\n placeholder: DEFAULT_PROJECT_NAME,\n initialValue: DEFAULT_PROJECT_NAME,\n validate: (input: string | undefined) => {\n try {\n validateProjectName(input ?? \"\");\n return undefined;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n },\n });\n if (isCancel(name)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return validateProjectName(name);\n}\n\n/** 询问是否移除现有文件并继续(目标目录非空时)。 */\nasync function confirmRemove(dirName: string): Promise<boolean> {\n const ok = await confirm({\n message: `目录 \"${dirName}\" 已存在且非空,移除现有文件并继续?`,\n initialValue: false,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 询问是否现在启动(默认 Y;-y 直接返回 true)。 */\nasync function askStartNow(yes: boolean): Promise<boolean> {\n if (yes) {\n return true;\n }\n const ok = await confirm({\n message: \"是否现在启动?\",\n initialValue: true,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 拼命令行时最小转义:含空白/引号则加双引号包裹(参数均为内部字面量,仅兜底)。 */\nfunction quoteShellArg(arg: string): string {\n return /[\\s\"]/.test(arg) ? `\"${arg.replace(/\"/g, '\\\\\"')}\"` : arg;\n}\n\n/** 前台执行命令(stdio 继承,用户可见进度/交互),返回退出码。 */\nfunction runInteractive(bin: string, args: string[], cwd: string): Promise<number> {\n return new Promise((resolve) => {\n // Windows 下 .cmd shim 不能直接 CreateProcess 执行,需 shell:true 经 cmd.exe;\n // 但 Node ≥22 对「shell:true + args 数组」触发 DEP0190 告警(args 仅简单拼接、不转义),\n // 因此 shell 模式下把命令拼成单个字符串传给 spawn 以规避告警;\n // POSIX 下无 shell,保持 bin + args。\n const shell = process.platform === \"win32\";\n const child = spawn(\n shell ? [bin, ...args.map(quoteShellArg)].join(\" \") : bin,\n shell ? [] : args,\n {\n cwd,\n stdio: \"inherit\",\n windowsHide: false,\n shell,\n },\n );\n child.on(\"exit\", (code) => {\n resolve(code ?? 0);\n });\n });\n}\n\nasync function main(): Promise<void> {\n const opts = parseArgs(process.argv.slice(2));\n if (opts.help) {\n console.log(HELP);\n return;\n }\n\n // intro:顶边框 + 名称 + 版本\n intro(`create-napukettoqq v${readVersion()}`);\n\n const dirName = opts.name ?? (await askProjectName(opts.yes));\n const targetDir = resolveTargetDir(dirName);\n\n // 目标目录准备:非空需确认清空(-f 跳过确认;-y 不隐式清空,保持默认安全语义)\n let overwrite = false;\n if ((await checkDirStatus(targetDir)) === \"nonempty\") {\n if (!opts.forced) {\n log.warn(`目标目录 \"${dirName}\" 已存在且非空。`);\n overwrite = await confirmRemove(dirName);\n if (!overwrite) {\n outro(\"已取消,未做任何修改。\");\n return;\n }\n } else {\n overwrite = true;\n }\n }\n\n // 生成骨架(spinner 进度)\n const s = spinner();\n s.start(`正在生成项目骨架 ${dirName} ...`);\n const result = await scaffoldProject(dirName, overwrite);\n s.stop(`项目骨架已生成:${dirName}`);\n log.success(`位置:${result.dir}`);\n\n // 自动安装依赖(用调用方包管理器)\n const pm = detectPackageManager();\n log.info(`包管理器:${pm}(自动检测)`);\n log.info(\"前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。\");\n log.info(`正在安装依赖(${pm} install)...`);\n const installCode = await runInteractive(pmBin(pm), [\"install\"], result.dir);\n if (installCode !== 0) {\n log.error(`依赖安装失败(退出码 ${installCode})。请手动执行:`);\n log.step(`cd ${result.dir}`);\n log.step(`${pm} install`);\n outro(\"创建失败,请手动安装后重试。\");\n process.exitCode = 1;\n return;\n }\n\n // 询问是否现在启动(默认 Y → 前台运行)\n if (await askStartNow(opts.yes)) {\n log.info(\"正在启动 NapukettoQQ(Ctrl+C 停止)...\\n\");\n const startCode = await runInteractive(pmBin(pm), [\"start\"], result.dir);\n if (startCode !== 0) {\n process.exitCode = startCode;\n }\n } else {\n log.message(\"你可以稍后这样启动:\");\n if (targetDir !== process.cwd()) {\n const related = relative(process.cwd(), targetDir);\n log.step(`cd ${related}`);\n }\n log.step(`${pm} start -q <你的QQ号>`);\n log.message(\"配置说明见项目内 napuketto.toml。\");\n }\n\n outro(\"完成。\");\n}\n\nmain().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.error(`创建失败:${message}`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-napukettoqq",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "一键生成 NapukettoQQ 机器人项目(yarn/pnpm/npm create napukettoqq)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^1.7.0",
|
|
16
|
-
"@napuketto/cli": "0.0.
|
|
16
|
+
"@napuketto/cli": "0.0.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsdown": "^0.22.14"
|
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
# ============================================================
|
|
2
2
|
# NapukettoQQ 全局配置文件(由 create-napukettoqq 生成)
|
|
3
3
|
# ============================================================
|
|
4
|
-
#
|
|
5
|
-
# 优先级:--data-dir <dir> > NAPKETTO_DATA 环境变量 > ~/.napuketto
|
|
6
|
-
# 配置文件路径解析:NAPKETTO_CONFIG > 项目根探测 > cwd > 数据根兜底。
|
|
4
|
+
# 本机配置不入库(.gitignore);修改后重启生效。
|
|
7
5
|
#
|
|
8
|
-
#
|
|
6
|
+
# 配置组织:一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在账号内
|
|
7
|
+
# ([accounts.onebot11] / [accounts.satori])。账号没有写某协议段 = 该账号
|
|
8
|
+
# 不启用该协议。账号必填(至少一个,qq 必填)。
|
|
9
9
|
|
|
10
10
|
# ------------------------------------------------------------
|
|
11
|
-
#
|
|
11
|
+
# 全局配置(跨账号)
|
|
12
12
|
# ------------------------------------------------------------
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# 数据根目录:账号目录/日志/缓存/QQ 数据存放位置。
|
|
15
|
+
# 缺省 = <项目根>/.napuketto(部署场景,跨平台,无需修改)。
|
|
16
|
+
# 支持绝对路径、"~/" 前缀(用户主目录)、相对路径(相对启动目录)。
|
|
17
|
+
# dataDir = ".napuketto"
|
|
15
18
|
|
|
16
|
-
# 数据根目录:账号目录/日志/缓存/QQ 数据存放位置(绝对路径)
|
|
17
19
|
# 多账号(supervisor)模式下,账号进程崩溃是否自动重启
|
|
18
20
|
autoRestart = true
|
|
19
21
|
|
|
20
22
|
# 崩溃后自动重启的延迟(毫秒)
|
|
21
23
|
restartDelayMs = 2000
|
|
22
24
|
|
|
23
|
-
# 多账号列表(留空 = 单账号模式:启动后自动快速登录/扫码)
|
|
24
|
-
# [[accounts]]
|
|
25
|
-
# qq = "123456" # QQ 号(必填)
|
|
26
|
-
# enabled = true # 是否启用(可选,缺省 true)
|
|
27
|
-
|
|
28
25
|
# ------------------------------------------------------------
|
|
29
|
-
#
|
|
26
|
+
# 账号(至少一个,qq 必填)
|
|
30
27
|
# ------------------------------------------------------------
|
|
31
|
-
|
|
28
|
+
|
|
29
|
+
[[accounts]]
|
|
30
|
+
qq = "123456" # QQ 号(必填)
|
|
31
|
+
enabled = true # 是否启用(可选,缺省 true)
|
|
32
|
+
|
|
33
|
+
[accounts.onebot11] # 该账号的 OneBot 11 配置(无此段 = 不启用 OB11)
|
|
32
34
|
|
|
33
35
|
# 鉴权 token(全局默认):HTTP/WS 连接必须携带。
|
|
34
36
|
# 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>
|
|
@@ -46,21 +48,21 @@ reportSelfMessage = false
|
|
|
46
48
|
messagePostFormat = "array"
|
|
47
49
|
|
|
48
50
|
# ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----
|
|
49
|
-
[[onebot11.httpServers]]
|
|
51
|
+
[[accounts.onebot11.httpServers]]
|
|
50
52
|
enabled = true
|
|
51
|
-
host = "127.0.0.1"
|
|
53
|
+
host = "127.0.0.1" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)
|
|
52
54
|
port = 3000
|
|
53
55
|
# token = "abc" # 可选:覆盖全局 token
|
|
54
56
|
|
|
55
57
|
# ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----
|
|
56
|
-
# [[onebot11.httpPostUrls]]
|
|
58
|
+
# [[accounts.onebot11.httpPostUrls]]
|
|
57
59
|
# enabled = true
|
|
58
60
|
# url = "http://127.0.0.1:8080/onebot" # 第三方接收地址
|
|
59
61
|
# token = "abc" # 可选:覆盖全局 token
|
|
60
62
|
# timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时
|
|
61
63
|
|
|
62
64
|
# ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----
|
|
63
|
-
[[onebot11.wsServers]]
|
|
65
|
+
[[accounts.onebot11.wsServers]]
|
|
64
66
|
enabled = true
|
|
65
67
|
host = "127.0.0.1"
|
|
66
68
|
port = 3001
|
|
@@ -68,7 +70,7 @@ port = 3001
|
|
|
68
70
|
# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
|
|
69
71
|
|
|
70
72
|
# ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----
|
|
71
|
-
# [[onebot11.wsReverseUrls]]
|
|
73
|
+
# [[accounts.onebot11.wsReverseUrls]]
|
|
72
74
|
# enabled = true
|
|
73
75
|
# url = "ws://127.0.0.1:8081/ws" # 第三方 WS 地址(ws:// 或 wss://)
|
|
74
76
|
# token = "abc" # 可选:覆盖全局 token
|
|
@@ -76,3 +78,17 @@ port = 3001
|
|
|
76
78
|
# maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)
|
|
77
79
|
# rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)
|
|
78
80
|
# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
|
|
81
|
+
|
|
82
|
+
# 第二个账号示例(只开 Satori):
|
|
83
|
+
# [[accounts]]
|
|
84
|
+
# qq = "654321"
|
|
85
|
+
# [accounts.satori]
|
|
86
|
+
# # token = "" # 可选:Satori 鉴权 token
|
|
87
|
+
# [[accounts.satori.httpServers]] # HTTP RPC(POST /v1/{resource}.{method})
|
|
88
|
+
# enabled = true
|
|
89
|
+
# host = "127.0.0.1"
|
|
90
|
+
# port = 5500
|
|
91
|
+
# [[accounts.satori.wsServers]] # WS 事件服务(/v1/events)
|
|
92
|
+
# enabled = true
|
|
93
|
+
# host = "127.0.0.1"
|
|
94
|
+
# port = 5501
|