anhedral 0.1.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.
- package/LICENSE +22 -0
- package/README.md +65 -0
- package/anhedral.svg +4 -0
- package/bin/anhedral.js +2 -0
- package/dist/branding.d.ts +3 -0
- package/dist/branding.js +9 -0
- package/dist/commands.d.ts +12 -0
- package/dist/commands.js +74 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +157 -0
- package/dist/scaffold.d.ts +25 -0
- package/dist/scaffold.js +629 -0
- package/dist/templates/backend.d.ts +2 -0
- package/dist/templates/backend.js +2337 -0
- package/dist/templates/default-web.d.ts +2 -0
- package/dist/templates/default-web.js +1368 -0
- package/dist/templates/extension.d.ts +2 -0
- package/dist/templates/extension.js +457 -0
- package/dist/templates/frontend.d.ts +2 -0
- package/dist/templates/frontend.js +1605 -0
- package/dist/toolchain.d.ts +14 -0
- package/dist/toolchain.js +34 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.js +99 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
All Rights Reserved
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anhedral, Inc.
|
|
4
|
+
|
|
5
|
+
THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL.
|
|
6
|
+
UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE CONTENTS
|
|
7
|
+
OF THIS PROJECT, VIA ANY MEDIUM, IS STRICTLY PROHIBITED.
|
|
8
|
+
|
|
9
|
+
The receipt or possession of the source code and/or any parts thereof
|
|
10
|
+
does not convey or imply any right to use them for any purpose other
|
|
11
|
+
than the purpose for which they were provided to you.
|
|
12
|
+
|
|
13
|
+
The software is provided "AS IS", without warranty of any kind,
|
|
14
|
+
express or implied, including but not limited to the warranties of
|
|
15
|
+
merchantability, fitness for a particular purpose and noninfringement.
|
|
16
|
+
In no event shall the authors or copyright holders be liable for any
|
|
17
|
+
claim, damages or other liability, whether in an action of contract,
|
|
18
|
+
tort or otherwise, arising from, out of or in connection with the
|
|
19
|
+
software or the use or other dealings in the software.
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be
|
|
22
|
+
included in all copies or substantial portions of the software.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# anhedral
|
|
2
|
+
|
|
3
|
+
Opinionated init CLI for product teams shipping with Next.js, Expo, Fastify, Drizzle, Neon, Clerk, Stripe, RevenueCat, and Cloudflare R2.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 20+
|
|
8
|
+
- `pnpm` available on your machine
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
Run the CLI directly without installing it globally:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm dlx anhedral init next
|
|
16
|
+
pnpm dlx anhedral init next-fullstack
|
|
17
|
+
pnpm dlx anhedral init expo-fullstack
|
|
18
|
+
pnpm dlx anhedral init backend
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx anhedral init next
|
|
23
|
+
npx anhedral init next-fullstack
|
|
24
|
+
npx anhedral init expo-fullstack
|
|
25
|
+
npx anhedral init backend
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Global install also works:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pnpm add -g anhedral
|
|
32
|
+
anhedral init next
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install -g anhedral
|
|
37
|
+
anhedral init next
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Stacks
|
|
41
|
+
|
|
42
|
+
- `next`: Next.js App Router for the fastest web-only SaaS path.
|
|
43
|
+
- `next-fullstack`: Next.js frontend plus Fastify backend for clearer service boundaries.
|
|
44
|
+
- `expo-fullstack`: Expo app plus Fastify backend for mobile-first or multi-client products.
|
|
45
|
+
- `backend`: Fastify-only API foundation for headless products and integrations.
|
|
46
|
+
|
|
47
|
+
Each scaffold writes a `stack.json` file with the selected architecture and generated outputs.
|
|
48
|
+
|
|
49
|
+
## Local Development
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pnpm install
|
|
53
|
+
pnpm build
|
|
54
|
+
pnpm test:all
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Publishing
|
|
58
|
+
|
|
59
|
+
This package is published once to the npm registry. `pnpm`, `npm`, `yarn`, and `bun` users all install that same published package.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pnpm build
|
|
63
|
+
pnpm test:all
|
|
64
|
+
npm publish
|
|
65
|
+
```
|
package/anhedral.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="4000" height="4000" viewBox="0 0 4000 4000" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M0 200C0 89.5431 89.5431 0 200 0H3800C3910.46 0 4000 89.5431 4000 200V3800C4000 3910.46 3910.46 4000 3800 4000H200C89.5431 4000 0 3910.46 0 3800V200Z" fill="black"/>
|
|
3
|
+
<path d="M2700.36 2965.84C2698.79 3089.66 2545.25 3146.21 2463.74 3052.98L1988.84 2509.79C2119.01 2206.09 2384.98 1972.11 2714.07 1888.26L2700.36 2965.84ZM2509.59 927.741C2599.33 861.325 2726.31 926.339 2724.89 1037.97L2714.28 1872.03C2679.28 1870.39 2643.87 1870.43 2608.15 1872.24C2239.16 1890.85 1922.8 2092.61 1742.64 2385.06L1027.5 2317.65C904.207 2306.03 860.316 2148.41 959.854 2074.73L2509.59 927.741Z" fill="white"/>
|
|
4
|
+
</svg>
|
package/bin/anhedral.js
ADDED
package/dist/branding.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeFile } from './util.js';
|
|
4
|
+
export const ANHEDRAL_LOGO_FILE_NAME = 'anhedral.svg';
|
|
5
|
+
export const ANHEDRAL_LOGO_PUBLIC_PATH = `/${ANHEDRAL_LOGO_FILE_NAME}`;
|
|
6
|
+
const ANHEDRAL_LOGO_SVG = readFileSync(new URL('../anhedral.svg', import.meta.url), 'utf8');
|
|
7
|
+
export function writeAnhedralLogo(root) {
|
|
8
|
+
writeFile(path.join(root, 'public', ANHEDRAL_LOGO_FILE_NAME), ANHEDRAL_LOGO_SVG);
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { InitMode } from './scaffold.js';
|
|
2
|
+
import { type ToolchainSpec } from './toolchain.js';
|
|
3
|
+
export type ScaffoldCommand = {
|
|
4
|
+
cmd: string;
|
|
5
|
+
stdinInput?: string;
|
|
6
|
+
skippable?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function getDefaultWebInitCommand(toolchain: ToolchainSpec, projectName: string): ScaffoldCommand;
|
|
9
|
+
export declare function getDefaultWebDependencyCommands(): ScaffoldCommand[];
|
|
10
|
+
export declare function getHeadlessInstallCommands(): ScaffoldCommand[];
|
|
11
|
+
export declare function getBackendInstallCommands(): ScaffoldCommand[];
|
|
12
|
+
export declare function getSkillCommands(mode: InitMode): string[];
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { toolPackageRef } from './toolchain.js';
|
|
2
|
+
const TYPESCRIPT_VERSION = '5.9.3';
|
|
3
|
+
const NODE_TYPES_VERSION = '25.5.0';
|
|
4
|
+
export function getDefaultWebInitCommand(toolchain, projectName) {
|
|
5
|
+
return {
|
|
6
|
+
cmd: [
|
|
7
|
+
'pnpm',
|
|
8
|
+
'dlx',
|
|
9
|
+
toolPackageRef('shadcn', toolchain.shadcn),
|
|
10
|
+
'init',
|
|
11
|
+
'-t',
|
|
12
|
+
'next',
|
|
13
|
+
'-n',
|
|
14
|
+
projectName,
|
|
15
|
+
'-d',
|
|
16
|
+
'-y',
|
|
17
|
+
'--css-variables',
|
|
18
|
+
].join(' '),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function getDefaultWebDependencyCommands() {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
cmd: 'pnpm add @clerk/nextjs @clerk/ui drizzle-orm @neondatabase/serverless stripe @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner',
|
|
25
|
+
skippable: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
cmd: 'pnpm add -D babel-plugin-react-compiler drizzle-kit tsx dotenv',
|
|
29
|
+
skippable: true,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function getHeadlessInstallCommands() {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
cmd: 'pnpm add fastify @fastify/cors better-auth drizzle-orm @neondatabase/serverless @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner dotenv',
|
|
37
|
+
skippable: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
cmd: `pnpm add -D typescript@${TYPESCRIPT_VERSION} tsx @types/node@${NODE_TYPES_VERSION} drizzle-kit`,
|
|
41
|
+
skippable: true,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
export function getBackendInstallCommands() {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
cmd: 'pnpm add fastify fastify-plugin @fastify/cors @fastify/env @fastify/compress @fastify/helmet @fastify/rate-limit @fastify/swagger @fastify/swagger-ui @fastify/multipart @clerk/fastify @neondatabase/serverless drizzle-orm @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner dotenv',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
cmd: 'pnpm add -D typescript@5.9.3 tsx @types/node@25.5.0 drizzle-kit vitest @vitest/coverage-v8 @vercel/node eslint@9.39.4 @eslint/js@9.39.4 globals typescript-eslint pino-pretty',
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
export function getSkillCommands(mode) {
|
|
56
|
+
switch (mode) {
|
|
57
|
+
case 'next':
|
|
58
|
+
case 'next-fullstack':
|
|
59
|
+
return [
|
|
60
|
+
'pnpm dlx skills add https://github.com/clerk/skills --skill clerk-custom-ui',
|
|
61
|
+
'pnpm dlx skills add https://github.com/stripe/ai --skill stripe-best-practices',
|
|
62
|
+
];
|
|
63
|
+
case 'expo-fullstack':
|
|
64
|
+
return [
|
|
65
|
+
'pnpm dlx skills add https://github.com/clerk/skills --skill clerk-custom-ui',
|
|
66
|
+
'pnpm dlx skills add https://github.com/revenuecat/revenuecat-skill --skill revenuecat',
|
|
67
|
+
'pnpm dlx skills add https://github.com/stripe/ai --skill stripe-best-practices',
|
|
68
|
+
];
|
|
69
|
+
case 'backend':
|
|
70
|
+
return [
|
|
71
|
+
'pnpm dlx skills add https://github.com/better-auth/skills --skill better-auth-best-practices',
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { argv, env, exit } from 'node:process';
|
|
4
|
+
import { STACK_IDS, scaffoldProject } from './scaffold.js';
|
|
5
|
+
import { TOOLCHAIN_CHANNELS, resolveToolchainChannel } from './toolchain.js';
|
|
6
|
+
const USAGE = `
|
|
7
|
+
anhedral init <next|next-fullstack|expo-fullstack|backend> [--toolchain <latest|stable>]
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
anhedral init next
|
|
11
|
+
Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2
|
|
12
|
+
|
|
13
|
+
anhedral init next-fullstack
|
|
14
|
+
Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2
|
|
15
|
+
|
|
16
|
+
anhedral init expo-fullstack
|
|
17
|
+
Expo, NativeWind/Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, RevenueCat + Stripe, Cloudflare R2
|
|
18
|
+
|
|
19
|
+
anhedral init backend
|
|
20
|
+
Fastify, TypeScript, Drizzle ORM, Neon Postgres, Clerk Auth or Better Auth, Stripe-ready integrations, Cloudflare R2
|
|
21
|
+
`;
|
|
22
|
+
function parseCli(args) {
|
|
23
|
+
const flags = {};
|
|
24
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
25
|
+
const token = args[index];
|
|
26
|
+
if (!token.startsWith('--')) {
|
|
27
|
+
if (flags.stack != null) {
|
|
28
|
+
throw new Error(`Unknown flag: ${token}`);
|
|
29
|
+
}
|
|
30
|
+
flags.stack = token;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (token === '--toolchain') {
|
|
34
|
+
const value = args[index + 1];
|
|
35
|
+
if (!value || value.startsWith('--')) {
|
|
36
|
+
throw new Error('Missing value for --toolchain');
|
|
37
|
+
}
|
|
38
|
+
flags.toolchain = value;
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Unknown flag: ${token}`);
|
|
43
|
+
}
|
|
44
|
+
if (!flags.stack) {
|
|
45
|
+
throw new Error(`Missing stack. Choose one of: ${STACK_IDS.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
return flags;
|
|
48
|
+
}
|
|
49
|
+
function resolveMode(flags) {
|
|
50
|
+
if (!STACK_IDS.includes(flags.stack)) {
|
|
51
|
+
throw new Error(`Unknown stack: ${flags.stack}. Choose one of: ${STACK_IDS.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
return flags.stack;
|
|
54
|
+
}
|
|
55
|
+
function deriveProjectName(cwd) {
|
|
56
|
+
const base = path.basename(cwd);
|
|
57
|
+
const sanitized = base
|
|
58
|
+
.trim()
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
61
|
+
.replace(/^-+|-+$/g, '');
|
|
62
|
+
return sanitized || 'anhedral-app';
|
|
63
|
+
}
|
|
64
|
+
function deriveDisplayName(cwd) {
|
|
65
|
+
const base = path.basename(cwd).trim();
|
|
66
|
+
return base || 'Anhedral App';
|
|
67
|
+
}
|
|
68
|
+
function buildOptions(flags) {
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const mode = resolveMode(flags);
|
|
71
|
+
const projectName = deriveProjectName(cwd);
|
|
72
|
+
const displayName = deriveDisplayName(cwd);
|
|
73
|
+
if (flags.toolchain != null && !TOOLCHAIN_CHANNELS.includes(flags.toolchain)) {
|
|
74
|
+
throw new Error(`--toolchain must be one of: ${TOOLCHAIN_CHANNELS.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
switch (mode) {
|
|
77
|
+
case 'next':
|
|
78
|
+
return {
|
|
79
|
+
mode,
|
|
80
|
+
projectName,
|
|
81
|
+
displayName,
|
|
82
|
+
auth: 'clerk',
|
|
83
|
+
payments: 'stripe',
|
|
84
|
+
db: 'neon',
|
|
85
|
+
orm: 'drizzle',
|
|
86
|
+
storage: 'r2',
|
|
87
|
+
api: null,
|
|
88
|
+
monorepo: false,
|
|
89
|
+
toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
|
|
90
|
+
};
|
|
91
|
+
case 'next-fullstack':
|
|
92
|
+
return {
|
|
93
|
+
mode,
|
|
94
|
+
projectName,
|
|
95
|
+
displayName,
|
|
96
|
+
auth: 'clerk',
|
|
97
|
+
payments: 'stripe',
|
|
98
|
+
db: 'neon',
|
|
99
|
+
orm: 'drizzle',
|
|
100
|
+
storage: 'r2',
|
|
101
|
+
api: 'fastify',
|
|
102
|
+
monorepo: true,
|
|
103
|
+
toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
|
|
104
|
+
};
|
|
105
|
+
case 'expo-fullstack':
|
|
106
|
+
return {
|
|
107
|
+
mode,
|
|
108
|
+
projectName,
|
|
109
|
+
displayName,
|
|
110
|
+
auth: 'clerk',
|
|
111
|
+
payments: 'revenuecat',
|
|
112
|
+
db: 'neon',
|
|
113
|
+
orm: 'drizzle',
|
|
114
|
+
storage: 'r2',
|
|
115
|
+
api: 'fastify',
|
|
116
|
+
monorepo: true,
|
|
117
|
+
toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
|
|
118
|
+
};
|
|
119
|
+
case 'backend':
|
|
120
|
+
return {
|
|
121
|
+
mode,
|
|
122
|
+
projectName,
|
|
123
|
+
displayName,
|
|
124
|
+
auth: 'betterauth',
|
|
125
|
+
payments: 'none',
|
|
126
|
+
db: 'neon',
|
|
127
|
+
orm: 'drizzle',
|
|
128
|
+
storage: 'r2',
|
|
129
|
+
api: 'fastify',
|
|
130
|
+
monorepo: false,
|
|
131
|
+
toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function main() {
|
|
136
|
+
const args = argv.slice(2);
|
|
137
|
+
const command = args[0];
|
|
138
|
+
if (!command || command === '--help' || command === '-h') {
|
|
139
|
+
console.log(USAGE);
|
|
140
|
+
exit(0);
|
|
141
|
+
}
|
|
142
|
+
if (command !== 'init') {
|
|
143
|
+
console.error(`Unknown command: ${command}`);
|
|
144
|
+
console.log(USAGE);
|
|
145
|
+
exit(1);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const rawArgs = args.slice(1);
|
|
149
|
+
const options = buildOptions(parseCli(rawArgs));
|
|
150
|
+
await scaffoldProject(options);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Error:', error.message);
|
|
154
|
+
exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
main();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ToolchainChannel } from './toolchain.js';
|
|
2
|
+
export declare const STACK_IDS: readonly ["next", "next-fullstack", "expo-fullstack", "backend"];
|
|
3
|
+
export type InitMode = (typeof STACK_IDS)[number];
|
|
4
|
+
export type AuthMode = 'clerk' | 'betterauth';
|
|
5
|
+
export type PaymentsMode = 'none' | 'stripe' | 'revenuecat';
|
|
6
|
+
export interface InitOptions {
|
|
7
|
+
mode: InitMode;
|
|
8
|
+
projectName: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
auth: AuthMode;
|
|
11
|
+
payments: PaymentsMode;
|
|
12
|
+
db: 'neon';
|
|
13
|
+
orm: 'drizzle';
|
|
14
|
+
storage: 'r2';
|
|
15
|
+
api: 'fastify' | null;
|
|
16
|
+
monorepo: boolean;
|
|
17
|
+
toolchainChannel: ToolchainChannel;
|
|
18
|
+
}
|
|
19
|
+
export interface ProjectOptions {
|
|
20
|
+
projectName: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
githubOrg: string | null;
|
|
23
|
+
frontendUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function scaffoldProject(options: InitOptions): Promise<void>;
|