create-absolutejs 0.3.12 → 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,31 @@ 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
+ }
103
+ if (databaseEngine === 'sqlite' &&
104
+ (!databaseHost || databaseHost === 'none')) {
105
+ scripts['db:sqlite'] = 'sqlite3 db/database.sqlite';
106
+ scripts['db:init'] = 'sqlite3 db/database.sqlite < db/init.sql';
107
+ }
88
108
  const packageJson = {
89
109
  dependencies,
90
110
  devDependencies,
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ import { AuthProvider } from '../../types';
2
+ export declare const generateSqliteSchema: (authProvider: AuthProvider) => "CREATE TABLE IF NOT EXISTS users (\n auth_sub TEXT PRIMARY KEY,\n created_at INTEGER NOT NULL DEFAULT ((julianday('now') - 2440587.5) * 86400000),\n metadata TEXT DEFAULT '{}'\n);" | "CREATE TABLE IF NOT EXISTS count_history (\n uid INTEGER PRIMARY KEY AUTOINCREMENT,\n count INTEGER NOT NULL,\n created_at INTEGER NOT NULL DEFAULT ((julianday('now') - 2440587.5) * 86400000)\n);";
@@ -0,0 +1,11 @@
1
+ export const generateSqliteSchema = (authProvider) => authProvider && authProvider !== 'none'
2
+ ? `CREATE TABLE IF NOT EXISTS users (
3
+ auth_sub TEXT PRIMARY KEY,
4
+ created_at INTEGER NOT NULL DEFAULT ((julianday('now') - 2440587.5) * 86400000),
5
+ metadata TEXT DEFAULT '{}'
6
+ );`
7
+ : `CREATE TABLE IF NOT EXISTS count_history (
8
+ uid INTEGER PRIMARY KEY AUTOINCREMENT,
9
+ count INTEGER NOT NULL,
10
+ created_at INTEGER NOT NULL DEFAULT ((julianday('now') - 2440587.5) * 86400000)
11
+ );`;
@@ -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,12 +1,16 @@
1
- import { copyFileSync, mkdirSync, writeFileSync } from 'fs';
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { $ } from 'bun';
3
4
  import { dim, yellow } from 'picocolors';
4
5
  import { isDrizzleDialect } from '../../typeGuards';
5
6
  import { checkDockerInstalled } from '../../utils/checkDockerInstalled';
7
+ import { checkSqliteInstalled } from '../../utils/checkSqliteInstalled';
6
8
  import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
9
+ import { generateDatabaseContainer } from './generateDBContainer';
7
10
  import { generateDBHandlers } from './generateDBHandlers';
8
11
  import { generateDrizzleSchema } from './generateDrizzleSchema';
9
- export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, templatesDirectory, authProvider, orm }) => {
12
+ import { generateSqliteSchema } from './generateSqliteSchema';
13
+ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authProvider, orm }) => {
10
14
  const projectDatabaseDirectory = join(projectName, databaseDirectory);
11
15
  const handlerDirectory = join(backendDirectory, 'handlers');
12
16
  mkdirSync(projectDatabaseDirectory, { recursive: true });
@@ -22,10 +26,13 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
22
26
  usesAuth
23
27
  });
24
28
  writeFileSync(join(handlerDirectory, handlerFileName), dbHandlers, 'utf-8');
25
- if (databaseEngine === 'postgresql' &&
26
- (databaseHost === undefined || databaseHost === 'none')) {
27
- await checkDockerInstalled();
28
- copyFileSync(join(templatesDirectory, 'db', 'docker-compose.db.yml'), join(projectDatabaseDirectory, 'docker-compose.db.yml'));
29
+ if (databaseEngine === 'sqlite') {
30
+ void ((orm === undefined || orm === 'none') &&
31
+ (await checkSqliteInstalled()));
32
+ const sqliteSchema = generateSqliteSchema(authProvider);
33
+ const sqliteSchemaFilePath = join(projectDatabaseDirectory, 'schema.sql');
34
+ writeFileSync(sqliteSchemaFilePath, sqliteSchema);
35
+ await $ `sqlite3 ${databaseDirectory}/database.sqlite ".read ${join(databaseDirectory, 'schema.sql')}"`.cwd(projectName);
29
36
  }
30
37
  if (orm === 'drizzle') {
31
38
  if (!isDrizzleDialect(databaseEngine)) {
@@ -43,5 +50,11 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
43
50
  }
44
51
  if (orm === 'prisma') {
45
52
  console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
53
+ return;
54
+ }
55
+ if (databaseEngine !== 'sqlite' && (orm === undefined || orm === 'none')) {
56
+ await checkDockerInstalled();
57
+ const dbContainer = generateDatabaseContainer(databaseEngine);
58
+ writeFileSync(join(projectDatabaseDirectory, 'docker-compose.db.yml'), dbContainer, 'utf-8');
46
59
  }
47
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("database.sqlite")
33
- const db = drizzle(sql, { schema })
34
- `;
35
- }
36
- if (isLocalSqlite) {
37
- return `
38
- const db = new Database("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,
@@ -0,0 +1 @@
1
+ export declare const checkSqliteInstalled: () => Promise<void>;
@@ -0,0 +1,133 @@
1
+ import os from 'os';
2
+ import { env, platform } from 'process';
3
+ import { confirm, spinner } from '@clack/prompts';
4
+ import { $ } from 'bun';
5
+ import { dim, yellow } from 'picocolors';
6
+ const SQLITE_URL = 'https://sqlite.org/download.html';
7
+ const isWSL = () => env.WSL_DISTRO_NAME !== undefined || /microsoft/i.test(os.release());
8
+ let hostEnv;
9
+ if (platform === 'win32') {
10
+ hostEnv = 'windows';
11
+ }
12
+ else if (isWSL()) {
13
+ hostEnv = 'wsl';
14
+ }
15
+ else {
16
+ hostEnv = 'linux';
17
+ }
18
+ const commandExists = async (cmd) => (platform === 'win32'
19
+ ? await $ `where ${cmd}`.quiet().nothrow()
20
+ : await $ `command -v ${cmd}`.quiet().nothrow()).exitCode === 0;
21
+ const ensureSudo = async () => {
22
+ if ((await $ `sudo -n true`.nothrow()).exitCode !== 0) {
23
+ console.log(`${dim('│')}\n${yellow('▲')} sudo password required`);
24
+ await $ `sudo -v`;
25
+ }
26
+ };
27
+ const hasSqlite = async () => (await $ `sqlite3 --version`.quiet().nothrow()).exitCode === 0;
28
+ const aptInstallSqlite = async () => {
29
+ await ensureSudo();
30
+ const spin = spinner();
31
+ spin.start('Installing sqlite3 with apt');
32
+ await $ `sudo DEBIAN_FRONTEND=noninteractive apt-get update`.quiet();
33
+ const res = await $ `sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sqlite3`
34
+ .quiet()
35
+ .nothrow();
36
+ spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'apt install failed');
37
+ return res.exitCode === 0;
38
+ };
39
+ const dnfInstallSqlite = async () => {
40
+ const spin = spinner();
41
+ spin.start('Installing sqlite3 with dnf');
42
+ const res = await $ `sudo dnf install -y sqlite`.quiet().nothrow();
43
+ spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'dnf install failed');
44
+ return res.exitCode === 0;
45
+ };
46
+ const pacmanInstallSqlite = async () => {
47
+ const spin = spinner();
48
+ spin.start('Installing sqlite3 with pacman');
49
+ const res = await $ `sudo pacman -S --noconfirm sqlite`.quiet().nothrow();
50
+ spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'pacman install failed');
51
+ return res.exitCode === 0;
52
+ };
53
+ const apkInstallSqlite = async () => {
54
+ const spin = spinner();
55
+ spin.start('Installing sqlite3 with apk');
56
+ const res = await $ `sudo apk add sqlite`.quiet().nothrow();
57
+ spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'apk install failed');
58
+ return res.exitCode === 0;
59
+ };
60
+ const installWindowsSqlite = async () => {
61
+ if (await commandExists('winget')) {
62
+ const spin = spinner();
63
+ spin.start('Installing sqlite3 with winget');
64
+ const res = await $ `winget install -e --id SQLite.SQLite`
65
+ .quiet()
66
+ .nothrow();
67
+ spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'winget install failed');
68
+ return res.exitCode === 0;
69
+ }
70
+ if (await commandExists('choco')) {
71
+ const spin = spinner();
72
+ spin.start('Installing sqlite3 with Chocolatey');
73
+ const res = await $ `choco install sqlite -y`.quiet().nothrow();
74
+ spin.stop(res.exitCode === 0
75
+ ? 'sqlite3 installed'
76
+ : 'Chocolatey install failed');
77
+ return res.exitCode === 0;
78
+ }
79
+ console.log(`Automatic Windows install failed. Get sqlite3 from ${SQLITE_URL}`);
80
+ return false;
81
+ };
82
+ const installWSLSqlite = async () => {
83
+ if (await hasSqlite())
84
+ return true;
85
+ if (await aptInstallSqlite())
86
+ return true;
87
+ return false;
88
+ };
89
+ const installLinuxSqlite = async () => {
90
+ if (await commandExists('apt-get')) {
91
+ if (await aptInstallSqlite())
92
+ return true;
93
+ }
94
+ if (await commandExists('dnf')) {
95
+ if (await dnfInstallSqlite())
96
+ return true;
97
+ }
98
+ if (await commandExists('pacman')) {
99
+ if (await pacmanInstallSqlite())
100
+ return true;
101
+ }
102
+ if (await commandExists('apk')) {
103
+ if (await apkInstallSqlite())
104
+ return true;
105
+ }
106
+ console.log(`Automatic Linux install failed. See ${SQLITE_URL}`);
107
+ return false;
108
+ };
109
+ export const checkSqliteInstalled = async () => {
110
+ if (await hasSqlite())
111
+ return;
112
+ const proceed = await confirm({
113
+ initialValue: true,
114
+ message: 'sqlite3 CLI is required for local SQLite databases. Install now?'
115
+ });
116
+ if (!proceed)
117
+ return;
118
+ switch (hostEnv) {
119
+ case 'windows':
120
+ if (await installWindowsSqlite())
121
+ return;
122
+ break;
123
+ case 'wsl':
124
+ if (await installWSLSqlite())
125
+ return;
126
+ break;
127
+ case 'linux':
128
+ if (await installLinuxSqlite())
129
+ return;
130
+ break;
131
+ }
132
+ console.log(`Couldn't install sqlite3 automatically. Download it from ${SQLITE_URL}`);
133
+ };
@@ -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.12"
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
- }