anhedral 0.8.6 → 0.10.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 +137 -38
- package/assets/anhedral-cli-init-technical.svg +15 -15
- package/assets/anhedral-cli-init.svg +16 -16
- package/dist/access.d.ts +10 -0
- package/dist/access.js +93 -0
- package/dist/auth-schema-snapshot.d.ts +346 -0
- package/dist/auth-schema-snapshot.js +347 -0
- package/dist/auth-schema.d.ts +1 -0
- package/dist/auth-schema.js +57 -0
- package/dist/bin.js +349 -39
- package/dist/bounded-file.d.ts +2 -0
- package/dist/bounded-file.js +30 -0
- package/dist/capabilities.d.ts +24 -42
- package/dist/capabilities.js +73 -75
- package/dist/cloudflare.d.ts +14 -0
- package/dist/cloudflare.js +66 -0
- package/dist/command.js +12 -3
- package/dist/delivery.d.ts +118 -0
- package/dist/delivery.js +611 -0
- package/dist/dependencies.d.ts +24 -3
- package/dist/dependencies.js +112 -74
- package/dist/dependency-order.d.ts +2 -0
- package/dist/dependency-order.js +24 -0
- package/dist/index.d.ts +16 -3
- package/dist/index.js +11 -2
- package/dist/managed-hosting.d.ts +49 -0
- package/dist/managed-hosting.js +373 -0
- package/dist/management.d.ts +3 -0
- package/dist/management.js +47 -0
- package/dist/platforms/desktop.d.ts +2 -2
- package/dist/platforms/desktop.js +25 -15
- package/dist/platforms/extension.d.ts +2 -2
- package/dist/platforms/extension.js +104 -66
- package/dist/platforms/mobile.d.ts +2 -2
- package/dist/platforms/mobile.js +13 -7
- package/dist/pnpm-policy.d.ts +0 -2
- package/dist/pnpm-policy.js +18 -13
- package/dist/project-state.d.ts +28 -0
- package/dist/project-state.js +226 -0
- package/dist/provider-readiness.d.ts +30 -0
- package/dist/provider-readiness.js +87 -0
- package/dist/provision.d.ts +46 -0
- package/dist/provision.js +197 -0
- package/dist/readiness.d.ts +4 -2
- package/dist/readiness.js +64 -14
- package/dist/registry/add.d.ts +18 -0
- package/dist/registry/add.js +163 -0
- package/dist/registry/bootstrap.d.ts +2 -0
- package/dist/registry/bootstrap.js +14 -0
- package/dist/registry/commands.d.ts +2 -0
- package/dist/registry/commands.js +48 -0
- package/dist/registry/context.d.ts +3 -0
- package/dist/registry/context.js +21 -0
- package/dist/registry/options.d.ts +2 -0
- package/dist/registry/options.js +65 -0
- package/dist/registry/receipt.d.ts +2 -0
- package/dist/registry/receipt.js +52 -0
- package/dist/registry/resolve.d.ts +10 -0
- package/dist/registry/resolve.js +98 -0
- package/dist/registry/types.d.ts +64 -0
- package/dist/registry/usage.d.ts +1 -0
- package/dist/registry/usage.js +37 -0
- package/dist/render.d.ts +0 -4
- package/dist/render.js +3 -16
- package/dist/repository.d.ts +24 -0
- package/dist/repository.js +96 -0
- package/dist/scaffold/api.d.ts +2 -0
- package/dist/scaffold/api.js +13 -0
- package/dist/scaffold/auth.d.ts +3 -0
- package/dist/scaffold/auth.js +95 -0
- package/dist/scaffold/bootstrap.d.ts +3 -0
- package/dist/scaffold/bootstrap.js +39 -0
- package/dist/scaffold/compose.d.ts +4 -0
- package/dist/scaffold/compose.js +34 -0
- package/dist/scaffold/database.d.ts +2 -0
- package/dist/scaffold/database.js +43 -0
- package/dist/scaffold/documentation.d.ts +3 -0
- package/dist/scaffold/documentation.js +91 -0
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +5 -0
- package/dist/scaffold/files.d.ts +25 -0
- package/dist/scaffold/files.js +108 -0
- package/dist/scaffold/integrations.d.ts +3 -0
- package/dist/scaffold/integrations.js +7 -0
- package/dist/scaffold/services.d.ts +5 -0
- package/dist/scaffold/services.js +84 -0
- package/dist/scaffold/web.d.ts +2 -0
- package/dist/scaffold/web.js +102 -0
- package/dist/scaffold/workspace.d.ts +3 -0
- package/dist/scaffold/workspace.js +150 -0
- package/dist/security-backports.js +7 -2
- package/dist/stack-registry.d.ts +80 -0
- package/dist/stack-registry.js +424 -0
- package/dist/standard.d.ts +6 -19
- package/dist/standard.js +45 -658
- package/dist/transaction.js +47 -56
- package/dist/util.js +12 -2
- package/dist/vercel.d.ts +98 -0
- package/dist/vercel.js +796 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.d.ts +1 -0
- package/dist/vinext.js +83 -0
- package/dist/workspace-graph.d.ts +7 -0
- package/dist/workspace-graph.js +73 -0
- package/docs/application-stack-standard.md +76 -91
- package/docs/delivery-workflow.md +83 -0
- package/docs/managed-hosting-architecture.md +87 -0
- package/docs/sponsorships.md +25 -0
- package/package.json +23 -29
- package/dist/evidence-validation.d.ts +0 -2
- package/dist/evidence-validation.js +0 -18
- package/dist/foundation.d.ts +0 -6
- package/dist/foundation.js +0 -49
- package/dist/progress-view.d.ts +0 -57
- package/dist/progress-view.js +0 -69
- package/dist/progress.d.ts +0 -95
- package/dist/progress.js +0 -327
- package/dist/project.d.ts +0 -5
- package/dist/standard-products.d.ts +0 -2
- package/dist/standard-products.js +0 -6
- /package/assets/{images/svg/logo-white-subtract.svg → anhedral-mark.svg} +0 -0
- /package/dist/{project.js → registry/types.js} +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { STACK_DEPENDENCIES } from './dependencies.js';
|
|
2
|
+
import { dependencyOrder } from './dependency-order.js';
|
|
3
|
+
import { GENERATOR_VERSION } from './version.js';
|
|
4
|
+
import { scaffoldWeb } from './scaffold/web.js';
|
|
5
|
+
import { scaffoldApi } from './scaffold/api.js';
|
|
6
|
+
import { scaffoldDatabase } from './scaffold/database.js';
|
|
7
|
+
import { scaffoldBetterAuth, scaffoldClerk } from './scaffold/auth.js';
|
|
8
|
+
import { scaffoldRealtime, scaffoldQueues, scaffoldCron, scaffoldWorkflows, } from './scaffold/services.js';
|
|
9
|
+
import { scaffoldExpo } from './platforms/mobile.js';
|
|
10
|
+
import { scaffoldElectron } from './platforms/desktop.js';
|
|
11
|
+
import { scaffoldWxt } from './platforms/extension.js';
|
|
12
|
+
import { scaffoldPrivateFiles, scaffoldRevenueCat } from './scaffold/integrations.js';
|
|
13
|
+
import { sharedPackage } from './scaffold/files.js';
|
|
14
|
+
function requirements(purpose, tools, access, credentials, resources, verify) {
|
|
15
|
+
return { purpose, tools, access, credentials, resources, verify };
|
|
16
|
+
}
|
|
17
|
+
function packageSource(name, source, dependencies) {
|
|
18
|
+
return { name, source, dependencies };
|
|
19
|
+
}
|
|
20
|
+
function defineItems(configurations) {
|
|
21
|
+
return Object.fromEntries(Object.entries(configurations).map(([name, configuration]) => [
|
|
22
|
+
name,
|
|
23
|
+
{
|
|
24
|
+
name,
|
|
25
|
+
version: GENERATOR_VERSION,
|
|
26
|
+
selection: {},
|
|
27
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
28
|
+
environment: [],
|
|
29
|
+
installer: configuration.type === 'foundation' ? 'bootstrap' : 'recipe',
|
|
30
|
+
...configuration,
|
|
31
|
+
},
|
|
32
|
+
]));
|
|
33
|
+
}
|
|
34
|
+
/** Every product owns its source recipe, setup requirements and composition policy here. */
|
|
35
|
+
const PRODUCT_ITEMS = defineItems({
|
|
36
|
+
vinext: {
|
|
37
|
+
type: 'application',
|
|
38
|
+
description: 'Vinext web application on Cloudflare Workers',
|
|
39
|
+
targets: ['apps/web'],
|
|
40
|
+
selection: { conflicts: ['next'] },
|
|
41
|
+
registryDependencies: ['ui'],
|
|
42
|
+
setup: requirements('Web application on Cloudflare', ['Browser control', 'Wrangler'], [], [], ['Vinext React application and Cloudflare Worker'], ['Web flows, accessibility, production Worker build and deployed preview']),
|
|
43
|
+
install: scaffoldWeb,
|
|
44
|
+
},
|
|
45
|
+
next: {
|
|
46
|
+
type: 'application',
|
|
47
|
+
description: 'Next.js web application on Vercel or OpenNext on Workers',
|
|
48
|
+
targets: ['apps/web'],
|
|
49
|
+
selection: { conflicts: ['vinext'] },
|
|
50
|
+
registryDependencies: ['ui'],
|
|
51
|
+
setup: requirements('Web application', ['Browser control'], [], [], ['Next.js app; configure the selected hosting provider'], ['Web flows, accessibility, production hosting build and deployed preview']),
|
|
52
|
+
install: scaffoldWeb,
|
|
53
|
+
},
|
|
54
|
+
expo: {
|
|
55
|
+
type: 'application',
|
|
56
|
+
description: 'Expo mobile application with native UI',
|
|
57
|
+
targets: ['apps/mobile'],
|
|
58
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
59
|
+
setup: requirements('Mobile application', ['Expo/EAS CLI', 'Xcode or Android SDK'], ['Client-owned Apple/Google developer accounts for store delivery'], ['Platform signing credentials; EAS access when used'], ['Expo app; native adapters, signing, store configuration'], ['Native simulator/device flows and signed native build; web export alone is insufficient']),
|
|
60
|
+
install: scaffoldExpo,
|
|
61
|
+
},
|
|
62
|
+
electron: {
|
|
63
|
+
type: 'application',
|
|
64
|
+
description: 'Electron desktop application with its own web UI',
|
|
65
|
+
targets: ['apps/desktop'],
|
|
66
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
67
|
+
setup: requirements('Desktop application', ['Electron tooling', 'Platform packaging tools'], ['Signing/notarization account for the target platform'], ['Signing credentials; notarization credentials when required'], ['Electron app; packaging and distribution configuration'], ['Packaged desktop app, permissions, update/recovery behavior']),
|
|
68
|
+
install: scaffoldElectron,
|
|
69
|
+
},
|
|
70
|
+
wxt: {
|
|
71
|
+
type: 'application',
|
|
72
|
+
description: 'WXT browser side-panel extension',
|
|
73
|
+
targets: ['apps/extension'],
|
|
74
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
75
|
+
setup: requirements('Chrome side-panel extension', ['Chrome', 'WXT'], ['Extension store publisher access for distribution'], ['Store publishing credentials when required'], ['Extension app; minimal browser permissions and store package'], ['Installed side panel, background/content messaging and permission behavior']),
|
|
76
|
+
install: scaffoldWxt,
|
|
77
|
+
},
|
|
78
|
+
hono: {
|
|
79
|
+
type: 'application',
|
|
80
|
+
description: 'Shared Hono OpenAPI Worker',
|
|
81
|
+
targets: ['apps/api'],
|
|
82
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
83
|
+
setup: requirements('Shared OpenAPI backend', ['Wrangler'], ['Cloudflare Workers deployment access'], [], ['API Worker; application routes and authorization'], ['Contract validation, unauthenticated rejection and resource ownership']),
|
|
84
|
+
install: scaffoldApi,
|
|
85
|
+
},
|
|
86
|
+
neon: {
|
|
87
|
+
type: 'package',
|
|
88
|
+
description: 'Direct Neon HTTP and Drizzle PostgreSQL starter',
|
|
89
|
+
targets: ['packages/db'],
|
|
90
|
+
selection: { conflicts: ['d1'] },
|
|
91
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
92
|
+
setup: requirements('PostgreSQL persistence through direct Neon HTTP and Drizzle', ['Neon integration or API'], ['Client-owned Neon project/branch access'], ['Server-side DATABASE_URL; separate environment roles'], ['Neon project/branches, database roles and reviewed migrations'], ['Migration, runtime connectivity and authorization; HTTP has no interactive transactions']),
|
|
93
|
+
install: scaffoldDatabase,
|
|
94
|
+
environment: ['DATABASE_URL'],
|
|
95
|
+
runtime: 'server',
|
|
96
|
+
},
|
|
97
|
+
d1: {
|
|
98
|
+
type: 'package',
|
|
99
|
+
description: 'Drizzle D1 database and migration tooling',
|
|
100
|
+
targets: ['packages/db'],
|
|
101
|
+
selection: { conflicts: ['neon'] },
|
|
102
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
103
|
+
setup: requirements('SQLite-compatible hosted persistence', ['Wrangler'], ['Cloudflare D1 access'], [], ['D1 database, DB binding and reviewed migrations'], ['Migration, constraints, transactional behavior and runtime queries']),
|
|
104
|
+
install: scaffoldDatabase,
|
|
105
|
+
configure: ({ options }) => ({
|
|
106
|
+
d1_databases: [
|
|
107
|
+
{
|
|
108
|
+
binding: 'DB',
|
|
109
|
+
database_name: `${options.name}-db`,
|
|
110
|
+
database_id: 'REPLACE_WITH_D1_ID',
|
|
111
|
+
migrations_dir: '../../packages/db/migrations',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
}),
|
|
115
|
+
runtime: 'worker',
|
|
116
|
+
},
|
|
117
|
+
'local-data': {
|
|
118
|
+
type: 'package',
|
|
119
|
+
description: 'Interface for platform-specific local persistence adapters',
|
|
120
|
+
targets: ['packages/local-data'],
|
|
121
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
122
|
+
setup: requirements('Device-local SQLite and files', ['Selected platform database/filesystem tooling'], [], [], ['Integration boundary; implement platform adapters and explicit synchronization if needed'], ['Persistence, offline writes and recovery on the selected device']),
|
|
123
|
+
package: packageSource('local-data', '// Device-local SQLite and filesystem need platform-specific adapters.\nexport interface LocalDataStore { read(key: string): Promise<string | null>; write(key: string, value: string): Promise<void>; }\n', {}),
|
|
124
|
+
},
|
|
125
|
+
clerk: {
|
|
126
|
+
type: 'integration',
|
|
127
|
+
description: 'Managed identity starter and runtime integration',
|
|
128
|
+
targets: ['apps/web', 'packages/auth'],
|
|
129
|
+
selection: { conflicts: ['better-auth'], requiresAny: ['vinext', 'next', 'hono'] },
|
|
130
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
131
|
+
setup: requirements('Managed identity', ['Clerk integration or dashboard'], ['Client-owned Clerk project access'], ['Clerk publishable key; server secret key; webhook secret when used'], ['Identity project, allowed URLs, SDK setup and protected routes'], ['Sign-in/out, session expiry, resource authorization and selected native client flows']),
|
|
132
|
+
install: scaffoldClerk,
|
|
133
|
+
environment: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
|
|
134
|
+
runtime: 'server',
|
|
135
|
+
resolveDependencies: ({ has }) => ['vinext', 'next', 'hono'].filter((id) => has(id)),
|
|
136
|
+
resolveTargets: ({ has }) => [
|
|
137
|
+
...(has('vinext') || has('next') ? ['apps/web'] : []),
|
|
138
|
+
...(has('hono') ? ['packages/auth'] : []),
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
'better-auth': {
|
|
142
|
+
type: 'integration',
|
|
143
|
+
description: 'Database-backed identity schema and runtime routes',
|
|
144
|
+
targets: ['packages/auth', 'packages/db', 'apps/web', 'apps/api'],
|
|
145
|
+
selection: { conflicts: ['clerk'], requiresAny: ['neon', 'd1'] },
|
|
146
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
147
|
+
setup: requirements('Application-managed identity', ['Selected database tooling'], ['Selected database migration/runtime access'], ['BETTER_AUTH_SECRET; BETTER_AUTH_URL'], ['Selected Neon or D1 database, auth schema, trusted origins and application routes'], ['Sign-in/out, session expiry, sign-up policy and resource authorization']),
|
|
148
|
+
install: scaffoldBetterAuth,
|
|
149
|
+
fallbackDependency: 'd1',
|
|
150
|
+
environment: ['BETTER_AUTH_SECRET', 'BETTER_AUTH_URL'],
|
|
151
|
+
runtime: 'server',
|
|
152
|
+
resolveDependencies: ({ has }) => [has('neon') ? 'neon' : 'd1'],
|
|
153
|
+
resolveTargets: ({ runtimeApp }) => ['packages/auth', 'packages/db', `apps/${runtimeApp}`],
|
|
154
|
+
},
|
|
155
|
+
r2: {
|
|
156
|
+
type: 'integration',
|
|
157
|
+
description: 'Private file binding and authorization guidance',
|
|
158
|
+
targets: ['apps/web', 'apps/api'],
|
|
159
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
160
|
+
setup: requirements('Private remote files', ['Wrangler'], ['Cloudflare R2 bucket/binding access'], [], ['Private R2 bucket and FILES binding; authorized upload/download endpoints'], [
|
|
161
|
+
'Public bucket access disabled; file ownership enforced; clients receive no storage credentials',
|
|
162
|
+
]),
|
|
163
|
+
install: scaffoldPrivateFiles,
|
|
164
|
+
configure: ({ options }) => ({
|
|
165
|
+
r2_buckets: [{ binding: 'FILES', bucket_name: `${options.name}-private-files` }],
|
|
166
|
+
}),
|
|
167
|
+
runtime: 'worker',
|
|
168
|
+
resolveTargets: ({ runtimeApp }) => [`apps/${runtimeApp}/private-files.md`],
|
|
169
|
+
},
|
|
170
|
+
kv: {
|
|
171
|
+
type: 'integration',
|
|
172
|
+
description: 'Eventually consistent cache binding',
|
|
173
|
+
targets: ['apps/web', 'apps/api'],
|
|
174
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
175
|
+
setup: requirements('Eventually consistent cache/configuration', ['Wrangler'], ['Cloudflare KV namespace access'], [], ['KV namespace and CONFIG binding'], ['TTL and propagation behavior; never use KV as transactional authority']),
|
|
176
|
+
configure: () => ({ kv_namespaces: [{ binding: 'CONFIG', id: 'REPLACE_WITH_KV_ID' }] }),
|
|
177
|
+
installer: 'configuration',
|
|
178
|
+
runtime: 'worker',
|
|
179
|
+
resolveTargets: ({ runtimeApp }) => [`apps/${runtimeApp}/wrangler.jsonc`],
|
|
180
|
+
},
|
|
181
|
+
realtime: {
|
|
182
|
+
type: 'service',
|
|
183
|
+
description: 'Durable Object room Worker with authorization boundary',
|
|
184
|
+
targets: ['apps/realtime'],
|
|
185
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
186
|
+
setup: requirements('Rooms and collaboration', ['Wrangler', 'Browser control'], ['Cloudflare Durable Objects deployment access'], [], ['Realtime Worker, Durable Object namespace/migration and WebSocket authorization'], ['Authorized room isolation, disconnect/reconnect and deployed WebSockets']),
|
|
187
|
+
install: scaffoldRealtime,
|
|
188
|
+
},
|
|
189
|
+
queues: {
|
|
190
|
+
type: 'service',
|
|
191
|
+
description: 'Queue producer binding, consumer and dead-letter handling',
|
|
192
|
+
targets: ['apps/jobs', 'apps/web', 'apps/api'],
|
|
193
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
194
|
+
setup: requirements('Deferred jobs', ['Wrangler'], ['Cloudflare Queues configuration access'], [], ['Queue, producer/consumer bindings and failure handling'], ['Retries, idempotency, concurrency and dead-letter recovery']),
|
|
195
|
+
install: scaffoldQueues,
|
|
196
|
+
configure: ({ options }) => ({
|
|
197
|
+
queues: { producers: [{ binding: 'JOBS', queue: `${options.name}-jobs` }] },
|
|
198
|
+
}),
|
|
199
|
+
runtime: 'optional-worker',
|
|
200
|
+
resolveTargets: ({ runtimeApp, has }) => [
|
|
201
|
+
'apps/jobs',
|
|
202
|
+
...(has('hono') || has('vinext') || has('next') ? [`apps/${runtimeApp}/wrangler.jsonc`] : []),
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
cron: {
|
|
206
|
+
type: 'service',
|
|
207
|
+
description: 'Scheduled Worker',
|
|
208
|
+
targets: ['apps/scheduled'],
|
|
209
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
210
|
+
setup: requirements('Scheduled work', ['Wrangler'], ['Cloudflare Worker deployment access'], [], ['Scheduled Worker and environment-specific Cron Triggers'], ['Schedule, idempotency, failure visibility and overlapping runs']),
|
|
211
|
+
install: scaffoldCron,
|
|
212
|
+
},
|
|
213
|
+
workflows: {
|
|
214
|
+
type: 'service',
|
|
215
|
+
description: 'Durable multi-step Worker',
|
|
216
|
+
targets: ['apps/workflows'],
|
|
217
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
218
|
+
setup: requirements('Durable multi-step processing', ['Wrangler'], ['Cloudflare Workflows deployment access'], [], ['Workflow Worker/binding and resumable steps'], ['Retry/resume, idempotency, cancellation and failure recovery']),
|
|
219
|
+
install: scaffoldWorkflows,
|
|
220
|
+
},
|
|
221
|
+
openai: {
|
|
222
|
+
type: 'package',
|
|
223
|
+
description: 'Server-side OpenAI SDK factory',
|
|
224
|
+
targets: ['packages/ai'],
|
|
225
|
+
selection: { conflicts: ['ai-sdk'] },
|
|
226
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
227
|
+
setup: requirements('Direct OpenAI integration', ['OpenAI integration or API'], ['Client-owned OpenAI project and model access'], ['OPENAI_API_KEY'], ['Server SDK factory; implement application behavior and spending limits'], ['Selected model, failure handling, private inputs and cost controls']),
|
|
228
|
+
package: packageSource('ai', "import OpenAI from 'openai';\nexport function createAI(apiKey: string) { return new OpenAI({ apiKey }); }\n", { openai: STACK_DEPENDENCIES['openai'] }),
|
|
229
|
+
environment: ['OPENAI_API_KEY'],
|
|
230
|
+
},
|
|
231
|
+
'ai-sdk': {
|
|
232
|
+
type: 'package',
|
|
233
|
+
description: 'Server-side AI SDK and provider exports',
|
|
234
|
+
targets: ['packages/ai'],
|
|
235
|
+
selection: { conflicts: ['openai'] },
|
|
236
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
237
|
+
setup: requirements('Streaming AI and provider abstraction', ['Selected AI provider integration or API'], ['Selected provider project/model access'], ['OPENAI_API_KEY for the generated default; selected provider credentials otherwise'], ['Server SDK factory; implement streaming, tools and application behavior'], ['Streaming, tool authorization, interruption and spending limits']),
|
|
238
|
+
package: packageSource('ai', "export { generateText, streamText } from 'ai';\nexport { createOpenAI } from '@ai-sdk/openai';\n", { ai: STACK_DEPENDENCIES['ai'], '@ai-sdk/openai': STACK_DEPENDENCIES['@ai-sdk/openai'] }),
|
|
239
|
+
environment: ['OPENAI_API_KEY'],
|
|
240
|
+
},
|
|
241
|
+
'workers-ai': {
|
|
242
|
+
type: 'integration',
|
|
243
|
+
description: 'Workers AI runtime binding',
|
|
244
|
+
targets: ['apps/web', 'apps/api'],
|
|
245
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
246
|
+
setup: requirements('Cloudflare-hosted inference', ['Wrangler'], ['Cloudflare Workers AI model access'], [], ['AI binding and selected model invocation'], ['Supported model behavior, authorization and usage limits']),
|
|
247
|
+
configure: () => ({ ai: { binding: 'AI' } }),
|
|
248
|
+
installer: 'configuration',
|
|
249
|
+
runtime: 'worker',
|
|
250
|
+
resolveTargets: ({ runtimeApp }) => [`apps/${runtimeApp}/wrangler.jsonc`],
|
|
251
|
+
},
|
|
252
|
+
resend: {
|
|
253
|
+
type: 'package',
|
|
254
|
+
description: 'Server-side email SDK factory',
|
|
255
|
+
targets: ['packages/email'],
|
|
256
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
257
|
+
setup: requirements('Outbound application email', ['Resend integration or API'], ['Client-owned Resend project; sending-domain DNS access'], ['RESEND_API_KEY; webhook secret when used'], ['Verified sending domain, sender and server integration'], [
|
|
258
|
+
'Delivery, suppression/bounce handling and webhook verification; inbound routing is separate',
|
|
259
|
+
]),
|
|
260
|
+
package: packageSource('email', "import { Resend } from 'resend';\nexport function createEmail(apiKey: string) { return new Resend(apiKey); }\n", { resend: STACK_DEPENDENCIES['resend'] }),
|
|
261
|
+
environment: ['RESEND_API_KEY'],
|
|
262
|
+
},
|
|
263
|
+
stripe: {
|
|
264
|
+
type: 'package',
|
|
265
|
+
description: 'Server-side billing SDK factory',
|
|
266
|
+
targets: ['packages/billing'],
|
|
267
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
268
|
+
setup: requirements('Web payments and subscriptions', ['Stripe integration or CLI'], ['Client-owned Stripe account; test/live access as required'], ['STRIPE_SECRET_KEY; STRIPE_WEBHOOK_SECRET'], ['Products/prices, signed webhooks and entitlement implementation'], ['Test purchase/cancellation, webhook replay and transactional idempotency']),
|
|
269
|
+
package: packageSource('billing', "import Stripe from 'stripe';\nexport function createBilling(apiKey: string) { return new Stripe(apiKey); }\n", { stripe: STACK_DEPENDENCIES['stripe'] }),
|
|
270
|
+
environment: ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
|
271
|
+
},
|
|
272
|
+
revenuecat: {
|
|
273
|
+
type: 'integration',
|
|
274
|
+
description: 'Cross-store entitlement integration guidance',
|
|
275
|
+
targets: ['packages/billing/revenuecat.md'],
|
|
276
|
+
selection: { requiresAll: ['stripe'] },
|
|
277
|
+
registryDependencies: ['stripe'],
|
|
278
|
+
setup: requirements('Cross-store subscription entitlements alongside Stripe', ['RevenueCat integration or API', 'Selected mobile store tooling'], ['RevenueCat project; App Store/Play Store product access'], ['Platform public SDK keys; server API/webhook credentials as required'], ['Integration guidance only; install platform SDKs and configure offerings/store products'], ['Sandbox purchase/restore, verified webhooks and entitlement reconciliation']),
|
|
279
|
+
install: scaffoldRevenueCat,
|
|
280
|
+
},
|
|
281
|
+
sentry: {
|
|
282
|
+
type: 'package',
|
|
283
|
+
description: 'Platform-specific monitoring integration boundary',
|
|
284
|
+
targets: ['packages/observability'],
|
|
285
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
286
|
+
setup: requirements('Additional runtime/crash diagnostics', ['Sentry integration or API'], ['Client-owned Sentry project access'], ['SENTRY_DSN; source-map upload token when required'], ['Integration boundary; install/init runtime SDKs and configure private source-map uploads'], ['Captured runtime errors, source maps, privacy filtering and alert delivery']),
|
|
287
|
+
package: packageSource('observability', "// Initialize the platform-specific Sentry SDK in each application's runtime entrypoint.\nexport const redactFields = ['authorization', 'cookie', 'password', 'token'];\n", {}),
|
|
288
|
+
environment: ['SENTRY_DSN'],
|
|
289
|
+
},
|
|
290
|
+
basin: {
|
|
291
|
+
type: 'package',
|
|
292
|
+
description: 'Cloudflare analytical stream binding and event helper',
|
|
293
|
+
targets: ['packages/analytics', 'packages/basin'],
|
|
294
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
295
|
+
setup: requirements('Cloudflare analytical events', ['Wrangler', 'Cloudflare integration or API'], [
|
|
296
|
+
'Cloudflare analytics/Pipelines access; plan supporting selected services; Pipelines Setup grant for managed R2 sink setup',
|
|
297
|
+
], [], [
|
|
298
|
+
'Basin dataset/stream, ANALYTICS binding, optional private R2 sink and authorized sink writes',
|
|
299
|
+
], ['Private ingestion, event delivery, query results and archive retrieval when selected']),
|
|
300
|
+
package: packageSource('analytics', `export interface EventStream { send(events: Record<string, unknown>[]): Promise<void> }
|
|
301
|
+
export async function recordEvents(stream: EventStream, events: Record<string, unknown>[]) { await stream.send(events); }
|
|
302
|
+
`, {}),
|
|
303
|
+
resolvePackage: ({ has }, recipe) => ({
|
|
304
|
+
...recipe,
|
|
305
|
+
name: has('posthog') ? 'basin' : 'analytics',
|
|
306
|
+
}),
|
|
307
|
+
resolveDependencies: ({ has }) => (has('posthog') ? ['posthog'] : []),
|
|
308
|
+
resolveTargets: ({ has }) => [has('posthog') ? 'packages/basin' : 'packages/analytics'],
|
|
309
|
+
configure: () => ({
|
|
310
|
+
pipelines: [{ binding: 'ANALYTICS', stream: 'REPLACE_WITH_BASIN_STREAM_ID' }],
|
|
311
|
+
}),
|
|
312
|
+
runtime: 'worker',
|
|
313
|
+
},
|
|
314
|
+
posthog: {
|
|
315
|
+
type: 'package',
|
|
316
|
+
description: 'Server-side product analytics factory',
|
|
317
|
+
targets: ['packages/analytics'],
|
|
318
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
319
|
+
setup: requirements('Specialized product analytics', ['PostHog integration or API'], ['Client-owned PostHog project access'], ['POSTHOG_API_KEY; POSTHOG_HOST; separate privileged key if needed'], ['Analytics client factory; implement consent, event schema and privacy filtering'], ['Event receipt, consent behavior and redaction of private data']),
|
|
320
|
+
package: packageSource('analytics', "import { PostHog } from 'posthog-node';\nexport function createAnalytics(apiKey: string, host: string) { return new PostHog(apiKey, { host }); }\n", { 'posthog-node': STACK_DEPENDENCIES['posthog-node'] }),
|
|
321
|
+
environment: ['POSTHOG_API_KEY', 'POSTHOG_HOST'],
|
|
322
|
+
},
|
|
323
|
+
styling: {
|
|
324
|
+
type: 'package',
|
|
325
|
+
description: 'Shared platform-independent styling tokens',
|
|
326
|
+
targets: ['packages/styling'],
|
|
327
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
328
|
+
setup: requirements('Shared styling definitions', [], [], [], ['Styling package; implement actual shared colors, typography, spacing and themes'], ['Consistency across selected interfaces; preserve platform-specific components']),
|
|
329
|
+
package: packageSource('styling', "export const styling = { colors: { foreground: '#171717', background: '#ffffff' }, spacing: { small: 4, medium: 8, large: 16 } } as const;\n", {}),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
export const STANDARD_PRODUCTS = Object.freeze(Object.keys(PRODUCT_ITEMS));
|
|
333
|
+
const FOUNDATION_ITEMS = defineItems({
|
|
334
|
+
workspace: {
|
|
335
|
+
type: 'foundation',
|
|
336
|
+
description: 'pnpm and Turborepo workspace, task graph and shared checks',
|
|
337
|
+
targets: ['package.json', 'pnpm-workspace.yaml', 'turbo.json', 'scripts/security'],
|
|
338
|
+
registryDependencies: [],
|
|
339
|
+
},
|
|
340
|
+
typescript: {
|
|
341
|
+
type: 'foundation',
|
|
342
|
+
description: 'Shared TypeScript configuration',
|
|
343
|
+
targets: ['packages/typescript-config'],
|
|
344
|
+
registryDependencies: ['workspace'],
|
|
345
|
+
},
|
|
346
|
+
eslint: {
|
|
347
|
+
type: 'foundation',
|
|
348
|
+
description: 'Shared ESLint configuration',
|
|
349
|
+
targets: ['packages/eslint-config'],
|
|
350
|
+
registryDependencies: ['workspace'],
|
|
351
|
+
},
|
|
352
|
+
ui: {
|
|
353
|
+
type: 'foundation',
|
|
354
|
+
description: 'Shared shadcn/ui, React, Tailwind and Lucide foundation for web apps',
|
|
355
|
+
targets: ['packages/ui'],
|
|
356
|
+
registryDependencies: ['workspace', 'typescript', 'eslint'],
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
function freeze(value) {
|
|
360
|
+
if (value && typeof value === 'object') {
|
|
361
|
+
for (const child of Object.values(value))
|
|
362
|
+
freeze(child);
|
|
363
|
+
Object.freeze(value);
|
|
364
|
+
}
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
const DEFINITIONS = freeze({ ...FOUNDATION_ITEMS, ...PRODUCT_ITEMS });
|
|
368
|
+
function describe(definition) {
|
|
369
|
+
const { install: _install, configure: _configure, resolveTargets: _targets, resolveDependencies: _dependencies, resolvePackage: _package, fallbackDependency: _fallback, ...metadata } = definition;
|
|
370
|
+
return structuredClone(metadata);
|
|
371
|
+
}
|
|
372
|
+
export function getStackDefinition(name) {
|
|
373
|
+
if (!Object.hasOwn(DEFINITIONS, name))
|
|
374
|
+
throw new Error(`Unknown stack registry item: ${name}.`);
|
|
375
|
+
return DEFINITIONS[name];
|
|
376
|
+
}
|
|
377
|
+
export function getStackItem(name) {
|
|
378
|
+
return describe(getStackDefinition(name));
|
|
379
|
+
}
|
|
380
|
+
export const STACK_ITEMS = freeze(Object.fromEntries(Object.keys(DEFINITIONS).map((name) => [name, getStackItem(name)])));
|
|
381
|
+
export const STACK_REGISTRY = freeze({
|
|
382
|
+
schemaVersion: 1,
|
|
383
|
+
name: 'anhedral',
|
|
384
|
+
cliVersion: GENERATOR_VERSION,
|
|
385
|
+
items: Object.values(STACK_ITEMS),
|
|
386
|
+
});
|
|
387
|
+
export function installStackItem(item, context) {
|
|
388
|
+
const definition = getStackDefinition(item.name);
|
|
389
|
+
if (item.package) {
|
|
390
|
+
const recipe = item.package;
|
|
391
|
+
sharedPackage(context.root, recipe.name, recipe.source, recipe.dependencies);
|
|
392
|
+
}
|
|
393
|
+
return definition.install?.(context);
|
|
394
|
+
}
|
|
395
|
+
/** Validate authored definitions during build, before shipping either catalog. */
|
|
396
|
+
export function validateStackCatalog() {
|
|
397
|
+
dependencyOrder(new Map(Object.entries(DEFINITIONS).map(([name, item]) => [name, item.registryDependencies])), 'Authored stack registry');
|
|
398
|
+
for (const [name, definition] of Object.entries(DEFINITIONS)) {
|
|
399
|
+
if (definition.name !== name)
|
|
400
|
+
throw new Error(`Registry identity mismatch: ${name}.`);
|
|
401
|
+
for (const dependency of [
|
|
402
|
+
...definition.registryDependencies,
|
|
403
|
+
...(definition.selection.conflicts ?? []),
|
|
404
|
+
...(definition.selection.requiresAny ?? []),
|
|
405
|
+
...(definition.selection.requiresAll ?? []),
|
|
406
|
+
])
|
|
407
|
+
getStackDefinition(dependency);
|
|
408
|
+
if (definition.type !== 'foundation' &&
|
|
409
|
+
(!definition.setup ||
|
|
410
|
+
(definition.installer === 'recipe' && !definition.install && !definition.package)))
|
|
411
|
+
throw new Error(`Incomplete registry recipe: ${name}.`);
|
|
412
|
+
if (definition.installer === 'configuration' && !definition.configure)
|
|
413
|
+
throw new Error(`Missing registry configuration: ${name}.`);
|
|
414
|
+
for (const target of definition.targets) {
|
|
415
|
+
if (!target ||
|
|
416
|
+
target.startsWith('/') ||
|
|
417
|
+
target.includes('\\') ||
|
|
418
|
+
target.split('/').some((part) => part === '..'))
|
|
419
|
+
throw new Error(`Invalid registry target: ${name}.`);
|
|
420
|
+
}
|
|
421
|
+
if (definition.environment.some((key) => !/^[A-Z][A-Z0-9_]*$/.test(key)))
|
|
422
|
+
throw new Error(`Invalid registry environment key: ${name}.`);
|
|
423
|
+
}
|
|
424
|
+
}
|
package/dist/standard.d.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
products: StandardProduct[];
|
|
8
|
-
hosting: 'cloudflare' | 'vercel';
|
|
9
|
-
layout?: 'workspace' | 'single';
|
|
10
|
-
extensionSurface?: 'sidepanel' | 'popup';
|
|
11
|
-
skipInstall: boolean;
|
|
12
|
-
dryRun: boolean;
|
|
13
|
-
json: boolean;
|
|
14
|
-
initializeGit: boolean;
|
|
15
|
-
}
|
|
16
|
-
export declare const STANDARD_USAGE: string;
|
|
17
|
-
export declare function parseStandardOptions(command: 'new' | 'init', args: readonly string[]): StandardOptions;
|
|
18
|
-
export declare function applyStandardOverlays(root: string, options: StandardOptions): Promise<void>;
|
|
19
|
-
export type StandardRunner = (command: string, args: string[], cwd: string) => void;
|
|
1
|
+
import { parseStandardOptions } from './registry/options.js';
|
|
2
|
+
import { STANDARD_USAGE } from './registry/usage.js';
|
|
3
|
+
import type { StandardOptions, StandardRunner } from './registry/types.js';
|
|
4
|
+
export { parseStandardOptions, STANDARD_USAGE };
|
|
5
|
+
export { STANDARD_PRODUCTS } from './stack-registry.js';
|
|
6
|
+
export type { StandardOptions, StandardProduct, StandardRunner } from './registry/types.js';
|
|
20
7
|
export declare function scaffoldStandardProject(options: StandardOptions, run?: StandardRunner): Promise<void>;
|