anhedral 0.10.0 → 0.10.1

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 (43) hide show
  1. package/README.md +2 -2
  2. package/assets/security/audit-deps.mjs +6 -1
  3. package/dist/bounded-file.d.ts +1 -0
  4. package/dist/bounded-file.js +1 -0
  5. package/dist/capabilities.d.ts +4 -4
  6. package/dist/capabilities.js +1 -1
  7. package/dist/dependencies.d.ts +32 -0
  8. package/dist/dependencies.js +47 -1
  9. package/dist/platforms/desktop.js +4 -5
  10. package/dist/platforms/extension.js +9 -73
  11. package/dist/platforms/mobile.js +6 -47
  12. package/dist/readiness.js +11 -3
  13. package/dist/registry/add.js +6 -3
  14. package/dist/registry/receipt.js +1 -0
  15. package/dist/registry/resolve.js +19 -13
  16. package/dist/registry/types.d.ts +1 -0
  17. package/dist/render.d.ts +2 -0
  18. package/dist/render.js +10 -0
  19. package/dist/scaffold/api.js +20 -3
  20. package/dist/scaffold/auth.js +73 -17
  21. package/dist/scaffold/bootstrap.d.ts +2 -0
  22. package/dist/scaffold/bootstrap.js +9 -0
  23. package/dist/scaffold/database.js +12 -6
  24. package/dist/scaffold/documentation.js +11 -8
  25. package/dist/scaffold/environment.d.ts +2 -0
  26. package/dist/scaffold/environment.js +18 -1
  27. package/dist/scaffold/files.d.ts +1 -0
  28. package/dist/scaffold/files.js +15 -4
  29. package/dist/scaffold/services.js +37 -13
  30. package/dist/scaffold/stack-documentation.d.ts +3 -0
  31. package/dist/scaffold/stack-documentation.js +52 -0
  32. package/dist/scaffold/styling.d.ts +3 -0
  33. package/dist/scaffold/styling.js +84 -0
  34. package/dist/scaffold/web.js +31 -11
  35. package/dist/scaffold/workspace.js +38 -22
  36. package/dist/stack-registry.d.ts +2 -2
  37. package/dist/stack-registry.js +38 -11
  38. package/dist/standard.js +2 -1
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/dist/vinext.js +9 -18
  42. package/dist/workspace-graph.js +3 -2
  43. package/package.json +1 -1
@@ -0,0 +1,84 @@
1
+ import { json, patchPackage, put, readJson } from './files.js';
2
+ /** Shared data and a Tailwind v3 preset for Expo/NativeWind and WXT. */
3
+ export function scaffoldStyling({ root }) {
4
+ put(root, 'packages/styling/src/tokens.cjs', `exports.light = {
5
+ '--background': '0 0% 100%',
6
+ '--foreground': '0 0% 9%',
7
+ '--card': '0 0% 100%',
8
+ '--card-foreground': '0 0% 9%',
9
+ '--popover': '0 0% 100%',
10
+ '--popover-foreground': '0 0% 9%',
11
+ '--primary': '0 0% 9%',
12
+ '--primary-foreground': '0 0% 98%',
13
+ '--secondary': '0 0% 96.1%',
14
+ '--secondary-foreground': '0 0% 9%',
15
+ '--muted': '0 0% 96.1%',
16
+ '--muted-foreground': '0 0% 45.1%',
17
+ '--accent': '0 0% 96.1%',
18
+ '--accent-foreground': '0 0% 9%',
19
+ '--destructive': '0 84.2% 60.2%',
20
+ '--destructive-foreground': '0 0% 98%',
21
+ '--border': '0 0% 89.8%',
22
+ '--input': '0 0% 89.8%',
23
+ '--ring': '0 0% 63.9%',
24
+ '--radius': '0.5rem',
25
+ };
26
+
27
+ exports.dark = {
28
+ ...exports.light,
29
+ '--background': '0 0% 9%',
30
+ '--foreground': '0 0% 98%',
31
+ '--card': '0 0% 9%',
32
+ '--card-foreground': '0 0% 98%',
33
+ '--popover': '0 0% 9%',
34
+ '--popover-foreground': '0 0% 98%',
35
+ '--primary': '0 0% 98%',
36
+ '--primary-foreground': '0 0% 9%',
37
+ '--secondary': '0 0% 14.9%',
38
+ '--secondary-foreground': '0 0% 98%',
39
+ '--muted': '0 0% 14.9%',
40
+ '--muted-foreground': '0 0% 63.9%',
41
+ '--accent': '0 0% 14.9%',
42
+ '--accent-foreground': '0 0% 98%',
43
+ '--destructive': '0 70.9% 59.4%',
44
+ '--border': '0 0% 14.9%',
45
+ '--input': '0 0% 14.9%',
46
+ };
47
+
48
+ exports.spacing = { small: 4, medium: 8, large: 16 };
49
+ `);
50
+ put(root, 'packages/styling/tailwind.cjs', `const { light, dark } = require('./src/tokens.cjs');
51
+ const color = (name) => 'hsl(var(--' + name + '))';
52
+ const surface = (name) => ({ DEFAULT: color(name), foreground: color(name + '-foreground') });
53
+
54
+ module.exports = {
55
+ darkMode: 'class',
56
+ theme: {
57
+ extend: {
58
+ colors: {
59
+ border: color('border'), input: color('input'), ring: color('ring'),
60
+ background: color('background'), foreground: color('foreground'),
61
+ primary: surface('primary'), secondary: surface('secondary'),
62
+ destructive: surface('destructive'), muted: surface('muted'),
63
+ accent: surface('accent'), popover: surface('popover'), card: surface('card'),
64
+ },
65
+ borderRadius: {
66
+ lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)',
67
+ },
68
+ },
69
+ },
70
+ plugins: [function themeVariables({ addBase }) {
71
+ addBase({ ':root': light, '.dark:root': dark });
72
+ }],
73
+ };
74
+ `);
75
+ patchPackage(root, 'packages/styling/package.json', {
76
+ exports: { '.': './src/index.ts', './tailwind': './tailwind.cjs' },
77
+ });
78
+ const config = readJson(root, 'packages/styling/tsconfig.json');
79
+ json(root, 'packages/styling/tsconfig.json', {
80
+ ...config,
81
+ compilerOptions: { ...config.compilerOptions, allowJs: true },
82
+ include: ['src/**/*.ts', 'src/**/*.cjs', 'tailwind.cjs'],
83
+ });
84
+ }
@@ -1,4 +1,6 @@
1
- import { STACK_DEPENDENCIES } from '../dependencies.js';
1
+ import { resourceName } from '../render.js';
2
+ import { writeEnvironmentFile } from './environment.js';
3
+ import { STACK_DEPENDENCIES, NEXT_VERCEL_VERSION, NEXT_OPENNEXT_VERSION } from '../dependencies.js';
2
4
  import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
3
5
  import path from 'node:path';
4
6
  import { configureVinext } from '../vinext.js';
@@ -46,10 +48,28 @@ function cleanShadcnStarter(root) {
46
48
  const themePath = 'apps/web/components/theme-provider.tsx';
47
49
  if (existsSync(path.join(root, themePath))) {
48
50
  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
+ const updated = source
51
52
  .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
+ .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 (!isThemeShortcut(event) || isTypingTarget(event.target)) return');
54
+ // Group modifier handling while preserving short-circuiting and avoiding guard arrays.
55
+ const modifierHelper = `function hasShortcutModifier(event: KeyboardEvent) {
56
+ return event.metaKey || event.ctrlKey || event.altKey
57
+ }
58
+
59
+ function isThemeShortcut(event: KeyboardEvent) {
60
+ if (event.defaultPrevented || event.repeat) return false
61
+ return !hasShortcutModifier(event) && event.key.toLowerCase() === "d"
62
+ }
63
+
64
+ `;
65
+ const needsModifierHelper = updated.includes('isThemeShortcut(event)') &&
66
+ !updated.includes('function hasShortcutModifier(');
67
+ const content = needsModifierHelper
68
+ ? updated.replace(/(?:export )?function ThemeProvider\b/, (declaration) => modifierHelper + declaration)
69
+ : updated;
70
+ if (needsModifierHelper && content === updated)
71
+ throw new Error('The upstream theme provider changed; review its shortcut integration.');
72
+ put(root, themePath, content);
53
73
  }
54
74
  }
55
75
  export function scaffoldWeb({ root, options, bindings, runtimeApp, has }) {
@@ -59,28 +79,28 @@ export function scaffoldWeb({ root, options, bindings, runtimeApp, has }) {
59
79
  scripts: { typecheck: 'tsc --noEmit', lint: 'eslint . --max-warnings 0' },
60
80
  });
61
81
  if (has('vinext')) {
62
- configureVinext(root, `${options.name}-web`, runtimeApp === 'web' ? bindings : {});
82
+ configureVinext(root, resourceName(options.name, 'web'), runtimeApp === 'web' ? bindings : {});
63
83
  }
64
84
  else if (options.hosting === 'cloudflare') {
65
85
  patchPackage(root, 'apps/web/package.json', {
66
86
  dependencies: {
67
87
  '@opennextjs/cloudflare': STACK_DEPENDENCIES['@opennextjs/cloudflare'],
68
- next: '16.3.8',
88
+ next: NEXT_OPENNEXT_VERSION,
69
89
  },
70
90
  devDependencies: { wrangler: STACK_DEPENDENCIES['wrangler'] },
71
91
  scripts: {
72
- preview: 'opennextjs-cloudflare build && opennextjs-cloudflare preview',
92
+ preview: 'opennextjs-cloudflare build && opennextjs-cloudflare preview --port 3000',
73
93
  deploy: 'opennextjs-cloudflare build && opennextjs-cloudflare deploy',
74
94
  'build:worker': 'opennextjs-cloudflare build',
75
95
  'cf:typegen': 'wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts',
76
96
  },
77
97
  });
78
98
  json(root, 'apps/web/wrangler.jsonc', {
79
- ...workerConfig(`${options.name}-web`),
99
+ ...workerConfig(resourceName(options.name, 'web')),
80
100
  compatibility_flags: ['nodejs_compat', 'global_fetch_strictly_public'],
81
101
  main: '.open-next/worker.js',
82
102
  assets: { directory: '.open-next/assets', binding: 'ASSETS' },
83
- services: [{ binding: 'WORKER_SELF_REFERENCE', service: `${options.name}-web` }],
103
+ services: [{ binding: 'WORKER_SELF_REFERENCE', service: resourceName(options.name, 'web') }],
84
104
  ...(runtimeApp === 'web' ? bindings : {}),
85
105
  });
86
106
  const nextConfig = ['next.config.ts', 'next.config.mjs', 'next.config.js'].find((file) => existsSync(path.join(root, 'apps/web', file)));
@@ -90,10 +110,10 @@ export function scaffoldWeb({ root, options, bindings, runtimeApp, has }) {
90
110
  }
91
111
  put(root, 'apps/web/open-next.config.ts', 'import { defineCloudflareConfig } from "@opennextjs/cloudflare";\nexport default defineCloudflareConfig();\n');
92
112
  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');
113
+ writeEnvironmentFile(root, 'apps/web/.dev.vars.example', { NEXTJS_ENV: 'development' });
94
114
  }
95
115
  else {
96
- patchPackage(root, 'apps/web/package.json', { dependencies: { next: '16.4.0' } });
116
+ patchPackage(root, 'apps/web/package.json', { dependencies: { next: NEXT_VERCEL_VERSION } });
97
117
  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
118
  for (const file of ['next.config.js', 'next.config.mjs'])
99
119
  rmSync(path.join(root, 'apps/web', file), { force: true });
@@ -1,4 +1,4 @@
1
- import { STACK_DEPENDENCIES } from '../dependencies.js';
1
+ import { STACK_DEPENDENCIES, WORKSPACE_NODE_ENGINE } from '../dependencies.js';
2
2
  import { setPnpmPolicy } from '../pnpm-policy.js';
3
3
  import { writeSecurityBackports } from '../security-backports.js';
4
4
  import { BOOTSTRAP_COMMAND } from '../registry/bootstrap.js';
@@ -7,14 +7,16 @@ import { PACKAGE_MANAGER } from '../dependencies.js';
7
7
  import { createSetupPlan } from '../capabilities.js';
8
8
  import { json, put, readJson, patchPackage, } from './files.js';
9
9
  import { writeSetupDocumentation, workerBuildSuffix } from './documentation.js';
10
+ import { writeStackDocumentation } from './stack-documentation.js';
10
11
  import { writeEnvironmentExample } from './environment.js';
11
- export function writeStandardRoot(root, options, registry, updating = false) {
12
+ function securityOverrides(options) {
12
13
  const has = (value) => options.products.includes(value);
13
- const existing = readJson(root, 'package.json');
14
- const securityOverrides = {
14
+ return {
15
15
  ...(has('wxt') ? { '@wxt-dev/module-react>@vitejs/plugin-react': '5.2.0' } : {}),
16
16
  ...(has('next') || has('vinext') ? { 'sharp@<0.35.5': '0.35.5' } : {}),
17
- 'source-map-js@<1.2.2': '1.2.2',
17
+ ...(options.products.some((product) => ['vinext', 'next', 'expo', 'electron', 'wxt', 'neon', 'd1'].includes(product))
18
+ ? { 'source-map-js@<1.2.2': '1.2.2' }
19
+ : {}),
18
20
  ...(has('next') && options.hosting === 'cloudflare'
19
21
  ? { 'glob@>=10.2.0 <10.5.0': '10.5.0', 'glob@>=11.0.0 <11.1.0': '11.1.0' }
20
22
  : {}),
@@ -34,11 +36,34 @@ export function writeStandardRoot(root, options, registry, updating = false) {
34
36
  'brace-expansion@>=3 <4': '3.0.9',
35
37
  'brace-expansion@>=4 <5.0.12': '5.0.12',
36
38
  };
39
+ }
40
+ function writeTurboConfiguration(root, registry) {
41
+ const turbo = readJson(root, 'turbo.json');
42
+ turbo.globalEnv = [
43
+ ...new Set([...(turbo.globalEnv ?? []), ...registry.items.flatMap((item) => item.environment)]),
44
+ ].sort();
45
+ turbo.tasks = {
46
+ ...turbo.tasks,
47
+ dev: { ...turbo.tasks.dev, cache: false, persistent: true },
48
+ lint: { ...turbo.tasks.lint, dependsOn: ['^lint'] },
49
+ typecheck: { ...turbo.tasks.typecheck, dependsOn: ['^typecheck'] },
50
+ test: { dependsOn: ['^build'], outputs: ['coverage/**'] },
51
+ build: {
52
+ ...turbo.tasks.build,
53
+ dependsOn: [...new Set(['^build', ...(turbo.tasks.build?.dependsOn ?? [])])],
54
+ outputs: ['.next/**', '!.next/cache/**', '.open-next/**', 'dist/**', '.output/**'],
55
+ },
56
+ };
57
+ json(root, 'turbo.json', turbo);
58
+ }
59
+ export function writeStandardRoot(root, options, registry, updating = false) {
60
+ const has = (value) => options.products.includes(value);
61
+ const existing = readJson(root, 'package.json');
37
62
  patchPackage(root, 'package.json', {
38
63
  name: options.name,
39
64
  private: true,
40
65
  packageManager: PACKAGE_MANAGER,
41
- engines: { node: '>=22.13.0' },
66
+ engines: { node: WORKSPACE_NODE_ENGINE },
42
67
  scripts: {
43
68
  dev: 'turbo dev',
44
69
  build: 'turbo build',
@@ -55,7 +80,10 @@ export function writeStandardRoot(root, options, registry, updating = false) {
55
80
  },
56
81
  devDependencies: { fallow: STACK_DEPENDENCIES['fallow'] },
57
82
  // Upstream starter lockfiles can retain vulnerable transitive versions. Keep fixes within compatible majors.
58
- pnpm: { ...existing.pnpm, overrides: { ...existing.pnpm?.overrides, ...securityOverrides } },
83
+ pnpm: {
84
+ ...existing.pnpm,
85
+ overrides: { ...existing.pnpm?.overrides, ...securityOverrides(options) },
86
+ },
59
87
  });
60
88
  put(root, 'scripts/audit.mjs', `import { spawnSync } from 'node:child_process';
61
89
  const base = process.env.AUDIT_BASE;
@@ -90,21 +118,7 @@ process.exitCode = result.status ?? 1;
90
118
  '**/cloudflare-env.d.ts',
91
119
  ],
92
120
  });
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);
121
+ writeTurboConfiguration(root, registry);
108
122
  setPnpmPolicy(root, {
109
123
  allowBuilds: undefined,
110
124
  onlyBuiltDependencies: [
@@ -128,8 +142,10 @@ process.exitCode = result.status ?? 1;
128
142
  '!.env.example',
129
143
  '.fallow/',
130
144
  '.anhedral/',
145
+ '.anhedral-audit-report.json',
131
146
  ]);
132
147
  writeEnvironmentExample(root, registry);
148
+ writeStackDocumentation(root, registry);
133
149
  json(root, 'anhedral.standard.json', {
134
150
  schemaVersion: 1,
135
151
  standard: 'anhedral-application-stack',
@@ -30,7 +30,7 @@ declare const PRODUCT_ITEMS: {
30
30
  styling: ItemDefinition;
31
31
  };
32
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")[];
33
+ export declare const STANDARD_PRODUCTS: readonly ("realtime" | "electron" | "wxt" | "expo" | "vinext" | "next" | "workflows" | "styling" | "hono" | "neon" | "d1" | "local-data" | "clerk" | "better-auth" | "r2" | "kv" | "queues" | "cron" | "openai" | "ai-sdk" | "workers-ai" | "resend" | "stripe" | "revenuecat" | "sentry" | "basin" | "posthog")[];
34
34
  declare const DEFINITIONS: {
35
35
  vinext: ItemDefinition;
36
36
  next: ItemDefinition;
@@ -67,7 +67,7 @@ declare const DEFINITIONS: {
67
67
  export type StackItemId = keyof typeof DEFINITIONS;
68
68
  export declare function getStackDefinition(name: string): ItemDefinition;
69
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>;
70
+ export declare const STACK_ITEMS: Record<"realtime" | "electron" | "eslint" | "typescript" | "wxt" | "expo" | "vinext" | "next" | "workflows" | "ui" | "workspace" | "styling" | "hono" | "neon" | "d1" | "local-data" | "clerk" | "better-auth" | "r2" | "kv" | "queues" | "cron" | "openai" | "ai-sdk" | "workers-ai" | "resend" | "stripe" | "revenuecat" | "sentry" | "basin" | "posthog", StackRegistryItem>;
71
71
  export declare const STACK_REGISTRY: {
72
72
  schemaVersion: number;
73
73
  name: string;
@@ -1,3 +1,5 @@
1
+ import { scaffoldStyling } from './scaffold/styling.js';
2
+ import { resourceName } from './render.js';
1
3
  import { STACK_DEPENDENCIES } from './dependencies.js';
2
4
  import { dependencyOrder } from './dependency-order.js';
3
5
  import { GENERATOR_VERSION } from './version.js';
@@ -55,7 +57,7 @@ const PRODUCT_ITEMS = defineItems({
55
57
  type: 'application',
56
58
  description: 'Expo mobile application with native UI',
57
59
  targets: ['apps/mobile'],
58
- registryDependencies: ['workspace', 'typescript', 'eslint'],
60
+ registryDependencies: ['workspace', 'typescript', 'eslint', 'styling'],
59
61
  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
62
  install: scaffoldExpo,
61
63
  },
@@ -71,7 +73,7 @@ const PRODUCT_ITEMS = defineItems({
71
73
  type: 'application',
72
74
  description: 'WXT browser side-panel extension',
73
75
  targets: ['apps/extension'],
74
- registryDependencies: ['workspace', 'typescript', 'eslint'],
76
+ registryDependencies: ['workspace', 'typescript', 'eslint', 'styling'],
75
77
  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
78
  install: scaffoldWxt,
77
79
  },
@@ -106,7 +108,7 @@ const PRODUCT_ITEMS = defineItems({
106
108
  d1_databases: [
107
109
  {
108
110
  binding: 'DB',
109
- database_name: `${options.name}-db`,
111
+ database_name: resourceName(options.name, 'db'),
110
112
  database_id: 'REPLACE_WITH_D1_ID',
111
113
  migrations_dir: '../../packages/db/migrations',
112
114
  },
@@ -120,7 +122,12 @@ const PRODUCT_ITEMS = defineItems({
120
122
  targets: ['packages/local-data'],
121
123
  registryDependencies: ['workspace', 'typescript', 'eslint'],
122
124
  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', {}),
125
+ package: packageSource('local-data', `// Device-local SQLite and filesystem need platform-specific adapters.
126
+ export interface LocalDataStore {
127
+ read(key: string): Promise<string | null>;
128
+ write(key: string, value: string): Promise<void>;
129
+ }
130
+ `, {}),
124
131
  },
125
132
  clerk: {
126
133
  type: 'integration',
@@ -130,7 +137,12 @@ const PRODUCT_ITEMS = defineItems({
130
137
  registryDependencies: ['workspace', 'typescript', 'eslint'],
131
138
  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
139
  install: scaffoldClerk,
133
- environment: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
140
+ environment: ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY'],
141
+ resolveEnvironment: ({ has }) => [
142
+ 'CLERK_SECRET_KEY',
143
+ ...(has('vinext') || has('next') ? ['NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY'] : []),
144
+ ...(has('hono') ? ['CLERK_PUBLISHABLE_KEY'] : []),
145
+ ],
134
146
  runtime: 'server',
135
147
  resolveDependencies: ({ has }) => ['vinext', 'next', 'hono'].filter((id) => has(id)),
136
148
  resolveTargets: ({ has }) => [
@@ -162,7 +174,7 @@ const PRODUCT_ITEMS = defineItems({
162
174
  ]),
163
175
  install: scaffoldPrivateFiles,
164
176
  configure: ({ options }) => ({
165
- r2_buckets: [{ binding: 'FILES', bucket_name: `${options.name}-private-files` }],
177
+ r2_buckets: [{ binding: 'FILES', bucket_name: resourceName(options.name, 'private-files') }],
166
178
  }),
167
179
  runtime: 'worker',
168
180
  resolveTargets: ({ runtimeApp }) => [`apps/${runtimeApp}/private-files.md`],
@@ -194,7 +206,7 @@ const PRODUCT_ITEMS = defineItems({
194
206
  setup: requirements('Deferred jobs', ['Wrangler'], ['Cloudflare Queues configuration access'], [], ['Queue, producer/consumer bindings and failure handling'], ['Retries, idempotency, concurrency and dead-letter recovery']),
195
207
  install: scaffoldQueues,
196
208
  configure: ({ options }) => ({
197
- queues: { producers: [{ binding: 'JOBS', queue: `${options.name}-jobs` }] },
209
+ queues: { producers: [{ binding: 'JOBS', queue: resourceName(options.name, 'jobs') }] },
198
210
  }),
199
211
  runtime: 'optional-worker',
200
212
  resolveTargets: ({ runtimeApp, has }) => [
@@ -298,7 +310,9 @@ const PRODUCT_ITEMS = defineItems({
298
310
  'Basin dataset/stream, ANALYTICS binding, optional private R2 sink and authorized sink writes',
299
311
  ], ['Private ingestion, event delivery, query results and archive retrieval when selected']),
300
312
  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); }
313
+ export async function recordEvents(stream: EventStream, events: Record<string, unknown>[]) {
314
+ await stream.send(events);
315
+ }
302
316
  `, {}),
303
317
  resolvePackage: ({ has }, recipe) => ({
304
318
  ...recipe,
@@ -325,8 +339,21 @@ export async function recordEvents(stream: EventStream, events: Record<string, u
325
339
  description: 'Shared platform-independent styling tokens',
326
340
  targets: ['packages/styling'],
327
341
  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", {}),
342
+ setup: requirements('Shared styling definitions', [], [], [], [
343
+ 'Shared light/dark tokens and Tailwind v3 preset for Expo/WXT; adapt platform typography and components',
344
+ ], ['Consistency across selected interfaces; preserve platform-specific components']),
345
+ package: packageSource('styling', `import { light, dark, spacing } from './tokens.cjs';
346
+
347
+ export const styling = {
348
+ colors: {
349
+ foreground: 'hsl(' + light['--foreground'] + ')',
350
+ background: 'hsl(' + light['--background'] + ')',
351
+ },
352
+ spacing,
353
+ themes: { light, dark },
354
+ };
355
+ `, {}),
356
+ install: scaffoldStyling,
330
357
  },
331
358
  });
332
359
  export const STANDARD_PRODUCTS = Object.freeze(Object.keys(PRODUCT_ITEMS));
@@ -366,7 +393,7 @@ function freeze(value) {
366
393
  }
367
394
  const DEFINITIONS = freeze({ ...FOUNDATION_ITEMS, ...PRODUCT_ITEMS });
368
395
  function describe(definition) {
369
- const { install: _install, configure: _configure, resolveTargets: _targets, resolveDependencies: _dependencies, resolvePackage: _package, fallbackDependency: _fallback, ...metadata } = definition;
396
+ const { install: _install, configure: _configure, resolveTargets: _targets, resolveEnvironment: _environment, resolveDependencies: _dependencies, resolvePackage: _package, fallbackDependency: _fallback, ...metadata } = definition;
370
397
  return structuredClone(metadata);
371
398
  }
372
399
  export function getStackDefinition(name) {
package/dist/standard.js CHANGED
@@ -8,7 +8,7 @@ import { registerVercelApplication } from './vercel.js';
8
8
  import { registerApplication } from './delivery.js';
9
9
  import { runStagedTransaction } from './transaction.js';
10
10
  import { createSetupPlan } from './capabilities.js';
11
- import { bootstrapWorkspace, assertBootstrapPlatform } from './scaffold/bootstrap.js';
11
+ import { bootstrapWorkspace, assertBootstrapPlatform, assertWorkspaceRuntime, } from './scaffold/bootstrap.js';
12
12
  import { composeStack } from './scaffold/compose.js';
13
13
  import { parseStandardOptions } from './registry/options.js';
14
14
  import { STANDARD_USAGE } from './registry/usage.js';
@@ -21,6 +21,7 @@ export async function scaffoldStandardProject(options, run = execFile) {
21
21
  return;
22
22
  }
23
23
  assertBootstrapPlatform(run);
24
+ assertWorkspaceRuntime(run);
24
25
  const root = options.root;
25
26
  const previousQuiet = process.env.ANHEDRAL_QUIET;
26
27
  if (options.json)
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const GENERATOR_VERSION = "0.10.0";
1
+ export declare const GENERATOR_VERSION = "0.10.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const GENERATOR_VERSION = '0.10.0';
1
+ export const GENERATOR_VERSION = '0.10.1';
package/dist/vinext.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { VINEXT_DEPENDENCIES } from './dependencies.js';
1
2
  import { existsSync, readFileSync, rmSync } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { writeFile } from './util.js';
@@ -8,28 +9,14 @@ export function configureVinext(root, workerName, bindings) {
8
9
  const pkg = JSON.parse(readFileSync(path.join(root, 'apps/web/package.json'), 'utf8'));
9
10
  delete pkg.dependencies?.['@opennextjs/cloudflare'];
10
11
  pkg.type = 'module';
11
- pkg.dependencies = {
12
- ...pkg.dependencies,
13
- vinext: '1.1.0',
14
- next: '16.4.0',
15
- react: '19.2.8',
16
- 'react-dom': '19.2.8',
17
- 'react-server-dom-webpack': '19.2.8',
18
- };
19
- pkg.devDependencies = {
20
- ...pkg.devDependencies,
21
- vite: '8.3.4',
22
- '@cloudflare/vite-plugin': '1.63.1',
23
- '@vitejs/plugin-rsc': '0.5.36',
24
- '@vitejs/plugin-react': '6.1.2',
25
- wrangler: '4.149.0',
26
- };
12
+ pkg.dependencies = { ...pkg.dependencies, ...VINEXT_DEPENDENCIES.dependencies };
13
+ pkg.devDependencies = { ...pkg.devDependencies, ...VINEXT_DEPENDENCIES.devDependencies };
27
14
  pkg.scripts = {
28
15
  ...pkg.scripts,
29
16
  dev: 'vite dev --host 127.0.0.1 --port 3000',
30
17
  build: 'vite build',
31
18
  start: 'vinext start',
32
- preview: 'wrangler dev --config dist/server/wrangler.json --persist-to .wrangler/state',
19
+ preview: 'wrangler dev --config dist/server/wrangler.json --port 3000 --persist-to .wrangler/state',
33
20
  deploy: 'vite build && wrangler deploy --config dist/server/wrangler.json',
34
21
  'build:worker': 'vite build',
35
22
  typecheck: 'wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts && tsc --noEmit',
@@ -77,7 +64,11 @@ export default config;
77
64
  const uiFile = path.join(root, 'packages/ui/package.json');
78
65
  if (existsSync(uiFile)) {
79
66
  const ui = JSON.parse(readFileSync(uiFile, 'utf8'));
80
- ui.dependencies = { ...ui.dependencies, react: '19.2.8', 'react-dom': '19.2.8' };
67
+ ui.dependencies = {
68
+ ...ui.dependencies,
69
+ react: VINEXT_DEPENDENCIES.dependencies.react,
70
+ 'react-dom': VINEXT_DEPENDENCIES.dependencies['react-dom'],
71
+ };
81
72
  writeFile(uiFile, JSON.stringify(ui, null, 2) + '\n');
82
73
  }
83
74
  }
@@ -1,4 +1,5 @@
1
- import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
1
+ import { readBoundedFile, MAX_PROJECT_JSON_BYTES } from './bounded-file.js';
2
+ import { existsSync, lstatSync, readdirSync } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { dependencyOrder } from './dependency-order.js';
4
5
  const dependencyFields = [
@@ -12,7 +13,7 @@ function readManifest(file, directory) {
12
13
  throw new Error(`Workspace manifest missing: ${directory}.`);
13
14
  if (lstatSync(file).isSymbolicLink())
14
15
  throw new Error(`Workspace symlinks are unsupported: ${directory}/package.json.`);
15
- const value = JSON.parse(readFileSync(file, 'utf8'));
16
+ const value = JSON.parse(readBoundedFile(file, MAX_PROJECT_JSON_BYTES).toString('utf8'));
16
17
  if (!value || typeof value !== 'object' || Array.isArray(value))
17
18
  throw new Error(`Invalid workspace manifest: ${directory}.`);
18
19
  const manifest = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anhedral",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Build and deliver applications with Cloudflare-first Vinext, selected services, optional Next.js on Vercel, and verified release evidence.",
5
5
  "type": "module",
6
6
  "exports": {