create-absolutejs 0.5.0 → 0.6.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 (63) hide show
  1. package/dist/data.d.ts +1 -1
  2. package/dist/data.js +2 -2
  3. package/dist/generators/configurations/generatePackageJson.d.ts +2 -2
  4. package/dist/generators/configurations/generatePackageJson.js +12 -4
  5. package/dist/generators/configurations/initializeRoot.js +2 -2
  6. package/dist/generators/db/generateDatabaseTypes.d.ts +3 -3
  7. package/dist/generators/db/generateDatabaseTypes.js +12 -7
  8. package/dist/generators/db/generateDrizzleSchema.d.ts +3 -3
  9. package/dist/generators/db/generateDrizzleSchema.js +4 -6
  10. package/dist/generators/db/generateSqliteSchema.d.ts +2 -2
  11. package/dist/generators/db/generateSqliteSchema.js +1 -1
  12. package/dist/generators/db/handlerTemplates.d.ts +40 -20
  13. package/dist/generators/db/handlerTemplates.js +131 -91
  14. package/dist/generators/db/scaffoldDatabase.d.ts +2 -2
  15. package/dist/generators/db/scaffoldDatabase.js +6 -6
  16. package/dist/generators/db/scaffoldDocker.d.ts +3 -3
  17. package/dist/generators/db/scaffoldDocker.js +2 -2
  18. package/dist/generators/html/scaffoldHTML.js +2 -2
  19. package/dist/generators/project/collectDependencies.d.ts +2 -2
  20. package/dist/generators/project/collectDependencies.js +2 -2
  21. package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +2 -0
  22. package/dist/generators/project/generateAbsoluteAuthConfig.js +128 -0
  23. package/dist/generators/project/generateDBBlock.js +17 -10
  24. package/dist/generators/project/generateImportsBlock.d.ts +2 -2
  25. package/dist/generators/project/generateImportsBlock.js +28 -23
  26. package/dist/generators/project/generateRoutesBlock.d.ts +3 -3
  27. package/dist/generators/project/generateRoutesBlock.js +57 -46
  28. package/dist/generators/project/generateServer.d.ts +2 -2
  29. package/dist/generators/project/generateServer.js +28 -12
  30. package/dist/generators/project/scaffoldBackend.d.ts +6 -0
  31. package/dist/generators/project/scaffoldBackend.js +23 -0
  32. package/dist/generators/project/scaffoldFrontends.d.ts +2 -2
  33. package/dist/generators/project/scaffoldFrontends.js +18 -3
  34. package/dist/generators/react/generateReactComponents.d.ts +5 -0
  35. package/dist/generators/react/generateReactComponents.js +126 -0
  36. package/dist/generators/react/scaffoldReact.d.ts +1 -1
  37. package/dist/generators/react/scaffoldReact.js +11 -2
  38. package/dist/index.js +1 -1
  39. package/dist/messages.d.ts +1 -1
  40. package/dist/messages.js +5 -4
  41. package/dist/prompt.js +4 -3
  42. package/dist/questions/authOption.d.ts +1 -0
  43. package/dist/questions/{authProvider.js → authOption.js} +5 -5
  44. package/dist/questions/databaseHost.js +12 -0
  45. package/dist/scaffold.d.ts +1 -1
  46. package/dist/scaffold.js +13 -9
  47. package/dist/templates/assets/svg/google-logo.svg +7 -0
  48. package/dist/templates/react/components/OAuthLink.tsx +39 -0
  49. package/dist/templates/react/components/ProfilePicture.tsx +56 -0
  50. package/dist/templates/styles/tailwind.css +1 -0
  51. package/dist/typeGuards.d.ts +2 -2
  52. package/dist/typeGuards.js +1 -1
  53. package/dist/types.d.ts +7 -2
  54. package/dist/utils/abort.js +1 -1
  55. package/dist/utils/parseCommandLineOptions.js +30 -9
  56. package/dist/utils/t3-utils.js +1 -1
  57. package/package.json +2 -1
  58. package/dist/generators/project/generateUseBlock.d.ts +0 -6
  59. package/dist/generators/project/generateUseBlock.js +0 -32
  60. package/dist/generators/react/generateReactPage.d.ts +0 -2
  61. package/dist/generators/react/generateReactPage.js +0 -23
  62. package/dist/questions/authProvider.d.ts +0 -1
  63. package/dist/templates/react/pages/ReactExample.tsx +0 -18
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { isDrizzleDialect } from '../../typeGuards';
4
- export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authProvider, databaseEngine, databaseHost, frontendDirectories }) => {
4
+ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authOption, databaseEngine, databaseHost, frontendDirectories }) => {
5
5
  const rawImports = [];
6
6
  const pushHandler = (cond, name) => cond &&
7
7
  rawImports.push(`import { ${name} } from '@absolutejs/absolute'`);
@@ -38,12 +38,12 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
38
38
  if (flags.requiresVue && !flags.requiresSvelte && vueDir !== undefined)
39
39
  rawImports.push(`import VueExample from '${buildExamplePath(vueDir, 'VueExample.vue')}'`);
40
40
  const connectorImports = {
41
- neon: [`import { Pool } from '@neondatabase/serverless'`],
42
- planetscale: [`import { connect } from '@planetscale/database'`],
41
+ neon: [`import { neon } from '@neondatabase/serverless'`],
42
+ planetscale: [`import { Client } from '@planetscale/database'`],
43
43
  turso: [`import { createClient } from '@libsql/client'`]
44
44
  };
45
45
  const dialectImports = {
46
- neon: [`import { drizzle } from 'drizzle-orm/neon-serverless'`],
46
+ neon: [`import { drizzle } from 'drizzle-orm/neon-http'`],
47
47
  planetscale: [
48
48
  `import { drizzle } from 'drizzle-orm/planetscale-serverless'`
49
49
  ],
@@ -58,11 +58,8 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
58
58
  ...(databaseEngine === 'sqlite' && !isRemoteHost
59
59
  ? []
60
60
  : [`import { getEnv } from '@absolutejs/absolute'`]),
61
- ...(authProvider === 'absoluteAuth'
62
- ? [
63
- `import { schema } from '../../db/schema'`,
64
- `import { User } from '../types/databaseTypes'`
65
- ]
61
+ ...(authOption === 'abs'
62
+ ? [`import { schema } from '../../db/schema'`]
66
63
  : [`import { schema } from '../../db/schema'`])
67
64
  ]
68
65
  };
@@ -87,7 +84,12 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
87
84
  `import { SQL } from 'bun'`,
88
85
  `import { drizzle } from 'drizzle-orm/bun-sql'`
89
86
  ]
90
- : [],
87
+ : isRemoteHost && databaseHost === 'planetscale'
88
+ ? [
89
+ `import { drizzle } from 'drizzle-orm/node-postgres'`,
90
+ `import { Pool } from 'pg'`
91
+ ]
92
+ : [],
91
93
  singlestore: [
92
94
  `import { drizzle } from 'drizzle-orm/singlestore'`,
93
95
  `import { createPool } from 'mysql2/promise'`
@@ -127,15 +129,20 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
127
129
  `import { SQL } from 'bun'`,
128
130
  `import { getEnv } from '@absolutejs/absolute'`
129
131
  ],
130
- postgresql: isRemoteHost
132
+ postgresql: isRemoteHost && databaseHost === 'neon'
131
133
  ? [
132
134
  ...connectorImports[databaseHost],
133
135
  `import { getEnv } from '@absolutejs/absolute'`
134
136
  ]
135
- : [
136
- `import { SQL } from 'bun'`,
137
- `import { getEnv } from '@absolutejs/absolute'`
138
- ],
137
+ : isRemoteHost && databaseHost === 'planetscale'
138
+ ? [
139
+ `import { Pool } from 'pg'`,
140
+ `import { getEnv } from '@absolutejs/absolute'`
141
+ ]
142
+ : [
143
+ `import { SQL } from 'bun'`,
144
+ `import { getEnv } from '@absolutejs/absolute'`
145
+ ],
139
146
  singlestore: [
140
147
  `import { createPool } from 'mysql2/promise'`,
141
148
  `import { getEnv } from '@absolutejs/absolute'`
@@ -150,7 +157,9 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
150
157
  if (orm === 'drizzle') {
151
158
  rawImports.push(...ormImports[orm]);
152
159
  }
153
- if (orm === 'drizzle' && isRemoteHost) {
160
+ if (orm == 'drizzle' &&
161
+ isRemoteHost &&
162
+ !(databaseEngine === 'postgresql' && databaseHost === 'planetscale')) {
154
163
  rawImports.push(...connectorImports[databaseHost], ...dialectImports[databaseHost]);
155
164
  }
156
165
  if (orm === 'drizzle' && isDrizzleDialect(databaseEngine)) {
@@ -159,13 +168,9 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authP
159
168
  if (noOrm && hasDatabase && noOrmImports[databaseEngine]) {
160
169
  rawImports.push(...noOrmImports[databaseEngine]);
161
170
  }
162
- if (authProvider === 'absoluteAuth')
163
- rawImports.push(`import { absoluteAuth, instantiateUserSession } from '@absolutejs/auth'`, ...(hasDatabase
164
- ? [
165
- `import { createUser, getUser } from './handlers/userHandlers'`
166
- ]
167
- : []));
168
- if (hasDatabase && (authProvider === undefined || authProvider === 'none'))
171
+ if (authOption === 'abs')
172
+ rawImports.push(`import { absoluteAuthConfig } from './utils/absoluteAuthConfig'`, `import { t } from 'elysia'`, `import { authProviderOption, providers, userSessionIdTypebox, getStatus } from '@absolutejs/auth'`);
173
+ if (hasDatabase && (authOption === undefined || authOption === 'none'))
169
174
  rawImports.push(`import { getCountHistory, createCountHistory } from './handlers/countHistoryHandlers'`, `import { t } from 'elysia'`);
170
175
  if (flags.requiresVue && flags.requiresSvelte) {
171
176
  const utilsDir = join(backendDirectory, 'utils');
@@ -1,10 +1,10 @@
1
- import type { AuthProvider, FrontendDirectories } from '../../types';
1
+ import type { AuthOption, FrontendDirectories } from '../../types';
2
2
  import type { FrameworkFlags } from './computeFlags';
3
3
  type GenerateRoutesBlockProps = {
4
4
  flags: FrameworkFlags;
5
5
  frontendDirectories: FrontendDirectories;
6
- authProvider: AuthProvider;
6
+ authOption: AuthOption;
7
7
  buildDirectory: string;
8
8
  };
9
- export declare const generateRoutesBlock: ({ flags, frontendDirectories, authProvider, buildDirectory }: GenerateRoutesBlockProps) => string;
9
+ export declare const generateRoutesBlock: ({ flags, frontendDirectories, authOption, buildDirectory }: GenerateRoutesBlockProps) => string;
10
10
  export {};
@@ -1,73 +1,84 @@
1
1
  import { isFrontend } from '../../typeGuards';
2
- export const generateRoutesBlock = ({ flags, frontendDirectories, authProvider, buildDirectory }) => {
2
+ export const generateRoutesBlock = ({ flags, frontendDirectories, authOption, buildDirectory }) => {
3
3
  const routes = [];
4
+ 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};
16
+ }`
17
+ : `() => ${handlerCall}`;
4
18
  const createHandlerCall = (frontend, directory) => {
5
19
  const base = `${buildDirectory}${directory ? `/${directory}` : ''}/pages`;
6
20
  if (frontend === 'html')
7
21
  return `handleHTMLPageRequest(\`${base}/HTMLExample.html\`)`;
8
22
  if (frontend === 'htmx')
9
23
  return `handleHTMXPageRequest(\`${base}/HTMXExample.html\`)`;
10
- if (frontend === 'react')
24
+ if (frontend === 'react') {
25
+ const reactProps = authOption === 'abs'
26
+ ? `{ initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS'), user, providerConfiguration }`
27
+ : `{ initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS') }`;
11
28
  return `handleReactPageRequest(
12
- ReactExample,
13
- asset(manifest, 'ReactExampleIndex'),
14
- { initialCount: 0, cssPath: asset(manifest, 'ReactExampleCSS') }
15
- )`;
29
+ ReactExample,
30
+ asset(manifest, 'ReactExampleIndex'),
31
+ ${reactProps}
32
+ )`;
33
+ }
16
34
  if (frontend === 'svelte')
17
35
  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
- )`;
36
+ SvelteExample,
37
+ asset(manifest, 'SvelteExample'),
38
+ asset(manifest, 'SvelteExampleIndex'),
39
+ { initialCount: 0, cssPath: asset(manifest, 'SvelteExampleCSS') }
40
+ )`;
41
+ if (frontend === 'vue') {
42
+ const vueComponent = flags.requiresSvelte
43
+ ? 'vueImports.VueExample'
44
+ : '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 }
55
+ )`;
56
+ }
47
57
  return '';
48
58
  };
49
59
  Object.entries(frontendDirectories).forEach(([frontend, directory], entryIndex) => {
50
60
  if (!isFrontend(frontend))
51
61
  return;
52
62
  const handlerCall = createHandlerCall(frontend, directory);
53
- if (entryIndex === 0)
54
- routes.push(`.get('/', () => ${handlerCall})`);
63
+ if (!handlerCall)
64
+ return;
65
+ const handler = wrap(handlerCall);
66
+ if (entryIndex === 0) {
67
+ routes.push(`.get('/', ${handler})`);
68
+ }
55
69
  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)`);
70
+ routes.push(`.get('/htmx', ${handler})`);
71
+ routes.push(`.post('/htmx/reset', ({ resetScopedStore }) => resetScopedStore())`, `.get('/htmx/count', ({ scopedStore }) => scopedStore.count)`, `.post('/htmx/increment', ({ scopedStore }) => ++scopedStore.count)`);
57
72
  }
58
73
  else {
59
- routes.push(`.get('/${frontend}', () => ${handlerCall})`);
74
+ routes.push(`.get('/${frontend}', ${handler})`);
60
75
  }
61
76
  });
62
- if (authProvider === undefined || authProvider === 'none') {
77
+ if (authOption === undefined || authOption === 'none') {
63
78
  routes.push(`.get('/count/:uid', ({ params: { uid } }) => getCountHistory(db, uid), {
64
- params: t.Object({
65
- uid: t.Number()
66
- })
79
+ params: t.Object({ uid: t.Number() })
67
80
  })`, `.post('/count', ({ body: { count } }) => createCountHistory(db, count), {
68
- body: t.Object({
69
- count: t.Number()
70
- })
81
+ body: t.Object({ count: t.Number() })
71
82
  })`);
72
83
  }
73
84
  return routes.join('\n ');
@@ -1,6 +1,6 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
- type CreateServerFileProps = Pick<CreateConfiguration, 'tailwind' | 'authProvider' | 'databaseEngine' | 'plugins' | 'buildDirectory' | 'databaseHost' | 'orm' | 'assetsDirectory' | 'frontendDirectories'> & {
2
+ type CreateServerFileProps = Pick<CreateConfiguration, 'tailwind' | 'authOption' | 'databaseEngine' | 'plugins' | 'buildDirectory' | 'databaseHost' | 'orm' | 'assetsDirectory' | 'frontendDirectories'> & {
3
3
  backendDirectory: string;
4
4
  };
5
- export declare const generateServerFile: ({ tailwind, authProvider, plugins, buildDirectory, databaseEngine, databaseHost, orm, assetsDirectory, frontendDirectories, backendDirectory }: CreateServerFileProps) => void;
5
+ export declare const generateServerFile: ({ tailwind, authOption, plugins, buildDirectory, databaseEngine, databaseHost, orm, assetsDirectory, frontendDirectories, backendDirectory }: CreateServerFileProps) => void;
6
6
  export {};
@@ -1,4 +1,4 @@
1
- import { writeFileSync, mkdirSync } from 'fs';
1
+ import { writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { collectDependencies } from './collectDependencies';
4
4
  import { computeFlags } from './computeFlags';
@@ -6,13 +6,12 @@ import { generateBuildBlock } from './generateBuildBlock';
6
6
  import { generateDBBlock } from './generateDBBlock';
7
7
  import { generateImportsBlock } from './generateImportsBlock';
8
8
  import { generateRoutesBlock } from './generateRoutesBlock';
9
- import { generateUseBlock } from './generateUseBlock';
10
- export const generateServerFile = ({ tailwind, authProvider, plugins, buildDirectory, databaseEngine, databaseHost, orm, assetsDirectory, frontendDirectories, backendDirectory }) => {
9
+ export const generateServerFile = ({ tailwind, authOption, plugins, buildDirectory, databaseEngine, databaseHost, orm, assetsDirectory, frontendDirectories, backendDirectory }) => {
11
10
  const serverFilePath = join(backendDirectory, 'server.ts');
12
11
  const flags = computeFlags(frontendDirectories);
13
- const deps = collectDependencies({ authProvider, flags, plugins });
12
+ const deps = collectDependencies({ authOption, flags, plugins });
14
13
  const importsBlock = generateImportsBlock({
15
- authProvider,
14
+ authOption,
16
15
  backendDirectory,
17
16
  databaseEngine,
18
17
  databaseHost,
@@ -31,13 +30,30 @@ export const generateServerFile = ({ tailwind, authProvider, plugins, buildDirec
31
30
  if (databaseEngine && databaseEngine !== 'none') {
32
31
  dbBlock = generateDBBlock({ databaseEngine, databaseHost, orm });
33
32
  }
34
- const useBlock = generateUseBlock({
35
- databaseEngine,
36
- deps,
37
- orm
38
- });
33
+ const useBlock = deps
34
+ .flatMap((dependency) => dependency.imports ?? [])
35
+ .filter((pluginImport) => pluginImport.isPlugin)
36
+ .map((pluginImport) => {
37
+ if (pluginImport.packageName === 'absoluteAuth') {
38
+ return `.use(absoluteAuth(absoluteAuthConfig(db)))`;
39
+ }
40
+ if (pluginImport.config === undefined) {
41
+ return `.use(${pluginImport.packageName})`;
42
+ }
43
+ if (pluginImport.config === null) {
44
+ return `.use(${pluginImport.packageName}())`;
45
+ }
46
+ return `.use(${pluginImport.packageName}(${JSON.stringify(pluginImport.config)}))`;
47
+ })
48
+ .join('\n');
49
+ const guardBlock = `.guard({
50
+ cookie: t.Cookie({
51
+ auth_provider: t.Optional(authProviderOption),
52
+ user_session_id: userSessionIdTypebox
53
+ })
54
+ })`;
39
55
  const routesBlock = generateRoutesBlock({
40
- authProvider,
56
+ authOption,
41
57
  buildDirectory,
42
58
  flags,
43
59
  frontendDirectories
@@ -48,12 +64,12 @@ ${manifestBlock}
48
64
  ${dbBlock}
49
65
  new Elysia()
50
66
  ${useBlock}
67
+ ${authOption === 'abs' ? guardBlock : ''}
51
68
  ${routesBlock}
52
69
  .on('error', err => {
53
70
  const { request } = err
54
71
  console.error(\`Server error on \${request.method} \${request.url}: \${err.message}\`)
55
72
  });
56
73
  `;
57
- mkdirSync(backendDirectory, { recursive: true });
58
74
  writeFileSync(serverFilePath, content);
59
75
  };
@@ -0,0 +1,6 @@
1
+ import { CreateConfiguration } from '../../types';
2
+ type ScaffoldBackendProps = Pick<CreateConfiguration, 'assetsDirectory' | 'absProviders' | 'authOption' | 'buildDirectory' | 'databaseEngine' | 'databaseHost' | 'frontendDirectories' | 'orm' | 'plugins' | 'tailwind'> & {
3
+ backendDirectory: string;
4
+ };
5
+ export declare const scaffoldBackend: ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, tailwind }: ScaffoldBackendProps) => void;
6
+ export {};
@@ -0,0 +1,23 @@
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { generateAbsoluteAuthConfig } from './generateAbsoluteAuthConfig';
4
+ import { generateServerFile } from './generateServer';
5
+ export const scaffoldBackend = ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, tailwind }) => {
6
+ generateServerFile({
7
+ assetsDirectory,
8
+ authOption,
9
+ backendDirectory,
10
+ buildDirectory,
11
+ databaseEngine,
12
+ databaseHost,
13
+ frontendDirectories,
14
+ orm,
15
+ plugins,
16
+ tailwind
17
+ });
18
+ if (authOption === 'abs') {
19
+ mkdirSync(join(backendDirectory, 'utils'), { recursive: true });
20
+ const absoluteAuthConfig = generateAbsoluteAuthConfig(absProviders);
21
+ writeFileSync(join(backendDirectory, 'utils', 'absoluteAuthConfig.ts'), absoluteAuthConfig, 'utf-8');
22
+ }
23
+ };
@@ -1,8 +1,8 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
- type ScaffoldFrontendsProps = Pick<CreateConfiguration, 'useHTMLScripts' | 'frontendDirectories' | 'frontends'> & {
2
+ type ScaffoldFrontendsProps = Pick<CreateConfiguration, 'useHTMLScripts' | 'frontendDirectories' | 'assetsDirectory' | 'frontends' | 'authOption' | 'absProviders'> & {
3
3
  frontendDirectory: string;
4
4
  templatesDirectory: string;
5
5
  projectAssetsDirectory: string;
6
6
  };
7
- export declare const scaffoldFrontends: ({ frontendDirectory, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
7
+ export declare const scaffoldFrontends: ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
8
8
  export {};
@@ -1,11 +1,11 @@
1
- import { cpSync, mkdirSync } from 'node:fs';
2
- import { join } from 'node:path';
1
+ import { cpSync, mkdirSync } from 'fs';
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, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }) => {
8
+ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, frontendDirectories, frontends }) => {
9
9
  const stylesTargetDirectory = join(frontendDirectory, 'styles');
10
10
  cpSync(join(templatesDirectory, 'styles'), stylesTargetDirectory, {
11
11
  recursive: true
@@ -25,6 +25,9 @@ export const scaffoldFrontends = ({ frontendDirectory, templatesDirectory, proje
25
25
  switch (frontendName) {
26
26
  case 'react':
27
27
  scaffoldReact({
28
+ absProviders,
29
+ assetsDirectory,
30
+ authOption,
28
31
  frontends,
29
32
  isSingleFrontend,
30
33
  projectAssetsDirectory,
@@ -34,6 +37,9 @@ export const scaffoldFrontends = ({ frontendDirectory, templatesDirectory, proje
34
37
  break;
35
38
  case 'svelte':
36
39
  scaffoldSvelte({
40
+ absProviders,
41
+ assetsDirectory,
42
+ authOption,
37
43
  frontends,
38
44
  isSingleFrontend,
39
45
  projectAssetsDirectory,
@@ -43,6 +49,9 @@ export const scaffoldFrontends = ({ frontendDirectory, templatesDirectory, proje
43
49
  break;
44
50
  case 'vue':
45
51
  scaffoldVue({
52
+ absProviders,
53
+ assetsDirectory,
54
+ authOption,
46
55
  frontends,
47
56
  projectAssetsDirectory,
48
57
  targetDirectory,
@@ -54,6 +63,9 @@ export const scaffoldFrontends = ({ frontendDirectory, templatesDirectory, proje
54
63
  break;
55
64
  case 'html':
56
65
  scaffoldHTML({
66
+ absProviders,
67
+ assetsDirectory,
68
+ authOption,
57
69
  frontends,
58
70
  isSingleFrontend,
59
71
  projectAssetsDirectory,
@@ -64,6 +76,9 @@ export const scaffoldFrontends = ({ frontendDirectory, templatesDirectory, proje
64
76
  break;
65
77
  case 'htmx':
66
78
  scaffoldHTMX({
79
+ absProviders,
80
+ assetsDirectory,
81
+ authOption,
67
82
  frontends,
68
83
  isSingleFrontend,
69
84
  projectAssetsDirectory,
@@ -0,0 +1,5 @@
1
+ import { ProviderOption } from '@absolutejs/auth';
2
+ import { AuthOption, Frontend } from '../../types';
3
+ export declare const generateDropdownComponent: (frontends: Frontend[]) => string;
4
+ export declare const generateSignInComponent: (absProviders: ProviderOption[] | undefined) => string;
5
+ export declare const generateReactExamplePage: (authOption: AuthOption) => string;
@@ -0,0 +1,126 @@
1
+ import { formatNavLink } from '../../utils/formatNavLink';
2
+ export const generateDropdownComponent = (frontends) => {
3
+ const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
4
+ return `import { useState } from 'react';
5
+
6
+ export const Dropdown = () => {
7
+ const [isOpen, setIsOpen] = useState(false);
8
+
9
+ return (
10
+ <details
11
+ onPointerEnter={() => setIsOpen(true)}
12
+ onPointerLeave={() => setIsOpen(false)}
13
+ open={isOpen}
14
+ >
15
+ <summary>Pages</summary>
16
+ <nav>
17
+ ${navLinks}
18
+ </nav>
19
+ </details>
20
+ );
21
+ };
22
+ `;
23
+ };
24
+ export const generateSignInComponent = (absProviders) => `import { useState } from 'react';
25
+ import { OAuthLink } from './OAuthLink';
26
+
27
+ export const SignIn = () => {
28
+ const [isOpen, setIsOpen] = useState(false);
29
+
30
+ return (
31
+ <details
32
+ onPointerEnter={() => setIsOpen(true)}
33
+ onPointerLeave={() => setIsOpen(false)}
34
+ open={isOpen}
35
+ >
36
+ <summary>Sign In</summary>
37
+ <nav>
38
+ ${absProviders && absProviders.length > 0
39
+ ? absProviders
40
+ .map((provider) => {
41
+ const logoUrl = `/assets/svg/google-logo.svg`;
42
+ const name = provider.charAt(0).toUpperCase() +
43
+ provider.slice(1).toLowerCase();
44
+ return `<OAuthLink provider="${provider}" logoUrl="${logoUrl}" name="${name}" mode="in" />`;
45
+ })
46
+ .join('\n\t\t\t')
47
+ : `<p>No OAuth providers configured</p>`}
48
+ </nav>
49
+ </details>
50
+ );
51
+ }
52
+ `;
53
+ export const generateReactExamplePage = (authOption) => {
54
+ const useBlockReturn = authOption === 'abs';
55
+ const propsType = `
56
+ type ReactExampleProps = {
57
+ initialCount: number;
58
+ cssPath: string;
59
+ ${authOption === 'abs' ? 'user: User | null;\n\tproviderConfiguration: ProviderConfiguration | undefined;' : ''}
60
+ };
61
+ `;
62
+ const fnSignature = authOption === 'abs'
63
+ ? `export const ReactExample = ({ initialCount, cssPath, user, providerConfiguration }: ReactExampleProps) => {`
64
+ : `export const ReactExample = ({ initialCount, cssPath }: ReactExampleProps) => (`;
65
+ const extractProps = ` const userImage =
66
+ user?.metadata && providerConfiguration?.picture
67
+ ? extractPropFromIdentity(
68
+ user.metadata,
69
+ providerConfiguration.picture,
70
+ 'string'
71
+ )
72
+ : undefined;
73
+
74
+ const givenName =
75
+ user?.metadata && providerConfiguration?.givenName
76
+ ? extractPropFromIdentity(
77
+ user.metadata,
78
+ providerConfiguration.givenName,
79
+ 'string'
80
+ )
81
+ : undefined;
82
+
83
+ const familyName =
84
+ user?.metadata && providerConfiguration?.familyName
85
+ ? extractPropFromIdentity(
86
+ user.metadata,
87
+ providerConfiguration.familyName,
88
+ 'string'
89
+ )
90
+ : undefined;`;
91
+ const userButtonsBlock = authOption === 'abs'
92
+ ? `{user ? <ProfilePicture userImage={userImage} givenName={givenName} familyName={familyName} /> : <SignIn />}`
93
+ : ``;
94
+ const rightGroup = authOption === 'abs'
95
+ ? `<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
96
+ <Dropdown />
97
+ ${userButtonsBlock}
98
+ </div>`
99
+ : `<Dropdown />`;
100
+ const closing = authOption === 'abs' ? `};` : `);`;
101
+ return `
102
+ ${authOption === 'abs' ? `import { User } from '../../../types/databaseTypes';\nimport { extractPropFromIdentity, ProviderConfiguration } from '@absolutejs/auth';` : ''}
103
+ import { App } from '../components/App';
104
+ import { Dropdown } from '../components/Dropdown';
105
+ import { Head } from '../components/Head';
106
+ ${authOption === 'abs' ? `import { ProfilePicture } from '../components/ProfilePicture';\nimport { SignIn } from '../components/SignIn';` : ''}
107
+
108
+ ${propsType}
109
+
110
+ ${fnSignature}
111
+ ${authOption === 'abs' ? extractProps : ''}
112
+ ${useBlockReturn ? 'return (' : ''}
113
+ <html>
114
+ <Head cssPath={cssPath} />
115
+ <body>
116
+ <header>
117
+ <a href="/">AbsoluteJS</a>
118
+ ${rightGroup}
119
+ </header>
120
+ <App initialCount={initialCount} />
121
+ </body>
122
+ </html>
123
+ ${useBlockReturn ? ');' : ''}
124
+ ${closing}
125
+ `;
126
+ };
@@ -1,2 +1,2 @@
1
1
  import { ScaffoldFrontendProps } from '../../types';
2
- export declare const scaffoldReact: ({ isSingleFrontend, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory }: ScaffoldFrontendProps) => void;
2
+ export declare const scaffoldReact: ({ isSingleFrontend, authOption, targetDirectory, templatesDirectory, frontends, assetsDirectory, projectAssetsDirectory, absProviders }: ScaffoldFrontendProps) => void;
@@ -1,14 +1,23 @@
1
1
  import { copyFileSync, cpSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { generateMarkupCSS } from '../project/generateMarkupCSS';
4
- import { generateDropdownComponent } from './generateReactPage';
5
- export const scaffoldReact = ({ isSingleFrontend, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory }) => {
4
+ import { generateDropdownComponent, generateReactExamplePage, generateSignInComponent } from './generateReactComponents';
5
+ export const scaffoldReact = ({ isSingleFrontend, authOption, targetDirectory, templatesDirectory, frontends, assetsDirectory, projectAssetsDirectory, absProviders }) => {
6
+ mkdirSync(join(projectAssetsDirectory, 'svg'), { recursive: true });
6
7
  copyFileSync(join(templatesDirectory, 'assets', 'svg', 'react.svg'), join(projectAssetsDirectory, 'svg', 'react.svg'));
8
+ copyFileSync(join(templatesDirectory, 'assets', 'svg', 'google-logo.svg'), join(projectAssetsDirectory, 'svg', 'google-logo.svg'));
7
9
  cpSync(join(templatesDirectory, 'react'), targetDirectory, {
8
10
  recursive: true
9
11
  });
10
12
  const dropdownComponent = generateDropdownComponent(frontends);
11
13
  writeFileSync(join(targetDirectory, 'components', 'Dropdown.tsx'), dropdownComponent, 'utf-8');
14
+ if (authOption === 'abs') {
15
+ const signInComponent = generateSignInComponent(absProviders);
16
+ writeFileSync(join(targetDirectory, 'components', 'SignIn.tsx'), signInComponent, 'utf-8');
17
+ }
18
+ const pageComponent = generateReactExamplePage(authOption);
19
+ mkdirSync(join(targetDirectory, 'pages'), { recursive: true });
20
+ writeFileSync(join(targetDirectory, 'pages', 'ReactExample.tsx'), pageComponent, 'utf-8');
12
21
  const cssOutputDir = join(targetDirectory, 'styles');
13
22
  mkdirSync(cssOutputDir, { recursive: true });
14
23
  const cssOutputFile = join(cssOutputDir, 'react-example.css');
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { exit } from 'node:process';
2
+ import { exit } from 'process';
3
3
  import { outro } from '@clack/prompts';
4
4
  import { getDebugMessage, getOutroMessage, helpMessage } from './messages';
5
5
  import { prompt } from './prompt';
@@ -10,5 +10,5 @@ type DebugMessageProps = {
10
10
  response: CreateConfiguration;
11
11
  packageManager: string;
12
12
  };
13
- export declare const getDebugMessage: ({ response: { projectName, codeQualityTool, directoryConfig, useTailwind, tailwind, frontends, useHTMLScripts, frontendDirectories, buildDirectory, assetsDirectory, databaseEngine, databaseHost, databaseDirectory, orm, authProvider, plugins, initializeGitNow, installDependenciesNow }, packageManager }: DebugMessageProps) => string;
13
+ export declare const getDebugMessage: ({ response: { projectName, codeQualityTool, directoryConfig, useTailwind, tailwind, frontends, useHTMLScripts, frontendDirectories, buildDirectory, assetsDirectory, databaseEngine, databaseHost, databaseDirectory, orm, authOption, plugins, initializeGitNow, installDependenciesNow }, packageManager }: DebugMessageProps) => string;
14
14
  export {};