create-absolutejs 0.6.1 → 0.7.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 (67) hide show
  1. package/LICENSE +24 -24
  2. package/README.md +179 -179
  3. package/dist/commands/formatProject.js +3 -2
  4. package/dist/commands/initializeGit.js +1 -1
  5. package/dist/commands/installDependencies.js +4 -3
  6. package/dist/data.js +22 -21
  7. package/dist/generators/configurations/generateDrizzleConfig.js +15 -15
  8. package/dist/generators/configurations/generatePackageJson.js +25 -23
  9. package/dist/generators/configurations/generatePrettierrc.js +9 -9
  10. package/dist/generators/db/dockerInitTemplates.js +79 -79
  11. package/dist/generators/db/generateDatabaseTypes.js +6 -6
  12. package/dist/generators/db/generateDockerContainer.js +14 -14
  13. package/dist/generators/db/generateDrizzleSchema.js +17 -17
  14. package/dist/generators/db/generateSqliteSchema.js +8 -8
  15. package/dist/generators/db/handlerTemplates.js +259 -259
  16. package/dist/generators/db/scaffoldDocker.js +1 -1
  17. package/dist/generators/html/generateHTMLPage.js +60 -60
  18. package/dist/generators/htmx/generateHTMXPage.js +86 -86
  19. package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +1 -1
  20. package/dist/generators/project/generateAbsoluteAuthConfig.js +100 -89
  21. package/dist/generators/project/generateDBBlock.js +9 -9
  22. package/dist/generators/project/generateMarkupCSS.js +145 -145
  23. package/dist/generators/project/generateRoutesBlock.d.ts +3 -2
  24. package/dist/generators/project/generateRoutesBlock.js +37 -36
  25. package/dist/generators/project/generateServer.js +22 -18
  26. package/dist/generators/project/scaffoldBackend.js +2 -1
  27. package/dist/generators/project/scaffoldFrontends.d.ts +2 -2
  28. package/dist/generators/project/scaffoldFrontends.js +5 -2
  29. package/dist/generators/react/generateReactComponents.js +95 -95
  30. package/dist/generators/svelte/generateSveltePage.js +210 -210
  31. package/dist/generators/vue/generateVuePage.js +261 -261
  32. package/dist/messages.js +43 -43
  33. package/dist/questions/projectName.js +1 -1
  34. package/dist/scaffold.js +2 -1
  35. package/dist/templates/README.md +35 -35
  36. package/dist/templates/assets/svg/google-logo.svg +7 -7
  37. package/dist/templates/assets/svg/htmx-logo-black.svg +9 -9
  38. package/dist/templates/assets/svg/htmx-logo-white.svg +9 -9
  39. package/dist/templates/assets/svg/vue-logo.svg +4 -4
  40. package/dist/templates/configurations/.prettierignore +3 -3
  41. package/dist/templates/configurations/.prettierrc.json +9 -9
  42. package/dist/templates/configurations/drizzle.config.ts +13 -13
  43. package/dist/templates/configurations/eslint.config.mjs +243 -243
  44. package/dist/templates/configurations/tsconfig.example.json +98 -98
  45. package/dist/templates/constants.ts +2 -2
  46. package/dist/templates/db/docker-compose.db.yml +15 -15
  47. package/dist/templates/git/gitignore +51 -51
  48. package/dist/templates/html/scripts/typescript-example.ts +21 -21
  49. package/dist/templates/react/components/App.tsx +52 -52
  50. package/dist/templates/react/components/Head.tsx +34 -34
  51. package/dist/templates/react/components/OAuthLink.tsx +39 -39
  52. package/dist/templates/react/components/ProfilePicture.tsx +56 -56
  53. package/dist/templates/styles/colors.ts +11 -11
  54. package/dist/templates/styles/reset.css +84 -84
  55. package/dist/templates/svelte/components/Counter.svelte +19 -19
  56. package/dist/templates/svelte/composables/counter.svelte.ts +14 -14
  57. package/dist/templates/tailwind/postcss.config.ts +8 -8
  58. package/dist/templates/tailwind/tailwind.config.ts +7 -7
  59. package/dist/templates/tailwind/tailwind.css +1 -1
  60. package/dist/templates/vue/components/CountButton.vue +39 -39
  61. package/dist/templates/vue/composables/useCount.ts +14 -14
  62. package/dist/utils/commandMaps.d.ts +1 -1
  63. package/dist/utils/commandMaps.js +4 -4
  64. package/dist/versions.d.ts +49 -0
  65. package/dist/versions.js +61 -0
  66. package/package.json +22 -21
  67. package/dist/templates/styles/tailwind.css +0 -1
@@ -1,148 +1,148 @@
1
- export const generateMarkupCSS = (frontend, color, isSingleFrontend) => `@import url('${isSingleFrontend ? '../styles/reset.css' : '../../styles/reset.css'}');
2
-
3
- header {
4
- align-items: center;
5
- background-color: #1a1a1a;
6
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
7
- display: flex;
8
- justify-content: space-between;
9
- padding: 2rem;
10
- text-align: center;
11
- }
12
-
13
- header a {
14
- position: relative;
15
- color: #5fbeeb;
16
- text-decoration: none;
17
- }
18
-
19
- header a::after {
20
- content: '';
21
- position: absolute;
22
- left: 0;
23
- bottom: 0;
24
- width: 100%;
25
- height: 2px;
26
- background: linear-gradient(90deg, #5fbeeb 0%, #35d5a2 50%, #ff4b91 100%);
27
- transform: scaleX(0);
28
- transform-origin: left;
29
- transition: transform 0.25s ease-in-out;
30
- }
31
-
32
- header a:hover::after {
33
- transform: scaleX(1);
34
- }
35
-
36
- h1 {
37
- font-size: 2.5rem;
38
- margin-top: 2rem;
39
- }
40
-
41
- .logo {
42
- height: 8rem;
43
- width: 8rem;
44
- will-change: filter;
45
- transition: filter 300ms;
46
- }
47
-
48
- .logo:hover {
49
- filter: drop-shadow(0 0 2rem #5fbeeb);
50
- }
51
-
52
- .logo.${frontend}:hover {
53
- filter: drop-shadow(0 0 2rem ${color});
54
- }
55
-
56
- nav {
57
- display: flex;
58
- gap: 4rem;
59
- justify-content: center;
60
- }
61
-
62
- header details {
63
- position: relative;
64
- }
65
-
66
- header details summary {
67
- list-style: none;
68
- appearance: none;
69
- -webkit-appearance: none;
70
- cursor: pointer;
71
- user-select: none;
72
- color: #5fbeeb;
73
- font-size: 1.5rem;
74
- font-weight: 500;
75
- padding: 0.5rem 1rem;
76
- }
77
-
78
- header summary::after {
79
- content: '▼';
80
- display: inline-block;
81
- margin-left: 0.5rem;
82
- font-size: 0.75rem;
83
- transition: transform 0.3s ease;
84
- }
85
-
86
- header details[open] summary::after {
87
- transform: rotate(180deg);
88
- }
89
-
90
- header details nav {
91
- position: absolute;
92
- top: 100%;
93
- right: -0.5rem;
94
- display: flex;
95
- flex-direction: column;
96
- gap: 0.75rem;
97
- background: rgba(185, 185, 185, 0.1);
98
- backdrop-filter: blur(4px);
99
- border: 1px solid #5fbeeb;
100
- border-radius: 1rem;
101
- padding: 1rem 1.5rem;
102
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
103
- opacity: 0;
104
- transform: translateY(-8px);
105
- pointer-events: none;
106
- transition:
107
- opacity 0.3s ease,
108
- transform 0.3s ease;
109
- z-index: 1000;
110
- }
111
-
112
- header details[open] nav {
113
- opacity: 1;
114
- transform: translateY(0);
115
- pointer-events: auto;
116
- }
117
-
118
- header details nav a {
119
- font-size: 1.1rem;
120
- padding: 0.25rem 0;
121
- white-space: nowrap;
122
- }
123
-
124
- @media (prefers-color-scheme: light) {
125
- header {
126
- background-color: #ffffff;
127
- }
128
-
129
- button {
130
- background-color: #ffffff;
131
- }
132
- }
1
+ export const generateMarkupCSS = (frontend, color, isSingleFrontend) => `@import url('${isSingleFrontend ? '../styles/reset.css' : '../../styles/reset.css'}');
2
+
3
+ header {
4
+ align-items: center;
5
+ background-color: #1a1a1a;
6
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
7
+ display: flex;
8
+ justify-content: space-between;
9
+ padding: 2rem;
10
+ text-align: center;
11
+ }
12
+
13
+ header a {
14
+ position: relative;
15
+ color: #5fbeeb;
16
+ text-decoration: none;
17
+ }
18
+
19
+ header a::after {
20
+ content: '';
21
+ position: absolute;
22
+ left: 0;
23
+ bottom: 0;
24
+ width: 100%;
25
+ height: 2px;
26
+ background: linear-gradient(90deg, #5fbeeb 0%, #35d5a2 50%, #ff4b91 100%);
27
+ transform: scaleX(0);
28
+ transform-origin: left;
29
+ transition: transform 0.25s ease-in-out;
30
+ }
31
+
32
+ header a:hover::after {
33
+ transform: scaleX(1);
34
+ }
35
+
36
+ h1 {
37
+ font-size: 2.5rem;
38
+ margin-top: 2rem;
39
+ }
40
+
41
+ .logo {
42
+ height: 8rem;
43
+ width: 8rem;
44
+ will-change: filter;
45
+ transition: filter 300ms;
46
+ }
47
+
48
+ .logo:hover {
49
+ filter: drop-shadow(0 0 2rem #5fbeeb);
50
+ }
51
+
52
+ .logo.${frontend}:hover {
53
+ filter: drop-shadow(0 0 2rem ${color});
54
+ }
55
+
56
+ nav {
57
+ display: flex;
58
+ gap: 4rem;
59
+ justify-content: center;
60
+ }
61
+
62
+ header details {
63
+ position: relative;
64
+ }
65
+
66
+ header details summary {
67
+ list-style: none;
68
+ appearance: none;
69
+ -webkit-appearance: none;
70
+ cursor: pointer;
71
+ user-select: none;
72
+ color: #5fbeeb;
73
+ font-size: 1.5rem;
74
+ font-weight: 500;
75
+ padding: 0.5rem 1rem;
76
+ }
77
+
78
+ header summary::after {
79
+ content: '▼';
80
+ display: inline-block;
81
+ margin-left: 0.5rem;
82
+ font-size: 0.75rem;
83
+ transition: transform 0.3s ease;
84
+ }
85
+
86
+ header details[open] summary::after {
87
+ transform: rotate(180deg);
88
+ }
89
+
90
+ header details nav {
91
+ position: absolute;
92
+ top: 100%;
93
+ right: -0.5rem;
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.75rem;
97
+ background: rgba(185, 185, 185, 0.1);
98
+ backdrop-filter: blur(4px);
99
+ border: 1px solid #5fbeeb;
100
+ border-radius: 1rem;
101
+ padding: 1rem 1.5rem;
102
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
103
+ opacity: 0;
104
+ transform: translateY(-8px);
105
+ pointer-events: none;
106
+ transition:
107
+ opacity 0.3s ease,
108
+ transform 0.3s ease;
109
+ z-index: 1000;
110
+ }
111
+
112
+ header details[open] nav {
113
+ opacity: 1;
114
+ transform: translateY(0);
115
+ pointer-events: auto;
116
+ }
117
+
118
+ header details nav a {
119
+ font-size: 1.1rem;
120
+ padding: 0.25rem 0;
121
+ white-space: nowrap;
122
+ }
123
+
124
+ @media (prefers-color-scheme: light) {
125
+ header {
126
+ background-color: #ffffff;
127
+ }
128
+
129
+ button {
130
+ background-color: #ffffff;
131
+ }
132
+ }
133
133
  ${frontend === 'react'
134
- ? `\n@keyframes logo-spin {
135
- from {
136
- transform: rotate(0deg);
137
- }
138
- to {
139
- transform: rotate(360deg);
140
- }
141
- }
142
-
143
- @media (prefers-reduced-motion: no-preference) {
144
- a:nth-of-type(2) .logo {
145
- animation: logo-spin infinite 20s linear;
146
- }
134
+ ? `\n@keyframes logo-spin {
135
+ from {
136
+ transform: rotate(0deg);
137
+ }
138
+ to {
139
+ transform: rotate(360deg);
140
+ }
141
+ }
142
+
143
+ @media (prefers-reduced-motion: no-preference) {
144
+ a:nth-of-type(2) .logo {
145
+ animation: logo-spin infinite 20s linear;
146
+ }
147
147
  }`
148
148
  : ''}`;
@@ -1,10 +1,11 @@
1
- import type { AuthOption, FrontendDirectories } from '../../types';
1
+ import type { AuthOption, CreateConfiguration, FrontendDirectories } from '../../types';
2
2
  import type { FrameworkFlags } from './computeFlags';
3
3
  type GenerateRoutesBlockProps = {
4
+ databaseEngine: CreateConfiguration['databaseEngine'];
4
5
  flags: FrameworkFlags;
5
6
  frontendDirectories: FrontendDirectories;
6
7
  authOption: AuthOption;
7
8
  buildDirectory: string;
8
9
  };
9
- export declare const generateRoutesBlock: ({ flags, frontendDirectories, authOption, buildDirectory }: GenerateRoutesBlockProps) => string;
10
+ export declare const generateRoutesBlock: ({ databaseEngine, flags, frontendDirectories, authOption, buildDirectory }: GenerateRoutesBlockProps) => string;
10
11
  export {};
@@ -1,18 +1,19 @@
1
1
  import { isFrontend } from '../../typeGuards';
2
- export const generateRoutesBlock = ({ flags, frontendDirectories, authOption, buildDirectory }) => {
2
+ export const generateRoutesBlock = ({ databaseEngine, flags, frontendDirectories, authOption, buildDirectory }) => {
3
+ const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
3
4
  const routes = [];
4
5
  const wrap = (handlerCall) => authOption === 'abs'
5
- ? `async ({ cookie: { auth_provider, user_session_id }, store: { session }, status }) => {
6
- const { user, error } = await getStatus(session, user_session_id);
7
-
8
- if (error) {
9
- return status(error.code, error.message);
10
- }
11
-
12
- const providerConfiguration =
13
- auth_provider.value && providers[auth_provider.value];
14
-
15
- return ${handlerCall};
6
+ ? `async ({ cookie: { auth_provider, user_session_id }, store: { session }, status }) => {
7
+ const { user, error } = await getStatus(session, user_session_id);
8
+
9
+ if (error) {
10
+ return status(error.code, error.message);
11
+ }
12
+
13
+ const providerConfiguration =
14
+ auth_provider.value && providers[auth_provider.value];
15
+
16
+ return ${handlerCall};
16
17
  }`
17
18
  : `() => ${handlerCall}`;
18
19
  const createHandlerCall = (frontend, directory) => {
@@ -25,33 +26,33 @@ export const generateRoutesBlock = ({ flags, frontendDirectories, authOption, bu
25
26
  const reactProps = authOption === 'abs'
26
27
  ? `{ initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS'), user, providerConfiguration }`
27
28
  : `{ initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS') }`;
28
- return `handleReactPageRequest(
29
- ReactExample,
30
- asset(manifest, 'ReactExampleIndex'),
31
- ${reactProps}
29
+ return `handleReactPageRequest(
30
+ ReactExample,
31
+ asset(manifest, 'ReactExampleIndex'),
32
+ ${reactProps}
32
33
  )`;
33
34
  }
34
35
  if (frontend === 'svelte')
35
- return `handleSveltePageRequest(
36
- SvelteExample,
37
- asset(manifest, 'SvelteExample'),
38
- asset(manifest, 'SvelteExampleIndex'),
39
- { initialCount: 0, cssPath: asset(manifest, 'SvelteExampleCSS') }
36
+ return `handleSveltePageRequest(
37
+ SvelteExample,
38
+ asset(manifest, 'SvelteExample'),
39
+ asset(manifest, 'SvelteExampleIndex'),
40
+ { initialCount: 0, cssPath: asset(manifest, 'SvelteExampleCSS') }
40
41
  )`;
41
42
  if (frontend === 'vue') {
42
43
  const vueComponent = flags.requiresSvelte
43
44
  ? 'vueImports.VueExample'
44
45
  : 'VueExample';
45
- return `handleVuePageRequest(
46
- ${vueComponent},
47
- asset(manifest, 'VueExample'),
48
- asset(manifest, 'VueExampleIndex'),
49
- generateHeadElement({
50
- cssPath: asset(manifest, 'VueExampleCSS'),
51
- title: 'AbsoluteJS + Vue',
52
- description: 'A Vue.js example with AbsoluteJS'
53
- }),
54
- { initialCount: 0 }
46
+ return `handleVuePageRequest(
47
+ ${vueComponent},
48
+ asset(manifest, 'VueExample'),
49
+ asset(manifest, 'VueExampleIndex'),
50
+ generateHeadElement({
51
+ cssPath: asset(manifest, 'VueExampleCSS'),
52
+ title: 'AbsoluteJS + Vue',
53
+ description: 'A Vue.js example with AbsoluteJS'
54
+ }),
55
+ { initialCount: 0 }
55
56
  )`;
56
57
  }
57
58
  return '';
@@ -74,11 +75,11 @@ export const generateRoutesBlock = ({ flags, frontendDirectories, authOption, bu
74
75
  routes.push(`.get('/${frontend}', ${handler})`);
75
76
  }
76
77
  });
77
- if (authOption === undefined || authOption === 'none') {
78
- routes.push(`.get('/count/:uid', ({ params: { uid } }) => getCountHistory(db, uid), {
79
- params: t.Object({ uid: t.Number() })
80
- })`, `.post('/count', ({ body: { count } }) => createCountHistory(db, count), {
81
- body: t.Object({ count: t.Number() })
78
+ if (hasDatabase && (authOption === undefined || authOption === 'none')) {
79
+ routes.push(`.get('/count/:uid', ({ params: { uid } }) => getCountHistory(db, uid), {
80
+ params: t.Object({ uid: t.Number() })
81
+ })`, `.post('/count', ({ body: { count } }) => createCountHistory(db, count), {
82
+ body: t.Object({ count: t.Number() })
82
83
  })`);
83
84
  }
84
85
  return routes.join('\n ');
@@ -35,7 +35,10 @@ export const generateServerFile = ({ tailwind, authOption, plugins, buildDirecto
35
35
  .filter((pluginImport) => pluginImport.isPlugin)
36
36
  .map((pluginImport) => {
37
37
  if (pluginImport.packageName === 'absoluteAuth') {
38
- return `.use(absoluteAuth(absoluteAuthConfig(db)))`;
38
+ const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
39
+ return hasDatabase
40
+ ? `.use(absoluteAuth(absoluteAuthConfig(db)))`
41
+ : `.use(absoluteAuth(absoluteAuthConfig()))`;
39
42
  }
40
43
  if (pluginImport.config === undefined) {
41
44
  return `.use(${pluginImport.packageName})`;
@@ -46,30 +49,31 @@ export const generateServerFile = ({ tailwind, authOption, plugins, buildDirecto
46
49
  return `.use(${pluginImport.packageName}(${JSON.stringify(pluginImport.config)}))`;
47
50
  })
48
51
  .join('\n');
49
- const guardBlock = `.guard({
50
- cookie: t.Cookie({
51
- auth_provider: t.Optional(authProviderOption),
52
- user_session_id: userSessionIdTypebox
53
- })
52
+ const guardBlock = `.guard({
53
+ cookie: t.Cookie({
54
+ auth_provider: t.Optional(authProviderOption),
55
+ user_session_id: userSessionIdTypebox
56
+ })
54
57
  })`;
55
58
  const routesBlock = generateRoutesBlock({
56
59
  authOption,
57
60
  buildDirectory,
61
+ databaseEngine,
58
62
  flags,
59
63
  frontendDirectories
60
64
  });
61
- const content = `${importsBlock}
62
-
63
- ${manifestBlock}
64
- ${dbBlock}
65
- new Elysia()
66
- ${useBlock}
67
- ${authOption === 'abs' ? guardBlock : ''}
68
- ${routesBlock}
69
- .on('error', err => {
70
- const { request } = err
71
- console.error(\`Server error on \${request.method} \${request.url}: \${err.message}\`)
72
- });
65
+ const content = `${importsBlock}
66
+
67
+ ${manifestBlock}
68
+ ${dbBlock}
69
+ new Elysia()
70
+ ${useBlock}
71
+ ${authOption === 'abs' ? guardBlock : ''}
72
+ ${routesBlock}
73
+ .on('error', err => {
74
+ const { request } = err
75
+ console.error(\`Server error on \${request.method} \${request.url}: \${err.message}\`)
76
+ });
73
77
  `;
74
78
  writeFileSync(serverFilePath, content);
75
79
  };
@@ -17,7 +17,8 @@ export const scaffoldBackend = ({ assetsDirectory, authOption, absProviders, bac
17
17
  });
18
18
  if (authOption === 'abs') {
19
19
  mkdirSync(join(backendDirectory, 'utils'), { recursive: true });
20
- const absoluteAuthConfig = generateAbsoluteAuthConfig(absProviders);
20
+ const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
21
+ const absoluteAuthConfig = generateAbsoluteAuthConfig(absProviders, hasDatabase);
21
22
  writeFileSync(join(backendDirectory, 'utils', 'absoluteAuthConfig.ts'), absoluteAuthConfig, 'utf-8');
22
23
  }
23
24
  };
@@ -1,8 +1,8 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
- type ScaffoldFrontendsProps = Pick<CreateConfiguration, 'useHTMLScripts' | 'frontendDirectories' | 'assetsDirectory' | 'frontends' | 'authOption' | 'absProviders'> & {
2
+ type ScaffoldFrontendsProps = Pick<CreateConfiguration, 'useHTMLScripts' | 'frontendDirectories' | 'assetsDirectory' | 'frontends' | 'authOption' | 'absProviders' | 'useTailwind'> & {
3
3
  frontendDirectory: string;
4
4
  templatesDirectory: string;
5
5
  projectAssetsDirectory: string;
6
6
  };
7
- export declare const scaffoldFrontends: ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
7
+ export declare const scaffoldFrontends: ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, useTailwind, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
8
8
  export {};
@@ -1,15 +1,18 @@
1
- import { cpSync, mkdirSync } from 'fs';
1
+ import { copyFileSync, cpSync, mkdirSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { scaffoldHTML } from '../html/scaffoldHTML';
4
4
  import { scaffoldHTMX } from '../htmx/scaffoldHTMX';
5
5
  import { scaffoldReact } from '../react/scaffoldReact';
6
6
  import { scaffoldSvelte } from '../svelte/scaffoldSvelte';
7
7
  import { scaffoldVue } from '../vue/scaffoldVue';
8
- export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }) => {
8
+ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, useTailwind, frontendDirectories, frontends }) => {
9
9
  const stylesTargetDirectory = join(frontendDirectory, 'styles');
10
10
  cpSync(join(templatesDirectory, 'styles'), stylesTargetDirectory, {
11
11
  recursive: true
12
12
  });
13
+ if (useTailwind) {
14
+ copyFileSync(join(templatesDirectory, 'tailwind', 'tailwind.css'), join(stylesTargetDirectory, 'tailwind.css'));
15
+ }
13
16
  const frontendEntries = Object.entries(frontendDirectories);
14
17
  const isSingleFrontend = frontendEntries.length === 1;
15
18
  const directoryMap = new Map();