@quatrain/cli 1.1.8

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.
@@ -0,0 +1,130 @@
1
+ import inquirer from 'inquirer'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ /**
6
+ * Triggers an interactive CLI prompt to generate a root `quatrain.json` configuration file.
7
+ * Automatically configures backend, authentication, queueing, and storage parameters.
8
+ */
9
+ export async function generateConfig() {
10
+ console.log('--- Initializing Quatrain Configuration ---\n')
11
+
12
+ const answers = await inquirer.prompt([
13
+ {
14
+ type: 'list',
15
+ name: 'backend',
16
+ message: 'Which Backend system to use?',
17
+ choices: [
18
+ { name: 'SQLite', value: 'sqlite' },
19
+ { name: 'PostgreSQL', value: 'postgres' },
20
+ { name: 'Firestore', value: 'firestore' },
21
+ { name: 'None (Mock)', value: 'mock' },
22
+ ],
23
+ },
24
+ {
25
+ type: 'list',
26
+ name: 'auth',
27
+ message: 'Which Authentication provider to use?',
28
+ choices: [
29
+ { name: 'Supabase', value: 'supabase' },
30
+ { name: 'Firebase', value: 'firebase' },
31
+ { name: 'None', value: 'none' },
32
+ ],
33
+ },
34
+ {
35
+ type: 'list',
36
+ name: 'queue',
37
+ message: 'Which Queue system to use?',
38
+ choices: [
39
+ { name: 'AMQP (RabbitMQ)', value: 'amqp' },
40
+ { name: 'AWS SQS', value: 'sqs' },
41
+ { name: 'GCP Pub/Sub', value: 'gcp' },
42
+ { name: 'None', value: 'none' },
43
+ ],
44
+ },
45
+ {
46
+ type: 'list',
47
+ name: 'storage',
48
+ message: 'Which Storage provider to use?',
49
+ choices: [
50
+ { name: 'AWS S3', value: 's3' },
51
+ { name: 'Supabase Storage', value: 'supabase' },
52
+ { name: 'Firebase Storage', value: 'firebase' },
53
+ { name: 'None', value: 'none' },
54
+ ],
55
+ }
56
+ ])
57
+
58
+ const config: any = {}
59
+
60
+ // --- Backend Config ---
61
+ if (answers.backend !== 'mock') {
62
+ config.backend = {
63
+ adapter: answers.backend === 'sqlite' ? 'SQLiteAdapter' :
64
+ answers.backend === 'postgres' ? 'PostgresAdapter' : 'FirestoreAdapter',
65
+ package: `@quatrain/backend-${answers.backend}`,
66
+ config: {}
67
+ }
68
+ if (answers.backend === 'sqlite') {
69
+ config.backend.config.database = 'env(DB_PATH)'
70
+ } else if (answers.backend === 'postgres') {
71
+ config.backend.config.host = 'env(PG_HOST)'
72
+ config.backend.config.port = 'env(PG_PORT)'
73
+ config.backend.config.user = 'env(PG_USER)'
74
+ config.backend.config.password = 'env(PG_PWD)'
75
+ config.backend.config.database = 'env(PG_DB)'
76
+ }
77
+ }
78
+
79
+ // --- Auth Config ---
80
+ if (answers.auth !== 'none') {
81
+ config.auth = {
82
+ adapter: answers.auth === 'supabase' ? 'SupabaseAuthAdapter' : 'FirebaseAuthAdapter',
83
+ package: `@quatrain/auth-${answers.auth}`,
84
+ config: {}
85
+ }
86
+ if (answers.auth === 'supabase') {
87
+ config.auth.config.supabaseUrl = 'env(SUPABASE_URL)'
88
+ config.auth.config.supabaseKey = 'env(SUPABASE_KEY)'
89
+ }
90
+ }
91
+
92
+ // --- Queue Config ---
93
+ if (answers.queue !== 'none') {
94
+ const adapterMap: Record<string, string> = { amqp: 'AmqpQueueAdapter', sqs: 'SqsAdapter', gcp: 'GcpPubSubAdapter' }
95
+ const pkgMap: Record<string, string> = { amqp: 'amqp', sqs: 'aws', gcp: 'gcp' }
96
+ config.queue = {
97
+ adapter: adapterMap[answers.queue as string],
98
+ package: `@quatrain/queue-${pkgMap[answers.queue as string]}`,
99
+ config: {}
100
+ }
101
+ if (answers.queue === 'amqp') {
102
+ config.queue.config.host = 'env(MQ_HOST)'
103
+ config.queue.config.port = 'env(MQ_PORT)'
104
+ config.queue.config.user = 'env(MQ_USER)'
105
+ config.queue.config.password = 'env(MQ_PWD)'
106
+ }
107
+ }
108
+
109
+ // --- Storage Config ---
110
+ if (answers.storage !== 'none') {
111
+ const adapterMap: Record<string, string> = { s3: 'S3StorageAdapter', supabase: 'SupabaseStorageAdapter', firebase: 'FirebaseStorageAdapter' }
112
+ config.storage = {
113
+ adapter: adapterMap[answers.storage as string],
114
+ package: `@quatrain/storage-${answers.storage}`,
115
+ config: {}
116
+ }
117
+ if (answers.storage === 's3') {
118
+ config.storage.config.accesskey = 'env(S3_ACCESS_KEY)'
119
+ config.storage.config.secret = 'env(S3_SECRET_KEY)'
120
+ config.storage.config.region = 'env(S3_REGION)'
121
+ config.storage.config.bucket = 'env(S3_BUCKET)'
122
+ }
123
+ }
124
+
125
+ const outputPath = path.resolve(process.cwd(), 'quatrain.json')
126
+ fs.writeFileSync(outputPath, JSON.stringify(config, null, 3), 'utf8')
127
+
128
+ console.log(`\nāœ… Configuration file successfully generated at: ${outputPath}`)
129
+ console.log('Tokens formatted as "env(...)" will automatically be replaced by the Bootloader with runtime environment variables.\n')
130
+ }
@@ -0,0 +1,49 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ /**
5
+ * Creates a new timestamp-prefixed migration script scaffold within the local `migrations/` folder.
6
+ *
7
+ * @param name - The semantic suffix name of the migration.
8
+ */
9
+ export async function generateMigration(name: string) {
10
+ if (!name) {
11
+ console.error('Error: Migration name is required.')
12
+ process.exit(1)
13
+ }
14
+
15
+ // Format YYYYMMDDHHmmss
16
+ const now = new Date()
17
+ const timestamp = now.getFullYear().toString() +
18
+ (now.getMonth() + 1).toString().padStart(2, '0') +
19
+ now.getDate().toString().padStart(2, '0') +
20
+ now.getHours().toString().padStart(2, '0') +
21
+ now.getMinutes().toString().padStart(2, '0') +
22
+ now.getSeconds().toString().padStart(2, '0')
23
+
24
+ const filename = `${timestamp}_${name}.ts`
25
+ const migrationsDir = path.resolve(process.cwd(), 'migrations')
26
+
27
+ if (!fs.existsSync(migrationsDir)) {
28
+ fs.mkdirSync(migrationsDir, { recursive: true })
29
+ }
30
+
31
+ const filePath = path.join(migrationsDir, filename)
32
+
33
+ const template = `import { Backend } from '@quatrain/backend'
34
+
35
+ export const up = async () => {
36
+ // const db = await Backend.getBackend('default')._connect()
37
+ // await db.exec(\`CREATE TABLE IF NOT EXISTS example (id TEXT PRIMARY KEY);\`)
38
+ }
39
+
40
+ export const down = async () => {
41
+ // const db = await Backend.getBackend('default')._connect()
42
+ // await db.exec(\`DROP TABLE IF EXISTS example;\`)
43
+ }
44
+ `
45
+
46
+ fs.writeFileSync(filePath, template, 'utf8')
47
+
48
+ console.log(`\nāœ… Migration file successfully generated at: ${filePath}\n`)
49
+ }
@@ -0,0 +1,86 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ /**
5
+ * Bootstraps a completely new, production-ready Quatrain project structure.
6
+ * Creates monorepo configuration files (package.json, tsconfig.json) and standard directories.
7
+ *
8
+ * @param projectName - The name and path of the target project directory.
9
+ */
10
+ export async function generateScaffold(projectName: string) {
11
+ const projectDir = path.resolve(process.cwd(), projectName)
12
+
13
+ if (fs.existsSync(projectDir)) {
14
+ console.error(`Error: The directory "${projectName}" already exists.`)
15
+ process.exit(1)
16
+ }
17
+
18
+ console.log(`\nšŸš€ Initializing Quatrain project: ${projectName}...\n`)
19
+
20
+ // 1. Create base directories
21
+ const dirs = ['apps', 'data', 'config', 'packages', 'migrations']
22
+ dirs.forEach(dir => {
23
+ const dirPath = path.join(projectDir, dir)
24
+ fs.mkdirSync(dirPath, { recursive: true })
25
+ // Add a .gitkeep file for git
26
+ fs.writeFileSync(path.join(dirPath, '.gitkeep'), '', 'utf8')
27
+ console.log(`šŸ“ Directory created: ${dir}/`)
28
+ })
29
+
30
+ // 2. Create base package.json (Monorepo)
31
+ const packageJson = {
32
+ name: projectName.toLowerCase(),
33
+ version: "1.0.0",
34
+ private: true,
35
+ workspaces: [
36
+ "apps/*",
37
+ "packages/*"
38
+ ],
39
+ scripts: {
40
+ "dev": "yarn workspaces foreach -p run dev",
41
+ "build": "yarn workspaces foreach -ptA run build"
42
+ },
43
+ dependencies: {
44
+ "@quatrain/core": "^1.0.0",
45
+ "@quatrain/app": "^1.0.0"
46
+ }
47
+ }
48
+
49
+ fs.writeFileSync(
50
+ path.join(projectDir, 'package.json'),
51
+ JSON.stringify(packageJson, null, 3),
52
+ 'utf8'
53
+ )
54
+ console.log(`šŸ“„ File created: package.json`)
55
+
56
+ // 3. Create root tsconfig.json
57
+ const tsconfigJson = {
58
+ compilerOptions: {
59
+ target: "ES2022",
60
+ module: "NodeNext",
61
+ moduleResolution: "NodeNext",
62
+ lib: ["ES2022"],
63
+ strict: true,
64
+ esModuleInterop: true,
65
+ skipLibCheck: true,
66
+ forceConsistentCasingInFileNames: true,
67
+ baseUrl: ".",
68
+ paths: {
69
+ "@app/*": ["apps/*/src/index.ts"],
70
+ "@packages/*": ["packages/*/src/index.ts"]
71
+ }
72
+ },
73
+ exclude: ["node_modules", "**/dist", "**/lib"]
74
+ }
75
+
76
+ fs.writeFileSync(
77
+ path.join(projectDir, 'tsconfig.json'),
78
+ JSON.stringify(tsconfigJson, null, 3),
79
+ 'utf8'
80
+ )
81
+ console.log(`šŸ“„ File created: tsconfig.json`)
82
+
83
+ // 4. Final instructions
84
+ console.log(`\nāœ… Project "${projectName}" successfully scaffolded!`)
85
+ console.log(`\nTo get started:\n cd ${projectName}\n yarn install\n`)
86
+ }
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ import inquirer from 'inquirer';
2
+
3
+ export { inquirer };
4
+
5
+ /**
6
+ * Ask a yes/no question to the user.
7
+ */
8
+ export async function askConfirm(message: string, defaultVal = true): Promise<boolean> {
9
+ const result = await inquirer.prompt([
10
+ {
11
+ type: 'confirm',
12
+ name: 'value',
13
+ message,
14
+ default: defaultVal,
15
+ },
16
+ ]);
17
+ return result.value;
18
+ }
19
+
20
+ /**
21
+ * Prompt the user for textual input.
22
+ */
23
+ export async function askInput(message: string, defaultVal?: string): Promise<string> {
24
+ const result = await inquirer.prompt([
25
+ {
26
+ type: 'input',
27
+ name: 'value',
28
+ message,
29
+ default: defaultVal,
30
+ },
31
+ ]);
32
+ return result.value;
33
+ }
34
+
35
+ /**
36
+ * Prompt the user to select from a list of choices.
37
+ */
38
+ export async function askChoice<T = string>(
39
+ message: string,
40
+ choices: (string | { name: string; value: T })[]
41
+ ): Promise<T> {
42
+ const result = await inquirer.prompt([
43
+ {
44
+ type: 'list',
45
+ name: 'value',
46
+ message,
47
+ choices,
48
+ },
49
+ ]);
50
+ return result.value;
51
+ }
52
+
53
+ import { Command } from 'commander';
54
+
55
+ /**
56
+ * Custom Commander subclass to wrap Command functionality inside @quatrain/cli.
57
+ */
58
+ export class CliCommand extends Command {}
59
+