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.
Files changed (123) hide show
  1. package/README.md +137 -38
  2. package/assets/anhedral-cli-init-technical.svg +15 -15
  3. package/assets/anhedral-cli-init.svg +16 -16
  4. package/dist/access.d.ts +10 -0
  5. package/dist/access.js +93 -0
  6. package/dist/auth-schema-snapshot.d.ts +346 -0
  7. package/dist/auth-schema-snapshot.js +347 -0
  8. package/dist/auth-schema.d.ts +1 -0
  9. package/dist/auth-schema.js +57 -0
  10. package/dist/bin.js +349 -39
  11. package/dist/bounded-file.d.ts +2 -0
  12. package/dist/bounded-file.js +30 -0
  13. package/dist/capabilities.d.ts +24 -42
  14. package/dist/capabilities.js +73 -75
  15. package/dist/cloudflare.d.ts +14 -0
  16. package/dist/cloudflare.js +66 -0
  17. package/dist/command.js +12 -3
  18. package/dist/delivery.d.ts +118 -0
  19. package/dist/delivery.js +611 -0
  20. package/dist/dependencies.d.ts +24 -3
  21. package/dist/dependencies.js +112 -74
  22. package/dist/dependency-order.d.ts +2 -0
  23. package/dist/dependency-order.js +24 -0
  24. package/dist/index.d.ts +16 -3
  25. package/dist/index.js +11 -2
  26. package/dist/managed-hosting.d.ts +49 -0
  27. package/dist/managed-hosting.js +373 -0
  28. package/dist/management.d.ts +3 -0
  29. package/dist/management.js +47 -0
  30. package/dist/platforms/desktop.d.ts +2 -2
  31. package/dist/platforms/desktop.js +25 -15
  32. package/dist/platforms/extension.d.ts +2 -2
  33. package/dist/platforms/extension.js +104 -66
  34. package/dist/platforms/mobile.d.ts +2 -2
  35. package/dist/platforms/mobile.js +13 -7
  36. package/dist/pnpm-policy.d.ts +0 -2
  37. package/dist/pnpm-policy.js +18 -13
  38. package/dist/project-state.d.ts +28 -0
  39. package/dist/project-state.js +226 -0
  40. package/dist/provider-readiness.d.ts +30 -0
  41. package/dist/provider-readiness.js +87 -0
  42. package/dist/provision.d.ts +46 -0
  43. package/dist/provision.js +197 -0
  44. package/dist/readiness.d.ts +4 -2
  45. package/dist/readiness.js +64 -14
  46. package/dist/registry/add.d.ts +18 -0
  47. package/dist/registry/add.js +163 -0
  48. package/dist/registry/bootstrap.d.ts +2 -0
  49. package/dist/registry/bootstrap.js +14 -0
  50. package/dist/registry/commands.d.ts +2 -0
  51. package/dist/registry/commands.js +48 -0
  52. package/dist/registry/context.d.ts +3 -0
  53. package/dist/registry/context.js +21 -0
  54. package/dist/registry/options.d.ts +2 -0
  55. package/dist/registry/options.js +65 -0
  56. package/dist/registry/receipt.d.ts +2 -0
  57. package/dist/registry/receipt.js +52 -0
  58. package/dist/registry/resolve.d.ts +10 -0
  59. package/dist/registry/resolve.js +98 -0
  60. package/dist/registry/types.d.ts +64 -0
  61. package/dist/registry/usage.d.ts +1 -0
  62. package/dist/registry/usage.js +37 -0
  63. package/dist/render.d.ts +0 -4
  64. package/dist/render.js +3 -16
  65. package/dist/repository.d.ts +24 -0
  66. package/dist/repository.js +96 -0
  67. package/dist/scaffold/api.d.ts +2 -0
  68. package/dist/scaffold/api.js +13 -0
  69. package/dist/scaffold/auth.d.ts +3 -0
  70. package/dist/scaffold/auth.js +95 -0
  71. package/dist/scaffold/bootstrap.d.ts +3 -0
  72. package/dist/scaffold/bootstrap.js +39 -0
  73. package/dist/scaffold/compose.d.ts +4 -0
  74. package/dist/scaffold/compose.js +34 -0
  75. package/dist/scaffold/database.d.ts +2 -0
  76. package/dist/scaffold/database.js +43 -0
  77. package/dist/scaffold/documentation.d.ts +3 -0
  78. package/dist/scaffold/documentation.js +91 -0
  79. package/dist/scaffold/environment.d.ts +2 -0
  80. package/dist/scaffold/environment.js +5 -0
  81. package/dist/scaffold/files.d.ts +25 -0
  82. package/dist/scaffold/files.js +108 -0
  83. package/dist/scaffold/integrations.d.ts +3 -0
  84. package/dist/scaffold/integrations.js +7 -0
  85. package/dist/scaffold/services.d.ts +5 -0
  86. package/dist/scaffold/services.js +84 -0
  87. package/dist/scaffold/web.d.ts +2 -0
  88. package/dist/scaffold/web.js +102 -0
  89. package/dist/scaffold/workspace.d.ts +3 -0
  90. package/dist/scaffold/workspace.js +150 -0
  91. package/dist/security-backports.js +7 -2
  92. package/dist/stack-registry.d.ts +80 -0
  93. package/dist/stack-registry.js +424 -0
  94. package/dist/standard.d.ts +6 -19
  95. package/dist/standard.js +45 -658
  96. package/dist/transaction.js +47 -56
  97. package/dist/util.js +12 -2
  98. package/dist/vercel.d.ts +98 -0
  99. package/dist/vercel.js +796 -0
  100. package/dist/version.d.ts +1 -1
  101. package/dist/version.js +1 -1
  102. package/dist/vinext.d.ts +1 -0
  103. package/dist/vinext.js +83 -0
  104. package/dist/workspace-graph.d.ts +7 -0
  105. package/dist/workspace-graph.js +73 -0
  106. package/docs/application-stack-standard.md +76 -91
  107. package/docs/delivery-workflow.md +83 -0
  108. package/docs/managed-hosting-architecture.md +87 -0
  109. package/docs/sponsorships.md +25 -0
  110. package/package.json +23 -29
  111. package/dist/evidence-validation.d.ts +0 -2
  112. package/dist/evidence-validation.js +0 -18
  113. package/dist/foundation.d.ts +0 -6
  114. package/dist/foundation.js +0 -49
  115. package/dist/progress-view.d.ts +0 -57
  116. package/dist/progress-view.js +0 -69
  117. package/dist/progress.d.ts +0 -95
  118. package/dist/progress.js +0 -327
  119. package/dist/project.d.ts +0 -5
  120. package/dist/standard-products.d.ts +0 -2
  121. package/dist/standard-products.js +0 -6
  122. /package/assets/{images/svg/logo-white-subtract.svg → anhedral-mark.svg} +0 -0
  123. /package/dist/{project.js → registry/types.js} +0 -0
@@ -0,0 +1,7 @@
1
+ import { put } from './files.js';
2
+ export function scaffoldPrivateFiles({ root, runtimeApp }) {
3
+ put(root, `apps/${runtimeApp}/private-files.md`, '# Private files\n\nFILES is a private R2 Worker binding. Authenticate the request and enforce file-level product authorization before calling get/put/delete. No public buckets, R2 keys, generic download endpoints, or client credentials are generated. Use the binding directly in Hono or import env from cloudflare:workers in Vinext; explicit OpenNext uses getCloudflareContext.\n');
4
+ }
5
+ export function scaffoldRevenueCat({ root }) {
6
+ put(root, 'packages/billing/revenuecat.md', '# RevenueCat + Stripe\n\nConfigure web/App Store/Play Store products and offerings in client-owned accounts. Add the platform SDK to each selected app. Verify signed provider webhooks, reconcile entitlement authority, and keep transactional idempotent credits/quotas separate from subscription status. No payment activation occurs during init.\n');
7
+ }
@@ -0,0 +1,5 @@
1
+ import type { RecipeContext } from '../registry/types.js';
2
+ export declare function scaffoldRealtime({ root, options }: RecipeContext): void;
3
+ export declare function scaffoldQueues({ root, options }: RecipeContext): void;
4
+ export declare function scaffoldCron({ root, options }: RecipeContext): void;
5
+ export declare function scaffoldWorkflows({ root, options }: RecipeContext): void;
@@ -0,0 +1,84 @@
1
+ import { workerConfig, worker } from './files.js';
2
+ export function scaffoldRealtime({ root, options }) {
3
+ worker(root, 'realtime', {
4
+ ...workerConfig(`${options.name}-realtime`),
5
+ durable_objects: { bindings: [{ name: 'ROOMS', class_name: 'Room' }] },
6
+ migrations: [{ tag: 'v1', new_sqlite_classes: ['Room'] }],
7
+ }, `import { DurableObject } from 'cloudflare:workers';
8
+ export class Room extends DurableObject<Env> {
9
+ async fetch(request: Request): Promise<Response> {
10
+ if (request.headers.get('Upgrade') !== 'websocket') return new Response('WebSocket required', { status: 426 });
11
+ const pair = new WebSocketPair();
12
+ this.ctx.acceptWebSocket(pair[1]);
13
+ return new Response(null, { status: 101, webSocket: pair[0] });
14
+ }
15
+ webSocketMessage(sender: WebSocket, message: string | ArrayBuffer) {
16
+ if ((typeof message === 'string' ? new TextEncoder().encode(message).byteLength : message.byteLength) > 16384) { sender.close(1009, 'Message too large'); return; }
17
+ this.ctx.getWebSockets().filter((socket) => socket !== sender).forEach((socket) => socket.send(message));
18
+ }
19
+ }
20
+ export default {
21
+ async fetch(): Promise<Response> {
22
+ // Authenticate and authorize room membership, then forward to env.ROOMS.getByName(room).fetch(request).
23
+ // Use short-lived single-use tickets for browser WebSockets. Never expose a long-lived service secret.
24
+ return new Response('Configure room authorization before enabling connections', { status: 501 });
25
+ },
26
+ };
27
+ `);
28
+ }
29
+ export function scaffoldQueues({ root, options }) {
30
+ worker(root, 'jobs', {
31
+ ...workerConfig(`${options.name}-jobs`),
32
+ queues: {
33
+ producers: [{ binding: 'JOBS', queue: `${options.name}-jobs` }],
34
+ consumers: [
35
+ {
36
+ queue: `${options.name}-jobs`,
37
+ max_batch_size: 10,
38
+ max_retries: 3,
39
+ dead_letter_queue: `${options.name}-jobs-dlq`,
40
+ },
41
+ ],
42
+ },
43
+ }, `export default {
44
+ async queue(batch: MessageBatch<unknown>): Promise<void> {
45
+ for (const message of batch.messages) {
46
+ // Add transactional idempotency and product processing before acknowledging.
47
+ console.error('Job handler requires product implementation', message.id);
48
+ message.retry({ delaySeconds: 60 });
49
+ }
50
+ },
51
+ };
52
+ `);
53
+ }
54
+ export function scaffoldCron({ root, options }) {
55
+ worker(root, 'scheduled', { ...workerConfig(`${options.name}-scheduled`), triggers: { crons: ['0 * * * *'] } }, `export default {
56
+ async scheduled(controller: ScheduledController): Promise<void> {
57
+ // Define a product schedule, idempotency key, overlap policy, and failure alerts before release.
58
+ console.log('Scheduled trigger', controller.scheduledTime);
59
+ },
60
+ };
61
+ `);
62
+ }
63
+ export function scaffoldWorkflows({ root, options }) {
64
+ worker(root, 'workflows', {
65
+ ...workerConfig(`${options.name}-workflows`),
66
+ workflows: [
67
+ {
68
+ name: `${options.name}-workflow`,
69
+ binding: 'WORKFLOW',
70
+ class_name: 'ApplicationWorkflow',
71
+ },
72
+ ],
73
+ }, `import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';
74
+ export class ApplicationWorkflow extends WorkflowEntrypoint<Env, { operationId: string }> {
75
+ async run(event: WorkflowEvent<{ operationId: string }>, step: WorkflowStep) {
76
+ return step.do('validate', { retries: { limit: 3, delay: '10 seconds', backoff: 'exponential' } }, async () => {
77
+ if (!event.payload.operationId) throw new Error('operationId required');
78
+ return { operationId: event.payload.operationId };
79
+ });
80
+ }
81
+ }
82
+ export default { async fetch() { return new Response('Use an authorized server binding to create workflows', { status: 403 }); } };
83
+ `);
84
+ }
@@ -0,0 +1,2 @@
1
+ import type { RecipeContext } from '../registry/types.js';
2
+ export declare function scaffoldWeb({ root, options, bindings, runtimeApp, has }: RecipeContext): void;
@@ -0,0 +1,102 @@
1
+ import { STACK_DEPENDENCIES } from '../dependencies.js';
2
+ import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { configureVinext } from '../vinext.js';
5
+ import { json, put, readJson, patchPackage, workerConfig } from './files.js';
6
+ function cleanWorkspaceManifest(root, directory) {
7
+ const manifestPath = `${directory}/package.json`;
8
+ if (!existsSync(path.join(root, manifestPath)))
9
+ return;
10
+ const manifest = readJson(root, manifestPath);
11
+ const sources = [];
12
+ const collect = (folder) => {
13
+ for (const entry of readdirSync(folder, { withFileTypes: true })) {
14
+ if (['node_modules', '.next', '.open-next', '.git'].includes(entry.name))
15
+ continue;
16
+ const file = path.join(folder, entry.name);
17
+ if (entry.isDirectory())
18
+ collect(file);
19
+ else if (/\.(tsx?|m?js|css)$/.test(entry.name))
20
+ sources.push(readFileSync(file, 'utf8'));
21
+ }
22
+ };
23
+ collect(path.join(root, directory));
24
+ for (const name of ['lucide-react', 'zod', 'next-themes']) {
25
+ if (!sources.some((source) => source.includes(`"${name}`) || source.includes(`'${name}`)))
26
+ delete manifest.dependencies?.[name];
27
+ }
28
+ if (directory === 'packages/ui') {
29
+ if (!sources.some((source) => source.includes('@turbo/gen')) &&
30
+ !Object.values(manifest.scripts ?? {}).some((script) => String(script).includes('turbo gen')))
31
+ delete manifest.devDependencies?.['@turbo/gen'];
32
+ manifest.devDependencies = {
33
+ ...manifest.devDependencies,
34
+ 'postcss-load-config': STACK_DEPENDENCIES['postcss-load-config'],
35
+ };
36
+ if (manifest.dependencies?.shadcn) {
37
+ manifest.devDependencies.shadcn = manifest.dependencies.shadcn;
38
+ delete manifest.dependencies.shadcn;
39
+ }
40
+ }
41
+ json(root, manifestPath, manifest);
42
+ }
43
+ function cleanShadcnStarter(root) {
44
+ for (const directory of ['apps/web', 'packages/ui'])
45
+ cleanWorkspaceManifest(root, directory);
46
+ const themePath = 'apps/web/components/theme-provider.tsx';
47
+ if (existsSync(path.join(root, themePath))) {
48
+ const source = readFileSync(path.join(root, themePath), 'utf8');
49
+ // Preserve the upstream keyboard behavior while reducing branching in its guards.
50
+ put(root, themePath, source
51
+ .replace(/target.tagName === "INPUT" \|\|\s*target.tagName === "TEXTAREA" \|\|\s*target.tagName === "SELECT"/, '["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)')
52
+ .replace(/if \(event.defaultPrevented \|\| event.repeat\) \{\s*return\s*\}\s*if \(event.metaKey \|\| event.ctrlKey \|\| event.altKey\) \{\s*return\s*\}\s*if \(event.key.toLowerCase\(\) !== "d"\) \{\s*return\s*\}\s*if \(isTypingTarget\(event.target\)\) \{\s*return\s*\}/, 'if ([event.defaultPrevented, event.repeat, event.metaKey, event.ctrlKey, event.altKey, event.key.toLowerCase() !== "d", isTypingTarget(event.target)].some(Boolean)) { return }'));
53
+ }
54
+ }
55
+ export function scaffoldWeb({ root, options, bindings, runtimeApp, has }) {
56
+ cleanShadcnStarter(root);
57
+ put(root, 'apps/web/eslint.config.js', 'import { nextJsConfig } from "@workspace/eslint-config/next-js";\nexport default [...nextJsConfig, { ignores: [".open-next/**", ".wrangler/**", "cloudflare-env.d.ts"] }];\n');
58
+ patchPackage(root, 'apps/web/package.json', {
59
+ scripts: { typecheck: 'tsc --noEmit', lint: 'eslint . --max-warnings 0' },
60
+ });
61
+ if (has('vinext')) {
62
+ configureVinext(root, `${options.name}-web`, runtimeApp === 'web' ? bindings : {});
63
+ }
64
+ else if (options.hosting === 'cloudflare') {
65
+ patchPackage(root, 'apps/web/package.json', {
66
+ dependencies: {
67
+ '@opennextjs/cloudflare': STACK_DEPENDENCIES['@opennextjs/cloudflare'],
68
+ next: '16.3.8',
69
+ },
70
+ devDependencies: { wrangler: STACK_DEPENDENCIES['wrangler'] },
71
+ scripts: {
72
+ preview: 'opennextjs-cloudflare build && opennextjs-cloudflare preview',
73
+ deploy: 'opennextjs-cloudflare build && opennextjs-cloudflare deploy',
74
+ 'build:worker': 'opennextjs-cloudflare build',
75
+ 'cf:typegen': 'wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts',
76
+ },
77
+ });
78
+ json(root, 'apps/web/wrangler.jsonc', {
79
+ ...workerConfig(`${options.name}-web`),
80
+ compatibility_flags: ['nodejs_compat', 'global_fetch_strictly_public'],
81
+ main: '.open-next/worker.js',
82
+ assets: { directory: '.open-next/assets', binding: 'ASSETS' },
83
+ services: [{ binding: 'WORKER_SELF_REFERENCE', service: `${options.name}-web` }],
84
+ ...(runtimeApp === 'web' ? bindings : {}),
85
+ });
86
+ const nextConfig = ['next.config.ts', 'next.config.mjs', 'next.config.js'].find((file) => existsSync(path.join(root, 'apps/web', file)));
87
+ if (nextConfig) {
88
+ const source = readFileSync(path.join(root, 'apps/web', nextConfig), 'utf8');
89
+ put(root, `apps/web/${nextConfig}`, `import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare';\n${source}\ninitOpenNextCloudflareForDev();\n`);
90
+ }
91
+ put(root, 'apps/web/open-next.config.ts', 'import { defineCloudflareConfig } from "@opennextjs/cloudflare";\nexport default defineCloudflareConfig();\n');
92
+ put(root, 'apps/web/public/_headers', '/_next/static/*\n Cache-Control: public,max-age=31536000,immutable\n');
93
+ put(root, 'apps/web/.dev.vars.example', 'NEXTJS_ENV=development\n');
94
+ }
95
+ else {
96
+ patchPackage(root, 'apps/web/package.json', { dependencies: { next: '16.4.0' } });
97
+ put(root, 'apps/web/next.config.ts', 'import type { NextConfig } from "next";\nconst config: NextConfig = { transpilePackages: ["@workspace/ui"], cacheComponents: true, partialPrefetching: true };\nexport default config;\n');
98
+ for (const file of ['next.config.js', 'next.config.mjs'])
99
+ rmSync(path.join(root, 'apps/web', file), { force: true });
100
+ json(root, 'apps/web/vercel.json', { framework: 'nextjs' });
101
+ }
102
+ }
@@ -0,0 +1,3 @@
1
+ import type { ResolvedStackRegistry } from '../registry/resolve.js';
2
+ import type { StandardOptions } from '../registry/types.js';
3
+ export declare function writeStandardRoot(root: string, options: StandardOptions, registry: ResolvedStackRegistry, updating?: boolean): void;
@@ -0,0 +1,150 @@
1
+ import { STACK_DEPENDENCIES } from '../dependencies.js';
2
+ import { setPnpmPolicy } from '../pnpm-policy.js';
3
+ import { writeSecurityBackports } from '../security-backports.js';
4
+ import { BOOTSTRAP_COMMAND } from '../registry/bootstrap.js';
5
+ import { appendGitignore } from '../util.js';
6
+ import { PACKAGE_MANAGER } from '../dependencies.js';
7
+ import { createSetupPlan } from '../capabilities.js';
8
+ import { json, put, readJson, patchPackage, } from './files.js';
9
+ import { writeSetupDocumentation, workerBuildSuffix } from './documentation.js';
10
+ import { writeEnvironmentExample } from './environment.js';
11
+ export function writeStandardRoot(root, options, registry, updating = false) {
12
+ const has = (value) => options.products.includes(value);
13
+ const existing = readJson(root, 'package.json');
14
+ const securityOverrides = {
15
+ ...(has('wxt') ? { '@wxt-dev/module-react>@vitejs/plugin-react': '5.2.0' } : {}),
16
+ ...(has('next') || has('vinext') ? { 'sharp@<0.35.5': '0.35.5' } : {}),
17
+ 'source-map-js@<1.2.2': '1.2.2',
18
+ ...(has('next') && options.hosting === 'cloudflare'
19
+ ? { 'glob@>=10.2.0 <10.5.0': '10.5.0', 'glob@>=11.0.0 <11.1.0': '11.1.0' }
20
+ : {}),
21
+ ...(has('wxt')
22
+ ? { 'shell-quote@<1.12.0': '1.12.0', 'tmp@<0.2.7': '0.2.7', 'adm-zip@<0.6.1': '0.6.1' }
23
+ : {}),
24
+ 'baseline-browser-mapping@<2.11.0': '2.11.27',
25
+ 'browserslist@<4.28.7': '4.29.3',
26
+ ...(((has('next') || has('vinext')) && options.hosting === 'cloudflare') ||
27
+ has('hono') ||
28
+ has('electron') ||
29
+ has('wxt')
30
+ ? { 'esbuild@<0.25.0': '0.25.12' }
31
+ : {}),
32
+ 'brace-expansion@<2': '1.1.21',
33
+ 'brace-expansion@>=2 <3': '2.1.7',
34
+ 'brace-expansion@>=3 <4': '3.0.9',
35
+ 'brace-expansion@>=4 <5.0.12': '5.0.12',
36
+ };
37
+ patchPackage(root, 'package.json', {
38
+ name: options.name,
39
+ private: true,
40
+ packageManager: PACKAGE_MANAGER,
41
+ engines: { node: '>=22.13.0' },
42
+ scripts: {
43
+ dev: 'turbo dev',
44
+ build: 'turbo build',
45
+ ...((has('next') || has('vinext')) && options.hosting === 'cloudflare'
46
+ ? { 'build:worker': 'pnpm --dir apps/web run build:worker' }
47
+ : {}),
48
+ lint: 'turbo lint',
49
+ typecheck: 'turbo typecheck',
50
+ test: 'turbo test',
51
+ audit: 'node scripts/audit.mjs',
52
+ 'audit:full': 'fallow --fail-on-issues',
53
+ 'audit:deps': 'node scripts/security/audit-deps.mjs',
54
+ check: `pnpm lint && pnpm typecheck && pnpm run audit && pnpm audit:deps && pnpm test && pnpm build${workerBuildSuffix(options)}`,
55
+ },
56
+ devDependencies: { fallow: STACK_DEPENDENCIES['fallow'] },
57
+ // Upstream starter lockfiles can retain vulnerable transitive versions. Keep fixes within compatible majors.
58
+ pnpm: { ...existing.pnpm, overrides: { ...existing.pnpm?.overrides, ...securityOverrides } },
59
+ });
60
+ put(root, 'scripts/audit.mjs', `import { spawnSync } from 'node:child_process';
61
+ const base = process.env.AUDIT_BASE;
62
+ const hasBase = base || ['origin/main', 'main'].find((ref) => spawnSync('git', ['rev-parse', '--verify', ref], { stdio: 'ignore' }).status === 0);
63
+ const args = hasBase ? ['exec', 'fallow', 'audit', '--base', hasBase] : ['exec', 'fallow', '--fail-on-issues'];
64
+ const entry = process.env.npm_execpath;
65
+ if (!entry) throw new Error('Run this check through pnpm run audit.');
66
+ const result = spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit', shell: false });
67
+ if (result.error) throw result.error;
68
+ process.exitCode = result.status ?? 1;
69
+ `);
70
+ json(root, '.fallowrc.json', {
71
+ ...(has('electron') ? { entry: ['apps/desktop/src/main/preload.cts'] } : {}),
72
+ // Expo web export and NativeWind load these runtime dependencies dynamically.
73
+ ...(has('expo')
74
+ ? {
75
+ ignoreDependencies: [
76
+ 'react-native-web',
77
+ 'react-native-css-interop',
78
+ 'react-native-reanimated',
79
+ 'react-native-worklets',
80
+ ],
81
+ }
82
+ : {}),
83
+ ignorePatterns: [
84
+ '**/next-env.d.ts',
85
+ '**/.next/**',
86
+ '**/.open-next/**',
87
+ '**/.wrangler/**',
88
+ '**/dist/**',
89
+ '**/worker-configuration.d.ts',
90
+ '**/cloudflare-env.d.ts',
91
+ ],
92
+ });
93
+ const turbo = readJson(root, 'turbo.json');
94
+ turbo.globalEnv = [...new Set(registry.items.flatMap((item) => item.environment))].sort();
95
+ turbo.tasks = {
96
+ ...turbo.tasks,
97
+ dev: { ...turbo.tasks.dev, cache: false, persistent: true },
98
+ lint: { ...turbo.tasks.lint, dependsOn: ['^lint'] },
99
+ typecheck: { ...turbo.tasks.typecheck, dependsOn: ['^typecheck'] },
100
+ test: { dependsOn: ['^build'], outputs: ['coverage/**'] },
101
+ build: {
102
+ ...turbo.tasks.build,
103
+ dependsOn: [...new Set(['^build', ...(turbo.tasks.build?.dependsOn ?? [])])],
104
+ outputs: ['.next/**', '!.next/cache/**', '.open-next/**', 'dist/**', '.output/**'],
105
+ },
106
+ };
107
+ json(root, 'turbo.json', turbo);
108
+ setPnpmPolicy(root, {
109
+ allowBuilds: undefined,
110
+ onlyBuiltDependencies: [
111
+ 'esbuild',
112
+ 'sharp',
113
+ 'unrs-resolver',
114
+ 'workerd',
115
+ ...(has('electron') ? ['electron'] : []),
116
+ ],
117
+ });
118
+ writeSecurityBackports(root);
119
+ appendGitignore(root, [
120
+ 'node_modules/',
121
+ '.open-next/',
122
+ '.wrangler/',
123
+ '.dev.vars',
124
+ '.dev.vars.*',
125
+ '!.dev.vars.example',
126
+ '.env',
127
+ '.env.*',
128
+ '!.env.example',
129
+ '.fallow/',
130
+ '.anhedral/',
131
+ ]);
132
+ writeEnvironmentExample(root, registry);
133
+ json(root, 'anhedral.standard.json', {
134
+ schemaVersion: 1,
135
+ standard: 'anhedral-application-stack',
136
+ products: options.products,
137
+ hosting: has('next') || has('vinext') ? options.hosting : null,
138
+ bootstrap: updating
139
+ ? readJson(root, 'anhedral.standard.json').bootstrap
140
+ : {
141
+ command: BOOTSTRAP_COMMAND.join(' '),
142
+ upstreamPackageManager: existing.packageManager,
143
+ },
144
+ status: 'starter',
145
+ resourcesProvisioned: false,
146
+ });
147
+ json(root, 'anhedral.setup.json', createSetupPlan({ ...options, dryRun: false }, registry));
148
+ if (!updating)
149
+ writeSetupDocumentation(root, options);
150
+ }
@@ -16,6 +16,11 @@ export function configureSecurityBackports(root) {
16
16
  });
17
17
  if (!entries.length)
18
18
  return false;
19
- mergePnpmPatches(root, Object.fromEntries(entries.map(({ name, version, patch }) => [`${name}@${version}`, `scripts/security/${patch}`])));
20
- return true;
19
+ const workspaceFile = path.join(root, 'pnpm-workspace.yaml');
20
+ const before = readFileSync(workspaceFile, 'utf8');
21
+ mergePnpmPatches(root, Object.fromEntries(entries.map(({ name, version, patch }) => [
22
+ `${name}@${version}`,
23
+ `scripts/security/${patch}`,
24
+ ])));
25
+ return readFileSync(workspaceFile, 'utf8') !== before;
21
26
  }
@@ -0,0 +1,80 @@
1
+ import type { ItemDefinition, StackRegistryItem, RecipeContext } from './registry/types.js';
2
+ /** Every product owns its source recipe, setup requirements and composition policy here. */
3
+ declare const PRODUCT_ITEMS: {
4
+ vinext: ItemDefinition;
5
+ next: ItemDefinition;
6
+ expo: ItemDefinition;
7
+ electron: ItemDefinition;
8
+ wxt: ItemDefinition;
9
+ hono: ItemDefinition;
10
+ neon: ItemDefinition;
11
+ d1: ItemDefinition;
12
+ 'local-data': ItemDefinition;
13
+ clerk: ItemDefinition;
14
+ 'better-auth': ItemDefinition;
15
+ r2: ItemDefinition;
16
+ kv: ItemDefinition;
17
+ realtime: ItemDefinition;
18
+ queues: ItemDefinition;
19
+ cron: ItemDefinition;
20
+ workflows: ItemDefinition;
21
+ openai: ItemDefinition;
22
+ 'ai-sdk': ItemDefinition;
23
+ 'workers-ai': ItemDefinition;
24
+ resend: ItemDefinition;
25
+ stripe: ItemDefinition;
26
+ revenuecat: ItemDefinition;
27
+ sentry: ItemDefinition;
28
+ basin: ItemDefinition;
29
+ posthog: ItemDefinition;
30
+ styling: ItemDefinition;
31
+ };
32
+ export type StandardProduct = keyof typeof PRODUCT_ITEMS;
33
+ export declare const STANDARD_PRODUCTS: readonly ("realtime" | "electron" | "wxt" | "expo" | "next" | "vinext" | "hono" | "neon" | "better-auth" | "queues" | "workflows" | "d1" | "local-data" | "clerk" | "r2" | "kv" | "cron" | "openai" | "ai-sdk" | "workers-ai" | "resend" | "stripe" | "revenuecat" | "sentry" | "basin" | "posthog" | "styling")[];
34
+ declare const DEFINITIONS: {
35
+ vinext: ItemDefinition;
36
+ next: ItemDefinition;
37
+ expo: ItemDefinition;
38
+ electron: ItemDefinition;
39
+ wxt: ItemDefinition;
40
+ hono: ItemDefinition;
41
+ neon: ItemDefinition;
42
+ d1: ItemDefinition;
43
+ 'local-data': ItemDefinition;
44
+ clerk: ItemDefinition;
45
+ 'better-auth': ItemDefinition;
46
+ r2: ItemDefinition;
47
+ kv: ItemDefinition;
48
+ realtime: ItemDefinition;
49
+ queues: ItemDefinition;
50
+ cron: ItemDefinition;
51
+ workflows: ItemDefinition;
52
+ openai: ItemDefinition;
53
+ 'ai-sdk': ItemDefinition;
54
+ 'workers-ai': ItemDefinition;
55
+ resend: ItemDefinition;
56
+ stripe: ItemDefinition;
57
+ revenuecat: ItemDefinition;
58
+ sentry: ItemDefinition;
59
+ basin: ItemDefinition;
60
+ posthog: ItemDefinition;
61
+ styling: ItemDefinition;
62
+ workspace: ItemDefinition;
63
+ typescript: ItemDefinition;
64
+ eslint: ItemDefinition;
65
+ ui: ItemDefinition;
66
+ };
67
+ export type StackItemId = keyof typeof DEFINITIONS;
68
+ export declare function getStackDefinition(name: string): ItemDefinition;
69
+ export declare function getStackItem(name: string): StackRegistryItem;
70
+ export declare const STACK_ITEMS: Record<"realtime" | "electron" | "eslint" | "typescript" | "wxt" | "expo" | "next" | "vinext" | "hono" | "neon" | "better-auth" | "queues" | "workflows" | "workspace" | "ui" | "d1" | "local-data" | "clerk" | "r2" | "kv" | "cron" | "openai" | "ai-sdk" | "workers-ai" | "resend" | "stripe" | "revenuecat" | "sentry" | "basin" | "posthog" | "styling", StackRegistryItem>;
71
+ export declare const STACK_REGISTRY: {
72
+ schemaVersion: number;
73
+ name: string;
74
+ cliVersion: string;
75
+ items: StackRegistryItem[];
76
+ };
77
+ export declare function installStackItem(item: StackRegistryItem, context: RecipeContext): void | Promise<void>;
78
+ /** Validate authored definitions during build, before shipping either catalog. */
79
+ export declare function validateStackCatalog(): void;
80
+ export {};