diff --git a/cocoscreator_projects/scripts/new-game.mjs b/cocoscreator_projects/scripts/new-game.mjs new file mode 100644 index 0000000..a19e2c8 --- /dev/null +++ b/cocoscreator_projects/scripts/new-game.mjs @@ -0,0 +1,40 @@ +#!/usr/bin/env node +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { SEED_DIR, GAMES_DIR, FRAMEWORK_SRC } from './lib/paths.mjs'; +import { copySeed, setProjectIdentity, scaffoldGameAssets } from './lib/scaffold.mjs'; +import { ensureJunction } from './lib/junction.mjs'; + +const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/; + +// 新建一款子游戏工程。opts 可注入路径用于测试。 +export function newGame(name, opts = {}) { + const seedDir = opts.seedDir ?? SEED_DIR; + const gamesDir = opts.gamesDir ?? GAMES_DIR; + const frameworkSrc = opts.frameworkSrc ?? FRAMEWORK_SRC; + + if (!name || !NAME_RE.test(name)) { + throw new Error(`非法子游戏名称: "${name}"(仅限小写字母/数字/-/_,且以字母数字开头)`); + } + const projectDir = join(gamesDir, name); + + copySeed(seedDir, projectDir); // 目标已存在会抛错 + setProjectIdentity(projectDir, name); + scaffoldGameAssets(projectDir, name); + const link = join(projectDir, 'assets', 'framework'); + ensureJunction(link, frameworkSrc); + + return { projectDir, link }; +} + +// CLI 入口 +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const name = process.argv[2]; + if (!name) { + console.error('用法: npm run new-game '); + process.exit(1); + } + const { projectDir } = newGame(name); + console.log(`已创建子游戏工程: ${projectDir}`); + console.log(`下一步: 用 Cocos Creator 打开该目录`); +} diff --git a/cocoscreator_projects/scripts/test/new-game.test.mjs b/cocoscreator_projects/scripts/test/new-game.test.mjs new file mode 100644 index 0000000..5113703 --- /dev/null +++ b/cocoscreator_projects/scripts/test/new-game.test.mjs @@ -0,0 +1,50 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { join } from 'node:path'; +import { existsSync, readFileSync } from 'node:fs'; +import { makeTempMonorepo, makeSeed, cleanup } from './helpers.mjs'; +import { newGame } from '../new-game.mjs'; +import { isLink } from '../lib/junction.mjs'; + +test('newGame 生成工程:改身份 + 挂 framework junction + 铺 game 骨架', () => { + const mono = makeTempMonorepo(); + try { + const seed = makeSeed(mono.root); + const result = newGame('doudizhu', { seedDir: seed, gamesDir: mono.games, frameworkSrc: mono.framework }); + const dest = join(mono.games, 'doudizhu'); + assert.equal(result.projectDir, dest); + const pkg = JSON.parse(readFileSync(join(dest, 'package.json'), 'utf8')); + assert.equal(pkg.name, 'doudizhu'); + assert.notEqual(pkg.uuid, 'seed-uuid'); + const link = join(dest, 'assets', 'framework'); + assert.ok(isLink(link)); + assert.equal(readFileSync(join(link, 'README.md'), 'utf8'), '# framework src\n'); + assert.ok(existsSync(join(dest, 'assets', 'game', 'GameModule.ts'))); + } finally { + cleanup(mono.root); + } +}); + +test('newGame 拒绝重名工程', () => { + const mono = makeTempMonorepo(); + try { + const seed = makeSeed(mono.root); + const opts = { seedDir: seed, gamesDir: mono.games, frameworkSrc: mono.framework }; + newGame('doudizhu', opts); + assert.throws(() => newGame('doudizhu', opts), /已存在/); + } finally { + cleanup(mono.root); + } +}); + +test('newGame 校验名称非空且合法', () => { + const mono = makeTempMonorepo(); + try { + const seed = makeSeed(mono.root); + const opts = { seedDir: seed, gamesDir: mono.games, frameworkSrc: mono.framework }; + assert.throws(() => newGame('', opts), /名称/); + assert.throws(() => newGame('Bad Name!', opts), /名称/); + } finally { + cleanup(mono.root); + } +});