Skip to content

Commit 23597c3

Browse files
committed
add parsing env file
1 parent fe2a9c1 commit 23597c3

File tree

4 files changed

+128
-7
lines changed

4 files changed

+128
-7
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,22 @@ should not be re-used.
484484
}
485485
```
486486

487+
### Parsing the env file
488+
489+
```json
490+
{
491+
"wireit": {
492+
"my-script": {
493+
"command": "my-command",
494+
"env-file": [
495+
".env",
496+
".dev.env"
497+
]
498+
}
499+
}
500+
}
501+
```
502+
487503
## Services
488504

489505
By default, Wireit assumes that your scripts will eventually exit by themselves.

schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@
117117
}
118118
]
119119
}
120+
},
121+
"env-file": {
122+
"type": "array",
123+
"items": {
124+
"type": "string",
125+
"minLength": 1
126+
}
120127
}
121128
},
122129
"type": "object"

src/analyzer.ts

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
} from './config.js';
3131
import type {Agent} from './cli-options.js';
3232
import {Logger} from './logging/logger.js';
33+
import {readEnvFile} from './read-env-file.js';
3334

3435
export interface AnalyzeResult {
3536
config: Result<ScriptConfig, Failure[]>;
@@ -630,7 +631,12 @@ export class Analyzer {
630631
files,
631632
);
632633

633-
const env = this.#processEnv(placeholder, packageJson, syntaxInfo, command);
634+
const entries = this.#processEnv(placeholder, packageJson, syntaxInfo, command);
635+
await this.#processEnvFile(placeholder, packageJson, syntaxInfo, command, entries);
636+
637+
// Sort for better fingerprint match rate.
638+
entries.sort();
639+
const env = Object.fromEntries(entries);
634640

635641
if (placeholder.failures.length > 0) {
636642
// A script with locally-determined errors doesn't get upgraded to
@@ -1319,13 +1325,13 @@ export class Analyzer {
13191325
packageJson: PackageJson,
13201326
syntaxInfo: ScriptSyntaxInfo,
13211327
command: JsonAstNode<string> | undefined,
1322-
): Record<string, string> {
1328+
): Array<[string, string]> {
13231329
if (syntaxInfo.wireitConfigNode === undefined) {
1324-
return {};
1330+
return [];
13251331
}
13261332
const envNode = findNodeAtLocation(syntaxInfo.wireitConfigNode, ['env']);
13271333
if (envNode === undefined) {
1328-
return {};
1334+
return [];
13291335
}
13301336
if (command === undefined) {
13311337
placeholder.failures.push({
@@ -1358,7 +1364,7 @@ export class Analyzer {
13581364
});
13591365
}
13601366
if (envNode.children === undefined) {
1361-
return {};
1367+
return [];
13621368
}
13631369
const entries: Array<[string, string]> = [];
13641370
for (const propNode of envNode.children) {
@@ -1428,8 +1434,67 @@ export class Analyzer {
14281434
}
14291435
}
14301436
// Sort for better fingerprint match rate.
1431-
entries.sort();
1432-
return Object.fromEntries(entries);
1437+
return entries;
1438+
}
1439+
1440+
async #processEnvFile(
1441+
placeholder: UnvalidatedConfig,
1442+
packageJson: PackageJson,
1443+
syntaxInfo: ScriptSyntaxInfo,
1444+
command: JsonAstNode<string> | undefined,
1445+
entries: Array<[string, string]>
1446+
): Promise<void> {
1447+
if (syntaxInfo.wireitConfigNode === undefined) {
1448+
return;
1449+
}
1450+
const envFileNode = findNodeAtLocation(syntaxInfo.wireitConfigNode, ['env-file']);
1451+
if (envFileNode === undefined) {
1452+
return;
1453+
}
1454+
if (command === undefined) {
1455+
placeholder.failures.push({
1456+
type: 'failure',
1457+
reason: 'invalid-config-syntax',
1458+
script: placeholder,
1459+
diagnostic: {
1460+
severity: 'error',
1461+
message: 'Can\'t set "env-file" unless "command" is set',
1462+
location: {
1463+
file: packageJson.jsonFile,
1464+
range: {length: envFileNode.length, offset: envFileNode.offset},
1465+
},
1466+
},
1467+
});
1468+
}
1469+
if (envFileNode.type !== 'array') {
1470+
placeholder.failures.push({
1471+
type: 'failure',
1472+
reason: 'invalid-config-syntax',
1473+
script: placeholder,
1474+
diagnostic: {
1475+
severity: 'error',
1476+
message: 'Expected an array',
1477+
location: {
1478+
file: packageJson.jsonFile,
1479+
range: {length: envFileNode.length, offset: envFileNode.offset},
1480+
},
1481+
},
1482+
});
1483+
}
1484+
if (envFileNode.children === undefined) {
1485+
return;
1486+
}
1487+
const promiseEnvFiles: Array<Promise<void>> = [];
1488+
for (const propNode of envFileNode.children) {
1489+
if (propNode.type !== 'string') {
1490+
throw new Error(
1491+
'Internal error: expected array JSON node child to be string',
1492+
);
1493+
}
1494+
const envFile = propNode.value as string;
1495+
promiseEnvFiles.push(readEnvFile(envFile, entries));
1496+
}
1497+
await Promise.all(promiseEnvFiles);
14331498
}
14341499

14351500
/**

src/read-env-file.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {readFile, stat} from 'node:fs/promises';
8+
9+
// TODO string-template-parser
10+
11+
export async function readEnvFile(filepath: string, entries: Array<[string, string]>): Promise<void> {
12+
const fileStat = await stat(filepath);
13+
if (!fileStat.isFile()) {
14+
console.warn('Skipping non-file env file: ' + filepath);
15+
return;
16+
}
17+
const content = await readFile(filepath, 'utf8');
18+
const lines = content.split('\n');
19+
for (const line of lines) {
20+
const trimmed = line.trim();
21+
if (trimmed.startsWith('#')) {
22+
continue;
23+
}
24+
const [key, ...rest] = trimmed.split('=');
25+
if (rest.length === 0) {
26+
continue;
27+
}
28+
const value = rest.join('=');
29+
if (typeof key === 'string' && typeof value === 'string') {
30+
entries.push([key, value]);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)