alchemy 0.3.0 → 0.4.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.
- package/README.md +8 -3
- package/lib/ai/astro-file.js +7 -24
- package/lib/ai/client.d.ts +7 -0
- package/lib/ai/client.js +45 -0
- package/lib/ai/css-file.js +7 -24
- package/lib/ai/data.d.ts +26 -21
- package/lib/ai/data.js +33 -22
- package/lib/ai/document.d.ts +76 -5
- package/lib/ai/document.js +112 -56
- package/lib/ai/html-file.js +7 -24
- package/lib/ai/json-file.js +11 -32
- package/lib/ai/typescript-file.d.ts +1 -2
- package/lib/ai/typescript-file.js +8 -39
- package/lib/ai/vue-file.js +7 -24
- package/lib/ai/yaml-file.js +12 -48
- package/lib/alchemy.d.ts +5 -0
- package/lib/alchemy.js +61 -3
- package/lib/apply.js +18 -11
- package/lib/aws/account-id.d.ts +4 -1
- package/lib/aws/account-id.js +1 -1
- package/lib/aws/credentials.d.ts +5 -0
- package/lib/aws/credentials.js +0 -0
- package/lib/aws/oidc/oidc-provider.js +2 -2
- package/lib/aws/role.js +35 -23
- package/lib/cloudflare/account-api-token.d.ts +168 -0
- package/lib/cloudflare/account-api-token.js +179 -0
- package/lib/cloudflare/account-id.d.ts +1 -0
- package/lib/cloudflare/account-id.js +0 -0
- package/lib/cloudflare/api.d.ts +38 -1
- package/lib/cloudflare/api.js +55 -4
- package/lib/cloudflare/bucket.d.ts +118 -3
- package/lib/cloudflare/bucket.js +269 -89
- package/lib/cloudflare/custom-domain.d.ts +75 -0
- package/lib/cloudflare/custom-domain.js +154 -0
- package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +5 -1
- package/lib/cloudflare/index.js +5 -1
- package/lib/cloudflare/permission-groups.d.ts +78 -0
- package/lib/cloudflare/permission-groups.js +48 -0
- package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
- package/lib/cloudflare/r2-rest-state-store.js +3 -2
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.d.ts +17 -18
- package/lib/cloudflare/static-site.js +19 -7
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
- package/lib/{util/encrypt.js → encrypt.js} +1 -1
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.js +1 -1
- package/lib/fs/file.d.ts +31 -0
- package/lib/fs/file.js +30 -5
- package/lib/fs/index.d.ts +6 -0
- package/lib/fs/index.js +6 -0
- 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/static-json-file.d.ts +2 -2
- package/lib/fs/static-json-file.js +3 -2
- package/lib/fs/static-text-file.d.ts +1 -1
- package/lib/fs/static-text-file.js +3 -2
- package/lib/fs/static-typescript-file.d.ts +2 -2
- package/lib/fs/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 +11 -8
- package/lib/fs/static-yaml-file.js +3 -2
- package/lib/internal/{providers.d.ts → docs/providers.d.ts} +14 -4
- package/lib/internal/docs/providers.js +196 -0
- package/lib/secret.d.ts +3 -0
- package/lib/secret.js +13 -0
- package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
- package/lib/{util/serde.js → serde.js} +12 -4
- package/lib/test/bun.js +3 -2
- package/lib/util/sha256.d.ts +1 -0
- package/lib/util/sha256.js +4 -0
- package/lib/web/vitepress/config.d.ts +11 -0
- package/lib/web/vitepress/config.js +32 -0
- package/lib/web/vitepress/home-page.d.ts +5 -3
- package/lib/web/vitepress/home-page.js +48 -35
- package/lib/web/vitepress/index.d.ts +2 -0
- package/lib/web/vitepress/index.js +2 -0
- package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
- package/lib/web/vitepress/process-front-matter-files.js +68 -0
- package/lib/web/vitepress/vitepress.d.ts +5 -4
- package/lib/web/vitepress/vitepress.js +20 -36
- package/package.json +8 -4
- package/src/ai/astro-file.ts +11 -30
- package/src/ai/client.ts +59 -0
- package/src/ai/css-file.ts +11 -30
- package/src/ai/data.ts +50 -25
- package/src/ai/document.ts +171 -66
- package/src/ai/html-file.ts +11 -30
- package/src/ai/json-file.ts +13 -37
- package/src/ai/typescript-file.ts +14 -48
- package/src/ai/vue-file.ts +11 -30
- package/src/ai/yaml-file.ts +13 -53
- package/src/alchemy.ts +74 -9
- package/src/apply.ts +27 -26
- package/src/aws/account-id.ts +6 -2
- package/src/aws/credentials.ts +6 -0
- package/src/aws/oidc/oidc-provider.ts +17 -17
- package/src/aws/role.ts +68 -54
- package/src/cloudflare/account-api-token.ts +365 -0
- package/src/cloudflare/account-id.ts +0 -0
- package/src/cloudflare/api.ts +88 -17
- package/src/cloudflare/bucket.ts +493 -133
- package/src/cloudflare/custom-domain.ts +318 -0
- package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +5 -1
- package/src/cloudflare/permission-groups.ts +137 -0
- package/src/cloudflare/r2-rest-state-store.ts +14 -13
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +27 -35
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/dns/import-dns.ts +4 -4
- package/src/{util/encrypt.ts → encrypt.ts} +7 -10
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +5 -5
- package/src/fs/file.ts +64 -9
- package/src/fs/index.ts +6 -0
- 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 +4 -3
- package/src/fs/static-text-file.ts +5 -2
- package/src/fs/static-typescript-file.ts +4 -3
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +13 -9
- package/src/internal/docs/providers.ts +281 -0
- package/src/secret.ts +16 -0
- package/src/{util/serde.ts → serde.ts} +16 -10
- package/src/test/bun.ts +8 -7
- package/src/util/sha256.ts +5 -0
- package/src/web/vitepress/config.ts +50 -0
- package/src/web/vitepress/home-page.ts +54 -42
- package/src/web/vitepress/index.ts +2 -0
- package/src/web/vitepress/process-front-matter-files.ts +98 -0
- package/src/web/vitepress/vitepress.ts +34 -50
- package/lib/internal/getting-started.d.ts +0 -10
- package/lib/internal/getting-started.js +0 -94
- package/lib/internal/project.d.ts +0 -13
- package/lib/internal/project.js +0 -101
- package/lib/internal/providers.js +0 -148
- package/src/internal/getting-started.ts +0 -107
- package/src/internal/project.ts +0 -119
- package/src/internal/providers.ts +0 -209
- /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { decryptWithKey, encrypt } from "./encrypt";
|
|
2
|
+
import { Scope } from "./scope";
|
|
3
|
+
import { Secret } from "./secret";
|
|
4
4
|
// zero-dependency type guard for ArkType
|
|
5
5
|
function isType(value) {
|
|
6
6
|
return (value &&
|
|
@@ -17,7 +17,7 @@ export async function serialize(scope, value, options) {
|
|
|
17
17
|
}
|
|
18
18
|
return {
|
|
19
19
|
"@secret": options?.encrypt !== false
|
|
20
|
-
? await
|
|
20
|
+
? await encrypt(value.unencrypted, scope.password)
|
|
21
21
|
: value.unencrypted,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
@@ -26,6 +26,11 @@ export async function serialize(scope, value, options) {
|
|
|
26
26
|
"@schema": value.toJSON(),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
else if (value instanceof Date) {
|
|
30
|
+
return {
|
|
31
|
+
"@date": value.toISOString(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
29
34
|
else if (value instanceof Scope) {
|
|
30
35
|
return undefined;
|
|
31
36
|
}
|
|
@@ -51,6 +56,9 @@ export async function deserialize(scope, value) {
|
|
|
51
56
|
else if ("@schema" in value) {
|
|
52
57
|
return value["@schema"];
|
|
53
58
|
}
|
|
59
|
+
else if ("@date" in value) {
|
|
60
|
+
return new Date(value["@date"]);
|
|
61
|
+
}
|
|
54
62
|
else {
|
|
55
63
|
return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, value]) => [
|
|
56
64
|
key,
|
package/lib/test/bun.js
CHANGED
|
@@ -32,12 +32,13 @@ alchemy.test = test;
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export function test(meta, defaultOptions) {
|
|
35
|
+
let isFailed = false;
|
|
35
36
|
defaultOptions = defaultOptions ?? {};
|
|
36
37
|
if (defaultOptions.stateStore === undefined &&
|
|
37
38
|
// process.env.CI &&
|
|
38
39
|
process.env.ALCHEMY_STATE_STORE === "cloudflare") {
|
|
39
40
|
defaultOptions.stateStore = (scope) => new R2RestStateStore(scope, {
|
|
40
|
-
apiKey: process.env.CLOUDFLARE_API_KEY,
|
|
41
|
+
apiKey: alchemy.secret(process.env.CLOUDFLARE_API_KEY),
|
|
41
42
|
email: process.env.CLOUDFLARE_EMAIL,
|
|
42
43
|
bucketName: process.env.CLOUDFLARE_BUCKET_NAME,
|
|
43
44
|
});
|
|
@@ -70,7 +71,7 @@ export function test(meta, defaultOptions) {
|
|
|
70
71
|
};
|
|
71
72
|
// Clean up test scope after all tests complete
|
|
72
73
|
afterAll(async () => {
|
|
73
|
-
if (defaultOptions?.destroy !== false) {
|
|
74
|
+
if (defaultOptions?.destroy !== false && !isFailed) {
|
|
74
75
|
await alchemy.destroy(test.scope);
|
|
75
76
|
}
|
|
76
77
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sha256(value: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DefaultTheme, HeadConfig, ThemeOptions } from "vitepress";
|
|
2
|
+
export type VitePressConfigProps = {
|
|
3
|
+
cwd: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
head?: HeadConfig[];
|
|
7
|
+
theme?: ThemeOptions;
|
|
8
|
+
themeConfig: DefaultTheme.Config;
|
|
9
|
+
};
|
|
10
|
+
export type VitePressConfig = Awaited<ReturnType<typeof VitePressConfig>>;
|
|
11
|
+
export declare function VitePressConfig(props: VitePressConfigProps): Promise<import("../../fs").File>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { StaticTypeScriptFile } from "../../fs/static-typescript-file";
|
|
3
|
+
export function VitePressConfig(props) {
|
|
4
|
+
return StaticTypeScriptFile(path.join(props.cwd, ".vitepress", "config.mts"), `import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
|
|
5
|
+
import footnotePlugin from "markdown-it-footnote";
|
|
6
|
+
import { defineConfig } from "vitepress";
|
|
7
|
+
|
|
8
|
+
// https://vitepress.dev/reference/site-config
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
title: ${JSON.stringify(props.title || "Alchemy")},
|
|
11
|
+
description: ${JSON.stringify(props.description || "Alchemy Docs")},
|
|
12
|
+
head: ${JSON.stringify(props.head || [])},
|
|
13
|
+
markdown: {
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
codeTransformers: [transformerTwoslash()],
|
|
16
|
+
theme: ${JSON.stringify(props.theme ?? {
|
|
17
|
+
light: "light-plus",
|
|
18
|
+
dark: "dark-plus",
|
|
19
|
+
})},
|
|
20
|
+
config: (md) => md.use(footnotePlugin),
|
|
21
|
+
},
|
|
22
|
+
// https://vitepress.dev/reference/default-theme-config
|
|
23
|
+
themeConfig: ${JSON.stringify({
|
|
24
|
+
...props.themeConfig,
|
|
25
|
+
search: props.themeConfig.search ?? { provider: "local" },
|
|
26
|
+
nav: props.themeConfig.nav ?? [{ text: "Home", link: "/" }],
|
|
27
|
+
sidebar: props.themeConfig.sidebar ?? [],
|
|
28
|
+
socialLinks: props.themeConfig.socialLinks ?? [],
|
|
29
|
+
})}
|
|
30
|
+
});
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Document } from "../../ai/document";
|
|
2
|
+
import { StaticTextFile } from "../../fs";
|
|
1
3
|
import type { Secret } from "../../secret";
|
|
2
4
|
/**
|
|
3
5
|
* Image that can be themed for light/dark mode
|
|
@@ -151,11 +153,11 @@ export interface HomePageProps {
|
|
|
151
153
|
/**
|
|
152
154
|
* User prompt describing the design of the home page
|
|
153
155
|
*/
|
|
154
|
-
prompt
|
|
156
|
+
prompt?: string;
|
|
155
157
|
/**
|
|
156
158
|
* Optional extension to the built-in system prompt
|
|
157
159
|
*/
|
|
158
|
-
|
|
160
|
+
system?: string;
|
|
159
161
|
/**
|
|
160
162
|
* Base URL for the OpenAI API
|
|
161
163
|
* @default 'https://api.openai.com/v1'
|
|
@@ -179,7 +181,7 @@ export interface HomePageProps {
|
|
|
179
181
|
/**
|
|
180
182
|
* Output type for the HomePage resource
|
|
181
183
|
*/
|
|
182
|
-
export type HomePage =
|
|
184
|
+
export type HomePage = StaticTextFile | Document;
|
|
183
185
|
/**
|
|
184
186
|
* Parse YAML frontmatter to home page config
|
|
185
187
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import alchemy from "alchemy";
|
|
2
1
|
import yaml from "yaml";
|
|
3
2
|
import { Document } from "../../ai/document";
|
|
3
|
+
import { alchemy } from "../../alchemy";
|
|
4
|
+
import { StaticTextFile } from "../../fs";
|
|
4
5
|
/**
|
|
5
6
|
* Parse YAML frontmatter to home page config
|
|
6
7
|
*/
|
|
@@ -90,45 +91,57 @@ Follow these guidelines:
|
|
|
90
91
|
6. Ensure all links are properly formatted
|
|
91
92
|
7. Use appropriate markdown formatting for headings, lists, etc.
|
|
92
93
|
|
|
93
|
-
${props.
|
|
94
|
+
${props.system || ""}
|
|
94
95
|
`;
|
|
95
96
|
// Convert hero and features to YAML strings if provided
|
|
96
97
|
const heroYaml = props.hero ? JSON.stringify(props.hero, null, 2) : "";
|
|
97
98
|
const featuresYaml = props.features
|
|
98
99
|
? JSON.stringify(props.features, null, 2)
|
|
99
100
|
: "";
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
${systemPrompt}
|
|
113
|
-
|
|
114
|
-
Create a VitePress homepage based on the following description:
|
|
115
|
-
${props.prompt}
|
|
116
|
-
|
|
117
|
-
${props.hero
|
|
118
|
-
? `Use this hero section configuration:
|
|
119
|
-
\`\`\`json
|
|
120
|
-
${heroYaml}
|
|
121
|
-
\`\`\``
|
|
122
|
-
: ""}
|
|
123
|
-
|
|
124
|
-
${props.features
|
|
125
|
-
? `Use these features:
|
|
126
|
-
\`\`\`json
|
|
127
|
-
${featuresYaml}
|
|
128
|
-
\`\`\``
|
|
129
|
-
: ""}
|
|
101
|
+
if (props.prompt) {
|
|
102
|
+
return Document(id, {
|
|
103
|
+
title: props.title,
|
|
104
|
+
path: props.outFile,
|
|
105
|
+
baseURL: props.baseURL,
|
|
106
|
+
apiKey: props.apiKey,
|
|
107
|
+
model: props.model ?? {
|
|
108
|
+
id: "claude-3-7-sonnet-latest",
|
|
109
|
+
provider: "anthropic",
|
|
110
|
+
},
|
|
111
|
+
temperature: props.temperature ?? 0.7,
|
|
112
|
+
prompt: await alchemy `
|
|
113
|
+
${systemPrompt}
|
|
114
|
+
|
|
115
|
+
Create a VitePress homepage based on the following description:
|
|
116
|
+
${props.prompt}
|
|
130
117
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
${props.hero
|
|
119
|
+
? `Use this hero section configuration:
|
|
120
|
+
\`\`\`json
|
|
121
|
+
${heroYaml}
|
|
122
|
+
\`\`\``
|
|
123
|
+
: ""}
|
|
124
|
+
|
|
125
|
+
${props.features
|
|
126
|
+
? `Use these features:
|
|
127
|
+
\`\`\`json
|
|
128
|
+
${featuresYaml}
|
|
129
|
+
\`\`\``
|
|
130
|
+
: ""}
|
|
131
|
+
|
|
132
|
+
The output should be a complete index.md file with proper YAML frontmatter and markdown content.
|
|
133
|
+
`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return StaticTextFile(id, props.outFile, `---
|
|
138
|
+
${yaml.stringify({
|
|
139
|
+
layout: "home",
|
|
140
|
+
name: props.title,
|
|
141
|
+
hero: props.hero,
|
|
142
|
+
features: props.features,
|
|
143
|
+
})}
|
|
144
|
+
---
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
134
147
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process markdown files with frontmatter to generate navigation items
|
|
3
|
+
* @param directoryPath The directory containing markdown files
|
|
4
|
+
* @param linkPrefix The prefix for navigation links
|
|
5
|
+
* @returns Sorted array of navigation items with text and link properties
|
|
6
|
+
*/
|
|
7
|
+
export declare function processFrontmatterFiles(directoryPath: string, linkPrefix: string): Promise<{
|
|
8
|
+
text: string;
|
|
9
|
+
link: string;
|
|
10
|
+
items?: {
|
|
11
|
+
text: string;
|
|
12
|
+
link: string;
|
|
13
|
+
items?: {
|
|
14
|
+
text: string;
|
|
15
|
+
link: string;
|
|
16
|
+
}[];
|
|
17
|
+
}[];
|
|
18
|
+
}[]>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Process markdown files with frontmatter to generate navigation items
|
|
5
|
+
* @param directoryPath The directory containing markdown files
|
|
6
|
+
* @param linkPrefix The prefix for navigation links
|
|
7
|
+
* @returns Sorted array of navigation items with text and link properties
|
|
8
|
+
*/
|
|
9
|
+
export async function processFrontmatterFiles(directoryPath, linkPrefix) {
|
|
10
|
+
const entries = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
11
|
+
const processedEntries = await Promise.all(entries
|
|
12
|
+
.filter((entry) => !entry.name.endsWith("index.md"))
|
|
13
|
+
.map(async (entry) => {
|
|
14
|
+
const fullPath = path.join(directoryPath, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
// Process subdirectory recursively
|
|
17
|
+
const items = await processFrontmatterFiles(fullPath, `${linkPrefix}/${entry.name}`);
|
|
18
|
+
// Create section for subdirectory
|
|
19
|
+
return {
|
|
20
|
+
text: entry.name.charAt(0).toUpperCase() + entry.name.slice(1),
|
|
21
|
+
collapsed: false,
|
|
22
|
+
items,
|
|
23
|
+
order: 10000, // Default order for directories
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Process markdown file
|
|
27
|
+
if (!entry.name.endsWith(".md")) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
31
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
32
|
+
let order = 10000;
|
|
33
|
+
let title;
|
|
34
|
+
if (frontmatterMatch) {
|
|
35
|
+
const frontmatter = frontmatterMatch[1];
|
|
36
|
+
const orderMatch = frontmatter.match(/order:\s*(\d+)/);
|
|
37
|
+
if (orderMatch) {
|
|
38
|
+
order = parseInt(orderMatch[1]);
|
|
39
|
+
}
|
|
40
|
+
const titleMatch = frontmatter.match(/title:\s*(.+)/);
|
|
41
|
+
if (titleMatch) {
|
|
42
|
+
title = titleMatch[1].trim();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// If no title in frontmatter, try to get first heading
|
|
46
|
+
if (!title) {
|
|
47
|
+
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
48
|
+
if (headingMatch) {
|
|
49
|
+
title = headingMatch[1].trim();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Fall back to filename if no title found
|
|
53
|
+
if (!title) {
|
|
54
|
+
const name = entry.name.replace(".md", "");
|
|
55
|
+
title = name.charAt(0).toUpperCase() + name.slice(1);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
text: title,
|
|
59
|
+
link: `${linkPrefix}/${entry.name}`,
|
|
60
|
+
order,
|
|
61
|
+
};
|
|
62
|
+
}));
|
|
63
|
+
// Filter out null entries and sort by order
|
|
64
|
+
return processedEntries
|
|
65
|
+
.filter((entry) => entry !== null)
|
|
66
|
+
.sort((a, b) => a.order - b.order)
|
|
67
|
+
.map(({ text, link, items }) => items ? { text, items, collapsed: true } : { text, link });
|
|
68
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DefaultTheme, ThemeOptions } from "vitepress";
|
|
2
1
|
import type { Context } from "../../context";
|
|
3
2
|
import { Resource } from "../../resource";
|
|
4
3
|
export interface VitePressProjectProps {
|
|
@@ -47,11 +46,9 @@ export interface VitePressProjectProps {
|
|
|
47
46
|
/**
|
|
48
47
|
* The theme options to use
|
|
49
48
|
*/
|
|
50
|
-
theme?: ThemeOptions;
|
|
51
49
|
/**
|
|
52
50
|
* The theme config to use
|
|
53
51
|
*/
|
|
54
|
-
themeConfig: DefaultTheme.Config;
|
|
55
52
|
/**
|
|
56
53
|
* Whether to delete the project folder during the delete phase
|
|
57
54
|
* @default true
|
|
@@ -69,5 +66,9 @@ export interface VitePressProject extends VitePressProjectProps, Resource {
|
|
|
69
66
|
* The name/path of the project
|
|
70
67
|
*/
|
|
71
68
|
name: string;
|
|
69
|
+
/**
|
|
70
|
+
* The directory of the project
|
|
71
|
+
*/
|
|
72
|
+
dir: string;
|
|
72
73
|
}
|
|
73
|
-
export declare const
|
|
74
|
+
export declare const VitepressProject: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<VitePressProject>, id: string, props: VitePressProjectProps) => Promise<VitePressProject>);
|
|
@@ -6,7 +6,7 @@ import { Folder, StaticJsonFile, StaticTextFile, StaticTypeScriptFile, } from ".
|
|
|
6
6
|
import { Resource } from "../../resource";
|
|
7
7
|
import { InstallDependencies, fixedDependencies } from "./dependencies";
|
|
8
8
|
const execAsync = promisify(exec);
|
|
9
|
-
export const
|
|
9
|
+
export const VitepressProject = Resource("project::VitepressProject", {
|
|
10
10
|
alwaysUpdate: true,
|
|
11
11
|
}, async function (id, props) {
|
|
12
12
|
const dir = props.dir ?? props.name;
|
|
@@ -14,8 +14,9 @@ export const VitePressProject = Resource("project::VitePressProject", {
|
|
|
14
14
|
try {
|
|
15
15
|
if (props.delete !== false) {
|
|
16
16
|
if (await fs.exists(dir)) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
18
|
+
// // Delete the entire project directory
|
|
19
|
+
// await execAsync(`rm -rf ${dir}`);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -57,11 +58,18 @@ export const VitePressProject = Resource("project::VitePressProject", {
|
|
|
57
58
|
await Folder(path.join(cwd, ".vitepress", "theme"));
|
|
58
59
|
await Promise.all([
|
|
59
60
|
StaticTextFile(path.join(cwd, ".gitignore"), `.vitepress/cache\n`),
|
|
60
|
-
StaticJsonFile(path.join(cwd, "tsconfig.json"), {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
// StaticJsonFile(path.join(cwd, "tsconfig.json"), {
|
|
62
|
+
// extends: props.tsconfig?.extends,
|
|
63
|
+
// references: props.tsconfig?.references?.map((path) => ({ path })),
|
|
64
|
+
// compilerOptions: props.tsconfig?.compilerOptions ?? {
|
|
65
|
+
// target: "ES2020",
|
|
66
|
+
// module: "ESNext",
|
|
67
|
+
// moduleResolution: "Node",
|
|
68
|
+
// allowJs: true,
|
|
69
|
+
// skipLibCheck: true,
|
|
70
|
+
// forceConsistentCasingInFileNames: true,
|
|
71
|
+
// },
|
|
72
|
+
// }),
|
|
65
73
|
// TypeScriptFile(
|
|
66
74
|
// path.join(cwd, "alchemy.run.ts"),
|
|
67
75
|
// `import alchemy from "alchemy";
|
|
@@ -232,34 +240,10 @@ export default {
|
|
|
232
240
|
.DocSearch {
|
|
233
241
|
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
|
234
242
|
}
|
|
235
|
-
`),
|
|
236
|
-
StaticTypeScriptFile(path.join(cwd, ".vitepress", "config.mts"), `import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
|
|
237
|
-
import footnotePlugin from "markdown-it-footnote";
|
|
238
|
-
import { defineConfig } from "vitepress";
|
|
239
|
-
|
|
240
|
-
// https://vitepress.dev/reference/site-config
|
|
241
|
-
export default defineConfig({
|
|
242
|
-
title: ${JSON.stringify(props.title || "Alchemy")},
|
|
243
|
-
description: ${JSON.stringify(props.description || "Alchemy Docs")},
|
|
244
|
-
markdown: {
|
|
245
|
-
// @ts-ignore
|
|
246
|
-
codeTransformers: [transformerTwoslash()],
|
|
247
|
-
theme: ${JSON.stringify(props.theme ?? {
|
|
248
|
-
light: "light-plus",
|
|
249
|
-
dark: "dark-plus",
|
|
250
|
-
})},
|
|
251
|
-
config: (md) => md.use(footnotePlugin),
|
|
252
|
-
},
|
|
253
|
-
// https://vitepress.dev/reference/default-theme-config
|
|
254
|
-
themeConfig: ${JSON.stringify({
|
|
255
|
-
...props.themeConfig,
|
|
256
|
-
search: props.themeConfig.search ?? { provider: "local" },
|
|
257
|
-
nav: props.themeConfig.nav ?? [{ text: "Home", link: "/" }],
|
|
258
|
-
sidebar: props.themeConfig.sidebar ?? [],
|
|
259
|
-
socialLinks: props.themeConfig.socialLinks ?? [],
|
|
260
|
-
})}
|
|
261
|
-
});
|
|
262
243
|
`),
|
|
263
244
|
]);
|
|
264
|
-
return this(
|
|
245
|
+
return this({
|
|
246
|
+
...props,
|
|
247
|
+
dir,
|
|
248
|
+
});
|
|
265
249
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,16 +38,18 @@
|
|
|
38
38
|
"@aws-sdk/client-sesv2": "^3.0.0",
|
|
39
39
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
40
40
|
"@aws-sdk/client-sts": "^3.0.0",
|
|
41
|
-
"arktype": "^2.0.0",
|
|
42
|
-
"prettier": "^3.0.0",
|
|
43
41
|
"@octokit/rest": "^21.1.1",
|
|
44
42
|
"ai": "^4.0.0",
|
|
43
|
+
"arktype": "^2.0.0",
|
|
44
|
+
"aws4fetch": "^1.0.20",
|
|
45
|
+
"cloudflare": "^4.0.0",
|
|
45
46
|
"diff": "^7.0.0",
|
|
46
47
|
"esbuild": "^0.25.1",
|
|
47
48
|
"glob": "^10.0.0",
|
|
48
49
|
"hono": "^4.0.0",
|
|
49
|
-
"libsodium-wrappers": "^0.7.15",
|
|
50
50
|
"jszip": "^3.0.0",
|
|
51
|
+
"libsodium-wrappers": "^0.7.15",
|
|
52
|
+
"prettier": "^3.0.0",
|
|
51
53
|
"stripe": "^17.0.0",
|
|
52
54
|
"turndown": "^7.0.0",
|
|
53
55
|
"yaml": "^2.0.0",
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
"devDependencies": {
|
|
57
59
|
"@ai-sdk/anthropic": "^1.1.6",
|
|
58
60
|
"@ai-sdk/openai": "^1.1.9",
|
|
61
|
+
"@aws-sdk/client-s3": "3.726.1",
|
|
59
62
|
"@biomejs/biome": "^1.9.4",
|
|
60
63
|
"@cloudflare/workers-types": "^4.20250303.0",
|
|
61
64
|
"@octokit/rest": "^21.1.1",
|
|
@@ -67,6 +70,7 @@
|
|
|
67
70
|
"ai": "^4.1.16",
|
|
68
71
|
"arktype": "^2.1.16",
|
|
69
72
|
"change-case": "^5.4.4",
|
|
73
|
+
"cloudflare": "^4.2.0",
|
|
70
74
|
"libsodium-wrappers": "^0.7.15",
|
|
71
75
|
"openpgp": "^6.1.0",
|
|
72
76
|
"prettier": "^3.5.3",
|
package/src/ai/astro-file.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import prettier from "prettier";
|
|
5
3
|
import type { Context } from "../context";
|
|
4
|
+
import { StaticAstroFile } from "../fs/static-astro-file";
|
|
6
5
|
import { Resource } from "../resource";
|
|
7
6
|
import type { Secret } from "../secret";
|
|
8
|
-
import { ignore } from "../util/ignore";
|
|
9
7
|
import { type ModelConfig, createModel } from "./client";
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -161,20 +159,10 @@ export const AstroFile = Resource(
|
|
|
161
159
|
async function (
|
|
162
160
|
this: Context<AstroFile>,
|
|
163
161
|
id: string,
|
|
164
|
-
props: AstroFileProps
|
|
162
|
+
props: AstroFileProps
|
|
165
163
|
): Promise<AstroFile> {
|
|
166
|
-
//
|
|
167
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
168
|
-
|
|
164
|
+
// Handle deletion phase
|
|
169
165
|
if (this.phase === "delete") {
|
|
170
|
-
try {
|
|
171
|
-
await fs.unlink(props.path);
|
|
172
|
-
} catch (error: any) {
|
|
173
|
-
// Ignore if file doesn't exist
|
|
174
|
-
if (error.code !== "ENOENT") {
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
166
|
return this.destroy();
|
|
179
167
|
}
|
|
180
168
|
|
|
@@ -213,7 +201,7 @@ export const AstroFile = Resource(
|
|
|
213
201
|
|
|
214
202
|
if (retryResult.error) {
|
|
215
203
|
throw new Error(
|
|
216
|
-
`Failed to generate valid Astro code: ${retryResult.error}
|
|
204
|
+
`Failed to generate valid Astro code: ${retryResult.error}`
|
|
217
205
|
);
|
|
218
206
|
}
|
|
219
207
|
|
|
@@ -235,24 +223,17 @@ export const AstroFile = Resource(
|
|
|
235
223
|
console.warn("Failed to format Astro code with Prettier:", error);
|
|
236
224
|
}
|
|
237
225
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Write content to file
|
|
243
|
-
await fs.writeFile(props.path, code);
|
|
244
|
-
|
|
245
|
-
// Get file stats for timestamps
|
|
246
|
-
const stats = await fs.stat(props.path);
|
|
226
|
+
// Use StaticAstroFile to create/update the file
|
|
227
|
+
const file = await StaticAstroFile("file", props.path, code);
|
|
247
228
|
|
|
248
229
|
// Return the resource
|
|
249
230
|
return this({
|
|
250
231
|
...props,
|
|
251
|
-
content:
|
|
252
|
-
createdAt:
|
|
253
|
-
updatedAt:
|
|
232
|
+
content: file.content,
|
|
233
|
+
createdAt: Date.now(),
|
|
234
|
+
updatedAt: Date.now(),
|
|
254
235
|
});
|
|
255
|
-
}
|
|
236
|
+
}
|
|
256
237
|
);
|
|
257
238
|
|
|
258
239
|
/**
|
|
@@ -263,7 +244,7 @@ export const AstroFile = Resource(
|
|
|
263
244
|
* @returns The extracted Astro code or error message
|
|
264
245
|
*/
|
|
265
246
|
async function extractAstroCode(
|
|
266
|
-
text: string
|
|
247
|
+
text: string
|
|
267
248
|
): Promise<{ code: string; error?: string }> {
|
|
268
249
|
const astroCodeRegex = /```astro\s*([\s\S]*?)```/g;
|
|
269
250
|
const matches = Array.from(text.matchAll(astroCodeRegex));
|
package/src/ai/client.ts
CHANGED
|
@@ -56,3 +56,62 @@ export function createModel(config: ClientConfig) {
|
|
|
56
56
|
return openai(config.model?.id ?? "gpt-4o");
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Maximum time to retry in milliseconds (5 minutes)
|
|
62
|
+
*/
|
|
63
|
+
const MAX_RETRY_TIME = 5 * 60 * 1000;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initial delay between retries in milliseconds
|
|
67
|
+
*/
|
|
68
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maximum number of retries
|
|
72
|
+
*/
|
|
73
|
+
const MAX_RETRIES = 10;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handles rate limiting with exponential backoff
|
|
77
|
+
* @param fn Function to retry
|
|
78
|
+
* @returns Result of the function
|
|
79
|
+
* @throws Error if max retries or time is exceeded
|
|
80
|
+
*/
|
|
81
|
+
export async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
82
|
+
let retryCount = 0;
|
|
83
|
+
let lastError: Error | null = null;
|
|
84
|
+
let startTime = Date.now();
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
try {
|
|
88
|
+
return await fn();
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
lastError = error;
|
|
91
|
+
|
|
92
|
+
console.log("retry error", error);
|
|
93
|
+
|
|
94
|
+
// Check if we should retry
|
|
95
|
+
const isRateLimit = error.statusCode === 429;
|
|
96
|
+
const timeElapsed = Date.now() - startTime;
|
|
97
|
+
const shouldRetry =
|
|
98
|
+
isRateLimit && retryCount < MAX_RETRIES && timeElapsed < MAX_RETRY_TIME;
|
|
99
|
+
|
|
100
|
+
if (!shouldRetry) {
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate delay with exponential backoff
|
|
105
|
+
const delay = Math.min(
|
|
106
|
+
INITIAL_RETRY_DELAY * Math.pow(2, retryCount),
|
|
107
|
+
MAX_RETRY_TIME - timeElapsed
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
console.log(`Retrying in ${delay}ms`);
|
|
111
|
+
|
|
112
|
+
// Wait before retrying
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
114
|
+
retryCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|