anhedral 0.9.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 (53) hide show
  1. package/README.md +6 -10
  2. package/assets/security/audit-deps.mjs +6 -1
  3. package/dist/bin.js +6 -2
  4. package/dist/bounded-file.d.ts +1 -0
  5. package/dist/bounded-file.js +1 -0
  6. package/dist/capabilities.d.ts +4 -4
  7. package/dist/capabilities.js +1 -1
  8. package/dist/dependencies.d.ts +32 -0
  9. package/dist/dependencies.js +47 -1
  10. package/dist/index.d.ts +0 -2
  11. package/dist/index.js +0 -1
  12. package/dist/platforms/desktop.js +4 -5
  13. package/dist/platforms/extension.js +9 -73
  14. package/dist/platforms/mobile.js +6 -47
  15. package/dist/readiness.js +12 -21
  16. package/dist/registry/add.d.ts +5 -9
  17. package/dist/registry/add.js +26 -58
  18. package/dist/registry/commands.js +5 -56
  19. package/dist/registry/receipt.d.ts +0 -1
  20. package/dist/registry/receipt.js +2 -22
  21. package/dist/registry/resolve.js +19 -13
  22. package/dist/registry/types.d.ts +1 -0
  23. package/dist/registry/usage.js +2 -3
  24. package/dist/render.d.ts +2 -0
  25. package/dist/render.js +10 -0
  26. package/dist/scaffold/api.js +20 -3
  27. package/dist/scaffold/auth.js +73 -17
  28. package/dist/scaffold/bootstrap.d.ts +3 -0
  29. package/dist/scaffold/bootstrap.js +15 -1
  30. package/dist/scaffold/database.js +12 -6
  31. package/dist/scaffold/documentation.js +11 -8
  32. package/dist/scaffold/environment.d.ts +2 -0
  33. package/dist/scaffold/environment.js +18 -1
  34. package/dist/scaffold/files.d.ts +1 -0
  35. package/dist/scaffold/files.js +15 -4
  36. package/dist/scaffold/services.js +37 -13
  37. package/dist/scaffold/stack-documentation.d.ts +3 -0
  38. package/dist/scaffold/stack-documentation.js +52 -0
  39. package/dist/scaffold/styling.d.ts +3 -0
  40. package/dist/scaffold/styling.js +84 -0
  41. package/dist/scaffold/web.js +31 -11
  42. package/dist/scaffold/workspace.js +38 -22
  43. package/dist/stack-registry.d.ts +2 -2
  44. package/dist/stack-registry.js +38 -11
  45. package/dist/standard.js +3 -8
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/dist/vinext.js +9 -18
  49. package/dist/workspace-graph.js +3 -2
  50. package/docs/delivery-workflow.md +1 -1
  51. package/package.json +1 -1
  52. package/dist/registry/remote.d.ts +0 -25
  53. package/dist/registry/remote.js +0 -168
package/README.md CHANGED
@@ -25,7 +25,7 @@ Starter prompt:
25
25
 
26
26
  Codex asks about the app, explains the stack, implements the product, configures selected services, and delivers into your authorized accounts. The CLI commands below are also available independently.
27
27
 
28
- Use Node.js 22.13+ and pnpm 10.34.5 for generated workspaces. Generate on Linux, macOS, or WSL.
28
+ Use Node.js 22.13+ on the 22.x line or Node.js 24+, with pnpm 10.34.5, for generated workspaces. Generate on Linux, macOS, or WSL.
29
29
 
30
30
  ```sh
31
31
  # Inspect a plan first: no project files or cloud resources are created
@@ -51,24 +51,20 @@ Anhedral Init is free and open source. Sponsorship helps fund ongoing developmen
51
51
 
52
52
  ## Choose your stack
53
53
 
54
- The bundled stack registry drives composition of every supported stack part. Run `anhedral registry list` or `anhedral registry view better-auth --json` to inspect items, destinations, dependencies and selection constraints. `new --dry-run --json` includes the resolved dependency graph. Scaffolding installs recipes in dependency order into `apps/*` and `packages/*`, validates local workspace references and cycles, and writes `anhedral.registry.json` with recipe versions and the resulting workspace graph. The same catalog is bundled with the plugin at build time.
54
+ The bundled stack registry drives composition of every supported stack part. Run `anhedral registry list` or `anhedral registry view better-auth --json` to inspect items, destinations, dependencies and selection constraints. `new --dry-run --json` includes the resolved dependency graph. Scaffolding installs recipes in dependency order into `apps/*` and `packages/*`, validates local workspace references and cycles, and writes `anhedral.registry.json` with recipe versions and the resulting workspace graph. The same catalog is bundled with the plugin at build time. Generated `docs/stack.md` lists the current resolved options, targets, environment names, required setup and verification; additions refresh it without rewriting the application README.
55
55
 
56
- Registry items are trusted recipes shipped in the CLI release: foundations, apps, services, shared packages and runtime integrations. They follow shadcn's source ownership and dependency composition practices; this catalog is an Anhedral schema, not a shadcn registry payload. Runtime-aware recipes coordinate bindings, manifests and auth routes across workspaces. Each registry entry owns its recipe, provider requirements, environment variables, bindings and composition rules. Resolved plans name the exact generated destinations, including runtime-dependent packages. Recipe versions follow the CLI release. The official shadcn bootstrap is pinned to 4.21.4; commit the generated source and lockfile to preserve the application artifact. Incremental `add` supports bundled items and versioned source registries from local files or HTTPS. Additions refuse to overwrite edited or untracked source; runtime relocation requires a deliberate migration.
56
+ Registry items are trusted recipes shipped in the CLI release: foundations, apps, services, shared packages and runtime integrations. They follow shadcn's source ownership and dependency composition practices; this catalog is an Anhedral schema, not a shadcn registry payload. Runtime-aware recipes coordinate bindings, manifests and auth routes across workspaces. Each registry entry owns its recipe, provider requirements, environment variables, bindings and composition rules. Resolved plans name the exact generated destinations, including runtime-dependent packages. Recipe versions follow the CLI release. The official shadcn bootstrap is pinned to 4.21.4; commit the generated source and lockfile to preserve the application artifact. Incremental `add` supports only the bundled catalog maintained and tested by Anhedral. External registries and customer-authored registry payloads are unsupported. Additions refuse to overwrite edited or untracked source; runtime relocation requires a deliberate migration.
57
57
 
58
58
  Use additions after initialization:
59
59
 
60
60
  ```sh
61
61
  anhedral add my-app resend stripe --dry-run --json
62
62
  anhedral add my-app resend stripe
63
- anhedral registry list --registry https://example.com/registry.json --json
64
- anhedral add my-app feature --registry https://example.com/registry.json --dry-run --json
63
+ anhedral registry list --json
64
+ anhedral registry view resend --json
65
65
  ```
66
66
 
67
- Review the dry-run file list and conflicts, then run the same addition without `--dry-run`. For external registries, pass the returned SHA-256 value with `--integrity` to require the reviewed payload. `--skip-install` still resolves the lockfile. Addition installs disable dependency lifecycle scripts; enable only reviewed native build scripts separately when required. Projects must have matching CLI versions and scaffold fingerprints; older projects need a deliberate migration. Existing provider identities stay intact, and no provider resources are created.
68
-
69
- Author registries with `anhedral registry build registry.source.json --output registry.json`: each file can declare a repository-relative `path` instead of embedded `content`. Build validates and embeds source before producing the distributable payload.
70
-
71
- External registries use Anhedral's declarative source schema: `schemaVersion: 1`, a registry `name`, and `items`. Each item declares `name`, semver `version`, `registryDependencies` (item names in the same registry), and `files` containing explicit `target` and UTF-8 `content`. Targets are confined to `apps/<name>/` and `packages/<name>/`; manifests must be private `@workspace` packages with versioned npm/workspace dependencies and no install hooks. Registries are limited to 2 MiB and 100 items. The receipt records the source, SHA-256 integrity, item versions, targets and generated-file hashes. This schema does not accept executable recipes or shadcn payloads. Review registry source as application code before using it.
67
+ Review the dry-run file list and conflicts, then run the same addition without `--dry-run`. A preview that needs a new web foundation reports `bootstrapRequired: true` and `filesChecked: false`; it describes the selection and targets without running the upstream generator or checking generated-file conflicts. The actual addition checks conflicts in an isolated transaction before committing files. `--skip-install` still resolves the lockfile; run `pnpm install --frozen-lockfile` afterward to prepare dependencies. Final installs run the bundled workspace's dependency preparation hooks, including WXT and Electron setup. Projects must have matching CLI versions and scaffold fingerprints; older projects and receipts containing external items need a deliberate migration. Existing provider identities stay intact, and no provider resources are created.
72
68
 
73
69
  Application recipes start from the official **shadcn pnpm/Turborepo monorepo**. The default creates **Vinext + React + TypeScript + Tailwind + shadcn/ui + Lucide**, targeting **Cloudflare Workers**. Select additional apps and services only when needed.
74
70
 
@@ -115,11 +115,16 @@ function validateAdvisory(advisory) {
115
115
  ensure(Array.isArray(advisory.findings), 'Malformed pnpm advisory');
116
116
  ensure(advisory.findings.length > 0, 'Malformed pnpm advisory');
117
117
  advisory.findings.forEach(validateFinding);
118
+ const versions = advisory.findings.map((finding) => finding.version);
119
+ ensure(new Set(versions).size === versions.length, 'Duplicate pnpm advisory versions');
118
120
  }
119
121
 
120
122
  function validateCount(advisories, severity, count) {
121
123
  ensure(Number.isInteger(count) && count >= 0, 'Malformed pnpm vulnerability counts');
122
- const details = advisories.filter((advisory) => advisory.severity === severity).length;
124
+ // The pnpm 10 registry endpoint counts affected versions, not unique advisory IDs.
125
+ const details = advisories
126
+ .filter((advisory) => advisory.severity === severity)
127
+ .reduce((total, advisory) => total + advisory.findings.length, 0);
123
128
  ensure(count === details, `Missing ${severity} advisory details: count ${count}, details ${details}`);
124
129
  }
125
130
 
package/dist/bin.js CHANGED
@@ -110,7 +110,7 @@ async function main() {
110
110
  return;
111
111
  }
112
112
  if (command !== 'new' && command !== 'init') {
113
- throw new Error(`Unknown command: ${command}. Use new, init, registry, doctor, repository, cloud, register, connect, provision, ship, status, manage, verify, rollback, adopt, recover, --help, or --version.`);
113
+ throw new Error(`Unknown command: ${command}. Use new, init, add, registry, doctor, repository, cloud, register, connect, provision, ship, status, manage, verify, rollback, adopt, recover, --help, or --version.`);
114
114
  }
115
115
  if (args.some((arg) => ['--help', '-h'].includes(arg))) {
116
116
  console.log(json ? JSON.stringify({ usage: STANDARD_USAGE }) : STANDARD_USAGE);
@@ -401,7 +401,11 @@ catch (error) {
401
401
  ? 'INVALID_ARGUMENT'
402
402
  : command === 'new' || command === 'init'
403
403
  ? 'GENERATION_FAILED'
404
- : 'UNKNOWN_COMMAND';
404
+ : command === 'add'
405
+ ? 'ADDITION_FAILED'
406
+ : command === 'registry'
407
+ ? 'REGISTRY_FAILED'
408
+ : 'UNKNOWN_COMMAND';
405
409
  console.error(json ? JSON.stringify({ error: message, code }) : `Error: ${message}`);
406
410
  process.exitCode = 1;
407
411
  }
@@ -1,2 +1,3 @@
1
+ export declare const MAX_PROJECT_JSON_BYTES: number;
1
2
  /** Reject devices, links and oversized or growing files before parsing local input. */
2
3
  export declare function readBoundedFile(filePath: string, maximumBytes: number): Buffer;
@@ -1,4 +1,5 @@
1
1
  import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from 'node:fs';
2
+ export const MAX_PROJECT_JSON_BYTES = 2 * 1024 * 1024;
2
3
  /** Reject devices, links and oversized or growing files before parsing local input. */
3
4
  export function readBoundedFile(filePath, maximumBytes) {
4
5
  const stat = lstatSync(filePath);
@@ -6,7 +6,7 @@ export declare const CAPABILITIES: Record<StandardProduct, Capability>;
6
6
  export declare const CAPABILITY_REGISTRY: {
7
7
  schemaVersion: number;
8
8
  cliVersion: string;
9
- capabilities: Record<"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", Capability>;
9
+ capabilities: Record<"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", Capability>;
10
10
  };
11
11
  export declare function createSetupPlan(options: StandardOptions, registry?: import("./registry/resolve.js").ResolvedStackRegistry): {
12
12
  schemaVersion: number;
@@ -15,10 +15,10 @@ export declare function createSetupPlan(options: StandardOptions, registry?: imp
15
15
  standard: string;
16
16
  root: string;
17
17
  name: string;
18
- products: ("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")[];
18
+ products: ("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")[];
19
19
  hosting: import("./registry/types.js").Hosting;
20
20
  registry: import("./registry/resolve.js").ResolvedStackRegistry;
21
- bootstrap: ("pnpm" | "shadcn@4.21.4" | "dlx" | "init" | "--monorepo" | "--template" | "next")[];
21
+ bootstrap: ("pnpm" | "next" | "shadcn@4.21.4" | "dlx" | "init" | "--monorepo" | "--template")[];
22
22
  dryRun: boolean;
23
23
  setup: {
24
24
  tools: string[];
@@ -32,7 +32,7 @@ export declare function createSetupPlan(options: StandardOptions, registry?: imp
32
32
  credentials: readonly string[];
33
33
  resources: readonly string[];
34
34
  verify: readonly string[];
35
- id: "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";
35
+ id: "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";
36
36
  }[];
37
37
  completion: string[];
38
38
  resourcesProvisioned: boolean;
@@ -61,7 +61,7 @@ export function createSetupPlan(options, registry = resolveStackRegistry(options
61
61
  tools: [
62
62
  ...new Set([
63
63
  'Git',
64
- 'Node.js 22.13+ for generated workspaces',
64
+ 'Node.js 22.13+ on 22.x, or Node.js 24+ for generated workspaces',
65
65
  PACKAGE_MANAGER.replace('@', ' '),
66
66
  'GitHub integration or gh',
67
67
  'Fallow',
@@ -2,14 +2,21 @@ export interface DependencyGroup {
2
2
  dependencies?: Record<string, string>;
3
3
  devDependencies?: Record<string, string>;
4
4
  }
5
+ export declare const NATIVE_ESLINT_VERSION = "10.0.3";
5
6
  export declare const DESKTOP_DEPENDENCIES: DependencyGroup;
6
7
  export declare const EXTENSION_DEPENDENCIES: DependencyGroup;
7
8
  export declare const MOBILE_APP_DEPENDENCIES: DependencyGroup;
8
9
  export declare const MOBILE_NATIVEWIND_DEPENDENCIES: DependencyGroup;
10
+ export declare const WORKSPACE_NODE_ENGINE = "^22.13.0 || >=24.0.0";
9
11
  export declare const PACKAGE_MANAGER = "pnpm@10.34.5";
10
12
  export declare const EAS_CLI_VERSION = "21.0.1";
11
13
  export declare const SHADCN_VERSION = "4.21.4";
14
+ export declare const NEXT_VERCEL_VERSION = "16.4.0";
15
+ export declare const NEXT_OPENNEXT_VERSION = "16.3.8";
12
16
  export declare const STACK_DEPENDENCIES: {
17
+ readonly 'better-auth': "1.7.7";
18
+ readonly '@better-auth/drizzle-adapter': "1.7.7";
19
+ readonly '@neondatabase/serverless': "1.2.0";
13
20
  readonly 'drizzle-orm': "0.45.4";
14
21
  readonly '@clerk/nextjs': "7.9.14";
15
22
  readonly '@clerk/backend': "3.23.2";
@@ -30,3 +37,28 @@ export declare const STACK_DEPENDENCIES: {
30
37
  readonly stripe: "20.4.1";
31
38
  readonly 'posthog-node': "5.55.2";
32
39
  };
40
+ /** Vinext's RSC/React versions form a separately verified compatibility group. */
41
+ export declare const VINEXT_DEPENDENCIES: DependencyGroup;
42
+ /** Separate HTTP and inspector ports let all selected Workers run together. */
43
+ export declare const WORKER_DEV_PORTS: {
44
+ readonly api: {
45
+ readonly http: 8787;
46
+ readonly inspector: 9231;
47
+ };
48
+ readonly realtime: {
49
+ readonly http: 8788;
50
+ readonly inspector: 9232;
51
+ };
52
+ readonly jobs: {
53
+ readonly http: 8789;
54
+ readonly inspector: 9233;
55
+ };
56
+ readonly scheduled: {
57
+ readonly http: 8790;
58
+ readonly inspector: 9234;
59
+ };
60
+ readonly workflows: {
61
+ readonly http: 8791;
62
+ readonly inspector: 9235;
63
+ };
64
+ };
@@ -1,3 +1,5 @@
1
+ // renovate: datasource=npm depName=eslint
2
+ export const NATIVE_ESLINT_VERSION = '10.0.3';
1
3
  export const DESKTOP_DEPENDENCIES = {
2
4
  dependencies: {
3
5
  // renovate: datasource=npm depName=react
@@ -24,7 +26,7 @@ export const DESKTOP_DEPENDENCIES = {
24
26
  electron: '43.7.8',
25
27
  // renovate: datasource=npm depName=electron-builder
26
28
  'electron-builder': '26.15.3',
27
- eslint: '10.0.3',
29
+ eslint: NATIVE_ESLINT_VERSION,
28
30
  // renovate: datasource=npm depName=tailwindcss
29
31
  tailwindcss: '4.1.18',
30
32
  // renovate: datasource=npm depName=postcss
@@ -129,12 +131,23 @@ export const MOBILE_NATIVEWIND_DEPENDENCIES = {
129
131
  tailwindcss: '3.4.19',
130
132
  },
131
133
  };
134
+ export const WORKSPACE_NODE_ENGINE = '^22.13.0 || >=24.0.0';
132
135
  export const PACKAGE_MANAGER = 'pnpm@10.34.5';
133
136
  // renovate: datasource=npm depName=eas-cli
134
137
  export const EAS_CLI_VERSION = '21.0.1';
135
138
  // renovate: datasource=npm depName=shadcn
136
139
  export const SHADCN_VERSION = '4.21.4';
140
+ // renovate: datasource=npm depName=next
141
+ export const NEXT_VERCEL_VERSION = '16.4.0';
142
+ // renovate: datasource=npm depName=next
143
+ export const NEXT_OPENNEXT_VERSION = '16.3.8';
137
144
  export const STACK_DEPENDENCIES = {
145
+ // renovate: datasource=npm depName=better-auth
146
+ 'better-auth': '1.7.7',
147
+ // renovate: datasource=npm depName=@better-auth/drizzle-adapter
148
+ '@better-auth/drizzle-adapter': '1.7.7',
149
+ // renovate: datasource=npm depName=@neondatabase/serverless
150
+ '@neondatabase/serverless': '1.2.0',
138
151
  // renovate: datasource=npm depName=drizzle-orm
139
152
  'drizzle-orm': '0.45.4',
140
153
  // renovate: datasource=npm depName=@clerk/nextjs
@@ -174,3 +187,36 @@ export const STACK_DEPENDENCIES = {
174
187
  // renovate: datasource=npm depName=posthog-node
175
188
  'posthog-node': '5.55.2',
176
189
  };
190
+ /** Vinext's RSC/React versions form a separately verified compatibility group. */
191
+ export const VINEXT_DEPENDENCIES = {
192
+ dependencies: {
193
+ // renovate: datasource=npm depName=vinext
194
+ vinext: '1.1.0',
195
+ next: NEXT_VERCEL_VERSION,
196
+ // renovate: datasource=npm depName=react
197
+ react: '19.2.8',
198
+ // renovate: datasource=npm depName=react-dom
199
+ 'react-dom': '19.2.8',
200
+ // renovate: datasource=npm depName=react-server-dom-webpack
201
+ 'react-server-dom-webpack': '19.2.8',
202
+ },
203
+ devDependencies: {
204
+ // renovate: datasource=npm depName=vite
205
+ vite: '8.3.4',
206
+ // renovate: datasource=npm depName=@cloudflare/vite-plugin
207
+ '@cloudflare/vite-plugin': '1.63.1',
208
+ // renovate: datasource=npm depName=@vitejs/plugin-rsc
209
+ '@vitejs/plugin-rsc': '0.5.36',
210
+ // renovate: datasource=npm depName=@vitejs/plugin-react
211
+ '@vitejs/plugin-react': '6.1.2',
212
+ wrangler: STACK_DEPENDENCIES.wrangler,
213
+ },
214
+ };
215
+ /** Separate HTTP and inspector ports let all selected Workers run together. */
216
+ export const WORKER_DEV_PORTS = {
217
+ api: { http: 8787, inspector: 9231 },
218
+ realtime: { http: 8788, inspector: 9232 },
219
+ jobs: { http: 8789, inspector: 9233 },
220
+ scheduled: { http: 8790, inspector: 9234 },
221
+ workflows: { http: 8791, inspector: 9235 },
222
+ };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,4 @@ export { inspectWorkspaceGraph } from './workspace-graph.js';
18
18
  export { resolveStackRegistry } from './registry/resolve.js';
19
19
  export type { ResolvedStackRegistry } from './registry/resolve.js';
20
20
  export { addStackItems } from './registry/add.js';
21
- export { buildSourceRegistry, loadSourceRegistry, validateSourceRegistry, resolveSourceItems, } from './registry/remote.js';
22
- export type { SourceRegistry, SourceItem, RemoteRegistry } from './registry/remote.js';
23
21
  export type { AddRequest } from './registry/add.js';
package/dist/index.js CHANGED
@@ -12,4 +12,3 @@ export { STACK_REGISTRY, STACK_ITEMS, getStackItem } from './stack-registry.js';
12
12
  export { inspectWorkspaceGraph } from './workspace-graph.js';
13
13
  export { resolveStackRegistry } from './registry/resolve.js';
14
14
  export { addStackItems } from './registry/add.js';
15
- export { buildSourceRegistry, loadSourceRegistry, validateSourceRegistry, resolveSourceItems, } from './registry/remote.js';
@@ -107,9 +107,6 @@ export default defineConfig({
107
107
  build: {
108
108
  outDir: 'dist/renderer',
109
109
  emptyOutDir: true,
110
- // Clerk is already loaded behind a dynamic import. Its optional account-management
111
- // surface is intentionally a large async chunk and does not inflate initial rendering.
112
- chunkSizeWarningLimit: 1600,
113
110
  },
114
111
  });
115
112
  `);
@@ -261,7 +258,7 @@ function isAllowedAppNavigation(value: string): boolean {
261
258
  }
262
259
 
263
260
  // Product-owned window options and navigation policy belong in this file.
264
- // Anhedral's managed main.ts imports this seam and wires lifecycle integrations around it.
261
+ // main.ts imports this seam and manages application lifecycle.
265
262
  export function createAppWindow(): BrowserWindow {
266
263
  const window = new BrowserWindow({
267
264
  width: 1180,
@@ -306,11 +303,13 @@ export function createAppWindow(): BrowserWindow {
306
303
  writeFile(path.join(dir, 'src/main/main.ts'), `import { app, BrowserWindow, session } from 'electron';
307
304
  import { createAppWindow } from './app-window.js';
308
305
 
309
-
310
306
  app.whenReady().then(() => {
311
307
  session.defaultSession.setPermissionCheckHandler(() => false);
312
308
  session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
313
309
  createAppWindow();
310
+ }).catch((error) => {
311
+ console.error('Unable to start the desktop app:', error);
312
+ app.quit();
314
313
  });
315
314
  app.on('window-all-closed', () => {
316
315
  if (process.platform !== 'darwin') app.quit();
@@ -1,3 +1,4 @@
1
+ import { NATIVE_ESLINT_VERSION } from '../dependencies.js';
1
2
  import path from 'node:path';
2
3
  import { writeReactLintConfig } from './lint.js';
3
4
  import { writeFile } from '../util.js';
@@ -41,11 +42,14 @@ function writePackageJson(dir, projectName) {
41
42
  typecheck: 'tsc --noEmit',
42
43
  lint: 'eslint src --max-warnings 0',
43
44
  },
44
- dependencies: EXTENSION_DEPENDENCIES.dependencies,
45
+ dependencies: {
46
+ ...EXTENSION_DEPENDENCIES.dependencies,
47
+ '@workspace/styling': 'workspace:*',
48
+ },
45
49
  devDependencies: {
46
50
  ...EXTENSION_DEPENDENCIES.devDependencies,
47
51
  '@workspace/eslint-config': 'workspace:*',
48
- eslint: '10.0.3',
52
+ eslint: NATIVE_ESLINT_VERSION,
49
53
  },
50
54
  }, null, 2) + '\n');
51
55
  }
@@ -55,13 +59,12 @@ function writeWxtConfig(dir, displayName) {
55
59
  const actionTitleLiteral = jsString(`Open ${displayName}`);
56
60
  writeFile(path.join(dir, 'wxt.config.ts'), `import { defineConfig } from 'wxt';
57
61
 
58
-
59
62
  export default defineConfig({
60
63
  srcDir: 'src',
64
+ dev: { server: { port: 3001, strictPort: true } },
61
65
  manifest: () => {
62
66
  const crxPublicKey = import.meta.env.VITE_CRX_PUBLIC_KEY || '';
63
67
 
64
-
65
68
  return {
66
69
  name: ${nameLiteral},
67
70
  description: ${descriptionLiteral},
@@ -176,51 +179,7 @@ function writeTailwindConfig(dir) {
176
179
  writeFile(path.join(dir, 'tailwind.config.cjs'), `/** @type {import('tailwindcss').Config} */
177
180
  module.exports = {
178
181
  content: ['./src/**/*.{ts,tsx,html}'],
179
- theme: {
180
- extend: {
181
- colors: {
182
- border: 'hsl(var(--border))',
183
- input: 'hsl(var(--input))',
184
- ring: 'hsl(var(--ring))',
185
- background: 'hsl(var(--background))',
186
- foreground: 'hsl(var(--foreground))',
187
- primary: {
188
- DEFAULT: 'hsl(var(--primary))',
189
- foreground: 'hsl(var(--primary-foreground))',
190
- },
191
- secondary: {
192
- DEFAULT: 'hsl(var(--secondary))',
193
- foreground: 'hsl(var(--secondary-foreground))',
194
- },
195
- destructive: {
196
- DEFAULT: 'hsl(var(--destructive))',
197
- foreground: 'hsl(var(--destructive-foreground))',
198
- },
199
- muted: {
200
- DEFAULT: 'hsl(var(--muted))',
201
- foreground: 'hsl(var(--muted-foreground))',
202
- },
203
- accent: {
204
- DEFAULT: 'hsl(var(--accent))',
205
- foreground: 'hsl(var(--accent-foreground))',
206
- },
207
- popover: {
208
- DEFAULT: 'hsl(var(--popover))',
209
- foreground: 'hsl(var(--popover-foreground))',
210
- },
211
- card: {
212
- DEFAULT: 'hsl(var(--card))',
213
- foreground: 'hsl(var(--card-foreground))',
214
- },
215
- },
216
- borderRadius: {
217
- lg: 'var(--radius)',
218
- md: 'calc(var(--radius) - 2px)',
219
- sm: 'calc(var(--radius) - 4px)',
220
- },
221
- },
222
- },
223
- plugins: [],
182
+ presets: [require('@workspace/styling/tailwind')],
224
183
  };
225
184
  `);
226
185
  }
@@ -259,7 +218,7 @@ export default defineBackground(() => {
259
218
  // Open the side panel when the extension icon is clicked.
260
219
  (chrome as ChromeWithSidePanel).sidePanel
261
220
  .setPanelBehavior({ openPanelOnActionClick: true })
262
- .catch(() => {});
221
+ .catch((error) => console.error('Unable to configure the side panel:', error));
263
222
  });
264
223
  `);
265
224
  }
@@ -328,7 +287,6 @@ export function SidePanelApp() {
328
287
  }
329
288
  }, []);
330
289
 
331
-
332
290
  return (
333
291
  <div style={{ padding: 24 }}>
334
292
  <h2>Welcome!</h2>
@@ -351,28 +309,6 @@ function writeStyles(dir) {
351
309
  @tailwind utilities;
352
310
 
353
311
  @layer base {
354
- :root {
355
- --background: 0 0% 100%;
356
- --foreground: 0 0% 9%;
357
- --card: 0 0% 100%;
358
- --card-foreground: 0 0% 9%;
359
- --popover: 0 0% 100%;
360
- --popover-foreground: 0 0% 9%;
361
- --primary: 0 0% 9%;
362
- --primary-foreground: 0 0% 98%;
363
- --secondary: 0 0% 96.1%;
364
- --secondary-foreground: 0 0% 9%;
365
- --muted: 0 0% 96.1%;
366
- --muted-foreground: 0 0% 45.1%;
367
- --accent: 0 0% 96.1%;
368
- --accent-foreground: 0 0% 9%;
369
- --destructive: 0 84.2% 60.2%;
370
- --destructive-foreground: 0 0% 98%;
371
- --border: 0 0% 89.8%;
372
- --input: 0 0% 89.8%;
373
- --ring: 0 0% 63.9%;
374
- --radius: 0.5rem;
375
- }
376
312
 
377
313
  * {
378
314
  border-color: hsl(var(--border));
@@ -1,3 +1,4 @@
1
+ import { NATIVE_ESLINT_VERSION } from '../dependencies.js';
1
2
  import path from 'node:path';
2
3
  import { writeReactLintConfig } from './lint.js';
3
4
  import { appendGitignore, writeFile } from '../util.js';
@@ -62,26 +63,9 @@ module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 }
62
63
 
63
64
  /** @type {import('tailwindcss').Config} */
64
65
  module.exports = {
65
- darkMode: 'class',
66
66
  content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
67
- presets: [require('nativewind/preset')],
68
- theme: {
69
- extend: {
70
- colors: {
71
- border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))',
72
- background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))',
73
- primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
74
- secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))' },
75
- destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))' },
76
- muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
77
- accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
78
- popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))' },
79
- card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
80
- },
81
- borderRadius: { lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)' },
82
- borderWidth: { hairline: hairlineWidth() },
83
- },
84
- },
67
+ presets: [require('nativewind/preset'), require('@workspace/styling/tailwind')],
68
+ theme: { extend: { borderWidth: { hairline: hairlineWidth() } } },
85
69
  future: { hoverOnlyWhenSupported: true },
86
70
  plugins: [require('tailwindcss-animate')],
87
71
  };
@@ -89,31 +73,6 @@ module.exports = {
89
73
  writeFile(path.join(dir, 'global.css'), `@tailwind base;
90
74
  @tailwind components;
91
75
  @tailwind utilities;
92
-
93
- @layer base {
94
- :root {
95
- --background: 0 0% 100%; --foreground: 0 0% 3.9%;
96
- --card: 0 0% 100%; --card-foreground: 0 0% 3.9%;
97
- --popover: 0 0% 100%; --popover-foreground: 0 0% 3.9%;
98
- --primary: 0 0% 9%; --primary-foreground: 0 0% 98%;
99
- --secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%;
100
- --muted: 0 0% 96.1%; --muted-foreground: 0 0% 45.1%;
101
- --accent: 0 0% 96.1%; --accent-foreground: 0 0% 9%;
102
- --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%;
103
- --border: 0 0% 89.8%; --input: 0 0% 89.8%; --ring: 0 0% 63%; --radius: 0.625rem;
104
- }
105
- .dark:root {
106
- --background: 0 0% 3.9%; --foreground: 0 0% 98%;
107
- --card: 0 0% 3.9%; --card-foreground: 0 0% 98%;
108
- --popover: 0 0% 3.9%; --popover-foreground: 0 0% 98%;
109
- --primary: 0 0% 98%; --primary-foreground: 0 0% 9%;
110
- --secondary: 0 0% 14.9%; --secondary-foreground: 0 0% 98%;
111
- --muted: 0 0% 14.9%; --muted-foreground: 0 0% 63.9%;
112
- --accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%;
113
- --destructive: 0 70.9% 59.4%; --destructive-foreground: 0 0% 98%;
114
- --border: 0 0% 14.9%; --input: 0 0% 14.9%; --ring: 300 0% 45%;
115
- }
116
- }
117
76
  `);
118
77
  }
119
78
  function expoScheme(projectName) {
@@ -160,11 +119,12 @@ export async function scaffoldExpo({ root, options }) {
160
119
  dependencies: {
161
120
  ...MOBILE_APP_DEPENDENCIES.dependencies,
162
121
  ...MOBILE_NATIVEWIND_DEPENDENCIES.dependencies,
122
+ '@workspace/styling': 'workspace:*',
163
123
  },
164
124
  devDependencies: {
165
125
  ...MOBILE_APP_DEPENDENCIES.devDependencies,
166
126
  '@workspace/eslint-config': 'workspace:*',
167
- eslint: '10.0.3',
127
+ eslint: NATIVE_ESLINT_VERSION,
168
128
  },
169
129
  }, null, 2) + '\n');
170
130
  writeFile(path.join(dir, 'app.json'), JSON.stringify({
@@ -200,7 +160,6 @@ export async function scaffoldExpo({ root, options }) {
200
160
  import { PortalHost } from '@rn-primitives/portal';
201
161
  import { Stack } from 'expo-router';
202
162
 
203
-
204
163
  export default function RootLayout() {
205
164
  return <><Stack /><PortalHost /></>;
206
165
  }
@@ -215,7 +174,7 @@ export default function HomeScreen() {
215
174
  <StatusBar style="auto" />
216
175
  <View style={styles.content}>
217
176
  <Text accessibilityRole="header" style={styles.title}>{${nameLiteral}}</Text>
218
- <Text selectable style={styles.subtitle}>Deterministic Expo application generated by Anhedral.</Text>
177
+ <Text selectable style={styles.subtitle}>Expo application scaffold generated by Anhedral.</Text>
219
178
  <Button onPress={() => void Linking.openURL('https://docs.expo.dev')}>Learn more</Button>
220
179
  </View>
221
180
  </ScrollView>
package/dist/readiness.js CHANGED
@@ -1,5 +1,5 @@
1
- import { assertSafeTarget } from './registry/receipt.js';
2
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { readBoundedFile, MAX_PROJECT_JSON_BYTES } from './bounded-file.js';
2
+ import { existsSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { isDeepStrictEqual } from 'node:util';
5
5
  import { inspectWorkspaceGraph } from './workspace-graph.js';
@@ -9,17 +9,22 @@ import { STANDARD_PRODUCTS } from './stack-registry.js';
9
9
  export function inspectProject(root) {
10
10
  const checks = [];
11
11
  const record = (id, ok, detail) => checks.push({ id, ok, detail });
12
- const read = (file) => JSON.parse(readFileSync(path.join(root, file), 'utf8'));
12
+ const read = (file) => JSON.parse(readBoundedFile(path.join(root, file), MAX_PROJECT_JSON_BYTES).toString('utf8'));
13
13
  record('workspace', existsSync(path.join(root, 'pnpm-workspace.yaml')), 'pnpm workspace manifest');
14
14
  record('lockfile', existsSync(path.join(root, 'pnpm-lock.yaml')), 'Committed dependency resolution required');
15
15
  try {
16
16
  const plan = read('anhedral.setup.json');
17
+ const recordedVersion = plan?.cliVersion;
18
+ const compatible = recordedVersion === CAPABILITY_REGISTRY.cliVersion;
19
+ record('cli-compatibility', compatible, /^\d+\.\d+\.\d+$/.test(recordedVersion ?? '')
20
+ ? `Use anhedral@${recordedVersion} for this project or review upgrade differences`
21
+ : 'Use the recorded project CLI version or review upgrade differences');
17
22
  validateSetupPlan(plan);
18
23
  const standard = read('anhedral.standard.json');
19
24
  record('setup-plan', true, `Requirements recorded by CLI ${plan.cliVersion}`);
20
25
  try {
21
26
  const installed = read('anhedral.registry.json');
22
- const { status, workspaces: _workspaces, files: _files, remoteItems, resourcesProvisioned, ...snapshot } = installed;
27
+ const { status, workspaces: _workspaces, files: _files, resourcesProvisioned, ...snapshot } = installed;
23
28
  if (status !== 'scaffolded' ||
24
29
  resourcesProvisioned !== false ||
25
30
  !isDeepStrictEqual(snapshot, plan.registry))
@@ -29,22 +34,6 @@ export function inspectProject(root) {
29
34
  if (!existsSync(path.join(root, target)))
30
35
  throw new Error(`Registry target missing: ${item.name}.`);
31
36
  }
32
- if (remoteItems !== undefined) {
33
- if (!Array.isArray(remoteItems))
34
- throw new Error('Invalid remote registry receipt.');
35
- for (const item of remoteItems) {
36
- if (!item ||
37
- typeof item.source !== 'string' ||
38
- typeof item.integrity !== 'string' ||
39
- !Array.isArray(item.targets))
40
- throw new Error('Invalid remote registry provenance.');
41
- for (const target of item.targets) {
42
- assertSafeTarget(root, target);
43
- if (!existsSync(path.join(root, target)))
44
- throw new Error('Missing remote registry target.');
45
- }
46
- }
47
- }
48
37
  record('stack-registry', true, 'Registry provenance and resolved targets match the setup plan');
49
38
  }
50
39
  catch {
@@ -57,7 +46,6 @@ export function inspectProject(root) {
57
46
  catch {
58
47
  record('workspace-graph', false, 'Invalid workspace manifests, references or dependency cycles');
59
48
  }
60
- record('cli-compatibility', plan.cliVersion === CAPABILITY_REGISTRY.cliVersion, 'Use the project CLI version or review upgrade differences');
61
49
  record('selection', standard.schemaVersion === 1 &&
62
50
  JSON.stringify(standard.products) === JSON.stringify(plan.products) &&
63
51
  standard.hosting ===
@@ -85,6 +73,9 @@ export function validateSetupPlan(plan) {
85
73
  value.registryVersion !== 1 ||
86
74
  typeof value.cliVersion !== 'string')
87
75
  throw new Error('Unsupported setup plan version.');
76
+ if (/^\d+\.\d+\.\d+$/.test(value.cliVersion) &&
77
+ value.cliVersion !== CAPABILITY_REGISTRY.cliVersion)
78
+ throw new Error(`Use anhedral@${value.cliVersion} for this project or review upgrade differences.`);
88
79
  if (!Array.isArray(value.products) ||
89
80
  !value.products.length ||
90
81
  !value.products.every((id) => STANDARD_PRODUCTS.includes(id)))