@lingfan-engine/create 0.0.0-stage → 0.1.4
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 +201 -0
- package/bin/create.mjs +18 -0
- package/package.json +44 -3
- package/src/cli/cli.ts +302 -0
- package/src/cli/prompts.ts +115 -0
- package/src/generate/dotfiles.ts +27 -0
- package/src/generate/index.ts +20 -0
- package/src/generate/project.ts +279 -0
- package/src/generate/replace.ts +91 -0
- package/src/index.ts +46 -0
- package/src/overlay/index.ts +10 -0
- package/src/overlay/merge.ts +143 -0
- package/src/registry/index.ts +15 -0
- package/src/registry/template-meta.ts +107 -0
- package/src/registry/versions.ts +109 -0
- package/src/shared/guards.ts +85 -0
- package/src/shared/index.ts +7 -0
- package/src/shared/texts.ts +55 -0
- package/template/README.md +138 -0
- package/template/__PROJECT__/Resources/Audio/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Fonts/README.md +10 -0
- package/template/__PROJECT__/Resources/Images/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Lang/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Stories/title/title_main.story +24 -0
- package/template/__PROJECT__/Resources/Video/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/project.json +7 -0
- package/template/__PROJECT__/gitignore +8 -0
- package/template/__PROJECT__/index.html +34 -0
- package/template/__PROJECT__/package.json +22 -0
- package/template/__PROJECT__/pnpm-workspace.yaml +8 -0
- package/template/__PROJECT__/src/host/boot.ts +181 -0
- package/template/__PROJECT__/src/host/dialogue-view.ts +235 -0
- package/template/__PROJECT__/src/host/effects.ts +68 -0
- package/template/__PROJECT__/src/host/engine-wiring.ts +206 -0
- package/template/__PROJECT__/src/host/frame-loop.ts +42 -0
- package/template/__PROJECT__/src/host/index.ts +27 -0
- package/template/__PROJECT__/src/host/input-map.ts +51 -0
- package/template/__PROJECT__/src/host/layer-z.ts +50 -0
- package/template/__PROJECT__/src/host/save-slots.ts +38 -0
- package/template/__PROJECT__/src/host/stage-dom.ts +74 -0
- package/template/__PROJECT__/src/host/theme-fonts.ts +60 -0
- package/template/__PROJECT__/src/host/toast.ts +29 -0
- package/template/__PROJECT__/src/main.ts +61 -0
- package/template/__PROJECT__/src/styles/stage.css +183 -0
- package/template/__PROJECT__/src/vite-env.d.ts +14 -0
- package/template/__PROJECT__/tsconfig.json +19 -0
- package/template/__PROJECT__/tsconfig.node.json +14 -0
- package/template/__PROJECT__/vite.config.ts +64 -0
- package/template/overlays/README.md +70 -0
- package/template/overlays/miniprogram/README.md +104 -0
- package/template/overlays/miniprogram/index.html +15 -0
- package/template/overlays/miniprogram/package.patch.json +40 -0
- package/template/overlays/miniprogram/scripts/serve-project.mjs +273 -0
- package/template/overlays/miniprogram/src/App.vue +23 -0
- package/template/overlays/miniprogram/src/components/ChoiceLayer.vue +208 -0
- package/template/overlays/miniprogram/src/components/DialogueLayer.vue +183 -0
- package/template/overlays/miniprogram/src/components/NodeTree.vue +44 -0
- package/template/overlays/miniprogram/src/components/NoticeLayer.vue +87 -0
- package/template/overlays/miniprogram/src/components/SaveLoadBar.vue +168 -0
- package/template/overlays/miniprogram/src/components/StageView.vue +50 -0
- package/template/overlays/miniprogram/src/env.d.ts +1 -0
- package/template/overlays/miniprogram/src/main.ts +12 -0
- package/template/overlays/miniprogram/src/manifest.json +21 -0
- package/template/overlays/miniprogram/src/mp/frame.ts +51 -0
- package/template/overlays/miniprogram/src/mp/guards.ts +28 -0
- package/template/overlays/miniprogram/src/mp/host.ts +252 -0
- package/template/overlays/miniprogram/src/mp/index.ts +9 -0
- package/template/overlays/miniprogram/src/mp/platform.ts +210 -0
- package/template/overlays/miniprogram/src/mp/ports.ts +84 -0
- package/template/overlays/miniprogram/src/mp/project-source.ts +77 -0
- package/template/overlays/miniprogram/src/mp/slots.ts +64 -0
- package/template/overlays/miniprogram/src/mp/surface.ts +239 -0
- package/template/overlays/miniprogram/src/mp/video-surface.ts +104 -0
- package/template/overlays/miniprogram/src/mp/view.ts +91 -0
- package/template/overlays/miniprogram/src/pages/index/index.vue +299 -0
- package/template/overlays/miniprogram/src/pages.json +18 -0
- package/template/overlays/miniprogram/tsconfig.json +20 -0
- package/template/overlays/miniprogram/vite.config.ts +87 -0
- package/template/overlays/react/index.html +16 -0
- package/template/overlays/react/package.patch.json +18 -0
- package/template/overlays/react/src/App.tsx +32 -0
- package/template/overlays/react/src/main.tsx +74 -0
- package/template/overlays/react/src/vite-env.d.ts +14 -0
- package/template/overlays/react/tsconfig.json +20 -0
- package/template/overlays/react/vite.config.ts +67 -0
- package/template/overlays/tauri/README.md +59 -0
- package/template/overlays/tauri/package.patch.json +14 -0
- package/template/overlays/tauri/src/main.ts +58 -0
- package/template/overlays/tauri/src-tauri/Cargo.toml +16 -0
- package/template/overlays/tauri/src-tauri/build.rs +6 -0
- package/template/overlays/tauri/src-tauri/capabilities/default.json +7 -0
- package/template/overlays/tauri/src-tauri/icons/128x128.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/128x128@2x.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/32x32.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square107x107Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square142x142Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square150x150Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square284x284Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square30x30Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square310x310Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square44x44Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square71x71Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square89x89Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/StoreLogo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.icns +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.ico +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.png +0 -0
- package/template/overlays/tauri/src-tauri/src/lib.rs +26 -0
- package/template/overlays/tauri/src-tauri/src/main.rs +7 -0
- package/template/overlays/tauri/src-tauri/src/preferences.rs +50 -0
- package/template/overlays/tauri/src-tauri/src/save.rs +178 -0
- package/template/overlays/tauri/src-tauri/tauri.conf.json +35 -0
- package/template/overlays/vue/index.html +16 -0
- package/template/overlays/vue/package.patch.json +16 -0
- package/template/overlays/vue/src/App.vue +30 -0
- package/template/overlays/vue/src/main.ts +68 -0
- package/template/overlays/vue/src/vite-env.d.ts +24 -0
- package/template/overlays/vue/tsconfig.json +19 -0
- package/template/overlays/vue/vite.config.ts +67 -0
- package/template/pack-template.ps1 +65 -0
- package/template/template-meta.json +40 -0
- package/README.md +0 -3
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模板目录定位与元数据读取。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要「两处找」:模板内容在仓库里是 `template/v1`(与版本号一起在 git 里演进),
|
|
5
|
+
* 但发布到 npm 的包里不可能把仓库根目录带上——`prepack` 时会把它复制进
|
|
6
|
+
* `packages/create/template`。所以运行期先在包内找,找不到再退回仓库布局,
|
|
7
|
+
* 让「仓内直接跑」与「装到别的工程里跑」走同一份代码。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
/** 本包根目录(`packages/create`):`src/registry/` 上溯两级 */
|
|
14
|
+
const PACKAGE_ROOT = resolve(
|
|
15
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
16
|
+
"..",
|
|
17
|
+
"..",
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/** 模板元数据:模板版本、占位符、可选的框架与壳 */
|
|
21
|
+
export interface TemplateMeta {
|
|
22
|
+
/** 模板自身版本,独立于引擎版本 */
|
|
23
|
+
readonly version: string;
|
|
24
|
+
/** 此模板要求的最低引擎版本 */
|
|
25
|
+
readonly minEngineVersion: string;
|
|
26
|
+
/** 生成时整体替换的占位符 */
|
|
27
|
+
readonly placeholder: string;
|
|
28
|
+
/** 可选框架,首项为缺省值 */
|
|
29
|
+
readonly frameworks: readonly TemplateOption[];
|
|
30
|
+
/** 可选壳,首项为缺省值 */
|
|
31
|
+
readonly shells: readonly TemplateOption[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 一个可选项(框架或壳):`id` 对应叠加层目录名 */
|
|
35
|
+
export interface TemplateOption {
|
|
36
|
+
/** 叠加层目录名,也是命令行取值 */
|
|
37
|
+
readonly id: string;
|
|
38
|
+
/** 人读名称,交互问答里显示 */
|
|
39
|
+
readonly label: string;
|
|
40
|
+
/** 一句话说明,交互问答里作为提示 */
|
|
41
|
+
readonly hint: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 解析模板根目录。
|
|
46
|
+
*
|
|
47
|
+
* 顺序:包内 `template/`(发布形态)→ 仓库 `template/v1`(开发形态)。
|
|
48
|
+
* 两者都不存在时返回 undefined,由调用方转成可读错误——这里不抛异常,
|
|
49
|
+
* 因为「模板缺席」是部署问题而非参数问题,报错措辞在 CLI 层统一。
|
|
50
|
+
*/
|
|
51
|
+
export function resolveTemplateRoot(): string | undefined {
|
|
52
|
+
const bundled = join(PACKAGE_ROOT, "template");
|
|
53
|
+
if (existsSync(join(bundled, "template-meta.json"))) return bundled;
|
|
54
|
+
const inRepo = resolve(PACKAGE_ROOT, "..", "..", "template", "v1");
|
|
55
|
+
if (existsSync(join(inRepo, "template-meta.json"))) return inRepo;
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 读取模板元数据。
|
|
61
|
+
*
|
|
62
|
+
* `frameworks` / `shells` 缺失时补成只剩缺省项的单元素表,而不是报错——旧模板
|
|
63
|
+
* (只有骨架、没有框架分层)仍然可以被生成器消费,向后兼容由此保证。
|
|
64
|
+
*/
|
|
65
|
+
export function readTemplateMeta(root: string): TemplateMeta {
|
|
66
|
+
const raw = JSON.parse(
|
|
67
|
+
readFileSync(join(root, "template-meta.json"), "utf8"),
|
|
68
|
+
) as Partial<TemplateMeta> & { version: string; placeholder: string };
|
|
69
|
+
return {
|
|
70
|
+
version: raw.version,
|
|
71
|
+
minEngineVersion: raw.minEngineVersion ?? "0.0.0",
|
|
72
|
+
placeholder: raw.placeholder,
|
|
73
|
+
frameworks: raw.frameworks ?? [SKELETON_ONLY],
|
|
74
|
+
shells: raw.shells ?? [BROWSER_ONLY],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 旧模板无框架分层时的缺省项:叠加层目录不存在 = 只用骨架 */
|
|
79
|
+
const SKELETON_ONLY: TemplateOption = {
|
|
80
|
+
id: "none",
|
|
81
|
+
label: "骨架(框架无关)",
|
|
82
|
+
hint: "不叠加任何框架层",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** 旧模板无壳分层时的缺省项 */
|
|
86
|
+
const BROWSER_ONLY: TemplateOption = {
|
|
87
|
+
id: "browser",
|
|
88
|
+
label: "浏览器",
|
|
89
|
+
hint: "只生成 Web 端工程",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** 叠加层目录名 */
|
|
93
|
+
export const OVERLAY_DIR = "overlays";
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 取某个选项对应的叠加层目录;不存在时返回 undefined。
|
|
97
|
+
*
|
|
98
|
+
* 「不存在」不一定是错误:缺省项(`none`/`browser`)本就没有叠加层,
|
|
99
|
+
* 判定交给调用方——它知道自己在要的是「必须存在的层」还是「可以缺席的缺省层」。
|
|
100
|
+
*/
|
|
101
|
+
export function overlayDirOf(
|
|
102
|
+
templateRoot: string,
|
|
103
|
+
optionId: string,
|
|
104
|
+
): string | undefined {
|
|
105
|
+
const dir = join(templateRoot, OVERLAY_DIR, optionId);
|
|
106
|
+
return existsSync(dir) ? dir : undefined;
|
|
107
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm registry 版本查询。
|
|
3
|
+
*
|
|
4
|
+
* 存在的理由:模板里记录的引擎版本是模板发布时的快照,装到新工程里可能已经落后。
|
|
5
|
+
* 生成时查一次当前最新版,把具体版本号钉进工程清单,让新工程一上来就是新的,
|
|
6
|
+
* 同时又不写 `latest` 标签——标签每次安装解析结果不同,构建会不可重现。
|
|
7
|
+
*
|
|
8
|
+
* 网络不可用时**不失败**:退回模板记录的版本并给出提示。生成一个工程不需要联网,
|
|
9
|
+
* 拉不到最新版不该让整件事失败。
|
|
10
|
+
*/
|
|
11
|
+
import { caretRange } from "../shared/index.ts";
|
|
12
|
+
|
|
13
|
+
/** 一个包的解析结果:`version` 是 registry 上的最新稳定版 */
|
|
14
|
+
export interface ResolvedVersion {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly version: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 查询超时:生成是交互式操作,宁可退回旧版本也不要让人干等 */
|
|
20
|
+
const TIMEOUT_MS = 8000;
|
|
21
|
+
|
|
22
|
+
/** 失败重试一次:并发查几个包时偶发单个连接被对端重置,重试即可,不必让使用者看见 */
|
|
23
|
+
const ATTEMPTS = 2;
|
|
24
|
+
|
|
25
|
+
/** 并发上限:一次甩出十几个连接会让 registry 直接限流,分批查反而快 */
|
|
26
|
+
const CONCURRENCY = 3;
|
|
27
|
+
|
|
28
|
+
/** registry 地址:允许经环境变量覆盖(企业内网镜像、离线代理) */
|
|
29
|
+
function registryBase(): string {
|
|
30
|
+
const configured = process.env.LINGFAN_CREATE_REGISTRY;
|
|
31
|
+
return (
|
|
32
|
+
configured !== undefined && configured !== ""
|
|
33
|
+
? configured
|
|
34
|
+
: "https://registry.npmjs.org"
|
|
35
|
+
).replace(/\/+$/, "");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 查询单个包的 `dist-tags.latest`。
|
|
40
|
+
*
|
|
41
|
+
* 只取标签页而不是完整文档(`/<pkg>/latest` 更小),并显式请求 `dist-tags`:
|
|
42
|
+
* 完整文档在 `react` 这类包上是几 MB,生成器没必要为拿一个版本号付这个代价。
|
|
43
|
+
*
|
|
44
|
+
* 失败重试一次:并发查询时偶发连接被对端重置,重试的代价远小于「某个包的版本
|
|
45
|
+
* 悄悄留在模板旧值」——那种结果看不出来,却让产物自相矛盾。
|
|
46
|
+
*/
|
|
47
|
+
export async function fetchLatestVersion(
|
|
48
|
+
name: string,
|
|
49
|
+
): Promise<string | undefined> {
|
|
50
|
+
for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(`${registryBase()}/${name}/latest`, {
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
headers: { accept: "application/json" },
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) continue;
|
|
59
|
+
const body = (await response.json()) as { version?: unknown };
|
|
60
|
+
if (typeof body.version === "string") return body.version;
|
|
61
|
+
} catch {
|
|
62
|
+
// 断网、超时、DNS 失败、JSON 损坏都走这里:还有一次机会,然后按「拿不到」处理
|
|
63
|
+
} finally {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 批量查询:分批并发,任一失败不影响其它。
|
|
72
|
+
*
|
|
73
|
+
* 返回表只含查到的项——调用方拿模板记录的版本兜底,因此「缺席」比「空串」更好判。
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveVersions(
|
|
76
|
+
names: readonly string[],
|
|
77
|
+
): Promise<Map<string, string>> {
|
|
78
|
+
const out = new Map<string, string>();
|
|
79
|
+
for (let start = 0; start < names.length; start += CONCURRENCY) {
|
|
80
|
+
const batch = names.slice(start, start + CONCURRENCY);
|
|
81
|
+
const settled = await Promise.all(
|
|
82
|
+
batch.map(
|
|
83
|
+
async (name) => [name, await fetchLatestVersion(name)] as const,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
for (const [name, version] of settled) {
|
|
87
|
+
if (version !== undefined) out.set(name, version);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 把查到的版本合进工程清单的依赖表。
|
|
95
|
+
*
|
|
96
|
+
* 规则:清单里有、也查到了 → 换成 `^最新`;查不到 → 保留模板记录的原值不动。
|
|
97
|
+
* 只改清单里已有的键,不新增依赖——依赖集合由模板决定,生成器不是包管理器。
|
|
98
|
+
*/
|
|
99
|
+
export function applyResolvedVersions(
|
|
100
|
+
dependencies: Readonly<Record<string, string>>,
|
|
101
|
+
resolved: ReadonlyMap<string, string>,
|
|
102
|
+
): Record<string, string> {
|
|
103
|
+
const out: Record<string, string> = {};
|
|
104
|
+
for (const [name, range] of Object.entries(dependencies)) {
|
|
105
|
+
const latest = resolved.get(name);
|
|
106
|
+
out[name] = latest === undefined ? range : caretRange(latest);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参数校验与小工具。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独成文件:CLI 与生成编排都要判「工程名是否可用」,两处各写一份必然漂移
|
|
5
|
+
* (一处放过 Windows 保留名,另一处不放),故只留这一处判定。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* npm 包名与目录名共同接受的形态:小写字母/数字/连字符/下划线。
|
|
10
|
+
*
|
|
11
|
+
* 首字符限字母或数字:npm 明确禁止包名以 `.` 或 `_` 开头,而工程名同时要当包名用,
|
|
12
|
+
* 放过下划线开头会让新工程在「日后想发布」时才发现不合规。
|
|
13
|
+
*/
|
|
14
|
+
const NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
15
|
+
|
|
16
|
+
/** Windows 上不可用作目录名的保留字(大小写不敏感) */
|
|
17
|
+
const RESERVED_NAMES = new Set([
|
|
18
|
+
"con",
|
|
19
|
+
"prn",
|
|
20
|
+
"aux",
|
|
21
|
+
"nul",
|
|
22
|
+
"com1",
|
|
23
|
+
"com2",
|
|
24
|
+
"com3",
|
|
25
|
+
"com4",
|
|
26
|
+
"com5",
|
|
27
|
+
"com6",
|
|
28
|
+
"com7",
|
|
29
|
+
"com8",
|
|
30
|
+
"com9",
|
|
31
|
+
"lpt1",
|
|
32
|
+
"lpt2",
|
|
33
|
+
"lpt3",
|
|
34
|
+
"lpt4",
|
|
35
|
+
"lpt5",
|
|
36
|
+
"lpt6",
|
|
37
|
+
"lpt7",
|
|
38
|
+
"lpt8",
|
|
39
|
+
"lpt9",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 工程名是否合法。
|
|
44
|
+
*
|
|
45
|
+
* 只接受小写形态:工程名同时进 `package.json` 的 `name`、`Resources/project.json`
|
|
46
|
+
* 的 `id` 与目录名,三处共用一份字符串。大写会让 `name` 违反 npm 规范(npm 拒绝
|
|
47
|
+
* 大写包名,日后想发布就卡住),故一开始就不放行,而不是事后改。
|
|
48
|
+
*/
|
|
49
|
+
export function isValidProjectName(name: string): boolean {
|
|
50
|
+
if (name.length === 0 || name.length > 64) return false;
|
|
51
|
+
if (!NAME_PATTERN.test(name)) return false;
|
|
52
|
+
return !RESERVED_NAMES.has(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 把任意人类输入收敛成合法工程名:小写、非法字符折叠成连字符、去首尾连字符。
|
|
57
|
+
*
|
|
58
|
+
* 用途是「给个建议值」——交互问答里预填,命令行参数仍然走严格校验,
|
|
59
|
+
* 免得用户以为自己指定的名字被悄悄改过。
|
|
60
|
+
*/
|
|
61
|
+
export function suggestProjectName(input: string): string {
|
|
62
|
+
const collapsed = input
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
65
|
+
.replace(/^-+|-+$/g, "");
|
|
66
|
+
return collapsed.length > 0 ? collapsed : "lingfan-story";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 把用户输入的版本号归一成 `^x.y.z`。
|
|
71
|
+
*
|
|
72
|
+
* 为什么不直接写 `latest` 标签:标签每次安装都解析到当时的最高版本,同一份
|
|
73
|
+
* `package.json` 在不同日子装出不同依赖,构建不可重现。生成时查一次、把具体
|
|
74
|
+
* 版本号钉进清单,之后由使用者显式升级。
|
|
75
|
+
*
|
|
76
|
+
* 已经带范围符号(`^`、`~`、`>`、`<`、`=`、`*`)的输入原样返回——参数来自
|
|
77
|
+
* registry 的 `latest` 字段时是裸版本号,但也会被直接传入,多包一层 `^~x` 会让
|
|
78
|
+
* 清单解析失败。
|
|
79
|
+
*/
|
|
80
|
+
export function caretRange(version: string): string {
|
|
81
|
+
return RANGE_PREFIXES.test(version) ? version : `^${version}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 版本范围符号;命中即视为调用方已指定范围 */
|
|
85
|
+
const RANGE_PREFIXES = /^[\^~><=*]/;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生成器集中文案。
|
|
3
|
+
*
|
|
4
|
+
* 为什么集中:CLI 呈现是唯一对使用者说话的地方,措辞散在多个文件里就会各自漂移
|
|
5
|
+
* (一半说「工程」一半说「项目」)。集中一处后,改口径只需改这里。
|
|
6
|
+
*
|
|
7
|
+
* 用法:除 CLI 层外不要直接拼中文字符串;文案一律走这里,便于日后整体替换语言。
|
|
8
|
+
*/
|
|
9
|
+
export const defaultCreateTexts = {
|
|
10
|
+
/** CLI 标题行,`<name>` 由调用方替换 */
|
|
11
|
+
banner: "灵泛叙事引擎 · 工程生成器",
|
|
12
|
+
help: [
|
|
13
|
+
"用法:",
|
|
14
|
+
" npm create @lingfan-engine [工程名] [选项]",
|
|
15
|
+
" pnpm create @lingfan-engine [工程名] [选项]",
|
|
16
|
+
"",
|
|
17
|
+
"选项:",
|
|
18
|
+
" -f, --framework <id> 框架(缺省取模板声明的默认值)",
|
|
19
|
+
" -s, --shell <id> 壳(缺省取模板声明的默认值)",
|
|
20
|
+
" -d, --dir <路径> 生成到哪个目录下(缺省 = 当前目录)",
|
|
21
|
+
" --no-install 生成后不自动安装依赖",
|
|
22
|
+
" -y, --yes 全部取缺省值,不进入交互问答",
|
|
23
|
+
" -h, --help 显示本说明",
|
|
24
|
+
"",
|
|
25
|
+
"示例:",
|
|
26
|
+
" npm create @lingfan-engine my-story -- --framework vanilla --shell browser",
|
|
27
|
+
].join("\n"),
|
|
28
|
+
/** 交互问答的提示语 */
|
|
29
|
+
promptProjectName: "工程名",
|
|
30
|
+
promptFramework: "框架",
|
|
31
|
+
promptShell: "壳",
|
|
32
|
+
promptInstall: "生成后立即安装依赖?",
|
|
33
|
+
/** 参数校验失败 */
|
|
34
|
+
nameRequired: "工程名不能为空",
|
|
35
|
+
nameInvalid:
|
|
36
|
+
"工程名只能包含小写字母、数字、连字符与下划线,且不能以点或连字符开头",
|
|
37
|
+
dirMissing: "目标目录不存在",
|
|
38
|
+
frameworkUnknown: "未知框架",
|
|
39
|
+
shellUnknown: "未知壳",
|
|
40
|
+
overlayMissing: "模板缺少该选项对应的叠加层",
|
|
41
|
+
targetExists: "目标目录已存在且非空",
|
|
42
|
+
templateMissing: "找不到模板目录:本包未随附模板内容,或开发态仓库结构不完整",
|
|
43
|
+
/** 生成过程 */
|
|
44
|
+
stageResolve: "解析模板与版本…",
|
|
45
|
+
stageCopy: "写入工程文件…",
|
|
46
|
+
stageInstall: "安装依赖…",
|
|
47
|
+
doneTitle: "工程已生成",
|
|
48
|
+
nextSteps: ["cd <name>", "pnpm install", "pnpm dev"].join("\n"),
|
|
49
|
+
/** 非致命提示 */
|
|
50
|
+
warnVersionOffline:
|
|
51
|
+
"无法查询 npm registry 上的最新版本,已沿用模板记录的版本",
|
|
52
|
+
warnInstallFailed: "依赖安装失败,请在工程目录内手动执行 pnpm install",
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
export type CreateTexts = typeof defaultCreateTexts;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# 灵泛工程脚手架 v1
|
|
2
|
+
|
|
3
|
+
**引擎服务 = 核心(框架无关)+ 脚手架(工程骨架)**。本目录是脚手架本体:
|
|
4
|
+
新工程由它生成,因此「选什么展示框架」只影响本模板内的宿主代码,**不影响引擎底层**。
|
|
5
|
+
|
|
6
|
+
模板自带的宿主**不依赖任何 UI 框架**(直接操作 DOM)——这既是「框架无关」的活证明,也让模板保持精简。
|
|
7
|
+
但**精简的底线是「不卡死」**:引擎能进入的每一个等待态,模板都必须给出可推进的出口,
|
|
8
|
+
并且 `project.json` 的 `shell.*` 配置必须被真正消费(否则作者改了配置却毫无效果)。
|
|
9
|
+
|
|
10
|
+
### 覆盖范围 / 有意不做(边界表)
|
|
11
|
+
|
|
12
|
+
| 能力 | 模板 | 说明 |
|
|
13
|
+
| ----------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------- |
|
|
14
|
+
| 对话层(打字机 + 内联标记) | 是 | `{p}/{w}` 停顿、样式标记零宽由 `@lingfan-engine/ui` 提供 |
|
|
15
|
+
| 等待态出口:`menu` / `input` / `wait` / `video` | 是 | 缺任何一个,含该命令的故事都会**永久停滞** |
|
|
16
|
+
| 等待态:`minigame` | 是(可见 fail-closed) | 模板不接注册表 → 显示「未注册」并说明接入方式,**不伪造完成** |
|
|
17
|
+
| 舞台元素层(36 类型 + 交互 + 12 个表现 op) | 是 | 含 `animate`/`transition`/`shake` 的帧驱动(不驱动会让动画队列只增不减) |
|
|
18
|
+
| 层级(z 序,`shell.layers`) | 是 | 各层 z 由 `resolveLayerZ` 解析后内联写入 |
|
|
19
|
+
| 存档(`shell.saves.slots` 槽位数) | 是 | 存/读为命令面;缩略图**不做**(见下行) |
|
|
20
|
+
| 存档缩略图(`shell.saves.thumbnail`) | 否 | 合成卡见 `apps/playground`;不传 `screenshot` 即为无缩略图存档 |
|
|
21
|
+
| 舞台适配(`shell.stage`) | 否 | 模板按固定视口布局;要接就取 `resolveStageConfig` 结果再算 `computeStageLayout` |
|
|
22
|
+
| 游玩界面禁选文本 | 是 | `body` 上 `user-select: none`;要提供复制就给部件加 `data-lf-selectable` |
|
|
23
|
+
| 历史面板与回溯 UI | 否 | 引擎已具备(`historyView`/`rollbackTo`/`back`/`forward`),UI 见 playground |
|
|
24
|
+
| 玩家偏好(音量/文字速度面板) | 否 | 未接 = 音量为 op 值、打字机取故事级 `text_typewriter` |
|
|
25
|
+
| I18N 供给(`Lang/{lang}/` 覆盖) | 否 | 未接 `i18nPort` = 原文直出(引擎契约缺省语义) |
|
|
26
|
+
| 对话框模板注册(`say template=`) | 否 | 未接 = 内建默认骨架 |
|
|
27
|
+
| 屏幕方向锁定(`shell.orientation`) | 不适用 | **纯 Web 形态没有方向 API**;该配置只在 Tauri 壳(playground)生效 |
|
|
28
|
+
| 资源加密 / 热重载 / 宿主信息 | 不适用 | 都是 Tauri 壳能力,见 playground 的组合根 |
|
|
29
|
+
|
|
30
|
+
> 「否」行均指**模板宿主未接**,不代表引擎没有该能力——引擎能力面以 `apps/playground` 为准
|
|
31
|
+
> (历史面板 / 玩家偏好 / I18N / 对话框模板在引擎侧均已具备,仅模板未接线)。
|
|
32
|
+
|
|
33
|
+
> 想升级为全功能实现,看引擎仓库的 `apps/playground`(它同时是模板的第一个实例):
|
|
34
|
+
> 把 `src/main.ts` 换成 Vue/React/原生任意一种都行——**引擎与适配器零改动**。
|
|
35
|
+
|
|
36
|
+
## 生成一个新工程
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
npm create @lingfan-engine my-story
|
|
40
|
+
pnpm create @lingfan-engine my-story
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
生成器会问两件事:工程名、框架与壳。当前框架可选「原生(框架无关)」「Vue 3」「React」,
|
|
44
|
+
壳可选「浏览器」「小程序(UniApp)」与「桌面(Tauri 2)」。全部取缺省值(原生 + 浏览器)可以跳过问答:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
npm create @lingfan-engine my-story -- --yes
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
生成后按提示 `cd my-story && pnpm install && pnpm dev`。
|
|
51
|
+
|
|
52
|
+
想不开交互问答、直接指定框架与壳:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
npm create @lingfan-engine my-story -- --framework vue --shell browser
|
|
56
|
+
npm create @lingfan-engine my-story -- --framework react --shell browser
|
|
57
|
+
npm create @lingfan-engine my-story -- --shell miniprogram
|
|
58
|
+
npm create @lingfan-engine my-story -- --framework vue --shell tauri
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**框架层只换页面结构与入口,`src/host/**`(装配、订阅、帧循环、输入映射)一行不改,
|
|
62
|
+
样式表仍是同一份 `src/styles/stage.css`**,所以换框架不影响游玩界面的外观口径,
|
|
63
|
+
也不影响引擎与适配器——它们不认识任何 UI 框架。选 Vue 3 是 `index.html` 换成挂载点、
|
|
64
|
+
加 `src/App.vue` 与 Vue 版组合根;选 React 是同一套做法换成 `src/App.tsx` 与 React 版组合根,
|
|
65
|
+
外加 `tsconfig.json` 的 `jsx: react-jsx`。
|
|
66
|
+
|
|
67
|
+
壳层与框架层可以叠加(先框架后壳):`--framework react --shell tauri` 得到 React 页面 + 桌面外壳。
|
|
68
|
+
小程序是例外——它自带 Vue 页面层,不与其他框架层叠加。
|
|
69
|
+
|
|
70
|
+
**壳层是换一整套页面与宿主**:小程序形态的页面结构由 `src/components/**` 承接,宿主装配在
|
|
71
|
+
`src/mp/**`(`uni.*` 只在 `src/mp/platform.ts` 出现一处),内容不进包体、走工程接口按基址取。
|
|
72
|
+
因此它是壳而不是框架,与 `--framework` 不叠加:小程序已自带 Vue 页面层,框架取缺省值即可。
|
|
73
|
+
它的 `vite` 与 `vue` 是一组钉住的版本(UniApp 插件对这些版本有硬约束),由
|
|
74
|
+
`package.patch.json` 的 `create.pinned` 保住,不会被改写成「跟最新」。
|
|
75
|
+
|
|
76
|
+
工程名只允许小写字母、数字、`-` 与 `_`,且不能以下划线或点开头——它同时进 `package.json` 的 `name`、
|
|
77
|
+
`project.json` 的 `id` 与目录名,大写与点开头在 npm 侧非法。
|
|
78
|
+
|
|
79
|
+
引擎包版本由生成器在**生成时**查一次 registry,把当时的最新版本写成 `^x.y.z` 记进产物。
|
|
80
|
+
不写 `latest` 标签是因为它每次安装都解析到当时最高版,同一份工程在两台机器上会装出不同版本。
|
|
81
|
+
|
|
82
|
+
## 依赖接入(引擎包)
|
|
83
|
+
|
|
84
|
+
模板依赖三个引擎包:`@lingfan-engine/core`(核心)、`@lingfan-engine/adapters`(预设适配器)、`@lingfan-engine/ui`(参考展示层)。
|
|
85
|
+
它们以 **TS 源码出口**(`exports: "./src/index.ts"`)发布——消费方由 Vite/TS 直接编译,**无中间构建步骤**,
|
|
86
|
+
`pnpm install` 直接可用。
|
|
87
|
+
|
|
88
|
+
## 工程结构(单资源根)
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
__PROJECT__/
|
|
92
|
+
├── project.json # 工程清单:id/name/entry/lang/formatVersion
|
|
93
|
+
├── package.json
|
|
94
|
+
├── pnpm-workspace.yaml # 只声明 allowBuilds:pnpm 12 起「未批准的构建脚本」是硬错误
|
|
95
|
+
├── tsconfig.json # 浏览器侧:src/ 下的 ts 与 vue
|
|
96
|
+
├── tsconfig.node.json # Node 侧:只覆盖 vite.config.ts(它要 node:fs 与 @types/node)
|
|
97
|
+
├── vite.config.ts # publicDir: "Resources" ← 资源根即静态根
|
|
98
|
+
├── index.html # RenderTargets 落地:stage/dialogue/choices/notifications/toolbar
|
|
99
|
+
├── src/main.ts # 组合根:装配适配器 → 组装工程 → 建引擎 → 订阅渲染 + 帧驱动
|
|
100
|
+
├── src/styles/stage.css # 页面与层样式(换框架不动这份:叠加层共用同一张表)
|
|
101
|
+
└── Resources/ # 故事工程根(自包含、可整体搬运)
|
|
102
|
+
├── project.json # 工程清单:id/name/entry/lang/formatVersion
|
|
103
|
+
├── Stories/ # 故事(JSON v1 / .story 混存合法)
|
|
104
|
+
├── Audio/ Images/ Video/
|
|
105
|
+
├── Lang/{lang}/ # I18N overlay
|
|
106
|
+
└── Saves/ # 运行期产物(不入 git)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**清单在资源根内**(不是项目根):只有 `Resources/` 内的文件才会进打包产物,dev 与 prod 因此走同一机制——
|
|
110
|
+
清单若留在项目根,dev 下 Vite 顺带服务根目录能取到,打包产物里却会 404。
|
|
111
|
+
|
|
112
|
+
故事里的资源路径**相对 `Resources/`**:`bgm "Audio/main.mp3"`、`video "Video/op.mp4"`。
|
|
113
|
+
|
|
114
|
+
## 打包分发
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
pwsh -File pack-template.ps1 -SourceDir ./__PROJECT__ -OutputFile ./__PROJECT__.zip
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
排除 `node_modules/`、`dist/`、`.git/`、`Resources/Saves/`(运行期产物不进模板)。
|
|
121
|
+
|
|
122
|
+
打包脚本是给「不走生成器、只想把骨架拿走」的场景准备的。正常路径是 `npm create @lingfan-engine`。
|
|
123
|
+
|
|
124
|
+
## 叠加层与占位符
|
|
125
|
+
|
|
126
|
+
`template-meta.json` 的 `frameworks` / `shells` 声明生成器会问哪些选项,每项的 `id` 对应
|
|
127
|
+
`overlays/<id>/` 目录:同路径文件**覆盖**骨架,根目录的 `*.patch.json` **深合并**进 `package.json`。
|
|
128
|
+
缺省项(`none` / `browser`)没有目录,表示「这一层什么都不加」,不是错误。
|
|
129
|
+
|
|
130
|
+
骨架里的占位符有两种大小写形态:`__PROJECT__`(清单 id/name、页面标题、目录名)与
|
|
131
|
+
`__project__`(`package.json` 的 name,npm 要求小写)。生成时两者都替换成工程名。
|
|
132
|
+
|
|
133
|
+
`.gitignore` 在模板里存成不带点的 `gitignore`:npm 把包装进 `node_modules` 时会把包内的
|
|
134
|
+
`.gitignore` 改名成 `.npmignore`,模板若按原名存,生成的工程就会既缺忽略规则又多个无用文件。
|
|
135
|
+
|
|
136
|
+
## 版本
|
|
137
|
+
|
|
138
|
+
`template-meta.json` 是模板版本的唯一来源(独立于引擎版本):仅当模板内容真正变化时才 bump。
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Fonts/——字体名注册表
|
|
2
|
+
|
|
3
|
+
把字体文件放进本目录即完成注册:**字体名 = 文件名去扩展名**(`楷体.ttf` → 字体名 `楷体`)。
|
|
4
|
+
|
|
5
|
+
- 参与注册的扩展名:`.ttf` / `.otf` / `.woff` / `.woff2`(其余文件忽略)。
|
|
6
|
+
- 引用:故事层只写字体名字符串——`{font=楷体}` 行内标签或 `say` 行参 `font: "楷体"`。
|
|
7
|
+
- 守门分两档:构建期对静态引用 fail-closed(未注册名 stories:build 直接报错);
|
|
8
|
+
运行期对动态拼接名回退默认字体并输出诊断(`configureStoryFonts` 宿主装配)。
|
|
9
|
+
- 宿主自动为本目录的字体注入 `@font-face`(Vite 构建期扫描),无需手写样式。
|
|
10
|
+
- 建议文件名只用字母数字、中文与连字符,避免空格等需转义字符。
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// __PROJECT__ 示例工程(文本形态 = JSON v1 的投影)
|
|
2
|
+
// 资源路径相对项目资源根 Resources/:如 bgm "Audio/main.mp3"
|
|
3
|
+
// 一个文件里可以写多列(label / scene);拆文件时把新文件加进 src/main.ts 的 STORIES 列表。
|
|
4
|
+
|
|
5
|
+
label title_main:
|
|
6
|
+
say "欢迎来到 __PROJECT__。"
|
|
7
|
+
say "在 Resources/Stories/ 下继续写你的故事;媒体放 Resources/{Audio,Images,Video}。"
|
|
8
|
+
input "先问一句:你叫什么名字?" store="player.name"
|
|
9
|
+
say "你好,{player.name}!"
|
|
10
|
+
menu "接下来去哪里?"
|
|
11
|
+
"看舞台元素" -> stage_demo
|
|
12
|
+
"看重来一次" -> title_main
|
|
13
|
+
|
|
14
|
+
// scene 列 = 声明式空间层:进入本列时整体装载,切换列时清空
|
|
15
|
+
scene stage_demo
|
|
16
|
+
panel x=5% y=10% width=90% zindex=10
|
|
17
|
+
text "scene 列 = 声明式空间层:这些方块是元素,不是对话"
|
|
18
|
+
text "panel 是容器,子元素用更深缩进写"
|
|
19
|
+
say "进入 scene 列:元素已整体装载(回顾/读档会自动随行)。"
|
|
20
|
+
say "下一条会隐藏对话框,再下一条让它回来。"
|
|
21
|
+
window hide
|
|
22
|
+
say "(对话框已隐藏——点击继续)"
|
|
23
|
+
window show
|
|
24
|
+
say "对话框回来了。层级、存档槽位等工程配置见 README 的边界表。"
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>__PROJECT__</title>
|
|
7
|
+
<!-- 页面外观与层样式:与「页面结构」分开,框架叠加层可复用同一份样式表 -->
|
|
8
|
+
<link rel="stylesheet" href="/src/styles/stage.css" />
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<!-- 舞台根:屏幕震动(shake)施于此;各层 z 由 main.ts 按 shell.layers 写入 -->
|
|
13
|
+
<div id="stage-root">
|
|
14
|
+
<div id="stage"></div>
|
|
15
|
+
<div id="nvl"></div>
|
|
16
|
+
<div id="dialogue">
|
|
17
|
+
<div id="speaker"></div>
|
|
18
|
+
<div id="text">加载中……</div>
|
|
19
|
+
<div id="hint"></div>
|
|
20
|
+
</div>
|
|
21
|
+
<div id="choices"></div>
|
|
22
|
+
<div id="notifications"></div>
|
|
23
|
+
<div id="toolbar">
|
|
24
|
+
<span>存档</span>
|
|
25
|
+
<select id="slot"></select>
|
|
26
|
+
<button id="save">存</button>
|
|
27
|
+
<button id="load">读</button>
|
|
28
|
+
</div>
|
|
29
|
+
<div id="transition"></div>
|
|
30
|
+
<div id="error"></div>
|
|
31
|
+
</div>
|
|
32
|
+
<script type="module" src="/src/main.ts"></script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__project__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@lingfan-engine/adapters": "^0.1.0",
|
|
14
|
+
"@lingfan-engine/core": "^0.1.0",
|
|
15
|
+
"@lingfan-engine/ui": "^0.1.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^26.6.3",
|
|
19
|
+
"typescript": "~6.0.3",
|
|
20
|
+
"vite": "^8.0.16"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# pnpm 12 起,「未批准的构建脚本」是硬错误(ERR_PNPM_IGNORED_BUILDS),
|
|
2
|
+
# 会让 `pnpm install` 整个失败、并连锁跳过其后的构建步骤。
|
|
3
|
+
# 这里的批准面只放 esbuild(vite 与 UniApp 工具链的既定安装步骤);
|
|
4
|
+
# 带安装脚本但不需要执行的包显式写 false ——「知道了、故意不跑」与「未批准」是两回事,
|
|
5
|
+
# 不写 false 每次安装都会以同一个错误收场。core-js 的 postinstall 只打印赞助信息。
|
|
6
|
+
allowBuilds:
|
|
7
|
+
core-js: false
|
|
8
|
+
esbuild: true
|