@townco/env 0.1.3 → 0.1.5

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Represents a line in an .env file
3
+ */
4
+ type EnvLine = {
5
+ type: "comment";
6
+ content: string;
7
+ } | {
8
+ type: "blank";
9
+ } | {
10
+ type: "entry";
11
+ key: string;
12
+ value: string;
13
+ raw: string;
14
+ };
15
+ /**
16
+ * A data structure that represents a parsed .env file while preserving
17
+ * all original content including comments, blank lines, and order.
18
+ */
19
+ export declare class EnvFile {
20
+ private lines;
21
+ constructor(lines?: EnvLine[]);
22
+ /**
23
+ * Parse a .env file string into an EnvFile structure
24
+ */
25
+ static parse(content: string): EnvFile;
26
+ /**
27
+ * Serialize the EnvFile back to a string
28
+ */
29
+ toString(): string;
30
+ /**
31
+ * Get all entries as a key-value record
32
+ */
33
+ toRecord(): Record<string, string>;
34
+ /**
35
+ * Find an entry line by key
36
+ */
37
+ private findEntry;
38
+ /**
39
+ * Find the index of an entry by key
40
+ */
41
+ private findEntryIndex;
42
+ /**
43
+ * Create an entry line from key and value
44
+ */
45
+ private createEntry;
46
+ /**
47
+ * Normalize a value by ensuring it's properly quoted
48
+ */
49
+ private normalizeValue;
50
+ /**
51
+ * Find the insertion index for a new entry (before the final blank line if present)
52
+ */
53
+ private findInsertionIndex;
54
+ /**
55
+ * Get the value for a specific key
56
+ */
57
+ get(key: string): string | undefined;
58
+ /**
59
+ * Set a value for a key. If the key exists, updates it in place.
60
+ * If it doesn't exist, appends it above the final newline (if present).
61
+ * Values are always double quoted.
62
+ */
63
+ set(key: string, value: string): this;
64
+ /**
65
+ * Delete a key-value entry
66
+ */
67
+ delete(key: string): this;
68
+ /**
69
+ * Check if a key exists
70
+ */
71
+ has(key: string): boolean;
72
+ /**
73
+ * Get all keys
74
+ */
75
+ keys(): string[];
76
+ /**
77
+ * Iterate over all entries
78
+ */
79
+ entries(): IterableIterator<[string, string]>;
80
+ /**
81
+ * Apply a function to all entries and return a new EnvFile
82
+ */
83
+ map(fn: (key: string, value: string) => [string, string]): EnvFile;
84
+ /**
85
+ * Filter entries based on a predicate
86
+ */
87
+ filter(fn: (key: string, value: string) => boolean): EnvFile;
88
+ /**
89
+ * Add a comment line
90
+ */
91
+ addComment(content: string): this;
92
+ /**
93
+ * Add a blank line
94
+ */
95
+ addBlank(): this;
96
+ /**
97
+ * Get the raw lines array for custom operations
98
+ */
99
+ getLines(): readonly EnvLine[];
100
+ /**
101
+ * Create a clone of this EnvFile
102
+ */
103
+ clone(): EnvFile;
104
+ }
105
+ export {};
@@ -0,0 +1,205 @@
1
+ /**
2
+ * A data structure that represents a parsed .env file while preserving
3
+ * all original content including comments, blank lines, and order.
4
+ */
5
+ export class EnvFile {
6
+ lines;
7
+ constructor(lines = []) {
8
+ this.lines = lines;
9
+ }
10
+ /**
11
+ * Parse a .env file string into an EnvFile structure
12
+ */
13
+ static parse(content) {
14
+ const lines = content.split("\n").map((line) => {
15
+ const trimmed = line.trim();
16
+ if (trimmed === "") {
17
+ return { type: "blank" };
18
+ }
19
+ if (trimmed.startsWith("#")) {
20
+ return { type: "comment", content: line };
21
+ }
22
+ const equalsIndex = line.indexOf("=");
23
+ if (equalsIndex === -1) {
24
+ // Malformed line, treat as comment
25
+ return { type: "comment", content: line };
26
+ }
27
+ const key = line.substring(0, equalsIndex).trim();
28
+ const value = line.substring(equalsIndex + 1);
29
+ return { type: "entry", key, value, raw: line };
30
+ });
31
+ return new EnvFile(lines);
32
+ }
33
+ /**
34
+ * Serialize the EnvFile back to a string
35
+ */
36
+ toString() {
37
+ return this.lines
38
+ .map((line) => {
39
+ switch (line.type) {
40
+ case "blank":
41
+ return "";
42
+ case "comment":
43
+ return line.content;
44
+ case "entry":
45
+ return line.raw;
46
+ default:
47
+ throw new Error(`Unknown line type`);
48
+ }
49
+ })
50
+ .join("\n");
51
+ }
52
+ /**
53
+ * Get all entries as a key-value record
54
+ */
55
+ toRecord() {
56
+ return Object.fromEntries(this.entries());
57
+ }
58
+ /**
59
+ * Find an entry line by key
60
+ */
61
+ findEntry(key) {
62
+ const line = this.lines.find((l) => l.type === "entry" && l.key === key);
63
+ return line;
64
+ }
65
+ /**
66
+ * Find the index of an entry by key
67
+ */
68
+ findEntryIndex(key) {
69
+ return this.lines.findIndex((l) => l.type === "entry" && l.key === key);
70
+ }
71
+ /**
72
+ * Create an entry line from key and value
73
+ */
74
+ createEntry(key, value) {
75
+ const quotedValue = this.normalizeValue(value);
76
+ return {
77
+ type: "entry",
78
+ key,
79
+ value: quotedValue,
80
+ raw: `${key}=${quotedValue}`,
81
+ };
82
+ }
83
+ /**
84
+ * Normalize a value by ensuring it's properly quoted
85
+ */
86
+ normalizeValue(value) {
87
+ // Strip existing quotes if present, then add quotes
88
+ const unquoted = value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
89
+ return `"${unquoted}"`;
90
+ }
91
+ /**
92
+ * Find the insertion index for a new entry (before the final blank line if present)
93
+ */
94
+ findInsertionIndex() {
95
+ const lastLine = this.lines[this.lines.length - 1];
96
+ return lastLine?.type === "blank"
97
+ ? this.lines.length - 1
98
+ : this.lines.length;
99
+ }
100
+ /**
101
+ * Get the value for a specific key
102
+ */
103
+ get(key) {
104
+ return this.findEntry(key)?.value;
105
+ }
106
+ /**
107
+ * Set a value for a key. If the key exists, updates it in place.
108
+ * If it doesn't exist, appends it above the final newline (if present).
109
+ * Values are always double quoted.
110
+ */
111
+ set(key, value) {
112
+ const index = this.findEntryIndex(key);
113
+ const entry = this.createEntry(key, value);
114
+ if (index !== -1) {
115
+ this.lines[index] = entry;
116
+ }
117
+ else {
118
+ this.lines.splice(this.findInsertionIndex(), 0, entry);
119
+ }
120
+ return this;
121
+ }
122
+ /**
123
+ * Delete a key-value entry
124
+ */
125
+ delete(key) {
126
+ this.lines = this.lines.filter((l) => !(l.type === "entry" && l.key === key));
127
+ return this;
128
+ }
129
+ /**
130
+ * Check if a key exists
131
+ */
132
+ has(key) {
133
+ return this.findEntry(key) !== undefined;
134
+ }
135
+ /**
136
+ * Get all keys
137
+ */
138
+ keys() {
139
+ return this.lines
140
+ .filter((l) => l.type === "entry")
141
+ .map((l) => l.key);
142
+ }
143
+ /**
144
+ * Iterate over all entries
145
+ */
146
+ *entries() {
147
+ for (const line of this.lines) {
148
+ if (line.type === "entry") {
149
+ yield [line.key, line.value];
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * Apply a function to all entries and return a new EnvFile
155
+ */
156
+ map(fn) {
157
+ const newLines = this.lines.map((line) => {
158
+ if (line.type === "entry") {
159
+ const [newKey, newValue] = fn(line.key, line.value);
160
+ return this.createEntry(newKey, newValue);
161
+ }
162
+ return line;
163
+ });
164
+ return new EnvFile(newLines);
165
+ }
166
+ /**
167
+ * Filter entries based on a predicate
168
+ */
169
+ filter(fn) {
170
+ const newLines = this.lines.filter((line) => {
171
+ if (line.type === "entry") {
172
+ return fn(line.key, line.value);
173
+ }
174
+ return true; // Keep comments and blank lines
175
+ });
176
+ return new EnvFile(newLines);
177
+ }
178
+ /**
179
+ * Add a comment line
180
+ */
181
+ addComment(content) {
182
+ const comment = content.startsWith("#") ? content : `# ${content}`;
183
+ this.lines.push({ type: "comment", content: comment });
184
+ return this;
185
+ }
186
+ /**
187
+ * Add a blank line
188
+ */
189
+ addBlank() {
190
+ this.lines.push({ type: "blank" });
191
+ return this;
192
+ }
193
+ /**
194
+ * Get the raw lines array for custom operations
195
+ */
196
+ getLines() {
197
+ return this.lines;
198
+ }
199
+ /**
200
+ * Create a clone of this EnvFile
201
+ */
202
+ clone() {
203
+ return new EnvFile([...this.lines]);
204
+ }
205
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ export declare const envSchema: z.ZodObject<{
3
+ ANTHROPIC_API_KEY: z.ZodString;
4
+ AWS_ACCESS_KEY_ID: z.ZodString;
5
+ AWS_SECRET_ACCESS_KEY: z.ZodString;
6
+ EXA_API_KEY: z.ZodString;
7
+ FLY_TOKEN: z.ZodString;
8
+ NEXT_PUBLIC_SITE_URL: z.ZodString;
9
+ NEXT_PUBLIC_SUPABASE_ANON_KEY: z.ZodString;
10
+ NEXT_PUBLIC_SUPABASE_URL: z.ZodString;
11
+ SUPABASE_ACCESS_TOKEN: z.ZodString;
12
+ SUPABASE_ANON_KEY: z.ZodString;
13
+ SUPABASE_DB_PASSWORD: z.ZodString;
14
+ SUPABASE_PROJECT_ID: z.ZodString;
15
+ SUPABASE_SRC_ARC_BUCKET: z.ZodString;
16
+ SUPABASE_URL: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export type Env = z.infer<typeof envSchema>;
19
+ export declare const parseEnv: (env?: NodeJS.ProcessEnv) => {
20
+ ANTHROPIC_API_KEY: string;
21
+ AWS_ACCESS_KEY_ID: string;
22
+ AWS_SECRET_ACCESS_KEY: string;
23
+ EXA_API_KEY: string;
24
+ FLY_TOKEN: string;
25
+ NEXT_PUBLIC_SITE_URL: string;
26
+ NEXT_PUBLIC_SUPABASE_ANON_KEY: string;
27
+ NEXT_PUBLIC_SUPABASE_URL: string;
28
+ SUPABASE_ACCESS_TOKEN: string;
29
+ SUPABASE_ANON_KEY: string;
30
+ SUPABASE_DB_PASSWORD: string;
31
+ SUPABASE_PROJECT_ID: string;
32
+ SUPABASE_SRC_ARC_BUCKET: string;
33
+ SUPABASE_URL: string;
34
+ };
35
+ export { EnvFile } from "./env-file";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ export const envSchema = z.object({
3
+ ANTHROPIC_API_KEY: z.string().min(1),
4
+ AWS_ACCESS_KEY_ID: z.string().min(1),
5
+ AWS_SECRET_ACCESS_KEY: z.string().min(1),
6
+ EXA_API_KEY: z.string().min(1),
7
+ FLY_TOKEN: z.string().min(1),
8
+ NEXT_PUBLIC_SITE_URL: z.string().min(1),
9
+ NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
10
+ NEXT_PUBLIC_SUPABASE_URL: z.string().min(1),
11
+ SUPABASE_ACCESS_TOKEN: z.string().min(1),
12
+ SUPABASE_ANON_KEY: z.string().min(1),
13
+ SUPABASE_DB_PASSWORD: z.string().min(1),
14
+ SUPABASE_PROJECT_ID: z.string().min(1),
15
+ SUPABASE_SRC_ARC_BUCKET: z.string().min(1),
16
+ SUPABASE_URL: z.string().min(1),
17
+ });
18
+ export const parseEnv = (env = process.env) => envSchema.parse(env);
19
+ export { EnvFile } from "./env-file";
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bun
2
+ export declare const updateSchema: ({ envSchemaSrc, ...opts }?: {
3
+ envFile?: string;
4
+ envSchemaSrc?: string;
5
+ }) => Promise<void>;
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bun
2
+ import path from "node:path";
3
+ import { Biome } from "@biomejs/js-api/nodejs";
4
+ import { findRoot } from "@townco/core";
5
+ import { Project, SyntaxKind } from "ts-morph";
6
+ const envSchemaTsSrc = path.join("packages", "env", "src", "index.ts");
7
+ export const updateSchema = async ({ envSchemaSrc = envSchemaTsSrc, ...opts } = {}) => {
8
+ const envFile = opts.envFile ?? path.join(await findRoot(), ".env.in");
9
+ const root = await findRoot();
10
+ const keys = (await Bun.file(envFile).text())
11
+ .split("\n")
12
+ .filter((line) => line.match(/^[A-Z_]+\=/))
13
+ .map((line) => line.split("=")[0])
14
+ .filter((k) => Boolean(k))
15
+ .sort();
16
+ const src = new Project().addSourceFileAtPath(envSchemaSrc);
17
+ const obj = src
18
+ .getVariableDeclaration("envSchema")
19
+ .getInitializerIfKindOrThrow(SyntaxKind.CallExpression)
20
+ .getArguments()[0]
21
+ ?.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
22
+ obj?.getProperties().forEach((p) => p.remove());
23
+ obj?.addPropertyAssignments(keys.map((name) => ({ name, initializer: "z.string().min(1)" })));
24
+ const biome = new Biome();
25
+ src.replaceWithText(biome.formatContent(biome.openProject(root).projectKey, src.getFullText(), {
26
+ filePath: src.getFilePath(),
27
+ }).content);
28
+ await src.save();
29
+ };
30
+ if (import.meta.main)
31
+ await updateSchema();
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@townco/env",
3
3
  "type": "module",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "description": "env",
6
6
  "license": "UNLICENSED",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "repository": "github:townco/town",
10
+ "files": [
11
+ "dist"
12
+ ],
10
13
  "exports": {
11
14
  ".": {
12
15
  "import": "./dist/index.js",
@@ -18,7 +21,7 @@
18
21
  }
19
22
  },
20
23
  "devDependencies": {
21
- "@townco/tsconfig": "0.1.50",
24
+ "@townco/tsconfig": "0.1.52",
22
25
  "ts-morph": "^27.0.2",
23
26
  "typescript": "^5.9.3"
24
27
  },
@@ -28,7 +31,8 @@
28
31
  "schema:update": "bun src/update-schema"
29
32
  },
30
33
  "dependencies": {
31
- "@townco/core": "0.0.31",
34
+ "@biomejs/js-api": "^4.0.0",
35
+ "@townco/core": "0.0.33",
32
36
  "zod": "^4.1.13"
33
37
  }
34
38
  }
package/src/env-file.ts DELETED
@@ -1,248 +0,0 @@
1
- /**
2
- * Represents a line in an .env file
3
- */
4
- type EnvLine =
5
- | { type: "comment"; content: string }
6
- | { type: "blank" }
7
- | { type: "entry"; key: string; value: string; raw: string };
8
-
9
- type EnvEntry = Extract<EnvLine, { type: "entry" }>;
10
-
11
- /**
12
- * A data structure that represents a parsed .env file while preserving
13
- * all original content including comments, blank lines, and order.
14
- */
15
- export class EnvFile {
16
- private lines: EnvLine[];
17
-
18
- constructor(lines: EnvLine[] = []) {
19
- this.lines = lines;
20
- }
21
-
22
- /**
23
- * Parse a .env file string into an EnvFile structure
24
- */
25
- static parse(content: string): EnvFile {
26
- const lines: EnvLine[] = content.split("\n").map((line): EnvLine => {
27
- const trimmed = line.trim();
28
-
29
- if (trimmed === "") {
30
- return { type: "blank" };
31
- }
32
-
33
- if (trimmed.startsWith("#")) {
34
- return { type: "comment", content: line };
35
- }
36
-
37
- const equalsIndex = line.indexOf("=");
38
- if (equalsIndex === -1) {
39
- // Malformed line, treat as comment
40
- return { type: "comment", content: line };
41
- }
42
-
43
- const key = line.substring(0, equalsIndex).trim();
44
- const value = line.substring(equalsIndex + 1);
45
-
46
- return { type: "entry", key, value, raw: line };
47
- });
48
-
49
- return new EnvFile(lines);
50
- }
51
-
52
- /**
53
- * Serialize the EnvFile back to a string
54
- */
55
- toString(): string {
56
- return this.lines
57
- .map((line) => {
58
- switch (line.type) {
59
- case "blank":
60
- return "";
61
- case "comment":
62
- return line.content;
63
- case "entry":
64
- return line.raw;
65
- default:
66
- throw new Error(`Unknown line type`);
67
- }
68
- })
69
- .join("\n");
70
- }
71
-
72
- /**
73
- * Get all entries as a key-value record
74
- */
75
- toRecord(): Record<string, string> {
76
- return Object.fromEntries(this.entries());
77
- }
78
-
79
- /**
80
- * Find an entry line by key
81
- */
82
- private findEntry(key: string): EnvEntry | undefined {
83
- const line = this.lines.find((l) => l.type === "entry" && l.key === key);
84
- return line as EnvEntry | undefined;
85
- }
86
-
87
- /**
88
- * Find the index of an entry by key
89
- */
90
- private findEntryIndex(key: string): number {
91
- return this.lines.findIndex((l) => l.type === "entry" && l.key === key);
92
- }
93
-
94
- /**
95
- * Create an entry line from key and value
96
- */
97
- private createEntry(key: string, value: string): EnvEntry {
98
- const quotedValue = this.normalizeValue(value);
99
- return {
100
- type: "entry",
101
- key,
102
- value: quotedValue,
103
- raw: `${key}=${quotedValue}`,
104
- };
105
- }
106
-
107
- /**
108
- * Normalize a value by ensuring it's properly quoted
109
- */
110
- private normalizeValue(value: string): string {
111
- // Strip existing quotes if present, then add quotes
112
- const unquoted =
113
- value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
114
- return `"${unquoted}"`;
115
- }
116
-
117
- /**
118
- * Find the insertion index for a new entry (before the final blank line if present)
119
- */
120
- private findInsertionIndex(): number {
121
- const lastLine = this.lines[this.lines.length - 1];
122
- return lastLine?.type === "blank"
123
- ? this.lines.length - 1
124
- : this.lines.length;
125
- }
126
-
127
- /**
128
- * Get the value for a specific key
129
- */
130
- get(key: string): string | undefined {
131
- return this.findEntry(key)?.value;
132
- }
133
-
134
- /**
135
- * Set a value for a key. If the key exists, updates it in place.
136
- * If it doesn't exist, appends it above the final newline (if present).
137
- * Values are always double quoted.
138
- */
139
- set(key: string, value: string): this {
140
- const index = this.findEntryIndex(key);
141
- const entry = this.createEntry(key, value);
142
-
143
- if (index !== -1) {
144
- this.lines[index] = entry;
145
- } else {
146
- this.lines.splice(this.findInsertionIndex(), 0, entry);
147
- }
148
-
149
- return this;
150
- }
151
-
152
- /**
153
- * Delete a key-value entry
154
- */
155
- delete(key: string): this {
156
- this.lines = this.lines.filter(
157
- (l) => !(l.type === "entry" && l.key === key),
158
- );
159
- return this;
160
- }
161
-
162
- /**
163
- * Check if a key exists
164
- */
165
- has(key: string): boolean {
166
- return this.findEntry(key) !== undefined;
167
- }
168
-
169
- /**
170
- * Get all keys
171
- */
172
- keys(): string[] {
173
- return this.lines
174
- .filter((l): l is EnvEntry => l.type === "entry")
175
- .map((l) => l.key);
176
- }
177
-
178
- /**
179
- * Iterate over all entries
180
- */
181
- *entries(): IterableIterator<[string, string]> {
182
- for (const line of this.lines) {
183
- if (line.type === "entry") {
184
- yield [line.key, line.value];
185
- }
186
- }
187
- }
188
-
189
- /**
190
- * Apply a function to all entries and return a new EnvFile
191
- */
192
- map(fn: (key: string, value: string) => [string, string]): EnvFile {
193
- const newLines = this.lines.map((line) => {
194
- if (line.type === "entry") {
195
- const [newKey, newValue] = fn(line.key, line.value);
196
- return this.createEntry(newKey, newValue);
197
- }
198
- return line;
199
- });
200
-
201
- return new EnvFile(newLines);
202
- }
203
-
204
- /**
205
- * Filter entries based on a predicate
206
- */
207
- filter(fn: (key: string, value: string) => boolean): EnvFile {
208
- const newLines = this.lines.filter((line) => {
209
- if (line.type === "entry") {
210
- return fn(line.key, line.value);
211
- }
212
- return true; // Keep comments and blank lines
213
- });
214
-
215
- return new EnvFile(newLines);
216
- }
217
-
218
- /**
219
- * Add a comment line
220
- */
221
- addComment(content: string): this {
222
- const comment = content.startsWith("#") ? content : `# ${content}`;
223
- this.lines.push({ type: "comment", content: comment });
224
- return this;
225
- }
226
-
227
- /**
228
- * Add a blank line
229
- */
230
- addBlank(): this {
231
- this.lines.push({ type: "blank" });
232
- return this;
233
- }
234
-
235
- /**
236
- * Get the raw lines array for custom operations
237
- */
238
- getLines(): readonly EnvLine[] {
239
- return this.lines;
240
- }
241
-
242
- /**
243
- * Create a clone of this EnvFile
244
- */
245
- clone(): EnvFile {
246
- return new EnvFile([...this.lines]);
247
- }
248
- }
package/src/index.ts DELETED
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const envSchema = z.object({
4
- ANTHROPIC_API_KEY: z.string().min(1),
5
- AWS_ACCESS_KEY_ID: z.string().min(1),
6
- AWS_SECRET_ACCESS_KEY: z.string().min(1),
7
- EXA_API_KEY: z.string().min(1),
8
- FLY_TOKEN: z.string().min(1),
9
- NEXT_PUBLIC_SITE_URL: z.string().min(1),
10
- NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
11
- NEXT_PUBLIC_SUPABASE_URL: z.string().min(1),
12
- SUPABASE_ACCESS_TOKEN: z.string().min(1),
13
- SUPABASE_ANON_KEY: z.string().min(1),
14
- SUPABASE_DB_PASSWORD: z.string().min(1),
15
- SUPABASE_PROJECT_ID: z.string().min(1),
16
- SUPABASE_SRC_ARC_BUCKET: z.string().min(1),
17
- SUPABASE_URL: z.string().min(1),
18
- });
19
-
20
- export type Env = z.infer<typeof envSchema>;
21
- export const parseEnv = (env = process.env) => envSchema.parse(env);
22
- export { EnvFile } from "./env-file";
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env bun
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { Biome } from "@biomejs/js-api/nodejs";
5
- import { findRoot } from "@townco/core";
6
- import { Project, SyntaxKind } from "ts-morph";
7
-
8
- const envSchemaTsSrc = path.join("packages", "env", "src", "index.ts");
9
-
10
- export const updateSchema = async ({
11
- envSchemaSrc = envSchemaTsSrc,
12
- ...opts
13
- }: {
14
- envFile?: string;
15
- envSchemaSrc?: string;
16
- } = {}) => {
17
- const envFile = opts.envFile ?? path.join(await findRoot(), ".env.in");
18
- const root = await findRoot();
19
- const keys = (await Bun.file(envFile).text())
20
- .split("\n")
21
- .filter((line) => line.match(/^[A-Z_]+\=/))
22
- .map((line) => line.split("=")[0])
23
- .filter((k): k is string => Boolean(k))
24
- .sort();
25
-
26
- const src = new Project().addSourceFileAtPath(envSchemaSrc);
27
- const obj = src
28
- .getVariableDeclaration("envSchema")!
29
- .getInitializerIfKindOrThrow(SyntaxKind.CallExpression)
30
- .getArguments()[0]
31
- ?.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
32
-
33
- obj?.getProperties().forEach((p) => p.remove());
34
- obj?.addPropertyAssignments(
35
- keys.map((name) => ({ name, initializer: "z.string().min(1)" })),
36
- );
37
-
38
- const biome = new Biome();
39
- src.replaceWithText(
40
- biome.formatContent(biome.openProject(root).projectKey, src.getFullText(), {
41
- filePath: src.getFilePath(),
42
- }).content,
43
- );
44
- await src.save();
45
- };
46
-
47
- if (import.meta.main) await updateSchema();
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "@townco/tsconfig",
3
- "compilerOptions": { "outDir": "./dist", "rootDir": "./src" },
4
- "include": ["src/**/*"],
5
- "exclude": ["node_modules", "dist", ".sst"]
6
- }