create-sonicjs 2.0.0-alpha.2
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/LICENSE +21 -0
- package/README.md +291 -0
- package/bin/create-sonicjs-app.js +3 -0
- package/package.json +50 -0
- package/src/cli.js +430 -0
- package/templates/starter/README.md +132 -0
- package/templates/starter/package-lock.json +4147 -0
- package/templates/starter/package.json +33 -0
- package/templates/starter/src/collections/blog-posts.collection.ts +75 -0
- package/templates/starter/src/index.ts +23 -0
- package/templates/starter/tsconfig.json +47 -0
- package/templates/starter/wrangler.toml +32 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import prompts from 'prompts'
|
|
7
|
+
import kleur from 'kleur'
|
|
8
|
+
import ora from 'ora'
|
|
9
|
+
import { execa } from 'execa'
|
|
10
|
+
import validatePackageName from 'validate-npm-package-name'
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
|
|
14
|
+
// Version
|
|
15
|
+
const VERSION = '2.0.0-alpha.2'
|
|
16
|
+
|
|
17
|
+
// Templates available
|
|
18
|
+
const TEMPLATES = {
|
|
19
|
+
starter: {
|
|
20
|
+
name: 'Starter (Blog & Content)',
|
|
21
|
+
description: 'Perfect for blogs, documentation, and content sites',
|
|
22
|
+
color: 'blue'
|
|
23
|
+
},
|
|
24
|
+
// Future templates
|
|
25
|
+
// ecommerce: {
|
|
26
|
+
// name: 'E-commerce',
|
|
27
|
+
// description: 'Online store with products and checkout',
|
|
28
|
+
// color: 'green'
|
|
29
|
+
// },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Banner
|
|
33
|
+
console.log()
|
|
34
|
+
console.log(kleur.bold().cyan('✨ Create SonicJS App'))
|
|
35
|
+
console.log(kleur.dim(` v${VERSION}`))
|
|
36
|
+
console.log()
|
|
37
|
+
|
|
38
|
+
// Parse arguments
|
|
39
|
+
const args = process.argv.slice(2)
|
|
40
|
+
const projectName = args[0]
|
|
41
|
+
const flags = {
|
|
42
|
+
skipInstall: args.includes('--skip-install'),
|
|
43
|
+
skipGit: args.includes('--skip-git'),
|
|
44
|
+
skipCloudflare: args.includes('--skip-cloudflare'),
|
|
45
|
+
template: args.find(arg => arg.startsWith('--template='))?.split('=')[1],
|
|
46
|
+
databaseName: args.find(arg => arg.startsWith('--database='))?.split('=')[1],
|
|
47
|
+
bucketName: args.find(arg => arg.startsWith('--bucket='))?.split('=')[1],
|
|
48
|
+
skipExample: args.includes('--skip-example'),
|
|
49
|
+
includeExample: args.includes('--include-example')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
try {
|
|
54
|
+
// Get project details
|
|
55
|
+
const answers = await getProjectDetails(projectName)
|
|
56
|
+
|
|
57
|
+
// Create project
|
|
58
|
+
await createProject(answers, flags)
|
|
59
|
+
|
|
60
|
+
// Success message
|
|
61
|
+
printSuccessMessage(answers)
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.message === 'cancelled') {
|
|
65
|
+
console.log()
|
|
66
|
+
console.log(kleur.yellow('⚠ Cancelled'))
|
|
67
|
+
process.exit(0)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.error()
|
|
71
|
+
console.error(kleur.red('✖ Error:'), error.message)
|
|
72
|
+
console.error()
|
|
73
|
+
process.exit(1)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function getProjectDetails(initialName) {
|
|
78
|
+
const questions = []
|
|
79
|
+
|
|
80
|
+
// Project name
|
|
81
|
+
if (!initialName) {
|
|
82
|
+
questions.push({
|
|
83
|
+
type: 'text',
|
|
84
|
+
name: 'projectName',
|
|
85
|
+
message: 'Project name:',
|
|
86
|
+
initial: 'my-sonicjs-app',
|
|
87
|
+
validate: (value) => {
|
|
88
|
+
if (!value) return 'Project name is required'
|
|
89
|
+
const validation = validatePackageName(value)
|
|
90
|
+
if (!validation.validForNewPackages) {
|
|
91
|
+
return validation.errors?.[0] || 'Invalid package name'
|
|
92
|
+
}
|
|
93
|
+
if (fs.existsSync(value)) {
|
|
94
|
+
return `Directory "${value}" already exists`
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Template selection
|
|
102
|
+
if (!flags.template) {
|
|
103
|
+
questions.push({
|
|
104
|
+
type: 'select',
|
|
105
|
+
name: 'template',
|
|
106
|
+
message: 'Choose a template:',
|
|
107
|
+
choices: Object.entries(TEMPLATES).map(([key, { name, description }]) => ({
|
|
108
|
+
title: name,
|
|
109
|
+
description: description,
|
|
110
|
+
value: key
|
|
111
|
+
})),
|
|
112
|
+
initial: 0
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Database name
|
|
117
|
+
if (!flags.databaseName) {
|
|
118
|
+
questions.push({
|
|
119
|
+
type: 'text',
|
|
120
|
+
name: 'databaseName',
|
|
121
|
+
message: 'Database name:',
|
|
122
|
+
initial: (prev, values) => `${values.projectName || initialName}-db`,
|
|
123
|
+
validate: (value) => value ? true : 'Database name is required'
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// R2 bucket name
|
|
128
|
+
if (!flags.bucketName) {
|
|
129
|
+
questions.push({
|
|
130
|
+
type: 'text',
|
|
131
|
+
name: 'bucketName',
|
|
132
|
+
message: 'R2 bucket name:',
|
|
133
|
+
initial: (prev, values) => `${values.projectName || initialName}-media`,
|
|
134
|
+
validate: (value) => value ? true : 'Bucket name is required'
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Include example collection (only ask if neither flag is set)
|
|
139
|
+
if (!flags.skipExample && !flags.includeExample) {
|
|
140
|
+
questions.push({
|
|
141
|
+
type: 'confirm',
|
|
142
|
+
name: 'includeExample',
|
|
143
|
+
message: 'Include example blog collection?',
|
|
144
|
+
initial: true
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Create Cloudflare resources
|
|
149
|
+
if (!flags.skipCloudflare) {
|
|
150
|
+
questions.push({
|
|
151
|
+
type: 'confirm',
|
|
152
|
+
name: 'createResources',
|
|
153
|
+
message: 'Create Cloudflare resources now? (requires wrangler)',
|
|
154
|
+
initial: false
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Initialize git
|
|
159
|
+
if (!flags.skipGit) {
|
|
160
|
+
questions.push({
|
|
161
|
+
type: 'confirm',
|
|
162
|
+
name: 'initGit',
|
|
163
|
+
message: 'Initialize git repository?',
|
|
164
|
+
initial: true
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const answers = await prompts(questions, {
|
|
169
|
+
onCancel: () => {
|
|
170
|
+
throw new Error('cancelled')
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
projectName: initialName || answers.projectName,
|
|
176
|
+
template: flags.template || answers.template,
|
|
177
|
+
databaseName: flags.databaseName || answers.databaseName || `${initialName || answers.projectName}-db`,
|
|
178
|
+
bucketName: flags.bucketName || answers.bucketName || `${initialName || answers.projectName}-media`,
|
|
179
|
+
includeExample: flags.skipExample ? false : (flags.includeExample ? true : (answers.includeExample !== undefined ? answers.includeExample : true)),
|
|
180
|
+
createResources: flags.skipCloudflare ? false : answers.createResources,
|
|
181
|
+
initGit: flags.skipGit ? false : answers.initGit,
|
|
182
|
+
skipInstall: flags.skipInstall
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function createProject(answers, flags) {
|
|
187
|
+
const {
|
|
188
|
+
projectName,
|
|
189
|
+
template,
|
|
190
|
+
databaseName,
|
|
191
|
+
bucketName,
|
|
192
|
+
includeExample,
|
|
193
|
+
createResources,
|
|
194
|
+
initGit,
|
|
195
|
+
skipInstall
|
|
196
|
+
} = answers
|
|
197
|
+
|
|
198
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
199
|
+
|
|
200
|
+
console.log()
|
|
201
|
+
const spinner = ora('Creating project...').start()
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// 1. Copy template
|
|
205
|
+
spinner.text = 'Copying template files...'
|
|
206
|
+
await copyTemplate(template, targetDir, {
|
|
207
|
+
projectName,
|
|
208
|
+
databaseName,
|
|
209
|
+
bucketName,
|
|
210
|
+
includeExample
|
|
211
|
+
})
|
|
212
|
+
spinner.succeed('Copied template files')
|
|
213
|
+
|
|
214
|
+
// 2. Create Cloudflare resources
|
|
215
|
+
let databaseId = 'YOUR_DATABASE_ID'
|
|
216
|
+
if (createResources) {
|
|
217
|
+
spinner.start('Creating Cloudflare resources...')
|
|
218
|
+
try {
|
|
219
|
+
databaseId = await createCloudflareResources(databaseName, bucketName, targetDir)
|
|
220
|
+
spinner.succeed('Created Cloudflare resources')
|
|
221
|
+
} catch (error) {
|
|
222
|
+
spinner.warn('Failed to create Cloudflare resources')
|
|
223
|
+
console.log(kleur.dim(' You can create them manually later'))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 3. Update wrangler.toml with database ID
|
|
228
|
+
spinner.start('Updating configuration...')
|
|
229
|
+
await updateWranglerConfig(targetDir, { databaseName, databaseId, bucketName })
|
|
230
|
+
spinner.succeed('Updated configuration')
|
|
231
|
+
|
|
232
|
+
// 4. Install dependencies
|
|
233
|
+
if (!skipInstall) {
|
|
234
|
+
spinner.start('Installing dependencies...')
|
|
235
|
+
await installDependencies(targetDir)
|
|
236
|
+
spinner.succeed('Installed dependencies')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 5. Initialize git
|
|
240
|
+
if (initGit) {
|
|
241
|
+
spinner.start('Initializing git repository...')
|
|
242
|
+
await initializeGit(targetDir)
|
|
243
|
+
spinner.succeed('Initialized git repository')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
spinner.succeed(kleur.bold().green('✓ Project created successfully!'))
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
spinner.fail('Failed to create project')
|
|
250
|
+
throw error
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function copyTemplate(templateName, targetDir, options) {
|
|
255
|
+
const templateDir = path.resolve(__dirname, '../../../templates/starter')
|
|
256
|
+
|
|
257
|
+
// Check if template exists
|
|
258
|
+
if (!fs.existsSync(templateDir)) {
|
|
259
|
+
throw new Error(`Template "${templateName}" not found`)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Copy template
|
|
263
|
+
await fs.copy(templateDir, targetDir, {
|
|
264
|
+
filter: (src) => {
|
|
265
|
+
// Skip node_modules, .git, dist, etc.
|
|
266
|
+
const name = path.basename(src)
|
|
267
|
+
if (['.git', 'node_modules', 'dist', '.wrangler', '.mf'].includes(name)) {
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
return true
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Update package.json
|
|
275
|
+
const packageJsonPath = path.join(targetDir, 'package.json')
|
|
276
|
+
const packageJson = await fs.readJson(packageJsonPath)
|
|
277
|
+
packageJson.name = options.projectName
|
|
278
|
+
packageJson.version = '0.1.0'
|
|
279
|
+
packageJson.private = true
|
|
280
|
+
|
|
281
|
+
// Add @sonicjs-cms/core dependency
|
|
282
|
+
packageJson.dependencies = {
|
|
283
|
+
'@sonicjs-cms/core': '^2.0.0-alpha.2',
|
|
284
|
+
...packageJson.dependencies
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 })
|
|
288
|
+
|
|
289
|
+
// Remove example collection if not wanted
|
|
290
|
+
if (!options.includeExample) {
|
|
291
|
+
const examplePath = path.join(targetDir, 'src/collections/blog-posts.collection.ts')
|
|
292
|
+
if (fs.existsSync(examplePath)) {
|
|
293
|
+
await fs.remove(examplePath)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function createCloudflareResources(databaseName, bucketName, targetDir) {
|
|
299
|
+
// Check if wrangler is installed
|
|
300
|
+
try {
|
|
301
|
+
await execa('wrangler', ['--version'], { cwd: targetDir })
|
|
302
|
+
} catch {
|
|
303
|
+
throw new Error('wrangler is not installed. Install with: npm install -g wrangler')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Create D1 database
|
|
307
|
+
let databaseId
|
|
308
|
+
try {
|
|
309
|
+
const { stdout } = await execa('wrangler', ['d1', 'create', databaseName], {
|
|
310
|
+
cwd: targetDir
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Parse database_id from output
|
|
314
|
+
const match = stdout.match(/database_id\s*=\s*["']([^"']+)["']/)
|
|
315
|
+
if (match) {
|
|
316
|
+
databaseId = match[1]
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.log(kleur.yellow(' D1 database creation failed'))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Create R2 bucket
|
|
323
|
+
try {
|
|
324
|
+
await execa('wrangler', ['r2', 'bucket', 'create', bucketName], {
|
|
325
|
+
cwd: targetDir
|
|
326
|
+
})
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.log(kleur.yellow(' R2 bucket creation failed'))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return databaseId
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function updateWranglerConfig(targetDir, { databaseName, databaseId, bucketName }) {
|
|
335
|
+
const wranglerPath = path.join(targetDir, 'wrangler.toml')
|
|
336
|
+
let content = await fs.readFile(wranglerPath, 'utf-8')
|
|
337
|
+
|
|
338
|
+
// Update database_name
|
|
339
|
+
content = content.replace(/database_name\s*=\s*"[^"]*"/, `database_name = "${databaseName}"`)
|
|
340
|
+
|
|
341
|
+
// Update database_id
|
|
342
|
+
content = content.replace(/database_id\s*=\s*"[^"]*"/, `database_id = "${databaseId}"`)
|
|
343
|
+
|
|
344
|
+
// Update bucket_name
|
|
345
|
+
content = content.replace(/bucket_name\s*=\s*"[^"]*"/, `bucket_name = "${bucketName}"`)
|
|
346
|
+
|
|
347
|
+
await fs.writeFile(wranglerPath, content)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function installDependencies(targetDir) {
|
|
351
|
+
// Detect package manager
|
|
352
|
+
const packageManager = await detectPackageManager()
|
|
353
|
+
|
|
354
|
+
const installCmd = packageManager === 'yarn' ? 'yarn' :
|
|
355
|
+
packageManager === 'pnpm' ? 'pnpm install' :
|
|
356
|
+
'npm install'
|
|
357
|
+
|
|
358
|
+
await execa(packageManager, packageManager === 'yarn' ? [] : ['install'], {
|
|
359
|
+
cwd: targetDir,
|
|
360
|
+
stdio: 'ignore'
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function detectPackageManager() {
|
|
365
|
+
// Check parent directories for lock files
|
|
366
|
+
let dir = process.cwd()
|
|
367
|
+
|
|
368
|
+
while (dir !== path.parse(dir).root) {
|
|
369
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
370
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn'
|
|
371
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm'
|
|
372
|
+
dir = path.dirname(dir)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return 'npm'
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function initializeGit(targetDir) {
|
|
379
|
+
try {
|
|
380
|
+
await execa('git', ['init'], { cwd: targetDir })
|
|
381
|
+
await execa('git', ['add', '.'], { cwd: targetDir })
|
|
382
|
+
await execa('git', ['commit', '-m', 'Initial commit from create-sonicjs-app'], {
|
|
383
|
+
cwd: targetDir
|
|
384
|
+
})
|
|
385
|
+
} catch (error) {
|
|
386
|
+
// Git init is optional, don't fail
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function printSuccessMessage(answers) {
|
|
391
|
+
const { projectName, createResources, skipInstall } = answers
|
|
392
|
+
|
|
393
|
+
console.log()
|
|
394
|
+
console.log(kleur.bold().green('🎉 Success!'))
|
|
395
|
+
console.log()
|
|
396
|
+
console.log(kleur.bold('Next steps:'))
|
|
397
|
+
console.log()
|
|
398
|
+
console.log(kleur.cyan(` cd ${projectName}`))
|
|
399
|
+
|
|
400
|
+
if (skipInstall) {
|
|
401
|
+
console.log(kleur.cyan(' npm install'))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!createResources) {
|
|
405
|
+
console.log()
|
|
406
|
+
console.log(kleur.bold('Create Cloudflare resources:'))
|
|
407
|
+
console.log(kleur.cyan(` wrangler d1 create ${answers.databaseName}`))
|
|
408
|
+
console.log(kleur.dim(' # Copy database_id to wrangler.toml'))
|
|
409
|
+
console.log(kleur.cyan(` wrangler r2 bucket create ${answers.bucketName}`))
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log()
|
|
413
|
+
console.log(kleur.bold('Run migrations:'))
|
|
414
|
+
console.log(kleur.cyan(' npm run db:migrate:local'))
|
|
415
|
+
|
|
416
|
+
console.log()
|
|
417
|
+
console.log(kleur.bold('Start development:'))
|
|
418
|
+
console.log(kleur.cyan(' npm run dev'))
|
|
419
|
+
|
|
420
|
+
console.log()
|
|
421
|
+
console.log(kleur.bold('Visit:'))
|
|
422
|
+
console.log(kleur.cyan(' http://localhost:8787/admin'))
|
|
423
|
+
|
|
424
|
+
console.log()
|
|
425
|
+
console.log(kleur.dim('Need help? Visit https://docs.sonicjs.com'))
|
|
426
|
+
console.log()
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Run
|
|
430
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# My SonicJS Application
|
|
2
|
+
|
|
3
|
+
A modern headless CMS built with [SonicJS](https://sonicjs.com) on Cloudflare's edge platform.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node.js 18 or higher
|
|
10
|
+
- A Cloudflare account (free tier works great)
|
|
11
|
+
- Wrangler CLI (installed with dependencies)
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
1. **Install dependencies:**
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. **Create your D1 database:**
|
|
21
|
+
```bash
|
|
22
|
+
npx wrangler d1 create my-sonicjs-db
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Copy the `database_id` from the output and update it in `wrangler.toml`.
|
|
26
|
+
|
|
27
|
+
3. **Create your R2 bucket:**
|
|
28
|
+
```bash
|
|
29
|
+
npx wrangler r2 bucket create my-sonicjs-media
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
4. **Run migrations:**
|
|
33
|
+
```bash
|
|
34
|
+
npm run db:migrate:local
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
5. **Start the development server:**
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
6. **Open your browser:**
|
|
43
|
+
Navigate to `http://localhost:8787/admin` to access the admin interface.
|
|
44
|
+
|
|
45
|
+
Default credentials:
|
|
46
|
+
- Email: `admin@sonicjs.com`
|
|
47
|
+
- Password: `admin`
|
|
48
|
+
|
|
49
|
+
## Project Structure
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
my-sonicjs-app/
|
|
53
|
+
├── src/
|
|
54
|
+
│ ├── collections/ # Your content type definitions
|
|
55
|
+
│ │ └── blog-posts.collection.ts
|
|
56
|
+
│ └── index.ts # Application entry point
|
|
57
|
+
├── wrangler.toml # Cloudflare Workers configuration
|
|
58
|
+
├── package.json
|
|
59
|
+
└── tsconfig.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Available Scripts
|
|
63
|
+
|
|
64
|
+
- `npm run dev` - Start development server
|
|
65
|
+
- `npm run deploy` - Deploy to Cloudflare
|
|
66
|
+
- `npm run db:migrate` - Run migrations on production database
|
|
67
|
+
- `npm run db:migrate:local` - Run migrations locally
|
|
68
|
+
- `npm run type-check` - Check TypeScript types
|
|
69
|
+
- `npm run test` - Run tests
|
|
70
|
+
|
|
71
|
+
## Creating Collections
|
|
72
|
+
|
|
73
|
+
Collections define your content types. Create a new file in `src/collections/`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// src/collections/products.collection.ts
|
|
77
|
+
import type { CollectionConfig } from '@sonicjs-cms/core'
|
|
78
|
+
|
|
79
|
+
export default {
|
|
80
|
+
name: 'products',
|
|
81
|
+
label: 'Products',
|
|
82
|
+
fields: {
|
|
83
|
+
name: { type: 'text', required: true },
|
|
84
|
+
price: { type: 'number', required: true },
|
|
85
|
+
description: { type: 'markdown' }
|
|
86
|
+
}
|
|
87
|
+
} satisfies CollectionConfig
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Access
|
|
91
|
+
|
|
92
|
+
Your collections are automatically available via REST API:
|
|
93
|
+
|
|
94
|
+
- `GET /api/content/blog-posts` - List all blog posts
|
|
95
|
+
- `GET /api/content/blog-posts/:id` - Get a single post
|
|
96
|
+
- `POST /api/content/blog-posts` - Create a post (requires auth)
|
|
97
|
+
- `PUT /api/content/blog-posts/:id` - Update a post (requires auth)
|
|
98
|
+
- `DELETE /api/content/blog-posts/:id` - Delete a post (requires auth)
|
|
99
|
+
|
|
100
|
+
## Deployment
|
|
101
|
+
|
|
102
|
+
1. **Login to Cloudflare:**
|
|
103
|
+
```bash
|
|
104
|
+
npx wrangler login
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. **Deploy your application:**
|
|
108
|
+
```bash
|
|
109
|
+
npm run deploy
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
3. **Run migrations on production:**
|
|
113
|
+
```bash
|
|
114
|
+
npm run db:migrate
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Documentation
|
|
118
|
+
|
|
119
|
+
- [SonicJS Documentation](https://docs.sonicjs.com)
|
|
120
|
+
- [Collection Configuration](https://docs.sonicjs.com/collections)
|
|
121
|
+
- [Plugin Development](https://docs.sonicjs.com/plugins)
|
|
122
|
+
- [API Reference](https://docs.sonicjs.com/api)
|
|
123
|
+
|
|
124
|
+
## Support
|
|
125
|
+
|
|
126
|
+
- [GitHub Issues](https://github.com/sonicjs/sonicjs/issues)
|
|
127
|
+
- [Discord Community](https://discord.gg/sonicjs)
|
|
128
|
+
- [Documentation](https://docs.sonicjs.com)
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|