1 | const cli = require('heroku-cli-util')
|
2 | const Dyno = require('heroku-run').Dyno
|
3 | const co = require('co')
|
4 | const api = require('../../lib/heroku-api')
|
5 | const git = require('../../lib/git')
|
6 | const source = require('../../lib/source')
|
7 | const TestRun = require('../../lib/test-run')
|
8 | const Utils = require('../../lib/utils')
|
9 |
|
10 |
|
11 | const SETUP_COMMAND = 'ci setup && eval $(ci env)'
|
12 |
|
13 | function* run (context, heroku) {
|
14 | const pipeline = yield Utils.getPipeline(context, heroku)
|
15 |
|
16 | const pipelineRepository = yield api.pipelineRepository(heroku, pipeline.id)
|
17 | const organization = pipelineRepository.organization &&
|
18 | pipelineRepository.organization.name
|
19 |
|
20 | const commit = yield git.readCommit('HEAD')
|
21 | const sourceBlobUrl = yield cli.action('Preparing source', co(function* () {
|
22 | return yield source.createSourceBlob(commit.ref, context, heroku)
|
23 | }))
|
24 |
|
25 |
|
26 | const testRun = yield cli.action('Creating test run', co(function* () {
|
27 | const run = yield api.createTestRun(heroku, {
|
28 | commit_branch: commit.branch,
|
29 | commit_message: commit.message,
|
30 | commit_sha: commit.ref,
|
31 | debug: true,
|
32 | clear_cache: !!context.flags['no-cache'],
|
33 | organization,
|
34 | pipeline: pipeline.id,
|
35 | source_blob_url: sourceBlobUrl
|
36 | })
|
37 |
|
38 | return yield TestRun.waitForStates(['debugging', 'errored'], run, { heroku })
|
39 | }))
|
40 |
|
41 | if (testRun.status === 'errored') {
|
42 | cli.exit(1, `Test run creation failed while ${testRun.error_state} with message "${testRun.message}"`)
|
43 | }
|
44 |
|
45 | const appSetup = yield api.appSetup(heroku, testRun.app_setup.id)
|
46 | const noSetup = context.flags['no-setup']
|
47 |
|
48 | cli.log(`${noSetup ? 'Attaching' : 'Running setup and attaching'} to test dyno...`)
|
49 |
|
50 | if (noSetup) {
|
51 | cli.warn('Skipping test setup phase.')
|
52 | cli.warn(`Run \`${SETUP_COMMAND}\``)
|
53 | cli.warn('to execute a build and configure the environment')
|
54 | }
|
55 |
|
56 | const testNodes = yield api.testNodes(heroku, testRun.id)
|
57 |
|
58 | const dyno = new Dyno({
|
59 | heroku,
|
60 | app: appSetup.app.id,
|
61 | showStatus: false
|
62 | })
|
63 |
|
64 | dyno.dyno = { attach_url: Utils.dig(testNodes, 0, 'dyno', 'attach_url') }
|
65 |
|
66 | function sendSetup (data, connection) {
|
67 | if (data.toString().includes('$')) {
|
68 | dyno.write(SETUP_COMMAND + '\n')
|
69 | dyno.removeListener('data', sendSetup)
|
70 | }
|
71 | }
|
72 |
|
73 | if (!noSetup) {
|
74 | dyno.on('data', sendSetup)
|
75 | }
|
76 |
|
77 | try {
|
78 | yield dyno.attach()
|
79 | } catch (err) {
|
80 | if (err.exitCode) cli.exit(err.exitCode, err)
|
81 | else throw err
|
82 | }
|
83 |
|
84 | yield cli.action(
|
85 | 'Cleaning up',
|
86 | api.updateTestRun(heroku, testRun.id, {
|
87 | status: 'cancelled',
|
88 | message: 'debug run cancelled by Heroku CLI'
|
89 | })
|
90 | )
|
91 | }
|
92 |
|
93 | module.exports = {
|
94 | topic: 'ci',
|
95 | command: 'debug',
|
96 | wantsApp: true,
|
97 | needsAuth: true,
|
98 | description: 'opens an interactive test debugging session with the contents of the current directory',
|
99 | help: `Example:
|
100 |
|
101 | $ heroku ci:debug
|
102 | Preparing source... done
|
103 | Creating test run... done
|
104 | Running setup and attaching to test dyno...
|
105 |
|
106 | ~ $
|
107 | `,
|
108 | flags: [
|
109 | {
|
110 | name: 'no-setup',
|
111 | hasValue: false,
|
112 | description: 'start test dyno without running test-setup'
|
113 | },
|
114 | {
|
115 | name: 'pipeline',
|
116 | char: 'p',
|
117 | hasValue: true,
|
118 | description: 'pipeline'
|
119 | },
|
120 | {
|
121 | name: 'no-cache',
|
122 | hasValue: false,
|
123 | description: 'start test run with an empty cache'
|
124 | }
|
125 | ],
|
126 | run: cli.command(co.wrap(run))
|
127 | }
|