From d0fe6711f572ed7d19826e93609fad4e6413c27c Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:02:00 -0700 Subject: [PATCH 01/12] build downconverter script --- .../schemas/containers/instances/Instance.yml | 2 +- .../instances/InstanceMigration.yml | 1 + .../schemas/dns/records/RecordTypes.yml | 4 +- .../hubs/integrations/IntegrationAuth.yml | 1 + .../hubs/integrations/IntegrationMeta.yml | 1 + .../spec/StackContainerConfigDeploy.yml | 1 + .../spec/StackContainerConfigIntegrations.yml | 1 + .../spec/StackContainerConfigNetwork.yml | 1 + .../spec/StackContainerConfigResources.yml | 1 + .../spec/StackContainerConfigRuntime.yml | 1 + .../spec/StackContainerConfigScaling.yml | 1 + .../scale/StackContainerScaleThreshold.yml | 1 + package-lock.json | 100 ++++--- package.json | 3 +- util/downconvert.ts | 266 ++++++++++++++++++ 15 files changed, 347 insertions(+), 38 deletions(-) create mode 100644 util/downconvert.ts diff --git a/components/schemas/containers/instances/Instance.yml b/components/schemas/containers/instances/Instance.yml index 3ddcde00..29189e35 100644 --- a/components/schemas/containers/instances/Instance.yml +++ b/components/schemas/containers/instances/Instance.yml @@ -110,4 +110,4 @@ properties: $ref: ../../DateTime.yml deleted: description: The timestamp of when the instance was deleted. - $ref: ../../DateTime.yml + $ref: ../../DateTime.yml \ No newline at end of file diff --git a/components/schemas/containers/instances/InstanceMigration.yml b/components/schemas/containers/instances/InstanceMigration.yml index 36281479..ddd012ae 100644 --- a/components/schemas/containers/instances/InstanceMigration.yml +++ b/components/schemas/containers/instances/InstanceMigration.yml @@ -1,4 +1,5 @@ title: InstanceMigration +x-ogen-name: InstanceMigrationResult type: object description: Information regarding the migration of an instance, such as the server that the instance came from or the server that the instance was moved to. required: diff --git a/components/schemas/dns/records/RecordTypes.yml b/components/schemas/dns/records/RecordTypes.yml index b36987ab..29c8e7b7 100644 --- a/components/schemas/dns/records/RecordTypes.yml +++ b/components/schemas/dns/records/RecordTypes.yml @@ -156,9 +156,7 @@ properties: - type: object properties: deployment: - type: - - object - - "null" + type: object description: Information about the deployment this record points to. required: - environment_id diff --git a/components/schemas/hubs/integrations/IntegrationAuth.yml b/components/schemas/hubs/integrations/IntegrationAuth.yml index 76bb07b0..6dc696e0 100644 --- a/components/schemas/hubs/integrations/IntegrationAuth.yml +++ b/components/schemas/hubs/integrations/IntegrationAuth.yml @@ -1,4 +1,5 @@ title: IntegrationAuth +x-ogen-name: HubIntegrationAuth type: object properties: region: diff --git a/components/schemas/hubs/integrations/IntegrationMeta.yml b/components/schemas/hubs/integrations/IntegrationMeta.yml index e90426d5..8cf88998 100644 --- a/components/schemas/hubs/integrations/IntegrationMeta.yml +++ b/components/schemas/hubs/integrations/IntegrationMeta.yml @@ -1,4 +1,5 @@ title: IntegrationMeta +x-ogen-name: HubIntegrationMeta type: object description: Additional fields that can be requested for an Integration on fetch. properties: diff --git a/components/schemas/stacks/spec/StackContainerConfigDeploy.yml b/components/schemas/stacks/spec/StackContainerConfigDeploy.yml index f5779459..17b56954 100644 --- a/components/schemas/stacks/spec/StackContainerConfigDeploy.yml +++ b/components/schemas/stacks/spec/StackContainerConfigDeploy.yml @@ -1,4 +1,5 @@ title: StackContainerConfigDeploy +x-ogen-name: StackSpecContainerConfigDeploy type: object description: Stack configuration options related to how the container behaves over its lifecycle (startup, shutdown, health checks, etc). required: diff --git a/components/schemas/stacks/spec/StackContainerConfigIntegrations.yml b/components/schemas/stacks/spec/StackContainerConfigIntegrations.yml index de30b9ce..a6c160d8 100644 --- a/components/schemas/stacks/spec/StackContainerConfigIntegrations.yml +++ b/components/schemas/stacks/spec/StackContainerConfigIntegrations.yml @@ -1,4 +1,5 @@ title: StackContainerConfigIntegrations +x-ogen-name: StackSpecContainerConfigIntegrations type: object description: Configuration options for additional integrations/features that Cycle provides. properties: diff --git a/components/schemas/stacks/spec/StackContainerConfigNetwork.yml b/components/schemas/stacks/spec/StackContainerConfigNetwork.yml index a5a48d4d..d7955572 100644 --- a/components/schemas/stacks/spec/StackContainerConfigNetwork.yml +++ b/components/schemas/stacks/spec/StackContainerConfigNetwork.yml @@ -1,4 +1,5 @@ title: StackContainerConfigNetwork +x-ogen-name: StackSpecContainerConfigNetwork description: Stack configuration options related to the container's network. type: object required: diff --git a/components/schemas/stacks/spec/StackContainerConfigResources.yml b/components/schemas/stacks/spec/StackContainerConfigResources.yml index d3e86334..6269a57e 100644 --- a/components/schemas/stacks/spec/StackContainerConfigResources.yml +++ b/components/schemas/stacks/spec/StackContainerConfigResources.yml @@ -1,4 +1,5 @@ title: StackContainerConfigResources +x-ogen-name: StackSpecContainerConfigResources description: Configuration options for container resource limits and reserves. type: object required: diff --git a/components/schemas/stacks/spec/StackContainerConfigRuntime.yml b/components/schemas/stacks/spec/StackContainerConfigRuntime.yml index 3e2ec70b..c1c4e15b 100644 --- a/components/schemas/stacks/spec/StackContainerConfigRuntime.yml +++ b/components/schemas/stacks/spec/StackContainerConfigRuntime.yml @@ -1,4 +1,5 @@ title: StackContainerConfigRuntime +x-ogen-name: StackSpecContainerConfigRuntime description: Configuration options related to how the container behaves while it is running (environment variables, command overrides, kernel capabilities, etc. ) type: object properties: diff --git a/components/schemas/stacks/spec/StackContainerConfigScaling.yml b/components/schemas/stacks/spec/StackContainerConfigScaling.yml index 2d3f9a0d..01d20712 100644 --- a/components/schemas/stacks/spec/StackContainerConfigScaling.yml +++ b/components/schemas/stacks/spec/StackContainerConfigScaling.yml @@ -1,4 +1,5 @@ title: StackContainerConfigScaling +x-ogen-name: StackSpecContainerConfigScaling type: object description: Stack configuration options for auto-scaling. required: diff --git a/components/schemas/stacks/spec/scale/StackContainerScaleThreshold.yml b/components/schemas/stacks/spec/scale/StackContainerScaleThreshold.yml index fdcaf31c..e9fc1928 100644 --- a/components/schemas/stacks/spec/scale/StackContainerScaleThreshold.yml +++ b/components/schemas/stacks/spec/scale/StackContainerScaleThreshold.yml @@ -16,3 +16,4 @@ oneOf: - $ref: ./StackContainerScaleThresholdNetworkConnections.yml - $ref: ./StackContainerScaleThresholdNetworkRequests.yml - $ref: ./StackContainerScaleThresholdNetworkThroughput.yml + - $ref: ./StackContainerScaleThresholdCustom.yml diff --git a/package-lock.json b/package-lock.json index 54023853..5ec8f92d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "@cycleplatform/api-spec", "version": "1.0.0", "license": "Apache-2.0", + "dependencies": { + "yamljs": "^0.3.0" + }, "devDependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", "@redocly/cli": "1.11.0", @@ -278,8 +281,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -297,7 +299,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -419,8 +420,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/core-js": { "version": "3.36.1", @@ -580,8 +580,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.3.3", @@ -616,7 +615,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -675,7 +673,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -684,8 +681,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -859,7 +855,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1137,7 +1132,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1162,7 +1156,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1605,6 +1598,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/stickyfill": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", @@ -1854,8 +1853,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { "version": "7.5.9", @@ -1908,6 +1906,29 @@ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "dev": true }, + "node_modules/yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yamljs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -2141,8 +2162,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "binary-extensions": { "version": "2.3.0", @@ -2154,7 +2174,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2253,8 +2272,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "core-js": { "version": "3.36.1", @@ -2383,8 +2401,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.3", @@ -2409,7 +2426,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2451,7 +2467,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2460,8 +2475,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-binary-path": { "version": "2.1.0", @@ -2596,7 +2610,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2779,7 +2792,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -2803,8 +2815,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "perfect-scrollbar": { "version": "1.5.5", @@ -3117,6 +3128,11 @@ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "stickyfill": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", @@ -3321,8 +3337,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "7.5.9", @@ -3355,6 +3370,25 @@ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "dev": true }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + } + } + }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", diff --git a/package.json b/package.json index 8df897d3..2b9273ae 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "devDependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", "@redocly/cli": "1.11.0", - "@types/node": "^20.12.7" + "@types/node": "^20.12.7", + "yamljs": "^0.3.0" } } diff --git a/util/downconvert.ts b/util/downconvert.ts new file mode 100644 index 00000000..1ac4427b --- /dev/null +++ b/util/downconvert.ts @@ -0,0 +1,266 @@ +type SchemaObject = { + type?: string | string[]; + nullable?: boolean; + oneOf?: SchemaObject[]; + anyOf?: SchemaObject[]; + allOf?: SchemaObject[]; + properties?: Record; + items?: SchemaObject; + [key: string]: any; +}; + +type MediaTypeObject = { + schema?: SchemaObject; + [key: string]: any; +}; + +type ContentObject = Record; + +type OperationObject = { + requestBody?: { + content?: ContentObject; + [key: string]: any; + }; + responses?: Record; + [key: string]: any; +}; + +type PathItemObject = { + [method: string]: OperationObject; +}; + +type ComponentsObject = { + schemas?: Record; + [key: string]: any; +}; + +type OpenAPISpec31 = { + openapi: "3.1.0"; + components?: ComponentsObject; + paths?: Record; + webhooks?: Record; + [key: string]: any; +}; + +type OpenAPISpec30 = { + openapi: "3.0.1"; + components?: ComponentsObject; + paths?: Record; + [key: string]: any; +}; + +export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { + const convertedSpec: OpenAPISpec30 = { ...spec, openapi: "3.0.1" }; + + // Map to store schemas for resolving $ref types + const schemaMap: Record = + convertedSpec.components?.schemas || {}; + + // Adjust schema properties + if (convertedSpec.components?.schemas) { + for (const schemaName in convertedSpec.components.schemas) { + const schema = convertedSpec.components.schemas[schemaName]; + adjustSchema(schema, schemaMap); + } + } + + // Remove or adjust unsupported features + if (convertedSpec.webhooks) { + delete convertedSpec.webhooks; // Webhooks are not supported in OpenAPI 3.0.1 + } + + // Adjust paths + if (convertedSpec.paths) { + for (const path in convertedSpec.paths) { + const pathItem = convertedSpec.paths[path]; + for (const method in pathItem) { + const operation = pathItem[method]; + if (operation.requestBody?.content) { + adjustContent(operation.requestBody.content, schemaMap); + } + if (operation.responses) { + for (const response in operation.responses) { + if (operation.responses[response]?.content) { + adjustContent(operation.responses[response].content!, schemaMap); + } + } + } + } + } + } + + return convertedSpec; +} + +function resolveRefType(ref: string, schemaMap: Record): string | undefined { + const refKey = ref.replace(/^#\/components\/schemas\//, ''); + const referencedSchema = schemaMap[refKey]; + if (referencedSchema && referencedSchema.type) { + return referencedSchema.type; + } + return undefined; + } + + function adjustSchema(schema: SchemaObject, schemaMap: Record): void { + // Remove unsupported properties + delete schema.$schema; + delete schema.$id; + delete schema.$comment; + delete schema.unevaluatedProperties; + delete schema.patternProperties; + delete schema.contentMediaType; + delete schema.contentEncoding; + + // Handle 'const' by converting it to 'enum' + if (schema.const !== undefined) { + schema.enum = [schema.const]; + delete schema.const; + } + + // Handle 'type' as an array including 'null' + if (Array.isArray(schema.type)) { + if (schema.type.includes('null')) { + const nonNullTypes = schema.type.filter(t => t !== 'null'); + if (nonNullTypes.length === 1) { + schema.type = nonNullTypes[0]; + schema.nullable = true; + } else { + schema.anyOf = nonNullTypes.map(type => ({ type })); + delete schema.type; + } + } else if (schema.type.length === 1) { + schema.type = schema.type[0]; // Reduce single-type array to a single value + } + } else if (schema.type === 'null') { + schema.type = 'object'; + schema.nullable = true; + } + + // Remove default if null type is removed + if (schema.default === null && !schema.nullable && schema.type !== 'null' && !schema.anyOf?.some(entry => entry.type === 'null')) { + delete schema.default; + } + + // Handle merging of anyOf/oneOf with $ref and nullable object/type + ['anyOf', 'oneOf'].forEach(keyword => { + if (schema[keyword]) { + let hasRef = false; + let refType: string | undefined; + + const filteredSchemas = schema[keyword].filter(entry => { + if (entry.$ref) { + hasRef = true; + refType = resolveRefType(entry.$ref, schemaMap) || refType; + return true; + } else if (entry.type === 'null') { + return false; // Ignore null type, do not include in anyOf/oneOf + } + return true; + }); + + if (filteredSchemas.length === 1) { + // If there's only one schema left, use it directly + Object.assign(schema, filteredSchemas[0]); + delete schema[keyword]; + } else if (filteredSchemas.length > 0) { + schema[keyword] = filteredSchemas; + // Ensure type isn't added unnecessarily + if (keyword === 'items' && (schema.oneOf || schema.anyOf)) { + delete schema.type; + } + } else { + delete schema[keyword]; + } + } + }); + + // Ensure type is not reintroduced as an array + if (Array.isArray(schema.type)) { + schema.type = schema.type.length === 1 ? schema.type[0] : schema.type; + } + + // Ensure no unnecessary 'type' in items with oneOf/anyOf + if (schema.items && (schema.items.oneOf || schema.items.anyOf)) { + delete schema.items.type; + } + + // Convert examples array to single example + if (schema.examples && Array.isArray(schema.examples)) { + schema.example = schema.examples.join('\n'); + delete schema.examples; + } + + // Recursively adjust nested schemas + if (schema.properties) { + Object.values(schema.properties).forEach(propSchema => adjustSchema(propSchema, schemaMap)); + } + + if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { + adjustSchema(schema.additionalProperties, schemaMap); + } + + if (schema.items && typeof schema.items === 'object') { + adjustSchema(schema.items, schemaMap); + } + + // Handle `allOf` schemas + if (schema.allOf) { + schema.allOf.forEach(entry => adjustSchema(entry, schemaMap)); + } + } + + function adjustContent(content: ContentObject, schemaMap: Record): void { + for (const contentType in content) { + const mediaTypeObject = content[contentType]; + if (mediaTypeObject.schema) { + adjustSchema(mediaTypeObject.schema, schemaMap); + } + if (mediaTypeObject.examples && Array.isArray(mediaTypeObject.examples)) { + mediaTypeObject.example = mediaTypeObject.examples.join('\n'); + delete mediaTypeObject.examples; + } + } + } + +import { promises as fs } from "fs"; +import path from "path"; +import YAML from "yamljs"; + +async function readYamlFile(filePath: string): Promise { + try { + // Resolve the full path to ensure it works with relative paths + const fullPath = path.resolve(filePath); + + // Read the YAML file asynchronously + const fileContents = await fs.readFile(fullPath, "utf8"); + + // Parse YAML into JSON + const parsedData = YAML.parse(fileContents); + + return parsedData; + } catch (err) { + throw new Error(`Error reading or parsing YAML file: ${err.message}`); + } +} + +async function writeJsonToFile(filePath: string, data: object): Promise { + try { + // Resolve the full path to ensure it works with relative paths + const fullPath = path.resolve(filePath); + + // Convert the data to a JSON string + const jsonString = JSON.stringify(data, null, 2); // Pretty print with 2-space indentation + + // Write the JSON string to the specified file asynchronously + await fs.writeFile(fullPath, jsonString, "utf8"); + console.log(`Successfully wrote to ${fullPath}`); + } catch (err) { + throw new Error(`Error writing JSON to file: ${err.message}`); + } +} + +readYamlFile("./dist/platform.yml") + .then((r) => downconvertOpenAPI31To30(r as any)) + .then((spec) => writeJsonToFile("./dist/platform-3.0.3.json", spec)); + +// downconvertOpenAPI31To30() From 3890fb97b9c0761125770160c6d203ae6537ffa9 Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Tue, 3 Sep 2024 08:25:42 -0700 Subject: [PATCH 02/12] improved downconverter --- util/downconvert.ts | 83 +++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/util/downconvert.ts b/util/downconvert.ts index 1ac4427b..079c1dfe 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -92,16 +92,7 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { return convertedSpec; } -function resolveRefType(ref: string, schemaMap: Record): string | undefined { - const refKey = ref.replace(/^#\/components\/schemas\//, ''); - const referencedSchema = schemaMap[refKey]; - if (referencedSchema && referencedSchema.type) { - return referencedSchema.type; - } - return undefined; - } - - function adjustSchema(schema: SchemaObject, schemaMap: Record): void { +function adjustSchema(schema: SchemaObject, schemaMap: Record): void { // Remove unsupported properties delete schema.$schema; delete schema.$id; @@ -117,7 +108,43 @@ function resolveRefType(ref: string, schemaMap: Record): s delete schema.const; } - // Handle 'type' as an array including 'null' + // Handle anyOf/oneOf + ['anyOf', 'oneOf'].forEach(keyword => { + if (schema[keyword]) { + let hasNullType = false; + + const filteredSchemas = schema[keyword].map(entry => { + if (entry.$ref) { + return entry; // Keep the $ref as-is, no need for allOf if there's no null handling + } else if (entry.type === 'null') { + hasNullType = true; + return undefined; // We'll handle null types separately + } + return entry; + }).filter(Boolean); + + if (hasNullType) { + schema.nullable = true; + } + + if (filteredSchemas.length === 1) { + // If there's only one schema left, use it directly + Object.assign(schema, filteredSchemas[0]); + delete schema[keyword]; + } else if (filteredSchemas.length > 1) { + schema[keyword] = filteredSchemas; + delete schema.type; // Remove the base type if using anyOf/oneOf + } + } + }); + + // Handle standalone $ref with nullable + if (schema.$ref && schema.nullable) { + schema.allOf = [{ $ref: schema.$ref }]; + delete schema.$ref; + } + + // Handle type as an array including 'null' if (Array.isArray(schema.type)) { if (schema.type.includes('null')) { const nonNullTypes = schema.type.filter(t => t !== 'null'); @@ -126,6 +153,7 @@ function resolveRefType(ref: string, schemaMap: Record): s schema.nullable = true; } else { schema.anyOf = nonNullTypes.map(type => ({ type })); + schema.anyOf.push({ type: nonNullTypes[0], nullable: true }); delete schema.type; } } else if (schema.type.length === 1) { @@ -141,39 +169,6 @@ function resolveRefType(ref: string, schemaMap: Record): s delete schema.default; } - // Handle merging of anyOf/oneOf with $ref and nullable object/type - ['anyOf', 'oneOf'].forEach(keyword => { - if (schema[keyword]) { - let hasRef = false; - let refType: string | undefined; - - const filteredSchemas = schema[keyword].filter(entry => { - if (entry.$ref) { - hasRef = true; - refType = resolveRefType(entry.$ref, schemaMap) || refType; - return true; - } else if (entry.type === 'null') { - return false; // Ignore null type, do not include in anyOf/oneOf - } - return true; - }); - - if (filteredSchemas.length === 1) { - // If there's only one schema left, use it directly - Object.assign(schema, filteredSchemas[0]); - delete schema[keyword]; - } else if (filteredSchemas.length > 0) { - schema[keyword] = filteredSchemas; - // Ensure type isn't added unnecessarily - if (keyword === 'items' && (schema.oneOf || schema.anyOf)) { - delete schema.type; - } - } else { - delete schema[keyword]; - } - } - }); - // Ensure type is not reintroduced as an array if (Array.isArray(schema.type)) { schema.type = schema.type.length === 1 ? schema.type[0] : schema.type; From 8514dfdc5717ad84a5555587b184d39a288c1dce Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:36:49 -0800 Subject: [PATCH 03/12] updates to downconverter --- .../origins/cycleUpload/CycleUploadOrigin.yml | 3 + package-lock.json | 58 +++-- util/downconvert.ts | 239 +++++++++--------- 3 files changed, 170 insertions(+), 130 deletions(-) diff --git a/components/schemas/images/origins/cycleUpload/CycleUploadOrigin.yml b/components/schemas/images/origins/cycleUpload/CycleUploadOrigin.yml index d1dc04f1..bd2baed9 100644 --- a/components/schemas/images/origins/cycleUpload/CycleUploadOrigin.yml +++ b/components/schemas/images/origins/cycleUpload/CycleUploadOrigin.yml @@ -5,6 +5,9 @@ description: | In order to utilize this image origin type, a tar file of an OCI compliant image will need to be generated and pushed directly to the factory. The authentication token is generated when this image is created, and expires at the provided time. Once you have a token, it can be uploaded as multipart form data under the key `file.tar`, directly to the factory at `https://factory.cycle.io:9414/v1/images//upload?hub-id=&token=`. +required: + - type + - details properties: type: type: string diff --git a/package-lock.json b/package-lock.json index 5ec8f92d..ef540411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,11 @@ "name": "@cycleplatform/api-spec", "version": "1.0.0", "license": "Apache-2.0", - "dependencies": { - "yamljs": "^0.3.0" - }, "devDependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", "@redocly/cli": "1.11.0", - "@types/node": "^20.12.7" + "@types/node": "^20.12.7", + "yamljs": "^0.3.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -281,7 +279,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -299,6 +298,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -420,7 +420,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/core-js": { "version": "3.36.1", @@ -580,7 +581,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -615,6 +617,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -673,6 +676,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -681,7 +685,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -855,6 +860,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1132,6 +1138,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "dependencies": { "wrappy": "1" } @@ -1156,6 +1163,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -1602,6 +1610,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/stickyfill": { @@ -1853,7 +1862,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "node_modules/ws": { "version": "7.5.9", @@ -1910,6 +1920,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -1924,6 +1935,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -2162,7 +2174,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "binary-extensions": { "version": "2.3.0", @@ -2174,6 +2187,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2272,7 +2286,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "core-js": { "version": "3.36.1", @@ -2401,7 +2416,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.3", @@ -2426,6 +2442,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2467,6 +2484,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2475,7 +2493,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -2610,6 +2629,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2792,6 +2812,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -2815,7 +2836,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "perfect-scrollbar": { "version": "1.5.5", @@ -3131,7 +3153,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, "stickyfill": { "version": "1.1.1", @@ -3337,7 +3360,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "ws": { "version": "7.5.9", @@ -3374,6 +3398,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dev": true, "requires": { "argparse": "^1.0.7", "glob": "^7.0.5" @@ -3383,6 +3408,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } diff --git a/util/downconvert.ts b/util/downconvert.ts index 079c1dfe..81ba86d0 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -92,130 +92,141 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { return convertedSpec; } -function adjustSchema(schema: SchemaObject, schemaMap: Record): void { - // Remove unsupported properties - delete schema.$schema; - delete schema.$id; - delete schema.$comment; - delete schema.unevaluatedProperties; - delete schema.patternProperties; - delete schema.contentMediaType; - delete schema.contentEncoding; - - // Handle 'const' by converting it to 'enum' - if (schema.const !== undefined) { - schema.enum = [schema.const]; - delete schema.const; +function adjustSchema( + schema: SchemaObject, + schemaMap: Record +): void { + // Remove unsupported properties + delete schema.$schema; + delete schema.$id; + delete schema.$comment; + delete schema.unevaluatedProperties; + delete schema.patternProperties; + delete schema.contentMediaType; + delete schema.contentEncoding; + + // Handle 'const' by converting it to 'enum' + if (schema.const !== undefined) { + schema.enum = [schema.const]; + delete schema.const; + } + + // Handle type arrays that include 'null' + if (Array.isArray(schema.type) && schema.type.includes("null")) { + const nonNullTypes = schema.type.filter((t) => t !== "null"); + if (nonNullTypes.length === 1) { + schema.type = nonNullTypes[0]; + schema.nullable = true; + } else if (nonNullTypes.length > 1) { + schema.anyOf = nonNullTypes.map((type) => ({ type })); + schema.anyOf.push({ type: "null" }); + delete schema.type; } - - // Handle anyOf/oneOf - ['anyOf', 'oneOf'].forEach(keyword => { - if (schema[keyword]) { - let hasNullType = false; - - const filteredSchemas = schema[keyword].map(entry => { - if (entry.$ref) { - return entry; // Keep the $ref as-is, no need for allOf if there's no null handling - } else if (entry.type === 'null') { - hasNullType = true; - return undefined; // We'll handle null types separately - } - return entry; - }).filter(Boolean); - - if (hasNullType) { - schema.nullable = true; + } else if (schema.type === "null") { + schema.type = "object"; // Default type if only 'null' is specified + schema.nullable = true; + } + + // Recursively handle `oneOf` or `anyOf` and ensure proper type conversion + ["oneOf", "anyOf"].forEach((keyword) => { + if (schema[keyword]) { + let refEntry: SchemaObject | null = null; + let hasNullType = false; + + schema[keyword] = schema[keyword]!.map((entry): SchemaObject => { + if (entry.$ref) { + refEntry = entry; // Keep track of the $ref entry + } else if (entry.type === "null") { + hasNullType = true; // Keep track of null type entries + return null; // Mark for removal } - - if (filteredSchemas.length === 1) { - // If there's only one schema left, use it directly - Object.assign(schema, filteredSchemas[0]); - delete schema[keyword]; - } else if (filteredSchemas.length > 1) { - schema[keyword] = filteredSchemas; - delete schema.type; // Remove the base type if using anyOf/oneOf + + // Recursively adjust nested schemas in oneOf/anyOf + adjustSchema(entry, schemaMap); + + return entry; + }).filter((entry): entry is SchemaObject => entry !== null); + + // If we have a $ref and a `null` type, merge them into a single nullable reference + if (refEntry && hasNullType) { + const refSchemaName = refEntry.$ref.split("/").pop()!; + const refSchema = schemaMap[refSchemaName]; + + // Convert into a single schema with $ref, nullable, and type + schema.$ref = refEntry.$ref; + schema.nullable = true; + + // Extract type from the referenced schema if available + if (refSchema && refSchema.type) { + schema.type = refSchema.type; } + + delete schema[keyword]; // Remove `oneOf` or `anyOf` since it's been merged } - }); - - // Handle standalone $ref with nullable - if (schema.$ref && schema.nullable) { - schema.allOf = [{ $ref: schema.$ref }]; - delete schema.$ref; - } - - // Handle type as an array including 'null' - if (Array.isArray(schema.type)) { - if (schema.type.includes('null')) { - const nonNullTypes = schema.type.filter(t => t !== 'null'); - if (nonNullTypes.length === 1) { - schema.type = nonNullTypes[0]; - schema.nullable = true; - } else { - schema.anyOf = nonNullTypes.map(type => ({ type })); - schema.anyOf.push({ type: nonNullTypes[0], nullable: true }); - delete schema.type; - } - } else if (schema.type.length === 1) { - schema.type = schema.type[0]; // Reduce single-type array to a single value + + // If only one schema remains after adjustments, merge it back into the parent + if (schema[keyword] && schema[keyword].length === 1) { + const singleSchema = schema[keyword][0]; + delete schema[keyword]; + Object.assign(schema, singleSchema); } - } else if (schema.type === 'null') { - schema.type = 'object'; - schema.nullable = true; - } - - // Remove default if null type is removed - if (schema.default === null && !schema.nullable && schema.type !== 'null' && !schema.anyOf?.some(entry => entry.type === 'null')) { - delete schema.default; - } - - // Ensure type is not reintroduced as an array - if (Array.isArray(schema.type)) { - schema.type = schema.type.length === 1 ? schema.type[0] : schema.type; - } - - // Ensure no unnecessary 'type' in items with oneOf/anyOf - if (schema.items && (schema.items.oneOf || schema.items.anyOf)) { - delete schema.items.type; } - - // Convert examples array to single example - if (schema.examples && Array.isArray(schema.examples)) { - schema.example = schema.examples.join('\n'); - delete schema.examples; - } - - // Recursively adjust nested schemas - if (schema.properties) { - Object.values(schema.properties).forEach(propSchema => adjustSchema(propSchema, schemaMap)); - } - - if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { - adjustSchema(schema.additionalProperties, schemaMap); - } - - if (schema.items && typeof schema.items === 'object') { - adjustSchema(schema.items, schemaMap); - } - - // Handle `allOf` schemas - if (schema.allOf) { - schema.allOf.forEach(entry => adjustSchema(entry, schemaMap)); + }); + + // Recursively adjust nested schemas in properties, additionalProperties, items, allOf + if (schema.properties) { + Object.values(schema.properties).forEach((propSchema) => + adjustSchema(propSchema, schemaMap) + ); + } + + if ( + schema.additionalProperties && + typeof schema.additionalProperties === "object" + ) { + adjustSchema(schema.additionalProperties, schemaMap); + } + + if (schema.items && typeof schema.items === "object") { + adjustSchema(schema.items, schemaMap); + } + + if (schema.allOf) { + schema.allOf.forEach((entry) => adjustSchema(entry, schemaMap)); + } + + // Convert examples array to single example + if (schema.examples && Array.isArray(schema.examples)) { + schema.example = schema.examples.join("\n"); + delete schema.examples; + } + + // Ensure no type is an array + if (Array.isArray(schema.type)) { + if (schema.type.length === 1) { + schema.type = schema.type[0]; // If there's only one type, convert it to a string + } else { + schema.anyOf = schema.type.map((type) => ({ type })); + delete schema.type; } } - - function adjustContent(content: ContentObject, schemaMap: Record): void { - for (const contentType in content) { - const mediaTypeObject = content[contentType]; - if (mediaTypeObject.schema) { - adjustSchema(mediaTypeObject.schema, schemaMap); - } - if (mediaTypeObject.examples && Array.isArray(mediaTypeObject.examples)) { - mediaTypeObject.example = mediaTypeObject.examples.join('\n'); - delete mediaTypeObject.examples; - } +} + +function adjustContent( + content: ContentObject, + schemaMap: Record +): void { + for (const contentType in content) { + const mediaTypeObject = content[contentType]; + if (mediaTypeObject.schema) { + adjustSchema(mediaTypeObject.schema, schemaMap); + } + if (mediaTypeObject.examples && Array.isArray(mediaTypeObject.examples)) { + mediaTypeObject.example = mediaTypeObject.examples.join("\n"); + delete mediaTypeObject.examples; } } +} import { promises as fs } from "fs"; import path from "path"; From c8d0acf58016a58f4ce5edc532637243a6dd791b Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:15:15 -0800 Subject: [PATCH 04/12] fic image source types --- components/schemas/images/sources/BucketImageSourceType.yml | 4 ++-- components/schemas/images/sources/DirectImageSourceType.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/schemas/images/sources/BucketImageSourceType.yml b/components/schemas/images/sources/BucketImageSourceType.yml index 8b8a6f19..9d6e3928 100644 --- a/components/schemas/images/sources/BucketImageSourceType.yml +++ b/components/schemas/images/sources/BucketImageSourceType.yml @@ -1,8 +1,8 @@ title: BucketImageSource type: object required: - - id - - origin + - type + - details properties: type: type: string diff --git a/components/schemas/images/sources/DirectImageSourceType.yml b/components/schemas/images/sources/DirectImageSourceType.yml index f09110ed..fd0eeda6 100644 --- a/components/schemas/images/sources/DirectImageSourceType.yml +++ b/components/schemas/images/sources/DirectImageSourceType.yml @@ -1,8 +1,8 @@ title: DirectImageSource type: object required: - - id - - origin + - type + - details properties: type: type: string From ecf239c7be0509564e31db6acdffbfe8fe9be8cb Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:46:36 -0800 Subject: [PATCH 05/12] improve image typing --- .../schemas/images/sources/StackImageSourceType.yml | 4 ++-- stackspec/schema/StackSpecContainerConfigDeploy.yml | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/components/schemas/images/sources/StackImageSourceType.yml b/components/schemas/images/sources/StackImageSourceType.yml index affec15a..c2545a28 100644 --- a/components/schemas/images/sources/StackImageSourceType.yml +++ b/components/schemas/images/sources/StackImageSourceType.yml @@ -1,8 +1,8 @@ title: StackImageSource type: object required: - - id - - origin + - type + - details properties: type: type: string diff --git a/stackspec/schema/StackSpecContainerConfigDeploy.yml b/stackspec/schema/StackSpecContainerConfigDeploy.yml index 60980cbf..a8dda0a0 100644 --- a/stackspec/schema/StackSpecContainerConfigDeploy.yml +++ b/stackspec/schema/StackSpecContainerConfigDeploy.yml @@ -156,14 +156,6 @@ properties: description: Signals that should be sent to the container on shutdown. items: type: string - enum: - - SIGTERM - - SIGINT - - SIGUSR1 - - SIGUSR2 - - SIGHUB - - SIGKILL - - SIGQUIT - $ref: StackVariable.yml - $ref: StackVariable.yml startup: From f2f2fb9cb90e0063c19a74d45b48a118e1b2b385 Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Mon, 4 Nov 2024 09:53:58 -0800 Subject: [PATCH 06/12] required fixes for downconvert + updated logic --- .../config/types/v1/V1LbConfigRouter.yml | 1 - .../schemas/monitoring/events/EventType.yml | 1 - stackspec/schema/StackSpecContainer.yml | 1 + .../types/v1/StackSpecV1LbRouterConfig.yml | 1 - util/downconvert.ts | 89 +++++++++++++++---- 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbConfigRouter.yml b/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbConfigRouter.yml index 18c6a41a..18b3c2c0 100644 --- a/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbConfigRouter.yml +++ b/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbConfigRouter.yml @@ -74,7 +74,6 @@ properties: type: integer description: If a destination is unavailable, retry up to [x] times, instead of immediately failing with a 503/504 error. destination_prioritization: - default: null oneOf: - type: string description: > diff --git a/components/schemas/monitoring/events/EventType.yml b/components/schemas/monitoring/events/EventType.yml index af9c65ce..8ea49cc3 100644 --- a/components/schemas/monitoring/events/EventType.yml +++ b/components/schemas/monitoring/events/EventType.yml @@ -14,7 +14,6 @@ enum: - container.instance.healthcheck.recovered - container.instance.volume.extend.failed - container.instance.healthcheck.restarted - - container.instance.migration.failed - container.instance.migration.completed - container.instance.migration.failed - container.instance.network.interfaces.create.failed diff --git a/stackspec/schema/StackSpecContainer.yml b/stackspec/schema/StackSpecContainer.yml index 65c2c305..a114fe02 100644 --- a/stackspec/schema/StackSpecContainer.yml +++ b/stackspec/schema/StackSpecContainer.yml @@ -11,6 +11,7 @@ properties: type: string description: The human-readable name of this container. image: + x-ogen-name: StackSpecContainerImageProperty description: Details about the image used for this container. oneOf: - $ref: StackSpecContainerImage.yml diff --git a/stackspec/schema/services/loadbalancer/types/v1/StackSpecV1LbRouterConfig.yml b/stackspec/schema/services/loadbalancer/types/v1/StackSpecV1LbRouterConfig.yml index 66161a09..d19c8715 100644 --- a/stackspec/schema/services/loadbalancer/types/v1/StackSpecV1LbRouterConfig.yml +++ b/stackspec/schema/services/loadbalancer/types/v1/StackSpecV1LbRouterConfig.yml @@ -95,7 +95,6 @@ properties: - type: integer - $ref: ../../../../StackVariable.yml destination_prioritization: - default: null oneOf: - type: string description: > diff --git a/util/downconvert.ts b/util/downconvert.ts index 81ba86d0..32a4ccff 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -56,7 +56,7 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { const schemaMap: Record = convertedSpec.components?.schemas || {}; - // Adjust schema properties + // Adjust schema properties in components if (convertedSpec.components?.schemas) { for (const schemaName in convertedSpec.components.schemas) { const schema = convertedSpec.components.schemas[schemaName]; @@ -75,9 +75,13 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { const pathItem = convertedSpec.paths[path]; for (const method in pathItem) { const operation = pathItem[method]; + + // Handle requestBody content if (operation.requestBody?.content) { adjustContent(operation.requestBody.content, schemaMap); } + + // Handle response content if (operation.responses) { for (const response in operation.responses) { if (operation.responses[response]?.content) { @@ -85,6 +89,15 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { } } } + + // Handle parameters in each operation + if (operation.parameters) { + operation.parameters.forEach((parameter) => { + if (parameter.schema) { + adjustSchema(parameter.schema, schemaMap); + } + }); + } } } } @@ -105,6 +118,9 @@ function adjustSchema( delete schema.contentMediaType; delete schema.contentEncoding; + // Remove examples property + delete schema.examples; + // Handle 'const' by converting it to 'enum' if (schema.const !== undefined) { schema.enum = [schema.const]; @@ -133,11 +149,11 @@ function adjustSchema( let refEntry: SchemaObject | null = null; let hasNullType = false; - schema[keyword] = schema[keyword]!.map((entry): SchemaObject => { + schema[keyword] = schema[keyword]!.map((entry): SchemaObject | null => { if (entry.$ref) { - refEntry = entry; // Keep track of the $ref entry + refEntry = entry; } else if (entry.type === "null") { - hasNullType = true; // Keep track of null type entries + hasNullType = true; return null; // Mark for removal } @@ -149,11 +165,11 @@ function adjustSchema( // If we have a $ref and a `null` type, merge them into a single nullable reference if (refEntry && hasNullType) { - const refSchemaName = refEntry.$ref.split("/").pop()!; + const refSchemaName = (refEntry as SchemaObject).$ref.split("/").pop()!; const refSchema = schemaMap[refSchemaName]; // Convert into a single schema with $ref, nullable, and type - schema.$ref = refEntry.$ref; + schema.$ref = (refEntry as SchemaObject).$ref; schema.nullable = true; // Extract type from the referenced schema if available @@ -164,6 +180,42 @@ function adjustSchema( delete schema[keyword]; // Remove `oneOf` or `anyOf` since it's been merged } + // Flatten if `oneOf` contains only primitive types that are identical + if ( + schema[keyword] && + schema[keyword]!.length >= 2 && + schema[keyword]!.every((entry) => typeof entry.type === "string" && entry.type === schema[keyword]![0].type) + ) { + schema.type = schema[keyword]![0].type; + delete schema[keyword]; + } + + // Flatten if `oneOf` contains only `$ref` entries that resolve to the same primitive type + if ( + schema[keyword] && + schema[keyword]!.length >= 2 && + schema[keyword]!.every((entry) => + entry.$ref && + schemaMap[entry.$ref.split("/").pop()!]?.type === schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type + ) + ) { + schema.type = schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type; + delete schema[keyword]; + } + + // Flatten if `oneOf` contains mixed `$ref` and primitive types that are identical + if ( + schema[keyword] && + schema[keyword]!.length >= 2 && + schema[keyword]!.every((entry) => + (entry.$ref && schemaMap[entry.$ref.split("/").pop()!]?.type === "string") || + (typeof entry.type === "string" && entry.type === "string") + ) + ) { + schema.type = "string"; + delete schema[keyword]; + } + // If only one schema remains after adjustments, merge it back into the parent if (schema[keyword] && schema[keyword].length === 1) { const singleSchema = schema[keyword][0]; @@ -173,11 +225,11 @@ function adjustSchema( } }); - // Recursively adjust nested schemas in properties, additionalProperties, items, allOf + // Recursively adjust nested schemas in properties, additionalProperties, items, allOf, etc. if (schema.properties) { - Object.values(schema.properties).forEach((propSchema) => - adjustSchema(propSchema, schemaMap) - ); + Object.entries(schema.properties).forEach(([key, propSchema]) => { + adjustSchema(propSchema, schemaMap); + }); } if ( @@ -195,11 +247,18 @@ function adjustSchema( schema.allOf.forEach((entry) => adjustSchema(entry, schemaMap)); } - // Convert examples array to single example - if (schema.examples && Array.isArray(schema.examples)) { - schema.example = schema.examples.join("\n"); - delete schema.examples; - } + // Adjust deeply nested `examples` in properties, items, allOf, oneOf, anyOf + ["properties", "items", "allOf", "oneOf", "anyOf"].forEach((keyword) => { + if (schema[keyword]) { + if (Array.isArray(schema[keyword])) { + schema[keyword].forEach((nestedSchema) => + adjustSchema(nestedSchema, schemaMap) + ); + } else if (typeof schema[keyword] === "object") { + adjustSchema(schema[keyword], schemaMap); + } + } + }); // Ensure no type is an array if (Array.isArray(schema.type)) { From 3a0649142786276f6c25b3d9a1dab0285c8d7d1d Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:13:23 -0800 Subject: [PATCH 07/12] dont flatten if discriminator field is present --- util/downconvert.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/util/downconvert.ts b/util/downconvert.ts index 32a4ccff..ff85529d 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -4,6 +4,10 @@ type SchemaObject = { oneOf?: SchemaObject[]; anyOf?: SchemaObject[]; allOf?: SchemaObject[]; + discriminator?: { + propertyName: string; + mapping?: Record; + }; properties?: Record; items?: SchemaObject; [key: string]: any; @@ -184,7 +188,12 @@ function adjustSchema( if ( schema[keyword] && schema[keyword]!.length >= 2 && - schema[keyword]!.every((entry) => typeof entry.type === "string" && entry.type === schema[keyword]![0].type) + schema[keyword]!.every( + (entry) => + typeof entry.type === "string" && + entry.type === schema[keyword]![0].type + ) && + !schema.discriminator ) { schema.type = schema[keyword]![0].type; delete schema[keyword]; @@ -194,12 +203,16 @@ function adjustSchema( if ( schema[keyword] && schema[keyword]!.length >= 2 && - schema[keyword]!.every((entry) => - entry.$ref && - schemaMap[entry.$ref.split("/").pop()!]?.type === schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type - ) + schema[keyword]!.every( + (entry) => + entry.$ref && + schemaMap[entry.$ref.split("/").pop()!]?.type === + schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type + ) && + !schema.discriminator ) { - schema.type = schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type; + schema.type = + schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type; delete schema[keyword]; } @@ -207,10 +220,13 @@ function adjustSchema( if ( schema[keyword] && schema[keyword]!.length >= 2 && - schema[keyword]!.every((entry) => - (entry.$ref && schemaMap[entry.$ref.split("/").pop()!]?.type === "string") || - (typeof entry.type === "string" && entry.type === "string") - ) + schema[keyword]!.every( + (entry) => + (entry.$ref && + schemaMap[entry.$ref.split("/").pop()!]?.type === "string") || + (typeof entry.type === "string" && entry.type === "string") + ) && + !schema.discriminator ) { schema.type = "string"; delete schema[keyword]; From c9a46111ac52e8fd405d478585f0bd8d3287bdea Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:01:47 -0800 Subject: [PATCH 08/12] even better downconverter function --- .../hubs/integrations/IntegrationAuth.yml | 1 - .../schema/StackSpecContainerConfigDeploy.yml | 8 + util/downconvert.ts | 494 +++++++++--------- 3 files changed, 263 insertions(+), 240 deletions(-) diff --git a/components/schemas/hubs/integrations/IntegrationAuth.yml b/components/schemas/hubs/integrations/IntegrationAuth.yml index 6dc696e0..76bb07b0 100644 --- a/components/schemas/hubs/integrations/IntegrationAuth.yml +++ b/components/schemas/hubs/integrations/IntegrationAuth.yml @@ -1,5 +1,4 @@ title: IntegrationAuth -x-ogen-name: HubIntegrationAuth type: object properties: region: diff --git a/stackspec/schema/StackSpecContainerConfigDeploy.yml b/stackspec/schema/StackSpecContainerConfigDeploy.yml index a8dda0a0..60980cbf 100644 --- a/stackspec/schema/StackSpecContainerConfigDeploy.yml +++ b/stackspec/schema/StackSpecContainerConfigDeploy.yml @@ -156,6 +156,14 @@ properties: description: Signals that should be sent to the container on shutdown. items: type: string + enum: + - SIGTERM + - SIGINT + - SIGUSR1 + - SIGUSR2 + - SIGHUB + - SIGKILL + - SIGQUIT - $ref: StackVariable.yml - $ref: StackVariable.yml startup: diff --git a/util/downconvert.ts b/util/downconvert.ts index ff85529d..67861cab 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -1,24 +1,26 @@ -type SchemaObject = { - type?: string | string[]; - nullable?: boolean; - oneOf?: SchemaObject[]; - anyOf?: SchemaObject[]; - allOf?: SchemaObject[]; - discriminator?: { - propertyName: string; - mapping?: Record; - }; - properties?: Record; - items?: SchemaObject; +type OpenAPISpec31 = { + openapi: "3.1.0"; + components?: ComponentsObject; + paths?: Record; + webhooks?: Record; [key: string]: any; }; -type MediaTypeObject = { - schema?: SchemaObject; +type OpenAPISpec30 = { + openapi: "3.0.1"; + components?: ComponentsObject; + paths?: Record; [key: string]: any; }; -type ContentObject = Record; +type ComponentsObject = { + schemas?: Record; + [key: string]: any; +}; + +type PathItemObject = { + [method: string]: OperationObject; +}; type OperationObject = { requestBody?: { @@ -26,53 +28,247 @@ type OperationObject = { [key: string]: any; }; responses?: Record; + parameters?: Array<{ + schema?: SchemaObject; + [key: string]: any; + }>; [key: string]: any; }; -type PathItemObject = { - [method: string]: OperationObject; -}; +type ContentObject = Record; -type ComponentsObject = { - schemas?: Record; +type MediaTypeObject = { + schema?: SchemaObject; [key: string]: any; }; -type OpenAPISpec31 = { - openapi: "3.1.0"; - components?: ComponentsObject; - paths?: Record; - webhooks?: Record; +type SchemaObject = { + type?: string | string[]; + nullable?: boolean; + oneOf?: SchemaObject[]; + anyOf?: SchemaObject[]; + allOf?: SchemaObject[]; + $ref?: string; + properties?: Record; + items?: SchemaObject; + required?: string[]; [key: string]: any; }; -type OpenAPISpec30 = { - openapi: "3.0.1"; - components?: ComponentsObject; - paths?: Record; +class Schema { + type?: string; + nullable?: boolean; + oneOf?: Schema[]; + anyOf?: Schema[]; + allOf?: Schema[]; + $ref?: string; + properties?: Record; + items?: Schema; + required?: string[]; + additionalProperties?: Schema; + discriminator?: { + propertyName: string; + mapping?: Record; + }; [key: string]: any; -}; + constructor(schema: Partial) { + Object.assign(this, schema); + + if (Array.isArray(schema.type)) { + this.handleTypeArray(schema.type); + } + + if (schema.oneOf) { + this.oneOf = schema.oneOf.map((entry) => new Schema(entry)); + } + if (schema.anyOf) { + this.anyOf = schema.anyOf.map((entry) => new Schema(entry)); + } + if (schema.allOf) { + this.allOf = schema.allOf.map((entry) => new Schema(entry)); + } + if (schema.properties) { + this.properties = {}; + for (const key in schema.properties) { + this.properties[key] = new Schema(schema.properties[key]); + } + } + if (schema.items) { + this.items = new Schema(schema.items); + } + if (schema.additionalProperties) { + this.additionalProperties = new Schema(schema.additionalProperties); + } + } + + // Handle `type` when provided as an array + handleTypeArray(types: string[]) { + if (types.includes("null")) { + this.nullable = true; + this.type = types.filter((type) => type !== "null")[0] || "object"; + } else { + this.type = types[0]; + } + } + + // Method to adjust the schema for OpenAPI 3.0.3 compatibility + adjust(schemaMap: Record) { + this.removeUnsupportedProperties(); + this.handleConstAsEnum(); + this.flattenAnyOfOrOneOfWithRefAndNull(schemaMap); + this.flattenIdenticalReferences(schemaMap); + this.adjustNestedSchemas(schemaMap); + } + + // Remove unsupported properties in OpenAPI 3.0.3 + removeUnsupportedProperties() { + delete this.$schema; + delete this.$id; + delete this.$comment; + delete this.unevaluatedProperties; + delete this.patternProperties; + delete this.contentMediaType; + delete this.contentEncoding; + delete this.examples; // Remove the `examples` property + } + + // Handle `const` by converting it to `enum` + handleConstAsEnum() { + if (this.const !== undefined) { + this.enum = [this.const]; + delete this.const; + } + } + + // Handle the specific case where `anyOf` or `oneOf` contains a `$ref` and `null` + flattenAnyOfOrOneOfWithRefAndNull(schemaMap: Record) { + const keywords = ["oneOf", "anyOf"] as const; + + keywords.forEach((keyword) => { + if (this[keyword]) { + let hasNullType = false; + let refEntry: Schema | null = null; + + // Check for `$ref` and `null` type in `oneOf` or `anyOf` + this[keyword]!.forEach((entry) => { + if (entry.type === "null") { + hasNullType = true; + } else if (entry.$ref) { + refEntry = entry; + } + }); + + // If both `$ref` and `null` are present, convert to `allOf` with `nullable: true` + if (refEntry && hasNullType) { + this.nullable = true; + this.allOf = [refEntry]; + delete this[keyword]; + } + } + }); + } + + // Flatten `oneOf` or `anyOf` if all `$ref` point to the same primitive type + flattenIdenticalReferences(schemaMap: Record) { + const keywords = ["oneOf", "anyOf"] as const; + + keywords.forEach((keyword) => { + if (this[keyword] && !this.discriminator) { + const entries = this[keyword]!; + let primitiveType: string | null = null; + let hasNullType = false; + + // Check each entry in `oneOf` or `anyOf` + entries.forEach((entry) => { + if (entry.$ref) { + const refSchemaName = entry.$ref.split("/").pop()!; + const refSchema = schemaMap[refSchemaName]; + + if (refSchema) { + if (typeof refSchema.type === "string") { + if (!primitiveType) { + primitiveType = refSchema.type; + } else if (primitiveType !== refSchema.type) { + primitiveType = null; // Different types found, cannot flatten + } + } + } + } else if (entry.type === "null") { + hasNullType = true; + } else if (typeof entry.type === "string") { + if (!primitiveType) { + primitiveType = entry.type; + } else if (primitiveType !== entry.type) { + primitiveType = null; // Different types found, cannot flatten + } + } + }); + + // If all references point to the same primitive type + if (primitiveType) { + this.type = primitiveType; + if (hasNullType) { + this.nullable = true; + } + delete this[keyword]; + } + } + }); + } + + // Adjust nested schemas recursively + adjustNestedSchemas(schemaMap: Record) { + if (this.properties) { + Object.values(this.properties).forEach((property) => { + property.adjust(schemaMap); + }); + } + + if (this.items) { + this.items.adjust(schemaMap); + } + + if (this.additionalProperties) { + this.additionalProperties.adjust(schemaMap); + } + + // Recursively adjust oneOf, anyOf, allOf entries + const keywords = ["oneOf", "anyOf", "allOf"] as const; + keywords.forEach((keyword) => { + if (this[keyword]) { + this[keyword] = this[keyword]!.map((entry) => { + entry.adjust(schemaMap); + return entry; + }); + } + }); + } +} + +// Function to downconvert OpenAPI 3.1 to 3.0.3 export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { const convertedSpec: OpenAPISpec30 = { ...spec, openapi: "3.0.1" }; // Map to store schemas for resolving $ref types - const schemaMap: Record = - convertedSpec.components?.schemas || {}; + const schemaMap: Record = convertedSpec.components?.schemas + ? Object.fromEntries( + Object.entries(convertedSpec.components.schemas).map(([key, value]) => [ + key, + new Schema(value), + ]) + ) + : {}; // Adjust schema properties in components if (convertedSpec.components?.schemas) { for (const schemaName in convertedSpec.components.schemas) { - const schema = convertedSpec.components.schemas[schemaName]; - adjustSchema(schema, schemaMap); + const schema = schemaMap[schemaName]; + schema.adjust(schemaMap); + convertedSpec.components.schemas[schemaName] = schema; } } - // Remove or adjust unsupported features - if (convertedSpec.webhooks) { - delete convertedSpec.webhooks; // Webhooks are not supported in OpenAPI 3.0.1 - } - // Adjust paths if (convertedSpec.paths) { for (const path in convertedSpec.paths) { @@ -82,23 +278,39 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { // Handle requestBody content if (operation.requestBody?.content) { - adjustContent(operation.requestBody.content, schemaMap); + Object.values(operation.requestBody.content).forEach((mediaType) => { + if (mediaType.schema) { + const schema = new Schema(mediaType.schema); + schema.adjust(schemaMap); + mediaType.schema = schema; + } + }); } // Handle response content if (operation.responses) { for (const response in operation.responses) { if (operation.responses[response]?.content) { - adjustContent(operation.responses[response].content!, schemaMap); + Object.values(operation.responses[response].content).forEach( + (mediaType) => { + if (mediaType.schema) { + const schema = new Schema(mediaType.schema); + schema.adjust(schemaMap); + mediaType.schema = schema; + } + } + ); } } } - // Handle parameters in each operation + // Handle parameters if (operation.parameters) { operation.parameters.forEach((parameter) => { if (parameter.schema) { - adjustSchema(parameter.schema, schemaMap); + const schema = new Schema(parameter.schema); + schema.adjust(schemaMap); + parameter.schema = schema; } }); } @@ -109,200 +321,6 @@ export function downconvertOpenAPI31To30(spec: OpenAPISpec31): OpenAPISpec30 { return convertedSpec; } -function adjustSchema( - schema: SchemaObject, - schemaMap: Record -): void { - // Remove unsupported properties - delete schema.$schema; - delete schema.$id; - delete schema.$comment; - delete schema.unevaluatedProperties; - delete schema.patternProperties; - delete schema.contentMediaType; - delete schema.contentEncoding; - - // Remove examples property - delete schema.examples; - - // Handle 'const' by converting it to 'enum' - if (schema.const !== undefined) { - schema.enum = [schema.const]; - delete schema.const; - } - - // Handle type arrays that include 'null' - if (Array.isArray(schema.type) && schema.type.includes("null")) { - const nonNullTypes = schema.type.filter((t) => t !== "null"); - if (nonNullTypes.length === 1) { - schema.type = nonNullTypes[0]; - schema.nullable = true; - } else if (nonNullTypes.length > 1) { - schema.anyOf = nonNullTypes.map((type) => ({ type })); - schema.anyOf.push({ type: "null" }); - delete schema.type; - } - } else if (schema.type === "null") { - schema.type = "object"; // Default type if only 'null' is specified - schema.nullable = true; - } - - // Recursively handle `oneOf` or `anyOf` and ensure proper type conversion - ["oneOf", "anyOf"].forEach((keyword) => { - if (schema[keyword]) { - let refEntry: SchemaObject | null = null; - let hasNullType = false; - - schema[keyword] = schema[keyword]!.map((entry): SchemaObject | null => { - if (entry.$ref) { - refEntry = entry; - } else if (entry.type === "null") { - hasNullType = true; - return null; // Mark for removal - } - - // Recursively adjust nested schemas in oneOf/anyOf - adjustSchema(entry, schemaMap); - - return entry; - }).filter((entry): entry is SchemaObject => entry !== null); - - // If we have a $ref and a `null` type, merge them into a single nullable reference - if (refEntry && hasNullType) { - const refSchemaName = (refEntry as SchemaObject).$ref.split("/").pop()!; - const refSchema = schemaMap[refSchemaName]; - - // Convert into a single schema with $ref, nullable, and type - schema.$ref = (refEntry as SchemaObject).$ref; - schema.nullable = true; - - // Extract type from the referenced schema if available - if (refSchema && refSchema.type) { - schema.type = refSchema.type; - } - - delete schema[keyword]; // Remove `oneOf` or `anyOf` since it's been merged - } - - // Flatten if `oneOf` contains only primitive types that are identical - if ( - schema[keyword] && - schema[keyword]!.length >= 2 && - schema[keyword]!.every( - (entry) => - typeof entry.type === "string" && - entry.type === schema[keyword]![0].type - ) && - !schema.discriminator - ) { - schema.type = schema[keyword]![0].type; - delete schema[keyword]; - } - - // Flatten if `oneOf` contains only `$ref` entries that resolve to the same primitive type - if ( - schema[keyword] && - schema[keyword]!.length >= 2 && - schema[keyword]!.every( - (entry) => - entry.$ref && - schemaMap[entry.$ref.split("/").pop()!]?.type === - schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type - ) && - !schema.discriminator - ) { - schema.type = - schemaMap[schema[keyword]![0].$ref.split("/").pop()!]?.type; - delete schema[keyword]; - } - - // Flatten if `oneOf` contains mixed `$ref` and primitive types that are identical - if ( - schema[keyword] && - schema[keyword]!.length >= 2 && - schema[keyword]!.every( - (entry) => - (entry.$ref && - schemaMap[entry.$ref.split("/").pop()!]?.type === "string") || - (typeof entry.type === "string" && entry.type === "string") - ) && - !schema.discriminator - ) { - schema.type = "string"; - delete schema[keyword]; - } - - // If only one schema remains after adjustments, merge it back into the parent - if (schema[keyword] && schema[keyword].length === 1) { - const singleSchema = schema[keyword][0]; - delete schema[keyword]; - Object.assign(schema, singleSchema); - } - } - }); - - // Recursively adjust nested schemas in properties, additionalProperties, items, allOf, etc. - if (schema.properties) { - Object.entries(schema.properties).forEach(([key, propSchema]) => { - adjustSchema(propSchema, schemaMap); - }); - } - - if ( - schema.additionalProperties && - typeof schema.additionalProperties === "object" - ) { - adjustSchema(schema.additionalProperties, schemaMap); - } - - if (schema.items && typeof schema.items === "object") { - adjustSchema(schema.items, schemaMap); - } - - if (schema.allOf) { - schema.allOf.forEach((entry) => adjustSchema(entry, schemaMap)); - } - - // Adjust deeply nested `examples` in properties, items, allOf, oneOf, anyOf - ["properties", "items", "allOf", "oneOf", "anyOf"].forEach((keyword) => { - if (schema[keyword]) { - if (Array.isArray(schema[keyword])) { - schema[keyword].forEach((nestedSchema) => - adjustSchema(nestedSchema, schemaMap) - ); - } else if (typeof schema[keyword] === "object") { - adjustSchema(schema[keyword], schemaMap); - } - } - }); - - // Ensure no type is an array - if (Array.isArray(schema.type)) { - if (schema.type.length === 1) { - schema.type = schema.type[0]; // If there's only one type, convert it to a string - } else { - schema.anyOf = schema.type.map((type) => ({ type })); - delete schema.type; - } - } -} - -function adjustContent( - content: ContentObject, - schemaMap: Record -): void { - for (const contentType in content) { - const mediaTypeObject = content[contentType]; - if (mediaTypeObject.schema) { - adjustSchema(mediaTypeObject.schema, schemaMap); - } - if (mediaTypeObject.examples && Array.isArray(mediaTypeObject.examples)) { - mediaTypeObject.example = mediaTypeObject.examples.join("\n"); - delete mediaTypeObject.examples; - } - } -} - import { promises as fs } from "fs"; import path from "path"; import YAML from "yamljs"; @@ -343,5 +361,3 @@ async function writeJsonToFile(filePath: string, data: object): Promise { readYamlFile("./dist/platform.yml") .then((r) => downconvertOpenAPI31To30(r as any)) .then((spec) => writeJsonToFile("./dist/platform-3.0.3.json", spec)); - -// downconvertOpenAPI31To30() From 36c86e8c789c79d2192ff1810c83faa1ee4c5ccd Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:24:27 -0800 Subject: [PATCH 09/12] fix small issues --- .../environments/EnvironmentFeatures.yml | 2 +- .../services/DiscoveryEnvironmentService.yml | 1 - .../LoadBalancerEnvironmentService.yml | 1 - .../services/SchedulerEnvironmentService.yml | 1 - .../services/VpnEnvironmentService.yml | 1 - .../services/discovery/DiscoveryConfig.yml | 8 +- .../loadbalancer/config/types/v1/V1LbType.yml | 4 +- .../config/types/v1/WafConfig.yml | 2 + .../types/v1/routers/HttpRouterConfig.yml | 4 +- .../types/StackSpecDefaultLbType.yml | 1 - util/downconvert.ts | 106 ++++++++++++++---- 11 files changed, 96 insertions(+), 35 deletions(-) diff --git a/components/schemas/environments/EnvironmentFeatures.yml b/components/schemas/environments/EnvironmentFeatures.yml index 9a435ed1..811a8098 100644 --- a/components/schemas/environments/EnvironmentFeatures.yml +++ b/components/schemas/environments/EnvironmentFeatures.yml @@ -19,6 +19,6 @@ properties: type: string enum: - limited - - standard + - basic - premium - enterprise diff --git a/components/schemas/environments/services/DiscoveryEnvironmentService.yml b/components/schemas/environments/services/DiscoveryEnvironmentService.yml index a6ddad5f..72aec9b3 100644 --- a/components/schemas/environments/services/DiscoveryEnvironmentService.yml +++ b/components/schemas/environments/services/DiscoveryEnvironmentService.yml @@ -5,7 +5,6 @@ required: - enable - container_id - high_availability - - config properties: enable: type: boolean diff --git a/components/schemas/environments/services/LoadBalancerEnvironmentService.yml b/components/schemas/environments/services/LoadBalancerEnvironmentService.yml index 5bc92976..882e240c 100644 --- a/components/schemas/environments/services/LoadBalancerEnvironmentService.yml +++ b/components/schemas/environments/services/LoadBalancerEnvironmentService.yml @@ -5,7 +5,6 @@ required: - enable - container_id - high_availability - - config properties: enable: type: boolean diff --git a/components/schemas/environments/services/SchedulerEnvironmentService.yml b/components/schemas/environments/services/SchedulerEnvironmentService.yml index 183cedea..2dff670d 100644 --- a/components/schemas/environments/services/SchedulerEnvironmentService.yml +++ b/components/schemas/environments/services/SchedulerEnvironmentService.yml @@ -7,7 +7,6 @@ required: - enable - container_id - high_availablity - # - config properties: enable: type: boolean diff --git a/components/schemas/environments/services/VpnEnvironmentService.yml b/components/schemas/environments/services/VpnEnvironmentService.yml index 9d8c8ddf..35f0f87d 100644 --- a/components/schemas/environments/services/VpnEnvironmentService.yml +++ b/components/schemas/environments/services/VpnEnvironmentService.yml @@ -5,7 +5,6 @@ required: - enable - container_id - high_availability - - config properties: enable: type: boolean diff --git a/components/schemas/environments/services/discovery/DiscoveryConfig.yml b/components/schemas/environments/services/discovery/DiscoveryConfig.yml index 25c97bcf..c29687b6 100644 --- a/components/schemas/environments/services/discovery/DiscoveryConfig.yml +++ b/components/schemas/environments/services/discovery/DiscoveryConfig.yml @@ -30,6 +30,8 @@ properties: type: string custom_resolvers: description: "A list of custom DNS resolver strings. Can specifify domains or ips." - type: array - items: - type: string + oneOf: + - type: array + items: + type: string + - type: "null" diff --git a/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbType.yml b/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbType.yml index 7df2d6c5..45d2d962 100644 --- a/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbType.yml +++ b/components/schemas/environments/services/loadbalancer/config/types/v1/V1LbType.yml @@ -17,7 +17,9 @@ properties: enum: - "v1" details: - $ref: V1LbConfig.yml + anyOf: + - $ref: V1LbConfig.yml + - type: "null" bind_host: description: | Binds the load balancer to the host server IP address. diff --git a/components/schemas/environments/services/loadbalancer/config/types/v1/WafConfig.yml b/components/schemas/environments/services/loadbalancer/config/types/v1/WafConfig.yml index 0c70a901..f7d3f09a 100644 --- a/components/schemas/environments/services/loadbalancer/config/types/v1/WafConfig.yml +++ b/components/schemas/environments/services/loadbalancer/config/types/v1/WafConfig.yml @@ -33,6 +33,8 @@ properties: enum: - any - all + # legacy value TODO remove + - "" conditions: description: An array of the specific conditions for the rule. type: array diff --git a/components/schemas/environments/services/loadbalancer/config/types/v1/routers/HttpRouterConfig.yml b/components/schemas/environments/services/loadbalancer/config/types/v1/routers/HttpRouterConfig.yml index 9e605b54..2e243c21 100644 --- a/components/schemas/environments/services/loadbalancer/config/types/v1/routers/HttpRouterConfig.yml +++ b/components/schemas/environments/services/loadbalancer/config/types/v1/routers/HttpRouterConfig.yml @@ -75,7 +75,9 @@ properties: description: Replacement value. type: string url: - type: string + type: + - string + - "null" description: | The URL to forward the request to. diff --git a/stackspec/schema/services/loadbalancer/types/StackSpecDefaultLbType.yml b/stackspec/schema/services/loadbalancer/types/StackSpecDefaultLbType.yml index a025ccc5..d27eb81a 100644 --- a/stackspec/schema/services/loadbalancer/types/StackSpecDefaultLbType.yml +++ b/stackspec/schema/services/loadbalancer/types/StackSpecDefaultLbType.yml @@ -20,5 +20,4 @@ properties: oneOf: - $ref: ./haproxy/StackSpecHaProxyConfig.yml - $ref: ./v1/StackSpecV1LbConfig.yml - - type: "null" diff --git a/util/downconvert.ts b/util/downconvert.ts index 67861cab..9bee3eee 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -116,7 +116,8 @@ class Schema { adjust(schemaMap: Record) { this.removeUnsupportedProperties(); this.handleConstAsEnum(); - this.flattenAnyOfOrOneOfWithRefAndNull(schemaMap); + this.handleAnyOfWithNull(); + this.handleOneOfWithNull(); this.flattenIdenticalReferences(schemaMap); this.adjustNestedSchemas(schemaMap); } @@ -141,40 +142,94 @@ class Schema { } } - // Handle the specific case where `anyOf` or `oneOf` contains a `$ref` and `null` - flattenAnyOfOrOneOfWithRefAndNull(schemaMap: Record) { - const keywords = ["oneOf", "anyOf"] as const; + // Handle `anyOf` containing `null` and a reference or another type + handleAnyOfWithNull() { + if (this.anyOf) { + let hasNullType = false; + let nonNullEntry: Schema | null = null; + + // Check for `null` type in `anyOf` + this.anyOf.forEach((entry) => { + if (entry.type === "null") { + hasNullType = true; + } else { + nonNullEntry = entry; + } + }); - keywords.forEach((keyword) => { - if (this[keyword]) { - let hasNullType = false; - let refEntry: Schema | null = null; + // If both `null` and another type are present, make the schema nullable and use `allOf` for reference + if (hasNullType && nonNullEntry) { + this.nullable = true; - // Check for `$ref` and `null` type in `oneOf` or `anyOf` - this[keyword]!.forEach((entry) => { - if (entry.type === "null") { - hasNullType = true; - } else if (entry.$ref) { - refEntry = entry; + if (nonNullEntry.$ref) { + // Convert `anyOf` to `allOf` with the reference + this.allOf = [{ $ref: nonNullEntry.$ref }]; + } else { + // Otherwise, retain all properties of the non-null entry + Object.assign(this, nonNullEntry); + } + + delete this.anyOf; + } + } + } + + // Handle `oneOf` containing `null` and another type + handleOneOfWithNull() { + // Do not modify `oneOf` if a discriminator is present + if (this.discriminator) { + return; + } + + if (this.oneOf) { + let hasNullType = false; + let nonNullEntry: Schema | null = null; + + // Iterate through the `oneOf` entries + this.oneOf.forEach((entry) => { + if (entry.type === "null") { + hasNullType = true; + } else { + nonNullEntry = entry; + } + }); + + // If both `null` and another type are present + if (hasNullType && nonNullEntry) { + // Set nullable to true + this.nullable = true; + + // Retain all properties of the non-null entry (type, enum, description, etc.) + for (const key in nonNullEntry) { + if (key !== "type" && key !== "nullable") { + (this as any)[key] = nonNullEntry[key]; } - }); + } - // If both `$ref` and `null` are present, convert to `allOf` with `nullable: true` - if (refEntry && hasNullType) { - this.nullable = true; - this.allOf = [refEntry]; - delete this[keyword]; + // Assign the type of the non-null entry + this.type = nonNullEntry.type; + + // If there's an enum, retain it + if (nonNullEntry.enum) { + this.enum = nonNullEntry.enum; } + + delete this.oneOf; } - }); + } } // Flatten `oneOf` or `anyOf` if all `$ref` point to the same primitive type flattenIdenticalReferences(schemaMap: Record) { + // Do not modify `oneOf` or `anyOf` if a discriminator is present + if (this.discriminator) { + return; + } + const keywords = ["oneOf", "anyOf"] as const; keywords.forEach((keyword) => { - if (this[keyword] && !this.discriminator) { + if (this[keyword]) { const entries = this[keyword]!; let primitiveType: string | null = null; let hasNullType = false; @@ -238,8 +293,11 @@ class Schema { keywords.forEach((keyword) => { if (this[keyword]) { this[keyword] = this[keyword]!.map((entry) => { - entry.adjust(schemaMap); - return entry; + // Ensure entry is converted to a Schema instance before adjustment + const schemaEntry = + entry instanceof Schema ? entry : new Schema(entry); + schemaEntry.adjust(schemaMap); + return schemaEntry; }); } }); From c799d6f6ad4473d949a568e99e246c78fbfe1f42 Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:33:40 -0800 Subject: [PATCH 10/12] update downconverter --- README.md | 96 ++++++++++++++++--- .../containers/config/ContainerDeploy.yml | 9 +- .../containers/config/ShutdownSignal.yml | 10 ++ package-lock.json | 79 +++++++++++++-- package.json | 7 +- .../schema/StackSpecContainerConfigDeploy.yml | 10 +- util/downconvert.ts | 31 ++++-- util/tsconfig.json | 20 ++++ 8 files changed, 216 insertions(+), 46 deletions(-) create mode 100644 components/schemas/containers/config/ShutdownSignal.yml create mode 100644 util/tsconfig.json diff --git a/README.md b/README.md index 72974337..052f6210 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,111 @@ # Cycle API Spec - + + - cycle + cycle + -This repository contains the [OpenAPI](https://www.openapis.org/) definitions for the [Cycle API](https://api-docs.cycle.io). It is used in several downstream API clients and projects, and is the basis of our API documentation. +This repository contains the [OpenAPI](https://www.openapis.org/) definitions for the [Cycle Platform](https://cycle.io). +This spec is used to generate various API clients, used by our team internally, and also the foundation of our API documentation. -⚠️ This spec is still under development. While most endpoints should be stable, there are still edge cases and small discrepancies that are actively being worked on. - -While no major breaking changes are expected, we can't guarantee that any downstream clients won't be affected by changes to schemas (such as marking properties as null or required). When we believe the spec to be stable, we'll release v1.0.0. +⚠️ This spec is still under development. While most endpoints should be stable, there are still edge cases and small discrepancies that are actively being worked on. While no major breaking changes are expected, we can't guarantee that any downstream clients won't be affected by changes to schemas. ## APIs -### Public API +### Platform API + +https://api.cycle.io + +[Documentation](https://api-docs.cycle.io) + +The platform API is the general use API for the Cycle Platform, and is available to all Cycle customers. It is the same API consumed by Cycle's portal, and is the main way to interact with all aspects of the platform. -The public API is the general use API for the Cycle Platform, located at https://api.cycle.io. The endpoints for this API are located under `/public`. +See the [platform](./platform/) folder for implementation. ### Internal API -The internal API is used within containers running on the Cycle Platform, and provide a convenient means to communicate with the platform from within a container to unlock advanced use cases (such as custom monitoring/tooling). +[Documentation](https://internal-api.docs.cycle.io) + +Inside every container running on Cycle, there is a Unix socket mounted at `/var/run/cycle/api/api.sock`. You can send HTTP requests over this socket to access information about the local environment, access secrets, and much more. The way this internal API functions is very similar to how Cycle's main API works, though the purpose is different. The internal API is primarily used by instances to learn about their environment, and dynamically update as deployments change. + +See the [internal](./internal/) folder for implementation. + +### Scheduler API + +[Documentation](https://scheduler-api.docs.cycle.io/) + +The scheduler API is used to interact with the [Scheduler Service](https://cycle.io/docs/platform/scheduler-service) inside of +an environment on Cycle. The scheduler service can be accessed from other containers in the environment over the private network, +or the scheduler service can be [made available over the public internet](https://cycle.io/docs/platform/scheduler-service). + +See the [scheduler](./scheduler/) folder for implementation. + +### Infrastructure Abstraction Layer API + +[Documentation](https://cycle-ial.redoc.ly/) + +Cycle's Infrastructure Abstract Layer (IAL) API. Endpoints listed here should be implemented by the customer, and Cycle will call the various endpoints over the lifecycle of provisioning and decommissioning servers. + +For more information, check out the official [Infrastructure Abstraction Layer Documentation](https://cycle.io/docs/platform/infrastructure-abstraction-layer). + +### Stack Spec + +While not truly an API spec, the stack spec defines the JSON schema for a [Cycle Stack File](https://cycle.io/docs/platform/introduction-to-stacks). The JSON schema is useful for editor validation etc. + +The compiled stack spec schema is committed to this repo and hosted on [JSON Schema Store](https://www.schemastore.org/json/) ## Building the APIs -The APIs are built using the [Redocly CLI](https://redocly.com/redocly-cli/). It stitches together the various files and combines them into a single spec file for easy consumption by other tools. +The APIs can be consumed by tools that support stitching together specs divided between multiple files, or built +into a single file using the [Redocly CLI](https://redocly.com/redocly-cli/). ### Prerequisites You must have `npm` installed on your machine, or run inside a container with `npm` in the PATH. -### Building the Public API +### Building the Platform API -`npm run build:public` +`npm run build:platform` -The outputted file is located at `/dist/public-api.yml` +The outputted file is located at `/dist/platform.yml` ### Building the Internal API `npm run build:internal` -The outputted file is located at `/dist/internal-api.yml` +The outputted file is located at `/dist/internal.yml` + +### Building the Scheduler API + +`npm run build:scheduler` + +The outputted file is located at `/dist/scheduler.yml` + +### Building the Infrastructure Abstraction Layer API + +`npm run build:ial` + +The outputted file is located at `/dist/ial.yml` + +### Building the Stack Spec + +`npm run build:stackspec` + +This will build the stack spec into a single JSON schema file, and save it to ./stackspec/stackspec.json. + +**This file should be committed to the repo.** + +## Downconverting to OpenAPI 3.0.3 + +The API specs in this repo are written using the OpenAPI 3.1.0 standard. Some code generators and other tools haven't yet +been updated to support 3.1.0, which can be an inconvenience. For us, it seems nearly all golang client generators don't +support 3.1.0 (if you find one let us know!). + +To resolve this, we've created a script that will downconvert the openapi spec to 3.0.3. Of course, some fidelity is lost +with these conversions, so it may not be a perfect 1:1 with the 'true' spec, but hopefully is good enough to generate clients +off of when necessary. + +To downconvert, run `npm run downconvert:platform`. The script will output to `dist/platform-3.0.3.yml`. \ No newline at end of file diff --git a/components/schemas/containers/config/ContainerDeploy.yml b/components/schemas/containers/config/ContainerDeploy.yml index 4e1f067b..f06eab7e 100644 --- a/components/schemas/containers/config/ContainerDeploy.yml +++ b/components/schemas/containers/config/ContainerDeploy.yml @@ -90,14 +90,7 @@ properties: - array - "null" items: - type: string - enum: - - SIGTERM - - SIGINT - - SIGUSR1 - - SIGUSR2 - - SIGHUP - - SIGQUIT + $ref: ShutdownSignal.yml description: Process signal sent to the container process. startup: type: object diff --git a/components/schemas/containers/config/ShutdownSignal.yml b/components/schemas/containers/config/ShutdownSignal.yml new file mode 100644 index 00000000..52438fc6 --- /dev/null +++ b/components/schemas/containers/config/ShutdownSignal.yml @@ -0,0 +1,10 @@ +title: ShutdownSignal +type: string +description: Process signals that Cycle can be configured to send to the container on a shutdown event. +enum: + - SIGTERM + - SIGINT + - SIGUSR1 + - SIGUSR2 + - SIGHUP + - SIGQUIT diff --git a/package-lock.json b/package-lock.json index ef540411..ca7b5471 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,14 @@ "name": "@cycleplatform/api-spec", "version": "1.0.0", "license": "Apache-2.0", + "dependencies": { + "@apiture/openapi-down-convert": "github:avandecreme/openapi-down-convert#nullable_pr" + }, "devDependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", "@redocly/cli": "1.11.0", "@types/node": "^20.12.7", + "@types/yamljs": "^0.2.34", "yamljs": "^0.3.0" } }, @@ -32,6 +36,19 @@ "url": "https://github.com/sponsors/philsturgeon" } }, + "node_modules/@apiture/openapi-down-convert": { + "version": "0.13.2", + "resolved": "git+ssh://git@github.com/avandecreme/openapi-down-convert.git#654309786f68362a6ae7b03f0d76f7e24abcf9f1", + "license": "ISC", + "dependencies": { + "commander": "^9.4.1", + "js-yaml": "^4.1.0", + "typescript": "^4.8.4" + }, + "bin": { + "openapi-down-convert": "lib/src/cli.js" + } + }, "node_modules/@babel/runtime": { "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", @@ -215,6 +232,13 @@ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", "dev": true }, + "node_modules/@types/yamljs": { + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.34.tgz", + "integrity": "sha512-gJvfRlv9ErxdOv7ux7UsJVePtX54NAvQyd8ncoiFqK8G5aeHIfQfGH2fbruvjAQ9657HwAaO54waS+Dsk2QTUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -267,8 +291,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -417,6 +440,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -758,7 +790,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -1780,6 +1811,19 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -1963,6 +2007,15 @@ "js-yaml": "^4.1.0" } }, + "@apiture/openapi-down-convert": { + "version": "git+ssh://git@github.com/avandecreme/openapi-down-convert.git#654309786f68362a6ae7b03f0d76f7e24abcf9f1", + "from": "@apiture/openapi-down-convert@github:avandecreme/openapi-down-convert#nullable_pr", + "requires": { + "commander": "^9.4.1", + "js-yaml": "^4.1.0", + "typescript": "^4.8.4" + } + }, "@babel/runtime": { "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", @@ -2125,6 +2178,12 @@ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", "dev": true }, + "@types/yamljs": { + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.34.tgz", + "integrity": "sha512-gJvfRlv9ErxdOv7ux7UsJVePtX54NAvQyd8ncoiFqK8G5aeHIfQfGH2fbruvjAQ9657HwAaO54waS+Dsk2QTUQ==", + "dev": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2162,8 +2221,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "asynckit": { "version": "0.4.0", @@ -2283,6 +2341,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2548,7 +2611,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -3290,6 +3352,11 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", diff --git a/package.json b/package.json index 2b9273ae..a6d4219d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "preview:platform": "npm run build:platform && npx @redocly/cli preview-docs dist/platform.yml", "preview:internal": "npm run build:internal && npx @redocly/cli preview-docs dist/internal.yml", "preview:scheduler": "npm run build:scheduler && npx @redocly/cli preview-docs cycle-scheduler-api", - "preview:ial": "npm run build:ial && npx @redocly/cli preview-docs cycle-ial" + "preview:ial": "npm run build:ial && npx @redocly/cli preview-docs cycle-ial", + "downconvert:platform": "npm run build:platform && openapi-down-convert --input dist/platform.yml --output dist/platform-3.0.3.yml" }, "author": "Petrichor Holdings, Inc.", "license": "Apache-2.0", @@ -25,6 +26,10 @@ "@apidevtools/json-schema-ref-parser": "^11.5.4", "@redocly/cli": "1.11.0", "@types/node": "^20.12.7", + "@types/yamljs": "^0.2.34", "yamljs": "^0.3.0" + }, + "dependencies": { + "@apiture/openapi-down-convert": "github:avandecreme/openapi-down-convert#nullable_pr" } } diff --git a/stackspec/schema/StackSpecContainerConfigDeploy.yml b/stackspec/schema/StackSpecContainerConfigDeploy.yml index 25bcf36d..80e2a059 100644 --- a/stackspec/schema/StackSpecContainerConfigDeploy.yml +++ b/stackspec/schema/StackSpecContainerConfigDeploy.yml @@ -155,15 +155,7 @@ properties: - type: array description: Signals that should be sent to the container on shutdown. items: - type: string - enum: - - SIGTERM - - SIGINT - - SIGUSR1 - - SIGUSR2 - - SIGHUB - - SIGKILL - - SIGQUIT + $ref: ../../components/schemas/containers/config/ShutdownSignal.yml - $ref: StackVariable.yml - $ref: StackVariable.yml startup: diff --git a/util/downconvert.ts b/util/downconvert.ts index 9bee3eee..c04d21b0 100644 --- a/util/downconvert.ts +++ b/util/downconvert.ts @@ -116,6 +116,7 @@ class Schema { adjust(schemaMap: Record) { this.removeUnsupportedProperties(); this.handleConstAsEnum(); + this.handleNullType(); this.handleAnyOfWithNull(); this.handleOneOfWithNull(); this.flattenIdenticalReferences(schemaMap); @@ -142,6 +143,14 @@ class Schema { } } + // 🔹 Convert `type: "null"` to `type: "object", nullable: true` + handleNullType() { + if (this.type === "null") { + this.type = "object"; + this.nullable = true; + } + } + // Handle `anyOf` containing `null` and a reference or another type handleAnyOfWithNull() { if (this.anyOf) { @@ -161,9 +170,9 @@ class Schema { if (hasNullType && nonNullEntry) { this.nullable = true; - if (nonNullEntry.$ref) { + if ((nonNullEntry as Schema).$ref) { // Convert `anyOf` to `allOf` with the reference - this.allOf = [{ $ref: nonNullEntry.$ref }]; + this.allOf = [{ $ref: (nonNullEntry as Schema).$ref } as Schema]; } else { // Otherwise, retain all properties of the non-null entry Object.assign(this, nonNullEntry); @@ -200,18 +209,18 @@ class Schema { this.nullable = true; // Retain all properties of the non-null entry (type, enum, description, etc.) - for (const key in nonNullEntry) { + for (const key in nonNullEntry as Schema) { if (key !== "type" && key !== "nullable") { (this as any)[key] = nonNullEntry[key]; } } // Assign the type of the non-null entry - this.type = nonNullEntry.type; + this.type = (nonNullEntry as Schema).type; // If there's an enum, retain it - if (nonNullEntry.enum) { - this.enum = nonNullEntry.enum; + if ((nonNullEntry as Schema).enum) { + this.enum = (nonNullEntry as Schema).enum; } delete this.oneOf; @@ -396,7 +405,10 @@ async function readYamlFile(filePath: string): Promise { return parsedData; } catch (err) { - throw new Error(`Error reading or parsing YAML file: ${err.message}`); + if (err && typeof err === "object" && "message" in err) { + throw new Error(`Error reading or parsing YAML file: ${err.message}`); + } + throw err; } } @@ -412,7 +424,10 @@ async function writeJsonToFile(filePath: string, data: object): Promise { await fs.writeFile(fullPath, jsonString, "utf8"); console.log(`Successfully wrote to ${fullPath}`); } catch (err) { - throw new Error(`Error writing JSON to file: ${err.message}`); + if (err && typeof err === "object" && "message" in err) { + throw new Error(`Error writing JSON to file: ${err.message}`); + } + throw err; } } diff --git a/util/tsconfig.json b/util/tsconfig.json new file mode 100644 index 00000000..af2c7e74 --- /dev/null +++ b/util/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "lib": [ + "ESNext" + ], + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "nodenext", + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + }, + "include": ["./downconvert.ts"], + "exclude": [ + "dist" + ] +} \ No newline at end of file From da1be9bb9e4cf3a303529f382091b377a6d4753d Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:07:50 -0800 Subject: [PATCH 11/12] remove x-ogen headers --- components/schemas/containers/instances/InstanceMigration.yml | 1 - components/schemas/hubs/integrations/IntegrationMeta.yml | 1 - components/schemas/images/Image.yml | 1 - components/schemas/infrastructure/servers/Server.yml | 1 - components/schemas/scoped-variables/ScopedVariableAccess.yml | 4 ---- components/schemas/stacks/builds/StackBuild.yml | 1 - components/schemas/stacks/builds/StackBuildInstructions.yml | 1 - 7 files changed, 10 deletions(-) diff --git a/components/schemas/containers/instances/InstanceMigration.yml b/components/schemas/containers/instances/InstanceMigration.yml index ddd012ae..36281479 100644 --- a/components/schemas/containers/instances/InstanceMigration.yml +++ b/components/schemas/containers/instances/InstanceMigration.yml @@ -1,5 +1,4 @@ title: InstanceMigration -x-ogen-name: InstanceMigrationResult type: object description: Information regarding the migration of an instance, such as the server that the instance came from or the server that the instance was moved to. required: diff --git a/components/schemas/hubs/integrations/IntegrationMeta.yml b/components/schemas/hubs/integrations/IntegrationMeta.yml index 8cf88998..e90426d5 100644 --- a/components/schemas/hubs/integrations/IntegrationMeta.yml +++ b/components/schemas/hubs/integrations/IntegrationMeta.yml @@ -1,5 +1,4 @@ title: IntegrationMeta -x-ogen-name: HubIntegrationMeta type: object description: Additional fields that can be requested for an Integration on fetch. properties: diff --git a/components/schemas/images/Image.yml b/components/schemas/images/Image.yml index 04d38ca4..47336540 100644 --- a/components/schemas/images/Image.yml +++ b/components/schemas/images/Image.yml @@ -158,7 +158,6 @@ properties: type: string description: A set command to be run if a signal is called. source: - x-ogen-name: ImageSourceDetails type: object discriminator: propertyName: type diff --git a/components/schemas/infrastructure/servers/Server.yml b/components/schemas/infrastructure/servers/Server.yml index 7aa6c293..a16fd8b1 100644 --- a/components/schemas/infrastructure/servers/Server.yml +++ b/components/schemas/infrastructure/servers/Server.yml @@ -1,5 +1,4 @@ title: Server -x-ogen-name: InfraServer type: object description: The server resource, referring to servers that have been deployed to a Cycle hub. required: diff --git a/components/schemas/scoped-variables/ScopedVariableAccess.yml b/components/schemas/scoped-variables/ScopedVariableAccess.yml index 230dacce..99e606a9 100644 --- a/components/schemas/scoped-variables/ScopedVariableAccess.yml +++ b/components/schemas/scoped-variables/ScopedVariableAccess.yml @@ -29,10 +29,6 @@ properties: type: - object - "null" - # avoid conflicting name in ogen generator with the Decode method - x-ogen-properties: - decode: - name: "DecodeBase64" required: - decode - path diff --git a/components/schemas/stacks/builds/StackBuild.yml b/components/schemas/stacks/builds/StackBuild.yml index fb818927..521c7d0b 100644 --- a/components/schemas/stacks/builds/StackBuild.yml +++ b/components/schemas/stacks/builds/StackBuild.yml @@ -22,7 +22,6 @@ properties: - $ref: ../../../../stackspec/schema/StackSpec.yml - type: "null" about: - x-ogen-name: StackBuildAboutDetails type: object description: Information about the stack build. required: diff --git a/components/schemas/stacks/builds/StackBuildInstructions.yml b/components/schemas/stacks/builds/StackBuildInstructions.yml index fe1ff6c2..5428b3cd 100644 --- a/components/schemas/stacks/builds/StackBuildInstructions.yml +++ b/components/schemas/stacks/builds/StackBuildInstructions.yml @@ -1,5 +1,4 @@ title: StackBuildInstructions -x-ogen-name: StackBuildInstructionsDetails type: object description: Additional instructions used when generating this stack build. properties: From b8a5a668a06b615f14606b27481db8cad43bf3ce Mon Sep 17 00:00:00 2001 From: Alexander Mattoni <5110855+mattoni@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:11:18 -0800 Subject: [PATCH 12/12] fix downconvert cmd --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index a6d4219d..a8bbca46 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "preview:internal": "npm run build:internal && npx @redocly/cli preview-docs dist/internal.yml", "preview:scheduler": "npm run build:scheduler && npx @redocly/cli preview-docs cycle-scheduler-api", "preview:ial": "npm run build:ial && npx @redocly/cli preview-docs cycle-ial", - "downconvert:platform": "npm run build:platform && openapi-down-convert --input dist/platform.yml --output dist/platform-3.0.3.yml" + "downconvert:platform": "npm run build:platform && npx tsx ./util/downconvert.ts" }, "author": "Petrichor Holdings, Inc.", "license": "Apache-2.0", @@ -28,8 +28,5 @@ "@types/node": "^20.12.7", "@types/yamljs": "^0.2.34", "yamljs": "^0.3.0" - }, - "dependencies": { - "@apiture/openapi-down-convert": "github:avandecreme/openapi-down-convert#nullable_pr" } }