canopycms 0.0.5 → 0.0.7
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/dist/auth/plugin.d.ts +9 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/authorization/groups/loader.d.ts.map +1 -1
- package/dist/authorization/groups/loader.js +2 -5
- package/dist/authorization/groups/loader.js.map +1 -1
- package/dist/authorization/permissions/loader.d.ts.map +1 -1
- package/dist/authorization/permissions/loader.js +2 -5
- package/dist/authorization/permissions/loader.js.map +1 -1
- package/dist/branch-metadata.d.ts +10 -0
- package/dist/branch-metadata.d.ts.map +1 -1
- package/dist/branch-metadata.js +133 -32
- package/dist/branch-metadata.js.map +1 -1
- package/dist/branch-workspace.d.ts +11 -0
- package/dist/branch-workspace.d.ts.map +1 -1
- package/dist/branch-workspace.js +21 -1
- package/dist/branch-workspace.js.map +1 -1
- package/dist/cli/generate-ai-content.d.ts +1 -0
- package/dist/cli/generate-ai-content.d.ts.map +1 -1
- package/dist/cli/generate-ai-content.js +3 -3
- package/dist/cli/generate-ai-content.js.map +1 -1
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +32 -20
- package/dist/cli/init.js.map +1 -1
- package/dist/content-reader.d.ts +0 -2
- package/dist/content-reader.d.ts.map +1 -1
- package/dist/content-reader.js +17 -13
- package/dist/content-reader.js.map +1 -1
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +3 -2
- package/dist/content-store.js.map +1 -1
- package/dist/context.js +5 -5
- package/dist/context.js.map +1 -1
- package/dist/paths/branch.js +1 -1
- package/dist/paths/branch.js.map +1 -1
- package/dist/schema/schema-store.d.ts.map +1 -1
- package/dist/schema/schema-store.js +3 -2
- package/dist/schema/schema-store.js.map +1 -1
- package/dist/task-queue/task-queue.d.ts.map +1 -1
- package/dist/task-queue/task-queue.js +8 -14
- package/dist/task-queue/task-queue.js.map +1 -1
- package/dist/utils/atomic-write.d.ts +15 -0
- package/dist/utils/atomic-write.d.ts.map +1 -0
- package/dist/utils/atomic-write.js +29 -0
- package/dist/utils/atomic-write.js.map +1 -0
- package/package.json +71 -78
- package/src/cli/init.ts +0 -462
package/src/cli/init.ts
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
|
|
3
|
-
import fs from 'node:fs/promises'
|
|
4
|
-
import { realpathSync } from 'node:fs'
|
|
5
|
-
import path from 'node:path'
|
|
6
|
-
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import * as p from '@clack/prompts'
|
|
8
|
-
import {
|
|
9
|
-
canopyCmsConfig,
|
|
10
|
-
canopyContext,
|
|
11
|
-
schemasTemplate,
|
|
12
|
-
apiRoute,
|
|
13
|
-
editPage,
|
|
14
|
-
aiConfig,
|
|
15
|
-
aiRoute,
|
|
16
|
-
dockerfileCms,
|
|
17
|
-
githubWorkflowCms,
|
|
18
|
-
} from './templates'
|
|
19
|
-
import { operatingStrategy } from '../operating-mode'
|
|
20
|
-
|
|
21
|
-
export interface InitOptions {
|
|
22
|
-
mode: 'prod-sim' | 'dev'
|
|
23
|
-
appDir: string
|
|
24
|
-
projectDir: string
|
|
25
|
-
force: boolean
|
|
26
|
-
nonInteractive: boolean
|
|
27
|
-
ai: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface InitDeployOptions {
|
|
31
|
-
cloud: 'aws'
|
|
32
|
-
projectDir: string
|
|
33
|
-
force: boolean
|
|
34
|
-
nonInteractive: boolean
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
38
|
-
try {
|
|
39
|
-
await fs.stat(filePath)
|
|
40
|
-
return true
|
|
41
|
-
} catch {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Write a file, prompting for overwrite confirmation if it already exists.
|
|
48
|
-
* Returns true if the file was written, false if skipped.
|
|
49
|
-
*/
|
|
50
|
-
async function writeFile(
|
|
51
|
-
filePath: string,
|
|
52
|
-
content: string,
|
|
53
|
-
options: { force: boolean; nonInteractive: boolean },
|
|
54
|
-
): Promise<boolean> {
|
|
55
|
-
const relativePath = path.relative(process.cwd(), filePath)
|
|
56
|
-
|
|
57
|
-
if (await fileExists(filePath)) {
|
|
58
|
-
if (options.force) {
|
|
59
|
-
// --force: overwrite without asking
|
|
60
|
-
} else if (options.nonInteractive) {
|
|
61
|
-
p.log.warn(`skip: ${relativePath} (already exists)`)
|
|
62
|
-
return false
|
|
63
|
-
} else {
|
|
64
|
-
const overwrite = await p.confirm({
|
|
65
|
-
message: `${relativePath} already exists. Overwrite?`,
|
|
66
|
-
initialValue: false,
|
|
67
|
-
})
|
|
68
|
-
if (p.isCancel(overwrite) || !overwrite) {
|
|
69
|
-
p.log.warn(`skip: ${relativePath}`)
|
|
70
|
-
return false
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
76
|
-
await fs.writeFile(filePath, content, 'utf-8')
|
|
77
|
-
p.log.success(`created: ${relativePath}`)
|
|
78
|
-
return true
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Compute the relative path from a file inside appDir to the project root.
|
|
83
|
-
* e.g. appDir="app" depth=1 → "../", appDir="src/app" depth=2 → "../../"
|
|
84
|
-
*/
|
|
85
|
-
function configImportPath(appDir: string, subdirs: number): string {
|
|
86
|
-
const appDepth = appDir.split('/').filter(Boolean).length
|
|
87
|
-
const totalDepth = appDepth + subdirs
|
|
88
|
-
return '../'.repeat(totalDepth) + 'canopycms.config'
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Framework integration: generates the files needed to add CanopyCMS
|
|
93
|
-
* editing to a Next.js app. Cloud-agnostic.
|
|
94
|
-
*/
|
|
95
|
-
export async function init(options: InitOptions): Promise<void> {
|
|
96
|
-
const { projectDir, mode, appDir, ai, force, nonInteractive } = options
|
|
97
|
-
const writeOpts = { force, nonInteractive }
|
|
98
|
-
|
|
99
|
-
p.intro('CanopyCMS init')
|
|
100
|
-
|
|
101
|
-
// Generate files
|
|
102
|
-
await writeFile(
|
|
103
|
-
path.join(projectDir, 'canopycms.config.ts'),
|
|
104
|
-
await canopyCmsConfig({ mode }),
|
|
105
|
-
writeOpts,
|
|
106
|
-
)
|
|
107
|
-
await writeFile(
|
|
108
|
-
path.join(projectDir, appDir, 'lib/canopy.ts'),
|
|
109
|
-
await canopyContext({ configImport: configImportPath(appDir, 1) }),
|
|
110
|
-
writeOpts,
|
|
111
|
-
)
|
|
112
|
-
await writeFile(path.join(projectDir, appDir, 'schemas.ts'), await schemasTemplate(), writeOpts)
|
|
113
|
-
await writeFile(
|
|
114
|
-
path.join(projectDir, appDir, 'api/canopycms/[...canopycms]/route.ts'),
|
|
115
|
-
await apiRoute({
|
|
116
|
-
canopyImport: '../'.repeat(3) + 'lib/canopy',
|
|
117
|
-
}),
|
|
118
|
-
writeOpts,
|
|
119
|
-
)
|
|
120
|
-
await writeFile(
|
|
121
|
-
path.join(projectDir, appDir, 'edit/page.tsx'),
|
|
122
|
-
await editPage({ configImport: configImportPath(appDir, 1) }),
|
|
123
|
-
writeOpts,
|
|
124
|
-
)
|
|
125
|
-
if (ai) {
|
|
126
|
-
await writeFile(path.join(projectDir, appDir, 'ai/config.ts'), await aiConfig(), writeOpts)
|
|
127
|
-
await writeFile(
|
|
128
|
-
path.join(projectDir, appDir, 'ai/[...path]/route.ts'),
|
|
129
|
-
await aiRoute({ configImport: configImportPath(appDir, 2) }),
|
|
130
|
-
writeOpts,
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Update .gitignore
|
|
135
|
-
const gitignorePath = path.join(projectDir, '.gitignore')
|
|
136
|
-
if (await fileExists(gitignorePath)) {
|
|
137
|
-
const content = await fs.readFile(gitignorePath, 'utf-8')
|
|
138
|
-
if (!content.includes('.canopy-prod-sim')) {
|
|
139
|
-
await fs.appendFile(gitignorePath, '\n# CanopyCMS\n.canopy-prod-sim/\n.canopy-dev/\n')
|
|
140
|
-
p.log.success('updated: .gitignore')
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
p.note(
|
|
145
|
-
[
|
|
146
|
-
'1. Install dependencies:',
|
|
147
|
-
` npm install canopycms canopycms-next canopycms-auth-clerk canopycms-auth-dev`,
|
|
148
|
-
'',
|
|
149
|
-
'2. Add transpilePackages to next.config.ts:',
|
|
150
|
-
" transpilePackages: ['canopycms']",
|
|
151
|
-
'',
|
|
152
|
-
'3. Customize ' + appDir + '/schemas.ts with your content schema',
|
|
153
|
-
'',
|
|
154
|
-
'4. Run: npm run dev',
|
|
155
|
-
'5. Visit: http://localhost:3000/edit',
|
|
156
|
-
].join('\n'),
|
|
157
|
-
'Next steps',
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
p.outro('Done!')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Cloud deployment artifacts: generates AWS-specific files
|
|
165
|
-
* (Dockerfile, CI workflow).
|
|
166
|
-
*/
|
|
167
|
-
export async function initDeployAws(options: InitDeployOptions): Promise<void> {
|
|
168
|
-
const { projectDir, force, nonInteractive } = options
|
|
169
|
-
const writeOpts = { force, nonInteractive }
|
|
170
|
-
|
|
171
|
-
p.intro('CanopyCMS init-deploy aws')
|
|
172
|
-
|
|
173
|
-
await writeFile(path.join(projectDir, 'Dockerfile.cms'), await dockerfileCms(), writeOpts)
|
|
174
|
-
await writeFile(
|
|
175
|
-
path.join(projectDir, '.github/workflows/deploy-cms.yml'),
|
|
176
|
-
await githubWorkflowCms(),
|
|
177
|
-
writeOpts,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
// Check if next.config already has CANOPY_BUILD support
|
|
181
|
-
const nextConfigPath = path.join(projectDir, 'next.config.ts')
|
|
182
|
-
const nextConfigMjsPath = path.join(projectDir, 'next.config.mjs')
|
|
183
|
-
const configPath = (await fileExists(nextConfigPath))
|
|
184
|
-
? nextConfigPath
|
|
185
|
-
: (await fileExists(nextConfigMjsPath))
|
|
186
|
-
? nextConfigMjsPath
|
|
187
|
-
: null
|
|
188
|
-
|
|
189
|
-
if (configPath) {
|
|
190
|
-
const content = await fs.readFile(configPath, 'utf-8')
|
|
191
|
-
if (!content.includes('CANOPY_BUILD')) {
|
|
192
|
-
p.note(
|
|
193
|
-
[
|
|
194
|
-
`Add dual build support to ${path.basename(configPath)}:`,
|
|
195
|
-
'',
|
|
196
|
-
" output: process.env.CANOPY_BUILD === 'cms' ? 'standalone' : 'export',",
|
|
197
|
-
].join('\n'),
|
|
198
|
-
'Manual step',
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
p.note(
|
|
204
|
-
'CDK constructs are available via the canopycms-cdk package.\nSee the deployment plan for CDK stack setup.',
|
|
205
|
-
'AWS deployment',
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
p.outro('Done!')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Worker run-once: process pending tasks, sync git, refresh auth cache, then exit.
|
|
213
|
-
* Used in prod-sim to trigger worker operations without a persistent daemon.
|
|
214
|
-
*/
|
|
215
|
-
export async function workerRunOnce(options: { projectDir: string }): Promise<void> {
|
|
216
|
-
// Dynamic import to avoid loading worker deps when not needed
|
|
217
|
-
const { getTaskQueueDir } = await import('../worker/task-queue-config')
|
|
218
|
-
|
|
219
|
-
// Determine workspace and mode from config
|
|
220
|
-
const cfgPath = path.join(options.projectDir, 'canopycms.config.ts')
|
|
221
|
-
let mode: 'prod' | 'prod-sim' = 'prod-sim'
|
|
222
|
-
try {
|
|
223
|
-
const configContent = await fs.readFile(cfgPath, 'utf-8')
|
|
224
|
-
// Match the mode property in the config object, not in comments or strings
|
|
225
|
-
if (/^\s*mode:\s*['"]prod['"]\s*[,}]/m.test(configContent)) {
|
|
226
|
-
mode = 'prod'
|
|
227
|
-
}
|
|
228
|
-
} catch {
|
|
229
|
-
// Default to prod-sim
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const taskDir = getTaskQueueDir({ mode })
|
|
233
|
-
if (!taskDir) {
|
|
234
|
-
console.log('Worker not needed in dev mode')
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// For prod-sim without GitHub, just refresh auth cache
|
|
239
|
-
const authMode = process.env.CANOPY_AUTH_MODE || 'dev'
|
|
240
|
-
const cachePath =
|
|
241
|
-
process.env.CANOPY_AUTH_CACHE_PATH ??
|
|
242
|
-
path.join(operatingStrategy(mode).getWorkspaceRoot(options.projectDir), '.cache')
|
|
243
|
-
|
|
244
|
-
let refreshAuthCache: (() => Promise<void>) | undefined
|
|
245
|
-
|
|
246
|
-
if (authMode === 'clerk') {
|
|
247
|
-
const clerkSecretKey = process.env.CLERK_SECRET_KEY
|
|
248
|
-
if (clerkSecretKey) {
|
|
249
|
-
const { refreshClerkCache } = await import('canopycms-auth-clerk/cache-writer')
|
|
250
|
-
refreshAuthCache = async () => {
|
|
251
|
-
const result = await refreshClerkCache({
|
|
252
|
-
secretKey: clerkSecretKey,
|
|
253
|
-
cachePath,
|
|
254
|
-
})
|
|
255
|
-
console.log(` ${result.userCount} users, ${result.groupCount} groups`)
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
} else if (authMode === 'dev') {
|
|
259
|
-
const { refreshDevCache } = await import('canopycms-auth-dev/cache-writer')
|
|
260
|
-
refreshAuthCache = async () => {
|
|
261
|
-
const result = await refreshDevCache({ cachePath })
|
|
262
|
-
console.log(` ${result.userCount} users, ${result.groupCount} groups`)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
console.log(`\nCanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})\n`)
|
|
267
|
-
|
|
268
|
-
// Refresh auth cache
|
|
269
|
-
if (refreshAuthCache) {
|
|
270
|
-
console.log('Refreshing auth cache...')
|
|
271
|
-
await refreshAuthCache()
|
|
272
|
-
console.log('Auth cache refreshed')
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Process task queue (if any pending tasks)
|
|
276
|
-
const { dequeueTask, completeTask } = await import('../worker/task-queue')
|
|
277
|
-
let taskCount = 0
|
|
278
|
-
let task
|
|
279
|
-
while ((task = await dequeueTask(taskDir)) !== null) {
|
|
280
|
-
console.log(`Processing task: ${task.action} (${task.id})`)
|
|
281
|
-
// In prod-sim without GitHub, just mark tasks as completed
|
|
282
|
-
// A real worker would execute the GitHub operations
|
|
283
|
-
console.warn(` WARNING: Task skipped — GitHub operations require the full worker daemon`)
|
|
284
|
-
await completeTask(taskDir, task.id, { skipped: true })
|
|
285
|
-
taskCount++
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (taskCount > 0) {
|
|
289
|
-
console.log(`Processed ${taskCount} task(s)`)
|
|
290
|
-
} else {
|
|
291
|
-
console.log('No pending tasks')
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
console.log('\nDone')
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** Parse CLI flags from argv, returning values and remaining positional args. */
|
|
298
|
-
function parseFlags(args: string[]): {
|
|
299
|
-
flags: Record<string, string | boolean>
|
|
300
|
-
positional: string[]
|
|
301
|
-
} {
|
|
302
|
-
const flags: Record<string, string | boolean> = {}
|
|
303
|
-
const positional: string[] = []
|
|
304
|
-
|
|
305
|
-
for (let i = 0; i < args.length; i++) {
|
|
306
|
-
const arg = args[i]
|
|
307
|
-
if (arg.startsWith('--')) {
|
|
308
|
-
const key = arg.slice(2)
|
|
309
|
-
// Boolean flags
|
|
310
|
-
if (key === 'force' || key === 'non-interactive' || key === 'no-ai') {
|
|
311
|
-
flags[key] = true
|
|
312
|
-
} else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
313
|
-
flags[key] = args[++i]
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
positional.push(arg)
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return { flags, positional }
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// CLI entrypoint
|
|
324
|
-
async function main() {
|
|
325
|
-
const args = process.argv.slice(2)
|
|
326
|
-
const { flags, positional } = parseFlags(args)
|
|
327
|
-
const command = positional[0]
|
|
328
|
-
|
|
329
|
-
if (command === 'init') {
|
|
330
|
-
const nonInteractive = flags['non-interactive'] === true
|
|
331
|
-
const force = flags['force'] === true
|
|
332
|
-
|
|
333
|
-
let mode: 'dev' | 'prod-sim'
|
|
334
|
-
if (flags['mode'] === 'dev' || flags['mode'] === 'prod-sim') {
|
|
335
|
-
mode = flags['mode']
|
|
336
|
-
} else if (nonInteractive) {
|
|
337
|
-
mode = 'dev'
|
|
338
|
-
} else {
|
|
339
|
-
const result = await p.select({
|
|
340
|
-
message: 'Which operating mode?',
|
|
341
|
-
options: [
|
|
342
|
-
{ value: 'dev' as const, label: 'dev', hint: 'Direct editing in current checkout' },
|
|
343
|
-
{
|
|
344
|
-
value: 'prod-sim' as const,
|
|
345
|
-
label: 'prod-sim',
|
|
346
|
-
hint: 'Simulates production with local branch clones',
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
initialValue: 'dev' as const,
|
|
350
|
-
})
|
|
351
|
-
if (p.isCancel(result)) {
|
|
352
|
-
p.cancel('Init cancelled.')
|
|
353
|
-
process.exit(0)
|
|
354
|
-
}
|
|
355
|
-
mode = result
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
let appDir: string
|
|
359
|
-
if (typeof flags['app-dir'] === 'string') {
|
|
360
|
-
appDir = flags['app-dir']
|
|
361
|
-
} else if (nonInteractive) {
|
|
362
|
-
appDir = 'app'
|
|
363
|
-
} else {
|
|
364
|
-
const result = await p.text({
|
|
365
|
-
message: 'App directory?',
|
|
366
|
-
placeholder: 'app',
|
|
367
|
-
defaultValue: 'app',
|
|
368
|
-
})
|
|
369
|
-
if (p.isCancel(result)) {
|
|
370
|
-
p.cancel('Init cancelled.')
|
|
371
|
-
process.exit(0)
|
|
372
|
-
}
|
|
373
|
-
appDir = result
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
let ai: boolean
|
|
377
|
-
if (flags['no-ai'] === true) {
|
|
378
|
-
ai = false
|
|
379
|
-
} else if (nonInteractive) {
|
|
380
|
-
ai = true
|
|
381
|
-
} else {
|
|
382
|
-
const result = await p.confirm({
|
|
383
|
-
message: 'Include AI content endpoint?',
|
|
384
|
-
initialValue: true,
|
|
385
|
-
})
|
|
386
|
-
if (p.isCancel(result)) {
|
|
387
|
-
p.cancel('Init cancelled.')
|
|
388
|
-
process.exit(0)
|
|
389
|
-
}
|
|
390
|
-
ai = result
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
await init({
|
|
394
|
-
mode,
|
|
395
|
-
appDir,
|
|
396
|
-
ai,
|
|
397
|
-
projectDir: process.cwd(),
|
|
398
|
-
force,
|
|
399
|
-
nonInteractive,
|
|
400
|
-
})
|
|
401
|
-
} else if (command === 'init-deploy') {
|
|
402
|
-
const cloud = positional[1]
|
|
403
|
-
if (cloud !== 'aws') {
|
|
404
|
-
console.error('Usage: canopycms init-deploy aws')
|
|
405
|
-
console.error('Only "aws" is currently supported.')
|
|
406
|
-
process.exit(1)
|
|
407
|
-
}
|
|
408
|
-
await initDeployAws({
|
|
409
|
-
cloud: 'aws',
|
|
410
|
-
projectDir: process.cwd(),
|
|
411
|
-
force: flags['force'] === true,
|
|
412
|
-
nonInteractive: flags['non-interactive'] === true,
|
|
413
|
-
})
|
|
414
|
-
} else if (command === 'worker') {
|
|
415
|
-
const subcommand = positional[1]
|
|
416
|
-
if (subcommand !== 'run-once') {
|
|
417
|
-
console.error('Usage: canopycms worker run-once')
|
|
418
|
-
process.exit(1)
|
|
419
|
-
}
|
|
420
|
-
await workerRunOnce({ projectDir: process.cwd() })
|
|
421
|
-
} else if (command === 'generate-ai-content') {
|
|
422
|
-
const { generateAIContentCLI } = await import('./generate-ai-content')
|
|
423
|
-
await generateAIContentCLI({
|
|
424
|
-
projectDir: process.cwd(),
|
|
425
|
-
outputDir: typeof flags['output'] === 'string' ? flags['output'] : undefined,
|
|
426
|
-
configPath: typeof flags['config'] === 'string' ? flags['config'] : undefined,
|
|
427
|
-
})
|
|
428
|
-
} else {
|
|
429
|
-
console.log('CanopyCMS CLI')
|
|
430
|
-
console.log('')
|
|
431
|
-
console.log('Commands:')
|
|
432
|
-
console.log(' init Add CanopyCMS to a Next.js app')
|
|
433
|
-
console.log(' --mode <dev|prod-sim> Operating mode (default: dev)')
|
|
434
|
-
console.log(' --app-dir <path> App directory (default: app)')
|
|
435
|
-
console.log(' --no-ai Skip AI content endpoint generation')
|
|
436
|
-
console.log(' --force Overwrite existing files without asking')
|
|
437
|
-
console.log(' --non-interactive Use defaults, no prompts')
|
|
438
|
-
console.log('')
|
|
439
|
-
console.log(' init-deploy aws Generate AWS deployment artifacts')
|
|
440
|
-
console.log(' --force Overwrite existing files without asking')
|
|
441
|
-
console.log(' --non-interactive Use defaults, no prompts')
|
|
442
|
-
console.log('')
|
|
443
|
-
console.log(' worker run-once Process tasks, sync git, refresh auth cache')
|
|
444
|
-
console.log(' generate-ai-content Generate static AI-ready content files')
|
|
445
|
-
console.log(' --output <dir> Output directory (default: public/ai)')
|
|
446
|
-
console.log(' --config <path> Path to AI content config file')
|
|
447
|
-
process.exit(0)
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Only run when executed directly as a CLI, not when imported in tests.
|
|
452
|
-
// Use realpathSync to resolve symlinks — npx creates a symlink in node_modules/.bin/
|
|
453
|
-
// that won't match import.meta.url's resolved real path.
|
|
454
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
455
|
-
const isDirectRun = realpathSync(process.argv[1]) === realpathSync(__filename)
|
|
456
|
-
|
|
457
|
-
if (isDirectRun) {
|
|
458
|
-
main().catch((err) => {
|
|
459
|
-
console.error('Error:', err instanceof Error ? err.message : String(err))
|
|
460
|
-
process.exit(1)
|
|
461
|
-
})
|
|
462
|
-
}
|