create-absolutejs 0.10.2 → 0.11.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.
Files changed (75) hide show
  1. package/dist/constants.d.ts +2 -2
  2. package/dist/constants.js +2 -2
  3. package/dist/data.d.ts +10 -10
  4. package/dist/data.js +104 -95
  5. package/dist/generators/angular/generateAngularPage.d.ts +7 -0
  6. package/dist/generators/angular/generateAngularPage.js +175 -0
  7. package/dist/generators/angular/scaffoldAngular.d.ts +2 -0
  8. package/dist/generators/angular/scaffoldAngular.js +34 -0
  9. package/dist/generators/configurations/generateEslintConfig.d.ts +2 -0
  10. package/dist/generators/configurations/generateEslintConfig.js +253 -0
  11. package/dist/generators/configurations/generatePackageJson.js +26 -3
  12. package/dist/generators/configurations/scaffoldConfigurationFiles.js +28 -3
  13. package/dist/generators/db/dockerInitTemplates.d.ts +9 -9
  14. package/dist/generators/db/dockerInitTemplates.js +9 -9
  15. package/dist/generators/db/generateDatabaseTypes.js +5 -0
  16. package/dist/generators/db/generateDockerContainer.js +4 -2
  17. package/dist/generators/db/generateDrizzleSchema.js +16 -6
  18. package/dist/generators/db/handlerTemplates.d.ts +5 -0
  19. package/dist/generators/db/handlerTemplates.js +6 -0
  20. package/dist/generators/db/scaffoldDocker.js +42 -30
  21. package/dist/generators/html/generateHTMLPage.d.ts +1 -1
  22. package/dist/generators/html/generateHTMLPage.js +2 -2
  23. package/dist/generators/html/scaffoldHTML.d.ts +1 -1
  24. package/dist/generators/html/scaffoldHTML.js +2 -2
  25. package/dist/generators/htmx/generateHTMXPage.d.ts +1 -1
  26. package/dist/generators/htmx/generateHTMXPage.js +2 -2
  27. package/dist/generators/htmx/scaffoldHTMX.d.ts +1 -1
  28. package/dist/generators/htmx/scaffoldHTMX.js +2 -2
  29. package/dist/generators/project/computeFlags.d.ts +1 -0
  30. package/dist/generators/project/computeFlags.js +1 -0
  31. package/dist/generators/project/generateBuildBlock.d.ts +2 -1
  32. package/dist/generators/project/generateBuildBlock.js +11 -7
  33. package/dist/generators/project/generateDBBlock.js +6 -0
  34. package/dist/generators/project/generateImportsBlock.js +39 -27
  35. package/dist/generators/project/generateMarkupCSS.js +4 -0
  36. package/dist/generators/project/generateRoutesBlock.d.ts +1 -2
  37. package/dist/generators/project/generateRoutesBlock.js +28 -17
  38. package/dist/generators/project/generateServer.js +5 -10
  39. package/dist/generators/project/scaffoldFrontends.js +20 -1
  40. package/dist/generators/react/generateReactComponents.d.ts +2 -2
  41. package/dist/generators/react/generateReactComponents.js +33 -33
  42. package/dist/generators/react/scaffoldReact.d.ts +1 -1
  43. package/dist/generators/react/scaffoldReact.js +2 -2
  44. package/dist/generators/svelte/generateSveltePage.d.ts +1 -1
  45. package/dist/generators/svelte/generateSveltePage.js +20 -2
  46. package/dist/generators/svelte/scaffoldSvelte.d.ts +1 -1
  47. package/dist/generators/svelte/scaffoldSvelte.js +2 -2
  48. package/dist/generators/vue/generateVuePage.d.ts +1 -1
  49. package/dist/generators/vue/generateVuePage.js +20 -2
  50. package/dist/generators/vue/scaffoldVue.d.ts +1 -1
  51. package/dist/generators/vue/scaffoldVue.js +2 -2
  52. package/dist/questions/databaseEngine.d.ts +1 -1
  53. package/dist/questions/frontendDirectoryConfigurations.d.ts +1 -1
  54. package/dist/questions/frontends.d.ts +1 -1
  55. package/dist/questions/frontends.js +3 -3
  56. package/dist/scaffold.js +14 -2
  57. package/dist/templates/assets/svg/angular.svg +18 -0
  58. package/dist/templates/configurations/{eslint.config.mjs → eslint.config.example.mjs} +10 -10
  59. package/dist/templates/configurations/tsconfig.example.json +12 -12
  60. package/dist/templates/react/components/App.tsx +2 -2
  61. package/dist/templates/react/components/Head.tsx +7 -7
  62. package/dist/templates/react/components/OAuthLink.tsx +2 -2
  63. package/dist/templates/styles/colors.ts +6 -8
  64. package/dist/templates/styles/reset.css +15 -0
  65. package/dist/templates/svelte/components/Counter.svelte +4 -0
  66. package/dist/templates/vue/components/CountButton.vue +1 -1
  67. package/dist/typeGuards.d.ts +6 -6
  68. package/dist/typeGuards.js +6 -6
  69. package/dist/types.d.ts +1 -0
  70. package/dist/utils/checkDockerInstalled.d.ts +4 -4
  71. package/dist/utils/checkDockerInstalled.js +78 -71
  72. package/dist/utils/parseCommandLineOptions.js +13 -16
  73. package/dist/versions.d.ts +45 -33
  74. package/dist/versions.js +55 -42
  75. package/package.json +10 -9
@@ -0,0 +1,253 @@
1
+ export const generateEslintConfig = (frontends) => {
2
+ const hasReact = frontends.includes('react');
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';
49
+ 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
+ import tseslint from 'typescript-eslint';
56
+
57
+ const __dirname = dirname(fileURLToPath(import.meta.url));
58
+
59
+ export default defineConfig([
60
+ pluginJs.configs.recommended,
61
+
62
+ ...tseslint.configs.recommended,
63
+
64
+ {
65
+ files: ['**/*.{ts,tsx}'],
66
+ languageOptions: {
67
+ globals: globals.browser,
68
+ parser: tsParser,
69
+ parserOptions: {
70
+ createDefaultProgram: true,
71
+ project: './tsconfig.json',
72
+ tsconfigRootDir: __dirname
73
+ }
74
+ }
75
+ },
76
+
77
+ {
78
+ files: ['**/*.{ts,tsx}'],
79
+ plugins: { '@stylistic': stylistic },
80
+ rules: {
81
+ '@stylistic/padding-line-between-statements': [
82
+ 'error',
83
+ { blankLine: 'always', next: 'return', prev: '*' }
84
+ ]
85
+ }
86
+ },
87
+
88
+ {
89
+ files: ['**/*.{js,mjs,cjs,ts,tsx,jsx}'],
90
+ ignores: ['example/build/**'],
91
+ plugins: {
92
+ absolute: fixupPluginRules(absolutePlugin),
93
+ import: fixupPluginRules(importPlugin),
94
+ promise: fixupPluginRules(promisePlugin),
95
+ security: fixupPluginRules(securityPlugin)
96
+ },
97
+ rules: {
98
+ 'absolute/explicit-object-types': 'error',
99
+ 'absolute/localize-react-props': 'error',
100
+ 'absolute/max-depth-extended': ['error', 1],
101
+ 'absolute/max-jsxnesting': ['error', 5],
102
+ 'absolute/min-var-length': [
103
+ 'error',
104
+ { allowedVars: ['_', 'id', 'db', 'OK'], minLength: 3 }
105
+ ],
106
+ 'absolute/no-button-navigation': 'error',
107
+ 'absolute/no-explicit-return-type': 'error',
108
+ 'absolute/no-inline-prop-types': 'error',
109
+ 'absolute/no-multi-style-objects': 'error',
110
+ 'absolute/no-nested-jsx-return': 'error',
111
+ 'absolute/no-or-none-component': 'error',
112
+ 'absolute/no-transition-cssproperties': 'error',
113
+ 'absolute/no-unnecessary-div': 'error',
114
+ 'absolute/no-unnecessary-key': 'error',
115
+ 'absolute/no-useless-function': 'error',
116
+ 'absolute/seperate-style-files': 'error',
117
+ 'absolute/sort-exports': [
118
+ 'error',
119
+ {
120
+ caseSensitive: true,
121
+ natural: true,
122
+ order: 'asc',
123
+ variablesBeforeFunctions: true
124
+ }
125
+ ],
126
+ 'absolute/sort-keys-fixable': [
127
+ 'error',
128
+ {
129
+ caseSensitive: true,
130
+ natural: true,
131
+ order: 'asc',
132
+ variablesBeforeFunctions: true
133
+ }
134
+ ],
135
+ 'arrow-body-style': ['error', 'as-needed'],
136
+ 'consistent-return': 'error',
137
+ eqeqeq: 'error',
138
+ 'func-style': [
139
+ 'error',
140
+ 'expression',
141
+ { allowArrowFunctions: true }
142
+ ],
143
+ 'import/no-cycle': 'error',
144
+ 'import/no-default-export': 'error',
145
+ 'import/no-relative-packages': 'error',
146
+ 'import/no-unused-modules': ['error', { missingExports: true }],
147
+ 'import/order': ['error', { alphabetize: { order: 'asc' } }],
148
+ 'no-await-in-loop': 'error',
149
+ 'no-console': ['error', { allow: ['warn', 'error'] }],
150
+ 'no-debugger': 'error',
151
+ 'no-duplicate-case': 'error',
152
+ 'no-duplicate-imports': 'error',
153
+ 'no-else-return': 'error',
154
+ 'no-empty-function': 'error',
155
+ 'no-empty-pattern': 'error',
156
+ 'no-empty-static-block': 'error',
157
+ 'no-fallthrough': 'error',
158
+ 'no-floating-decimal': 'error',
159
+ 'no-global-assign': 'error',
160
+ 'no-implicit-coercion': 'error',
161
+ 'no-implicit-globals': 'error',
162
+ 'no-loop-func': 'error',
163
+ 'no-magic-numbers': [
164
+ 'warn',
165
+ { detectObjects: false, enforceConst: true, ignore: [0, 1] }
166
+ ],
167
+ 'no-misleading-character-class': 'error',
168
+ 'no-nested-ternary': 'error',
169
+ 'no-new-native-nonconstructor': 'error',
170
+ 'no-new-wrappers': 'error',
171
+ 'no-param-reassign': 'error',
172
+ 'no-restricted-imports': [
173
+ 'error',
174
+ {
175
+ paths: [
176
+ {
177
+ importNames: ['default'],
178
+ message:
179
+ 'Import only named React exports for tree-shaking.',
180
+ name: 'react'
181
+ },
182
+ {
183
+ importNames: ['default'],
184
+ message: 'Import only the required Bun exports.',
185
+ name: 'bun'
186
+ }
187
+ ]
188
+ }
189
+ ],
190
+ 'no-return-await': 'error',
191
+ 'no-shadow': 'error',
192
+ 'no-undef': 'error',
193
+ 'no-unneeded-ternary': 'error',
194
+ 'no-unreachable': 'error',
195
+ 'no-useless-assignment': 'error',
196
+ 'no-useless-concat': 'error',
197
+ 'no-useless-return': 'error',
198
+ 'no-var': 'error',
199
+ 'prefer-arrow-callback': 'error',
200
+ 'prefer-const': 'error',
201
+ 'prefer-destructuring': [
202
+ 'error',
203
+ { array: true, object: true },
204
+ { enforceForRenamedProperties: false }
205
+ ],
206
+ 'prefer-template': 'error',
207
+ 'promise/always-return': 'warn',
208
+ 'promise/avoid-new': 'warn',
209
+ 'promise/catch-or-return': 'error',
210
+ 'promise/no-callback-in-promise': 'warn',
211
+ 'promise/no-nesting': 'warn',
212
+ 'promise/no-promise-in-callback': 'warn',
213
+ 'promise/no-return-wrap': 'error',
214
+ 'promise/param-names': 'error'
215
+ }
216
+ },
217
+ ${reactBlock} {
218
+ files: [
219
+ 'example/server.ts',
220
+ 'example/indexes/*.tsx',
221
+ 'example/db/migrate.ts'
222
+ ],
223
+ rules: {
224
+ 'import/no-unused-modules': 'off'
225
+ }
226
+ },
227
+ {
228
+ files: ['example/db/migrate.ts', 'example/utils/absoluteAuthConfig.ts'],
229
+ rules: {
230
+ 'no-console': 'off'
231
+ }
232
+ },
233
+ {
234
+ files: ['eslint.config.mjs'],
235
+ rules: {
236
+ 'no-magic-numbers': 'off'
237
+ }
238
+ },
239
+ {
240
+ files: ['eslint.config.mjs'],
241
+ rules: {
242
+ 'import/no-default-export': 'off'
243
+ }
244
+ },
245
+ {
246
+ files: ['example/db/schema.ts'],
247
+ rules: {
248
+ 'absolute/explicit-object-types': 'off'
249
+ }
250
+ }
251
+ ]);
252
+ `;
253
+ };
@@ -52,6 +52,15 @@ export const createPackageJson = async ({ projectName, authOption, plugins, data
52
52
  for (const dep of eslintReactDependencies)
53
53
  packageNames.add(dep.value);
54
54
  }
55
+ if (flags.requiresAngular) {
56
+ packageNames.add('@angular/common');
57
+ packageNames.add('@angular/compiler');
58
+ packageNames.add('@angular/compiler-cli');
59
+ packageNames.add('@angular/core');
60
+ packageNames.add('@angular/platform-browser');
61
+ packageNames.add('@angular/platform-server');
62
+ packageNames.add('@angular/ssr');
63
+ }
55
64
  if (flags.requiresSvelte)
56
65
  packageNames.add('svelte');
57
66
  if (flags.requiresSvelte && codeQualityTool === 'eslint+prettier')
@@ -139,6 +148,20 @@ export const createPackageJson = async ({ projectName, authOption, plugins, data
139
148
  devDependencies[dep.value] = resolveVersion(dep.value, dep.latestVersion);
140
149
  });
141
150
  }
151
+ if (flags.requiresAngular) {
152
+ const angularPackages = [
153
+ '@angular/common',
154
+ '@angular/compiler',
155
+ '@angular/compiler-cli',
156
+ '@angular/core',
157
+ '@angular/platform-browser',
158
+ '@angular/platform-server',
159
+ '@angular/ssr'
160
+ ];
161
+ angularPackages.forEach((pkg) => {
162
+ dependencies[pkg] = resolveVersion(pkg, versions[pkg]);
163
+ });
164
+ }
142
165
  if (flags.requiresSvelte) {
143
166
  dependencies['svelte'] = resolveVersion('svelte', versions['svelte']);
144
167
  }
@@ -168,9 +191,9 @@ export const createPackageJson = async ({ projectName, authOption, plugins, data
168
191
  if (latest)
169
192
  s.stop(green('Package versions resolved'));
170
193
  const scripts = {
171
- dev: 'absolutejs dev',
172
- format: `absolutejs prettier --write "./**/*.{js,ts,css,json,mjs,md${flags.requiresReact ? ',jsx,tsx' : ''}${flags.requiresSvelte ? ',svelte' : ''}${flags.requiresVue ? ',vue' : ''}${flags.requiresHtml || flags.requiresHtmx ? ',html' : ''}}"`,
173
- lint: 'absolutejs eslint',
194
+ dev: 'absolute dev',
195
+ format: `absolute prettier --write "./**/*.{js,ts,css,json,mjs,md${flags.requiresReact ? ',jsx,tsx' : ''}${flags.requiresSvelte ? ',svelte' : ''}${flags.requiresVue ? ',vue' : ''}${flags.requiresHtml || flags.requiresHtmx ? ',html' : ''}}"`,
196
+ lint: 'absolute eslint',
174
197
  test: 'echo "Error: no test specified" && exit 1',
175
198
  typecheck: 'bun run tsc --noEmit'
176
199
  };
@@ -1,10 +1,35 @@
1
- import { copyFileSync, writeFileSync } from 'fs';
1
+ import { copyFileSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { dim, yellow } from 'picocolors';
4
4
  import { generateEnv } from './generateEnv';
5
+ import { generateEslintConfig } from './generateEslintConfig';
5
6
  import { generatePrettierrc } from './generatePrettierrc';
6
7
  export const scaffoldConfigurationFiles = ({ tailwind, templatesDirectory, databaseEngine, envVariables, databaseHost, codeQualityTool, frontends, initializeGitNow, projectName }) => {
7
- copyFileSync(join(templatesDirectory, 'configurations', 'tsconfig.example.json'), join(projectName, 'tsconfig.json'));
8
+ const hasAngular = frontends.includes('angular');
9
+ if (hasAngular) {
10
+ const templatePath = join(templatesDirectory, 'configurations', 'tsconfig.example.json');
11
+ const raw = readFileSync(templatePath, 'utf-8');
12
+ const stripped = raw
13
+ .replace(/\/\*[\s\S]*?\*\//g, '')
14
+ .replace(/\/\/[^\n]*/g, '')
15
+ .replace(/,\s*([}\]])/g, '$1');
16
+ const tsconfig = JSON.parse(stripped);
17
+ tsconfig.compilerOptions['emitDecoratorMetadata'] = true;
18
+ tsconfig.compilerOptions['experimentalDecorators'] = true;
19
+ tsconfig.compilerOptions['useDefineForClassFields'] = false;
20
+ const withAngular = {
21
+ angularCompilerOptions: {
22
+ enableI18nLegacyMessageIdFormat: false,
23
+ strictInjectionParameters: true,
24
+ strictTemplates: true
25
+ },
26
+ ...tsconfig
27
+ };
28
+ writeFileSync(join(projectName, 'tsconfig.json'), JSON.stringify(withAngular, null, '\t'));
29
+ }
30
+ else {
31
+ copyFileSync(join(templatesDirectory, 'configurations', 'tsconfig.example.json'), join(projectName, 'tsconfig.json'));
32
+ }
8
33
  if (tailwind) {
9
34
  copyFileSync(join(templatesDirectory, 'tailwind', 'postcss.config.ts'), join(projectName, 'postcss.config.ts'));
10
35
  copyFileSync(join(templatesDirectory, 'tailwind', 'tailwind.config.ts'), join(projectName, 'tailwind.config.ts'));
@@ -12,7 +37,7 @@ export const scaffoldConfigurationFiles = ({ tailwind, templatesDirectory, datab
12
37
  if (initializeGitNow)
13
38
  copyFileSync(join(templatesDirectory, 'git', 'gitignore'), join(projectName, '.gitignore'));
14
39
  if (codeQualityTool === 'eslint+prettier') {
15
- copyFileSync(join(templatesDirectory, 'configurations', 'eslint.config.mjs'), join(projectName, 'eslint.config.mjs'));
40
+ writeFileSync(join(projectName, 'eslint.config.mjs'), generateEslintConfig(frontends));
16
41
  copyFileSync(join(templatesDirectory, 'configurations', '.prettierignore'), join(projectName, '.prettierignore'));
17
42
  const prettierrc = generatePrettierrc(frontends);
18
43
  writeFileSync(join(projectName, '.prettierrc.json'), prettierrc);
@@ -1,12 +1,3 @@
1
- export declare const userTables: {
2
- readonly cockroachdb: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'::jsonb\n);";
3
- readonly gel: "create type users {\n create required property auth_sub: str {\n create constraint exclusive;\n };\n\n create required property created_at: datetime {\n set default := datetime_current();\n };\n\n create required property metadata: json {\n set default := to_json('{}');\n };\n};";
4
- readonly mariadb: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT ('{}')\n);";
5
- readonly mssql: "IF OBJECT_ID('users','U') IS NULL\nBEGIN\n CREATE TABLE users (\n auth_sub NVARCHAR(255) PRIMARY KEY,\n created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),\n metadata NVARCHAR(MAX) NULL\n );\nEND;";
6
- readonly mysql: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT (JSON_OBJECT())\n);";
7
- readonly postgresql: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'::jsonb\n);";
8
- readonly singlestore: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT ('{}')\n);";
9
- };
10
1
  export declare const countHistoryTables: {
11
2
  readonly cockroachdb: "CREATE SEQUENCE IF NOT EXISTS count_history_uid_seq START WITH 1 INCREMENT BY 1;\nCREATE TABLE IF NOT EXISTS count_history (\n uid BIGINT PRIMARY KEY DEFAULT nextval('count_history_uid_seq'),\n count INT NOT NULL,\n created_at TIMESTAMP NOT NULL DEFAULT NOW()\n);";
12
3
  readonly gel: "create scalar type CountHistoryUid extending sequence;\ncreate type count_history {\n create required property uid: CountHistoryUid {\n create constraint exclusive;\n set default := sequence_next(introspect CountHistoryUid);\n };\n\n create required property count: int16;\n\n create required property created_at: datetime {\n set default := datetime_current();\n };\n};";
@@ -50,3 +41,12 @@ export declare const initTemplates: {
50
41
  readonly wait: "until singlestore -u root -ppassword -e \"SELECT 1\" >/dev/null 2>&1; do sleep 1; done";
51
42
  };
52
43
  };
44
+ export declare const userTables: {
45
+ readonly cockroachdb: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'::jsonb\n);";
46
+ readonly gel: "create type users {\n create required property auth_sub: str {\n create constraint exclusive;\n };\n\n create required property created_at: datetime {\n set default := datetime_current();\n };\n\n create required property metadata: json {\n set default := to_json('{}');\n };\n};";
47
+ readonly mariadb: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT ('{}')\n);";
48
+ readonly mssql: "IF OBJECT_ID('users','U') IS NULL\nBEGIN\n CREATE TABLE users (\n auth_sub NVARCHAR(255) PRIMARY KEY,\n created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),\n metadata NVARCHAR(MAX) NULL\n );\nEND;";
49
+ readonly mysql: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT (JSON_OBJECT())\n);";
50
+ readonly postgresql: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n metadata JSONB DEFAULT '{}'::jsonb\n);";
51
+ readonly singlestore: "CREATE TABLE IF NOT EXISTS users (\n auth_sub VARCHAR(255) PRIMARY KEY,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n metadata JSON DEFAULT ('{}')\n);";
52
+ };
@@ -91,15 +91,6 @@ create type count_history {
91
91
  set default := datetime_current();
92
92
  };
93
93
  };`;
94
- export const userTables = {
95
- cockroachdb: cockroachdbUsers,
96
- gel: gelUsers,
97
- mariadb: mariadbUsers,
98
- mssql: mssqlUsers,
99
- mysql: mysqlUsers,
100
- postgresql: postgresqlUsers,
101
- singlestore: singlestoreUsers
102
- };
103
94
  export const countHistoryTables = {
104
95
  cockroachdb: cockroachdbCountHistory,
105
96
  gel: gelCountHistory,
@@ -143,3 +134,12 @@ export const initTemplates = {
143
134
  wait: 'until singlestore -u root -ppassword -e "SELECT 1" >/dev/null 2>&1; do sleep 1; done'
144
135
  }
145
136
  };
137
+ export const userTables = {
138
+ cockroachdb: cockroachdbUsers,
139
+ gel: gelUsers,
140
+ mariadb: mariadbUsers,
141
+ mssql: mssqlUsers,
142
+ mysql: mysqlUsers,
143
+ postgresql: postgresqlUsers,
144
+ singlestore: singlestoreUsers
145
+ };
@@ -34,6 +34,11 @@ export const generateDatabaseTypes = ({ databaseEngine, databaseHost, authOption
34
34
  dbTypeLine =
35
35
  'export type DatabaseType = Mysql2Database<SchemaType>;';
36
36
  break;
37
+ case 'mssql':
38
+ dbImport = `import { NodeMssqlDatabase } from 'drizzle-orm/node-mssql';`;
39
+ dbTypeLine =
40
+ 'export type DatabaseType = NodeMssqlDatabase<SchemaType>;';
41
+ break;
37
42
  case 'postgresql':
38
43
  dbImport = `import { BunSQLDatabase } from 'drizzle-orm/bun-sql';`;
39
44
  dbTypeLine =
@@ -104,6 +104,7 @@ const templates = {
104
104
  test: 'singlestore -u root -ppassword -e "SELECT 1" >/dev/null 2>&1'
105
105
  },
106
106
  image: 'ghcr.io/singlestore-labs/singlestoredb-dev', // NOTE: No tag specified due to data persistence
107
+ platform: 'linux/amd64', // Required for ARM64 (Apple Silicon); no-op on amd64
107
108
  port: '3306:3306',
108
109
  volumePath: '/data'
109
110
  }
@@ -114,14 +115,15 @@ export const generateDockerContainer = (databaseEngine) => {
114
115
  databaseEngine === 'sqlite') {
115
116
  throw new Error('Internal type error: Expected a valid local database engine');
116
117
  }
117
- const { command, env, healthcheck, image, port, volumePath } = templates[databaseEngine];
118
+ const { command, env, healthcheck, image, platform, port, volumePath } = templates[databaseEngine];
118
119
  const commandLine = command ? `\n command: ${command}` : '';
120
+ const platformLine = platform ? `\n platform: ${platform}` : '';
119
121
  const envLines = Object.entries(env)
120
122
  .map(([key, value]) => ` ${key}: ${value}`)
121
123
  .join('\n');
122
124
  return `services:
123
125
  db:
124
- image: ${image}
126
+ image: ${image}${platformLine}
125
127
  restart: always
126
128
  environment:
127
129
  ${envLines}
@@ -15,6 +15,14 @@ const DIALECTS = {
15
15
  table: 'mysqlTable',
16
16
  time: 'timestamp()'
17
17
  },
18
+ mssql: {
19
+ builders: ['datetime2', 'int', 'mssqlTable', 'nvarchar', 'json'],
20
+ json: "nvarchar({ length: 'max', mode: 'json' })",
21
+ pkg: 'mssql-core',
22
+ string: 'nvarchar({ length: 255 })',
23
+ table: 'mssqlTable',
24
+ time: 'datetime2()'
25
+ },
18
26
  mysql: {
19
27
  builders: ['json', 'mysqlTable', 'timestamp', 'varchar', 'int'],
20
28
  json: 'json()',
@@ -51,9 +59,10 @@ const DIALECTS = {
51
59
  const builder = (expr) => expr.split('(')[0];
52
60
  export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
53
61
  const cfg = DIALECTS[databaseEngine];
54
- const intBuilder = databaseEngine === 'mysql' ||
55
- databaseEngine === 'singlestore' ||
56
- databaseEngine === 'mariadb'
62
+ const intBuilder = databaseEngine === 'mariadb' ||
63
+ databaseEngine === 'mssql' ||
64
+ databaseEngine === 'mysql' ||
65
+ databaseEngine === 'singlestore'
57
66
  ? 'int'
58
67
  : 'integer';
59
68
  const timeBuilder = builder(cfg.time);
@@ -68,9 +77,10 @@ export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
68
77
  ? `import { sql } from 'drizzle-orm';\n`
69
78
  : '';
70
79
  let uidColumn;
71
- if (databaseEngine === 'mysql' ||
72
- databaseEngine === 'singlestore' ||
73
- databaseEngine === 'mariadb') {
80
+ if (databaseEngine === 'mariadb' ||
81
+ databaseEngine === 'mssql' ||
82
+ databaseEngine === 'mysql' ||
83
+ databaseEngine === 'singlestore') {
74
84
  uidColumn = `${intBuilder}('uid').primaryKey().autoincrement()`;
75
85
  }
76
86
  else if (databaseEngine === 'sqlite') {
@@ -35,6 +35,11 @@ declare const driverConfigurations: {
35
35
  readonly importLines: "";
36
36
  readonly queries: QueryOperations;
37
37
  };
38
+ readonly 'mssql:drizzle:local': {
39
+ readonly dbType: "NodeMssqlDatabase<SchemaType>";
40
+ readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
41
+ readonly queries: QueryOperations;
42
+ };
38
43
  readonly 'mssql:sql:local': {
39
44
  readonly dbType: "ConnectionPool";
40
45
  readonly importLines: "";
@@ -353,6 +353,12 @@ import { schema } from '../../../db/schema'`,
353
353
  importLines: ``,
354
354
  queries: mongodbQueryOperations
355
355
  },
356
+ 'mssql:drizzle:local': {
357
+ dbType: 'NodeMssqlDatabase<SchemaType>',
358
+ importLines: `import { eq } from 'drizzle-orm'
359
+ import { schema } from '../../../db/schema'`,
360
+ queries: drizzleQueryOperations
361
+ },
356
362
  'mssql:sql:local': {
357
363
  dbType: 'ConnectionPool',
358
364
  importLines: ``,
@@ -6,6 +6,33 @@ import { green, red } from 'picocolors';
6
6
  import { checkDockerInstalled, ensureDockerDaemonRunning, resolveDockerExe, shutdownDockerDaemon } from '../../utils/checkDockerInstalled';
7
7
  import { countHistoryTables, initTemplates, userTables } from './dockerInitTemplates';
8
8
  import { generateDockerContainer } from './generateDockerContainer';
9
+ const initDockerSchema = async ({ authOption, databaseEngine, docker, projectName, spin }) => {
10
+ const dbKey = databaseEngine;
11
+ const { wait, cli } = initTemplates[dbKey];
12
+ const usesAuth = authOption !== undefined && authOption !== 'none';
13
+ const dbCommand = usesAuth ? userTables[dbKey] : countHistoryTables[dbKey];
14
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d db`
15
+ .cwd(projectName)
16
+ .quiet();
17
+ spin.message(`Initializing ${databaseEngine} schema`);
18
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml exec -T db \
19
+ bash -lc '${wait} && ${cli} "${dbCommand}"'`
20
+ .cwd(projectName)
21
+ .quiet();
22
+ spin.message(`Stopping ${databaseEngine} container`);
23
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`
24
+ .cwd(projectName)
25
+ .quiet();
26
+ };
27
+ const verifyDockerContainer = async ({ databaseEngine, docker, projectName, spin }) => {
28
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d --wait db`
29
+ .cwd(projectName)
30
+ .quiet();
31
+ spin.message(`Stopping ${databaseEngine} container`);
32
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`
33
+ .cwd(projectName)
34
+ .quiet();
35
+ };
9
36
  export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }) => {
10
37
  if (databaseEngine === undefined ||
11
38
  databaseEngine === 'none' ||
@@ -19,37 +46,22 @@ export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory,
19
46
  const docker = resolveDockerExe();
20
47
  const spin = spinner();
21
48
  spin.start(`Starting ${databaseEngine} container`);
49
+ const dockerAction = databaseEngine in userTables
50
+ ? () => initDockerSchema({
51
+ authOption,
52
+ databaseEngine,
53
+ docker,
54
+ projectName,
55
+ spin
56
+ })
57
+ : () => verifyDockerContainer({
58
+ databaseEngine,
59
+ docker,
60
+ projectName,
61
+ spin
62
+ });
22
63
  try {
23
- const hasSchemaInit = databaseEngine in userTables;
24
- if (hasSchemaInit) {
25
- const dbKey = databaseEngine;
26
- const { wait, cli } = initTemplates[dbKey];
27
- const usesAuth = authOption !== undefined && authOption !== 'none';
28
- const dbCommand = usesAuth
29
- ? userTables[dbKey]
30
- : countHistoryTables[dbKey];
31
- await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d db`
32
- .cwd(projectName)
33
- .quiet();
34
- spin.message(`Initializing ${databaseEngine} schema`);
35
- await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml exec -T db \
36
- bash -lc '${wait} && ${cli} "${dbCommand}"'`
37
- .cwd(projectName)
38
- .quiet();
39
- spin.message(`Stopping ${databaseEngine} container`);
40
- await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`
41
- .cwd(projectName)
42
- .quiet();
43
- }
44
- else {
45
- await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d --wait db`
46
- .cwd(projectName)
47
- .quiet();
48
- spin.message(`Stopping ${databaseEngine} container`);
49
- await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`
50
- .cwd(projectName)
51
- .quiet();
52
- }
64
+ await dockerAction();
53
65
  spin.stop(green('Docker container verified'));
54
66
  }
55
67
  catch (err) {
@@ -1,2 +1,2 @@
1
1
  import { CreateConfiguration, Frontend } from '../../types';
2
- export declare const generateHTMLPage: (frontends: Frontend[], useHTMLScripts: CreateConfiguration["useHTMLScripts"]) => string;
2
+ export declare const generateHTMLPage: (frontends: Frontend[], useHTMLScripts: CreateConfiguration["useHTMLScripts"], editBasePath: string) => string;
@@ -1,5 +1,5 @@
1
1
  import { formatNavLink } from '../../utils/formatNavLink';
2
- export const generateHTMLPage = (frontends, useHTMLScripts) => {
2
+ export const generateHTMLPage = (frontends, useHTMLScripts, editBasePath) => {
3
3
  const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
4
4
  const initialCount = useHTMLScripts ? '0' : 'disabled';
5
5
  const scriptTagBlock = useHTMLScripts
@@ -54,7 +54,7 @@ export const generateHTMLPage = (frontends, useHTMLScripts) => {
54
54
  count is <span id="counter">${initialCount}</span>
55
55
  </button>
56
56
  <p>
57
- Edit <code>example/html/pages/HtmlExample.html</code> and save
57
+ Edit <code>${editBasePath}/pages/HTMLExample.html</code> and save
58
58
  to test HMR.
59
59
  </p>
60
60
  ${frontends.length > 1
@@ -2,5 +2,5 @@ import { ScaffoldFrontendProps } from '../../types';
2
2
  type ScaffoldHTMLProps = ScaffoldFrontendProps & {
3
3
  useHTMLScripts: boolean;
4
4
  };
5
- export declare const scaffoldHTML: ({ isSingleFrontend, targetDirectory, frontends, useHTMLScripts, templatesDirectory, projectAssetsDirectory }: ScaffoldHTMLProps) => void;
5
+ export declare const scaffoldHTML: ({ editBasePath, isSingleFrontend, targetDirectory, frontends, useHTMLScripts, templatesDirectory, projectAssetsDirectory }: ScaffoldHTMLProps) => void;
6
6
  export {};
@@ -2,9 +2,9 @@ import { copyFileSync, cpSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { generateMarkupCSS } from '../project/generateMarkupCSS';
4
4
  import { generateHTMLPage } from './generateHTMLPage';
5
- export const scaffoldHTML = ({ isSingleFrontend, targetDirectory, frontends, useHTMLScripts, templatesDirectory, projectAssetsDirectory }) => {
5
+ export const scaffoldHTML = ({ editBasePath, 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, useHTMLScripts);
7
+ const htmlPage = generateHTMLPage(frontends, useHTMLScripts, editBasePath);
8
8
  const pagesDirectory = join(targetDirectory, 'pages');
9
9
  mkdirSync(pagesDirectory, { recursive: true });
10
10
  const htmlFilePath = join(pagesDirectory, 'HTMLExample.html');
@@ -1,2 +1,2 @@
1
1
  import { Frontend } from '../../types';
2
- export declare const generateHTMXPage: (isSingle: boolean, frontends: Frontend[]) => string;
2
+ export declare const generateHTMXPage: (isSingle: boolean, frontends: Frontend[], editBasePath: string) => string;