alchemy 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/approve.d.ts +99 -0
  3. package/lib/ai/approve.js +76 -0
  4. package/lib/ai/astro-file.d.ts +137 -0
  5. package/lib/ai/astro-file.js +156 -0
  6. package/lib/ai/client.d.ts +7 -8
  7. package/lib/ai/client.js +48 -19
  8. package/lib/ai/css-file.d.ts +131 -0
  9. package/lib/ai/css-file.js +141 -0
  10. package/lib/ai/data.d.ts +51 -6
  11. package/lib/ai/data.js +58 -12
  12. package/lib/ai/document.d.ts +108 -24
  13. package/lib/ai/document.js +166 -52
  14. package/lib/ai/html-file.d.ts +128 -0
  15. package/lib/ai/html-file.js +138 -0
  16. package/lib/ai/index.d.ts +9 -0
  17. package/lib/ai/index.js +9 -0
  18. package/lib/ai/json-file.d.ts +160 -0
  19. package/lib/ai/json-file.js +187 -0
  20. package/lib/ai/review.d.ts +122 -0
  21. package/lib/ai/review.js +101 -0
  22. package/lib/ai/typescript-file.d.ts +139 -0
  23. package/lib/ai/typescript-file.js +145 -0
  24. package/lib/ai/vue-file.d.ts +126 -0
  25. package/lib/ai/vue-file.js +136 -0
  26. package/lib/ai/yaml-file.d.ts +169 -0
  27. package/lib/ai/yaml-file.js +200 -0
  28. package/lib/alchemy.js +17 -2
  29. package/lib/apply.js +1 -1
  30. package/lib/cloudflare/dns.d.ts +88 -0
  31. package/lib/cloudflare/dns.js +192 -0
  32. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  33. package/lib/cloudflare/index.d.ts +2 -1
  34. package/lib/cloudflare/index.js +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  36. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  37. package/lib/cloudflare/response.d.ts +12 -0
  38. package/lib/cloudflare/response.js +0 -0
  39. package/lib/cloudflare/static-site-router.js +16 -7
  40. package/lib/cloudflare/static-site.js +3 -0
  41. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  42. package/lib/cloudflare/zone.d.ts +18 -0
  43. package/lib/cloudflare/zone.js +17 -4
  44. package/lib/dns/godaddy.d.ts +7 -0
  45. package/lib/dns/godaddy.js +21 -0
  46. package/lib/dns/import-dns.d.ts +82 -0
  47. package/lib/dns/import-dns.js +114 -0
  48. package/lib/dns/index.d.ts +2 -0
  49. package/lib/dns/index.js +2 -0
  50. package/lib/dns/record.d.ts +88 -0
  51. package/lib/dns/record.js +13 -0
  52. package/lib/fs/copy-file.d.ts +54 -0
  53. package/lib/fs/copy-file.js +63 -0
  54. package/lib/fs/file-system-state-store.d.ts +17 -0
  55. package/lib/fs/file-system-state-store.js +87 -0
  56. package/lib/fs/file.d.ts +13 -0
  57. package/lib/fs/file.js +24 -5
  58. package/lib/fs/folder.d.ts +10 -0
  59. package/lib/fs/folder.js +2 -2
  60. package/lib/fs/index.d.ts +10 -4
  61. package/lib/fs/index.js +10 -4
  62. package/lib/fs/static-astro-file.d.ts +34 -0
  63. package/lib/fs/static-astro-file.js +39 -0
  64. package/lib/fs/static-css-file.d.ts +24 -0
  65. package/lib/fs/static-css-file.js +29 -0
  66. package/lib/fs/static-html-file.d.ts +31 -0
  67. package/lib/fs/static-html-file.js +36 -0
  68. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
  69. package/lib/fs/static-json-file.js +15 -0
  70. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  71. package/lib/fs/static-text-file.js +8 -0
  72. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
  73. package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
  74. package/lib/fs/static-vue-file.d.ts +28 -0
  75. package/lib/fs/static-vue-file.js +33 -0
  76. package/lib/fs/static-yaml-file.d.ts +22 -0
  77. package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
  78. package/lib/internal/getting-started.d.ts +21 -0
  79. package/lib/internal/getting-started.js +87 -0
  80. package/lib/internal/index.d.ts +3 -0
  81. package/lib/internal/index.js +3 -0
  82. package/lib/internal/providers.d.ts +27 -0
  83. package/lib/internal/providers.js +172 -0
  84. package/lib/internal/tutorial.d.ts +104 -0
  85. package/lib/internal/tutorial.js +251 -0
  86. package/lib/resource.js +8 -1
  87. package/lib/scope.d.ts +1 -1
  88. package/lib/scope.js +4 -2
  89. package/lib/state.d.ts +1 -16
  90. package/lib/state.js +0 -87
  91. package/lib/test/bun.d.ts +11 -0
  92. package/lib/test/bun.js +36 -8
  93. package/lib/web/astro.d.ts +147 -0
  94. package/lib/web/astro.js +414 -0
  95. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  96. package/lib/web/shadcn.d.ts +97 -0
  97. package/lib/web/shadcn.js +99 -0
  98. package/lib/web/tailwind.d.ts +64 -0
  99. package/lib/web/tailwind.js +97 -0
  100. package/lib/{vite → web}/vite.d.ts +5 -0
  101. package/lib/{vite → web}/vite.js +44 -44
  102. package/lib/web/vitepress/config.d.ts +10 -0
  103. package/lib/web/vitepress/config.js +31 -0
  104. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  105. package/lib/web/vitepress/custom-theme.js +336 -0
  106. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  107. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  108. package/lib/web/vitepress/home-page.d.ts +246 -0
  109. package/lib/web/vitepress/home-page.js +147 -0
  110. package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
  111. package/lib/{vitepress → web/vitepress}/index.js +2 -0
  112. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
  113. package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
  114. package/package.json +6 -2
  115. package/src/ai/approve.ts +163 -0
  116. package/src/ai/astro-file.ts +269 -0
  117. package/src/ai/client.ts +60 -21
  118. package/src/ai/css-file.ts +247 -0
  119. package/src/ai/data.ts +81 -19
  120. package/src/ai/document.ts +240 -62
  121. package/src/ai/html-file.ts +244 -0
  122. package/src/ai/index.ts +9 -0
  123. package/src/ai/json-file.ts +325 -0
  124. package/src/ai/review.ts +213 -0
  125. package/src/ai/typescript-file.ts +259 -0
  126. package/src/ai/vue-file.ts +242 -0
  127. package/src/ai/yaml-file.ts +328 -0
  128. package/src/alchemy.ts +18 -7
  129. package/src/apply.ts +8 -9
  130. package/src/cloudflare/dns.ts +344 -0
  131. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  132. package/src/cloudflare/index.ts +2 -1
  133. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  134. package/src/cloudflare/response.ts +9 -0
  135. package/src/cloudflare/static-site-router.ts +18 -8
  136. package/src/cloudflare/static-site.ts +11 -7
  137. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  138. package/src/cloudflare/worker.ts +44 -43
  139. package/src/cloudflare/zone.ts +39 -23
  140. package/src/dns/godaddy.ts +29 -0
  141. package/src/dns/import-dns.ts +213 -0
  142. package/src/dns/index.ts +2 -0
  143. package/src/dns/record.ts +121 -0
  144. package/src/fs/copy-file.ts +115 -0
  145. package/src/fs/file-system-state-store.ts +109 -0
  146. package/src/fs/file.ts +35 -9
  147. package/src/fs/folder.ts +14 -2
  148. package/src/fs/index.ts +10 -4
  149. package/src/fs/static-astro-file.ts +45 -0
  150. package/src/fs/static-css-file.ts +35 -0
  151. package/src/fs/static-html-file.ts +42 -0
  152. package/src/fs/static-json-file.ts +34 -0
  153. package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
  154. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
  155. package/src/fs/static-vue-file.ts +39 -0
  156. package/src/fs/static-yaml-file.ts +33 -0
  157. package/src/internal/getting-started.ts +115 -0
  158. package/src/internal/index.ts +3 -0
  159. package/src/internal/providers.ts +241 -0
  160. package/src/internal/tutorial.ts +392 -0
  161. package/src/resource.ts +10 -1
  162. package/src/scope.ts +5 -6
  163. package/src/state.ts +1 -111
  164. package/src/test/bun.ts +60 -10
  165. package/src/web/astro.ts +644 -0
  166. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  167. package/src/web/shadcn.ts +219 -0
  168. package/src/web/tailwind.ts +167 -0
  169. package/src/{vite → web}/vite.ts +54 -56
  170. package/src/web/vitepress/config.ts +48 -0
  171. package/src/web/vitepress/custom-theme.ts +514 -0
  172. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  173. package/src/web/vitepress/home-page.ts +375 -0
  174. package/src/{vitepress → web/vitepress}/index.ts +2 -0
  175. package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
  176. package/lib/cloudflare/state.d.ts +0 -82
  177. package/lib/cloudflare/state.js +0 -108
  178. package/lib/fs/json-file.js +0 -7
  179. package/lib/fs/text-file.js +0 -7
  180. package/lib/fs/yaml-file.d.ts +0 -19
  181. package/lib/internal/docs.d.ts +0 -5
  182. package/lib/internal/docs.js +0 -287
  183. package/lib/shadcn/index.d.ts +0 -1
  184. package/lib/shadcn/index.js +0 -1
  185. package/lib/vite/index.d.ts +0 -1
  186. package/lib/vite/index.js +0 -1
  187. package/lib/vitepress/home-page.d.ts +0 -133
  188. package/lib/vitepress/home-page.js +0 -11
  189. package/src/cloudflare/state.ts +0 -138
  190. package/src/fs/json-file.ts +0 -23
  191. package/src/fs/yaml-file.ts +0 -26
  192. package/src/internal/docs.ts +0 -338
  193. package/src/shadcn/index.ts +0 -1
  194. package/src/vite/index.ts +0 -1
  195. package/src/vitepress/home-page.ts +0 -166
  196. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  197. /package/src/{vitepress → web/vitepress}/index.md +0 -0
@@ -0,0 +1,82 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { type DnsRecordType } from "./record";
4
+ /**
5
+ * DNS record response structure from Cloudflare DNS API
6
+ * with both data (original) and content (for compatibility)
7
+ */
8
+ export interface DnsApiRecord {
9
+ name: string;
10
+ type: DnsRecordType;
11
+ TTL: number;
12
+ data: string;
13
+ content: string;
14
+ ttl: number;
15
+ priority?: number;
16
+ }
17
+ /**
18
+ * Properties for importing DNS records
19
+ */
20
+ export interface ImportDnsRecordsProps {
21
+ /**
22
+ * The domain to fetch DNS records for
23
+ */
24
+ domain: string;
25
+ /**
26
+ * Specific record types to fetch. If not provided, defaults to all supported types.
27
+ */
28
+ recordTypes?: DnsRecordType[];
29
+ /**
30
+ * Bump the resource to force a new import
31
+ */
32
+ bump?: number;
33
+ }
34
+ /**
35
+ * Output returned after DNS records import
36
+ */
37
+ export interface ImportDnsRecords extends Resource<"dns::ImportDnsRecords">, ImportDnsRecordsProps {
38
+ /**
39
+ * The DNS records as a flat array, directly compatible with DnsRecords function
40
+ */
41
+ records: DnsApiRecord[];
42
+ /**
43
+ * Time at which the records were imported
44
+ */
45
+ importedAt: number;
46
+ }
47
+ /**
48
+ * Import DNS records for a domain using Cloudflare's DNS-over-HTTPS API.
49
+ * This resource allows you to fetch DNS records for a domain and store them
50
+ * in a structured format.
51
+ *
52
+ * @example
53
+ * // Import all default record types
54
+ * const allRecords = await ImportDnsRecords("example.com", {
55
+ * domain: "example.com"
56
+ * });
57
+ *
58
+ * @example
59
+ * // Import only specific record types
60
+ * const specificRecords = await ImportDnsRecords("example.com", {
61
+ * domain: "example.com",
62
+ * recordTypes: ["A", "MX"]
63
+ * });
64
+ *
65
+ * @example
66
+ * // Import DNS records and transfer them to a Cloudflare zone
67
+ * const dnsRecords = await ImportDnsRecords("dns-records", {
68
+ * domain: "example.com",
69
+ * });
70
+ *
71
+ * const zone = await Zone("example.com", {
72
+ * name: "example.com",
73
+ * type: "full",
74
+ * });
75
+ *
76
+ * // Records are directly compatible with DnsRecords function
77
+ * await DnsRecords("transfer-dns-records", {
78
+ * zoneId: zone.id,
79
+ * records: dnsRecords.records,
80
+ * });
81
+ */
82
+ export declare const ImportDnsRecords: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<ImportDnsRecords>, id: string, props: ImportDnsRecordsProps) => Promise<ImportDnsRecords>);
@@ -0,0 +1,114 @@
1
+ import { Resource } from "../resource";
2
+ import { DEFAULT_RECORD_TYPES } from "./record";
3
+ /**
4
+ * Map numeric DNS record type to string type
5
+ */
6
+ function mapDnsRecordType(type) {
7
+ const typeMap = {
8
+ 1: "A",
9
+ 2: "NS",
10
+ 5: "CNAME",
11
+ 6: "SOA",
12
+ 15: "MX",
13
+ 16: "TXT",
14
+ 28: "AAAA",
15
+ 33: "SRV",
16
+ 12: "PTR",
17
+ };
18
+ return typeMap[type] || "A";
19
+ }
20
+ /**
21
+ * Import DNS records for a domain using Cloudflare's DNS-over-HTTPS API.
22
+ * This resource allows you to fetch DNS records for a domain and store them
23
+ * in a structured format.
24
+ *
25
+ * @example
26
+ * // Import all default record types
27
+ * const allRecords = await ImportDnsRecords("example.com", {
28
+ * domain: "example.com"
29
+ * });
30
+ *
31
+ * @example
32
+ * // Import only specific record types
33
+ * const specificRecords = await ImportDnsRecords("example.com", {
34
+ * domain: "example.com",
35
+ * recordTypes: ["A", "MX"]
36
+ * });
37
+ *
38
+ * @example
39
+ * // Import DNS records and transfer them to a Cloudflare zone
40
+ * const dnsRecords = await ImportDnsRecords("dns-records", {
41
+ * domain: "example.com",
42
+ * });
43
+ *
44
+ * const zone = await Zone("example.com", {
45
+ * name: "example.com",
46
+ * type: "full",
47
+ * });
48
+ *
49
+ * // Records are directly compatible with DnsRecords function
50
+ * await DnsRecords("transfer-dns-records", {
51
+ * zoneId: zone.id,
52
+ * records: dnsRecords.records,
53
+ * });
54
+ */
55
+ export const ImportDnsRecords = Resource("dns::ImportDnsRecords", async function (id, props) {
56
+ // For delete phase, just return destroyed state since this is a read-only resource
57
+ if (this.phase === "delete") {
58
+ return this.destroy();
59
+ }
60
+ const recordTypes = props.recordTypes || DEFAULT_RECORD_TYPES;
61
+ const allRecords = [];
62
+ for (const type of recordTypes) {
63
+ try {
64
+ const res = await fetch(`https://cloudflare-dns.com/dns-query?name=${props.domain}&type=${type}`, {
65
+ headers: {
66
+ accept: "application/dns-json",
67
+ },
68
+ });
69
+ if (!res.ok) {
70
+ throw new Error(`Failed to fetch ${type} records: ${res.statusText}`);
71
+ }
72
+ const data = (await res.json());
73
+ if (data.Answer) {
74
+ // Transform records to be compatible with DnsRecords function
75
+ const compatRecords = data.Answer.map((record) => {
76
+ const recordType = mapDnsRecordType(record.type);
77
+ const result = {
78
+ name: record.name,
79
+ type: recordType,
80
+ TTL: record.TTL,
81
+ data: record.data,
82
+ content: record.data, // Default mapping
83
+ ttl: record.TTL, // Normalized lowercase ttl
84
+ };
85
+ // Special handling for MX records to split priority and content
86
+ if (recordType === "MX") {
87
+ const parts = record.data.split(" ");
88
+ if (parts.length >= 2) {
89
+ const priority = Number.parseInt(parts[0], 10);
90
+ // Join the rest as hostname in case there are spaces
91
+ const hostname = parts.slice(1).join(" ");
92
+ if (!isNaN(priority)) {
93
+ result.priority = priority;
94
+ result.content = hostname;
95
+ }
96
+ }
97
+ }
98
+ return result;
99
+ });
100
+ allRecords.push(...compatRecords);
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.warn(`Failed to fetch ${type} records for ${props.domain}:`, error);
105
+ }
106
+ }
107
+ // Return the resource with fetched records as a flat array
108
+ return this({
109
+ domain: props.domain,
110
+ recordTypes: [...recordTypes],
111
+ records: allRecords,
112
+ importedAt: Date.now(),
113
+ });
114
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./import-dns";
2
+ export * from "./record";
@@ -0,0 +1,2 @@
1
+ export * from "./import-dns";
2
+ export * from "./record";
@@ -0,0 +1,88 @@
1
+ /**
2
+ * DNS record types supported across different providers
3
+ */
4
+ export type DnsRecordType = "A" | "AAAA" | "MX" | "TXT" | "NS" | "CNAME" | "SOA" | "SRV" | "PTR";
5
+ /**
6
+ * Default DNS record types to fetch
7
+ */
8
+ export declare const DEFAULT_RECORD_TYPES: readonly DnsRecordType[];
9
+ /**
10
+ * Base DNS record interface with common properties
11
+ */
12
+ export interface DnsRecordBase {
13
+ /**
14
+ * DNS record name (e.g., "example.com", "subdomain.example.com")
15
+ */
16
+ name: string;
17
+ /**
18
+ * Record type (A, AAAA, CNAME, etc.)
19
+ */
20
+ type: DnsRecordType;
21
+ }
22
+ /**
23
+ * DNS record response structure from DNS-over-HTTPS APIs
24
+ */
25
+ export interface DohDnsRecord extends DnsRecordBase {
26
+ /**
27
+ * Time To Live in seconds
28
+ */
29
+ TTL: number;
30
+ /**
31
+ * Record data/content (varies by record type)
32
+ */
33
+ data: string;
34
+ }
35
+ /**
36
+ * Comprehensive DNS record structure for creating/managing records
37
+ */
38
+ export interface DnsRecord extends DnsRecordBase {
39
+ /**
40
+ * DNS record content (e.g., IP address, hostname)
41
+ */
42
+ content: string;
43
+ /**
44
+ * Time To Live (TTL) in seconds
45
+ * Setting to 1 means 'automatic'
46
+ * Value must be between 60 and 86400, with minimum reduced to 30 for Enterprise zones
47
+ * @default 1
48
+ */
49
+ ttl?: number;
50
+ /**
51
+ * Whether the record is receiving proxied traffic through Cloudflare
52
+ * @default false
53
+ */
54
+ proxied?: boolean;
55
+ /**
56
+ * Comments or notes about the record
57
+ */
58
+ comment?: string;
59
+ /**
60
+ * Record tags
61
+ */
62
+ tags?: string[];
63
+ /**
64
+ * Priority value for MX/SRV records
65
+ */
66
+ priority?: number;
67
+ }
68
+ /**
69
+ * DNS record with provider-specific ID and metadata
70
+ */
71
+ export interface DnsRecordWithMetadata extends DnsRecord {
72
+ /**
73
+ * Record ID
74
+ */
75
+ id: string;
76
+ /**
77
+ * Zone or domain ID the record belongs to
78
+ */
79
+ zoneId: string;
80
+ /**
81
+ * Time at which the record was created
82
+ */
83
+ createdAt: number;
84
+ /**
85
+ * Time at which the record was last modified
86
+ */
87
+ modifiedAt?: number;
88
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Default DNS record types to fetch
3
+ */
4
+ export const DEFAULT_RECORD_TYPES = [
5
+ "A",
6
+ "AAAA",
7
+ "MX",
8
+ "TXT",
9
+ "NS",
10
+ "CNAME",
11
+ "SOA",
12
+ "SRV",
13
+ ];
@@ -0,0 +1,54 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ /**
4
+ * Properties for creating a CopyFile resource
5
+ */
6
+ export interface CopyFileProps {
7
+ /**
8
+ * Source path of the file to copy
9
+ */
10
+ src: string;
11
+ /**
12
+ * Destination path where the file should be copied to
13
+ */
14
+ dest: string;
15
+ /**
16
+ * Whether to overwrite the destination file if it already exists
17
+ * @default true
18
+ */
19
+ overwrite?: boolean;
20
+ }
21
+ /**
22
+ * Output returned after CopyFile creation/update
23
+ */
24
+ export interface CopyFile extends Resource<"fs::CopyFile">, CopyFileProps {
25
+ /**
26
+ * Time at which the object was created
27
+ */
28
+ createdAt: number;
29
+ /**
30
+ * Whether the file was successfully copied
31
+ */
32
+ copied: boolean;
33
+ }
34
+ /**
35
+ * CopyFile Resource
36
+ *
37
+ * Copies a file from a source location to a destination location.
38
+ *
39
+ * @example
40
+ * // Copy a file to a new location
41
+ * const copiedFile = await CopyFile("config-copy", {
42
+ * src: "config.json",
43
+ * dest: "backup/config.json"
44
+ * });
45
+ *
46
+ * @example
47
+ * // Copy a file without overwriting if destination exists
48
+ * const safeCopy = await CopyFile("safe-copy", {
49
+ * src: "data.json",
50
+ * dest: "backup/data.json",
51
+ * overwrite: false
52
+ * });
53
+ */
54
+ export declare const CopyFile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<CopyFile>, id: string, props: CopyFileProps) => Promise<CopyFile>);
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs";
2
+ import { Resource } from "../resource";
3
+ import { ignore } from "../util/ignore";
4
+ /**
5
+ * CopyFile Resource
6
+ *
7
+ * Copies a file from a source location to a destination location.
8
+ *
9
+ * @example
10
+ * // Copy a file to a new location
11
+ * const copiedFile = await CopyFile("config-copy", {
12
+ * src: "config.json",
13
+ * dest: "backup/config.json"
14
+ * });
15
+ *
16
+ * @example
17
+ * // Copy a file without overwriting if destination exists
18
+ * const safeCopy = await CopyFile("safe-copy", {
19
+ * src: "data.json",
20
+ * dest: "backup/data.json",
21
+ * overwrite: false
22
+ * });
23
+ */
24
+ export const CopyFile = Resource("fs::CopyFile", async function (id, props) {
25
+ const { src, dest, overwrite = true } = props;
26
+ if (this.phase === "delete") {
27
+ // When deleting, remove the destination file
28
+ await ignore("ENOENT", async () => fs.promises.unlink(dest));
29
+ return this.destroy();
30
+ }
31
+ else {
32
+ try {
33
+ // Check if source file exists
34
+ await fs.promises.access(src, fs.constants.F_OK);
35
+ // If this is an update and the destination has changed, delete the old file
36
+ if (this.phase === "update" &&
37
+ this.output?.dest &&
38
+ this.output.dest !== dest) {
39
+ await ignore("ENOENT", async () => fs.promises.unlink(this.output.dest));
40
+ }
41
+ // Check if destination file exists
42
+ const destinationExists = await fs.promises
43
+ .access(dest, fs.constants.F_OK)
44
+ .then(() => true)
45
+ .catch(() => false);
46
+ // Copy file if destination doesn't exist or overwrite is true
47
+ if (!destinationExists || overwrite) {
48
+ await fs.promises.copyFile(src, dest);
49
+ }
50
+ return this({
51
+ src,
52
+ dest,
53
+ overwrite,
54
+ copied: true,
55
+ createdAt: Date.now(),
56
+ });
57
+ }
58
+ catch (error) {
59
+ console.error(`Error copying file from ${src} to ${dest}:`, error);
60
+ throw error;
61
+ }
62
+ }
63
+ });
@@ -0,0 +1,17 @@
1
+ import type { Scope } from "../scope";
2
+ import type { State, StateStore } from "../state";
3
+ export declare class FileSystemStateStore implements StateStore {
4
+ readonly scope: Scope;
5
+ readonly dir: string;
6
+ constructor(scope: Scope);
7
+ init(): Promise<void>;
8
+ deinit(): Promise<void>;
9
+ count(): Promise<number>;
10
+ list(): Promise<string[]>;
11
+ get(key: string): Promise<State | undefined>;
12
+ set(key: string, value: State): Promise<void>;
13
+ delete(key: string): Promise<void>;
14
+ all(): Promise<Record<string, State>>;
15
+ getBatch(ids: string[]): Promise<Record<string, State>>;
16
+ private getPath;
17
+ }
@@ -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/file.d.ts CHANGED
@@ -79,6 +79,19 @@ export interface File extends Resource<"fs::File"> {
79
79
  * path: "logs/app.log",
80
80
  * content: "application log entry"
81
81
  * });
82
+ *
83
+ * @example
84
+ * // Update file content and path
85
+ * let file = await File("config.json", {
86
+ * path: "config.json",
87
+ * content: '{ "version": "1.0.0" }'
88
+ * });
89
+ *
90
+ * // Later, update the path and content (old file will be removed)
91
+ * file = await File("config.json", {
92
+ * path: "config/config.json",
93
+ * content: '{ "version": "1.0.1" }'
94
+ * });
82
95
  */
83
96
  export declare const File: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<File>, id: string, props: {
84
97
  path: string;
package/lib/fs/file.js CHANGED
@@ -36,6 +36,19 @@ alchemy.files = async (...args) => {
36
36
  * path: "logs/app.log",
37
37
  * content: "application log entry"
38
38
  * });
39
+ *
40
+ * @example
41
+ * // Update file content and path
42
+ * let file = await File("config.json", {
43
+ * path: "config.json",
44
+ * content: '{ "version": "1.0.0" }'
45
+ * });
46
+ *
47
+ * // Later, update the path and content (old file will be removed)
48
+ * file = await File("config.json", {
49
+ * path: "config/config.json",
50
+ * content: '{ "version": "1.0.1" }'
51
+ * });
39
52
  */
40
53
  export const File = Resource("fs::File", async function (id, props) {
41
54
  const filePath = props?.path ?? id;
@@ -43,12 +56,18 @@ export const File = Resource("fs::File", async function (id, props) {
43
56
  await ignore("ENOENT", async () => fs.promises.unlink(filePath));
44
57
  return this.destroy();
45
58
  }
46
- else {
47
- await fs.promises.mkdir(path.dirname(filePath), {
48
- recursive: true,
49
- });
50
- await fs.promises.writeFile(filePath, props.content);
59
+ else if (this.phase === "update" &&
60
+ this.output &&
61
+ this.output.path !== filePath) {
62
+ // If path has changed, delete the old file
63
+ console.log(`File: Path changed from ${this.output.path} to ${filePath}, removing old file`);
64
+ await ignore("ENOENT", async () => fs.promises.unlink(this.output.path));
51
65
  }
66
+ // Create directory and write file
67
+ await fs.promises.mkdir(path.dirname(filePath), {
68
+ recursive: true,
69
+ });
70
+ await fs.promises.writeFile(filePath, props.content);
52
71
  return this({
53
72
  path: filePath,
54
73
  content: props.content,
@@ -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
@@ -1,8 +1,14 @@
1
+ export * from "./copy-file";
1
2
  export * from "./file";
2
3
  export * from "./file-collection";
3
4
  export * from "./file-ref";
5
+ export * from "./file-system-state-store";
4
6
  export * from "./folder";
5
- export * from "./json-file";
6
- export * from "./text-file";
7
- export * from "./typescript-file";
8
- export * from "./yaml-file";
7
+ export * from "./static-astro-file";
8
+ export * from "./static-css-file";
9
+ export * from "./static-html-file";
10
+ export * from "./static-json-file";
11
+ export * from "./static-text-file";
12
+ export * from "./static-typescript-file";
13
+ export * from "./static-vue-file";
14
+ export * from "./static-yaml-file";
package/lib/fs/index.js CHANGED
@@ -1,8 +1,14 @@
1
+ export * from "./copy-file";
1
2
  export * from "./file";
2
3
  export * from "./file-collection";
3
4
  export * from "./file-ref";
5
+ export * from "./file-system-state-store";
4
6
  export * from "./folder";
5
- export * from "./json-file";
6
- export * from "./text-file";
7
- export * from "./typescript-file";
8
- export * from "./yaml-file";
7
+ export * from "./static-astro-file";
8
+ export * from "./static-css-file";
9
+ export * from "./static-html-file";
10
+ export * from "./static-json-file";
11
+ export * from "./static-text-file";
12
+ export * from "./static-typescript-file";
13
+ export * from "./static-vue-file";
14
+ export * from "./static-yaml-file";