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,121 @@
1
+ /**
2
+ * DNS record types supported across different providers
3
+ */
4
+ export type DnsRecordType =
5
+ | "A"
6
+ | "AAAA"
7
+ | "MX"
8
+ | "TXT"
9
+ | "NS"
10
+ | "CNAME"
11
+ | "SOA"
12
+ | "SRV"
13
+ | "PTR";
14
+
15
+ /**
16
+ * Default DNS record types to fetch
17
+ */
18
+ export const DEFAULT_RECORD_TYPES: readonly DnsRecordType[] = [
19
+ "A",
20
+ "AAAA",
21
+ "MX",
22
+ "TXT",
23
+ "NS",
24
+ "CNAME",
25
+ "SOA",
26
+ "SRV",
27
+ ] as const;
28
+
29
+ /**
30
+ * Base DNS record interface with common properties
31
+ */
32
+ export interface DnsRecordBase {
33
+ /**
34
+ * DNS record name (e.g., "example.com", "subdomain.example.com")
35
+ */
36
+ name: string;
37
+
38
+ /**
39
+ * Record type (A, AAAA, CNAME, etc.)
40
+ */
41
+ type: DnsRecordType;
42
+ }
43
+
44
+ /**
45
+ * DNS record response structure from DNS-over-HTTPS APIs
46
+ */
47
+ export interface DohDnsRecord extends DnsRecordBase {
48
+ /**
49
+ * Time To Live in seconds
50
+ */
51
+ TTL: number;
52
+
53
+ /**
54
+ * Record data/content (varies by record type)
55
+ */
56
+ data: string;
57
+ }
58
+
59
+ /**
60
+ * Comprehensive DNS record structure for creating/managing records
61
+ */
62
+ export interface DnsRecord extends DnsRecordBase {
63
+ /**
64
+ * DNS record content (e.g., IP address, hostname)
65
+ */
66
+ content: string;
67
+
68
+ /**
69
+ * Time To Live (TTL) in seconds
70
+ * Setting to 1 means 'automatic'
71
+ * Value must be between 60 and 86400, with minimum reduced to 30 for Enterprise zones
72
+ * @default 1
73
+ */
74
+ ttl?: number;
75
+
76
+ /**
77
+ * Whether the record is receiving proxied traffic through Cloudflare
78
+ * @default false
79
+ */
80
+ proxied?: boolean;
81
+
82
+ /**
83
+ * Comments or notes about the record
84
+ */
85
+ comment?: string;
86
+
87
+ /**
88
+ * Record tags
89
+ */
90
+ tags?: string[];
91
+
92
+ /**
93
+ * Priority value for MX/SRV records
94
+ */
95
+ priority?: number;
96
+ }
97
+
98
+ /**
99
+ * DNS record with provider-specific ID and metadata
100
+ */
101
+ export interface DnsRecordWithMetadata extends DnsRecord {
102
+ /**
103
+ * Record ID
104
+ */
105
+ id: string;
106
+
107
+ /**
108
+ * Zone or domain ID the record belongs to
109
+ */
110
+ zoneId: string;
111
+
112
+ /**
113
+ * Time at which the record was created
114
+ */
115
+ createdAt: number;
116
+
117
+ /**
118
+ * Time at which the record was last modified
119
+ */
120
+ modifiedAt?: number;
121
+ }
@@ -0,0 +1,115 @@
1
+ import fs from "node:fs";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
4
+ import { ignore } from "../util/ignore";
5
+
6
+ /**
7
+ * Properties for creating a CopyFile resource
8
+ */
9
+ export interface CopyFileProps {
10
+ /**
11
+ * Source path of the file to copy
12
+ */
13
+ src: string;
14
+
15
+ /**
16
+ * Destination path where the file should be copied to
17
+ */
18
+ dest: string;
19
+
20
+ /**
21
+ * Whether to overwrite the destination file if it already exists
22
+ * @default true
23
+ */
24
+ overwrite?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Output returned after CopyFile creation/update
29
+ */
30
+ export interface CopyFile extends Resource<"fs::CopyFile">, CopyFileProps {
31
+ /**
32
+ * Time at which the object was created
33
+ */
34
+ createdAt: number;
35
+
36
+ /**
37
+ * Whether the file was successfully copied
38
+ */
39
+ copied: boolean;
40
+ }
41
+
42
+ /**
43
+ * CopyFile Resource
44
+ *
45
+ * Copies a file from a source location to a destination location.
46
+ *
47
+ * @example
48
+ * // Copy a file to a new location
49
+ * const copiedFile = await CopyFile("config-copy", {
50
+ * src: "config.json",
51
+ * dest: "backup/config.json"
52
+ * });
53
+ *
54
+ * @example
55
+ * // Copy a file without overwriting if destination exists
56
+ * const safeCopy = await CopyFile("safe-copy", {
57
+ * src: "data.json",
58
+ * dest: "backup/data.json",
59
+ * overwrite: false
60
+ * });
61
+ */
62
+ export const CopyFile = Resource(
63
+ "fs::CopyFile",
64
+ async function (
65
+ this: Context<CopyFile>,
66
+ id: string,
67
+ props: CopyFileProps
68
+ ): Promise<CopyFile> {
69
+ const { src, dest, overwrite = true } = props;
70
+
71
+ if (this.phase === "delete") {
72
+ // When deleting, remove the destination file
73
+ await ignore("ENOENT", async () => fs.promises.unlink(dest));
74
+ return this.destroy();
75
+ } else {
76
+ try {
77
+ // Check if source file exists
78
+ await fs.promises.access(src, fs.constants.F_OK);
79
+
80
+ // If this is an update and the destination has changed, delete the old file
81
+ if (
82
+ this.phase === "update" &&
83
+ this.output?.dest &&
84
+ this.output.dest !== dest
85
+ ) {
86
+ await ignore("ENOENT", async () =>
87
+ fs.promises.unlink(this.output.dest)
88
+ );
89
+ }
90
+
91
+ // Check if destination file exists
92
+ const destinationExists = await fs.promises
93
+ .access(dest, fs.constants.F_OK)
94
+ .then(() => true)
95
+ .catch(() => false);
96
+
97
+ // Copy file if destination doesn't exist or overwrite is true
98
+ if (!destinationExists || overwrite) {
99
+ await fs.promises.copyFile(src, dest);
100
+ }
101
+
102
+ return this({
103
+ src,
104
+ dest,
105
+ overwrite,
106
+ copied: true,
107
+ createdAt: Date.now(),
108
+ });
109
+ } catch (error) {
110
+ console.error(`Error copying file from ${src} to ${dest}:`, error);
111
+ throw error;
112
+ }
113
+ }
114
+ }
115
+ );
@@ -0,0 +1,109 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { Scope } from "../scope";
4
+ import type { State, StateStore } from "../state";
5
+ import { ignore } from "../util/ignore";
6
+ import { deserialize, serialize } from "../util/serde";
7
+
8
+ const stateRootDir = path.join(process.cwd(), ".alchemy");
9
+
10
+ export class FileSystemStateStore implements StateStore {
11
+ public readonly dir: string;
12
+ constructor(public readonly scope: Scope) {
13
+ this.dir = path.join(stateRootDir, ...scope.chain);
14
+ }
15
+
16
+ async init(): Promise<void> {
17
+ await fs.mkdir(stateRootDir, { recursive: true });
18
+ await fs.mkdir(this.dir, { recursive: true });
19
+ }
20
+
21
+ async deinit(): Promise<void> {
22
+ await ignore("ENOENT", () => fs.rmdir(this.dir));
23
+ }
24
+
25
+ async count(): Promise<number> {
26
+ return Object.keys(await this.list()).length;
27
+ }
28
+
29
+ async list(): Promise<string[]> {
30
+ try {
31
+ const files = await fs.readdir(this.dir, {
32
+ withFileTypes: true,
33
+ });
34
+ return files
35
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
36
+ .map((dirent) => dirent.name.replace(/\.json$/, ""))
37
+ .map((key) => key.replaceAll(":", "/"));
38
+ } catch (error: any) {
39
+ if (error.code === "ENOENT") {
40
+ return [];
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ async get(key: string): Promise<State | undefined> {
47
+ try {
48
+ const content = await fs.readFile(await this.getPath(key), "utf8");
49
+ const state = (await deserialize(
50
+ this.scope,
51
+ JSON.parse(content),
52
+ )) as State;
53
+ if (state.output === undefined) {
54
+ state.output = {} as any;
55
+ }
56
+ state.output.Scope = this.scope;
57
+ return state;
58
+ } catch (error: any) {
59
+ if (error.code === "ENOENT") {
60
+ return undefined;
61
+ }
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ async set(key: string, value: State): Promise<void> {
67
+ return fs.writeFile(
68
+ await this.getPath(key),
69
+ JSON.stringify(await serialize(this.scope, value), null, 2),
70
+ );
71
+ }
72
+
73
+ async delete(key: string): Promise<void> {
74
+ return fs.unlink(await this.getPath(key));
75
+ }
76
+
77
+ async all(): Promise<Record<string, State>> {
78
+ return this.getBatch(await this.list());
79
+ }
80
+
81
+ async getBatch(ids: string[]): Promise<Record<string, State>> {
82
+ return Object.fromEntries(
83
+ (
84
+ await Promise.all(
85
+ Array.from(ids).flatMap(async (id) => {
86
+ const s = await this.get(id);
87
+ if (s === undefined) {
88
+ return [] as const;
89
+ }
90
+ return [[id, s]] as const;
91
+ }),
92
+ )
93
+ ).flat(),
94
+ );
95
+ }
96
+
97
+ private async getPath(key: string): Promise<string> {
98
+ if (key.includes(":")) {
99
+ throw new Error(`ID cannot include colons: ${key}`);
100
+ }
101
+ if (key.includes("/")) {
102
+ key = key.replaceAll("/", ":");
103
+ }
104
+ const file = path.join(this.dir, `${key}.json`);
105
+ const dir = path.dirname(file);
106
+ await fs.mkdir(dir, { recursive: true });
107
+ return file;
108
+ }
109
+ }
package/src/fs/file.ts CHANGED
@@ -72,8 +72,8 @@ alchemy.files = async (
72
72
  paths.map(async (path) => [
73
73
  path,
74
74
  await fs.promises.readFile(path, "utf-8"),
75
- ]),
76
- ),
75
+ ])
76
+ )
77
77
  ),
78
78
  };
79
79
  };
@@ -111,6 +111,19 @@ export interface File extends Resource<"fs::File"> {
111
111
  * path: "logs/app.log",
112
112
  * content: "application log entry"
113
113
  * });
114
+ *
115
+ * @example
116
+ * // Update file content and path
117
+ * let file = await File("config.json", {
118
+ * path: "config.json",
119
+ * content: '{ "version": "1.0.0" }'
120
+ * });
121
+ *
122
+ * // Later, update the path and content (old file will be removed)
123
+ * file = await File("config.json", {
124
+ * path: "config/config.json",
125
+ * content: '{ "version": "1.0.1" }'
126
+ * });
114
127
  */
115
128
  export const File = Resource(
116
129
  "fs::File",
@@ -120,21 +133,34 @@ export const File = Resource(
120
133
  props: {
121
134
  path: string;
122
135
  content: string;
123
- },
136
+ }
124
137
  ): Promise<File> {
125
138
  const filePath = props?.path ?? id;
139
+
126
140
  if (this.phase === "delete") {
127
141
  await ignore("ENOENT", async () => fs.promises.unlink(filePath));
128
142
  return this.destroy();
129
- } else {
130
- await fs.promises.mkdir(path.dirname(filePath), {
131
- recursive: true,
132
- });
133
- await fs.promises.writeFile(filePath, props.content);
143
+ } else if (
144
+ this.phase === "update" &&
145
+ this.output &&
146
+ this.output.path !== filePath
147
+ ) {
148
+ // If path has changed, delete the old file
149
+ console.log(
150
+ `File: Path changed from ${this.output.path} to ${filePath}, removing old file`
151
+ );
152
+ await ignore("ENOENT", async () => fs.promises.unlink(this.output.path));
134
153
  }
154
+
155
+ // Create directory and write file
156
+ await fs.promises.mkdir(path.dirname(filePath), {
157
+ recursive: true,
158
+ });
159
+ await fs.promises.writeFile(filePath, props.content);
160
+
135
161
  return this({
136
162
  path: filePath,
137
163
  content: props.content,
138
164
  });
139
- },
165
+ }
140
166
  );
package/src/fs/folder.ts CHANGED
@@ -13,6 +13,18 @@ export interface FolderProps {
13
13
  * @default true
14
14
  */
15
15
  delete?: boolean;
16
+
17
+ /**
18
+ * Whether to clean the folder during the deletion phase (even if it contains existing files)
19
+ * @default false
20
+ */
21
+ clean?: boolean;
22
+
23
+ /**
24
+ * Whether to create the folder recursively
25
+ * @default true
26
+ */
27
+ recursive?: boolean;
16
28
  }
17
29
 
18
30
  /**
@@ -56,13 +68,13 @@ export const Folder = Resource(
56
68
  if (props?.delete !== false) {
57
69
  // we just do a best effort attempt
58
70
  await ignore(["ENOENT", "ENOTEMPTY"], async () =>
59
- fs.promises.rmdir(dirPath),
71
+ fs.promises.rmdir(dirPath, { recursive: props?.clean ?? false }),
60
72
  );
61
73
  }
62
74
  return this.destroy();
63
75
  } else {
64
76
  await ignore("EEXIST", async () =>
65
- fs.promises.mkdir(dirPath, { recursive: true }),
77
+ fs.promises.mkdir(dirPath, { recursive: props?.recursive ?? true }),
66
78
  );
67
79
  }
68
80
  return this({
package/src/fs/index.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";
@@ -0,0 +1,45 @@
1
+ import { File } from "./file";
2
+
3
+ export type StaticAstroFile = File;
4
+
5
+ /**
6
+ * Creates a static Astro component file
7
+ *
8
+ * @example
9
+ * // Create an Astro component file with content
10
+ * const header = await StaticAstroFile("Header.astro",
11
+ * `---
12
+ * import Logo from '../components/Logo.astro';
13
+ * const navItems = ['Home', 'About', 'Contact'];
14
+ * ---
15
+ *
16
+ * <header class="header">
17
+ * <Logo />
18
+ * <nav>
19
+ * <ul>
20
+ * {navItems.map(item => (
21
+ * <li><a href={`/${item.toLowerCase()}`}>{item}</a></li>
22
+ * ))}
23
+ * </ul>
24
+ * </nav>
25
+ * </header>
26
+ *
27
+ * <style>
28
+ * .header {
29
+ * display: flex;
30
+ * justify-content: space-between;
31
+ * padding: 1rem;
32
+ * }
33
+ * </style>`
34
+ * );
35
+ */
36
+ export function StaticAstroFile(
37
+ id: string,
38
+ ...args: [content: string] | [path: string, content: string]
39
+ ): Promise<StaticAstroFile> {
40
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
41
+ return File(id, {
42
+ path,
43
+ content,
44
+ });
45
+ }
@@ -0,0 +1,35 @@
1
+ import { File } from "./file";
2
+
3
+ export type StaticCSSFile = File;
4
+
5
+ /**
6
+ * Creates a static CSS file
7
+ *
8
+ * @example
9
+ * // Create a CSS file with styles
10
+ * const styles = await StaticCSSFile("styles.css",
11
+ * `.container {
12
+ * max-width: 1200px;
13
+ * margin: 0 auto;
14
+ * padding: 0 1rem;
15
+ * }
16
+ *
17
+ * .button {
18
+ * background-color: #0062ff;
19
+ * color: white;
20
+ * border: none;
21
+ * padding: 0.5rem 1rem;
22
+ * border-radius: 4px;
23
+ * }`
24
+ * );
25
+ */
26
+ export function StaticCSSFile(
27
+ id: string,
28
+ ...args: [content: string] | [path: string, content: string]
29
+ ): Promise<StaticCSSFile> {
30
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
31
+ return File(id, {
32
+ path,
33
+ content,
34
+ });
35
+ }
@@ -0,0 +1,42 @@
1
+ import { File } from "./file";
2
+
3
+ export type StaticHTMLFile = File;
4
+
5
+ /**
6
+ * Creates a static HTML file
7
+ *
8
+ * @example
9
+ * // Create an HTML file with content
10
+ * const page = await StaticHTMLFile("index.html",
11
+ * `<!DOCTYPE html>
12
+ * <html lang="en">
13
+ * <head>
14
+ * <meta charset="UTF-8">
15
+ * <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ * <title>My Website</title>
17
+ * <link rel="stylesheet" href="styles.css">
18
+ * </head>
19
+ * <body>
20
+ * <header>
21
+ * <h1>Welcome to My Website</h1>
22
+ * </header>
23
+ * <main>
24
+ * <p>This is the main content of the page.</p>
25
+ * </main>
26
+ * <footer>
27
+ * <p>&copy; 2024 My Company</p>
28
+ * </footer>
29
+ * </body>
30
+ * </html>`
31
+ * );
32
+ */
33
+ export function StaticHTMLFile(
34
+ id: string,
35
+ ...args: [content: string] | [path: string, content: string]
36
+ ): Promise<StaticHTMLFile> {
37
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
38
+ return File(id, {
39
+ path,
40
+ content,
41
+ });
42
+ }
@@ -0,0 +1,34 @@
1
+ import { File } from "./file";
2
+
3
+ /**
4
+ * Creates a JSON file with formatted content
5
+ *
6
+ * @example
7
+ * // Create a JSON configuration file
8
+ * const config = await StaticJsonFile("config.json", {
9
+ * api: {
10
+ * endpoint: "https://api.example.com",
11
+ * version: "v1"
12
+ * },
13
+ * features: ["auth", "logging"]
14
+ * });
15
+ */
16
+ export type StaticJsonFile = File;
17
+
18
+ export async function StaticJsonFile(
19
+ id: string,
20
+ ...args: [content: any] | [path: string, content: any]
21
+ ): Promise<StaticJsonFile> {
22
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
23
+ const prettier = await import("prettier");
24
+ return File(id, {
25
+ path,
26
+ content: await prettier.format(JSON.stringify(content), {
27
+ parser: "json",
28
+ editor: {
29
+ tabWidth: 2,
30
+ indentWidth: 2,
31
+ },
32
+ }),
33
+ });
34
+ }
@@ -9,11 +9,17 @@ import { File } from "./file";
9
9
  * "# Project Name\n\nProject description goes here."
10
10
  * );
11
11
  */
12
- export type TextFile = File;
12
+ export type StaticTextFile = File;
13
13
 
14
- export function TextFile(id: string, content: string): Promise<TextFile> {
14
+ export function StaticTextFile(
15
+ id: string,
16
+ ...args:
17
+ | [content: string]
18
+ | [path: string, content: string]
19
+ ): Promise<StaticTextFile> {
20
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
15
21
  return File(id, {
16
- path: id,
22
+ path,
17
23
  content,
18
24
  });
19
25
  }
@@ -5,7 +5,7 @@ import { File } from "./file";
5
5
  *
6
6
  * @example
7
7
  * // Create a TypeScript file
8
- * const component = await TypeScriptFile("Component.ts", `
8
+ * const component = await StaticTypeScriptFile("Component.ts", `
9
9
  * interface Props {
10
10
  * name: string;
11
11
  * age: number;
@@ -16,15 +16,16 @@ import { File } from "./file";
16
16
  * }
17
17
  * `);
18
18
  */
19
- export type TypeScriptFile = File;
19
+ export type StaticTypeScriptFile = File;
20
20
 
21
- export async function TypeScriptFile(
21
+ export async function StaticTypeScriptFile(
22
22
  id: string,
23
- content: string,
24
- ): Promise<TypeScriptFile> {
23
+ ...args: [content: string] | [path: string, content: string]
24
+ ): Promise<StaticTypeScriptFile> {
25
+ const [path, content] = args.length === 1 ? [id, args[0]] : args;
25
26
  const prettier = await import("prettier");
26
27
  return File(id, {
27
- path: id,
28
+ path,
28
29
  content: await prettier.format(content, {
29
30
  parser: "typescript",
30
31
  editor: {