create-tosijs-platform-app 1.0.3 → 1.0.5
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/create-tosijs-platform-app.js +445 -521
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
@@ -7,16 +7,22 @@ import path from 'path'
|
|
|
7
7
|
import fs from 'fs'
|
|
8
8
|
import readline from 'readline'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
input: process.stdin,
|
|
12
|
-
output: process.stdout,
|
|
13
|
-
})
|
|
14
|
-
|
|
10
|
+
// Helper to prompt user
|
|
15
11
|
function question(query) {
|
|
16
|
-
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
})
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
rl.question(query, (answer) => {
|
|
18
|
+
rl.close()
|
|
19
|
+
resolve(answer)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
// Execute shell command
|
|
25
|
+
function exec(command, options = {}) {
|
|
20
26
|
try {
|
|
21
27
|
return execSync(command, {
|
|
22
28
|
stdio: options.silent ? 'pipe' : 'inherit',
|
|
@@ -24,13 +30,41 @@ function execCommand(command, options = {}) {
|
|
|
24
30
|
...options,
|
|
25
31
|
})
|
|
26
32
|
} catch (error) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
33
|
+
if (options.allowFailure) {
|
|
34
|
+
return null
|
|
29
35
|
}
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Execute and parse JSON output
|
|
41
|
+
function execJson(command) {
|
|
42
|
+
try {
|
|
43
|
+
const result = execSync(command, {
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
encoding: 'utf-8',
|
|
46
|
+
})
|
|
47
|
+
return JSON.parse(result)
|
|
48
|
+
} catch {
|
|
30
49
|
return null
|
|
31
50
|
}
|
|
32
51
|
}
|
|
33
52
|
|
|
53
|
+
// Validation helpers
|
|
54
|
+
function validateProjectId(projectId) {
|
|
55
|
+
const re = /^[a-z][a-z0-9-]*[a-z0-9]$/
|
|
56
|
+
return re.test(projectId) && projectId.length >= 6 && projectId.length <= 30
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateEmail(email) {
|
|
60
|
+
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
61
|
+
return re.test(email)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Step 1: Check prerequisites (Bun, Firebase CLI, logged in)
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
34
68
|
function checkBun() {
|
|
35
69
|
try {
|
|
36
70
|
const version = execSync('bun --version', {
|
|
@@ -39,8 +73,7 @@ function checkBun() {
|
|
|
39
73
|
}).trim()
|
|
40
74
|
console.log(`✓ Bun installed (${version})`)
|
|
41
75
|
return true
|
|
42
|
-
} catch
|
|
43
|
-
console.log('✗ Bun not found')
|
|
76
|
+
} catch {
|
|
44
77
|
return false
|
|
45
78
|
}
|
|
46
79
|
}
|
|
@@ -53,8 +86,7 @@ function checkFirebaseCLI() {
|
|
|
53
86
|
}).trim()
|
|
54
87
|
console.log(`✓ Firebase CLI installed (${version})`)
|
|
55
88
|
return true
|
|
56
|
-
} catch
|
|
57
|
-
console.log('✗ Firebase CLI not found')
|
|
89
|
+
} catch {
|
|
58
90
|
return false
|
|
59
91
|
}
|
|
60
92
|
}
|
|
@@ -68,262 +100,364 @@ function checkFirebaseLogin() {
|
|
|
68
100
|
if (result.includes('No authorized accounts')) {
|
|
69
101
|
return false
|
|
70
102
|
}
|
|
71
|
-
console.log('✓
|
|
103
|
+
console.log('✓ Logged in to Firebase')
|
|
72
104
|
return true
|
|
73
|
-
} catch
|
|
105
|
+
} catch {
|
|
74
106
|
return false
|
|
75
107
|
}
|
|
76
108
|
}
|
|
77
109
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
return re.test(email)
|
|
81
|
-
}
|
|
110
|
+
async function ensurePrerequisites() {
|
|
111
|
+
console.log('\n📋 Checking prerequisites...\n')
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
if (!checkBun()) {
|
|
114
|
+
console.log('\n❌ Bun is required but not installed.')
|
|
115
|
+
console.log('\nInstall Bun:')
|
|
116
|
+
console.log(' curl -fsSL https://bun.sh/install | bash\n')
|
|
117
|
+
process.exit(1)
|
|
118
|
+
}
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.log('\
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
stdio: 'pipe',
|
|
95
|
-
encoding: 'utf-8',
|
|
96
|
-
}
|
|
97
|
-
)
|
|
120
|
+
if (!checkFirebaseCLI()) {
|
|
121
|
+
console.log('\n❌ Firebase CLI is required but not installed.')
|
|
122
|
+
console.log('\nInstall Firebase CLI:')
|
|
123
|
+
console.log(' bun install -g firebase-tools\n')
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (error.message.includes('No apps found')) {
|
|
108
|
-
console.log(' You need to create a Web App in Firebase Console first.')
|
|
127
|
+
if (!checkFirebaseLogin()) {
|
|
128
|
+
console.log('\n❌ Not logged in to Firebase.')
|
|
129
|
+
console.log('\nLogging you in now...\n')
|
|
130
|
+
exec('firebase login')
|
|
131
|
+
|
|
132
|
+
if (!checkFirebaseLogin()) {
|
|
133
|
+
console.log('\n❌ Login failed. Please try again.\n')
|
|
134
|
+
process.exit(1)
|
|
109
135
|
}
|
|
110
|
-
return null
|
|
111
136
|
}
|
|
112
137
|
}
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return false
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Step 2 & 3: Get project ID and verify it exists
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
function getProjects() {
|
|
144
|
+
const result = execJson('firebase projects:list --json')
|
|
145
|
+
if (result?.status === 'success') {
|
|
146
|
+
return result.result || []
|
|
123
147
|
}
|
|
148
|
+
return []
|
|
124
149
|
}
|
|
125
150
|
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
151
|
+
async function getProjectId() {
|
|
152
|
+
console.log('\n📋 Firebase Project Configuration\n')
|
|
153
|
+
|
|
154
|
+
const projects = getProjects()
|
|
155
|
+
|
|
156
|
+
if (projects.length > 0) {
|
|
157
|
+
console.log('Your Firebase projects:')
|
|
158
|
+
projects.forEach((p, i) => {
|
|
159
|
+
console.log(` ${i + 1}. ${p.projectId} (${p.displayName})`)
|
|
132
160
|
})
|
|
161
|
+
console.log()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let projectId = await question('Enter Firebase Project ID: ')
|
|
133
165
|
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
while (!validateProjectId(projectId)) {
|
|
167
|
+
console.log(
|
|
168
|
+
'❌ Invalid project ID. Use lowercase letters, numbers, and hyphens (6-30 chars).'
|
|
169
|
+
)
|
|
170
|
+
projectId = await question('Enter Firebase Project ID: ')
|
|
171
|
+
}
|
|
136
172
|
|
|
137
|
-
|
|
138
|
-
|
|
173
|
+
// Check if project exists in user's account
|
|
174
|
+
const project = projects.find((p) => p.projectId === projectId)
|
|
139
175
|
|
|
140
|
-
|
|
176
|
+
if (!project) {
|
|
177
|
+
console.log(
|
|
178
|
+
`\n❌ Project "${projectId}" not found in your Firebase account.`
|
|
179
|
+
)
|
|
180
|
+
console.log('\nTo create a new project:')
|
|
181
|
+
console.log(' 1. Go to: https://console.firebase.google.com')
|
|
182
|
+
console.log(' 2. Click "Add project"')
|
|
183
|
+
console.log(` 3. Name it "${projectId}"`)
|
|
184
|
+
console.log(' 4. Enable Google Analytics (optional)')
|
|
185
|
+
console.log(' 5. Upgrade to Blaze plan (required for Cloud Functions)')
|
|
186
|
+
console.log('\nThen run this command again.\n')
|
|
187
|
+
process.exit(1)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log(`✓ Found project: ${project.displayName}`)
|
|
191
|
+
return projectId
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Step 4: Check Blaze plan
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
function checkBlazePlan(projectId) {
|
|
199
|
+
// Try to list functions - this will fail with specific error if not on Blaze
|
|
200
|
+
try {
|
|
141
201
|
execSync(`firebase functions:list --project ${projectId}`, {
|
|
142
202
|
stdio: 'pipe',
|
|
143
203
|
encoding: 'utf-8',
|
|
144
204
|
})
|
|
145
|
-
|
|
146
|
-
// If the command succeeds, they likely have functions enabled (Blaze plan)
|
|
147
205
|
return true
|
|
148
206
|
} catch (error) {
|
|
149
|
-
|
|
207
|
+
const msg = error.message || error.stderr || ''
|
|
150
208
|
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
209
|
+
msg.includes('billing') ||
|
|
210
|
+
msg.includes('Billing account') ||
|
|
211
|
+
msg.includes('upgrade') ||
|
|
212
|
+
msg.includes('pay-as-you-go') ||
|
|
213
|
+
msg.includes('Cloud Functions')
|
|
155
214
|
) {
|
|
156
215
|
return false
|
|
157
216
|
}
|
|
158
|
-
//
|
|
159
|
-
return
|
|
217
|
+
// Could be other errors (no functions deployed yet, etc.) - assume OK
|
|
218
|
+
return true
|
|
160
219
|
}
|
|
161
220
|
}
|
|
162
221
|
|
|
163
|
-
async function
|
|
164
|
-
console.log('\n
|
|
222
|
+
async function ensureBlazePlan(projectId) {
|
|
223
|
+
console.log('\n🔍 Checking billing plan...')
|
|
165
224
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
225
|
+
const hasBlaze = checkBlazePlan(projectId)
|
|
226
|
+
|
|
227
|
+
if (!hasBlaze) {
|
|
228
|
+
console.log('\n❌ Blaze plan required but not detected.')
|
|
229
|
+
console.log(
|
|
230
|
+
'\ntosijs-platform uses Cloud Functions, which require the Blaze (pay-as-you-go) plan.'
|
|
231
|
+
)
|
|
232
|
+
console.log('\n💡 Blaze includes generous FREE tier:')
|
|
233
|
+
console.log(' • 2M function invocations/month')
|
|
234
|
+
console.log(' • 5GB storage')
|
|
235
|
+
console.log(' • 10GB hosting transfer')
|
|
236
|
+
console.log(' Most small sites stay completely free!')
|
|
237
|
+
console.log('\nTo upgrade:')
|
|
238
|
+
console.log(
|
|
239
|
+
` 1. Go to: https://console.firebase.google.com/project/${projectId}/usage/details`
|
|
240
|
+
)
|
|
241
|
+
console.log(' 2. Click "Modify plan" → "Blaze"')
|
|
242
|
+
console.log(' 3. Add billing account')
|
|
243
|
+
console.log('\nThen run this command again.\n')
|
|
169
244
|
process.exit(1)
|
|
170
245
|
}
|
|
171
246
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const gitRepo = 'https://github.com/tonioloewald/tosijs-platform.git'
|
|
247
|
+
console.log('✓ Blaze plan active')
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
176
250
|
|
|
177
|
-
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Step 5: Check/create web app
|
|
253
|
+
// ============================================================================
|
|
178
254
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
process.exit(1)
|
|
255
|
+
function getWebApps(projectId) {
|
|
256
|
+
const result = execJson(
|
|
257
|
+
`firebase apps:list WEB --project ${projectId} --json`
|
|
258
|
+
)
|
|
259
|
+
if (result?.status === 'success') {
|
|
260
|
+
return result.result || []
|
|
186
261
|
}
|
|
262
|
+
return []
|
|
263
|
+
}
|
|
187
264
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
265
|
+
function createWebApp(projectId, appName) {
|
|
266
|
+
console.log(`\n📱 Creating web app "${appName}"...`)
|
|
267
|
+
try {
|
|
268
|
+
exec(`firebase apps:create WEB "${appName}" --project ${projectId}`, {
|
|
269
|
+
silent: true,
|
|
270
|
+
})
|
|
271
|
+
console.log('✓ Web app created')
|
|
272
|
+
return true
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.log('❌ Failed to create web app:', error.message)
|
|
275
|
+
return false
|
|
195
276
|
}
|
|
277
|
+
}
|
|
196
278
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
console.log('\n❌ You need to be logged in to Firebase.')
|
|
200
|
-
console.log('\nRun this command to log in:')
|
|
201
|
-
console.log(' firebase login\n')
|
|
202
|
-
console.log('Then run this command again.\n')
|
|
203
|
-
process.exit(1)
|
|
204
|
-
}
|
|
279
|
+
async function ensureWebApp(projectId, projectName) {
|
|
280
|
+
console.log('\n🔍 Checking web apps...')
|
|
205
281
|
|
|
206
|
-
|
|
207
|
-
console.log('Required information:\n')
|
|
282
|
+
const apps = getWebApps(projectId)
|
|
208
283
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
// Look for an app with matching name
|
|
285
|
+
let app = apps.find((a) => a.displayName === projectName)
|
|
286
|
+
|
|
287
|
+
if (!app && apps.length > 0) {
|
|
288
|
+
console.log('\nExisting web apps:')
|
|
289
|
+
apps.forEach((a, i) => {
|
|
290
|
+
console.log(` ${i + 1}. ${a.displayName}`)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const answer = await question(
|
|
294
|
+
`\nUse existing app, or create new "${projectName}"? [1-${apps.length}/new]: `
|
|
213
295
|
)
|
|
214
|
-
firebaseProjectId = await question('Firebase Project ID: ')
|
|
215
|
-
}
|
|
216
296
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
297
|
+
if (answer.toLowerCase() === 'new') {
|
|
298
|
+
createWebApp(projectId, projectName)
|
|
299
|
+
// Refresh the list
|
|
300
|
+
const newApps = getWebApps(projectId)
|
|
301
|
+
app = newApps.find((a) => a.displayName === projectName)
|
|
302
|
+
} else {
|
|
303
|
+
const index = parseInt(answer) - 1
|
|
304
|
+
if (index >= 0 && index < apps.length) {
|
|
305
|
+
app = apps[index]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} else if (!app) {
|
|
309
|
+
// No apps exist, create one
|
|
310
|
+
createWebApp(projectId, projectName)
|
|
311
|
+
const newApps = getWebApps(projectId)
|
|
312
|
+
app = newApps.find((a) => a.displayName === projectName) || newApps[0]
|
|
221
313
|
}
|
|
222
314
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const firestoreExists = checkFirestoreExists(firebaseProjectId)
|
|
226
|
-
if (firestoreExists) {
|
|
315
|
+
if (!app) {
|
|
316
|
+
console.log('\n❌ No web app available. Please create one manually:')
|
|
227
317
|
console.log(
|
|
228
|
-
|
|
318
|
+
` https://console.firebase.google.com/project/${projectId}/settings/general`
|
|
229
319
|
)
|
|
230
|
-
|
|
320
|
+
process.exit(1)
|
|
231
321
|
}
|
|
232
322
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
console.log('━'.repeat(60))
|
|
237
|
-
console.log(
|
|
238
|
-
'\ntosijs-platform uses Cloud Functions for secure data access.'
|
|
239
|
-
)
|
|
240
|
-
console.log(
|
|
241
|
-
'Cloud Functions require the Firebase Blaze (pay-as-you-go) plan.\n'
|
|
242
|
-
)
|
|
243
|
-
console.log('⚠️ Your project is currently on the FREE (Spark) plan.')
|
|
244
|
-
console.log('\n❌ The platform will NOT work without upgrading.\n')
|
|
245
|
-
console.log('To upgrade to Blaze plan:')
|
|
246
|
-
console.log(
|
|
247
|
-
`1. Visit: https://console.firebase.google.com/project/${firebaseProjectId}/usage/details`
|
|
248
|
-
)
|
|
249
|
-
console.log('2. Click "Modify plan"')
|
|
250
|
-
console.log('3. Select "Blaze - Pay as you go"')
|
|
251
|
-
console.log('4. Add a billing account\n')
|
|
252
|
-
console.log('💡 Blaze plan includes generous free tier:')
|
|
253
|
-
console.log(' • 2M function invocations/month FREE')
|
|
254
|
-
console.log(' • 5GB storage FREE')
|
|
255
|
-
console.log(' • Most small sites stay within free limits\n')
|
|
256
|
-
console.log('━'.repeat(60))
|
|
257
|
-
|
|
258
|
-
const answer = await new Promise((resolve) => {
|
|
259
|
-
const newRl = readline.createInterface({
|
|
260
|
-
input: process.stdin,
|
|
261
|
-
output: process.stdout,
|
|
262
|
-
})
|
|
263
|
-
newRl.question(
|
|
264
|
-
'\nContinue anyway? (you must upgrade before deploying) [y/N]: ',
|
|
265
|
-
(ans) => {
|
|
266
|
-
newRl.close()
|
|
267
|
-
resolve(ans)
|
|
268
|
-
}
|
|
269
|
-
)
|
|
270
|
-
})
|
|
323
|
+
console.log(`✓ Using web app: ${app.displayName}`)
|
|
324
|
+
return app
|
|
325
|
+
}
|
|
271
326
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
process.exit(0)
|
|
277
|
-
}
|
|
327
|
+
// ============================================================================
|
|
328
|
+
// Step 6: Get SDK config
|
|
329
|
+
// ============================================================================
|
|
278
330
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
331
|
+
function getWebAppConfig(projectId, appId) {
|
|
332
|
+
const result = execJson(
|
|
333
|
+
`firebase apps:sdkconfig WEB ${appId} --project ${projectId} --json`
|
|
334
|
+
)
|
|
335
|
+
if (result?.status === 'success' && result.result?.sdkConfig) {
|
|
336
|
+
return result.result.sdkConfig
|
|
337
|
+
}
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Main
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
async function main() {
|
|
346
|
+
console.log('\n🚀 Create tosijs-platform App\n')
|
|
347
|
+
console.log('━'.repeat(50))
|
|
348
|
+
|
|
349
|
+
// Check command line args
|
|
350
|
+
if (process.argv.length < 3) {
|
|
351
|
+
console.log('\nUsage: bunx create-tosijs-platform-app <project-name>')
|
|
352
|
+
console.log('\nExample:')
|
|
353
|
+
console.log(' bunx create-tosijs-platform-app my-awesome-site\n')
|
|
354
|
+
process.exit(1)
|
|
286
355
|
}
|
|
287
356
|
|
|
288
|
-
const
|
|
357
|
+
const projectName = process.argv[2]
|
|
358
|
+
const currentPath = process.cwd()
|
|
359
|
+
const projectPath = path.join(currentPath, projectName)
|
|
360
|
+
const gitRepo = 'https://github.com/tonioloewald/tosijs-platform.git'
|
|
289
361
|
|
|
290
|
-
if
|
|
291
|
-
|
|
292
|
-
console.log(
|
|
293
|
-
|
|
294
|
-
console.log(
|
|
295
|
-
`https://console.firebase.google.com/project/${firebaseProjectId}/settings/general\n`
|
|
296
|
-
)
|
|
362
|
+
// Check if directory already exists
|
|
363
|
+
if (fs.existsSync(projectPath)) {
|
|
364
|
+
console.log(`\n❌ Directory "${projectName}" already exists.\n`)
|
|
365
|
+
process.exit(1)
|
|
297
366
|
}
|
|
298
367
|
|
|
299
|
-
|
|
368
|
+
// Step 1: Prerequisites
|
|
369
|
+
await ensurePrerequisites()
|
|
300
370
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
371
|
+
// Step 2 & 3: Get and validate project ID
|
|
372
|
+
const projectId = await getProjectId()
|
|
373
|
+
|
|
374
|
+
// Step 4: Ensure Blaze plan
|
|
375
|
+
await ensureBlazePlan(projectId)
|
|
376
|
+
|
|
377
|
+
// Step 5: Ensure web app exists
|
|
378
|
+
const webApp = await ensureWebApp(projectId, projectName)
|
|
379
|
+
|
|
380
|
+
// Step 6: Get SDK config
|
|
381
|
+
console.log('\n🔍 Fetching SDK configuration...')
|
|
382
|
+
const sdkConfig = getWebAppConfig(projectId, webApp.appId)
|
|
383
|
+
|
|
384
|
+
if (!sdkConfig) {
|
|
385
|
+
console.log('❌ Failed to get SDK config')
|
|
309
386
|
process.exit(1)
|
|
310
387
|
}
|
|
388
|
+
console.log('✓ Got SDK configuration')
|
|
389
|
+
|
|
390
|
+
// Get admin email
|
|
391
|
+
console.log()
|
|
392
|
+
let adminEmail = await question(
|
|
393
|
+
'Admin email (Google account for owner access): '
|
|
394
|
+
)
|
|
395
|
+
while (!validateEmail(adminEmail)) {
|
|
396
|
+
console.log('❌ Invalid email address.')
|
|
397
|
+
adminEmail = await question('Admin email: ')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Create project
|
|
402
|
+
// ============================================================================
|
|
311
403
|
|
|
312
|
-
console.log('
|
|
313
|
-
|
|
404
|
+
console.log('\n📦 Creating project...\n')
|
|
405
|
+
|
|
406
|
+
fs.mkdirSync(projectPath)
|
|
407
|
+
|
|
408
|
+
console.log('📥 Cloning template...')
|
|
409
|
+
exec(`git clone --depth 1 ${gitRepo} ${projectPath}`, { silent: true })
|
|
314
410
|
|
|
315
411
|
process.chdir(projectPath)
|
|
316
412
|
|
|
317
413
|
console.log('🧹 Cleaning up...')
|
|
318
414
|
fs.rmSync(path.join(projectPath, '.git'), { recursive: true })
|
|
319
|
-
// Remove create-script directory (utility scripts are in scripts/)
|
|
320
415
|
if (fs.existsSync(path.join(projectPath, 'create-script'))) {
|
|
321
416
|
fs.rmSync(path.join(projectPath, 'create-script'), { recursive: true })
|
|
322
417
|
}
|
|
323
418
|
|
|
324
|
-
// Configure
|
|
325
|
-
|
|
326
|
-
|
|
419
|
+
// Configure firebase-config.ts with full SDK config
|
|
420
|
+
console.log('⚙️ Configuring Firebase...')
|
|
421
|
+
|
|
422
|
+
const configContent = `// Auto-generated by create-tosijs-platform-app
|
|
423
|
+
|
|
424
|
+
const PROJECT_ID = '${sdkConfig.projectId}'
|
|
425
|
+
|
|
426
|
+
export const PRODUCTION_BASE = \`//us-central1-\${PROJECT_ID}.cloudfunctions.net/\`
|
|
427
|
+
|
|
428
|
+
export const config = {
|
|
429
|
+
apiKey: '${sdkConfig.apiKey}',
|
|
430
|
+
authDomain: '${sdkConfig.authDomain}',
|
|
431
|
+
projectId: '${sdkConfig.projectId}',
|
|
432
|
+
storageBucket: '${sdkConfig.storageBucket}',
|
|
433
|
+
messagingSenderId: '${sdkConfig.messagingSenderId}',
|
|
434
|
+
appId: '${sdkConfig.appId}',
|
|
435
|
+
measurementId: '${sdkConfig.measurementId || ''}',
|
|
436
|
+
}
|
|
437
|
+
`
|
|
438
|
+
fs.writeFileSync(
|
|
439
|
+
path.join(projectPath, 'src/firebase-config.ts'),
|
|
440
|
+
configContent
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
// Configure .firebaserc
|
|
444
|
+
fs.writeFileSync(
|
|
445
|
+
path.join(projectPath, '.firebaserc'),
|
|
446
|
+
JSON.stringify({ projects: { default: projectId } }, null, 2)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
// Update package.json
|
|
450
|
+
const packageJson = JSON.parse(
|
|
451
|
+
fs.readFileSync(path.join(projectPath, 'package.json'), 'utf8')
|
|
452
|
+
)
|
|
453
|
+
packageJson.name = projectName
|
|
454
|
+
packageJson.version = '0.1.0'
|
|
455
|
+
fs.writeFileSync(
|
|
456
|
+
path.join(projectPath, 'package.json'),
|
|
457
|
+
JSON.stringify(packageJson, null, 2)
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
// Update initial_state role with admin email
|
|
327
461
|
const roleFilePath = path.join(
|
|
328
462
|
projectPath,
|
|
329
463
|
'initial_state/firestore/role.json'
|
|
@@ -332,105 +466,44 @@ async function main() {
|
|
|
332
466
|
const roles = JSON.parse(fs.readFileSync(roleFilePath, 'utf8'))
|
|
333
467
|
const ownerRole = roles.find((r) => r._id === 'owner-role')
|
|
334
468
|
if (ownerRole) {
|
|
335
|
-
// Update the owner role's email contact
|
|
336
469
|
ownerRole.contacts = [{ type: 'email', value: adminEmail }]
|
|
337
470
|
}
|
|
338
471
|
fs.writeFileSync(roleFilePath, JSON.stringify(roles, null, 2))
|
|
339
472
|
}
|
|
340
473
|
|
|
341
|
-
//
|
|
474
|
+
// Clear test auth users
|
|
342
475
|
const authUsersPath = path.join(projectPath, 'initial_state/auth/users.json')
|
|
343
476
|
if (fs.existsSync(authUsersPath)) {
|
|
344
477
|
fs.writeFileSync(authUsersPath, '[]')
|
|
345
478
|
}
|
|
346
479
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
let configContent
|
|
350
|
-
if (firebaseConfig) {
|
|
351
|
-
configContent = `const PROJECT_ID = '${firebaseProjectId}'
|
|
352
|
-
|
|
353
|
-
export const PRODUCTION_BASE = \`//us-central1-\${PROJECT_ID}.cloudfunctions.net/\`
|
|
354
|
-
|
|
355
|
-
export const config = {
|
|
356
|
-
authDomain: '${firebaseConfig.authDomain}',
|
|
357
|
-
projectId: '${firebaseConfig.projectId}',
|
|
358
|
-
storageBucket: '${firebaseConfig.storageBucket}',
|
|
359
|
-
apiKey: '${firebaseConfig.apiKey}',
|
|
360
|
-
messagingSenderId: '${firebaseConfig.messagingSenderId}',
|
|
361
|
-
appId: '${firebaseConfig.appId}',
|
|
362
|
-
measurementId: '${firebaseConfig.measurementId || 'G-XXXXXXXXXX'}',
|
|
363
|
-
}
|
|
364
|
-
`
|
|
365
|
-
} else {
|
|
366
|
-
configContent = `const PROJECT_ID = '${firebaseProjectId}'
|
|
367
|
-
|
|
368
|
-
export const PRODUCTION_BASE = \`//us-central1-\${PROJECT_ID}.cloudfunctions.net/\`
|
|
369
|
-
|
|
370
|
-
export const config = {
|
|
371
|
-
authDomain: \`\${PROJECT_ID}.firebaseapp.com\`,
|
|
372
|
-
projectId: PROJECT_ID,
|
|
373
|
-
storageBucket: \`\${PROJECT_ID}.appspot.com\`,
|
|
374
|
-
apiKey: 'YOUR_API_KEY_HERE',
|
|
375
|
-
messagingSenderId: 'YOUR_SENDER_ID_HERE',
|
|
376
|
-
appId: 'YOUR_APP_ID_HERE',
|
|
377
|
-
measurementId: 'YOUR_MEASUREMENT_ID_HERE',
|
|
378
|
-
}
|
|
379
|
-
`
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
fs.writeFileSync(
|
|
383
|
-
path.join(projectPath, 'src/firebase-config.ts'),
|
|
384
|
-
configContent
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
// Update config.json with project-specific values
|
|
480
|
+
// Update config.json
|
|
388
481
|
const configJsonPath = path.join(
|
|
389
482
|
projectPath,
|
|
390
483
|
'initial_state/firestore/config.json'
|
|
391
484
|
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// Update blog config
|
|
404
|
-
const blogConfig = configData.find((c) => c._id === 'blog')
|
|
405
|
-
if (blogConfig) {
|
|
406
|
-
blogConfig.prefix = `${projectName} | `
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
fs.writeFileSync(configJsonPath, JSON.stringify(configData, null, 2))
|
|
410
|
-
|
|
411
|
-
const firebaserc = {
|
|
412
|
-
projects: {
|
|
413
|
-
default: firebaseProjectId,
|
|
414
|
-
},
|
|
485
|
+
if (fs.existsSync(configJsonPath)) {
|
|
486
|
+
const configData = JSON.parse(fs.readFileSync(configJsonPath, 'utf8'))
|
|
487
|
+
const appConfig = configData.find((c) => c._id === 'app')
|
|
488
|
+
if (appConfig) {
|
|
489
|
+
appConfig.title = projectName
|
|
490
|
+
appConfig.subtitle = `Welcome to ${projectName}`
|
|
491
|
+
appConfig.description = `A tosijs-platform site`
|
|
492
|
+
appConfig.host = `${projectId}.web.app`
|
|
493
|
+
}
|
|
494
|
+
fs.writeFileSync(configJsonPath, JSON.stringify(configData, null, 2))
|
|
415
495
|
}
|
|
416
496
|
|
|
417
|
-
|
|
418
|
-
path.join(projectPath, '.firebaserc'),
|
|
419
|
-
JSON.stringify(firebaserc, null, 2)
|
|
420
|
-
)
|
|
421
|
-
|
|
497
|
+
// Generate index.html
|
|
422
498
|
const indexHtml = `<!DOCTYPE html>
|
|
423
499
|
<html lang="en">
|
|
424
500
|
<head>
|
|
425
501
|
<meta charset="utf-8" />
|
|
426
|
-
|
|
427
502
|
<title>${projectName}</title>
|
|
428
503
|
<meta name="description" content="A tosijs-platform site" />
|
|
429
|
-
<meta property="og:
|
|
430
|
-
<meta property="og:
|
|
431
|
-
<meta property="og:description" content="" />
|
|
504
|
+
<meta property="og:title" content="${projectName}" />
|
|
505
|
+
<meta property="og:description" content="A tosijs-platform site" />
|
|
432
506
|
<meta property="og:image" content="/logo.png" />
|
|
433
|
-
|
|
434
507
|
<link rel="icon" href="/favicon.ico" />
|
|
435
508
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
436
509
|
<meta name="theme-color" content="#000000" />
|
|
@@ -441,301 +514,152 @@ export const config = {
|
|
|
441
514
|
<body></body>
|
|
442
515
|
</html>
|
|
443
516
|
`
|
|
444
|
-
|
|
445
517
|
fs.writeFileSync(path.join(projectPath, 'public/index.html'), indexHtml)
|
|
446
518
|
|
|
447
|
-
|
|
448
|
-
fs.readFileSync(path.join(projectPath, 'package.json'), 'utf8')
|
|
449
|
-
)
|
|
450
|
-
packageJson.name = projectName
|
|
451
|
-
packageJson.version = '0.1.0'
|
|
452
|
-
delete packageJson.bin
|
|
453
|
-
|
|
454
|
-
fs.writeFileSync(
|
|
455
|
-
path.join(projectPath, 'package.json'),
|
|
456
|
-
JSON.stringify(packageJson, null, 2)
|
|
457
|
-
)
|
|
458
|
-
|
|
519
|
+
// Generate setup.js
|
|
459
520
|
const setupScript = `#!/usr/bin/env node
|
|
460
|
-
|
|
461
|
-
/*
|
|
462
|
-
* Setup admin user and seed initial content
|
|
463
|
-
* Run after deploying functions: node setup.js
|
|
464
|
-
*/
|
|
521
|
+
// Setup script - run after deploying: node setup.js
|
|
465
522
|
|
|
466
523
|
import admin from 'firebase-admin'
|
|
467
|
-
import fs from 'fs'
|
|
468
|
-
import path from 'path'
|
|
469
|
-
import { fileURLToPath } from 'url'
|
|
470
|
-
|
|
471
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
472
524
|
|
|
473
525
|
const ADMIN_EMAIL = '${adminEmail}'
|
|
474
|
-
const PROJECT_ID = '${
|
|
475
|
-
const SKIP_EXISTING_DATA = ${firestoreExists}
|
|
476
|
-
|
|
477
|
-
admin.initializeApp({
|
|
478
|
-
projectId: PROJECT_ID,
|
|
479
|
-
})
|
|
526
|
+
const PROJECT_ID = '${projectId}'
|
|
480
527
|
|
|
528
|
+
admin.initializeApp({ projectId: PROJECT_ID })
|
|
481
529
|
const db = admin.firestore()
|
|
482
530
|
|
|
483
|
-
async function
|
|
484
|
-
|
|
485
|
-
console.log('Skipping seed data: database already exists')
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
console.log('Seeding Firestore from initial_state...')
|
|
490
|
-
const firestorePath = path.join(__dirname, 'initial_state/firestore')
|
|
491
|
-
|
|
492
|
-
if (!fs.existsSync(firestorePath)) {
|
|
493
|
-
console.log(' No seed data found')
|
|
494
|
-
return
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const files = fs.readdirSync(firestorePath).filter(f => f.endsWith('.json'))
|
|
498
|
-
|
|
499
|
-
for (const file of files) {
|
|
500
|
-
const collectionPath = file.replace(/\\.json$/, '').replace(/\\|/g, '/')
|
|
501
|
-
const filePath = path.join(firestorePath, file)
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
const documents = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
|
|
505
|
-
|
|
506
|
-
if (!Array.isArray(documents)) continue
|
|
507
|
-
|
|
508
|
-
let count = 0
|
|
509
|
-
for (const doc of documents) {
|
|
510
|
-
const docId = doc._id
|
|
511
|
-
if (!docId) continue
|
|
512
|
-
|
|
513
|
-
const data = { ...doc }
|
|
514
|
-
delete data._id
|
|
515
|
-
|
|
516
|
-
const now = new Date().toISOString()
|
|
517
|
-
data._created = data._created || now
|
|
518
|
-
data._modified = data._modified || now
|
|
519
|
-
data._path = \`\${collectionPath}/\${docId}\`
|
|
520
|
-
|
|
521
|
-
await db.collection(collectionPath).doc(docId).set(data)
|
|
522
|
-
count++
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
console.log(\` \${collectionPath}: \${count} documents\`)
|
|
526
|
-
} catch (error) {
|
|
527
|
-
console.log(\` Error processing \${file}: \${error.message}\`)
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async function setupAdminRole() {
|
|
533
|
-
console.log('Setting up admin role...\\n')
|
|
531
|
+
async function setup() {
|
|
532
|
+
console.log('\\nSetting up admin access...\\n')
|
|
534
533
|
|
|
535
534
|
try {
|
|
536
|
-
const
|
|
537
|
-
|
|
535
|
+
const user = await admin.auth().getUserByEmail(ADMIN_EMAIL)
|
|
536
|
+
console.log(\`Found user: \${ADMIN_EMAIL} (uid: \${user.uid})\`)
|
|
538
537
|
|
|
539
|
-
console.log(\`Found user: \${ADMIN_EMAIL} (uid: \${userId})\`)
|
|
540
|
-
|
|
541
|
-
// Update the owner role document to include this user's UID
|
|
542
538
|
const rolesSnapshot = await db.collection('role')
|
|
543
539
|
.where('contacts', 'array-contains', { type: 'email', value: ADMIN_EMAIL })
|
|
544
540
|
.get()
|
|
545
541
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
console.log(\`User already in role: \${data.name}\`)
|
|
556
|
-
}
|
|
542
|
+
for (const doc of rolesSnapshot.docs) {
|
|
543
|
+
const data = doc.data()
|
|
544
|
+
if (!data.userIds?.includes(user.uid)) {
|
|
545
|
+
await doc.ref.update({
|
|
546
|
+
userIds: admin.firestore.FieldValue.arrayUnion(user.uid)
|
|
547
|
+
})
|
|
548
|
+
console.log(\`Added to role: \${data.name}\`)
|
|
549
|
+
} else {
|
|
550
|
+
console.log(\`Already in role: \${data.name}\`)
|
|
557
551
|
}
|
|
558
|
-
} else {
|
|
559
|
-
console.log('No matching role found, creating owner role...')
|
|
560
|
-
await db.collection('role').doc('owner-role').set({
|
|
561
|
-
name: 'Owner',
|
|
562
|
-
contacts: [{ type: 'email', value: ADMIN_EMAIL }],
|
|
563
|
-
roles: ['owner', 'developer', 'admin', 'editor', 'author'],
|
|
564
|
-
userIds: [userId],
|
|
565
|
-
_created: new Date().toISOString(),
|
|
566
|
-
_modified: new Date().toISOString(),
|
|
567
|
-
_path: 'role/owner-role'
|
|
568
|
-
})
|
|
569
552
|
}
|
|
570
553
|
|
|
571
|
-
console.log('\\
|
|
554
|
+
console.log('\\n✓ Setup complete!')
|
|
555
|
+
console.log(\`\\nYou can now sign in at: https://${projectId}.web.app\`)
|
|
556
|
+
|
|
572
557
|
} catch (error) {
|
|
573
558
|
if (error.code === 'auth/user-not-found') {
|
|
574
|
-
console.
|
|
575
|
-
console.
|
|
576
|
-
console.
|
|
577
|
-
console.
|
|
578
|
-
console.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
throw error
|
|
583
|
-
}
|
|
584
|
-
return true
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
async function setup() {
|
|
588
|
-
console.log('\\nSetting up ${projectName}...\\n')
|
|
589
|
-
|
|
590
|
-
try {
|
|
591
|
-
await seedFirestore()
|
|
592
|
-
console.log('')
|
|
593
|
-
const success = await setupAdminRole()
|
|
594
|
-
|
|
595
|
-
if (success) {
|
|
596
|
-
console.log(\`\\nYou can now:
|
|
597
|
-
1. Run: bun start
|
|
598
|
-
2. Visit: https://localhost:8020
|
|
599
|
-
3. Sign in with: ${adminEmail}
|
|
600
|
-
\`)
|
|
559
|
+
console.log(\`User \${ADMIN_EMAIL} not found.\\n\`)
|
|
560
|
+
console.log('Please:')
|
|
561
|
+
console.log(\` 1. Visit https://${projectId}.web.app\`)
|
|
562
|
+
console.log(\` 2. Sign in with \${ADMIN_EMAIL}\`)
|
|
563
|
+
console.log(' 3. Run this script again: node setup.js')
|
|
564
|
+
} else {
|
|
565
|
+
throw error
|
|
601
566
|
}
|
|
602
|
-
} catch (error) {
|
|
603
|
-
console.error('Setup failed:', error.message)
|
|
604
|
-
console.error('\\nMake sure:')
|
|
605
|
-
console.error(' 1. Functions are deployed: bun deploy-functions')
|
|
606
|
-
console.error(' 2. Firebase services are enabled (Auth, Firestore, Storage)')
|
|
607
|
-
process.exit(1)
|
|
608
567
|
}
|
|
609
568
|
}
|
|
610
569
|
|
|
611
|
-
setup()
|
|
570
|
+
setup().catch(console.error)
|
|
612
571
|
`
|
|
613
|
-
|
|
614
572
|
fs.writeFileSync(path.join(projectPath, 'setup.js'), setupScript)
|
|
615
|
-
fs.chmodSync(path.join(projectPath, 'setup.js'), '755')
|
|
616
573
|
|
|
617
|
-
console.log('📦 Installing
|
|
618
|
-
|
|
574
|
+
console.log('📦 Installing dependencies...')
|
|
575
|
+
exec('bun install', { silent: true })
|
|
576
|
+
exec('cd functions && bun install', { silent: true })
|
|
619
577
|
|
|
620
|
-
console.log('
|
|
621
|
-
|
|
578
|
+
console.log('🔐 Generating TLS certificates...')
|
|
579
|
+
exec('cd tls && ./create-dev-certs.sh', { silent: true })
|
|
622
580
|
|
|
623
|
-
|
|
624
|
-
|
|
581
|
+
// ============================================================================
|
|
582
|
+
// Success output
|
|
583
|
+
// ============================================================================
|
|
625
584
|
|
|
585
|
+
console.log('\n' + '━'.repeat(50))
|
|
626
586
|
console.log('\n✅ Project created successfully!\n')
|
|
627
|
-
console.log('━'.repeat(
|
|
587
|
+
console.log('━'.repeat(50))
|
|
628
588
|
console.log(`\n📁 Project: ${projectName}`)
|
|
629
|
-
console.log(
|
|
589
|
+
console.log(`�� Firebase: ${projectId}`)
|
|
630
590
|
console.log(`👤 Admin: ${adminEmail}`)
|
|
631
|
-
|
|
632
|
-
console.log(`⚠️ Existing data: Will be preserved`)
|
|
633
|
-
}
|
|
634
|
-
console.log('\n━'.repeat(60))
|
|
591
|
+
console.log('\n' + '━'.repeat(50))
|
|
635
592
|
|
|
636
|
-
|
|
593
|
+
// Step 7: Offer initial deployment
|
|
594
|
+
console.log('\n🚀 Ready to deploy!\n')
|
|
637
595
|
|
|
638
|
-
|
|
639
|
-
console.log('⚠️ FIRST: Upgrade to Blaze Plan (REQUIRED)')
|
|
640
|
-
console.log(
|
|
641
|
-
` https://console.firebase.google.com/project/${firebaseProjectId}/usage/details`
|
|
642
|
-
)
|
|
643
|
-
console.log(' Without Blaze plan, Cloud Functions will not work!\n')
|
|
644
|
-
}
|
|
596
|
+
const deploy = await question('Run initial deployment now? [Y/n]: ')
|
|
645
597
|
|
|
646
|
-
if (
|
|
647
|
-
console.log(
|
|
648
|
-
|
|
649
|
-
)
|
|
650
|
-
console.log(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
598
|
+
if (deploy.toLowerCase() !== 'n') {
|
|
599
|
+
console.log('\n📦 Running initial deployment...\n')
|
|
600
|
+
console.log('This will:')
|
|
601
|
+
console.log(' 1. Build the client and functions')
|
|
602
|
+
console.log(' 2. Deploy to Firebase')
|
|
603
|
+
console.log(' 3. Seed the database\n')
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
exec('bun run initial-deploy')
|
|
607
|
+
|
|
608
|
+
console.log('\n' + '━'.repeat(50))
|
|
609
|
+
console.log('\n✅ Deployment complete!\n')
|
|
610
|
+
console.log('━'.repeat(50))
|
|
611
|
+
console.log(`\nYour site is live at: https://${projectId}.web.app`)
|
|
612
|
+
console.log('\nNext steps:')
|
|
613
|
+
console.log(` 1. Visit your site and sign in with ${adminEmail}`)
|
|
614
|
+
console.log(' 2. Run: node setup.js')
|
|
615
|
+
console.log(' 3. Start developing: bun start')
|
|
616
|
+
} catch {
|
|
617
|
+
console.log('\n⚠️ Deployment had issues. You can run it manually:')
|
|
618
|
+
console.log(` cd ${projectName}`)
|
|
619
|
+
console.log(' bun initial-deploy')
|
|
620
|
+
}
|
|
656
621
|
} else {
|
|
657
|
-
console.log(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}. ✓ Firebase config automatically configured\n`
|
|
661
|
-
)
|
|
622
|
+
console.log('\nTo deploy later:')
|
|
623
|
+
console.log(` cd ${projectName}`)
|
|
624
|
+
console.log(' bun initial-deploy')
|
|
662
625
|
}
|
|
663
626
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
` https://console.firebase.google.com/project/${firebaseProjectId}`
|
|
667
|
-
)
|
|
668
|
-
console.log(` • Authentication → Google sign-in method`)
|
|
669
|
-
console.log(` • Firestore Database → Create database (production mode)`)
|
|
670
|
-
console.log(` • Cloud Storage → Get started`)
|
|
671
|
-
if (hasBlazePlan !== true) {
|
|
672
|
-
console.log(` • ⚠️ UPGRADE TO BLAZE PLAN (required for Cloud Functions)`)
|
|
673
|
-
}
|
|
627
|
+
// Generate TODO.md
|
|
628
|
+
const todoContent = `# ${projectName} Setup
|
|
674
629
|
|
|
675
|
-
|
|
676
|
-
console.log(` cd ${projectName}`)
|
|
677
|
-
console.log(` bun deploy-functions`)
|
|
630
|
+
## Project Info
|
|
678
631
|
|
|
679
|
-
|
|
680
|
-
|
|
632
|
+
- **Project:** ${projectName}
|
|
633
|
+
- **Firebase:** ${projectId}
|
|
634
|
+
- **Admin:** ${adminEmail}
|
|
635
|
+
- **URL:** https://${projectId}.web.app
|
|
681
636
|
|
|
682
|
-
|
|
683
|
-
console.log(` https://${firebaseProjectId}.web.app`)
|
|
637
|
+
## Commands
|
|
684
638
|
|
|
685
|
-
|
|
686
|
-
|
|
639
|
+
\`\`\`bash
|
|
640
|
+
bun start # Local dev (production Firebase)
|
|
641
|
+
bun start-emulated # Local dev (emulators)
|
|
642
|
+
bun deploy # Deploy everything
|
|
643
|
+
bun deploy-functions # Deploy functions only
|
|
644
|
+
bun deploy-hosting # Deploy hosting only
|
|
645
|
+
\`\`\`
|
|
687
646
|
|
|
688
|
-
|
|
689
|
-
console.log(` bun start # Uses production Firebase`)
|
|
690
|
-
console.log(` bun start-emulated # Uses local emulators`)
|
|
691
|
-
console.log(` Visit https://localhost:8020`)
|
|
647
|
+
## Setup Checklist
|
|
692
648
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
`1. Go to: https://console.firebase.google.com/project/${firebaseProjectId}/hosting/sites`
|
|
698
|
-
)
|
|
699
|
-
console.log('2. Click "Add custom domain"')
|
|
700
|
-
console.log('3. Follow the wizard to:')
|
|
701
|
-
console.log(' • Verify domain ownership (add TXT record to DNS)')
|
|
702
|
-
console.log(' • Add A/AAAA records to point to Firebase')
|
|
703
|
-
console.log('4. Wait for SSL certificate provisioning (~15 minutes)')
|
|
704
|
-
console.log(
|
|
705
|
-
'5. Update the host field in the config/app document in Firestore:'
|
|
706
|
-
)
|
|
707
|
-
console.log(
|
|
708
|
-
` https://console.firebase.google.com/project/${firebaseProjectId}/firestore/data/~2Fconfig~2Fapp`
|
|
709
|
-
)
|
|
710
|
-
console.log(` Set host to: yoursite.com\n`)
|
|
711
|
-
|
|
712
|
-
console.log('━'.repeat(60))
|
|
713
|
-
console.log('\n🔐 API Keys for AI Features (Optional):\n')
|
|
714
|
-
console.log('If you want to use the /gen endpoint for AI completions,')
|
|
715
|
-
console.log('you need to set up API keys using Firebase Secrets Manager:\n')
|
|
716
|
-
console.log(' # For Gemini (Google AI):')
|
|
717
|
-
console.log(
|
|
718
|
-
' firebase functions:secrets:set gemini-api-key --project ' +
|
|
719
|
-
firebaseProjectId
|
|
720
|
-
)
|
|
721
|
-
console.log(' # Get your key at: https://aistudio.google.com/apikey\n')
|
|
722
|
-
console.log(' # For ChatGPT (OpenAI):')
|
|
723
|
-
console.log(
|
|
724
|
-
' firebase functions:secrets:set chatgpt-api-key --project ' +
|
|
725
|
-
firebaseProjectId
|
|
726
|
-
)
|
|
727
|
-
console.log(' # Get your key at: https://platform.openai.com/api-keys\n')
|
|
728
|
-
console.log(
|
|
729
|
-
'After setting secrets, redeploy functions: bun deploy-functions\n'
|
|
730
|
-
)
|
|
649
|
+
- [ ] Enable Google sign-in: https://console.firebase.google.com/project/${projectId}/authentication/providers
|
|
650
|
+
- [ ] Deploy: \`bun initial-deploy\`
|
|
651
|
+
- [ ] Sign in at https://${projectId}.web.app
|
|
652
|
+
- [ ] Run: \`node setup.js\`
|
|
731
653
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
)
|
|
654
|
+
## Resources
|
|
655
|
+
|
|
656
|
+
- Documentation: https://github.com/tonioloewald/tosijs-platform
|
|
657
|
+
- tosijs: https://tosijs.net
|
|
658
|
+
- tosijs-ui: https://ui.tosijs.net
|
|
659
|
+
`
|
|
660
|
+
fs.writeFileSync(path.join(projectPath, 'TODO.md'), todoContent)
|
|
661
|
+
|
|
662
|
+
console.log('\n📝 Setup instructions saved to TODO.md')
|
|
739
663
|
console.log('\n🎉 Happy building!\n')
|
|
740
664
|
}
|
|
741
665
|
|