create-absolutejs 0.17.2 → 0.18.0
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/CHANGELOG.md +12 -0
- package/README.md +37 -4
- package/changelog.json +25 -5
- package/dist/data.d.ts +1 -1
- package/dist/data.js +0 -1
- package/dist/generators/angular/generateAngularPage.d.ts +5 -6
- package/dist/generators/angular/generateAngularPage.js +16 -33
- package/dist/generators/angular/scaffoldAngular.d.ts +1 -1
- package/dist/generators/angular/scaffoldAngular.js +8 -9
- package/dist/generators/configurations/generateDrizzleConfig.js +1 -1
- package/dist/generators/configurations/generateEslintConfig.d.ts +1 -0
- package/dist/generators/configurations/generateEslintConfig.js +47 -255
- package/dist/generators/configurations/generatePackageJson.js +3 -3
- package/dist/generators/db/generateDatabaseTypes.js +18 -25
- package/dist/generators/db/generateDrizzleSchema.js +13 -9
- package/dist/generators/db/generateHandlers.js +3 -2
- package/dist/generators/db/generateRelationalSchema.d.ts +1 -1
- package/dist/generators/db/generateRelationalSchema.js +1 -1
- package/dist/generators/db/handlerTemplates.d.ts +7 -1
- package/dist/generators/db/handlerTemplates.js +7 -2
- package/dist/generators/db/scaffoldDatabase.d.ts +2 -1
- package/dist/generators/db/scaffoldDatabase.js +8 -6
- package/dist/generators/db/scaffoldDocker.d.ts +2 -1
- package/dist/generators/db/scaffoldDocker.js +5 -3
- package/dist/generators/htmx/scaffoldHTMX.js +1 -0
- package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +1 -1
- package/dist/generators/project/generateAbsoluteAuthConfig.js +11 -10
- package/dist/generators/project/generateDBBlock.js +5 -5
- package/dist/generators/project/generateIdentity.d.ts +1 -0
- package/dist/generators/project/generateIdentity.js +15 -0
- package/dist/generators/project/generateImportsBlock.js +4 -9
- package/dist/generators/project/generateRoutesBlock.d.ts +2 -1
- package/dist/generators/project/generateRoutesBlock.js +8 -13
- package/dist/generators/project/generateServer.js +26 -16
- package/dist/generators/project/scaffoldAgentic.d.ts +1 -1
- package/dist/generators/project/scaffoldAgentic.js +45 -45
- package/dist/generators/project/scaffoldBackend.js +4 -9
- package/dist/generators/react/scaffoldReact.js +2 -2
- package/dist/index.js +0 -0
- package/dist/prompt.js +3 -2
- package/dist/questions/authOption.js +1 -1
- package/dist/questions/databaseEngine.d.ts +1 -1
- package/dist/questions/orm.js +2 -5
- package/dist/scaffold.d.ts +2 -1
- package/dist/scaffold.js +16 -5
- package/dist/templates/configurations/eslint.config.example.mjs +43 -238
- package/dist/templates/htmx/LICENSE +13 -0
- package/dist/templates/htmx/htmx.2.0.11.min.js +1 -0
- package/dist/utils/abort.d.ts +1 -1
- package/dist/utils/abort.js +2 -4
- package/dist/utils/getPackageVersion.js +7 -2
- package/dist/utils/parseCommandLineOptions.js +7 -15
- package/dist/utils/registryChannel.d.ts +2 -0
- package/dist/utils/registryChannel.js +20 -0
- package/dist/versions.d.ts +67 -66
- package/dist/versions.js +67 -66
- package/package.json +26 -23
- package/dist/templates/htmx/htmx.2.0.6.min.js +0 -1
|
@@ -1,261 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const reactImports = hasReact
|
|
4
|
-
? `import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
|
|
5
|
-
import reactPlugin from 'eslint-plugin-react';
|
|
6
|
-
import reactCompilerPlugin from 'eslint-plugin-react-compiler';
|
|
7
|
-
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
|
8
|
-
`
|
|
9
|
-
: '';
|
|
10
|
-
const reactBlock = hasReact
|
|
11
|
-
? ` {
|
|
12
|
-
files: ['example/**/*.{js,jsx,ts,tsx}'],
|
|
13
|
-
plugins: {
|
|
14
|
-
'jsx-a11y': fixupPluginRules(jsxA11yPlugin),
|
|
15
|
-
react: fixupPluginRules(reactPlugin),
|
|
16
|
-
'react-compiler': reactCompilerPlugin,
|
|
17
|
-
'react-hooks': reactHooksPlugin
|
|
18
|
-
},
|
|
19
|
-
rules: {
|
|
20
|
-
'jsx-a11y/prefer-tag-over-role': 'error',
|
|
21
|
-
'react-compiler/react-compiler': 'error',
|
|
22
|
-
'react-hooks/exhaustive-deps': 'warn',
|
|
23
|
-
'react-hooks/rules-of-hooks': 'error',
|
|
24
|
-
'react/checked-requires-onchange-or-readonly': 'error',
|
|
25
|
-
'react/destructuring-assignment': ['error', 'always'],
|
|
26
|
-
'react/jsx-filename-extension': ['error', { extensions: ['.tsx'] }],
|
|
27
|
-
'react/jsx-no-leaked-render': 'error',
|
|
28
|
-
'react/jsx-no-target-blank': 'error',
|
|
29
|
-
'react/jsx-no-useless-fragment': 'error',
|
|
30
|
-
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
|
|
31
|
-
'react/no-multi-comp': 'error',
|
|
32
|
-
'react/no-unknown-property': 'off',
|
|
33
|
-
'react/react-in-jsx-scope': 'off',
|
|
34
|
-
'react/self-closing-comp': 'error'
|
|
35
|
-
},
|
|
36
|
-
settings: {
|
|
37
|
-
react: { version: 'detect' }
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
`
|
|
41
|
-
: '';
|
|
42
|
-
return `// eslint.config.mjs
|
|
43
|
-
import { dirname } from 'path';
|
|
44
|
-
import { fileURLToPath } from 'url';
|
|
45
|
-
import { fixupPluginRules } from '@eslint/compat';
|
|
46
|
-
import pluginJs from '@eslint/js';
|
|
47
|
-
import stylistic from '@stylistic/eslint-plugin';
|
|
48
|
-
import tsParser from '@typescript-eslint/parser';
|
|
1
|
+
/** Correctness defaults; project-specific formatting belongs in Prettier. */
|
|
2
|
+
export const generateEslintConfig = (frontends) => `
|
|
49
3
|
import { defineConfig } from 'eslint/config';
|
|
50
|
-
import absolutePlugin from 'eslint-plugin-absolute';
|
|
51
|
-
import importPlugin from 'eslint-plugin-import';
|
|
52
|
-
${reactImports}import promisePlugin from 'eslint-plugin-promise';
|
|
53
|
-
import securityPlugin from 'eslint-plugin-security';
|
|
54
|
-
import globals from 'globals';
|
|
55
4
|
import tseslint from 'typescript-eslint';
|
|
56
|
-
|
|
57
|
-
|
|
5
|
+
import absolute from 'eslint-plugin-absolute';
|
|
6
|
+
${frontends.includes('react') ? "import reactHooks from 'eslint-plugin-react-hooks';" : ''}
|
|
58
7
|
|
|
59
8
|
export default defineConfig([
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
'absolute/max-jsxnesting': ['error', 5],
|
|
104
|
-
'absolute/min-var-length': [
|
|
105
|
-
'error',
|
|
106
|
-
{ allowedVars: ['_', 'id', 'db', 'OK'], minLength: 3 }
|
|
107
|
-
],
|
|
108
|
-
'absolute/no-button-navigation': 'error',
|
|
109
|
-
'absolute/no-explicit-return-type': 'error',
|
|
110
|
-
'absolute/no-inline-prop-types': 'error',
|
|
111
|
-
'absolute/no-multi-style-objects': 'error',
|
|
112
|
-
'absolute/no-nested-jsx-return': 'error',
|
|
113
|
-
'absolute/no-or-none-component': 'error',
|
|
114
|
-
'absolute/no-transition-cssproperties': 'error',
|
|
115
|
-
'absolute/no-unnecessary-div': 'error',
|
|
116
|
-
'absolute/no-unnecessary-key': 'error',
|
|
117
|
-
'absolute/no-useless-function': 'error',
|
|
118
|
-
'absolute/seperate-style-files': 'error',
|
|
119
|
-
'absolute/sort-exports': [
|
|
120
|
-
'error',
|
|
121
|
-
{
|
|
122
|
-
caseSensitive: true,
|
|
123
|
-
natural: true,
|
|
124
|
-
order: 'asc',
|
|
125
|
-
variablesBeforeFunctions: true
|
|
126
|
-
}
|
|
127
|
-
],
|
|
128
|
-
'absolute/sort-keys-fixable': [
|
|
129
|
-
'error',
|
|
130
|
-
{
|
|
131
|
-
caseSensitive: true,
|
|
132
|
-
natural: true,
|
|
133
|
-
order: 'asc',
|
|
134
|
-
variablesBeforeFunctions: true
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
'arrow-body-style': ['error', 'as-needed'],
|
|
138
|
-
'consistent-return': 'error',
|
|
139
|
-
eqeqeq: 'error',
|
|
140
|
-
'func-style': [
|
|
141
|
-
'error',
|
|
142
|
-
'expression',
|
|
143
|
-
{ allowArrowFunctions: true }
|
|
144
|
-
],
|
|
145
|
-
'import/no-cycle': 'error',
|
|
146
|
-
'import/no-default-export': 'error',
|
|
147
|
-
'import/no-relative-packages': 'error',
|
|
148
|
-
'import/no-unused-modules': ['error', { missingExports: true }],
|
|
149
|
-
'import/order': ['error', { alphabetize: { order: 'asc' } }],
|
|
150
|
-
'no-await-in-loop': 'error',
|
|
151
|
-
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
152
|
-
'no-debugger': 'error',
|
|
153
|
-
'no-duplicate-case': 'error',
|
|
154
|
-
'no-duplicate-imports': 'error',
|
|
155
|
-
'no-else-return': 'error',
|
|
156
|
-
'no-empty-function': 'error',
|
|
157
|
-
'no-empty-pattern': 'error',
|
|
158
|
-
'no-empty-static-block': 'error',
|
|
159
|
-
'no-fallthrough': 'error',
|
|
160
|
-
'no-floating-decimal': 'error',
|
|
161
|
-
'no-global-assign': 'error',
|
|
162
|
-
'no-implicit-coercion': 'error',
|
|
163
|
-
'no-implicit-globals': 'error',
|
|
164
|
-
'no-loop-func': 'error',
|
|
165
|
-
'no-magic-numbers': [
|
|
166
|
-
'warn',
|
|
167
|
-
{ detectObjects: false, enforceConst: true, ignore: [0, 1] }
|
|
168
|
-
],
|
|
169
|
-
'no-misleading-character-class': 'error',
|
|
170
|
-
'no-nested-ternary': 'error',
|
|
171
|
-
'no-new-native-nonconstructor': 'error',
|
|
172
|
-
'no-new-wrappers': 'error',
|
|
173
|
-
'no-param-reassign': 'error',
|
|
174
|
-
'no-restricted-imports': [
|
|
175
|
-
'error',
|
|
176
|
-
{
|
|
177
|
-
paths: [
|
|
178
|
-
{
|
|
179
|
-
importNames: ['default'],
|
|
180
|
-
message:
|
|
181
|
-
'Import only named React exports for tree-shaking.',
|
|
182
|
-
name: 'react'
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
importNames: ['default'],
|
|
186
|
-
message: 'Import only the required Bun exports.',
|
|
187
|
-
name: 'bun'
|
|
188
|
-
}
|
|
189
|
-
]
|
|
190
|
-
}
|
|
191
|
-
],
|
|
192
|
-
'no-return-await': 'error',
|
|
193
|
-
'no-shadow': 'error',
|
|
194
|
-
'no-undef': 'error',
|
|
195
|
-
'no-unneeded-ternary': 'error',
|
|
196
|
-
'no-unreachable': 'error',
|
|
197
|
-
'no-useless-assignment': 'error',
|
|
198
|
-
'no-useless-concat': 'error',
|
|
199
|
-
'no-useless-return': 'error',
|
|
200
|
-
'no-var': 'error',
|
|
201
|
-
'prefer-arrow-callback': 'error',
|
|
202
|
-
'prefer-const': 'error',
|
|
203
|
-
'prefer-destructuring': [
|
|
204
|
-
'error',
|
|
205
|
-
{ array: true, object: true },
|
|
206
|
-
{ enforceForRenamedProperties: false }
|
|
207
|
-
],
|
|
208
|
-
'prefer-template': 'error',
|
|
209
|
-
'promise/always-return': 'warn',
|
|
210
|
-
'promise/avoid-new': 'warn',
|
|
211
|
-
'promise/catch-or-return': 'error',
|
|
212
|
-
'promise/no-callback-in-promise': 'warn',
|
|
213
|
-
'promise/no-nesting': 'warn',
|
|
214
|
-
'promise/no-promise-in-callback': 'warn',
|
|
215
|
-
'promise/no-return-wrap': 'error',
|
|
216
|
-
'promise/param-names': 'error'
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
files: ['**/*.{html,vue,svelte,gjs,gts}'],
|
|
221
|
-
ignores: ['example/build/**'],
|
|
222
|
-
plugins: { absolute: fixupPluginRules(absolutePlugin) },
|
|
223
|
-
processor: 'absolute/template-source'
|
|
224
|
-
},
|
|
225
|
-
${reactBlock} {
|
|
226
|
-
files: [
|
|
227
|
-
'example/server.ts',
|
|
228
|
-
'example/indexes/*.tsx',
|
|
229
|
-
'example/db/migrate.ts'
|
|
230
|
-
],
|
|
231
|
-
rules: {
|
|
232
|
-
'import/no-unused-modules': 'off'
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
files: ['example/db/migrate.ts', 'example/utils/absoluteAuthConfig.ts'],
|
|
237
|
-
rules: {
|
|
238
|
-
'no-console': 'off'
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
files: ['eslint.config.mjs'],
|
|
243
|
-
rules: {
|
|
244
|
-
'no-magic-numbers': 'off'
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
files: ['eslint.config.mjs'],
|
|
249
|
-
rules: {
|
|
250
|
-
'import/no-default-export': 'off'
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
files: ['example/db/schema.ts'],
|
|
255
|
-
rules: {
|
|
256
|
-
'absolute/explicit-object-types': 'off'
|
|
257
|
-
}
|
|
258
|
-
}
|
|
9
|
+
// The copied HTMX distribution is third-party minified runtime code.
|
|
10
|
+
{ ignores: ['src/frontend/**/htmx.min.js', 'node_modules/**', 'build/**', 'dist/**', '.absolutejs/**', '.data/**', '**/.absolutejs-hmr-*', 'drizzle/**'] },
|
|
11
|
+
{ linterOptions: { noInlineConfig: true, reportUnusedDisableDirectives: 'error' } },
|
|
12
|
+
...tseslint.configs.recommended,
|
|
13
|
+
{
|
|
14
|
+
files: ['**/*.{ts,tsx,js,jsx,mjs}'],
|
|
15
|
+
plugins: { absolute },
|
|
16
|
+
rules: {
|
|
17
|
+
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-check': true, 'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': true }],
|
|
18
|
+
'absolute/no-chained-type-assertions': 'error',
|
|
19
|
+
'absolute/no-unsafe-schema-types': 'error',
|
|
20
|
+
'absolute/prefer-drizzle-query-builders': 'error',
|
|
21
|
+
'absolute/elysia-composition-boundaries': 'error',
|
|
22
|
+
'absolute/no-nondeterministic-render': 'error',
|
|
23
|
+
'absolute/button-icon-is-hidden': 'error',
|
|
24
|
+
'absolute/icon-button-has-accessible-name': 'error',
|
|
25
|
+
'absolute/loading-indicator-has-aria-busy': 'error',
|
|
26
|
+
'absolute/progressbar-has-state': 'error',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
files: ['src/backend/**/*.{ts,tsx}'],
|
|
31
|
+
rules: { 'absolute/elysia-no-response-return': 'error' },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
files: ['src/frontend/**/*.{ts,tsx,js,jsx}'],
|
|
35
|
+
rules: {
|
|
36
|
+
'no-restricted-syntax': ['error',
|
|
37
|
+
{ selector: "CallExpression[callee.name='fetch']", message: 'Use a directly typed Eden subapp client.' },
|
|
38
|
+
{ selector: "CallExpression[callee.property.name='fetch']", message: 'Use a directly typed Eden subapp client.' },
|
|
39
|
+
],
|
|
40
|
+
${frontends.includes('react') ? "'absolute/eden-requires-react-query': 'error'," : ''}
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{ files: ['**/*.{html,vue,svelte,gjs,gts}'], plugins: { absolute }, processor: 'absolute/template-source' },
|
|
44
|
+
${frontends.includes('react')
|
|
45
|
+
? `{
|
|
46
|
+
// React Hooks rules do not apply to Angular's unrelated usePageContext API.
|
|
47
|
+
files: ['src/frontend/**/*.{tsx,jsx}', 'src/frontend/**/hooks/**/*.{ts,js}'],
|
|
48
|
+
plugins: { 'react-hooks': reactHooks },
|
|
49
|
+
rules: { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'error' },
|
|
50
|
+
},`
|
|
51
|
+
: ''}
|
|
259
52
|
]);
|
|
260
53
|
`;
|
|
261
|
-
};
|
|
@@ -147,7 +147,7 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
|
|
|
147
147
|
dependencies[absoluteAuthPlugin.value] = resolveVersion(absoluteAuthPlugin.value, absoluteAuthPlugin.latestVersion);
|
|
148
148
|
}
|
|
149
149
|
if (agentic) {
|
|
150
|
-
|
|
150
|
+
[
|
|
151
151
|
'@absolutejs/a2a',
|
|
152
152
|
'@absolutejs/agency',
|
|
153
153
|
'@absolutejs/agent-discovery',
|
|
@@ -166,9 +166,9 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
|
|
|
166
166
|
'@absolutejs/secrets',
|
|
167
167
|
'@absolutejs/sync-bus-pg',
|
|
168
168
|
'@absolutejs/wallet'
|
|
169
|
-
]) {
|
|
169
|
+
].forEach((name) => {
|
|
170
170
|
dependencies[name] = resolveVersion(name, versions[name]);
|
|
171
|
-
}
|
|
171
|
+
});
|
|
172
172
|
devDependencies['@absolutejs/agent-conformance'] = resolveVersion('@absolutejs/agent-conformance', versions['@absolutejs/agent-conformance']);
|
|
173
173
|
}
|
|
174
174
|
for (const pluginValue of plugins) {
|
|
@@ -108,56 +108,49 @@ export const generateDatabaseTypes = ({ databaseEngine, databaseHost, authOption
|
|
|
108
108
|
let dbImport = '';
|
|
109
109
|
let dbTypeLine = '';
|
|
110
110
|
if (databaseHost === 'neon') {
|
|
111
|
-
dbImport = `import {
|
|
112
|
-
dbTypeLine = 'export type DatabaseType =
|
|
111
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/neon-http';`;
|
|
112
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
113
113
|
}
|
|
114
114
|
else if (databaseHost === 'planetscale' && databaseEngine === 'mysql') {
|
|
115
|
-
dbImport = `import {
|
|
116
|
-
dbTypeLine =
|
|
117
|
-
'export type DatabaseType = PlanetScaleDatabase<SchemaType>;';
|
|
115
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/planetscale-serverless';`;
|
|
116
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
118
117
|
}
|
|
119
118
|
else if (databaseHost === 'planetscale' &&
|
|
120
119
|
databaseEngine === 'postgresql') {
|
|
121
|
-
dbImport = `import {
|
|
122
|
-
dbTypeLine = 'export type DatabaseType =
|
|
120
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/node-postgres';`;
|
|
121
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
123
122
|
}
|
|
124
123
|
else if (databaseHost === 'turso') {
|
|
125
|
-
dbImport = `import {
|
|
126
|
-
dbTypeLine = 'export type DatabaseType =
|
|
124
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/libsql';`;
|
|
125
|
+
dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
127
126
|
}
|
|
128
127
|
if ((!databaseHost || databaseHost === 'none') &&
|
|
129
128
|
isDrizzleDialect(databaseEngine)) {
|
|
130
129
|
switch (databaseEngine) {
|
|
131
|
-
case 'gel':
|
|
132
|
-
dbImport = `import { GelJsDatabase } from 'drizzle-orm/gel';`;
|
|
133
|
-
dbTypeLine =
|
|
134
|
-
'export type DatabaseType = GelJsDatabase<SchemaType>;';
|
|
135
|
-
break;
|
|
136
130
|
case 'mariadb':
|
|
137
131
|
case 'mysql':
|
|
138
|
-
dbImport = `import {
|
|
132
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/mysql2';`;
|
|
139
133
|
dbTypeLine =
|
|
140
|
-
'export type DatabaseType =
|
|
134
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
141
135
|
break;
|
|
142
136
|
case 'mssql':
|
|
143
|
-
dbImport = `import {
|
|
137
|
+
dbImport = `import type { NodeMsSqlDatabase } from 'drizzle-orm/node-mssql';`;
|
|
144
138
|
dbTypeLine =
|
|
145
|
-
'export type DatabaseType =
|
|
139
|
+
'export type DatabaseType = NodeMsSqlDatabase;';
|
|
146
140
|
break;
|
|
147
141
|
case 'postgresql':
|
|
148
|
-
dbImport = `import {
|
|
142
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/bun-sql';`;
|
|
149
143
|
dbTypeLine =
|
|
150
|
-
'export type DatabaseType =
|
|
144
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
151
145
|
break;
|
|
152
146
|
case 'singlestore':
|
|
153
|
-
dbImport = `import { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
|
|
154
|
-
dbTypeLine =
|
|
155
|
-
'export type DatabaseType = SingleStoreDriverDatabase<SchemaType>;';
|
|
147
|
+
dbImport = `import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
|
|
148
|
+
dbTypeLine = 'export type DatabaseType = SingleStoreDriverDatabase;';
|
|
156
149
|
break;
|
|
157
150
|
case 'sqlite':
|
|
158
|
-
dbImport = `import {
|
|
151
|
+
dbImport = `import type { drizzle } from 'drizzle-orm/bun-sqlite';`;
|
|
159
152
|
dbTypeLine =
|
|
160
|
-
'export type DatabaseType =
|
|
153
|
+
'export type DatabaseType = ReturnType<typeof drizzle>;';
|
|
161
154
|
break;
|
|
162
155
|
}
|
|
163
156
|
}
|
|
@@ -53,7 +53,7 @@ const DIALECTS = {
|
|
|
53
53
|
pkg: 'sqlite-core',
|
|
54
54
|
string: 'text()',
|
|
55
55
|
table: 'sqliteTable',
|
|
56
|
-
time: "integer({ mode: '
|
|
56
|
+
time: "integer({ mode: 'timestamp_ms' })"
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
const builder = (expr) => expr.split('(')[0];
|
|
@@ -72,13 +72,15 @@ export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
|
|
|
72
72
|
? [cfg.table, stringBuilder, timeBuilder, jsonBuilder]
|
|
73
73
|
: [cfg.table, intBuilder, timeBuilder];
|
|
74
74
|
const uniqueBuilders = Array.from(new Set(importBuilders));
|
|
75
|
-
const builderImport =
|
|
76
|
-
const sqliteImports = databaseEngine === 'sqlite'
|
|
75
|
+
const builderImport = `${authOption === 'abs' ? "import type { UserIdentity } from '../src/types/userIdentity';\n" : ''}import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`;
|
|
76
|
+
const sqliteImports = databaseEngine === 'sqlite' || databaseEngine === 'mssql'
|
|
77
77
|
? `import { sql } from 'drizzle-orm';\n`
|
|
78
78
|
: '';
|
|
79
79
|
let uidColumn;
|
|
80
|
-
if (databaseEngine === '
|
|
81
|
-
|
|
80
|
+
if (databaseEngine === 'mssql') {
|
|
81
|
+
uidColumn = `int('uid').identity().primaryKey()`;
|
|
82
|
+
}
|
|
83
|
+
else if (databaseEngine === 'mariadb' ||
|
|
82
84
|
databaseEngine === 'mysql' ||
|
|
83
85
|
databaseEngine === 'singlestore') {
|
|
84
86
|
uidColumn = `${intBuilder}('uid').primaryKey().autoincrement()`;
|
|
@@ -93,14 +95,16 @@ export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
|
|
|
93
95
|
? `const JULIAN_DAY_UNIX_EPOCH_OFFSET = 2440587.5;
|
|
94
96
|
const MILLIS_PER_DAY = 86400000;\n\n`
|
|
95
97
|
: '';
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
let timestampColumn = `${cfg.time}.notNull().defaultNow()`;
|
|
99
|
+
if (databaseEngine === 'sqlite')
|
|
100
|
+
timestampColumn = `${cfg.time}.notNull().default(sql\`(julianday('now') - \${JULIAN_DAY_UNIX_EPOCH_OFFSET}) * \${MILLIS_PER_DAY}\`)`;
|
|
101
|
+
if (databaseEngine === 'mssql')
|
|
102
|
+
timestampColumn = `${cfg.time}.notNull().default(sql\`sysdatetime()\`)`;
|
|
99
103
|
const tableBlock = authOption === 'abs'
|
|
100
104
|
? `export const users = ${cfg.table}('users', {
|
|
101
105
|
auth_sub: ${cfg.string}.primaryKey(),
|
|
102
106
|
created_at: ${timestampColumn},
|
|
103
|
-
metadata: ${cfg.json}.$type<
|
|
107
|
+
metadata: ${cfg.json}.$type<UserIdentity>().default({})
|
|
104
108
|
});`
|
|
105
109
|
: `export const countHistory = ${cfg.table}('count_history', {
|
|
106
110
|
uid: ${uidColumn},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAuthTemplate, getCountTemplate } from './handlerTemplates';
|
|
1
|
+
import { getAuthTemplate, getCountTemplate, isDriverConfigurationKey } from './handlerTemplates';
|
|
2
2
|
export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth }) => {
|
|
3
3
|
if (databaseEngine === undefined || databaseEngine === 'none') {
|
|
4
4
|
throw new Error('Internal Error: databaseEngine is undefined or "none".');
|
|
@@ -6,6 +6,7 @@ export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth
|
|
|
6
6
|
const host = databaseHost && databaseHost !== 'none' ? databaseHost : 'local';
|
|
7
7
|
const ormKey = orm === 'drizzle' ? 'drizzle' : 'sql';
|
|
8
8
|
const key = `${databaseEngine}:${ormKey}:${host}`;
|
|
9
|
-
|
|
9
|
+
if (!isDriverConfigurationKey(key))
|
|
10
|
+
throw new Error(`Unsupported database configuration: ${key}`);
|
|
10
11
|
return usesAuth ? getAuthTemplate(key) : getCountTemplate(key);
|
|
11
12
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { AuthOption, DatabaseEngine } from '../../types';
|
|
2
|
-
export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
|
|
3
2
|
export declare const generateRelationalSchema: (databaseEngine: DatabaseEngine, authOption: AuthOption) => string;
|
|
3
|
+
export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
|
|
@@ -30,7 +30,6 @@ const DIALECTS = {
|
|
|
30
30
|
timestamp: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP'
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
|
|
34
33
|
export const generateRelationalSchema = (databaseEngine, authOption) => {
|
|
35
34
|
if (databaseEngine === undefined || !(databaseEngine in DIALECTS)) {
|
|
36
35
|
throw new Error(`Internal error: no relational DDL for engine "${databaseEngine}"`);
|
|
@@ -50,3 +49,4 @@ export const generateRelationalSchema = (databaseEngine, authOption) => {
|
|
|
50
49
|
);
|
|
51
50
|
`;
|
|
52
51
|
};
|
|
52
|
+
export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
|
|
@@ -31,7 +31,12 @@ declare const driverConfigurations: {
|
|
|
31
31
|
};
|
|
32
32
|
readonly 'mssql:drizzle:local': {
|
|
33
33
|
readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
|
|
34
|
-
readonly queries:
|
|
34
|
+
readonly queries: {
|
|
35
|
+
readonly insertHistory: string;
|
|
36
|
+
readonly insertUser: string;
|
|
37
|
+
readonly selectUser: string;
|
|
38
|
+
readonly selectHistory: string;
|
|
39
|
+
};
|
|
35
40
|
};
|
|
36
41
|
readonly 'mssql:sql:local': {
|
|
37
42
|
readonly importLines: "";
|
|
@@ -105,4 +110,5 @@ declare const driverConfigurations: {
|
|
|
105
110
|
type DriverConfigurationKey = keyof typeof driverConfigurations;
|
|
106
111
|
export declare const getAuthTemplate: (key: DriverConfigurationKey) => string;
|
|
107
112
|
export declare const getCountTemplate: (key: DriverConfigurationKey) => string;
|
|
113
|
+
export declare const isDriverConfigurationKey: (key: string) => key is DriverConfigurationKey;
|
|
108
114
|
export {};
|
|
@@ -19,7 +19,7 @@ export const getUser = async (
|
|
|
19
19
|
((await selectUserRow(db, authSub)) as User | null) ?? null;`}
|
|
20
20
|
|
|
21
21
|
export const createUser = async (db: DatabaseType, newUserData: NewUser) => {
|
|
22
|
-
const { auth_sub: authSub, metadata: userIdentity } = newUserData;
|
|
22
|
+
${rowsAreTyped ? '' : 'const { auth_sub: authSub, metadata: userIdentity } = newUserData;'}
|
|
23
23
|
${queries.insertUser}
|
|
24
24
|
}`;
|
|
25
25
|
/* Takes `db` as the `DatabaseType` alias rather than naming the driver type
|
|
@@ -367,7 +367,11 @@ import { schema } from '../../../db/schema'`,
|
|
|
367
367
|
'mssql:drizzle:local': {
|
|
368
368
|
importLines: `import { eq } from 'drizzle-orm'
|
|
369
369
|
import { schema } from '../../../db/schema'`,
|
|
370
|
-
queries:
|
|
370
|
+
queries: {
|
|
371
|
+
...drizzleQueryOperations,
|
|
372
|
+
insertHistory: drizzleQueryOperations.insertHistory.replace('.values({ count }).returning()', '.output().values({ count })'),
|
|
373
|
+
insertUser: drizzleQueryOperations.insertUser.replace('.values(newUserData)\n\t\t.returning()', '.output()\n\t\t.values(newUserData)')
|
|
374
|
+
}
|
|
371
375
|
},
|
|
372
376
|
'mssql:sql:local': {
|
|
373
377
|
importLines: ``,
|
|
@@ -462,3 +466,4 @@ export const getCountTemplate = (key) => {
|
|
|
462
466
|
throw new Error(`Unsupported driver configuration: ${key}`);
|
|
463
467
|
return buildSqlCountTemplate(configuration);
|
|
464
468
|
};
|
|
469
|
+
export const isDriverConfigurationKey = (key) => Object.hasOwn(driverConfigurations, key);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CreateConfiguration } from '../../types';
|
|
2
2
|
type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'databaseHost' | 'orm' | 'databaseDirectory' | 'authOption' | 'databaseEngine'> & {
|
|
3
3
|
databaseDirectory: string;
|
|
4
|
+
verifyLocalDatabase?: boolean;
|
|
4
5
|
backendDirectory: string;
|
|
5
6
|
typesDirectory: string;
|
|
6
7
|
};
|
|
7
|
-
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }: ScaffoldDatabaseProps) => Promise<{
|
|
8
|
+
export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory, verifyLocalDatabase }: ScaffoldDatabaseProps) => Promise<{
|
|
8
9
|
dockerFreshInstall: boolean;
|
|
9
10
|
}>;
|
|
10
11
|
export {};
|
|
@@ -10,7 +10,7 @@ import { generateDBHandlers } from './generateHandlers';
|
|
|
10
10
|
import { generateRelationalSchema, supportsRelationalSchema } from './generateRelationalSchema';
|
|
11
11
|
import { generateSqliteSchema } from './generateSqliteSchema';
|
|
12
12
|
import { scaffoldDocker } from './scaffoldDocker';
|
|
13
|
-
export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }) => {
|
|
13
|
+
export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory, verifyLocalDatabase = true }) => {
|
|
14
14
|
const projectDatabaseDirectory = join(projectName, databaseDirectory);
|
|
15
15
|
const handlerDirectory = join(backendDirectory, 'handlers');
|
|
16
16
|
mkdirSync(projectDatabaseDirectory, { recursive: true });
|
|
@@ -64,17 +64,19 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
64
64
|
seeded.run(sqliteSchema);
|
|
65
65
|
seeded.close();
|
|
66
66
|
}
|
|
67
|
+
let dockerFreshInstall = false;
|
|
67
68
|
if ((databaseHost === 'none' || databaseHost === undefined) &&
|
|
68
69
|
databaseEngine !== 'sqlite' &&
|
|
69
70
|
databaseEngine !== undefined &&
|
|
70
71
|
databaseEngine !== 'none') {
|
|
71
|
-
const
|
|
72
|
+
const result = await scaffoldDocker({
|
|
72
73
|
authOption,
|
|
73
74
|
databaseEngine,
|
|
74
75
|
projectDatabaseDirectory,
|
|
75
|
-
projectName
|
|
76
|
+
projectName,
|
|
77
|
+
verifyLocalDatabase
|
|
76
78
|
});
|
|
77
|
-
|
|
79
|
+
({ dockerFreshInstall } = result);
|
|
78
80
|
}
|
|
79
81
|
if (orm === 'drizzle') {
|
|
80
82
|
if (!isDrizzleDialect(databaseEngine)) {
|
|
@@ -93,10 +95,10 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
|
|
|
93
95
|
orm
|
|
94
96
|
});
|
|
95
97
|
writeFileSync(join(typesDirectory, 'databaseTypes.ts'), drizzleTypes);
|
|
96
|
-
return { dockerFreshInstall
|
|
98
|
+
return { dockerFreshInstall };
|
|
97
99
|
}
|
|
98
100
|
if (orm === 'prisma') {
|
|
99
101
|
console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
|
|
100
102
|
}
|
|
101
|
-
return { dockerFreshInstall
|
|
103
|
+
return { dockerFreshInstall };
|
|
102
104
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AuthOption, DatabaseEngine } from '../../types';
|
|
2
2
|
type ScaffoldDockerProps = {
|
|
3
|
+
verifyLocalDatabase?: boolean;
|
|
3
4
|
databaseEngine: DatabaseEngine;
|
|
4
5
|
projectDatabaseDirectory: string;
|
|
5
6
|
authOption: AuthOption;
|
|
6
7
|
projectName: string;
|
|
7
8
|
};
|
|
8
|
-
export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }: ScaffoldDockerProps) => Promise<{
|
|
9
|
+
export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, verifyLocalDatabase }: ScaffoldDockerProps) => Promise<{
|
|
9
10
|
dockerFreshInstall: boolean;
|
|
10
11
|
}>;
|
|
11
12
|
export {};
|
|
@@ -33,16 +33,18 @@ const verifyDockerContainer = async ({ databaseEngine, docker, projectName, spin
|
|
|
33
33
|
.cwd(projectName)
|
|
34
34
|
.quiet();
|
|
35
35
|
};
|
|
36
|
-
export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }) => {
|
|
36
|
+
export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption, verifyLocalDatabase = true }) => {
|
|
37
37
|
if (databaseEngine === undefined ||
|
|
38
38
|
databaseEngine === 'none' ||
|
|
39
39
|
databaseEngine === 'sqlite') {
|
|
40
40
|
throw new Error('Internal type error: databaseEngine must be defined and not "none" or "sqlite"');
|
|
41
41
|
}
|
|
42
|
-
const { freshInstall } = await checkDockerInstalled(databaseEngine);
|
|
43
|
-
const { daemonWasStarted } = await ensureDockerDaemonRunning();
|
|
44
42
|
const dbContainer = generateDockerContainer(databaseEngine);
|
|
45
43
|
writeFileSync(join(projectDatabaseDirectory, 'docker-compose.db.yml'), dbContainer, 'utf-8');
|
|
44
|
+
if (!verifyLocalDatabase)
|
|
45
|
+
return { dockerFreshInstall: false };
|
|
46
|
+
const { freshInstall } = await checkDockerInstalled(databaseEngine);
|
|
47
|
+
const { daemonWasStarted } = await ensureDockerDaemonRunning();
|
|
46
48
|
const docker = resolveDockerExe();
|
|
47
49
|
const spin = spinner();
|
|
48
50
|
spin.start(`Starting ${databaseEngine} container`);
|
|
@@ -6,6 +6,7 @@ import { generateHTMXPage } from './generateHTMXPage';
|
|
|
6
6
|
export const scaffoldHTMX = ({ editBasePath, includeExamples, targetDirectory, templatesDirectory, projectAssetsDirectory, frontends, isSingleFrontend, stylesIndexesDirectory }) => {
|
|
7
7
|
const pagesDirectory = join(targetDirectory, 'pages');
|
|
8
8
|
const cssOutputFile = join(stylesIndexesDirectory, 'htmx-example.css');
|
|
9
|
+
copyFileSync(join(templatesDirectory, 'htmx', 'LICENSE'), join(targetDirectory, 'htmx.LICENSE'));
|
|
9
10
|
// htmx.min.js is the framework runtime, copied in both modes.
|
|
10
11
|
const glob = new Glob('htmx*.min.js');
|
|
11
12
|
for (const relativePath of glob.scanSync({
|