create-eigen-game 0.1.0 → 0.2.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.
@@ -3,7 +3,10 @@ import { execFileSync } from "child_process";
3
3
  import { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
4
4
  import { basename, dirname, resolve } from "path";
5
5
  import { fileURLToPath } from "url";
6
- var templatesRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../templates");
6
+ var packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7
+ var templatesRoot = resolve(packageRoot, "templates");
8
+ var engineVersion = `^${JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")).version}`;
9
+ var flutterClientVersion = "^0.1.0";
7
10
  var gameSlug = (value) => {
8
11
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
9
12
  throw new Error("destination directory name must be a lowercase kebab-case slug, for example my-game");
@@ -25,6 +28,7 @@ function render(contents, name, manager) {
25
28
  const replacements = [
26
29
  ["@game/example-game-server", `@game/${name}-server`],
27
30
  ["{{PACKAGE_MANAGER}}", manager],
31
+ ["{{ENGINE_VERSION}}", engineVersion],
28
32
  ["ExampleGame", identifier(name)],
29
33
  ["Example Game", title(name)],
30
34
  ["example_game", dartName(name)],
@@ -105,7 +109,7 @@ function scaffoldGame(options) {
105
109
  }
106
110
  renderTree(resolve(templatesRoot, "app-overlay"), appRoot, name, manager);
107
111
  if (bootstrap) {
108
- run("flutter", ["pub", "add", "eigen_flutter@^0.1.0", "firebase_core@^4.9.0", "firebase_messaging@^16.2.2"], appRoot);
112
+ run("flutter", ["pub", "add", `eigen_flutter@${flutterClientVersion}`, "firebase_core@^4.9.0", "firebase_messaging@^16.2.2"], appRoot);
109
113
  const [install, installArgs] = packageCommand(manager, "install");
110
114
  run(install, installArgs, serverRoot);
111
115
  const [contract, contractArgs] = packageCommand(manager, "contract");
@@ -124,4 +128,4 @@ export {
124
128
  detectPackageManager,
125
129
  scaffoldGame
126
130
  };
127
- //# sourceMappingURL=chunk-BSJVRXI4.js.map
131
+ //# sourceMappingURL=chunk-KIW4QEXQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type PackageManager = \"npm\" | \"pnpm\";\n\nexport interface ScaffoldOptions {\n directory: string;\n org?: string;\n packageManager?: PackageManager;\n /**\n * Run Flutter, package installation, and contract/payload generation.\n * Kept as a programmatic test seam; the public CLI always bootstraps both\n * halves of the combined project.\n */\n bootstrap?: boolean;\n run?: (command: string, args: string[], cwd: string) => void;\n}\n\nexport interface ScaffoldResult {\n root: string;\n name: string;\n}\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst templatesRoot = resolve(packageRoot, \"templates\");\n\n/**\n * The engine range emitted into a scaffolded project's package.json.\n *\n * Derived from this package's OWN version, which is meaningful only because\n * `create-eigen-game` is a member of the `fixed` group in\n * .changeset/config.json: it carries the same version as\n * @eigeninteractive/rules, kernel, server and testkit. That makes the pin\n * incapable of drifting, because there is no second number anyone could forget\n * to bump — as opposed to a literal in the template, which silently keeps\n * scaffolding the previous engine after a release.\n *\n * It also leaves older scaffolders self-consistent rather than broken:\n * `create-eigen-game@0.1.0` emits `^0.1.0`, which still resolves. `npm create`\n * takes the latest by default, so this only matters to someone who pinned\n * deliberately — and they get a coherent pairing.\n */\nconst engineVersion = `^${(JSON.parse(readFileSync(resolve(packageRoot, \"package.json\"), \"utf8\")) as { version: string }).version}`;\n\n/**\n * The Flutter client range installed into the app half.\n *\n * Unlike the engine range above this CANNOT be derived. `eigen_flutter` lives\n * in the eigen-flutter repository and versions independently, so nothing in\n * this workspace knows its current version. Update it deliberately when a\n * compatible client ships; the pairing is documented at\n * https://eigeninteractive.com/docs/reference/compatibility.\n */\nconst flutterClientVersion = \"^0.1.0\";\n\nconst gameSlug = (value: string): string => {\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {\n throw new Error(\"destination directory name must be a lowercase kebab-case slug, for example my-game\");\n }\n return value;\n};\n\nconst dartName = (value: string): string => value.replaceAll(\"-\", \"_\");\n\nconst title = (value: string): string =>\n value\n .split(\"-\")\n .map((part) => `${part[0]?.toUpperCase() ?? \"\"}${part.slice(1)}`)\n .join(\" \");\n\nconst identifier = (value: string): string => {\n const pascal = value\n .split(\"-\")\n .map((part) => `${part[0]?.toUpperCase() ?? \"\"}${part.slice(1)}`)\n .join(\"\");\n return /^[0-9]/.test(pascal) ? `Game${pascal}` : pascal;\n};\n\nexport function detectPackageManager(userAgent = process.env.npm_config_user_agent): PackageManager | undefined {\n if (userAgent?.startsWith(\"pnpm/\")) return \"pnpm\";\n if (userAgent?.startsWith(\"npm/\")) return \"npm\";\n return undefined;\n}\n\nfunction render(contents: string, name: string, manager: PackageManager): string {\n const replacements: ReadonlyArray<readonly [string, string]> = [\n [\"@game/example-game-server\", `@game/${name}-server`],\n [\"{{PACKAGE_MANAGER}}\", manager],\n [\"{{ENGINE_VERSION}}\", engineVersion],\n [\"ExampleGame\", identifier(name)],\n [\"Example Game\", title(name)],\n [\"example_game\", dartName(name)],\n [\"example-game\", name],\n ];\n return replacements.reduce((result, [from, to]) => result.replaceAll(from, to), contents);\n}\n\nfunction renderTree(source: string, destination: string, name: string, manager: PackageManager): void {\n cpSync(source, destination, { recursive: true });\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const path = resolve(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else if (entry.isFile()) {\n writeFileSync(path, render(readFileSync(path, \"utf8\"), name, manager));\n // Template-only source must not be claimed by language servers before\n // it has an enclosing generated package. Restore the real extension\n // only in the rendered project.\n if (entry.name.endsWith(\".template\")) {\n renameSync(path, path.slice(0, -\".template\".length));\n }\n }\n }\n };\n visit(destination);\n\n // npm deliberately excludes files named `.gitignore` from package\n // tarballs. Their packaged twins live outside the C3 template so a direct\n // Git-based C3 render does not inherit a scaffolder-only helper file.\n const packagedGitignore = resolve(templatesRoot, \"scaffold\", `${basename(source)}.gitignore`);\n if (existsSync(packagedGitignore)) {\n writeFileSync(resolve(destination, \".gitignore\"), render(readFileSync(packagedGitignore, \"utf8\"), name, manager));\n }\n}\n\nfunction packageCommand(manager: PackageManager, operation: \"install\" | \"contract\"): [string, string[]] {\n if (operation === \"install\") return [manager, [\"install\"]];\n return [manager, [\"run\", \"contract\"]];\n}\n\nconst androidDesugaring = `// flutter_local_notifications requires desugaring in the application module.\n// A library plugin cannot enable this compiler setting transitively.\nandroid {\n compileOptions {\n isCoreLibraryDesugaringEnabled = true\n }\n}\n\ndependencies {\n coreLibraryDesugaring(\"com.android.tools:desugar_jdk_libs:2.1.4\")\n}`;\n\nfunction enableAndroidCoreLibraryDesugaring(appRoot: string): void {\n const gradlePath = resolve(appRoot, \"android/app/build.gradle.kts\");\n const gradle = readFileSync(gradlePath, \"utf8\");\n if (!gradle.includes('id(\"com.android.application\")')) {\n throw new Error(`Flutter created an unexpected Android application build file: ${gradlePath}`);\n }\n const settingExists = gradle.includes(\"isCoreLibraryDesugaringEnabled\");\n const dependencyExists = gradle.includes(\"coreLibraryDesugaring(\");\n if (settingExists && dependencyExists) return;\n if (settingExists || dependencyExists) {\n throw new Error(`Flutter created an incomplete Android core library desugaring configuration: ${gradlePath}`);\n }\n appendFileSync(gradlePath, `${gradle.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\"}${androidDesugaring}\\n`);\n}\n\nexport function scaffoldGame(options: ScaffoldOptions): ScaffoldResult {\n const root = resolve(options.directory);\n const name = gameSlug(basename(root));\n const manager = options.packageManager ?? detectPackageManager() ?? \"pnpm\";\n const org = options.org?.trim() || \"com.example\";\n const bootstrap = options.bootstrap ?? true;\n const run = options.run ?? ((command, args, cwd) => execFileSync(command, args, { cwd, stdio: \"inherit\" }));\n\n if (existsSync(root)) throw new Error(`target already exists: ${root}`);\n mkdirSync(dirname(root), { recursive: true });\n\n // Build beside the destination and publish it with one rename. Failed\n // subprocesses never leave a half-scaffolded project at the requested path.\n const stagingRoot = mkdtempSync(resolve(dirname(root), `.${basename(root)}-`));\n try {\n const serverRoot = resolve(stagingRoot, \"server\");\n const appRoot = resolve(stagingRoot, \"app\");\n\n renderTree(resolve(templatesRoot, \"worker\"), serverRoot, name, manager);\n renderTree(resolve(templatesRoot, \"project\"), stagingRoot, name, manager);\n\n if (bootstrap) {\n run(\"flutter\", [\"create\", \"--empty\", \"--platforms\", \"android,web\", \"--project-name\", dartName(name), \"--org\", org, appRoot], stagingRoot);\n enableAndroidCoreLibraryDesugaring(appRoot);\n } else {\n mkdirSync(appRoot, { recursive: true });\n }\n renderTree(resolve(templatesRoot, \"app-overlay\"), appRoot, name, manager);\n\n if (bootstrap) {\n run(\"flutter\", [\"pub\", \"add\", `eigen_flutter@${flutterClientVersion}`, \"firebase_core@^4.9.0\", \"firebase_messaging@^16.2.2\"], appRoot);\n const [install, installArgs] = packageCommand(manager, \"install\");\n run(install, installArgs, serverRoot);\n const [contract, contractArgs] = packageCommand(manager, \"contract\");\n run(contract, contractArgs, serverRoot);\n run(\"dart\", [\"run\", \"eigen_flutter:generate_payloads\", \"--contract\", \"../server/game-contract.json\", \"--output\", \"lib/game/generated/payloads.dart\", \"--fixtures-output\", \"test/fixtures\"], appRoot);\n }\n\n renameSync(stagingRoot, root);\n return { root, name };\n } catch (error) {\n rmSync(stagingRoot, { recursive: true, force: true });\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  detectPackageManager,
4
4
  scaffoldGame
5
- } from "./chunk-BSJVRXI4.js";
5
+ } from "./chunk-KIW4QEXQ.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { parseArgs } from "util";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  detectPackageManager,
3
3
  scaffoldGame
4
- } from "./chunk-BSJVRXI4.js";
4
+ } from "./chunk-KIW4QEXQ.js";
5
5
  export {
6
6
  detectPackageManager,
7
7
  scaffoldGame
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-eigen-game",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Scaffold an Eigen game Worker and Flutter app without cloning the engine repositories.",
6
6
  "type": "module",
@@ -25,9 +25,9 @@
25
25
  "vitest": "^4.1.10",
26
26
  "wrangler": "^4.110.0",
27
27
  "zod": "^4.4.3",
28
- "@eigeninteractive/rules": "0.1.0",
29
- "@eigeninteractive/testkit": "0.1.0",
30
- "@eigeninteractive/server": "0.1.0"
28
+ "@eigeninteractive/rules": "0.2.0",
29
+ "@eigeninteractive/server": "0.2.0",
30
+ "@eigeninteractive/testkit": "0.2.0"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=22"
@@ -37,12 +37,12 @@
37
37
  "typecheck": "wrangler types && tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@eigeninteractive/rules": "^0.1.0",
41
- "@eigeninteractive/server": "^0.1.0",
40
+ "@eigeninteractive/rules": "{{ENGINE_VERSION}}",
41
+ "@eigeninteractive/server": "{{ENGINE_VERSION}}",
42
42
  "zod": "^4.4.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@eigeninteractive/testkit": "^0.1.0",
45
+ "@eigeninteractive/testkit": "{{ENGINE_VERSION}}",
46
46
  "@types/node": "^26.1.1",
47
47
  "typescript": "^5.9.3",
48
48
  "vitest": "^4.1.10",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type PackageManager = \"npm\" | \"pnpm\";\n\nexport interface ScaffoldOptions {\n directory: string;\n org?: string;\n packageManager?: PackageManager;\n /**\n * Run Flutter, package installation, and contract/payload generation.\n * Kept as a programmatic test seam; the public CLI always bootstraps both\n * halves of the combined project.\n */\n bootstrap?: boolean;\n run?: (command: string, args: string[], cwd: string) => void;\n}\n\nexport interface ScaffoldResult {\n root: string;\n name: string;\n}\n\nconst templatesRoot = resolve(dirname(fileURLToPath(import.meta.url)), \"../templates\");\n\nconst gameSlug = (value: string): string => {\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {\n throw new Error(\"destination directory name must be a lowercase kebab-case slug, for example my-game\");\n }\n return value;\n};\n\nconst dartName = (value: string): string => value.replaceAll(\"-\", \"_\");\n\nconst title = (value: string): string =>\n value\n .split(\"-\")\n .map((part) => `${part[0]?.toUpperCase() ?? \"\"}${part.slice(1)}`)\n .join(\" \");\n\nconst identifier = (value: string): string => {\n const pascal = value\n .split(\"-\")\n .map((part) => `${part[0]?.toUpperCase() ?? \"\"}${part.slice(1)}`)\n .join(\"\");\n return /^[0-9]/.test(pascal) ? `Game${pascal}` : pascal;\n};\n\nexport function detectPackageManager(userAgent = process.env.npm_config_user_agent): PackageManager | undefined {\n if (userAgent?.startsWith(\"pnpm/\")) return \"pnpm\";\n if (userAgent?.startsWith(\"npm/\")) return \"npm\";\n return undefined;\n}\n\nfunction render(contents: string, name: string, manager: PackageManager): string {\n const replacements: ReadonlyArray<readonly [string, string]> = [\n [\"@game/example-game-server\", `@game/${name}-server`],\n [\"{{PACKAGE_MANAGER}}\", manager],\n [\"ExampleGame\", identifier(name)],\n [\"Example Game\", title(name)],\n [\"example_game\", dartName(name)],\n [\"example-game\", name],\n ];\n return replacements.reduce((result, [from, to]) => result.replaceAll(from, to), contents);\n}\n\nfunction renderTree(source: string, destination: string, name: string, manager: PackageManager): void {\n cpSync(source, destination, { recursive: true });\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n const path = resolve(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else if (entry.isFile()) {\n writeFileSync(path, render(readFileSync(path, \"utf8\"), name, manager));\n // Template-only source must not be claimed by language servers before\n // it has an enclosing generated package. Restore the real extension\n // only in the rendered project.\n if (entry.name.endsWith(\".template\")) {\n renameSync(path, path.slice(0, -\".template\".length));\n }\n }\n }\n };\n visit(destination);\n\n // npm deliberately excludes files named `.gitignore` from package\n // tarballs. Their packaged twins live outside the C3 template so a direct\n // Git-based C3 render does not inherit a scaffolder-only helper file.\n const packagedGitignore = resolve(templatesRoot, \"scaffold\", `${basename(source)}.gitignore`);\n if (existsSync(packagedGitignore)) {\n writeFileSync(resolve(destination, \".gitignore\"), render(readFileSync(packagedGitignore, \"utf8\"), name, manager));\n }\n}\n\nfunction packageCommand(manager: PackageManager, operation: \"install\" | \"contract\"): [string, string[]] {\n if (operation === \"install\") return [manager, [\"install\"]];\n return [manager, [\"run\", \"contract\"]];\n}\n\nconst androidDesugaring = `// flutter_local_notifications requires desugaring in the application module.\n// A library plugin cannot enable this compiler setting transitively.\nandroid {\n compileOptions {\n isCoreLibraryDesugaringEnabled = true\n }\n}\n\ndependencies {\n coreLibraryDesugaring(\"com.android.tools:desugar_jdk_libs:2.1.4\")\n}`;\n\nfunction enableAndroidCoreLibraryDesugaring(appRoot: string): void {\n const gradlePath = resolve(appRoot, \"android/app/build.gradle.kts\");\n const gradle = readFileSync(gradlePath, \"utf8\");\n if (!gradle.includes('id(\"com.android.application\")')) {\n throw new Error(`Flutter created an unexpected Android application build file: ${gradlePath}`);\n }\n const settingExists = gradle.includes(\"isCoreLibraryDesugaringEnabled\");\n const dependencyExists = gradle.includes(\"coreLibraryDesugaring(\");\n if (settingExists && dependencyExists) return;\n if (settingExists || dependencyExists) {\n throw new Error(`Flutter created an incomplete Android core library desugaring configuration: ${gradlePath}`);\n }\n appendFileSync(gradlePath, `${gradle.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\"}${androidDesugaring}\\n`);\n}\n\nexport function scaffoldGame(options: ScaffoldOptions): ScaffoldResult {\n const root = resolve(options.directory);\n const name = gameSlug(basename(root));\n const manager = options.packageManager ?? detectPackageManager() ?? \"pnpm\";\n const org = options.org?.trim() || \"com.example\";\n const bootstrap = options.bootstrap ?? true;\n const run = options.run ?? ((command, args, cwd) => execFileSync(command, args, { cwd, stdio: \"inherit\" }));\n\n if (existsSync(root)) throw new Error(`target already exists: ${root}`);\n mkdirSync(dirname(root), { recursive: true });\n\n // Build beside the destination and publish it with one rename. Failed\n // subprocesses never leave a half-scaffolded project at the requested path.\n const stagingRoot = mkdtempSync(resolve(dirname(root), `.${basename(root)}-`));\n try {\n const serverRoot = resolve(stagingRoot, \"server\");\n const appRoot = resolve(stagingRoot, \"app\");\n\n renderTree(resolve(templatesRoot, \"worker\"), serverRoot, name, manager);\n renderTree(resolve(templatesRoot, \"project\"), stagingRoot, name, manager);\n\n if (bootstrap) {\n run(\"flutter\", [\"create\", \"--empty\", \"--platforms\", \"android,web\", \"--project-name\", dartName(name), \"--org\", org, appRoot], stagingRoot);\n enableAndroidCoreLibraryDesugaring(appRoot);\n } else {\n mkdirSync(appRoot, { recursive: true });\n }\n renderTree(resolve(templatesRoot, \"app-overlay\"), appRoot, name, manager);\n\n if (bootstrap) {\n run(\"flutter\", [\"pub\", \"add\", \"eigen_flutter@^0.1.0\", \"firebase_core@^4.9.0\", \"firebase_messaging@^16.2.2\"], appRoot);\n const [install, installArgs] = packageCommand(manager, \"install\");\n run(install, installArgs, serverRoot);\n const [contract, contractArgs] = packageCommand(manager, \"contract\");\n run(contract, contractArgs, serverRoot);\n run(\"dart\", [\"run\", \"eigen_flutter:generate_payloads\", \"--contract\", \"../server/game-contract.json\", \"--output\", \"lib/game/generated/payloads.dart\", \"--fixtures-output\", \"test/fixtures\"], appRoot);\n }\n\n renameSync(stagingRoot, root);\n return { root, name };\n } catch (error) {\n rmSync(stagingRoot, { recursive: true, force: true });\n throw error;\n }\n}\n"],"mappings":"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","names":[]}