@@ -248,7 +248,7 @@ describe('lint markdown content', () => {
248248 describe . each ( mdToLint ) (
249249 '%s' ,
250250 ( markdownRelPath , markdownAbsPath ) => {
251- let content , ast , links , isHidden , isEarlyAccess , isSitePolicy , frontmatterErrors , frontmatterData
251+ let content , ast , links , yamlScheduledWorkflows , isHidden , isEarlyAccess , isSitePolicy , frontmatterErrors , frontmatterData
252252
253253 beforeAll ( async ( ) => {
254254 const fileContents = await readFileAsync ( markdownAbsPath , 'utf8' )
@@ -266,6 +266,23 @@ describe('lint markdown content', () => {
266266 visit ( ast , [ 'link' , 'definition' ] , node => {
267267 links . push ( node . url )
268268 } )
269+
270+ yamlScheduledWorkflows = [ ]
271+ visit ( ast , 'code' , node => {
272+ if ( / y a ? m l / . test ( node . lang ) && node . value . includes ( 'schedule' ) && node . value . includes ( 'cron' ) ) {
273+ yamlScheduledWorkflows . push ( node . value )
274+ }
275+ } )
276+
277+ // visit is not async-friendly so we need to do an async map to parse the YML snippets
278+ yamlScheduledWorkflows = ( await Promise . all ( yamlScheduledWorkflows . map ( async ( snippet ) => {
279+ // If we don't parse the Liquid first, yaml loading chokes on {% raw %} tags
280+ const rendered = await renderContent . liquid . parseAndRender ( snippet )
281+ const parsed = yaml . safeLoad ( rendered )
282+ return parsed . on . schedule
283+ } ) ) )
284+ . flat ( )
285+ . map ( schedule => schedule . cron )
269286 } )
270287
271288 // We need to support some non-Early Access hidden docs in Site Policy
@@ -293,6 +310,20 @@ describe('lint markdown content', () => {
293310 expect ( matches . length , errorMessage ) . toBe ( 0 )
294311 } )
295312
313+ test ( 'yaml snippets that include scheduled workflows must not run on the hour' , async ( ) => {
314+ const hourlySchedules = yamlScheduledWorkflows . filter ( schedule => {
315+ const hour = schedule . split ( ' ' ) [ 0 ]
316+ // return any minute cron segments that equal 0, 00, 000, etc.
317+ return ! / [ ^ 0 ] / . test ( hour )
318+ } )
319+ expect ( hourlySchedules ) . toEqual ( [ ] )
320+ } )
321+
322+ // Note this only ensures that scheduled workflow snippets are unique _per Markdown file_
323+ test ( 'yaml snippets that include scheduled workflows run at unique times' , ( ) => {
324+ expect ( yamlScheduledWorkflows . length ) . toEqual ( new Set ( yamlScheduledWorkflows ) . size )
325+ } )
326+
296327 test ( 'must not leak Early Access doc URLs' , async ( ) => {
297328 // Only execute for docs that are NOT Early Access
298329 if ( ! isEarlyAccess ) {
0 commit comments