UNPKG

@puls-atlas/cli

Version:

The Puls Atlas CLI tool for managing Atlas projects

134 lines 5.29 kB
import fs from 'fs'; import path from 'path'; import { runGcloudFileCommand } from '../../utils/gcloud.js'; import { writeJsonFile } from '../../utils/file.js'; import { getAtlasGeneratedArtifactsDir, resolveRootPath } from '../../utils/atlas.js'; import { normalizeOptionalString } from '../../utils/value.js'; import { bundleSyncMapperEntry } from './mapperBundler.js'; import { resolveSyncCloudRunDeployConfig } from './deploymentConfig.js'; const SYNC_MAPPER_ARTIFACT_VERSION = 1; const normalizePathSeparators = value => value.replaceAll('\\', '/'); const ensureDirectory = directoryPath => { if (!fs.existsSync(directoryPath)) { fs.mkdirSync(directoryPath, { recursive: true }); } }; const normalizeGsUri = value => { const normalized = normalizeOptionalString(value); if (!normalized) { return null; } if (!normalized.startsWith('gs://')) { throw new Error('Atlas sync mapper upload currently supports only gs:// manifest destinations.'); } const withoutTrailingSlash = normalized.replace(/\/+$/, ''); const pathPart = withoutTrailingSlash.slice('gs://'.length); const firstSlashIndex = pathPart.indexOf('/'); if (firstSlashIndex <= 0 || firstSlashIndex === pathPart.length - 1) { throw new Error('Atlas sync mapper manifest destination must include both a bucket and an object path.'); } return withoutTrailingSlash; }; const getGsUriDirectory = gsUri => gsUri.slice(0, gsUri.lastIndexOf('/')); const joinGsUri = (baseUri, relativePath) => `${baseUri}/${normalizePathSeparators(relativePath)}`; const getSyncMapperArtifactRoot = (projectId, cwd = process.cwd()) => path.join(getAtlasGeneratedArtifactsDir(cwd), 'sync', 'mappers', projectId); const createUploadOperation = (localPath, remoteUri) => ({ command: 'gcloud storage cp', localPath, remoteUri }); export const buildSyncMapperArtifact = async (context, options = {}, cwd = process.cwd()) => { const deployConfig = resolveSyncCloudRunDeployConfig(context); const destinationUri = normalizeGsUri(options.destinationUri ?? deployConfig.mapperManifestUri); if (!destinationUri) { throw new Error('Atlas sync mapper upload requires a gs:// manifest destination. ' + 'Set deploy.cloudRun.mapperManifestUri in the Atlas sync config or provide --destination-uri.'); } const mapperEntries = Object.entries(context.config.mappers ?? {}); if (mapperEntries.length === 0) { throw new Error('Atlas sync mapper upload requires at least one configured mapper.'); } const mapperSourceEntries = mapperEntries.map(([mapperName, mapperConfig]) => { if (!mapperConfig?.source || !mapperConfig?.export) { throw new Error(`Atlas sync mapper "${mapperName}" must define both source and export before it can be uploaded.`); } const sourcePath = normalizePathSeparators(mapperConfig.source); const sourceFilePath = resolveRootPath(sourcePath, cwd); if (!fs.existsSync(sourceFilePath)) { throw new Error(`Atlas sync mapper source file does not exist: ${sourceFilePath}`); } return { exportName: mapperConfig.export, mapperName, sourceFilePath, sourcePath }; }); const artifactRoot = getSyncMapperArtifactRoot(context.projectId, cwd); const filesRoot = path.join(artifactRoot, 'files'); const manifestFilePath = path.join(artifactRoot, 'manifest.json'); const remoteBaseUri = getGsUriDirectory(destinationUri); fs.rmSync(artifactRoot, { force: true, recursive: true }); ensureDirectory(filesRoot); const bundledEntriesBySourcePath = new Map(); const mappers = {}; const uploadOperations = []; for (const mapperEntry of mapperSourceEntries) { if (bundledEntriesBySourcePath.has(mapperEntry.sourceFilePath)) { continue; } const bundledEntry = await bundleSyncMapperEntry({ artifactRoot, cwd, mapperName: mapperEntry.mapperName, sourceFilePath: mapperEntry.sourceFilePath }); bundledEntriesBySourcePath.set(mapperEntry.sourceFilePath, bundledEntry); uploadOperations.push(createUploadOperation(bundledEntry.localModulePath, joinGsUri(remoteBaseUri, bundledEntry.modulePath))); } for (const mapperEntry of mapperSourceEntries) { const { modulePath } = bundledEntriesBySourcePath.get(mapperEntry.sourceFilePath); const moduleUri = joinGsUri(remoteBaseUri, modulePath); mappers[mapperEntry.mapperName] = { export: mapperEntry.exportName, modulePath, moduleUri, source: mapperEntry.sourcePath }; } const manifest = { mappers, createdAt: new Date().toISOString(), manifestUri: destinationUri, projectId: context.projectId, version: SYNC_MAPPER_ARTIFACT_VERSION }; writeJsonFile(manifestFilePath, manifest); uploadOperations.push(createUploadOperation(manifestFilePath, destinationUri)); return { artifactRoot, destinationUri, filesRoot, manifest, manifestFilePath, uploadOperations }; }; export const executeSyncMapperUpload = artifact => { for (const operation of artifact.uploadOperations) { runGcloudFileCommand(['storage', 'cp', operation.localPath, operation.remoteUri], { stdio: 'inherit' }); } return artifact; }; export default { buildSyncMapperArtifact, executeSyncMapperUpload };