Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw when values are updated in CI #17

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ jobs:

- name: Build
run: npm run build

- name: Check Markdown Docs
run: npm run markdown-inject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love this is an integration test - it looks to small to be useful but really it is!

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Options:
-b, --block-prefix <prefix> specifies the prefix for START and END HTML
comment blocks (default: "CODEBLOCK")
-n, --no-follow-symbolic-links prevents globs from following symlinks
-f --force-write write blocks when CI is truthy (default:
false)
-q, --quiet emits no console log statements (default:
false)
-e, --no-system-environment prevents "command"s from receiving system
Expand Down Expand Up @@ -235,6 +237,8 @@ My password is:

<!-- CODEBLOCK_END_EXAMPLE_MAP_ENV -->

Calling `markdown-inject` with a truthy `$CI` environment variable will exit with code `1` without writing any changes if any blocks would have been modified. If you instead would intentionally like to write content in a CI environment, this can be bypassed with the [`--force-write` CLI flag](#usage).
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved

## Codeblock Configuration

The `CODEBLOCK_START` HTML comment config block has the following properties:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"clean": "rm -rf dist",
"lint": "eslint .",
"test": "jest",
"prepare": "husky install",
"prepare": "husky install && ( [ \"$CI\" = 'true' ] || npm run build )",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and clean are not directly Windows friendly

image

Of course a bash or zsh prompt from within VS Code works.

"validate": "npm run lint && npm test && npm run build",
"markdown-inject": "node dist './**/*.md'"
},
Expand Down
118 changes: 117 additions & 1 deletion src/__tests__/md-inject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ describe('Markdown injection', () => {
jest.mock('../Logger')
logger = new Logger()
Logger.mockImplementation(() => logger)
delete process.env.CI
})

process.env = originalProcessEnv
afterEach(() => {
process.exitCode = 0
process.env = {
...originalProcessEnv,
}
})

it('collects all in-repo markdown files', async () => {
Expand Down Expand Up @@ -292,6 +298,116 @@ console.log('baz')
expect(fs.writeFile).toHaveBeenCalledWith('foo.md', outFile)
})

it.each([[true], ['true'], ['something'], ['yes']])(
'throws gracefully when a change would be written in CI',
async (CI) => {
process.env.CI = CI as string
mock({
config: {
type: 'file',
value: 'bar.js',
},
mockResponse: `console.log('baz')`,
})

await injectMarkdown()

expect(process.exitCode).toBe(1)
}
)

it.each([[true], ['true'], ['something'], ['yes']])(
'does not write when a change would be written in CI',
async (CI) => {
process.env.CI = CI as string
mock({
config: {
type: 'file',
value: 'bar.js',
},
mockResponse: `console.log('baz')`,
})

await injectMarkdown()

expect(fs.writeFile).not.toHaveBeenCalled()
}
)

it.each([[true], ['true'], ['something'], ['yes']])(
'writes file changes and does not throw in CI when --force-write is passed',
async (CI) => {
process.env.CI = CI as string
mock({
config: {
type: 'file',
value: 'bar.js',
},
mockResponse: `console.log('baz')`,
})

await injectMarkdown({
forceWrite: true,

// defaults
blockPrefix: 'CODEBLOCK',
followSymbolicLinks: true,
globPattern: '**/*.md',
quiet: false,
})

const outFile = `
<!-- CODEBLOCK_START {"type":"file","value":"bar.js"} -->
<!-- prettier-ignore -->
~~~~~~~~~~js
File: bar.js

console.log('baz')
~~~~~~~~~~

<!-- CODEBLOCK_END -->`
expect(fs.writeFile).toHaveBeenCalledWith('foo.md', outFile)
expect(process.exitCode).not.toBe(1)
}
)

it.each([[''], ['false'], [false], [undefined]])(
bmuenzenmeyer marked this conversation as resolved.
Show resolved Hide resolved
'does not throw when CI is %s',
async (CI) => {
process.env.CI = CI as string
mock({
config: {
type: 'file',
value: 'bar.js',
},
mockResponse: `console.log('baz')`,
})

await injectMarkdown({
forceWrite: true,

// defaults
blockPrefix: 'CODEBLOCK',
followSymbolicLinks: true,
globPattern: '**/*.md',
quiet: false,
})

const outFile = `
<!-- CODEBLOCK_START {"type":"file","value":"bar.js"} -->
<!-- prettier-ignore -->
~~~~~~~~~~js
File: bar.js

console.log('baz')
~~~~~~~~~~

<!-- CODEBLOCK_END -->`
expect(fs.writeFile).toHaveBeenCalledWith('foo.md', outFile)
expect(process.exitCode).not.toBe(1)
}
)

it('trims whitespace (command)', async () => {
mock({
config: {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ program
'-n, --no-follow-symbolic-links',
'prevents globs from following symlinks'
)
.option('-f --force-write', 'write blocks when CI is truthy', false)
.option('-q, --quiet', 'emits no console log statements', false)
.option(
'-e, --no-system-environment',
Expand All @@ -40,6 +41,7 @@ Examples:
await injectMarkdown({
blockPrefix: options.blockPrefix,
followSymbolicLinks: options.followSymbolicLinks,
forceWrite: options.forceWrite,
globPattern,
quiet: options.quiet,
useSystemEnvironment: options.systemEnvironment,
Expand Down
27 changes: 25 additions & 2 deletions src/md-inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs-extra'
import path from 'path'
import { exec } from 'child_process'
import Logger from './Logger'
import { isCI } from './utils'

enum BlockSourceType {
file = 'file',
Expand All @@ -26,6 +27,7 @@ interface BlockInputOptions extends Omit<BlockOptions, 'type'> {
interface ReplaceOptions {
blockPrefix: string
followSymbolicLinks: boolean
forceWrite: boolean
globPattern: string
quiet: boolean
useSystemEnvironment: boolean
Expand All @@ -35,19 +37,27 @@ const main = async (
{
blockPrefix,
followSymbolicLinks,
forceWrite,
globPattern,
quiet,
useSystemEnvironment,
}: ReplaceOptions = {
blockPrefix: 'CODEBLOCK',
followSymbolicLinks: true,
forceWrite: false,
globPattern: '**/*.md',
quiet: false,
useSystemEnvironment: true,
}
): Promise<void> => {
const logger = new Logger(quiet)
logger.group('Injecting Markdown Blocks')

const writeChangedBlocks = forceWrite || !isCI()
logger.group(
writeChangedBlocks
? 'Injecting Markdown Blocks'
: 'Checking Markdown Blocks'
)

const markdownFiles = await glob(globPattern, {
followSymbolicLinks,
Expand Down Expand Up @@ -204,7 +214,9 @@ ${endPragma}`
}

if (modifiedFileContents !== originalFileContents) {
await fs.writeFile(fileName, modifiedFileContents)
if (writeChangedBlocks) {
await fs.writeFile(fileName, modifiedFileContents)
}
logger.log(
`${fileName}: ${blocksChanged} of ${totalBlocks} blocks changed (${blocksIgnored} ignored)`
)
Expand Down Expand Up @@ -243,8 +255,19 @@ ${endPragma}`
logger.log(
`Total: ${totalChanges} of ${totalBlocks} blocks (${totalIgnored} ignored)`
)

if (!writeChangedBlocks && totalChanges !== 0) {
logger.log()
logger.error(
'ERROR: Block updates detected in CI.\nNo block changes were written.\nCall with --force-write to bypass.'
)
logger.log()
process.exitCode = 1
}
} catch (err) {
logger.log()
logger.error(err)
logger.log()
process.exitCode = 1
}
logger.groupEnd()
Expand Down
9 changes: 9 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const isCI = (): boolean => {
const { CI } = process.env

const falsyValues = [undefined, null, '', false, false.toString()]

const isNotCi = falsyValues.some((falsyValue) => falsyValue === CI)

return !isNotCi
}