@flare-engine/create 0.4.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.
- package/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +43 -0
- package/dist/bin.js +10 -0
- package/dist/bin.js.map +1 -0
- package/dist/bin.mjs +12 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/cli-BGXZ73ie.mjs +189 -0
- package/dist/cli-BGXZ73ie.mjs.map +1 -0
- package/dist/cli-JzwtwZhm.js +230 -0
- package/dist/cli-JzwtwZhm.js.map +1 -0
- package/dist/index.d.mts +101 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.mjs +2 -0
- package/package.json +61 -0
- package/templates/blank/README.md +59 -0
- package/templates/blank/app.json +10 -0
- package/templates/blank/babel.config.js +7 -0
- package/templates/blank/gitignore +8 -0
- package/templates/blank/index.ts +4 -0
- package/templates/blank/index.web.ts +17 -0
- package/templates/blank/metro.config.js +3 -0
- package/templates/blank/package.json +36 -0
- package/templates/blank/scripts/copy-canvaskit.mjs +15 -0
- package/templates/blank/src/App.tsx +45 -0
- package/templates/blank/tsconfig.json +15 -0
- package/templates/shmup/README.md +59 -0
- package/templates/shmup/app.json +10 -0
- package/templates/shmup/babel.config.js +7 -0
- package/templates/shmup/gitignore +8 -0
- package/templates/shmup/index.ts +4 -0
- package/templates/shmup/index.web.ts +17 -0
- package/templates/shmup/metro.config.js +3 -0
- package/templates/shmup/package.json +39 -0
- package/templates/shmup/scripts/copy-canvaskit.mjs +15 -0
- package/templates/shmup/src/App.tsx +171 -0
- package/templates/shmup/src/logic.ts +356 -0
- package/templates/shmup/tests/logic.test.ts +235 -0
- package/templates/shmup/tsconfig.json +15 -0
- package/templates/topdown/README.md +79 -0
- package/templates/topdown/app.json +10 -0
- package/templates/topdown/babel.config.js +7 -0
- package/templates/topdown/gitignore +8 -0
- package/templates/topdown/index.ts +4 -0
- package/templates/topdown/index.web.ts +17 -0
- package/templates/topdown/metro.config.js +3 -0
- package/templates/topdown/package.json +39 -0
- package/templates/topdown/scripts/copy-canvaskit.mjs +15 -0
- package/templates/topdown/src/App.tsx +138 -0
- package/templates/topdown/src/map.ts +126 -0
- package/templates/topdown/tests/tilemap.test.ts +201 -0
- package/templates/topdown/tsconfig.json +15 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
let commander = require("commander");
|
|
2
|
+
let node_child_process = require("node:child_process");
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let node_url = require("node:url");
|
|
6
|
+
//#region package.json
|
|
7
|
+
var version = "0.4.0";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/manifest.ts
|
|
10
|
+
/** Built-in templates shipped inside `@flare-engine/create`. */
|
|
11
|
+
const TEMPLATES = {
|
|
12
|
+
blank: {
|
|
13
|
+
id: "blank",
|
|
14
|
+
displayName: "Blank",
|
|
15
|
+
description: "Empty scene with a single sprite — smallest starting point.",
|
|
16
|
+
engines: { node: ">=20" },
|
|
17
|
+
postInstall: "bun run start"
|
|
18
|
+
},
|
|
19
|
+
topdown: {
|
|
20
|
+
id: "topdown",
|
|
21
|
+
displayName: "Top-down",
|
|
22
|
+
description: "Analog player movement with tilemap collision + camera follow.",
|
|
23
|
+
engines: { node: ">=20" },
|
|
24
|
+
postInstall: "bun run start"
|
|
25
|
+
},
|
|
26
|
+
shmup: {
|
|
27
|
+
id: "shmup",
|
|
28
|
+
displayName: "Shoot-em-up",
|
|
29
|
+
description: "Auto-fire player, enemy spawner, HUD score — mirrors a minimum vertical-slice shmup.",
|
|
30
|
+
engines: { node: ">=20" },
|
|
31
|
+
postInstall: "bun run start"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function isTemplateId(value) {
|
|
35
|
+
return Object.hasOwn(TEMPLATES, value);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/scaffold.ts
|
|
39
|
+
const DEFAULT_TEMPLATES_DIR = (0, node_path.resolve)((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)), "..", "templates");
|
|
40
|
+
const VALID_APP_NAME = /^[a-z0-9@/_-]+$/i;
|
|
41
|
+
/** Returns true when the name is safe to use as an app name and shell argument. */
|
|
42
|
+
function isValidAppName(name) {
|
|
43
|
+
return VALID_APP_NAME.test(name);
|
|
44
|
+
}
|
|
45
|
+
/** Detect the package manager from `--package-manager` > `npm_config_user_agent` > `bun`. */
|
|
46
|
+
function detectPackageManager(explicit) {
|
|
47
|
+
if (explicit === "bun" || explicit === "npm" || explicit === "pnpm") return explicit;
|
|
48
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
49
|
+
if (agent.startsWith("bun")) return "bun";
|
|
50
|
+
if (agent.startsWith("pnpm")) return "pnpm";
|
|
51
|
+
if (agent.startsWith("npm") || agent.startsWith("yarn")) return "npm";
|
|
52
|
+
return "bun";
|
|
53
|
+
}
|
|
54
|
+
async function pathExists(p) {
|
|
55
|
+
try {
|
|
56
|
+
await (0, node_fs_promises.stat)(p);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const TOKEN_FILE_EXTS = /* @__PURE__ */ new Set([
|
|
63
|
+
".ts",
|
|
64
|
+
".tsx",
|
|
65
|
+
".js",
|
|
66
|
+
".jsx",
|
|
67
|
+
".json",
|
|
68
|
+
".md",
|
|
69
|
+
".txt",
|
|
70
|
+
".html",
|
|
71
|
+
".css"
|
|
72
|
+
]);
|
|
73
|
+
function shouldApplyTokens(path) {
|
|
74
|
+
const dot = path.lastIndexOf(".");
|
|
75
|
+
if (dot < 0) return false;
|
|
76
|
+
return TOKEN_FILE_EXTS.has(path.slice(dot).toLowerCase());
|
|
77
|
+
}
|
|
78
|
+
function replaceTokens(content, tokens) {
|
|
79
|
+
let out = content;
|
|
80
|
+
for (const [k, v] of Object.entries(tokens)) out = out.split(k).join(v);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
async function walkFiles(dir, base, out) {
|
|
84
|
+
const entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
|
|
85
|
+
for (const e of entries) {
|
|
86
|
+
const full = (0, node_path.join)(dir, e.name);
|
|
87
|
+
const rel = full.slice(base.length + 1);
|
|
88
|
+
if (e.isDirectory()) await walkFiles(full, base, out);
|
|
89
|
+
else if (e.isFile()) out.push(rel);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Returns the spawn configuration for the given package manager on the given platform. */
|
|
93
|
+
function installSpawnSpec(pm, platform) {
|
|
94
|
+
return {
|
|
95
|
+
cmd: pm,
|
|
96
|
+
shell: platform === "win32"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function runInstall(pm, cwd) {
|
|
100
|
+
return new Promise((resolvePromise) => {
|
|
101
|
+
const { cmd, shell } = installSpawnSpec(pm, process.platform);
|
|
102
|
+
const child = (0, node_child_process.spawn)(cmd, ["install"], {
|
|
103
|
+
cwd,
|
|
104
|
+
stdio: "inherit",
|
|
105
|
+
shell
|
|
106
|
+
});
|
|
107
|
+
child.on("exit", (code) => resolvePromise(code ?? 1));
|
|
108
|
+
child.on("error", () => resolvePromise(1));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Copy the chosen template into `targetDir`, apply token replacements, and
|
|
113
|
+
* optionally run the package manager's install. Fails if the target exists
|
|
114
|
+
* and is non-empty.
|
|
115
|
+
*/
|
|
116
|
+
async function scaffoldApp(opts) {
|
|
117
|
+
if (!isValidAppName(opts.name)) throw new Error(`[flare-create] invalid app name: ${opts.name}`);
|
|
118
|
+
const templateId = opts.template ?? "blank";
|
|
119
|
+
if (!isTemplateId(templateId)) throw new Error(`[flare-create] unknown template: ${templateId}`);
|
|
120
|
+
if (!TEMPLATES[templateId]) throw new Error(`[flare-create] missing manifest for ${templateId}`);
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
const target = opts.targetDir ? (0, node_path.isAbsolute)(opts.targetDir) ? opts.targetDir : (0, node_path.resolve)(cwd, opts.targetDir) : (0, node_path.resolve)(cwd, opts.name);
|
|
123
|
+
if (await pathExists(target)) {
|
|
124
|
+
if ((await (0, node_fs_promises.readdir)(target)).length > 0) throw new Error(`[flare-create] target ${target} already exists and is not empty`);
|
|
125
|
+
} else await (0, node_fs_promises.mkdir)(target, { recursive: true });
|
|
126
|
+
const src = (0, node_path.join)(opts.templatesDir ?? DEFAULT_TEMPLATES_DIR, templateId);
|
|
127
|
+
if (!await pathExists(src)) throw new Error(`[flare-create] template directory not found: ${src}`);
|
|
128
|
+
await (0, node_fs_promises.cp)(src, target, { recursive: true });
|
|
129
|
+
const gi = (0, node_path.join)(target, "gitignore");
|
|
130
|
+
if (await pathExists(gi)) await (0, node_fs_promises.rename)(gi, (0, node_path.join)(target, ".gitignore"));
|
|
131
|
+
const tokens = {
|
|
132
|
+
"{{APP_NAME}}": opts.name,
|
|
133
|
+
"{{FLARE_VERSION}}": opts.flareVersion ?? `^${version}`,
|
|
134
|
+
"{{TEMPLATE_ID}}": templateId
|
|
135
|
+
};
|
|
136
|
+
const files = [];
|
|
137
|
+
await walkFiles(target, target, files);
|
|
138
|
+
for (const rel of files) {
|
|
139
|
+
if (!shouldApplyTokens(rel)) continue;
|
|
140
|
+
const p = (0, node_path.join)(target, rel);
|
|
141
|
+
const content = await (0, node_fs_promises.readFile)(p, "utf8");
|
|
142
|
+
const next = replaceTokens(content, tokens);
|
|
143
|
+
if (next !== content) await (0, node_fs_promises.writeFile)(p, next, "utf8");
|
|
144
|
+
}
|
|
145
|
+
const pm = detectPackageManager(opts.packageManager);
|
|
146
|
+
let installed = false;
|
|
147
|
+
if (!opts.skipInstall) {
|
|
148
|
+
const code = await runInstall(pm, target);
|
|
149
|
+
if (code !== 0) throw new Error(`[flare-create] ${pm} install exited with code ${code}`);
|
|
150
|
+
installed = true;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
targetDir: target,
|
|
154
|
+
template: templateId,
|
|
155
|
+
packageManager: pm,
|
|
156
|
+
installed
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/cli.ts
|
|
161
|
+
function parseTemplateArg(value) {
|
|
162
|
+
if (value !== "blank" && value !== "topdown" && value !== "shmup") throw new Error(`invalid --template: ${value} (expected blank|topdown|shmup)`);
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function parsePmArg(value) {
|
|
166
|
+
if (value !== "bun" && value !== "npm" && value !== "pnpm") throw new Error(`invalid --package-manager: ${value} (expected bun|npm|pnpm)`);
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
/** Build the `flare-create` CLI program. Exposed for tests. */
|
|
170
|
+
function buildProgram() {
|
|
171
|
+
const program = new commander.Command();
|
|
172
|
+
program.name("flare-create").description("Scaffold a new Flare engine app").version(version).argument("<name>", "directory name / package name for the new app").option("-t, --template <name>", "template (blank|topdown|shmup)", parseTemplateArg, "blank").option("-p, --package-manager <pm>", "package manager (bun|npm|pnpm)", parsePmArg).option("--skip-install", "skip running install after scaffolding", false).action(async (name, opts) => {
|
|
173
|
+
const result = await scaffoldApp({
|
|
174
|
+
name,
|
|
175
|
+
template: opts.template,
|
|
176
|
+
packageManager: opts.packageManager,
|
|
177
|
+
skipInstall: opts.skipInstall === true
|
|
178
|
+
});
|
|
179
|
+
const manifest = TEMPLATES[result.template];
|
|
180
|
+
process.stdout.write(`[flare-create] scaffolded ${manifest?.displayName ?? result.template} at ${result.targetDir}\n`);
|
|
181
|
+
if (!result.installed) process.stdout.write(`[flare-create] skipped install — run \`cd ${name} && ${result.packageManager} install\` when ready\n`);
|
|
182
|
+
else if (manifest?.postInstall) process.stdout.write(`[flare-create] next: cd ${name} && ${manifest.postInstall}\n`);
|
|
183
|
+
});
|
|
184
|
+
return program;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
Object.defineProperty(exports, "TEMPLATES", {
|
|
188
|
+
enumerable: true,
|
|
189
|
+
get: function() {
|
|
190
|
+
return TEMPLATES;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
Object.defineProperty(exports, "buildProgram", {
|
|
194
|
+
enumerable: true,
|
|
195
|
+
get: function() {
|
|
196
|
+
return buildProgram;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
Object.defineProperty(exports, "detectPackageManager", {
|
|
200
|
+
enumerable: true,
|
|
201
|
+
get: function() {
|
|
202
|
+
return detectPackageManager;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
Object.defineProperty(exports, "installSpawnSpec", {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
get: function() {
|
|
208
|
+
return installSpawnSpec;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, "isTemplateId", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function() {
|
|
214
|
+
return isTemplateId;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
Object.defineProperty(exports, "isValidAppName", {
|
|
218
|
+
enumerable: true,
|
|
219
|
+
get: function() {
|
|
220
|
+
return isValidAppName;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Object.defineProperty(exports, "scaffoldApp", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function() {
|
|
226
|
+
return scaffoldApp;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=cli-JzwtwZhm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-JzwtwZhm.js","names":["Command"],"sources":["../package.json","../src/manifest.ts","../src/scaffold.ts","../src/cli.ts"],"sourcesContent":["","/** Declared metadata for a `@flare-engine/create` template. */\nexport interface TemplateManifest {\n /** Template directory name (`blank`, `topdown`, `shmup`, ...). */\n id: string;\n /** Human-readable name shown in logs. */\n displayName: string;\n /** One-line description of what the template produces. */\n description: string;\n /** Engines required — mirrors Node/npm engines field in scaffolded package.json. */\n engines: { node: string };\n /** Optional post-install instructions printed after scaffold completes. */\n postInstall?: string;\n}\n\n/** Built-in templates shipped inside `@flare-engine/create`. */\nexport const TEMPLATES = {\n blank: {\n id: 'blank',\n displayName: 'Blank',\n description: 'Empty scene with a single sprite — smallest starting point.',\n engines: { node: '>=20' },\n postInstall: 'bun run start',\n },\n topdown: {\n id: 'topdown',\n displayName: 'Top-down',\n description: 'Analog player movement with tilemap collision + camera follow.',\n engines: { node: '>=20' },\n postInstall: 'bun run start',\n },\n shmup: {\n id: 'shmup',\n displayName: 'Shoot-em-up',\n description:\n 'Auto-fire player, enemy spawner, HUD score — mirrors a minimum vertical-slice shmup.',\n engines: { node: '>=20' },\n postInstall: 'bun run start',\n },\n} satisfies Record<string, TemplateManifest>;\n\nexport type TemplateId = keyof typeof TEMPLATES;\n\nexport function isTemplateId(value: string): value is TemplateId {\n return Object.hasOwn(TEMPLATES, value);\n}\n","import { spawn } from 'node:child_process';\nimport { cp, mkdir, readdir, readFile, rename, stat, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { version } from '../package.json';\nimport { isTemplateId, TEMPLATES, type TemplateId } from './manifest.js';\n\n/** Options for `scaffoldApp`. */\nexport interface ScaffoldOptions {\n /** App name — also used as the target directory and `name` in package.json. */\n name: string;\n /** Template id (default: `blank`). */\n template?: string;\n /** Destination directory (default: `<cwd>/<name>`). */\n targetDir?: string;\n /** Package manager used for `engines` + post-install command. */\n packageManager?: 'bun' | 'npm' | 'pnpm';\n /** Skip `install` after copying (default: false). */\n skipInstall?: boolean;\n /** Pinned Flare version for template {{FLARE_VERSION}} token (default: current package version prefixed with ^). */\n flareVersion?: string;\n /** Override templates root — used by tests. */\n templatesDir?: string;\n}\n\n/** Result returned by `scaffoldApp`. */\nexport interface ScaffoldResult {\n /** Absolute path of the scaffolded app. */\n targetDir: string;\n /** Resolved template id. */\n template: TemplateId;\n /** Package manager used. */\n packageManager: 'bun' | 'npm' | 'pnpm';\n /** Whether install was run. */\n installed: boolean;\n}\n\nconst FILENAME = fileURLToPath(import.meta.url);\nconst DEFAULT_TEMPLATES_DIR = resolve(dirname(FILENAME), '..', 'templates');\n\nconst VALID_APP_NAME = /^[a-z0-9@/_-]+$/i;\n\n/** Returns true when the name is safe to use as an app name and shell argument. */\nexport function isValidAppName(name: string): boolean {\n return VALID_APP_NAME.test(name);\n}\n\n/** Detect the package manager from `--package-manager` > `npm_config_user_agent` > `bun`. */\nexport function detectPackageManager(explicit: string | undefined): 'bun' | 'npm' | 'pnpm' {\n if (explicit === 'bun' || explicit === 'npm' || explicit === 'pnpm') return explicit;\n const agent = process.env.npm_config_user_agent ?? '';\n if (agent.startsWith('bun')) return 'bun';\n if (agent.startsWith('pnpm')) return 'pnpm';\n if (agent.startsWith('npm') || agent.startsWith('yarn')) return 'npm';\n return 'bun';\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nconst TOKEN_FILE_EXTS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.json',\n '.md',\n '.txt',\n '.html',\n '.css',\n]);\n\nfunction shouldApplyTokens(path: string): boolean {\n const dot = path.lastIndexOf('.');\n if (dot < 0) return false;\n return TOKEN_FILE_EXTS.has(path.slice(dot).toLowerCase());\n}\n\nfunction replaceTokens(content: string, tokens: Record<string, string>): string {\n let out = content;\n for (const [k, v] of Object.entries(tokens)) out = out.split(k).join(v);\n return out;\n}\n\nasync function walkFiles(dir: string, base: string, out: string[]): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const e of entries) {\n const full = join(dir, e.name);\n const rel = full.slice(base.length + 1);\n if (e.isDirectory()) {\n await walkFiles(full, base, out);\n } else if (e.isFile()) {\n out.push(rel);\n }\n }\n}\n\n/** Returns the spawn configuration for the given package manager on the given platform. */\nexport function installSpawnSpec(\n pm: 'bun' | 'npm' | 'pnpm',\n platform: NodeJS.Platform,\n): { cmd: string; shell: boolean } {\n return { cmd: pm, shell: platform === 'win32' };\n}\n\nfunction runInstall(pm: 'bun' | 'npm' | 'pnpm', cwd: string): Promise<number> {\n return new Promise((resolvePromise) => {\n const { cmd, shell } = installSpawnSpec(pm, process.platform);\n const child = spawn(cmd, ['install'], {\n cwd,\n stdio: 'inherit',\n shell,\n });\n child.on('exit', (code) => resolvePromise(code ?? 1));\n child.on('error', () => resolvePromise(1));\n });\n}\n\n/**\n * Copy the chosen template into `targetDir`, apply token replacements, and\n * optionally run the package manager's install. Fails if the target exists\n * and is non-empty.\n */\nexport async function scaffoldApp(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n if (!isValidAppName(opts.name)) {\n throw new Error(`[flare-create] invalid app name: ${opts.name}`);\n }\n const templateId = opts.template ?? 'blank';\n if (!isTemplateId(templateId)) {\n throw new Error(`[flare-create] unknown template: ${templateId}`);\n }\n const manifest = TEMPLATES[templateId];\n if (!manifest) throw new Error(`[flare-create] missing manifest for ${templateId}`);\n\n const cwd = process.cwd();\n const target = opts.targetDir\n ? isAbsolute(opts.targetDir)\n ? opts.targetDir\n : resolve(cwd, opts.targetDir)\n : resolve(cwd, opts.name);\n\n if (await pathExists(target)) {\n const entries = await readdir(target);\n if (entries.length > 0) {\n throw new Error(`[flare-create] target ${target} already exists and is not empty`);\n }\n } else {\n await mkdir(target, { recursive: true });\n }\n\n const templatesRoot = opts.templatesDir ?? DEFAULT_TEMPLATES_DIR;\n const src = join(templatesRoot, templateId);\n if (!(await pathExists(src))) {\n throw new Error(`[flare-create] template directory not found: ${src}`);\n }\n\n await cp(src, target, { recursive: true });\n\n const gi = join(target, 'gitignore');\n if (await pathExists(gi)) {\n await rename(gi, join(target, '.gitignore'));\n }\n\n const tokens: Record<string, string> = {\n '{{APP_NAME}}': opts.name,\n '{{FLARE_VERSION}}': opts.flareVersion ?? `^${version}`,\n '{{TEMPLATE_ID}}': templateId,\n };\n\n const files: string[] = [];\n await walkFiles(target, target, files);\n for (const rel of files) {\n if (!shouldApplyTokens(rel)) continue;\n const p = join(target, rel);\n const content = await readFile(p, 'utf8');\n const next = replaceTokens(content, tokens);\n if (next !== content) await writeFile(p, next, 'utf8');\n }\n\n const pm = detectPackageManager(opts.packageManager);\n let installed = false;\n if (!opts.skipInstall) {\n const code = await runInstall(pm, target);\n if (code !== 0) {\n throw new Error(`[flare-create] ${pm} install exited with code ${code}`);\n }\n installed = true;\n }\n\n return {\n targetDir: target,\n template: templateId,\n packageManager: pm,\n installed,\n };\n}\n","import { Command } from 'commander';\nimport { version } from '../package.json';\nimport { TEMPLATES } from './manifest.js';\nimport { scaffoldApp } from './scaffold.js';\n\nfunction parseTemplateArg(value: string): 'blank' | 'topdown' | 'shmup' {\n if (value !== 'blank' && value !== 'topdown' && value !== 'shmup') {\n throw new Error(`invalid --template: ${value} (expected blank|topdown|shmup)`);\n }\n return value;\n}\n\nfunction parsePmArg(value: string): 'bun' | 'npm' | 'pnpm' {\n if (value !== 'bun' && value !== 'npm' && value !== 'pnpm') {\n throw new Error(`invalid --package-manager: ${value} (expected bun|npm|pnpm)`);\n }\n return value;\n}\n\n/** Build the `flare-create` CLI program. Exposed for tests. */\nexport function buildProgram(): Command {\n const program = new Command();\n program\n .name('flare-create')\n .description('Scaffold a new Flare engine app')\n .version(version)\n .argument('<name>', 'directory name / package name for the new app')\n .option('-t, --template <name>', 'template (blank|topdown|shmup)', parseTemplateArg, 'blank')\n .option('-p, --package-manager <pm>', 'package manager (bun|npm|pnpm)', parsePmArg)\n .option('--skip-install', 'skip running install after scaffolding', false)\n .action(async (name: string, opts) => {\n const result = await scaffoldApp({\n name,\n template: opts.template,\n packageManager: opts.packageManager,\n skipInstall: opts.skipInstall === true,\n });\n const manifest = TEMPLATES[result.template];\n process.stdout.write(\n `[flare-create] scaffolded ${\n manifest?.displayName ?? result.template\n } at ${result.targetDir}\\n`,\n );\n if (!result.installed) {\n process.stdout.write(\n `[flare-create] skipped install — run \\`cd ${name} && ${result.packageManager} install\\` when ready\\n`,\n );\n } else if (manifest?.postInstall) {\n process.stdout.write(`[flare-create] next: cd ${name} && ${manifest.postInstall}\\n`);\n }\n });\n return program;\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
//#region src/cli.d.ts
|
|
3
|
+
/** Build the `flare-create` CLI program. Exposed for tests. */
|
|
4
|
+
declare function buildProgram(): Command;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/manifest.d.ts
|
|
7
|
+
/** Declared metadata for a `@flare-engine/create` template. */
|
|
8
|
+
interface TemplateManifest {
|
|
9
|
+
/** Template directory name (`blank`, `topdown`, `shmup`, ...). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Human-readable name shown in logs. */
|
|
12
|
+
displayName: string;
|
|
13
|
+
/** One-line description of what the template produces. */
|
|
14
|
+
description: string;
|
|
15
|
+
/** Engines required — mirrors Node/npm engines field in scaffolded package.json. */
|
|
16
|
+
engines: {
|
|
17
|
+
node: string;
|
|
18
|
+
};
|
|
19
|
+
/** Optional post-install instructions printed after scaffold completes. */
|
|
20
|
+
postInstall?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Built-in templates shipped inside `@flare-engine/create`. */
|
|
23
|
+
declare const TEMPLATES: {
|
|
24
|
+
blank: {
|
|
25
|
+
id: string;
|
|
26
|
+
displayName: string;
|
|
27
|
+
description: string;
|
|
28
|
+
engines: {
|
|
29
|
+
node: string;
|
|
30
|
+
};
|
|
31
|
+
postInstall: string;
|
|
32
|
+
};
|
|
33
|
+
topdown: {
|
|
34
|
+
id: string;
|
|
35
|
+
displayName: string;
|
|
36
|
+
description: string;
|
|
37
|
+
engines: {
|
|
38
|
+
node: string;
|
|
39
|
+
};
|
|
40
|
+
postInstall: string;
|
|
41
|
+
};
|
|
42
|
+
shmup: {
|
|
43
|
+
id: string;
|
|
44
|
+
displayName: string;
|
|
45
|
+
description: string;
|
|
46
|
+
engines: {
|
|
47
|
+
node: string;
|
|
48
|
+
};
|
|
49
|
+
postInstall: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
type TemplateId = keyof typeof TEMPLATES;
|
|
53
|
+
declare function isTemplateId(value: string): value is TemplateId;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/scaffold.d.ts
|
|
56
|
+
/** Options for `scaffoldApp`. */
|
|
57
|
+
interface ScaffoldOptions {
|
|
58
|
+
/** App name — also used as the target directory and `name` in package.json. */
|
|
59
|
+
name: string;
|
|
60
|
+
/** Template id (default: `blank`). */
|
|
61
|
+
template?: string;
|
|
62
|
+
/** Destination directory (default: `<cwd>/<name>`). */
|
|
63
|
+
targetDir?: string;
|
|
64
|
+
/** Package manager used for `engines` + post-install command. */
|
|
65
|
+
packageManager?: 'bun' | 'npm' | 'pnpm';
|
|
66
|
+
/** Skip `install` after copying (default: false). */
|
|
67
|
+
skipInstall?: boolean;
|
|
68
|
+
/** Pinned Flare version for template {{FLARE_VERSION}} token (default: current package version prefixed with ^). */
|
|
69
|
+
flareVersion?: string;
|
|
70
|
+
/** Override templates root — used by tests. */
|
|
71
|
+
templatesDir?: string;
|
|
72
|
+
}
|
|
73
|
+
/** Result returned by `scaffoldApp`. */
|
|
74
|
+
interface ScaffoldResult {
|
|
75
|
+
/** Absolute path of the scaffolded app. */
|
|
76
|
+
targetDir: string;
|
|
77
|
+
/** Resolved template id. */
|
|
78
|
+
template: TemplateId;
|
|
79
|
+
/** Package manager used. */
|
|
80
|
+
packageManager: 'bun' | 'npm' | 'pnpm';
|
|
81
|
+
/** Whether install was run. */
|
|
82
|
+
installed: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** Returns true when the name is safe to use as an app name and shell argument. */
|
|
85
|
+
declare function isValidAppName(name: string): boolean;
|
|
86
|
+
/** Detect the package manager from `--package-manager` > `npm_config_user_agent` > `bun`. */
|
|
87
|
+
declare function detectPackageManager(explicit: string | undefined): 'bun' | 'npm' | 'pnpm';
|
|
88
|
+
/** Returns the spawn configuration for the given package manager on the given platform. */
|
|
89
|
+
declare function installSpawnSpec(pm: 'bun' | 'npm' | 'pnpm', platform: NodeJS.Platform): {
|
|
90
|
+
cmd: string;
|
|
91
|
+
shell: boolean;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Copy the chosen template into `targetDir`, apply token replacements, and
|
|
95
|
+
* optionally run the package manager's install. Fails if the target exists
|
|
96
|
+
* and is non-empty.
|
|
97
|
+
*/
|
|
98
|
+
declare function scaffoldApp(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
99
|
+
//#endregion
|
|
100
|
+
export { type ScaffoldOptions, type ScaffoldResult, TEMPLATES, type TemplateId, type TemplateManifest, buildProgram, detectPackageManager, installSpawnSpec, isTemplateId, isValidAppName, scaffoldApp };
|
|
101
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/cli.ts","../src/manifest.ts","../src/scaffold.ts"],"mappings":";;;iBAoBgB,gBAAgB;;;;UCnBf;;EAEf;;EAEA;;EAEA;;EAEA;IAAW;;;EAEX;;;cAIW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyBD,0BAA0B;iBAEtB,aAAa,gBAAgB,SAAS;;;;UClCrC;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;EAEA,UAAU;;EAEV;;EAEA;;;iBASc,eAAe;;iBAKf,qBAAqB;;iBAwDrB,iBACd,4BACA,UAAU,OAAO;EACd;EAAa;;;;;;;iBAsBI,YAAY,MAAM,kBAAkB,QAAQ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
//#region src/cli.d.ts
|
|
3
|
+
/** Build the `flare-create` CLI program. Exposed for tests. */
|
|
4
|
+
declare function buildProgram(): Command;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/manifest.d.ts
|
|
7
|
+
/** Declared metadata for a `@flare-engine/create` template. */
|
|
8
|
+
interface TemplateManifest {
|
|
9
|
+
/** Template directory name (`blank`, `topdown`, `shmup`, ...). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Human-readable name shown in logs. */
|
|
12
|
+
displayName: string;
|
|
13
|
+
/** One-line description of what the template produces. */
|
|
14
|
+
description: string;
|
|
15
|
+
/** Engines required — mirrors Node/npm engines field in scaffolded package.json. */
|
|
16
|
+
engines: {
|
|
17
|
+
node: string;
|
|
18
|
+
};
|
|
19
|
+
/** Optional post-install instructions printed after scaffold completes. */
|
|
20
|
+
postInstall?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Built-in templates shipped inside `@flare-engine/create`. */
|
|
23
|
+
declare const TEMPLATES: {
|
|
24
|
+
blank: {
|
|
25
|
+
id: string;
|
|
26
|
+
displayName: string;
|
|
27
|
+
description: string;
|
|
28
|
+
engines: {
|
|
29
|
+
node: string;
|
|
30
|
+
};
|
|
31
|
+
postInstall: string;
|
|
32
|
+
};
|
|
33
|
+
topdown: {
|
|
34
|
+
id: string;
|
|
35
|
+
displayName: string;
|
|
36
|
+
description: string;
|
|
37
|
+
engines: {
|
|
38
|
+
node: string;
|
|
39
|
+
};
|
|
40
|
+
postInstall: string;
|
|
41
|
+
};
|
|
42
|
+
shmup: {
|
|
43
|
+
id: string;
|
|
44
|
+
displayName: string;
|
|
45
|
+
description: string;
|
|
46
|
+
engines: {
|
|
47
|
+
node: string;
|
|
48
|
+
};
|
|
49
|
+
postInstall: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
type TemplateId = keyof typeof TEMPLATES;
|
|
53
|
+
declare function isTemplateId(value: string): value is TemplateId;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/scaffold.d.ts
|
|
56
|
+
/** Options for `scaffoldApp`. */
|
|
57
|
+
interface ScaffoldOptions {
|
|
58
|
+
/** App name — also used as the target directory and `name` in package.json. */
|
|
59
|
+
name: string;
|
|
60
|
+
/** Template id (default: `blank`). */
|
|
61
|
+
template?: string;
|
|
62
|
+
/** Destination directory (default: `<cwd>/<name>`). */
|
|
63
|
+
targetDir?: string;
|
|
64
|
+
/** Package manager used for `engines` + post-install command. */
|
|
65
|
+
packageManager?: 'bun' | 'npm' | 'pnpm';
|
|
66
|
+
/** Skip `install` after copying (default: false). */
|
|
67
|
+
skipInstall?: boolean;
|
|
68
|
+
/** Pinned Flare version for template {{FLARE_VERSION}} token (default: current package version prefixed with ^). */
|
|
69
|
+
flareVersion?: string;
|
|
70
|
+
/** Override templates root — used by tests. */
|
|
71
|
+
templatesDir?: string;
|
|
72
|
+
}
|
|
73
|
+
/** Result returned by `scaffoldApp`. */
|
|
74
|
+
interface ScaffoldResult {
|
|
75
|
+
/** Absolute path of the scaffolded app. */
|
|
76
|
+
targetDir: string;
|
|
77
|
+
/** Resolved template id. */
|
|
78
|
+
template: TemplateId;
|
|
79
|
+
/** Package manager used. */
|
|
80
|
+
packageManager: 'bun' | 'npm' | 'pnpm';
|
|
81
|
+
/** Whether install was run. */
|
|
82
|
+
installed: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** Returns true when the name is safe to use as an app name and shell argument. */
|
|
85
|
+
declare function isValidAppName(name: string): boolean;
|
|
86
|
+
/** Detect the package manager from `--package-manager` > `npm_config_user_agent` > `bun`. */
|
|
87
|
+
declare function detectPackageManager(explicit: string | undefined): 'bun' | 'npm' | 'pnpm';
|
|
88
|
+
/** Returns the spawn configuration for the given package manager on the given platform. */
|
|
89
|
+
declare function installSpawnSpec(pm: 'bun' | 'npm' | 'pnpm', platform: NodeJS.Platform): {
|
|
90
|
+
cmd: string;
|
|
91
|
+
shell: boolean;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Copy the chosen template into `targetDir`, apply token replacements, and
|
|
95
|
+
* optionally run the package manager's install. Fails if the target exists
|
|
96
|
+
* and is non-empty.
|
|
97
|
+
*/
|
|
98
|
+
declare function scaffoldApp(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
99
|
+
//#endregion
|
|
100
|
+
export { type ScaffoldOptions, type ScaffoldResult, TEMPLATES, type TemplateId, type TemplateManifest, buildProgram, detectPackageManager, installSpawnSpec, isTemplateId, isValidAppName, scaffoldApp };
|
|
101
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/cli.ts","../src/manifest.ts","../src/scaffold.ts"],"mappings":";;;iBAoBgB,gBAAgB;;;;UCnBf;;EAEf;;EAEA;;EAEA;;EAEA;IAAW;;;EAEX;;;cAIW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyBD,0BAA0B;iBAEtB,aAAa,gBAAgB,SAAS;;;;UClCrC;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;EAEA,UAAU;;EAEV;;EAEA;;;iBASc,eAAe;;iBAKf,qBAAqB;;iBAwDrB,iBACd,4BACA,UAAU,OAAO;EACd;EAAa;;;;;;;iBAsBI,YAAY,MAAM,kBAAkB,QAAQ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_cli = require("./cli-JzwtwZhm.js");
|
|
3
|
+
exports.TEMPLATES = require_cli.TEMPLATES;
|
|
4
|
+
exports.buildProgram = require_cli.buildProgram;
|
|
5
|
+
exports.detectPackageManager = require_cli.detectPackageManager;
|
|
6
|
+
exports.installSpawnSpec = require_cli.installSpawnSpec;
|
|
7
|
+
exports.isTemplateId = require_cli.isTemplateId;
|
|
8
|
+
exports.isValidAppName = require_cli.isValidAppName;
|
|
9
|
+
exports.scaffoldApp = require_cli.scaffoldApp;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as scaffoldApp, i as isValidAppName, n as detectPackageManager, o as TEMPLATES, r as installSpawnSpec, s as isTemplateId, t as buildProgram } from "./cli-BGXZ73ie.mjs";
|
|
2
|
+
export { TEMPLATES, buildProgram, detectPackageManager, installSpawnSpec, isTemplateId, isValidAppName, scaffoldApp };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flare-engine/create",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "One-shot scaffolder for Flare engine apps — `npm create @flare-engine`",
|
|
5
|
+
"keywords": ["flare-engine", "scaffold", "create-app", "expo", "react-native"],
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"author": "Grzegorz Otto",
|
|
8
|
+
"homepage": "https://github.com/grzott/flare-engine#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/grzott/flare-engine/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/grzott/flare-engine.git",
|
|
15
|
+
"directory": "packages/create"
|
|
16
|
+
},
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"module": "dist/index.mjs",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/index.d.mts",
|
|
24
|
+
"default": "./dist/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"flare-create": "dist/bin.js"
|
|
34
|
+
},
|
|
35
|
+
"files": ["dist", "templates", "README.md", "LICENSE", "NOTICE"],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"lint": "biome check src/",
|
|
40
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.templates.json"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"commander": "^13.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@flare-engine/ecs": "^0.4.0",
|
|
47
|
+
"@flare-engine/engine": "^0.4.0",
|
|
48
|
+
"@flare-engine/physics": "^0.4.0",
|
|
49
|
+
"@flare-engine/react": "^0.4.0",
|
|
50
|
+
"@flare-engine/render": "^0.4.0",
|
|
51
|
+
"@flare-engine/tilemap": "^0.4.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "restricted",
|
|
58
|
+
"registry": "https://registry.npmjs.org/"
|
|
59
|
+
},
|
|
60
|
+
"sideEffects": false
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
A Flare engine app scaffolded from the `{{TEMPLATE_ID}}` template.
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun run android # or: bun run ios
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`@shopify/react-native-skia` is a **native** module, so this template needs a
|
|
13
|
+
dev build — it cannot run in Expo Go. `bun run start` then pressing `a`/`i`
|
|
14
|
+
opens Expo Go and red-screens with a missing-native-module error. `bun run
|
|
15
|
+
android` builds and installs a dev client instead; after that first build,
|
|
16
|
+
`bun run start` attaches to it normally.
|
|
17
|
+
|
|
18
|
+
The first Android build takes a few minutes and needs the Android SDK plus a
|
|
19
|
+
JDK. Two things that will otherwise cost you an afternoon:
|
|
20
|
+
|
|
21
|
+
- **Build from a short path.** Skia's CMake step generates paths ~120
|
|
22
|
+
characters longer than your project directory, and Windows still fails at 260
|
|
23
|
+
(`CreateProcess error=2`). `C:\src\my-app` is fine; a deep temp path is not.
|
|
24
|
+
- **Do not remove `trustedDependencies` from `package.json`.** Bun blocks
|
|
25
|
+
install scripts it does not trust, and Skia's `postinstall` is what unpacks
|
|
26
|
+
its prebuilt binaries. Without it the install still succeeds and the Android
|
|
27
|
+
build fails minutes later with `Could not find libskia.a`.
|
|
28
|
+
|
|
29
|
+
## Run in a browser
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun run web
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The `web` script copies CanvasKit's wasm binary into `public/` before starting
|
|
36
|
+
Metro, because Skia on web is CanvasKit and it has to be served alongside the
|
|
37
|
+
bundle. Starting the dev server any other way — `bun run start` then pressing
|
|
38
|
+
`w` — skips that copy and the canvas stays blank.
|
|
39
|
+
|
|
40
|
+
The binary is ~8 MB and is `.gitignore`d: it is a copy of
|
|
41
|
+
`canvaskit-wasm/bin/full/canvaskit.wasm`, reproduced by the script on demand.
|
|
42
|
+
It must be the `full` build; the smaller default build is ABI-incompatible with
|
|
43
|
+
the loader.
|
|
44
|
+
|
|
45
|
+
## Layout
|
|
46
|
+
|
|
47
|
+
- `src/App.tsx` — root component; mounts `<GameView>` and configures the Flare game loop.
|
|
48
|
+
- `index.ts` — Expo entry that registers `App` as the root component (native).
|
|
49
|
+
- `index.web.ts` — web entry; awaits `LoadSkiaWeb()` **before** importing `App`.
|
|
50
|
+
`package.json`'s `main` is extensionless so Metro resolves the right entry per
|
|
51
|
+
platform. The split is required: RN-Skia's web module reads `global.CanvasKit`
|
|
52
|
+
when it is first imported, so importing `App` any earlier leaves Skia hollow.
|
|
53
|
+
- `scripts/copy-canvaskit.mjs` — copies the CanvasKit wasm into `public/` for the
|
|
54
|
+
web build.
|
|
55
|
+
- `app.json` — Expo configuration.
|
|
56
|
+
|
|
57
|
+
## Docs
|
|
58
|
+
|
|
59
|
+
- [Flare engine](https://github.com/grzott/flare-engine)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
|
|
2
|
+
import { registerRootComponent } from "expo";
|
|
3
|
+
|
|
4
|
+
// Metro picks this file over index.ts on web because package.json's `main` is
|
|
5
|
+
// extensionless. Keeping the web entry separate is not cosmetic: RN-Skia's web
|
|
6
|
+
// module builds its Skia object from global.CanvasKit at module-eval time, so
|
|
7
|
+
// anything that imports Skia must be loaded only after LoadSkiaWeb resolves.
|
|
8
|
+
// A static `import { App }` here would leave Skia permanently hollow and
|
|
9
|
+
// GameView would throw on Skia.Paint.
|
|
10
|
+
//
|
|
11
|
+
// `bun run web` copies canvaskit.wasm into public/, which Expo serves at the
|
|
12
|
+
// web root — that is what locateFile points at.
|
|
13
|
+
void (async () => {
|
|
14
|
+
await LoadSkiaWeb({ locateFile: (file: string) => `/${file}` });
|
|
15
|
+
const { App } = await import("./src/App");
|
|
16
|
+
registerRootComponent(App);
|
|
17
|
+
})();
|