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

feat: Add hidden snapshot command for future implementation #120

Merged
merged 17 commits into from
Apr 16, 2019
Merged
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
2 changes: 1 addition & 1 deletion src/commands/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class Exec extends PercyCommand {
static flags = {
'network-idle-timeout': flags.integer({
char: 't',
default: 50,
default: Constants.NETWORK_IDLE_TIMEOUT,
description: 'asset discovery network idle timeout (in milliseconds)',
}),
'port': flags.integer({
Expand Down
102 changes: 102 additions & 0 deletions src/commands/snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {flags} from '@oclif/command'
import Constants from '../services/constants'
import {StaticSnapshotOptions} from '../services/static-snapshot-options'
import StaticSnapshotService from '../services/static-snapshot-service'
import logger from '../utils/logger'
import PercyCommand from './percy-command'

export default class Snapshot extends PercyCommand {
static description = 'Snapshot a directory containing a pre-built static website'
static hidden = true

static args = [{
name: 'snapshotDirectory',
description: 'A path to the directory you would like to snapshot',
required: true,
}]

static examples = [
'$ percy snapshot _site/',
'$ percy snapshot _site/ --base-url "/blog"',
'$ percy snapshot _site/ --ignore-files "\.(blog|docs)$"',
]

static flags = {
'snapshot-files': flags.string({
char: 'c',
description: 'Regular expression for matching the files to snapshot.',
default: '\.(html|htm)$',
}),
'ignore-files': flags.string({
char: 'i',
description: 'Regular expression for matching the files to ignore.',
default: '',
}),
'base-url': flags.string({
char: 'b',
description: 'If your static files will be hosted in a subdirectory, instead \n' +
'of the webserver\'s root path, set that subdirectory with this flag.',
default: '/',
}),
// from exec command. needed to start the agent service.
'network-idle-timeout': flags.integer({
char: 't',
default: Constants.NETWORK_IDLE_TIMEOUT,
description: 'Asset discovery network idle timeout (in milliseconds)',
}),
'port': flags.integer({
char: 'p',
default: Constants.PORT,
description: 'Port',
}),
}

async run() {
await super.run()

const {args, flags} = this.parse(Snapshot)

const isWindows = process.platform === 'win32'

const snapshotDirectory = args.snapshotDirectory as string
const port = flags.port as number
const staticServerPort = port + 1
const networkIdleTimeout = flags['network-idle-timeout'] as number
const baseUrl = flags['base-url'] as string
const ignoreFilesRegex = flags['ignore-files'] as string
const snapshotFilesRegex = flags['snapshot-files'] as string

// exit gracefully if percy will not run
if (!this.percyWillRun()) { this.exit(0) }

// check that base url starts with a slash and exit if it is missing
if (baseUrl[0] !== '/') {
logger.warn('The base-url flag must begin with a slash.')
this.exit(1)
}

// start the agent service
await this.agentService.start({port, networkIdleTimeout})
this.logStart()

const options: StaticSnapshotOptions = {
port: staticServerPort,
snapshotDirectory,
baseUrl,
snapshotFilesRegex,
ignoreFilesRegex,
}

const staticSnapshotService = new StaticSnapshotService(options)

// start the snapshot service
await staticSnapshotService.start()

// take the snapshots
await staticSnapshotService.snapshotAll()

// stop the static snapshot and agent services
await staticSnapshotService.stop()
await this.agentService.stop()
}
}
2 changes: 1 addition & 1 deletion src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class Start extends PercyCommand {
}),
'network-idle-timeout': flags.integer({
char: 't',
default: 50,
default: Constants.NETWORK_IDLE_TIMEOUT,
description: 'asset discovery network idle timeout (in milliseconds)',
}),
'port': flags.integer({
Expand Down
1 change: 1 addition & 0 deletions src/services/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default class Constants {
static readonly PORT: number = 5338
static readonly NETWORK_IDLE_TIMEOUT: number = 50 // in milliseconds
Copy link
Contributor

Choose a reason for hiding this comment

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

Fab


// Agent Service paths
static readonly SNAPSHOT_PATH = '/percy/snapshot'
Expand Down
7 changes: 7 additions & 0 deletions src/services/static-snapshot-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface StaticSnapshotOptions {
snapshotDirectory: string,
port: number,
baseUrl: string,
snapshotFilesRegex: string,
ignoreFilesRegex?: string,
}
20 changes: 20 additions & 0 deletions src/services/static-snapshot-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logger from '../utils/logger'
import {StaticSnapshotOptions} from './static-snapshot-options'

export default class StaticSnapshotService {
Copy link
Contributor

Choose a reason for hiding this comment

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

As I read through this, I wonder if it would make sense to have the snapshot service extend the agent service? Or at least take care of the setup of the agent service in this constructor, so you don't have to do that in the run command method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I feel like this conversation/decision might be better had on #137 since that fleshes out (and changes) the static snapshot service @Robdel12

constructor(options: StaticSnapshotOptions) {
// logger.info('calling constructor...')
}

async start() {
// logger.info('starting static snapshot service...')
}

async snapshotAll() {
// logger.info('taking snapshots of the static site...')
}

async stop() {
// logger.info('stopping static snapshot service...')
}
}
73 changes: 73 additions & 0 deletions test/commands/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as chai from 'chai'
import {describe} from 'mocha'
import * as sinon from 'sinon'
import Snapshot from '../../src/commands/snapshot'
import AgentService from '../../src/services/agent-service'
import StaticSnapshotService from '../../src/services/static-snapshot-service'
import {captureStdOut} from '../helpers/stdout'

import {expect, test} from '@oclif/test'

describe('snapshot', () => {
describe('#run', () => {
const sandbox = sinon.createSandbox()

afterEach(() => {
sandbox.restore()
})

function AgentServiceStub(): AgentService {
const agentService = AgentService.prototype as AgentService
sandbox.stub(agentService, 'start')

const start = new Snapshot([], '') as Snapshot
sandbox.stub(start, 'agentService').returns(agentService)

return agentService
}

function StaticSnapshotServiceStub(): StaticSnapshotService {
const staticSnapshotService = StaticSnapshotService.prototype as StaticSnapshotService
sandbox.stub(staticSnapshotService, 'snapshotAll')
sandbox.stub(staticSnapshotService, 'start')

return staticSnapshotService
}

it('starts the static snapshot service', async () => {
const expectedAgentOptions = {networkIdleTimeout: 50, port: 5338}

const agentServiceStub = AgentServiceStub()
const staticSnapshotServiceStub = StaticSnapshotServiceStub()

const stdout = await captureStdOut(async () => {
await Snapshot.run(['./dummy-test-dir'])
})

chai.expect(agentServiceStub.start).to.be.calledWith(expectedAgentOptions)
chai.expect(staticSnapshotServiceStub.start).to.have.callCount(1)
chai.expect(staticSnapshotServiceStub.snapshotAll).to.have.callCount(1)
chai.expect(stdout).to.match(/\[percy\] percy has started./)
})

xit('starts the snapshot service on the correct port')
})

describe('snapshot command', () => {
test
.stub(process, 'env', {PERCY_TOKEN: ''})
.stderr()
.command(['snapshot', './test_dir'])
.exit(0)
.do((output) => expect(output.stderr).to.contain(
'Warning: Skipping visual tests. PERCY_TOKEN was not provided.',
))
.it('warns about PERCY_TOKEN not being set and exits gracefully')

test
.env({PERCY_TOKEN: 'abc'})
.command(['snapshot'])
.exit(2)
.it('exits when the asset directory arg is missing')
})
})