@hypersonic-js/cli 0.2.0 β†’ 0.2.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.
Files changed (37) hide show
  1. package/README.md +13 -0
  2. package/dist/package.json +6 -6
  3. package/dist/src/commands/new/generate-files.d.ts +98 -0
  4. package/dist/src/commands/new/generate-files.d.ts.map +1 -0
  5. package/dist/src/commands/new/generate-files.js +79 -0
  6. package/dist/src/commands/new/generate-files.js.map +1 -0
  7. package/dist/src/commands/new/index.d.ts +31 -0
  8. package/dist/src/commands/new/index.d.ts.map +1 -0
  9. package/dist/src/commands/new/index.js +102 -0
  10. package/dist/src/commands/new/index.js.map +1 -0
  11. package/dist/src/commands/new/run-setup.d.ts +34 -0
  12. package/dist/src/commands/new/run-setup.d.ts.map +1 -0
  13. package/dist/src/commands/new/run-setup.js +70 -0
  14. package/dist/src/commands/new/run-setup.js.map +1 -0
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +2 -0
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/templates/new/.env.example +2 -0
  19. package/dist/templates/new/_env +2 -0
  20. package/dist/templates/new/eslint.config.js +17 -0
  21. package/dist/templates/new/hypersonic.config.ts +17 -0
  22. package/dist/templates/new/package.json +39 -0
  23. package/dist/templates/new/prisma/schema.prisma +71 -0
  24. package/dist/templates/new/prisma.config.ts +18 -0
  25. package/dist/templates/new/resources/css/app.css +1 -0
  26. package/dist/templates/new/resources/js/Pages/Auth/Login.tsx +96 -0
  27. package/dist/templates/new/resources/js/Pages/Auth/Register.tsx +107 -0
  28. package/dist/templates/new/resources/js/Pages/Welcome.tsx +54 -0
  29. package/dist/templates/new/resources/js/app.tsx +15 -0
  30. package/dist/templates/new/resources/js/lib/auth-client.ts +5 -0
  31. package/dist/templates/new/server.ts +71 -0
  32. package/dist/templates/new/src/middleware.ts +27 -0
  33. package/dist/templates/new/src/types.ts +17 -0
  34. package/dist/templates/new/tsconfig.json +14 -0
  35. package/dist/templates/new/types.ts +17 -0
  36. package/dist/templates/new/vite.config.ts +13 -0
  37. package/package.json +8 -8
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @hypersonic-js/cli
2
+
3
+ Hypersonic.js CLI β€” developer tooling for the Hypersonic framework
4
+
5
+ πŸ“– **[Full documentation β†’ hypersonic-js.com](https://hypersonic-js.com)**
6
+
7
+ ## Install
8
+
9
+ npm install @hypersonic-js/cli
10
+
11
+ ## License
12
+
13
+ MIT
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypersonic-js/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Hypersonic.js CLI β€” developer tooling for the Hypersonic framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,10 +20,10 @@
20
20
  "access": "public"
21
21
  },
22
22
  "engines": {
23
- "node": "^24.0.0"
23
+ "node": ">=24.0.0"
24
24
  },
25
25
  "scripts": {
26
- "build": "tsc",
26
+ "build": "tsc && node --input-type=module --eval \"import{cpSync}from'node:fs';cpSync('templates','dist/templates',{recursive:true})\"",
27
27
  "test": "vitest run",
28
28
  "test:coverage": "vitest run --coverage",
29
29
  "type-check": "tsc --noEmit",
@@ -41,9 +41,9 @@
41
41
  "@prisma/adapter-pg": "7.8.0",
42
42
  "@prisma/client": "7.8.0",
43
43
  "@types/node": "25.9.3",
44
- "@vitest/coverage-v8": "4.1.8",
45
- "better-auth": "1.6.17",
44
+ "@vitest/coverage-v8": "4.1.9",
45
+ "better-auth": "1.6.19",
46
46
  "typescript": "6.0.3",
47
- "vitest": "4.1.8"
47
+ "vitest": "4.1.9"
48
48
  }
49
49
  }
@@ -0,0 +1,98 @@
1
+ export interface GenerateFilesOptions {
2
+ projectDir: string;
3
+ projectName: string;
4
+ secret: string;
5
+ }
6
+ export interface WrittenFile {
7
+ /** Destination path relative to the project root (e.g. 'prisma/schema.prisma'). */
8
+ dest: string;
9
+ }
10
+ /**
11
+ * Injectable I/O deps so unit tests never touch the real filesystem.
12
+ * templatesDir lets tests point at a fake templates directory.
13
+ */
14
+ export interface GenerateFilesDeps {
15
+ readFile: (filePath: string) => string;
16
+ mkdir: (dirPath: string) => void;
17
+ writeFile: (filePath: string, content: string) => void;
18
+ templatesDir: string;
19
+ }
20
+ /**
21
+ * Static list of every file the `new` command writes.
22
+ *
23
+ * `src` β€” filename inside templates/new/ (relative, may differ from dest)
24
+ * `dest` β€” filename written into the project (relative to projectDir)
25
+ *
26
+ * The only src↔dest rename is `_env` β†’ `.env`: the root .gitignore has a bare
27
+ * `.env` pattern that matches files at any depth, so the template is stored
28
+ * under a neutral name and renamed on write.
29
+ */
30
+ export declare const TEMPLATE_FILES: readonly [{
31
+ readonly src: "package.json";
32
+ readonly dest: "package.json";
33
+ }, {
34
+ readonly src: "hypersonic.config.ts";
35
+ readonly dest: "hypersonic.config.ts";
36
+ }, {
37
+ readonly src: "_env";
38
+ readonly dest: ".env";
39
+ }, {
40
+ readonly src: ".env.example";
41
+ readonly dest: ".env.example";
42
+ }, {
43
+ readonly src: ".gitignore";
44
+ readonly dest: ".gitignore";
45
+ }, {
46
+ readonly src: "tsconfig.json";
47
+ readonly dest: "tsconfig.json";
48
+ }, {
49
+ readonly src: "eslint.config.js";
50
+ readonly dest: "eslint.config.js";
51
+ }, {
52
+ readonly src: "vite.config.ts";
53
+ readonly dest: "vite.config.ts";
54
+ }, {
55
+ readonly src: "prisma/schema.prisma";
56
+ readonly dest: "prisma/schema.prisma";
57
+ }, {
58
+ readonly src: "prisma.config.ts";
59
+ readonly dest: "prisma.config.ts";
60
+ }, {
61
+ readonly src: "server.ts";
62
+ readonly dest: "server.ts";
63
+ }, {
64
+ readonly src: "src/types.ts";
65
+ readonly dest: "src/types.ts";
66
+ }, {
67
+ readonly src: "src/middleware.ts";
68
+ readonly dest: "src/middleware.ts";
69
+ }, {
70
+ readonly src: "resources/css/app.css";
71
+ readonly dest: "resources/css/app.css";
72
+ }, {
73
+ readonly src: "resources/js/app.tsx";
74
+ readonly dest: "resources/js/app.tsx";
75
+ }, {
76
+ readonly src: "resources/js/lib/auth-client.ts";
77
+ readonly dest: "resources/js/lib/auth-client.ts";
78
+ }, {
79
+ readonly src: "resources/js/Pages/Welcome.tsx";
80
+ readonly dest: "resources/js/Pages/Welcome.tsx";
81
+ }, {
82
+ readonly src: "resources/js/Pages/Auth/Login.tsx";
83
+ readonly dest: "resources/js/Pages/Auth/Login.tsx";
84
+ }, {
85
+ readonly src: "resources/js/Pages/Auth/Register.tsx";
86
+ readonly dest: "resources/js/Pages/Auth/Register.tsx";
87
+ }];
88
+ /**
89
+ * Replaces every `{{KEY}}` placeholder in `content` with the corresponding
90
+ * value from `vars`. Unknown placeholders are left untouched.
91
+ */
92
+ export declare function applySubstitutions(content: string, vars: Record<string, string>): string;
93
+ /**
94
+ * Reads every template file, applies placeholder substitution, and writes it
95
+ * into the target project directory.
96
+ */
97
+ export declare function generateFiles(opts: GenerateFilesOptions, deps?: GenerateFilesDeps): Promise<WrittenFile[]>;
98
+ //# sourceMappingURL=generate-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-files.d.ts","sourceRoot":"","sources":["../../../../src/commands/new/generate-files.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACtD,YAAY,EAAE,MAAM,CAAA;CACrB;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBjB,CAAA;AAIV;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,MAAM,CAIR;AAkBD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,oBAAoB,EAC1B,IAAI,GAAE,iBAAqC,GAC1C,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB"}
@@ -0,0 +1,79 @@
1
+ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // ── Template file manifest ─────────────────────────────────────────────────
5
+ /**
6
+ * Static list of every file the `new` command writes.
7
+ *
8
+ * `src` β€” filename inside templates/new/ (relative, may differ from dest)
9
+ * `dest` β€” filename written into the project (relative to projectDir)
10
+ *
11
+ * The only src↔dest rename is `_env` β†’ `.env`: the root .gitignore has a bare
12
+ * `.env` pattern that matches files at any depth, so the template is stored
13
+ * under a neutral name and renamed on write.
14
+ */
15
+ export const TEMPLATE_FILES = [
16
+ { src: 'package.json', dest: 'package.json' },
17
+ { src: 'hypersonic.config.ts', dest: 'hypersonic.config.ts' },
18
+ { src: '_env', dest: '.env' },
19
+ { src: '.env.example', dest: '.env.example' },
20
+ { src: '.gitignore', dest: '.gitignore' },
21
+ { src: 'tsconfig.json', dest: 'tsconfig.json' },
22
+ { src: 'eslint.config.js', dest: 'eslint.config.js' },
23
+ { src: 'vite.config.ts', dest: 'vite.config.ts' },
24
+ { src: 'prisma/schema.prisma', dest: 'prisma/schema.prisma' },
25
+ { src: 'prisma.config.ts', dest: 'prisma.config.ts' },
26
+ { src: 'server.ts', dest: 'server.ts' },
27
+ { src: 'src/types.ts', dest: 'src/types.ts' },
28
+ { src: 'src/middleware.ts', dest: 'src/middleware.ts' },
29
+ { src: 'resources/css/app.css', dest: 'resources/css/app.css' },
30
+ { src: 'resources/js/app.tsx', dest: 'resources/js/app.tsx' },
31
+ { src: 'resources/js/lib/auth-client.ts', dest: 'resources/js/lib/auth-client.ts' },
32
+ { src: 'resources/js/Pages/Welcome.tsx', dest: 'resources/js/Pages/Welcome.tsx' },
33
+ { src: 'resources/js/Pages/Auth/Login.tsx', dest: 'resources/js/Pages/Auth/Login.tsx' },
34
+ { src: 'resources/js/Pages/Auth/Register.tsx', dest: 'resources/js/Pages/Auth/Register.tsx' },
35
+ ];
36
+ // ── Substitution ───────────────────────────────────────────────────────────
37
+ /**
38
+ * Replaces every `{{KEY}}` placeholder in `content` with the corresponding
39
+ * value from `vars`. Unknown placeholders are left untouched.
40
+ */
41
+ export function applySubstitutions(content, vars) {
42
+ return content.replace(/\{\{(\w+)\}\}/g, (match, key) => key in vars ? (vars[key] ?? match) : match);
43
+ }
44
+ // ── Default deps ───────────────────────────────────────────────────────────
45
+ function makeDefaultDeps() {
46
+ const __filename = fileURLToPath(import.meta.url);
47
+ const __dirname = dirname(__filename);
48
+ return {
49
+ readFile: (filePath) => readFileSync(filePath, 'utf-8'),
50
+ mkdir: (dirPath) => mkdirSync(dirPath, { recursive: true }),
51
+ writeFile: (filePath, content) => writeFileSync(filePath, content, 'utf-8'),
52
+ templatesDir: join(__dirname, '../../../templates/new'),
53
+ };
54
+ }
55
+ // ── generateFiles ──────────────────────────────────────────────────────────
56
+ /**
57
+ * Reads every template file, applies placeholder substitution, and writes it
58
+ * into the target project directory.
59
+ */
60
+ export async function generateFiles(opts, deps = makeDefaultDeps()) {
61
+ const { projectDir, projectName, secret } = opts;
62
+ const { readFile, mkdir, writeFile, templatesDir } = deps;
63
+ const vars = {
64
+ PROJECT_NAME: projectName,
65
+ SECRET: secret,
66
+ };
67
+ const written = [];
68
+ for (const { src, dest } of TEMPLATE_FILES) {
69
+ const srcPath = join(templatesDir, src);
70
+ const destPath = join(projectDir, dest);
71
+ const raw = readFile(srcPath);
72
+ const content = applySubstitutions(raw, vars);
73
+ mkdir(dirname(destPath));
74
+ writeFile(destPath, content);
75
+ written.push({ dest });
76
+ }
77
+ return written;
78
+ }
79
+ //# sourceMappingURL=generate-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-files.js","sourceRoot":"","sources":["../../../../src/commands/new/generate-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Command } from 'commander';
2
+ import { type PromptFn } from '../../utils/prompt.js';
3
+ import { generateFiles } from './generate-files.js';
4
+ import { runSetup } from './run-setup.js';
5
+ export interface NewCommandDeps {
6
+ prompt: PromptFn;
7
+ readdirSync: (path: string) => string[];
8
+ mkdirSync: (path: string, opts: {
9
+ recursive: boolean;
10
+ }) => void;
11
+ generateFiles: typeof generateFiles;
12
+ runSetup: typeof runSetup;
13
+ randomBytes: (size: number) => Buffer;
14
+ cwd: () => string;
15
+ }
16
+ /**
17
+ * Registers the `hypersonic new` top-level command.
18
+ *
19
+ * Fully interactive β€” no arguments accepted. Prompts:
20
+ * 1. Directory choice (new subdirectory | current directory)
21
+ * 2. Warning + confirm if current directory is not empty (current dir only)
22
+ * 3. Project name (always required)
23
+ * 4. Warning + confirm if new subdirectory already exists (new dir only)
24
+ *
25
+ * Then generates all project files, runs npm install, runs Prisma migrations,
26
+ * scaffolds the admin dashboard, generates admin metadata, and finally runs
27
+ * `hypersonic admin create-admin` interactively so the user creates their
28
+ * first admin account before the command exits.
29
+ */
30
+ export declare function registerNewCommand(program: Command, deps?: NewCommandDeps): void;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/new/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAIzC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,QAAQ,CAAA;IAChB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IACvC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAC/D,aAAa,EAAE,OAAO,aAAa,CAAA;IACnC,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAkBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,cAA2B,GAChC,IAAI,CAoFN"}
@@ -0,0 +1,102 @@
1
+ import { readdirSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { prompt as defaultPrompt } from '../../utils/prompt.js';
5
+ import { logger } from '../../utils/logger.js';
6
+ import { generateFiles } from './generate-files.js';
7
+ import { runSetup } from './run-setup.js';
8
+ // ── Dependency loader ──────────────────────────────────────────────────────
9
+ function loadDeps() {
10
+ return {
11
+ prompt: defaultPrompt,
12
+ readdirSync,
13
+ mkdirSync: (p, opts) => mkdirSync(p, opts),
14
+ generateFiles,
15
+ runSetup,
16
+ randomBytes,
17
+ cwd: () => process.cwd(),
18
+ };
19
+ }
20
+ // ── Registration ───────────────────────────────────────────────────────────
21
+ /**
22
+ * Registers the `hypersonic new` top-level command.
23
+ *
24
+ * Fully interactive β€” no arguments accepted. Prompts:
25
+ * 1. Directory choice (new subdirectory | current directory)
26
+ * 2. Warning + confirm if current directory is not empty (current dir only)
27
+ * 3. Project name (always required)
28
+ * 4. Warning + confirm if new subdirectory already exists (new dir only)
29
+ *
30
+ * Then generates all project files, runs npm install, runs Prisma migrations,
31
+ * scaffolds the admin dashboard, generates admin metadata, and finally runs
32
+ * `hypersonic admin create-admin` interactively so the user creates their
33
+ * first admin account before the command exits.
34
+ */
35
+ export function registerNewCommand(program, deps = loadDeps()) {
36
+ program
37
+ .command('new')
38
+ .description('Scaffold a new Hypersonic.js project interactively')
39
+ .action(async () => {
40
+ const { prompt, readdirSync: readdir, mkdirSync: mkdir, generateFiles: doGenerateFiles, runSetup: doRunSetup, randomBytes: rb, cwd, } = deps;
41
+ // ── 1. Directory choice ──────────────────────────────────────────────
42
+ logger.info('Where would you like to create your project?');
43
+ logger.info(' 1. Create a new directory');
44
+ logger.info(' 2. Use current directory');
45
+ const dirChoice = await prompt('Choice [1]: ');
46
+ const useCurrentDir = dirChoice.trim() === '2';
47
+ // ── 2. Warn if current directory is not empty ────────────────────────
48
+ if (useCurrentDir) {
49
+ const existing = readdir(cwd());
50
+ if (existing.length > 0) {
51
+ logger.warn(`Current directory is not empty (${existing.length} file(s) found).`);
52
+ const confirm = await prompt('Continue anyway? [y/N]: ');
53
+ if (confirm.trim().toLowerCase() !== 'y') {
54
+ logger.info('Aborted.');
55
+ return;
56
+ }
57
+ }
58
+ }
59
+ // ── 3. Project name (mandatory) ──────────────────────────────────────
60
+ const rawName = await prompt('Project name: ');
61
+ const projectName = rawName.trim();
62
+ if (!projectName) {
63
+ logger.error('Project name is required.');
64
+ process.exit(1);
65
+ }
66
+ // ── 4. Resolve project directory ─────────────────────────────────────
67
+ const projectDir = useCurrentDir ? cwd() : join(cwd(), projectName);
68
+ if (!useCurrentDir) {
69
+ // Guard: warn if the target subdirectory already exists and is non-empty.
70
+ // A thrown error means the directory does not exist yet β€” that is fine.
71
+ try {
72
+ const existing = readdir(projectDir);
73
+ if (existing.length > 0) {
74
+ logger.warn(`Directory "${projectName}" already exists and is not empty (${existing.length} file(s) found).`);
75
+ const confirm = await prompt('Continue anyway? [y/N]: ');
76
+ if (confirm.trim().toLowerCase() !== 'y') {
77
+ logger.info('Aborted.');
78
+ return;
79
+ }
80
+ }
81
+ }
82
+ catch {
83
+ // Directory does not exist yet β€” mkdirSync will create it below.
84
+ }
85
+ mkdir(projectDir, { recursive: true });
86
+ }
87
+ // ── 5. Generate project files ─────────────────────────────────────────
88
+ const secret = rb(32).toString('hex');
89
+ logger.info(`\nCreating project in ${projectDir}…`);
90
+ await doGenerateFiles({ projectDir, projectName, secret });
91
+ logger.success('Project files written.');
92
+ // ── 6. Run setup steps ────────────────────────────────────────────────
93
+ await doRunSetup({ projectDir });
94
+ // ── 7. Done ───────────────────────────────────────────────────────────
95
+ logger.success('\nYour project is ready!');
96
+ if (!useCurrentDir) {
97
+ logger.info(`\n cd ${projectName}`);
98
+ }
99
+ logger.info(' npm run dev\n');
100
+ });
101
+ }
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/new/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ export interface RunSetupOptions {
2
+ projectDir: string;
3
+ }
4
+ /** Runs a shell command with the given working directory. */
5
+ export type ExecFn = (command: string, cwd: string) => void;
6
+ export interface RunSetupDeps {
7
+ exec: ExecFn;
8
+ scaffoldAdmin: (opts: {
9
+ targetDir: string;
10
+ force: boolean;
11
+ }) => Promise<{
12
+ written: string[];
13
+ skipped: string[];
14
+ }>;
15
+ generateAdminMeta: (schemaPath: string, outputPath: string) => Promise<void>;
16
+ }
17
+ /**
18
+ * Runs all post-generation setup steps in order:
19
+ *
20
+ * 1. npm install β€” installs project dependencies
21
+ * 2. prisma migrate dev β€” creates the SQLite database and runs migrations
22
+ * 3. prisma generate β€” generates the Prisma client from the schema
23
+ * 4. admin scaffold β€” copies admin React page components into the project
24
+ * 5. admin generate-meta β€” generates prisma/admin-meta.json from the schema
25
+ * 6. admin create-admin β€” interactive: the user creates their admin account
26
+ *
27
+ * Steps 1–3 run as child processes (stdio: inherit so the user sees output).
28
+ * Steps 4–5 call the existing CLI functions directly to avoid a second spawn.
29
+ * Step 6 spawns `hypersonic admin create-admin` in the project directory so
30
+ * it resolves its own node_modules (better-auth, @prisma/client, etc.) from
31
+ * the newly-installed project rather than from the CLI's install location.
32
+ */
33
+ export declare function runSetup(opts: RunSetupOptions, deps?: RunSetupDeps): Promise<void>;
34
+ //# sourceMappingURL=run-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-setup.d.ts","sourceRoot":"","sources":["../../../../src/commands/new/run-setup.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,6DAA6D;AAC7D,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;AAE3D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC;QACtE,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,OAAO,EAAE,MAAM,EAAE,CAAA;KAClB,CAAC,CAAA;IACF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7E;AA2BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,eAAe,EACrB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -0,0 +1,70 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { scaffoldAdmin } from '@hypersonic-js/admin';
5
+ import { runGenerateMeta } from '../admin/generate-meta.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ // ── Dependency loader ──────────────────────────────────────────────────────
8
+ async function loadDeps() {
9
+ const { getDMMF } = await import('@prisma/get-dmmf');
10
+ return {
11
+ exec: (command, cwd) => execSync(command, { cwd, stdio: 'inherit' }),
12
+ scaffoldAdmin,
13
+ generateAdminMeta: async (schemaPath, outputPath) => {
14
+ await runGenerateMeta({ schema: schemaPath, output: outputPath }, {
15
+ getDMMF,
16
+ readFile: (p) => readFileSync(p, 'utf-8'),
17
+ writeFile: (p, c) => writeFileSync(p, c),
18
+ });
19
+ },
20
+ };
21
+ }
22
+ // ── Main ───────────────────────────────────────────────────────────────────
23
+ /**
24
+ * Runs all post-generation setup steps in order:
25
+ *
26
+ * 1. npm install β€” installs project dependencies
27
+ * 2. prisma migrate dev β€” creates the SQLite database and runs migrations
28
+ * 3. prisma generate β€” generates the Prisma client from the schema
29
+ * 4. admin scaffold β€” copies admin React page components into the project
30
+ * 5. admin generate-meta β€” generates prisma/admin-meta.json from the schema
31
+ * 6. admin create-admin β€” interactive: the user creates their admin account
32
+ *
33
+ * Steps 1–3 run as child processes (stdio: inherit so the user sees output).
34
+ * Steps 4–5 call the existing CLI functions directly to avoid a second spawn.
35
+ * Step 6 spawns `hypersonic admin create-admin` in the project directory so
36
+ * it resolves its own node_modules (better-auth, @prisma/client, etc.) from
37
+ * the newly-installed project rather than from the CLI's install location.
38
+ */
39
+ export async function runSetup(opts, deps) {
40
+ // `await` cannot be used in a default parameter expression of an async
41
+ // function β€” it is a syntax error. Resolve lazily inside the body instead.
42
+ const { exec, scaffoldAdmin: doScaffold, generateAdminMeta } = deps ?? (await loadDeps());
43
+ const { projectDir } = opts;
44
+ const pagesDir = join(projectDir, 'resources/js/Pages');
45
+ const schemaPath = join(projectDir, 'prisma/schema.prisma');
46
+ const metaPath = join(projectDir, 'prisma/admin-meta.json');
47
+ // ── 1. Install dependencies ──────────────────────────────────────────────
48
+ logger.info('Installing dependencies…');
49
+ exec('npm install', projectDir);
50
+ // ── 2. Run database migrations ───────────────────────────────────────────
51
+ logger.info('Running database migrations…');
52
+ exec('npx prisma migrate dev --name init', projectDir);
53
+ // ── 3. Generate Prisma client ────────────────────────────────────────────
54
+ logger.info('Generating Prisma client…');
55
+ exec('npx prisma generate', projectDir);
56
+ // ── 4. Scaffold admin pages ──────────────────────────────────────────────
57
+ logger.info('Scaffolding admin pages…');
58
+ const scaffoldResult = await doScaffold({ targetDir: pagesDir, force: false });
59
+ for (const file of scaffoldResult.written) {
60
+ logger.success(`Written resources/js/Pages/Admin/${file}`);
61
+ }
62
+ // ── 5. Generate admin metadata ───────────────────────────────────────────
63
+ logger.info('Generating admin metadata…');
64
+ await generateAdminMeta(schemaPath, metaPath);
65
+ logger.success('Admin meta written to prisma/admin-meta.json');
66
+ // ── 6. Create admin user (interactive subprocess) ────────────────────────
67
+ logger.info('Creating your admin account…');
68
+ exec('npx hypersonic admin create-admin', projectDir);
69
+ }
70
+ //# sourceMappingURL=run-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-setup.js","sourceRoot":"","sources":["../../../../src/commands/new/run-setup.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAInC,eAAO,MAAM,WAAW,EAAE,MAAoB,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CASvC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAKnC,eAAO,MAAM,WAAW,EAAE,MAAoB,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAUvC"}
package/dist/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { registerAdminCommands } from './commands/admin/index.js';
3
+ import { registerNewCommand } from './commands/new/index.js';
3
4
  import pkg from '../package.json' with { type: 'json' };
4
5
  export const CLI_VERSION = pkg.version;
5
6
  /**
@@ -13,6 +14,7 @@ export function createProgram() {
13
14
  .description('Hypersonic.js framework CLI')
14
15
  .version(CLI_VERSION, '-v, --version', 'Print the CLI version');
15
16
  registerAdminCommands(program);
17
+ registerNewCommand(program);
16
18
  return program;
17
19
  }
18
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAEvD,MAAM,CAAC,MAAM,WAAW,GAAW,GAAG,CAAC,OAAO,CAAA;AAE9C;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;SAC1B,IAAI,CAAC,YAAY,CAAC;SAClB,WAAW,CAAC,6BAA6B,CAAC;SAC1C,OAAO,CAAC,WAAW,EAAE,eAAe,EAAE,uBAAuB,CAAC,CAAA;IAEjE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAE9B,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAEvD,MAAM,CAAC,MAAM,WAAW,GAAW,GAAG,CAAC,OAAO,CAAA;AAE9C;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;SAC1B,IAAI,CAAC,YAAY,CAAC;SAClB,WAAW,CAAC,6BAA6B,CAAC;SAC1C,OAAO,CAAC,WAAW,EAAE,eAAe,EAAE,uBAAuB,CAAC,CAAA;IAEjE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAE3B,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ DATABASE_URL="file:./dev.db"
2
+ BETTER_AUTH_SECRET="replace-with-a-random-32-character-secret"
@@ -0,0 +1,2 @@
1
+ DATABASE_URL="file:./dev.db"
2
+ BETTER_AUTH_SECRET="{{SECRET}}"
@@ -0,0 +1,17 @@
1
+ import js from '@eslint/js'
2
+ import tseslint from 'typescript-eslint'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+
5
+ export default tseslint.config(
6
+ { ignores: ['public/', 'node_modules/'] },
7
+ {
8
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
9
+ files: ['**/*.{ts,tsx}'],
10
+ plugins: {
11
+ 'react-hooks': reactHooks,
12
+ },
13
+ rules: {
14
+ ...reactHooks.configs.recommended.rules,
15
+ },
16
+ },
17
+ )
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from '@hypersonic-js/complete'
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ port: 3000,
6
+ host: 'localhost',
7
+ },
8
+ database: {
9
+ provider: 'sqlite',
10
+ },
11
+ auth: {
12
+ trustedOrigins: ['http://localhost:3000'],
13
+ },
14
+ inertia: {
15
+ ssr: false,
16
+ },
17
+ })
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "node --experimental-strip-types server.ts",
7
+ "build": "vite build",
8
+ "lint": "eslint .",
9
+ "db:migrate": "prisma migrate dev",
10
+ "db:generate": "prisma generate"
11
+ },
12
+ "dependencies": {
13
+ "@hypersonic-js/complete": "0.2.0",
14
+ "@prisma/adapter-better-sqlite3": "7.8.0",
15
+ "better-sqlite3": "12.11.1",
16
+ "dotenv": "17.4.2",
17
+ "@prisma/client": "7.8.0",
18
+ "better-auth": "1.6.19"
19
+ },
20
+ "devDependencies": {
21
+ "@eslint/js": "10.0.1",
22
+ "@hypersonic-js/cli": "0.2.0",
23
+ "@inertiajs/react": "3.4.0",
24
+ "@tailwindcss/vite": "4.3.1",
25
+ "@types/better-sqlite3": "7.6.13",
26
+ "@types/node": "25.9.3",
27
+ "@types/react": "19.2.17",
28
+ "@types/react-dom": "19.2.3",
29
+ "eslint": "10.5.0",
30
+ "eslint-plugin-react-hooks": "7.1.1",
31
+ "prisma": "7.8.0",
32
+ "react": "19.2.7",
33
+ "react-dom": "19.2.7",
34
+ "tailwindcss": "4.3.1",
35
+ "typescript": "6.0.3",
36
+ "typescript-eslint": "8.61.1",
37
+ "vite": "8.0.16"
38
+ }
39
+ }
@@ -0,0 +1,71 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "sqlite"
7
+ }
8
+
9
+ // ── Required by Better Auth ────────────────────────────────────────────────
10
+
11
+ model User {
12
+ id String @id
13
+ name String
14
+ email String @unique
15
+ emailVerified Boolean
16
+ image String?
17
+ createdAt DateTime
18
+ updatedAt DateTime
19
+ // Better Auth admin plugin fields
20
+ role Role @default(user)
21
+ banned Boolean @default(false)
22
+ banReason String?
23
+ banExpires DateTime?
24
+ sessions Session[]
25
+ accounts Account[]
26
+ }
27
+
28
+ enum Role {
29
+ user
30
+ admin
31
+ }
32
+
33
+ model Session {
34
+ id String @id
35
+ expiresAt DateTime
36
+ token String @unique
37
+ createdAt DateTime
38
+ updatedAt DateTime
39
+ ipAddress String?
40
+ userAgent String?
41
+ userId String
42
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
43
+ }
44
+
45
+ model Account {
46
+ id String @id
47
+ accountId String
48
+ providerId String
49
+ userId String
50
+ accessToken String?
51
+ refreshToken String?
52
+ idToken String?
53
+ accessTokenExpiresAt DateTime?
54
+ refreshTokenExpiresAt DateTime?
55
+ scope String?
56
+ password String?
57
+ createdAt DateTime
58
+ updatedAt DateTime
59
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
60
+ }
61
+
62
+ model Verification {
63
+ id String @id
64
+ identifier String
65
+ value String
66
+ expiresAt DateTime
67
+ createdAt DateTime?
68
+ updatedAt DateTime?
69
+ }
70
+
71
+ // ── Your models go here ────────────────────────────────────────────────────
@@ -0,0 +1,18 @@
1
+ import 'dotenv/config'
2
+ import { defineConfig } from 'prisma/config'
3
+
4
+ if (!process.env.DATABASE_URL) {
5
+ throw new Error(
6
+ 'DATABASE_URL is not defined. Please add it to your .env file before running Prisma commands.',
7
+ )
8
+ }
9
+
10
+ export default defineConfig({
11
+ schema: 'prisma/schema.prisma',
12
+ migrations: {
13
+ path: 'prisma/migrations',
14
+ },
15
+ datasource: {
16
+ url: process.env.DATABASE_URL,
17
+ },
18
+ })
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,96 @@
1
+ import { useState } from 'react'
2
+ import { router } from '@inertiajs/react'
3
+ import { authClient } from '../../lib/auth-client'
4
+
5
+ interface Props {
6
+ error?: string
7
+ }
8
+
9
+ export default function Login({ error }: Props) {
10
+ const [email, setEmail] = useState('')
11
+ const [password, setPassword] = useState('')
12
+ const [submitting, setSubmitting] = useState(false)
13
+ const [formError, setFormError] = useState(error ?? '')
14
+
15
+ async function handleSubmit(e: React.FormEvent) {
16
+ e.preventDefault()
17
+ setSubmitting(true)
18
+ setFormError('')
19
+
20
+ try {
21
+ const result = await authClient.signIn.email({
22
+ email,
23
+ password,
24
+ callbackURL: '/',
25
+ })
26
+
27
+ if (result.error) {
28
+ setFormError(result.error.message ?? 'Invalid email or password')
29
+ return
30
+ }
31
+
32
+ router.visit('/')
33
+ } catch {
34
+ setFormError('An unexpected error occurred. Please try again.')
35
+ } finally {
36
+ setSubmitting(false)
37
+ }
38
+ }
39
+
40
+ return (
41
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
42
+ <div className="w-full max-w-sm bg-white rounded-2xl shadow p-8">
43
+ <h1 className="text-2xl font-semibold text-gray-900 mb-6">Sign in</h1>
44
+
45
+ {formError && (
46
+ <p className="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700">
47
+ {formError}
48
+ </p>
49
+ )}
50
+
51
+ <form onSubmit={handleSubmit} className="space-y-4">
52
+ <div>
53
+ <label className="block text-sm font-medium text-gray-700 mb-1">
54
+ Email
55
+ </label>
56
+ <input
57
+ type="email"
58
+ value={email}
59
+ onChange={(e) => setEmail(e.target.value)}
60
+ required
61
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
62
+ />
63
+ </div>
64
+
65
+ <div>
66
+ <label className="block text-sm font-medium text-gray-700 mb-1">
67
+ Password
68
+ </label>
69
+ <input
70
+ type="password"
71
+ value={password}
72
+ onChange={(e) => setPassword(e.target.value)}
73
+ required
74
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
75
+ />
76
+ </div>
77
+
78
+ <button
79
+ type="submit"
80
+ disabled={submitting}
81
+ className="w-full rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 disabled:opacity-50"
82
+ >
83
+ {submitting ? 'Signing in…' : 'Sign in'}
84
+ </button>
85
+ </form>
86
+
87
+ <p className="mt-6 text-center text-sm text-gray-500">
88
+ No account?{' '}
89
+ <a href="/register" className="text-indigo-600 hover:underline">
90
+ Create one
91
+ </a>
92
+ </p>
93
+ </div>
94
+ </div>
95
+ )
96
+ }
@@ -0,0 +1,107 @@
1
+ import { useState } from 'react'
2
+ import { router } from '@inertiajs/react'
3
+ import { authClient } from '../../lib/auth-client'
4
+
5
+ export default function Register() {
6
+ const [name, setName] = useState('')
7
+ const [email, setEmail] = useState('')
8
+ const [password, setPassword] = useState('')
9
+ const [submitting, setSubmitting] = useState(false)
10
+ const [formError, setFormError] = useState('')
11
+
12
+ async function handleSubmit(e: React.FormEvent) {
13
+ e.preventDefault()
14
+ setSubmitting(true)
15
+ setFormError('')
16
+
17
+ try {
18
+ const result = await authClient.signUp.email({
19
+ name,
20
+ email,
21
+ password,
22
+ callbackURL: '/',
23
+ })
24
+
25
+ if (result.error) {
26
+ setFormError(result.error.message ?? 'Could not create account')
27
+ return
28
+ }
29
+
30
+ router.visit('/')
31
+ } catch {
32
+ setFormError('An unexpected error occurred. Please try again.')
33
+ } finally {
34
+ setSubmitting(false)
35
+ }
36
+ }
37
+
38
+ return (
39
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
40
+ <div className="w-full max-w-sm bg-white rounded-2xl shadow p-8">
41
+ <h1 className="text-2xl font-semibold text-gray-900 mb-6">Create account</h1>
42
+
43
+ {formError && (
44
+ <p className="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700">
45
+ {formError}
46
+ </p>
47
+ )}
48
+
49
+ <form onSubmit={handleSubmit} className="space-y-4">
50
+ <div>
51
+ <label className="block text-sm font-medium text-gray-700 mb-1">
52
+ Name
53
+ </label>
54
+ <input
55
+ type="text"
56
+ value={name}
57
+ onChange={(e) => setName(e.target.value)}
58
+ required
59
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
60
+ />
61
+ </div>
62
+
63
+ <div>
64
+ <label className="block text-sm font-medium text-gray-700 mb-1">
65
+ Email
66
+ </label>
67
+ <input
68
+ type="email"
69
+ value={email}
70
+ onChange={(e) => setEmail(e.target.value)}
71
+ required
72
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
73
+ />
74
+ </div>
75
+
76
+ <div>
77
+ <label className="block text-sm font-medium text-gray-700 mb-1">
78
+ Password
79
+ </label>
80
+ <input
81
+ type="password"
82
+ value={password}
83
+ onChange={(e) => setPassword(e.target.value)}
84
+ required
85
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
86
+ />
87
+ </div>
88
+
89
+ <button
90
+ type="submit"
91
+ disabled={submitting}
92
+ className="w-full rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 disabled:opacity-50"
93
+ >
94
+ {submitting ? 'Creating account…' : 'Create account'}
95
+ </button>
96
+ </form>
97
+
98
+ <p className="mt-6 text-center text-sm text-gray-500">
99
+ Already have an account?{' '}
100
+ <a href="/login" className="text-indigo-600 hover:underline">
101
+ Sign in
102
+ </a>
103
+ </p>
104
+ </div>
105
+ </div>
106
+ )
107
+ }
@@ -0,0 +1,54 @@
1
+ interface Route {
2
+ path: string
3
+ description: string
4
+ }
5
+
6
+ interface Props {
7
+ routes: Route[]
8
+ }
9
+
10
+ export default function Welcome({ routes }: Props) {
11
+ return (
12
+ <div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-8">
13
+ <div className="max-w-lg w-full">
14
+ <h1 className="text-4xl font-bold text-gray-900 mb-2">
15
+ Welcome to Hypersonic.js
16
+ </h1>
17
+ <p className="text-gray-500 mb-8">
18
+ Your app is running. Here&apos;s where to go next.
19
+ </p>
20
+
21
+ <div className="bg-white rounded-2xl shadow-sm border border-gray-100 divide-y divide-gray-100 mb-8">
22
+ {routes.map((route) => (
23
+ <a
24
+ key={route.path}
25
+ href={route.path}
26
+ className="flex items-center justify-between px-6 py-4 hover:bg-gray-50 transition-colors"
27
+ >
28
+ <div>
29
+ <p className="font-mono text-sm font-medium text-gray-900">
30
+ {route.path}
31
+ </p>
32
+ <p className="text-sm text-gray-500">{route.description}</p>
33
+ </div>
34
+ <span className="text-gray-300">&rarr;</span>
35
+ </a>
36
+ ))}
37
+ </div>
38
+
39
+ <p className="text-center text-sm text-gray-400">
40
+ Read the{' '}
41
+ <a
42
+ href="https://hypersonic-js.com/guide/"
43
+ className="text-indigo-600 hover:underline"
44
+ target="_blank"
45
+ rel="noreferrer"
46
+ >
47
+ documentation
48
+ </a>{' '}
49
+ to learn more.
50
+ </p>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
@@ -0,0 +1,15 @@
1
+ import { createInertiaApp } from '@inertiajs/react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import '../css/app.css'
4
+
5
+ createInertiaApp({
6
+ resolve: (name) => {
7
+ const pages = import.meta.glob('./Pages/**/*.tsx', { eager: true })
8
+ const page = pages[`./Pages/${name}.tsx`]
9
+ if (!page) throw new Error(`Inertia page not found: ${name}`)
10
+ return page as never
11
+ },
12
+ setup({ el, App, props }) {
13
+ createRoot(el).render(<App {...props} />)
14
+ },
15
+ })
@@ -0,0 +1,5 @@
1
+ import { createAuthClient } from 'better-auth/react'
2
+
3
+ export const authClient = createAuthClient({
4
+ baseURL: 'http://localhost:3000',
5
+ })
@@ -0,0 +1,71 @@
1
+ import 'dotenv/config'
2
+ import { createRequire } from 'node:module'
3
+ import type { PrismaClient as PrismaClientType } from '@prisma/client'
4
+ import {
5
+ createApp,
6
+ loadConfig,
7
+ createDatabaseAdapter,
8
+ createInertiaErrorHandler,
9
+ mountAdmin,
10
+ } from '@hypersonic-js/complete'
11
+ import type { AdminModelMeta } from '@hypersonic-js/complete'
12
+ import { createAuthGuard } from './src/middleware.ts'
13
+
14
+ // PrismaClient is CommonJS β€” use createRequire to load it in an ESM context.
15
+ const require = createRequire(import.meta.url)
16
+ const adminMeta = require('./prisma/admin-meta.json') as AdminModelMeta[]
17
+ const { PrismaClient } = require('@prisma/client') as {
18
+ PrismaClient: typeof PrismaClientType
19
+ }
20
+
21
+ const { config, env } = await loadConfig()
22
+
23
+ // Prisma v7 requires a driver adapter β€” never instantiate PrismaClient bare.
24
+ const adapter = await createDatabaseAdapter(config.database.provider, env.DATABASE_URL)
25
+ const prisma = new PrismaClient({ adapter })
26
+
27
+ const app = await createApp({ config, env, prisma })
28
+
29
+ // ── Auth guard (use on any route you want to protect) ──────────────────────
30
+
31
+ const requireAuth = createAuthGuard(app.auth)
32
+
33
+ // ── Public routes ──────────────────────────────────────────────────────────
34
+
35
+ app.express.get('/', (_req, res) => {
36
+ res.inertia!('Welcome', {
37
+ routes: [
38
+ { path: '/login', description: 'Sign in to your account' },
39
+ { path: '/register', description: 'Create a new account' },
40
+ { path: '/admin', description: 'Admin dashboard (admin role required and required to /login first)' },
41
+ ],
42
+ })
43
+ })
44
+
45
+ app.express.get('/login', (_req, res) => {
46
+ res.inertia!('Auth/Login', {})
47
+ })
48
+
49
+ app.express.get('/register', (_req, res) => {
50
+ res.inertia!('Auth/Register', {})
51
+ })
52
+
53
+ // ── Protected routes β€” example ─────────────────────────────────────────────
54
+ // app.express.get('/dashboard', requireAuth, (req, res) => {
55
+ // res.inertia!('Dashboard', { user: req.sessionUser })
56
+ // })
57
+
58
+ // ── Admin ───────────────────────────────────────────────────────────────────
59
+
60
+ mountAdmin(app.express, prisma, {
61
+ meta: adminMeta,
62
+ auth: app.auth,
63
+ logger: app.logger,
64
+ })
65
+
66
+ app.express.use(createInertiaErrorHandler())
67
+
68
+ await app.start()
69
+ console.log(`Listening on http://${config.server.host}:${config.server.port}`)
70
+
71
+ export { requireAuth }
@@ -0,0 +1,27 @@
1
+ import type { Response, NextFunction, RequestHandler } from 'express'
2
+ import type { AuthLike, AuthRequest } from './types.js'
3
+
4
+ /**
5
+ * Returns a middleware that checks for a valid Better Auth session.
6
+ * Redirects to /login when the session is absent.
7
+ * Attaches session.user to req.sessionUser on success.
8
+ */
9
+ export function createAuthGuard(auth: AuthLike): RequestHandler {
10
+ return async function requireAuth(
11
+ req: AuthRequest,
12
+ res: Response,
13
+ next: NextFunction,
14
+ ): Promise<void> {
15
+ const session = await auth.api.getSession({
16
+ headers: req.headers as unknown as Headers,
17
+ })
18
+
19
+ if (!session) {
20
+ res.redirect('/login')
21
+ return
22
+ }
23
+
24
+ req.sessionUser = session.user
25
+ next()
26
+ }
27
+ }
@@ -0,0 +1,17 @@
1
+ import type { Request } from 'express'
2
+
3
+ export interface SessionUser {
4
+ id: string
5
+ name: string
6
+ email: string
7
+ }
8
+
9
+ export interface AuthRequest extends Request {
10
+ sessionUser?: SessionUser
11
+ }
12
+
13
+ export interface AuthLike {
14
+ api: {
15
+ getSession(opts: { headers: unknown }): Promise<{ user: SessionUser } | null>
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "types": ["vite/client"],
10
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
11
+ },
12
+ "include": ["**/*.ts", "**/*.tsx"],
13
+ "exclude": ["node_modules", "public"]
14
+ }
@@ -0,0 +1,17 @@
1
+ import type { Request } from 'express'
2
+
3
+ export interface SessionUser {
4
+ id: string
5
+ name: string
6
+ email: string
7
+ }
8
+
9
+ export interface AuthRequest extends Request {
10
+ sessionUser?: SessionUser
11
+ }
12
+
13
+ export interface AuthLike {
14
+ api: {
15
+ getSession(opts: { headers: unknown }): Promise<{ user: SessionUser } | null>
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vite'
2
+ import tailwindcss from '@tailwindcss/vite'
3
+
4
+ export default defineConfig({
5
+ plugins: [tailwindcss()],
6
+ build: {
7
+ outDir: 'public',
8
+ manifest: true,
9
+ rollupOptions: {
10
+ input: 'resources/js/app.tsx',
11
+ },
12
+ },
13
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypersonic-js/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Hypersonic.js CLI β€” developer tooling for the Hypersonic framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,27 +20,27 @@
20
20
  "access": "public"
21
21
  },
22
22
  "engines": {
23
- "node": "^24.0.0"
23
+ "node": ">=24.0.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "commander": "15.0.0",
27
27
  "dotenv": "17.4.2",
28
28
  "picocolors": "1.1.1",
29
29
  "@prisma/get-dmmf": "7.8.0",
30
- "@hypersonic-js/admin": "0.2.0"
30
+ "@hypersonic-js/admin": "0.2.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@prisma/adapter-pg": "7.8.0",
34
34
  "@prisma/client": "7.8.0",
35
35
  "@types/node": "25.9.3",
36
- "@vitest/coverage-v8": "4.1.8",
37
- "better-auth": "1.6.17",
36
+ "@vitest/coverage-v8": "4.1.9",
37
+ "better-auth": "1.6.19",
38
38
  "typescript": "6.0.3",
39
- "vitest": "4.1.8",
40
- "@hypersonic-js/core": "0.2.0"
39
+ "vitest": "4.1.9",
40
+ "@hypersonic-js/core": "0.2.1"
41
41
  },
42
42
  "scripts": {
43
- "build": "tsc",
43
+ "build": "tsc && node --input-type=module --eval \"import{cpSync}from'node:fs';cpSync('templates','dist/templates',{recursive:true})\"",
44
44
  "test": "vitest run",
45
45
  "test:coverage": "vitest run --coverage",
46
46
  "type-check": "tsc --noEmit",