create-absolutejs 0.3.9 → 0.3.10
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/README.md +15 -15
- package/dist/data.d.ts +4 -2
- package/dist/data.js +23 -8
- package/dist/generators/configurations/generateDrizzleConfig.d.ts +2 -1
- package/dist/generators/configurations/generateDrizzleConfig.js +16 -6
- package/dist/generators/configurations/generatePackageJson.d.ts +2 -2
- package/dist/generators/configurations/generatePackageJson.js +39 -12
- package/dist/generators/db/generateDBHandlers.d.ts +6 -0
- package/dist/generators/db/generateDBHandlers.js +11 -0
- package/dist/generators/db/generateDrizzleSchema.d.ts +8 -0
- package/dist/generators/db/generateDrizzleSchema.js +122 -0
- package/dist/generators/db/handlerTemplates.d.ts +52 -0
- package/dist/generators/db/handlerTemplates.js +168 -0
- package/dist/generators/db/scaffoldDatabase.d.ts +5 -6
- package/dist/generators/db/scaffoldDatabase.js +37 -6
- package/dist/generators/html/generateHTMLPage.d.ts +2 -2
- package/dist/generators/html/generateHTMLPage.js +7 -4
- package/dist/generators/html/scaffoldHTML.js +1 -1
- package/dist/generators/project/collectDependencies.d.ts +9 -0
- package/dist/generators/project/collectDependencies.js +16 -0
- package/dist/generators/project/computeFlags.d.ts +9 -0
- package/dist/generators/project/computeFlags.js +7 -0
- package/dist/generators/project/generateBuildBlock.d.ts +9 -0
- package/dist/generators/project/generateBuildBlock.js +13 -0
- package/dist/generators/project/generateDBBlock.d.ts +4 -0
- package/dist/generators/project/generateDBBlock.js +69 -0
- package/dist/generators/project/generateImportsBlock.d.ts +13 -0
- package/dist/generators/project/generateImportsBlock.js +124 -0
- package/dist/generators/project/generateRoutesBlock.d.ts +10 -0
- package/dist/generators/project/generateRoutesBlock.js +74 -0
- package/dist/generators/project/generateServer.d.ts +3 -4
- package/dist/generators/project/generateServer.js +46 -170
- package/dist/generators/project/generateUseBlock.d.ts +6 -0
- package/dist/generators/project/generateUseBlock.js +28 -0
- package/dist/messages.js +13 -13
- package/dist/prompt.js +6 -7
- package/dist/questions/databaseEngine.d.ts +1 -1
- package/dist/questions/databaseEngine.js +2 -1
- package/dist/questions/databaseHost.d.ts +1 -1
- package/dist/questions/databaseHost.js +10 -30
- package/dist/questions/orm.d.ts +2 -1
- package/dist/questions/orm.js +13 -7
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.js +16 -8
- package/dist/templates/db/docker-compose.db.yml +15 -0
- package/dist/typeGuards.d.ts +3 -1
- package/dist/typeGuards.js +5 -19
- package/dist/types.d.ts +3 -1
- package/dist/utils/checkDockerInstalled.d.ts +2 -0
- package/dist/utils/checkDockerInstalled.js +179 -0
- package/dist/utils/parseCommandLineOptions.js +47 -15
- package/package.json +2 -2
- package/dist/templates/html/pages/HTMLExample.html +0 -66
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const buildSqlAuthTemplate = ({ importLines, dbType, queries }) => `
|
|
2
|
+
import { isValidProviderOption, providers } from 'citra'
|
|
3
|
+
${importLines}
|
|
4
|
+
|
|
5
|
+
type UserHandlerProps = {
|
|
6
|
+
authProvider: string
|
|
7
|
+
db: ${dbType}
|
|
8
|
+
userIdentity: Record<string, unknown>
|
|
9
|
+
}
|
|
10
|
+
|
|
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}
|
|
16
|
+
}
|
|
17
|
+
|
|
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}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
const buildSqlCountTemplate = ({ importLines, dbType, queries }) => `
|
|
26
|
+
${importLines}
|
|
27
|
+
|
|
28
|
+
export const getCountHistory = async (db: ${dbType}, uid: number) => {
|
|
29
|
+
${queries.selectHistory}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createCountHistory = async (db: ${dbType}, count: number) => {
|
|
33
|
+
${queries.insertHistory}
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
const drizzleQueryOperations = {
|
|
37
|
+
insertHistory: `const [newHistory] = await db.insert(schema.countHistory).values({ count }).returning()
|
|
38
|
+
return newHistory`,
|
|
39
|
+
insertUser: `const [newUser] = await db.insert(schema.users).values({ auth_sub: authSub, metadata: userIdentity }).returning()
|
|
40
|
+
if (!newUser) throw new Error('Failed to create user')
|
|
41
|
+
return newUser`,
|
|
42
|
+
selectHistory: `const [history] = await db.select().from(schema.countHistory).where(eq(schema.countHistory.uid, uid)).execute()
|
|
43
|
+
return history`,
|
|
44
|
+
selectUser: `const [user] = await db.select().from(schema.users).where(eq(schema.users.auth_sub, authSub)).execute()
|
|
45
|
+
return user`
|
|
46
|
+
};
|
|
47
|
+
const libsqlQueryOperations = {
|
|
48
|
+
insertHistory: `const { rows } = await db.execute({ sql: 'INSERT INTO count_history (count) VALUES (?) RETURNING *', args: [count] })
|
|
49
|
+
return rows[0]`,
|
|
50
|
+
insertUser: `const { rows } = await db.execute({ sql: 'INSERT INTO users (auth_sub, metadata) VALUES (?, ?) RETURNING *', args: [authSub, JSON.stringify(userIdentity)] })
|
|
51
|
+
const newUser = rows[0]
|
|
52
|
+
if (!newUser) throw new Error('Failed to create user')
|
|
53
|
+
return newUser`,
|
|
54
|
+
selectHistory: `const { rows } = await db.execute({ sql: 'SELECT * FROM count_history WHERE uid = ? LIMIT 1', args: [uid] })
|
|
55
|
+
return rows[0] ?? null`,
|
|
56
|
+
selectUser: `const { rows } = await db.execute({ sql: 'SELECT * FROM users WHERE auth_sub = ? LIMIT 1', args: [authSub] })
|
|
57
|
+
return rows[0] ?? null`
|
|
58
|
+
};
|
|
59
|
+
const bunSqliteQueryOperations = {
|
|
60
|
+
insertHistory: `db.run('INSERT INTO count_history (count) VALUES (?)', [count])
|
|
61
|
+
const statement = db.query('SELECT * FROM count_history ORDER BY rowid DESC LIMIT 1')
|
|
62
|
+
const [newHistory] = statement.all()
|
|
63
|
+
return newHistory`,
|
|
64
|
+
insertUser: `db.run('INSERT INTO users (auth_sub, metadata) VALUES (?, ?)', [authSub, JSON.stringify(userIdentity)])
|
|
65
|
+
const statement = db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1')
|
|
66
|
+
const [newUser] = statement.all(authSub)
|
|
67
|
+
if (!newUser) throw new Error('Failed to create user')
|
|
68
|
+
return newUser`,
|
|
69
|
+
selectHistory: `const statement = db.query('SELECT * FROM count_history WHERE uid = ? LIMIT 1')
|
|
70
|
+
const [history] = statement.all(uid)
|
|
71
|
+
return history ?? null`,
|
|
72
|
+
selectUser: `const statement = db.query('SELECT * FROM users WHERE auth_sub = ? LIMIT 1')
|
|
73
|
+
const [user] = statement.all(authSub)
|
|
74
|
+
return user ?? null`
|
|
75
|
+
};
|
|
76
|
+
const postgresSqlQueryOperations = {
|
|
77
|
+
insertHistory: `const [newHistory] = await db\`
|
|
78
|
+
INSERT INTO count_history (count)
|
|
79
|
+
VALUES (\${count})
|
|
80
|
+
RETURNING *
|
|
81
|
+
\`
|
|
82
|
+
return newHistory`,
|
|
83
|
+
insertUser: `const [newUser] = await db\`
|
|
84
|
+
INSERT INTO users (auth_sub, metadata)
|
|
85
|
+
VALUES (\${authSub}, \${userIdentity})
|
|
86
|
+
RETURNING *
|
|
87
|
+
\`
|
|
88
|
+
if (!newUser) throw new Error('Failed to create user')
|
|
89
|
+
return newUser`,
|
|
90
|
+
selectHistory: `const [history] = await db\`
|
|
91
|
+
SELECT * FROM count_history
|
|
92
|
+
WHERE uid = \${uid}
|
|
93
|
+
LIMIT 1
|
|
94
|
+
\`
|
|
95
|
+
return history ?? null`,
|
|
96
|
+
selectUser: `const [user] = await db\`
|
|
97
|
+
SELECT * FROM users
|
|
98
|
+
WHERE auth_sub = \${authSub}
|
|
99
|
+
LIMIT 1
|
|
100
|
+
\`
|
|
101
|
+
return user ?? null`
|
|
102
|
+
};
|
|
103
|
+
const driverConfigurations = {
|
|
104
|
+
'postgresql:drizzle:local': {
|
|
105
|
+
dbType: 'BunSQLDatabase<SchemaType>',
|
|
106
|
+
importLines: `
|
|
107
|
+
import { eq } from 'drizzle-orm'
|
|
108
|
+
import { BunSQLDatabase } from 'drizzle-orm/bun-sql'
|
|
109
|
+
import { schema, type SchemaType } from '../../../db/schema'`,
|
|
110
|
+
queries: drizzleQueryOperations
|
|
111
|
+
},
|
|
112
|
+
'postgresql:drizzle:neon': {
|
|
113
|
+
dbType: 'NeonHttpDatabase<SchemaType>',
|
|
114
|
+
importLines: `
|
|
115
|
+
import { eq } from 'drizzle-orm'
|
|
116
|
+
import { NeonHttpDatabase } from 'drizzle-orm/neon-http'
|
|
117
|
+
import { schema, type SchemaType } from '../../../db/schema'`,
|
|
118
|
+
queries: drizzleQueryOperations
|
|
119
|
+
},
|
|
120
|
+
'postgresql:sql:local': {
|
|
121
|
+
dbType: 'SQL',
|
|
122
|
+
importLines: `import { SQL } from 'bun'`,
|
|
123
|
+
queries: postgresSqlQueryOperations
|
|
124
|
+
},
|
|
125
|
+
'postgresql:sql:neon': {
|
|
126
|
+
dbType: 'NeonQueryFunction<false, false>',
|
|
127
|
+
importLines: `import { NeonQueryFunction } from '@neondatabase/serverless'`,
|
|
128
|
+
queries: postgresSqlQueryOperations
|
|
129
|
+
},
|
|
130
|
+
'sqlite:drizzle:local': {
|
|
131
|
+
dbType: 'BunSQLiteDatabase<SchemaType>',
|
|
132
|
+
importLines: `
|
|
133
|
+
import { eq } from 'drizzle-orm'
|
|
134
|
+
import { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'
|
|
135
|
+
import { schema, type SchemaType } from '../../../db/schema'`,
|
|
136
|
+
queries: drizzleQueryOperations
|
|
137
|
+
},
|
|
138
|
+
'sqlite:drizzle:turso': {
|
|
139
|
+
dbType: 'LibSQLDatabase<SchemaType>',
|
|
140
|
+
importLines: `
|
|
141
|
+
import { eq } from 'drizzle-orm'
|
|
142
|
+
import { LibSQLDatabase } from 'drizzle-orm/libsql'
|
|
143
|
+
import { schema, type SchemaType } from '../../../db/schema'`,
|
|
144
|
+
queries: drizzleQueryOperations
|
|
145
|
+
},
|
|
146
|
+
'sqlite:sql:local': {
|
|
147
|
+
dbType: 'Database',
|
|
148
|
+
importLines: `import { Database } from 'bun:sqlite'`,
|
|
149
|
+
queries: bunSqliteQueryOperations
|
|
150
|
+
},
|
|
151
|
+
'sqlite:sql:turso': {
|
|
152
|
+
dbType: 'Client',
|
|
153
|
+
importLines: `import { Client } from '@libsql/client'`,
|
|
154
|
+
queries: libsqlQueryOperations
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
export const getAuthTemplate = (key) => {
|
|
158
|
+
const configuration = driverConfigurations[key];
|
|
159
|
+
if (!configuration)
|
|
160
|
+
throw new Error(`Unsupported driver configuration: ${key}`);
|
|
161
|
+
return buildSqlAuthTemplate(configuration);
|
|
162
|
+
};
|
|
163
|
+
export const getCountTemplate = (key) => {
|
|
164
|
+
const configuration = driverConfigurations[key];
|
|
165
|
+
if (!configuration)
|
|
166
|
+
throw new Error(`Unsupported driver configuration: ${key}`);
|
|
167
|
+
return buildSqlCountTemplate(configuration);
|
|
168
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
type ScaffoldDatabaseProps = {
|
|
3
|
-
projectName: string;
|
|
4
|
-
orm: ORM;
|
|
5
|
-
databaseEngine: DatabaseEngine;
|
|
1
|
+
import type { CreateConfiguration } from '../../types';
|
|
2
|
+
type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'databaseHost' | 'orm' | 'databaseDirectory' | 'authProvider' | 'databaseEngine'> & {
|
|
6
3
|
databaseDirectory: string;
|
|
4
|
+
templatesDirectory: string;
|
|
5
|
+
backendDirectory: string;
|
|
7
6
|
};
|
|
8
|
-
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseDirectory, orm }: ScaffoldDatabaseProps) => void
|
|
7
|
+
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, templatesDirectory, authProvider, orm }: ScaffoldDatabaseProps) => Promise<void>;
|
|
9
8
|
export {};
|
|
@@ -1,14 +1,45 @@
|
|
|
1
|
-
import { mkdirSync } from 'fs';
|
|
1
|
+
import { copyFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { dim, yellow } from 'picocolors';
|
|
4
|
+
import { isDrizzleDialect } from '../../typeGuards';
|
|
5
|
+
import { checkDockerInstalled } from '../../utils/checkDockerInstalled';
|
|
4
6
|
import { createDrizzleConfig } from '../configurations/generateDrizzleConfig';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { generateDBHandlers } from './generateDBHandlers';
|
|
8
|
+
import { generateDrizzleSchema } from './generateDrizzleSchema';
|
|
9
|
+
export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, templatesDirectory, authProvider, orm }) => {
|
|
10
|
+
const projectDatabaseDirectory = join(projectName, databaseDirectory);
|
|
11
|
+
const handlerDirectory = join(backendDirectory, 'handlers');
|
|
12
|
+
mkdirSync(projectDatabaseDirectory, { recursive: true });
|
|
13
|
+
mkdirSync(handlerDirectory, { recursive: true });
|
|
14
|
+
const usesAuth = authProvider !== undefined && authProvider !== 'none';
|
|
15
|
+
const handlerFileName = usesAuth
|
|
16
|
+
? 'userHandlers.ts'
|
|
17
|
+
: 'countHistoryHandlers.ts';
|
|
18
|
+
const dbHandlers = generateDBHandlers({
|
|
19
|
+
databaseEngine,
|
|
20
|
+
databaseHost,
|
|
21
|
+
orm,
|
|
22
|
+
usesAuth
|
|
23
|
+
});
|
|
24
|
+
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'));
|
|
9
29
|
}
|
|
10
30
|
if (orm === 'drizzle') {
|
|
11
|
-
|
|
31
|
+
if (!isDrizzleDialect(databaseEngine)) {
|
|
32
|
+
throw new Error('Internal type error: Expected a Drizzle dialect');
|
|
33
|
+
}
|
|
34
|
+
const drizzleSchema = generateDrizzleSchema({
|
|
35
|
+
authProvider,
|
|
36
|
+
databaseEngine,
|
|
37
|
+
databaseHost
|
|
38
|
+
});
|
|
39
|
+
const schemaFilePath = join(projectDatabaseDirectory, 'schema.ts');
|
|
40
|
+
writeFileSync(schemaFilePath, drizzleSchema);
|
|
41
|
+
createDrizzleConfig({ databaseDirectory, databaseEngine, projectName });
|
|
42
|
+
return;
|
|
12
43
|
}
|
|
13
44
|
if (orm === 'prisma') {
|
|
14
45
|
console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Frontend } from '../../types';
|
|
2
|
-
export declare const generateHTMLPage: (frontends: Frontend[]) => string;
|
|
1
|
+
import { CreateConfiguration, Frontend } from '../../types';
|
|
2
|
+
export declare const generateHTMLPage: (frontends: Frontend[], useHTMLScripts: CreateConfiguration["useHTMLScripts"]) => string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { formatNavLink } from '../../utils/formatNavLink';
|
|
2
|
-
export const generateHTMLPage = (frontends) => {
|
|
2
|
+
export const generateHTMLPage = (frontends, useHTMLScripts) => {
|
|
3
3
|
const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
|
|
4
|
+
const initialCount = useHTMLScripts ? '0' : 'disabled';
|
|
5
|
+
const scriptTagBlock = useHTMLScripts
|
|
6
|
+
? ` <script src="../scripts/typescript-example.ts"></script>\n`
|
|
7
|
+
: '';
|
|
4
8
|
return `<!doctype html>
|
|
5
9
|
<html>
|
|
6
10
|
<head>
|
|
@@ -44,7 +48,7 @@ export const generateHTMLPage = (frontends) => {
|
|
|
44
48
|
</nav>
|
|
45
49
|
<h1>AbsoluteJS + HTML</h1>
|
|
46
50
|
<button id="counter-button">
|
|
47
|
-
count is <span id="counter"
|
|
51
|
+
count is <span id="counter">${initialCount}</span>
|
|
48
52
|
</button>
|
|
49
53
|
<p>
|
|
50
54
|
Edit <code>example/html/pages/HtmlExample.html</code> save and
|
|
@@ -59,8 +63,7 @@ export const generateHTMLPage = (frontends) => {
|
|
|
59
63
|
Click on the AbsoluteJS and HTML logos to learn more.
|
|
60
64
|
</p>
|
|
61
65
|
</main>
|
|
62
|
-
|
|
63
|
-
</body>
|
|
66
|
+
${scriptTagBlock} </body>
|
|
64
67
|
</html>
|
|
65
68
|
`;
|
|
66
69
|
};
|
|
@@ -4,7 +4,7 @@ import { generateMarkupCSS } from '../project/generateMarkupCSS';
|
|
|
4
4
|
import { generateHTMLPage } from './generateHTMLPage';
|
|
5
5
|
export const scaffoldHTML = ({ isSingleFrontend, targetDirectory, frontends, useHTMLScripts, templatesDirectory, projectAssetsDirectory }) => {
|
|
6
6
|
copyFileSync(join(templatesDirectory, 'assets', 'svg', 'HTML5_Badge.svg'), join(projectAssetsDirectory, 'svg', 'HTML5_Badge.svg'));
|
|
7
|
-
const htmlPage = generateHTMLPage(frontends);
|
|
7
|
+
const htmlPage = generateHTMLPage(frontends, useHTMLScripts);
|
|
8
8
|
const pagesDirectory = join(targetDirectory, 'pages');
|
|
9
9
|
mkdirSync(pagesDirectory, { recursive: true });
|
|
10
10
|
const htmlFilePath = join(pagesDirectory, 'HTMLExample.html');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CreateConfiguration } from '../../types';
|
|
2
|
+
import type { FrameworkFlags } from './computeFlags';
|
|
3
|
+
type CollectDependenciesProps = {
|
|
4
|
+
plugins: string[];
|
|
5
|
+
authProvider: CreateConfiguration['authProvider'];
|
|
6
|
+
flags: FrameworkFlags;
|
|
7
|
+
};
|
|
8
|
+
export declare const collectDependencies: ({ plugins, authProvider, flags }: CollectDependenciesProps) => import("../../types").AvailableDependency[];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defaultDependencies, defaultPlugins, absoluteAuthPlugin, scopedStatePlugin, availablePlugins } from '../../data';
|
|
2
|
+
export const collectDependencies = ({ plugins, authProvider, flags }) => {
|
|
3
|
+
const customSelections = availablePlugins.filter((plugin) => plugins.includes(plugin.value));
|
|
4
|
+
const authPlugins = authProvider === 'absoluteAuth' ? [absoluteAuthPlugin] : [];
|
|
5
|
+
const htmxPlugins = flags.requiresHtmx ? [scopedStatePlugin] : [];
|
|
6
|
+
const allDeps = [
|
|
7
|
+
...defaultDependencies,
|
|
8
|
+
...defaultPlugins,
|
|
9
|
+
...customSelections,
|
|
10
|
+
...authPlugins,
|
|
11
|
+
...htmxPlugins
|
|
12
|
+
];
|
|
13
|
+
const uniqueDeps = Array.from(new Map(allDeps.map((dependency) => [dependency.value, dependency])).values());
|
|
14
|
+
uniqueDeps.sort((firstDep, secondDep) => firstDep.value.localeCompare(secondDep.value));
|
|
15
|
+
return uniqueDeps;
|
|
16
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FrontendDirectories } from '../../types';
|
|
2
|
+
export type FrameworkFlags = ReturnType<typeof computeFlags>;
|
|
3
|
+
export declare const computeFlags: (dirs: FrontendDirectories) => {
|
|
4
|
+
requiresHtml: boolean;
|
|
5
|
+
requiresHtmx: boolean;
|
|
6
|
+
requiresReact: boolean;
|
|
7
|
+
requiresSvelte: boolean;
|
|
8
|
+
requiresVue: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CreateConfiguration, FrontendDirectories } from '../../types';
|
|
2
|
+
type GenerateBuildBlockProps = {
|
|
3
|
+
assetsDirectory: string;
|
|
4
|
+
buildDirectory: string;
|
|
5
|
+
frontendDirectories: FrontendDirectories;
|
|
6
|
+
tailwind: CreateConfiguration['tailwind'];
|
|
7
|
+
};
|
|
8
|
+
export declare const generateBuildBlock: ({ assetsDirectory, buildDirectory, frontendDirectories, tailwind }: GenerateBuildBlockProps) => string;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const generateBuildBlock = ({ assetsDirectory, buildDirectory, frontendDirectories, tailwind }) => {
|
|
2
|
+
const opts = [
|
|
3
|
+
`assetsDirectory: '${assetsDirectory}'`,
|
|
4
|
+
`buildDirectory: '${buildDirectory}'`,
|
|
5
|
+
...Object.entries(frontendDirectories).map(([f, dir]) => `${f}Directory: 'src/frontend${dir ? `/${dir}` : ''}'`),
|
|
6
|
+
tailwind ? `tailwind: ${JSON.stringify(tailwind)}` : ''
|
|
7
|
+
]
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.join(',\n ');
|
|
10
|
+
const frameworks = ['react', 'svelte', 'vue'];
|
|
11
|
+
const nonFrameworkOnly = frameworks.every((f) => frontendDirectories[f] === undefined);
|
|
12
|
+
return `${nonFrameworkOnly ? '' : 'const manifest = '}await build({\n ${opts}\n});`;
|
|
13
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CreateConfiguration } from '../../types';
|
|
2
|
+
type GenerateDBBlockProps = Pick<CreateConfiguration, 'databaseEngine' | 'orm' | 'databaseHost'>;
|
|
3
|
+
export declare const generateDBBlock: ({ databaseEngine, orm, databaseHost }: GenerateDBBlockProps) => string;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
`;
|
|
9
|
+
}
|
|
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
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
if (isTursoSqlite) {
|
|
49
|
+
return `
|
|
50
|
+
const db = createClient({ url: getEnv("DATABASE_URL") })
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
if (orm !== 'drizzle') {
|
|
54
|
+
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
|
+
return `
|
|
66
|
+
${initLine}
|
|
67
|
+
const db = drizzle(sql, { schema })
|
|
68
|
+
`;
|
|
69
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AvailableDependency, CreateConfiguration } from '../../types';
|
|
2
|
+
import type { FrameworkFlags } from './computeFlags';
|
|
3
|
+
type GenerateImportsBlockProps = {
|
|
4
|
+
backendDirectory: string;
|
|
5
|
+
deps: AvailableDependency[];
|
|
6
|
+
flags: FrameworkFlags;
|
|
7
|
+
orm: CreateConfiguration['orm'];
|
|
8
|
+
authProvider: CreateConfiguration['authProvider'];
|
|
9
|
+
databaseEngine: CreateConfiguration['databaseEngine'];
|
|
10
|
+
databaseHost: CreateConfiguration['databaseHost'];
|
|
11
|
+
};
|
|
12
|
+
export declare const generateImportsBlock: ({ backendDirectory, deps, flags, orm, authProvider, databaseEngine, databaseHost }: GenerateImportsBlockProps) => string;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authProvider, databaseEngine, databaseHost }) => {
|
|
4
|
+
const rawImports = [];
|
|
5
|
+
const pushHandler = (cond, name) => cond &&
|
|
6
|
+
rawImports.push(`import { ${name} } from '@absolutejs/absolute'`);
|
|
7
|
+
pushHandler(flags.requiresHtml, 'handleHTMLPageRequest');
|
|
8
|
+
pushHandler(flags.requiresReact, 'handleReactPageRequest');
|
|
9
|
+
pushHandler(flags.requiresSvelte, 'handleSveltePageRequest');
|
|
10
|
+
pushHandler(flags.requiresVue, 'handleVuePageRequest');
|
|
11
|
+
pushHandler(flags.requiresVue, 'generateHeadElement');
|
|
12
|
+
pushHandler(flags.requiresHtmx, 'handleHTMXPageRequest');
|
|
13
|
+
const nonFrameworkOnly = (flags.requiresHtml || flags.requiresHtmx) &&
|
|
14
|
+
!flags.requiresReact &&
|
|
15
|
+
!flags.requiresSvelte &&
|
|
16
|
+
!flags.requiresVue;
|
|
17
|
+
for (const dependency of deps) {
|
|
18
|
+
const importsList = dependency.imports ?? [];
|
|
19
|
+
const relevantImports = nonFrameworkOnly
|
|
20
|
+
? importsList.filter((imp) => imp.packageName !== 'asset')
|
|
21
|
+
: importsList;
|
|
22
|
+
if (relevantImports.length === 0)
|
|
23
|
+
continue;
|
|
24
|
+
rawImports.push(`import { ${relevantImports
|
|
25
|
+
.map((imp) => imp.packageName)
|
|
26
|
+
.sort()
|
|
27
|
+
.join(', ')} } from '${dependency.value}'`);
|
|
28
|
+
}
|
|
29
|
+
if (flags.requiresReact)
|
|
30
|
+
rawImports.push(`import { ReactExample } from '../frontend/react/pages/ReactExample'`);
|
|
31
|
+
if (flags.requiresSvelte)
|
|
32
|
+
rawImports.push(`import SvelteExample from '../frontend/svelte/pages/SvelteExample.svelte'`);
|
|
33
|
+
if (flags.requiresVue && !flags.requiresSvelte)
|
|
34
|
+
rawImports.push(`import VueExample from '../frontend/vue/pages/VueExample.vue'`);
|
|
35
|
+
const connectorImports = {
|
|
36
|
+
neon: ["import { neon } from '@neondatabase/serverless'"],
|
|
37
|
+
planetscale: ["import { connect } from '@planetscale/database'"],
|
|
38
|
+
turso: ["import { createClient } from '@libsql/client'"]
|
|
39
|
+
};
|
|
40
|
+
const dialectImports = {
|
|
41
|
+
neon: ["import { drizzle } from 'drizzle-orm/neon-http'"],
|
|
42
|
+
planetscale: [
|
|
43
|
+
"import { drizzle } from 'drizzle-orm/planetscale-serverless'"
|
|
44
|
+
],
|
|
45
|
+
turso: ["import { drizzle } from 'drizzle-orm/libsql'"]
|
|
46
|
+
};
|
|
47
|
+
const isRemoteHost = databaseHost !== undefined && databaseHost !== 'none';
|
|
48
|
+
if (orm === 'drizzle' && isRemoteHost) {
|
|
49
|
+
const key = databaseHost;
|
|
50
|
+
rawImports.push(...connectorImports[key], ...dialectImports[key]);
|
|
51
|
+
}
|
|
52
|
+
if (orm === 'drizzle' && !isRemoteHost && databaseEngine === 'postgresql')
|
|
53
|
+
rawImports.push(`import { SQL } from 'bun'`, `import { drizzle } from 'drizzle-orm/bun-sql'`);
|
|
54
|
+
if (orm === 'drizzle' && databaseEngine === 'sqlite' && !isRemoteHost)
|
|
55
|
+
rawImports.push(`import { Database } from 'bun:sqlite'`, `import { drizzle } from 'drizzle-orm/bun-sqlite'`);
|
|
56
|
+
if ((orm ?? 'none') === 'none' && databaseEngine === 'sqlite')
|
|
57
|
+
rawImports.push(...(databaseHost === 'turso'
|
|
58
|
+
? [
|
|
59
|
+
`import { createClient } from '@libsql/client'`,
|
|
60
|
+
`import { getEnv } from '@absolutejs/absolute'`
|
|
61
|
+
]
|
|
62
|
+
: [`import { Database } from 'bun:sqlite'`]));
|
|
63
|
+
if (orm === 'drizzle') {
|
|
64
|
+
rawImports.push(`import { Elysia } from 'elysia'`, ...(databaseEngine === 'sqlite' && !isRemoteHost
|
|
65
|
+
? []
|
|
66
|
+
: [`import { getEnv } from '@absolutejs/absolute'`]), authProvider === 'absoluteAuth'
|
|
67
|
+
? `import { schema, User } from '../../db/schema'`
|
|
68
|
+
: `import { schema } from '../../db/schema'`);
|
|
69
|
+
}
|
|
70
|
+
if ((orm === undefined || orm === 'none') &&
|
|
71
|
+
databaseEngine === 'postgresql')
|
|
72
|
+
rawImports.push(...(isRemoteHost
|
|
73
|
+
? connectorImports[databaseHost]
|
|
74
|
+
: [`import { SQL } from 'bun'`]), `import { getEnv } from '@absolutejs/absolute'`);
|
|
75
|
+
if (authProvider === 'absoluteAuth')
|
|
76
|
+
rawImports.push(`import { absoluteAuth, instantiateUserSession } from '@absolutejs/auth'`, ...(databaseEngine && databaseEngine !== 'none'
|
|
77
|
+
? [
|
|
78
|
+
`import { createUser, getUser } from './handlers/userHandlers'`
|
|
79
|
+
]
|
|
80
|
+
: []));
|
|
81
|
+
if ((authProvider === undefined || authProvider === 'none') &&
|
|
82
|
+
databaseEngine !== undefined &&
|
|
83
|
+
databaseEngine !== 'none')
|
|
84
|
+
rawImports.push(`import { getCountHistory, createCountHistory } from './handlers/countHistoryHandlers'`, `import { t } from 'elysia'`);
|
|
85
|
+
if (flags.requiresVue && flags.requiresSvelte) {
|
|
86
|
+
const utilsDir = join(backendDirectory, 'utils');
|
|
87
|
+
mkdirSync(utilsDir, { recursive: true });
|
|
88
|
+
writeFileSync(join(utilsDir, 'vueImporter.ts'), `import VueExample from "../../frontend/vue/pages/VueExample.vue"\n\nexport const vueImports = { VueExample } as const\n`);
|
|
89
|
+
rawImports.push(`import { vueImports } from './utils/vueImporter'`);
|
|
90
|
+
}
|
|
91
|
+
const importMap = new Map();
|
|
92
|
+
for (const stmt of rawImports) {
|
|
93
|
+
const match = stmt.match(/^import\s+(.+)\s+from\s+['"](.+)['"];?/);
|
|
94
|
+
if (!match)
|
|
95
|
+
continue;
|
|
96
|
+
const [, importClause, modulePath] = match;
|
|
97
|
+
if (!importClause || !modulePath)
|
|
98
|
+
continue;
|
|
99
|
+
const entry = importMap.get(modulePath) ?? {
|
|
100
|
+
defaultImport: null,
|
|
101
|
+
namedImports: new Set()
|
|
102
|
+
};
|
|
103
|
+
importMap.set(modulePath, entry);
|
|
104
|
+
void (importClause.startsWith('{')
|
|
105
|
+
? importClause
|
|
106
|
+
.slice(1, -1)
|
|
107
|
+
.split(',')
|
|
108
|
+
.map((segment) => segment.trim())
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.forEach((name) => entry.namedImports.add(name))
|
|
111
|
+
: (entry.defaultImport = importClause.trim()));
|
|
112
|
+
}
|
|
113
|
+
return Array.from(importMap.entries())
|
|
114
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
115
|
+
.map(([path, { defaultImport, namedImports }]) => {
|
|
116
|
+
const parts = [];
|
|
117
|
+
if (defaultImport)
|
|
118
|
+
parts.push(defaultImport);
|
|
119
|
+
if (namedImports.size)
|
|
120
|
+
parts.push(`{ ${[...namedImports].sort().join(', ')} }`);
|
|
121
|
+
return `import ${parts.join(', ')} from '${path}'`;
|
|
122
|
+
})
|
|
123
|
+
.join('\n');
|
|
124
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuthProvider, FrontendDirectories } from '../../types';
|
|
2
|
+
import type { FrameworkFlags } from './computeFlags';
|
|
3
|
+
type GenerateRoutesBlockProps = {
|
|
4
|
+
flags: FrameworkFlags;
|
|
5
|
+
frontendDirectories: FrontendDirectories;
|
|
6
|
+
authProvider: AuthProvider;
|
|
7
|
+
buildDirectory: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const generateRoutesBlock: ({ flags, frontendDirectories, authProvider, buildDirectory }: GenerateRoutesBlockProps) => string;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { isFrontend } from '../../typeGuards';
|
|
2
|
+
export const generateRoutesBlock = ({ flags, frontendDirectories, authProvider, buildDirectory }) => {
|
|
3
|
+
const routes = [];
|
|
4
|
+
const createHandlerCall = (frontend, directory) => {
|
|
5
|
+
const base = `${buildDirectory}${directory ? `/${directory}` : ''}/pages`;
|
|
6
|
+
if (frontend === 'html')
|
|
7
|
+
return `handleHTMLPageRequest(\`${base}/HTMLExample.html\`)`;
|
|
8
|
+
if (frontend === 'htmx')
|
|
9
|
+
return `handleHTMXPageRequest(\`${base}/HTMXExample.html\`)`;
|
|
10
|
+
if (frontend === 'react')
|
|
11
|
+
return `handleReactPageRequest(
|
|
12
|
+
ReactExample,
|
|
13
|
+
asset(manifest, 'ReactExampleIndex'),
|
|
14
|
+
{ initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS') }
|
|
15
|
+
)`;
|
|
16
|
+
if (frontend === 'svelte')
|
|
17
|
+
return `handleSveltePageRequest(
|
|
18
|
+
SvelteExample,
|
|
19
|
+
asset(manifest, 'SvelteExample'),
|
|
20
|
+
asset(manifest, 'SvelteExampleIndex'),
|
|
21
|
+
{ initialCount: 0, cssPath: asset(manifest, 'SvelteExampleCSS') }
|
|
22
|
+
)`;
|
|
23
|
+
if (frontend === 'vue')
|
|
24
|
+
return flags.requiresSvelte
|
|
25
|
+
? `handleVuePageRequest(
|
|
26
|
+
vueImports.VueExample,
|
|
27
|
+
asset(manifest, 'VueExample'),
|
|
28
|
+
asset(manifest, 'VueExampleIndex'),
|
|
29
|
+
generateHeadElement({
|
|
30
|
+
cssPath: asset(manifest, 'VueExampleCSS'),
|
|
31
|
+
title: 'AbsoluteJS + Vue',
|
|
32
|
+
description: 'A Vue.js example with AbsoluteJS'
|
|
33
|
+
}),
|
|
34
|
+
{ initialCount: 0 }
|
|
35
|
+
)`
|
|
36
|
+
: `handleVuePageRequest(
|
|
37
|
+
VueExample,
|
|
38
|
+
asset(manifest, 'VueExample'),
|
|
39
|
+
asset(manifest, 'VueExampleIndex'),
|
|
40
|
+
generateHeadElement({
|
|
41
|
+
cssPath: asset(manifest, 'VueExampleCSS'),
|
|
42
|
+
title: 'AbsoluteJS + Vue',
|
|
43
|
+
description: 'A Vue.js example with AbsoluteJS'
|
|
44
|
+
}),
|
|
45
|
+
{ initialCount: 0 }
|
|
46
|
+
)`;
|
|
47
|
+
return '';
|
|
48
|
+
};
|
|
49
|
+
Object.entries(frontendDirectories).forEach(([frontend, directory], entryIndex) => {
|
|
50
|
+
if (!isFrontend(frontend))
|
|
51
|
+
return;
|
|
52
|
+
const handlerCall = createHandlerCall(frontend, directory);
|
|
53
|
+
if (entryIndex === 0)
|
|
54
|
+
routes.push(`.get('/', () => ${handlerCall})`);
|
|
55
|
+
if (frontend === 'htmx') {
|
|
56
|
+
routes.push(`.get('/htmx', () => ${handlerCall})`, `.post('/htmx/reset', ({ resetScopedStore }) => resetScopedStore())`, `.get('/htmx/count', ({ scopedStore }) => scopedStore.count)`, `.post('/htmx/increment', ({ scopedStore }) => ++scopedStore.count)`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
routes.push(`.get('/${frontend}', () => ${handlerCall})`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (authProvider === undefined || authProvider === 'none') {
|
|
63
|
+
routes.push(`.get('/count/:uid', ({ params: { uid } }) => getCountHistory(db, uid), {
|
|
64
|
+
params: t.Object({
|
|
65
|
+
uid: t.Number()
|
|
66
|
+
})
|
|
67
|
+
})`, `.post('/count', ({ body: { count } }) => createCountHistory(db, count), {
|
|
68
|
+
body: t.Object({
|
|
69
|
+
count: t.Number()
|
|
70
|
+
})
|
|
71
|
+
})`);
|
|
72
|
+
}
|
|
73
|
+
return routes.join('\n ');
|
|
74
|
+
};
|