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.
- package/dist/generators/configurations/addConfigurationFiles.d.ts +3 -2
- package/dist/generators/configurations/addConfigurationFiles.js +8 -1
- package/dist/generators/configurations/generateEnv.d.ts +6 -0
- package/dist/generators/configurations/generateEnv.js +25 -0
- package/dist/generators/configurations/generatePackageJson.js +21 -1
- package/dist/generators/db/generateDBContainer.d.ts +2 -0
- package/dist/generators/db/generateDBContainer.js +105 -0
- package/dist/generators/db/generateSqliteSchema.d.ts +2 -0
- package/dist/generators/db/generateSqliteSchema.js +11 -0
- package/dist/generators/db/handlerTemplates.d.ts +40 -0
- package/dist/generators/db/handlerTemplates.js +188 -17
- package/dist/generators/db/scaffoldDatabase.d.ts +1 -2
- package/dist/generators/db/scaffoldDatabase.js +19 -6
- package/dist/generators/project/generateDBBlock.js +56 -59
- package/dist/generators/project/generateImportsBlock.js +5 -0
- package/dist/index.js +2 -2
- package/dist/scaffold.d.ts +2 -1
- package/dist/scaffold.js +5 -3
- package/dist/utils/checkSqliteInstalled.d.ts +1 -0
- package/dist/utils/checkSqliteInstalled.js +133 -0
- package/dist/utils/parseCommandLineOptions.d.ts +1 -0
- package/dist/utils/parseCommandLineOptions.js +19 -0
- package/package.json +1 -1
- package/dist/templates/db/drizzle-schema.ts +0 -20
- package/dist/templates/db/schema.prisma +0 -18
|
@@ -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
|
|
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,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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
+
${queries.selectHistory}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const createCountHistory = async (db: ${dbType}, count: number) => {
|
|
33
|
-
|
|
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,
|
|
6
|
+
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authProvider, orm }: ScaffoldDatabaseProps) => Promise<void>;
|
|
8
7
|
export {};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 === '
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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 =
|
|
53
|
+
const db = ${hostCfg.expr}
|
|
54
|
+
${hostCfg.connect ? 'await db.connect();\n' : ''}
|
|
51
55
|
`;
|
|
52
56
|
}
|
|
53
|
-
if (
|
|
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
|
-
${
|
|
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,
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -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
|
+
};
|
|
@@ -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
|
@@ -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
|
-
}
|