Template base para proyectos SaaS multi-tenant. Estructura inicial: - apps/backend (NestJS API) - apps/frontend (React/Vite) - apps/database (PostgreSQL DDL) - docs/ (Documentación) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
99 lines
2.5 KiB
JavaScript
99 lines
2.5 KiB
JavaScript
'use strict'
|
|
|
|
function _resolveEscapeSequences (value) {
|
|
return value.replace(/\\\$/g, '$')
|
|
}
|
|
|
|
function expandValue (value, processEnv, runningParsed) {
|
|
const env = { ...runningParsed, ...processEnv } // process.env wins
|
|
|
|
const regex = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
|
|
|
|
let result = value
|
|
let match
|
|
const seen = new Set() // self-referential checker
|
|
|
|
while ((match = regex.exec(result)) !== null) {
|
|
seen.add(result)
|
|
|
|
const [template, bracedExpression, unbracedExpression] = match
|
|
const expression = bracedExpression || unbracedExpression
|
|
|
|
// match the operators `:+`, `+`, `:-`, and `-`
|
|
const opRegex = /(:\+|\+|:-|-)/
|
|
// find first match
|
|
const opMatch = expression.match(opRegex)
|
|
const splitter = opMatch ? opMatch[0] : null
|
|
|
|
const r = expression.split(splitter)
|
|
|
|
let defaultValue
|
|
let value
|
|
|
|
const key = r.shift()
|
|
|
|
if ([':+', '+'].includes(splitter)) {
|
|
defaultValue = env[key] ? r.join(splitter) : ''
|
|
value = null
|
|
} else {
|
|
defaultValue = r.join(splitter)
|
|
value = env[key]
|
|
}
|
|
|
|
if (value) {
|
|
// self-referential check
|
|
if (seen.has(value)) {
|
|
result = result.replace(template, defaultValue)
|
|
} else {
|
|
result = result.replace(template, value)
|
|
}
|
|
} else {
|
|
result = result.replace(template, defaultValue)
|
|
}
|
|
|
|
// if the result equaled what was in process.env and runningParsed then stop expanding
|
|
if (result === runningParsed[key]) {
|
|
break
|
|
}
|
|
|
|
regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function expand (options) {
|
|
// for use with progressive expansion
|
|
const runningParsed = {}
|
|
|
|
let processEnv = process.env
|
|
if (options && options.processEnv != null) {
|
|
processEnv = options.processEnv
|
|
}
|
|
|
|
// dotenv.config() ran before this so the assumption is process.env has already been set
|
|
for (const key in options.parsed) {
|
|
let value = options.parsed[key]
|
|
|
|
// short-circuit scenario: process.env was already set prior to the file value
|
|
if (processEnv[key] && processEnv[key] !== value) {
|
|
value = processEnv[key]
|
|
} else {
|
|
value = expandValue(value, processEnv, runningParsed)
|
|
}
|
|
|
|
options.parsed[key] = _resolveEscapeSequences(value)
|
|
|
|
// for use with progressive expansion
|
|
runningParsed[key] = _resolveEscapeSequences(value)
|
|
}
|
|
|
|
for (const processKey in options.parsed) {
|
|
processEnv[processKey] = options.parsed[processKey]
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
module.exports.expand = expand
|