create-tosijs-platform-app 1.0.4 → 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 -525
- 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
|
-
console.log('⚠️ Could not verify billing plan (you may need Blaze plan)')
|
|
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
|
|
286
337
|
}
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
287
340
|
|
|
288
|
-
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Main
|
|
343
|
+
// ============================================================================
|
|
289
344
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
)
|
|
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)
|
|
297
355
|
}
|
|
298
356
|
|
|
299
|
-
|
|
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'
|
|
300
361
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (err.code === 'EEXIST') {
|
|
305
|
-
console.log(`❌ Directory "${projectName}" already exists.\n`)
|
|
306
|
-
} else {
|
|
307
|
-
console.log(`❌ Error creating directory: ${err.message}\n`)
|
|
308
|
-
}
|
|
362
|
+
// Check if directory already exists
|
|
363
|
+
if (fs.existsSync(projectPath)) {
|
|
364
|
+
console.log(`\n❌ Directory "${projectName}" already exists.\n`)
|
|
309
365
|
process.exit(1)
|
|
310
366
|
}
|
|
311
367
|
|
|
312
|
-
|
|
313
|
-
|
|
368
|
+
// Step 1: Prerequisites
|
|
369
|
+
await ensurePrerequisites()
|
|
370
|
+
|
|
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')
|
|
386
|
+
process.exit(1)
|
|
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
|
+
// ============================================================================
|
|
403
|
+
|
|
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,305 +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
|
-
|
|
538
|
-
|
|
539
|
-
console.log(\`Found user: \${ADMIN_EMAIL} (uid: \${userId})\`)
|
|
535
|
+
const user = await admin.auth().getUserByEmail(ADMIN_EMAIL)
|
|
536
|
+
console.log(\`Found user: \${ADMIN_EMAIL} (uid: \${user.uid})\`)
|
|
540
537
|
|
|
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
|
-
|
|
637
|
-
console.log(
|
|
638
|
-
console.log(` bun initial-deploy`)
|
|
639
|
-
console.log('\n━'.repeat(60))
|
|
593
|
+
// Step 7: Offer initial deployment
|
|
594
|
+
console.log('\n🚀 Ready to deploy!\n')
|
|
640
595
|
|
|
641
|
-
|
|
596
|
+
const deploy = await question('Run initial deployment now? [Y/n]: ')
|
|
642
597
|
|
|
643
|
-
if (
|
|
644
|
-
console.log('
|
|
645
|
-
console.log(
|
|
646
|
-
|
|
647
|
-
)
|
|
648
|
-
console.log('
|
|
649
|
-
}
|
|
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')
|
|
650
604
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
}
|
|
661
621
|
} else {
|
|
662
|
-
console.log(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}. ✓ Firebase config automatically configured\n`
|
|
666
|
-
)
|
|
622
|
+
console.log('\nTo deploy later:')
|
|
623
|
+
console.log(` cd ${projectName}`)
|
|
624
|
+
console.log(' bun initial-deploy')
|
|
667
625
|
}
|
|
668
626
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
` https://console.firebase.google.com/project/${firebaseProjectId}`
|
|
672
|
-
)
|
|
673
|
-
console.log(` • Authentication → Google sign-in method`)
|
|
674
|
-
console.log(` • Firestore Database → Create database (production mode)`)
|
|
675
|
-
console.log(` • Cloud Storage → Get started`)
|
|
676
|
-
if (hasBlazePlan !== true) {
|
|
677
|
-
console.log(` • ⚠️ UPGRADE TO BLAZE PLAN (required for Cloud Functions)`)
|
|
678
|
-
}
|
|
627
|
+
// Generate TODO.md
|
|
628
|
+
const todoContent = `# ${projectName} Setup
|
|
679
629
|
|
|
680
|
-
|
|
681
|
-
`\n3. Run initial deployment (builds, deploys, and seeds database):`
|
|
682
|
-
)
|
|
683
|
-
console.log(` cd ${projectName}`)
|
|
684
|
-
console.log(` bun initial-deploy`)
|
|
630
|
+
## Project Info
|
|
685
631
|
|
|
686
|
-
|
|
687
|
-
|
|
632
|
+
- **Project:** ${projectName}
|
|
633
|
+
- **Firebase:** ${projectId}
|
|
634
|
+
- **Admin:** ${adminEmail}
|
|
635
|
+
- **URL:** https://${projectId}.web.app
|
|
688
636
|
|
|
689
|
-
|
|
690
|
-
console.log(` https://${firebaseProjectId}.web.app`)
|
|
637
|
+
## Commands
|
|
691
638
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
\`\`\`
|
|
696
646
|
|
|
697
|
-
|
|
698
|
-
console.log('\n📚 Custom Domain Setup:\n')
|
|
699
|
-
console.log('To add a custom domain (e.g., yoursite.com):')
|
|
700
|
-
console.log(
|
|
701
|
-
`1. Go to: https://console.firebase.google.com/project/${firebaseProjectId}/hosting/sites`
|
|
702
|
-
)
|
|
703
|
-
console.log('2. Click "Add custom domain"')
|
|
704
|
-
console.log('3. Follow the wizard to:')
|
|
705
|
-
console.log(' • Verify domain ownership (add TXT record to DNS)')
|
|
706
|
-
console.log(' • Add A/AAAA records to point to Firebase')
|
|
707
|
-
console.log('4. Wait for SSL certificate provisioning (~15 minutes)')
|
|
708
|
-
console.log(
|
|
709
|
-
'5. Update the host field in the config/app document in Firestore:'
|
|
710
|
-
)
|
|
711
|
-
console.log(
|
|
712
|
-
` https://console.firebase.google.com/project/${firebaseProjectId}/firestore/data/~2Fconfig~2Fapp`
|
|
713
|
-
)
|
|
714
|
-
console.log(` Set host to: yoursite.com\n`)
|
|
715
|
-
|
|
716
|
-
console.log('━'.repeat(60))
|
|
717
|
-
console.log('\n🔐 API Keys for AI Features (Optional):\n')
|
|
718
|
-
console.log('If you want to use the /gen endpoint for AI completions,')
|
|
719
|
-
console.log('you need to set up API keys using Firebase Secrets Manager:\n')
|
|
720
|
-
console.log(' # For Gemini (Google AI):')
|
|
721
|
-
console.log(
|
|
722
|
-
' firebase functions:secrets:set gemini-api-key --project ' +
|
|
723
|
-
firebaseProjectId
|
|
724
|
-
)
|
|
725
|
-
console.log(' # Get your key at: https://aistudio.google.com/apikey\n')
|
|
726
|
-
console.log(' # For ChatGPT (OpenAI):')
|
|
727
|
-
console.log(
|
|
728
|
-
' firebase functions:secrets:set chatgpt-api-key --project ' +
|
|
729
|
-
firebaseProjectId
|
|
730
|
-
)
|
|
731
|
-
console.log(' # Get your key at: https://platform.openai.com/api-keys\n')
|
|
732
|
-
console.log(
|
|
733
|
-
'After setting secrets, redeploy functions: bun deploy-functions\n'
|
|
734
|
-
)
|
|
647
|
+
## Setup Checklist
|
|
735
648
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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\`
|
|
653
|
+
|
|
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')
|
|
743
663
|
console.log('\n🎉 Happy building!\n')
|
|
744
664
|
}
|
|
745
665
|
|