From 0b3719daec9b6cd5c1bc9c3dea59539473a81cfe Mon Sep 17 00:00:00 2001 From: Aidan Cunniffe Date: Sun, 20 Mar 2022 10:30:35 -0400 Subject: [PATCH] feat: added version command --- src/index.ts | 3 ++ src/workflows/actions.ts | 85 +++++++++++++++++++++++++++++++-- src/workflows/cli-ux.ts | 15 ++++++ src/workflows/commands.ts | 15 ++++++ src/workflows/file-resolvers.ts | 4 +- 5 files changed, 118 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5613390b..b5bcc459 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { addDeleteOperationCommand, addGetOperationCommand, addListOperationCommand, + createVersionCommand, } from "./workflows/commands"; const apiCheckService = newSnykApiCheckService(); @@ -27,6 +28,8 @@ const workflowCommand = new Command("workflow").description( ); workflowCommand.addCommand(createResourceCommand()); +workflowCommand.addCommand(createVersionCommand()); + const operationCommand = new Command("operation").description( "add common operations to an OpenAPI file", ); diff --git a/src/workflows/actions.ts b/src/workflows/actions.ts index 995a420b..9be9e359 100644 --- a/src/workflows/actions.ts +++ b/src/workflows/actions.ts @@ -1,6 +1,6 @@ -import fs from "fs"; +import fs from "fs-extra"; import path from "path"; -import { writeYaml } from "@useoptic/openapi-io"; +import { writeYaml, loadYaml } from "@useoptic/openapi-io"; import { applyTemplate } from "@useoptic/openapi-cli"; import { addCreateOperation } from "./templates/operations/create"; import { addListOperation } from "./templates/operations/list"; @@ -13,7 +13,11 @@ import { resolveResourcesDirectory, resolveResourceVersion, } from "./file-resolvers"; -import { LogUpdatingSpecification } from "./cli-ux"; +import { + LogNewDateVersionSpecification, + LogUpdatingSpecification, +} from "./cli-ux"; +import { OpenAPIV3 } from "@useoptic/openapi-utilities"; export async function createResourceAction(resourceName, pluralResourceName) { // TODO: the SDK should probably help with the generation of new files @@ -50,6 +54,81 @@ export async function createResourceAction(resourceName, pluralResourceName) { fs.writeFileSync(initialSpecFilePath, specYaml); } +export async function promoteVersionAction( + resourceName: string, + targetStability: string, +) { + const stabilityProgression = ["wip", "experimental", "beta", "ga"]; + + if (!stabilityProgression.includes(targetStability)) + throw new Error( + `target stability must be one of ${JSON.stringify(stabilityProgression)}`, + ); + + const specFilePath = await resolveResourceVersion( + process.cwd(), + resourceName, + "latest", + true, + ); + + const currentDateVersion = (() => { + const components = specFilePath.split("/"); + components.pop(); // spec.yaml + return components.pop(); // date string + })(); + + const openAPI: OpenAPIV3.Document = loadYaml( + (await fs.readFile(specFilePath)).toString(), + ) as OpenAPIV3.Document; + + const currentStability = openAPI["x-snyk-api-stability"]; + + if ( + stabilityProgression.indexOf(currentStability) > + stabilityProgression.indexOf(targetStability) + ) + throw new Error( + `stability can not go backwards from ${currentStability} to ${targetStability}`, + ); + + if (formatResourceVersion() === currentDateVersion) + throw new Error( + `can not promote stability on the same day as the latest version ${currentDateVersion}`, + ); + + // advance the stability, while copying everything else over + openAPI["x-snyk-api-stability"] = targetStability; + + const resourcesDirectory = await resolveResourcesDirectory(); + + const newDateVersion = formatResourceVersion(); + + await fs.mkdirSync( + path.join(resourcesDirectory, resourceName, newDateVersion), + { + recursive: true, + }, + ); + + const initialSpecFilePath = path.join( + resourcesDirectory, + resourceName, + newDateVersion, + "spec.yaml", + ); + + const specYaml = writeYaml(openAPI); + fs.writeFileSync(initialSpecFilePath, specYaml); + + LogNewDateVersionSpecification( + resourceName, + newDateVersion, + targetStability, + initialSpecFilePath, + ); +} + export const addCreateOperationAction = buildOperationAction(addCreateOperation); export const addDeleteOperationAction = diff --git a/src/workflows/cli-ux.ts b/src/workflows/cli-ux.ts index 20abd960..0328ba26 100644 --- a/src/workflows/cli-ux.ts +++ b/src/workflows/cli-ux.ts @@ -14,6 +14,21 @@ export function LogUpdatingSpecification( ); } +export function LogNewDateVersionSpecification( + resource: string, + version: string, + stability: string, + filePath: string, +) { + console.log( + chalk.white( + `new version and OpenAPI description for: ${chalk.blue.bold( + resource, + )} ${chalk.green.bold(version)} ${chalk.green.bold(stability)}`, + ) + `\n at ${filePath}:0:0\n`, + ); +} + export function AlreadyInSpec(method: string, path: string) { console.log( chalk.red( diff --git a/src/workflows/commands.ts b/src/workflows/commands.ts index e25aecdb..ed192ae6 100644 --- a/src/workflows/commands.ts +++ b/src/workflows/commands.ts @@ -6,6 +6,7 @@ import { addListOperationAction, addUpdateOperationAction, createResourceAction, + promoteVersionAction, } from "./actions"; export function createResourceCommand() { @@ -67,3 +68,17 @@ function buildOperationCommand(name: string, description: string, action) { return command; } + +export function createVersionCommand() { + const command = new Command("version") + .addArgument(new Argument("", "[resource-name]")) + .addArgument(new Argument("", "stability")) + + .action(async (resourceName, stability) => { + return promoteVersionAction(resourceName, stability); + }); + + command.description("create a new resource"); + + return command; +} diff --git a/src/workflows/file-resolvers.ts b/src/workflows/file-resolvers.ts index 9afa56d1..b666c063 100644 --- a/src/workflows/file-resolvers.ts +++ b/src/workflows/file-resolvers.ts @@ -22,6 +22,7 @@ export async function resolveResourceVersion( workingDirectory: string = process.cwd(), resourceName: string, resourceVersion: string = "latest", + silent: boolean = false, ): Promise { const resources = await resolveResourcesDirectory(); const resourceNameLowerCase = resourceName.toLowerCase(); @@ -53,7 +54,8 @@ export async function resolveResourceVersion( "spec.yaml", ); - LogUpdatingSpecification(resourceName, resourceVersion, finalPath); + if (!silent) + LogUpdatingSpecification(resourceName, resourceVersion, finalPath); return finalPath; }