export const CURRENT_GITZONE_CLI_SCHEMA_VERSION = 2; export interface ISmartconfigMigrationResult { migrated: boolean; fromVersion: number; toVersion: number; } const CLI_NAMESPACE = "@git.zone/cli"; const isPlainObject = (value: unknown): value is Record => { return typeof value === "object" && value !== null && !Array.isArray(value); }; const ensureObject = (parent: Record, key: string): Record => { if (!isPlainObject(parent[key])) { parent[key] = {}; } return parent[key]; }; const normalizeRegistryList = (registries: unknown[]): string[] => { const result: string[] = []; for (const registry of registries) { if (typeof registry !== "string" || !registry.trim()) { continue; } const normalizedRegistry = normalizeRegistryUrl(registry); if (!result.includes(normalizedRegistry)) { result.push(normalizedRegistry); } } return result; }; const migrateLegacyReleaseArray = (smartconfigJson: Record): boolean => { const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE); if (!Array.isArray(cliConfig.release)) { return false; } const registries = normalizeRegistryList(cliConfig.release); cliConfig.release = { targets: { npm: { enabled: registries.length > 0, registries, }, }, }; return true; }; const migrateNamespaceKeys = (smartconfigJson: Record): boolean => { let migrated = false; const migrations = [ { oldKey: "gitzone", newKey: CLI_NAMESPACE }, { oldKey: "tsdoc", newKey: "@git.zone/tsdoc" }, { oldKey: "npmdocker", newKey: "@git.zone/tsdocker" }, { oldKey: "npmci", newKey: "@ship.zone/szci" }, { oldKey: "szci", newKey: "@ship.zone/szci" }, ]; for (const { oldKey, newKey } of migrations) { if (!isPlainObject(smartconfigJson[oldKey])) { continue; } if (!isPlainObject(smartconfigJson[newKey])) { smartconfigJson[newKey] = smartconfigJson[oldKey]; } else { smartconfigJson[newKey] = { ...smartconfigJson[oldKey], ...smartconfigJson[newKey], }; } delete smartconfigJson[oldKey]; migrated = true; } return migrated; }; const migrateToV2 = (smartconfigJson: Record): boolean => { const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE); let migrated = migrateLegacyReleaseArray(smartconfigJson); const releaseConfig = ensureObject(cliConfig, "release"); const targets = ensureObject(releaseConfig, "targets"); const shipzoneConfig = smartconfigJson["@ship.zone/szci"]; if (isPlainObject(releaseConfig.git) && !isPlainObject(targets.git)) { targets.git = releaseConfig.git; delete releaseConfig.git; migrated = true; } if (isPlainObject(releaseConfig.npm) && !isPlainObject(targets.npm)) { targets.npm = releaseConfig.npm; delete releaseConfig.npm; migrated = true; } if (isPlainObject(releaseConfig.docker)) { targets.docker = isPlainObject(targets.docker) ? { ...releaseConfig.docker, ...targets.docker } : releaseConfig.docker; delete releaseConfig.docker; migrated = true; } if (Array.isArray(releaseConfig.registries)) { const npmTarget = ensureObject(targets, "npm"); if (!Array.isArray(npmTarget.registries)) { npmTarget.registries = releaseConfig.registries; } delete releaseConfig.registries; migrated = true; } if (releaseConfig.accessLevel) { const npmTarget = ensureObject(targets, "npm"); if (!npmTarget.accessLevel) { npmTarget.accessLevel = releaseConfig.accessLevel; } delete releaseConfig.accessLevel; migrated = true; } if (isPlainObject(shipzoneConfig)) { if (shipzoneConfig.npmAccessLevel) { const npmTarget = ensureObject(targets, "npm"); if (!npmTarget.accessLevel) { npmTarget.accessLevel = shipzoneConfig.npmAccessLevel; } delete shipzoneConfig.npmAccessLevel; migrated = true; } if (shipzoneConfig.npmRegistryUrl) { const npmTarget = ensureObject(targets, "npm"); const registry = normalizeRegistryUrl(shipzoneConfig.npmRegistryUrl); const registries = Array.isArray(npmTarget.registries) ? npmTarget.registries : []; if (!registries.includes(registry)) { registries.push(registry); } npmTarget.registries = registries; delete shipzoneConfig.npmRegistryUrl; migrated = true; } } if (Array.isArray(releaseConfig.steps)) { const steps = releaseConfig.steps as string[]; const preflight = ensureObject(releaseConfig, "preflight"); if (steps.includes("test") && preflight.test === undefined) { preflight.test = true; } if (steps.includes("build") && preflight.build === undefined) { preflight.build = true; } if (steps.includes("push")) { const gitTarget = ensureObject(targets, "git"); if (gitTarget.enabled === undefined) { gitTarget.enabled = true; } } if (steps.includes("publishNpm")) { const npmTarget = ensureObject(targets, "npm"); if (npmTarget.enabled === undefined) { npmTarget.enabled = true; } } if (steps.includes("publishDocker")) { const dockerTarget = ensureObject(targets, "docker"); if (dockerTarget.enabled === undefined) { dockerTarget.enabled = true; } dockerTarget.engine = "tsdocker"; } delete releaseConfig.steps; migrated = true; } if (isPlainObject(targets.docker)) { if (targets.docker.images) { delete targets.docker.images; migrated = true; } if (targets.docker.engine !== "tsdocker") { targets.docker.engine = "tsdocker"; migrated = true; } if (!Array.isArray(targets.docker.patterns)) { targets.docker.patterns = []; migrated = true; } } if (releaseConfig.changelog) { delete releaseConfig.changelog; migrated = true; } cliConfig.schemaVersion = 2; return migrated || true; }; const normalizeRegistryUrl = (url: string): string => { let normalizedUrl = url.trim(); if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) { normalizedUrl = `https://${normalizedUrl}`; } return normalizedUrl.endsWith("/") ? normalizedUrl.slice(0, -1) : normalizedUrl; }; export const migrateSmartconfigData = ( smartconfigJson: Record, targetVersion = CURRENT_GITZONE_CLI_SCHEMA_VERSION, ): ISmartconfigMigrationResult => { let migrated = false; migrated = migrateNamespaceKeys(smartconfigJson) || migrated; const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE); const fromVersion = typeof cliConfig.schemaVersion === "number" ? cliConfig.schemaVersion : 1; let currentVersion = fromVersion; if (targetVersion >= 2) { migrated = migrateLegacyReleaseArray(smartconfigJson) || migrated; } if (currentVersion < 2 && targetVersion >= 2) { migrated = migrateToV2(smartconfigJson) || migrated; currentVersion = 2; } if (targetVersion === CURRENT_GITZONE_CLI_SCHEMA_VERSION && cliConfig.schemaVersion !== targetVersion) { cliConfig.schemaVersion = targetVersion; migrated = true; } return { migrated, fromVersion, toVersion: Math.min(targetVersion, CURRENT_GITZONE_CLI_SCHEMA_VERSION), }; };