anhedral 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -269
- package/assets/anhedral-cli-init-technical.svg +322 -0
- package/assets/anhedral-cli-init.svg +257 -173
- package/dist/bin.js +28 -191
- package/dist/dependencies.d.ts +7 -57
- package/dist/dependencies.js +96 -308
- package/dist/index.d.ts +2 -7
- package/dist/index.js +1 -4
- package/dist/platforms/desktop.js +401 -0
- package/dist/platforms/extension.js +401 -0
- package/dist/platforms/mobile.js +232 -0
- package/dist/project.d.ts +0 -26
- package/dist/standard-products.d.ts +2 -0
- package/dist/standard-products.js +6 -0
- package/dist/standard.d.ts +18 -0
- package/dist/standard.js +578 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/application-stack-standard.md +700 -0
- package/package.json +34 -51
- package/CHANGELOG.md +0 -178
- package/CONTRIBUTING.md +0 -77
- package/SECURITY.md +0 -37
- package/SKILL.md +0 -339
- package/assets/images/svg/ably-symbol-color.svg +0 -8
- package/bin/anhedral.js +0 -2
- package/dist/architecture/contributions.d.ts +0 -28
- package/dist/architecture/contributions.js +0 -125
- package/dist/architecture/index.d.ts +0 -12
- package/dist/architecture/index.js +0 -6
- package/dist/architecture/manifest.d.ts +0 -53
- package/dist/architecture/manifest.js +0 -283
- package/dist/architecture/modules.d.ts +0 -41
- package/dist/architecture/modules.js +0 -187
- package/dist/architecture/plan.d.ts +0 -58
- package/dist/architecture/plan.js +0 -145
- package/dist/architecture/templates.d.ts +0 -10
- package/dist/architecture/templates.js +0 -23
- package/dist/cli.d.ts +0 -32
- package/dist/cli.js +0 -281
- package/dist/composers/root.d.ts +0 -6
- package/dist/composers/root.js +0 -35
- package/dist/print.d.ts +0 -8
- package/dist/print.js +0 -48
- package/dist/prompts.d.ts +0 -6
- package/dist/prompts.js +0 -41
- package/dist/scaffold.d.ts +0 -64
- package/dist/scaffold.js +0 -2102
- package/dist/template-source.d.ts +0 -20
- package/dist/template-source.js +0 -180
- package/dist/templates/api.d.ts +0 -2
- package/dist/templates/api.js +0 -3187
- package/dist/templates/assets-private-proxy.d.ts +0 -3
- package/dist/templates/assets-private-proxy.js +0 -277
- package/dist/templates/desktop.js +0 -792
- package/dist/templates/electron-updater.d.ts +0 -3
- package/dist/templates/electron-updater.js +0 -358
- package/dist/templates/extension.js +0 -856
- package/dist/templates/first-run.d.ts +0 -2
- package/dist/templates/first-run.js +0 -167
- package/dist/templates/mobile.js +0 -861
- package/dist/templates/shared.d.ts +0 -2
- package/dist/templates/shared.js +0 -577
- package/dist/templates/web.d.ts +0 -2
- package/dist/templates/web.js +0 -569
- package/dist/toolchain.d.ts +0 -3
- package/dist/toolchain.js +0 -8
- package/dist/ui.d.ts +0 -33
- package/dist/ui.js +0 -199
- package/dist/workspace-config.d.ts +0 -11
- package/dist/workspace-config.js +0 -372
- package/docs/NORTHSTAR.md +0 -114
- package/docs/conventions.md +0 -94
- package/docs/engineering-principles.md +0 -88
- package/docs/master-stack-map.md +0 -199
- package/docs/output-tree-contract.md +0 -493
- package/docs/references/manual-scaffolding.md +0 -421
- package/docs/references/provisioning.md +0 -577
- package/scripts/render-master-stack-map.mjs +0 -510
- package/templates/api-fastify/apps/api/tsconfig.json +0 -20
- package/templates/catalog.json +0 -23
- package/templates/db-drizzle/packages/db/tsconfig.json +0 -14
- package/templates/desktop-electron/apps/desktop/tsconfig.json +0 -37
- package/templates/extension-wxt/apps/extension/tsconfig.json +0 -41
- package/templates/mobile-expo/apps/mobile/expo-env.d.ts +0 -1
- package/templates/web-next/apps/web/next-env.d.ts +0 -6
- /package/dist/{templates → platforms}/desktop.d.ts +0 -0
- /package/dist/{templates → platforms}/extension.d.ts +0 -0
- /package/dist/{templates → platforms}/mobile.d.ts +0 -0
package/SKILL.md
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: anhedral-init
|
|
3
|
-
description: Guide a user through creating, provisioning, extending, explaining, or diagnosing a complete Anhedral TypeScript stack. Use for skill-led new/init/add/ui/doctor workflows; Computer Use or subagent-assisted cloud setup; domains, DNS, Vercel, Cloudflare, Neon, Clerk, R2, billing, stores, and Electron updates; development inside a generated workspace; ownership interpretation; manual reproduction; or verification.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Anhedral Init
|
|
7
|
-
|
|
8
|
-
Anhedral is a stack generator, not an application runtime or programming language. Generated projects use ordinary framework source and expose the complete integration code. Do not invent Anhedral-specific models, routes, queries, components, or client hooks when the selected framework already has a documented convention.
|
|
9
|
-
|
|
10
|
-
For every new project or provisioning request, begin by asking for the project
|
|
11
|
-
name and whether the user already owns a custom domain for it. Do not run init
|
|
12
|
-
or create provider resources until those answers establish canonical names.
|
|
13
|
-
|
|
14
|
-
`new` creates a destination directory; `init` works in an empty current
|
|
15
|
-
directory where `.git`, `.gitignore`, and `.DS_Store` are allowed.
|
|
16
|
-
|
|
17
|
-
## Bootstrap a blank workstation
|
|
18
|
-
|
|
19
|
-
Do not assume Node.js, npm, pnpm, Git, a browser, provider CLIs, or native build
|
|
20
|
-
tools are installed. After collecting the project name and domain status, audit
|
|
21
|
-
the workstation before generation:
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
node --version
|
|
25
|
-
npm --version
|
|
26
|
-
pnpm --version
|
|
27
|
-
git --version
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
For a new installation, recommend the current Node.js 24 LTS release. The
|
|
31
|
-
generator itself accepts `^20.19.0 || >=22.12.0`, but mobile projects require
|
|
32
|
-
`^22.13.0 || ^24.3.0 || >=25`; Node.js 24 LTS is the single supported choice
|
|
33
|
-
that covers every Anhedral module without using an end-of-life line.
|
|
34
|
-
|
|
35
|
-
If Node.js is absent, use Computer Use to open `https://nodejs.org/en/download`,
|
|
36
|
-
select the current Node.js 24 LTS installer for the user's operating system and
|
|
37
|
-
architecture, and stop for the user at the installer, administrator-password,
|
|
38
|
-
or operating-system approval boundary. npm is bundled with Node.js and may be
|
|
39
|
-
used to install pnpm, but npm is not the package manager for a generated
|
|
40
|
-
workspace. Installing the public Anhedral package does not require an npm
|
|
41
|
-
account or `npm login`.
|
|
42
|
-
|
|
43
|
-
Install the exact pnpm release recorded by this generator's
|
|
44
|
-
`package.json#packageManager` (currently `pnpm@10.34.5`):
|
|
45
|
-
|
|
46
|
-
```sh
|
|
47
|
-
npm install --global pnpm@10.34.5
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Ask before a global or administrator-level installation. Verify the four
|
|
51
|
-
commands above again after installation. Install Git with the operating-system
|
|
52
|
-
package manager or official installer when it is missing; stop for UAC,
|
|
53
|
-
administrator credentials, Xcode Command Line Tools approval, or a license
|
|
54
|
-
prompt. Configure the user's real Git name and email only after asking what
|
|
55
|
-
identity they want commits to use.
|
|
56
|
-
|
|
57
|
-
A modern browser is required for OAuth device flows and provider dashboards.
|
|
58
|
-
No global Vercel, Neon, Wrangler, EAS, shadcn, or Anhedral installation is
|
|
59
|
-
required: generated scripts invoke reviewed CLI versions through `pnpm dlx`.
|
|
60
|
-
GitHub CLI is recommended for repository creation. Clerk CLI and Ably CLI are
|
|
61
|
-
optional supplemental control planes; use them only after the corresponding
|
|
62
|
-
module is selected, record the resolved version, and do not run `clerk init`
|
|
63
|
-
inside an Anhedral project because the integration source already exists.
|
|
64
|
-
|
|
65
|
-
Native tools are conditional:
|
|
66
|
-
|
|
67
|
-
- EAS cloud builds do not require local Xcode or Android Studio.
|
|
68
|
-
- Local iOS builds and manual App Store uploads require macOS and Xcode.
|
|
69
|
-
- Local Android builds/emulators require Android Studio and a compatible JDK.
|
|
70
|
-
- Signed Electron releases must be built on each target OS; macOS notarization
|
|
71
|
-
needs Xcode tooling and Apple credentials, while Windows distribution needs
|
|
72
|
-
an organization-owned code-signing certificate and Windows runner.
|
|
73
|
-
- Extension development requires a Chromium browser; store publication does
|
|
74
|
-
not require another build CLI beyond the generated WXT scripts.
|
|
75
|
-
|
|
76
|
-
Anhedral intentionally requires neither Docker nor local Postgres. Database
|
|
77
|
-
modules use managed Neon.
|
|
78
|
-
|
|
79
|
-
## Choose the construction path
|
|
80
|
-
|
|
81
|
-
- Use the manual path whenever the user asks to avoid the Anhedral CLI, the CLI is unavailable, or the task requires explaining/reproducing the scaffold itself.
|
|
82
|
-
- Before creating or changing files manually, read [docs/references/manual-scaffolding.md](docs/references/manual-scaffolding.md) completely and follow its ordered procedure. Treat it as the canonical manual checklist, including its source-file matrix and feature gates.
|
|
83
|
-
- Use the CLI path only when the user permits it. Do not silently substitute `anhedral init`, `anhedral add`, imported generator code, or a generated demo for the manual path.
|
|
84
|
-
- For an exact-version scaffold inside the Anhedral source repository, render from the current `src/templates/*.ts`, `src/scaffold.ts`, and `src/dependencies.ts`; do not copy stale demo output. Outside that repository, implement the behavior and file contract in the manual reference and pin a mutually compatible toolchain.
|
|
85
|
-
|
|
86
|
-
The manual path must leave a complete working workspace, not merely a directory tree or list of commands. Write the source, configuration, tests, environment examples, CI, deployment configuration, and database migration gate selected by the module plan. Install and verify when the environment permits it.
|
|
87
|
-
|
|
88
|
-
## Select modules
|
|
89
|
-
|
|
90
|
-
Choose app surfaces from `web`, `mobile`, `api`, `desktop`, and `extension`.
|
|
91
|
-
|
|
92
|
-
Choose features from `db`, `auth`, `billing`, `storage`, `native-subscriptions`, and `electron-updater`.
|
|
93
|
-
|
|
94
|
-
Apply these dependency rules automatically:
|
|
95
|
-
|
|
96
|
-
```text
|
|
97
|
-
auth -> api + db
|
|
98
|
-
billing -> auth
|
|
99
|
-
storage -> auth
|
|
100
|
-
native-subscriptions -> mobile + billing
|
|
101
|
-
electron-updater -> desktop
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
With no module flags in an interactive terminal, Anhedral prompts and suggests
|
|
105
|
-
a focused web app. In a noninteractive environment, no flags retain the full
|
|
106
|
-
stack for compatibility. Use `--all` when the complete stack is intentional,
|
|
107
|
-
or pass explicit modules for reproducible automation.
|
|
108
|
-
|
|
109
|
-
## Master stack map
|
|
110
|
-
|
|
111
|
-
Read [docs/master-stack-map.md](docs/master-stack-map.md) completely before
|
|
112
|
-
explaining the whole platform, selecting modules for a complete product, or
|
|
113
|
-
planning cross-provider provisioning. Treat it as the canonical map of the
|
|
114
|
-
stack, command surface, runtime connections, agent workflow, and release path.
|
|
115
|
-
|
|
116
|
-
```text
|
|
117
|
-
project + domain intake
|
|
118
|
-
|
|
|
119
|
-
v
|
|
120
|
-
1 select + plan -> 2 generate safely -> 3 one pnpm/Turborepo workspace
|
|
121
|
-
|
|
|
122
|
-
v
|
|
123
|
-
4 connected product capabilities -> 5 provision cloud + DNS
|
|
124
|
-
|
|
|
125
|
-
v
|
|
126
|
-
6 generated package.json commands -> 7 developer/agent/Computer Use
|
|
127
|
-
|
|
|
128
|
-
v
|
|
129
|
-
8 verify, release, and operate
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Use the full fixed-width ASCII map from the document when the user asks for the
|
|
133
|
-
master stack map. Do not redraw it from memory or omit unselected modules when
|
|
134
|
-
describing Anhedral's complete capability surface; mark modules as selected or
|
|
135
|
-
unselected separately.
|
|
136
|
-
|
|
137
|
-
## Run the guided init workflow
|
|
138
|
-
|
|
139
|
-
Read [docs/references/provisioning.md](docs/references/provisioning.md) completely before
|
|
140
|
-
starting a new project or provisioning any provider. Follow its intake,
|
|
141
|
-
capability discovery, Computer Use, subagent, authentication, secret-handling,
|
|
142
|
-
domain, provider-order, and verification gates.
|
|
143
|
-
|
|
144
|
-
The lead agent must:
|
|
145
|
-
|
|
146
|
-
1. Ask for project name and existing/custom-domain status first.
|
|
147
|
-
2. Audit and bootstrap the workstation, then discover whether Computer
|
|
148
|
-
Use/browser control and subagents are available.
|
|
149
|
-
3. Resolve modules, required accounts, and canonical project/resource names.
|
|
150
|
-
4. Run the explicit CLI command, then read the generated `PRODUCTION.md`,
|
|
151
|
-
`anhedral.json`, root scripts, and package-local environment examples.
|
|
152
|
-
5. Provision only selected providers in dependency order, pausing for sign-in, MFA, secret
|
|
153
|
-
generation, purchases, and final submissions exactly as the reference says.
|
|
154
|
-
6. Keep secrets out of chat and tool output; have the user paste them directly
|
|
155
|
-
into uncommitted environment files and protected provider fields.
|
|
156
|
-
7. Verify code, cloud resources, DNS, TLS, and provider health before handoff.
|
|
157
|
-
|
|
158
|
-
## Initialize
|
|
159
|
-
|
|
160
|
-
```sh
|
|
161
|
-
pnpm dlx anhedral@latest new my-product
|
|
162
|
-
cd my-product
|
|
163
|
-
pnpm first-run
|
|
164
|
-
pnpm ready
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
`first-run` creates only missing package-local environment files and never
|
|
168
|
-
overwrites existing configuration. `ready` reports missing filenames and
|
|
169
|
-
required variable names without printing values; resolve every blocker before
|
|
170
|
-
starting affected services.
|
|
171
|
-
|
|
172
|
-
Prefer explicit modules when the user requests a smaller stack:
|
|
173
|
-
|
|
174
|
-
```sh
|
|
175
|
-
pnpm dlx anhedral@latest new my-product --web --api --db --auth
|
|
176
|
-
pnpm dlx anhedral@latest new my-product --web --mobile --ui button,dialog --native-styling nativewind
|
|
177
|
-
pnpm dlx anhedral@latest new my-api --api --skip-install
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Use the stable toolchain for normal generation. The `latest` value is retained only as a metadata compatibility channel for maintainer investigations:
|
|
181
|
-
|
|
182
|
-
```sh
|
|
183
|
-
pnpm dlx anhedral@latest new my-product --toolchain stable
|
|
184
|
-
pnpm dlx anhedral@latest new my-product --toolchain latest
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Both channels generate the same exact verified dependencies and integrity-checked bundled templates; `latest` only records investigation intent in `anhedral.json`. Upstream refreshes happen outside user projects and are shipped only after review and checksum verification. Neither channel invokes mutable upstream framework generators inside a user project.
|
|
188
|
-
|
|
189
|
-
Do not run any command in this section during the manual path.
|
|
190
|
-
|
|
191
|
-
## Develop inside a generated project
|
|
192
|
-
|
|
193
|
-
Read these files before changing product code:
|
|
194
|
-
|
|
195
|
-
1. `README.md` for the selected modules, first run, and source-location map.
|
|
196
|
-
2. `docs/DEVELOPMENT.md` for end-to-end feature recipes.
|
|
197
|
-
3. `docs/STACK.md` for responsibility boundaries and official tool documentation.
|
|
198
|
-
4. `PRODUCTION.md` only for provisioning, deployment, DNS, stores, or release work.
|
|
199
|
-
|
|
200
|
-
Use the native source conventions:
|
|
201
|
-
|
|
202
|
-
- Next.js code belongs in `apps/web/app`, `apps/web/components`, and `apps/web/lib`.
|
|
203
|
-
- Expo Router code belongs in `apps/mobile/app` and `apps/mobile/components`.
|
|
204
|
-
- Product Fastify route registration belongs in `apps/api/src/routes/app.ts`. Server-only feature modules live alongside the managed integrations in `apps/api/src`.
|
|
205
|
-
- Product Zod network schemas belong in `packages/contracts/src/app.ts`.
|
|
206
|
-
- Product client-safe HTTP methods belong in `packages/api-client/src/app.ts`.
|
|
207
|
-
- Product Drizzle tables belong in `packages/db/src/app-schema.ts`; generate and review SQL migrations.
|
|
208
|
-
|
|
209
|
-
Implement an end-to-end feature as `contracts -> database/service -> route -> API client -> frontend`. The generated `items` feature demonstrates that same path on every selected client—Next.js, Expo, Electron, and WXT—using one contract and API client with platform-native UI. Frontends may import contracts and client-safe packages, never API services, database connections, or server environments. This stack uses managed Neon and intentionally has no local Postgres service.
|
|
210
|
-
|
|
211
|
-
## Add modules safely
|
|
212
|
-
|
|
213
|
-
Run `add` only from a project containing `anhedral.json` schema v5 at the current generator version. If `doctor` identifies a supported older generator, preview and apply its transactional upgrade first:
|
|
214
|
-
|
|
215
|
-
```sh
|
|
216
|
-
pnpm dlx anhedral@latest upgrade --dry-run
|
|
217
|
-
pnpm dlx anhedral@latest upgrade
|
|
218
|
-
pnpm dlx anhedral@latest add desktop extension
|
|
219
|
-
pnpm dlx anhedral@latest add storage --dry-run
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Use `--dry-run` before a consequential add. Use `--json` when another program needs the plan; JSON failures include a stable `code` and a human-readable `error`. Use `--verbose` for interactive child-command diagnostics. Anhedral preserves recorded user-owned extension files and intentionally refuses modified managed files, symlinks, and unowned collisions; do not bypass those conflicts.
|
|
223
|
-
|
|
224
|
-
For a 0.3 upgrade blocked by a modified managed file, preserve the change in source control, restore that file to its recorded 0.3 content, apply the upgrade, and port the product behavior into the corresponding 0.4 user-owned extension seam. Never approve the upgrade by rewriting manifest hashes.
|
|
225
|
-
|
|
226
|
-
Require the manifest generator version to match the running CLI. Regenerate projects created by another version; do not add compatibility branches.
|
|
227
|
-
|
|
228
|
-
Do not rewrite a generated project's README or custom workflows while adding modules. Preserve custom package fields and mergeable root configuration.
|
|
229
|
-
|
|
230
|
-
`README.md` and `PRODUCTION.md` are user-owned and are not regenerated by
|
|
231
|
-
`anhedral add`. After adding modules, do not assume those two files describe the
|
|
232
|
-
new provider set. Reconcile them against `anhedral.json`, the managed project
|
|
233
|
-
`SKILL.md`, root `package.json` scripts, and every package-local `.env.example`;
|
|
234
|
-
then update the user-owned documentation explicitly with the developer's
|
|
235
|
-
approval.
|
|
236
|
-
|
|
237
|
-
For manual projects without a trustworthy schema-v5 `anhedral.json`, add modules with the manual reference instead. Never fabricate manifest ownership records, template provenance, UI installation records, or hashes. A manually authored workspace is valid as an application workspace but is not CLI-managed until it has been regenerated by a matching Anhedral version.
|
|
238
|
-
|
|
239
|
-
## Add UI components
|
|
240
|
-
|
|
241
|
-
Use the unified UI wrapper for source-owned components:
|
|
242
|
-
|
|
243
|
-
```sh
|
|
244
|
-
pnpm dlx anhedral@latest ui add button dialog
|
|
245
|
-
pnpm dlx anhedral@latest ui add select --target mobile
|
|
246
|
-
pnpm dlx anhedral@latest ui add data-table --target web --dry-run
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Anhedral routes web, desktop, and extension targets to shadcn/ui. It routes Expo mobile to React Native Reusables using the manifest's NativeWind or Uniwind selection. Omit `--target` to add the provider-specific implementation to every selected UI client. Provider writes occur in transaction staging and must pass the same ownership checks as module additions.
|
|
250
|
-
|
|
251
|
-
## Diagnose
|
|
252
|
-
|
|
253
|
-
Dry-runs never perform transaction recovery. If a recovery journal is pending, rerun the original command without `--dry-run` before requesting a new preview.
|
|
254
|
-
|
|
255
|
-
Check recorded files and hashes with:
|
|
256
|
-
|
|
257
|
-
```sh
|
|
258
|
-
pnpm dlx anhedral@latest doctor
|
|
259
|
-
pnpm dlx anhedral@latest doctor --json
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Explain that warnings can represent mergeable or user-owned drift, while modified or missing managed files make the project unhealthy.
|
|
263
|
-
|
|
264
|
-
## Configure the generated project
|
|
265
|
-
|
|
266
|
-
Create only missing local environment files, fill selected provider values, and
|
|
267
|
-
verify readiness:
|
|
268
|
-
|
|
269
|
-
```sh
|
|
270
|
-
pnpm install
|
|
271
|
-
pnpm first-run
|
|
272
|
-
pnpm ready
|
|
273
|
-
pnpm verify
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
`first-run` copies only examples that exist for selected surfaces and never
|
|
277
|
-
overwrites an existing file. Client packages use package-local runtime files
|
|
278
|
-
(for example, `apps/web/.env.local` and `apps/desktop/.env`); the root
|
|
279
|
-
`.env.example` is an inventory, not a runtime environment file.
|
|
280
|
-
|
|
281
|
-
Common selected keys are:
|
|
282
|
-
|
|
283
|
-
- Neon: `DATABASE_URL`
|
|
284
|
-
- Clerk server: `CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`
|
|
285
|
-
- Clerk clients: `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`, `VITE_CLERK_PUBLISHABLE_KEY`
|
|
286
|
-
- RevenueCat: `RC_WEBHOOK_SECRET`, `RC_SECRET_API_KEY`, `RC_ENTITLEMENT_ID`, `EXPO_PUBLIC_RC_*`
|
|
287
|
-
- Ably billing synchronization: server-only `ABLY_API_KEY`; clients obtain scoped tokens from the API
|
|
288
|
-
- R2: `BASE_URL`, `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`, `R2_PREFIX=storage`, `R2_PROXY_READ_URL_TTL_SECONDS=600`; keep `CLOUDFLARE_API_TOKEN` operations/CI-only
|
|
289
|
-
- Internal billing/storage jobs: high-entropy server-only `CRON_SECRET`
|
|
290
|
-
- Electron updates: `DESKTOP_UPDATE_BASE_URL` in `apps/desktop/electron-builder.env`; the private R2 binding needs no credential in the packaged application
|
|
291
|
-
|
|
292
|
-
Never commit real `.env` files. Keep `ANHEDRAL_DEMO=false` in production.
|
|
293
|
-
|
|
294
|
-
Run only scripts present for installed modules. `pnpm dev` starts the primary
|
|
295
|
-
product loop—web plus API when web is selected, otherwise the first selected
|
|
296
|
-
surface. A multi-surface project also exposes `pnpm dev:all`; use it only when
|
|
297
|
-
the user wants every selected development server. Typical focused commands
|
|
298
|
-
include `dev:web`, `dev:mobile`, `dev:api`, `dev:desktop`, `dev:extension`,
|
|
299
|
-
corresponding `verify:*` scripts, and database scripts for `db` projects.
|
|
300
|
-
|
|
301
|
-
## Understand deployment
|
|
302
|
-
|
|
303
|
-
Treat the root `vercel.json` as the only Vercel Services configuration. It uses the current `services` schema, declares `apps/api` and `apps/web` as independent service roots, and routes `/api/(.*)` before the web catch-all. Vercel preserves the original path, so Fastify mounts its routes under `/api` locally and in deployment. Select the Services framework preset before deploying.
|
|
304
|
-
|
|
305
|
-
For billing, keep Neon authoritative: RevenueCat reconciliation writes the subscription revision and realtime outbox atomically, Ably carries only per-user invalidations, and every client refetches the entitlement. Schedule the authenticated outbox flush every five minutes and verify purchase, webhook, reconnect, and foreground recovery paths.
|
|
306
|
-
|
|
307
|
-
The generated billing substrate does not implement Stripe Checkout, Stripe
|
|
308
|
-
webhooks, or a web purchase screen. RevenueCat's Test Store can exercise the
|
|
309
|
-
entitlement flow without Apple, Google, or Stripe accounts. Native purchases
|
|
310
|
-
require `native-subscriptions` plus Apple/Google store setup. If the user wants
|
|
311
|
-
web subscriptions through RevenueCat Billing or a manual Stripe integration,
|
|
312
|
-
treat that as an explicit product feature with additional account, checkout,
|
|
313
|
-
webhook, tax, and customer-portal work; do not claim the generated billing
|
|
314
|
-
module already completed it.
|
|
315
|
-
|
|
316
|
-
Use `apps/mobile` for EAS, `pnpm desktop:build` for a current-host Electron artifact, matching platform-specific desktop scripts in CI, and `pnpm extension:zip` for the WXT archive.
|
|
317
|
-
|
|
318
|
-
For `electron-updater`, provision the generated private R2 bucket and
|
|
319
|
-
`desktop-updater` Worker at `updates.<domain>`. Keep the Worker custom domain and
|
|
320
|
-
`DESKTOP_UPDATE_BASE_URL` identical. Build signed artifacts on each native
|
|
321
|
-
platform and publish artifacts before mutable channel metadata.
|
|
322
|
-
|
|
323
|
-
For storage, generate `apps/assets-private-proxy` plus `cloudflare/r2-cors.template.json` and keep the R2 bucket private. The Worker must be named `assets-private-proxy`, bind the normalized `<project>-assets` bucket as `ASSETS`, disable `workers.dev`, and own `assets.<domain>` as a Worker Custom Domain. Presigned uploads continue on the R2 S3 API hostname. Root every key below `R2_PREFIX=storage`; expose only `storage/confirmed/` through the Worker and reject staging plus `generation-inputs`. Authenticated private reads must verify upload ownership and return a presigned GET URL bounded by `R2_PROXY_READ_URL_TTL_SECONDS`.
|
|
324
|
-
|
|
325
|
-
When explaining domains, distinguish DNS delegation from registrar transfer. A domain can use Cloudflare authoritative nameservers while remaining with its current registrar; transferring registration/billing to Cloudflare Registrar is optional and subject to eligibility locks. Keep Vercel app/API A or CNAME records DNS-only in Cloudflare, while Worker Custom Domains stay Cloudflare-managed and proxied.
|
|
326
|
-
|
|
327
|
-
## Verify outcomes
|
|
328
|
-
|
|
329
|
-
After generation or repair, run the strongest practical checks:
|
|
330
|
-
|
|
331
|
-
```sh
|
|
332
|
-
pnpm typecheck
|
|
333
|
-
pnpm verify
|
|
334
|
-
pnpm build
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
Use `pnpm --filter ./apps/api test` when the API exists. If verification fails, report the exact package and command rather than replacing generated ownership records manually.
|
|
338
|
-
|
|
339
|
-
For exact representative trees, consult `docs/output-tree-contract.md` in the Anhedral repository. During manual construction, also compare the finished workspace with the file matrix in [docs/references/manual-scaffolding.md](docs/references/manual-scaffolding.md); every selected conditional file must exist and every unselected provider integration must be absent.
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<svg width="78" height="64" viewBox="0 0 78 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<path d="M38.5717 0L6.29575 59.0738L0 54.6586L29.8639 0H38.5717ZM39.0214 0L71.2973 59.0738L77.5931 54.6586L47.7292 0H39.0214Z" fill="url(#ably-symbol-a)"/>
|
|
3
|
-
<path d="M70.8477 59.4213L38.7966 34.3201L6.74548 59.4213L13.2865 64L38.7966 44.0294L64.3066 64L70.8477 59.4213Z" fill="url(#ably-symbol-b)"/>
|
|
4
|
-
<defs>
|
|
5
|
-
<linearGradient id="ably-symbol-a" x1="10.9472" y1="74.8439" x2="64.9206" y2="14.9005" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
|
|
6
|
-
<linearGradient id="ably-symbol-b" x1="21.4169" y1="78.7187" x2="53.3166" y2="43.2904" gradientUnits="userSpaceOnUse"><stop stop-color="#FF5416"/><stop offset="0.461" stop-color="#FF4712"/><stop offset="1" stop-color="#FF0000"/></linearGradient>
|
|
7
|
-
</defs>
|
|
8
|
-
</svg>
|
package/bin/anhedral.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type ModuleId } from './modules.js';
|
|
2
|
-
export type EnvironmentContribution = {
|
|
3
|
-
readonly owner: ModuleId;
|
|
4
|
-
readonly name: string;
|
|
5
|
-
readonly defaultValue: string;
|
|
6
|
-
};
|
|
7
|
-
export type CronContribution = {
|
|
8
|
-
readonly owner: ModuleId;
|
|
9
|
-
readonly id: string;
|
|
10
|
-
readonly path: string;
|
|
11
|
-
readonly schedule: string;
|
|
12
|
-
};
|
|
13
|
-
export type ModuleCompositionContribution = {
|
|
14
|
-
readonly module: ModuleId;
|
|
15
|
-
readonly environment: readonly Omit<EnvironmentContribution, 'owner'>[];
|
|
16
|
-
readonly crons: readonly Omit<CronContribution, 'owner'>[];
|
|
17
|
-
};
|
|
18
|
-
export type CompositionModel = {
|
|
19
|
-
readonly modules: readonly ModuleId[];
|
|
20
|
-
readonly environment: readonly EnvironmentContribution[];
|
|
21
|
-
readonly crons: readonly CronContribution[];
|
|
22
|
-
};
|
|
23
|
-
export type CompositionErrorCode = 'DUPLICATE_ENVIRONMENT_CONTRIBUTION' | 'DUPLICATE_CRON_CONTRIBUTION';
|
|
24
|
-
export declare class CompositionError extends Error {
|
|
25
|
-
readonly code: CompositionErrorCode;
|
|
26
|
-
constructor(code: CompositionErrorCode, message: string);
|
|
27
|
-
}
|
|
28
|
-
export declare function collectModuleContributions(requestedModules: readonly ModuleId[], overrides?: Readonly<Partial<Record<ModuleId, ModuleCompositionContribution>>>): CompositionModel;
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { resolveModules } from './modules.js';
|
|
2
|
-
export class CompositionError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
constructor(code, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.code = code;
|
|
7
|
-
this.name = 'CompositionError';
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
const EMPTY = Object.freeze([]);
|
|
11
|
-
const MODULE_CONTRIBUTIONS = Object.freeze({
|
|
12
|
-
web: Object.freeze({ module: 'web', environment: EMPTY, crons: EMPTY }),
|
|
13
|
-
mobile: Object.freeze({ module: 'mobile', environment: EMPTY, crons: EMPTY }),
|
|
14
|
-
api: Object.freeze({
|
|
15
|
-
module: 'api',
|
|
16
|
-
environment: Object.freeze([
|
|
17
|
-
{ name: 'ANHEDRAL_DEMO', defaultValue: 'false' },
|
|
18
|
-
{ name: 'PORT', defaultValue: '8787' },
|
|
19
|
-
{ name: 'TRUST_PROXY_HOPS', defaultValue: '0' },
|
|
20
|
-
{ name: 'CORS_ORIGINS', defaultValue: '' },
|
|
21
|
-
]),
|
|
22
|
-
crons: EMPTY,
|
|
23
|
-
}),
|
|
24
|
-
desktop: Object.freeze({ module: 'desktop', environment: EMPTY, crons: EMPTY }),
|
|
25
|
-
extension: Object.freeze({ module: 'extension', environment: EMPTY, crons: EMPTY }),
|
|
26
|
-
db: Object.freeze({
|
|
27
|
-
module: 'db',
|
|
28
|
-
environment: Object.freeze([{ name: 'DATABASE_URL', defaultValue: 'YOUR_NEON_POSTGRES_URL' }]),
|
|
29
|
-
crons: EMPTY,
|
|
30
|
-
}),
|
|
31
|
-
auth: Object.freeze({
|
|
32
|
-
module: 'auth',
|
|
33
|
-
environment: Object.freeze([
|
|
34
|
-
{ name: 'CLERK_PUBLISHABLE_KEY', defaultValue: 'pk_test_***' },
|
|
35
|
-
{ name: 'CLERK_SECRET_KEY', defaultValue: 'sk_test_***' },
|
|
36
|
-
]),
|
|
37
|
-
crons: EMPTY,
|
|
38
|
-
}),
|
|
39
|
-
billing: Object.freeze({
|
|
40
|
-
module: 'billing',
|
|
41
|
-
environment: Object.freeze([
|
|
42
|
-
{ name: 'RC_WEBHOOK_SECRET', defaultValue: '' },
|
|
43
|
-
{ name: 'RC_SECRET_API_KEY', defaultValue: '' },
|
|
44
|
-
{ name: 'RC_ENTITLEMENT_ID', defaultValue: 'pro' },
|
|
45
|
-
{ name: 'ABLY_API_KEY', defaultValue: '' },
|
|
46
|
-
{ name: 'CRON_SECRET', defaultValue: '' },
|
|
47
|
-
]),
|
|
48
|
-
crons: Object.freeze([
|
|
49
|
-
{ id: 'realtime-outbox', path: '/api/internal/realtime/flush', schedule: '*/5 * * * *' },
|
|
50
|
-
]),
|
|
51
|
-
}),
|
|
52
|
-
storage: Object.freeze({
|
|
53
|
-
module: 'storage',
|
|
54
|
-
environment: Object.freeze([
|
|
55
|
-
{ name: 'BASE_URL', defaultValue: 'http://localhost:8787' },
|
|
56
|
-
{ name: 'R2_ACCOUNT_ID', defaultValue: '' },
|
|
57
|
-
{ name: 'R2_ACCESS_KEY_ID', defaultValue: '' },
|
|
58
|
-
{ name: 'R2_SECRET_ACCESS_KEY', defaultValue: '' },
|
|
59
|
-
{ name: 'R2_BUCKET_NAME', defaultValue: '' },
|
|
60
|
-
{ name: 'R2_PREFIX', defaultValue: 'storage' },
|
|
61
|
-
{ name: 'R2_PROXY_READ_URL_TTL_SECONDS', defaultValue: '600' },
|
|
62
|
-
{ name: 'CLOUDFLARE_API_TOKEN', defaultValue: '' },
|
|
63
|
-
{ name: 'CRON_SECRET', defaultValue: '' },
|
|
64
|
-
]),
|
|
65
|
-
crons: Object.freeze([
|
|
66
|
-
{ id: 'storage-cleanup', path: '/api/internal/storage/cleanup', schedule: '0 3 * * *' },
|
|
67
|
-
]),
|
|
68
|
-
}),
|
|
69
|
-
'native-subscriptions': Object.freeze({
|
|
70
|
-
module: 'native-subscriptions',
|
|
71
|
-
environment: EMPTY,
|
|
72
|
-
crons: EMPTY,
|
|
73
|
-
}),
|
|
74
|
-
'electron-updater': Object.freeze({
|
|
75
|
-
module: 'electron-updater',
|
|
76
|
-
environment: Object.freeze([
|
|
77
|
-
{ name: 'DESKTOP_UPDATE_BASE_URL', defaultValue: 'https://updates.example.com' },
|
|
78
|
-
]),
|
|
79
|
-
crons: EMPTY,
|
|
80
|
-
}),
|
|
81
|
-
});
|
|
82
|
-
function collectEnvironmentFromRegistry(modules, registry) {
|
|
83
|
-
const byName = new Map();
|
|
84
|
-
for (const module of modules) {
|
|
85
|
-
for (const contribution of registry[module].environment) {
|
|
86
|
-
const candidate = Object.freeze({ owner: module, ...contribution });
|
|
87
|
-
const previous = byName.get(candidate.name);
|
|
88
|
-
if (previous && previous.defaultValue !== candidate.defaultValue) {
|
|
89
|
-
throw new CompositionError('DUPLICATE_ENVIRONMENT_CONTRIBUTION', `Environment variable ${candidate.name} has incompatible defaults from ${previous.owner} and ${module}.`);
|
|
90
|
-
}
|
|
91
|
-
if (!previous)
|
|
92
|
-
byName.set(candidate.name, candidate);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return Object.freeze([...byName.values()]);
|
|
96
|
-
}
|
|
97
|
-
function collectCronsFromRegistry(modules, registry) {
|
|
98
|
-
const byId = new Map();
|
|
99
|
-
for (const module of modules) {
|
|
100
|
-
for (const contribution of registry[module].crons) {
|
|
101
|
-
const candidate = Object.freeze({ owner: module, ...contribution });
|
|
102
|
-
const previous = byId.get(candidate.id);
|
|
103
|
-
if (previous && (previous.path !== candidate.path || previous.schedule !== candidate.schedule)) {
|
|
104
|
-
throw new CompositionError('DUPLICATE_CRON_CONTRIBUTION', `Cron ${candidate.id} has incompatible definitions from ${previous.owner} and ${module}.`);
|
|
105
|
-
}
|
|
106
|
-
if (!previous)
|
|
107
|
-
byId.set(candidate.id, candidate);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return Object.freeze([...byId.values()]);
|
|
111
|
-
}
|
|
112
|
-
export function collectModuleContributions(requestedModules, overrides = {}) {
|
|
113
|
-
const modules = resolveModules(requestedModules).resolvedModules;
|
|
114
|
-
const contributions = Object.freeze({ ...MODULE_CONTRIBUTIONS, ...overrides });
|
|
115
|
-
for (const module of modules) {
|
|
116
|
-
if (contributions[module].module !== module) {
|
|
117
|
-
throw new Error(`Composition contribution key ${module} must declare the same module ID.`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return Object.freeze({
|
|
121
|
-
modules,
|
|
122
|
-
environment: collectEnvironmentFromRegistry(modules, contributions),
|
|
123
|
-
crons: collectCronsFromRegistry(modules, contributions),
|
|
124
|
-
});
|
|
125
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { APP_MODULES, DEFAULT_MODULE_DEFINITIONS, DEFAULT_MODULE_REGISTRY, FEATURE_MODULES, MODULE_IDS, ModuleRegistryError, ModuleResolutionError, createModuleRegistry, isModuleId, resolveModules, } from './modules.js';
|
|
2
|
-
export type { AppModule, DependencyEdge, FeatureModule, ModuleDefinition, ModuleId, ModuleKind, ModuleRegistry, ModuleRegistryErrorCode, ModuleResolution, ModuleResolutionErrorCode, } from './modules.js';
|
|
3
|
-
export { FILE_OWNERSHIP_CLASSES, PlanBuildError, buildGenerationPlan, hashContent, isFileOwnershipClass, isSha256, normalizePlanPath, writePolicyForOwnership, } from './plan.js';
|
|
4
|
-
export type { BuildGenerationPlanInput, FileOwner, FileOwnershipClass, FileWritePolicy, GenerationOperation, GenerationPlan, ModulePlanContribution, PlanBuildErrorCode, PlanFileInput, PlannedFile, } from './plan.js';
|
|
5
|
-
export { MANIFEST_SCHEMA_VERSION, ManifestValidationError, createManifest, readManifest, serializeManifest, } from './manifest.js';
|
|
6
|
-
export { TEMPLATE_IDS, isTemplateId, templateIdsForModules, } from './templates.js';
|
|
7
|
-
export type { TemplateId, TemplateProvenance, TemplateProvenanceMap, } from './templates.js';
|
|
8
|
-
export { CompositionError, collectModuleContributions, } from './contributions.js';
|
|
9
|
-
export type { CompositionErrorCode, CompositionModel, CronContribution, EnvironmentContribution, ModuleCompositionContribution, } from './contributions.js';
|
|
10
|
-
export type { CreateManifestInput, ManifestFileRecord, ManifestToolchain, ManifestValidationErrorCode, ProjectManifest, } from './manifest.js';
|
|
11
|
-
export { NATIVE_STYLING_LIBRARIES, UI_PROVIDERS, UI_TARGETS, buildUiInstallCommands, isNativeStylingLibrary, isUiProvider, isUiTarget, mergeUiInstalls, normalizeUiComponentName, parseUiComponentList, providerForTarget, registrySourceFor, resolveUiInstalls, uiInstallKey, } from '../ui.js';
|
|
12
|
-
export type { NativeStylingLibrary, UiComponentInstall, UiInstallCommand, UiProvider, UiTarget, } from '../ui.js';
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { APP_MODULES, DEFAULT_MODULE_DEFINITIONS, DEFAULT_MODULE_REGISTRY, FEATURE_MODULES, MODULE_IDS, ModuleRegistryError, ModuleResolutionError, createModuleRegistry, isModuleId, resolveModules, } from './modules.js';
|
|
2
|
-
export { FILE_OWNERSHIP_CLASSES, PlanBuildError, buildGenerationPlan, hashContent, isFileOwnershipClass, isSha256, normalizePlanPath, writePolicyForOwnership, } from './plan.js';
|
|
3
|
-
export { MANIFEST_SCHEMA_VERSION, ManifestValidationError, createManifest, readManifest, serializeManifest, } from './manifest.js';
|
|
4
|
-
export { TEMPLATE_IDS, isTemplateId, templateIdsForModules, } from './templates.js';
|
|
5
|
-
export { CompositionError, collectModuleContributions, } from './contributions.js';
|
|
6
|
-
export { NATIVE_STYLING_LIBRARIES, UI_PROVIDERS, UI_TARGETS, buildUiInstallCommands, isNativeStylingLibrary, isUiProvider, isUiTarget, mergeUiInstalls, normalizeUiComponentName, parseUiComponentList, providerForTarget, registrySourceFor, resolveUiInstalls, uiInstallKey, } from '../ui.js';
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { type ModuleId, type ModuleRegistry } from './modules.js';
|
|
2
|
-
import { type FileOwner, type FileOwnershipClass, type GenerationPlan } from './plan.js';
|
|
3
|
-
import { type TemplateProvenanceMap } from './templates.js';
|
|
4
|
-
import { type NativeStylingLibrary, type UiComponentInstall } from '../ui.js';
|
|
5
|
-
export declare const MANIFEST_SCHEMA_VERSION: 5;
|
|
6
|
-
export type ManifestToolchain = 'stable' | 'latest';
|
|
7
|
-
export type ManifestFileRecord = {
|
|
8
|
-
readonly owner: FileOwner;
|
|
9
|
-
readonly ownership: FileOwnershipClass;
|
|
10
|
-
readonly hash: string;
|
|
11
|
-
/** Normalized Unix permission bits (0o000-0o777), or null on platforms without portable modes. */
|
|
12
|
-
readonly mode: number | null;
|
|
13
|
-
};
|
|
14
|
-
export type ProjectManifest = {
|
|
15
|
-
readonly schemaVersion: typeof MANIFEST_SCHEMA_VERSION;
|
|
16
|
-
readonly generatorVersion: string;
|
|
17
|
-
readonly project: {
|
|
18
|
-
readonly name: string;
|
|
19
|
-
readonly displayName: string;
|
|
20
|
-
};
|
|
21
|
-
/** The complete, canonically ordered module dependency closure. */
|
|
22
|
-
readonly modules: readonly ModuleId[];
|
|
23
|
-
readonly toolchain: ManifestToolchain;
|
|
24
|
-
/** Immutable template catalog entries used to seed the generated workspace. */
|
|
25
|
-
readonly templates: TemplateProvenanceMap;
|
|
26
|
-
readonly ui: {
|
|
27
|
-
readonly nativeStyling: NativeStylingLibrary;
|
|
28
|
-
readonly components: readonly UiComponentInstall[];
|
|
29
|
-
};
|
|
30
|
-
readonly files: Readonly<Record<string, ManifestFileRecord>>;
|
|
31
|
-
};
|
|
32
|
-
export type CreateManifestInput = {
|
|
33
|
-
readonly generatorVersion: string;
|
|
34
|
-
readonly project: {
|
|
35
|
-
readonly name: string;
|
|
36
|
-
readonly displayName: string;
|
|
37
|
-
};
|
|
38
|
-
readonly plan: GenerationPlan;
|
|
39
|
-
readonly toolchain: ManifestToolchain;
|
|
40
|
-
readonly templates: TemplateProvenanceMap;
|
|
41
|
-
readonly nativeStyling?: NativeStylingLibrary;
|
|
42
|
-
readonly components?: readonly UiComponentInstall[];
|
|
43
|
-
readonly registry?: ModuleRegistry;
|
|
44
|
-
};
|
|
45
|
-
export type ManifestValidationErrorCode = 'INVALID_JSON' | 'INVALID_MANIFEST' | 'INVALID_SCHEMA_VERSION' | 'FUTURE_SCHEMA_VERSION' | 'UNKNOWN_MODULE' | 'DUPLICATE_MODULE' | 'INVALID_MODULE_CLOSURE' | 'INVALID_FILE_RECORD';
|
|
46
|
-
export declare class ManifestValidationError extends Error {
|
|
47
|
-
readonly code: ManifestValidationErrorCode;
|
|
48
|
-
readonly path: string;
|
|
49
|
-
constructor(code: ManifestValidationErrorCode, message: string, path: string);
|
|
50
|
-
}
|
|
51
|
-
export declare function readManifest(input: string | unknown, registry?: ModuleRegistry): ProjectManifest;
|
|
52
|
-
export declare function createManifest(input: CreateManifestInput): ProjectManifest;
|
|
53
|
-
export declare function serializeManifest(manifest: ProjectManifest): string;
|