@supernovaio/cli
Version:
Supernova.io Command Line Interface
188 lines (186 loc) • 9.48 kB
JavaScript
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="234261e7-3ea7-524f-8429-b89d3e65efb0")}catch(e){}}();
import AdmZip from "adm-zip";
import axios, { isAxiosError } from "axios";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { createApiClient } from "../../http-client.js";
import { watchAnalyzeStatus } from "../../analyze-status.js";
import { deriveRepoPathFromSnapshotRoot, detectSnapshotScannerType, hasUploadableSnapshotContent, listSnapshotRootsUnderPath, } from "../helpers.js";
import { emphasis, muted, success, warning } from "../ui.js";
export function createScanService(input) {
return {
persistAnalyzePackages(componentPackages) {
const config = input.configService.get();
input.configService.update({
analyze: {
...config?.analyze,
packages: componentPackages,
},
});
},
async runScanAgainstSourcePath(runInput) {
const previousCwd = process.cwd();
const before = new Set(listSnapshotRootsUnderPath(runInput.scanSourcePath));
try {
process.chdir(runInput.scanSourcePath);
const args = ["--dryRun", "--suppressDryRunNotice", "--designSystemId", runInput.designSystemId];
for (const componentPackage of runInput.componentPackages) {
args.push("--package", componentPackage);
}
await input.runAnalyzeCommand(runInput.scanSourcePath, args);
}
finally {
process.chdir(previousCwd);
}
const after = listSnapshotRootsUnderPath(runInput.scanSourcePath);
const createdSnapshotRoots = after.filter(snapshotRoot => !before.has(snapshotRoot));
const uploadableSnapshotRoots = createdSnapshotRoots.filter(snapshotRoot => hasUploadableSnapshotContent(snapshotRoot));
const emptySnapshotRoots = createdSnapshotRoots.filter(snapshotRoot => !hasUploadableSnapshotContent(snapshotRoot));
for (const snapshotRoot of emptySnapshotRoots) {
fs.rmSync(snapshotRoot, { force: true, recursive: true });
}
if (uploadableSnapshotRoots.length === 0) {
input.error("No analyzable scan results were produced. Run setup from the repository that contains your design system packages and component usage.");
}
return uploadableSnapshotRoots;
},
async uploadAnalysisSnapshots(uploadInput) {
const { designSystemId, scanOutputPaths } = uploadInput;
if (scanOutputPaths.length === 0)
return { snapshotIds: [] };
input.log("");
input.log(emphasis("Uploading analysis snapshots to Supernova..."));
const apiClient = await createApiClient(input.env);
const analyzeConfig = input.configService.get()?.analyze;
const repoId = analyzeConfig?.repoId ?? crypto.randomUUID();
const uploadedSnapshotIds = [];
const failedUploads = [];
for (const [index, snapshotRoot] of scanOutputPaths.entries()) {
const repoPath = deriveRepoPathFromSnapshotRoot(snapshotRoot);
const localSnapshotId = path.basename(snapshotRoot);
const archive = createArchiveFromSnapshotRoot(snapshotRoot);
const uploadId = `A${String(index + 1).padStart(2, "0")}`;
const scannerType = detectSnapshotScannerType(snapshotRoot);
let initResponse;
try {
const response = await apiClient.post("/code-snapshots/upload", {
archiveChecksum: archive.checksum,
archiveName: archive.archiveName,
archiveSize: archive.sizeBytes,
designSystemId,
repoId,
repoName: analyzeConfig?.repoName ?? readRepoNameFromRepo(repoPath),
repoPackageName: readPackageNameFromRepo(repoPath),
scannerType,
});
initResponse = response.data.result;
}
catch (error) {
logHttpError(input.log, "snapshot upload init", error);
failedUploads.push({ localSnapshotId, scannerType, stage: "init", uploadId });
input.log(warning(`Skipping ${uploadId} ${localSnapshotId} (${scannerType}) after init failure.`));
continue;
}
try {
await axios.put(initResponse.uploadUrl, archive.archive, {
headers: {
"Content-Length": archive.archive.length,
"Content-Type": "application/zip",
},
});
}
catch (error) {
logHttpError(input.log, "snapshot upload PUT", error);
failedUploads.push({ localSnapshotId, scannerType, stage: "put", uploadId });
input.log(warning(`Skipping ${uploadId} ${localSnapshotId} (${scannerType}) after archive upload failure.`));
continue;
}
try {
await apiClient.post(`/code-snapshots/${initResponse.snapshotId}/finalize`, {});
}
catch (error) {
logHttpError(input.log, "snapshot finalize", error);
failedUploads.push({ localSnapshotId, scannerType, stage: "finalize", uploadId });
input.log(warning(`Skipping ${uploadId} ${localSnapshotId} (${scannerType}) after finalize failure.`));
continue;
}
uploadedSnapshotIds.push(initResponse.snapshotId);
input.log(`${success("Uploaded")} ${localSnapshotId} ${muted(`(${scannerType})`)}`);
}
if (failedUploads.length > 0) {
input.log(warning(`Failed uploads: ${failedUploads.length}`));
for (const failure of failedUploads) {
input.log(muted(`- ${failure.uploadId} ${failure.localSnapshotId} (${failure.scannerType}) stage=${failure.stage}`));
}
}
if (uploadedSnapshotIds.length === 0) {
input.error("No analysis snapshots were uploaded successfully.");
}
try {
const response = await apiClient.post("/code-snapshots/process-run", { designSystemId });
const { result } = response.data;
input.log(`${success("Started snapshot processing")} ${muted(`run: ${result.processingRunId} snapshots: ${result.snapshotIds.length}`)}`);
return result;
}
catch (error) {
logHttpError(input.log, "process-run", error);
input.error("Analysis snapshots uploaded, but starting processing run failed.");
}
},
async waitForAnalysisProcessing(waitInput) {
input.log("Checking design system analysis processing status. If analysis is still running, setup will continue automatically when it finishes.");
await watchAnalyzeStatus({
apiClient: await createApiClient(input.env),
designSystemId: waitInput.designSystemId,
error: input.error,
log: input.log,
processingRunId: waitInput.processingRunId,
});
},
};
}
function createArchiveFromSnapshotRoot(snapshotRoot) {
const zip = new AdmZip();
zip.addLocalFolder(snapshotRoot);
const archive = zip.toBuffer();
const checksum = crypto.createHash("sha256").update(archive).digest("hex");
return {
archive,
archiveName: `${path.basename(snapshotRoot)}.zip`,
checksum,
sizeBytes: archive.byteLength,
};
}
function readPackageNameFromRepo(repoPath) {
const packageJsonPath = path.join(repoPath, "package.json");
if (!fs.existsSync(packageJsonPath)) {
return path.basename(repoPath);
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
return packageJson.name ?? path.basename(repoPath);
}
catch {
return path.basename(repoPath);
}
}
function readRepoNameFromRepo(repoPath) {
return path.basename(repoPath);
}
function logHttpError(log, step, error) {
if (isAxiosError(error)) {
const status = error.response?.status;
const responseData = error.response?.data;
const message = typeof responseData === "string"
? responseData
: typeof responseData?.message === "string"
? responseData.message
: error.message;
log(`${warning(step)}${status ? ` (${status})` : ""}: ${message}`);
return;
}
log(`${warning(step)}: ${error instanceof Error ? error.message : String(error)}`);
}
//# sourceMappingURL=scan-service.js.map
//# debugId=234261e7-3ea7-524f-8429-b89d3e65efb0