create-gamenative-app 0.1.1

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.
Files changed (39) hide show
  1. package/README.md +116 -0
  2. package/bin/create.js +255 -0
  3. package/create-gamenative-app/README.md +33 -0
  4. package/create-gamenative-app/index.js +107 -0
  5. package/create-gamenative-app/package.json +33 -0
  6. package/create-gamenative-app/sync-template.js +177 -0
  7. package/create-gamenative-app/template/FRAMEWORK.md +14 -0
  8. package/create-gamenative-app/template/README.md +10 -0
  9. package/create-gamenative-app/template/assets/icon.png +0 -0
  10. package/create-gamenative-app/template/game.config.json +9 -0
  11. package/create-gamenative-app/template/package.json +39 -0
  12. package/create-gamenative-app/template/scripts/copy-config.js +8 -0
  13. package/create-gamenative-app/template/src/Game.ts +97 -0
  14. package/create-gamenative-app/template/src/Graphics.ts +93 -0
  15. package/create-gamenative-app/template/src/Input.ts +83 -0
  16. package/create-gamenative-app/template/src/config.ts +25 -0
  17. package/create-gamenative-app/template/src/games/Game.ts +32 -0
  18. package/create-gamenative-app/template/src/icon.ts +19 -0
  19. package/create-gamenative-app/template/src/index.ts +6 -0
  20. package/create-gamenative-app/template/src/main.ts +39 -0
  21. package/create-gamenative-app/template/src/pngjs.d.ts +5 -0
  22. package/create-gamenative-app/template/src/types.ts +35 -0
  23. package/create-gamenative-app/template/tsconfig.json +23 -0
  24. package/example.ts +45 -0
  25. package/game.config.json +9 -0
  26. package/package.json +47 -0
  27. package/scripts/copy-config.js +8 -0
  28. package/src/Game.ts +97 -0
  29. package/src/Graphics.ts +93 -0
  30. package/src/Input.ts +83 -0
  31. package/src/assets/icon.png +0 -0
  32. package/src/config.ts +25 -0
  33. package/src/games/ExampleGame.ts +49 -0
  34. package/src/icon.ts +19 -0
  35. package/src/index.ts +6 -0
  36. package/src/main.ts +39 -0
  37. package/src/pngjs.d.ts +5 -0
  38. package/src/types.ts +35 -0
  39. package/tsconfig.json +18 -0
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # GameNative
2
+
3
+ Native TypeScript framework for desktop games. No game engine — just a thin layer over **SDL2** (window, input) and **WebGL** (via [@kmamal/gl](https://github.com/kmamal/headless-gl)) so you write games in TypeScript and run them with Node.
4
+
5
+ - **Window**: SDL window with OpenGL, resize, vsync, optional icon.
6
+ - **Input**: Keyboard (scancode-based) and mouse (position + buttons).
7
+ - **Rendering**: Raw WebGL context; optional 2D helper (`createDraw2D`) for clear + filled rects.
8
+ - **Loop**: Fixed lifecycle: `init(ctx)` once, then `update(dt)` and `draw()` every frame.
9
+
10
+ Works on **Windows**, **macOS**, and **Linux** (x64; arm64 on Mac).
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ ## Create a new game project (CLI)
19
+
20
+ **Without cloning the repo** (recommended):
21
+
22
+ ```bash
23
+ npx create-gamenative-app@latest
24
+ ```
25
+
26
+ **From the GameNative repo** (local CLI):
27
+
28
+ ```bash
29
+ npm run create
30
+ ```
31
+
32
+ A TUI will prompt for **project name** and **directory**. It then:
33
+
34
+ - Creates a new folder with the framework wired up
35
+ - Sets `game.config.json` (window title, size, default icon)
36
+ - Adds `assets/` and copies the default `icon.png`
37
+ - Adds a starter game at `src/games/Game.ts`
38
+ - Runs `npm install` in the new project
39
+
40
+ Then `cd <directory>/<project-name>` and `npm run dev` to start. Edit `src/games/Game.ts` and `game.config.json` to build your game. The new project includes **FRAMEWORK.md** (2D, 3D, lighting, assets, sound, UI, camera).
41
+
42
+ ## Run (dev)
43
+
44
+ ```bash
45
+ npm run dev
46
+ ```
47
+
48
+ Runs the app from source with the game and window settings from `game.config.json`. Edit code and restart to see changes.
49
+
50
+ ## Config: `game.config.json`
51
+
52
+ At the project root. Controls which game runs and the window.
53
+
54
+ ```json
55
+ {
56
+ "window": {
57
+ "title": "My Game",
58
+ "width": 800,
59
+ "height": 600,
60
+ "icon": "assets/icon.png"
61
+ },
62
+ "game": "ExampleGame"
63
+ }
64
+ ```
65
+
66
+ - **window.title** — Window title.
67
+ - **window.width / height** — Window size.
68
+ - **window.icon** — Optional path to a PNG (relative to project root) for window/taskbar icon.
69
+ - **game** — Name of the game to run (no extension). Must match a file under `src/games/<name>.ts`.
70
+
71
+ ## Adding a game
72
+
73
+ 1. Add `src/games/MyGame.ts`.
74
+ 2. Export a default object that implements `IGame` (`init?`, `update(dt)`, `draw()`, `dispose?`).
75
+ 3. Set `"game": "MyGame"` in `game.config.json`.
76
+
77
+ Example game: `src/games/ExampleGame.ts`. Escape to exit.
78
+
79
+ ## Build and run
80
+
81
+ ```bash
82
+ npm run build
83
+ npm start
84
+ ```
85
+
86
+ Runs the compiled app from `dist/` using `game.config.json` in the current directory.
87
+
88
+ ## Build to .exe (Windows)
89
+
90
+ ```bash
91
+ npm run build:exe
92
+ ```
93
+
94
+ Produces `release/GameNative.exe` and copies `game.config.json` into `release/`. Run the exe from the `release/` folder (or put the exe and `game.config.json` in the same folder). You can change window title, icon path, and game name in that config; the exe includes the default game(s) from `dist/games/`.
95
+
96
+ Requires [pkg](https://github.com/vercel/pkg); target is `node18-win-x64`. For other platforms, adjust the `pkg.targets` in `package.json`.
97
+
98
+ ## Scripts
99
+
100
+ | Script | Description |
101
+ |---------------|--------------------------------------------------|
102
+ | `npm run dev` | Run from source (tsx) using `game.config.json` |
103
+ | `npm run build` | Compile TypeScript to `dist/` |
104
+ | `npm start` | Run `dist/main.js` (needs `game.config.json`) |
105
+ | `npm run build:exe` | Build then package to `release/GameNative.exe` |
106
+
107
+ ## API overview (for game code)
108
+
109
+ - **`run(game, config?)`** — Framework entry; config: `title`, `width`, `height`, `vsync`, `resizable`, `icon`.
110
+ - **`IGame`**: optional `init(ctx, config)`, required `update(dt)`, `draw()`, optional `dispose()`.
111
+ - **`GameContext`**: `gl`, `input`, `width`, `height`.
112
+ - **Input**: `ctx.input.mouseX`, `mouseY`, `mouseLeft`, `mouseRight`, `mouseMiddle`, `isKeyDown(scancode)`.
113
+ - **`SCANCODE`**: e.g. `SCANCODE.W`, `SCANCODE.Escape`.
114
+ - **`createDraw2D(gl)`**: `clear(r,g,b,a?)`, `fillRect(x,y,w,h,r,g,b,a?)`, `dispose()`.
115
+
116
+ For custom rendering, use `ctx.gl` (WebGL 1) directly.
package/bin/create.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import inquirer from 'inquirer'
3
+ import { mkdir, writeFile, copyFile, readFile } from 'fs/promises'
4
+ import { join, dirname } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { spawn } from 'child_process'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+ const FRAMEWORK_ROOT = join(__dirname, '..')
10
+
11
+ function kebab(s) {
12
+ return s
13
+ .trim()
14
+ .replace(/\s+/g, '-')
15
+ .replace(/[^a-zA-Z0-9-]/g, '')
16
+ .toLowerCase() || 'my-game'
17
+ }
18
+
19
+ async function copyFrameworkFile(relPath) {
20
+ return readFile(join(FRAMEWORK_ROOT, relPath), 'utf-8')
21
+ }
22
+
23
+ async function scaffold(projectName, parentDir) {
24
+ const name = kebab(projectName)
25
+ const projectPath = join(parentDir, name)
26
+ const src = join(projectPath, 'src')
27
+ const games = join(projectPath, 'src', 'games')
28
+ const assets = join(projectPath, 'assets')
29
+ const scripts = join(projectPath, 'scripts')
30
+
31
+ await mkdir(projectPath, { recursive: true })
32
+ await mkdir(games, { recursive: true })
33
+ await mkdir(assets, { recursive: true })
34
+ await mkdir(scripts, { recursive: true })
35
+
36
+ const [
37
+ mainTs,
38
+ configTs,
39
+ gameTs,
40
+ inputTs,
41
+ graphicsTs,
42
+ typesTs,
43
+ indexTs,
44
+ iconTs,
45
+ pngjsDts,
46
+ copyConfigJs,
47
+ ] = await Promise.all([
48
+ copyFrameworkFile('src/main.ts'),
49
+ copyFrameworkFile('src/config.ts'),
50
+ copyFrameworkFile('src/Game.ts'),
51
+ copyFrameworkFile('src/Input.ts'),
52
+ copyFrameworkFile('src/Graphics.ts'),
53
+ copyFrameworkFile('src/types.ts'),
54
+ copyFrameworkFile('src/index.ts'),
55
+ copyFrameworkFile('src/icon.ts'),
56
+ copyFrameworkFile('src/pngjs.d.ts'),
57
+ readFile(join(FRAMEWORK_ROOT, 'scripts/copy-config.js'), 'utf-8'),
58
+ ])
59
+
60
+ const gameConfig = {
61
+ window: {
62
+ title: projectName.trim() || name,
63
+ width: 800,
64
+ height: 600,
65
+ icon: 'assets/icon.png',
66
+ },
67
+ game: 'Game',
68
+ }
69
+
70
+ const packageJson = {
71
+ name,
72
+ version: '0.1.0',
73
+ description: `Game built with GameNative`,
74
+ type: 'module',
75
+ main: 'dist/main.js',
76
+ scripts: {
77
+ dev: 'cross-env NODE_ENV=development tsx src/main.ts',
78
+ build: 'tsc',
79
+ start: 'node dist/main.js',
80
+ 'build:exe': 'npm run build && node scripts/copy-config.js && pkg . --output release/' + name + '.exe',
81
+ },
82
+ dependencies: {
83
+ '@kmamal/gl': '^9.1.0',
84
+ '@kmamal/sdl': '^0.11.13',
85
+ pngjs: '^7.0.0',
86
+ },
87
+ devDependencies: {
88
+ '@types/node': '^22.10.0',
89
+ cross-env: '^7.0.3',
90
+ pkg: '^5.8.1',
91
+ tsx: '^4.19.2',
92
+ typescript: '^5.7.0',
93
+ },
94
+ engines: { node: '>=18' },
95
+ pkg: {
96
+ scripts: 'dist/main.js',
97
+ assets: ['node_modules/@kmamal/sdl/**', 'node_modules/@kmamal/gl/**', 'dist/games/**'],
98
+ targets: ['node18-win-x64'],
99
+ },
100
+ }
101
+
102
+ const tsconfig = {
103
+ compilerOptions: {
104
+ target: 'ES2022',
105
+ module: 'NodeNext',
106
+ moduleResolution: 'NodeNext',
107
+ outDir: 'dist',
108
+ rootDir: '.',
109
+ strict: true,
110
+ declaration: true,
111
+ declarationMap: true,
112
+ sourceMap: true,
113
+ skipLibCheck: true,
114
+ esModuleInterop: true,
115
+ forceConsistentCasingInFileNames: true,
116
+ },
117
+ include: ['src/**/*.ts'],
118
+ exclude: ['node_modules', 'dist'],
119
+ }
120
+
121
+ const gameTemplate = `import { createDraw2D, SCANCODE } from '../index.js'
122
+ import type { GameContext, IGame } from '../types.js'
123
+
124
+ interface Game extends IGame {
125
+ ctx: GameContext | null
126
+ draw2d: ReturnType<typeof createDraw2D> | null
127
+ }
128
+
129
+ const game: Game = {
130
+ ctx: null,
131
+ draw2d: null,
132
+
133
+ async init(ctx: GameContext) {
134
+ this.ctx = ctx
135
+ this.draw2d = createDraw2D(ctx.gl)
136
+ },
137
+
138
+ update(dt: number) {
139
+ if (this.ctx?.input.isKeyDown(SCANCODE.Escape)) process.exit(0)
140
+ },
141
+
142
+ draw() {
143
+ this.draw2d?.clear(0.08, 0.08, 0.12, 1)
144
+ this.draw2d?.fillRect(100, 100, 120, 80, 0.2, 0.5, 0.9, 1)
145
+ },
146
+
147
+ dispose() {
148
+ this.draw2d?.dispose()
149
+ },
150
+ }
151
+
152
+ export default game
153
+ `
154
+
155
+ await Promise.all([
156
+ writeFile(join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2)),
157
+ writeFile(join(projectPath, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2)),
158
+ writeFile(join(projectPath, 'game.config.json'), JSON.stringify(gameConfig, null, 2)),
159
+ writeFile(join(src, 'main.ts'), mainTs),
160
+ writeFile(join(src, 'config.ts'), configTs),
161
+ writeFile(join(src, 'Game.ts'), gameTs),
162
+ writeFile(join(src, 'Input.ts'), inputTs),
163
+ writeFile(join(src, 'Graphics.ts'), graphicsTs),
164
+ writeFile(join(src, 'types.ts'), typesTs),
165
+ writeFile(join(src, 'index.ts'), indexTs),
166
+ writeFile(join(src, 'icon.ts'), iconTs),
167
+ writeFile(join(src, 'pngjs.d.ts'), pngjsDts),
168
+ writeFile(join(games, 'Game.ts'), gameTemplate),
169
+ writeFile(join(scripts, 'copy-config.js'), copyConfigJs),
170
+ ])
171
+
172
+ const iconSrc = join(FRAMEWORK_ROOT, 'src', 'assets', 'icon.png')
173
+ try {
174
+ await copyFile(iconSrc, join(assets, 'icon.png'))
175
+ } catch {
176
+ // no icon if missing
177
+ }
178
+
179
+ const readme = `# ${projectName.trim() || name}
180
+
181
+ Game built with [GameNative](https://github.com/your-org/GameNative).
182
+
183
+ - \`npm run dev\` — run from source
184
+ - \`npm run build\` then \`npm start\` — run built app
185
+ - Edit \`game.config.json\` for window title, size, icon
186
+ - Edit \`src/games/Game.ts\` to build your game
187
+
188
+ See FRAMEWORK.md for 2D, 3D, lighting, sound, UI, and camera.
189
+ `
190
+ const frameworkMd = `# GameNative capabilities
191
+
192
+ Your game has access to:
193
+
194
+ - **2D**: \`createDraw2D(ctx.gl)\` — \`clear()\`, \`fillRect()\`. Use \`ctx.gl\` (WebGL 1) for textures, sprites, batching.
195
+ - **3D**: Use \`ctx.gl\` directly: buffers, shaders, matrices. Implement camera (view/projection), meshes, and lighting in shaders.
196
+ - **Lighting**: In 3D shaders use uniforms for light position/color; in 2D use tint or custom fragment shaders.
197
+ - **Assets**: Load images (decode PNG with pngjs or similar), upload to \`ctx.gl\` textures; load audio (see sound).
198
+ - **Sound**: \`ctx.sdl.audio\` (SDL audio) — open device, enqueue buffers. Or add a small wrapper in your game.
199
+ - **Visuals**: Full WebGL — post-process by rendering to framebuffer, then to screen; particles, blur, etc. in shaders.
200
+ - **UI**: Draw quads with \`fillRect\` or textured quads; track \`ctx.input.mouseX/Y\` and \`mouseLeft\` for clicks; implement panels/buttons in \`update\`/\`draw\`.
201
+ - **Camera**: 2D: store \`offsetX, offsetY, scale\` and pass to your draw calls or a uniform. 3D: \`view\` and \`projection\` matrices from position/target/up and perspective.
202
+
203
+ All of this is done in your \`Game.ts\` using \`ctx.gl\`, \`ctx.input\`, and optional helpers you add.
204
+ `
205
+ await writeFile(join(projectPath, 'README.md'), readme)
206
+ await writeFile(join(projectPath, 'FRAMEWORK.md'), frameworkMd)
207
+
208
+ return { projectPath: projectPath, name }
209
+ }
210
+
211
+ function npmInstall(cwd) {
212
+ return new Promise((resolve, reject) => {
213
+ const isWin = process.platform === 'win32'
214
+ const child = spawn(isWin ? 'npm.cmd' : 'npm', ['install'], {
215
+ cwd,
216
+ stdio: 'inherit',
217
+ shell: isWin,
218
+ })
219
+ child.on('close', (code) => (code === 0 ? resolve() : reject(new Error('npm install failed'))))
220
+ })
221
+ }
222
+
223
+ async function main() {
224
+ console.log('\n GameNative — create a new game project\n')
225
+ const answers = await inquirer.prompt([
226
+ {
227
+ type: 'input',
228
+ name: 'projectName',
229
+ message: 'Project name:',
230
+ default: 'my-game',
231
+ validate: (v) => (v?.trim() ? true : 'Enter a name'),
232
+ },
233
+ {
234
+ type: 'input',
235
+ name: 'directory',
236
+ message: 'Create project in directory:',
237
+ default: '.',
238
+ },
239
+ ])
240
+ const dir = answers.directory?.trim() || '.'
241
+ const resolvedDir = join(process.cwd(), dir)
242
+ console.log('\n Creating project...')
243
+ const { projectPath, name } = await scaffold(answers.projectName, resolvedDir)
244
+ console.log(' Installing dependencies...')
245
+ await npmInstall(projectPath)
246
+ console.log('\n Done!\n')
247
+ console.log(' Next:\n')
248
+ console.log(` cd ${dir}/${name}`)
249
+ console.log(' npm run dev\n')
250
+ }
251
+
252
+ main().catch((err) => {
253
+ console.error(err)
254
+ process.exit(1)
255
+ })
@@ -0,0 +1,33 @@
1
+ # create-gamenative-app
2
+
3
+ Scaffold a new [GameNative](https://github.com/your-org/GameNative) desktop game project.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-gamenative-app@latest
9
+ ```
10
+
11
+ You’ll be prompted for **project name** and **directory**. The CLI creates the project, installs dependencies, and copies the default window icon into `assets/`.
12
+
13
+ Then:
14
+
15
+ ```bash
16
+ cd <directory>/<project-name>
17
+ npm run dev
18
+ ```
19
+
20
+ ## Publish (maintainers)
21
+
22
+ From the GameNative repo:
23
+
24
+ 1. Update framework code in `src/`, `scripts/`, etc. as needed.
25
+ 2. From `create-gamenative-app/`: run `node sync-template.js` to refresh `template/` from the parent repo (or rely on `prepublishOnly`).
26
+ 3. Bump version in `create-gamenative-app/package.json`, then:
27
+
28
+ ```bash
29
+ cd create-gamenative-app
30
+ npm publish
31
+ ```
32
+
33
+ Users can then run `npx create-gamenative-app@latest` to get the new template.
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import inquirer from 'inquirer'
3
+ import { mkdir, readFile, writeFile, copyFile } from 'fs/promises'
4
+ import { join, dirname } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { spawn } from 'child_process'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
9
+ const TEMPLATE_DIR = join(__dirname, 'template')
10
+
11
+ function kebab(s) {
12
+ return s
13
+ .trim()
14
+ .replace(/\s+/g, '-')
15
+ .replace(/[^a-zA-Z0-9-]/g, '')
16
+ .toLowerCase() || 'my-game'
17
+ }
18
+
19
+ const TEMPLATE_FILES = [
20
+ 'package.json',
21
+ 'tsconfig.json',
22
+ 'game.config.json',
23
+ 'README.md',
24
+ 'FRAMEWORK.md',
25
+ 'src/main.ts',
26
+ 'src/config.ts',
27
+ 'src/Game.ts',
28
+ 'src/Input.ts',
29
+ 'src/Graphics.ts',
30
+ 'src/types.ts',
31
+ 'src/index.ts',
32
+ 'src/icon.ts',
33
+ 'src/pngjs.d.ts',
34
+ 'src/games/Game.ts',
35
+ 'scripts/copy-config.js',
36
+ ]
37
+
38
+ async function scaffold(projectName, parentDir) {
39
+ const name = kebab(projectName)
40
+ const title = projectName.trim() || name
41
+ const projectPath = join(parentDir, name)
42
+
43
+ await mkdir(projectPath, { recursive: true })
44
+ await mkdir(join(projectPath, 'src', 'games'), { recursive: true })
45
+ await mkdir(join(projectPath, 'scripts'), { recursive: true })
46
+ await mkdir(join(projectPath, 'assets'), { recursive: true })
47
+
48
+ for (const rel of TEMPLATE_FILES) {
49
+ const src = join(TEMPLATE_DIR, rel)
50
+ const dest = join(projectPath, rel)
51
+ const content = await readFile(src, 'utf-8')
52
+ const out = content.replace(/\{\{NAME\}\}/g, name).replace(/\{\{TITLE\}\}/g, title)
53
+ await writeFile(dest, out)
54
+ }
55
+
56
+ try {
57
+ await copyFile(join(TEMPLATE_DIR, 'assets', 'icon.png'), join(projectPath, 'assets', 'icon.png'))
58
+ } catch {}
59
+
60
+ return { projectPath, name }
61
+ }
62
+
63
+ function npmInstall(cwd) {
64
+ return new Promise((resolve, reject) => {
65
+ const isWin = process.platform === 'win32'
66
+ const child = spawn(isWin ? 'npm.cmd' : 'npm', ['install'], {
67
+ cwd,
68
+ stdio: 'inherit',
69
+ shell: isWin,
70
+ })
71
+ child.on('close', (code) => (code === 0 ? resolve() : reject(new Error('npm install failed'))))
72
+ })
73
+ }
74
+
75
+ async function main() {
76
+ console.log('\n GameNative — create a new game project\n')
77
+ const answers = await inquirer.prompt([
78
+ {
79
+ type: 'input',
80
+ name: 'projectName',
81
+ message: 'Project name:',
82
+ default: 'my-game',
83
+ validate: (v) => (v?.trim() ? true : 'Enter a name'),
84
+ },
85
+ {
86
+ type: 'input',
87
+ name: 'directory',
88
+ message: 'Create project in directory:',
89
+ default: '.',
90
+ },
91
+ ])
92
+ const dir = answers.directory?.trim() || '.'
93
+ const resolvedDir = join(process.cwd(), dir)
94
+ console.log('\n Creating project...')
95
+ const { projectPath, name } = await scaffold(answers.projectName, resolvedDir)
96
+ console.log(' Installing dependencies...')
97
+ await npmInstall(projectPath)
98
+ console.log('\n Done!\n')
99
+ console.log(' Next:\n')
100
+ console.log(` cd ${dir}/${name}`)
101
+ console.log(' npm run dev\n')
102
+ }
103
+
104
+ main().catch((err) => {
105
+ console.error(err)
106
+ process.exit(1)
107
+ })
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "create-gamenative-app",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a new GameNative desktop game project",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "create-gamenative-app": "./index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "template"
13
+ ],
14
+ "scripts": {
15
+ "prepublishOnly": "node sync-template.js"
16
+ },
17
+ "dependencies": {
18
+ "inquirer": "^9.2.23"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": [
24
+ "gamenative",
25
+ "game",
26
+ "sdl",
27
+ "webgl",
28
+ "typescript",
29
+ "scaffold",
30
+ "create-app"
31
+ ],
32
+ "license": "MIT"
33
+ }