alchemy 0.0.8 → 0.1.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.
Files changed (105) hide show
  1. package/package.json +23 -22
  2. package/src/alchemize.ts +151 -0
  3. package/src/apply.ts +117 -0
  4. package/src/components/agent/agent.ts +45 -0
  5. package/src/components/agent/anthropic.ts +27 -0
  6. package/src/components/agent/index.ts +4 -0
  7. package/src/components/agent/model.ts +22 -0
  8. package/src/components/agent/openai.ts +44 -0
  9. package/src/components/agent/package.ts +126 -0
  10. package/src/components/agent/requirements.ts +111 -0
  11. package/src/components/agent/scrape.ts +47 -0
  12. package/src/components/agent/tsconfig.ts +150 -0
  13. package/src/components/agent/typescript.ts +177 -0
  14. package/src/components/aws/bucket.ts +168 -0
  15. package/src/components/aws/function.ts +302 -0
  16. package/src/components/aws/index.ts +6 -0
  17. package/src/components/aws/policy.ts +245 -0
  18. package/src/components/aws/queue.ts +222 -0
  19. package/src/components/aws/role.ts +245 -0
  20. package/src/components/aws/table.ts +172 -0
  21. package/src/components/esbuild.ts +128 -0
  22. package/src/components/fs.ts +45 -0
  23. package/src/destroy.ts +63 -0
  24. package/src/error.ts +16 -0
  25. package/src/global.ts +72 -0
  26. package/src/index.ts +6 -0
  27. package/src/input.ts +16 -0
  28. package/src/main.ts +34 -0
  29. package/src/output.ts +49 -0
  30. package/src/resource.ts +313 -0
  31. package/src/state.ts +100 -0
  32. package/test/aws/function.test.ts +144 -0
  33. package/test/aws/queue.test.ts +85 -0
  34. package/test/aws/role.test.ts +153 -0
  35. package/test/aws/table.test.ts +73 -0
  36. package/test/esbuild.test.ts +41 -0
  37. package/test/handler.ts +9 -0
  38. package/tsconfig.json +7 -0
  39. package/Column.js +0 -1
  40. package/Create.js +0 -1
  41. package/Delete.js +0 -1
  42. package/Drop.js +0 -1
  43. package/Insert.js +0 -1
  44. package/Makefile +0 -8
  45. package/README.md +0 -0
  46. package/Select.js +0 -1
  47. package/Table.js +0 -1
  48. package/Transaction.js +0 -1
  49. package/Types.js +0 -1
  50. package/Update.js +0 -1
  51. package/alchemy.js +0 -57
  52. package/alchemy.sublime-project +0 -9
  53. package/alchemy.sublime-workspace +0 -2304
  54. package/config/app.js +0 -8
  55. package/config/db.js +0 -20
  56. package/copy.js +0 -1
  57. package/defaultEngine.js +0 -8
  58. package/engine.js +0 -282
  59. package/getInstantiatorFunction.js +0 -28
  60. package/index.js +0 -52
  61. package/jstest.js +0 -3
  62. package/loggerFactory.js +0 -27
  63. package/logs/alchemy.log +0 -46
  64. package/logs/sql.log +0 -41
  65. package/sql/Column.js +0 -158
  66. package/sql/Columnable.js +0 -21
  67. package/sql/Create.js +0 -15
  68. package/sql/Delete.js +0 -34
  69. package/sql/Drop.js +0 -36
  70. package/sql/Insert.js +0 -96
  71. package/sql/SchemaElement.js +0 -11
  72. package/sql/Select.js +0 -192
  73. package/sql/SqlBase.js +0 -134
  74. package/sql/SqlStatement.js +0 -80
  75. package/sql/Table.js +0 -96
  76. package/sql/Transaction.js +0 -45
  77. package/sql/Update.js +0 -105
  78. package/sql/defineElement.js +0 -26
  79. package/sql/defineStatement.js +0 -26
  80. package/sql/types/BaseType.js +0 -43
  81. package/sql/types/BigSerialType.js +0 -5
  82. package/sql/types/IntegerType.js +0 -5
  83. package/sql/types/JsonType.js +0 -5
  84. package/sql/types/SerialType.js +0 -5
  85. package/sql/types/StringType.js +0 -31
  86. package/sql/types/TimestampType.js +0 -5
  87. package/sql/types/UUIDType.js +0 -5
  88. package/sql/types/defineType.js +0 -25
  89. package/sql/types/getType.js +0 -12
  90. package/sql/types/index.js +0 -11
  91. package/sql/where.js +0 -31
  92. package/sql/whereToString.js +0 -29
  93. package/test/ColumnTests.js +0 -80
  94. package/test/CreateTable.js +0 -54
  95. package/test/DeleteTests.js +0 -28
  96. package/test/DropTests.js +0 -23
  97. package/test/GetTypeTests.js +0 -56
  98. package/test/InsertTests.js +0 -86
  99. package/test/SelectExistsTests.js +0 -26
  100. package/test/SelectTests.js +0 -130
  101. package/test/TableTests.js +0 -46
  102. package/test.js +0 -8
  103. package/test.ls +0 -4
  104. package/validate.js +0 -26
  105. package/x.js +0 -5
@@ -0,0 +1,111 @@
1
+ import { generateText } from "ai";
2
+ import { mkdir, unlink, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+ import { z } from "zod";
5
+ import { Agent } from "./agent";
6
+ import { resolveModel } from "./model";
7
+ import { scrapeWebPage } from "./scrape";
8
+
9
+ export const RequirementsInput = z.object({
10
+ /**
11
+ * The ID of the model to use for generating requirements documentation
12
+ * @default "gpt-4o"
13
+ */
14
+ modelId: z.string().optional(),
15
+
16
+ /**
17
+ * List of requirements to document and analyze
18
+ */
19
+ requirements: z.array(z.string()),
20
+
21
+ /**
22
+ * One-line summary of the requirements document
23
+ */
24
+ title: z.string(),
25
+
26
+ /**
27
+ * Temperature setting for model generation (higher = more creative, lower = more focused)
28
+ * @default 0.7
29
+ */
30
+ temperature: z.number().optional(),
31
+
32
+ /**
33
+ * File path where the requirements document should be written
34
+ */
35
+ path: z.string(),
36
+ });
37
+
38
+ export const RequirementsOutput = z.object({
39
+ /**
40
+ * Generated markdown document containing organized and analyzed requirements
41
+ */
42
+ content: z.string(),
43
+ });
44
+
45
+ export type RequirementsInput = z.infer<typeof RequirementsInput>;
46
+ export type RequirementsOutput = z.infer<typeof RequirementsOutput>;
47
+
48
+ export class Requirements extends Agent(
49
+ "code::Requirements",
50
+ {
51
+ description:
52
+ "This Agent is responsible for writing and refining a detailed requirements document from a list of requirements.",
53
+ input: RequirementsInput,
54
+ output: RequirementsOutput,
55
+ },
56
+ async (ctx, input) => {
57
+ if (ctx.event === "delete") {
58
+ await unlink(input.path);
59
+ return;
60
+ }
61
+
62
+ // Get the appropriate model based on the ID
63
+ const { model } = await resolveModel(input.modelId ?? "gpt-4o");
64
+
65
+ // Generate the requirements document
66
+ const { text } = await generateText({
67
+ model,
68
+ temperature: input.temperature ?? 0.7,
69
+ maxSteps: 3, // Allow multiple steps for potential tool usage
70
+ tools: {
71
+ scrapeWebPage,
72
+ },
73
+ messages: [
74
+ {
75
+ role: "system",
76
+ content:
77
+ "You are a helpful assistant that creates markdown documents from requirements. You can use the scrapeWebPage tool to get additional requirements from relevant URLs if needed.",
78
+ },
79
+ {
80
+ role: "user",
81
+ content: `Please analyze the following system requirements and create a comprehensive, unambiguous markdown document that covers all aspects:
82
+
83
+ Title: ${input.title}
84
+
85
+ Requirements:
86
+ ${input.requirements.map((req) => `- ${req}`).join("\n")}
87
+
88
+ Guidelines:
89
+ 1. Make explicit decisions for any ambiguous requirements - do not leave open questions
90
+ 2. Document the reasoning behind key decisions
91
+ 3. Be specific about technical choices and implementation details
92
+ 4. Avoid phrases like "could be", "might need", or "possibly requires" - be definitive
93
+ 5. Organize requirements into logical sections with clear dependencies and relationships
94
+
95
+ Format the output in markdown with appropriate headers, lists, and sections, starting with the title as an H1 header.
96
+ Include technical considerations and implementation details, ensuring every requirement has a clear, actionable specification.`,
97
+ },
98
+ ],
99
+ });
100
+
101
+ // Ensure the directory exists
102
+ await mkdir(dirname(input.path), { recursive: true });
103
+
104
+ // Write the requirements to the file
105
+ await writeFile(input.path, text);
106
+
107
+ return {
108
+ content: text,
109
+ };
110
+ },
111
+ ) {}
@@ -0,0 +1,47 @@
1
+ import { tool } from "ai";
2
+ import TurndownService from "turndown";
3
+ import { z } from "zod";
4
+
5
+ export const scrapeWebPage = tool({
6
+ description: "Scrapes a web page and converts HTML content to Markdown",
7
+ parameters: z.object({
8
+ url: z.string().url().describe("The URL to scrape content from"),
9
+ turndownOptions: z
10
+ .object({
11
+ headingStyle: z.enum(["setext", "atx"]).optional(),
12
+ hr: z.string().optional(),
13
+ bulletListMarker: z.enum(["-", "*", "+"]).optional(),
14
+ codeBlockStyle: z.enum(["indented", "fenced"]).optional(),
15
+ emDelimiter: z.enum(["_", "*"]).optional(),
16
+ strongDelimiter: z.enum(["__", "**"]).optional(),
17
+ linkStyle: z.enum(["inlined", "referenced"]).optional(),
18
+ linkReferenceStyle: z
19
+ .enum(["full", "collapsed", "shortcut"])
20
+ .optional(),
21
+ })
22
+ .optional()
23
+ .describe("Custom options for Turndown HTML to Markdown conversion"),
24
+ }),
25
+ execute: async ({ url, turndownOptions }) => {
26
+ console.log("Scraping web page:", url);
27
+ try {
28
+ // Fetch the page content
29
+ const response = await fetch(url);
30
+ if (!response.ok) {
31
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
32
+ }
33
+
34
+ // Get the HTML content
35
+ const html = await response.text();
36
+
37
+ // Initialize Turndown with options
38
+ const turndownService = new TurndownService(turndownOptions);
39
+
40
+ // Convert HTML to Markdown and return
41
+ return turndownService.turndown(html);
42
+ } catch (error) {
43
+ console.error("Error scraping web page:", error);
44
+ throw error;
45
+ }
46
+ },
47
+ });
@@ -0,0 +1,150 @@
1
+ import { generateObject } from "ai";
2
+ import { mkdir, unlink, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+ import { z } from "zod";
5
+ import { Agent } from "./agent";
6
+ import { resolveModel } from "./model";
7
+
8
+ export type TsConfigProps = z.infer<typeof TsConfigProps>;
9
+
10
+ export const TsConfigProps = z.object({
11
+ /**
12
+ * The ID of the model to use for generating tsconfig configuration
13
+ * @default "gpt-4o"
14
+ */
15
+ modelId: z.string().optional(),
16
+
17
+ /**
18
+ * List of requirements for the TypeScript configuration
19
+ * Can include module settings, compilation options, etc.
20
+ */
21
+ requirements: z.array(z.string()),
22
+
23
+ /**
24
+ * Temperature setting for model generation
25
+ * @default 0.3
26
+ */
27
+ temperature: z.number().optional(),
28
+
29
+ /**
30
+ * Path to the tsconfig.json file to generate
31
+ */
32
+ path: z.string(),
33
+ });
34
+
35
+ export type TsConfigJson = z.infer<typeof TsConfigSchema>;
36
+ export const TsConfigSchema = z.object({
37
+ $schema: z.string().optional(),
38
+ compilerOptions: z.object({
39
+ target: z.enum([
40
+ "es2015",
41
+ "es2016",
42
+ "es2017",
43
+ "es2018",
44
+ "es2019",
45
+ "es2020",
46
+ "es2021",
47
+ "es2022",
48
+ "esnext",
49
+ ]),
50
+ module: z.enum([
51
+ "commonjs",
52
+ "es2015",
53
+ "es2020",
54
+ "es2022",
55
+ "esnext",
56
+ "node16",
57
+ "nodenext",
58
+ ]),
59
+ lib: z.array(z.string()).optional(),
60
+ declaration: z.boolean().optional(),
61
+ declarationMap: z.boolean().optional(),
62
+ sourceMap: z.boolean().optional(),
63
+ outDir: z.string().optional(),
64
+ rootDir: z.string().optional(),
65
+ strict: z.boolean(),
66
+ esModuleInterop: z.boolean().optional(),
67
+ skipLibCheck: z.boolean().optional(),
68
+ forceConsistentCasingInFileNames: z.boolean().optional(),
69
+ moduleResolution: z
70
+ .enum(["node", "node16", "nodenext", "bundler"])
71
+ .optional(),
72
+ resolveJsonModule: z.boolean().optional(),
73
+ isolatedModules: z.boolean().optional(),
74
+ allowJs: z.boolean().optional(),
75
+ checkJs: z.boolean().optional(),
76
+ noEmit: z.boolean().optional(),
77
+ incremental: z.boolean().optional(),
78
+ composite: z.boolean().optional(),
79
+ tsBuildInfoFile: z.string().optional(),
80
+ paths: z.record(z.array(z.string())).optional(),
81
+ baseUrl: z.string().optional(),
82
+ types: z.array(z.string()).optional(),
83
+ typeRoots: z.array(z.string()).optional(),
84
+ }),
85
+ include: z.array(z.string()).optional(),
86
+ exclude: z.array(z.string()).optional(),
87
+ references: z
88
+ .array(
89
+ z.object({
90
+ path: z.string(),
91
+ }),
92
+ )
93
+ .optional(),
94
+ });
95
+
96
+ export class TypeScriptConfig extends Agent(
97
+ "code::tsconfig",
98
+ {
99
+ description:
100
+ "This Agent is responsible for generating tsconfig.json configuration based on requirements.",
101
+ input: TsConfigProps,
102
+ output: TsConfigSchema,
103
+ },
104
+ async (ctx, props) => {
105
+ if (ctx.event === "delete") {
106
+ await unlink(props.path);
107
+ return;
108
+ }
109
+
110
+ // Get the appropriate model
111
+ const { model } = await resolveModel(props.modelId ?? "gpt-4o");
112
+
113
+ // Generate the tsconfig configuration using generateObject for type safety
114
+ const result = await generateObject({
115
+ model,
116
+ schema: TsConfigSchema,
117
+ temperature: props.temperature ?? 0.3,
118
+ messages: [
119
+ {
120
+ role: "system",
121
+ content:
122
+ "You are an expert at creating TypeScript configurations. You will generate a tsconfig.json configuration based on requirements.",
123
+ },
124
+ {
125
+ role: "user",
126
+ content: `Please generate a tsconfig.json configuration based on these requirements:
127
+
128
+ Requirements:
129
+ ${props.requirements.map((req) => `- ${req}`).join("\n")}
130
+
131
+ Rules:
132
+ 1. Use modern TypeScript features
133
+ 2. Enable strict type checking
134
+ 3. Use ESM modules by default
135
+ 4. Include source maps for better debugging
136
+ 5. Infer appropriate settings for declarations, module resolution, and other options based on the requirements
137
+ 6. Use appropriate paths for source and output directories`,
138
+ },
139
+ ],
140
+ });
141
+
142
+ // Ensure the directory exists
143
+ await mkdir(dirname(props.path), { recursive: true });
144
+
145
+ // Write the tsconfig.json file
146
+ await writeFile(props.path, JSON.stringify(result.object, null, 2));
147
+
148
+ return result.object;
149
+ },
150
+ ) {}
@@ -0,0 +1,177 @@
1
+ import type { CoreMessage } from "ai";
2
+ import { generateText } from "ai";
3
+ import { mkdir, unlink, writeFile } from "fs/promises";
4
+ import { dirname } from "path";
5
+ import { z } from "zod";
6
+ import { Agent } from "./agent";
7
+ import { resolveModel } from "./model";
8
+
9
+ export type CodeProps = z.infer<typeof CodeProps>;
10
+
11
+ export type File = z.infer<typeof File>;
12
+ export const File = z.object({
13
+ path: z.string(),
14
+ content: z.string(),
15
+ });
16
+
17
+ const CodeProps = z.object({
18
+ /**
19
+ * The ID of the model to use for generating TypeScript code
20
+ * @default "gpt-4o"
21
+ */
22
+ modelId: z.string().optional(),
23
+
24
+ /**
25
+ * The name of the file to write the code to
26
+ */
27
+ path: z.string(),
28
+
29
+ /**
30
+ * The requirements for the TypeScript file
31
+ */
32
+ requirements: z.string(),
33
+
34
+ /**
35
+ * List of other code files that it depends on
36
+ */
37
+ dependencies: z.array(File).optional(),
38
+
39
+ /**
40
+ * Temperature setting for model generation (higher = more creative, lower = more focused)
41
+ * @default 0.7
42
+ */
43
+ temperature: z.number().optional(),
44
+ });
45
+
46
+ export type CodeOutput = z.infer<typeof CodeOutput>;
47
+ export const CodeOutput = File;
48
+
49
+ export class TypeScriptFile extends Agent(
50
+ "code::TypeScriptFile",
51
+ {
52
+ description:
53
+ "This Agent is responsible for generating TypeScript code based on requirements and context from other code files.",
54
+ input: CodeProps,
55
+ output: CodeOutput,
56
+ },
57
+ async (ctx, props) => {
58
+ if (ctx.event === "delete") {
59
+ await unlink(props.path);
60
+ return;
61
+ }
62
+
63
+ // Get the appropriate model based on the ID
64
+ const { model } = await resolveModel(props.modelId ?? "gpt-4o");
65
+
66
+ // Generate the TypeScript code with retries
67
+ const content = await generateCodeWithRetries(model, props);
68
+
69
+ // Ensure the directory exists
70
+ await mkdir(dirname(props.path), { recursive: true });
71
+
72
+ // Write the TypeScript file
73
+ await writeFile(props.path, content);
74
+
75
+ return {
76
+ path: props.path,
77
+ content: content,
78
+ };
79
+ },
80
+ ) {}
81
+
82
+ /**
83
+ * Attempts to generate valid TypeScript code with retries
84
+ */
85
+ async function generateCodeWithRetries(
86
+ model: any,
87
+ props: CodeProps,
88
+ maxAttempts = 3,
89
+ ): Promise<string> {
90
+ const baseMessages: CoreMessage[] = [
91
+ {
92
+ role: "system",
93
+ content:
94
+ "You are an expert TypeScript developer that generates clean, well-documented code following best practices. " +
95
+ "Your response must follow this exact format:\n" +
96
+ "1. A single sentence describing what the code will do\n" +
97
+ "2. A single TypeScript code block surrounded by ```ts and ``` tags\n" +
98
+ "Do not include any other explanations or multiple code blocks.",
99
+ },
100
+ {
101
+ role: "user",
102
+ content: `Please generate TypeScript code based on the following specifications:
103
+
104
+ Requirements:
105
+ ${props.requirements}
106
+
107
+ ${
108
+ props.dependencies?.length
109
+ ? `Context (other code files):
110
+ ${props.dependencies.map((dep) => dep.content).join("\n\n")}`
111
+ : ""
112
+ }
113
+
114
+ The code should:
115
+ 1. Be well-documented with JSDoc comments
116
+ 2. Follow TypeScript best practices
117
+ 3. Include proper type definitions
118
+ 4. Be ready to use with the provided context files
119
+
120
+ Your response MUST follow this format:
121
+ - One sentence summary of what the code will do
122
+ - Single TypeScript code block surrounded by \`\`\`ts and \`\`\` tags
123
+ - No other explanations or multiple code blocks`,
124
+ },
125
+ ];
126
+
127
+ const messages = [...baseMessages];
128
+
129
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
130
+ const { text } = await generateText({
131
+ model,
132
+ temperature: Math.max(
133
+ 0.1,
134
+ (props.temperature ?? 0.7) * (1 - attempt * 0.2),
135
+ ), // Reduce temperature with each attempt
136
+ messages,
137
+ });
138
+
139
+ const code = extractTypeScriptCode(text);
140
+ if (code) {
141
+ return code;
142
+ }
143
+
144
+ if (attempt === maxAttempts) {
145
+ throw new Error(
146
+ "Failed to generate valid TypeScript code after maximum attempts",
147
+ );
148
+ }
149
+
150
+ // Add the failed attempt and correction request to the message chain
151
+ messages.push(
152
+ {
153
+ role: "assistant",
154
+ content: text,
155
+ },
156
+ {
157
+ role: "user",
158
+ content:
159
+ "Your response did not match the required format. Please provide your response with exactly one sentence summary" +
160
+ " followed by a single TypeScript code block surrounded by ```ts and ``` tags. No other text or explanations.",
161
+ },
162
+ );
163
+ }
164
+
165
+ // This should never be reached due to the throw above
166
+ throw new Error("Unexpected code generation failure");
167
+ }
168
+
169
+ /**
170
+ * Extracts TypeScript code from a markdown code block
171
+ * @param text Response text that may contain markdown
172
+ * @returns The TypeScript code if found, undefined otherwise
173
+ */
174
+ function extractTypeScriptCode(text: string): string | undefined {
175
+ const matches = text.match(/```(?:ts|typescript)\n([\s\S]*?)```/);
176
+ return matches?.[1]?.trim();
177
+ }
@@ -0,0 +1,168 @@
1
+ import {
2
+ CreateBucketCommand,
3
+ DeleteBucketCommand,
4
+ GetBucketAclCommand,
5
+ GetBucketLocationCommand,
6
+ GetBucketTaggingCommand,
7
+ GetBucketVersioningCommand,
8
+ HeadBucketCommand,
9
+ NoSuchBucket,
10
+ PutBucketTaggingCommand,
11
+ S3Client,
12
+ } from "@aws-sdk/client-s3";
13
+ import { ignore } from "../../error";
14
+ import { Resource } from "../../resource";
15
+
16
+ export interface BucketProps {
17
+ bucketName: string;
18
+ tags?: Record<string, string>;
19
+ }
20
+
21
+ export interface BucketOutput extends BucketProps {
22
+ id: string; // Same as bucketName for AWS
23
+ arn: string;
24
+ bucketDomainName: string; // Format: bucket-name.s3.amazonaws.com
25
+ bucketRegionalDomainName?: string; // Format: bucket-name.s3.region.amazonaws.com
26
+ hostedZoneId?: string; // S3 hosted zone ID for the region
27
+ region?: string; // AWS Region where bucket resides
28
+ websiteEndpoint?: string; // Only if website hosting is enabled
29
+ websiteDomain?: string; // Only if website hosting is enabled
30
+ versioningEnabled?: boolean;
31
+ acl?: string;
32
+ }
33
+
34
+ export class Bucket extends Resource(
35
+ "s3::Bucket",
36
+ async (ctx, props: BucketProps) => {
37
+ const client = new S3Client({});
38
+
39
+ if (ctx.event === "delete") {
40
+ await ignore(NoSuchBucket.name, () =>
41
+ client.send(
42
+ new DeleteBucketCommand({
43
+ Bucket: props.bucketName,
44
+ }),
45
+ ),
46
+ );
47
+ return props;
48
+ } else {
49
+ try {
50
+ // Check if bucket exists
51
+ await client.send(
52
+ new HeadBucketCommand({
53
+ Bucket: props.bucketName,
54
+ }),
55
+ );
56
+
57
+ // Update tags if they changed and bucket exists
58
+ if (ctx.event === "update" && props.tags) {
59
+ await client.send(
60
+ new PutBucketTaggingCommand({
61
+ Bucket: props.bucketName,
62
+ Tagging: {
63
+ TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
64
+ Key,
65
+ Value,
66
+ })),
67
+ },
68
+ }),
69
+ );
70
+ }
71
+ } catch (error: any) {
72
+ if (error.name === "NotFound") {
73
+ // Create bucket if it doesn't exist
74
+ await client.send(
75
+ new CreateBucketCommand({
76
+ Bucket: props.bucketName,
77
+ // Add tags during creation if specified
78
+ ...(props.tags && {
79
+ Tagging: {
80
+ TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
81
+ Key,
82
+ Value,
83
+ })),
84
+ },
85
+ }),
86
+ }),
87
+ );
88
+ } else {
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ // Get bucket details
94
+ const [locationResponse, versioningResponse, aclResponse] =
95
+ await Promise.all([
96
+ client.send(
97
+ new GetBucketLocationCommand({ Bucket: props.bucketName }),
98
+ ),
99
+ client.send(
100
+ new GetBucketVersioningCommand({ Bucket: props.bucketName }),
101
+ ),
102
+ client.send(new GetBucketAclCommand({ Bucket: props.bucketName })),
103
+ ]);
104
+
105
+ const region = locationResponse.LocationConstraint || "us-east-1";
106
+
107
+ // Get tags if they exist
108
+ let tags = props.tags;
109
+ if (!tags) {
110
+ try {
111
+ const taggingResponse = await client.send(
112
+ new GetBucketTaggingCommand({ Bucket: props.bucketName }),
113
+ );
114
+ tags = Object.fromEntries(
115
+ taggingResponse.TagSet?.map(({ Key, Value }) => [Key, Value]) || [],
116
+ );
117
+ } catch (error: any) {
118
+ if (error.name !== "NoSuchTagSet") {
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ const output: BucketOutput = {
125
+ bucketName: props.bucketName,
126
+ id: props.bucketName,
127
+ arn: `arn:aws:s3:::${props.bucketName}`,
128
+ bucketDomainName: `${props.bucketName}.s3.amazonaws.com`,
129
+ bucketRegionalDomainName: `${props.bucketName}.s3.${region}.amazonaws.com`,
130
+ region,
131
+ hostedZoneId: getHostedZoneId(region),
132
+ versioningEnabled: versioningResponse.Status === "Enabled",
133
+ acl: aclResponse.Grants?.[0]?.Permission?.toLowerCase(),
134
+ ...(tags && { tags }),
135
+ };
136
+
137
+ return output;
138
+ }
139
+ },
140
+ ) {}
141
+
142
+ // Helper function to get S3 hosted zone IDs by region
143
+ function getHostedZoneId(region: string): string {
144
+ const hostedZoneIds: Record<string, string> = {
145
+ "us-east-1": "Z3AQBSTGFYJSTF",
146
+ "us-east-2": "Z2O1EMRO9K5GLX",
147
+ "us-west-1": "Z2F56UZL2M1ACD",
148
+ "us-west-2": "Z3BJ6K6RIION7M",
149
+ "af-south-1": "Z11KHD8FBVPUYU",
150
+ "ap-east-1": "ZNB98KWMFR0R6",
151
+ "ap-south-1": "Z11RGJOFQNVJUP",
152
+ "ap-northeast-1": "Z2M4EHUR26P7ZW",
153
+ "ap-northeast-2": "Z3W03O7B5YMIYP",
154
+ "ap-northeast-3": "Z2YQB5RD63NC85",
155
+ "ap-southeast-1": "Z3O0J2DXBE1FTB",
156
+ "ap-southeast-2": "Z1WCIGYICN2BYD",
157
+ "ca-central-1": "Z1QDHH18159H29",
158
+ "eu-central-1": "Z21DNDUVLTQW6Q",
159
+ "eu-west-1": "Z1BKCTXD74EZPE",
160
+ "eu-west-2": "Z3GKZC51ZF0DB4",
161
+ "eu-west-3": "Z3R1K369G5AVDG",
162
+ "eu-north-1": "Z3BAZG2TWCNX0D",
163
+ "eu-south-1": "Z30OZKI7KPW7MI",
164
+ "me-south-1": "Z1MPMWCPA7YB62",
165
+ "sa-east-1": "Z7KQH4QJS55SO",
166
+ };
167
+ return hostedZoneIds[region] || "Z3AQBSTGFYJSTF"; // Default to us-east-1 if region not found
168
+ }