@wyxos/zephyr 0.2.31 → 0.3.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 +55 -2
- package/bin/zephyr.mjs +3 -1
- package/package.json +7 -2
- package/src/application/configuration/app-details.mjs +89 -0
- package/src/application/configuration/app-selection.mjs +87 -0
- package/src/application/configuration/preset-selection.mjs +59 -0
- package/src/application/configuration/select-deployment-target.mjs +165 -0
- package/src/application/configuration/server-selection.mjs +87 -0
- package/src/application/configuration/service.mjs +109 -0
- package/src/application/deploy/build-remote-deployment-plan.mjs +174 -0
- package/src/application/deploy/bump-local-package-version.mjs +81 -0
- package/src/application/deploy/execute-remote-deployment-plan.mjs +61 -0
- package/src/{utils/task-planner.mjs → application/deploy/plan-laravel-deployment-tasks.mjs} +5 -4
- package/src/application/deploy/prepare-local-deployment.mjs +52 -0
- package/src/application/deploy/resolve-local-deployment-context.mjs +17 -0
- package/src/application/deploy/resolve-pending-snapshot.mjs +45 -0
- package/src/application/deploy/run-deployment.mjs +147 -0
- package/src/application/deploy/run-local-deployment-checks.mjs +80 -0
- package/src/application/release/release-node-package.mjs +340 -0
- package/src/application/release/release-packagist-package.mjs +223 -0
- package/src/config/project.mjs +13 -0
- package/src/deploy/local-repo.mjs +187 -67
- package/src/deploy/remote-exec.mjs +2 -3
- package/src/index.mjs +27 -85
- package/src/main.mjs +78 -641
- package/src/release/shared.mjs +104 -0
- package/src/release-node.mjs +20 -424
- package/src/release-packagist.mjs +20 -291
- package/src/runtime/app-context.mjs +36 -0
- package/src/targets/index.mjs +24 -0
- package/src/utils/output.mjs +41 -16
- package/src/utils/config-flow.mjs +0 -284
- /package/src/{utils/php-version.mjs → infrastructure/php/version.mjs} +0 -0
|
@@ -1,296 +1,25 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
|
-
import fs from 'node:fs'
|
|
4
1
|
import process from 'node:process'
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import {createChalkLogger} from './utils/output.mjs'
|
|
4
|
+
import {
|
|
5
|
+
parseReleaseArgs,
|
|
6
|
+
} from './release/shared.mjs'
|
|
7
|
+
import {releasePackagistPackage} from './application/release/release-packagist-package.mjs'
|
|
11
8
|
|
|
12
|
-
const
|
|
13
|
-
const OK_PREFIX = '✔'
|
|
14
|
-
const WARN_PREFIX = '⚠'
|
|
15
|
-
|
|
16
|
-
const IS_WINDOWS = process.platform === 'win32'
|
|
17
|
-
|
|
18
|
-
function logStep(message) {
|
|
19
|
-
writeStdoutLine(`${STEP_PREFIX} ${message}`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function logSuccess(message) {
|
|
23
|
-
writeStdoutLine(`${OK_PREFIX} ${message}`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function logWarning(message) {
|
|
27
|
-
writeStderrLine(`${WARN_PREFIX} ${message}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function runCommand(command, args, { cwd = process.cwd(), capture = false } = {}) {
|
|
31
|
-
if (capture) {
|
|
32
|
-
const { stdout, stderr } = await runCommandCaptureBase(command, args, { cwd })
|
|
33
|
-
return { stdout: stdout.trim(), stderr: stderr.trim() }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
await runCommandBase(command, args, { cwd })
|
|
37
|
-
return undefined
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function readComposer(rootDir = process.cwd()) {
|
|
41
|
-
const composerPath = join(rootDir, 'composer.json')
|
|
42
|
-
const raw = await readFile(composerPath, 'utf8')
|
|
43
|
-
return JSON.parse(raw)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function writeComposer(rootDir, composer, composerPath = null) {
|
|
47
|
-
const pathToUse = composerPath || join(rootDir, 'composer.json')
|
|
48
|
-
const content = JSON.stringify(composer, null, 2) + '\n'
|
|
49
|
-
await writeFile(pathToUse, content, 'utf8')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function hasComposerScript(composer, scriptName) {
|
|
53
|
-
return composer?.scripts?.[scriptName] !== undefined
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function hasLaravelPint(rootDir = process.cwd()) {
|
|
57
|
-
const pintPath = join(rootDir, 'vendor', 'bin', 'pint')
|
|
58
|
-
try {
|
|
59
|
-
await fs.promises.access(pintPath)
|
|
60
|
-
const stats = await fs.promises.stat(pintPath)
|
|
61
|
-
return stats.isFile()
|
|
62
|
-
} catch {
|
|
63
|
-
return false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function hasArtisan(rootDir = process.cwd()) {
|
|
68
|
-
const artisanPath = join(rootDir, 'artisan')
|
|
69
|
-
try {
|
|
70
|
-
await fs.promises.access(artisanPath)
|
|
71
|
-
const stats = await fs.promises.stat(artisanPath)
|
|
72
|
-
return stats.isFile()
|
|
73
|
-
} catch {
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function ensureCleanWorkingTree(rootDir = process.cwd()) {
|
|
79
|
-
const { stdout } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
80
|
-
|
|
81
|
-
if (stdout.length > 0) {
|
|
82
|
-
throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Git helpers imported from src/utils/git.mjs
|
|
87
|
-
|
|
88
|
-
function parseArgs() {
|
|
89
|
-
const args = process.argv.slice(2)
|
|
90
|
-
// Filter out --type flag as it's handled by zephyr CLI
|
|
91
|
-
const filteredArgs = args.filter((arg) => !arg.startsWith('--type='))
|
|
92
|
-
const positionals = filteredArgs.filter((arg) => !arg.startsWith('--'))
|
|
93
|
-
const flags = new Set(filteredArgs.filter((arg) => arg.startsWith('--')))
|
|
94
|
-
|
|
95
|
-
const releaseType = positionals[0] ?? 'patch'
|
|
96
|
-
const skipTests = flags.has('--skip-tests')
|
|
97
|
-
const skipLint = flags.has('--skip-lint')
|
|
98
|
-
|
|
99
|
-
const allowedTypes = new Set([
|
|
100
|
-
'major',
|
|
101
|
-
'minor',
|
|
102
|
-
'patch',
|
|
103
|
-
'premajor',
|
|
104
|
-
'preminor',
|
|
105
|
-
'prepatch',
|
|
106
|
-
'prerelease'
|
|
107
|
-
])
|
|
108
|
-
|
|
109
|
-
if (!allowedTypes.has(releaseType)) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`Invalid release type "${releaseType}". Use one of: ${Array.from(allowedTypes).join(', ')}.`
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return { releaseType, skipTests, skipLint }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function runLint(skipLint, rootDir = process.cwd()) {
|
|
119
|
-
if (skipLint) {
|
|
120
|
-
logWarning('Skipping lint because --skip-lint flag was provided.')
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const hasPint = await hasLaravelPint(rootDir)
|
|
125
|
-
if (!hasPint) {
|
|
126
|
-
logStep('Skipping lint (Laravel Pint not found).')
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
logStep('Running Laravel Pint...')
|
|
131
|
-
const pintPath = IS_WINDOWS ? 'vendor\\bin\\pint' : 'vendor/bin/pint'
|
|
132
|
-
|
|
133
|
-
let dotInterval = null
|
|
134
|
-
try {
|
|
135
|
-
// Capture output and show dots as progress
|
|
136
|
-
process.stdout.write(' ')
|
|
137
|
-
dotInterval = setInterval(() => {
|
|
138
|
-
process.stdout.write('.')
|
|
139
|
-
}, 200)
|
|
140
|
-
|
|
141
|
-
await runCommand('php', [pintPath], { capture: true, cwd: rootDir })
|
|
142
|
-
|
|
143
|
-
if (dotInterval) {
|
|
144
|
-
clearInterval(dotInterval)
|
|
145
|
-
dotInterval = null
|
|
146
|
-
}
|
|
147
|
-
process.stdout.write('\n')
|
|
148
|
-
logSuccess('Lint passed.')
|
|
149
|
-
} catch (error) {
|
|
150
|
-
// Clear dots and show error output
|
|
151
|
-
if (dotInterval) {
|
|
152
|
-
clearInterval(dotInterval)
|
|
153
|
-
dotInterval = null
|
|
154
|
-
}
|
|
155
|
-
process.stdout.write('\n')
|
|
156
|
-
if (error.stdout) {
|
|
157
|
-
writeStderr(error.stdout)
|
|
158
|
-
}
|
|
159
|
-
if (error.stderr) {
|
|
160
|
-
writeStderr(error.stderr)
|
|
161
|
-
}
|
|
162
|
-
throw error
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function runTests(skipTests, composer, rootDir = process.cwd()) {
|
|
167
|
-
if (skipTests) {
|
|
168
|
-
logWarning('Skipping tests because --skip-tests flag was provided.')
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const hasArtisanFile = await hasArtisan(rootDir)
|
|
173
|
-
const hasTestScript = hasComposerScript(composer, 'test')
|
|
174
|
-
|
|
175
|
-
if (!hasArtisanFile && !hasTestScript) {
|
|
176
|
-
logStep('Skipping tests (no artisan file or test script found).')
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
logStep('Running test suite...')
|
|
181
|
-
|
|
182
|
-
let dotInterval = null
|
|
183
|
-
try {
|
|
184
|
-
// Capture output and show dots as progress
|
|
185
|
-
process.stdout.write(' ')
|
|
186
|
-
dotInterval = setInterval(() => {
|
|
187
|
-
process.stdout.write('.')
|
|
188
|
-
}, 200)
|
|
189
|
-
|
|
190
|
-
if (hasArtisanFile) {
|
|
191
|
-
await runCommand('php', ['artisan', 'test', '--compact'], { capture: true, cwd: rootDir })
|
|
192
|
-
} else if (hasTestScript) {
|
|
193
|
-
await runCommand('composer', ['test'], { capture: true, cwd: rootDir })
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (dotInterval) {
|
|
197
|
-
clearInterval(dotInterval)
|
|
198
|
-
dotInterval = null
|
|
199
|
-
}
|
|
200
|
-
process.stdout.write('\n')
|
|
201
|
-
logSuccess('Tests passed.')
|
|
202
|
-
} catch (error) {
|
|
203
|
-
// Clear dots and show error output
|
|
204
|
-
if (dotInterval) {
|
|
205
|
-
clearInterval(dotInterval)
|
|
206
|
-
dotInterval = null
|
|
207
|
-
}
|
|
208
|
-
process.stdout.write('\n')
|
|
209
|
-
if (error.stdout) {
|
|
210
|
-
writeStderr(error.stdout)
|
|
211
|
-
}
|
|
212
|
-
if (error.stderr) {
|
|
213
|
-
writeStderr(error.stderr)
|
|
214
|
-
}
|
|
215
|
-
throw error
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function bumpVersion(releaseType, rootDir = process.cwd()) {
|
|
220
|
-
logStep(`Bumping composer version...`)
|
|
221
|
-
|
|
222
|
-
const composer = await readComposer(rootDir)
|
|
223
|
-
const currentVersion = composer.version || '0.0.0'
|
|
224
|
-
|
|
225
|
-
if (!semver.valid(currentVersion)) {
|
|
226
|
-
throw new Error(`Invalid current version "${currentVersion}" in composer.json. Must be a valid semver.`)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const newVersion = semver.inc(currentVersion, releaseType)
|
|
230
|
-
if (!newVersion) {
|
|
231
|
-
throw new Error(`Failed to calculate next ${releaseType} version from ${currentVersion}`)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
composer.version = newVersion
|
|
235
|
-
await writeComposer(rootDir, composer)
|
|
236
|
-
|
|
237
|
-
logStep('Staging composer.json...')
|
|
238
|
-
await runCommand('git', ['add', 'composer.json'], { cwd: rootDir })
|
|
239
|
-
|
|
240
|
-
const commitMessage = `chore: release ${newVersion}`
|
|
241
|
-
logStep('Committing version bump...')
|
|
242
|
-
await runCommand('git', ['commit', '-m', commitMessage], { cwd: rootDir })
|
|
243
|
-
|
|
244
|
-
logStep('Creating git tag...')
|
|
245
|
-
await runCommand('git', ['tag', `v${newVersion}`], { cwd: rootDir })
|
|
246
|
-
|
|
247
|
-
logSuccess(`Version updated to ${newVersion}.`)
|
|
248
|
-
return { ...composer, version: newVersion }
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function pushChanges(rootDir = process.cwd()) {
|
|
252
|
-
logStep('Pushing commits to origin...')
|
|
253
|
-
await runCommand('git', ['push'], { cwd: rootDir })
|
|
254
|
-
|
|
255
|
-
logStep('Pushing tags to origin...')
|
|
256
|
-
await runCommand('git', ['push', 'origin', '--tags'], { cwd: rootDir })
|
|
257
|
-
|
|
258
|
-
logSuccess('Git push completed.')
|
|
259
|
-
}
|
|
9
|
+
const {logProcessing: logStep, logSuccess, logWarning} = createChalkLogger(chalk)
|
|
260
10
|
|
|
261
11
|
export async function releasePackagist() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
logStep('Checking working tree status...')
|
|
276
|
-
await ensureCleanWorkingTree(rootDir)
|
|
277
|
-
|
|
278
|
-
const branch = await getGitCurrentBranch(rootDir, { method: 'show-current' })
|
|
279
|
-
if (!branch) {
|
|
280
|
-
throw new Error('Unable to determine current branch.')
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
logStep(`Current branch: ${branch}`)
|
|
284
|
-
const upstreamRef = await getUpstreamRef(rootDir)
|
|
285
|
-
await ensureUpToDateWithUpstream({ branch, upstreamRef, rootDir, logStep, logWarning })
|
|
286
|
-
|
|
287
|
-
await runLint(skipLint, rootDir)
|
|
288
|
-
await runTests(skipTests, composer, rootDir)
|
|
289
|
-
|
|
290
|
-
const updatedComposer = await bumpVersion(releaseType, rootDir)
|
|
291
|
-
await pushChanges(rootDir)
|
|
292
|
-
|
|
293
|
-
logSuccess(`Release workflow completed for ${composer.name}@${updatedComposer.version}.`)
|
|
294
|
-
logStep('Note: Packagist will automatically detect the new git tag and update the package.')
|
|
12
|
+
const {releaseType, skipTests, skipLint} = parseReleaseArgs({
|
|
13
|
+
booleanFlags: ['--skip-tests', '--skip-lint']
|
|
14
|
+
})
|
|
15
|
+
const rootDir = process.cwd()
|
|
16
|
+
await releasePackagistPackage({
|
|
17
|
+
releaseType,
|
|
18
|
+
skipTests,
|
|
19
|
+
skipLint,
|
|
20
|
+
rootDir,
|
|
21
|
+
logStep,
|
|
22
|
+
logSuccess,
|
|
23
|
+
logWarning
|
|
24
|
+
})
|
|
295
25
|
}
|
|
296
|
-
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
import {NodeSSH} from 'node-ssh'
|
|
4
|
+
|
|
5
|
+
import {createChalkLogger} from '../utils/output.mjs'
|
|
6
|
+
import {runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase} from '../utils/command.mjs'
|
|
7
|
+
import {createLocalCommandRunners} from './local-command.mjs'
|
|
8
|
+
import {createRunPrompt} from './prompt.mjs'
|
|
9
|
+
import {createSshClientFactory} from './ssh-client.mjs'
|
|
10
|
+
|
|
11
|
+
export function createAppContext({
|
|
12
|
+
chalkInstance = chalk,
|
|
13
|
+
inquirerInstance = inquirer,
|
|
14
|
+
NodeSSHClass = NodeSSH,
|
|
15
|
+
runCommandImpl = runCommandBase,
|
|
16
|
+
runCommandCaptureImpl = runCommandCaptureBase
|
|
17
|
+
} = {}) {
|
|
18
|
+
const {logProcessing, logSuccess, logWarning, logError} = createChalkLogger(chalkInstance)
|
|
19
|
+
const runPrompt = createRunPrompt({inquirer: inquirerInstance})
|
|
20
|
+
const createSshClient = createSshClientFactory({NodeSSH: NodeSSHClass})
|
|
21
|
+
const {runCommand, runCommandCapture} = createLocalCommandRunners({
|
|
22
|
+
runCommandBase: runCommandImpl,
|
|
23
|
+
runCommandCaptureBase: runCommandCaptureImpl
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
logProcessing,
|
|
28
|
+
logSuccess,
|
|
29
|
+
logWarning,
|
|
30
|
+
logError,
|
|
31
|
+
runPrompt,
|
|
32
|
+
createSshClient,
|
|
33
|
+
runCommand,
|
|
34
|
+
runCommandCapture
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import {createAppContext} from '../runtime/app-context.mjs'
|
|
4
|
+
import {createConfigurationService} from '../application/configuration/service.mjs'
|
|
5
|
+
import {selectDeploymentTarget as selectDeploymentTargetImpl} from '../application/configuration/select-deployment-target.mjs'
|
|
6
|
+
|
|
7
|
+
const appContext = createAppContext()
|
|
8
|
+
const {
|
|
9
|
+
logSuccess,
|
|
10
|
+
logWarning,
|
|
11
|
+
logProcessing,
|
|
12
|
+
runPrompt
|
|
13
|
+
} = appContext
|
|
14
|
+
const configurationService = createConfigurationService(appContext)
|
|
15
|
+
|
|
16
|
+
export async function selectDeploymentTarget({rootDir = process.cwd()} = {}) {
|
|
17
|
+
return selectDeploymentTargetImpl(rootDir, {
|
|
18
|
+
configurationService,
|
|
19
|
+
runPrompt,
|
|
20
|
+
logProcessing,
|
|
21
|
+
logSuccess,
|
|
22
|
+
logWarning
|
|
23
|
+
})
|
|
24
|
+
}
|
package/src/utils/output.mjs
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
+
export const LOG_PREFIXES = Object.freeze({
|
|
4
|
+
processing: '→',
|
|
5
|
+
success: '✔',
|
|
6
|
+
warning: '⚠',
|
|
7
|
+
error: '✖'
|
|
8
|
+
})
|
|
9
|
+
|
|
3
10
|
export function writeStdoutLine(message = '') {
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
const text = message == null ? '' : String(message)
|
|
12
|
+
process.stdout.write(`${text}\n`)
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
export function writeStderrLine(message = '') {
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
const text = message == null ? '' : String(message)
|
|
17
|
+
process.stderr.write(`${text}\n`)
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
export function writeStderr(message = '') {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
const text = message == null ? '' : String(message)
|
|
22
|
+
process.stderr.write(text)
|
|
23
|
+
if (text && !text.endsWith('\n')) {
|
|
24
|
+
process.stderr.write('\n')
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
export function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
export function formatLogMessage(message = '', prefix = '') {
|
|
29
|
+
const text = message == null ? '' : String(message)
|
|
30
|
+
|
|
31
|
+
if (!prefix || text.length === 0) {
|
|
32
|
+
return text
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const leadingNewlines = text.match(/^\n+/)?.[0] ?? ''
|
|
36
|
+
const body = text.slice(leadingNewlines.length)
|
|
37
|
+
|
|
38
|
+
if (body.length === 0) {
|
|
39
|
+
return `${leadingNewlines}${prefix}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `${leadingNewlines}${prefix} ${body}`
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
export function createChalkLogger(chalk, {
|
|
46
|
+
prefixes = LOG_PREFIXES
|
|
47
|
+
} = {}) {
|
|
48
|
+
return {
|
|
49
|
+
logProcessing: (message = '') => writeStdoutLine(chalk.yellow(formatLogMessage(message, prefixes.processing))),
|
|
50
|
+
logSuccess: (message = '') => writeStdoutLine(chalk.green(formatLogMessage(message, prefixes.success))),
|
|
51
|
+
logWarning: (message = '') => writeStderrLine(chalk.yellow(formatLogMessage(message, prefixes.warning))),
|
|
52
|
+
logError: (message = '') => writeStderrLine(chalk.red(formatLogMessage(message, prefixes.error)))
|
|
53
|
+
}
|
|
54
|
+
}
|