create-absolutejs 0.9.1 → 0.10.1

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/LICENSE CHANGED
@@ -1,24 +1,80 @@
1
- ```
2
- # Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0)
1
+ # Business Source License 1.1
3
2
 
4
- By using this project, you agree to the following terms under the Creative Commons Attribution-NonCommercial 4.0 International License.
3
+ **Licensor:** Alex Kahn
5
4
 
6
- ## License Summary
7
- This license allows you to:
8
- - **Share**: Copy and redistribute the material in any medium or format.
9
- - **Adapt**: Remix, transform, and build upon the material.
5
+ **Licensed Work:** AbsoluteJS (https://github.com/absolutejs/create-absolutejs)
10
6
 
11
- **Under the following conditions**:
12
- - **Attribution**: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
13
- - **Non-Commercial**: You may not use the material for commercial purposes.
7
+ **Change Date:** February 16, 2036
14
8
 
15
- ## Full License Text
16
- The full license text can be found at: https://creativecommons.org/licenses/by-nc/4.0/legalcode
9
+ **Change License:** Apache License, Version 2.0
17
10
 
18
- ## Contact Information for Commercial Use
19
- For commercial use, licensing inquiries, or additional permissions, please contact:
20
- Alex Kahn
21
- alexkahndev@gmail.com
22
- alexkahndev.github.io
23
- ```
11
+ ---
24
12
 
13
+ ## Terms
14
+
15
+ The Licensor hereby grants you the right to copy, modify, create derivative
16
+ works, redistribute, and make non-production use of the Licensed Work. The
17
+ Licensor may make an Additional Use Grant, permitting limited production use.
18
+
19
+ ### Additional Use Grant
20
+
21
+ You may use the Licensed Work in production, provided your use does not include
22
+ any of the following:
23
+
24
+ 1. **Offering a Competing Service.** You may not offer the Licensed Work, or
25
+ any derivative of it, to third parties as a hosted or managed service that
26
+ competes with the Licensed Work itself (for example, a "framework-as-a-service"
27
+ platform, deployment service, or cloud offering where AbsoluteJS or a
28
+ substantial portion of its functionality is a primary component of the value
29
+ provided to users).
30
+
31
+ 2. **Resale or Redistribution as a Standalone Product.** You may not sell,
32
+ license, or distribute the Licensed Work, or any derivative or fork of it,
33
+ as a standalone commercial product or framework.
34
+
35
+ 3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
36
+ the Licensed Work must prominently credit AbsoluteJS and include a link to
37
+ the original project repository (https://github.com/absolutejs/absolutejs).
38
+
39
+ For clarity, the following uses are expressly permitted:
40
+
41
+ - Using AbsoluteJS to build and deploy your own applications, websites, or
42
+ SaaS products (whether commercial or non-commercial).
43
+ - Using AbsoluteJS as a dependency in commercial software that you build and sell,
44
+ as long as the software is not itself a competing framework or hosting service.
45
+ - Providing consulting, development, or professional services to clients using
46
+ AbsoluteJS.
47
+ - Forking and modifying the Licensed Work for your own internal use, provided
48
+ attribution is maintained.
49
+
50
+ ### Change Date and Change License
51
+
52
+ On the Change Date specified above, or on such other date as the Licensor may
53
+ specify by written notice, the Licensed Work will be made available under the
54
+ Change License (Apache License, Version 2.0). Until the Change Date, the terms
55
+ of this Business Source License 1.1 apply.
56
+
57
+ ### Notices
58
+
59
+ You must not remove or obscure any licensing, copyright, or other notices
60
+ included in the Licensed Work.
61
+
62
+ ### No Warranty
63
+
64
+ THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
65
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
66
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
67
+ EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
68
+ WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
69
+ IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
70
+ LICENSED WORK.
71
+
72
+ ---
73
+
74
+ ## Contact
75
+
76
+ For commercial licensing inquiries or additional permissions, contact:
77
+
78
+ - **Alex Kahn**
79
+ - alexkahndev@gmail.com
80
+ - alexkahndev.github.io
package/README.md CHANGED
@@ -176,4 +176,4 @@ Contributions are welcome! Feel free to open issues or submit pull requests to i
176
176
 
177
177
  ## License
178
178
 
179
- Licensed under CC BY-NC 4.0.
179
+ **Business Source License 1.1 (BSL-1.1)** – see [`LICENSE`](./LICENSE) for details.
@@ -3,5 +3,5 @@ type CreatePackageJsonProps = Pick<CreateConfiguration, 'authOption' | 'useTailw
3
3
  projectName: string;
4
4
  latest: boolean;
5
5
  };
6
- export declare const createPackageJson: ({ projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool }: CreatePackageJsonProps) => void;
6
+ export declare const createPackageJson: ({ projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool }: CreatePackageJsonProps) => Promise<void>;
7
7
  export {};
@@ -3,7 +3,7 @@ import { join } from 'path';
3
3
  import { spinner } from '@clack/prompts';
4
4
  import { green } from 'picocolors';
5
5
  import { absoluteAuthPlugin, availablePlugins, defaultDependencies, defaultPlugins, eslintAndPrettierDependencies, eslintReactDependencies } from '../../data';
6
- import { getPackageVersion } from '../../utils/getPackageVersion';
6
+ import { getPackageVersions } from '../../utils/getPackageVersion';
7
7
  import { versions } from '../../versions';
8
8
  import { computeFlags } from '../project/computeFlags';
9
9
  const dbClientCommands = {
@@ -16,15 +16,93 @@ const dbClientCommands = {
16
16
  postgresql: 'psql -h localhost -U user -d database',
17
17
  singlestore: 'singlestore -u root -ppassword -D database'
18
18
  };
19
- export const createPackageJson = ({ projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool }) => {
19
+ export const createPackageJson = async ({ projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool }) => {
20
+ const flags = computeFlags(frontendDirectories);
21
+ const isLocal = !databaseHost || databaseHost === 'none';
22
+ /* ── Collect all package names that need versions ─────────── */
23
+ const packageNames = new Set();
24
+ packageNames.add('typescript');
25
+ for (const p of defaultPlugins)
26
+ packageNames.add(p.value);
27
+ for (const dep of defaultDependencies)
28
+ packageNames.add(dep.value);
29
+ if (authOption === 'abs')
30
+ packageNames.add(absoluteAuthPlugin.value);
31
+ for (const pluginValue of plugins) {
32
+ const meta = availablePlugins.find((p) => p.value === pluginValue);
33
+ if (meta)
34
+ packageNames.add(meta.value);
35
+ }
36
+ if (codeQualityTool === 'eslint+prettier') {
37
+ for (const dep of eslintAndPrettierDependencies)
38
+ packageNames.add(dep.value);
39
+ }
40
+ if (useTailwind) {
41
+ packageNames.add('autoprefixer');
42
+ packageNames.add('postcss');
43
+ packageNames.add('tailwindcss');
44
+ packageNames.add('@tailwindcss/cli');
45
+ }
46
+ if (flags.requiresReact) {
47
+ packageNames.add('react');
48
+ packageNames.add('react-dom');
49
+ packageNames.add('@types/react');
50
+ }
51
+ if (flags.requiresReact && codeQualityTool === 'eslint+prettier') {
52
+ for (const dep of eslintReactDependencies)
53
+ packageNames.add(dep.value);
54
+ }
55
+ if (flags.requiresSvelte)
56
+ packageNames.add('svelte');
57
+ if (flags.requiresSvelte && codeQualityTool === 'eslint+prettier')
58
+ packageNames.add('prettier-plugin-svelte');
59
+ if (flags.requiresVue)
60
+ packageNames.add('vue');
61
+ if (flags.requiresHtmx)
62
+ packageNames.add('elysia-scoped-state');
63
+ if (orm === 'drizzle')
64
+ packageNames.add('drizzle-orm');
65
+ switch (databaseHost) {
66
+ case 'neon':
67
+ packageNames.add('@neondatabase/serverless');
68
+ break;
69
+ case 'planetscale':
70
+ packageNames.add('@planetscale/database');
71
+ break;
72
+ case 'turso':
73
+ packageNames.add('@libsql/client');
74
+ break;
75
+ }
76
+ if (isLocal &&
77
+ (databaseEngine === 'mysql' || databaseEngine === 'mariadb') &&
78
+ orm === 'drizzle')
79
+ packageNames.add('mysql2');
80
+ if (isLocal && databaseEngine === 'singlestore')
81
+ packageNames.add('mysql2');
82
+ if (databaseEngine === 'postgresql' && databaseHost === 'planetscale') {
83
+ packageNames.add('pg');
84
+ packageNames.add('@types/pg');
85
+ }
86
+ if (isLocal && databaseEngine === 'mssql') {
87
+ packageNames.add('mssql');
88
+ packageNames.add('@types/mssql');
89
+ }
90
+ if (isLocal && databaseEngine === 'gel')
91
+ packageNames.add('gel');
92
+ if (databaseEngine === 'mongodb')
93
+ packageNames.add('mongodb');
94
+ /* ── Fetch all versions in parallel ──────────────────────── */
20
95
  const s = spinner();
21
- if (latest)
96
+ let latestVersions = new Map();
97
+ if (latest) {
22
98
  s.start('Resolving package versions…');
23
- const resolveVersion = (name, listed) => latest ? (getPackageVersion(name) ?? listed) : listed;
99
+ latestVersions = await getPackageVersions([...packageNames]);
100
+ }
101
+ const resolveVersion = (name, listed) => latest ? (latestVersions.get(name) ?? listed) : listed;
102
+ /* ── Build dependency maps ───────────────────────────────── */
24
103
  const dependencies = {};
25
104
  const devDependencies = {};
26
105
  devDependencies['typescript'] = resolveVersion('typescript', versions['typescript']);
27
- const flags = computeFlags(frontendDirectories);
28
106
  for (const p of defaultPlugins) {
29
107
  dependencies[p.value] = resolveVersion(p.value, p.latestVersion);
30
108
  }
@@ -91,13 +169,13 @@ export const createPackageJson = ({ projectName, authOption, plugins, databaseEn
91
169
  s.stop(green('Package versions resolved'));
92
170
  const scripts = {
93
171
  dev: 'absolutejs dev',
94
- format: `prettier --write "./**/*.{js,ts,css,json,mjs,md${flags.requiresReact ? ',jsx,tsx' : ''}${flags.requiresSvelte ? ',svelte' : ''}${flags.requiresVue ? ',vue' : ''}${flags.requiresHtml || flags.requiresHtmx ? ',html' : ''}}"`,
95
- lint: 'eslint ./src',
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',
96
174
  test: 'echo "Error: no test specified" && exit 1',
97
175
  typecheck: 'bun run tsc --noEmit'
98
176
  };
99
- const isLocal = !databaseHost || databaseHost === 'none';
100
- if (isLocal &&
177
+ const isLocalDb = isLocal;
178
+ if (isLocalDb &&
101
179
  databaseEngine !== undefined &&
102
180
  databaseEngine !== 'none' &&
103
181
  databaseEngine !== 'sqlite') {
@@ -111,29 +189,29 @@ export const createPackageJson = ({ projectName, authOption, plugins, databaseEn
111
189
  scripts[`predb:${databaseEngine}`] = 'bun db:up';
112
190
  scripts[`postdb:${databaseEngine}`] = 'bun db:down';
113
191
  }
114
- if (isLocal &&
192
+ if (isLocalDb &&
115
193
  (databaseEngine === 'mysql' || databaseEngine === 'mariadb') &&
116
194
  orm === 'drizzle') {
117
195
  dependencies['mysql2'] = resolveVersion('mysql2', versions['mysql2']);
118
196
  }
119
- if (isLocal && databaseEngine === 'singlestore') {
197
+ if (isLocalDb && databaseEngine === 'singlestore') {
120
198
  dependencies['mysql2'] = resolveVersion('mysql2', versions['mysql2']);
121
199
  }
122
200
  if (databaseEngine === 'postgresql' && databaseHost === 'planetscale') {
123
201
  dependencies['pg'] = resolveVersion('pg', versions['pg']);
124
202
  devDependencies['@types/pg'] = resolveVersion('@types/pg', versions['@types/pg']);
125
203
  }
126
- if (isLocal && databaseEngine === 'mssql') {
204
+ if (isLocalDb && databaseEngine === 'mssql') {
127
205
  dependencies['mssql'] = resolveVersion('mssql', versions['mssql']);
128
206
  devDependencies['@types/mssql'] = resolveVersion('@types/mssql', versions['@types/mssql']);
129
207
  }
130
- if (isLocal && databaseEngine === 'gel') {
208
+ if (isLocalDb && databaseEngine === 'gel') {
131
209
  dependencies['gel'] = resolveVersion('gel', versions['gel']);
132
210
  }
133
211
  if (databaseEngine === 'mongodb') {
134
212
  dependencies['mongodb'] = resolveVersion('mongodb', versions['mongodb']);
135
213
  }
136
- if (isLocal && databaseEngine === 'sqlite') {
214
+ if (isLocalDb && databaseEngine === 'sqlite') {
137
215
  scripts['db:sqlite'] = 'sqlite3 db/database.sqlite';
138
216
  scripts['db:init'] = 'sqlite3 db/database.sqlite < db/init.sql';
139
217
  }
@@ -22,7 +22,10 @@ export const generateHTMLPage = (frontends, useHTMLScripts) => {
22
22
  <body>
23
23
  <header>
24
24
  <a href="/">AbsoluteJS</a>
25
- <details>
25
+ <details
26
+ onpointerenter="if(event.pointerType==='mouse')this.open=true"
27
+ onpointerleave="if(event.pointerType==='mouse')this.open=false"
28
+ >
26
29
  <summary>Pages</summary>
27
30
  <nav>
28
31
  ${navLinks}
@@ -51,15 +54,15 @@ export const generateHTMLPage = (frontends, useHTMLScripts) => {
51
54
  count is <span id="counter">${initialCount}</span>
52
55
  </button>
53
56
  <p>
54
- Edit <code>example/html/pages/HtmlExample.html</code> save and
55
- rebuild to update the page.
57
+ Edit <code>example/html/pages/HtmlExample.html</code> and save
58
+ to test HMR.
56
59
  </p>
57
- <p style="color: #777">( Hot Module Reloading is coming soon )</p>
58
- <p style="margin-top: 2rem">
59
- Explore the other pages to see how AbsoluteJS seamlessly unifies
60
- multiple frameworks on a single server.
61
- </p>
62
- <p style="margin-top: 2rem; font-size: 1rem; color: #777">
60
+ ${frontends.length > 1
61
+ ? ` <p style="margin-top: 2rem">
62
+ Explore the other pages to see multiple frameworks running
63
+ together.
64
+ </p>\n`
65
+ : ''} <p style="margin-top: 2rem; font-size: 1rem; color: #777">
63
66
  Click on the AbsoluteJS and HTML logos to learn more.
64
67
  </p>
65
68
  </main>
@@ -24,8 +24,8 @@ export const generateHTMXPage = (isSingle, frontends) => {
24
24
  <header>
25
25
  <a href="/">AbsoluteJS</a>
26
26
  <details
27
- hx-on:pointerenter="this.open = true"
28
- hx-on:pointerleave="this.open = false"
27
+ onpointerenter="if(event.pointerType==='mouse')this.open=true"
28
+ onpointerleave="if(event.pointerType==='mouse')this.open=false"
29
29
  >
30
30
  <summary>Pages</summary>
31
31
  <nav>
@@ -73,15 +73,15 @@ export const generateHTMXPage = (isSingle, frontends) => {
73
73
  >
74
74
  </button>
75
75
  <p>
76
- Edit <code>example/htmx/pages/HtmxHome.html</code> save and
77
- rebuild to update the page.
76
+ Edit <code>example/htmx/pages/HtmxHome.html</code> and save
77
+ to test HMR.
78
78
  </p>
79
- <p style="color: #777">( Hot Module Reloading is coming soon )</p>
80
- <p style="margin-top: 2rem">
81
- Explore the other pages to see how AbsoluteJS seamlessly unifies
82
- multiple frameworks on a single server.
83
- </p>
84
- <p style="margin-top: 2rem; font-size: 1rem; color: #777">
79
+ ${frontends.length > 1
80
+ ? ` <p style="margin-top: 2rem">
81
+ Explore the other pages to see multiple frameworks running
82
+ together.
83
+ </p>\n`
84
+ : ''} <p style="margin-top: 2rem; font-size: 1rem; color: #777">
85
85
  Click on the AbsoluteJS and HTML logos to learn more.
86
86
  </p>
87
87
  </main>
@@ -88,6 +88,7 @@ header details[open] summary::after {
88
88
  }
89
89
 
90
90
  header details nav {
91
+ content-visibility: visible;
91
92
  position: absolute;
92
93
  top: 100%;
93
94
  right: -0.5rem;
@@ -121,6 +122,29 @@ header details nav a {
121
122
  white-space: nowrap;
122
123
  }
123
124
 
125
+ @media (max-width: 480px) {
126
+ header {
127
+ padding: 1rem;
128
+ }
129
+
130
+ h1 {
131
+ font-size: 1.75rem;
132
+ }
133
+
134
+ .logo {
135
+ height: 5rem;
136
+ width: 5rem;
137
+ }
138
+
139
+ nav {
140
+ gap: 2rem;
141
+ }
142
+
143
+ header details summary {
144
+ font-size: 1.2rem;
145
+ }
146
+ }
147
+
124
148
  @media (prefers-color-scheme: light) {
125
149
  header {
126
150
  background-color: #ffffff;
@@ -3,6 +3,7 @@ type ScaffoldFrontendsProps = Pick<CreateConfiguration, 'useHTMLScripts' | 'fron
3
3
  frontendDirectory: string;
4
4
  templatesDirectory: string;
5
5
  projectAssetsDirectory: string;
6
+ typesDirectory: string;
6
7
  };
7
- export declare const scaffoldFrontends: ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, useHTMLScripts, useTailwind, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
8
+ export declare const scaffoldFrontends: ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, typesDirectory, useHTMLScripts, useTailwind, frontendDirectories, frontends }: ScaffoldFrontendsProps) => void;
8
9
  export {};
@@ -5,7 +5,7 @@ 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, useTailwind, frontendDirectories, frontends }) => {
8
+ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProviders, authOption, templatesDirectory, projectAssetsDirectory, typesDirectory, useHTMLScripts, useTailwind, frontendDirectories, frontends }) => {
9
9
  const stylesTargetDirectory = join(frontendDirectory, 'styles');
10
10
  cpSync(join(templatesDirectory, 'styles'), stylesTargetDirectory, {
11
11
  recursive: true
@@ -60,6 +60,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
60
60
  targetDirectory,
61
61
  templatesDirectory
62
62
  });
63
+ copyFileSync(join(templatesDirectory, 'types', 'vue-shim.d.ts'), join(typesDirectory, 'vue-shim.d.ts'));
63
64
  break;
64
65
  case 'angular':
65
66
  console.warn('Angular is not yet supported. Refer to the documentation for more information.');
@@ -1,5 +1,6 @@
1
1
  import { ProviderOption } from '@absolutejs/auth';
2
2
  import { AuthOption, Frontend } from '../../types';
3
+ export declare const generateAppComponent: (frontends: Frontend[]) => string;
3
4
  export declare const generateDropdownComponent: (frontends: Frontend[]) => string;
4
5
  export declare const generateSignInComponent: (absProviders: ProviderOption[] | undefined) => string;
5
6
  export declare const generateReactExamplePage: (authOption: AuthOption) => string;
@@ -1,40 +1,95 @@
1
1
  import { formatNavLink } from '../../utils/formatNavLink';
2
- export const generateDropdownComponent = (frontends) => {
3
- const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
2
+ export const generateAppComponent = (frontends) => {
3
+ const exploreBlock = frontends.length > 1
4
+ ? `\n\t\t\t<p style={{ marginTop: '2rem' }}>\n\t\t\t\tExplore the other pages to see multiple frameworks running\n\t\t\t\ttogether.\n\t\t\t</p>`
5
+ : '';
4
6
  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
7
 
27
- export const SignIn = () => {
28
- const [isOpen, setIsOpen] = useState(false);
29
-
8
+ type AppProps = { initialCount: number };
9
+
10
+ export const App = ({ initialCount }: AppProps) => {
11
+ const [count, setCount] = useState(initialCount);
12
+
30
13
  return (
31
- <details
32
- onPointerEnter={() => setIsOpen(true)}
33
- onPointerLeave={() => setIsOpen(false)}
34
- open={isOpen}
35
- >
36
- <summary>Sign In</summary>
14
+ <main>
37
15
  <nav>
16
+ <a href="https://absolutejs.com" target="_blank">
17
+ <img
18
+ className="logo"
19
+ src="/assets/png/absolutejs-temp.png"
20
+ alt="AbsoluteJS Logo"
21
+ />
22
+ </a>
23
+ <a href="https://react.dev/">
24
+ <img
25
+ className="logo react"
26
+ src="/assets/svg/react.svg"
27
+ alt="React Logo"
28
+ />
29
+ </a>
30
+ </nav>
31
+ <h1>AbsoluteJS + React</h1>
32
+ <button onClick={() => setCount(count + 1)}>
33
+ count is {count}
34
+ </button>
35
+ <p>
36
+ Edit <code>example/react/pages/ReactExample.tsx</code> and save
37
+ to test HMR.
38
+ </p>${exploreBlock}
39
+ <p
40
+ style={{
41
+ color: '#777',
42
+ fontSize: '1rem',
43
+ marginTop: '2rem'
44
+ }}
45
+ >
46
+ Click on the AbsoluteJS and React logos to learn more.
47
+ </p>
48
+ </main>
49
+ );
50
+ };
51
+ `;
52
+ };
53
+ export const generateDropdownComponent = (frontends) => {
54
+ const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
55
+ return `export const Dropdown = () => (
56
+ <details
57
+ onPointerEnter={(event) => {
58
+ if (event.pointerType === 'mouse') {
59
+ event.currentTarget.open = true;
60
+ }
61
+ }}
62
+ onPointerLeave={(event) => {
63
+ if (event.pointerType === 'mouse') {
64
+ event.currentTarget.open = false;
65
+ }
66
+ }}
67
+ >
68
+ <summary>Pages</summary>
69
+ <nav>
70
+ ${navLinks}
71
+ </nav>
72
+ </details>
73
+ );
74
+ `;
75
+ };
76
+ export const generateSignInComponent = (absProviders) => `import { OAuthLink } from './OAuthLink';
77
+
78
+ export const SignIn = () => (
79
+ <details
80
+ onPointerEnter={(event) => {
81
+ if (event.pointerType === 'mouse') {
82
+ event.currentTarget.open = true;
83
+ }
84
+ }}
85
+ onPointerLeave={(event) => {
86
+ if (event.pointerType === 'mouse') {
87
+ event.currentTarget.open = false;
88
+ }
89
+ }}
90
+ >
91
+ <summary>Sign In</summary>
92
+ <nav>
38
93
  ${absProviders && absProviders.length > 0
39
94
  ? absProviders
40
95
  .map((provider) => {
@@ -47,8 +102,7 @@ export const SignIn = () => {
47
102
  : `<p>No OAuth providers configured</p>`}
48
103
  </nav>
49
104
  </details>
50
- );
51
- }
105
+ );
52
106
  `;
53
107
  export const generateReactExamplePage = (authOption) => {
54
108
  const useBlockReturn = authOption === 'abs';
@@ -1,7 +1,7 @@
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, generateReactExamplePage, generateSignInComponent } from './generateReactComponents';
4
+ import { generateAppComponent, generateDropdownComponent, generateReactExamplePage, generateSignInComponent } from './generateReactComponents';
5
5
  export const scaffoldReact = ({ isSingleFrontend, authOption, targetDirectory, templatesDirectory, frontends, assetsDirectory, projectAssetsDirectory, absProviders }) => {
6
6
  mkdirSync(join(projectAssetsDirectory, 'svg'), { recursive: true });
7
7
  copyFileSync(join(templatesDirectory, 'assets', 'svg', 'react.svg'), join(projectAssetsDirectory, 'svg', 'react.svg'));
@@ -9,6 +9,8 @@ export const scaffoldReact = ({ isSingleFrontend, authOption, targetDirectory, t
9
9
  cpSync(join(templatesDirectory, 'react'), targetDirectory, {
10
10
  recursive: true
11
11
  });
12
+ const appComponent = generateAppComponent(frontends);
13
+ writeFileSync(join(targetDirectory, 'components', 'App.tsx'), appComponent, 'utf-8');
12
14
  const dropdownComponent = generateDropdownComponent(frontends);
13
15
  writeFileSync(join(targetDirectory, 'components', 'Dropdown.tsx'), dropdownComponent, 'utf-8');
14
16
  if (authOption === 'abs') {
@@ -9,7 +9,19 @@ export const generateSveltePage = (frontends) => {
9
9
  import Counter from '../components/Counter.svelte';
10
10
 
11
11
  let { initialCount, cssPath }: SvelteExampleProps = $props();
12
- let isOpen = $state(false);
12
+ let dropdown: HTMLDetailsElement;
13
+
14
+ const openDropdown = (event: PointerEvent) => {
15
+ if (event.pointerType === 'mouse') {
16
+ dropdown.open = true;
17
+ }
18
+ };
19
+
20
+ const closeDropdown = (event: PointerEvent) => {
21
+ if (event.pointerType === 'mouse') {
22
+ dropdown.open = false;
23
+ }
24
+ };
13
25
  </script>
14
26
 
15
27
  <svelte:head>
@@ -34,9 +46,9 @@ export const generateSveltePage = (frontends) => {
34
46
  <header>
35
47
  <a href="/">AbsoluteJS</a>
36
48
  <details
37
- open={isOpen}
38
- onpointerenter={() => (isOpen = true)}
39
- onpointerleave={() => (isOpen = false)}
49
+ bind:this={dropdown}
50
+ onpointerenter={openDropdown}
51
+ onpointerleave={closeDropdown}
40
52
  >
41
53
  <summary>Pages</summary>
42
54
  <nav>
@@ -65,15 +77,15 @@ export const generateSveltePage = (frontends) => {
65
77
  <h1>AbsoluteJS + Svelte</h1>
66
78
  <Counter {initialCount} />
67
79
  <p>
68
- Edit <code>example/svelte/pages/SvelteExample.svelte</code> then save and
69
- refresh to update the page.
70
- </p>
71
- <p style="color: #777">( Hot Module Reloading is coming soon )</p>
72
- <p style="margin-top: 2rem;">
73
- Explore the other pages to see how AbsoluteJS seamlessly unifies
74
- multiple frameworks on a single server.
80
+ Edit <code>example/svelte/pages/SvelteExample.svelte</code> and save
81
+ to test HMR.
75
82
  </p>
76
- <p style="color: #777; font-size: 1rem; margin-top: 2rem;">
83
+ ${frontends.length > 1
84
+ ? ` <p style="margin-top: 2rem;">
85
+ Explore the other pages to see multiple frameworks running
86
+ together.
87
+ </p>\n`
88
+ : ''} <p style="color: #777; font-size: 1rem; margin-top: 2rem;">
77
89
  Click on the AbsoluteJS and Svelte logos to learn more.
78
90
  </p>
79
91
  </main>
@@ -172,6 +184,7 @@ export const generateSveltePage = (frontends) => {
172
184
  }
173
185
 
174
186
  header details nav {
187
+ content-visibility: visible;
175
188
  position: absolute;
176
189
  top: 100%;
177
190
  right: -0.5rem;
@@ -205,6 +218,29 @@ export const generateSveltePage = (frontends) => {
205
218
  white-space: nowrap;
206
219
  }
207
220
 
221
+ @media (max-width: 480px) {
222
+ header {
223
+ padding: 1rem;
224
+ }
225
+
226
+ h1 {
227
+ font-size: 1.75rem;
228
+ }
229
+
230
+ .logo {
231
+ height: 5rem;
232
+ width: 5rem;
233
+ }
234
+
235
+ nav {
236
+ gap: 2rem;
237
+ }
238
+
239
+ header details summary {
240
+ font-size: 1.2rem;
241
+ }
242
+ }
243
+
208
244
  @media (prefers-color-scheme: light) {
209
245
  header {
210
246
  background-color: #ffffff;
@@ -2,24 +2,36 @@ import { formatNavLink } from '../../utils/formatNavLink';
2
2
  export const generateVuePage = (frontends) => {
3
3
  const navLinks = frontends.map(formatNavLink).join('\n\t\t\t');
4
4
  return `<script setup lang="ts">
5
- import { ref } from 'vue';
6
5
  import CountButton from '../components/CountButton.vue';
6
+ import { ref } from 'vue';
7
7
 
8
8
  const props = defineProps<{
9
9
  initialCount: number;
10
10
  }>();
11
11
 
12
12
  const count = ref(props.initialCount);
13
- const isOpen = ref(false);
13
+ const dropdown = ref<HTMLDetailsElement>();
14
+
15
+ const openDropdown = (event: PointerEvent) => {
16
+ if (event.pointerType === 'mouse' && dropdown.value) {
17
+ dropdown.value.open = true;
18
+ }
19
+ };
20
+
21
+ const closeDropdown = (event: PointerEvent) => {
22
+ if (event.pointerType === 'mouse' && dropdown.value) {
23
+ dropdown.value.open = false;
24
+ }
25
+ };
14
26
  </script>
15
27
 
16
28
  <template>
17
29
  <header>
18
30
  <a href="/">AbsoluteJS</a>
19
31
  <details
20
- :open="isOpen"
21
- @pointerenter="isOpen = true"
22
- @pointerleave="isOpen = false"
32
+ ref="dropdown"
33
+ @pointerenter="openDropdown"
34
+ @pointerleave="closeDropdown"
23
35
  >
24
36
  <summary>Pages</summary>
25
37
  <nav>
@@ -48,15 +60,15 @@ const isOpen = ref(false);
48
60
  <h1>AbsoluteJS + Vue</h1>
49
61
  <CountButton :initialCount="count" />
50
62
  <p>
51
- Edit <code>example/vue/pages/VueExample.vue</code> then save and
52
- refresh to update the page.
53
- </p>
54
- <p style="color: #777">( Hot Module Reloading is coming soon )</p>
55
- <p style="margin-top: 2rem">
56
- Explore the other pages to see how AbsoluteJS seamlessly unifies
57
- multiple frameworks on a single server.
63
+ Edit <code>example/vue/pages/VueExample.vue</code> and save
64
+ to test HMR.
58
65
  </p>
59
- <p style="color: #777; font-size: 1rem; margin-top: 2rem">
66
+ ${frontends.length > 1
67
+ ? ` <p style="margin-top: 2rem">
68
+ Explore the other pages to see multiple frameworks running
69
+ together.
70
+ </p>\n`
71
+ : ''} <p style="color: #777; font-size: 1rem; margin-top: 2rem">
60
72
  Click on the AbsoluteJS and Vue logos to learn more.
61
73
  </p>
62
74
  </main>
@@ -214,6 +226,7 @@ header details[open] summary::after {
214
226
  }
215
227
 
216
228
  header details nav {
229
+ content-visibility: visible;
217
230
  position: absolute;
218
231
  top: 100%;
219
232
  right: -0.5rem;
@@ -247,6 +260,41 @@ header details nav a {
247
260
  white-space: nowrap;
248
261
  }
249
262
 
263
+ @media (max-width: 480px) {
264
+ :global(main) {
265
+ padding: 1rem;
266
+ }
267
+
268
+ :global(p) {
269
+ font-size: 1rem;
270
+ }
271
+
272
+ header {
273
+ padding: 1rem;
274
+ }
275
+
276
+ a {
277
+ font-size: 1.2rem;
278
+ }
279
+
280
+ h1 {
281
+ font-size: 1.75rem;
282
+ }
283
+
284
+ .logo {
285
+ height: 5rem;
286
+ width: 5rem;
287
+ }
288
+
289
+ nav {
290
+ gap: 2rem;
291
+ }
292
+
293
+ header details summary {
294
+ font-size: 1.2rem;
295
+ }
296
+ }
297
+
250
298
  @media (prefers-color-scheme: light) {
251
299
  :global(body) {
252
300
  background-color: #f5f5f5;
package/dist/scaffold.js CHANGED
@@ -26,7 +26,7 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
26
26
  tailwind,
27
27
  templatesDirectory
28
28
  });
29
- createPackageJson({
29
+ await createPackageJson({
30
30
  authOption,
31
31
  codeQualityTool,
32
32
  databaseEngine,
@@ -77,6 +77,7 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
77
77
  frontends,
78
78
  projectAssetsDirectory,
79
79
  templatesDirectory,
80
+ typesDirectory,
80
81
  useHTMLScripts,
81
82
  useTailwind
82
83
  });
@@ -3,9 +3,9 @@
3
3
  /* Visit https://aka.ms/tsconfig to read more about this file */
4
4
 
5
5
  /* Projects */
6
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
6
+ "incremental": true,
7
+ "tsBuildInfoFile": ".absolutejs/tsconfig.tsbuildinfo",
7
8
  // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8
- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9
9
  // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10
10
  // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11
11
  // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
@@ -85,12 +85,6 @@
85
85
  "skipLibCheck": true /* Type Checking */,
86
86
  /* Skip type checking all .d.ts files. */ "strict": true /* Visit https://aka.ms/tsconfig to read more about this file */,
87
87
  /* Enable all strict type-checking options. */ /* Projects */
88
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
89
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
90
- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
91
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
92
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
93
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
94
88
  /* Language and Environment */
95
89
  "target": "ESNext"
96
90
  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ /* Skip type checking all .d.ts files. */
@@ -12,10 +12,14 @@ button.addEventListener('click', () => {
12
12
  counter.textContent = (++count).toString();
13
13
  });
14
14
 
15
- details.addEventListener('pointerenter', () => {
16
- details.open = true;
15
+ details.addEventListener('pointerenter', (event) => {
16
+ if (event.pointerType === 'mouse') {
17
+ details.open = true;
18
+ }
17
19
  });
18
20
 
19
- details.addEventListener('pointerleave', () => {
20
- details.open = false;
21
+ details.addEventListener('pointerleave', (event) => {
22
+ if (event.pointerType === 'mouse') {
23
+ details.open = false;
24
+ }
21
25
  });
@@ -28,15 +28,12 @@ export const App = ({ initialCount }: AppProps) => {
28
28
  count is {count}
29
29
  </button>
30
30
  <p>
31
- Edit <code>example/react/pages/ReactExample.tsx</code> then save
32
- and refresh to update the page.
33
- </p>
34
- <p style={{ color: '#777' }}>
35
- ( Hot Module Reloading is coming soon )
31
+ Edit <code>example/react/pages/ReactExample.tsx</code> and save
32
+ to test HMR.
36
33
  </p>
37
34
  <p style={{ marginTop: '2rem' }}>
38
- Explore the other pages to see how AbsoluteJS seamlessly unifies
39
- multiple frameworks on a single server.
35
+ Explore the other pages to see multiple frameworks running
36
+ together.
40
37
  </p>
41
38
  <p
42
39
  style={{
@@ -76,6 +76,20 @@ button:focus-visible {
76
76
  outline: 4px auto -webkit-focus-ring-color;
77
77
  }
78
78
 
79
+ @media (max-width: 480px) {
80
+ main {
81
+ padding: 1rem;
82
+ }
83
+
84
+ p {
85
+ font-size: 1rem;
86
+ }
87
+
88
+ a {
89
+ font-size: 1.2rem;
90
+ }
91
+ }
92
+
79
93
  @media (prefers-color-scheme: light) {
80
94
  body {
81
95
  background-color: #f5f5f5;
@@ -0,0 +1,10 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+
4
+ const component: DefineComponent<
5
+ Record<string, unknown>,
6
+ Record<string, unknown>,
7
+ unknown
8
+ >;
9
+ export default component;
10
+ }
@@ -1 +1 @@
1
- export declare const getPackageVersion: (packageName: string) => string | null;
1
+ export declare const getPackageVersions: (packageNames: string[]) => Promise<Map<string, string>>;
@@ -1,12 +1,18 @@
1
- import { execSync } from 'child_process';
2
- export const getPackageVersion = (packageName) => {
3
- try {
4
- const raw = execSync(`curl -s https://registry.npmjs.org/${packageName}/latest`);
5
- const { version } = JSON.parse(raw.toString());
6
- return version;
7
- }
8
- catch (err) {
9
- console.error(`Error fetching version for ${packageName}:`, err);
10
- return null;
1
+ export const getPackageVersions = async (packageNames) => {
2
+ const results = await Promise.all(packageNames.map(async (name) => {
3
+ try {
4
+ const res = await fetch(`https://registry.npmjs.org/${name}/latest`);
5
+ const data = (await res.json());
6
+ return [name, data.version];
7
+ }
8
+ catch {
9
+ return [name, null];
10
+ }
11
+ }));
12
+ const map = new Map();
13
+ for (const [name, version] of results) {
14
+ if (version)
15
+ map.set(name, version);
11
16
  }
17
+ return map;
12
18
  };
@@ -4,7 +4,7 @@
4
4
  * Run `bun run check-versions` to compare against latest npm versions.
5
5
  */
6
6
  export declare const versions: {
7
- readonly '@absolutejs/absolute': "0.15.7";
7
+ readonly '@absolutejs/absolute': "0.16.9";
8
8
  readonly '@absolutejs/auth': "0.22.0";
9
9
  readonly '@elysiajs/static': "1.4.7";
10
10
  readonly elysia: "1.4.22";
package/dist/versions.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export const versions = {
7
7
  /* ── Core ─────────────────────────────────────────────── */
8
- '@absolutejs/absolute': '0.15.7',
8
+ '@absolutejs/absolute': '0.16.9',
9
9
  '@absolutejs/auth': '0.22.0',
10
10
  '@elysiajs/static': '1.4.7',
11
11
  elysia: '1.4.22',
package/package.json CHANGED
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "description": "A CLI tool to create a new AbsoluteJS project",
12
12
  "devDependencies": {
13
+ "@absolutejs/absolute": "0.16.9",
13
14
  "@stylistic/eslint-plugin-ts": "4.2.0",
14
15
  "@types/bun": "1.3.8",
15
16
  "@types/react": "19.2.13",
@@ -32,7 +33,7 @@
32
33
  "dist",
33
34
  "dist/templates"
34
35
  ],
35
- "license": "CC BY-NC 4.0",
36
+ "license": "BSL-1.1",
36
37
  "main": "dist/index.js",
37
38
  "name": "create-absolutejs",
38
39
  "peerDependencies": {
@@ -49,5 +50,5 @@
49
50
  "typecheck": "bun run tsc --noEmit"
50
51
  },
51
52
  "type": "module",
52
- "version": "0.9.1"
53
+ "version": "0.10.1"
53
54
  }