alchemy 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/ai/astro-file.d.ts +137 -0
- package/lib/ai/astro-file.js +173 -0
- package/lib/ai/client.d.ts +2 -10
- package/lib/ai/client.js +8 -24
- package/lib/ai/css-file.d.ts +131 -0
- package/lib/ai/css-file.js +158 -0
- package/lib/ai/data.d.ts +45 -5
- package/lib/ai/data.js +45 -10
- package/lib/ai/document.d.ts +44 -20
- package/lib/ai/document.js +95 -28
- package/lib/ai/html-file.d.ts +128 -0
- package/lib/ai/html-file.js +155 -0
- package/lib/ai/index.d.ts +7 -0
- package/lib/ai/index.js +7 -0
- package/lib/ai/json-file.d.ts +160 -0
- package/lib/ai/json-file.js +208 -0
- package/lib/ai/typescript-file.d.ts +140 -0
- package/lib/ai/typescript-file.js +176 -0
- package/lib/ai/vue-file.d.ts +126 -0
- package/lib/ai/vue-file.js +153 -0
- package/lib/ai/yaml-file.d.ts +169 -0
- package/lib/ai/yaml-file.js +236 -0
- package/lib/cloudflare/dns.d.ts +88 -0
- package/lib/cloudflare/dns.js +192 -0
- 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/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/file-system-state-store.d.ts +17 -0
- package/lib/fs/file-system-state-store.js +87 -0
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +2 -2
- package/lib/fs/index.d.ts +4 -4
- package/lib/fs/index.js +4 -4
- package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +2 -2
- package/lib/fs/static-json-file.js +14 -0
- package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
- package/lib/fs/{text-file.js → static-text-file.js} +1 -1
- package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +2 -2
- package/lib/fs/{typescript-file.js → static-typescript-file.js} +1 -1
- package/lib/fs/{yaml-file.d.ts → static-yaml-file.d.ts} +2 -2
- package/lib/fs/{yaml-file.js → static-yaml-file.js} +1 -1
- package/lib/internal/getting-started.d.ts +10 -0
- package/lib/internal/getting-started.js +94 -0
- package/lib/internal/project.d.ts +13 -0
- package/lib/internal/project.js +101 -0
- package/lib/internal/providers.d.ts +23 -0
- package/lib/internal/providers.js +148 -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/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 +244 -0
- package/lib/web/vitepress/home-page.js +134 -0
- package/lib/{vitepress → web/vitepress}/index.d.ts +1 -0
- package/lib/{vitepress → web/vitepress}/index.js +1 -0
- package/lib/{vitepress → web/vitepress}/vitepress.d.ts +10 -8
- package/lib/{vitepress → web/vitepress}/vitepress.js +11 -12
- package/package.json +6 -2
- package/src/ai/astro-file.ts +288 -0
- package/src/ai/client.ts +8 -28
- package/src/ai/css-file.ts +266 -0
- package/src/ai/data.ts +53 -16
- package/src/ai/document.ts +130 -33
- package/src/ai/html-file.ts +263 -0
- package/src/ai/index.ts +7 -0
- package/src/ai/json-file.ts +349 -0
- package/src/ai/typescript-file.ts +293 -0
- package/src/ai/vue-file.ts +261 -0
- package/src/ai/yaml-file.ts +368 -0
- package/src/apply.ts +0 -1
- package/src/cloudflare/dns.ts +344 -0
- 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/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/file-system-state-store.ts +109 -0
- package/src/fs/folder.ts +14 -2
- package/src/fs/index.ts +4 -4
- package/src/fs/{json-file.ts → static-json-file.ts} +13 -3
- package/src/fs/{text-file.ts → static-text-file.ts} +5 -2
- package/src/fs/{typescript-file.ts → static-typescript-file.ts} +3 -3
- package/src/fs/{yaml-file.ts → static-yaml-file.ts} +5 -2
- package/src/internal/getting-started.ts +107 -0
- package/src/internal/project.ts +119 -0
- package/src/internal/providers.ts +209 -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/custom-theme.ts +514 -0
- package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
- package/src/web/vitepress/home-page.ts +363 -0
- package/src/{vitepress → web/vitepress}/index.ts +1 -0
- package/src/{vitepress → web/vitepress}/vitepress.ts +34 -27
- 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/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/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,87 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ignore } from "../util/ignore";
|
|
4
|
+
import { deserialize, serialize } from "../util/serde";
|
|
5
|
+
const stateRootDir = path.join(process.cwd(), ".alchemy");
|
|
6
|
+
export class FileSystemStateStore {
|
|
7
|
+
scope;
|
|
8
|
+
dir;
|
|
9
|
+
constructor(scope) {
|
|
10
|
+
this.scope = scope;
|
|
11
|
+
this.dir = path.join(stateRootDir, ...scope.chain);
|
|
12
|
+
}
|
|
13
|
+
async init() {
|
|
14
|
+
await fs.mkdir(stateRootDir, { recursive: true });
|
|
15
|
+
await fs.mkdir(this.dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
async deinit() {
|
|
18
|
+
await ignore("ENOENT", () => fs.rmdir(this.dir));
|
|
19
|
+
}
|
|
20
|
+
async count() {
|
|
21
|
+
return Object.keys(await this.list()).length;
|
|
22
|
+
}
|
|
23
|
+
async list() {
|
|
24
|
+
try {
|
|
25
|
+
const files = await fs.readdir(this.dir, {
|
|
26
|
+
withFileTypes: true,
|
|
27
|
+
});
|
|
28
|
+
return files
|
|
29
|
+
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
|
|
30
|
+
.map((dirent) => dirent.name.replace(/\.json$/, ""))
|
|
31
|
+
.map((key) => key.replaceAll(":", "/"));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === "ENOENT") {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async get(key) {
|
|
41
|
+
try {
|
|
42
|
+
const content = await fs.readFile(await this.getPath(key), "utf8");
|
|
43
|
+
const state = (await deserialize(this.scope, JSON.parse(content)));
|
|
44
|
+
if (state.output === undefined) {
|
|
45
|
+
state.output = {};
|
|
46
|
+
}
|
|
47
|
+
state.output.Scope = this.scope;
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code === "ENOENT") {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async set(key, value) {
|
|
58
|
+
return fs.writeFile(await this.getPath(key), JSON.stringify(await serialize(this.scope, value), null, 2));
|
|
59
|
+
}
|
|
60
|
+
async delete(key) {
|
|
61
|
+
return fs.unlink(await this.getPath(key));
|
|
62
|
+
}
|
|
63
|
+
async all() {
|
|
64
|
+
return this.getBatch(await this.list());
|
|
65
|
+
}
|
|
66
|
+
async getBatch(ids) {
|
|
67
|
+
return Object.fromEntries((await Promise.all(Array.from(ids).flatMap(async (id) => {
|
|
68
|
+
const s = await this.get(id);
|
|
69
|
+
if (s === undefined) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return [[id, s]];
|
|
73
|
+
}))).flat());
|
|
74
|
+
}
|
|
75
|
+
async getPath(key) {
|
|
76
|
+
if (key.includes(":")) {
|
|
77
|
+
throw new Error(`ID cannot include colons: ${key}`);
|
|
78
|
+
}
|
|
79
|
+
if (key.includes("/")) {
|
|
80
|
+
key = key.replaceAll("/", ":");
|
|
81
|
+
}
|
|
82
|
+
const file = path.join(this.dir, `${key}.json`);
|
|
83
|
+
const dir = path.dirname(file);
|
|
84
|
+
await fs.mkdir(dir, { recursive: true });
|
|
85
|
+
return file;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/lib/fs/folder.d.ts
CHANGED
|
@@ -10,6 +10,16 @@ export interface FolderProps {
|
|
|
10
10
|
* @default true
|
|
11
11
|
*/
|
|
12
12
|
delete?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to clean the folder during the deletion phase (even if it contains existing files)
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
clean?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether to create the folder recursively
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
recursive?: boolean;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Base folder resource type
|
package/lib/fs/folder.js
CHANGED
|
@@ -28,12 +28,12 @@ export const Folder = Resource("fs::Folder", async function (id, props) {
|
|
|
28
28
|
if (this.phase === "delete") {
|
|
29
29
|
if (props?.delete !== false) {
|
|
30
30
|
// we just do a best effort attempt
|
|
31
|
-
await ignore(["ENOENT", "ENOTEMPTY"], async () => fs.promises.rmdir(dirPath));
|
|
31
|
+
await ignore(["ENOENT", "ENOTEMPTY"], async () => fs.promises.rmdir(dirPath, { recursive: props?.clean ?? false }));
|
|
32
32
|
}
|
|
33
33
|
return this.destroy();
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
await ignore("EEXIST", async () => fs.promises.mkdir(dirPath, { recursive: true }));
|
|
36
|
+
await ignore("EEXIST", async () => fs.promises.mkdir(dirPath, { recursive: props?.recursive ?? true }));
|
|
37
37
|
}
|
|
38
38
|
return this({
|
|
39
39
|
path: dirPath,
|
package/lib/fs/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from "./file";
|
|
|
2
2
|
export * from "./file-collection";
|
|
3
3
|
export * from "./file-ref";
|
|
4
4
|
export * from "./folder";
|
|
5
|
-
export * from "./json-file";
|
|
6
|
-
export * from "./text-file";
|
|
7
|
-
export * from "./typescript-file";
|
|
8
|
-
export * from "./yaml-file";
|
|
5
|
+
export * from "./static-json-file";
|
|
6
|
+
export * from "./static-text-file";
|
|
7
|
+
export * from "./static-typescript-file";
|
|
8
|
+
export * from "./static-yaml-file";
|
package/lib/fs/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export * from "./file";
|
|
|
2
2
|
export * from "./file-collection";
|
|
3
3
|
export * from "./file-ref";
|
|
4
4
|
export * from "./folder";
|
|
5
|
-
export * from "./json-file";
|
|
6
|
-
export * from "./text-file";
|
|
7
|
-
export * from "./typescript-file";
|
|
8
|
-
export * from "./yaml-file";
|
|
5
|
+
export * from "./static-json-file";
|
|
6
|
+
export * from "./static-text-file";
|
|
7
|
+
export * from "./static-typescript-file";
|
|
8
|
+
export * from "./static-yaml-file";
|
|
@@ -12,5 +12,5 @@ import { File } from "./file";
|
|
|
12
12
|
* features: ["auth", "logging"]
|
|
13
13
|
* });
|
|
14
14
|
*/
|
|
15
|
-
export type
|
|
16
|
-
export declare function
|
|
15
|
+
export type StaticJsonFile = File;
|
|
16
|
+
export declare function StaticJsonFile(id: string, content: any): Promise<StaticJsonFile>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { File } from "./file";
|
|
2
|
+
export async function StaticJsonFile(id, content) {
|
|
3
|
+
const prettier = await import("prettier");
|
|
4
|
+
return File(id, {
|
|
5
|
+
path: id,
|
|
6
|
+
content: await prettier.format(JSON.stringify(content), {
|
|
7
|
+
parser: "json",
|
|
8
|
+
editor: {
|
|
9
|
+
tabWidth: 2,
|
|
10
|
+
indentWidth: 2,
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -8,5 +8,5 @@ import { File } from "./file";
|
|
|
8
8
|
* "# Project Name\n\nProject description goes here."
|
|
9
9
|
* );
|
|
10
10
|
*/
|
|
11
|
-
export type
|
|
12
|
-
export declare function
|
|
11
|
+
export type StaticTextFile = File;
|
|
12
|
+
export declare function StaticTextFile(id: string, content: string): Promise<StaticTextFile>;
|
|
@@ -15,5 +15,5 @@ import { File } from "./file";
|
|
|
15
15
|
* }
|
|
16
16
|
* `);
|
|
17
17
|
*/
|
|
18
|
-
export type
|
|
19
|
-
export declare function
|
|
18
|
+
export type StaticTypeScriptFile = File;
|
|
19
|
+
export declare function StaticTypeScriptFile(id: string, content: string): Promise<StaticTypeScriptFile>;
|
|
@@ -15,5 +15,5 @@ import { File } from "./file";
|
|
|
15
15
|
* max: 10
|
|
16
16
|
* });
|
|
17
17
|
*/
|
|
18
|
-
export type
|
|
19
|
-
export declare function
|
|
18
|
+
export type StaticYamlFile = File;
|
|
19
|
+
export declare function StaticYamlFile(id: string, content: any): Promise<StaticYamlFile>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Document } from "alchemy/ai";
|
|
2
|
+
import type { Folder } from "alchemy/fs";
|
|
3
|
+
export interface GettingStartedProps {
|
|
4
|
+
/**
|
|
5
|
+
* The output directory for the getting started document.
|
|
6
|
+
*/
|
|
7
|
+
outDir: string | Folder;
|
|
8
|
+
}
|
|
9
|
+
export type GettingStarted = Document;
|
|
10
|
+
export declare function GettingStarted({ outDir, }: GettingStartedProps): Promise<Document>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import alchemy from "alchemy";
|
|
2
|
+
import { Document } from "alchemy/ai";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export async function GettingStarted({ outDir, }) {
|
|
5
|
+
outDir = typeof outDir === "string" ? outDir : outDir.path;
|
|
6
|
+
return Document(`docs/getting-started`, {
|
|
7
|
+
title: "Getting Started with Alchemy",
|
|
8
|
+
path: path.join(outDir, "index.md"),
|
|
9
|
+
prompt: await alchemy `
|
|
10
|
+
You are a technical writer creating a getting started guide for Alchemy, an infrastructure as code (IaC) framework.
|
|
11
|
+
See ${alchemy.file("../README.md")} to understand the overview of Alchemy.
|
|
12
|
+
See ${alchemy.file("../.cursorrules")} to better understand the structure and conventions of Alchemy.
|
|
13
|
+
|
|
14
|
+
See ${alchemy.file("../alchemy/test/cloudflare/worker.test.ts")} for an example of how testing works.
|
|
15
|
+
|
|
16
|
+
Write a comprehensive getting started guide for Alchemy that covers the following topics:
|
|
17
|
+
|
|
18
|
+
# Getting Started with Alchemy
|
|
19
|
+
|
|
20
|
+
(Brief introduction to Alchemy as an infrastructure as code framework)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
\`\`\`bash
|
|
25
|
+
# Install Alchemy
|
|
26
|
+
bun add alchemy
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## Creating Your First Alchemy Project
|
|
30
|
+
|
|
31
|
+
(Step-by-step guide to creating a basic Alchemy project)
|
|
32
|
+
|
|
33
|
+
\`\`\`ts
|
|
34
|
+
// Example of a simple Alchemy project
|
|
35
|
+
import alchemy from "alchemy";
|
|
36
|
+
|
|
37
|
+
// Sample code here
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
## Core Concepts
|
|
41
|
+
|
|
42
|
+
### Resources
|
|
43
|
+
|
|
44
|
+
(Explanation of Alchemy resources and how they work)
|
|
45
|
+
|
|
46
|
+
\`\`\`ts
|
|
47
|
+
// Example of creating a resource
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
### Context
|
|
51
|
+
|
|
52
|
+
(Explanation of Alchemy context and lifecycle management)
|
|
53
|
+
|
|
54
|
+
\`\`\`ts
|
|
55
|
+
// Example of working with context
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## Working with Secrets
|
|
59
|
+
|
|
60
|
+
(How to handle secrets in Alchemy)
|
|
61
|
+
|
|
62
|
+
\`\`\`ts
|
|
63
|
+
// Example of using alchemy.secret()
|
|
64
|
+
const apiKey = alchemy.secret("API_KEY");
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
## Testing
|
|
68
|
+
|
|
69
|
+
(How to test Alchemy resources)
|
|
70
|
+
|
|
71
|
+
\`\`\`ts
|
|
72
|
+
// Example of testing Alchemy resources
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
## Deployment
|
|
76
|
+
|
|
77
|
+
(How to deploy resources with Alchemy)
|
|
78
|
+
|
|
79
|
+
\`\`\`ts
|
|
80
|
+
// Example of deployment
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
## Next Steps
|
|
84
|
+
|
|
85
|
+
(Where to go next to learn more about Alchemy)
|
|
86
|
+
|
|
87
|
+
> [!CAUTION]
|
|
88
|
+
> Avoid the temptation to over explain or over describe. Focus on concise, simple, high value snippets.
|
|
89
|
+
|
|
90
|
+
> [!TIP]
|
|
91
|
+
> Make sure the examples follow a natural progression from minimal examples to more complex use cases.
|
|
92
|
+
`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VitePressProject } from "../web/vitepress";
|
|
2
|
+
import type { AlchemyProviderDocs } from "./providers";
|
|
3
|
+
export interface AlchemyProjectProps {
|
|
4
|
+
hero: {
|
|
5
|
+
text: string;
|
|
6
|
+
tagline: string;
|
|
7
|
+
};
|
|
8
|
+
docs: {
|
|
9
|
+
providers?: AlchemyProviderDocs;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export type AlchemyProject = VitePressProject;
|
|
13
|
+
export declare function AlchemyProject({ hero, docs, }: AlchemyProjectProps): Promise<AlchemyProject>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { VitePressProject } from "../web/vitepress";
|
|
3
|
+
export function AlchemyProject({ hero, docs, }) {
|
|
4
|
+
return VitePressProject("project", {
|
|
5
|
+
name: "alchemy-web",
|
|
6
|
+
title: "Alchemy",
|
|
7
|
+
dir: process.cwd(),
|
|
8
|
+
description: "Alchemy is an TypeScript-native, embeddable IaC library",
|
|
9
|
+
overwrite: true,
|
|
10
|
+
delete: false,
|
|
11
|
+
scripts: {
|
|
12
|
+
deploy: "bun --env-file ../.env alchemy.run.ts",
|
|
13
|
+
},
|
|
14
|
+
tsconfig: {
|
|
15
|
+
extends: "../tsconfig.base.json",
|
|
16
|
+
references: ["../alchemy/tsconfig.json"],
|
|
17
|
+
},
|
|
18
|
+
devDependencies: {
|
|
19
|
+
alchemy: "workspace:*",
|
|
20
|
+
},
|
|
21
|
+
theme: {
|
|
22
|
+
light: "light-plus",
|
|
23
|
+
dark: "dark-plus",
|
|
24
|
+
},
|
|
25
|
+
themeConfig: {
|
|
26
|
+
sidebar: {
|
|
27
|
+
"/blog/": [{ text: "Blog", items: [{ text: "Blog", link: "/blog/" }] }],
|
|
28
|
+
"/docs/": [
|
|
29
|
+
{
|
|
30
|
+
text: "Getting Started",
|
|
31
|
+
items: [{ text: "Install", link: "/docs/getting-started/install" }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
text: "Guides",
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
text: "Custom Resource",
|
|
38
|
+
link: "/docs/guides/custom-resource",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
text: "Automating with LLMs",
|
|
42
|
+
link: "/docs/guides/llms",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
text: "Core",
|
|
48
|
+
collapsed: true,
|
|
49
|
+
items: [
|
|
50
|
+
{ text: "App", link: "/docs/core/app" },
|
|
51
|
+
{ text: "Resource", link: "/docs/core/resource" },
|
|
52
|
+
{ text: "Scope", link: "/docs/core/scope" },
|
|
53
|
+
{ text: "Phase", link: "/docs/core/phase" },
|
|
54
|
+
{ text: "Finalize", link: "/docs/core/finalize" },
|
|
55
|
+
{ text: "State", link: "/docs/core/state" },
|
|
56
|
+
{ text: "Secret", link: "/docs/core/secret" },
|
|
57
|
+
{ text: "Context", link: "/docs/core/context" },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
text: "Providers",
|
|
62
|
+
items: (docs.providers ?? [])
|
|
63
|
+
.map((provider) => ({
|
|
64
|
+
text: provider.provider,
|
|
65
|
+
// link: `/docs/providers/${provider.provider}`,
|
|
66
|
+
collapsed: true,
|
|
67
|
+
items: provider.documents
|
|
68
|
+
.map((document) => ({
|
|
69
|
+
text: document.title,
|
|
70
|
+
link: `/docs/providers/${provider.provider}/${path.basename(document.path, ".md")}`,
|
|
71
|
+
}))
|
|
72
|
+
.sort((a, b) => a.text.localeCompare(b.text)),
|
|
73
|
+
}))
|
|
74
|
+
.sort((a, b) => a.text.localeCompare(b.text)),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
"/examples/": [
|
|
78
|
+
{
|
|
79
|
+
text: "Examples",
|
|
80
|
+
items: [{ text: "Foo", link: "/examples/foo" }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
"/": [
|
|
84
|
+
{
|
|
85
|
+
text: "Home",
|
|
86
|
+
items: [
|
|
87
|
+
{ text: "Markdown Examples", link: "/markdown-examples" },
|
|
88
|
+
{ text: "Runtime API Examples", link: "/api-examples" },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
socialLinks: [
|
|
94
|
+
{
|
|
95
|
+
icon: "github",
|
|
96
|
+
link: "https://github.com/sam-goodwin/alchemy",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Document } from "alchemy/ai";
|
|
2
|
+
import { Folder } from "alchemy/fs";
|
|
3
|
+
export interface DocsProps {
|
|
4
|
+
/**
|
|
5
|
+
* The output directory for the docs.
|
|
6
|
+
*/
|
|
7
|
+
outDir: string | Folder;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to filter the docs.
|
|
10
|
+
* If true, include all docs.
|
|
11
|
+
* If false, include none.
|
|
12
|
+
* If a number, include that many providers.
|
|
13
|
+
*
|
|
14
|
+
* @default true (all docs)
|
|
15
|
+
*/
|
|
16
|
+
filter?: boolean | number;
|
|
17
|
+
}
|
|
18
|
+
export type AlchemyProviderDocs = {
|
|
19
|
+
dir: string;
|
|
20
|
+
provider: string;
|
|
21
|
+
documents: Document[];
|
|
22
|
+
}[];
|
|
23
|
+
export declare function AlchemyProviderDocs({ outDir, filter, }: DocsProps): Promise<AlchemyProviderDocs>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import alchemy from "alchemy";
|
|
2
|
+
import { Data, Document } from "alchemy/ai";
|
|
3
|
+
import { Folder } from "alchemy/fs";
|
|
4
|
+
import { type } from "arktype";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
export async function AlchemyProviderDocs({ outDir, filter, }) {
|
|
8
|
+
outDir = typeof outDir === "string" ? outDir : outDir.path;
|
|
9
|
+
const providersDir = (await Folder(path.join(outDir, "providers"))).path;
|
|
10
|
+
const exclude = ["util", "test", "vitepress", "vite", "shadcn", "internal"];
|
|
11
|
+
// Get all folders in the alchemy/src directory
|
|
12
|
+
let providers = (await fs.readdir(path.resolve("..", "alchemy", "src"), {
|
|
13
|
+
withFileTypes: true,
|
|
14
|
+
}))
|
|
15
|
+
.filter((dirent) => dirent.isDirectory() && !exclude.includes(dirent.name))
|
|
16
|
+
.map((dirent) => path.join(dirent.parentPath, dirent.name));
|
|
17
|
+
// For each provider, list all files
|
|
18
|
+
if (filter === false) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
else if (typeof filter === "number") {
|
|
22
|
+
providers = providers.slice(0, filter);
|
|
23
|
+
}
|
|
24
|
+
return await Promise.all(providers.map(async (provider) => {
|
|
25
|
+
const providerName = path.basename(provider);
|
|
26
|
+
const files = (await fs.readdir(path.resolve(provider), {
|
|
27
|
+
withFileTypes: true,
|
|
28
|
+
}))
|
|
29
|
+
.filter((dirent) => dirent.isFile())
|
|
30
|
+
.map((dirent) => path.relative(process.cwd(), path.resolve(provider, dirent.name)))
|
|
31
|
+
.filter((file) => file.endsWith(".ts") && !file.endsWith("index.ts"));
|
|
32
|
+
const { object: { groups }, } = await Data(`docs/${providerName}`, {
|
|
33
|
+
model: {
|
|
34
|
+
id: "gpt-4o",
|
|
35
|
+
provider: "openai",
|
|
36
|
+
// options: {
|
|
37
|
+
// reasoningEffort: "high",
|
|
38
|
+
// },
|
|
39
|
+
},
|
|
40
|
+
temperature: 0.1,
|
|
41
|
+
schema: type({
|
|
42
|
+
groups: type({
|
|
43
|
+
title: type("string").describe("The title of the group, should be the Resource Name exactly as it's defined in code (const ResourceName translates to 'Resource Name') without spaces, e.g. Bucket or Static Site."),
|
|
44
|
+
filename: type("string").describe("The filename of the Resource's Document, e.g. bucket.md or static-site.md"),
|
|
45
|
+
category: type("'Resource'|'Client'|'Utility'|'Types'").describe("The classification of the Resource's Document, one of: Resource, Client, Utility, or Types."),
|
|
46
|
+
}).array(),
|
|
47
|
+
}),
|
|
48
|
+
system: await alchemy `
|
|
49
|
+
You are a technical writer tasked with identifying the distinct documents that need to be written for a document group (folder) in a documentation site.
|
|
50
|
+
You will be provided with a list of documents and instructions on how to classify them.
|
|
51
|
+
Each document has a title, file name, and category.
|
|
52
|
+
`,
|
|
53
|
+
prompt: await alchemy `
|
|
54
|
+
Identify and classify the documents that need to be written for the '${provider}' Service's Alchemy Resources.
|
|
55
|
+
For background knowledge on Alchemy, see ${alchemy.file("../README.md")}.
|
|
56
|
+
For background knowledge on the structure of an Alchemy Resource, see ${alchemy.file("../.cursorrules")}.
|
|
57
|
+
|
|
58
|
+
The ${provider} Service has the following resources:
|
|
59
|
+
${alchemy.files(files)}
|
|
60
|
+
|
|
61
|
+
A file is considered a "Resource" if it contains a const <ResourceName> = Resource(...) call or if it is a function that calls a Resource function, e.g. const TypeScriptFile = () => File(...).
|
|
62
|
+
A file is considered a "Client" if it exposes a wrapper around creating a SDK client or fetch.
|
|
63
|
+
A file is considered a "Utility" if it contains utility functions that are not resources or clients.
|
|
64
|
+
A file is considered a "Types" if it contains just type definitions and maybe helpers around working with those types.
|
|
65
|
+
|
|
66
|
+
The title should be simply the name of the resource's const in code (with spaces added in between each word), e.g. "Bucket" or "Function", except with spaces, e.g. "Static Site" for "const StaticSite". Maintain all other casing.
|
|
67
|
+
|
|
68
|
+
// "Resource Name"
|
|
69
|
+
const ResourceName = Resource(...)
|
|
70
|
+
`,
|
|
71
|
+
});
|
|
72
|
+
// console.log(groups);
|
|
73
|
+
const providerDocsDir = (await Folder(path.join(providersDir, providerName))).path;
|
|
74
|
+
const documents = await Promise.all(groups
|
|
75
|
+
.filter((g) => g.category === "Resource")
|
|
76
|
+
.map(async (g) => Document(`docs/${providerName}/${g.title}`, {
|
|
77
|
+
title: g.title,
|
|
78
|
+
path: path.join(providerDocsDir, `${g.filename.replace(".ts", "").replace(".md", "")}.md`),
|
|
79
|
+
prompt: await alchemy `
|
|
80
|
+
You are a technical writer writing API documentation for an Alchemy IaC Resource.
|
|
81
|
+
See ${alchemy.file("../README.md")} to understand the overview of Alchemy.
|
|
82
|
+
See ${alchemy.file("../.cursorrules")} to better understand the structure and convention of an Alchemy Resource.
|
|
83
|
+
|
|
84
|
+
Relevant files for the ${providerName} Service:
|
|
85
|
+
${alchemy.files(files)}
|
|
86
|
+
|
|
87
|
+
Write concise documentation for the "${g.title}" Resource.
|
|
88
|
+
|
|
89
|
+
> [!CAUTION]
|
|
90
|
+
> Avoid the temptation to over explain or over describe. Focus on concise, simple, high value snippets. One heading and 0-1 descriptions per snippet.
|
|
91
|
+
|
|
92
|
+
> [!TIP]
|
|
93
|
+
> Make sure the examples follow a natural progression from the minimal example to logical next steps of how the Resource might be used.
|
|
94
|
+
|
|
95
|
+
Each document must follow the following format:
|
|
96
|
+
|
|
97
|
+
# ${g.title}
|
|
98
|
+
|
|
99
|
+
(simple description with an external link to the provider's website)
|
|
100
|
+
e.g.
|
|
101
|
+
The Efs component lets you add [Amazon Elastic File System (EFS)](https://docs.aws.amazon.com/efs/latest/ug/whatisefs.html) to your app.
|
|
102
|
+
|
|
103
|
+
# Minimal Example
|
|
104
|
+
|
|
105
|
+
\`\`\`ts
|
|
106
|
+
import { ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
|
|
107
|
+
|
|
108
|
+
(example)
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Create the ${g.title}
|
|
113
|
+
|
|
114
|
+
\`\`\`ts
|
|
115
|
+
import { ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
|
|
116
|
+
|
|
117
|
+
(example)
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
${providerName === "cloudflare"
|
|
121
|
+
? await alchemy `# Bind to a Worker
|
|
122
|
+
(if it is a Cloudflare Resource)
|
|
123
|
+
|
|
124
|
+
\`\`\`ts
|
|
125
|
+
import { Worker, ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
|
|
126
|
+
|
|
127
|
+
const myResource = await ${g.title.replaceAll(" ", "")}("my-resource", {
|
|
128
|
+
// ...
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await Worker("my-worker", {
|
|
132
|
+
name: "my-worker",
|
|
133
|
+
script: "console.log('Hello, world!')",
|
|
134
|
+
bindings: {
|
|
135
|
+
myResource,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
\`\`\``
|
|
139
|
+
: ""}
|
|
140
|
+
`,
|
|
141
|
+
})));
|
|
142
|
+
return {
|
|
143
|
+
dir: providerDocsDir,
|
|
144
|
+
provider: providerName,
|
|
145
|
+
documents,
|
|
146
|
+
};
|
|
147
|
+
}));
|
|
148
|
+
}
|
package/lib/resource.js
CHANGED
|
@@ -15,7 +15,14 @@ export function Resource(type, ...args) {
|
|
|
15
15
|
if (scope.resources.has(resourceID)) {
|
|
16
16
|
// TODO(sam): do we want to throw?
|
|
17
17
|
// it's kind of awesome that you can re-create a resource and call apply
|
|
18
|
-
|
|
18
|
+
const otherResource = scope.resources.get(resourceID);
|
|
19
|
+
if (otherResource?.Kind !== type) {
|
|
20
|
+
scope.fail();
|
|
21
|
+
throw new Error(`Resource ${resourceID} already exists in the stack and is of a different type: '${otherResource?.Kind}' !== '${type}'`);
|
|
22
|
+
}
|
|
23
|
+
// console.warn(
|
|
24
|
+
// `Resource ${resourceID} already exists in the stack: ${scope.chain.join("/")}`,
|
|
25
|
+
// );
|
|
19
26
|
}
|
|
20
27
|
// get a sequence number (unique within the scope) for the resource
|
|
21
28
|
const seq = scope.seq();
|
package/lib/scope.d.ts
CHANGED
package/lib/scope.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { destroy } from "./destroy";
|
|
3
|
-
import { FileSystemStateStore
|
|
3
|
+
import { FileSystemStateStore } from "./fs/file-system-state-store";
|
|
4
4
|
const scopeStorage = new AsyncLocalStorage();
|
|
5
5
|
export class Scope {
|
|
6
6
|
static get() {
|
|
@@ -32,7 +32,9 @@ export class Scope {
|
|
|
32
32
|
throw new Error("Scope name is required when creating a child scope");
|
|
33
33
|
}
|
|
34
34
|
this.password = options.password;
|
|
35
|
-
this.state =
|
|
35
|
+
this.state = options.stateStore
|
|
36
|
+
? options.stateStore(this)
|
|
37
|
+
: new FileSystemStateStore(this);
|
|
36
38
|
}
|
|
37
39
|
async delete(resourceID) {
|
|
38
40
|
await this.state.delete(resourceID);
|