feat(adapter): E2 从 sourceProject 真构建 formalize——scaffold-from-source + 单测
U2(plan 002):把「sourceProject → 可构建 game 工件」从手工探针formalize成可复用函数, 即后端确定性 modify guard-pending 在等的引擎线交付物(plan 001 U6 / plan 002 KTD2)。 - buildFactorySource(sourceProject)→ generated-factory.js 文本(适配器 import + 内联 source + 默认导出工厂)。 - scaffoldFromSource(...)→ 写 generated-factory + 拷 entry-bundle/index 模板,返 build.mjs 入口。 - 零依赖单测 3/3:文本合契约 / 内联 source 往返深等 / adapterImport 可覆盖。 - e2e 验(集成段,本机):scaffoldFromSource 落变体源(5 目标)→ build.mjs → 211KB bundle(LittleJS 真入包); 端到端九门由 _adapter-probe 验过(046454d9)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
046454d95e
commit
d763755117
59
game-runtime/src/adapter/scaffold-from-source.mjs
Normal file
59
game-runtime/src/adapter/scaffold-from-source.mjs
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
/**
|
||||
* scaffold-from-source.mjs —— E2「从 sourceProject 真构建」的工件落地(U2,plan 002)
|
||||
* owner:前端+引擎线(Mac)
|
||||
*
|
||||
* 【做什么】把存储的 SourceProject 落成可被 build.mjs 编译的 game 目录工件:
|
||||
* ① buildFactorySource(sourceProject) → generated-factory.js 文本(适配器 import + 内联 source + 默认导出工厂);
|
||||
* ② scaffoldFromSource(...) → 写 generated-factory.js + 拷 entry-bundle/index 模板,返 build.mjs 入口路径。
|
||||
*
|
||||
* 【为什么】当前两路(worker/SAA)都直写 factorySrc(整工厂)、不消费 sourceProject;后端确定性 modify guard-pending
|
||||
* 在「从 sourceProject 真构建」上(plan 001 U6 / plan 002 KTD2)。本模块 = 引擎线交付的那个落地工件:
|
||||
* sourceProject →(本模块)→ generated-factory.js(entry-bundle 的固定 import 目标)→ build.mjs → __GameBundle。
|
||||
* 端到端已由 _adapter-probe 真九门验证(commit 046454d9)。
|
||||
*
|
||||
* 【边界】纯文本生成 buildFactorySource 零 fs(可零依赖单测);scaffoldFromSource 做 fs 编排(集成段用,需模板路径)。
|
||||
*/
|
||||
|
||||
import { writeFileSync, copyFileSync, mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/** 标准 game 目录(games/_wg1-gen/<game>/)到适配器的相对 import(与 entry-bundle 的 ../../../src 同深)。 */
|
||||
export const STD_ADAPTER_IMPORT = '../../../src/adapter/create-game-from-definition.js';
|
||||
|
||||
/**
|
||||
* 生成 generated-factory.js 文本:适配器 import + 内联 SourceProject + 默认导出 GameHostFactory。
|
||||
* @param {object} sourceProject SourceProject(source-project.schema.json)。
|
||||
* @param {object} [opts] { adapterImport?:string } 适配器相对 import(默认标准深度)。
|
||||
* @returns {string} generated-factory.js 文件内容。
|
||||
*/
|
||||
export function buildFactorySource(sourceProject, opts = {}) {
|
||||
const adapterImport = opts.adapterImport || STD_ADAPTER_IMPORT;
|
||||
// 内联 source 保证 esbuild 自包含打包(无运行期外部 JSON 请求)。2 空格缩进,可读 + diff 友好。
|
||||
const json = JSON.stringify(sourceProject, null, 2);
|
||||
return [
|
||||
"'use strict';",
|
||||
'// 自动生成(E2 scaffold-from-source.mjs):SourceProject → GameHostFactory。勿手改,改源重生成。',
|
||||
`import { adaptGameDefinition } from '${adapterImport}';`,
|
||||
`const SOURCE = ${json};`,
|
||||
'export default adaptGameDefinition(SOURCE);',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 SourceProject 落成可构建 game 目录:写 generated-factory.js + 拷 entry-bundle/index 模板。
|
||||
* @param {object} args
|
||||
* @param {object} args.sourceProject SourceProject。
|
||||
* @param {string} args.gameDir 目标 game 目录绝对路径(须为 games/_wg1-gen/<x>/ 标准深度,否则传 opts.adapterImport)。
|
||||
* @param {string} args.sharedDir _shared 目录绝对路径(取 entry-bundle.template.js / index.template.html)。
|
||||
* @param {object} [args.opts] 透传 buildFactorySource opts。
|
||||
* @returns {string} build.mjs 入口路径(gameDir/entry-bundle.js)。
|
||||
*/
|
||||
export function scaffoldFromSource({ sourceProject, gameDir, sharedDir, opts }) {
|
||||
mkdirSync(gameDir, { recursive: true });
|
||||
writeFileSync(join(gameDir, 'generated-factory.js'), buildFactorySource(sourceProject, opts || {}));
|
||||
copyFileSync(join(sharedDir, 'entry-bundle.template.js'), join(gameDir, 'entry-bundle.js'));
|
||||
copyFileSync(join(sharedDir, 'index.template.html'), join(gameDir, 'index.html'));
|
||||
return join(gameDir, 'entry-bundle.js'); // → node scripts/build.mjs <此> <out> --global-name=__GameBundle
|
||||
}
|
||||
41
game-runtime/src/adapter/scaffold.test.mjs
Normal file
41
game-runtime/src/adapter/scaffold.test.mjs
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* scaffold.test.mjs —— E2 buildFactorySource 零依赖 node 单测(node --test)
|
||||
* owner:前端+引擎线(Mac)|计划 002 U2。验「SourceProject → generated-factory.js 文本」工件正确。
|
||||
*
|
||||
* 注:scaffoldFromSource 的 fs 编排 + build.mjs 全链是集成段(需 esbuild),已由 _adapter-probe 真九门验证(commit 046454d9);
|
||||
* 本单测只覆盖纯文本生成 buildFactorySource(零 fs/零 esbuild,守 package.json「lane 本机零依赖只跑 node --test」)。
|
||||
*/
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
import { buildFactorySource, STD_ADAPTER_IMPORT } from './scaffold-from-source.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const SOURCE = JSON.parse(readFileSync(join(HERE, 'fixtures', 'tap-target.source.json'), 'utf8'));
|
||||
|
||||
test('buildFactorySource 产合契约 generated-factory 文本', () => {
|
||||
const src = buildFactorySource(SOURCE);
|
||||
// 适配器 import(标准深度)+ 默认导出工厂 + 内联 SOURCE。
|
||||
assert.ok(src.includes(`import { adaptGameDefinition } from '${STD_ADAPTER_IMPORT}'`), '应含适配器 import');
|
||||
assert.ok(src.includes('export default adaptGameDefinition(SOURCE)'), '应默认导出工厂');
|
||||
assert.ok(src.includes('const SOURCE ='), '应内联 SOURCE 常量');
|
||||
});
|
||||
|
||||
test('内联 SOURCE 可往返 parse 且与原 SourceProject 深等', () => {
|
||||
const src = buildFactorySource(SOURCE);
|
||||
// 抽出 "const SOURCE = {...};" 的 JSON 体,JSON.parse 往返应与原 source 深等(无丢字段/无污染)。
|
||||
const m = src.match(/const SOURCE = ([\s\S]*?);\nexport default/);
|
||||
assert.ok(m, '应能定位内联 SOURCE 字面量');
|
||||
const roundTrip = JSON.parse(m[1]);
|
||||
assert.deepEqual(roundTrip, SOURCE, '内联 source 应与原 SourceProject 深等');
|
||||
});
|
||||
|
||||
test('adapterImport 可覆盖(非标准深度 game 目录)', () => {
|
||||
const src = buildFactorySource(SOURCE, { adapterImport: '../../src/adapter/create-game-from-definition.js' });
|
||||
assert.ok(src.includes(`from '../../src/adapter/create-game-from-definition.js'`), '应用覆盖的 import 路径');
|
||||
assert.ok(!src.includes(STD_ADAPTER_IMPORT), '不应再含标准路径');
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user