create-pika-minigame 1.0.0 → 1.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.
package/bin/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { execSync, spawn } = require('child_process');
5
6
 
6
7
  // Colors for terminal output
7
8
  const colors = {
@@ -35,13 +36,12 @@ ${colors.bright}Options:${colors.reset}
35
36
  ${colors.bright}Examples:${colors.reset}
36
37
  npx create-pika-minigame my-awesome-game
37
38
  npx create-pika-minigame pronunciation-game
38
- npx create-pika-minigame quiz-game
39
39
 
40
40
  ${colors.bright}After creation:${colors.reset}
41
41
  cd <project-name>
42
- yarn install
43
- yarn dev # Run with mock host (standalone)
44
- yarn build:ios # Build for production
42
+ npm install
43
+ cd ios && pod install && cd ..
44
+ npm run ios # Run on iOS simulator
45
45
  `);
46
46
  }
47
47
 
@@ -50,13 +50,6 @@ function showVersion() {
50
50
  console.log(pkg.version);
51
51
  }
52
52
 
53
- function toKebabCase(str) {
54
- return str
55
- .replace(/([a-z])([A-Z])/g, '$1-$2')
56
- .replace(/[\s_]+/g, '-')
57
- .toLowerCase();
58
- }
59
-
60
53
  function toPascalCase(str) {
61
54
  return str
62
55
  .split(/[-_\s]+/)
@@ -64,11 +57,13 @@ function toPascalCase(str) {
64
57
  .join('');
65
58
  }
66
59
 
67
- function toSnakeCase(str) {
68
- return str
69
- .replace(/([a-z])([A-Z])/g, '$1_$2')
70
- .replace(/[\s-]+/g, '_')
71
- .toLowerCase();
60
+ function runCommand(command, cwd = process.cwd()) {
61
+ try {
62
+ execSync(command, { cwd, stdio: 'inherit' });
63
+ return true;
64
+ } catch (error) {
65
+ return false;
66
+ }
72
67
  }
73
68
 
74
69
  function copyDir(src, dest, replacements = {}) {
@@ -98,10 +93,47 @@ function copyDir(src, dest, replacements = {}) {
98
93
  }
99
94
  }
100
95
 
96
+ function patchPodfile(projectDir) {
97
+ const podfilePath = path.join(projectDir, 'ios', 'Podfile');
98
+
99
+ if (!fs.existsSync(podfilePath)) {
100
+ log.warn('Podfile not found, skipping patch');
101
+ return;
102
+ }
103
+
104
+ let content = fs.readFileSync(podfilePath, 'utf8');
105
+
106
+ // Add fmt auto-patch to post_install
107
+ const fmtFix = `
108
+ # Fix fmt consteval issue with newer Xcode (auto-patch)
109
+ fmt_base_h = File.join(__dir__, 'Pods/fmt/include/fmt/base.h')
110
+ if File.exist?(fmt_base_h)
111
+ content = File.read(fmt_base_h)
112
+ patched = content.gsub(
113
+ /#elif defined\\(__apple_build_version__\\) && __apple_build_version__ < \\d+L/,
114
+ '#elif defined(__apple_build_version__)'
115
+ )
116
+ if content != patched
117
+ File.write(fmt_base_h, patched)
118
+ Pod::UI.puts "Patched fmt/base.h for Xcode compatibility".green
119
+ end
120
+ end`;
121
+
122
+ // Insert after react_native_post_install
123
+ if (content.includes('react_native_post_install')) {
124
+ content = content.replace(
125
+ /(react_native_post_install\([^)]+\))/,
126
+ `$1\n${fmtFix}`
127
+ );
128
+ fs.writeFileSync(podfilePath, content);
129
+ log.success('Patched Podfile with auto fmt fix');
130
+ }
131
+ }
132
+
133
+
101
134
  function main() {
102
135
  const args = process.argv.slice(2);
103
136
 
104
- // Handle flags
105
137
  if (args.includes('--help') || args.includes('-h') || args.length === 0) {
106
138
  showHelp();
107
139
  process.exit(0);
@@ -127,6 +159,7 @@ function main() {
127
159
  }
128
160
 
129
161
  const targetDir = path.resolve(process.cwd(), projectName);
162
+ const pascalName = toPascalCase(projectName);
130
163
 
131
164
  if (fs.existsSync(targetDir)) {
132
165
  log.error(`Directory "${projectName}" already exists.`);
@@ -143,44 +176,86 @@ ${colors.bright}${colors.cyan}
143
176
  ${colors.reset}
144
177
  `);
145
178
 
146
- log.info(`Creating a new Pika mini-game in ${colors.bright}${targetDir}${colors.reset}`);
179
+ log.info(`Creating a new Pika mini-game: ${colors.bright}${pascalName}${colors.reset}`);
147
180
 
148
- const kebabName = toKebabCase(projectName);
149
- const pascalName = toPascalCase(projectName);
150
- const snakeName = toSnakeCase(projectName);
181
+ // Step 1: Create React Native project
182
+ log.step(1, 'Creating React Native 0.77 project...');
183
+
184
+ const initSuccess = runCommand(
185
+ `npx @react-native-community/cli init ${pascalName} --version 0.77.0 --directory "${projectName}" --skip-install`,
186
+ process.cwd()
187
+ );
188
+
189
+ if (!initSuccess) {
190
+ log.error('Failed to create React Native project');
191
+ process.exit(1);
192
+ }
193
+
194
+ // Step 2: Copy Pika mini-game source files
195
+ log.step(2, 'Adding Pika mini-game template files...');
196
+
197
+ const templatesDir = path.join(__dirname, '..', 'templates');
198
+ const srcTemplateDir = path.join(templatesDir, 'src');
199
+ const devTemplateDir = path.join(templatesDir, 'dev');
151
200
 
152
201
  const replacements = {
153
202
  PROJECT_NAME: projectName,
154
- PROJECT_NAME_KEBAB: kebabName,
155
203
  PROJECT_NAME_PASCAL: pascalName,
156
- PROJECT_NAME_SNAKE: snakeName,
157
- YEAR: new Date().getFullYear().toString(),
158
204
  };
159
205
 
160
- log.step(1, 'Creating project structure...');
206
+ // Copy src folder
207
+ if (fs.existsSync(srcTemplateDir)) {
208
+ copyDir(srcTemplateDir, path.join(targetDir, 'src'), replacements);
209
+ }
161
210
 
162
- const templatesDir = path.join(__dirname, '..', 'templates');
163
- copyDir(templatesDir, targetDir, replacements);
211
+ // Copy dev folder
212
+ if (fs.existsSync(devTemplateDir)) {
213
+ copyDir(devTemplateDir, path.join(targetDir, 'dev'), replacements);
214
+ }
164
215
 
165
- log.step(2, 'Project created successfully!');
216
+ // Step 3: Patch Podfile for fmt compatibility
217
+ log.step(3, 'Patching for Xcode compatibility...');
218
+ patchPodfile(targetDir);
219
+
220
+ // Step 4: Update package.json with additional dependencies
221
+ log.step(4, 'Updating package.json...');
222
+
223
+ const pkgPath = path.join(targetDir, 'package.json');
224
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
225
+
226
+ pkg.dependencies = {
227
+ ...pkg.dependencies,
228
+ 'react-native-gesture-handler': '~2.20.0',
229
+ 'react-native-reanimated': '~3.16.0',
230
+ 'react-native-safe-area-context': '4.12.0',
231
+ };
232
+
233
+ // No additional scripts needed - fmt fix is automatic in Podfile
234
+
235
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
236
+
237
+ log.step(5, 'Project created successfully!');
166
238
 
167
239
  console.log(`
168
- ${colors.green}Success!${colors.reset} Created ${colors.bright}${projectName}${colors.reset} at ${targetDir}
240
+ ${colors.green}Success!${colors.reset} Created ${colors.bright}${pascalName}${colors.reset} at ${targetDir}
169
241
 
170
242
  ${colors.bright}Next steps:${colors.reset}
171
243
 
172
244
  ${colors.cyan}cd ${projectName}${colors.reset}
173
- ${colors.cyan}yarn install${colors.reset}
245
+ ${colors.cyan}npm install --legacy-peer-deps${colors.reset}
246
+ ${colors.cyan}cd ios && pod install && cd ..${colors.reset}
247
+ ${colors.cyan}npm run ios${colors.reset}
174
248
 
175
249
  ${colors.bright}Available commands:${colors.reset}
176
250
 
177
- ${colors.cyan}yarn dev${colors.reset} Start development with mock host (standalone)
178
- ${colors.cyan}yarn build:ios${colors.reset} Build for iOS production
179
- ${colors.cyan}yarn build:android${colors.reset} Build for Android production
180
- ${colors.cyan}yarn deploy${colors.reset} Deploy to CDN (requires setup)
251
+ ${colors.cyan}npm run ios${colors.reset} Run on iOS simulator
252
+ ${colors.cyan}npm run android${colors.reset} Run on Android emulator
253
+ ${colors.cyan}npm start${colors.reset} Start Metro bundler
254
+
255
+ ${colors.bright}Project structure:${colors.reset}
181
256
 
182
- ${colors.bright}Documentation:${colors.reset}
183
- See README.md in your project for detailed instructions.
257
+ src/ Game source code (edit this!)
258
+ dev/ Development helpers (MockHost)
184
259
 
185
260
  Happy coding! ${colors.yellow}🎮${colors.reset}
186
261
  `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-pika-minigame",
3
- "version": "1.0.0",
4
- "description": "CLI to scaffold Pika mini-game projects with standalone dev environment",
3
+ "version": "1.2.0",
4
+ "description": "CLI to scaffold Pika mini-game projects (bare React Native 0.77)",
5
5
  "author": "Pika Team",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -1,25 +1,24 @@
1
1
  # {{PROJECT_NAME_PASCAL}}
2
2
 
3
- A Pika mini-game built with React Native and Module Federation.
3
+ Pika Mini-Game built with React Native 0.77
4
4
 
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
8
  # Install dependencies
9
- yarn install
9
+ npm install --legacy-peer-deps
10
10
 
11
- # Run in development mode (standalone with mock host)
12
- yarn dev
11
+ # iOS setup (auto-patches Xcode compatibility)
12
+ cd ios && pod install && cd ..
13
13
 
14
- # Build for production
15
- yarn build:ios
16
- yarn build:android
14
+ # Run
15
+ npm run ios
17
16
  ```
18
17
 
19
18
  ## Project Structure
20
19
 
21
20
  ```
22
- {{PROJECT_NAME_KEBAB}}/
21
+ {{PROJECT_NAME}}/
23
22
  ├── src/ # Your game source code
24
23
  │ ├── App.tsx # Main entry component
25
24
  │ ├── tokens.ts # Design system tokens
@@ -30,26 +29,34 @@ yarn build:android
30
29
  │ ├── TopBar.tsx
31
30
  │ └── Spinner.tsx
32
31
 
33
- ├── dev/ # Standalone development
34
- │ ├── index.js # Dev entry point
35
- │ ├── DevApp.tsx # Wrapper with mock host
32
+ ├── dev/ # Development helpers
36
33
  │ └── MockHost.ts # Mock native capabilities
37
34
 
38
- ├── index.js # Production entry (Module Federation)
39
- ├── rspack.config.mjs # Re.Pack bundler config
40
- ├── app.json # Expo config (for dev mode)
35
+ ├── scripts/ # Build scripts
36
+ │ └── fix-fmt.sh # Xcode compatibility fix
37
+
38
+ ├── ios/ # iOS native code
39
+ ├── android/ # Android native code
41
40
  └── package.json
42
41
  ```
43
42
 
44
43
  ## Development
45
44
 
46
- ### Running Standalone
45
+ ### Running on iOS
46
+
47
+ ```bash
48
+ # Terminal 1: Start Metro
49
+ npm start
47
50
 
48
- The `yarn dev` command runs your mini-game as a standalone Expo app with a mocked host bridge. This lets you:
51
+ # Terminal 2: Run app
52
+ npm run ios
53
+ ```
49
54
 
50
- - Develop without the main Pika app
51
- - Test UI and game logic independently
52
- - See console logs for mock API calls
55
+ ### Running on Android
56
+
57
+ ```bash
58
+ npm run android
59
+ ```
53
60
 
54
61
  ### Mock Host
55
62
 
@@ -58,7 +65,7 @@ Edit `dev/MockHost.ts` to customize mock behavior:
58
65
  ```typescript
59
66
  // Return specific scores for testing
60
67
  stopAndCheck: async (word) => ({
61
- totalScore: 95, // Always high score
68
+ totalScore: 95,
62
69
  words: [{ word, score: 95, letters: [...] }]
63
70
  })
64
71
 
@@ -66,9 +73,6 @@ stopAndCheck: async (word) => ({
66
73
  startRecording: async () => {
67
74
  throw new Error('Microphone permission denied');
68
75
  }
69
-
70
- // Add delays for loading state testing
71
- await delay(3000);
72
76
  ```
73
77
 
74
78
  ## Design System
@@ -82,13 +86,13 @@ import { color, semantic, space, radius, typography } from './tokens';
82
86
 
83
87
  const styles = StyleSheet.create({
84
88
  container: {
85
- backgroundColor: semantic.bg, // Dark background
86
- padding: space[4], // 16px
87
- borderRadius: radius.lg, // 16px
89
+ backgroundColor: semantic.bg,
90
+ padding: space[4],
91
+ borderRadius: radius.lg,
88
92
  },
89
93
  title: {
90
- color: semantic.text, // White
91
- fontSize: typography.title1.size, // 28
94
+ color: semantic.text,
95
+ fontSize: typography.title1.size,
92
96
  fontWeight: typography.title1.fontWeight,
93
97
  },
94
98
  });
@@ -101,18 +105,9 @@ Pre-built components in `src/components/`:
101
105
  ```typescript
102
106
  import { Button, Card, TopBar, Spinner } from './components';
103
107
 
104
- // Button variants: primary, secondary, danger, ghost
105
108
  <Button label="Start" variant="primary" size="lg" onPress={...} />
106
-
107
- // Card with press interaction
108
- <Card onPress={...}>
109
- <Text>Tap me</Text>
110
- </Card>
111
-
112
- // TopBar with back/close buttons
109
+ <Card onPress={...}><Text>Tap me</Text></Card>
113
110
  <TopBar left="back" onLeftPress={goBack} title="Game" />
114
-
115
- // Loading spinner
116
111
  <Spinner size="lg" color={color.cyan[400]} />
117
112
  ```
118
113
 
@@ -130,67 +125,46 @@ interface HostBridge {
130
125
  }
131
126
  ```
132
127
 
133
- Usage in your game:
128
+ Usage:
134
129
 
135
130
  ```typescript
136
131
  const App: React.FC<GameProps> = ({ host }) => {
137
132
  const startRecording = async () => {
138
133
  await host?.pronunciation.startRecording();
139
134
  };
140
-
141
- const checkPronunciation = async (word: string) => {
142
- const result = await host?.pronunciation.stopAndCheck(word);
143
- console.log('Score:', result.totalScore);
144
- };
145
135
  };
146
136
  ```
147
137
 
148
- ## Building for Production
138
+ ## Troubleshooting
149
139
 
150
- ### 1. Build the bundle
140
+ ### Xcode fmt errors
151
141
 
142
+ Re-run pod install (it auto-patches):
152
143
  ```bash
153
- # iOS
154
- yarn build:ios
155
-
156
- # Android
157
- yarn build:android
144
+ cd ios && rm -rf Pods Podfile.lock && pod install && cd ..
145
+ # Then in Xcode: Cmd+Shift+K (Clean), Cmd+R (Run)
158
146
  ```
159
147
 
160
- ### 2. Serve locally for testing
148
+ ### Metro port in use
161
149
 
162
150
  ```bash
163
- yarn serve:ios # Serves on http://localhost:9000
151
+ lsof -ti:8081 | xargs kill -9
152
+ npm start
164
153
  ```
165
154
 
166
- ### 3. Deploy to CDN
155
+ ### Pod install fails
167
156
 
168
- Edit `package.json` to add your deployment command:
169
-
170
- ```json
171
- {
172
- "scripts": {
173
- "deploy:ios": "MF_PUBLIC_PATH=https://your-cdn.com/games/{{PROJECT_NAME_KEBAB}}/ yarn build:ios && <your-deploy-command>"
174
- }
175
- }
157
+ ```bash
158
+ cd ios
159
+ rm -rf Pods Podfile.lock
160
+ pod install
161
+ cd ..
176
162
  ```
177
163
 
178
164
  ## Integration with Pika App
179
165
 
180
- Once deployed, the main Pika app can load your mini-game:
181
-
182
- ```typescript
183
- // In the host app
184
- const game = await loadRemote('{{PROJECT_NAME_SNAKE}}/App');
185
- ```
186
-
187
- ## Tips
188
-
189
- 1. **Test on real devices** - Mock host simulates behavior, but test on device for real performance
190
- 2. **Keep it fast** - Mini-games should load instantly; minimize bundle size
191
- 3. **Handle errors** - Always check if `host` is defined before using
192
- 4. **Dark theme** - Default tokens are dark-themed for immersive game feel
166
+ Once ready, this mini-game can be loaded via Module Federation in the main Pika app.
193
167
 
194
- ## License
168
+ ---
195
169
 
196
- MIT © {{YEAR}} Pika Team
170
+ Created with `create-pika-minigame` v1.1.0
@@ -4,11 +4,8 @@
4
4
  "slug": "{{PROJECT_NAME_KEBAB}}",
5
5
  "version": "1.0.0",
6
6
  "orientation": "portrait",
7
- "icon": "./assets/icon.png",
8
7
  "userInterfaceStyle": "dark",
9
8
  "splash": {
10
- "image": "./assets/splash.png",
11
- "resizeMode": "contain",
12
9
  "backgroundColor": "#0B0B0B"
13
10
  },
14
11
  "ios": {
@@ -16,14 +13,7 @@
16
13
  "bundleIdentifier": "com.pika.minigame.{{PROJECT_NAME_KEBAB}}"
17
14
  },
18
15
  "android": {
19
- "adaptiveIcon": {
20
- "foregroundImage": "./assets/adaptive-icon.png",
21
- "backgroundColor": "#0B0B0B"
22
- },
23
16
  "package": "com.pika.minigame.{{PROJECT_NAME_SNAKE}}"
24
- },
25
- "plugins": [
26
- "expo-router"
27
- ]
17
+ }
28
18
  }
29
19
  }
@@ -0,0 +1,15 @@
1
+ const { getDefaultConfig } = require('expo/metro-config');
2
+ const path = require('path');
3
+
4
+ const projectRoot = __dirname;
5
+
6
+ const config = getDefaultConfig(projectRoot);
7
+
8
+ // Isolate this project - don't resolve from parent directories
9
+ config.watchFolders = [projectRoot];
10
+ config.resolver.nodeModulesPaths = [path.resolve(projectRoot, 'node_modules')];
11
+
12
+ // Disable looking up in parent directories
13
+ config.resolver.disableHierarchicalLookup = true;
14
+
15
+ module.exports = config;