From d505488eba1e901a48de5f6536bd34b52ec40965 Mon Sep 17 00:00:00 2001 From: Rachael Sewell Date: Wed, 13 Dec 2023 11:55:46 -0800 Subject: [PATCH 1/3] Remove duplicate fixtures (#47777) --- .../comparing-githubs-rest-api-and-graphql-api.md | 13 ------------- .../content/rest/about-the-rest-api/index.md | 12 ------------ 2 files changed, 25 deletions(-) delete mode 100644 tests/fixtures/content/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api.md delete mode 100644 tests/fixtures/content/rest/about-the-rest-api/index.md diff --git a/tests/fixtures/content/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api.md b/tests/fixtures/content/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api.md deleted file mode 100644 index c7af7b15be7b..000000000000 --- a/tests/fixtures/content/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: About GitHub's APIs -intro: 'Gentle and fun intro about APIs' -versions: - fpt: '*' - ghes: '*' - ghae: '*' - ghec: '*' ---- - -## About {% data variables.product.company_short %}'s APIs - -First paragraph right here. diff --git a/tests/fixtures/content/rest/about-the-rest-api/index.md b/tests/fixtures/content/rest/about-the-rest-api/index.md deleted file mode 100644 index 36340c4bbdcf..000000000000 --- a/tests/fixtures/content/rest/about-the-rest-api/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: REST API overview -shortTitle: Overview -intro: 'Learn about resources, libraries, previews and troubleshooting for {% data variables.product.prodname_dotcom %}''s REST API.' -versions: - fpt: '*' - ghes: '*' - ghae: '*' - ghec: '*' -children: - - /comparing-githubs-rest-api-and-graphql-api ---- From 97c70307b6b6b63297394c07b3def2879775684e Mon Sep 17 00:00:00 2001 From: Rachael Sewell Date: Wed, 13 Dec 2023 11:58:43 -0800 Subject: [PATCH 2/3] Consolidate uses of AJV (#47662) --- src/content-linter/tests/lint-versioning.js | 20 +++------- src/events/middleware.js | 10 ++--- src/events/tests/middleware-errors.js | 15 +++---- src/frame/lib/read-frontmatter.js | 35 ++++++----------- src/frame/tests/site-tree.js | 12 +++--- src/github-apps/scripts/sync.js | 14 +++++-- src/graphql/tests/validate-schema.js | 37 +++++++----------- src/learning-track/tests/validate-schema.js | 12 +++--- src/products/tests/products.js | 13 +++---- src/release-notes/tests/validate-schema.js | 12 +++--- src/rest/scripts/utils/operation.js | 8 +++- src/rest/scripts/utils/validate-data.js | 10 ----- src/secret-scanning/tests/validate-schema.js | 12 +++--- src/tests/lib/ajv-validate.js | 16 -------- src/tests/lib/validate-json-schema.js | 41 ++++++++++++++++++++ src/versions/tests/versions.js | 11 +++--- src/webhooks/scripts/webhook.js | 11 +++--- 17 files changed, 139 insertions(+), 150 deletions(-) delete mode 100644 src/rest/scripts/utils/validate-data.js delete mode 100644 src/tests/lib/ajv-validate.js create mode 100644 src/tests/lib/validate-json-schema.js diff --git a/src/content-linter/tests/lint-versioning.js b/src/content-linter/tests/lint-versioning.js index 3e6376aca1df..4d76d08fdb0f 100644 --- a/src/content-linter/tests/lint-versioning.js +++ b/src/content-linter/tests/lint-versioning.js @@ -1,10 +1,8 @@ import { jest } from '@jest/globals' -import Ajv from 'ajv' -import addErrors from 'ajv-errors' -import semver from 'semver' import featureVersionsSchema from '../lib/feature-versions-schema.js' import { getDeepDataByLanguage } from '#src/data-directory/lib/get-data.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' /* @@ -18,26 +16,18 @@ import { formatAjvErrors } from '#src/tests/helpers/schemas.js' jest.useFakeTimers({ legacyFakeTimers: true }) const featureVersions = Object.entries(getDeepDataByLanguage('features', 'en')) - -const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }) -addErrors(ajv) -// *** TODO: We can drop this override once the frontmatter schema has been updated to work with AJV. *** -ajv.addFormat('semver', { - validate: (x) => semver.validRange(x), -}) -// *** End TODO *** -const validate = ajv.compile(featureVersionsSchema) +const validate = getJsonValidator(featureVersionsSchema) // Make sure data/features/*.yml contains valid versioning. describe('lint feature versions', () => { test.each(featureVersions)('data/features/%s matches the schema', (name, featureVersion) => { - const valid = validate(featureVersion) + const isValid = validate(featureVersion) let errors - if (!valid) { + if (!isValid) { errors = formatAjvErrors(validate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) diff --git a/src/events/middleware.js b/src/events/middleware.js index 94f5313d3516..e70d6bd9516b 100644 --- a/src/events/middleware.js +++ b/src/events/middleware.js @@ -1,23 +1,19 @@ import express from 'express' import { omit, without, mapValues } from 'lodash-es' -import Ajv from 'ajv' -import addFormats from 'ajv-formats' import QuickLRU from 'quick-lru' import { schemas, hydroNames } from './lib/schema.js' import catchMiddlewareError from '#src/observability/middleware/catch-middleware-error.js' import { noCacheControl } from '#src/frame/middleware/cache-control.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { formatErrors } from './lib/middleware-errors.js' import { publish as _publish } from './lib/hydro.js' const router = express.Router() -const ajv = new Ajv() -addFormats(ajv) const OMIT_FIELDS = ['type'] const allowedTypes = new Set(without(Object.keys(schemas), 'validation')) const isProd = process.env.NODE_ENV === 'production' -const validations = mapValues(schemas, (schema) => ajv.compile(schema)) - +const validators = mapValues(schemas, (schema) => getJsonValidator(schema)) // In production, fire and not wait to respond. // _publish will send an error to failbot, // so we don't get alerts but we still track it. @@ -47,7 +43,7 @@ router.post( } // Validate the data matches the corresponding data schema - const validate = validations[type] + const validate = validators[type] if (!validate(req.body)) { const hash = `${req.ip}:${validate.errors .map((error) => error.message + error.instancePath) diff --git a/src/events/tests/middleware-errors.js b/src/events/tests/middleware-errors.js index 8a986e655fad..931b42f5a13f 100644 --- a/src/events/tests/middleware-errors.js +++ b/src/events/tests/middleware-errors.js @@ -1,17 +1,13 @@ -import Ajv from 'ajv' -import addFormats from 'ajv-formats' +import { validateJson } from '#src/tests/lib/validate-json-schema.js' import { formatErrors } from '../lib/middleware-errors.js' import { schemas } from '../lib/schema.js' -const ajv = new Ajv() -addFormats(ajv) - expect.extend({ toMatchSchema(data, schema) { - const isValid = ajv.validate(schema, data) + const { isValid, errors } = validateJson(schema, data) return { pass: isValid, - message: () => (isValid ? '' : ajv.errorsText()), + message: () => (isValid ? '' : errors.message), } }, }) @@ -19,8 +15,9 @@ expect.extend({ describe('formatErrors', () => { it('should produce objects that match the validation spec', () => { // Produce an error - ajv.validate({ type: 'string' }, 0) - for (const formatted of formatErrors(ajv.errors, '')) { + const { errors } = validateJson({ type: 'string' }, 0) + const formattedErrors = formatErrors(errors, '') + for (const formatted of formattedErrors) { expect(formatted).toMatchSchema(schemas.validation) } }) diff --git a/src/frame/lib/read-frontmatter.js b/src/frame/lib/read-frontmatter.js index 82779b5ee98b..0cfdb1bbeddf 100644 --- a/src/frame/lib/read-frontmatter.js +++ b/src/frame/lib/read-frontmatter.js @@ -1,25 +1,12 @@ import matter from 'gray-matter' -import Ajv from 'ajv' -import addErrors from 'ajv-errors' -import addFormats from 'ajv-formats' -import semver from 'semver' - -const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }) -ajv.addKeyword({ - keyword: 'translatable', -}) -ajv.addFormat('semver', { - validate: (x) => semver.validRange(x), -}) -addErrors(ajv) -addFormats(ajv) + +import { validateJson } from '#src/tests/lib/validate-json-schema.js' function readFrontmatter(markdown, opts = {}) { const schema = opts.schema || { type: 'object', properties: {} } const filepath = opts.filepath || null let content, data - let errors = [] try { ;({ content, data } = matter(markdown)) @@ -39,18 +26,13 @@ function readFrontmatter(markdown, opts = {}) { } if (filepath) error.filepath = filepath - errors.push(error) + const errors = [error] console.warn(errors) return { errors } } - const ajvValidate = ajv.compile(schema) - const valid = ajvValidate(data) - - if (!valid) { - errors = ajvValidate.errors - } + const validate = validateJson(schema, data) // Combine the AJV-supplied `instancePath` and `params` into a more user-friendly frontmatter path. // For example, given: @@ -69,8 +51,10 @@ function readFrontmatter(markdown, opts = {}) { return typeof mainProps !== 'object' ? `${prefixProps}.${mainProps}` : prefixProps } - if (!valid && filepath) { - errors = ajvValidate.errors.map((error) => { + const errors = [] + + if (!validate.isValid && filepath) { + const formattedErrors = validate.errors.map((error) => { const userFriendly = {} userFriendly.property = cleanPropertyPath(error.params, error.instancePath) userFriendly.message = error.message @@ -78,6 +62,9 @@ function readFrontmatter(markdown, opts = {}) { userFriendly.filepath = filepath return userFriendly }) + errors.push(...formattedErrors) + } else if (!validate.isValid) { + errors.push(...validate.errors) } return { content, data, errors } diff --git a/src/frame/tests/site-tree.js b/src/frame/tests/site-tree.js index a3e3f0623691..a7940ce902b5 100644 --- a/src/frame/tests/site-tree.js +++ b/src/frame/tests/site-tree.js @@ -1,5 +1,6 @@ -import Ajv from 'ajv' import { jest } from '@jest/globals' + +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import schema from '#src/tests/helpers/schemas/site-tree-schema.js' import EnterpriseServerReleases from '#src/versions/lib/enterprise-server-releases.js' import { loadSiteTree } from '#src/frame/lib/page-data.js' @@ -8,8 +9,7 @@ import { formatAjvErrors } from '#src/tests/helpers/schemas.js' const latestEnterpriseRelease = EnterpriseServerReleases.latest -const ajv = new Ajv({ allErrors: true }) -const siteTreeValidate = ajv.compile(schema.childPage) +const siteTreeValidate = getJsonValidator(schema.childPage) describe('siteTree', () => { jest.setTimeout(3 * 60 * 1000) @@ -58,14 +58,14 @@ describe('siteTree', () => { function validate(currentPage) { ;(currentPage.childPages || []).forEach((childPage) => { - const valid = siteTreeValidate(childPage) + const isValid = siteTreeValidate(childPage) let errors - if (!valid) { + if (!isValid) { errors = `file ${childPage.page.fullPath}: ${formatAjvErrors(siteTreeValidate.errors)}` } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) // Run recurisvely until we run out of child pages validate(childPage) diff --git a/src/github-apps/scripts/sync.js b/src/github-apps/scripts/sync.js index 544fb18a42ff..9a2021ae75a4 100644 --- a/src/github-apps/scripts/sync.js +++ b/src/github-apps/scripts/sync.js @@ -10,7 +10,7 @@ import yaml from 'js-yaml' import { getContents } from '#src/workflows/git-utils.js' import permissionSchema from './permission-list-schema.js' import enabledSchema from './enabled-list-schema.js' -import { validateData } from '../../rest/scripts/utils/validate-data.js' +import { validateJson } from '#src/tests/lib/validate-json-schema.js' const ENABLED_APPS_DIR = 'src/github-apps/data' const CONFIG_FILE = 'src/github-apps/lib/config.json' @@ -287,12 +287,20 @@ function initAppData(storage, category, data) { async function validateAppData(data, pageType) { if (pageType.includes('permissions')) { for (const value of Object.values(data)) { - validateData(value, permissionSchema) + const { isValid, errors } = validateJson(permissionSchema, value) + if (!isValid) { + console.error(JSON.stringify(errors, null, 2)) + throw new Error('GitHub Apps permission schema validation failed') + } } } else { for (const arrayItems of Object.values(data)) { for (const item of arrayItems) { - validateData(item, enabledSchema) + const { isValid, errors } = validateJson(enabledSchema, item) + if (!isValid) { + console.error(JSON.stringify(errors, null, 2)) + throw new Error('GitHub Apps enabled apps schema validation failed') + } } } } diff --git a/src/graphql/tests/validate-schema.js b/src/graphql/tests/validate-schema.js index 1d6bc78dffbe..f201e24d3b34 100644 --- a/src/graphql/tests/validate-schema.js +++ b/src/graphql/tests/validate-schema.js @@ -1,6 +1,6 @@ import { jest } from '@jest/globals' -import Ajv from 'ajv' +import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' import readJsonFile from '#src/frame/lib/read-json-file.js' import { schemaValidator, previewsValidator, upcomingChangesValidator } from '../lib/validator.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' @@ -11,15 +11,8 @@ const allVersionValues = Object.values(allVersions) const graphqlVersions = allVersionValues.map((v) => v.openApiVersionName) const graphqlTypes = readJsonFile('./src/graphql/lib/types.json').map((t) => t.kind) -const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }) -const previewsValidate = ajv.compile(previewsValidator) -const upcomingChangesValidate = ajv.compile(upcomingChangesValidator) -// setup ajv validator functions for each graphql type (e.g. queries, mutations, -// etc.) -const schemaValidatorFunctions = {} -graphqlTypes.forEach((type) => { - schemaValidatorFunctions[type] = ajv.compile(schemaValidator[type]) -}) +const previewsValidate = getJsonValidator(previewsValidator) +const upcomingChangesValidate = getJsonValidator(upcomingChangesValidator) describe('graphql json files', () => { jest.setTimeout(3 * 60 * 1000) @@ -38,16 +31,16 @@ describe('graphql json files', () => { if (typeObjsTested.has(key)) return typeObjsTested.add(key) - const valid = schemaValidatorFunctions[type](typeObj) - let errors + const { isValid, errors } = validateJson(schemaValidator[type], typeObj) - if (!valid) { - errors = `kind: ${typeObj.kind}, name: ${typeObj.name}: ${formatAjvErrors( - schemaValidatorFunctions[type].errors, + let formattedErrors = errors + if (!isValid) { + formattedErrors = `kind: ${typeObj.kind}, name: ${typeObj.name}: ${formatAjvErrors( + errors, )}` } - expect(valid, errors).toBe(true) + expect(isValid, formattedErrors).toBe(true) }) }) }) @@ -57,14 +50,14 @@ describe('graphql json files', () => { graphqlVersions.forEach((version) => { const previews = readJsonFile(`${GRAPHQL_DATA_DIR}/${version}/previews.json`) previews.forEach((preview) => { - const valid = previewsValidate(preview) + const isValid = previewsValidate(preview) let errors - if (!valid) { + if (!isValid) { errors = formatAjvErrors(previewsValidate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) }) @@ -75,14 +68,14 @@ describe('graphql json files', () => { for (const changes of Object.values(upcomingChanges)) { // each object value is an array of changes changes.forEach((changeObj) => { - const valid = upcomingChangesValidate(changeObj) + const isValid = upcomingChangesValidate(changeObj) let errors - if (!valid) { + if (!isValid) { errors = formatAjvErrors(upcomingChangesValidate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) } }) diff --git a/src/learning-track/tests/validate-schema.js b/src/learning-track/tests/validate-schema.js index 333e5becac74..766600fc5711 100644 --- a/src/learning-track/tests/validate-schema.js +++ b/src/learning-track/tests/validate-schema.js @@ -6,10 +6,10 @@ import { jest } from '@jest/globals' import { liquid } from '#src/content-render/index.js' import learningTracksSchema from '../lib/learning-tracks-schema.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' -import { ajvValidate } from '#src/tests/lib/ajv-validate.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' const learningTrackRootPath = 'data/learning-tracks' -const jsonValidator = ajvValidate(learningTracksSchema) +const validate = getJsonValidator(learningTracksSchema) const yamlWalkOptions = { globs: ['**/*.yml'], directories: false, @@ -31,14 +31,14 @@ describe('lint learning tracks', () => { }) it('matches the schema', () => { - const valid = jsonValidator(yamlContent) + const isValid = validate(yamlContent) let errors - if (!valid) { - errors = formatAjvErrors(jsonValidator.errors) + if (!isValid) { + errors = formatAjvErrors(validate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) it('contains valid liquid', () => { diff --git a/src/products/tests/products.js b/src/products/tests/products.js index 0c87ac34c83f..b86a427a71d4 100644 --- a/src/products/tests/products.js +++ b/src/products/tests/products.js @@ -1,10 +1,9 @@ -import Ajv from 'ajv' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { productMap } from '#src/products/lib/all-products.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' import schema from '#src/tests/helpers/schemas/products-schema.js' -const ajv = new Ajv({ allErrors: true }) -const validate = ajv.compile(schema) +const validate = getJsonValidator(schema) describe('products module', () => { test('is an object with product ids as keys', () => { @@ -14,13 +13,13 @@ describe('products module', () => { test('every product is valid', () => { Object.values(productMap).forEach((product) => { - const valid = validate(product) + const isValid = validate(product) let errors - if (!valid) { - errors = formatAjvErrors(valid.errors) + if (!isValid) { + errors = formatAjvErrors(validate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) }) diff --git a/src/release-notes/tests/validate-schema.js b/src/release-notes/tests/validate-schema.js index 841b3889cd20..dbdb0b394e7b 100644 --- a/src/release-notes/tests/validate-schema.js +++ b/src/release-notes/tests/validate-schema.js @@ -5,10 +5,10 @@ import { jest } from '@jest/globals' import releaseNotesSchema from '../lib/release-notes-schema.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' -import { ajvValidate } from '#src/tests/lib/ajv-validate.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' const ghesReleaseNoteRootPath = 'data/release-notes' -const jsonValidator = ajvValidate(releaseNotesSchema) +const validate = getJsonValidator(releaseNotesSchema) const yamlWalkOptions = { globs: ['**/*.yml'], directories: false, @@ -29,14 +29,14 @@ describe('lint enterprise release notes', () => { }) it('matches the schema', () => { - const valid = jsonValidator(yamlContent) + const isValid = validate(yamlContent) let errors - if (!valid) { - errors = formatAjvErrors(jsonValidator.errors) + if (!isValid) { + errors = formatAjvErrors(validate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) }) diff --git a/src/rest/scripts/utils/operation.js b/src/rest/scripts/utils/operation.js index 5d0e2fa0691d..ad655d6fcdb1 100644 --- a/src/rest/scripts/utils/operation.js +++ b/src/rest/scripts/utils/operation.js @@ -7,7 +7,7 @@ import mergeAllOf from 'json-schema-merge-allof' import { renderContent } from '#src/content-render/index.js' import getCodeSamples from './create-rest-examples.js' import operationSchema from './operation-schema.js' -import { validateData } from './validate-data.js' +import { validateJson } from '#src/tests/lib/validate-json-schema.js' import { getBodyParams } from './get-body-params.js' export default class Operation { @@ -59,7 +59,11 @@ export default class Operation { this.renderPreviewNotes(), ]) - validateData(this, operationSchema) + const { isValid, errors } = validateJson(operationSchema, this) + if (!isValid) { + console.error(JSON.stringify(errors, null, 2)) + throw new Error('Invalid OpenAPI operation found') + } } async renderDescription() { diff --git a/src/rest/scripts/utils/validate-data.js b/src/rest/scripts/utils/validate-data.js deleted file mode 100644 index 38095df00451..000000000000 --- a/src/rest/scripts/utils/validate-data.js +++ /dev/null @@ -1,10 +0,0 @@ -import Ajv from 'ajv' -const ajv = new Ajv() - -export async function validateData(data, schema) { - const valid = ajv.validate(schema, data) - if (!valid) { - console.error(JSON.stringify(ajv.errors, null, 2)) - throw new Error('Invalid OpenAPI operation found') - } -} diff --git a/src/secret-scanning/tests/validate-schema.js b/src/secret-scanning/tests/validate-schema.js index 02c182881b5f..294a40973812 100644 --- a/src/secret-scanning/tests/validate-schema.js +++ b/src/secret-scanning/tests/validate-schema.js @@ -2,7 +2,7 @@ import fs from 'fs' import yaml from 'js-yaml' import { jest } from '@jest/globals' -import { ajvValidate } from '#src/tests/lib/ajv-validate.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' import secretScanningSchema from '../lib/secret-scanning-schema.js' @@ -10,16 +10,16 @@ jest.useFakeTimers({ legacyFakeTimers: true }) describe('lint secret-scanning', () => { const yamlContent = yaml.load(fs.readFileSync('data/secret-scanning.yml', 'utf8')) - const jsonValidate = ajvValidate(secretScanningSchema) + const validate = getJsonValidator(secretScanningSchema) test('matches the schema', () => { - const valid = jsonValidate(yamlContent) + const isValid = validate(yamlContent) let errors - if (!valid) { - errors = formatAjvErrors(jsonValidate.errors) + if (!isValid) { + errors = formatAjvErrors(validate.errors) } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) diff --git a/src/tests/lib/ajv-validate.js b/src/tests/lib/ajv-validate.js deleted file mode 100644 index 0a520e23504f..000000000000 --- a/src/tests/lib/ajv-validate.js +++ /dev/null @@ -1,16 +0,0 @@ -import Ajv from 'ajv' -import addErrors from 'ajv-errors' -import addFormats from 'ajv-formats' -import semver from 'semver' - -const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }) -addFormats(ajv) -addErrors(ajv) -// *** TODO: We can drop this override once the frontmatter schema has been updated to work with AJV. *** -ajv.addFormat('semver', { - validate: (x) => semver.validRange(x), -}) - -export function ajvValidate(schema) { - return ajv.compile(schema) -} diff --git a/src/tests/lib/validate-json-schema.js b/src/tests/lib/validate-json-schema.js new file mode 100644 index 000000000000..f5ba06951f2c --- /dev/null +++ b/src/tests/lib/validate-json-schema.js @@ -0,0 +1,41 @@ +import Ajv from 'ajv' +import addErrors from 'ajv-errors' +import addFormats from 'ajv-formats' +import semver from 'semver' + +const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }) +addFormats(ajv) +addErrors(ajv) + +// Custom JSON keywords +ajv.addKeyword({ + keyword: 'translatable', +}) + +// Custom JSON formats +ajv.addFormat('semver', { + validate: (x) => semver.validRange(x), +}) + +// The ajv.validate function is supposed to cache +// the compiled schema, but the documentation says +// that the best permformance is achieved by calling +// the compile function and then calling validate. +// So when the same schema is validated multiple times, +// this is the best function to use. If the schema +// changes from one call to the next, then the validateJson +// function makes more sense to use. +export function getJsonValidator(schema) { + return ajv.compile(schema) +} + +// The next call to ajv.validate will overwrite +// the ajv.errors property, so returning it here +// ensures that it remains accessible. +export function validateJson(schema, data) { + const isValid = ajv.validate(schema, data) + return { + isValid, + errors: isValid ? null : structuredClone(ajv.errors), + } +} diff --git a/src/versions/tests/versions.js b/src/versions/tests/versions.js index 52859d93da0c..1c1950199d89 100644 --- a/src/versions/tests/versions.js +++ b/src/versions/tests/versions.js @@ -1,6 +1,6 @@ import { jest } from '@jest/globals' -import Ajv from 'ajv' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { allVersions } from '#src/versions/lib/all-versions.js' import { latest } from '#src/versions/lib/enterprise-server-releases.js' import schema from '#src/tests/helpers/schemas/versions-schema.js' @@ -9,8 +9,7 @@ import { formatAjvErrors } from '#src/tests/helpers/schemas.js' jest.useFakeTimers({ legacyFakeTimers: true }) -const ajv = new Ajv({ allErrors: true }) -const validate = ajv.compile(schema) +const validate = getJsonValidator(schema) describe('versions module', () => { test('is an object with versions as keys', () => { @@ -20,14 +19,14 @@ describe('versions module', () => { test('every version is valid', () => { Object.values(allVersions).forEach((versionObj) => { - const valid = validate(versionObj) + const isValid = validate(versionObj) let errors - if (!valid) { + if (!isValid) { errors = `version '${versionObj.version}': ${formatAjvErrors(validate.errors)}` } - expect(valid, errors).toBe(true) + expect(isValid, errors).toBe(true) }) }) diff --git a/src/webhooks/scripts/webhook.js b/src/webhooks/scripts/webhook.js index 0c770154b5b7..59e56a0bc390 100644 --- a/src/webhooks/scripts/webhook.js +++ b/src/webhooks/scripts/webhook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import Ajv from 'ajv' import { get, isPlainObject } from 'lodash-es' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' import { renderContent } from '#src/content-render/index.js' import webhookSchema from './webhook-schema.js' import { getBodyParams } from '../../rest/scripts/utils/get-body-params.js' @@ -15,6 +15,8 @@ const NO_CHILD_PROPERTIES = [ 'sender', ] +const validate = getJsonValidator(webhookSchema) + export default class Webhook { #webhook constructor(webhook) { @@ -50,10 +52,9 @@ export default class Webhook { async process() { await Promise.all([this.renderDescription(), this.renderBodyParameterDescriptions()]) - const ajv = new Ajv() - const valid = ajv.validate(webhookSchema, this) - if (!valid) { - console.error(JSON.stringify(ajv.errors, null, 2)) + const isValid = validate(this) + if (!isValid) { + console.error(JSON.stringify(validate.errors, null, 2)) throw new Error(`Invalid OpenAPI webhook found: ${this.category}`) } } From c2bcee148ef3226c73b8175cbd1b9ac2798bc6db Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:18:44 -0700 Subject: [PATCH 3/3] [Improvement]: Get started (phase 1): Moving "Create a repo" from Quickstart to Repositories (#47448) Co-authored-by: Anne-Marie <102995847+am-stead@users.noreply.github.com> --- content/get-started/quickstart/github-flow.md | 2 +- content/get-started/quickstart/index.md | 1 - content/get-started/quickstart/set-up-git.md | 2 +- content/github-cli/index.md | 2 +- .../about-repositories.md | 2 +- .../index.md | 1 + .../quickstart-for-repositories.md} | 29 ++++--------------- content/repositories/index.md | 2 +- .../getting-started/create-a-repository.md | 2 +- 9 files changed, 12 insertions(+), 31 deletions(-) rename content/{get-started/quickstart/create-a-repo.md => repositories/creating-and-managing-repositories/quickstart-for-repositories.md} (75%) diff --git a/content/get-started/quickstart/github-flow.md b/content/get-started/quickstart/github-flow.md index fed42a111759..3d6a943855f4 100644 --- a/content/get-started/quickstart/github-flow.md +++ b/content/get-started/quickstart/github-flow.md @@ -23,7 +23,7 @@ topics: ## Prerequisites -To follow {% data variables.product.prodname_dotcom %} flow, you will need a {% data variables.product.prodname_dotcom %} account and a repository. {% ifversion fpt or ghec %}For information on how to create an account, see "[AUTOTITLE](/get-started/quickstart/creating-an-account-on-github)."{% elsif ghes or ghae %}For more information, contact your site administrator.{% endif %} For information on how to create a repository, see "[AUTOTITLE](/get-started/quickstart/create-a-repo)."{% ifversion fpt or ghec %} For information on how to find an existing repository to contribute to, see "[AUTOTITLE](/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)."{% endif %} +To follow {% data variables.product.prodname_dotcom %} flow, you will need a {% data variables.product.prodname_dotcom %} account and a repository. {% ifversion fpt or ghec %}For information on how to create an account, see "[AUTOTITLE](/get-started/quickstart/creating-an-account-on-github)."{% elsif ghes or ghae %}For more information, contact your site administrator.{% endif %} For information on how to create a repository, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/quickstart-for-repositories)."{% ifversion fpt or ghec %} For information on how to find an existing repository to contribute to, see "[AUTOTITLE](/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)."{% endif %} ## Following {% data variables.product.prodname_dotcom %} flow diff --git a/content/get-started/quickstart/index.md b/content/get-started/quickstart/index.md index 48d1917428a5..f715bbd20b7f 100644 --- a/content/get-started/quickstart/index.md +++ b/content/get-started/quickstart/index.md @@ -15,7 +15,6 @@ children: - /creating-an-account-on-github - /hello-world - /set-up-git - - /create-a-repo - /fork-a-repo - /github-flow - /contributing-to-projects diff --git a/content/get-started/quickstart/set-up-git.md b/content/get-started/quickstart/set-up-git.md index 829bf4347069..1b3e69421807 100644 --- a/content/get-started/quickstart/set-up-git.md +++ b/content/get-started/quickstart/set-up-git.md @@ -32,7 +32,7 @@ If you want to work with Git locally, but do not want to use the command line, y If you do not need to work with files locally, {% data variables.product.product_name %} lets you complete many Git-related actions directly in the browser, including: -- [Creating a repository](/get-started/quickstart/create-a-repo) +- [AUTOTITLE](/repositories/creating-and-managing-repositories/quickstart-for-repositories) - [Forking a repository](/get-started/quickstart/fork-a-repo) - [Managing files](/repositories/working-with-files/managing-files) - [Being social](/get-started/quickstart/be-social) diff --git a/content/github-cli/index.md b/content/github-cli/index.md index 247a894f343e..c3916551f379 100644 --- a/content/github-cli/index.md +++ b/content/github-cli/index.md @@ -23,7 +23,7 @@ featuredLinks: - /pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request - /issues/tracking-your-work-with-issues/creating-an-issue - /authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account - - /get-started/quickstart/create-a-repo + - /repositories/creating-and-managing-repositories/quickstart-for-repositories - /pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally - /pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/merging-a-pull-request - /get-started/quickstart/fork-a-repo diff --git a/content/repositories/creating-and-managing-repositories/about-repositories.md b/content/repositories/creating-and-managing-repositories/about-repositories.md index 68c638f790d1..52b89db93886 100644 --- a/content/repositories/creating-and-managing-repositories/about-repositories.md +++ b/content/repositories/creating-and-managing-repositories/about-repositories.md @@ -24,7 +24,7 @@ topics: A repository is the most basic element of {% data variables.product.prodname_dotcom %}. It's a place where you can store your code, your files, and each file's revision history. Repositories can have multiple collaborators and can be either public{% ifversion ghes or ghec %}, internal,{% endif %} or private. -To create a new repository, go to [https://github.com/new](https://github.com/new). For instructions, see "[AUTOTITLE](/get-started/quickstart/create-a-repo)." +To create a new repository, go to [https://github.com/new](https://github.com/new). For instructions, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/quickstart-for-repositories)." ## Repository terminology diff --git a/content/repositories/creating-and-managing-repositories/index.md b/content/repositories/creating-and-managing-repositories/index.md index 765c772c722f..72a922e064b8 100644 --- a/content/repositories/creating-and-managing-repositories/index.md +++ b/content/repositories/creating-and-managing-repositories/index.md @@ -14,6 +14,7 @@ topics: children: - /about-repositories - /best-practices-for-repositories + - /quickstart-for-repositories - /repository-limits - /creating-a-new-repository - /creating-a-repository-from-a-template diff --git a/content/get-started/quickstart/create-a-repo.md b/content/repositories/creating-and-managing-repositories/quickstart-for-repositories.md similarity index 75% rename from content/get-started/quickstart/create-a-repo.md rename to content/repositories/creating-and-managing-repositories/quickstart-for-repositories.md index 5dc81bf1a225..87da8931ae50 100644 --- a/content/get-started/quickstart/create-a-repo.md +++ b/content/repositories/creating-and-managing-repositories/quickstart-for-repositories.md @@ -1,11 +1,13 @@ --- -title: Create a repo +title: Quickstart for repositories +type: quick_start redirect_from: - /create-a-repo - /articles/create-a-repo - /github/getting-started-with-github/create-a-repo - /github/getting-started-with-github/quickstart/create-a-repo -intro: 'To put your project up on {% data variables.product.prodname_dotcom %}, you will need to create a repository for it to live in.' + - /get-started/quickstart/create-a-repo +intro: 'Learn how to create a new repository and commit your first change in 5 minutes.' versions: fpt: '*' ghes: '*' @@ -19,28 +21,7 @@ topics: --- ## Create a repository -{% ifversion fpt or ghec %} - -You can store a variety of projects in {% data variables.product.prodname_dotcom %} repositories, including open source projects. With open source projects, you can share code to make better, more reliable software. You can use repositories to collaborate with others and track your work. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/about-repositories)." To learn more about open source projects, visit [OpenSource.org](https://opensource.org/about). - -{% elsif ghes or ghae %} - -You can store a variety of projects in {% data variables.product.product_name %} repositories, including innersource projects. With innersource, you can share code to make better, more reliable software. For more information on innersource, see {% data variables.product.company_short %}'s white paper "[An introduction to innersource](https://resources.github.com/whitepapers/introduction-to-innersource/)." - -{% endif %} - -{% ifversion fpt or ghec %} - -{% note %} - -**Notes:** -- You can create public repositories for an open source project. When creating your public repository, make sure to include a [license file](https://choosealicense.com/) that determines how you want your project to be shared with others. {% data reusables.open-source.open-source-guide-repositories %} -- {% data reusables.open-source.open-source-learning %} -- You can also add community health files to your repositories, to set guidelines on how to contribute, keep your repositories safe, and much more. For more information, see "[AUTOTITLE](/communities/setting-up-your-project-for-healthy-contributions/creating-a-default-community-health-file)." - -{% endnote %} - -{% endif %} +{% data variables.product.product_name %} repositories store a variety of projects. In this guide, you'll create a repository and commit your first change. {% webui %} diff --git a/content/repositories/index.md b/content/repositories/index.md index 09c9105c3101..fed306eb9f74 100644 --- a/content/repositories/index.md +++ b/content/repositories/index.md @@ -3,7 +3,7 @@ title: Repositories documentation shortTitle: Repositories intro: Learn to use and manage the repositories that allow you to store and collaborate on your project's code. introLinks: - quickstart: /get-started/quickstart/create-a-repo + quickstart: /repositories/creating-and-managing-repositories/quickstart-for-repositories overview: /repositories/creating-and-managing-repositories/about-repositories featuredLinks: startHere: diff --git a/data/reusables/getting-started/create-a-repository.md b/data/reusables/getting-started/create-a-repository.md index 83343b0b2954..588d68ceb39f 100644 --- a/data/reusables/getting-started/create-a-repository.md +++ b/data/reusables/getting-started/create-a-repository.md @@ -1 +1 @@ -Creating a repository for your project allows you to store code in {% data variables.product.prodname_dotcom %}. This provides a backup of your work that you can choose to share with other developers. For more information, see “[Create a repository](/get-started/quickstart/create-a-repo)." +Creating a repository for your project allows you to store code in {% data variables.product.prodname_dotcom %}. This provides a backup of your work that you can choose to share with other developers. For more information, see “[AUTOTITLE](/repositories/creating-and-managing-repositories/quickstart-for-repositories)."