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.
- package/README.md +6 -10
- package/assets/security/audit-deps.mjs +6 -1
- package/dist/bin.js +6 -2
- package/dist/bounded-file.d.ts +1 -0
- package/dist/bounded-file.js +1 -0
- package/dist/capabilities.d.ts +4 -4
- package/dist/capabilities.js +1 -1
- package/dist/dependencies.d.ts +32 -0
- package/dist/dependencies.js +47 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/dist/platforms/desktop.js +4 -5
- package/dist/platforms/extension.js +9 -73
- package/dist/platforms/mobile.js +6 -47
- package/dist/readiness.js +12 -21
- package/dist/registry/add.d.ts +5 -9
- package/dist/registry/add.js +26 -58
- package/dist/registry/commands.js +5 -56
- package/dist/registry/receipt.d.ts +0 -1
- package/dist/registry/receipt.js +2 -22
- package/dist/registry/resolve.js +19 -13
- package/dist/registry/types.d.ts +1 -0
- package/dist/registry/usage.js +2 -3
- package/dist/render.d.ts +2 -0
- package/dist/render.js +10 -0
- package/dist/scaffold/api.js +20 -3
- package/dist/scaffold/auth.js +73 -17
- package/dist/scaffold/bootstrap.d.ts +3 -0
- package/dist/scaffold/bootstrap.js +15 -1
- package/dist/scaffold/database.js +12 -6
- package/dist/scaffold/documentation.js +11 -8
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +18 -1
- package/dist/scaffold/files.d.ts +1 -0
- package/dist/scaffold/files.js +15 -4
- package/dist/scaffold/services.js +37 -13
- package/dist/scaffold/stack-documentation.d.ts +3 -0
- package/dist/scaffold/stack-documentation.js +52 -0
- package/dist/scaffold/styling.d.ts +3 -0
- package/dist/scaffold/styling.js +84 -0
- package/dist/scaffold/web.js +31 -11
- package/dist/scaffold/workspace.js +38 -22
- package/dist/stack-registry.d.ts +2 -2
- package/dist/stack-registry.js +38 -11
- package/dist/standard.js +3 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.js +9 -18
- package/dist/workspace-graph.js +3 -2
- package/docs/delivery-workflow.md +1 -1
- package/package.json +1 -1
- package/dist/registry/remote.d.ts +0 -25
- package/dist/registry/remote.js +0 -168
|
@@ -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
|
-
|
|
12
|
+
function securityOverrides(options) {
|
|
12
13
|
const has = (value) => options.products.includes(value);
|
|
13
|
-
|
|
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
|
-
|
|
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:
|
|
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: {
|
|
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
|
-
|
|
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',
|
package/dist/stack-registry.d.ts
CHANGED
|
@@ -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" | "
|
|
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" | "
|
|
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;
|
package/dist/stack-registry.js
CHANGED
|
@@ -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:
|
|
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',
|
|
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:
|
|
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:
|
|
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>[]) {
|
|
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', [], [], [], [
|
|
329
|
-
|
|
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,25 +8,20 @@ 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 } 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';
|
|
15
15
|
export { parseStandardOptions, STANDARD_USAGE };
|
|
16
16
|
export { STANDARD_PRODUCTS } from './stack-registry.js';
|
|
17
|
-
|
|
18
|
-
function assertBootstrapPlatform(run) {
|
|
19
|
-
if (process.platform === 'win32' && run === defaultRunner) {
|
|
20
|
-
throw new Error('Native Windows initialization is blocked because the upstream shadcn monorepo bootstrap writes component files outside the project. Run Anhedral inside WSL. Planning and doctor remain available on Windows.');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function scaffoldStandardProject(options, run = defaultRunner) {
|
|
17
|
+
export async function scaffoldStandardProject(options, run = execFile) {
|
|
24
18
|
const plan = createSetupPlan(options);
|
|
25
19
|
if (options.dryRun) {
|
|
26
20
|
console.log(options.json ? JSON.stringify(plan, null, 2) : JSON.stringify(plan));
|
|
27
21
|
return;
|
|
28
22
|
}
|
|
29
23
|
assertBootstrapPlatform(run);
|
|
24
|
+
assertWorkspaceRuntime(run);
|
|
30
25
|
const root = options.root;
|
|
31
26
|
const previousQuiet = process.env.ANHEDRAL_QUIET;
|
|
32
27
|
if (options.json)
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const GENERATOR_VERSION = "0.
|
|
1
|
+
export declare const GENERATOR_VERSION = "0.10.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const GENERATOR_VERSION = '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
|
-
|
|
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 = {
|
|
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
|
}
|
package/dist/workspace-graph.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
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;
|
|
@@ -6,7 +6,7 @@ Codex implements the product; the CLI supplies scaffolding, selected resource re
|
|
|
6
6
|
|
|
7
7
|
1. Choose only needed components from the [stack standard](application-stack-standard.md). Record decisions, ownership, environment/cost assumptions and actual connections in the application's `docs/architecture.md`.
|
|
8
8
|
2. Match installed plugin, registry `cliVersion` and executable versions. Check Node/pnpm, Git identity, toolchains and customer provider access. For unpublished development use `pnpm build` and `node dist/bin.js`; never silently substitute `@latest`.
|
|
9
|
-
3. Inspect `new <unused-directory> <flags> --dry-run --json`, then generate with those choices. Review README, AGENTS, setup manifests and resolved lockfile. For initialized apps, preview `add <directory> <items...> --dry-run --json`; additions protect edited source and record registry provenance.
|
|
9
|
+
3. Inspect `new <unused-directory> <flags> --dry-run --json`, then generate with those choices. Review README, AGENTS, setup manifests and resolved lockfile. For initialized apps, preview `add <directory> <items...> --dry-run --json`; additions protect edited source and record registry provenance. Select only items from the bundled Anhedral catalog. If a web addition reports `filesChecked: false`, the preview covers selection and targets only; the actual transaction checks generated-file conflicts before committing. Older projects and runtime relocation require explicit migration. `doctor --providers --json` checks access without configuring infrastructure or certifying production.
|
|
10
10
|
4. Implement screens, APIs, persistence, authorization and integration behavior. Replace placeholders with working flows. Configure real secrets, origins and reviewed migrations using provider tools; starters do not establish setup completion. Keep credentials out of source, logs and handoffs.
|
|
11
11
|
|
|
12
12
|
## Source and release authority
|
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export interface SourceItem {
|
|
2
|
-
name: string;
|
|
3
|
-
version: string;
|
|
4
|
-
registryDependencies: string[];
|
|
5
|
-
files: {
|
|
6
|
-
target: string;
|
|
7
|
-
content: string;
|
|
8
|
-
}[];
|
|
9
|
-
}
|
|
10
|
-
export interface SourceRegistry {
|
|
11
|
-
schemaVersion: 1;
|
|
12
|
-
name: string;
|
|
13
|
-
items: SourceItem[];
|
|
14
|
-
}
|
|
15
|
-
export interface RemoteRegistry {
|
|
16
|
-
source: string;
|
|
17
|
-
integrity: string;
|
|
18
|
-
registry: SourceRegistry;
|
|
19
|
-
}
|
|
20
|
-
/** Declarative source only: no executable recipe callbacks, hooks or destination templates. */
|
|
21
|
-
export declare function validateSourceRegistry(value: unknown): SourceRegistry;
|
|
22
|
-
export declare function loadSourceRegistry(source: string, fetcher?: typeof fetch): Promise<RemoteRegistry>;
|
|
23
|
-
export declare function resolveSourceItems(registry: SourceRegistry, selected: readonly string[]): SourceItem[];
|
|
24
|
-
/** Compile repository-relative source paths into a distributable JSON payload. */
|
|
25
|
-
export declare function buildSourceRegistry(manifestFile: string): SourceRegistry;
|
package/dist/registry/remote.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readBoundedFile } from '../bounded-file.js';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { dependencyOrder } from '../dependency-order.js';
|
|
5
|
-
import { assertSafeTarget } from './receipt.js';
|
|
6
|
-
const identifier = /^[a-z][a-z0-9-]{0,62}$/;
|
|
7
|
-
const limit = 2 * 1024 * 1024;
|
|
8
|
-
/** Declarative source only: no executable recipe callbacks, hooks or destination templates. */
|
|
9
|
-
export function validateSourceRegistry(value) {
|
|
10
|
-
if (!value || typeof value !== 'object')
|
|
11
|
-
throw new Error('Invalid source registry.');
|
|
12
|
-
const registry = value;
|
|
13
|
-
if (registry.schemaVersion !== 1 ||
|
|
14
|
-
typeof registry.name !== 'string' ||
|
|
15
|
-
!identifier.test(registry.name) ||
|
|
16
|
-
!Array.isArray(registry.items) ||
|
|
17
|
-
!registry.items.length ||
|
|
18
|
-
registry.items.length > 100)
|
|
19
|
-
throw new Error('Unsupported source registry schema.');
|
|
20
|
-
const names = new Set();
|
|
21
|
-
const files = new Set();
|
|
22
|
-
for (const item of registry.items) {
|
|
23
|
-
if (!item ||
|
|
24
|
-
typeof item.name !== 'string' ||
|
|
25
|
-
!identifier.test(item.name) ||
|
|
26
|
-
names.has(item.name) ||
|
|
27
|
-
typeof item.version !== 'string' ||
|
|
28
|
-
!/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/.test(item.version) ||
|
|
29
|
-
!Array.isArray(item.registryDependencies) ||
|
|
30
|
-
!item.registryDependencies.every((id) => typeof id === 'string' && identifier.test(id)) ||
|
|
31
|
-
!Array.isArray(item.files) ||
|
|
32
|
-
!item.files.length)
|
|
33
|
-
throw new Error('Invalid or duplicate source registry item.');
|
|
34
|
-
names.add(item.name);
|
|
35
|
-
for (const file of item.files) {
|
|
36
|
-
if (!file || typeof file.target !== 'string' || typeof file.content !== 'string')
|
|
37
|
-
throw new Error('Invalid registry file.');
|
|
38
|
-
assertSafeTarget('/registry-validation', file.target);
|
|
39
|
-
if (!/^(?:apps|packages)\/[a-z][a-z0-9-]*\//.test(file.target) ||
|
|
40
|
-
file.target
|
|
41
|
-
.split('/')
|
|
42
|
-
.some((part) => part.startsWith('.') ||
|
|
43
|
-
['node_modules', 'dist', 'release', 'anhedral.registry.json'].includes(part)) ||
|
|
44
|
-
files.has(file.target))
|
|
45
|
-
throw new Error(`Unsafe or duplicate source target: ${file.target}.`);
|
|
46
|
-
files.add(file.target);
|
|
47
|
-
if (path.basename(file.target) === 'package.json') {
|
|
48
|
-
const manifest = JSON.parse(file.content);
|
|
49
|
-
if (!manifest ||
|
|
50
|
-
typeof manifest !== 'object' ||
|
|
51
|
-
Array.isArray(manifest) ||
|
|
52
|
-
manifest.private !== true ||
|
|
53
|
-
typeof manifest.name !== 'string' ||
|
|
54
|
-
!/^@workspace\/[a-z][a-z0-9-]*$/.test(manifest.name))
|
|
55
|
-
throw new Error('Remote workspace manifests must be private @workspace packages.');
|
|
56
|
-
if (manifest.scripts &&
|
|
57
|
-
Object.keys(manifest.scripts).some((key) => /^(?:preinstall|install|postinstall|preprepare|prepare|postprepare|prepublish|prepublishOnly|pnpm:devPreinstall)$/.test(key)))
|
|
58
|
-
throw new Error('Remote installation hooks are unsupported.');
|
|
59
|
-
for (const field of [
|
|
60
|
-
'dependencies',
|
|
61
|
-
'devDependencies',
|
|
62
|
-
'optionalDependencies',
|
|
63
|
-
'peerDependencies',
|
|
64
|
-
]) {
|
|
65
|
-
if (manifest[field] &&
|
|
66
|
-
(typeof manifest[field] !== 'object' ||
|
|
67
|
-
Array.isArray(manifest[field]) ||
|
|
68
|
-
Object.values(manifest[field]).some((version) => typeof version !== 'string' ||
|
|
69
|
-
!/^(?:workspace:[*^~]|[~^]?\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?)$/.test(version))))
|
|
70
|
-
throw new Error('Remote dependencies require versioned npm or workspace references.');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
dependencyOrder(new Map(registry.items.map((item) => [item.name, item.registryDependencies])), 'Remote registry');
|
|
76
|
-
return structuredClone(registry);
|
|
77
|
-
}
|
|
78
|
-
export async function loadSourceRegistry(source, fetcher = fetch) {
|
|
79
|
-
let bytes;
|
|
80
|
-
if (/^https?:/.test(source)) {
|
|
81
|
-
const url = new URL(source);
|
|
82
|
-
if (url.protocol !== 'https:' ||
|
|
83
|
-
url.username ||
|
|
84
|
-
url.password ||
|
|
85
|
-
url.hash ||
|
|
86
|
-
url.search ||
|
|
87
|
-
!/^[a-z0-9.-]+$/i.test(url.hostname) ||
|
|
88
|
-
url.hostname === 'localhost' ||
|
|
89
|
-
!url.hostname.includes('.') ||
|
|
90
|
-
/^\d+(?:\.\d+){3}$/.test(url.hostname))
|
|
91
|
-
throw new Error('Remote registries require a public HTTPS URL without credentials or query parameters.');
|
|
92
|
-
const response = await fetcher(url, { redirect: 'error', signal: AbortSignal.timeout(15_000) });
|
|
93
|
-
if (!response.ok || !response.body)
|
|
94
|
-
throw new Error('Cannot retrieve source registry.');
|
|
95
|
-
const reader = response.body.getReader();
|
|
96
|
-
const chunks = [];
|
|
97
|
-
let size = 0;
|
|
98
|
-
try {
|
|
99
|
-
while (true) {
|
|
100
|
-
const result = await reader.read();
|
|
101
|
-
if (result.done)
|
|
102
|
-
break;
|
|
103
|
-
size += result.value.byteLength;
|
|
104
|
-
if (size > limit)
|
|
105
|
-
throw new Error('Source registry exceeds 2 MiB.');
|
|
106
|
-
chunks.push(result.value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
await reader.cancel();
|
|
111
|
-
}
|
|
112
|
-
bytes = Buffer.concat(chunks);
|
|
113
|
-
source = url.href;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
source = path.resolve(source);
|
|
117
|
-
bytes = readBoundedFile(source, limit);
|
|
118
|
-
}
|
|
119
|
-
if (bytes.length > limit)
|
|
120
|
-
throw new Error('Source registry exceeds 2 MiB.');
|
|
121
|
-
return {
|
|
122
|
-
source,
|
|
123
|
-
integrity: `sha256-${createHash('sha256').update(bytes).digest('base64')}`,
|
|
124
|
-
registry: validateSourceRegistry(JSON.parse(bytes.toString('utf8'))),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
export function resolveSourceItems(registry, selected) {
|
|
128
|
-
const catalog = new Map(registry.items.map((item) => [item.name, item]));
|
|
129
|
-
const graph = new Map();
|
|
130
|
-
const collect = (name) => {
|
|
131
|
-
if (graph.has(name))
|
|
132
|
-
return;
|
|
133
|
-
const item = catalog.get(name);
|
|
134
|
-
if (!item)
|
|
135
|
-
throw new Error(`Unknown remote registry item: ${name}.`);
|
|
136
|
-
graph.set(name, item.registryDependencies);
|
|
137
|
-
item.registryDependencies.forEach(collect);
|
|
138
|
-
};
|
|
139
|
-
selected.forEach(collect);
|
|
140
|
-
return dependencyOrder(graph, 'Remote registry').map((name) => catalog.get(name));
|
|
141
|
-
}
|
|
142
|
-
/** Compile repository-relative source paths into a distributable JSON payload. */
|
|
143
|
-
export function buildSourceRegistry(manifestFile) {
|
|
144
|
-
const root = path.dirname(path.resolve(manifestFile));
|
|
145
|
-
const registry = JSON.parse(readBoundedFile(manifestFile, limit).toString('utf8'));
|
|
146
|
-
if (!Array.isArray(registry.items))
|
|
147
|
-
throw new Error('Invalid source registry manifest.');
|
|
148
|
-
for (const item of registry.items) {
|
|
149
|
-
if (!Array.isArray(item.files))
|
|
150
|
-
throw new Error('Invalid registry file list.');
|
|
151
|
-
for (const file of item.files) {
|
|
152
|
-
if (file.path === undefined)
|
|
153
|
-
continue;
|
|
154
|
-
if (typeof file.path !== 'string' || file.content !== undefined)
|
|
155
|
-
throw new Error('Declare one source path or embedded content.');
|
|
156
|
-
assertSafeTarget(root, file.path);
|
|
157
|
-
if (!/^(?:apps|packages)\/[a-z][a-z0-9-]*\//.test(file.path) ||
|
|
158
|
-
file.path.split('/').some((part) => part.startsWith('.') || part === 'node_modules'))
|
|
159
|
-
throw new Error('Registry source paths must stay in application or package source.');
|
|
160
|
-
const source = path.join(root, file.path);
|
|
161
|
-
file.content = readBoundedFile(source, limit).toString('utf8');
|
|
162
|
-
delete file.path;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (Buffer.byteLength(JSON.stringify(registry)) > limit)
|
|
166
|
-
throw new Error('Source registry exceeds 2 MiB.');
|
|
167
|
-
return validateSourceRegistry(registry);
|
|
168
|
-
}
|