@apistudio/apim-cli
Version:
CLI for API Management Products
131 lines (113 loc) • 4.51 kB
text/typescript
import { BaseAsset } from "@apic/studio-client-model";
import { checkForNullOrUndefined } from "../common/data-helper.js";
import AdmZip from "adm-zip";
import path from "node:path";
import fs from "node:fs";
import { showError, showInfo, showWarning } from "../common/message-helper.js";
import { AssetCacheModel } from "../../model/asset-cache-model.js";
import { APIAsset } from "../../model/assets-model.js";
const bundleApiDependency = (
asset: BaseAsset,
searchResult: fs.Dirent<string>,
cachedUnProcessedAsset: AssetCacheModel,
rootDirPath: string,
project: string,
zipFile: AdmZip
) => {
const sourceProjectName = cachedUnProcessedAsset.sourceProject;
if (!sourceProjectName) {
showError(`Source project not found for API dependency ${cachedUnProcessedAsset.ref}`);
return;
}
// Pass the timestamp as the unique value that gets appended to project name
const timeStamp = Date.now();
// Add API with project folder structure
const apiRelativePath = addApiDependencyAsset(
searchResult,
zipFile,
project, // target project name
sourceProjectName,
rootDirPath,
timeStamp
);
showInfo(`API added: ${project}/${apiRelativePath}`);
// Also add the API specification
resolveAndAddApiSpec(
asset,
zipFile,
apiRelativePath,
project, // target project name
sourceProjectName,
rootDirPath,
timeStamp
);
}
const addApiDependencyAsset = (
file: fs.Dirent,
zip: AdmZip,
targetProjectName: string,
sourceProjectName: string,
rootDirPath: string,
timeStamp: number
) => {
// Calculate relative path from target project root
const targetProjectPath = path.join(rootDirPath, targetProjectName);
const relativePath = path.relative(targetProjectPath, path.join(file.parentPath, file.name));
// Add to zip with project folder structure nested inside source project
// Result: sourceProject/targetProject/api-assets/api.yml
const zipPath = path.join(sourceProjectName, `${targetProjectName}_${timeStamp}`, relativePath);
zip.addLocalFile(
path.join(file.parentPath, file.name),
path.dirname(zipPath)
);
return relativePath;
};
const resolveAndAddApiSpec = (
asset: BaseAsset,
zip: AdmZip,
apiFileRelativePath: string,
targetProjectName: string,
sourceProjectName: string,
rootDirPath: string,
timeStamp: number
) => {
const apiAsset = asset as unknown as APIAsset;
const spec = checkForNullOrUndefined(
apiAsset.spec,
`Spec is not defined for the asset with kind 'API' and name '${apiAsset.metadata?.name}'`
);
const apiSpec = checkForNullOrUndefined(
spec["api-spec"],
`Attribute 'api-spec' is not defined
for kind 'API' and name '${apiAsset.metadata?.name}'`
);
const apiSpecPath = checkForNullOrUndefined(
apiSpec.$path,
`API Definition Path is not found for ${asset}`
);
// Get the directory where the API file is located
const apiFileDir = path.dirname(apiFileRelativePath);
// Resolve api spec path relative to project folder by default
let resolvedSpecPath = apiSpecPath;
if (apiSpecPath.startsWith('../') || apiSpecPath.startsWith('./')) {
// Resolve relative path from API file's directory
// Example: apiFileDir = "api-assets", apiSpecPath = "../specs/petstore.yaml"
// Result: "specs/petstore.yaml"
const specPathFromProjectRoot = path.join(apiFileDir, apiSpecPath);
resolvedSpecPath = path.normalize(specPathFromProjectRoot);
}
// Build absolute file system path for reading the spec
const absoluteSpecPath = path.join(rootDirPath, targetProjectName, resolvedSpecPath);
if (!fs.existsSync(absoluteSpecPath)) {
showWarning(
`API spec not found: ${absoluteSpecPath} for API ${apiAsset.metadata?.namespace}:${apiAsset.metadata?.name}:${apiAsset.metadata.version}`
);
return;
}
// Build zip path using the relative spec path from API
// apiSpecPath is already relative (e.g., "specs/petstore.yaml")
const zipSpecPath = path.join(sourceProjectName, `${targetProjectName}_${timeStamp}`, resolvedSpecPath);
zip.addLocalFile(absoluteSpecPath, path.dirname(zipSpecPath));
showInfo(`Spec added: ${zipSpecPath}`);
};
export { bundleApiDependency, addApiDependencyAsset, resolveAndAddApiSpec }