From 93691d656d2c69d4c8fd081249f3c90c8ffc85f8 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 16:09:16 +0300 Subject: [PATCH 01/12] feat: handle multilines in env files continues #974 --- .size-limit.json | 4 ++-- src/util.ts | 43 ++++++++++++++++++++++++++++++++++--------- test/util.test.js | 26 +++++++++++++------------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index eedbac486c..d3aba336a1 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -9,7 +9,7 @@ { "name": "zx/index", "path": "build/*.{js,cjs}", - "limit": "805 kB", + "limit": "805.5 kB", "brotli": false, "gzip": false }, @@ -30,7 +30,7 @@ { "name": "all", "path": "build/*", - "limit": "842 kB", + "limit": "842.5 kB", "brotli": false, "gzip": false } diff --git a/src/util.ts b/src/util.ts index 9fb4fc211e..0000a32d56 100644 --- a/src/util.ts +++ b/src/util.ts @@ -358,15 +358,40 @@ export const toCamelCase = (str: string) => export const parseBool = (v: string): boolean | string => ({ true: true, false: false })[v] ?? v -export const parseDotenv = (content: string): NodeJS.ProcessEnv => - content.split(/\r?\n/).reduce((r, line) => { - if (line.startsWith('export ')) line = line.slice(7) - const i = line.indexOf('=') - const k = line.slice(0, i).trim() - const v = line.slice(i + 1).trim() - if (k && v) r[k] = v - return r - }, {}) +// prettier-ignore +export const parseDotenv = (content: string): NodeJS.ProcessEnv => { + const e: Record = {} + let k = '' + let c = '' + let q = '' + const cap = () => {if (c && k) { e[k] = c; c = ''; k = '' }} + + for (const s of content) { + if (s === ' ' && !q) { + if (!k && c === 'export') c = '' + continue + } + if (s === '=' && !q) { + if (!k) { k = c; c = ''; continue } + } + if (s === '\n' && !q) { + cap() + continue + } + if (s === '"' || s === "'") { + if (q === s) { + q = '' + cap() + continue + } + q = s + continue + } + c += s + } + + return e +} export const readEnvFromFile = ( filepath: string, diff --git a/test/util.test.js b/test/util.test.js index 6a98189c62..448242b025 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -143,24 +143,24 @@ describe('util', () => { }) test('parseDotenv()', () => { - assert.deepEqual( - parseDotenv('ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \nexport ENV4=v4'), - { - ENV: 'v1', - ENV2: 'v2', - ENV3: 'v3', - ENV4: 'v4', - } - ) - assert.deepEqual(parseDotenv(''), {}) - - // TBD: multiline const multiline = `SIMPLE=xyz123 NON_INTERPOLATED='raw text without variable interpolation' MULTILINE = """ long text here, e.g. a private SSH key -"""` +""" +ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \n export ENV4=v4 +` + + assert.deepEqual(parseDotenv(multiline), { + SIMPLE: 'xyz123', + NON_INTERPOLATED: 'raw text without variable interpolation', + MULTILINE: '\nlong text here,\ne.g. a private SSH key\n', + ENV: 'v1', + ENV2: 'v2', + ENV3: 'v3', + ENV4: 'v4', + }) }) describe('readEnvFromFile()', () => { From b0fda391217c324a57e339b2ca6f91d83ce8d843 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 16:25:23 +0300 Subject: [PATCH 02/12] fix: check donenv names --- src/util.ts | 7 +++++- test/cli.test.js | 2 +- test/util.test.js | 62 ++++++++++++++++++++++++++--------------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/util.ts b/src/util.ts index 0000a32d56..efb4ce0cc3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -361,10 +361,14 @@ export const parseBool = (v: string): boolean | string => // prettier-ignore export const parseDotenv = (content: string): NodeJS.ProcessEnv => { const e: Record = {} + const r = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/ let k = '' let c = '' let q = '' - const cap = () => {if (c && k) { e[k] = c; c = ''; k = '' }} + const cap = () => { if (c && k) { + if (!r.test(k)) throw new Error(`Invalid identifier: ${k}`) + e[k] = c; c = ''; k = '' + }} for (const s of content) { if (s === ' ' && !q) { @@ -389,6 +393,7 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { } c += s } + cap() return e } diff --git a/test/cli.test.js b/test/cli.test.js index ebaa02f93b..da35f16d20 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -150,7 +150,7 @@ describe('cli', () => { assert.ok(p.stderr.endsWith(cwd + '\n')) }) - test('supports `--env` options with file', async () => { + test('supports `--env` option', async () => { const env = tmpfile( '.env', `FOO=BAR diff --git a/test/util.test.js b/test/util.test.js index 448242b025..91dec29d11 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -140,10 +140,9 @@ describe('util', () => { assert.equal(toCamelCase('SOME_MORE_BIG_STR'), 'someMoreBigStr') assert.equal(toCamelCase('kebab-input-str'), 'kebabInputStr') }) -}) -test('parseDotenv()', () => { - const multiline = `SIMPLE=xyz123 + test('parseDotenv()', () => { + const multiline = `SIMPLE=xyz123 NON_INTERPOLATED='raw text without variable interpolation' MULTILINE = """ long text here, @@ -152,32 +151,39 @@ e.g. a private SSH key ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \n export ENV4=v4 ` - assert.deepEqual(parseDotenv(multiline), { - SIMPLE: 'xyz123', - NON_INTERPOLATED: 'raw text without variable interpolation', - MULTILINE: '\nlong text here,\ne.g. a private SSH key\n', - ENV: 'v1', - ENV2: 'v2', - ENV3: 'v3', - ENV4: 'v4', - }) -}) - -describe('readEnvFromFile()', () => { - test('handles correct proccess.env', () => { - const file = tempfile('.env', 'ENV=value1\nENV2=value24') - const env = readEnvFromFile(file) - assert.equal(env.ENV, 'value1') - assert.equal(env.ENV2, 'value24') - assert.ok(env.NODE_VERSION !== '') + assert.deepEqual(parseDotenv(multiline), { + SIMPLE: 'xyz123', + NON_INTERPOLATED: 'raw text without variable interpolation', + MULTILINE: '\nlong text here,\ne.g. a private SSH key\n', + ENV: 'v1', + ENV2: 'v2', + ENV3: 'v3', + ENV4: 'v4', + }) + + assert.deepEqual( + parseDotenv(`FOO=BAR + BAR=FOO+`), + { FOO: 'BAR', BAR: 'FOO+' } + ) }) - test('handles correct some env', () => { - const file = tempfile('.env', 'ENV=value1\nENV2=value24') - const env = readEnvFromFile(file, { version: '1.0.0', name: 'zx' }) - assert.equal(env.ENV, 'value1') - assert.equal(env.ENV2, 'value24') - assert.equal(env.version, '1.0.0') - assert.equal(env.name, 'zx') + describe('readEnvFromFile()', () => { + test('handles correct proccess.env', () => { + const file = tempfile('.env', 'ENV=value1\nENV2=value24') + const env = readEnvFromFile(file) + assert.equal(env.ENV, 'value1') + assert.equal(env.ENV2, 'value24') + assert.ok(env.NODE_VERSION !== '') + }) + + test('handles correct some env', () => { + const file = tempfile('.env', 'ENV=value1\nENV2=value24') + const env = readEnvFromFile(file, { version: '1.0.0', name: 'zx' }) + assert.equal(env.ENV, 'value1') + assert.equal(env.ENV2, 'value24') + assert.equal(env.version, '1.0.0') + assert.equal(env.name, 'zx') + }) }) }) From 1de7a33bf46cd64808b33b79204367f6abaae407 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 17:06:42 +0300 Subject: [PATCH 03/12] fix: handle dotenv comments --- src/util.ts | 29 +++++++++++++++++++++-------- test/util.test.js | 9 ++++++--- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/util.ts b/src/util.ts index efb4ce0cc3..77c1e1f3d1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -365,23 +365,36 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { let k = '' let c = '' let q = '' + let i = false const cap = () => { if (c && k) { if (!r.test(k)) throw new Error(`Invalid identifier: ${k}`) e[k] = c; c = ''; k = '' }} for (const s of content) { - if (s === ' ' && !q) { - if (!k && c === 'export') c = '' + if (i) { + if (s === '\n') i = false continue } - if (s === '=' && !q) { - if (!k) { k = c; c = ''; continue } - } - if (s === '\n' && !q) { - cap() - continue + if (!q) { + if (s === '#') { + i = true + continue + } + if (s === ' ') { + if (!k && c === 'export') c = '' + continue + } + if (s === '=') { + if (!k) { k = c; c = ''; continue } + } + if (s === '\n') { + i = false + cap() + continue + } } + if (s === '"' || s === "'") { if (q === s) { q = '' diff --git a/test/util.test.js b/test/util.test.js index 91dec29d11..839cceeaaa 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -143,22 +143,25 @@ describe('util', () => { test('parseDotenv()', () => { const multiline = `SIMPLE=xyz123 -NON_INTERPOLATED='raw text without variable interpolation' +# comment ### +NON_INTERPOLATED='raw text without variable interpolation' MULTILINE = """ -long text here, +long text here, # not-comment e.g. a private SSH key """ ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \n export ENV4=v4 +ENV5=v5 # comment ` assert.deepEqual(parseDotenv(multiline), { SIMPLE: 'xyz123', NON_INTERPOLATED: 'raw text without variable interpolation', - MULTILINE: '\nlong text here,\ne.g. a private SSH key\n', + MULTILINE: '\nlong text here, # not-comment\ne.g. a private SSH key\n', ENV: 'v1', ENV2: 'v2', ENV3: 'v3', ENV4: 'v4', + ENV5: 'v5', }) assert.deepEqual( From 17678faffc2f2dc987b7b9df5214f06e6cf94b59 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 17:34:45 +0300 Subject: [PATCH 04/12] fix: handle tabs in dotenvs --- src/util.ts | 41 +++++++++++++++++++++-------------------- test/util.test.js | 4 ++-- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/util.ts b/src/util.ts index 77c1e1f3d1..be8155446d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -361,50 +361,51 @@ export const parseBool = (v: string): boolean | string => // prettier-ignore export const parseDotenv = (content: string): NodeJS.ProcessEnv => { const e: Record = {} - const r = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/ + const kr = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/ + const sr = /\s/ let k = '' - let c = '' + let b = '' let q = '' let i = false - const cap = () => { if (c && k) { - if (!r.test(k)) throw new Error(`Invalid identifier: ${k}`) - e[k] = c; c = ''; k = '' + const cap = () => { if (b && k) { + if (!kr.test(k)) throw new Error(`Invalid identifier: ${k}`) + e[k] = b; b = ''; k = '' }} - for (const s of content) { + for (const c of content.replace(/\r\n?/mg, '\n')) { if (i) { - if (s === '\n') i = false + if (c === '\n') i = false continue } if (!q) { - if (s === '#') { + if (c === '#') { i = true continue } - if (s === ' ') { - if (!k && c === 'export') c = '' - continue - } - if (s === '=') { - if (!k) { k = c; c = ''; continue } - } - if (s === '\n') { + if (c === '\n') { i = false cap() continue } + if (sr.test(c)) { + if (!k && b === 'export') b = '' + continue + } + if (c === '=') { + if (!k) { k = b; b = ''; continue } + } } - if (s === '"' || s === "'") { - if (q === s) { + if (c === '"' || c === "'") { + if (q === c) { q = '' cap() continue } - q = s + q = c continue } - c += s + b += c } cap() diff --git a/test/util.test.js b/test/util.test.js index 839cceeaaa..790cc40c70 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -141,7 +141,7 @@ describe('util', () => { assert.equal(toCamelCase('kebab-input-str'), 'kebabInputStr') }) - test('parseDotenv()', () => { + test.only('parseDotenv()', () => { const multiline = `SIMPLE=xyz123 # comment ### NON_INTERPOLATED='raw text without variable interpolation' @@ -149,7 +149,7 @@ MULTILINE = """ long text here, # not-comment e.g. a private SSH key """ -ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \n export ENV4=v4 +ENV=v1\nENV2=v2\n\n\n\t\t ENV3 = v3 \n export ENV4=v4 ENV5=v5 # comment ` From ce32882db2da32444cbefe3dee90e222f0d572a9 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 17:39:37 +0300 Subject: [PATCH 05/12] fix: handle backtick in dotenv --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index be8155446d..1c6ff911cc 100644 --- a/src/util.ts +++ b/src/util.ts @@ -396,7 +396,7 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { } } - if (c === '"' || c === "'") { + if (c === '"' || c === "'" || c === '`') { if (q === c) { q = '' cap() From 6eb26f5b09020996ac8274e760e6f4ceaa6783ea Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 18:19:49 +0300 Subject: [PATCH 06/12] chore: parseDotenv tweak ups --- src/util.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/util.ts b/src/util.ts index 1c6ff911cc..f4e13ceee0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -366,7 +366,7 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { let k = '' let b = '' let q = '' - let i = false + let i = 0 const cap = () => { if (b && k) { if (!kr.test(k)) throw new Error(`Invalid identifier: ${k}`) e[k] = b; b = ''; k = '' @@ -374,16 +374,15 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { for (const c of content.replace(/\r\n?/mg, '\n')) { if (i) { - if (c === '\n') i = false + if (c === '\n') i = 0 continue } if (!q) { if (c === '#') { - i = true + i = 1 continue } if (c === '\n') { - i = false cap() continue } From 489595f24c254eec5361aee96abd6b930d3d15e8 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 18:50:06 +0300 Subject: [PATCH 07/12] chore: shrink a few bytes --- src/util.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util.ts b/src/util.ts index f4e13ceee0..fa562f5849 100644 --- a/src/util.ts +++ b/src/util.ts @@ -360,16 +360,16 @@ export const parseBool = (v: string): boolean | string => // prettier-ignore export const parseDotenv = (content: string): NodeJS.ProcessEnv => { - const e: Record = {} - const kr = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/ + const kr = /^[a-zA-Z_]+\w*$/ const sr = /\s/ + const e: Record = {} let k = '' let b = '' let q = '' let i = 0 const cap = () => { if (b && k) { if (!kr.test(k)) throw new Error(`Invalid identifier: ${k}`) - e[k] = b; b = ''; k = '' + e[k] = b; b = k = '' }} for (const c of content.replace(/\r\n?/mg, '\n')) { From 1852d43f6fcc98996f478793d87c0f50e7becc09 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 24 Dec 2024 23:25:00 +0300 Subject: [PATCH 08/12] chore: dotenv parse imprs --- src/util.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util.ts b/src/util.ts index fa562f5849..94a3a57f3c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -396,13 +396,15 @@ export const parseDotenv = (content: string): NodeJS.ProcessEnv => { } if (c === '"' || c === "'" || c === '`') { + if (!q) { + q = c + continue + } if (q === c) { q = '' cap() continue } - q = c - continue } b += c } From 8bcbcdef491f02a75454f5c99f1ab5b185fda3e1 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Wed, 25 Dec 2024 00:01:17 +0300 Subject: [PATCH 09/12] chore: move custom dotenv parser to external pkg --- .size-limit.json | 6 ++--- package-lock.json | 8 ++++++ package.json | 1 + scripts/build-dts.mjs | 1 + src/util.ts | 59 ++----------------------------------------- src/vendor-core.ts | 1 + test/util.test.js | 2 +- 7 files changed, 17 insertions(+), 61 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index d3aba336a1..092a3185ec 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -9,7 +9,7 @@ { "name": "zx/index", "path": "build/*.{js,cjs}", - "limit": "805.5 kB", + "limit": "806 kB", "brotli": false, "gzip": false }, @@ -23,14 +23,14 @@ { "name": "vendor", "path": "build/vendor-*", - "limit": "761 kB", + "limit": "762 kB", "brotli": false, "gzip": false }, { "name": "all", "path": "build/*", - "limit": "842.5 kB", + "limit": "843 kB", "brotli": false, "gzip": false } diff --git a/package-lock.json b/package-lock.json index b36bd58337..046a18e22c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "create-require": "^1.1.1", "depseek": "^0.4.1", "dts-bundle-generator": "^9.5.1", + "envapi": "^0.0.0", "esbuild": "^0.24.2", "esbuild-node-externals": "^1.16.0", "esbuild-plugin-entry-chunks": "^0.1.15", @@ -3165,6 +3166,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/envapi": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/envapi/-/envapi-0.0.0.tgz", + "integrity": "sha512-vO+00FBxMQMY1dvt1wSr34yFTGkCAlPGZfwGgX0dLdlxRG/YZLn7GKfxFaSw1SQkF3SJYygVIh3YLNWNvJfzYA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", diff --git a/package.json b/package.json index e0434226bf..4963192780 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "create-require": "^1.1.1", "depseek": "^0.4.1", "dts-bundle-generator": "^9.5.1", + "envapi": "^0.0.0", "esbuild": "^0.24.2", "esbuild-node-externals": "^1.16.0", "esbuild-plugin-entry-chunks": "^0.1.15", diff --git a/scripts/build-dts.mjs b/scripts/build-dts.mjs index 373f2fb258..f97a5eaa85 100644 --- a/scripts/build-dts.mjs +++ b/scripts/build-dts.mjs @@ -60,6 +60,7 @@ const entries = [ '@webpod/ingrid', 'chalk', 'zurk', + 'envapi' ], // args['external-inlines'], }, output, diff --git a/src/util.ts b/src/util.ts index 94a3a57f3c..456065dbe6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,10 +15,10 @@ import os from 'node:os' import path from 'node:path' import fs from 'node:fs' -import { chalk, type RequestInfo, type RequestInit } from './vendor-core.js' +import { chalk, type RequestInfo, type RequestInit, parseDotenv } from './vendor-core.js' import { inspect } from 'node:util' -export { isStringLiteral } from './vendor-core.js' +export { isStringLiteral, parseDotenv } from './vendor-core.js' export function tempdir(prefix: string = `zx-${randomId()}`): string { const dirpath = path.join(os.tmpdir(), prefix) @@ -358,61 +358,6 @@ export const toCamelCase = (str: string) => export const parseBool = (v: string): boolean | string => ({ true: true, false: false })[v] ?? v -// prettier-ignore -export const parseDotenv = (content: string): NodeJS.ProcessEnv => { - const kr = /^[a-zA-Z_]+\w*$/ - const sr = /\s/ - const e: Record = {} - let k = '' - let b = '' - let q = '' - let i = 0 - const cap = () => { if (b && k) { - if (!kr.test(k)) throw new Error(`Invalid identifier: ${k}`) - e[k] = b; b = k = '' - }} - - for (const c of content.replace(/\r\n?/mg, '\n')) { - if (i) { - if (c === '\n') i = 0 - continue - } - if (!q) { - if (c === '#') { - i = 1 - continue - } - if (c === '\n') { - cap() - continue - } - if (sr.test(c)) { - if (!k && b === 'export') b = '' - continue - } - if (c === '=') { - if (!k) { k = b; b = ''; continue } - } - } - - if (c === '"' || c === "'" || c === '`') { - if (!q) { - q = c - continue - } - if (q === c) { - q = '' - cap() - continue - } - } - b += c - } - cap() - - return e -} - export const readEnvFromFile = ( filepath: string, env: NodeJS.ProcessEnv = process.env diff --git a/src/vendor-core.ts b/src/vendor-core.ts index 19fbfcb1bb..d74e6708c7 100644 --- a/src/vendor-core.ts +++ b/src/vendor-core.ts @@ -26,3 +26,4 @@ export type RequestInit = Parameters[1] export { default as chalk, type ChalkInstance } from 'chalk' export { default as which } from 'which' export { default as ps } from '@webpod/ps' +export { parse as parseDotenv } from 'envapi' diff --git a/test/util.test.js b/test/util.test.js index 790cc40c70..01462f195f 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -156,7 +156,7 @@ ENV5=v5 # comment assert.deepEqual(parseDotenv(multiline), { SIMPLE: 'xyz123', NON_INTERPOLATED: 'raw text without variable interpolation', - MULTILINE: '\nlong text here, # not-comment\ne.g. a private SSH key\n', + MULTILINE: 'long text here, # not-comment\ne.g. a private SSH key', ENV: 'v1', ENV2: 'v2', ENV3: 'v3', From 64aa2bedea7e58b36e4be202550ee0f9750efeff Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Wed, 25 Dec 2024 00:48:47 +0300 Subject: [PATCH 10/12] chore: linting --- scripts/build-dts.mjs | 2 +- src/util.ts | 7 ++++++- src/vendor-core.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/build-dts.mjs b/scripts/build-dts.mjs index f97a5eaa85..b4b5e893ec 100644 --- a/scripts/build-dts.mjs +++ b/scripts/build-dts.mjs @@ -60,7 +60,7 @@ const entries = [ '@webpod/ingrid', 'chalk', 'zurk', - 'envapi' + 'envapi', ], // args['external-inlines'], }, output, diff --git a/src/util.ts b/src/util.ts index 456065dbe6..5ad6fac23d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,7 +15,12 @@ import os from 'node:os' import path from 'node:path' import fs from 'node:fs' -import { chalk, type RequestInfo, type RequestInit, parseDotenv } from './vendor-core.js' +import { + chalk, + type RequestInfo, + type RequestInit, + parseDotenv, +} from './vendor-core.js' import { inspect } from 'node:util' export { isStringLiteral, parseDotenv } from './vendor-core.js' diff --git a/src/vendor-core.ts b/src/vendor-core.ts index d74e6708c7..c039cde3c4 100644 --- a/src/vendor-core.ts +++ b/src/vendor-core.ts @@ -26,4 +26,4 @@ export type RequestInit = Parameters[1] export { default as chalk, type ChalkInstance } from 'chalk' export { default as which } from 'which' export { default as ps } from '@webpod/ps' -export { parse as parseDotenv } from 'envapi' +export { parse as parseDotenv } from 'envapi' From 94356b5a269fe41924447608e567df1413c6b2c5 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Sat, 28 Dec 2024 12:06:09 +0300 Subject: [PATCH 11/12] chore: rebase --- src/goods.ts | 14 ++------------ src/util.ts | 8 +------- test/goods.test.js | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/goods.ts b/src/goods.ts index 511d725527..2d5789d7c1 100644 --- a/src/goods.ts +++ b/src/goods.ts @@ -28,6 +28,7 @@ import { fs, minimist, nodeFetch, + parseDotenv, type RequestInfo, type RequestInit, } from './vendor.js' @@ -224,18 +225,7 @@ export async function spinner( * Read env files and collects it into environment variables */ export const dotenv = (() => { - const parse = (content: string | Buffer): NodeJS.ProcessEnv => - content - .toString() - .split(/\r?\n/) - .reduce((r, line) => { - if (line.startsWith('export ')) line = line.slice(7) - const i = line.indexOf('=') - const k = line.slice(0, i).trim() - const v = line.slice(i + 1).trim() - if (k && v) r[k] = v - return r - }, {}) + const parse = parseDotenv const _load = ( read: (file: string) => string, diff --git a/src/util.ts b/src/util.ts index d03e243225..73fe38d93b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,12 +15,7 @@ import os from 'node:os' import path from 'node:path' import fs from 'node:fs' -import { - chalk, - type RequestInfo, - type RequestInit, - parseDotenv, -} from './vendor-core.js' +import { chalk, type RequestInfo, type RequestInit } from './vendor-core.js' import { inspect } from 'node:util' export { isStringLiteral, parseDotenv } from './vendor-core.js' @@ -362,4 +357,3 @@ export const toCamelCase = (str: string) => export const parseBool = (v: string): boolean | string => ({ true: true, false: false })[v] ?? v - diff --git a/test/goods.test.js b/test/goods.test.js index 18db48ebe9..e41d8e544f 100644 --- a/test/goods.test.js +++ b/test/goods.test.js @@ -176,6 +176,7 @@ describe('goods', () => { describe('dotenv', () => { test('parse()', () => { + assert.deepEqual(dotenv.parse(''), {}) assert.deepEqual( dotenv.parse('ENV=v1\nENV2=v2\n\n\n ENV3 = v3 \nexport ENV4=v4'), { @@ -185,15 +186,27 @@ describe('goods', () => { ENV4: 'v4', } ) - assert.deepEqual(dotenv.parse(''), {}) - // TBD: multiline const multiline = `SIMPLE=xyz123 -NON_INTERPOLATED='raw text without variable interpolation' +# comment ### +NON_INTERPOLATED='raw text without variable interpolation' MULTILINE = """ -long text here, +long text here, # not-comment e.g. a private SSH key -"""` +""" +ENV=v1\nENV2=v2\n\n\n\t\t ENV3 = v3 \n export ENV4=v4 +ENV5=v5 # comment +` + assert.deepEqual(dotenv.parse(multiline), { + SIMPLE: 'xyz123', + NON_INTERPOLATED: 'raw text without variable interpolation', + MULTILINE: 'long text here, # not-comment\ne.g. a private SSH key', + ENV: 'v1', + ENV2: 'v2', + ENV3: 'v3', + ENV4: 'v4', + ENV5: 'v5', + }) }) describe('load()', () => { From f0a1483bb0a630bc8da84b51132d10140fd7b557 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Mon, 30 Dec 2024 11:10:41 +0300 Subject: [PATCH 12/12] feat: reexport `envapi` --- .size-limit.json | 4 ++-- docs/api.md | 3 ++- package-lock.json | 8 ++++---- package.json | 2 +- scripts/build-dts.mjs | 2 +- scripts/build-tests.mjs | 2 +- src/goods.ts | 34 ---------------------------------- src/index.ts | 1 + src/util.ts | 2 +- src/vendor-core.ts | 1 - src/vendor-extra.ts | 1 + test/export.test.js | 7 +++++++ test/goods.test.js | 4 ++-- test/index.test.js | 2 ++ 14 files changed, 25 insertions(+), 48 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index e7b374e59f..6fcc8c0557 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -23,14 +23,14 @@ { "name": "vendor", "path": "build/vendor-*", - "limit": "762 kB", + "limit": "763 kB", "brotli": false, "gzip": false }, { "name": "all", "path": "build/*", - "limit": "843 kB", + "limit": "844 kB", "brotli": false, "gzip": false } diff --git a/docs/api.md b/docs/api.md index 320a0342d7..23e58ef045 100644 --- a/docs/api.md +++ b/docs/api.md @@ -366,7 +366,8 @@ console.log(YAML.parse('foo: bar').foo) ``` ## dotenv -[dotenv](https://www.npmjs.com/package/dotenv)-like environment variables loading API +The [envapi](https://www.npmjs.com/package/envapi) package. +An API to interact with environment vars in [dotenv](https://www.npmjs.com/package/dotenv) format. ```js // parse diff --git a/package-lock.json b/package-lock.json index 1345da9e01..391f07de13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "create-require": "^1.1.1", "depseek": "^0.4.1", "dts-bundle-generator": "^9.5.1", - "envapi": "^0.0.0", + "envapi": "^0.1.0", "esbuild": "^0.24.2", "esbuild-node-externals": "^1.16.0", "esbuild-plugin-entry-chunks": "^0.1.15", @@ -2507,9 +2507,9 @@ } }, "node_modules/envapi": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/envapi/-/envapi-0.0.0.tgz", - "integrity": "sha512-vO+00FBxMQMY1dvt1wSr34yFTGkCAlPGZfwGgX0dLdlxRG/YZLn7GKfxFaSw1SQkF3SJYygVIh3YLNWNvJfzYA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/envapi/-/envapi-0.1.0.tgz", + "integrity": "sha512-qlegDm1lfcl+azrvph6SAPPs1TY6HYZJ6SWf+iW76mD3VAzl7pxSRXLDnCVe75HOX+9OZIQeuO7a16bOcnPFbg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 4a6f92c80f..392372cd90 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "create-require": "^1.1.1", "depseek": "^0.4.1", "dts-bundle-generator": "^9.5.1", - "envapi": "^0.0.0", + "envapi": "^0.1.0", "esbuild": "^0.24.2", "esbuild-node-externals": "^1.16.0", "esbuild-plugin-entry-chunks": "^0.1.15", diff --git a/scripts/build-dts.mjs b/scripts/build-dts.mjs index b4b5e893ec..cd0f8ae555 100644 --- a/scripts/build-dts.mjs +++ b/scripts/build-dts.mjs @@ -45,6 +45,7 @@ const entries = [ // '@webpod/ps', '@webpod/ingrid', 'depseek', + 'envapi', ], // args['external-inlines'], }, output, @@ -60,7 +61,6 @@ const entries = [ '@webpod/ingrid', 'chalk', 'zurk', - 'envapi', ], // args['external-inlines'], }, output, diff --git a/scripts/build-tests.mjs b/scripts/build-tests.mjs index 1aea865a03..56a0456407 100644 --- a/scripts/build-tests.mjs +++ b/scripts/build-tests.mjs @@ -26,7 +26,7 @@ const modules = [ ['core', core], ['cli', cli], ['index', index], - ['vendor', vendor, ['chalk', 'depseek', 'fs', 'glob', 'minimist', 'ps', 'which', 'YAML',]], + ['vendor', vendor, ['chalk', 'depseek', 'dotenv', 'fs', 'glob', 'minimist', 'ps', 'which', 'YAML',]], ] const root = path.resolve(new URL(import.meta.url).pathname, '../..') const filePath = path.resolve(root, `test/export.test.js`) diff --git a/src/goods.ts b/src/goods.ts index 2d5789d7c1..297a652b86 100644 --- a/src/goods.ts +++ b/src/goods.ts @@ -28,7 +28,6 @@ import { fs, minimist, nodeFetch, - parseDotenv, type RequestInfo, type RequestInit, } from './vendor.js' @@ -220,36 +219,3 @@ export async function spinner( } }) } - -/** - * Read env files and collects it into environment variables - */ -export const dotenv = (() => { - const parse = parseDotenv - - const _load = ( - read: (file: string) => string, - ...files: string[] - ): NodeJS.ProcessEnv => - files - .reverse() - .reduce((m, f) => Object.assign(m, parse(read(path.resolve(f)))), {}) - const load = (...files: string[]): NodeJS.ProcessEnv => - _load((file) => fs.readFileSync(file, 'utf8'), ...files) - const loadSafe = (...files: string[]): NodeJS.ProcessEnv => - _load( - (file: string): string => - fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '', - ...files - ) - - const config = (def = '.env', ...files: string[]): NodeJS.ProcessEnv => - Object.assign(process.env, loadSafe(def, ...files)) - - return { - parse, - load, - loadSafe, - config, - } -})() diff --git a/src/index.ts b/src/index.ts index 59f92e0f79..2de80d056a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export * from './goods.js' export { minimist, chalk, + dotenv, fs, which, YAML, diff --git a/src/util.ts b/src/util.ts index 73fe38d93b..2f63edffb5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -18,7 +18,7 @@ import fs from 'node:fs' import { chalk, type RequestInfo, type RequestInit } from './vendor-core.js' import { inspect } from 'node:util' -export { isStringLiteral, parseDotenv } from './vendor-core.js' +export { isStringLiteral } from './vendor-core.js' export function tempdir(prefix: string = `zx-${randomId()}`): string { const dirpath = path.join(os.tmpdir(), prefix) diff --git a/src/vendor-core.ts b/src/vendor-core.ts index c039cde3c4..19fbfcb1bb 100644 --- a/src/vendor-core.ts +++ b/src/vendor-core.ts @@ -26,4 +26,3 @@ export type RequestInit = Parameters[1] export { default as chalk, type ChalkInstance } from 'chalk' export { default as which } from 'which' export { default as ps } from '@webpod/ps' -export { parse as parseDotenv } from 'envapi' diff --git a/src/vendor-extra.ts b/src/vendor-extra.ts index c411551adc..3a29fd3251 100644 --- a/src/vendor-extra.ts +++ b/src/vendor-extra.ts @@ -116,3 +116,4 @@ export const fs: typeof import('fs-extra') = _fs export { depseekSync as depseek } from 'depseek' export { default as minimist } from 'minimist' +export { default as dotenv } from 'envapi' diff --git a/test/export.test.js b/test/export.test.js index 5593841efd..a2a0e5f5c1 100644 --- a/test/export.test.js +++ b/test/export.test.js @@ -159,6 +159,7 @@ describe('index', () => { assert.equal(typeof index.dotenv.load, 'function', 'index.dotenv.load') assert.equal(typeof index.dotenv.loadSafe, 'function', 'index.dotenv.loadSafe') assert.equal(typeof index.dotenv.parse, 'function', 'index.dotenv.parse') + assert.equal(typeof index.dotenv.stringify, 'function', 'index.dotenv.stringify') assert.equal(typeof index.echo, 'function', 'index.echo') assert.equal(typeof index.expBackoff, 'function', 'index.expBackoff') assert.equal(typeof index.fetch, 'function', 'index.fetch') @@ -420,6 +421,12 @@ describe('vendor', () => { assert.equal(typeof vendor.chalk, 'function', 'vendor.chalk') assert.equal(typeof vendor.chalk.level, 'number', 'vendor.chalk.level') assert.equal(typeof vendor.depseek, 'function', 'vendor.depseek') + assert.equal(typeof vendor.dotenv, 'object', 'vendor.dotenv') + assert.equal(typeof vendor.dotenv.config, 'function', 'vendor.dotenv.config') + assert.equal(typeof vendor.dotenv.load, 'function', 'vendor.dotenv.load') + assert.equal(typeof vendor.dotenv.loadSafe, 'function', 'vendor.dotenv.loadSafe') + assert.equal(typeof vendor.dotenv.parse, 'function', 'vendor.dotenv.parse') + assert.equal(typeof vendor.dotenv.stringify, 'function', 'vendor.dotenv.stringify') assert.equal(typeof vendor.fs, 'object', 'vendor.fs') assert.equal(typeof vendor.fs.Dir, 'function', 'vendor.fs.Dir') assert.equal(typeof vendor.fs.Dirent, 'function', 'vendor.fs.Dirent') diff --git a/test/goods.test.js b/test/goods.test.js index e41d8e544f..c620451c67 100644 --- a/test/goods.test.js +++ b/test/goods.test.js @@ -14,8 +14,8 @@ import assert from 'node:assert' import { test, describe, after } from 'node:test' -import { $, chalk, fs, tempfile } from '../build/index.js' -import { echo, sleep, parseArgv, dotenv } from '../build/goods.js' +import { $, chalk, fs, tempfile, dotenv } from '../build/index.js' +import { echo, sleep, parseArgv } from '../build/goods.js' describe('goods', () => { function zx(script) { diff --git a/test/index.test.js b/test/index.test.js index 768d06fc5b..53368f8e22 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -30,6 +30,7 @@ import { ProcessOutput, ProcessPromise, defaults, + dotenv, minimist, chalk, fs, @@ -106,6 +107,7 @@ describe('index', () => { assert(which) assert(YAML) assert(ps) + assert(dotenv) // utils assert(quote)