arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
100 lines • 4 kB
JavaScript
import path from "node:path";
import fs from "fs-extra";
import fg from "fast-glob";
export async function createSliceVersion(repoPath, slice, version) {
if (version < 2) {
throw new Error("Version number must be 2 or greater.");
}
const sliceDir = await findSliceDirectory(repoPath, slice);
if (!sliceDir) {
throw new Error(`Slice "${slice}" not found. Expected at features/${slice} or src/features/${slice}.`);
}
const parentDir = path.dirname(sliceDir);
const targetDir = path.join(parentDir, `${slice}-v${version}`);
if (await fs.pathExists(targetDir)) {
throw new Error(`Target slice directory already exists: ${targetDir}`);
}
await fs.copy(sliceDir, targetDir);
const previousVersion = version - 1;
await updateVersionMarkers(targetDir, previousVersion, version);
const specPath = await findSpecForSlice(repoPath, slice);
let newSpecPath;
if (specPath) {
const ext = path.extname(specPath);
const baseName = path.basename(specPath, ext);
const versionedName = appendVersionSuffix(baseName, version);
newSpecPath = path.join(path.dirname(specPath), `${versionedName}${ext}`);
if (await fs.pathExists(newSpecPath)) {
throw new Error(`Versioned OpenAPI spec already exists: ${newSpecPath}`);
}
await fs.copy(specPath, newSpecPath);
await replaceVersionInFile(newSpecPath, previousVersion, version);
}
return {
newSlicePath: targetDir,
newSpecPath,
};
}
async function findSliceDirectory(repoPath, slice) {
const candidates = [
path.join(repoPath, "features", slice),
path.join(repoPath, "src", "features", slice),
path.join(repoPath, slice),
];
for (const candidate of candidates) {
if (await fs.pathExists(candidate)) {
return candidate;
}
}
return undefined;
}
async function updateVersionMarkers(dir, previousVersion, nextVersion) {
if (previousVersion < 1) {
return;
}
const files = await fg(["**/*.{ts,tsx,js,jsx,tsm,cts,mts,md,yaml,yml,json}"], {
cwd: dir,
dot: false,
onlyFiles: true,
});
await Promise.all(files.map(async (file) => {
const absolutePath = path.join(dir, file);
await replaceVersionInFile(absolutePath, previousVersion, nextVersion);
}));
}
async function replaceVersionInFile(filePath, previousVersion, nextVersion) {
const contents = await fs.readFile(filePath, "utf8");
const apiSegment = new RegExp(`/v${previousVersion}(?=/)`, "g");
const looseMarker = new RegExp(`\\bv${previousVersion}\\b`, "g");
const versionLabel = new RegExp(`(version\\s*:?\\s*)(${previousVersion})(\\b)`, "gi");
let updated = contents.replace(apiSegment, `/v${nextVersion}`);
updated = updated.replace(looseMarker, `v${nextVersion}`);
updated = updated.replace(versionLabel, (_match, prefix, _value, suffix) => `${prefix}${nextVersion}${suffix}`);
if (updated !== contents) {
await fs.writeFile(filePath, updated, "utf8");
}
}
async function findSpecForSlice(repoPath, slice) {
const normalized = slice.replace(/-v\d+$/, "");
const exactCandidates = [
path.join(repoPath, "openapi", `${normalized}.yaml`),
path.join(repoPath, "openapi", `${normalized}.yml`),
path.join(repoPath, "openapi", `${normalized}-api.yaml`),
path.join(repoPath, "openapi", `${normalized}-api.yml`),
];
for (const candidate of exactCandidates) {
if (await fs.pathExists(candidate)) {
return candidate;
}
}
const matches = await fg(`openapi/**/*${normalized}*.{yaml,yml,json}`, {
cwd: repoPath,
absolute: true,
});
return matches[0];
}
function appendVersionSuffix(base, version) {
const withoutExistingVersion = base.replace(/-v\d+$/, "");
return `${withoutExistingVersion}-v${version}`;
}
//# sourceMappingURL=version-creator.js.map