Files
leon-ai--leon/scripts/setup/setup-dotenv.js
T
2026-07-13 12:44:08 +08:00

119 lines
3.2 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import { PROFILE_DOT_ENV_PATH } from '@/constants'
import { createSetupStatus } from './setup-status'
const DOT_ENV_PATH = PROFILE_DOT_ENV_PATH
const DOT_ENV_SAMPLE_PATH = path.join(process.cwd(), '.env.sample')
const ENV_LINE_SEPARATOR_PATTERN = /\r?\n/
function splitEnvLines(content) {
return content.split(ENV_LINE_SEPARATOR_PATTERN)
}
function getEnvVariableName(line) {
const trimmedLine = line.trim()
if (
trimmedLine === '' ||
trimmedLine.startsWith('#') ||
!trimmedLine.includes('=')
) {
return null
}
const variableName = trimmedLine.slice(0, trimmedLine.indexOf('=')).trim()
return /^[A-Z0-9_]+$/.test(variableName) ? variableName : null
}
async function mergeMissingEnvVariables() {
const sampleContent = await fs.promises.readFile(DOT_ENV_SAMPLE_PATH, 'utf8')
const dotEnvContent = await fs.promises.readFile(DOT_ENV_PATH, 'utf8')
const dotEnvLines = splitEnvLines(dotEnvContent)
const existingVariableNames = new Set(
dotEnvLines
.map((line) => getEnvVariableName(line))
.filter((variableName) => variableName !== null)
)
const missingSampleLines = sampleContent
.split(ENV_LINE_SEPARATOR_PATTERN)
.filter((line) => {
const variableName = getEnvVariableName(line)
return variableName !== null && !existingVariableNames.has(variableName)
})
if (missingSampleLines.length === 0) {
return '.env: up-to-date'
}
const normalizedDotEnvContent = dotEnvContent.endsWith('\n')
? dotEnvContent
: `${dotEnvContent}\n`
const separator = normalizedDotEnvContent.trim() === '' ? '' : '\n'
const mergedContent = `${normalizedDotEnvContent}${separator}${missingSampleLines.join('\n')}\n`
await fs.promises.writeFile(DOT_ENV_PATH, mergedContent)
return `.env: +${missingSampleLines.length} variable${
missingSampleLines.length > 1 ? 's' : ''
}`
}
/**
* Upsert a single variable inside `.env`.
*/
export async function updateDotEnvVariable(variableName, value) {
const dotEnvContent = fs.existsSync(DOT_ENV_PATH)
? await fs.promises.readFile(DOT_ENV_PATH, 'utf8')
: ''
const dotEnvLines = dotEnvContent === '' ? [] : splitEnvLines(dotEnvContent)
const nextLine = `${variableName}=${value}`
let hasUpdatedLine = false
const updatedLines = dotEnvLines.map((line) => {
if (getEnvVariableName(line) !== variableName) {
return line
}
hasUpdatedLine = true
return nextLine
})
if (!hasUpdatedLine) {
updatedLines.push(nextLine)
}
const normalizedLines = updatedLines.filter(
(line, index, lines) => !(index === lines.length - 1 && line === '')
)
await fs.promises.mkdir(path.dirname(DOT_ENV_PATH), { recursive: true })
await fs.promises.writeFile(
DOT_ENV_PATH,
`${normalizedLines.join('\n')}\n`
)
}
/**
* Duplicate the .env.sample to .env file
*/
export default async () => {
const status = createSetupStatus('Preparing .env...').start()
if (!fs.existsSync(DOT_ENV_PATH)) {
await fs.promises.mkdir(path.dirname(DOT_ENV_PATH), { recursive: true })
await fs.promises.copyFile(DOT_ENV_SAMPLE_PATH, DOT_ENV_PATH)
status.succeed('.env: created')
return
}
status.succeed(await mergeMissingEnvVariables())
}