@wyxos/zephyr 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,9 +40,12 @@ Common workflows:
40
40
  # Deploy an app using the saved preset or the interactive prompts
41
41
  zephyr
42
42
 
43
- # Deploy an app and bump the local npm package version first
43
+ # Deploy an app with a local npm package version bump
44
44
  zephyr minor
45
45
 
46
+ # Deploy an app while bypassing Zephyr's built-in local checks
47
+ zephyr minor --skip-checks
48
+
46
49
  # Deploy a configured app non-interactively
47
50
  zephyr --non-interactive --preset wyxos-release --maintenance off
48
51
 
@@ -155,6 +158,8 @@ npm run release
155
158
  - `npm run release` is the recommended app/package entrypoint once the release script has been installed.
156
159
  - For `--type node` workflows, Zephyr runs your project's `lint` script when present.
157
160
  - For `--type node` workflows, Zephyr runs `test:run` or `test` when present.
161
+ - For fresh Laravel app deploys, Zephyr runs local lint/test checks before creating the version-bump commit so failed checks do not force repeated bump retries.
162
+ - For critical app deploys, `--skip-checks` is shorthand for `--skip-lint --skip-tests`. It skips Zephyr's built-in local checks, but any local `pre-push` hook can still run its own checks during git push unless you also opt into `--skip-git-hooks`.
158
163
  - For non-interactive app deploys, use a saved preset name instead of relying on prompt fallback.
159
164
 
160
165
  ## Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/zephyr",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A streamlined deployment tool for web applications with intelligent Laravel project detection",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -10,6 +10,8 @@ export async function prepareLocalDeployment(config, {
10
10
  rootDir = process.cwd(),
11
11
  versionArg = null,
12
12
  skipGitHooks = false,
13
+ skipTests = false,
14
+ skipLint = false,
13
15
  runPrompt,
14
16
  runCommand,
15
17
  runCommandCapture,
@@ -31,10 +33,29 @@ export async function prepareLocalDeployment(config, {
31
33
  const checkSupport = await resolveLocalDeploymentCheckSupport({
32
34
  rootDir,
33
35
  isLaravel: context.isLaravel,
36
+ skipTests,
37
+ skipLint,
34
38
  runCommandCapture
35
39
  })
36
40
 
37
41
  if (!snapshot && context.isLaravel) {
42
+ await runLocalDeploymentChecks({
43
+ rootDir,
44
+ isLaravel: context.isLaravel,
45
+ hasHook: context.hasHook,
46
+ skipGitHooks,
47
+ skipTests,
48
+ skipLint,
49
+ forceRunWhenHookPresent: true,
50
+ runCommand,
51
+ runCommandCapture,
52
+ logProcessing,
53
+ logSuccess,
54
+ logWarning,
55
+ lintCommand: checkSupport.lintCommand,
56
+ testCommand: checkSupport.testCommand
57
+ })
58
+
38
59
  await bumpLocalPackageVersion(rootDir, {
39
60
  versionArg,
40
61
  skipGitHooks,
@@ -52,6 +73,8 @@ export async function prepareLocalDeployment(config, {
52
73
  logWarning,
53
74
  skipGitHooks
54
75
  })
76
+
77
+ return context
55
78
  }
56
79
 
57
80
  await runLocalDeploymentChecks({
@@ -59,6 +82,8 @@ export async function prepareLocalDeployment(config, {
59
82
  isLaravel: context.isLaravel,
60
83
  hasHook: context.hasHook,
61
84
  skipGitHooks,
85
+ skipTests,
86
+ skipLint,
62
87
  runCommand,
63
88
  runCommandCapture,
64
89
  logProcessing,
@@ -366,6 +366,8 @@ export async function runDeployment(config, options = {}) {
366
366
  rootDir,
367
367
  versionArg,
368
368
  skipGitHooks: executionMode?.skipGitHooks === true,
369
+ skipTests: executionMode?.skipTests === true,
370
+ skipLint: executionMode?.skipLint === true,
369
371
  runPrompt,
370
372
  runCommand,
371
373
  runCommandCapture: context.runCommandCapture,
@@ -50,19 +50,23 @@ async function resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture} =
50
50
  export async function resolveLocalDeploymentCheckSupport({
51
51
  rootDir,
52
52
  isLaravel,
53
+ skipTests = false,
54
+ skipLint = false,
53
55
  runCommandCapture
54
56
  } = {}) {
55
57
  let lintCommand = null
56
58
 
57
- try {
58
- lintCommand = await preflight.resolveSupportedLintCommand(rootDir, {commandExists})
59
- } catch (error) {
60
- if (error?.code !== 'ZEPHYR_LINT_COMMAND_NOT_FOUND') {
61
- throw error
59
+ if (!skipLint) {
60
+ try {
61
+ lintCommand = await preflight.resolveSupportedLintCommand(rootDir, {commandExists})
62
+ } catch (error) {
63
+ if (error?.code !== 'ZEPHYR_LINT_COMMAND_NOT_FOUND') {
64
+ throw error
65
+ }
62
66
  }
63
67
  }
64
68
 
65
- const testCommand = isLaravel
69
+ const testCommand = isLaravel && !skipTests
66
70
  ? await resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture})
67
71
  : null
68
72
 
@@ -95,6 +99,9 @@ export async function runLocalDeploymentChecks({
95
99
  isLaravel,
96
100
  hasHook,
97
101
  skipGitHooks = false,
102
+ skipTests = false,
103
+ skipLint = false,
104
+ forceRunWhenHookPresent = false,
98
105
  runCommand,
99
106
  runCommandCapture,
100
107
  logProcessing,
@@ -108,6 +115,8 @@ export async function runLocalDeploymentChecks({
108
115
  : await resolveLocalDeploymentCheckSupport({
109
116
  rootDir,
110
117
  isLaravel,
118
+ skipTests,
119
+ skipLint,
111
120
  runCommandCapture
112
121
  })
113
122
 
@@ -116,6 +125,10 @@ export async function runLocalDeploymentChecks({
116
125
  logWarning?.(
117
126
  'Pre-push git hook detected. Zephyr will run its built-in release checks manually because --skip-git-hooks is enabled, and the hook will be bypassed during git push.'
118
127
  )
128
+ } else if (forceRunWhenHookPresent) {
129
+ logProcessing?.(
130
+ 'Pre-push git hook detected. Zephyr will run its built-in release checks now before bumping the deployment version. The hook will still run again during git push.'
131
+ )
119
132
  } else {
120
133
  logProcessing?.(
121
134
  'Pre-push git hook detected. Built-in release checks are supported, but Zephyr will skip executing them here. If Zephyr pushes local commits during this release, the hook will run during git push.'
@@ -124,11 +137,19 @@ export async function runLocalDeploymentChecks({
124
137
  }
125
138
  }
126
139
 
127
- if (support.lintCommand === null) {
140
+ if (hasHook && !skipGitHooks && (skipLint || skipTests)) {
141
+ logWarning?.(
142
+ 'Pre-push git hook detected. --skip-lint/--skip-tests only skip Zephyr\'s built-in checks; your hook may still run its own checks during git push.'
143
+ )
144
+ }
145
+
146
+ if (skipLint) {
147
+ logWarning?.('Skipping lint because --skip-lint flag was provided.')
148
+ } else if (support.lintCommand === null) {
128
149
  logWarning?.('No supported lint command was found. Skipping linting checks.')
129
150
  }
130
151
 
131
- const lintRan = support.lintCommand === null
152
+ const lintRan = skipLint || support.lintCommand === null
132
153
  ? false
133
154
  : await preflight.runLinting(rootDir, {
134
155
  runCommand,
@@ -152,7 +173,9 @@ export async function runLocalDeploymentChecks({
152
173
  }
153
174
  }
154
175
 
155
- if (isLaravel) {
176
+ if (isLaravel && skipTests) {
177
+ logWarning?.('Skipping tests because --skip-tests flag was provided.')
178
+ } else if (isLaravel) {
156
179
  await runLocalLaravelTests(rootDir, {
157
180
  runCommand,
158
181
  logProcessing,
@@ -36,8 +36,9 @@ export function parseCliOptions(args = process.argv.slice(2)) {
36
36
  .option('--discard-pending', 'Discard a saved pending deployment snapshot without prompting.')
37
37
  .option('--maintenance <mode>', 'Laravel maintenance mode policy for app deployments (on|off).')
38
38
  .option('--skip-git-hooks', 'Bypass local git hooks for any commits and pushes Zephyr performs.')
39
- .option('--skip-tests', 'Skip test execution in package release workflows.')
40
- .option('--skip-lint', 'Skip lint execution in package release workflows.')
39
+ .option('--skip-checks', 'Skip Zephyr local lint and test execution.')
40
+ .option('--skip-tests', 'Skip Zephyr local test execution in package release and app deployment workflows.')
41
+ .option('--skip-lint', 'Skip Zephyr local lint execution in package release and app deployment workflows.')
41
42
  .option('--skip-build', 'Skip build execution in node/vue release workflows.')
42
43
  .option('--skip-deploy', 'Skip GitHub Pages deployment in node/vue release workflows.')
43
44
  .argument(
@@ -68,8 +69,9 @@ export function parseCliOptions(args = process.argv.slice(2)) {
68
69
  discardPending: Boolean(options.discardPending),
69
70
  maintenanceMode: normalizeMaintenanceMode(options.maintenance),
70
71
  skipGitHooks: Boolean(options.skipGitHooks),
71
- skipTests: Boolean(options.skipTests),
72
- skipLint: Boolean(options.skipLint),
72
+ skipChecks: Boolean(options.skipChecks),
73
+ skipTests: Boolean(options.skipTests || options.skipChecks),
74
+ skipLint: Boolean(options.skipLint || options.skipChecks),
73
75
  skipBuild: Boolean(options.skipBuild),
74
76
  skipDeploy: Boolean(options.skipDeploy)
75
77
  }
@@ -84,8 +86,6 @@ export function validateCliOptions(options = {}) {
84
86
  resumePending = false,
85
87
  discardPending = false,
86
88
  maintenanceMode = null,
87
- skipTests = false,
88
- skipLint = false,
89
89
  skipBuild = false,
90
90
  skipDeploy = false
91
91
  } = options
@@ -113,8 +113,8 @@ export function validateCliOptions(options = {}) {
113
113
  throw new InvalidCliOptionsError('--maintenance is only valid for app deployments.')
114
114
  }
115
115
  } else {
116
- if (skipTests || skipLint || skipBuild || skipDeploy) {
117
- throw new InvalidCliOptionsError('Release-only skip flags are not valid for app deployments.')
116
+ if (skipBuild || skipDeploy) {
117
+ throw new InvalidCliOptionsError('--skip-build and --skip-deploy are only valid for node/vue release workflows.')
118
118
  }
119
119
 
120
120
  if (nonInteractive && !presetName) {
@@ -2,7 +2,6 @@ import { getCurrentBranch as getCurrentBranchImpl, getUpstreamRef as getUpstream
2
2
  import {hasPrePushHook} from './preflight.mjs'
3
3
  import {gitCommitArgs, gitPushArgs} from '../utils/git-hooks.mjs'
4
4
  import {
5
- buildFallbackCommitMessage,
6
5
  formatWorkingTreePreview,
7
6
  parseWorkingTreeEntries,
8
7
  suggestCommitMessage as suggestCommitMessageImpl
@@ -213,35 +212,24 @@ async function commitAndPushPendingChanges(targetBranch, rootDir, {
213
212
  logStep: logProcessing,
214
213
  logWarning,
215
214
  statusEntries
216
- }) ?? buildFallbackCommitMessage(statusEntries)
217
-
218
- const changeLabel = statusEntries.length === 1 ? 'change' : 'changes'
219
- const {shouldCommitPendingChanges} = await runPrompt([
215
+ })
216
+ const { commitMessage } = await runPrompt([
220
217
  {
221
- type: 'confirm',
222
- name: 'shouldCommitPendingChanges',
218
+ type: 'input',
219
+ name: 'commitMessage',
223
220
  message:
224
- `Pending ${changeLabel} detected before deployment:\n\n` +
221
+ 'Pending changes detected before deployment:\n\n' +
225
222
  `${formatWorkingTreePreview(statusEntries)}\n\n` +
226
- 'Stage and commit all current changes before continuing?',
227
- default: true
223
+ 'Enter a commit message to stage and commit all current changes before continuing.\n' +
224
+ 'Leave blank to cancel.',
225
+ default: suggestedCommitMessage ?? ''
228
226
  }
229
227
  ])
230
228
 
231
- if (!shouldCommitPendingChanges) {
229
+ if (!commitMessage || commitMessage.trim().length === 0) {
232
230
  throw new Error(DIRTY_DEPLOYMENT_CANCELLED_MESSAGE)
233
231
  }
234
232
 
235
- const { commitMessage } = await runPrompt([
236
- {
237
- type: 'input',
238
- name: 'commitMessage',
239
- message: 'Commit message for pending deployment changes',
240
- default: suggestedCommitMessage,
241
- validate: (value) => (value && value.trim().length > 0 ? true : 'Commit message cannot be empty.')
242
- }
243
- ])
244
-
245
233
  const message = commitMessage.trim()
246
234
 
247
235
  logProcessing?.('Staging all pending changes before deployment...')
package/src/main.mjs CHANGED
@@ -36,8 +36,9 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
36
36
  discardPending: firstArg.discardPending === true,
37
37
  maintenanceMode: firstArg.maintenanceMode ?? null,
38
38
  skipGitHooks: firstArg.skipGitHooks === true,
39
- skipTests: firstArg.skipTests === true,
40
- skipLint: firstArg.skipLint === true,
39
+ skipChecks: firstArg.skipChecks === true,
40
+ skipTests: firstArg.skipTests === true || firstArg.skipChecks === true,
41
+ skipLint: firstArg.skipLint === true || firstArg.skipChecks === true,
41
42
  skipBuild: firstArg.skipBuild === true,
42
43
  skipDeploy: firstArg.skipDeploy === true,
43
44
  context: firstArg.context ?? null
@@ -54,6 +55,7 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
54
55
  discardPending: false,
55
56
  maintenanceMode: null,
56
57
  skipGitHooks: false,
58
+ skipChecks: false,
57
59
  skipTests: false,
58
60
  skipLint: false,
59
61
  skipBuild: false,
@@ -109,6 +111,9 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
109
111
  presetName: options.presetName,
110
112
  maintenanceMode: options.maintenanceMode,
111
113
  skipGitHooks: options.skipGitHooks === true,
114
+ skipChecks: options.skipChecks === true,
115
+ skipTests: options.skipTests === true,
116
+ skipLint: options.skipLint === true,
112
117
  resumePending: options.resumePending,
113
118
  discardPending: options.discardPending
114
119
  }
@@ -138,6 +143,9 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
138
143
  presetName: currentExecutionMode.presetName,
139
144
  maintenanceMode: currentExecutionMode.maintenanceMode,
140
145
  skipGitHooks: currentExecutionMode.skipGitHooks === true,
146
+ skipChecks: currentExecutionMode.skipChecks === true,
147
+ skipTests: currentExecutionMode.skipTests === true,
148
+ skipLint: currentExecutionMode.skipLint === true,
141
149
  resumePending: currentExecutionMode.resumePending,
142
150
  discardPending: currentExecutionMode.discardPending
143
151
  }
@@ -20,9 +20,12 @@ const GENERIC_SUBJECT_PATTERNS = [
20
20
  /^update work$/i,
21
21
  /^misc(ellaneous)?( updates?)?$/i,
22
22
  /^changes$/i,
23
- /^updates?$/i
23
+ /^updates?$/i,
24
+ /^(improve|update|adjust|refine|align|support|enable)\s+.+\s+(workflow|process|flow)$/i
24
25
  ]
25
26
  const MAX_WORKING_TREE_PREVIEW = 20
27
+ const MAX_DIFF_EXCERPT_LINES = 60
28
+ const MAX_DIFF_EXCERPT_CHARS = 4000
26
29
  const STATUS_LABELS = {
27
30
  A: 'added',
28
31
  C: 'copied',
@@ -261,10 +264,10 @@ export function buildFallbackCommitMessage(statusEntries = []) {
261
264
  }
262
265
 
263
266
  if (commitType === 'ci') {
264
- return `ci: update ${primaryTopic} workflow`
267
+ return `ci: update ${primaryTopic} pipeline`
265
268
  }
266
269
 
267
- return `chore: improve ${primaryTopic} workflow`
270
+ return `chore: update ${primaryTopic} handling`
268
271
  }
269
272
 
270
273
  async function collectDiffNumstat(rootDir, {runCommand} = {}) {
@@ -297,12 +300,68 @@ async function collectDiffNumstat(rootDir, {runCommand} = {}) {
297
300
  }
298
301
  }
299
302
 
303
+ function shouldIncludeDiffExcerptLine(line = '') {
304
+ if (line.startsWith('diff --git ')) {
305
+ return true
306
+ }
307
+
308
+ if (line.startsWith('@@')) {
309
+ return true
310
+ }
311
+
312
+ return (line.startsWith('+') || line.startsWith('-')) && !line.startsWith('+++') && !line.startsWith('---')
313
+ }
314
+
315
+ function trimDiffExcerpt(diffText = '') {
316
+ const selectedLines = []
317
+ let totalChars = 0
318
+
319
+ for (const rawLine of diffText.split(/\r?\n/)) {
320
+ const line = rawLine.trimEnd()
321
+
322
+ if (!shouldIncludeDiffExcerptLine(line)) {
323
+ continue
324
+ }
325
+
326
+ const truncatedLine = line.length > 180 ? `${line.slice(0, 177)}...` : line
327
+ const nextTotal = totalChars + truncatedLine.length + 1
328
+
329
+ if (selectedLines.length >= MAX_DIFF_EXCERPT_LINES || nextTotal > MAX_DIFF_EXCERPT_CHARS) {
330
+ break
331
+ }
332
+
333
+ selectedLines.push(truncatedLine)
334
+ totalChars = nextTotal
335
+ }
336
+
337
+ return selectedLines.join('\n')
338
+ }
339
+
340
+ async function collectDiffExcerpt(rootDir, {runCommand} = {}) {
341
+ try {
342
+ const {stdout} = await runCommand('git', ['diff', '--unified=0', '--no-color', 'HEAD', '--'], {
343
+ capture: true,
344
+ cwd: rootDir
345
+ })
346
+
347
+ return trimDiffExcerpt(stdout)
348
+ } catch {
349
+ return ''
350
+ }
351
+ }
352
+
300
353
  async function buildCommitMessageContext(rootDir, {
301
354
  runCommand,
302
355
  statusEntries = []
303
356
  } = {}) {
304
357
  const changeCountsByPath = await collectDiffNumstat(rootDir, {runCommand})
305
- return statusEntries.map((entry) => `- ${summarizeWorkingTreeEntry(entry, {changeCountsByPath})}`).join('\n')
358
+ const summary = statusEntries.map((entry) => `- ${summarizeWorkingTreeEntry(entry, {changeCountsByPath})}`).join('\n')
359
+ const diffExcerpt = await collectDiffExcerpt(rootDir, {runCommand})
360
+
361
+ return {
362
+ summary,
363
+ diffExcerpt
364
+ }
306
365
  }
307
366
 
308
367
  export async function suggestCommitMessage(rootDir = process.cwd(), {
@@ -328,6 +387,21 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
328
387
 
329
388
  logStep?.('Generating a suggested commit message with Codex...')
330
389
 
390
+ const promptSections = [
391
+ 'Write exactly one short conventional commit message for these pending changes.',
392
+ 'Use the exact format "<type>: <subject>" with no scope, no exclamation mark, and no extra text.',
393
+ 'Choose the most appropriate type from: fix, feat, chore, docs, refactor, test, style, perf, build, ci, revert.',
394
+ 'Base the subject on the actual behavior, safeguard, bug fix, feature, or API change shown below.',
395
+ 'Avoid generic nouns like "workflow", "process", "flow", "changes", or "updates" unless the diff truly changes CI or docs.',
396
+ 'Do not describe the commit itself, staging, or "pending changes"; describe the underlying code or product change.',
397
+ 'Pending change summary:',
398
+ commitContext.summary || '- changed files present'
399
+ ]
400
+
401
+ if (commitContext.diffExcerpt) {
402
+ promptSections.push('Diff excerpt:', commitContext.diffExcerpt)
403
+ }
404
+
331
405
  await runCommand('codex', [
332
406
  'exec',
333
407
  '--ephemeral',
@@ -338,15 +412,7 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
338
412
  '--skip-git-repo-check',
339
413
  '--output-last-message',
340
414
  outputPath,
341
- [
342
- 'Write exactly one short conventional commit message for these pending changes.',
343
- 'Use the exact format "<type>: <subject>" with no scope, no exclamation mark, and no extra text.',
344
- 'Choose the most appropriate type from: fix, feat, chore, docs, refactor, test, style, perf, build, ci, revert.',
345
- 'Make the subject specific enough to describe the actual behavior or workflow change, not just that files changed.',
346
- 'Do not describe the commit itself, staging, or "pending changes"; describe the underlying behavior or workflow fix.',
347
- 'Pending change summary:',
348
- commitContext || '- changed files present'
349
- ].join('\n\n')
415
+ promptSections.join('\n\n')
350
416
  ], {
351
417
  capture: true,
352
418
  cwd: rootDir
@@ -10,7 +10,6 @@ import {
10
10
  getUpstreamRef
11
11
  } from '../utils/git.mjs'
12
12
  import {
13
- buildFallbackCommitMessage,
14
13
  formatWorkingTreePreview,
15
14
  parseWorkingTreeEntries,
16
15
  parseWorkingTreeStatus,
@@ -117,35 +116,24 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
117
116
  logStep,
118
117
  logWarning,
119
118
  statusEntries
120
- }) ?? buildFallbackCommitMessage(statusEntries)
121
-
122
- const changeLabel = statusEntries.length === 1 ? 'change' : 'changes'
123
- const {shouldCommitPendingChanges} = await runPrompt([
119
+ })
120
+ const {commitMessage} = await runPrompt([
124
121
  {
125
- type: 'confirm',
126
- name: 'shouldCommitPendingChanges',
122
+ type: 'input',
123
+ name: 'commitMessage',
127
124
  message:
128
- `Pending ${changeLabel} detected before release:\n\n` +
125
+ 'Pending changes detected before release:\n\n' +
129
126
  `${formatWorkingTreePreview(statusEntries)}\n\n` +
130
- 'Stage and commit all current changes before continuing?',
131
- default: true
127
+ 'Enter a commit message to stage and commit all current changes before continuing.\n' +
128
+ 'Leave blank to cancel.',
129
+ default: suggestedCommitMessage ?? ''
132
130
  }
133
131
  ])
134
132
 
135
- if (!shouldCommitPendingChanges) {
133
+ if (!commitMessage || commitMessage.trim().length === 0) {
136
134
  throw new Error(DIRTY_WORKING_TREE_CANCELLED_MESSAGE)
137
135
  }
138
136
 
139
- const {commitMessage} = await runPrompt([
140
- {
141
- type: 'input',
142
- name: 'commitMessage',
143
- message: 'Commit message for pending release changes',
144
- default: suggestedCommitMessage,
145
- validate: (value) => (value && value.trim().length > 0 ? true : 'Commit message cannot be empty.')
146
- }
147
- ])
148
-
149
137
  const message = commitMessage.trim()
150
138
 
151
139
  logStep?.('Staging all pending changes before release...')
@@ -25,6 +25,9 @@ export function createAppContext({
25
25
  presetName: executionMode.presetName ?? null,
26
26
  maintenanceMode: executionMode.maintenanceMode ?? null,
27
27
  skipGitHooks: executionMode.skipGitHooks === true,
28
+ skipChecks: executionMode.skipChecks === true,
29
+ skipTests: executionMode.skipTests === true || executionMode.skipChecks === true,
30
+ skipLint: executionMode.skipLint === true || executionMode.skipChecks === true,
28
31
  resumePending: executionMode.resumePending === true,
29
32
  discardPending: executionMode.discardPending === true
30
33
  }