alchemy 0.0.8 → 0.1.2
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 +355 -0
- package/package.json +24 -20
- package/src/alchemize.ts +151 -0
- package/src/apply.ts +117 -0
- package/src/components/agent/agent.ts +45 -0
- package/src/components/agent/anthropic.ts +27 -0
- package/src/components/agent/index.ts +4 -0
- package/src/components/agent/model.ts +22 -0
- package/src/components/agent/openai.ts +44 -0
- package/src/components/agent/package.ts +126 -0
- package/src/components/agent/requirements.ts +111 -0
- package/src/components/agent/scrape.ts +47 -0
- package/src/components/agent/tsconfig.ts +150 -0
- package/src/components/agent/typescript.ts +177 -0
- package/src/components/aws/bucket.ts +168 -0
- package/src/components/aws/function.ts +302 -0
- package/src/components/aws/index.ts +6 -0
- package/src/components/aws/policy.ts +245 -0
- package/src/components/aws/queue.ts +222 -0
- package/src/components/aws/role.ts +245 -0
- package/src/components/aws/table.ts +172 -0
- package/src/components/esbuild.ts +128 -0
- package/src/components/fs.ts +45 -0
- package/src/destroy.ts +63 -0
- package/src/error.ts +16 -0
- package/src/global.ts +72 -0
- package/src/index.ts +6 -0
- package/src/input.ts +16 -0
- package/src/main.ts +34 -0
- package/src/output.ts +49 -0
- package/src/resource.ts +313 -0
- package/src/state.ts +100 -0
- package/test/aws/function.test.ts +144 -0
- package/test/aws/queue.test.ts +85 -0
- package/test/aws/role.test.ts +153 -0
- package/test/aws/table.test.ts +73 -0
- package/test/esbuild.test.ts +41 -0
- package/test/handler.ts +9 -0
- package/tsconfig.json +7 -0
- package/Column.js +0 -1
- package/Create.js +0 -1
- package/Delete.js +0 -1
- package/Drop.js +0 -1
- package/Insert.js +0 -1
- package/Makefile +0 -8
- package/Select.js +0 -1
- package/Table.js +0 -1
- package/Transaction.js +0 -1
- package/Types.js +0 -1
- package/Update.js +0 -1
- package/alchemy.js +0 -57
- package/alchemy.sublime-project +0 -9
- package/alchemy.sublime-workspace +0 -2304
- package/config/app.js +0 -8
- package/config/db.js +0 -20
- package/copy.js +0 -1
- package/defaultEngine.js +0 -8
- package/engine.js +0 -282
- package/getInstantiatorFunction.js +0 -28
- package/index.js +0 -52
- package/jstest.js +0 -3
- package/loggerFactory.js +0 -27
- package/logs/alchemy.log +0 -46
- package/logs/sql.log +0 -41
- package/sql/Column.js +0 -158
- package/sql/Columnable.js +0 -21
- package/sql/Create.js +0 -15
- package/sql/Delete.js +0 -34
- package/sql/Drop.js +0 -36
- package/sql/Insert.js +0 -96
- package/sql/SchemaElement.js +0 -11
- package/sql/Select.js +0 -192
- package/sql/SqlBase.js +0 -134
- package/sql/SqlStatement.js +0 -80
- package/sql/Table.js +0 -96
- package/sql/Transaction.js +0 -45
- package/sql/Update.js +0 -105
- package/sql/defineElement.js +0 -26
- package/sql/defineStatement.js +0 -26
- package/sql/types/BaseType.js +0 -43
- package/sql/types/BigSerialType.js +0 -5
- package/sql/types/IntegerType.js +0 -5
- package/sql/types/JsonType.js +0 -5
- package/sql/types/SerialType.js +0 -5
- package/sql/types/StringType.js +0 -31
- package/sql/types/TimestampType.js +0 -5
- package/sql/types/UUIDType.js +0 -5
- package/sql/types/defineType.js +0 -25
- package/sql/types/getType.js +0 -12
- package/sql/types/index.js +0 -11
- package/sql/where.js +0 -31
- package/sql/whereToString.js +0 -29
- package/test/ColumnTests.js +0 -80
- package/test/CreateTable.js +0 -54
- package/test/DeleteTests.js +0 -28
- package/test/DropTests.js +0 -23
- package/test/GetTypeTests.js +0 -56
- package/test/InsertTests.js +0 -86
- package/test/SelectExistsTests.js +0 -26
- package/test/SelectTests.js +0 -130
- package/test/TableTests.js +0 -46
- package/test.js +0 -8
- package/test.ls +0 -4
- package/validate.js +0 -26
- package/x.js +0 -5
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateTableCommand,
|
|
3
|
+
DeleteTableCommand,
|
|
4
|
+
DescribeTableCommand,
|
|
5
|
+
DynamoDBClient,
|
|
6
|
+
type KeySchemaElement,
|
|
7
|
+
ResourceInUseException,
|
|
8
|
+
ResourceNotFoundException,
|
|
9
|
+
} from "@aws-sdk/client-dynamodb";
|
|
10
|
+
import { ignore } from "../../error";
|
|
11
|
+
import { type Context, Resource } from "../../resource";
|
|
12
|
+
|
|
13
|
+
export interface TableProps {
|
|
14
|
+
tableName: string;
|
|
15
|
+
partitionKey: {
|
|
16
|
+
name: string;
|
|
17
|
+
type: "S" | "N" | "B";
|
|
18
|
+
};
|
|
19
|
+
sortKey?: {
|
|
20
|
+
name: string;
|
|
21
|
+
type: "S" | "N" | "B";
|
|
22
|
+
};
|
|
23
|
+
billingMode?: "PROVISIONED" | "PAY_PER_REQUEST";
|
|
24
|
+
readCapacity?: number;
|
|
25
|
+
writeCapacity?: number;
|
|
26
|
+
tags?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TableOutput extends TableProps {
|
|
30
|
+
id: string; // Same as tableName
|
|
31
|
+
arn: string;
|
|
32
|
+
streamArn?: string;
|
|
33
|
+
tableId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Table extends Resource(
|
|
37
|
+
"dynamo::Table",
|
|
38
|
+
async (
|
|
39
|
+
ctx: Context<TableOutput>,
|
|
40
|
+
props: TableProps,
|
|
41
|
+
): Promise<TableOutput> => {
|
|
42
|
+
const client = new DynamoDBClient({});
|
|
43
|
+
|
|
44
|
+
if (ctx.event === "delete") {
|
|
45
|
+
await ignore(ResourceNotFoundException.name, () =>
|
|
46
|
+
client.send(
|
|
47
|
+
new DeleteTableCommand({
|
|
48
|
+
TableName: props.tableName,
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Wait for table to be deleted
|
|
54
|
+
let tableDeleted = false;
|
|
55
|
+
while (!tableDeleted) {
|
|
56
|
+
try {
|
|
57
|
+
await client.send(
|
|
58
|
+
new DescribeTableCommand({
|
|
59
|
+
TableName: props.tableName,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
// If we get here, table still exists
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ResourceNotFoundException) {
|
|
66
|
+
tableDeleted = true;
|
|
67
|
+
} else {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...props,
|
|
75
|
+
id: props.tableName,
|
|
76
|
+
arn: `arn:aws:dynamodb:${client.config.region}:${process.env.AWS_ACCOUNT_ID}:table/${props.tableName}`,
|
|
77
|
+
tableId: "",
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
try {
|
|
81
|
+
const attributeDefinitions = [
|
|
82
|
+
{
|
|
83
|
+
AttributeName: props.partitionKey.name,
|
|
84
|
+
AttributeType: props.partitionKey.type,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const keySchema: KeySchemaElement[] = [
|
|
89
|
+
{
|
|
90
|
+
AttributeName: props.partitionKey.name,
|
|
91
|
+
KeyType: "HASH",
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
if (props.sortKey) {
|
|
96
|
+
attributeDefinitions.push({
|
|
97
|
+
AttributeName: props.sortKey.name,
|
|
98
|
+
AttributeType: props.sortKey.type,
|
|
99
|
+
});
|
|
100
|
+
keySchema.push({
|
|
101
|
+
AttributeName: props.sortKey.name,
|
|
102
|
+
KeyType: "RANGE",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await client.send(
|
|
107
|
+
new CreateTableCommand({
|
|
108
|
+
TableName: props.tableName,
|
|
109
|
+
AttributeDefinitions: attributeDefinitions,
|
|
110
|
+
KeySchema: keySchema,
|
|
111
|
+
BillingMode: props.billingMode || "PAY_PER_REQUEST",
|
|
112
|
+
ProvisionedThroughput:
|
|
113
|
+
props.billingMode === "PROVISIONED"
|
|
114
|
+
? {
|
|
115
|
+
ReadCapacityUnits: props.readCapacity || 5,
|
|
116
|
+
WriteCapacityUnits: props.writeCapacity || 5,
|
|
117
|
+
}
|
|
118
|
+
: undefined,
|
|
119
|
+
Tags: props.tags
|
|
120
|
+
? Object.entries(props.tags).map(([Key, Value]) => ({
|
|
121
|
+
Key,
|
|
122
|
+
Value,
|
|
123
|
+
}))
|
|
124
|
+
: undefined,
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Wait for table to be active
|
|
129
|
+
let tableActive = false;
|
|
130
|
+
let tableDescription;
|
|
131
|
+
while (!tableActive) {
|
|
132
|
+
const response = await client.send(
|
|
133
|
+
new DescribeTableCommand({
|
|
134
|
+
TableName: props.tableName,
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
tableActive = response.Table?.TableStatus === "ACTIVE";
|
|
138
|
+
if (tableActive) {
|
|
139
|
+
tableDescription = response.Table;
|
|
140
|
+
} else {
|
|
141
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...props,
|
|
147
|
+
id: props.tableName,
|
|
148
|
+
arn: tableDescription!.TableArn!,
|
|
149
|
+
streamArn: tableDescription!.LatestStreamArn,
|
|
150
|
+
tableId: tableDescription!.TableId!,
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof ResourceInUseException) {
|
|
154
|
+
// Table already exists, get its details
|
|
155
|
+
const response = await client.send(
|
|
156
|
+
new DescribeTableCommand({
|
|
157
|
+
TableName: props.tableName,
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
...props,
|
|
162
|
+
id: props.tableName,
|
|
163
|
+
arn: response.Table!.TableArn!,
|
|
164
|
+
streamArn: response.Table!.LatestStreamArn,
|
|
165
|
+
tableId: response.Table!.TableId!,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
) {}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as esbuild from "esbuild";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { Resource } from "../resource";
|
|
6
|
+
|
|
7
|
+
export interface BundleProps {
|
|
8
|
+
/**
|
|
9
|
+
* Entry point for the bundle
|
|
10
|
+
*/
|
|
11
|
+
entryPoint: string;
|
|
12
|
+
/**
|
|
13
|
+
* Output directory for the bundle
|
|
14
|
+
*/
|
|
15
|
+
outdir?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Output filename for the bundle
|
|
18
|
+
*/
|
|
19
|
+
outfile?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Bundle format (iife, cjs, esm)
|
|
22
|
+
*/
|
|
23
|
+
format?: "iife" | "cjs" | "esm";
|
|
24
|
+
/**
|
|
25
|
+
* Target environment (e.g., 'node16', 'es2020')
|
|
26
|
+
*/
|
|
27
|
+
target?: string | string[];
|
|
28
|
+
/**
|
|
29
|
+
* Whether to minify the output
|
|
30
|
+
*/
|
|
31
|
+
minify?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to generate sourcemaps
|
|
34
|
+
*/
|
|
35
|
+
sourcemap?: boolean | "inline" | "external" | "both";
|
|
36
|
+
/**
|
|
37
|
+
* External packages to exclude from bundle
|
|
38
|
+
*/
|
|
39
|
+
external?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Platform to target (browser, node, neutral)
|
|
42
|
+
*/
|
|
43
|
+
platform?: "browser" | "node" | "neutral";
|
|
44
|
+
/**
|
|
45
|
+
* Additional esbuild options
|
|
46
|
+
*/
|
|
47
|
+
options?: Partial<esbuild.BuildOptions>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface BundleOutput {
|
|
51
|
+
/**
|
|
52
|
+
* Path to the bundled file
|
|
53
|
+
*/
|
|
54
|
+
path: string;
|
|
55
|
+
/**
|
|
56
|
+
* SHA-256 hash of the bundle contents
|
|
57
|
+
*/
|
|
58
|
+
hash: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class Bundle extends Resource(
|
|
62
|
+
"esbuild::Bundle",
|
|
63
|
+
async (ctx, props: BundleProps): Promise<BundleOutput> => {
|
|
64
|
+
// Determine output path
|
|
65
|
+
const outputPath =
|
|
66
|
+
props.outfile ||
|
|
67
|
+
path.join(
|
|
68
|
+
props.outdir || "dist",
|
|
69
|
+
path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Ensure output directory exists
|
|
73
|
+
await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
74
|
+
|
|
75
|
+
if (ctx.event === "delete") {
|
|
76
|
+
await fs.promises.unlink(outputPath).catch(() => {});
|
|
77
|
+
// Also clean up sourcemap if it exists
|
|
78
|
+
await fs.promises.unlink(outputPath + ".map").catch(() => {});
|
|
79
|
+
return { path: outputPath, hash: "" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build the bundle
|
|
83
|
+
const result = await esbuild.build({
|
|
84
|
+
entryPoints: [props.entryPoint],
|
|
85
|
+
outfile: outputPath,
|
|
86
|
+
bundle: true,
|
|
87
|
+
format: props.format,
|
|
88
|
+
target: props.target,
|
|
89
|
+
minify: props.minify,
|
|
90
|
+
sourcemap: props.sourcemap,
|
|
91
|
+
external: props.external,
|
|
92
|
+
platform: props.platform,
|
|
93
|
+
metafile: true,
|
|
94
|
+
write: true,
|
|
95
|
+
...props.options,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Calculate hash of the output
|
|
99
|
+
const contents = await fs.promises.readFile(outputPath);
|
|
100
|
+
const hash = crypto.createHash("sha256").update(contents).digest("hex");
|
|
101
|
+
|
|
102
|
+
// Store metadata in context
|
|
103
|
+
await ctx.set("metafile", result.metafile);
|
|
104
|
+
await ctx.set("hash", hash);
|
|
105
|
+
|
|
106
|
+
// Return output info
|
|
107
|
+
return {
|
|
108
|
+
path: outputPath,
|
|
109
|
+
hash,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
) {
|
|
113
|
+
/**
|
|
114
|
+
* Get the metafile from the last build
|
|
115
|
+
*/
|
|
116
|
+
public async getMetafile(ctx: {
|
|
117
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
118
|
+
}) {
|
|
119
|
+
return ctx.get<esbuild.Metafile>("metafile");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get the hash from the last build
|
|
124
|
+
*/
|
|
125
|
+
public async getHash(ctx: { get<T>(key: string): Promise<T | undefined> }) {
|
|
126
|
+
return ctx.get<string>("hash");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ignore } from "../error";
|
|
4
|
+
import { Resource } from "../resource";
|
|
5
|
+
|
|
6
|
+
export class File extends Resource(
|
|
7
|
+
"fs::File",
|
|
8
|
+
async (ctx, filePath: string, content: string) => {
|
|
9
|
+
if (ctx.event === "delete") {
|
|
10
|
+
await ignore("ENOENT", () => fs.promises.unlink(filePath));
|
|
11
|
+
} else {
|
|
12
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
13
|
+
await fs.promises.writeFile(filePath, content);
|
|
14
|
+
}
|
|
15
|
+
return filePath;
|
|
16
|
+
},
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
export class Folder extends Resource(
|
|
20
|
+
"fs::Folder",
|
|
21
|
+
async (ctx, dirPath: string) => {
|
|
22
|
+
if (ctx.event === "delete") {
|
|
23
|
+
if (await ctx.get("isOwned")) {
|
|
24
|
+
// only delete the folder if we own it (we created it)
|
|
25
|
+
await ignore("ENOENT", () => fs.promises.rmdir(dirPath));
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
try {
|
|
29
|
+
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
30
|
+
} catch (error: any) {
|
|
31
|
+
if (error.code === "EEXIST") {
|
|
32
|
+
// record that we own the directory
|
|
33
|
+
await ctx.set("isOwned", true);
|
|
34
|
+
} else {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return dirPath;
|
|
40
|
+
},
|
|
41
|
+
) {
|
|
42
|
+
public file(filePath: string, content: string) {
|
|
43
|
+
return new File(path.basename(filePath), filePath, content);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/destroy.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defaultStage, stateStore } from "./global";
|
|
2
|
+
import type { Output } from "./output";
|
|
3
|
+
import { Provider, ResourceID, isResource } from "./resource";
|
|
4
|
+
import type { State } from "./state";
|
|
5
|
+
|
|
6
|
+
interface DestroyOptions {
|
|
7
|
+
stage?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Prune all resources from an Output and "down", i.e. that branches from it.
|
|
12
|
+
*/
|
|
13
|
+
export async function destroy<T>(
|
|
14
|
+
output: Output<T>,
|
|
15
|
+
options?: DestroyOptions,
|
|
16
|
+
): Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export async function destroy<T>(
|
|
22
|
+
stage: string,
|
|
23
|
+
resourceID: ResourceID,
|
|
24
|
+
resourceState: State,
|
|
25
|
+
resourceProvider: Provider,
|
|
26
|
+
): Promise<void>;
|
|
27
|
+
|
|
28
|
+
export async function destroy<T>(
|
|
29
|
+
...args: [Output<T>, DestroyOptions?] | [string, ResourceID, State, Provider]
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
let resourceID: ResourceID;
|
|
32
|
+
let resourceState: State;
|
|
33
|
+
let resourceProvider: Provider;
|
|
34
|
+
let stage: string;
|
|
35
|
+
|
|
36
|
+
if (args.length === 4) {
|
|
37
|
+
stage = args[0];
|
|
38
|
+
resourceID = args[1];
|
|
39
|
+
resourceState = args[2];
|
|
40
|
+
resourceProvider = args[3];
|
|
41
|
+
} else if (isResource(args[0])) {
|
|
42
|
+
const resource = args[0];
|
|
43
|
+
stage = args[1]?.stage ?? defaultStage;
|
|
44
|
+
resourceID = resource[ResourceID];
|
|
45
|
+
// First destroy all dependencies
|
|
46
|
+
const _resourceState = await stateStore.get(stage, resourceID);
|
|
47
|
+
if (_resourceState === undefined) {
|
|
48
|
+
// we have no record of this resource, we must assume it's already deleted
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
resourceState = _resourceState;
|
|
52
|
+
resourceProvider = resource[Provider];
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error("Not implemented: must handle destroy a Output chain");
|
|
55
|
+
}
|
|
56
|
+
await resourceProvider.delete(
|
|
57
|
+
stage,
|
|
58
|
+
resourceID,
|
|
59
|
+
resourceState,
|
|
60
|
+
resourceState.inputs as [],
|
|
61
|
+
);
|
|
62
|
+
await stateStore.delete(stage, resourceID);
|
|
63
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export async function ignore<T>(
|
|
2
|
+
codes: string | string[],
|
|
3
|
+
fn: () => Promise<T>,
|
|
4
|
+
): Promise<T | undefined> {
|
|
5
|
+
try {
|
|
6
|
+
return await fn();
|
|
7
|
+
} catch (error: any) {
|
|
8
|
+
const errorCode = error.code || error.name;
|
|
9
|
+
if (
|
|
10
|
+
Array.isArray(codes) ? codes.includes(errorCode) : errorCode === codes
|
|
11
|
+
) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/global.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { Provider, Resource, ResourceID, ResourceType } from "./resource";
|
|
4
|
+
import { defaultStore, type StateStore } from "./state";
|
|
5
|
+
|
|
6
|
+
export interface Config {
|
|
7
|
+
defaultStage?: string;
|
|
8
|
+
stateStore?: StateStore;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const alchemy = path.resolve(process.cwd(), "alchemy.ts");
|
|
12
|
+
|
|
13
|
+
let _config: Config | undefined;
|
|
14
|
+
|
|
15
|
+
const exists =
|
|
16
|
+
fs.promises.exists ??
|
|
17
|
+
async function exists(path: string): Promise<boolean> {
|
|
18
|
+
try {
|
|
19
|
+
await fs.promises.access(path);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (await exists(alchemy)) {
|
|
27
|
+
const mod = await import(alchemy);
|
|
28
|
+
if (mod) {
|
|
29
|
+
const config = mod.default as Config | undefined;
|
|
30
|
+
if (config) {
|
|
31
|
+
if (typeof config !== "object") {
|
|
32
|
+
throw new Error("Config is not an object");
|
|
33
|
+
}
|
|
34
|
+
if (typeof config.defaultStage !== "string") {
|
|
35
|
+
throw new Error("Config.stage is not a string");
|
|
36
|
+
}
|
|
37
|
+
_config = config;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const _stage = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
|
|
43
|
+
|
|
44
|
+
export const config: Config = _config ?? {
|
|
45
|
+
defaultStage: _stage,
|
|
46
|
+
stateStore: defaultStore,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const defaultStage = config.defaultStage ?? _stage;
|
|
50
|
+
|
|
51
|
+
export const stateStore = config.stateStore ?? defaultStore;
|
|
52
|
+
|
|
53
|
+
export const nodes = new Map<
|
|
54
|
+
ResourceID,
|
|
55
|
+
{
|
|
56
|
+
provider: Provider<any, any[], any>;
|
|
57
|
+
resource: Resource<any, any>;
|
|
58
|
+
}
|
|
59
|
+
>();
|
|
60
|
+
|
|
61
|
+
export const providers = new Map<
|
|
62
|
+
ResourceType,
|
|
63
|
+
Provider<ResourceType, any[], any>
|
|
64
|
+
>();
|
|
65
|
+
|
|
66
|
+
export const deletions: {
|
|
67
|
+
id: string;
|
|
68
|
+
data: Record<string, any>;
|
|
69
|
+
inputs: any[];
|
|
70
|
+
}[] = [];
|
|
71
|
+
|
|
72
|
+
await stateStore.init?.();
|
package/src/index.ts
ADDED
package/src/input.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Output } from "./output";
|
|
2
|
+
|
|
3
|
+
export type Inputs<P extends any[]> = P extends [infer First, ...infer Rest]
|
|
4
|
+
? [Input<First>, ...Inputs<Rest>]
|
|
5
|
+
: [];
|
|
6
|
+
|
|
7
|
+
export type Input<T> =
|
|
8
|
+
| T
|
|
9
|
+
| Output<T>
|
|
10
|
+
| (T extends any[] ? Inputs<T> : never)
|
|
11
|
+
| (T extends string | number | boolean ? Output<T> : never)
|
|
12
|
+
| (T extends object
|
|
13
|
+
? {
|
|
14
|
+
[k in keyof T]: Input<T[k]>;
|
|
15
|
+
}
|
|
16
|
+
: never);
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from "util";
|
|
3
|
+
|
|
4
|
+
const { values, positionals } = parseArgs({
|
|
5
|
+
args: Bun.argv.slice(2),
|
|
6
|
+
options: {
|
|
7
|
+
help: {
|
|
8
|
+
type: "boolean",
|
|
9
|
+
short: "h",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
strict: true,
|
|
13
|
+
allowPositionals: true,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (values.help || positionals.length === 0) {
|
|
17
|
+
console.error("Usage: iac <command> [args]");
|
|
18
|
+
console.error("\nCommands:");
|
|
19
|
+
console.error(" destroy [stack names] Delete the specified stacks");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const [command, ...restArgs] = positionals;
|
|
24
|
+
|
|
25
|
+
if (command.toLowerCase() === "destroy") {
|
|
26
|
+
if (restArgs.length === 0) {
|
|
27
|
+
console.error("Error: Please specify at least one stack name to destroy");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
console.log("Deleting stacks:", restArgs);
|
|
31
|
+
} else {
|
|
32
|
+
console.error(`Unknown command: ${command}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
package/src/output.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type Eval<U> = U extends Output<infer V> ? Eval<V> : U;
|
|
2
|
+
|
|
3
|
+
export type Outputs<P extends readonly any[]> = P extends [
|
|
4
|
+
infer First,
|
|
5
|
+
...infer Rest,
|
|
6
|
+
]
|
|
7
|
+
? [Output<First>, ...Outputs<Rest>]
|
|
8
|
+
: [];
|
|
9
|
+
|
|
10
|
+
export type Output<T> = {
|
|
11
|
+
apply<U>(fn: (value: T) => U): Output<Eval<U>>;
|
|
12
|
+
} & (T extends string
|
|
13
|
+
? {
|
|
14
|
+
[k in keyof typeof String.prototype]: Output<
|
|
15
|
+
(typeof String.prototype)[k]
|
|
16
|
+
>;
|
|
17
|
+
}
|
|
18
|
+
: T extends number
|
|
19
|
+
? {
|
|
20
|
+
[K in keyof typeof Number.prototype]: Output<
|
|
21
|
+
(typeof Number.prototype)[K]
|
|
22
|
+
>;
|
|
23
|
+
}
|
|
24
|
+
: T extends boolean
|
|
25
|
+
? {
|
|
26
|
+
[K in keyof typeof Boolean.prototype]: Output<
|
|
27
|
+
(typeof Boolean.prototype)[K]
|
|
28
|
+
>;
|
|
29
|
+
}
|
|
30
|
+
: T extends any[]
|
|
31
|
+
? Outputs<T>
|
|
32
|
+
: {
|
|
33
|
+
[k in keyof T]: Output<T[k]>;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const Output = class<T, U> {
|
|
37
|
+
constructor(
|
|
38
|
+
public readonly parent: Output<T>,
|
|
39
|
+
public readonly fn: (value: T) => U,
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
public apply<V>(fn: (value: U) => V): Output<V> {
|
|
43
|
+
// @ts-expect-error - we know we are an "Output"
|
|
44
|
+
return new Output(this, fn);
|
|
45
|
+
}
|
|
46
|
+
} as new <T, U>(
|
|
47
|
+
parent: Output<T>,
|
|
48
|
+
fn: (value: T) => U,
|
|
49
|
+
) => Output<U>;
|