autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
213 lines (212 loc) • 6.35 kB
JavaScript
/**
* @module YamlConfigParser
* @description YAML 配置解析器 — 从 XcodeGen project.yml 中提取项目结构信息
*
* 支持解析:
* - targets(构建目标)及其依赖关系
* - settings(项目级/目标级构建设置)
* - schemes(构建方案)
* - sources(源文件路径)
*
* 使用 js-yaml 进行安全解析(禁用危险的 YAML 特性)。
*/
import yaml from 'js-yaml';
// ── 目标类型 → 层级推断 ────────────────────────────────
const TARGET_TYPE_LAYER = {
application: 'App',
'app-extension': 'Extension',
framework: 'Framework',
'static-library': 'Library',
'dynamic-library': 'Library',
bundle: 'Resource',
'unit-test': 'Test',
'ui-test': 'Test',
tool: 'Tool',
};
// ── 公开 API ────────────────────────────────────────
/**
* 解析 XcodeGen project.yml 内容
* 返回与 RubyDslParser 兼容的 ParsedProjectConfig
*/
export function parseXcodeGenProject(content) {
let doc;
try {
doc = yaml.load(content, { schema: yaml.CORE_SCHEMA });
}
catch {
return { layers: [], globalDependencies: [] };
}
if (!doc || typeof doc !== 'object') {
return { layers: [], globalDependencies: [] };
}
const result = {
layers: [],
globalDependencies: [],
};
// 提取宿主应用
if (doc.name) {
result.hostApp = { name: doc.name, version: '0.0.0' };
}
if (!doc.targets) {
return result;
}
// 将 targets 按类型分层
const layerMap = new Map();
for (const [targetName, target] of Object.entries(doc.targets)) {
const targetType = target.type ?? 'framework';
const layerName = TARGET_TYPE_LAYER[targetType] ?? 'Other';
const isLocal = !isExternalTarget(targetName, doc);
const mod = {
name: targetName,
version: '0.0.0',
isLocal,
localPath: extractSourcePath(target),
group: layerName,
};
if (!layerMap.has(layerName)) {
layerMap.set(layerName, []);
}
layerMap.get(layerName)?.push(mod);
}
// 转为 ParsedLayer[]
let order = 0;
for (const [name, modules] of layerMap.entries()) {
result.layers.push({
name,
order: order++,
accessibleLayers: [],
modules,
});
}
// 提取 SPM packages → globalDependencies
if (doc.packages) {
for (const [pkgName, pkg] of Object.entries(doc.packages)) {
result.globalDependencies.push({
name: pkgName,
version: pkg.from ?? pkg.version ?? pkg.branch ?? '0.0.0',
isLocal: !!pkg.path,
localPath: pkg.path,
});
}
}
return result;
}
/**
* 解析单个 target 为 ParsedModuleSpec 格式
*/
export function parseXcodeGenTarget(targetName, content) {
let doc;
try {
doc = yaml.load(content, { schema: yaml.CORE_SCHEMA });
}
catch {
return null;
}
if (!doc?.targets?.[targetName]) {
return null;
}
const target = doc.targets[targetName];
return {
name: targetName,
version: '0.0.0',
sources: extractSourcePath(target) ?? targetName,
dependencies: extractTargetDependencies(target),
publicHeaders: [],
deploymentTarget: extractDeploymentTarget(target),
};
}
/**
* 提取所有 target 的依赖图
* 返回 [from, to][] 形式的有向边列表
*/
export function extractXcodeGenDependencyEdges(content) {
let doc;
try {
doc = yaml.load(content, { schema: yaml.CORE_SCHEMA });
}
catch {
return [];
}
if (!doc?.targets) {
return [];
}
const edges = [];
for (const [targetName, target] of Object.entries(doc.targets)) {
if (!target.dependencies) {
continue;
}
for (const dep of target.dependencies) {
const depName = dep.target ?? dep.framework ?? dep.package ?? dep.carthage;
if (depName) {
edges.push([targetName, depName]);
}
}
}
return edges;
}
// ── 内部帮助函数 ────────────────────────────────────
function extractSourcePath(target) {
if (!target.sources || target.sources.length === 0) {
return undefined;
}
const first = target.sources[0];
if (typeof first === 'string') {
return first;
}
return first.path;
}
function extractTargetDependencies(target) {
if (!target.dependencies) {
return [];
}
return target.dependencies
.map((dep) => dep.target ?? dep.framework ?? dep.package ?? dep.carthage)
.filter((name) => !!name);
}
function extractDeploymentTarget(target) {
if (!target.deploymentTarget) {
return undefined;
}
if (typeof target.deploymentTarget === 'string') {
return target.deploymentTarget;
}
// Object form: { iOS: "15.0", macOS: "12.0" }
const values = Object.values(target.deploymentTarget);
return values[0];
}
function isExternalTarget(targetName, doc) {
// SPM packages referenced as targets are external
if (doc.packages?.[targetName]) {
return true;
}
// Targets defined in the project are local
return false;
}
/**
* 解析 melos.yaml 内容
* 提取项目名、包路径 glob 模式、scripts 列表
*/
export function parseMelosProject(content) {
const result = {
name: '',
packageGlobs: [],
scripts: [],
};
try {
const doc = yaml.load(content, { schema: yaml.CORE_SCHEMA });
if (!doc) {
return result;
}
result.name = doc.name ?? '';
if (Array.isArray(doc.packages)) {
result.packageGlobs = doc.packages.filter((p) => typeof p === 'string');
}
if (doc.scripts && typeof doc.scripts === 'object') {
result.scripts = Object.keys(doc.scripts);
}
}
catch {
// YAML 解析失败时返回空结果
}
return result;
}