diff --git a/build-system/common/diff.js b/build-system/common/diff.js new file mode 100644 index 000000000000..45e90d04bd1e --- /dev/null +++ b/build-system/common/diff.js @@ -0,0 +1,68 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const argv = require('minimist')(process.argv.slice(2)); +const tempy = require('tempy'); +const {blue, bold, cyan, red} = require('kleur/colors'); +const {getStdout} = require('./process'); +const {log, logWithoutTimestamp} = require('./logging'); +const {writeFile} = require('fs-extra'); + +/** + * Diffs a file against content that might replace it. + * @param {string} filepath + * @param {string} content + * @return {!Promise} + */ +const diffTentative = (filepath, content) => + tempy.write.task(content, (temporary) => + getStdout(`git -c color.ui=always diff -U1 ${filepath} ${temporary}`) + .trim() + .replace(new RegExp(temporary, 'g'), `/${filepath}`) + ); + +/** + * Diffs a file against new content. + * If `argv.fix` is true, the file is written with the new content, otherwise + * errors out. + * @param {string} callerTask + * @param {string} filepath + * @param {string} tentative + */ +async function writeDiffOrFail(callerTask, filepath, tentative) { + const diff = await diffTentative(filepath, tentative); + + if (!diff.length) { + return; + } + + logWithoutTimestamp(); + logWithoutTimestamp(diff); + logWithoutTimestamp(); + + if (!argv.fix) { + log(red('ERROR:'), cyan(filepath), 'is missing the changes above.'); + log('⤷ To automatically apply them, run', cyan(`gulp ${callerTask} --fix`)); + throw new Error(`${filepath} is outdated`); + } + + await writeFile(filepath, tentative); + log('Wrote', bold(blue(filepath))); +} + +module.exports = { + diffTentative, + writeDiffOrFail, +}; diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js index 70774149b0f0..94260f140136 100644 --- a/build-system/pr-check/checks.js +++ b/build-system/pr-check/checks.js @@ -42,6 +42,7 @@ function pushBuildWorkflow() { timedExecOrDie('gulp check-types'); timedExecOrDie('gulp check-sourcemaps'); timedExecOrDie('gulp performance-urls'); + timedExecOrDie('gulp check-video-interface-list'); } async function prBuildWorkflow() { @@ -103,6 +104,7 @@ async function prBuildWorkflow() { timedExecOrDie('gulp check-types'); timedExecOrDie('gulp check-sourcemaps'); timedExecOrDie('gulp performance-urls'); + timedExecOrDie('gulp check-video-interface-list'); } } diff --git a/build-system/tasks/check-video-interface-list.js b/build-system/tasks/check-video-interface-list.js new file mode 100644 index 000000000000..c492aee5686f --- /dev/null +++ b/build-system/tasks/check-video-interface-list.js @@ -0,0 +1,87 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const {getStdout} = require('../common/process'); +const {readFile} = require('fs-extra'); +const {writeDiffOrFail} = require('../common/diff'); + +/** Checks or updates 3rd party video player list on this Markdown file. */ +const filepath = 'spec/amp-video-interface.md'; + +/** Excludes these extensions since they're on a separate list. */ +const excludeGeneric = ['amp-video', 'amp-video-iframe']; + +/** Determines whether a file belongs to an extension that should be listed. */ +const grepJsContent = '@implements {.*VideoInterface}'; + +/** Finds extension files here. */ +const grepJsFiles = 'extensions/**/*.js'; + +/** + * Returns a formatted list entry. + * @param {string} name + * @return {name} + */ +const entry = (name) => + `- [${name}](https://github.com/ampproject/amphtml/blob/master/extensions/${name}/${name}.md)\n`; + +/** + * Generates Markdown list by finding matching extensions. + * @return {string} + */ +const generateList = () => + getStdout( + ['grep -lr', `"${grepJsContent}"`, ...globby.sync(grepJsFiles)].join(' ') + ) + .trim() + .split('\n') + .reduce((list, path) => { + const name = path.substr('extensions/'.length).split('/').shift(); + return list + (excludeGeneric.includes(name) ? '' : entry(name)); + }, ''); + +/** + * Returns a RegExp that matches the existing list. + * @return {RegExp} + */ +const getListRegExp = () => + new RegExp( + `(${entry('NAME') + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/\NAME/g, `((?!${excludeGeneric.join('|')})[a-z0-9-]+)`)})+`, + 'gim' + ); + +/** + * Checks or updates 3rd party video player list. + */ +async function checkVideoInterfaceList() { + const current = await readFile(filepath, 'utf-8'); + const tentative = current.replace(getListRegExp(), generateList()); + if (current !== tentative) { + await writeDiffOrFail('check-video-interface-list', filepath, tentative); + } +} + +module.exports = { + checkVideoInterfaceList, +}; + +checkVideoInterfaceList.description = `Checks or updates 3rd party video player list on ${filepath}`; + +checkVideoInterfaceList.flags = { + 'fix': ' Write to file', +}; diff --git a/gulpfile.js b/gulpfile.js index 28067b2af430..8f2441f3e126 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -140,4 +140,9 @@ createTask('unit', 'unit', 'unit'); createTask('update-packages', 'updatePackages', 'update-packages'); createTask('validator', 'validator', 'validator'); createTask('validator-webui', 'validatorWebui', 'validator'); +createTask( + 'check-video-interface-list', + 'checkVideoInterfaceList', + 'check-video-interface-list' +); createTask('visual-diff', 'visualDiff', 'visual-diff'); diff --git a/spec/amp-video-interface.md b/spec/amp-video-interface.md index 9f4977ef4c28..1bf971f52bc3 100644 --- a/spec/amp-video-interface.md +++ b/spec/amp-video-interface.md @@ -16,22 +16,33 @@ these players implement. ### For 3rd party services -- [amp-3q-player](https://amp.dev/documentation/components/amp-3q-player) -- [amp-brid-player](https://amp.dev/documentation/components/amp-brid-player) -- [amp-brightcove](https://amp.dev/documentation/components/amp-brightcove) -- [amp-dailymotion](https://amp.dev/documentation/components/amp-dailymotion) -- [amp-delight-player](https://amp.dev/documentation/components/amp-delight-player) -- [amp-gfycat](https://amp.dev/documentation/components/amp-gfycat) -- [amp-ima-video](https://amp.dev/documentation/components/amp-ima-video) -- [amp-minute-media-player](https://amp.dev/documentation/components/amp-minute-media-player) -- [amp-mowplayer](https://amp.dev/documentation/components/amp-mowplayer) -- [amp-nexxtv-player](https://amp.dev/documentation/components/amp-nexxtv-player) -- [amp-ooyala-player](https://amp.dev/documentation/components/amp-ooyala-player) -- [amp-powr-player](https://amp.dev/documentation/components/amp-powr-player) -- [amp-vimeo](https://amp.dev/documentation/components/amp-vimeo) -- [amp-viqeo-player](https://amp.dev/documentation/components/amp-viqeo-player) -- [amp-wistia-player](https://amp.dev/documentation/components/amp-wistia-player) -- [amp-youtube](https://amp.dev/documentation/components/amp-youtube) + + +- [amp-3q-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-3q-player/amp-3q-player.md) +- [amp-brid-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-brid-player/amp-brid-player.md) +- [amp-brightcove](https://github.com/ampproject/amphtml/blob/master/extensions/amp-brightcove/amp-brightcove.md) +- [amp-dailymotion](https://github.com/ampproject/amphtml/blob/master/extensions/amp-dailymotion/amp-dailymotion.md) +- [amp-delight-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-delight-player/amp-delight-player.md) +- [amp-gfycat](https://github.com/ampproject/amphtml/blob/master/extensions/amp-gfycat/amp-gfycat.md) +- [amp-ima-video](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ima-video/amp-ima-video.md) +- [amp-jwplayer](https://github.com/ampproject/amphtml/blob/master/extensions/amp-jwplayer/amp-jwplayer.md) +- [amp-minute-media-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-minute-media-player/amp-minute-media-player.md) +- [amp-mowplayer](https://github.com/ampproject/amphtml/blob/master/extensions/amp-mowplayer/amp-mowplayer.md) +- [amp-nexxtv-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-nexxtv-player/amp-nexxtv-player.md) +- [amp-ooyala-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ooyala-player/amp-ooyala-player.md) +- [amp-powr-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-powr-player/amp-powr-player.md) +- [amp-redbull-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-redbull-player/amp-redbull-player.md) +- [amp-vimeo](https://github.com/ampproject/amphtml/blob/master/extensions/amp-vimeo/amp-vimeo.md) +- [amp-viqeo-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-viqeo-player/amp-viqeo-player.md) +- [amp-wistia-player](https://github.com/ampproject/amphtml/blob/master/extensions/amp-wistia-player/amp-wistia-player.md) +- [amp-youtube](https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube/amp-youtube.md)