ultraenv 1.0.0

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2058 -0
  3. package/bin/ultraenv.mjs +3 -0
  4. package/dist/chunk-2USZPWLZ.js +288 -0
  5. package/dist/chunk-3UV2QNJL.js +270 -0
  6. package/dist/chunk-3VYXPTYV.js +179 -0
  7. package/dist/chunk-4XUYMRK5.js +366 -0
  8. package/dist/chunk-5G2DU52U.js +189 -0
  9. package/dist/chunk-6KS56D6E.js +172 -0
  10. package/dist/chunk-AWN6ADV7.js +328 -0
  11. package/dist/chunk-CHVO6NWI.js +203 -0
  12. package/dist/chunk-CIFMBJ4H.js +3975 -0
  13. package/dist/chunk-GC7RXHLA.js +253 -0
  14. package/dist/chunk-HFXQGJY3.js +445 -0
  15. package/dist/chunk-IGFVP24Q.js +91 -0
  16. package/dist/chunk-IKPTKALB.js +78 -0
  17. package/dist/chunk-JB7RKV3C.js +66 -0
  18. package/dist/chunk-MNVFG7H4.js +611 -0
  19. package/dist/chunk-MSXMESFP.js +1910 -0
  20. package/dist/chunk-N5PAV4NM.js +127 -0
  21. package/dist/chunk-NBOABPHM.js +158 -0
  22. package/dist/chunk-OMAOROL4.js +49 -0
  23. package/dist/chunk-R7PZRSZ7.js +105 -0
  24. package/dist/chunk-TE7HPLA6.js +73 -0
  25. package/dist/chunk-TMT5KCO3.js +101 -0
  26. package/dist/chunk-UEWYFN6A.js +189 -0
  27. package/dist/chunk-WMHN5RW2.js +128 -0
  28. package/dist/chunk-XC65ORJ5.js +70 -0
  29. package/dist/chunk-YMMP4VQL.js +118 -0
  30. package/dist/chunk-YN2KGTCB.js +33 -0
  31. package/dist/chunk-YTICOB5M.js +65 -0
  32. package/dist/chunk-YVWLXFUT.js +107 -0
  33. package/dist/ci-check-sync-VBMSVWIV.js +48 -0
  34. package/dist/ci-scan-24MT5XGS.js +41 -0
  35. package/dist/ci-setup-C2NKEFRD.js +135 -0
  36. package/dist/ci-validate-7AW24LSQ.js +57 -0
  37. package/dist/cli/index.cjs +9217 -0
  38. package/dist/cli/index.d.cts +9 -0
  39. package/dist/cli/index.d.ts +9 -0
  40. package/dist/cli/index.js +339 -0
  41. package/dist/comparator-RDKX3OI7.js +13 -0
  42. package/dist/completion-MW35C2XO.js +168 -0
  43. package/dist/config-O5YRQP5Z.js +13 -0
  44. package/dist/debug-PTPXAF3K.js +131 -0
  45. package/dist/declaration-LEME4AFZ.js +10 -0
  46. package/dist/doctor-FZAUPKHS.js +129 -0
  47. package/dist/envs-compare-5K3HESX5.js +49 -0
  48. package/dist/envs-create-2XXHXMGA.js +58 -0
  49. package/dist/envs-list-NQM5252B.js +59 -0
  50. package/dist/envs-switch-6L2AQYID.js +50 -0
  51. package/dist/envs-validate-FL73Q76T.js +89 -0
  52. package/dist/fs-VH7ATUS3.js +31 -0
  53. package/dist/generator-LFZBMZZS.js +14 -0
  54. package/dist/git-BZS4DPAI.js +30 -0
  55. package/dist/help-3XJBXEHE.js +121 -0
  56. package/dist/index.cjs +12907 -0
  57. package/dist/index.d.cts +2562 -0
  58. package/dist/index.d.ts +2562 -0
  59. package/dist/index.js +3212 -0
  60. package/dist/init-Y7JQ2KYJ.js +146 -0
  61. package/dist/install-hook-SKXIV6NV.js +111 -0
  62. package/dist/json-schema-I26YNQBH.js +10 -0
  63. package/dist/key-manager-O3G55WPU.js +25 -0
  64. package/dist/middleware/express.cjs +103 -0
  65. package/dist/middleware/express.d.cts +115 -0
  66. package/dist/middleware/express.d.ts +115 -0
  67. package/dist/middleware/express.js +8 -0
  68. package/dist/middleware/fastify.cjs +91 -0
  69. package/dist/middleware/fastify.d.cts +111 -0
  70. package/dist/middleware/fastify.d.ts +111 -0
  71. package/dist/middleware/fastify.js +8 -0
  72. package/dist/module-IDIZPP4M.js +10 -0
  73. package/dist/protect-NCWPM6VC.js +161 -0
  74. package/dist/scan-TRLY36TT.js +58 -0
  75. package/dist/schema/index.cjs +4074 -0
  76. package/dist/schema/index.d.cts +1244 -0
  77. package/dist/schema/index.d.ts +1244 -0
  78. package/dist/schema/index.js +152 -0
  79. package/dist/sync-TMHMTLH2.js +186 -0
  80. package/dist/typegen-SQOSXBWM.js +80 -0
  81. package/dist/validate-IOAM5HWS.js +100 -0
  82. package/dist/vault-decrypt-U6HJZNBV.js +111 -0
  83. package/dist/vault-diff-B3ZOQTWI.js +132 -0
  84. package/dist/vault-encrypt-GUSLCSKS.js +112 -0
  85. package/dist/vault-init-GUBOTOUL.js +106 -0
  86. package/dist/vault-rekey-DAHT7JCN.js +132 -0
  87. package/dist/vault-status-GDLRU2OK.js +90 -0
  88. package/dist/vault-verify-CD76FJSF.js +102 -0
  89. package/package.json +106 -0
@@ -0,0 +1,189 @@
1
+ // src/core/errors.ts
2
+ var UltraenvError = class extends Error {
3
+ /** Machine-readable error code (e.g., 'PARSE_ERROR', 'ENCRYPTION_ERROR') */
4
+ code;
5
+ /** Human-readable hint for how to fix this error */
6
+ hint;
7
+ /** The original cause of this error, if wrapping another error */
8
+ cause;
9
+ constructor(message, options = {}) {
10
+ super(message);
11
+ this.name = this.constructor.name;
12
+ this.code = options.code ?? "ULTRAENV_ERROR";
13
+ this.hint = options.hint;
14
+ this.cause = options.cause;
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ /** Return a formatted string with code, message, and hint */
18
+ toString() {
19
+ const parts = [`[${this.code}] ${this.message}`];
20
+ if (this.hint !== void 0) {
21
+ parts.push(` Hint: ${this.hint}`);
22
+ }
23
+ return parts.join("\n");
24
+ }
25
+ };
26
+ var ValidationError = class extends UltraenvError {
27
+ /** The variable name that failed validation */
28
+ field;
29
+ /** The actual value that was provided */
30
+ value;
31
+ /** The schema definition that was violated */
32
+ schema;
33
+ /** Expected type or constraint description */
34
+ expected;
35
+ constructor(field, value, message, options = {}) {
36
+ const fullMessage = message || `Validation failed for "${field}"`;
37
+ super(fullMessage, {
38
+ code: "VALIDATION_ERROR",
39
+ hint: options.hint ?? `Check the value of "${field}" in your .env file. Expected: ${options.expected ?? "see schema definition"}.`,
40
+ cause: options.cause
41
+ });
42
+ this.field = field;
43
+ this.value = value;
44
+ this.schema = options.schema;
45
+ this.expected = options.expected ?? "valid value";
46
+ }
47
+ };
48
+ var ParseError = class extends UltraenvError {
49
+ /** 1-based line number where the error occurred */
50
+ line;
51
+ /** 1-based column number where the error occurred */
52
+ column;
53
+ /** The raw content of the line that caused the error */
54
+ raw;
55
+ /** Path to the file being parsed */
56
+ filePath;
57
+ constructor(message, options = {}) {
58
+ const location = options.line !== void 0 ? ` at line ${options.line}${options.column !== void 0 ? `, column ${options.column}` : ""}` : "";
59
+ const file = options.filePath !== void 0 ? ` in "${options.filePath}"` : "";
60
+ const fullMessage = `${message}${file}${location}`;
61
+ super(fullMessage, {
62
+ code: "PARSE_ERROR",
63
+ hint: options.hint ?? "Check your .env file for invalid syntax. Each line should be in the format KEY=VALUE or # comment.",
64
+ cause: options.cause
65
+ });
66
+ this.line = options.line ?? 0;
67
+ this.column = options.column ?? 0;
68
+ this.raw = options.raw ?? "";
69
+ this.filePath = options.filePath ?? "";
70
+ }
71
+ };
72
+ var InterpolationError = class extends UltraenvError {
73
+ /** The variable reference that caused the error */
74
+ variable;
75
+ /** Whether this was caused by a circular reference */
76
+ circular;
77
+ constructor(message, options = {}) {
78
+ super(message, {
79
+ code: "INTERPOLATION_ERROR",
80
+ hint: options.hint ?? (options.circular ? "Circular variable references are not allowed. Check that no two variables reference each other." : `Make sure "${options.variable ?? "the referenced variable"}" is defined before it is referenced.`),
81
+ cause: options.cause
82
+ });
83
+ this.variable = options.variable ?? "";
84
+ this.circular = options.circular ?? false;
85
+ }
86
+ };
87
+ var EncryptionError = class extends UltraenvError {
88
+ constructor(message, options = {}) {
89
+ super(message, {
90
+ code: "ENCRYPTION_ERROR",
91
+ hint: options.hint ?? 'Ensure your encryption key is valid and has not been corrupted. Try regenerating the key with "ultraenv key generate".',
92
+ cause: options.cause
93
+ });
94
+ }
95
+ };
96
+ var VaultError = class extends UltraenvError {
97
+ /** The environment name involved */
98
+ environment;
99
+ /** The vault operation that failed */
100
+ operation;
101
+ constructor(message, options = {}) {
102
+ super(message, {
103
+ code: "VAULT_ERROR",
104
+ hint: options.hint ?? `Check that the vault file exists and is properly formatted for environment "${options.environment ?? "unknown"}". Run "ultraenv vault status" for diagnostics.`,
105
+ cause: options.cause
106
+ });
107
+ this.environment = options.environment ?? "";
108
+ this.operation = options.operation ?? "";
109
+ }
110
+ };
111
+ var ScanError = class extends UltraenvError {
112
+ /** The file being scanned */
113
+ file;
114
+ /** The line number where the error occurred */
115
+ line;
116
+ /** The secret pattern that triggered the error */
117
+ pattern;
118
+ constructor(message, options = {}) {
119
+ super(message, {
120
+ code: "SCAN_ERROR",
121
+ hint: options.hint ?? "Check that all files being scanned are accessible and contain valid text content.",
122
+ cause: options.cause
123
+ });
124
+ this.file = options.file ?? "";
125
+ this.line = options.line ?? 0;
126
+ this.pattern = options.pattern ?? "";
127
+ }
128
+ };
129
+ var ConfigError = class extends UltraenvError {
130
+ /** The configuration field that is invalid */
131
+ field;
132
+ constructor(message, options = {}) {
133
+ super(message, {
134
+ code: "CONFIG_ERROR",
135
+ hint: options.hint ?? `Check your ultraenv configuration. The field "${options.field ?? "unknown"}" may have an invalid value or type.`,
136
+ cause: options.cause
137
+ });
138
+ this.field = options.field ?? "";
139
+ }
140
+ };
141
+ var FileSystemError = class extends UltraenvError {
142
+ /** The file or directory path involved */
143
+ path;
144
+ /** The operation that failed (e.g., 'read', 'write', 'mkdir') */
145
+ operation;
146
+ /** The underlying system error code (e.g., 'ENOENT', 'EACCES') */
147
+ code;
148
+ constructor(message, options = {}) {
149
+ const fsCode = options.code ?? options.cause?.code ?? "UNKNOWN";
150
+ const opHint = options.path !== void 0 ? `Could not ${options.operation ?? "access"} "${options.path}".` : "";
151
+ super(message, {
152
+ code: `FS_${fsCode}`,
153
+ hint: options.hint ?? `${opHint} Ensure the file exists and you have the necessary permissions.`,
154
+ cause: options.cause
155
+ });
156
+ this.path = options.path ?? "";
157
+ this.operation = options.operation ?? "";
158
+ this.code = fsCode;
159
+ }
160
+ };
161
+ function isUltraenvError(error) {
162
+ return error instanceof UltraenvError;
163
+ }
164
+ function getErrorMessage(error) {
165
+ if (error instanceof UltraenvError) {
166
+ return error.toString();
167
+ }
168
+ if (error instanceof Error) {
169
+ return error.message;
170
+ }
171
+ if (typeof error === "string") {
172
+ return error;
173
+ }
174
+ return `Unknown error: ${String(error)}`;
175
+ }
176
+
177
+ export {
178
+ UltraenvError,
179
+ ValidationError,
180
+ ParseError,
181
+ InterpolationError,
182
+ EncryptionError,
183
+ VaultError,
184
+ ScanError,
185
+ ConfigError,
186
+ FileSystemError,
187
+ isUltraenvError,
188
+ getErrorMessage
189
+ };
@@ -0,0 +1,172 @@
1
+ import {
2
+ writeFile
3
+ } from "./chunk-3VYXPTYV.js";
4
+
5
+ // src/typegen/json-schema.ts
6
+ var JSON_SCHEMA_DRAFT_07 = "http://json-schema.org/draft-07/schema#";
7
+ function schemaToJsonSchemaProperty(key, schema, includeDescriptions) {
8
+ const r = schema;
9
+ const schemaType = r.type;
10
+ const property = {
11
+ type: "string"
12
+ };
13
+ if (includeDescriptions && schema.description !== void 0) {
14
+ property.description = schema.description;
15
+ } else if (includeDescriptions) {
16
+ property.description = key;
17
+ }
18
+ if (schema.default !== void 0) {
19
+ property.default = schema.default;
20
+ } else if (r.hasDefault === true && typeof r.rawDefaultValue === "string") {
21
+ const raw = r.rawDefaultValue;
22
+ if (raw === "true") {
23
+ property.default = true;
24
+ } else if (raw === "false") {
25
+ property.default = false;
26
+ } else if (raw !== "" && !Number.isNaN(Number(raw)) && raw === String(Number(raw))) {
27
+ property.default = Number(raw);
28
+ } else {
29
+ property.default = raw;
30
+ }
31
+ }
32
+ switch (schemaType) {
33
+ case "string": {
34
+ property.type = "string";
35
+ const enumValues = r.enum;
36
+ if (Array.isArray(enumValues) && enumValues.length > 0) {
37
+ property.enum = [...enumValues];
38
+ }
39
+ if (typeof r.minLength === "number") {
40
+ property.minLength = r.minLength;
41
+ }
42
+ if (typeof r.maxLength === "number") {
43
+ property.maxLength = r.maxLength;
44
+ }
45
+ if (r.pattern instanceof RegExp) {
46
+ property.pattern = r.pattern.source;
47
+ }
48
+ if (typeof r.format === "string") {
49
+ property.format = mapFormat(r.format);
50
+ }
51
+ break;
52
+ }
53
+ case "number": {
54
+ property.type = r.integer === true ? "integer" : "number";
55
+ if (typeof r.min === "number") {
56
+ property.minimum = r.min;
57
+ if (r.positive === true) {
58
+ property.exclusiveMinimum = true;
59
+ }
60
+ }
61
+ if (typeof r.max === "number") {
62
+ property.maximum = r.max;
63
+ if (r.negative === true) {
64
+ property.exclusiveMaximum = true;
65
+ }
66
+ }
67
+ break;
68
+ }
69
+ case "boolean": {
70
+ property.type = "boolean";
71
+ break;
72
+ }
73
+ case "enum": {
74
+ property.type = "string";
75
+ const values = r.values;
76
+ if (Array.isArray(values) && values.length > 0) {
77
+ property.enum = [...values];
78
+ }
79
+ break;
80
+ }
81
+ case "array": {
82
+ property.type = "array";
83
+ property.items = { type: "string" };
84
+ if (typeof r.minItems === "number") {
85
+ property.minItems = r.minItems;
86
+ }
87
+ if (typeof r.maxItems === "number") {
88
+ property.maxItems = r.maxItems;
89
+ }
90
+ if (r.unique === true) {
91
+ property.uniqueItems = true;
92
+ }
93
+ if (typeof r.separator === "string" && r.separator !== ",") {
94
+ property.description = (property.description ?? "") + ` (separator: "${r.separator}")`;
95
+ }
96
+ break;
97
+ }
98
+ case "json": {
99
+ property.type = "object";
100
+ break;
101
+ }
102
+ case "date": {
103
+ property.type = "string";
104
+ property.format = "date-time";
105
+ break;
106
+ }
107
+ case "bigint": {
108
+ property.type = "string";
109
+ property.description = (property.description ?? "") + " (bigint as string)";
110
+ break;
111
+ }
112
+ default: {
113
+ property.type = "string";
114
+ break;
115
+ }
116
+ }
117
+ return property;
118
+ }
119
+ function mapFormat(format) {
120
+ const formatMap = /* @__PURE__ */ new Map([
121
+ ["email", "email"],
122
+ ["url", "uri"],
123
+ ["uuid", "uuid"],
124
+ ["hostname", "hostname"],
125
+ ["ip", "string"],
126
+ ["ipv4", "ipv4"],
127
+ ["ipv6", "ipv6"]
128
+ ]);
129
+ return formatMap.get(format) ?? "string";
130
+ }
131
+ async function generateJsonSchema(schema, outputPath, options) {
132
+ const content = generateJsonSchemaContent(schema, options);
133
+ if (outputPath !== void 0) {
134
+ await writeFile(outputPath, content);
135
+ }
136
+ return content;
137
+ }
138
+ function generateJsonSchemaContent(schema, options) {
139
+ const includeDescriptions = options?.includeDescriptions ?? true;
140
+ const indent = options?.indent ?? 2;
141
+ const title = options?.title ?? "Environment Variables";
142
+ const description = options?.description ?? "Schema for environment variables managed by ultraenv";
143
+ const properties = {};
144
+ const required = [];
145
+ const sortedKeys = Object.keys(schema).sort();
146
+ for (const key of sortedKeys) {
147
+ const schemaEntry = schema[key];
148
+ if (schemaEntry === void 0) continue;
149
+ properties[key] = schemaToJsonSchemaProperty(key, schemaEntry, includeDescriptions);
150
+ const r = schemaEntry;
151
+ const isOptional = schemaEntry.optional === true || schemaEntry.default !== void 0 || r.hasDefault === true;
152
+ if (!isOptional) {
153
+ required.push(key);
154
+ }
155
+ }
156
+ required.sort();
157
+ const document = {
158
+ $schema: JSON_SCHEMA_DRAFT_07,
159
+ title,
160
+ description,
161
+ type: "object",
162
+ properties,
163
+ required,
164
+ additionalProperties: false
165
+ };
166
+ return JSON.stringify(document, null, indent) + "\n";
167
+ }
168
+
169
+ export {
170
+ generateJsonSchema,
171
+ generateJsonSchemaContent
172
+ };
@@ -0,0 +1,328 @@
1
+ import {
2
+ parseEnvFile
3
+ } from "./chunk-HFXQGJY3.js";
4
+ import {
5
+ exists,
6
+ readFile,
7
+ removeFile,
8
+ writeFile
9
+ } from "./chunk-3VYXPTYV.js";
10
+ import {
11
+ FileSystemError
12
+ } from "./chunk-5G2DU52U.js";
13
+
14
+ // src/environments/creator.ts
15
+ import { resolve, join } from "path";
16
+ var NODEJS_TEMPLATE = `# Node.js Backend Environment
17
+ # Created by ultraenv
18
+
19
+ # Server
20
+ PORT=3000
21
+ HOST=localhost
22
+ NODE_ENV=development
23
+
24
+ # Database
25
+ DATABASE_URL=
26
+ DATABASE_POOL_SIZE=10
27
+
28
+ # Authentication
29
+ JWT_SECRET=
30
+ JWT_EXPIRES_IN=7d
31
+
32
+ # CORS
33
+ ALLOWED_ORIGINS=http://localhost:3000
34
+
35
+ # Logging
36
+ LOG_LEVEL=info
37
+ LOG_FORMAT=json
38
+
39
+ # Feature Flags
40
+ DEBUG=false
41
+ VERBOSE=false
42
+ `;
43
+ var NEXTJS_TEMPLATE = `# Next.js Frontend Environment
44
+ # Created by ultraenv
45
+
46
+ # Framework
47
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
48
+ NEXT_PUBLIC_API_URL=http://localhost:3001
49
+
50
+ # Analytics
51
+ NEXT_PUBLIC_GA_ID=
52
+ NEXT_PUBLIC_SENTRY_DSN=
53
+
54
+ # Feature Flags
55
+ NEXT_PUBLIC_DEBUG=false
56
+ NEXT_PUBLIC_MAINTENANCE=false
57
+ `;
58
+ var DOCKER_TEMPLATE = `# Docker Deployment Environment
59
+ # Created by ultraenv
60
+
61
+ # Container
62
+ CONTAINER_PORT=3000
63
+ HOST_PORT=3000
64
+
65
+ # Database
66
+ POSTGRES_HOST=localhost
67
+ POSTGRES_PORT=5432
68
+ POSTGRES_USER=
69
+ POSTGRES_PASSWORD=
70
+ POSTGRES_DB=
71
+
72
+ # Redis
73
+ REDIS_HOST=localhost
74
+ REDIS_PORT=6379
75
+ REDIS_PASSWORD=
76
+
77
+ # Networking
78
+ NETWORK_NAME=app-network
79
+ `;
80
+ var BUILTIN_TEMPLATES = /* @__PURE__ */ new Map([
81
+ ["nodejs", NODEJS_TEMPLATE],
82
+ ["nextjs", NEXTJS_TEMPLATE],
83
+ ["docker", DOCKER_TEMPLATE]
84
+ ]);
85
+ function extractVariables(content, schema) {
86
+ const parsed = parseEnvFile(content);
87
+ const variables = [];
88
+ const seenKeys = /* @__PURE__ */ new Set();
89
+ if (schema !== void 0) {
90
+ for (const [key, schemaEntry] of Object.entries(schema)) {
91
+ if (seenKeys.has(key)) continue;
92
+ seenKeys.add(key);
93
+ const r = schemaEntry;
94
+ variables.push({
95
+ key,
96
+ value: "",
97
+ description: schemaEntry.description,
98
+ isSecret: r.isSecret === true,
99
+ isRequired: schemaEntry.optional !== true && schemaEntry.default === void 0 && r.hasDefault !== true,
100
+ hasDefault: schemaEntry.default !== void 0 || r.hasDefault === true,
101
+ defaultValue: typeof r.rawDefaultValue === "string" ? r.rawDefaultValue : void 0,
102
+ type: typeof r.typeName === "string" ? r.typeName : "string"
103
+ });
104
+ }
105
+ }
106
+ for (const envVar of parsed.vars) {
107
+ if (seenKeys.has(envVar.key)) continue;
108
+ seenKeys.add(envVar.key);
109
+ variables.push({
110
+ key: envVar.key,
111
+ value: envVar.value,
112
+ isSecret: false,
113
+ isRequired: false,
114
+ hasDefault: false,
115
+ type: "string"
116
+ });
117
+ }
118
+ return variables;
119
+ }
120
+ function generateEnvironmentContent(variables, values) {
121
+ const lines = [];
122
+ lines.push(`# Environment: generated by ultraenv`);
123
+ lines.push(`# Created: ${(/* @__PURE__ */ new Date()).toISOString()}`);
124
+ lines.push("");
125
+ for (const variable of variables) {
126
+ if (variable.description !== void 0) {
127
+ lines.push(`# ${variable.description}`);
128
+ }
129
+ lines.push(`# type: ${variable.type}`);
130
+ if (variable.isRequired) {
131
+ lines.push("# required");
132
+ } else if (variable.hasDefault && variable.defaultValue !== void 0) {
133
+ lines.push(`# default: ${variable.defaultValue}`);
134
+ } else {
135
+ lines.push("# optional");
136
+ }
137
+ if (variable.isSecret) {
138
+ lines.push("# [SECRET]");
139
+ }
140
+ const providedValue = values[variable.key];
141
+ if (providedValue !== void 0) {
142
+ lines.push(`${variable.key}=${providedValue}`);
143
+ } else if (variable.hasDefault && variable.defaultValue !== void 0) {
144
+ lines.push(`${variable.key}=${variable.defaultValue}`);
145
+ } else if (variable.isSecret) {
146
+ lines.push(`${variable.key}=`);
147
+ } else if (variable.isRequired) {
148
+ lines.push(`${variable.key}=`);
149
+ } else {
150
+ lines.push(`# ${variable.key}=`);
151
+ }
152
+ lines.push("");
153
+ }
154
+ return lines.join("\n");
155
+ }
156
+ function collectValues(variables, providedValues) {
157
+ const values = { ...providedValues };
158
+ for (const variable of variables) {
159
+ if (variable.key in values) continue;
160
+ if (!variable.isRequired && !variable.hasDefault) continue;
161
+ if (variable.hasDefault && variable.defaultValue !== void 0) {
162
+ values[variable.key] = variable.defaultValue;
163
+ }
164
+ }
165
+ return values;
166
+ }
167
+ function validateEnvironmentName(name) {
168
+ if (name.length === 0) {
169
+ throw new FileSystemError("Environment name cannot be empty", {
170
+ path: "",
171
+ operation: "validate",
172
+ hint: 'Provide a valid environment name (e.g., "staging", "production").'
173
+ });
174
+ }
175
+ if (name.length > 64) {
176
+ throw new FileSystemError(
177
+ `Environment name too long: ${name.length} characters (max 64)`,
178
+ {
179
+ path: "",
180
+ operation: "validate",
181
+ hint: "Use a shorter environment name."
182
+ }
183
+ );
184
+ }
185
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
186
+ throw new FileSystemError(
187
+ `Invalid environment name: "${name}". Only alphanumeric characters, hyphens, and underscores are allowed.`,
188
+ {
189
+ path: "",
190
+ operation: "validate",
191
+ hint: 'Use names like "staging", "production", "ci", "dev-v2", etc.'
192
+ }
193
+ );
194
+ }
195
+ const reserved = ["local", "example", "template", "bak", "backup", "old", "tmp", "temp"];
196
+ if (reserved.includes(name.toLowerCase())) {
197
+ throw new FileSystemError(
198
+ `Reserved environment name: "${name}"`,
199
+ {
200
+ path: "",
201
+ operation: "validate",
202
+ hint: `The name "${name}" is reserved. Choose a different name.`
203
+ }
204
+ );
205
+ }
206
+ }
207
+ async function createEnvironment(name, options) {
208
+ validateEnvironmentName(name);
209
+ const baseDir = resolve(options?.cwd ?? process.cwd());
210
+ const outputPath = join(baseDir, `.env.${name}`);
211
+ if (await exists(outputPath)) {
212
+ throw new FileSystemError(
213
+ `Environment file already exists: ".env.${name}"`,
214
+ {
215
+ path: outputPath,
216
+ operation: "create",
217
+ hint: "Delete the existing file first, or use a different environment name."
218
+ }
219
+ );
220
+ }
221
+ let content;
222
+ let variables = [];
223
+ if (options?.copyFrom !== void 0) {
224
+ const sourcePath = join(baseDir, `.env.${options.copyFrom}`);
225
+ if (!await exists(sourcePath)) {
226
+ throw new FileSystemError(
227
+ `Source environment not found: ".env.${options.copyFrom}"`,
228
+ {
229
+ path: sourcePath,
230
+ operation: "read",
231
+ hint: `Ensure ".env.${options.copyFrom}" exists before copying.`
232
+ }
233
+ );
234
+ }
235
+ content = await readFile(sourcePath);
236
+ variables = extractVariables(content, options?.schema);
237
+ const sourceParsed = parseEnvFile(content, sourcePath);
238
+ const sourceValues = {};
239
+ for (const envVar of sourceParsed.vars) {
240
+ sourceValues[envVar.key] = envVar.value;
241
+ }
242
+ content = generateEnvironmentContent(variables, {
243
+ ...sourceValues,
244
+ ...options?.values
245
+ });
246
+ } else if (options?.fromTemplate !== void 0) {
247
+ const templatePath = resolve(options.fromTemplate);
248
+ const builtinTemplate = BUILTIN_TEMPLATES.get(options.fromTemplate.toLowerCase());
249
+ if (builtinTemplate !== void 0) {
250
+ content = builtinTemplate;
251
+ } else if (await exists(templatePath)) {
252
+ content = await readFile(templatePath);
253
+ } else {
254
+ throw new FileSystemError(
255
+ `Template not found: "${options.fromTemplate}"`,
256
+ {
257
+ path: templatePath,
258
+ operation: "read",
259
+ hint: "Use a built-in template name (nodejs, nextjs, docker) or a valid file path."
260
+ }
261
+ );
262
+ }
263
+ variables = extractVariables(content, options?.schema);
264
+ const values = collectValues(variables, options?.values);
265
+ content = generateEnvironmentContent(variables, values);
266
+ } else if (options?.schema !== void 0) {
267
+ const schemaVars = [];
268
+ for (const [key, schemaEntry] of Object.entries(options.schema)) {
269
+ const r = schemaEntry;
270
+ schemaVars.push({
271
+ key,
272
+ value: "",
273
+ description: schemaEntry.description,
274
+ isSecret: r.isSecret === true,
275
+ isRequired: schemaEntry.optional !== true && schemaEntry.default === void 0 && r.hasDefault !== true,
276
+ hasDefault: schemaEntry.default !== void 0 || r.hasDefault === true,
277
+ defaultValue: typeof r.rawDefaultValue === "string" ? r.rawDefaultValue : void 0,
278
+ type: typeof r.typeName === "string" ? r.typeName : "string"
279
+ });
280
+ }
281
+ const values = collectValues(schemaVars, options?.values);
282
+ content = generateEnvironmentContent(schemaVars, values);
283
+ } else {
284
+ content = [
285
+ `# Environment: ${name}`,
286
+ `# Created by ultraenv on ${(/* @__PURE__ */ new Date()).toISOString()}`,
287
+ "",
288
+ "# Add your environment variables here",
289
+ ""
290
+ ].join("\n");
291
+ }
292
+ await writeFile(outputPath, content);
293
+ }
294
+ function listTemplates() {
295
+ return [
296
+ { name: "nodejs", description: "Node.js backend environment (PORT, DATABASE_URL, JWT, CORS, etc.)" },
297
+ { name: "nextjs", description: "Next.js frontend environment (NEXT_PUBLIC_* variables, analytics, etc.)" },
298
+ { name: "docker", description: "Docker deployment environment (container ports, Postgres, Redis, etc.)" }
299
+ ];
300
+ }
301
+ async function removeEnvironment(name, cwd) {
302
+ const baseDir = resolve(cwd ?? process.cwd());
303
+ const filePath = join(baseDir, `.env.${name}`);
304
+ if (!await exists(filePath)) {
305
+ throw new FileSystemError(
306
+ `Environment file not found: ".env.${name}"`,
307
+ {
308
+ path: filePath,
309
+ operation: "unlink",
310
+ hint: "The environment does not exist."
311
+ }
312
+ );
313
+ }
314
+ await removeFile(filePath);
315
+ }
316
+ async function duplicateEnvironment(sourceName, targetName, cwd) {
317
+ await createEnvironment(targetName, {
318
+ copyFrom: sourceName,
319
+ cwd
320
+ });
321
+ }
322
+
323
+ export {
324
+ createEnvironment,
325
+ listTemplates,
326
+ removeEnvironment,
327
+ duplicateEnvironment
328
+ };