create-absolutejs 0.3.13 → 0.3.14

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.
@@ -1,7 +1,8 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
- type AddConfigurationProps = Pick<CreateConfiguration, 'tailwind' | 'initializeGitNow' | 'codeQualityTool' | 'frontends'> & {
2
+ type AddConfigurationProps = Pick<CreateConfiguration, 'tailwind' | 'initializeGitNow' | 'codeQualityTool' | 'frontends' | 'projectName' | 'databaseEngine' | 'orm'> & {
3
3
  templatesDirectory: string;
4
4
  projectName: string;
5
+ envVariables: string[] | undefined;
5
6
  };
6
- export declare const addConfigurationFiles: ({ tailwind, templatesDirectory, codeQualityTool, frontends, initializeGitNow, projectName }: AddConfigurationProps) => void;
7
+ export declare const addConfigurationFiles: ({ tailwind, templatesDirectory, orm, databaseEngine, envVariables, codeQualityTool, frontends, initializeGitNow, projectName }: AddConfigurationProps) => void;
7
8
  export {};
@@ -1,8 +1,9 @@
1
1
  import { copyFileSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { dim, yellow } from 'picocolors';
4
+ import { generateEnv } from './generateEnv';
4
5
  import { generatePrettierrc } from './generatePrettierrc';
5
- export const addConfigurationFiles = ({ tailwind, templatesDirectory, codeQualityTool, frontends, initializeGitNow, projectName }) => {
6
+ export const addConfigurationFiles = ({ tailwind, templatesDirectory, orm, databaseEngine, envVariables, codeQualityTool, frontends, initializeGitNow, projectName }) => {
6
7
  copyFileSync(join(templatesDirectory, 'configurations', 'tsconfig.example.json'), join(projectName, 'tsconfig.json'));
7
8
  if (tailwind) {
8
9
  copyFileSync(join(templatesDirectory, 'tailwind', 'postcss.config.ts'), join(projectName, 'postcss.config.ts'));
@@ -18,4 +19,10 @@ export const addConfigurationFiles = ({ tailwind, templatesDirectory, codeQualit
18
19
  }
19
20
  else
20
21
  console.warn(`${dim('│')}\n${yellow('▲')} Biome support not implemented yet`);
22
+ generateEnv({
23
+ databaseEngine,
24
+ envVariables,
25
+ orm,
26
+ projectName
27
+ });
21
28
  };
@@ -0,0 +1,6 @@
1
+ import { CreateConfiguration } from '../../types';
2
+ type GenerateEnvProps = Pick<CreateConfiguration, 'databaseEngine' | 'orm' | 'projectName'> & {
3
+ envVariables?: string[];
4
+ };
5
+ export declare const generateEnv: ({ databaseEngine, orm, envVariables, projectName }: GenerateEnvProps) => void;
6
+ export {};
@@ -0,0 +1,25 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ const databaseURLS = {
4
+ cockroachdb: 'cockroachdb://user:password@localhost:26257/database',
5
+ gel: 'gel://user:password@localhost:5432/database',
6
+ mariadb: 'mariadb://user:password@localhost:3306/database',
7
+ mongodb: 'mongodb://user:password@localhost:27017/database',
8
+ mssql: 'mssql://user:password@localhost:1433/database',
9
+ mysql: 'mysql://user:password@localhost:3306/database',
10
+ postgresql: 'postgresql://user:password@localhost:5432/database',
11
+ singlestore: 'singlestore://user:password@localhost:3306/database'
12
+ };
13
+ export const generateEnv = ({ databaseEngine, orm, envVariables = [], projectName }) => {
14
+ const vars = [...envVariables];
15
+ if (databaseEngine !== 'sqlite' &&
16
+ databaseEngine !== 'none' &&
17
+ databaseEngine !== undefined &&
18
+ (orm === 'none' || orm === undefined)) {
19
+ vars.push(`DATABASE_URL=${databaseURLS[databaseEngine]}`);
20
+ }
21
+ if (vars.length === 0)
22
+ return;
23
+ const envPath = join(projectName, '.env');
24
+ writeFileSync(envPath, vars.join('\n'), 'utf8');
25
+ };
@@ -80,11 +80,26 @@ export const createPackageJson = ({ projectName, authProvider, plugins, database
80
80
  scripts['db:up'] =
81
81
  'sh -c "docker info >/dev/null 2>&1 || sudo service docker start; docker compose -f db/docker-compose.db.yml up -d db"';
82
82
  scripts['db:down'] = 'docker compose -f db/docker-compose.db.yml down';
83
+ scripts['db:reset'] =
84
+ 'docker compose -f db/docker-compose.db.yml down -v';
83
85
  scripts['db:psql'] =
84
- 'docker compose -f db/docker-compose.db.yml exec db psql -U postgres -d appdb';
86
+ 'docker compose -f db/docker-compose.db.yml exec db psql -U postgres';
85
87
  scripts['predev'] = 'bun db:up';
86
88
  scripts['postdev'] = 'bun db:down';
87
89
  }
90
+ if (databaseEngine === 'mysql') {
91
+ dependencies['mysql2'] = resolveVersion('mysql2', '3.14.2');
92
+ }
93
+ if (databaseEngine === 'mysql' &&
94
+ (!databaseHost || databaseHost === 'none')) {
95
+ scripts['db:up'] =
96
+ 'sh -c "docker info >/dev/null 2>&1 || sudo service docker start; docker compose -f db/docker-compose.db.yml up -d db"';
97
+ scripts['db:down'] = 'docker compose -f db/docker-compose.db.yml down';
98
+ scripts['predev'] = 'bun db:up';
99
+ scripts['postdev'] = 'bun db:down';
100
+ scripts['db:mysql'] =
101
+ 'docker compose -f db/docker-compose.db.yml exec db mysql -u appuser -pappuser appdb';
102
+ }
88
103
  if (databaseEngine === 'sqlite' &&
89
104
  (!databaseHost || databaseHost === 'none')) {
90
105
  scripts['db:sqlite'] = 'sqlite3 db/database.sqlite';
@@ -0,0 +1,2 @@
1
+ import { DatabaseEngine } from '../../types';
2
+ export declare const generateDatabaseContainer: (databaseEngine: DatabaseEngine) => string;
@@ -0,0 +1,105 @@
1
+ const templates = {
2
+ cockroachdb: {
3
+ env: {
4
+ COCKROACH_INSECURE: 'true'
5
+ },
6
+ image: 'cockroachdb/cockroach:v24.1.0',
7
+ port: '26257:26257',
8
+ volumePath: '/cockroach/cockroach-data'
9
+ },
10
+ gel: {
11
+ env: {
12
+ GEL_DB: 'database',
13
+ GEL_PASSWORD: 'password',
14
+ GEL_USER: 'user'
15
+ },
16
+ image: 'gel:latest',
17
+ port: '4000:4000',
18
+ volumePath: '/var/lib/gel'
19
+ },
20
+ mariadb: {
21
+ env: {
22
+ MYSQL_DATABASE: 'database',
23
+ MYSQL_PASSWORD: 'userpassword',
24
+ MYSQL_ROOT_PASSWORD: 'rootpassword',
25
+ MYSQL_USER: 'user'
26
+ },
27
+ image: 'mariadb:11.4',
28
+ port: '3306:3306',
29
+ volumePath: '/var/lib/mysql'
30
+ },
31
+ mongodb: {
32
+ env: {
33
+ MONGO_INITDB_DATABASE: 'database',
34
+ MONGO_INITDB_ROOT_PASSWORD: 'password',
35
+ MONGO_INITDB_ROOT_USERNAME: 'user'
36
+ },
37
+ image: 'mongo:7.0',
38
+ port: '27017:27017',
39
+ volumePath: '/data/db'
40
+ },
41
+ mssql: {
42
+ env: {
43
+ ACCEPT_EULA: 'Y',
44
+ MSSQL_PID: 'Express',
45
+ SA_PASSWORD: 'Strong_Passw0rd'
46
+ },
47
+ image: 'mcr.microsoft.com/mssql/server:2022-latest',
48
+ port: '1433:1433',
49
+ volumePath: '/var/opt/mssql'
50
+ },
51
+ mysql: {
52
+ env: {
53
+ MYSQL_DATABASE: 'database',
54
+ MYSQL_PASSWORD: 'userpassword',
55
+ MYSQL_ROOT_PASSWORD: 'rootpassword',
56
+ MYSQL_USER: 'user'
57
+ },
58
+ image: 'mysql:8.0',
59
+ port: '3306:3306',
60
+ volumePath: '/var/lib/mysql'
61
+ },
62
+ postgresql: {
63
+ env: {
64
+ POSTGRES_DB: 'database',
65
+ POSTGRES_PASSWORD: 'password',
66
+ POSTGRES_USER: 'user'
67
+ },
68
+ image: 'postgres:15',
69
+ port: '5432:5432',
70
+ volumePath: '/var/lib/postgresql/data'
71
+ },
72
+ singlestore: {
73
+ env: {
74
+ ROOT_PASSWORD: 'password'
75
+ },
76
+ image: 'singlestore/cluster-in-a-box:latest',
77
+ port: '3306:3306',
78
+ volumePath: '/var/lib/memsql'
79
+ }
80
+ };
81
+ export const generateDatabaseContainer = (databaseEngine) => {
82
+ if (databaseEngine === undefined ||
83
+ databaseEngine === 'none' ||
84
+ databaseEngine === 'sqlite') {
85
+ throw new Error('Internal type error: Expected a valid local database engine');
86
+ }
87
+ const { image, port, env, volumePath } = templates[databaseEngine];
88
+ const envLines = Object.entries(env)
89
+ .map(([key, value]) => ` ${key}: ${value}`)
90
+ .join('\n');
91
+ return `services:
92
+ db:
93
+ image: ${image}
94
+ restart: always
95
+ environment:
96
+ ${envLines}
97
+ ports:
98
+ - "${port}"
99
+ volumes:
100
+ - db_data:${volumePath}
101
+
102
+ volumes:
103
+ db_data:
104
+ `;
105
+ };
@@ -4,7 +4,42 @@ type QueryOperations = {
4
4
  selectHistory: string;
5
5
  insertHistory: string;
6
6
  };
7
+ type HandlerType = {
8
+ CountHistoryRow: string;
9
+ UserRow: string;
10
+ };
7
11
  declare const driverConfigurations: {
12
+ readonly 'cockroachdb:sql:local': {
13
+ readonly dbType: "Pool";
14
+ readonly importLines: "import { Pool } from 'pg'";
15
+ readonly queries: QueryOperations;
16
+ };
17
+ readonly 'gel:sql:local': {
18
+ readonly dbType: "GelClient";
19
+ readonly importLines: "import { GelClient } from 'gel'";
20
+ readonly queries: QueryOperations;
21
+ };
22
+ readonly 'mariadb:sql:local': {
23
+ readonly dbType: "Pool";
24
+ readonly importLines: "import { Pool } from 'mariadb'";
25
+ readonly queries: QueryOperations;
26
+ };
27
+ readonly 'mongodb:native:local': {
28
+ readonly dbType: "Db";
29
+ readonly importLines: "import { Db } from 'mongodb'";
30
+ readonly queries: QueryOperations;
31
+ };
32
+ readonly 'mssql:sql:local': {
33
+ readonly dbType: "ConnectionPool";
34
+ readonly importLines: "import { ConnectionPool } from 'mssql'";
35
+ readonly queries: QueryOperations;
36
+ };
37
+ readonly 'mysql:sql:local': {
38
+ readonly dbType: "Pool";
39
+ readonly handlerTypes: HandlerType;
40
+ readonly importLines: "import { Pool, ResultSetHeader, RowDataPacket } from 'mysql2/promise'";
41
+ readonly queries: QueryOperations;
42
+ };
8
43
  readonly 'postgresql:drizzle:local': {
9
44
  readonly dbType: "BunSQLDatabase<SchemaType>";
10
45
  readonly importLines: "\nimport { eq } from 'drizzle-orm'\nimport { BunSQLDatabase } from 'drizzle-orm/bun-sql'\nimport { schema, type SchemaType } from '../../../db/schema'";
@@ -25,6 +60,11 @@ declare const driverConfigurations: {
25
60
  readonly importLines: "import { NeonQueryFunction } from '@neondatabase/serverless'";
26
61
  readonly queries: QueryOperations;
27
62
  };
63
+ readonly 'singlestore:sql:local': {
64
+ readonly dbType: "Connection";
65
+ readonly importLines: "import { Connection } from '@singlestore/db-client'";
66
+ readonly queries: QueryOperations;
67
+ };
28
68
  readonly 'sqlite:drizzle:local': {
29
69
  readonly dbType: "BunSQLiteDatabase<SchemaType>";
30
70
  readonly importLines: "\nimport { eq } from 'drizzle-orm'\nimport { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'\nimport { schema, type SchemaType } from '../../../db/schema'";
@@ -1,36 +1,36 @@
1
- const buildSqlAuthTemplate = ({ importLines, dbType, queries }) => `
1
+ const buildSqlAuthTemplate = ({ importLines, handlerTypes, dbType, queries }) => `
2
2
  import { isValidProviderOption, providers } from 'citra'
3
3
  ${importLines}
4
-
4
+ ${handlerTypes?.UserRow ? `\ntype UserRow = ${handlerTypes.UserRow}` : ''}
5
5
  type UserHandlerProps = {
6
- authProvider: string
7
- db: ${dbType}
8
- userIdentity: Record<string, unknown>
6
+ authProvider: string
7
+ db: ${dbType}
8
+ userIdentity: Record<string, unknown>
9
9
  }
10
10
 
11
11
  export const getUser = async ({ authProvider, db, userIdentity }: UserHandlerProps) => {
12
- if (!isValidProviderOption(authProvider)) throw new Error(\`Invalid auth provider: \${authProvider}\`)
13
- const subject = providers[authProvider].extractSubjectFromIdentity(userIdentity)
14
- const authSub = \`\${authProvider.toUpperCase()}|\${subject}\`
15
- ${queries.selectUser}
12
+ if (!isValidProviderOption(authProvider)) throw new Error(\`Invalid auth provider: \${authProvider}\`)
13
+ const subject = providers[authProvider].extractSubjectFromIdentity(userIdentity)
14
+ const authSub = \`\${authProvider.toUpperCase()}|\${subject}\`
15
+ ${queries.selectUser}
16
16
  }
17
17
 
18
18
  export const createUser = async ({ authProvider, db, userIdentity }: UserHandlerProps) => {
19
- if (!isValidProviderOption(authProvider)) throw new Error(\`Invalid auth provider: \${authProvider}\`)
20
- const subject = providers[authProvider].extractSubjectFromIdentity(userIdentity)
21
- const authSub = \`\${authProvider.toUpperCase()}|\${subject}\`
22
- ${queries.insertUser}
19
+ if (!isValidProviderOption(authProvider)) throw new Error(\`Invalid auth provider: \${authProvider}\`)
20
+ const subject = providers[authProvider].extractSubjectFromIdentity(userIdentity)
21
+ const authSub = \`\${authProvider.toUpperCase()}|\${subject}\`
22
+ ${queries.insertUser}
23
23
  }
24
24
  `;
25
- const buildSqlCountTemplate = ({ importLines, dbType, queries }) => `
25
+ const buildSqlCountTemplate = ({ importLines, handlerTypes, dbType, queries }) => `
26
26
  ${importLines}
27
-
27
+ ${handlerTypes?.CountHistoryRow ? `\ntype CountHistoryRow = ${handlerTypes.CountHistoryRow}\n` : ''}
28
28
  export const getCountHistory = async (db: ${dbType}, uid: number) => {
29
- ${queries.selectHistory}
29
+ ${queries.selectHistory}
30
30
  }
31
31
 
32
32
  export const createCountHistory = async (db: ${dbType}, count: number) => {
33
- ${queries.insertHistory}
33
+ ${queries.insertHistory}
34
34
  }
35
35
  `;
36
36
  const drizzleQueryOperations = {
@@ -100,7 +100,173 @@ const postgresSqlQueryOperations = {
100
100
  \`
101
101
  return user ?? null`
102
102
  };
103
+ const mongodbQueryOperations = {
104
+ insertHistory: `const { insertedId } = await db.collection('count_history').insertOne({ count })
105
+ const newHistory = await db.collection('count_history').findOne({ _id: insertedId })
106
+ return newHistory`,
107
+ insertUser: `const { insertedId } = await db.collection('users').insertOne({ auth_sub: authSub, metadata: userIdentity })
108
+ const newUser = await db.collection('users').findOne({ _id: insertedId })
109
+ if (!newUser) throw new Error('Failed to create user')
110
+ return newUser`,
111
+ selectHistory: `const history = await db.collection('count_history').findOne({ uid })
112
+ return history ?? null`,
113
+ selectUser: `const user = await db.collection('users').findOne({ auth_sub: authSub })
114
+ return user ?? null`
115
+ };
116
+ const mariadbSqlQueryOperations = {
117
+ insertHistory: `await db.query('INSERT INTO count_history (count) VALUES (?)', [count])
118
+ const [rows] = await db.query('SELECT * FROM count_history ORDER BY uid DESC LIMIT 1')
119
+ return rows[0]`,
120
+ insertUser: `await db.query('INSERT INTO users (auth_sub, metadata) VALUES (?, ?)', [authSub, JSON.stringify(userIdentity)])
121
+ const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
122
+ const newUser = rows[0]
123
+ if (!newUser) throw new Error('Failed to create user')
124
+ return newUser`,
125
+ selectHistory: `const [rows] = await db.query('SELECT * FROM count_history WHERE uid = ? LIMIT 1', [uid])
126
+ return rows[0] ?? null`,
127
+ selectUser: `const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
128
+ return rows[0] ?? null`
129
+ };
130
+ const gelSqlQueryOperations = {
131
+ insertHistory: `await db.query('INSERT INTO count_history (count) VALUES (?)', [count])
132
+ const [rows] = await db.query('SELECT * FROM count_history ORDER BY uid DESC LIMIT 1')
133
+ return rows[0]`,
134
+ insertUser: `await db.query('INSERT INTO users (auth_sub, metadata) VALUES (?, ?)', [authSub, JSON.stringify(userIdentity)])
135
+ const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
136
+ const newUser = rows[0]
137
+ if (!newUser) throw new Error('Failed to create user')
138
+ return newUser`,
139
+ selectHistory: `const [rows] = await db.query('SELECT * FROM count_history WHERE uid = ? LIMIT 1', [uid])
140
+ return rows[0] ?? null`,
141
+ selectUser: `const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
142
+ return rows[0] ?? null`
143
+ };
144
+ const singlestoreSqlQueryOperations = {
145
+ insertHistory: `await db.query('INSERT INTO count_history (count) VALUES (?)', [count])
146
+ const [rows] = await db.query('SELECT * FROM count_history ORDER BY uid DESC LIMIT 1')
147
+ return rows[0]`,
148
+ insertUser: `await db.query('INSERT INTO users (auth_sub, metadata) VALUES (?, ?)', [authSub, JSON.stringify(userIdentity)])
149
+ const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
150
+ const newUser = rows[0]
151
+ if (!newUser) throw new Error('Failed to create user')
152
+ return newUser`,
153
+ selectHistory: `const [rows] = await db.query('SELECT * FROM count_history WHERE uid = ? LIMIT 1', [uid])
154
+ return rows[0] ?? null`,
155
+ selectUser: `const [rows] = await db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1', [authSub])
156
+ return rows[0] ?? null`
157
+ };
158
+ const cockroachdbPoolQueryOperations = {
159
+ insertHistory: `const { rows } = await db.query('INSERT INTO count_history (count) VALUES ($1) RETURNING *', [count])
160
+ return rows[0]`,
161
+ insertUser: `const { rows } = await db.query('INSERT INTO users (auth_sub, metadata) VALUES ($1, $2) RETURNING *', [authSub, userIdentity])
162
+ const newUser = rows[0]
163
+ if (!newUser) throw new Error('Failed to create user')
164
+ return newUser`,
165
+ selectHistory: `const { rows } = await db.query('SELECT * FROM count_history WHERE uid = $1 LIMIT 1', [uid])
166
+ return rows[0] ?? null`,
167
+ selectUser: `const { rows } = await db.query('SELECT * FROM users WHERE auth_sub = $1 LIMIT 1', [authSub])
168
+ return rows[0] ?? null`
169
+ };
170
+ const mssqlSqlQueryOperations = {
171
+ insertHistory: `await db.request().input('count', count).query('INSERT INTO count_history (count) VALUES (@count)')
172
+ const result = await db.request().query('SELECT TOP 1 * FROM count_history ORDER BY uid DESC')
173
+ return result.recordset[0]`,
174
+ insertUser: `await db.request().input('authSub', authSub).input('metadata', JSON.stringify(userIdentity)).query('INSERT INTO users (auth_sub, metadata) VALUES (@authSub, @metadata)')
175
+ const result = await db.request().input('authSub', authSub).query('SELECT TOP 1 * FROM users WHERE auth_sub = @authSub')
176
+ const newUser = result.recordset[0]
177
+ if (!newUser) throw new Error('Failed to create user')
178
+ return newUser`,
179
+ selectHistory: `const result = await db.request().input('uid', uid).query('SELECT TOP 1 * FROM count_history WHERE uid = @uid')
180
+ return result.recordset[0] ?? null`,
181
+ selectUser: `const result = await db.request().input('authSub', authSub).query('SELECT TOP 1 * FROM users WHERE auth_sub = @authSub')
182
+ return result.recordset[0] ?? null`
183
+ };
184
+ const mysqlSqlQueryOperations = {
185
+ insertHistory: `
186
+ const [result] = await db.query<ResultSetHeader>(
187
+ 'INSERT INTO count_history (count) VALUES (?)',
188
+ [count]
189
+ );
190
+ const insertId = result.insertId;
191
+ const [rows] = await db.query<CountHistoryRow[]>(
192
+ 'SELECT * FROM count_history WHERE uid = ? LIMIT 1',
193
+ [insertId]
194
+ );
195
+ if (!rows[0]) throw new Error('Could not retrieve the newly‑inserted history');
196
+ return rows[0];
197
+ `,
198
+ insertUser: `
199
+ const [result] = await db.query<ResultSetHeader>(
200
+ 'INSERT INTO users (auth_sub, metadata) VALUES (?, ?)',
201
+ [authSub, JSON.stringify(userIdentity)]
202
+ );
203
+ const insertId = result.insertId;
204
+ const [rows] = await db.query<UserRow[]>(
205
+ 'SELECT * FROM users WHERE uid = ? LIMIT 1',
206
+ [insertId]
207
+ );
208
+ if (!rows[0]) throw new Error('Failed to create user');
209
+ return rows[0];
210
+ `,
211
+ selectHistory: `
212
+ const [rows] = await db.query<CountHistoryRow[]>(
213
+ 'SELECT * FROM count_history WHERE uid = ? LIMIT 1',
214
+ [uid]
215
+ );
216
+ return rows[0] ?? null;
217
+ `,
218
+ selectUser: `
219
+ const [rows] = await db.query<UserRow[]>(
220
+ 'SELECT * FROM users WHERE auth_sub = ? LIMIT 1',
221
+ [authSub]
222
+ );
223
+ return rows[0] ?? null;
224
+ `
225
+ };
226
+ const mysqlHandlerTypes = {
227
+ CountHistoryRow: `RowDataPacket & {
228
+ uid: number;
229
+ count: number;
230
+ created_at: number;
231
+ }`,
232
+ UserRow: `RowDataPacket & {
233
+ uid: number;
234
+ auth_sub: string;
235
+ metadata: string;
236
+ }`
237
+ };
103
238
  const driverConfigurations = {
239
+ 'cockroachdb:sql:local': {
240
+ dbType: 'Pool',
241
+ importLines: `import { Pool } from 'pg'`,
242
+ queries: cockroachdbPoolQueryOperations
243
+ },
244
+ 'gel:sql:local': {
245
+ dbType: 'GelClient',
246
+ importLines: `import { GelClient } from 'gel'`,
247
+ queries: gelSqlQueryOperations
248
+ },
249
+ 'mariadb:sql:local': {
250
+ dbType: 'Pool',
251
+ importLines: `import { Pool } from 'mariadb'`,
252
+ queries: mariadbSqlQueryOperations
253
+ },
254
+ 'mongodb:native:local': {
255
+ dbType: 'Db',
256
+ importLines: `import { Db } from 'mongodb'`,
257
+ queries: mongodbQueryOperations
258
+ },
259
+ 'mssql:sql:local': {
260
+ dbType: 'ConnectionPool',
261
+ importLines: `import { ConnectionPool } from 'mssql'`,
262
+ queries: mssqlSqlQueryOperations
263
+ },
264
+ 'mysql:sql:local': {
265
+ dbType: 'Pool',
266
+ handlerTypes: mysqlHandlerTypes,
267
+ importLines: `import { Pool, ResultSetHeader, RowDataPacket } from 'mysql2/promise'`,
268
+ queries: mysqlSqlQueryOperations
269
+ },
104
270
  'postgresql:drizzle:local': {
105
271
  dbType: 'BunSQLDatabase<SchemaType>',
106
272
  importLines: `
@@ -127,6 +293,11 @@ import { schema, type SchemaType } from '../../../db/schema'`,
127
293
  importLines: `import { NeonQueryFunction } from '@neondatabase/serverless'`,
128
294
  queries: postgresSqlQueryOperations
129
295
  },
296
+ 'singlestore:sql:local': {
297
+ dbType: 'Connection',
298
+ importLines: `import { Connection } from '@singlestore/db-client'`,
299
+ queries: singlestoreSqlQueryOperations
300
+ },
130
301
  'sqlite:drizzle:local': {
131
302
  dbType: 'BunSQLiteDatabase<SchemaType>',
132
303
  importLines: `
@@ -1,8 +1,7 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
2
  type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'databaseHost' | 'orm' | 'databaseDirectory' | 'authProvider' | 'databaseEngine'> & {
3
3
  databaseDirectory: string;
4
- templatesDirectory: string;
5
4
  backendDirectory: string;
6
5
  };
7
- export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, templatesDirectory, authProvider, orm }: ScaffoldDatabaseProps) => Promise<void>;
6
+ export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authProvider, orm }: ScaffoldDatabaseProps) => Promise<void>;
8
7
  export {};
@@ -1,4 +1,4 @@
1
- import { copyFileSync, mkdirSync, writeFileSync } from 'fs';
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { $ } from 'bun';
4
4
  import { dim, yellow } from 'picocolors';
@@ -6,10 +6,11 @@ import { isDrizzleDialect } from '../../typeGuards';
6
6
  import { checkDockerInstalled } from '../../utils/checkDockerInstalled';
7
7
  import { checkSqliteInstalled } from '../../utils/checkSqliteInstalled';
8
8
  import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
9
+ import { generateDatabaseContainer } from './generateDBContainer';
9
10
  import { generateDBHandlers } from './generateDBHandlers';
10
11
  import { generateDrizzleSchema } from './generateDrizzleSchema';
11
12
  import { generateSqliteSchema } from './generateSqliteSchema';
12
- export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, templatesDirectory, authProvider, orm }) => {
13
+ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authProvider, orm }) => {
13
14
  const projectDatabaseDirectory = join(projectName, databaseDirectory);
14
15
  const handlerDirectory = join(backendDirectory, 'handlers');
15
16
  mkdirSync(projectDatabaseDirectory, { recursive: true });
@@ -51,8 +52,9 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
51
52
  console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
52
53
  return;
53
54
  }
54
- if (databaseEngine === 'postgresql') {
55
+ if (databaseEngine !== 'sqlite' && (orm === undefined || orm === 'none')) {
55
56
  await checkDockerInstalled();
56
- copyFileSync(join(templatesDirectory, 'db', 'docker-compose.db.yml'), join(projectDatabaseDirectory, 'docker-compose.db.yml'));
57
+ const dbContainer = generateDatabaseContainer(databaseEngine);
58
+ writeFileSync(join(projectDatabaseDirectory, 'docker-compose.db.yml'), dbContainer, 'utf-8');
57
59
  }
58
60
  };
@@ -1,69 +1,66 @@
1
- export const generateDBBlock = ({ databaseEngine, orm, databaseHost }) => {
2
- const isLocalPostgres = databaseEngine === 'postgresql' &&
3
- (!databaseHost || databaseHost === 'none');
4
- if (isLocalPostgres && orm === 'drizzle') {
5
- return `
6
- const sql = new SQL(getEnv("DATABASE_URL"))
7
- const db = drizzle(sql, { schema })
8
- `;
1
+ import { availableDrizzleDialects } from '../../data';
2
+ const connectionMap = {
3
+ cockroachdb: {
4
+ none: { connect: true, expr: 'new SQL(getEnv("DATABASE_URL"))' }
5
+ },
6
+ gel: {
7
+ none: { expr: 'gelClient({ url: getEnv("DATABASE_URL") })' }
8
+ },
9
+ mariadb: {
10
+ none: { expr: 'createPool(getEnv("DATABASE_URL"))' }
11
+ },
12
+ mongodb: {
13
+ none: { expr: 'new MongoClient(getEnv("DATABASE_URL"))' }
14
+ },
15
+ mssql: {
16
+ none: { expr: 'connect(getEnv("DATABASE_URL"))' }
17
+ },
18
+ mysql: {
19
+ none: { expr: 'createPool(getEnv("DATABASE_URL"))' },
20
+ planetscale: { expr: 'connect({ url: getEnv("DATABASE_URL") })' }
21
+ },
22
+ postgresql: {
23
+ neon: { expr: 'neon(getEnv("DATABASE_URL"))' },
24
+ none: { connect: true, expr: 'new SQL(getEnv("DATABASE_URL"))' }
25
+ },
26
+ singlestore: {
27
+ none: { expr: 'createClient({ url: getEnv("DATABASE_URL") })' }
28
+ },
29
+ sqlite: {
30
+ none: { expr: 'new Database("db/database.sqlite")' },
31
+ turso: { expr: 'createClient({ url: getEnv("DATABASE_URL") })' }
9
32
  }
10
- if (isLocalPostgres) {
11
- return `
12
- const db = new SQL(getEnv("DATABASE_URL"))
13
- await db.connect();
14
- `;
15
- }
16
- const isNeonPostgres = databaseEngine === 'postgresql' && databaseHost === 'neon';
17
- if (isNeonPostgres && orm === 'drizzle') {
18
- return `
19
- const sql = neon(getEnv("DATABASE_URL"))
20
- const db = drizzle(sql, { schema })
21
- `;
22
- }
23
- if (isNeonPostgres) {
24
- return `
25
- const db = neon(getEnv("DATABASE_URL"))
26
- `;
27
- }
28
- const isLocalSqlite = databaseEngine === 'sqlite' &&
29
- (!databaseHost || databaseHost === 'none');
30
- if (isLocalSqlite && orm === 'drizzle') {
31
- return `
32
- const sql = new Database("db/database.sqlite")
33
- const db = drizzle(sql, { schema })
34
- `;
35
- }
36
- if (isLocalSqlite) {
37
- return `
38
- const db = new Database("db/database.sqlite")
39
- `;
40
- }
41
- const isTursoSqlite = databaseEngine === 'sqlite' && databaseHost === 'turso';
42
- if (isTursoSqlite && orm === 'drizzle') {
43
- return `
44
- const sql = createClient({ url: getEnv("DATABASE_URL") })
45
- const db = drizzle(sql, { schema })
46
- `;
33
+ };
34
+ const remoteDrizzleInit = {
35
+ neon: 'neon(getEnv("DATABASE_URL"))',
36
+ planetscale: 'connect({ url: getEnv("DATABASE_URL") })',
37
+ turso: 'createClient({ url: getEnv("DATABASE_URL") })'
38
+ };
39
+ const drizzleDialectSet = new Set([...availableDrizzleDialects]);
40
+ export const generateDBBlock = ({ databaseEngine, orm, databaseHost }) => {
41
+ if (!databaseEngine || databaseEngine === 'none') {
42
+ throw new Error('Internal type error: Expected a valid database engine');
47
43
  }
48
- if (isTursoSqlite) {
44
+ const hostKey = databaseHost ?? 'none';
45
+ const engineGroup = connectionMap[databaseEngine];
46
+ if (!engineGroup)
47
+ return '';
48
+ if (orm !== 'drizzle') {
49
+ const hostCfg = engineGroup[hostKey];
50
+ if (!hostCfg)
51
+ return '';
49
52
  return `
50
- const db = createClient({ url: getEnv("DATABASE_URL") })
53
+ const db = ${hostCfg.expr}
54
+ ${hostCfg.connect ? 'await db.connect();\n' : ''}
51
55
  `;
52
56
  }
53
- if (orm !== 'drizzle') {
57
+ if (!drizzleDialectSet.has(databaseEngine))
58
+ return '';
59
+ const expr = engineGroup[hostKey]?.expr ?? remoteDrizzleInit[hostKey];
60
+ if (!expr)
54
61
  return '';
55
- }
56
- const clientInitMap = {
57
- neon: 'const sql = neon(getEnv("DATABASE_URL"))',
58
- planetscale: 'const sql = connect({ url: getEnv("DATABASE_URL") })',
59
- turso: 'const sql = createClient({ url: getEnv("DATABASE_URL") })'
60
- };
61
- if (databaseHost === 'none' || databaseHost === undefined) {
62
- return ``;
63
- }
64
- const initLine = clientInitMap[databaseHost];
65
62
  return `
66
- ${initLine}
63
+ const sql = ${expr}
67
64
  const db = drizzle(sql, { schema })
68
65
  `;
69
66
  };
@@ -66,6 +66,11 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
66
66
  `import { getEnv } from '@absolutejs/absolute'`
67
67
  ]
68
68
  : [`import { Database } from 'bun:sqlite'`]));
69
+ if (noOrm && databaseEngine === 'mysql') {
70
+ rawImports.push(...(isRemoteHost
71
+ ? connectorImports[databaseHost]
72
+ : [`import { createPool } from 'mysql2/promise'`]), `import { getEnv } from '@absolutejs/absolute'`);
73
+ }
69
74
  if (noOrm && databaseEngine === 'postgresql')
70
75
  rawImports.push(...(isRemoteHost
71
76
  ? connectorImports[databaseHost]
package/dist/index.js CHANGED
@@ -7,13 +7,13 @@ import { scaffold } from './scaffold';
7
7
  import { parseCommandLineOptions } from './utils/parseCommandLineOptions';
8
8
  import { getUserPackageManager } from './utils/t3-utils';
9
9
  const packageManager = getUserPackageManager();
10
- const { help, argumentConfiguration, latest, debug } = parseCommandLineOptions();
10
+ const { help, argumentConfiguration, latest, debug, envVariables } = parseCommandLineOptions();
11
11
  if (help === true) {
12
12
  console.log(helpMessage);
13
13
  exit(0);
14
14
  }
15
15
  const response = await prompt(argumentConfiguration);
16
- await scaffold({ latest, packageManager, response });
16
+ await scaffold({ envVariables, latest, packageManager, response });
17
17
  const debugMessage = debug !== false
18
18
  ? getDebugMessage({
19
19
  packageManager,
@@ -3,6 +3,7 @@ type ScaffoldProps = {
3
3
  response: CreateConfiguration;
4
4
  packageManager: PackageManager;
5
5
  latest: boolean;
6
+ envVariables: string[] | undefined;
6
7
  };
7
- export declare const scaffold: ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, orm, frontends, plugins, authProvider, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, packageManager }: ScaffoldProps) => Promise<void>;
8
+ export declare const scaffold: ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, orm, frontends, plugins, authProvider, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, envVariables, packageManager }: ScaffoldProps) => Promise<void>;
8
9
  export {};
package/dist/scaffold.js CHANGED
@@ -10,15 +10,18 @@ import { initalizeRoot } from './generators/configurations/initializeRoot';
10
10
  import { scaffoldDatabase } from './generators/db/scaffoldDatabase';
11
11
  import { generateServerFile } from './generators/project/generateServer';
12
12
  import { scaffoldFrontends } from './generators/project/scaffoldFrontends';
13
- export const scaffold = async ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, orm, frontends, plugins, authProvider, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, packageManager }) => {
13
+ export const scaffold = async ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, orm, frontends, plugins, authProvider, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, envVariables, packageManager }) => {
14
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
15
  const templatesDirectory = join(__dirname, '/templates');
16
16
  const { frontendDirectory, backendDirectory, projectAssetsDirectory } = initalizeRoot(projectName, templatesDirectory);
17
17
  copyFileSync(join(templatesDirectory, 'README.md'), join(projectName, 'README.md'));
18
18
  addConfigurationFiles({
19
19
  codeQualityTool,
20
+ databaseEngine,
21
+ envVariables,
20
22
  frontends,
21
23
  initializeGitNow,
24
+ orm,
22
25
  projectName,
23
26
  tailwind,
24
27
  templatesDirectory
@@ -57,8 +60,7 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
57
60
  databaseEngine,
58
61
  databaseHost,
59
62
  orm,
60
- projectName,
61
- templatesDirectory
63
+ projectName
62
64
  })));
63
65
  scaffoldFrontends({
64
66
  frontendDirectories,
@@ -2,6 +2,7 @@ import type { ArgumentConfiguration } from '../types';
2
2
  export declare const parseCommandLineOptions: () => {
3
3
  argumentConfiguration: ArgumentConfiguration;
4
4
  debug: boolean;
5
+ envVariables: string[] | undefined;
5
6
  help: boolean;
6
7
  latest: boolean;
7
8
  };
@@ -20,6 +20,7 @@ export const parseCommandLineOptions = () => {
20
20
  'db-host': { type: 'string' },
21
21
  debug: { default: false, short: 'd', type: 'boolean' },
22
22
  directory: { type: 'string' },
23
+ env: { multiple: true, type: 'string' },
23
24
  'eslint+prettier': { type: 'boolean' },
24
25
  git: { type: 'boolean' },
25
26
  help: { default: false, short: 'h', type: 'boolean' },
@@ -197,6 +198,23 @@ export const parseCommandLineOptions = () => {
197
198
  console.warn('Warning: Tailwind CSS input/output files are specified but Tailwind is disabled.');
198
199
  tailwind = undefined;
199
200
  }
201
+ const rawEnv = values.env ?? [];
202
+ const validEnv = [];
203
+ for (const entry of rawEnv) {
204
+ const idx = entry.indexOf('=');
205
+ const key = idx > 0 ? entry.slice(0, idx) : '';
206
+ const badFormat = idx <= 0;
207
+ const badKey = !badFormat && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
208
+ const errorMsg = (badFormat &&
209
+ `Invalid --env entry: "${entry}". Expected KEY=VALUE`) ||
210
+ (badKey && `Invalid env var name: "${key}"`) ||
211
+ undefined;
212
+ void (errorMsg && console.error(errorMsg));
213
+ if (errorMsg)
214
+ continue;
215
+ validEnv.push(entry);
216
+ }
217
+ values.env = validEnv.length ? validEnv : undefined;
200
218
  const argumentConfiguration = {
201
219
  assetsDirectory: values.assets,
202
220
  authProvider,
@@ -220,6 +238,7 @@ export const parseCommandLineOptions = () => {
220
238
  return {
221
239
  argumentConfiguration,
222
240
  debug: values.debug,
241
+ envVariables: values.env,
223
242
  help: values.help,
224
243
  latest: values.lts
225
244
  };
package/package.json CHANGED
@@ -47,5 +47,5 @@
47
47
  "typecheck": "bun run tsc --noEmit"
48
48
  },
49
49
  "type": "module",
50
- "version": "0.3.13"
50
+ "version": "0.3.14"
51
51
  }
@@ -1,20 +0,0 @@
1
- import { pgTable, timestamp, varchar } from 'drizzle-orm/pg-core';
2
-
3
- export const users = pgTable('users', {
4
- auth_sub: varchar('auth_sub', { length: 255 }).primaryKey(),
5
- created_at: timestamp('created_at').notNull().defaultNow(),
6
- email: varchar('email', { length: 255 }).notNull().unique(),
7
- family_name: varchar('family_name', { length: 255 }),
8
- given_name: varchar('given_name', { length: 255 }),
9
- picture: varchar('picture', { length: 255 })
10
- });
11
-
12
- export const schema = {
13
- users
14
- };
15
-
16
- // Type Definitions
17
- export type SchemaType = typeof schema;
18
-
19
- export type User = typeof users.$inferSelect;
20
- export type NewUser = typeof users.$inferInsert;
@@ -1,18 +0,0 @@
1
- datasource db {
2
- provider = "postgresql"
3
- url = env("DATABASE_URL")
4
- }
5
-
6
- generator client {
7
- provider = "prisma-client-js"
8
- output = "./src/generated/prisma-client"
9
- }
10
-
11
- model User {
12
- authSub String @id
13
- createdAt DateTime @default(now())
14
- email String @unique
15
- familyName String
16
- givenName String
17
- picture String
18
- }