diff --git a/.ci/examples-readme-hydration/.gitignore b/.ci/examples-readme-hydration/.gitignore deleted file mode 100644 index b512c09d..00000000 --- a/.ci/examples-readme-hydration/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/.ci/examples-readme-hydration/index.js b/.ci/examples-readme-hydration/index.js deleted file mode 100644 index e4290754..00000000 --- a/.ci/examples-readme-hydration/index.js +++ /dev/null @@ -1,39 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const YAML = require('yaml'); - -const examplesPath = path.resolve(__dirname, '../../examples/'); -const readMeTemplatePath = path.resolve(examplesPath, 'README_TEMPLATE.md'); -const readMeOutputPath = path.resolve(examplesPath, 'README.md'); -const extractor = //g; -const disclaimer = ` -`; - -(async () => { - let readMe = await fs.promises.readFile(readMeTemplatePath, 'utf8'); - const includes = readMe.matchAll(extractor); - for await(let include of includes) { - const fileName = include[1]; - const format = include[2]; - let fileContent = await fs.promises.readFile(path.resolve(examplesPath, fileName), 'utf8'); - if (format === 'yaml') { - try { - const schema = JSON.parse(fileContent); - fileContent = YAML.stringify(schema); - } - catch(ex) { - console.error('Enable to parse JSON or convert it to YAML, output as it is.', ex); - } - } - fileContent = fileContent.trim() + '\n'; - readMe = readMe.replace(include[0], fileContent); - }; - await fs.promises.writeFile(readMeOutputPath, disclaimer + readMe, { encoding: 'utf8', flag: 'w' }); -})(); \ No newline at end of file diff --git a/.ci/examples-readme-hydration/package-lock.json b/.ci/examples-readme-hydration/package-lock.json deleted file mode 100644 index f2b43996..00000000 --- a/.ci/examples-readme-hydration/package-lock.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "examples-readme-hydration", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "examples-readme-hydration", - "version": "0.1.0", - "license": "ISC", - "dependencies": { - "yaml": "^2.3.4" - } - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - } - } -} diff --git a/.ci/examples-readme-hydration/package.json b/.ci/examples-readme-hydration/package.json deleted file mode 100644 index 0607ff7b..00000000 --- a/.ci/examples-readme-hydration/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "examples-readme-hydration", - "version": "0.1.0", - "description": "Builds ./examples/README.md based on ./examples/README_TEMPLATE.md", - "main": "src/index.js", - "scripts": { - "start": "node ./index.js" - }, - "keywords": ["cncf", "serverless", "workflow", "specification"], - "author": "CNCF Serverless Workflow Specification", - "license": "ISC", - "dependencies": { - "yaml": "^2.3.4" - } -} diff --git a/.ci/validation/package-lock.json b/.ci/validation/package-lock.json index 76f271bb..a2a7346c 100644 --- a/.ci/validation/package-lock.json +++ b/.ci/validation/package-lock.json @@ -10,10 +10,12 @@ "license": "ISC", "dependencies": { "ajv": "^8.12.0", - "ajv-formats": "^2.1.1" + "ajv-formats": "^2.1.1", + "js-yaml": "^4.1.0" }, "devDependencies": { "@types/jest": "^29.5.12", + "@types/js-yaml": "^4.0.9", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" @@ -729,6 +731,30 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1250,6 +1276,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true + }, "node_modules/@types/node": { "version": "20.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", @@ -1394,14 +1426,9 @@ "dev": true }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/babel-jest": { "version": "29.7.0", @@ -3039,14 +3066,11 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "peer": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" diff --git a/.ci/validation/package.json b/.ci/validation/package.json index 611d8e66..d6371b25 100644 --- a/.ci/validation/package.json +++ b/.ci/validation/package.json @@ -7,17 +7,24 @@ "start": "ts-node ./src/index.ts", "test": "jest" }, - "keywords": ["cncf", "serverless", "workflow", "specification"], + "keywords": [ + "cncf", + "serverless", + "workflow", + "specification" + ], "author": "CNCF Serverless Workflow Specification", "license": "ISC", "devDependencies": { "@types/jest": "^29.5.12", + "@types/js-yaml": "^4.0.9", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, "dependencies": { "ajv": "^8.12.0", - "ajv-formats": "^2.1.1" + "ajv-formats": "^2.1.1", + "js-yaml": "^4.1.0" } } diff --git a/.ci/validation/src/index.test.ts b/.ci/validation/src/index.test.ts index 6ad098ac..eda2381a 100644 --- a/.ci/validation/src/index.test.ts +++ b/.ci/validation/src/index.test.ts @@ -14,26 +14,34 @@ * limitations under the License. */ -import { SWSchemaValidator } from './index' -import fs from 'fs' -import { join } from 'path' +import { SWSchemaValidator } from "./index"; +import fs from "fs"; +import { join } from "path"; -SWSchemaValidator.prepareSchemas() +SWSchemaValidator.prepareSchemas(); -const examplePath = "../../../examples" +const examplePath = "../../../examples"; describe(`Verify every example in the repository`, () => { - fs.readdirSync(join(__dirname, examplePath), { encoding: SWSchemaValidator.defaultEncoding, recursive: false, withFileTypes: true }) - .forEach(file => { - if (file.isFile() && file.name.endsWith(".json")) { - test(`Example ${file.name}`, () => { - const workflow = JSON.parse(fs.readFileSync(join(__dirname, `${examplePath}/${file.name}`), SWSchemaValidator.defaultEncoding)) - const results = SWSchemaValidator.validateSchema(workflow) - if (results?.errors != null) { - console.warn(`Schema validation on ${file.name} failed with: `, JSON.stringify(results.errors, null, 2)) - } - expect(results?.valid).toBeTruthy() - }); - } - }) -}) + fs.readdirSync(join(__dirname, examplePath), { + encoding: SWSchemaValidator.defaultEncoding, + recursive: false, + withFileTypes: true, + }).forEach((file) => { + if (file.isFile() && file.name.endsWith(".yaml")) { + test(`Example ${file.name}`, () => { + const workflow = SWSchemaValidator.toJSON( + join(__dirname, `${examplePath}/${file.name}`) + ); + const results = SWSchemaValidator.validateSchema(workflow); + if (results?.errors != null) { + console.warn( + `Schema validation on ${file.name} failed with: `, + JSON.stringify(results.errors, null, 2) + ); + } + expect(results?.valid).toBeTruthy(); + }); + } + }); +}); diff --git a/.ci/validation/src/index.ts b/.ci/validation/src/index.ts index 625f4192..f7a16112 100644 --- a/.ci/validation/src/index.ts +++ b/.ci/validation/src/index.ts @@ -14,45 +14,55 @@ * limitations under the License. */ -import fs from 'fs' -import Ajv from "ajv" -import addFormats from "ajv-formats" -import { join } from 'path' - +import fs from "fs"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { join } from "path"; +import yaml = require("js-yaml"); export module SWSchemaValidator { - const ajv = new Ajv({ strict: false, allowUnionTypes: true }) - addFormats(ajv) + const ajv = new Ajv({ strict: false, allowUnionTypes: true }); + addFormats(ajv); + const workflowSchemaId = + "https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.json"; + const schemaPath = "../../../schema"; + export const defaultEncoding = "utf-8"; - const workflowSchemaId = 'https://serverlessworkflow.io/schemas/0.9/workflow.json' - const schemaPath = '../../../schema' - export const defaultEncoding = 'utf-8' + export function prepareSchemas() { + fs.readdirSync(join(__dirname, schemaPath), { + encoding: defaultEncoding, + recursive: false, + withFileTypes: true, + }).forEach((file) => { + if (file.isFile()) { + ajv.addSchema(syncReadSchema(file.name)); + } + }); + } - export function prepareSchemas() { - fs.readdirSync(join(__dirname, schemaPath), { encoding: defaultEncoding, recursive: false, withFileTypes: true }) - .forEach(file => { - if (file.isFile()) { - ajv.addSchema(syncReadSchema(file.name)) - } - }) - } + function syncReadSchema(filename: string) { + return toJSON(join(__dirname, `${schemaPath}/${filename}`)); + } - function syncReadSchema(filename: string) { - return JSON.parse(fs.readFileSync(join(__dirname, `${schemaPath}/${filename}`), defaultEncoding)); - } + export function toJSON(filename: string) { + const yamlObj = yaml.load(fs.readFileSync(filename, defaultEncoding), { + json: true, + }); + return JSON.parse(JSON.stringify(yamlObj, null, 2)); + } - export function validateSchema(workflow: JSON) { - const validate = ajv.getSchema(workflowSchemaId) - if (validate != undefined) { - const isValid = validate(workflow) - return { - valid: isValid, - errors: validate.errors - } - } - // throw error + export function validateSchema(workflow: JSON) { + const validate = ajv.getSchema(workflowSchemaId); + if (validate != undefined) { + const isValid = validate(workflow); + return { + valid: isValid, + errors: validate.errors, + }; } + throw new Error(`Failed to validate schema on workflow`); + } } -console.log("To use this application see the test file index.test.ts") +console.log("To use this application see the test file index.test.ts"); diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 15fa2db8..e034e351 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,27 +8,22 @@ Enhancements or bugs in a specification are not always easy to describe at first We kindly ask you to consider opening a discussion or an issue using the Github tab menu above. The community will be more than happy to discuss your proposals there. --> -**Many thanks for submitting your Pull Request :heart:!** - **Please specify parts of this PR update:** - [ ] Specification - [ ] Schema - [ ] Examples - [ ] Extensions -- [ ] Roadmap - [ ] Use Cases - [ ] Community -- [ ] TCK +- [ ] CTK - [ ] Other **Discussion or Issue link**: -**What this PR does / why we need it**: +**What this PR does**: -**Special notes for reviewers**: - **Additional information:** \ No newline at end of file diff --git a/.github/workflows/examples-readme-check.yml b/.github/workflows/examples-readme-check.yml deleted file mode 100644 index da149e4b..00000000 --- a/.github/workflows/examples-readme-check.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Check examples README manual updates - -on: - pull_request: - paths: - - 'examples/README.md' - -jobs: - verification: - runs-on: ubuntu-latest - steps: - - if: contains(github.head_ref, 'automation-examples-readme') - name: pass - run: echo "The update is made by the bot, as expected." - - - if: contains(github.head_ref, 'automation-examples-readme') == false - name: fail - run: | - echo "The file examples/README.md should not be manually edited !" - echo "Please update examples/README_TEMPLATE.md instead" - exit 1 diff --git a/.github/workflows/examples-readme-hydration.yml b/.github/workflows/examples-readme-hydration.yml deleted file mode 100644 index e9e2fb47..00000000 --- a/.github/workflows/examples-readme-hydration.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Examples README hydration - -on: - pull_request: - types: - - closed - branches: [ 'main'] - paths: - - 'examples/**/*' - - '!examples/README.md' - -jobs: - build: - if: github.repository == 'serverlessworkflow/specification' && github.event.pull_request.merged == true - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.BOT_PAT }} - - - - uses: actions/setup-node@v4 - with: - node-version: 21 - - - name: Import GPG key - id: import-gpg - uses: crazy-max/ghaction-import-gpg@v6 - with: - gpg_private_key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} - git_config_global: true - git_user_signingkey: true - git_commit_gpgsign: true - git_tag_gpgsign: true - - - run: | - set -e - - # Reset origin - git remote set-url origin https://${{ secrets.BOT_USERNAME }}:${{ secrets.BOT_PAT }}@github.com/${{ github.repository }}.git - git checkout ${{ github.ref_name }} - - # Create a new git branch - git checkout -b automation-examples-readme-${{ github.event.pull_request.number }} - - # Install & run JS scripts - cd .ci/examples-readme-hydration/ - npm ci - npm start - - # Commit & push changes - git config user.name '${{ secrets.BOT_USERNAME }}' - git config user.email '${{ secrets.BOT_EMAIL }}' - git commit -S -a -m 'Rebuilt examples README.md' - git push origin automation-examples-readme-${{ github.event.pull_request.number }} - - # Create a PR on GH - gh pr create -B main -H automation-examples-readme-${{ github.event.pull_request.number }} --title '[From CI] Rebuilt examples README' --body 'Automatic hydration of examples README.md' - env: - GITHUB_TOKEN: ${{ secrets.BOT_PAT }} diff --git a/.github/workflows/schema-check.yaml b/.github/workflows/schema-check.yaml index f2b3f661..6505b290 100644 --- a/.github/workflows/schema-check.yaml +++ b/.github/workflows/schema-check.yaml @@ -37,7 +37,7 @@ jobs: build: defaults: run: - working-directory: .ci/validation + working-directory: .ci/validation/ runs-on: ubuntu-latest strategy: matrix: @@ -50,5 +50,6 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' + cache-dependency-path: .ci/validation/package-lock.json - run: npm install - run: npm test diff --git a/schema/workflow.yaml b/schema/workflow.yaml index 0d14745d..018fdb2e 100644 --- a/schema/workflow.yaml +++ b/schema/workflow.yaml @@ -1,4 +1,4 @@ -id: https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.json +$id: https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.json $schema: http://json-schema.org/draft-07/schema description: Serverless Workflow DSL - Workflow Schema type: object @@ -719,14 +719,16 @@ $defs: description: An event filter is a mechanism used to selectively process or handle events based on predefined criteria, such as event type, source, or specific attributes. correlate: type: object - properties: - from: - type: string - description: A runtime expression used to extract the correlation value from the filtered event. - expect: - type: string - description: A constant or a runtime expression, if any, used to determine whether or not the extracted correlation value matches expectations. If not set, the first extracted value will be used as the correlation's expectation. - required: [ from ] + additionalProperties: + type: object + properties: + from: + type: string + description: A runtime expression used to extract the correlation value from the filtered event. + expect: + type: string + description: A constant or a runtime expression, if any, used to determine whether or not the extracted correlation value matches expectations. If not set, the first extracted value will be used as the correlation's expectation. + required: [ from ] description: A correlation is a link between events and data, established by mapping event attributes to specific data attributes, allowing for coordinated processing or handling based on event characteristics. required: [ with ] description: An event filter is a mechanism used to selectively process or handle events based on predefined criteria, such as event type, source, or specific attributes.