anhedral 0.1.0 → 0.1.3
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/README.md +82 -20
- package/dist/branding.d.ts +2 -0
- package/dist/branding.js +9 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +86 -0
- package/dist/commands.d.ts +2 -3
- package/dist/commands.js +9 -33
- package/dist/index.js +8 -134
- package/dist/print.d.ts +8 -0
- package/dist/print.js +33 -0
- package/dist/scaffold.d.ts +3 -3
- package/dist/scaffold.js +770 -381
- package/dist/templates/backend.js +76 -190
- package/dist/templates/default-web.js +819 -237
- package/dist/templates/extension.js +200 -18
- package/dist/templates/frontend.js +12 -11
- package/dist/toolchain.d.ts +0 -3
- package/dist/toolchain.js +3 -6
- package/dist/util.d.ts +1 -1
- package/dist/util.js +100 -43
- package/favicon.ico +0 -0
- package/package.json +16 -10
package/README.md
CHANGED
|
@@ -9,40 +9,42 @@ Opinionated init CLI for product teams shipping with Next.js, Expo, Fastify, Dri
|
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
|
-
Run the CLI directly without installing it globally:
|
|
12
|
+
Run the latest published CLI directly without installing it globally:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
pnpm dlx anhedral init
|
|
16
|
-
pnpm dlx anhedral init next
|
|
17
|
-
pnpm dlx anhedral init
|
|
18
|
-
pnpm dlx anhedral init
|
|
15
|
+
pnpm dlx anhedral@latest init
|
|
16
|
+
pnpm dlx anhedral@latest init --next
|
|
17
|
+
pnpm dlx anhedral@latest init --extension
|
|
18
|
+
pnpm dlx anhedral@latest init --next --extension
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
npx anhedral init
|
|
23
|
-
npx anhedral init next
|
|
24
|
-
npx anhedral init
|
|
25
|
-
npx anhedral init
|
|
22
|
+
npx anhedral@latest init
|
|
23
|
+
npx anhedral@latest init --next
|
|
24
|
+
npx anhedral@latest init --extension
|
|
25
|
+
npx anhedral@latest init --next --extension
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Global install also works:
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
pnpm add -g anhedral
|
|
32
|
-
anhedral init
|
|
31
|
+
pnpm add -g anhedral@latest
|
|
32
|
+
anhedral init
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
npm install -g anhedral
|
|
37
|
-
anhedral init
|
|
36
|
+
npm install -g anhedral@latest
|
|
37
|
+
anhedral init
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Stacks
|
|
41
41
|
|
|
42
|
-
- `
|
|
43
|
-
- `next
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
42
|
+
- `init`: Expo app plus Fastify backend. This is the default.
|
|
43
|
+
- `init --next`: Next.js frontend plus Fastify backend.
|
|
44
|
+
- `init --extension`: default Expo fullstack plus a WXT Chrome extension.
|
|
45
|
+
- `init --next --extension`: Next.js fullstack plus a WXT Chrome extension.
|
|
46
|
+
|
|
47
|
+
Every scaffold is a pnpm workspace with `apps/*` and `packages/*`. Shared code such as the Drizzle schema lives under `packages/db` so apps do not need separate database schemas.
|
|
46
48
|
|
|
47
49
|
Each scaffold writes a `stack.json` file with the selected architecture and generated outputs.
|
|
48
50
|
|
|
@@ -58,8 +60,68 @@ pnpm test:all
|
|
|
58
60
|
|
|
59
61
|
This package is published once to the npm registry. `pnpm`, `npm`, `yarn`, and `bun` users all install that same published package.
|
|
60
62
|
|
|
63
|
+
Official releases are automatic on merges to `main`. GitHub Actions bumps the patch version, runs the release checks once, creates a release commit and tag, and publishes that version to npm.
|
|
64
|
+
|
|
65
|
+
Recommended release flow:
|
|
66
|
+
|
|
61
67
|
```sh
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
git push origin main
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Release publishing is handled by:
|
|
72
|
+
|
|
73
|
+
- `.github/workflows/release-on-main.yml`: runs on pushes to `main`, bumps `package.json`, verifies the repo, pushes a `v*.*.*` tag, and publishes to npm.
|
|
74
|
+
- `.github/workflows/release.yml`: manual retry workflow for publishing the current checked-out package version.
|
|
75
|
+
|
|
76
|
+
The repository is private, so npm provenance publishing is not supported. GitHub Actions publishes with `--provenance=false` and skips npm lifecycle scripts because `pnpm release:check` has already run. Configure an npm automation token as `NPM_TOKEN` in the `npm` GitHub environment or repository secrets.
|
|
77
|
+
|
|
78
|
+
Manual local publish is still available as an emergency fallback:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pnpm release:check
|
|
82
|
+
npm publish --provenance=false --ignore-scripts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Maintenance
|
|
86
|
+
|
|
87
|
+
This repo is now maintained in two lanes:
|
|
88
|
+
|
|
89
|
+
- `stable`: the default user-facing toolchain, pinned to known-good upstream CLI versions in `src/toolchain.ts`.
|
|
90
|
+
- `latest`: an early-warning lane that tracks upstream scaffold tools without pinning, so breakage is detected before it reaches the default path.
|
|
91
|
+
|
|
92
|
+
The practical rule is simple: detect drift on `latest`, verify it, then promote the pinned `stable` versions only after the e2e matrix passes.
|
|
93
|
+
|
|
94
|
+
### Recommended automation
|
|
95
|
+
|
|
96
|
+
- Renovate is configured in `renovate.json` to keep this repo's own dependencies current.
|
|
97
|
+
- GitHub Actions CI runs on pushes and pull requests in `.github/workflows/ci.yml`.
|
|
98
|
+
- Weekly toolchain drift checks run in `.github/workflows/toolchain-drift.yml` for both `stable` and `latest`.
|
|
99
|
+
- Automatic releases publish through `.github/workflows/release-on-main.yml`; `.github/workflows/release.yml` is for manual retry only.
|
|
100
|
+
- Stable toolchain pins in `src/toolchain.ts` are annotated so Renovate can open PRs when upstream scaffold CLIs publish updates.
|
|
101
|
+
- `prepublishOnly` stays strict for manual local publishing. GitHub Actions skips npm lifecycle scripts during publish after running `pnpm release:check`.
|
|
102
|
+
|
|
103
|
+
### Upgrade workflow
|
|
104
|
+
|
|
105
|
+
Use this sequence when Renovate opens a dependency or toolchain PR:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pnpm install --frozen-lockfile
|
|
109
|
+
pnpm test
|
|
110
|
+
pnpm test:e2e
|
|
111
|
+
npm pack --dry-run
|
|
65
112
|
```
|
|
113
|
+
|
|
114
|
+
If you want to validate upstream scaffold drift manually, use the explicit latest-lane script:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
pnpm test:e2e:latest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
When a toolchain pin PR passes and the generated outputs look good, update the `verifiedAt` date in `src/toolchain.ts` and release normally.
|
|
121
|
+
|
|
122
|
+
### Release policy
|
|
123
|
+
|
|
124
|
+
- Publish `patch` releases for scaffold fixes, dependency bumps, and safer defaults.
|
|
125
|
+
- Publish `minor` releases for new stacks, new flags, or new generated capabilities.
|
|
126
|
+
- Publish `major` releases for breaking CLI changes or incompatible generated-project structure changes.
|
|
127
|
+
- Never publish from a dirty worktree.
|
package/dist/branding.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare const ANHEDRAL_LOGO_FILE_NAME = "anhedral.svg";
|
|
2
2
|
export declare const ANHEDRAL_LOGO_PUBLIC_PATH = "/anhedral.svg";
|
|
3
|
+
export declare const ANHEDRAL_FAVICON_FILE_NAME = "favicon.ico";
|
|
3
4
|
export declare function writeAnhedralLogo(root: string): void;
|
|
5
|
+
export declare function writeAnhedralWebBranding(root: string): void;
|
package/dist/branding.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { writeFile } from './util.js';
|
|
4
4
|
export const ANHEDRAL_LOGO_FILE_NAME = 'anhedral.svg';
|
|
5
5
|
export const ANHEDRAL_LOGO_PUBLIC_PATH = `/${ANHEDRAL_LOGO_FILE_NAME}`;
|
|
6
|
+
export const ANHEDRAL_FAVICON_FILE_NAME = 'favicon.ico';
|
|
6
7
|
const ANHEDRAL_LOGO_SVG = readFileSync(new URL('../anhedral.svg', import.meta.url), 'utf8');
|
|
8
|
+
const ANHEDRAL_FAVICON = readFileSync(new URL(`../${ANHEDRAL_FAVICON_FILE_NAME}`, import.meta.url));
|
|
7
9
|
export function writeAnhedralLogo(root) {
|
|
8
10
|
writeFile(path.join(root, 'public', ANHEDRAL_LOGO_FILE_NAME), ANHEDRAL_LOGO_SVG);
|
|
9
11
|
}
|
|
12
|
+
export function writeAnhedralWebBranding(root) {
|
|
13
|
+
writeAnhedralLogo(root);
|
|
14
|
+
const faviconPath = path.join(root, 'app', ANHEDRAL_FAVICON_FILE_NAME);
|
|
15
|
+
mkdirSync(path.dirname(faviconPath), { recursive: true });
|
|
16
|
+
writeFileSync(faviconPath, ANHEDRAL_FAVICON);
|
|
17
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FrontendMode, InitOptions } from './scaffold.js';
|
|
2
|
+
export declare const DEFAULT_FRONTEND_MODE: FrontendMode;
|
|
3
|
+
export declare const USAGE = "\nanhedral init [--next] [--extension] [--toolchain <latest|stable>]\n\nCommands:\n anhedral init\n Expo, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, RevenueCat + Stripe, Cloudflare R2, shared packages\n\n anhedral init --next\n Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2, shared packages\n\n anhedral init --extension\n Expo + Fastify plus a WXT Chrome extension\n\n anhedral init --next --extension\n Next.js + Fastify plus a WXT Chrome extension\n";
|
|
4
|
+
export type ParsedFlags = {
|
|
5
|
+
next: boolean;
|
|
6
|
+
extension: boolean;
|
|
7
|
+
toolchain?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function parseCli(args: string[]): ParsedFlags;
|
|
10
|
+
export declare function deriveProjectName(cwd: string): string;
|
|
11
|
+
export declare function deriveDisplayName(cwd: string): string;
|
|
12
|
+
export declare function buildOptions(flags: ParsedFlags): InitOptions;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { env } from 'node:process';
|
|
3
|
+
import { TOOLCHAIN_CHANNELS, resolveToolchainChannel } from './toolchain.js';
|
|
4
|
+
export const DEFAULT_FRONTEND_MODE = 'expo';
|
|
5
|
+
export const USAGE = `
|
|
6
|
+
anhedral init [--next] [--extension] [--toolchain <latest|stable>]
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
anhedral init
|
|
10
|
+
Expo, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, RevenueCat + Stripe, Cloudflare R2, shared packages
|
|
11
|
+
|
|
12
|
+
anhedral init --next
|
|
13
|
+
Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2, shared packages
|
|
14
|
+
|
|
15
|
+
anhedral init --extension
|
|
16
|
+
Expo + Fastify plus a WXT Chrome extension
|
|
17
|
+
|
|
18
|
+
anhedral init --next --extension
|
|
19
|
+
Next.js + Fastify plus a WXT Chrome extension
|
|
20
|
+
`;
|
|
21
|
+
export function parseCli(args) {
|
|
22
|
+
const flags = {
|
|
23
|
+
next: false,
|
|
24
|
+
extension: false,
|
|
25
|
+
};
|
|
26
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
27
|
+
const token = args[index];
|
|
28
|
+
if (!token.startsWith('--')) {
|
|
29
|
+
throw new Error(`Unexpected argument: ${token}. Use flags: --next, --extension, --toolchain`);
|
|
30
|
+
}
|
|
31
|
+
if (token === '--next') {
|
|
32
|
+
flags.next = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (token === '--extension') {
|
|
36
|
+
flags.extension = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (token === '--toolchain') {
|
|
40
|
+
const value = args[index + 1];
|
|
41
|
+
if (!value || value.startsWith('--')) {
|
|
42
|
+
throw new Error('Missing value for --toolchain');
|
|
43
|
+
}
|
|
44
|
+
flags.toolchain = value;
|
|
45
|
+
index += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Unknown flag: ${token}`);
|
|
49
|
+
}
|
|
50
|
+
return flags;
|
|
51
|
+
}
|
|
52
|
+
export function deriveProjectName(cwd) {
|
|
53
|
+
const base = path.basename(cwd);
|
|
54
|
+
const sanitized = base
|
|
55
|
+
.trim()
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
58
|
+
.replace(/^-+|-+$/g, '');
|
|
59
|
+
return sanitized || 'anhedral-app';
|
|
60
|
+
}
|
|
61
|
+
export function deriveDisplayName(cwd) {
|
|
62
|
+
const base = path.basename(cwd).trim();
|
|
63
|
+
return base || 'Anhedral App';
|
|
64
|
+
}
|
|
65
|
+
export function buildOptions(flags) {
|
|
66
|
+
const cwd = process.cwd();
|
|
67
|
+
const projectName = deriveProjectName(cwd);
|
|
68
|
+
const displayName = deriveDisplayName(cwd);
|
|
69
|
+
if (flags.toolchain != null && !TOOLCHAIN_CHANNELS.includes(flags.toolchain)) {
|
|
70
|
+
throw new Error(`--toolchain must be one of: ${TOOLCHAIN_CHANNELS.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
frontend: flags.next ? 'next' : DEFAULT_FRONTEND_MODE,
|
|
74
|
+
extension: flags.extension,
|
|
75
|
+
projectName,
|
|
76
|
+
displayName,
|
|
77
|
+
auth: 'clerk',
|
|
78
|
+
payments: flags.next ? 'stripe' : 'revenuecat',
|
|
79
|
+
db: 'neon',
|
|
80
|
+
orm: 'drizzle',
|
|
81
|
+
storage: 'r2',
|
|
82
|
+
api: 'fastify',
|
|
83
|
+
monorepo: true,
|
|
84
|
+
toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InitOptions } from './scaffold.js';
|
|
2
2
|
import { type ToolchainSpec } from './toolchain.js';
|
|
3
3
|
export type ScaffoldCommand = {
|
|
4
4
|
cmd: string;
|
|
@@ -7,6 +7,5 @@ export type ScaffoldCommand = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare function getDefaultWebInitCommand(toolchain: ToolchainSpec, projectName: string): ScaffoldCommand;
|
|
9
9
|
export declare function getDefaultWebDependencyCommands(): ScaffoldCommand[];
|
|
10
|
-
export declare function getHeadlessInstallCommands(): ScaffoldCommand[];
|
|
11
10
|
export declare function getBackendInstallCommands(): ScaffoldCommand[];
|
|
12
|
-
export declare function getSkillCommands(
|
|
11
|
+
export declare function getSkillCommands(options: Pick<InitOptions, 'frontend' | 'extension'>): string[];
|
package/dist/commands.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { toolPackageRef } from './toolchain.js';
|
|
2
|
-
const TYPESCRIPT_VERSION = '5.9.3';
|
|
3
|
-
const NODE_TYPES_VERSION = '25.5.0';
|
|
4
2
|
export function getDefaultWebInitCommand(toolchain, projectName) {
|
|
5
3
|
return {
|
|
6
4
|
cmd: [
|
|
@@ -21,7 +19,7 @@ export function getDefaultWebInitCommand(toolchain, projectName) {
|
|
|
21
19
|
export function getDefaultWebDependencyCommands() {
|
|
22
20
|
return [
|
|
23
21
|
{
|
|
24
|
-
cmd: 'pnpm add @clerk/nextjs
|
|
22
|
+
cmd: 'pnpm add @clerk/nextjs drizzle-orm @neondatabase/serverless stripe @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner',
|
|
25
23
|
skippable: true,
|
|
26
24
|
},
|
|
27
25
|
{
|
|
@@ -30,18 +28,6 @@ export function getDefaultWebDependencyCommands() {
|
|
|
30
28
|
},
|
|
31
29
|
];
|
|
32
30
|
}
|
|
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
31
|
export function getBackendInstallCommands() {
|
|
46
32
|
return [
|
|
47
33
|
{
|
|
@@ -52,23 +38,13 @@ export function getBackendInstallCommands() {
|
|
|
52
38
|
},
|
|
53
39
|
];
|
|
54
40
|
}
|
|
55
|
-
export function getSkillCommands(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
];
|
|
41
|
+
export function getSkillCommands(options) {
|
|
42
|
+
const commands = [
|
|
43
|
+
'pnpm dlx skills add https://github.com/clerk/skills --skill clerk-custom-ui',
|
|
44
|
+
'pnpm dlx skills add https://github.com/stripe/ai --skill stripe-best-practices',
|
|
45
|
+
];
|
|
46
|
+
if (options.frontend === 'expo') {
|
|
47
|
+
commands.splice(1, 0, 'pnpm dlx skills add https://github.com/revenuecat/revenuecat-skill --skill revenuecat');
|
|
73
48
|
}
|
|
49
|
+
return commands;
|
|
74
50
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,137 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
}
|
|
2
|
+
import { argv, exit } from 'node:process';
|
|
3
|
+
import { buildOptions, parseCli, USAGE } from './cli.js';
|
|
4
|
+
import { scaffoldProject } from './scaffold.js';
|
|
135
5
|
async function main() {
|
|
136
6
|
const args = argv.slice(2);
|
|
137
7
|
const command = args[0];
|
|
@@ -144,8 +14,12 @@ async function main() {
|
|
|
144
14
|
console.log(USAGE);
|
|
145
15
|
exit(1);
|
|
146
16
|
}
|
|
17
|
+
const rawArgs = args.slice(1);
|
|
18
|
+
if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
|
|
19
|
+
console.log(USAGE);
|
|
20
|
+
exit(0);
|
|
21
|
+
}
|
|
147
22
|
try {
|
|
148
|
-
const rawArgs = args.slice(1);
|
|
149
23
|
const options = buildOptions(parseCli(rawArgs));
|
|
150
24
|
await scaffoldProject(options);
|
|
151
25
|
}
|
package/dist/print.d.ts
ADDED
package/dist/print.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { stdout } from 'node:process';
|
|
2
|
+
const USE_COLOR = Boolean(stdout.isTTY) && process.env.NO_COLOR == null;
|
|
3
|
+
function paint(code, message) {
|
|
4
|
+
if (!USE_COLOR)
|
|
5
|
+
return message;
|
|
6
|
+
return `\x1b[${code}m${message}\x1b[0m`;
|
|
7
|
+
}
|
|
8
|
+
const dim = (value) => paint('2', value);
|
|
9
|
+
const bold = (value) => paint('1', value);
|
|
10
|
+
const cyan = (value) => paint('36', value);
|
|
11
|
+
const green = (value) => paint('32', value);
|
|
12
|
+
const red = (value) => paint('31', value);
|
|
13
|
+
const magenta = (value) => paint('35', value);
|
|
14
|
+
export const anhedralPrint = {
|
|
15
|
+
banner(message) {
|
|
16
|
+
console.log(`${bold(magenta('>'))} ${bold(message)}\n`);
|
|
17
|
+
},
|
|
18
|
+
section(message) {
|
|
19
|
+
console.log(`\n${cyan('==')} ${bold(message)}`);
|
|
20
|
+
},
|
|
21
|
+
step(message) {
|
|
22
|
+
console.log(` ${cyan('>')} ${message}`);
|
|
23
|
+
},
|
|
24
|
+
done(message) {
|
|
25
|
+
console.log(` ${green('ok')} ${message}`);
|
|
26
|
+
},
|
|
27
|
+
info(message) {
|
|
28
|
+
console.log(` ${dim(message)}`);
|
|
29
|
+
},
|
|
30
|
+
fail(message) {
|
|
31
|
+
console.log(` ${red('x')} ${message}`);
|
|
32
|
+
},
|
|
33
|
+
};
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type ToolchainChannel } from './toolchain.js';
|
|
2
|
-
export
|
|
3
|
-
export type InitMode = (typeof STACK_IDS)[number];
|
|
2
|
+
export type FrontendMode = 'expo' | 'next';
|
|
4
3
|
export type AuthMode = 'clerk' | 'betterauth';
|
|
5
4
|
export type PaymentsMode = 'none' | 'stripe' | 'revenuecat';
|
|
6
5
|
export interface InitOptions {
|
|
7
|
-
|
|
6
|
+
frontend: FrontendMode;
|
|
7
|
+
extension: boolean;
|
|
8
8
|
projectName: string;
|
|
9
9
|
displayName: string;
|
|
10
10
|
auth: AuthMode;
|