create-pika-minigame 2.6.1 → 2.6.3
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 +20 -1
- package/package.json +1 -1
- package/templates/dev/MockHost.ts +16 -0
- package/templates/scripts/deploy-vercel.sh +30 -16
- package/templates/src/App.tsx +14 -1
- package/templates/src/hooks/useHostBridge.ts +78 -0
- package/templates/src/types.ts +7 -0
package/bin/cli.js
CHANGED
|
@@ -291,7 +291,26 @@ ${colors.reset}
|
|
|
291
291
|
|
|
292
292
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
// Step 5: Update .gitignore for Vercel
|
|
295
|
+
log.step(5, 'Updating .gitignore...');
|
|
296
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
297
|
+
if (fs.existsSync(gitignorePath)) {
|
|
298
|
+
let gitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
299
|
+
if (!gitignore.includes('.vercel-prod-url')) {
|
|
300
|
+
gitignore += `
|
|
301
|
+
# Vercel
|
|
302
|
+
.vercel
|
|
303
|
+
.vercel-prod-url
|
|
304
|
+
|
|
305
|
+
# Build outputs
|
|
306
|
+
build/
|
|
307
|
+
`;
|
|
308
|
+
fs.writeFileSync(gitignorePath, gitignore);
|
|
309
|
+
log.success('Updated .gitignore');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
log.step(6, 'Project created successfully!');
|
|
295
314
|
|
|
296
315
|
console.log(`
|
|
297
316
|
${colors.green}Success!${colors.reset} Created ${colors.bright}${pascalName}${colors.reset} at ${targetDir}
|
package/package.json
CHANGED
|
@@ -13,6 +13,10 @@ let recordingStartTime = 0;
|
|
|
13
13
|
|
|
14
14
|
// Configure mock behavior (edit these for testing)
|
|
15
15
|
const MOCK_CONFIG = {
|
|
16
|
+
// Mock access token for API calls (replace with real token for testing)
|
|
17
|
+
accessToken: '__DEV_TOKEN__',
|
|
18
|
+
// Mock user ID
|
|
19
|
+
userId: 'dev-user-123',
|
|
16
20
|
// Set to a number (0-100) to always return this score, or null for random
|
|
17
21
|
fixedScore: null as number | null,
|
|
18
22
|
// Minimum recording time in ms (like real API)
|
|
@@ -48,6 +52,16 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
48
52
|
* Edit these to test different scenarios.
|
|
49
53
|
*/
|
|
50
54
|
export const mockHost: HostBridge = {
|
|
55
|
+
auth: {
|
|
56
|
+
getAccessToken: () => {
|
|
57
|
+
console.log('[MockHost] getAccessToken called');
|
|
58
|
+
return MOCK_CONFIG.accessToken;
|
|
59
|
+
},
|
|
60
|
+
getUserId: () => {
|
|
61
|
+
console.log('[MockHost] getUserId called');
|
|
62
|
+
return MOCK_CONFIG.userId;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
51
65
|
pronunciation: {
|
|
52
66
|
startRecording: async () => {
|
|
53
67
|
if (isRecording) {
|
|
@@ -128,6 +142,7 @@ export const mockHost: HostBridge = {
|
|
|
128
142
|
* Mock host that always fails - useful for testing error states.
|
|
129
143
|
*/
|
|
130
144
|
export const mockHostWithErrors: HostBridge = {
|
|
145
|
+
auth: mockHost.auth,
|
|
131
146
|
pronunciation: {
|
|
132
147
|
startRecording: async () => {
|
|
133
148
|
throw new Error('Microphone permission denied');
|
|
@@ -143,6 +158,7 @@ export const mockHostWithErrors: HostBridge = {
|
|
|
143
158
|
* Mock host with slow responses - useful for testing loading states.
|
|
144
159
|
*/
|
|
145
160
|
export const mockHostSlow: HostBridge = {
|
|
161
|
+
auth: mockHost.auth,
|
|
146
162
|
pronunciation: {
|
|
147
163
|
startRecording: async () => {
|
|
148
164
|
await delay(2000);
|
|
@@ -8,17 +8,16 @@ set -e
|
|
|
8
8
|
# Usage:
|
|
9
9
|
# ./scripts/deploy-vercel.sh [--prod]
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# First deploy creates a new Vercel project and saves the production URL.
|
|
12
|
+
# Subsequent deploys use the saved URL automatically.
|
|
13
|
+
#
|
|
14
|
+
# To reset and create a new Vercel project:
|
|
15
|
+
# rm -rf .vercel .vercel-prod-url && ./scripts/deploy-vercel.sh --prod
|
|
14
16
|
#
|
|
15
17
|
# Prerequisites:
|
|
16
18
|
# - Vercel CLI: npm i -g vercel
|
|
17
19
|
# - Logged in: vercel login
|
|
18
20
|
#
|
|
19
|
-
# Example:
|
|
20
|
-
# VERCEL_PROD_URL=https://deploy-pink-mu.vercel.app ./scripts/deploy-vercel.sh --prod
|
|
21
|
-
#
|
|
22
21
|
# =============================================================================
|
|
23
22
|
|
|
24
23
|
# Colors
|
|
@@ -41,16 +40,23 @@ fi
|
|
|
41
40
|
|
|
42
41
|
# Parse arguments
|
|
43
42
|
PROD_FLAG=""
|
|
43
|
+
PROD_URL_FILE=".vercel-prod-url"
|
|
44
|
+
|
|
44
45
|
if [[ "$1" == "--prod" ]]; then
|
|
45
46
|
PROD_FLAG="--prod"
|
|
46
47
|
log_info "Deploying to PRODUCTION"
|
|
47
48
|
|
|
48
|
-
# Check for
|
|
49
|
+
# Check for saved production URL (project-local)
|
|
50
|
+
if [[ -f "$PROD_URL_FILE" ]]; then
|
|
51
|
+
SAVED_PROD_URL=$(cat "$PROD_URL_FILE" | tr -d '[:space:]')
|
|
52
|
+
if [[ -n "$SAVED_PROD_URL" ]]; then
|
|
53
|
+
log_info "Using saved production URL: $SAVED_PROD_URL"
|
|
54
|
+
VERCEL_PROD_URL="$SAVED_PROD_URL"
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
49
58
|
if [[ -z "$VERCEL_PROD_URL" ]]; then
|
|
50
|
-
|
|
51
|
-
log_warn "For reliable deploys, set: VERCEL_PROD_URL=https://your-project.vercel.app"
|
|
52
|
-
else
|
|
53
|
-
log_info "Production URL: $VERCEL_PROD_URL"
|
|
59
|
+
log_info "No saved production URL found. Will detect after first deploy."
|
|
54
60
|
fi
|
|
55
61
|
else
|
|
56
62
|
log_info "Deploying to PREVIEW (add --prod for production)"
|
|
@@ -129,9 +135,11 @@ cat > "$BUILD_DIR/index.html" << EOF
|
|
|
129
135
|
</html>
|
|
130
136
|
EOF
|
|
131
137
|
|
|
132
|
-
# Create vercel.json for proper routing
|
|
138
|
+
# Create vercel.json with project name for proper routing
|
|
139
|
+
# Using project name ensures each game gets its own Vercel project
|
|
133
140
|
cat > "$BUILD_DIR/vercel.json" << EOF
|
|
134
141
|
{
|
|
142
|
+
"name": "${PROJECT_NAME}",
|
|
135
143
|
"headers": [
|
|
136
144
|
{
|
|
137
145
|
"source": "/(.*)",
|
|
@@ -168,11 +176,12 @@ fi
|
|
|
168
176
|
# For production, use VERCEL_PROD_URL if set, otherwise try to auto-detect
|
|
169
177
|
if [[ "$PROD_FLAG" == "--prod" ]]; then
|
|
170
178
|
if [[ -n "$VERCEL_PROD_URL" ]]; then
|
|
171
|
-
# Use the
|
|
179
|
+
# Use the saved/provided production URL
|
|
172
180
|
DEPLOY_URL="$VERCEL_PROD_URL"
|
|
173
|
-
log_info "Using
|
|
181
|
+
log_info "Using production URL: $DEPLOY_URL"
|
|
174
182
|
else
|
|
175
|
-
log_info "
|
|
183
|
+
log_info "Detecting production alias from Vercel..."
|
|
184
|
+
|
|
176
185
|
# Try to get alias from the deployed URL
|
|
177
186
|
PROD_DOMAIN=$(vercel inspect "$DEPLOY_URL" --json 2>/dev/null | node -e "
|
|
178
187
|
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
|
|
@@ -186,8 +195,13 @@ if [[ "$PROD_FLAG" == "--prod" ]]; then
|
|
|
186
195
|
DEPLOY_URL="https://$PROD_DOMAIN"
|
|
187
196
|
log_info "Detected production alias: $DEPLOY_URL"
|
|
188
197
|
else
|
|
189
|
-
|
|
198
|
+
# Use the deployment URL as-is (first deploy)
|
|
199
|
+
log_info "Using deployment URL as production URL: $DEPLOY_URL"
|
|
190
200
|
fi
|
|
201
|
+
|
|
202
|
+
# Save for future deploys
|
|
203
|
+
echo "$DEPLOY_URL" > "$PROD_URL_FILE"
|
|
204
|
+
log_success "Saved production URL to $PROD_URL_FILE"
|
|
191
205
|
fi
|
|
192
206
|
fi
|
|
193
207
|
|
package/templates/src/App.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native';
|
|
3
3
|
import { TopBar, Button, Card } from './components';
|
|
4
4
|
import { semantic, space, typography, color, radius } from './tokens';
|
|
5
5
|
import { GameProps, Screen } from './types';
|
|
6
|
+
import { getAccessToken, getUserId } from './hooks/useHostBridge';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* {{PROJECT_NAME_PASCAL}} - Main App Component
|
|
@@ -16,6 +17,18 @@ const App: React.FC<GameProps> = ({ onExit, onGameOver, host }) => {
|
|
|
16
17
|
const [screen, setScreen] = useState<Screen>('home');
|
|
17
18
|
const [score, setScore] = useState(0);
|
|
18
19
|
|
|
20
|
+
// Example: Get access token from host (falls back to mock in dev mode)
|
|
21
|
+
const accessToken = getAccessToken(host);
|
|
22
|
+
const userId = getUserId(host);
|
|
23
|
+
|
|
24
|
+
// Example: Use token for API calls
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (__DEV__) {
|
|
27
|
+
console.log('[Game] Access Token:', accessToken ? `${accessToken.substring(0, 20)}...` : 'none');
|
|
28
|
+
console.log('[Game] User ID:', userId);
|
|
29
|
+
}
|
|
30
|
+
}, [accessToken, userId]);
|
|
31
|
+
|
|
19
32
|
const handleStartGame = useCallback(() => {
|
|
20
33
|
setScreen('game');
|
|
21
34
|
setScore(0);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to access host bridge (auth, pronunciation, etc.)
|
|
3
|
+
* Falls back to mock values when running standalone (not in host app)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface HostAuth {
|
|
7
|
+
getAccessToken: () => string;
|
|
8
|
+
getUserId: () => string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HostBridge {
|
|
12
|
+
auth: HostAuth;
|
|
13
|
+
pronunciation?: {
|
|
14
|
+
startRecording: () => Promise<void>;
|
|
15
|
+
stopAndCheck: (target: string) => Promise<any>;
|
|
16
|
+
cancel: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GameBridgeProps {
|
|
21
|
+
onExit: () => void;
|
|
22
|
+
onGameOver?: (result: { score: number }) => void;
|
|
23
|
+
host: HostBridge;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Mock token for standalone development - replace with your test token
|
|
27
|
+
const MOCK_ACCESS_TOKEN = '__DEV_TOKEN__';
|
|
28
|
+
const MOCK_USER_ID = 'dev-user-123';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a mock host bridge for standalone development
|
|
32
|
+
*/
|
|
33
|
+
export function createMockHostBridge(): HostBridge {
|
|
34
|
+
return {
|
|
35
|
+
auth: {
|
|
36
|
+
getAccessToken: () => MOCK_ACCESS_TOKEN,
|
|
37
|
+
getUserId: () => MOCK_USER_ID,
|
|
38
|
+
},
|
|
39
|
+
pronunciation: {
|
|
40
|
+
startRecording: async () => {
|
|
41
|
+
console.log('[MockHost] startRecording');
|
|
42
|
+
},
|
|
43
|
+
stopAndCheck: async (target: string) => {
|
|
44
|
+
console.log('[MockHost] stopAndCheck:', target);
|
|
45
|
+
return { totalScore: 85, words: [] };
|
|
46
|
+
},
|
|
47
|
+
cancel: async () => {
|
|
48
|
+
console.log('[MockHost] cancel');
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get access token from host bridge
|
|
56
|
+
* @param host - Host bridge from props (undefined when running standalone)
|
|
57
|
+
* @returns Access token string
|
|
58
|
+
*/
|
|
59
|
+
export function getAccessToken(host?: HostBridge): string {
|
|
60
|
+
if (host?.auth?.getAccessToken) {
|
|
61
|
+
return host.auth.getAccessToken();
|
|
62
|
+
}
|
|
63
|
+
// Fallback for standalone development
|
|
64
|
+
console.warn('[useHostBridge] No host bridge, using mock token');
|
|
65
|
+
return MOCK_ACCESS_TOKEN;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get user ID from host bridge
|
|
70
|
+
* @param host - Host bridge from props (undefined when running standalone)
|
|
71
|
+
* @returns User ID or null
|
|
72
|
+
*/
|
|
73
|
+
export function getUserId(host?: HostBridge): string | null {
|
|
74
|
+
if (host?.auth?.getUserId) {
|
|
75
|
+
return host.auth.getUserId();
|
|
76
|
+
}
|
|
77
|
+
return MOCK_USER_ID;
|
|
78
|
+
}
|
package/templates/src/types.ts
CHANGED
|
@@ -18,6 +18,13 @@ export interface PronunciationResult {
|
|
|
18
18
|
* In dev mode, this is mocked by MockHost.
|
|
19
19
|
*/
|
|
20
20
|
export interface HostBridge {
|
|
21
|
+
/** Auth info from host app */
|
|
22
|
+
auth: {
|
|
23
|
+
/** Get current access token (empty if not logged in) */
|
|
24
|
+
getAccessToken: () => string;
|
|
25
|
+
/** Get current user ID (null if not logged in) */
|
|
26
|
+
getUserId: () => string | null;
|
|
27
|
+
};
|
|
21
28
|
pronunciation: {
|
|
22
29
|
startRecording: () => Promise<void>;
|
|
23
30
|
stopAndCheck: (target: string) => Promise<PronunciationResult>;
|