configorama 1.0.2 → 1.1.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.
@@ -0,0 +1,27 @@
1
+ // Classifies config files by type, treating dotenv files as ".env"
2
+ // so extension-less names like .env / .env.local / deploy.env resolve consistently
3
+ const path = require('path')
4
+
5
+ /**
6
+ * Whether a path is a dotenv file (.env, .env.local, deploy.env, ...).
7
+ * `path.extname('.env')` is '' (dotfile), so detection is by basename.
8
+ * @param {string} filePath - File path or name
9
+ * @returns {boolean} True for dotenv files
10
+ */
11
+ function isEnvFile(filePath) {
12
+ const base = path.basename(String(filePath || ''))
13
+ return base === '.env' || base.startsWith('.env.') || base.endsWith('.env')
14
+ }
15
+
16
+ /**
17
+ * Normalized config file type: '.env' for dotenv files, else the lowercased
18
+ * extension.
19
+ * @param {string} filePath - File path or name
20
+ * @returns {string} File type (e.g. '.yml', '.env', '')
21
+ */
22
+ function configFileType(filePath) {
23
+ if (isEnvFile(filePath)) return '.env'
24
+ return path.extname(String(filePath || '')).toLowerCase()
25
+ }
26
+
27
+ module.exports = { isEnvFile, configFileType }
@@ -0,0 +1,41 @@
1
+ /* Tests for config file type classification (dotenv detection) */
2
+ const { test } = require('uvu')
3
+ const assert = require('uvu/assert')
4
+ const { isEnvFile, configFileType } = require('./fileType')
5
+
6
+ test('isEnvFile detects the env-stage-loader precedence names', () => {
7
+ // .env.{environment}.local, .env.{environment}, .env.local, .env
8
+ assert.ok(isEnvFile('.env'))
9
+ assert.ok(isEnvFile('.env.local'))
10
+ assert.ok(isEnvFile('.env.production'))
11
+ assert.ok(isEnvFile('.env.production.local'))
12
+ assert.ok(isEnvFile('/path/to/.env.staging.local'))
13
+ assert.ok(isEnvFile('deploy.env'))
14
+ })
15
+
16
+ test('isEnvFile rejects non-dotenv names', () => {
17
+ assert.is(isEnvFile('config.yml'), false)
18
+ assert.is(isEnvFile('env.js'), false)
19
+ assert.is(isEnvFile('environment.json'), false)
20
+ assert.is(isEnvFile('.environment'), false)
21
+ assert.is(isEnvFile(''), false)
22
+ })
23
+
24
+ test('configFileType returns .env for staged dotenv names', () => {
25
+ assert.is(configFileType('.env.production.local'), '.env')
26
+ assert.is(configFileType('.env.staging'), '.env')
27
+ })
28
+
29
+ test('configFileType returns .env for dotenv files', () => {
30
+ assert.is(configFileType('.env'), '.env')
31
+ assert.is(configFileType('/x/.env.local'), '.env')
32
+ assert.is(configFileType('deploy.env'), '.env')
33
+ })
34
+
35
+ test('configFileType returns lowercased extension otherwise', () => {
36
+ assert.is(configFileType('config.YML'), '.yml')
37
+ assert.is(configFileType('data.json'), '.json')
38
+ assert.is(configFileType('noext'), '')
39
+ })
40
+
41
+ test.run()
@@ -31,6 +31,10 @@ function findLineForKey(keyToFind, lines, fileType) {
31
31
  if (fileType === '.ini') {
32
32
  return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
33
33
  }
34
+ // dotenv: KEY= or export KEY=
35
+ if (fileType === '.env') {
36
+ return new RegExp(`^\\s*(?:export\\s+)?${escapedKey}\\s*=`).test(line)
37
+ }
34
38
  // JS/TS/ESM: key: or "key": or 'key': or `key`: or [`key`]:
35
39
  if (['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(fileType)) {
36
40
  return new RegExp(`(?:${escapedKey}|"${escapedKey}"|'${escapedKey}'|\`${escapedKey}\`|\\[\`${escapedKey}\`\\])\\s*:`).test(line)
@@ -55,13 +59,32 @@ function findLineByPath(configPath, lines, fileType) {
55
59
 
56
60
  const isYaml = fileType === '.yml' || fileType === '.yaml'
57
61
  const isJson = fileType === '.json' || fileType === '.json5' || fileType === '.jsonc'
58
- if (!isYaml && !isJson) return 0
62
+ const isEnv = fileType === '.env'
63
+ if (!isYaml && !isJson && !isEnv) return 0
59
64
 
60
65
  const segments = configPath.split('.')
66
+ if (isEnv) return findLineByPathEnv(segments, lines)
61
67
  if (isYaml) return findLineByPathYaml(segments, lines)
62
68
  return findLineByPathJson(segments, lines)
63
69
  }
64
70
 
71
+ /**
72
+ * Find a dotenv key's line. .env is flat, so the key is the first path segment.
73
+ * Handles leading whitespace and an optional `export ` prefix.
74
+ * @param {string[]} segments
75
+ * @param {string[]} lines
76
+ * @returns {number}
77
+ */
78
+ function findLineByPathEnv(segments, lines) {
79
+ const key = segments[0]
80
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
81
+ const pattern = new RegExp(`^\\s*(?:export\\s+)?${escaped}\\s*=`)
82
+ for (let li = 0; li < lines.length; li++) {
83
+ if (pattern.test(lines[li])) return li + 1
84
+ }
85
+ return 0
86
+ }
87
+
65
88
  /**
66
89
  * @param {string[]} segments
67
90
  * @param {string[]} lines
@@ -3,7 +3,7 @@
3
3
  */
4
4
  const { test } = require('uvu')
5
5
  const assert = require('uvu/assert')
6
- const { findLineForKey } = require('./findLineForKey')
6
+ const { findLineForKey, findLineByPath } = require('./findLineForKey')
7
7
 
8
8
  // YAML tests
9
9
  test('YAML - finds key at start of line', () => {
@@ -123,4 +123,23 @@ test('unknown file type falls back to YAML-style', () => {
123
123
  assert.equal(findLineForKey('foo', lines, '.unknown'), 1)
124
124
  })
125
125
 
126
+ // dotenv
127
+ test('dotenv - finds KEY= line', () => {
128
+ const lines = ['API_URL=https://x', 'stage=${opt:stage}']
129
+ assert.equal(findLineForKey('stage', lines, '.env'), 2)
130
+ assert.equal(findLineForKey('API_URL', lines, '.env'), 1)
131
+ })
132
+
133
+ test('dotenv - finds export-prefixed and whitespace-padded keys', () => {
134
+ const lines = ['# comment', 'export TOKEN=abc', ' PADDED = x']
135
+ assert.equal(findLineForKey('TOKEN', lines, '.env'), 2)
136
+ assert.equal(findLineForKey('PADDED', lines, '.env'), 3)
137
+ })
138
+
139
+ test('findLineByPath - dotenv key lookup', () => {
140
+ const lines = ['A=1', 'stage=${opt:stage}', 'B=2']
141
+ assert.equal(findLineByPath('stage', lines, '.env'), 2)
142
+ assert.equal(findLineByPath('missing', lines, '.env'), 0)
143
+ })
144
+
126
145
  test.run()