Skip to content

Commit

Permalink
feat: add out-of-box support for .env files (#527)
Browse files Browse the repository at this point in the history
  • Loading branch information
wellwelwel authored Jul 9, 2024
1 parent e9ae81b commit 4ef3ecf
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ deno run npm:poku

### Common Options

- [**env**](https://poku.io/docs/documentation/helpers/env) _(process an environment file)_
- [**watch**](https://poku.io/docs/documentation/poku/options/watch) _(watch for changes and re-run related test files)_
- [**parallel**](https://poku.io/docs/documentation/poku/options/parallel) _(run tests in parallel)_
- [**debug**](https://poku.io/docs/documentation/poku/options/debug) _(shows all logs)_
Expand Down
17 changes: 17 additions & 0 deletions fixtures/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
HOST="123.123.123.123"
# USER0="user"
USER1='#user' # main user
USER2=support # TT1 # TT2
PASS1="nmnm@!sdf&*#@'RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH&%GN"
PASS2='nmnm@!sdf&*#@"RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH&%GN'
PASS3=nmnm@!sdf*@RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH%GN
PORT1=8080
PORT2=#8081
PORT3=${MY_PORT} # undefined variable
WHO_AM_I="I'm ${MY_VAR}"
SPACED1 = yes
SPACED2 = "yes"
SPACED3 = 'yes'
NO_VALUE1
NO_VALUE2=
NO_VALUE3 =
8 changes: 8 additions & 0 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fileResults } from '../configs/files.js';
import { platformIsValid } from '../parsers/get-runtime.js';
import { format } from '../services/format.js';
import { kill } from '../modules/helpers/kill.js';
import { envFile } from '../modules/helpers/env.js';
import { mapTests, normalizePath } from '../services/map-tests.js';
import { watch, type Watcher } from '../services/watch.js';
import { onSigint, poku } from '../modules/essentials/poku.js';
Expand Down Expand Up @@ -51,6 +52,7 @@ const quiet = hasArg('quiet');
const debug = hasArg('debug');
const failFast = hasArg('fail-fast');
const watchMode = hasArg('watch');
const hasEnvFile = hasArg('env-file');

const concurrency = parallel
? Number(getArg('concurrency')) || undefined
Expand Down Expand Up @@ -79,6 +81,12 @@ if (killPID) {
tasks.push(kill.pid(PIDs));
}

if (hasEnvFile) {
const envFilePath = getArg('env-file');

tasks.push(envFile(envFilePath));
}

const options: Configs = {
platform: platformIsValid(platform) ? platform : undefined,
filter: filter ? new RegExp(escapeRegExp(filter)) : undefined,
Expand Down
37 changes: 37 additions & 0 deletions src/modules/helpers/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* c8 ignore start */ // ?
import { readFile } from '../../polyfills/fs.js';
import { sanitizePath } from './list-files.js';
import {
parseEnvLine,
removeComments,
resolveEnvVariables,
} from '../../services/env.js';

const regex = {
comment: /^\s*#/,
};

/** Reads an environment file and sets the environment variables. */
export const envFile = async (filePath = '.env') => {
/* c8 ignore stop */
const mapEnv = new Map<string, string>();
const env = await readFile(sanitizePath(filePath), 'utf8');
const lines = env
.split('\n')
.map((line) => removeComments(line.trim()))
.filter((line) => line.length > 0 && !regex.comment.test(line));

for (const line of lines) {
const parsedLine = parseEnvLine(line);

if (parsedLine) {
const { arg, value } = parsedLine;

mapEnv.set(arg, value ? resolveEnvVariables(value, process.env) : value);
}
}

for (const [arg, value] of mapEnv) {
process.env[arg] = value;
}
};
1 change: 1 addition & 0 deletions src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { assert } from './essentials/assert.js';
export { test } from './helpers/test.js';
export { describe } from './helpers/describe.js';
export { it } from './helpers/it.js';
export { envFile } from './helpers/env.js';
export { skip } from './helpers/skip.js';
export { beforeEach, afterEach } from './helpers/each.js';
export { docker } from './helpers/container.js';
Expand Down
72 changes: 72 additions & 0 deletions src/services/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* c8 ignore next */ // ?
export const removeComments = (input: string) => {
let output = '';
let quoteChar = '';
let inQuote = false;

for (let i = 0; i < input.length; i++) {
const char = input[i];

if (inQuote) {
output += char;

if (char === quoteChar && input[i - 1] !== '\\') {
inQuote = false;
}
} else if (char === '"' || char === "'") {
inQuote = true;
quoteChar = char;
output += char;
} else if (char === '#') {
break;
} else {
output += char;
}
}

return output.trim();
};

/* c8 ignore net */ // ?
export const parseEnvLine = (line: string) => {
const index = line.indexOf('=');

if (index === -1) {
return null;
}

const arg = line.substring(0, index).trim();
const value = line
.substring(index + 1)
.trim()
.replace(/^['"]|['"]$/g, '');

return { arg, value };
};

/* c8 ignore next */ // ?
export const resolveEnvVariables = (str: string, env: typeof process.env) => {
let result = '';
let i = 0;

while (i < str.length) {
if (str[i] === '$' && str[i + 1] === '{') {
i += 2;

let varName = '';

while (i < str.length && str[i] !== '}') {
varName += str[i];
i++;
}

i++;
result += env[varName] || '';
} else {
result += str[i];
i++;
}
}

return result;
};
20 changes: 20 additions & 0 deletions test/c8.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { env } from 'node:process';
import { poku, test, describe, it, assert } from '../src/modules/index.js';
import { isWindows } from '../src/parsers/get-runner.js';
import { inspectCLI } from './helpers/capture-cli.test.js';
Expand Down Expand Up @@ -65,6 +66,25 @@ test(async () => {

assert.strictEqual(results.exitCode, 0, 'Passed');
});

await it('Parallel + Options (CLI Env Variables Propagation)', async () => {
const results = await inspectCLI(
isWindows
? 'npx tsx src/bin/index.ts --include=test/integration/env --env-file="fixtures/.env.test"'
: 'npx tsx src/bin/index.ts --include=test/integration/env --env-file=fixtures/.env.test',
{
env: {
...env,
MY_VAR: 'Poku',
},
}
);

console.log(results.stdout);
console.log(results.stderr);

assert.strictEqual(results.exitCode, 0, 'Passed');
});
});

await describe('API', async () => {
Expand Down
68 changes: 68 additions & 0 deletions test/integration/env/set-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import process from 'node:process';
import { test } from '../../../src/modules/helpers/test.js';
import { skip } from '../../../src/modules/helpers/skip.js';
import { assert } from '../../../src/modules/essentials/assert.js';

if (process.env.My_VAR !== 'Poku') {
skip("Skip tests when MY_VAR is not set to 'Poku'");
}

test('Defining Variables', () => {
assert.strictEqual(process.env.HOST, '123.123.123.123', 'Basic');
assert.strictEqual(process.env.USER0, undefined, 'Comented Line');
assert.strictEqual(process.env.USER1, '#user', 'Using quoted #');
assert.strictEqual(
process.env.USER2,
'support',
'Comments after the variable'
);
assert.strictEqual(
process.env.PASS1,
"nmnm@!sdf&*#@'RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH&%GN",
'Complex string double quoted'
);
assert.strictEqual(
process.env.PASS2,
'nmnm@!sdf&*#@"RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH&%GN',
'Complex single double quoted'
);
assert.strictEqual(
process.env.PASS3,
'nmnm@!sdf*@RBUY3efPZpsqufHdhgdfhU!Bi90q.Zm.b7.C-8fpOIUSH%GN',
'Complex string no quoted'
);
assert.strictEqual(process.env.PORT1, '8080', 'Using a number');
assert.strictEqual(
process.env.PORT2,
'',
'Valid env with full commented value'
);
assert.strictEqual(process.env.PORT3, '', 'Undefined local variable');
assert.strictEqual(
process.env.WHO_AM_I,
"I'm Poku",
'Resolved Env Variables'
);
assert.strictEqual(
process.env.SPACED1,
'yes',
'Using space between env and value'
);
assert.strictEqual(
process.env.SPACED2,
'yes',
'Using space between env and value with double quotes'
);
assert.strictEqual(
process.env.SPACED3,
'yes',
'Using space between env and value with single quotes'
);
assert.strictEqual(
process.env.NO_VALUE1,
undefined,
'Undefined value (invalid)'
);
assert.strictEqual(process.env.NO_VALUE2, '', 'No value (valid)');
assert.strictEqual(process.env.NO_VALUE3, '', 'No value (valid with spaces)');
});
1 change: 1 addition & 0 deletions test/integration/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ index.test('Import Suite', () => {
index.assert.ok(index.poku, 'Importing poku method');
index.assert.ok(index.assert, 'Importing assert method');

index.assert.ok(index.envFile, 'Importing envFile method');
index.assert.ok(index.assertPromise, 'Importing assertPromise method');
index.assert.ok(index.startService, 'Importing startService method');
index.assert.ok(index.startScript, 'Importing startScript method');
Expand Down
Loading

0 comments on commit 4ef3ecf

Please sign in to comment.