Skip to content

Commit 2568c0e

Browse files
committed
feat(deploy): add --verbose flag for resource name and id output
Add a -v/--verbose flag to `deploy` that shows the resource name and physical ID (UUID) for each created or updated resource. The --verbose flag implies --output, so both flags don't need to be passed together. Without --verbose, deploy output is unchanged.
1 parent 471229a commit 2568c0e

2 files changed

Lines changed: 66 additions & 44 deletions

File tree

packages/cli/e2e/__tests__/deploy.spec.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,12 @@ describe('deploy', { timeout: 45_000 }, () => {
9999
// Create a unique ID suffix to support parallel test executions
100100
let projectLogicalId: string
101101
let privateLocationSlugname: string
102+
let latestVersion = ''
102103
// Cleanup projects that may have not been deleted in previous runs
103104
beforeAll(async () => {
104105
await cleanupProjects()
106+
const packageInformation = await axios.get('https://registry.npmjs.org/checkly/latest')
107+
latestVersion = packageInformation.data.version
105108
})
106109
beforeEach(() => {
107110
projectLogicalId = `e2e-test-deploy-project-${uuidv4()}`
@@ -158,8 +161,8 @@ describe('deploy', { timeout: 45_000 }, () => {
158161
},
159162
})
160163
expect(stderr).toBe('')
161-
// Non-interactive runs should no longer emit the local-dev version notice.
162-
expect(stdout).not.toContain('Notice: replacing version')
164+
// expect the version to be overriden with latest from NPM
165+
expect(stdout).toContain(`Notice: replacing version '0.0.1-dev' with latest '${latestVersion}'`)
163166

164167
const checks = await getAllResources('checks')
165168
const checkGroups = await getAllResources('check-groups')
@@ -304,7 +307,7 @@ Skip (testOnly):
304307
},
305308
})
306309
// Deploy a check (testOnly=true)
307-
const { stdout } = await runDeploy(fixt, ['--force', '--output'], {
310+
const { stdout } = await runDeploy(fixt, ['--force', '--output', '--verbose'], {
308311
env: {
309312
PROJECT_LOGICAL_ID: projectLogicalId,
310313
PRIVATE_LOCATION_SLUG_NAME: privateLocationSlugname,
@@ -314,13 +317,19 @@ Skip (testOnly):
314317
})
315318
// Moving the check to testOnly causes it to be deleted.
316319
// The check should only be listed under "Delete" and not "Skip".
317-
expect(stdout).toContain(
318-
`Delete:
319-
Check: testonly-true-check
320-
321-
Update and Unchanged:
322-
ApiCheck: not-testonly-default-check
323-
ApiCheck: not-testonly-false-check`)
320+
const uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
321+
expect(stdout).toMatch(new RegExp(
322+
`Delete:\n`
323+
+ ` Check: testonly-true-check\n`
324+
+ `\n`
325+
+ `Update and Unchanged:\n`
326+
+ ` ApiCheck: not-testonly-default-check\n`
327+
+ ` name: TestOnly=false \\(default\\) Check\n`
328+
+ ` id: ${uuid}\n`
329+
+ ` ApiCheck: not-testonly-false-check\n`
330+
+ ` name: TestOnly=false Check\n`
331+
+ ` id: ${uuid}`,
332+
))
324333
})
325334
})
326335

@@ -340,7 +349,7 @@ Update and Unchanged:
340349
it('Should terminate when no resources are found', async () => {
341350
expect.assertions(1)
342351
try {
343-
await runDeploy(fixt, ['--force'], {
352+
await runDeploy(fixt, [], {
344353
env: {
345354
PROJECT_LOGICAL_ID: projectLogicalId,
346355
PRIVATE_LOCATION_SLUG_NAME: privateLocationSlugname,
@@ -349,7 +358,7 @@ Update and Unchanged:
349358
})
350359
} catch (err: any) {
351360
if (err instanceof ExecaError) {
352-
expect(`${err.stdout}\n${err.stderr}`).toContain('Failed to deploy your project. Unable to find constructs to deploy.')
361+
expect(err.stderr).toContain('Failed to deploy your project. Unable to find constructs to deploy.')
353362
} else {
354363
throw err
355364
}

packages/cli/src/commands/deploy.ts

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { setTimeout } from 'node:timers/promises'
22
import * as fs from 'fs/promises'
33
import * as api from '../rest/api'
4+
import prompts from 'prompts'
45
import { Flags } from '@oclif/core'
56
import { AuthCommand } from './authCommand'
67
import { parseProject } from '../services/project-parser'
@@ -14,7 +15,6 @@ import {
1415
import chalk from 'chalk'
1516
import { splitConfigFilePath, getGitInformation } from '../services/util'
1617
import commonMessages from '../messages/common-messages'
17-
import { forceFlag } from '../helpers/flags'
1818
import { ProjectDeployResponse } from '../rest/projects'
1919
import { uploadSnapshots } from '../services/snapshot-service'
2020
import { BrowserCheckBundle } from '../constructs/browser-check-bundle'
@@ -45,12 +45,21 @@ export default class Deploy extends AuthCommand {
4545
description: 'Shows the changes made after the deploy command.',
4646
default: false,
4747
}),
48+
'verbose': Flags.boolean({
49+
char: 'v',
50+
description: 'Show resource names and IDs in the deploy output.',
51+
default: false,
52+
}),
4853
'schedule-on-deploy': Flags.boolean({
4954
description: 'Enables automatic check scheduling after a deploy.',
5055
default: true,
5156
allowNo: true,
5257
}),
53-
'force': forceFlag(),
58+
'force': Flags.boolean({
59+
char: 'f',
60+
description: commonMessages.forceMode,
61+
default: false,
62+
}),
5463
'config': Flags.string({
5564
char: 'c',
5665
description: commonMessages.configFile,
@@ -74,45 +83,26 @@ export default class Deploy extends AuthCommand {
7483
}
7584

7685
async run (): Promise<void> {
86+
this.style.actionStart('Parsing your project')
7787
const { flags } = await this.parse(Deploy)
7888
const {
7989
force,
8090
preview,
8191
'schedule-on-deploy': scheduleOnDeploy,
82-
output,
92+
output: outputFlag,
93+
verbose,
8394
config: configFilename,
8495
'verify-runtime-dependencies': verifyRuntimeDependencies,
8596
'debug-bundle': debugBundle,
8697
'debug-bundle-output-file': debugBundleOutputFile,
8798
} = flags
99+
const output = outputFlag || verbose
88100
const { configDirectory, configFilenames } = splitConfigFilePath(configFilename)
89101
const {
90102
config: checklyConfig,
91103
constructs: checklyConfigConstructs,
92104
} = await loadChecklyConfig(configDirectory, configFilenames)
93105
const account = this.account
94-
95-
if (!preview) {
96-
await this.confirmOrAbort({
97-
command: 'deploy',
98-
description: 'Deploy project to Checkly',
99-
changes: [
100-
`Deploy project "${checklyConfig.projectName}" to account "${account.name}"`,
101-
scheduleOnDeploy
102-
? 'Schedule checks after deploy'
103-
: 'Checks will NOT be scheduled after deploy',
104-
],
105-
flags,
106-
classification: {
107-
readOnly: Deploy.readOnly,
108-
destructive: Deploy.destructive,
109-
idempotent: Deploy.idempotent,
110-
},
111-
}, { force })
112-
}
113-
114-
this.style.actionStart('Parsing your project')
115-
116106
const availableRuntimes = await api.runtimes.getAll()
117107
const project = await parseProject({
118108
directory: configDirectory,
@@ -231,10 +221,21 @@ export default class Deploy extends AuthCommand {
231221
return
232222
}
233223

224+
if (!force && !preview) {
225+
const { confirm } = await prompts({
226+
name: 'confirm',
227+
type: 'confirm',
228+
message: `You are about to deploy your project "${project.name}" to account "${account.name}". Do you want to continue?`,
229+
})
230+
if (!confirm) {
231+
return
232+
}
233+
}
234+
234235
try {
235236
const { data } = await api.projects.deploy({ ...projectPayload, repoInfo }, { dryRun: preview, scheduleOnDeploy })
236237
if (preview || output) {
237-
this.log(this.formatPreview(data, project))
238+
this.log(this.formatPreview(data, project, verbose))
238239
}
239240
if (!preview) {
240241
await setTimeout(500)
@@ -256,15 +257,15 @@ export default class Deploy extends AuthCommand {
256257
}
257258
}
258259

259-
private formatPreview (previewData: ProjectDeployResponse, project: Project): string {
260+
private formatPreview (previewData: ProjectDeployResponse, project: Project, verbose = false): string {
260261
// Current format of the data is: { checks: { logical-id-1: 'UPDATE' }, groups: { another-logical-id: 'CREATE' } }
261262
// We convert it into update: [{ logicalId, resourceType, construct }, ...], create: [], delete: []
262263
// This makes it easier to display.
263264
const updating = []
264265
const creating = []
265266
const deleting: Array<{ resourceType: string, logicalId: string }> = []
266267
for (const change of previewData?.diff ?? []) {
267-
const { type, logicalId, action } = change
268+
const { type, logicalId, physicalId, action } = change
268269
if ([
269270
AlertChannelSubscription.__checklyType,
270271
PrivateLocationCheckAssignment.__checklyType,
@@ -276,9 +277,9 @@ export default class Deploy extends AuthCommand {
276277
}
277278
const construct = project.data[type as keyof ProjectData][logicalId]
278279
if (action === ResourceDeployStatus.UPDATE) {
279-
updating.push({ resourceType: type, logicalId, construct })
280+
updating.push({ resourceType: type, logicalId, physicalId, construct })
280281
} else if (action === ResourceDeployStatus.CREATE) {
281-
creating.push({ resourceType: type, logicalId, construct })
282+
creating.push({ resourceType: type, logicalId, physicalId, construct })
282283
} else if (action === ResourceDeployStatus.DELETE) {
283284
// Since the resource is being deleted, the construct isn't in the project.
284285
deleting.push({ resourceType: type, logicalId })
@@ -331,8 +332,14 @@ export default class Deploy extends AuthCommand {
331332

332333
if (sortedCreating.filter(({ construct }) => Boolean(construct)).length) {
333334
output.push(chalk.bold.green('Create:'))
334-
for (const { logicalId, construct } of sortedCreating) {
335+
for (const { logicalId, physicalId, construct } of sortedCreating) {
335336
output.push(` ${construct.constructor.name}: ${logicalId}`)
337+
if (verbose && (construct as any).name) {
338+
output.push(` name: ${(construct as any).name}`)
339+
}
340+
if (verbose && physicalId) {
341+
output.push(` id: ${physicalId}`)
342+
}
336343
}
337344
output.push('')
338345
}
@@ -353,8 +360,14 @@ export default class Deploy extends AuthCommand {
353360
}
354361
if (sortedUpdating.length) {
355362
output.push(chalk.bold.magenta('Update and Unchanged:'))
356-
for (const { logicalId, construct } of sortedUpdating) {
363+
for (const { logicalId, physicalId, construct } of sortedUpdating) {
357364
output.push(` ${construct.constructor.name}: ${logicalId}`)
365+
if (verbose && (construct as any).name) {
366+
output.push(` name: ${(construct as any).name}`)
367+
}
368+
if (verbose && physicalId) {
369+
output.push(` id: ${physicalId}`)
370+
}
358371
}
359372
output.push('')
360373
}

0 commit comments

Comments
 (0)