mirror of
https://github.com/Lydanne/spaceflow.git
synced 2026-03-09 18:52:24 +08:00
refactor(cli): 重构配置和 .env 文件查找逻辑,支持从 cwd 向上遍历目录树
- 调整 getSpaceflowDir:从 cwd 向上遍历查找已存在的 .spaceflow 目录,未找到时回退到 cwd/.spaceflow - 重构 getConfigPaths 和 getEnvFilePaths:支持向上遍历目录树收集所有祖先目录的配置文件,越靠近 cwd 优先级越高 - 新增 local 选项:getDependencies 和 readConfigSync 支持 local 模式,仅读取当前目录和全局配置,不向上遍历
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@spaceflow/core": "workspace:*",
|
||||
"@spaceflow/shared": "workspace:*",
|
||||
"@spaceflow/shell": "workspace:*",
|
||||
"@spaceflow/scripts": "workspace:*",
|
||||
"@spaceflow/review": "workspace:*",
|
||||
|
||||
5
extensions/publish/.spaceflow/.gitignore
vendored
5
extensions/publish/.spaceflow/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
# Spaceflow Extension dependencies
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
config-schema.json
|
||||
bin/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "spaceflow",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@spaceflow/core": "latest"
|
||||
}
|
||||
}
|
||||
5
extensions/review/.spaceflow/.gitignore
vendored
5
extensions/review/.spaceflow/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
# Spaceflow Extension dependencies
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
config-schema.json
|
||||
bin/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "spaceflow",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@spaceflow/core": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { join, dirname, resolve } from "path";
|
||||
import { execSync } from "child_process";
|
||||
import { homedir } from "os";
|
||||
import {
|
||||
@@ -22,25 +22,39 @@ import {
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取 .spaceflow 目录路径(优先本地,回退全局)
|
||||
* 获取 .spaceflow 目录路径
|
||||
* 从 cwd 向上遍历查找已存在的 .spaceflow 目录,
|
||||
* 如果整个目录树中都没有,则回退到 cwd/.spaceflow
|
||||
*/
|
||||
function getSpaceflowDir(): string {
|
||||
const localDir = join(process.cwd(), SPACEFLOW_DIR);
|
||||
if (existsSync(localDir)) {
|
||||
return localDir;
|
||||
let current = resolve(process.cwd());
|
||||
const home = homedir();
|
||||
|
||||
while (true) {
|
||||
const candidate = join(current, SPACEFLOW_DIR);
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
const parent = dirname(current);
|
||||
if (parent === current) break; // 文件系统根
|
||||
current = parent;
|
||||
}
|
||||
const globalDir = join(homedir(), SPACEFLOW_DIR);
|
||||
|
||||
// 检查全局目录
|
||||
const globalDir = join(home, SPACEFLOW_DIR);
|
||||
if (existsSync(globalDir)) {
|
||||
return globalDir;
|
||||
}
|
||||
return localDir;
|
||||
|
||||
// 都没有,回退到 cwd(后续 ensureSpaceflowPackageJson 会创建)
|
||||
return join(process.cwd(), SPACEFLOW_DIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 spaceflow.json / .spaceflowrc 读取外部扩展包名列表
|
||||
*/
|
||||
function readExternalExtensions(): string[] {
|
||||
const deps = getDependencies();
|
||||
const deps = getDependencies(undefined, { local: true });
|
||||
return Object.keys(deps);
|
||||
}
|
||||
|
||||
@@ -55,8 +69,7 @@ function generateIndexContent(extensions: string[]): string {
|
||||
.map((name) => ` import('${name}').then(m => m.default || m.extension || m),`)
|
||||
.join("\n");
|
||||
|
||||
return `import { exec, initCliI18n } from '@spaceflow/core';
|
||||
import { loadEnvFiles, getEnvFilePaths } from '@spaceflow/shared';
|
||||
return `import { exec, initCliI18n, loadEnvFiles, getEnvFilePaths } from '@spaceflow/core';
|
||||
|
||||
async function bootstrap() {
|
||||
// 1. 先加载 .env 文件,确保 process.env 在 schema 求值前已就绪
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./spaceflow.config";
|
||||
export * from "./schema-generator.service";
|
||||
export { loadEnvFiles } from "@spaceflow/shared";
|
||||
export { loadEnvFiles, getEnvFilePaths } from "@spaceflow/shared";
|
||||
|
||||
@@ -108,7 +108,9 @@ export class OpenAIAdapter implements LlmAdapter {
|
||||
const model = options?.model || openaiConf.model;
|
||||
|
||||
if (shouldLog(options?.verbose, 1)) {
|
||||
console.log(`[LLMProxy.OpenAIAdapter.chatStream] 配置: Model=${model}`);
|
||||
console.log(
|
||||
`[LLMProxy.OpenAIAdapter.chatStream] 配置: Model=${model}, BaseURL=${openaiConf.baseUrl || "默认"}`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
const stream = await client.chat.completions.create({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { readFileSync, existsSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { join, dirname, resolve } from "path";
|
||||
import { homedir } from "os";
|
||||
import stringify from "json-stringify-pretty-compact";
|
||||
import { config as dotenvConfig } from "dotenv";
|
||||
@@ -69,39 +69,89 @@ export function getConfigPath(cwd?: string): string {
|
||||
|
||||
/**
|
||||
* 获取所有配置文件路径(按优先级从低到高排列)
|
||||
* 优先级: ~/.spaceflow/spaceflow.json < ~/.spaceflowrc < ./.spaceflow/spaceflow.json < ./.spaceflowrc
|
||||
* 从 cwd 逐级向上遍历查找 .spaceflowrc 和 .spaceflow/spaceflow.json,
|
||||
* 越靠近 cwd 的优先级越高。全局配置优先级最低。
|
||||
*
|
||||
* 优先级示例(从低到高):
|
||||
* ~/.spaceflow/spaceflow.json < ~/.spaceflowrc
|
||||
* < /project/.spaceflow/spaceflow.json < /project/.spaceflowrc
|
||||
* < /project/extensions/publish/.spaceflow/spaceflow.json < /project/extensions/publish/.spaceflowrc
|
||||
*
|
||||
* @param cwd 工作目录,默认为 process.cwd()
|
||||
*/
|
||||
export function getConfigPaths(cwd?: string): string[] {
|
||||
const workDir = cwd || process.cwd();
|
||||
return [
|
||||
join(homedir(), ".spaceflow", CONFIG_FILE_NAME),
|
||||
join(homedir(), RC_FILE_NAME),
|
||||
join(workDir, ".spaceflow", CONFIG_FILE_NAME),
|
||||
join(workDir, RC_FILE_NAME),
|
||||
];
|
||||
export function getConfigPaths(cwd?: string, options?: { local?: boolean }): string[] {
|
||||
const workDir = resolve(cwd || process.cwd());
|
||||
const home = homedir();
|
||||
|
||||
// local 模式:只读当前目录和全局目录,不向上遍历
|
||||
if (options?.local) {
|
||||
return [
|
||||
join(home, ".spaceflow", CONFIG_FILE_NAME),
|
||||
join(home, RC_FILE_NAME),
|
||||
join(workDir, ".spaceflow", CONFIG_FILE_NAME),
|
||||
join(workDir, RC_FILE_NAME),
|
||||
];
|
||||
}
|
||||
|
||||
// 从 cwd 向上收集所有祖先目录(不含 home,home 单独处理)
|
||||
const ancestors: string[] = [];
|
||||
let current = workDir;
|
||||
while (true) {
|
||||
ancestors.push(current);
|
||||
const parent = dirname(current);
|
||||
if (parent === current) break; // 到达文件系统根
|
||||
current = parent;
|
||||
}
|
||||
|
||||
// 全局配置(最低优先级)
|
||||
const paths: string[] = [join(home, ".spaceflow", CONFIG_FILE_NAME), join(home, RC_FILE_NAME)];
|
||||
|
||||
// 从最远祖先到 cwd(优先级递增)
|
||||
for (let i = ancestors.length - 1; i >= 0; i--) {
|
||||
const dir = ancestors[i];
|
||||
// 跳过 home 目录(已在全局配置中处理)
|
||||
if (dir === home) continue;
|
||||
paths.push(join(dir, ".spaceflow", CONFIG_FILE_NAME));
|
||||
paths.push(join(dir, RC_FILE_NAME));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 .env 文件路径(按优先级从高到低排列,供 ConfigModule.envFilePath 使用)
|
||||
*
|
||||
* NestJS ConfigModule 中 envFilePath 数组靠前的优先级更高(先读到的变量不会被后面覆盖)
|
||||
* 因此返回顺序为从高到低:
|
||||
* 1. ./.env (程序启动目录,最高优先级)
|
||||
* 2. ./.spaceflow/.env (项目配置目录)
|
||||
* 3. ~/.env (全局 home 目录)
|
||||
* 4. ~/.spaceflow/.env (全局配置目录,最低优先级)
|
||||
* 获取所有 .env 文件路径(按优先级从高到低排列)
|
||||
* 从 cwd 逐级向上遍历查找 .env 和 .spaceflow/.env,
|
||||
* 越靠近 cwd 的优先级越高(先加载的变量不会被后加载的覆盖)。
|
||||
*
|
||||
* @param cwd 工作目录,默认为 process.cwd()
|
||||
*/
|
||||
export function getEnvFilePaths(cwd?: string): string[] {
|
||||
const workDir = cwd || process.cwd();
|
||||
return [
|
||||
join(workDir, ENV_FILE_NAME),
|
||||
join(workDir, ".spaceflow", ENV_FILE_NAME),
|
||||
join(homedir(), ENV_FILE_NAME),
|
||||
join(homedir(), ".spaceflow", ENV_FILE_NAME),
|
||||
];
|
||||
const workDir = resolve(cwd || process.cwd());
|
||||
const home = homedir();
|
||||
|
||||
// 从 cwd 向上收集所有祖先目录
|
||||
const ancestors: string[] = [];
|
||||
let current = workDir;
|
||||
while (true) {
|
||||
ancestors.push(current);
|
||||
const parent = dirname(current);
|
||||
if (parent === current) break;
|
||||
current = parent;
|
||||
}
|
||||
|
||||
// 从 cwd 到最远祖先(优先级递减)
|
||||
const paths: string[] = [];
|
||||
for (const dir of ancestors) {
|
||||
if (dir === home) continue;
|
||||
paths.push(join(dir, ENV_FILE_NAME));
|
||||
paths.push(join(dir, ".spaceflow", ENV_FILE_NAME));
|
||||
}
|
||||
|
||||
// 全局配置(最低优先级)
|
||||
paths.push(join(home, ENV_FILE_NAME));
|
||||
paths.push(join(home, ".spaceflow", ENV_FILE_NAME));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,8 +194,11 @@ function readSingleConfigSync(configPath: string): Record<string, unknown> {
|
||||
* 4. ./.spaceflowrc (项目根目录 RC 配置,最高优先级)
|
||||
* @param cwd 工作目录,默认为 process.cwd()
|
||||
*/
|
||||
export function readConfigSync(cwd?: string): Record<string, unknown> {
|
||||
const configPaths = getConfigPaths(cwd);
|
||||
export function readConfigSync(
|
||||
cwd?: string,
|
||||
options?: { local?: boolean },
|
||||
): Record<string, unknown> {
|
||||
const configPaths = getConfigPaths(cwd, options);
|
||||
const configs = configPaths.map((p) => readSingleConfigSync(p));
|
||||
return deepMerge(...configs);
|
||||
}
|
||||
@@ -173,8 +226,11 @@ export function getSupportedEditors(cwd?: string): string[] {
|
||||
* 获取 dependencies
|
||||
* @param cwd 工作目录,默认为 process.cwd()
|
||||
*/
|
||||
export function getDependencies(cwd?: string): Record<string, string> {
|
||||
const config = readConfigSync(cwd);
|
||||
export function getDependencies(
|
||||
cwd?: string,
|
||||
options?: { local?: boolean },
|
||||
): Record<string, string> {
|
||||
const config = readConfigSync(cwd, options);
|
||||
return (config.dependencies as Record<string, string>) || {};
|
||||
}
|
||||
|
||||
|
||||
@@ -102,13 +102,13 @@ export function ensureSpaceflowPackageJson(spaceflowDir: string): void {
|
||||
const packageJsonPath = join(spaceflowDir, PACKAGE_JSON);
|
||||
const coreVersion = getSpaceflowCoreVersion();
|
||||
|
||||
// 从 spaceflow.json/.spaceflowrc 读取扩展依赖
|
||||
const extDeps = getDependencies();
|
||||
// 只从当前目录级别读取扩展依赖(不向上遍历祖先目录)
|
||||
const projectDir = join(spaceflowDir, "..");
|
||||
const extDeps = getDependencies(projectDir, { local: true });
|
||||
|
||||
// 构建期望的 dependencies:@spaceflow/core + @spaceflow/shared + 所有扩展包
|
||||
// 构建期望的 dependencies:@spaceflow/core + 所有扩展包
|
||||
const expectedDeps: Record<string, string> = {
|
||||
"@spaceflow/core": coreVersion,
|
||||
"@spaceflow/shared": coreVersion,
|
||||
...extDeps,
|
||||
};
|
||||
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -95,9 +95,6 @@ importers:
|
||||
'@spaceflow/scripts':
|
||||
specifier: workspace:*
|
||||
version: link:../extensions/scripts
|
||||
'@spaceflow/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/shared
|
||||
'@spaceflow/shell':
|
||||
specifier: workspace:*
|
||||
version: link:../extensions/shell
|
||||
|
||||
Reference in New Issue
Block a user