anhedral 0.1.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/CONTRIBUTING.md +70 -0
  3. package/LICENSE +21 -17
  4. package/README.md +151 -152
  5. package/SECURITY.md +35 -0
  6. package/SKILL.md +177 -0
  7. package/bin/anhedral.js +1 -1
  8. package/dist/architecture/contributions.d.ts +28 -0
  9. package/dist/architecture/contributions.js +118 -0
  10. package/dist/architecture/index.d.ts +12 -0
  11. package/dist/architecture/index.js +6 -0
  12. package/dist/architecture/manifest.d.ts +53 -0
  13. package/dist/architecture/manifest.js +283 -0
  14. package/dist/architecture/modules.d.ts +37 -0
  15. package/dist/architecture/modules.js +176 -0
  16. package/dist/architecture/plan.d.ts +58 -0
  17. package/dist/architecture/plan.js +145 -0
  18. package/dist/architecture/templates.d.ts +10 -0
  19. package/dist/architecture/templates.js +23 -0
  20. package/dist/bin.d.ts +2 -0
  21. package/dist/bin.js +167 -0
  22. package/dist/cli.d.ts +24 -2
  23. package/dist/cli.js +186 -21
  24. package/dist/composers/root.d.ts +6 -0
  25. package/dist/composers/root.js +34 -0
  26. package/dist/dependencies.d.ts +29 -9
  27. package/dist/dependencies.js +220 -60
  28. package/dist/index.d.ts +8 -2
  29. package/dist/index.js +5 -31
  30. package/dist/print.js +15 -0
  31. package/dist/prompts.d.ts +5 -0
  32. package/dist/prompts.js +19 -0
  33. package/dist/render.d.ts +11 -0
  34. package/dist/render.js +74 -0
  35. package/dist/scaffold.d.ts +56 -12
  36. package/dist/scaffold.js +1466 -1158
  37. package/dist/template-source.d.ts +20 -0
  38. package/dist/template-source.js +180 -0
  39. package/dist/templates/api.d.ts +2 -0
  40. package/dist/templates/api.js +3114 -0
  41. package/dist/templates/assets-private-proxy.d.ts +3 -0
  42. package/dist/templates/assets-private-proxy.js +277 -0
  43. package/dist/templates/desktop.d.ts +2 -0
  44. package/dist/templates/desktop.js +572 -0
  45. package/dist/templates/extension.d.ts +1 -1
  46. package/dist/templates/extension.js +322 -248
  47. package/dist/templates/mobile.d.ts +2 -0
  48. package/dist/templates/mobile.js +720 -0
  49. package/dist/templates/shared.d.ts +2 -0
  50. package/dist/templates/shared.js +503 -0
  51. package/dist/templates/web.d.ts +2 -0
  52. package/dist/templates/web.js +412 -0
  53. package/dist/toolchain.d.ts +0 -8
  54. package/dist/toolchain.js +0 -21
  55. package/dist/transaction.d.ts +22 -0
  56. package/dist/transaction.js +822 -0
  57. package/dist/ui.d.ts +33 -0
  58. package/dist/ui.js +199 -0
  59. package/dist/util.d.ts +1 -5
  60. package/dist/util.js +70 -159
  61. package/dist/version.d.ts +2 -0
  62. package/dist/version.js +2 -0
  63. package/dist/workspace-config.d.ts +11 -0
  64. package/dist/workspace-config.js +372 -0
  65. package/docs/conventions.md +89 -0
  66. package/docs/output-tree-contract.md +442 -0
  67. package/package.json +56 -13
  68. package/references/manual-scaffolding.md +386 -0
  69. package/templates/api-fastify/apps/api/tsconfig.json +20 -0
  70. package/templates/catalog.json +23 -0
  71. package/templates/db-drizzle/packages/db/tsconfig.json +14 -0
  72. package/templates/desktop-electron/apps/desktop/tsconfig.json +37 -0
  73. package/templates/extension-wxt/apps/extension/tsconfig.json +41 -0
  74. package/templates/mobile-expo/apps/mobile/expo-env.d.ts +1 -0
  75. package/templates/web-next/apps/web/next-env.d.ts +6 -0
  76. package/dist/branding.d.ts +0 -5
  77. package/dist/branding.js +0 -17
  78. package/dist/commands.d.ts +0 -7
  79. package/dist/commands.js +0 -24
  80. package/dist/templates/backend.d.ts +0 -2
  81. package/dist/templates/backend.js +0 -2115
  82. package/dist/templates/frontend.d.ts +0 -2
  83. package/dist/templates/frontend.js +0 -1268
@@ -0,0 +1,412 @@
1
+ import path from 'node:path';
2
+ import { writeFile } from '../util.js';
3
+ import { anhedralPrint } from '../print.js';
4
+ import { WEB_APP_DEPENDENCIES } from '../dependencies.js';
5
+ import { childPackageName, jsString } from '../render.js';
6
+ export async function scaffoldWeb(root, options) {
7
+ const dir = path.join(root, 'apps/web');
8
+ anhedralPrint.section('Web (Next.js + shadcn/ui)');
9
+ anhedralPrint.step('Materializing bundled Next.js + shadcn substrate');
10
+ anhedralPrint.done('Next.js + shadcn substrate materialized');
11
+ anhedralPrint.step('Writing web app customizations');
12
+ writePackageJson(dir, options);
13
+ writeTsConfig(dir);
14
+ writeNextConfig(dir);
15
+ writePostcssConfig(dir);
16
+ writeShadcnConfig(dir);
17
+ writeEnvExample(dir, options);
18
+ writeUiFiles(dir, options);
19
+ writeAppFiles(dir, options);
20
+ anhedralPrint.done('Next.js web app written');
21
+ }
22
+ function writePackageJson(dir, options) {
23
+ const dependencies = { ...(WEB_APP_DEPENDENCIES.dependencies ?? {}) };
24
+ if (!options.apps.api)
25
+ delete dependencies['@shared/api-client'];
26
+ if (!options.features.billing)
27
+ delete dependencies['@shared/realtime'];
28
+ if (!options.features.auth) {
29
+ delete dependencies['@clerk/nextjs'];
30
+ delete dependencies['@clerk/ui'];
31
+ }
32
+ writeFile(path.join(dir, 'package.json'), JSON.stringify({
33
+ name: childPackageName(options.projectName, 'web'),
34
+ version: '0.1.0',
35
+ private: true,
36
+ type: 'module',
37
+ scripts: {
38
+ dev: 'next dev',
39
+ build: 'next build',
40
+ start: 'next start',
41
+ typecheck: 'tsc --noEmit',
42
+ },
43
+ dependencies,
44
+ devDependencies: WEB_APP_DEPENDENCIES.devDependencies,
45
+ }, null, 2) + '\n');
46
+ }
47
+ function writeTsConfig(dir) {
48
+ writeFile(path.join(dir, 'tsconfig.json'), JSON.stringify({
49
+ compilerOptions: {
50
+ target: 'ES2017',
51
+ lib: ['dom', 'dom.iterable', 'esnext'],
52
+ allowJs: true,
53
+ skipLibCheck: true,
54
+ strict: true,
55
+ noEmit: true,
56
+ esModuleInterop: true,
57
+ module: 'esnext',
58
+ moduleResolution: 'bundler',
59
+ resolveJsonModule: true,
60
+ isolatedModules: true,
61
+ jsx: 'react-jsx',
62
+ incremental: true,
63
+ plugins: [{ name: 'next' }],
64
+ paths: { '@/*': ['./*'] },
65
+ },
66
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts', '.next/dev/types/**/*.ts'],
67
+ exclude: ['node_modules'],
68
+ }, null, 2) + '\n');
69
+ }
70
+ function writeNextConfig(dir) {
71
+ writeFile(path.join(dir, 'next.config.ts'), `import type { NextConfig } from 'next';
72
+
73
+ const nextConfig: NextConfig = {
74
+ async rewrites() {
75
+ if (process.env.NODE_ENV !== 'development') return [];
76
+ return [{ source: '/api/:path*', destination: 'http://localhost:8787/api/:path*' }];
77
+ },
78
+ };
79
+
80
+ export default nextConfig;
81
+ `);
82
+ }
83
+ function writePostcssConfig(dir) {
84
+ writeFile(path.join(dir, 'postcss.config.mjs'), `const config = {
85
+ plugins: {
86
+ '@tailwindcss/postcss': {},
87
+ },
88
+ };
89
+
90
+ export default config;
91
+ `);
92
+ }
93
+ function writeShadcnConfig(dir) {
94
+ writeFile(path.join(dir, 'components.json'), JSON.stringify({
95
+ $schema: 'https://ui.shadcn.com/schema.json',
96
+ style: 'new-york',
97
+ rsc: true,
98
+ tsx: true,
99
+ tailwind: {
100
+ config: '',
101
+ css: 'app/globals.css',
102
+ baseColor: 'neutral',
103
+ cssVariables: true,
104
+ prefix: '',
105
+ },
106
+ aliases: {
107
+ components: '@/components',
108
+ utils: '@/lib/utils',
109
+ ui: '@/components/ui',
110
+ lib: '@/lib',
111
+ hooks: '@/hooks',
112
+ },
113
+ iconLibrary: 'lucide',
114
+ }, null, 2) + '\n');
115
+ }
116
+ function writeEnvExample(dir, options) {
117
+ const lines = [
118
+ options.apps.api ? 'NEXT_PUBLIC_API_URL=http://localhost:8787/api' : null,
119
+ options.features.auth ? 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***' : null,
120
+ ].filter((value) => value !== null);
121
+ writeFile(path.join(dir, '.env.example'), lines.join('\n') + (lines.length > 0 ? '\n' : ''));
122
+ }
123
+ function writeUiFiles(dir, options) {
124
+ writeFile(path.join(dir, 'lib/utils.ts'), `import { clsx, type ClassValue } from 'clsx';
125
+ import { twMerge } from 'tailwind-merge';
126
+
127
+ export function cn(...inputs: ClassValue[]) {
128
+ return twMerge(clsx(inputs));
129
+ }
130
+ `);
131
+ if (options.apps.api)
132
+ writeFile(path.join(dir, 'lib/api.ts'), `import { ApiClient, normalizeApiBaseUrl } from '@shared/api-client';
133
+
134
+ function normalizeWebApiBaseUrl(value: string): string {
135
+ const candidate = value.trim();
136
+ if (!candidate.startsWith('/')) return normalizeApiBaseUrl(candidate, 'NEXT_PUBLIC_API_URL');
137
+ if (candidate.startsWith('//') || candidate.includes('\\\\')) {
138
+ throw new Error('NEXT_PUBLIC_API_URL must be a single-slash root-relative path or an absolute URL');
139
+ }
140
+ if (typeof window === 'undefined') {
141
+ throw new Error('A root-relative NEXT_PUBLIC_API_URL can only be resolved in the browser');
142
+ }
143
+ return normalizeApiBaseUrl(new URL(candidate, window.location.origin).toString(), 'NEXT_PUBLIC_API_URL');
144
+ }
145
+
146
+ function apiBaseUrl(): string {
147
+ const configured = process.env.NEXT_PUBLIC_API_URL?.trim();
148
+ const candidate = configured || (process.env.NODE_ENV === 'development' ? 'http://localhost:8787/api' : '/api');
149
+ const normalized = normalizeWebApiBaseUrl(candidate);
150
+ const url = new URL(normalized);
151
+ if (process.env.NODE_ENV === 'production' && url.protocol !== 'https:') {
152
+ throw new Error('NEXT_PUBLIC_API_URL must use https: in production');
153
+ }
154
+ return normalized;
155
+ }
156
+
157
+ export function createApiClient(getToken?: () => Promise<string | null>) {
158
+ return new ApiClient({
159
+ baseUrl: apiBaseUrl(),
160
+ getToken: getToken ?? (async () => null),
161
+ });
162
+ }
163
+ `);
164
+ if (options.apps.api && options.features.auth) {
165
+ writeFile(path.join(dir, 'hooks/use-api-client.ts'), `'use client';
166
+
167
+ import { useAuth } from '@clerk/nextjs';
168
+ import { useMemo } from 'react';
169
+ import { createApiClient } from '@/lib/api';
170
+
171
+ export function useApiClient() {
172
+ const { getToken } = useAuth();
173
+ return useMemo(() => createApiClient(() => getToken()), [getToken]);
174
+ }
175
+ `);
176
+ }
177
+ if (options.features.billing) {
178
+ writeFile(path.join(dir, 'hooks/use-entitlement.ts'), `'use client';
179
+
180
+ import { subscribeToSubscriptionChanges } from '@shared/realtime';
181
+ import { useAuth } from '@clerk/nextjs';
182
+ import * as React from 'react';
183
+ import { useApiClient } from './use-api-client';
184
+
185
+ export function useEntitlement() {
186
+ const api = useApiClient();
187
+ const { isLoaded, isSignedIn, userId } = useAuth();
188
+ const [state, setState] = React.useState<Awaited<ReturnType<typeof api.getEntitlement>> | null>(null);
189
+ const [error, setError] = React.useState<string | null>(null);
190
+ const revision = React.useRef(0);
191
+ const refresh = React.useCallback(async () => {
192
+ if (!isLoaded || !isSignedIn) return;
193
+ try {
194
+ const next = await api.getEntitlement();
195
+ revision.current = Math.max(revision.current, next.revision);
196
+ setState(next);
197
+ setError(null);
198
+ } catch (cause) {
199
+ setError(cause instanceof Error ? cause.message : 'Unable to load subscription');
200
+ }
201
+ }, [api, isLoaded, isSignedIn]);
202
+
203
+ React.useEffect(() => { void refresh(); }, [refresh]);
204
+ React.useEffect(() => {
205
+ const onVisibility = () => { if (document.visibilityState === 'visible') void refresh(); };
206
+ document.addEventListener('visibilitychange', onVisibility);
207
+ return () => document.removeEventListener('visibilitychange', onVisibility);
208
+ }, [refresh]);
209
+ React.useEffect(() => {
210
+ if (!userId || !isSignedIn) return;
211
+ return subscribeToSubscriptionChanges({
212
+ userId,
213
+ getTokenRequest: () => api.getRealtimeToken(),
214
+ onChange: (nextRevision) => {
215
+ if (nextRevision > revision.current) void refresh();
216
+ },
217
+ onError: (cause) => setError(cause.message),
218
+ });
219
+ }, [api, isSignedIn, refresh, userId]);
220
+ return { entitlement: state, error, refresh };
221
+ }
222
+ `);
223
+ writeFile(path.join(dir, 'components/subscription-status.tsx'), `'use client';
224
+
225
+ import { useEntitlement } from '@/hooks/use-entitlement';
226
+
227
+ export function SubscriptionStatus() {
228
+ const { entitlement, error } = useEntitlement();
229
+ if (error) return <p role="alert" className="text-sm text-red-700">{error}</p>;
230
+ if (!entitlement) return <p className="text-sm text-muted-foreground">Loading subscription…</p>;
231
+ return <p className="text-sm text-muted-foreground">Plan: {entitlement.entitlement} ({entitlement.status})</p>;
232
+ }
233
+ `);
234
+ }
235
+ if (options.features.auth) {
236
+ writeFile(path.join(dir, 'components/account-actions.tsx'), `'use client';
237
+
238
+ import { Show, SignInButton, UserButton } from '@clerk/nextjs';
239
+ import { Button } from '@/components/ui/button';
240
+
241
+ export function AccountActions() {
242
+ return (
243
+ <>
244
+ <Show when="signed-out">
245
+ <SignInButton mode="modal">
246
+ <Button variant="outline">Sign in</Button>
247
+ </SignInButton>
248
+ </Show>
249
+ <Show when="signed-in">
250
+ <UserButton />
251
+ </Show>
252
+ </>
253
+ );
254
+ }
255
+ `);
256
+ }
257
+ writeFile(path.join(dir, 'components/ui/button.tsx'), `import * as React from 'react';
258
+ import { cn } from '@/lib/utils';
259
+
260
+ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
261
+ variant?: 'default' | 'outline';
262
+ };
263
+
264
+ export function Button({ className, variant = 'default', type = 'button', ...props }: ButtonProps) {
265
+ return (
266
+ <button
267
+ type={type}
268
+ className={cn(
269
+ 'inline-flex h-9 items-center justify-center rounded-md px-4 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
270
+ variant === 'outline' ? 'border border-input bg-background hover:bg-accent' : 'bg-primary text-primary-foreground hover:bg-primary/90',
271
+ className,
272
+ )}
273
+ {...props}
274
+ />
275
+ );
276
+ }
277
+ `);
278
+ writeFile(path.join(dir, 'components/ui/card.tsx'), `import * as React from 'react';
279
+ import { cn } from '@/lib/utils';
280
+
281
+ export function Card({ className, ...props }: React.ComponentProps<'div'>) {
282
+ return <div className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />;
283
+ }
284
+
285
+ export function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
286
+ return <div className={cn('flex flex-col gap-1.5 p-6', className)} {...props} />;
287
+ }
288
+
289
+ export function CardTitle({ className, ...props }: React.ComponentProps<'h3'>) {
290
+ return <h3 className={cn('text-2xl font-semibold leading-none', className)} {...props} />;
291
+ }
292
+
293
+ export function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
294
+ return <div className={cn('p-6 pt-0', className)} {...props} />;
295
+ }
296
+ `);
297
+ }
298
+ function writeAppFiles(dir, options) {
299
+ const { displayName } = options;
300
+ const displayNameLiteral = jsString(displayName);
301
+ const descriptionLiteral = jsString(`${displayName} web app`);
302
+ const accountActionsImport = options.features.auth
303
+ ? "import { AccountActions } from '@/components/account-actions';\n"
304
+ : '';
305
+ const accountAction = options.features.auth
306
+ ? `{process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ? (
307
+ <AccountActions />
308
+ ) : (
309
+ <Button variant="outline" disabled>Configure Clerk to sign in</Button>
310
+ )}`
311
+ : '<Button variant="outline">View account</Button>';
312
+ const clerkStyles = options.features.auth ? '@import "@clerk/ui/themes/shadcn.css";\n' : '';
313
+ writeFile(path.join(dir, 'app/globals.css'), `@import "tailwindcss";
314
+ ${clerkStyles}
315
+
316
+ :root {
317
+ --background: oklch(1 0 0);
318
+ --foreground: oklch(0.145 0 0);
319
+ --card: oklch(1 0 0);
320
+ --card-foreground: oklch(0.145 0 0);
321
+ --primary: oklch(0.205 0 0);
322
+ --primary-foreground: oklch(0.985 0 0);
323
+ --muted: oklch(0.97 0 0);
324
+ --muted-foreground: oklch(0.556 0 0);
325
+ --accent: oklch(0.97 0 0);
326
+ --border: oklch(0.922 0 0);
327
+ --input: oklch(0.922 0 0);
328
+ }
329
+
330
+ @theme inline {
331
+ --color-background: var(--background);
332
+ --color-foreground: var(--foreground);
333
+ --color-card: var(--card);
334
+ --color-card-foreground: var(--card-foreground);
335
+ --color-primary: var(--primary);
336
+ --color-primary-foreground: var(--primary-foreground);
337
+ --color-muted: var(--muted);
338
+ --color-muted-foreground: var(--muted-foreground);
339
+ --color-accent: var(--accent);
340
+ --color-border: var(--border);
341
+ --color-input: var(--input);
342
+ }
343
+
344
+ body {
345
+ background: var(--background);
346
+ color: var(--foreground);
347
+ font-family: Arial, Helvetica, sans-serif;
348
+ }
349
+ `);
350
+ const authImport = options.features.auth
351
+ ? "import { ClerkProvider } from '@clerk/nextjs';\nimport { shadcn } from '@clerk/ui/themes';\n"
352
+ : '';
353
+ const providers = options.features.auth ? `function Providers({ children }: Readonly<{ children: React.ReactNode }>) {
354
+ const publishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
355
+ if (!publishableKey) return <>{children}</>;
356
+ return <ClerkProvider publishableKey={publishableKey} appearance={{ theme: shadcn }}>{children}</ClerkProvider>;
357
+ }` : `function Providers({ children }: Readonly<{ children: React.ReactNode }>) {
358
+ return <>{children}</>;
359
+ }`;
360
+ writeFile(path.join(dir, 'app/layout.tsx'), `import type { Metadata } from 'next';
361
+ ${authImport}import './globals.css';
362
+
363
+ export const metadata: Metadata = {
364
+ title: ${displayNameLiteral},
365
+ description: ${descriptionLiteral},
366
+ };
367
+
368
+ ${providers}
369
+
370
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
371
+ return (
372
+ <html lang="en">
373
+ <body>
374
+ <Providers>{children}</Providers>
375
+ </body>
376
+ </html>
377
+ );
378
+ }
379
+ `);
380
+ const subscriptionImport = options.features.billing ? "import { SubscriptionStatus } from '@/components/subscription-status';\n" : '';
381
+ const subscriptionStatus = options.features.billing
382
+ ? ' {process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ? <SubscriptionStatus /> : null}\n'
383
+ : '';
384
+ writeFile(path.join(dir, 'app/page.tsx'), `${accountActionsImport}${subscriptionImport}import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
385
+ import { Button } from '@/components/ui/button';
386
+
387
+ export default function Home() {
388
+ return (
389
+ <main className="mx-auto flex min-h-screen max-w-5xl flex-col gap-8 px-6 py-10">
390
+ <section className="flex flex-col gap-3">
391
+ <h1 className="text-4xl font-semibold tracking-normal">{${displayNameLiteral}}</h1>
392
+ <p className="max-w-2xl text-muted-foreground">
393
+ ${options.apps.api ? 'Next.js + shadcn/ui web client connected to the shared Fastify API.' : 'Next.js + shadcn/ui web application.'}
394
+ </p>
395
+ <div className="flex gap-3">
396
+ <Button>Open app</Button>
397
+ ${accountAction}
398
+ </div>
399
+ ${subscriptionStatus} </section>
400
+ <Card>
401
+ <CardHeader>
402
+ <CardTitle>Shared modules</CardTitle>
403
+ </CardHeader>
404
+ <CardContent className="text-sm text-muted-foreground">
405
+ ${options.apps.api ? 'Contracts, API access, and configuration are shared through workspace packages.' : 'Add modules later with anhedral add; user-owned source remains untouched.'}
406
+ </CardContent>
407
+ </Card>
408
+ </main>
409
+ );
410
+ }
411
+ `);
412
+ }
@@ -1,11 +1,3 @@
1
1
  export declare const TOOLCHAIN_CHANNELS: readonly ["latest", "stable"];
2
2
  export type ToolchainChannel = (typeof TOOLCHAIN_CHANNELS)[number];
3
- export type ToolchainSpec = {
4
- verifiedAt: string | null;
5
- shadcn: string;
6
- reactNativeReusables: string;
7
- wxt: string;
8
- };
9
3
  export declare function resolveToolchainChannel(value: string | undefined): ToolchainChannel;
10
- export declare function resolveToolchain(channel: ToolchainChannel): ToolchainSpec;
11
- export declare function toolPackageRef(pkg: string, versionTag: string): string;
package/dist/toolchain.js CHANGED
@@ -1,19 +1,4 @@
1
- import { TOOLCHAIN_DEPENDENCIES, VERIFIED_AT } from './dependencies.js';
2
1
  export const TOOLCHAIN_CHANNELS = ['latest', 'stable'];
3
- const TOOLCHAIN_SPECS = {
4
- latest: {
5
- verifiedAt: null,
6
- shadcn: 'latest',
7
- reactNativeReusables: 'latest',
8
- wxt: 'latest',
9
- },
10
- stable: {
11
- verifiedAt: VERIFIED_AT,
12
- shadcn: TOOLCHAIN_DEPENDENCIES.shadcn,
13
- reactNativeReusables: TOOLCHAIN_DEPENDENCIES.reactNativeReusables,
14
- wxt: TOOLCHAIN_DEPENDENCIES.wxt,
15
- },
16
- };
17
2
  export function resolveToolchainChannel(value) {
18
3
  if (!value)
19
4
  return 'stable';
@@ -21,9 +6,3 @@ export function resolveToolchainChannel(value) {
21
6
  return value;
22
7
  throw new Error(`--toolchain must be one of: ${TOOLCHAIN_CHANNELS.join(', ')}`);
23
8
  }
24
- export function resolveToolchain(channel) {
25
- return TOOLCHAIN_SPECS[channel];
26
- }
27
- export function toolPackageRef(pkg, versionTag) {
28
- return `${pkg}@${versionTag}`;
29
- }
@@ -0,0 +1,22 @@
1
+ /** Recover an interrupted commit while holding the same lock used by normal transactions. */
2
+ export declare function recoverInterruptedTransaction(root: string): boolean;
3
+ /** The filesystem commit succeeded, but a follow-up action such as install failed. */
4
+ export declare class PostCommitError extends Error {
5
+ readonly committedPaths: readonly string[];
6
+ constructor(committedPaths: readonly string[], cause: unknown);
7
+ }
8
+ /**
9
+ * Build changes away from the destination and install only the declared paths.
10
+ * Every replaced path is journaled first, so errors and later invocations can
11
+ * restore the original workspace.
12
+ */
13
+ export declare function runStagedTransaction(root: string, options: {
14
+ commitPaths: string[];
15
+ deletePaths?: string[];
16
+ seedPaths?: string[];
17
+ dryRun?: boolean;
18
+ prepare?: () => boolean | void | Promise<boolean | void>;
19
+ afterCommit?: () => void | Promise<void>;
20
+ onPlan?: (paths: readonly string[]) => void;
21
+ build: (stageRoot: string) => Promise<void>;
22
+ }): Promise<readonly string[]>;