alchemy 0.2.5 → 0.3.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 +3 -3
- package/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.d.ts +137 -0
- package/lib/ai/astro-file.js +156 -0
- package/lib/ai/client.d.ts +7 -8
- package/lib/ai/client.js +48 -19
- package/lib/ai/css-file.d.ts +131 -0
- package/lib/ai/css-file.js +141 -0
- package/lib/ai/data.d.ts +51 -6
- package/lib/ai/data.js +58 -12
- package/lib/ai/document.d.ts +108 -24
- package/lib/ai/document.js +166 -52
- package/lib/ai/html-file.d.ts +128 -0
- package/lib/ai/html-file.js +138 -0
- package/lib/ai/index.d.ts +9 -0
- package/lib/ai/index.js +9 -0
- package/lib/ai/json-file.d.ts +160 -0
- package/lib/ai/json-file.js +187 -0
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +139 -0
- package/lib/ai/typescript-file.js +145 -0
- package/lib/ai/vue-file.d.ts +126 -0
- package/lib/ai/vue-file.js +136 -0
- package/lib/ai/yaml-file.d.ts +169 -0
- package/lib/ai/yaml-file.js +200 -0
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/dns.d.ts +88 -0
- package/lib/cloudflare/dns.js +192 -0
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +2 -1
- package/lib/cloudflare/index.js +2 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
- package/lib/cloudflare/r2-rest-state-store.js +254 -0
- package/lib/cloudflare/response.d.ts +12 -0
- package/lib/cloudflare/response.js +0 -0
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.js +3 -0
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/cloudflare/zone.d.ts +18 -0
- package/lib/cloudflare/zone.js +17 -4
- package/lib/dns/godaddy.d.ts +7 -0
- package/lib/dns/godaddy.js +21 -0
- package/lib/dns/import-dns.d.ts +82 -0
- package/lib/dns/import-dns.js +114 -0
- package/lib/dns/index.d.ts +2 -0
- package/lib/dns/index.js +2 -0
- package/lib/dns/record.d.ts +88 -0
- package/lib/dns/record.js +13 -0
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.d.ts +17 -0
- package/lib/fs/file-system-state-store.js +87 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +2 -2
- package/lib/fs/index.d.ts +10 -4
- package/lib/fs/index.js +10 -4
- package/lib/fs/static-astro-file.d.ts +34 -0
- package/lib/fs/static-astro-file.js +39 -0
- package/lib/fs/static-css-file.d.ts +24 -0
- package/lib/fs/static-css-file.js +29 -0
- package/lib/fs/static-html-file.d.ts +31 -0
- package/lib/fs/static-html-file.js +36 -0
- package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
- package/lib/fs/static-json-file.js +15 -0
- package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
- package/lib/fs/static-text-file.js +8 -0
- package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
- package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
- package/lib/fs/static-vue-file.d.ts +28 -0
- package/lib/fs/static-vue-file.js +33 -0
- package/lib/fs/static-yaml-file.d.ts +22 -0
- package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
- package/lib/internal/getting-started.d.ts +21 -0
- package/lib/internal/getting-started.js +87 -0
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +27 -0
- package/lib/internal/providers.js +172 -0
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/resource.js +8 -1
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +4 -2
- package/lib/state.d.ts +1 -16
- package/lib/state.js +0 -87
- package/lib/test/bun.d.ts +11 -0
- package/lib/test/bun.js +36 -8
- package/lib/web/astro.d.ts +147 -0
- package/lib/web/astro.js +414 -0
- package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
- package/lib/web/shadcn.d.ts +97 -0
- package/lib/web/shadcn.js +99 -0
- package/lib/web/tailwind.d.ts +64 -0
- package/lib/web/tailwind.js +97 -0
- package/lib/{vite → web}/vite.d.ts +5 -0
- package/lib/{vite → web}/vite.js +44 -44
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/custom-theme.d.ts +173 -0
- package/lib/web/vitepress/custom-theme.js +336 -0
- package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
- package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
- package/lib/web/vitepress/home-page.d.ts +246 -0
- package/lib/web/vitepress/home-page.js +147 -0
- package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
- package/lib/{vitepress → web/vitepress}/index.js +2 -0
- package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
- package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
- package/package.json +6 -2
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +269 -0
- package/src/ai/client.ts +60 -21
- package/src/ai/css-file.ts +247 -0
- package/src/ai/data.ts +81 -19
- package/src/ai/document.ts +240 -62
- package/src/ai/html-file.ts +244 -0
- package/src/ai/index.ts +9 -0
- package/src/ai/json-file.ts +325 -0
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +259 -0
- package/src/ai/vue-file.ts +242 -0
- package/src/ai/yaml-file.ts +328 -0
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -9
- package/src/cloudflare/dns.ts +344 -0
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +2 -1
- package/src/cloudflare/r2-rest-state-store.ts +353 -0
- package/src/cloudflare/response.ts +9 -0
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +11 -7
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/cloudflare/zone.ts +39 -23
- package/src/dns/godaddy.ts +29 -0
- package/src/dns/import-dns.ts +213 -0
- package/src/dns/index.ts +2 -0
- package/src/dns/record.ts +121 -0
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +109 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/folder.ts +14 -2
- package/src/fs/index.ts +10 -4
- package/src/fs/static-astro-file.ts +45 -0
- package/src/fs/static-css-file.ts +35 -0
- package/src/fs/static-html-file.ts +42 -0
- package/src/fs/static-json-file.ts +34 -0
- package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
- package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +33 -0
- package/src/internal/getting-started.ts +115 -0
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +241 -0
- package/src/internal/tutorial.ts +392 -0
- package/src/resource.ts +10 -1
- package/src/scope.ts +5 -6
- package/src/state.ts +1 -111
- package/src/test/bun.ts +60 -10
- package/src/web/astro.ts +644 -0
- package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
- package/src/web/shadcn.ts +219 -0
- package/src/web/tailwind.ts +167 -0
- package/src/{vite → web}/vite.ts +54 -56
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/custom-theme.ts +514 -0
- package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
- package/src/web/vitepress/home-page.ts +375 -0
- package/src/{vitepress → web/vitepress}/index.ts +2 -0
- package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
- package/lib/cloudflare/state.d.ts +0 -82
- package/lib/cloudflare/state.js +0 -108
- package/lib/fs/json-file.js +0 -7
- package/lib/fs/text-file.js +0 -7
- package/lib/fs/yaml-file.d.ts +0 -19
- package/lib/internal/docs.d.ts +0 -5
- package/lib/internal/docs.js +0 -287
- package/lib/shadcn/index.d.ts +0 -1
- package/lib/shadcn/index.js +0 -1
- package/lib/vite/index.d.ts +0 -1
- package/lib/vite/index.js +0 -1
- package/lib/vitepress/home-page.d.ts +0 -133
- package/lib/vitepress/home-page.js +0 -11
- package/src/cloudflare/state.ts +0 -138
- package/src/fs/json-file.ts +0 -23
- package/src/fs/yaml-file.ts +0 -26
- package/src/internal/docs.ts +0 -338
- package/src/shadcn/index.ts +0 -1
- package/src/vite/index.ts +0 -1
- package/src/vitepress/home-page.ts +0 -166
- /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
- /package/src/{vitepress → web/vitepress}/index.md +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import type { CoreMessage } from "ai";
|
|
2
|
+
import { Approve, Document, Review } from "../ai";
|
|
3
|
+
import type { ModelConfig } from "../ai/client";
|
|
4
|
+
import { alchemy } from "../alchemy";
|
|
5
|
+
import type { Context } from "../context";
|
|
6
|
+
import type { Folder } from "../fs";
|
|
7
|
+
import { Resource } from "../resource";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Properties for creating or updating a Tutorial
|
|
11
|
+
*/
|
|
12
|
+
export interface TutorialProps {
|
|
13
|
+
/**
|
|
14
|
+
* The output directory for the tutorial document.
|
|
15
|
+
*/
|
|
16
|
+
path: string | Folder;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The title of the tutorial.
|
|
20
|
+
*/
|
|
21
|
+
title: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The prompt to use for generating the tutorial.
|
|
25
|
+
* This should include specific details about the tool or framework.
|
|
26
|
+
*/
|
|
27
|
+
prompt: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Optional difficulty level for the tutorial.
|
|
31
|
+
* Defaults to "beginner".
|
|
32
|
+
*/
|
|
33
|
+
difficulty?: "beginner" | "intermediate" | "advanced";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Optional estimated time to complete the tutorial in minutes.
|
|
37
|
+
* Defaults to 30.
|
|
38
|
+
*/
|
|
39
|
+
estimatedTime?: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Optional model configuration for generating the tutorial.
|
|
43
|
+
* Defaults to Claude 3.5 Sonnet.
|
|
44
|
+
*/
|
|
45
|
+
model?: ModelConfig;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of review iterations.
|
|
49
|
+
* Defaults to 3.
|
|
50
|
+
*/
|
|
51
|
+
maxIterations?: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initial message history for the conversation.
|
|
55
|
+
* If not provided, a new conversation will be started.
|
|
56
|
+
*/
|
|
57
|
+
messages?: CoreMessage[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Output returned after Tutorial creation/update
|
|
62
|
+
*/
|
|
63
|
+
export interface Tutorial extends TutorialProps, Resource<"docs::Tutorial"> {
|
|
64
|
+
/**
|
|
65
|
+
* The Document resource
|
|
66
|
+
*/
|
|
67
|
+
document: Document;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The content of the tutorial
|
|
71
|
+
*/
|
|
72
|
+
content: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Message history from the conversation.
|
|
76
|
+
*/
|
|
77
|
+
messages: CoreMessage[];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Time at which the tutorial was created
|
|
81
|
+
*/
|
|
82
|
+
createdAt: number;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Time at which the tutorial was last updated
|
|
86
|
+
*/
|
|
87
|
+
updatedAt: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates a tutorial document with iterative review and improvement
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // Create a basic tutorial
|
|
95
|
+
* const tutorial = await Tutorial("getting-started", {
|
|
96
|
+
* path: "docs/tutorials/getting-started",
|
|
97
|
+
* title: "Getting Started with Alchemy",
|
|
98
|
+
* prompt: "Create a tutorial for getting started with the Alchemy framework",
|
|
99
|
+
* difficulty: "beginner",
|
|
100
|
+
* estimatedTime: 30
|
|
101
|
+
* });
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* // Create an advanced tutorial with custom model and continue from existing messages
|
|
105
|
+
* const advancedTutorial = await Tutorial("advanced-features", {
|
|
106
|
+
* path: "docs/tutorials/advanced-features",
|
|
107
|
+
* title: "Advanced Alchemy Features",
|
|
108
|
+
* prompt: "Create a tutorial covering advanced features of the Alchemy framework",
|
|
109
|
+
* difficulty: "advanced",
|
|
110
|
+
* estimatedTime: 60,
|
|
111
|
+
* model: {
|
|
112
|
+
* id: "claude-3-7-sonnet-latest",
|
|
113
|
+
* provider: "anthropic"
|
|
114
|
+
* },
|
|
115
|
+
* maxIterations: 5,
|
|
116
|
+
* messages: basicTutorial.messages
|
|
117
|
+
* });
|
|
118
|
+
*/
|
|
119
|
+
export const Tutorial = Resource(
|
|
120
|
+
"docs::Tutorial",
|
|
121
|
+
async function (
|
|
122
|
+
this: Context<Tutorial>,
|
|
123
|
+
id: string,
|
|
124
|
+
props: TutorialProps
|
|
125
|
+
): Promise<Tutorial> {
|
|
126
|
+
const {
|
|
127
|
+
path: outFile,
|
|
128
|
+
title,
|
|
129
|
+
prompt,
|
|
130
|
+
difficulty = "beginner",
|
|
131
|
+
estimatedTime = 30,
|
|
132
|
+
model = {
|
|
133
|
+
id: "claude-3-5-sonnet-latest",
|
|
134
|
+
provider: "anthropic",
|
|
135
|
+
},
|
|
136
|
+
maxIterations = 3,
|
|
137
|
+
messages: initialMessages = [],
|
|
138
|
+
} = props;
|
|
139
|
+
|
|
140
|
+
console.log(`Tutorial: Starting creation of "${title}" (ID: ${id})`);
|
|
141
|
+
|
|
142
|
+
// Handle deletion phase
|
|
143
|
+
if (this.phase === "delete") {
|
|
144
|
+
console.log(`Tutorial: Deleting tutorial "${title}" (ID: ${id})`);
|
|
145
|
+
return this.destroy();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// System prompts
|
|
149
|
+
const docSystemPrompt = `You are a technical writer creating a comprehensive tutorial.
|
|
150
|
+
|
|
151
|
+
Your task is to create a detailed tutorial about ${title} with difficulty level: ${difficulty} and estimated time to complete: ${estimatedTime} minutes.
|
|
152
|
+
|
|
153
|
+
The tutorial should take users from zero knowledge to a working understanding of the tool or framework.
|
|
154
|
+
|
|
155
|
+
The tutorial should be structured as follows:
|
|
156
|
+
|
|
157
|
+
# ${title}
|
|
158
|
+
|
|
159
|
+
## Overview
|
|
160
|
+
|
|
161
|
+
(Provide a brief introduction to the tool/framework, its purpose, and what users will learn in this tutorial)
|
|
162
|
+
|
|
163
|
+
## Prerequisites
|
|
164
|
+
|
|
165
|
+
(List any prerequisites, tools, or knowledge required before starting the tutorial)
|
|
166
|
+
|
|
167
|
+
## Setup
|
|
168
|
+
|
|
169
|
+
### Installation
|
|
170
|
+
|
|
171
|
+
\`\`\`bash
|
|
172
|
+
# Installation commands
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
### Configuration
|
|
176
|
+
|
|
177
|
+
(Explain any necessary configuration steps)
|
|
178
|
+
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
# Configuration commands or code
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
## Step 1: [First Step Title]
|
|
184
|
+
|
|
185
|
+
(Explain the first step in detail)
|
|
186
|
+
|
|
187
|
+
\`\`\`bash
|
|
188
|
+
# Commands or code for the first step
|
|
189
|
+
\`\`\`
|
|
190
|
+
|
|
191
|
+
(Explain what the code does and why it's important)
|
|
192
|
+
|
|
193
|
+
## Step 2: [Second Step Title]
|
|
194
|
+
|
|
195
|
+
(Explain the second step in detail)
|
|
196
|
+
|
|
197
|
+
\`\`\`bash
|
|
198
|
+
# Commands or code for the second step
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
(Explain what the code does and why it's important)
|
|
202
|
+
|
|
203
|
+
## Step 3: [Third Step Title]
|
|
204
|
+
|
|
205
|
+
(Explain the third step in detail)
|
|
206
|
+
|
|
207
|
+
\`\`\`bash
|
|
208
|
+
# Commands or code for the third step
|
|
209
|
+
\`\`\`
|
|
210
|
+
|
|
211
|
+
(Explain what the code does and why it's important)
|
|
212
|
+
|
|
213
|
+
## Step 4: [Fourth Step Title]
|
|
214
|
+
|
|
215
|
+
(Explain the fourth step in detail)
|
|
216
|
+
|
|
217
|
+
\`\`\`bash
|
|
218
|
+
# Commands or code for the fourth step
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
(Explain what the code does and why it's important)
|
|
222
|
+
|
|
223
|
+
## Step 5: [Fifth Step Title]
|
|
224
|
+
|
|
225
|
+
(Explain the fifth step in detail)
|
|
226
|
+
|
|
227
|
+
\`\`\`bash
|
|
228
|
+
# Commands or code for the fifth step
|
|
229
|
+
\`\`\`
|
|
230
|
+
|
|
231
|
+
(Explain what the code does and why it's important)
|
|
232
|
+
|
|
233
|
+
## Testing Your Work
|
|
234
|
+
|
|
235
|
+
(Explain how to verify that everything is working correctly)
|
|
236
|
+
|
|
237
|
+
\`\`\`bash
|
|
238
|
+
# Testing commands or code
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
## Troubleshooting
|
|
242
|
+
|
|
243
|
+
(Common issues users might encounter and how to resolve them)
|
|
244
|
+
|
|
245
|
+
## Next Steps
|
|
246
|
+
|
|
247
|
+
(Suggest additional resources, advanced topics, or projects to try next)
|
|
248
|
+
|
|
249
|
+
## Additional Resources
|
|
250
|
+
|
|
251
|
+
(Links to documentation, community resources, or related tutorials)`;
|
|
252
|
+
|
|
253
|
+
// Initial message if none provided
|
|
254
|
+
const startingMessages =
|
|
255
|
+
initialMessages.length > 0
|
|
256
|
+
? initialMessages
|
|
257
|
+
: [
|
|
258
|
+
{
|
|
259
|
+
role: "user" as const,
|
|
260
|
+
content: `Create a comprehensive tutorial about ${title}.\n\n${prompt}\n\nDifficulty level: ${difficulty}\nEstimated time to complete: ${estimatedTime} minutes`,
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
// Generate the initial tutorial
|
|
265
|
+
console.log(`Tutorial: Generating initial tutorial for "${title}"`);
|
|
266
|
+
let tutorial = await Document(`document`, {
|
|
267
|
+
title: title,
|
|
268
|
+
path: typeof outFile === "string" ? outFile : outFile.path,
|
|
269
|
+
model,
|
|
270
|
+
messages: startingMessages,
|
|
271
|
+
system: docSystemPrompt,
|
|
272
|
+
});
|
|
273
|
+
console.log(
|
|
274
|
+
`Tutorial: Initial tutorial generated for "${title}" (${tutorial.content.length} chars)`
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Review and improve the tutorial in a loop
|
|
278
|
+
let iteration = 0;
|
|
279
|
+
let approved = false;
|
|
280
|
+
let finalMessages = tutorial.messages;
|
|
281
|
+
|
|
282
|
+
while (!approved && iteration < maxIterations) {
|
|
283
|
+
iteration++;
|
|
284
|
+
console.log(
|
|
285
|
+
`Tutorial: Starting review iteration ${iteration}/${maxIterations} for "${title}"`
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Review the tutorial using system prompt
|
|
289
|
+
console.log(`Tutorial: Requesting review for iteration ${iteration}`);
|
|
290
|
+
const review = await Review(`review-${iteration}`, {
|
|
291
|
+
messages: finalMessages,
|
|
292
|
+
system:
|
|
293
|
+
await alchemy`Please review this tutorial for correctness, conciseness, logical flow, and accurateness.
|
|
294
|
+
|
|
295
|
+
The tutorial should:
|
|
296
|
+
1. Be technically accurate and free of errors
|
|
297
|
+
2. Be concise without over-explaining
|
|
298
|
+
3. Have a logical flow from basic to advanced concepts
|
|
299
|
+
4. Be accurate in its descriptions and instructions
|
|
300
|
+
5. Be appropriate for the ${difficulty} difficulty level
|
|
301
|
+
6. Be completable within approximately ${estimatedTime} minutes
|
|
302
|
+
|
|
303
|
+
Provide specific feedback on areas that need improvement.`,
|
|
304
|
+
model,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Extract the actual review content from the last assistant message
|
|
308
|
+
const reviewContent =
|
|
309
|
+
review.messages[review.messages.length - 1]?.content || "";
|
|
310
|
+
console.log(
|
|
311
|
+
`Tutorial: Review received for iteration ${iteration} (${reviewContent.length} chars)`
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// Check if the tutorial is approved using system prompt
|
|
315
|
+
console.log(`Tutorial: Evaluating approval for iteration ${iteration}`);
|
|
316
|
+
const approvalResult = await Approve(`approval-${iteration}`, {
|
|
317
|
+
messages: review.messages,
|
|
318
|
+
system:
|
|
319
|
+
await alchemy`Based on the review above, determine if the tutorial meets the quality standards.
|
|
320
|
+
The tutorial should be approved if:
|
|
321
|
+
1. It is technically accurate and free of errors
|
|
322
|
+
2. It is concise without over-explaining
|
|
323
|
+
3. It has a logical flow from basic to advanced concepts
|
|
324
|
+
4. It is accurate in its descriptions and instructions
|
|
325
|
+
5. It is appropriate for the ${difficulty} difficulty level
|
|
326
|
+
6. It is completable within approximately ${estimatedTime} minutes
|
|
327
|
+
|
|
328
|
+
Respond with APPROVED or DENIED, followed by your explanation.`,
|
|
329
|
+
});
|
|
330
|
+
console.log(
|
|
331
|
+
`Tutorial: Approval result for iteration ${iteration}: ${approvalResult.approved ? "APPROVED" : "DENIED"}`
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Log approval status
|
|
335
|
+
if (approvalResult.approved) {
|
|
336
|
+
console.log(
|
|
337
|
+
`Tutorial: Final tutorial approved: ${approvalResult.explanation}`
|
|
338
|
+
);
|
|
339
|
+
approved = true;
|
|
340
|
+
finalMessages = approvalResult.messages;
|
|
341
|
+
} else {
|
|
342
|
+
console.log(
|
|
343
|
+
`Tutorial: Tutorial needs improvement: ${approvalResult.explanation}`
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Add improvement request and generate an improved tutorial
|
|
347
|
+
const improvementMessages = [
|
|
348
|
+
...approvalResult.messages,
|
|
349
|
+
{
|
|
350
|
+
role: "user" as const,
|
|
351
|
+
content: `Please create an improved version of the tutorial that addresses the issues identified in the review: ${approvalResult.explanation}`,
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
console.log(
|
|
356
|
+
`Tutorial: Regenerating tutorial based on feedback for iteration ${iteration}`
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
tutorial = await Document(`tutorial-${iteration}`, {
|
|
360
|
+
title: title,
|
|
361
|
+
path: typeof outFile === "string" ? outFile : outFile.path,
|
|
362
|
+
model,
|
|
363
|
+
messages: improvementMessages,
|
|
364
|
+
system: docSystemPrompt,
|
|
365
|
+
});
|
|
366
|
+
console.log(
|
|
367
|
+
`Tutorial: Improved tutorial generated for iteration ${iteration} (${tutorial.content.length} chars)`
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
finalMessages = tutorial.messages;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!approved) {
|
|
375
|
+
console.log(
|
|
376
|
+
`Tutorial: Tutorial not approved after ${maxIterations} iterations. Using the best version available.`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(`Tutorial: Completed creation of "${title}" (ID: ${id})`);
|
|
381
|
+
|
|
382
|
+
// Return the tutorial resource
|
|
383
|
+
return this({
|
|
384
|
+
...props,
|
|
385
|
+
document: tutorial,
|
|
386
|
+
content: tutorial.content,
|
|
387
|
+
messages: finalMessages,
|
|
388
|
+
createdAt: Date.now(),
|
|
389
|
+
updatedAt: Date.now(),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
);
|
package/src/resource.ts
CHANGED
|
@@ -112,7 +112,16 @@ export function Resource<
|
|
|
112
112
|
if (scope.resources.has(resourceID)) {
|
|
113
113
|
// TODO(sam): do we want to throw?
|
|
114
114
|
// it's kind of awesome that you can re-create a resource and call apply
|
|
115
|
-
|
|
115
|
+
const otherResource = scope.resources.get(resourceID);
|
|
116
|
+
if (otherResource?.Kind !== type) {
|
|
117
|
+
scope.fail();
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Resource ${resourceID} already exists in the stack and is of a different type: '${otherResource?.Kind}' !== '${type}'`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
// console.warn(
|
|
123
|
+
// `Resource ${resourceID} already exists in the stack: ${scope.chain.join("/")}`,
|
|
124
|
+
// );
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
// get a sequence number (unique within the scope) for the resource
|
package/src/scope.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { destroy } from "./destroy";
|
|
3
|
+
import { FileSystemStateStore } from "./fs/file-system-state-store";
|
|
3
4
|
import type { PendingResource, ResourceID } from "./resource";
|
|
4
|
-
import {
|
|
5
|
-
FileSystemStateStore,
|
|
6
|
-
type StateStore,
|
|
7
|
-
type StateStoreType,
|
|
8
|
-
} from "./state";
|
|
5
|
+
import type { StateStore, StateStoreType } from "./state";
|
|
9
6
|
|
|
10
7
|
const scopeStorage = new AsyncLocalStorage<Scope>();
|
|
11
8
|
|
|
@@ -53,7 +50,9 @@ export class Scope {
|
|
|
53
50
|
throw new Error("Scope name is required when creating a child scope");
|
|
54
51
|
}
|
|
55
52
|
this.password = options.password;
|
|
56
|
-
this.state =
|
|
53
|
+
this.state = options.stateStore
|
|
54
|
+
? options.stateStore(this)
|
|
55
|
+
: new FileSystemStateStore(this);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
public async delete(resourceID: ResourceID) {
|
package/src/state.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import type { Resource, ResourceProps } from "./resource";
|
|
4
2
|
import type { Scope } from "./scope";
|
|
5
|
-
import { ignore } from "./util/ignore";
|
|
6
|
-
import { deserialize, serialize } from "./util/serde";
|
|
7
3
|
|
|
8
4
|
export interface State<
|
|
9
5
|
Kind extends string = string,
|
|
@@ -28,7 +24,7 @@ export interface State<
|
|
|
28
24
|
output: Out;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
export type StateStoreType =
|
|
27
|
+
export type StateStoreType = (scope: Scope) => StateStore;
|
|
32
28
|
|
|
33
29
|
export interface StateStore {
|
|
34
30
|
/** Initialize the state container if one is required */
|
|
@@ -45,109 +41,3 @@ export interface StateStore {
|
|
|
45
41
|
set(key: string, value: State): Promise<void>;
|
|
46
42
|
delete(key: string): Promise<void>;
|
|
47
43
|
}
|
|
48
|
-
|
|
49
|
-
const stateRootDir = path.join(process.cwd(), ".alchemy");
|
|
50
|
-
|
|
51
|
-
export class FileSystemStateStore implements StateStore {
|
|
52
|
-
public readonly dir: string;
|
|
53
|
-
constructor(public readonly scope: Scope) {
|
|
54
|
-
this.dir = path.join(stateRootDir, ...scope.chain);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async init(): Promise<void> {
|
|
58
|
-
await fs.promises.mkdir(stateRootDir, { recursive: true });
|
|
59
|
-
await fs.promises.mkdir(this.dir, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async deinit(): Promise<void> {
|
|
63
|
-
await ignore("ENOENT", () => fs.promises.rmdir(this.dir));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async count(): Promise<number> {
|
|
67
|
-
return Object.keys(await this.list()).length;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async list(): Promise<string[]> {
|
|
71
|
-
try {
|
|
72
|
-
const files = await fs.promises.readdir(this.dir, {
|
|
73
|
-
withFileTypes: true,
|
|
74
|
-
});
|
|
75
|
-
return files
|
|
76
|
-
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
|
|
77
|
-
.map((dirent) => dirent.name.replace(/\.json$/, ""))
|
|
78
|
-
.map((key) => key.replaceAll(":", "/"));
|
|
79
|
-
} catch (error: any) {
|
|
80
|
-
if (error.code === "ENOENT") {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async get(key: string): Promise<State | undefined> {
|
|
88
|
-
try {
|
|
89
|
-
const content = await fs.promises.readFile(
|
|
90
|
-
await this.getPath(key),
|
|
91
|
-
"utf8",
|
|
92
|
-
);
|
|
93
|
-
const state = (await deserialize(
|
|
94
|
-
this.scope,
|
|
95
|
-
JSON.parse(content),
|
|
96
|
-
)) as State;
|
|
97
|
-
if (state.output === undefined) {
|
|
98
|
-
state.output = {} as any;
|
|
99
|
-
}
|
|
100
|
-
state.output.Scope = this.scope;
|
|
101
|
-
return state;
|
|
102
|
-
} catch (error: any) {
|
|
103
|
-
if (error.code === "ENOENT") {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async set(key: string, value: State): Promise<void> {
|
|
111
|
-
return fs.promises.writeFile(
|
|
112
|
-
await this.getPath(key),
|
|
113
|
-
JSON.stringify(await serialize(this.scope, value), null, 2),
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async delete(key: string): Promise<void> {
|
|
118
|
-
return fs.promises.unlink(await this.getPath(key));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async all(): Promise<Record<string, State>> {
|
|
122
|
-
return this.getBatch(await this.list());
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async getBatch(ids: string[]): Promise<Record<string, State>> {
|
|
126
|
-
return Object.fromEntries(
|
|
127
|
-
(
|
|
128
|
-
await Promise.all(
|
|
129
|
-
Array.from(ids).flatMap(async (id) => {
|
|
130
|
-
const s = await this.get(id);
|
|
131
|
-
if (s === undefined) {
|
|
132
|
-
return [] as const;
|
|
133
|
-
}
|
|
134
|
-
return [[id, s]] as const;
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
).flat(),
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private async getPath(key: string): Promise<string> {
|
|
142
|
-
if (key.includes(":")) {
|
|
143
|
-
throw new Error(`ID cannot include colons: ${key}`);
|
|
144
|
-
}
|
|
145
|
-
if (key.includes("/")) {
|
|
146
|
-
key = key.replaceAll("/", ":");
|
|
147
|
-
}
|
|
148
|
-
const file = path.join(this.dir, `${key}.json`);
|
|
149
|
-
const dir = path.dirname(file);
|
|
150
|
-
await fs.promises.mkdir(dir, { recursive: true });
|
|
151
|
-
return file;
|
|
152
|
-
}
|
|
153
|
-
}
|
package/src/test/bun.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/// <reference types="bun" />
|
|
2
2
|
|
|
3
|
-
import { afterAll, it } from "bun:test";
|
|
3
|
+
import { afterAll, beforeAll, it } from "bun:test";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { alchemy } from "../alchemy";
|
|
6
|
+
import { R2RestStateStore } from "../cloudflare";
|
|
6
7
|
import { destroy } from "../destroy";
|
|
7
8
|
import type { Scope } from "../scope";
|
|
9
|
+
import type { StateStoreType } from "../state";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Extend the Alchemy interface to include test functionality
|
|
@@ -41,12 +43,17 @@ export interface TestOptions {
|
|
|
41
43
|
* @default "test-password".
|
|
42
44
|
*/
|
|
43
45
|
password?: string;
|
|
44
|
-
}
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Override the default state store for the test.
|
|
49
|
+
*/
|
|
50
|
+
stateStore?: StateStoreType;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Prefix to use for the scope to isolate tests and environments.
|
|
54
|
+
*/
|
|
55
|
+
prefix?: string;
|
|
56
|
+
}
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
59
|
* Test function type definition with overloads
|
|
@@ -80,6 +87,10 @@ type test = {
|
|
|
80
87
|
*/
|
|
81
88
|
skipIf(condition: boolean): test;
|
|
82
89
|
|
|
90
|
+
beforeAll(fn: (scope: Scope) => Promise<void>): void;
|
|
91
|
+
|
|
92
|
+
afterAll(fn: (scope: Scope) => Promise<void>): void;
|
|
93
|
+
|
|
83
94
|
/**
|
|
84
95
|
* Current test scope
|
|
85
96
|
*/
|
|
@@ -110,6 +121,20 @@ type test = {
|
|
|
110
121
|
* ```
|
|
111
122
|
*/
|
|
112
123
|
export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
124
|
+
defaultOptions = defaultOptions ?? {};
|
|
125
|
+
if (
|
|
126
|
+
defaultOptions.stateStore === undefined &&
|
|
127
|
+
// process.env.CI &&
|
|
128
|
+
process.env.ALCHEMY_STATE_STORE === "cloudflare"
|
|
129
|
+
) {
|
|
130
|
+
defaultOptions.stateStore = (scope) =>
|
|
131
|
+
new R2RestStateStore(scope, {
|
|
132
|
+
apiKey: process.env.CLOUDFLARE_API_KEY,
|
|
133
|
+
email: process.env.CLOUDFLARE_EMAIL,
|
|
134
|
+
bucketName: process.env.CLOUDFLARE_BUCKET_NAME!,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
113
138
|
// Add skipIf functionality
|
|
114
139
|
test.skipIf = (condition: boolean) => {
|
|
115
140
|
if (condition) {
|
|
@@ -120,13 +145,35 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
120
145
|
};
|
|
121
146
|
|
|
122
147
|
// Create local test scope based on filename
|
|
123
|
-
const localTestScope = alchemy.scope(
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
const localTestScope = alchemy.scope(
|
|
149
|
+
`${defaultOptions.prefix ? `${defaultOptions.prefix}-` : ""}${path.basename(meta.filename)}`,
|
|
150
|
+
{
|
|
151
|
+
// parent: globalTestScope,
|
|
152
|
+
stateStore: defaultOptions?.stateStore,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
126
155
|
test.scope = localTestScope;
|
|
127
156
|
|
|
157
|
+
test.beforeAll = (fn: (scope: Scope) => Promise<void>) => {
|
|
158
|
+
return beforeAll(async () => {
|
|
159
|
+
test.scope.enter();
|
|
160
|
+
await fn(test.scope);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
test.afterAll = (fn: (scope: Scope) => Promise<void>) => {
|
|
165
|
+
return afterAll(async () => {
|
|
166
|
+
test.scope.enter();
|
|
167
|
+
await fn(test.scope);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
128
171
|
// Clean up test scope after all tests complete
|
|
129
|
-
afterAll(() =>
|
|
172
|
+
afterAll(async () => {
|
|
173
|
+
if (defaultOptions?.destroy !== false) {
|
|
174
|
+
await alchemy.destroy(test.scope);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
130
177
|
|
|
131
178
|
return test as any;
|
|
132
179
|
|
|
@@ -173,6 +220,9 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
173
220
|
scope.enter();
|
|
174
221
|
try {
|
|
175
222
|
await fn(scope);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error(err);
|
|
225
|
+
throw err;
|
|
176
226
|
} finally {
|
|
177
227
|
if (options.destroy !== false) {
|
|
178
228
|
await destroy(scope);
|