@akshxy/envgit 0.3.0 → 0.4.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.
- package/bin/envgit.js +3 -2
- package/package.json +1 -1
- package/src/commands/set.js +27 -8
- package/src/envfile.js +417 -8
package/bin/envgit.js
CHANGED
|
@@ -37,9 +37,10 @@ program
|
|
|
37
37
|
.action(status);
|
|
38
38
|
|
|
39
39
|
program
|
|
40
|
-
.command('set
|
|
41
|
-
.description('Set
|
|
40
|
+
.command('set [assignments...]')
|
|
41
|
+
.description('Set KEY=VALUE pairs, or load from a file with -f')
|
|
42
42
|
.option('--env <name>', 'target environment')
|
|
43
|
+
.option('-f, --file <path>', 'read variables from a .env file')
|
|
43
44
|
.action(set);
|
|
44
45
|
|
|
45
46
|
program
|
package/package.json
CHANGED
package/src/commands/set.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
1
3
|
import { requireProjectRoot, loadKey } from '../keystore.js';
|
|
2
4
|
import { resolveEnv } from '../config.js';
|
|
3
5
|
import { readEncEnv, writeEncEnv } from '../enc.js';
|
|
6
|
+
import { readEnvFile } from '../envfile.js';
|
|
4
7
|
import { getCurrentEnv } from '../state.js';
|
|
5
8
|
import { ok, fatal, label } from '../ui.js';
|
|
6
9
|
|
|
@@ -11,15 +14,31 @@ export async function set(assignments, options) {
|
|
|
11
14
|
|
|
12
15
|
const vars = readEncEnv(projectRoot, envName, key);
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
fatal(`
|
|
17
|
+
if (options.file) {
|
|
18
|
+
const filePath = join(projectRoot, options.file);
|
|
19
|
+
if (!existsSync(filePath)) {
|
|
20
|
+
fatal(`File not found: ${options.file}`);
|
|
21
|
+
}
|
|
22
|
+
const fileVars = readEnvFile(filePath);
|
|
23
|
+
const entries = Object.entries(fileVars);
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
fatal(`No variables found in ${options.file}`);
|
|
26
|
+
}
|
|
27
|
+
for (const [k, v] of entries) {
|
|
28
|
+
vars[k] = v;
|
|
29
|
+
ok(`Set ${k} in ${label(envName)}`);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
for (const assignment of assignments) {
|
|
33
|
+
const eqIdx = assignment.indexOf('=');
|
|
34
|
+
if (eqIdx === -1) {
|
|
35
|
+
fatal(`Invalid assignment '${assignment}'. Expected KEY=VALUE format.`);
|
|
36
|
+
}
|
|
37
|
+
const k = assignment.slice(0, eqIdx).trim();
|
|
38
|
+
const v = assignment.slice(eqIdx + 1);
|
|
39
|
+
vars[k] = v;
|
|
40
|
+
ok(`Set ${k} in ${label(envName)}`);
|
|
18
41
|
}
|
|
19
|
-
const k = assignment.slice(0, eqIdx).trim();
|
|
20
|
-
const v = assignment.slice(eqIdx + 1);
|
|
21
|
-
vars[k] = v;
|
|
22
|
-
ok(`Set ${k} in ${label(envName)}`);
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
writeEncEnv(projectRoot, envName, key, vars);
|
package/src/envfile.js
CHANGED
|
@@ -1,5 +1,338 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
2
|
|
|
3
|
+
// ─── Section label mappings (prefix → section name) ──────────────────────────
|
|
4
|
+
const SECTION_LABELS = {
|
|
5
|
+
// ── App / Runtime ──────────────────────────────────────────────────────────
|
|
6
|
+
NODE: 'App / Runtime',
|
|
7
|
+
APP: 'App / Config',
|
|
8
|
+
SERVER: 'App / Server',
|
|
9
|
+
SERVICE: 'App / Service',
|
|
10
|
+
WORKER: 'App / Worker',
|
|
11
|
+
|
|
12
|
+
// ── Frontend Frameworks ────────────────────────────────────────────────────
|
|
13
|
+
NEXT: 'Next.js',
|
|
14
|
+
NEXT_PUBLIC: 'Next.js / Public (client-side)',
|
|
15
|
+
NUXT: 'Nuxt',
|
|
16
|
+
NUXT_PUBLIC: 'Nuxt / Public (client-side)',
|
|
17
|
+
VITE: 'Vite',
|
|
18
|
+
VITE_APP: 'Vite / Public (client-side)',
|
|
19
|
+
REACT: 'React',
|
|
20
|
+
REACT_APP: 'React / Public (client-side)',
|
|
21
|
+
GATSBY: 'Gatsby',
|
|
22
|
+
SVELTE: 'SvelteKit',
|
|
23
|
+
PUBLIC: 'Public (client-side)',
|
|
24
|
+
|
|
25
|
+
// ── Databases ──────────────────────────────────────────────────────────────
|
|
26
|
+
DB: 'Database',
|
|
27
|
+
DATABASE: 'Database',
|
|
28
|
+
POSTGRES: 'Database / PostgreSQL',
|
|
29
|
+
POSTGRESQL: 'Database / PostgreSQL',
|
|
30
|
+
PG: 'Database / PostgreSQL',
|
|
31
|
+
PGHOST: 'Database / PostgreSQL',
|
|
32
|
+
PGPORT: 'Database / PostgreSQL',
|
|
33
|
+
PGUSER: 'Database / PostgreSQL',
|
|
34
|
+
PGPASSWORD: 'Database / PostgreSQL',
|
|
35
|
+
PGDATABASE: 'Database / PostgreSQL',
|
|
36
|
+
MYSQL: 'Database / MySQL',
|
|
37
|
+
MARIADB: 'Database / MariaDB',
|
|
38
|
+
MONGO: 'Database / MongoDB',
|
|
39
|
+
MONGODB: 'Database / MongoDB',
|
|
40
|
+
SQLITE: 'Database / SQLite',
|
|
41
|
+
COCKROACH: 'Database / CockroachDB',
|
|
42
|
+
COCKROACHDB: 'Database / CockroachDB',
|
|
43
|
+
NEON: 'Database / Neon',
|
|
44
|
+
PLANETSCALE: 'Database / PlanetScale',
|
|
45
|
+
TURSO: 'Database / Turso',
|
|
46
|
+
SUPABASE: 'Supabase',
|
|
47
|
+
XATA: 'Database / Xata',
|
|
48
|
+
CONVEX: 'Database / Convex',
|
|
49
|
+
|
|
50
|
+
// ── Cache / Queues ─────────────────────────────────────────────────────────
|
|
51
|
+
REDIS: 'Cache / Redis',
|
|
52
|
+
UPSTASH: 'Cache / Upstash Redis',
|
|
53
|
+
MEMCACHED: 'Cache / Memcached',
|
|
54
|
+
KAFKA: 'Queue / Kafka',
|
|
55
|
+
RABBITMQ: 'Queue / RabbitMQ',
|
|
56
|
+
SQS: 'Queue / AWS SQS',
|
|
57
|
+
BULL: 'Queue / Bull',
|
|
58
|
+
INNGEST: 'Queue / Inngest',
|
|
59
|
+
TRIGGER: 'Queue / Trigger.dev',
|
|
60
|
+
QSTASH: 'Queue / QStash',
|
|
61
|
+
|
|
62
|
+
// ── Auth ───────────────────────────────────────────────────────────────────
|
|
63
|
+
AUTH: 'Auth',
|
|
64
|
+
AUTH0: 'Auth / Auth0',
|
|
65
|
+
CLERK: 'Auth / Clerk',
|
|
66
|
+
NEXTAUTH: 'Auth / NextAuth',
|
|
67
|
+
NEXT_AUTH: 'Auth / NextAuth',
|
|
68
|
+
LUCIA: 'Auth / Lucia',
|
|
69
|
+
BETTER_AUTH: 'Auth / Better Auth',
|
|
70
|
+
SUPABASE_AUTH: 'Auth / Supabase Auth',
|
|
71
|
+
FIREBASE_AUTH: 'Auth / Firebase Auth',
|
|
72
|
+
COGNITO: 'Auth / AWS Cognito',
|
|
73
|
+
OKTA: 'Auth / Okta',
|
|
74
|
+
WORKOS: 'Auth / WorkOS',
|
|
75
|
+
PROPELAUTH: 'Auth / PropelAuth',
|
|
76
|
+
STYTCH: 'Auth / Stytch',
|
|
77
|
+
MAGIC: 'Auth / Magic',
|
|
78
|
+
OAUTH: 'Auth / OAuth',
|
|
79
|
+
JWT: 'Auth / JWT',
|
|
80
|
+
SESSION: 'Auth / Session',
|
|
81
|
+
COOKIE: 'Auth / Cookie',
|
|
82
|
+
CSRF: 'Auth / CSRF',
|
|
83
|
+
TOTP: 'Auth / 2FA',
|
|
84
|
+
MFA: 'Auth / 2FA',
|
|
85
|
+
|
|
86
|
+
// ── AWS ────────────────────────────────────────────────────────────────────
|
|
87
|
+
AWS: 'AWS',
|
|
88
|
+
S3: 'AWS / S3',
|
|
89
|
+
EC2: 'AWS / EC2',
|
|
90
|
+
ECS: 'AWS / ECS',
|
|
91
|
+
EKS: 'AWS / EKS',
|
|
92
|
+
LAMBDA: 'AWS / Lambda',
|
|
93
|
+
CLOUDFRONT: 'AWS / CloudFront',
|
|
94
|
+
CLOUDWATCH: 'AWS / CloudWatch',
|
|
95
|
+
COGNITO: 'AWS / Cognito',
|
|
96
|
+
DYNAMODB: 'AWS / DynamoDB',
|
|
97
|
+
SES: 'AWS / SES',
|
|
98
|
+
SNS: 'AWS / SNS',
|
|
99
|
+
SQS: 'AWS / SQS',
|
|
100
|
+
ROUTE53: 'AWS / Route 53',
|
|
101
|
+
ECR: 'AWS / ECR',
|
|
102
|
+
SECRETS: 'AWS / Secrets Manager',
|
|
103
|
+
|
|
104
|
+
// ── Google Cloud ───────────────────────────────────────────────────────────
|
|
105
|
+
GCP: 'Google Cloud',
|
|
106
|
+
GOOGLE: 'Google',
|
|
107
|
+
GOOGLE_CLOUD: 'Google Cloud',
|
|
108
|
+
FIREBASE: 'Firebase',
|
|
109
|
+
FIRESTORE: 'Firebase / Firestore',
|
|
110
|
+
GCS: 'Google Cloud / Storage',
|
|
111
|
+
BIGQUERY: 'Google Cloud / BigQuery',
|
|
112
|
+
PUBSUB: 'Google Cloud / Pub/Sub',
|
|
113
|
+
GCLOUD: 'Google Cloud',
|
|
114
|
+
|
|
115
|
+
// ── Azure ──────────────────────────────────────────────────────────────────
|
|
116
|
+
AZURE: 'Azure',
|
|
117
|
+
AZURE_AD: 'Azure / Active Directory',
|
|
118
|
+
COSMOS: 'Azure / Cosmos DB',
|
|
119
|
+
COSMOSDB: 'Azure / Cosmos DB',
|
|
120
|
+
BLOB: 'Azure / Blob Storage',
|
|
121
|
+
|
|
122
|
+
// ── AI / LLMs ──────────────────────────────────────────────────────────────
|
|
123
|
+
OPENAI: 'AI / OpenAI',
|
|
124
|
+
ANTHROPIC: 'AI / Anthropic',
|
|
125
|
+
GEMINI: 'AI / Google Gemini',
|
|
126
|
+
COHERE: 'AI / Cohere',
|
|
127
|
+
REPLICATE: 'AI / Replicate',
|
|
128
|
+
HUGGINGFACE: 'AI / HuggingFace',
|
|
129
|
+
HF: 'AI / HuggingFace',
|
|
130
|
+
TOGETHER: 'AI / Together AI',
|
|
131
|
+
GROQ: 'AI / Groq',
|
|
132
|
+
MISTRAL: 'AI / Mistral',
|
|
133
|
+
PERPLEXITY: 'AI / Perplexity',
|
|
134
|
+
FIREWORKS: 'AI / Fireworks AI',
|
|
135
|
+
ANYSCALE: 'AI / Anyscale',
|
|
136
|
+
AI21: 'AI / AI21 Labs',
|
|
137
|
+
STABILITY: 'AI / Stability AI',
|
|
138
|
+
DEEPINFRA: 'AI / DeepInfra',
|
|
139
|
+
ELEVENLABS: 'AI / ElevenLabs',
|
|
140
|
+
ASSEMBLYAI: 'AI / AssemblyAI',
|
|
141
|
+
DEEPGRAM: 'AI / Deepgram',
|
|
142
|
+
|
|
143
|
+
// ── Vector DBs ─────────────────────────────────────────────────────────────
|
|
144
|
+
PINECONE: 'Vector DB / Pinecone',
|
|
145
|
+
WEAVIATE: 'Vector DB / Weaviate',
|
|
146
|
+
QDRANT: 'Vector DB / Qdrant',
|
|
147
|
+
CHROMA: 'Vector DB / Chroma',
|
|
148
|
+
MILVUS: 'Vector DB / Milvus',
|
|
149
|
+
|
|
150
|
+
// ── Payments ───────────────────────────────────────────────────────────────
|
|
151
|
+
STRIPE: 'Payments / Stripe',
|
|
152
|
+
PAYPAL: 'Payments / PayPal',
|
|
153
|
+
BRAINTREE: 'Payments / Braintree',
|
|
154
|
+
SQUARE: 'Payments / Square',
|
|
155
|
+
LEMON: 'Payments / Lemon Squeezy',
|
|
156
|
+
LEMONSQUEEZY: 'Payments / Lemon Squeezy',
|
|
157
|
+
PADDLE: 'Payments / Paddle',
|
|
158
|
+
COINBASE: 'Payments / Coinbase Commerce',
|
|
159
|
+
RAZORPAY: 'Payments / Razorpay',
|
|
160
|
+
|
|
161
|
+
// ── Email ──────────────────────────────────────────────────────────────────
|
|
162
|
+
SMTP: 'Email / SMTP',
|
|
163
|
+
MAIL: 'Email',
|
|
164
|
+
EMAIL: 'Email',
|
|
165
|
+
SENDGRID: 'Email / SendGrid',
|
|
166
|
+
RESEND: 'Email / Resend',
|
|
167
|
+
MAILGUN: 'Email / Mailgun',
|
|
168
|
+
POSTMARK: 'Email / Postmark',
|
|
169
|
+
MAILCHIMP: 'Email / Mailchimp',
|
|
170
|
+
MANDRILL: 'Email / Mandrill',
|
|
171
|
+
SES: 'Email / AWS SES',
|
|
172
|
+
SPARKPOST: 'Email / SparkPost',
|
|
173
|
+
CONVERTKIT: 'Email / ConvertKit',
|
|
174
|
+
LOOPS: 'Email / Loops',
|
|
175
|
+
|
|
176
|
+
// ── SMS / Communications ───────────────────────────────────────────────────
|
|
177
|
+
TWILIO: 'SMS / Twilio',
|
|
178
|
+
VONAGE: 'SMS / Vonage',
|
|
179
|
+
MESSAGEBIRD: 'SMS / MessageBird',
|
|
180
|
+
TELNYX: 'SMS / Telnyx',
|
|
181
|
+
SINCH: 'SMS / Sinch',
|
|
182
|
+
BANDWIDTH: 'SMS / Bandwidth',
|
|
183
|
+
|
|
184
|
+
// ── Search ─────────────────────────────────────────────────────────────────
|
|
185
|
+
ALGOLIA: 'Search / Algolia',
|
|
186
|
+
MEILISEARCH: 'Search / Meilisearch',
|
|
187
|
+
TYPESENSE: 'Search / Typesense',
|
|
188
|
+
ELASTICSEARCH: 'Search / Elasticsearch',
|
|
189
|
+
OPENSEARCH: 'Search / OpenSearch',
|
|
190
|
+
|
|
191
|
+
// ── Storage / CDN ──────────────────────────────────────────────────────────
|
|
192
|
+
CLOUDINARY: 'Storage / Cloudinary',
|
|
193
|
+
IMAGEKIT: 'Storage / ImageKit',
|
|
194
|
+
UPLOADTHING: 'Storage / UploadThing',
|
|
195
|
+
BUNNY: 'Storage / Bunny CDN',
|
|
196
|
+
BACKBLAZE: 'Storage / Backblaze B2',
|
|
197
|
+
R2: 'Storage / Cloudflare R2',
|
|
198
|
+
TIGRIS: 'Storage / Tigris',
|
|
199
|
+
|
|
200
|
+
// ── Observability ──────────────────────────────────────────────────────────
|
|
201
|
+
SENTRY: 'Observability / Sentry',
|
|
202
|
+
DATADOG: 'Observability / Datadog',
|
|
203
|
+
NEWRELIC: 'Observability / New Relic',
|
|
204
|
+
NEW_RELIC: 'Observability / New Relic',
|
|
205
|
+
GRAFANA: 'Observability / Grafana',
|
|
206
|
+
PROMETHEUS: 'Observability / Prometheus',
|
|
207
|
+
LOGTAIL: 'Observability / Logtail',
|
|
208
|
+
AXIOM: 'Observability / Axiom',
|
|
209
|
+
BETTERSTACK: 'Observability / Better Stack',
|
|
210
|
+
LOGFLARE: 'Observability / Logflare',
|
|
211
|
+
HIGHLIGHT: 'Observability / Highlight',
|
|
212
|
+
BASELIME: 'Observability / Baselime',
|
|
213
|
+
|
|
214
|
+
// ── Analytics ──────────────────────────────────────────────────────────────
|
|
215
|
+
POSTHOG: 'Analytics / PostHog',
|
|
216
|
+
SEGMENT: 'Analytics / Segment',
|
|
217
|
+
MIXPANEL: 'Analytics / Mixpanel',
|
|
218
|
+
AMPLITUDE: 'Analytics / Amplitude',
|
|
219
|
+
HEAP: 'Analytics / Heap',
|
|
220
|
+
HOTJAR: 'Analytics / Hotjar',
|
|
221
|
+
GA: 'Analytics / Google Analytics',
|
|
222
|
+
GOOGLE_ANALYTICS: 'Analytics / Google Analytics',
|
|
223
|
+
PLAUSIBLE: 'Analytics / Plausible',
|
|
224
|
+
FATHOM: 'Analytics / Fathom',
|
|
225
|
+
PIRSCH: 'Analytics / Pirsch',
|
|
226
|
+
UMAMI: 'Analytics / Umami',
|
|
227
|
+
JUNE: 'Analytics / June',
|
|
228
|
+
OPENPANEL: 'Analytics / OpenPanel',
|
|
229
|
+
|
|
230
|
+
// ── Feature Flags ──────────────────────────────────────────────────────────
|
|
231
|
+
LAUNCHDARKLY: 'Feature Flags / LaunchDarkly',
|
|
232
|
+
GROWTHBOOK: 'Feature Flags / GrowthBook',
|
|
233
|
+
FLAGSMITH: 'Feature Flags / Flagsmith',
|
|
234
|
+
STATSIG: 'Feature Flags / Statsig',
|
|
235
|
+
UNLEASH: 'Feature Flags / Unleash',
|
|
236
|
+
HYPERTUNE: 'Feature Flags / Hypertune',
|
|
237
|
+
|
|
238
|
+
// ── CMS ────────────────────────────────────────────────────────────────────
|
|
239
|
+
CONTENTFUL: 'CMS / Contentful',
|
|
240
|
+
SANITY: 'CMS / Sanity',
|
|
241
|
+
STRAPI: 'CMS / Strapi',
|
|
242
|
+
DIRECTUS: 'CMS / Directus',
|
|
243
|
+
PAYLOAD: 'CMS / Payload',
|
|
244
|
+
PRISMIC: 'CMS / Prismic',
|
|
245
|
+
GHOST: 'CMS / Ghost',
|
|
246
|
+
STORYBLOK: 'CMS / Storyblok',
|
|
247
|
+
BUILDER: 'CMS / Builder.io',
|
|
248
|
+
|
|
249
|
+
// ── Hosting / Deploy ───────────────────────────────────────────────────────
|
|
250
|
+
VERCEL: 'Hosting / Vercel',
|
|
251
|
+
NETLIFY: 'Hosting / Netlify',
|
|
252
|
+
RAILWAY: 'Hosting / Railway',
|
|
253
|
+
RENDER: 'Hosting / Render',
|
|
254
|
+
FLY: 'Hosting / Fly.io',
|
|
255
|
+
COOLIFY: 'Hosting / Coolify',
|
|
256
|
+
DOKKU: 'Hosting / Dokku',
|
|
257
|
+
HEROKU: 'Hosting / Heroku',
|
|
258
|
+
DENO: 'Hosting / Deno Deploy',
|
|
259
|
+
CLOUDFLARE: 'Hosting / Cloudflare',
|
|
260
|
+
|
|
261
|
+
// ── Social / OAuth Providers ───────────────────────────────────────────────
|
|
262
|
+
GITHUB: 'OAuth / GitHub',
|
|
263
|
+
GITLAB: 'OAuth / GitLab',
|
|
264
|
+
TWITTER: 'OAuth / Twitter / X',
|
|
265
|
+
TWITTER_X: 'OAuth / Twitter / X',
|
|
266
|
+
X: 'OAuth / Twitter / X',
|
|
267
|
+
FACEBOOK: 'OAuth / Facebook',
|
|
268
|
+
INSTAGRAM: 'OAuth / Instagram',
|
|
269
|
+
LINKEDIN: 'OAuth / LinkedIn',
|
|
270
|
+
DISCORD: 'OAuth / Discord',
|
|
271
|
+
SPOTIFY: 'OAuth / Spotify',
|
|
272
|
+
APPLE: 'OAuth / Apple',
|
|
273
|
+
MICROSOFT: 'OAuth / Microsoft',
|
|
274
|
+
|
|
275
|
+
// ── Collaboration / Productivity ───────────────────────────────────────────
|
|
276
|
+
SLACK: 'Integrations / Slack',
|
|
277
|
+
NOTION: 'Integrations / Notion',
|
|
278
|
+
LINEAR: 'Integrations / Linear',
|
|
279
|
+
JIRA: 'Integrations / Jira',
|
|
280
|
+
AIRTABLE: 'Integrations / Airtable',
|
|
281
|
+
ZAPIER: 'Integrations / Zapier',
|
|
282
|
+
MAKE: 'Integrations / Make',
|
|
283
|
+
|
|
284
|
+
// ── Maps / Location ────────────────────────────────────────────────────────
|
|
285
|
+
MAPBOX: 'Maps / Mapbox',
|
|
286
|
+
GOOGLE_MAPS: 'Maps / Google Maps',
|
|
287
|
+
MAPS: 'Maps / Google Maps',
|
|
288
|
+
HERE: 'Maps / HERE',
|
|
289
|
+
IPINFO: 'Maps / IPinfo',
|
|
290
|
+
MAXMIND: 'Maps / MaxMind',
|
|
291
|
+
|
|
292
|
+
// ── Crypto / Web3 ──────────────────────────────────────────────────────────
|
|
293
|
+
ALCHEMY: 'Web3 / Alchemy',
|
|
294
|
+
INFURA: 'Web3 / Infura',
|
|
295
|
+
MORALIS: 'Web3 / Moralis',
|
|
296
|
+
THIRDWEB: 'Web3 / Thirdweb',
|
|
297
|
+
WALLET: 'Web3 / Wallet',
|
|
298
|
+
|
|
299
|
+
// ── Testing / Dev Tools ────────────────────────────────────────────────────
|
|
300
|
+
PLAYWRIGHT: 'Testing / Playwright',
|
|
301
|
+
CYPRESS: 'Testing / Cypress',
|
|
302
|
+
BROWSERSTACK: 'Testing / BrowserStack',
|
|
303
|
+
SAUCE: 'Testing / Sauce Labs',
|
|
304
|
+
STORYBOOK: 'Dev / Storybook',
|
|
305
|
+
CHROMATIC: 'Dev / Chromatic',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const STANDALONE_LABELS = {
|
|
309
|
+
PORT: 'App / Config',
|
|
310
|
+
HOST: 'App / Config',
|
|
311
|
+
NODE_ENV: 'App / Config',
|
|
312
|
+
APP_ENV: 'App / Config',
|
|
313
|
+
ENVIRONMENT: 'App / Config',
|
|
314
|
+
BASE_URL: 'App / Config',
|
|
315
|
+
API_URL: 'App / Config',
|
|
316
|
+
SITE_URL: 'App / Config',
|
|
317
|
+
FRONTEND_URL: 'App / Config',
|
|
318
|
+
BACKEND_URL: 'App / Config',
|
|
319
|
+
PUBLIC_URL: 'App / Config',
|
|
320
|
+
LOG_LEVEL: 'App / Config',
|
|
321
|
+
DEBUG: 'App / Config',
|
|
322
|
+
TZ: 'App / Config',
|
|
323
|
+
TIMEZONE: 'App / Config',
|
|
324
|
+
LOCALE: 'App / Config',
|
|
325
|
+
LANG: 'App / Config',
|
|
326
|
+
SECRET: 'Secrets',
|
|
327
|
+
SECRET_KEY: 'Secrets',
|
|
328
|
+
ENCRYPTION_KEY: 'Secrets',
|
|
329
|
+
API_KEY: 'API Keys',
|
|
330
|
+
API_SECRET: 'API Keys',
|
|
331
|
+
ACCESS_TOKEN: 'API Keys',
|
|
332
|
+
PRIVATE_KEY: 'Secrets',
|
|
333
|
+
PUBLIC_KEY: 'Secrets',
|
|
334
|
+
};
|
|
335
|
+
|
|
3
336
|
export function parseEnv(content) {
|
|
4
337
|
const vars = {};
|
|
5
338
|
for (const line of content.split('\n')) {
|
|
@@ -9,7 +342,6 @@ export function parseEnv(content) {
|
|
|
9
342
|
if (eqIdx === -1) continue;
|
|
10
343
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
11
344
|
let value = trimmed.slice(eqIdx + 1).trim();
|
|
12
|
-
// Strip surrounding quotes
|
|
13
345
|
if (
|
|
14
346
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
15
347
|
(value.startsWith("'") && value.endsWith("'"))
|
|
@@ -21,6 +353,7 @@ export function parseEnv(content) {
|
|
|
21
353
|
return vars;
|
|
22
354
|
}
|
|
23
355
|
|
|
356
|
+
// Used internally for encryption — keeps insertion order, no formatting
|
|
24
357
|
export function stringifyEnv(vars) {
|
|
25
358
|
const lines = Object.entries(vars).map(([k, v]) => {
|
|
26
359
|
const needsQuotes = /[\s"'\\#]/.test(v) || v === '';
|
|
@@ -30,19 +363,95 @@ export function stringifyEnv(vars) {
|
|
|
30
363
|
return lines.join('\n') + (lines.length ? '\n' : '');
|
|
31
364
|
}
|
|
32
365
|
|
|
366
|
+
function formatValue(v) {
|
|
367
|
+
const needsQuotes = /[\s"'\\#]/.test(v) || v === '';
|
|
368
|
+
const escaped = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
369
|
+
return needsQuotes ? `"${escaped}"` : v;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function getSectionForKey(key) {
|
|
373
|
+
if (STANDALONE_LABELS[key]) return STANDALONE_LABELS[key];
|
|
374
|
+
|
|
375
|
+
// Try progressively shorter prefixes — longest match wins
|
|
376
|
+
const parts = key.split('_');
|
|
377
|
+
for (let len = parts.length - 1; len >= 1; len--) {
|
|
378
|
+
const prefix = parts.slice(0, len).join('_');
|
|
379
|
+
if (SECTION_LABELS[prefix]) return SECTION_LABELS[prefix];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return 'General';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function groupAndSort(vars) {
|
|
386
|
+
const sections = {};
|
|
387
|
+
|
|
388
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
389
|
+
const section = getSectionForKey(key);
|
|
390
|
+
if (!sections[section]) sections[section] = [];
|
|
391
|
+
sections[section].push([key, value]);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const section of Object.keys(sections)) {
|
|
395
|
+
sections[section].sort(([a], [b]) => a.localeCompare(b));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// App/Config first, Secrets near top, General at bottom
|
|
399
|
+
const priority = {
|
|
400
|
+
'App / Config': 0,
|
|
401
|
+
'App / Runtime': 1,
|
|
402
|
+
'App / Server': 2,
|
|
403
|
+
'Secrets': 3,
|
|
404
|
+
'API Keys': 4,
|
|
405
|
+
'General': 999,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return Object.entries(sections).sort(([a], [b]) => {
|
|
409
|
+
const pa = priority[a] ?? 50;
|
|
410
|
+
const pb = priority[b] ?? 50;
|
|
411
|
+
if (pa !== pb) return pa - pb;
|
|
412
|
+
return a.localeCompare(b);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
33
416
|
export function readEnvFile(filePath) {
|
|
34
417
|
if (!existsSync(filePath)) return {};
|
|
35
418
|
return parseEnv(readFileSync(filePath, 'utf8'));
|
|
36
419
|
}
|
|
37
420
|
|
|
38
421
|
export function writeEnvFile(filePath, vars, { envName, projectRoot } = {}) {
|
|
39
|
-
|
|
422
|
+
const entries = Object.entries(vars);
|
|
423
|
+
const projectName = projectRoot ? projectRoot.split('/').pop() : null;
|
|
424
|
+
const lines = [];
|
|
425
|
+
|
|
426
|
+
// Header
|
|
40
427
|
if (envName) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
428
|
+
const width = 50;
|
|
429
|
+
const bar = '─'.repeat(width);
|
|
430
|
+
lines.push(`# ${bar}`);
|
|
431
|
+
lines.push(`# envgit — encrypted environment manager`);
|
|
432
|
+
if (projectName) lines.push(`# Project : ${projectName}`);
|
|
433
|
+
lines.push(`# Env : ${envName}`);
|
|
434
|
+
lines.push(`# Edit : envgit set KEY=VALUE --env ${envName}`);
|
|
435
|
+
lines.push(`# ${bar}`);
|
|
436
|
+
lines.push('');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (entries.length === 0) {
|
|
440
|
+
writeFileSync(filePath, lines.join('\n'), 'utf8');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const grouped = groupAndSort(vars);
|
|
445
|
+
const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
|
|
446
|
+
|
|
447
|
+
for (const [section, sectionVars] of grouped) {
|
|
448
|
+
lines.push(`# ── ${section} ${'─'.repeat(Math.max(0, 44 - section.length))}`);
|
|
449
|
+
for (const [k, v] of sectionVars) {
|
|
450
|
+
const padding = ' '.repeat(maxKeyLen - k.length);
|
|
451
|
+
lines.push(`${k}${padding} = ${formatValue(v)}`);
|
|
452
|
+
}
|
|
453
|
+
lines.push('');
|
|
45
454
|
}
|
|
46
|
-
|
|
47
|
-
writeFileSync(filePath,
|
|
455
|
+
|
|
456
|
+
writeFileSync(filePath, lines.join('\n'), 'utf8');
|
|
48
457
|
}
|