anhedral 0.1.10 → 0.3.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/CHANGELOG.md +97 -0
- package/CONTRIBUTING.md +70 -0
- package/LICENSE +21 -17
- package/README.md +151 -152
- package/SECURITY.md +35 -0
- package/SKILL.md +177 -0
- package/bin/anhedral.js +1 -1
- package/dist/architecture/contributions.d.ts +28 -0
- package/dist/architecture/contributions.js +118 -0
- package/dist/architecture/index.d.ts +12 -0
- package/dist/architecture/index.js +6 -0
- package/dist/architecture/manifest.d.ts +53 -0
- package/dist/architecture/manifest.js +283 -0
- package/dist/architecture/modules.d.ts +37 -0
- package/dist/architecture/modules.js +176 -0
- package/dist/architecture/plan.d.ts +58 -0
- package/dist/architecture/plan.js +145 -0
- package/dist/architecture/templates.d.ts +10 -0
- package/dist/architecture/templates.js +23 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +167 -0
- package/dist/cli.d.ts +24 -2
- package/dist/cli.js +186 -21
- package/dist/composers/root.d.ts +6 -0
- package/dist/composers/root.js +34 -0
- package/dist/dependencies.d.ts +29 -9
- package/dist/dependencies.js +220 -60
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -31
- package/dist/print.js +15 -0
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +19 -0
- package/dist/render.d.ts +11 -0
- package/dist/render.js +74 -0
- package/dist/scaffold.d.ts +56 -12
- package/dist/scaffold.js +1466 -1158
- package/dist/template-source.d.ts +20 -0
- package/dist/template-source.js +180 -0
- package/dist/templates/api.d.ts +2 -0
- package/dist/templates/api.js +3114 -0
- package/dist/templates/assets-private-proxy.d.ts +3 -0
- package/dist/templates/assets-private-proxy.js +277 -0
- package/dist/templates/desktop.d.ts +2 -0
- package/dist/templates/desktop.js +572 -0
- package/dist/templates/extension.d.ts +1 -1
- package/dist/templates/extension.js +322 -248
- package/dist/templates/mobile.d.ts +2 -0
- package/dist/templates/mobile.js +720 -0
- package/dist/templates/shared.d.ts +2 -0
- package/dist/templates/shared.js +503 -0
- package/dist/templates/web.d.ts +2 -0
- package/dist/templates/web.js +412 -0
- package/dist/toolchain.d.ts +0 -8
- package/dist/toolchain.js +0 -21
- package/dist/transaction.d.ts +22 -0
- package/dist/transaction.js +822 -0
- package/dist/ui.d.ts +33 -0
- package/dist/ui.js +199 -0
- package/dist/util.d.ts +1 -5
- package/dist/util.js +70 -159
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/workspace-config.d.ts +11 -0
- package/dist/workspace-config.js +372 -0
- package/docs/conventions.md +89 -0
- package/docs/output-tree-contract.md +442 -0
- package/package.json +56 -13
- package/references/manual-scaffolding.md +386 -0
- package/templates/api-fastify/apps/api/tsconfig.json +20 -0
- package/templates/catalog.json +23 -0
- package/templates/db-drizzle/packages/db/tsconfig.json +14 -0
- package/templates/desktop-electron/apps/desktop/tsconfig.json +37 -0
- package/templates/extension-wxt/apps/extension/tsconfig.json +41 -0
- package/templates/mobile-expo/apps/mobile/expo-env.d.ts +1 -0
- package/templates/web-next/apps/web/next-env.d.ts +6 -0
- package/dist/branding.d.ts +0 -5
- package/dist/branding.js +0 -17
- package/dist/commands.d.ts +0 -7
- package/dist/commands.js +0 -24
- package/dist/templates/backend.d.ts +0 -2
- package/dist/templates/backend.js +0 -2115
- package/dist/templates/frontend.d.ts +0 -2
- package/dist/templates/frontend.js +0 -1268
package/SKILL.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anhedral-init
|
|
3
|
+
description: Create, extend, explain, or diagnose complete Anhedral-generated TypeScript stacks. Use for new/init/add/ui/doctor; ordinary Next.js, Expo, Fastify, Drizzle/Neon, Clerk, R2, Electron, or WXT development inside a generated workspace; interpreting anhedral.json ownership; reproducing the scaffold manually; or verifying generated applications.
|
|
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
|
+
Use Node.js 20.19+ or 22.12+ and pnpm. `new` creates a destination directory; `init` works in an empty current directory where `.git`, `.gitignore`, and `.DS_Store` are allowed.
|
|
11
|
+
|
|
12
|
+
## Choose the construction path
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- Before creating or changing files manually, read [references/manual-scaffolding.md](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.
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
## Select modules
|
|
22
|
+
|
|
23
|
+
Choose app surfaces from `web`, `mobile`, `api`, `desktop`, and `extension`.
|
|
24
|
+
|
|
25
|
+
Choose backend features from `db`, `auth`, `billing`, `storage`, and `native-subscriptions`.
|
|
26
|
+
|
|
27
|
+
Apply these dependency rules automatically:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
auth -> api + db
|
|
31
|
+
billing -> auth
|
|
32
|
+
storage -> auth
|
|
33
|
+
native-subscriptions -> mobile + billing
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
With no module flags, generate the full stack.
|
|
37
|
+
|
|
38
|
+
## Initialize
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm dlx anhedral@latest new my-product
|
|
42
|
+
cd my-product
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Prefer explicit modules when the user requests a smaller stack:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm dlx anhedral@latest new my-product --web --api --db --auth
|
|
49
|
+
pnpm dlx anhedral@latest new my-product --web --mobile --ui button,dialog --native-styling nativewind
|
|
50
|
+
pnpm dlx anhedral@latest new my-api --api --skip-install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use the stable toolchain for normal generation. The `latest` value is retained only as a metadata compatibility channel for maintainer investigations:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pnpm dlx anhedral@latest new my-product --toolchain stable
|
|
57
|
+
pnpm dlx anhedral@latest new my-product --toolchain latest
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
Do not run any command in this section during the manual path.
|
|
63
|
+
|
|
64
|
+
## Develop inside a generated project
|
|
65
|
+
|
|
66
|
+
Read these files before changing product code:
|
|
67
|
+
|
|
68
|
+
1. `README.md` for the selected modules, first run, and source-location map.
|
|
69
|
+
2. `docs/DEVELOPMENT.md` for end-to-end feature recipes.
|
|
70
|
+
3. `docs/STACK.md` for responsibility boundaries and official tool documentation.
|
|
71
|
+
4. `PRODUCTION.md` only for provisioning, deployment, DNS, stores, or release work.
|
|
72
|
+
|
|
73
|
+
Use the native source conventions:
|
|
74
|
+
|
|
75
|
+
- Next.js code belongs in `apps/web/app`, `apps/web/components`, and `apps/web/lib`.
|
|
76
|
+
- Expo Router code belongs in `apps/mobile/app` and `apps/mobile/components`.
|
|
77
|
+
- Fastify routes and server-only services belong in `apps/api/src/routes` and `apps/api/src/services`.
|
|
78
|
+
- Shared Zod network schemas belong in `packages/contracts/src`.
|
|
79
|
+
- Client-safe HTTP methods belong in `packages/api-client/src`.
|
|
80
|
+
- Drizzle schema and queries belong in `packages/db`; generate and review SQL migrations.
|
|
81
|
+
|
|
82
|
+
Implement an end-to-end feature as `contracts -> database/service -> route -> API client -> frontend`. 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.
|
|
83
|
+
|
|
84
|
+
## Add modules safely
|
|
85
|
+
|
|
86
|
+
Run `add` only from a project containing `anhedral.json` schema v5:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm dlx anhedral@latest add desktop extension
|
|
90
|
+
pnpm dlx anhedral@latest add storage --dry-run
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
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. Do not bypass ownership conflicts: Anhedral intentionally refuses modified managed files, user-owned files, symlinks, and unowned collisions.
|
|
94
|
+
|
|
95
|
+
Require the manifest generator version to match the running CLI. Regenerate projects created by another version; do not add compatibility branches.
|
|
96
|
+
|
|
97
|
+
Do not rewrite a generated project's README or custom workflows while adding modules. Preserve custom package fields and mergeable root configuration.
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
## Add UI components
|
|
102
|
+
|
|
103
|
+
Use the unified UI wrapper for source-owned components:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
pnpm dlx anhedral@latest ui add button dialog
|
|
107
|
+
pnpm dlx anhedral@latest ui add select --target mobile
|
|
108
|
+
pnpm dlx anhedral@latest ui add data-table --target web --dry-run
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
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.
|
|
112
|
+
|
|
113
|
+
## Diagnose
|
|
114
|
+
|
|
115
|
+
Dry-runs never perform transaction recovery. If a recovery journal is pending, rerun the original command without `--dry-run` before requesting a new preview.
|
|
116
|
+
|
|
117
|
+
Check recorded files and hashes with:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
pnpm dlx anhedral@latest doctor
|
|
121
|
+
pnpm dlx anhedral@latest doctor --json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Explain that warnings can represent mergeable or user-owned drift, while modified or missing managed files make the project unhealthy.
|
|
125
|
+
|
|
126
|
+
## Configure the generated project
|
|
127
|
+
|
|
128
|
+
Copy environment examples, fill only selected provider values, install, and verify:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
cp apps/api/.env.example apps/api/.env
|
|
132
|
+
cp packages/db/.env.example packages/db/.env
|
|
133
|
+
pnpm install
|
|
134
|
+
pnpm verify
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Copy only examples that exist for the selected surfaces. Client packages use package-local runtime files (for example, copy `apps/web/.env.example` to `apps/web/.env.local` and `apps/desktop/.env.example` to `apps/desktop/.env`); the root `.env.example` is an inventory, not a runtime environment file.
|
|
138
|
+
|
|
139
|
+
Common selected keys are:
|
|
140
|
+
|
|
141
|
+
- Neon: `DATABASE_URL`
|
|
142
|
+
- Clerk server: `CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`
|
|
143
|
+
- Clerk clients: `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`, `VITE_CLERK_PUBLISHABLE_KEY`
|
|
144
|
+
- RevenueCat: `RC_WEBHOOK_SECRET`, `RC_SECRET_API_KEY`, `RC_ENTITLEMENT_ID`, `EXPO_PUBLIC_RC_*`
|
|
145
|
+
- Ably billing synchronization: server-only `ABLY_API_KEY`; clients obtain scoped tokens from the API
|
|
146
|
+
- 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
|
|
147
|
+
- Internal billing/storage jobs: high-entropy server-only `CRON_SECRET`
|
|
148
|
+
|
|
149
|
+
Never commit real `.env` files. Keep `ANHEDRAL_DEMO=false` in production.
|
|
150
|
+
|
|
151
|
+
Run only scripts present for installed modules. Typical commands include `dev:web`, `dev:mobile`, `dev:api`, `dev:desktop`, `dev:extension`, corresponding `verify:*` scripts, and database scripts for `db` projects.
|
|
152
|
+
|
|
153
|
+
## Understand deployment
|
|
154
|
+
|
|
155
|
+
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.
|
|
156
|
+
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
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.
|
|
160
|
+
|
|
161
|
+
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`.
|
|
162
|
+
|
|
163
|
+
When explaining domains, distinguish DNS delegation from registrar transfer. A GoDaddy registration can use Cloudflare authoritative nameservers immediately; 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.
|
|
164
|
+
|
|
165
|
+
## Verify outcomes
|
|
166
|
+
|
|
167
|
+
After generation or repair, run the strongest practical checks:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
pnpm typecheck
|
|
171
|
+
pnpm verify
|
|
172
|
+
pnpm build
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
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.
|
|
176
|
+
|
|
177
|
+
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 [references/manual-scaffolding.md](references/manual-scaffolding.md); every selected conditional file must exist and every unselected provider integration must be absent.
|
package/bin/anhedral.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import '../dist/
|
|
2
|
+
import '../dist/bin.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
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;
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
});
|
|
75
|
+
function collectEnvironmentFromRegistry(modules, registry) {
|
|
76
|
+
const byName = new Map();
|
|
77
|
+
for (const module of modules) {
|
|
78
|
+
for (const contribution of registry[module].environment) {
|
|
79
|
+
const candidate = Object.freeze({ owner: module, ...contribution });
|
|
80
|
+
const previous = byName.get(candidate.name);
|
|
81
|
+
if (previous && previous.defaultValue !== candidate.defaultValue) {
|
|
82
|
+
throw new CompositionError('DUPLICATE_ENVIRONMENT_CONTRIBUTION', `Environment variable ${candidate.name} has incompatible defaults from ${previous.owner} and ${module}.`);
|
|
83
|
+
}
|
|
84
|
+
if (!previous)
|
|
85
|
+
byName.set(candidate.name, candidate);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze([...byName.values()]);
|
|
89
|
+
}
|
|
90
|
+
function collectCronsFromRegistry(modules, registry) {
|
|
91
|
+
const byId = new Map();
|
|
92
|
+
for (const module of modules) {
|
|
93
|
+
for (const contribution of registry[module].crons) {
|
|
94
|
+
const candidate = Object.freeze({ owner: module, ...contribution });
|
|
95
|
+
const previous = byId.get(candidate.id);
|
|
96
|
+
if (previous && (previous.path !== candidate.path || previous.schedule !== candidate.schedule)) {
|
|
97
|
+
throw new CompositionError('DUPLICATE_CRON_CONTRIBUTION', `Cron ${candidate.id} has incompatible definitions from ${previous.owner} and ${module}.`);
|
|
98
|
+
}
|
|
99
|
+
if (!previous)
|
|
100
|
+
byId.set(candidate.id, candidate);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return Object.freeze([...byId.values()]);
|
|
104
|
+
}
|
|
105
|
+
export function collectModuleContributions(requestedModules, overrides = {}) {
|
|
106
|
+
const modules = resolveModules(requestedModules).resolvedModules;
|
|
107
|
+
const contributions = Object.freeze({ ...MODULE_CONTRIBUTIONS, ...overrides });
|
|
108
|
+
for (const module of modules) {
|
|
109
|
+
if (contributions[module].module !== module) {
|
|
110
|
+
throw new Error(`Composition contribution key ${module} must declare the same module ID.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
modules,
|
|
115
|
+
environment: collectEnvironmentFromRegistry(modules, contributions),
|
|
116
|
+
crons: collectCronsFromRegistry(modules, contributions),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { DEFAULT_MODULE_DEFINITIONS, DEFAULT_MODULE_REGISTRY, MODULE_IDS, ModuleRegistryError, ModuleResolutionError, createModuleRegistry, isModuleId, resolveModules, } from './modules.js';
|
|
2
|
+
export type { DependencyEdge, 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';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DEFAULT_MODULE_DEFINITIONS, DEFAULT_MODULE_REGISTRY, 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';
|
|
@@ -0,0 +1,53 @@
|
|
|
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;
|