@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.
Files changed (33) hide show
  1. package/README.md +55 -2
  2. package/bin/zephyr.mjs +3 -1
  3. package/package.json +7 -2
  4. package/src/application/configuration/app-details.mjs +89 -0
  5. package/src/application/configuration/app-selection.mjs +87 -0
  6. package/src/application/configuration/preset-selection.mjs +59 -0
  7. package/src/application/configuration/select-deployment-target.mjs +165 -0
  8. package/src/application/configuration/server-selection.mjs +87 -0
  9. package/src/application/configuration/service.mjs +109 -0
  10. package/src/application/deploy/build-remote-deployment-plan.mjs +174 -0
  11. package/src/application/deploy/bump-local-package-version.mjs +81 -0
  12. package/src/application/deploy/execute-remote-deployment-plan.mjs +61 -0
  13. package/src/{utils/task-planner.mjs → application/deploy/plan-laravel-deployment-tasks.mjs} +5 -4
  14. package/src/application/deploy/prepare-local-deployment.mjs +52 -0
  15. package/src/application/deploy/resolve-local-deployment-context.mjs +17 -0
  16. package/src/application/deploy/resolve-pending-snapshot.mjs +45 -0
  17. package/src/application/deploy/run-deployment.mjs +147 -0
  18. package/src/application/deploy/run-local-deployment-checks.mjs +80 -0
  19. package/src/application/release/release-node-package.mjs +340 -0
  20. package/src/application/release/release-packagist-package.mjs +223 -0
  21. package/src/config/project.mjs +13 -0
  22. package/src/deploy/local-repo.mjs +187 -67
  23. package/src/deploy/remote-exec.mjs +2 -3
  24. package/src/index.mjs +27 -85
  25. package/src/main.mjs +78 -641
  26. package/src/release/shared.mjs +104 -0
  27. package/src/release-node.mjs +20 -424
  28. package/src/release-packagist.mjs +20 -291
  29. package/src/runtime/app-context.mjs +36 -0
  30. package/src/targets/index.mjs +24 -0
  31. package/src/utils/output.mjs +41 -16
  32. package/src/utils/config-flow.mjs +0 -284
  33. /package/src/{utils/php-version.mjs → infrastructure/php/version.mjs} +0 -0
@@ -0,0 +1,147 @@
1
+ import fs from 'node:fs/promises'
2
+ import os from 'node:os'
3
+ import process from 'node:process'
4
+
5
+ import {
6
+ acquireRemoteLock,
7
+ compareLocksAndPrompt,
8
+ releaseLocalLock,
9
+ releaseRemoteLock
10
+ } from '../../deploy/locks.mjs'
11
+ import {createRemoteExecutor} from '../../deploy/remote-exec.mjs'
12
+ import {resolveSshKeyPath} from '../../ssh/keys.mjs'
13
+ import {cleanupOldLogs, closeLogFile, getLogFilePath, writeToLogFile} from '../../utils/log-file.mjs'
14
+ import {resolveRemotePath} from '../../utils/remote-path.mjs'
15
+ import {buildRemoteDeploymentPlan} from './build-remote-deployment-plan.mjs'
16
+ import {executeRemoteDeploymentPlan} from './execute-remote-deployment-plan.mjs'
17
+ import {prepareLocalDeployment} from './prepare-local-deployment.mjs'
18
+
19
+ async function resolveRemoteHome(ssh, sshUser) {
20
+ const remoteHomeResult = await ssh.execCommand('printf "%s" "$HOME"')
21
+ return remoteHomeResult.stdout.trim() || `/home/${sshUser}`
22
+ }
23
+
24
+ export async function runDeployment(config, options = {}) {
25
+ const {
26
+ snapshot = null,
27
+ rootDir = process.cwd(),
28
+ versionArg = null,
29
+ context
30
+ } = options
31
+
32
+ const {
33
+ logProcessing,
34
+ logSuccess,
35
+ logWarning,
36
+ logError,
37
+ runPrompt,
38
+ createSshClient,
39
+ runCommand
40
+ } = context
41
+
42
+ await cleanupOldLogs(rootDir)
43
+
44
+ const {requiredPhpVersion} = await prepareLocalDeployment(config, {
45
+ snapshot,
46
+ rootDir,
47
+ versionArg,
48
+ runPrompt,
49
+ runCommand,
50
+ runCommandCapture: context.runCommandCapture,
51
+ logProcessing,
52
+ logSuccess,
53
+ logWarning
54
+ })
55
+
56
+ const ssh = createSshClient()
57
+ const sshUser = config.sshUser || os.userInfo().username
58
+ const privateKeyPath = await resolveSshKeyPath(config.sshKey)
59
+ const privateKey = await fs.readFile(privateKeyPath, 'utf8')
60
+ let remoteCwd = null
61
+
62
+ logProcessing(`\nConnecting to ${config.serverIp} as ${sshUser}...`)
63
+
64
+ let lockAcquired = false
65
+
66
+ try {
67
+ await ssh.connect({
68
+ host: config.serverIp,
69
+ username: sshUser,
70
+ privateKey
71
+ })
72
+
73
+ const remoteHome = await resolveRemoteHome(ssh, sshUser)
74
+ remoteCwd = resolveRemotePath(config.projectPath, remoteHome)
75
+
76
+ logProcessing('Connection established. Acquiring deployment lock on server...')
77
+ await acquireRemoteLock(ssh, remoteCwd, rootDir, {runPrompt, logWarning})
78
+ lockAcquired = true
79
+ logProcessing(`Lock acquired. Running deployment commands in ${remoteCwd}...`)
80
+
81
+ const executeRemote = createRemoteExecutor({
82
+ ssh,
83
+ rootDir,
84
+ remoteCwd,
85
+ writeToLogFile,
86
+ logProcessing,
87
+ logSuccess,
88
+ logError
89
+ })
90
+
91
+ const remotePlan = await buildRemoteDeploymentPlan({
92
+ config,
93
+ snapshot,
94
+ rootDir,
95
+ requiredPhpVersion,
96
+ ssh,
97
+ remoteCwd,
98
+ executeRemote,
99
+ logProcessing,
100
+ logSuccess,
101
+ logWarning
102
+ })
103
+
104
+ await executeRemoteDeploymentPlan({
105
+ rootDir,
106
+ executeRemote,
107
+ steps: remotePlan.steps,
108
+ usefulSteps: remotePlan.usefulSteps,
109
+ pendingSnapshot: remotePlan.pendingSnapshot,
110
+ logProcessing
111
+ })
112
+
113
+ logSuccess('\nDeployment commands completed successfully.')
114
+
115
+ const logPath = await getLogFilePath(rootDir)
116
+ logSuccess(`\nAll task output has been logged to: ${logPath}`)
117
+ } catch (error) {
118
+ const logPath = await getLogFilePath(rootDir).catch(() => null)
119
+ if (logPath) {
120
+ logError(`\nTask output has been logged to: ${logPath}`)
121
+ }
122
+
123
+ if (lockAcquired && ssh && remoteCwd) {
124
+ try {
125
+ await compareLocksAndPrompt(rootDir, ssh, remoteCwd, {runPrompt, logWarning})
126
+ } catch {
127
+ // Ignore lock comparison errors during error handling
128
+ }
129
+ }
130
+
131
+ throw new Error(`Deployment failed: ${error.message}`)
132
+ } finally {
133
+ if (lockAcquired && ssh && remoteCwd) {
134
+ try {
135
+ await releaseRemoteLock(ssh, remoteCwd, {logWarning})
136
+ await releaseLocalLock(rootDir, {logWarning})
137
+ } catch (error) {
138
+ logWarning(`Failed to release lock: ${error.message}`)
139
+ }
140
+ }
141
+
142
+ await closeLogFile()
143
+ if (ssh) {
144
+ ssh.dispose()
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,80 @@
1
+ import * as localRepo from '../../deploy/local-repo.mjs'
2
+ import * as preflight from '../../deploy/preflight.mjs'
3
+ import {commandExists} from '../../utils/command.mjs'
4
+
5
+ async function getGitStatus(rootDir, {runCommandCapture} = {}) {
6
+ return await localRepo.getGitStatus(rootDir, {runCommandCapture})
7
+ }
8
+
9
+ async function hasUncommittedChanges(rootDir, {runCommandCapture} = {}) {
10
+ return await localRepo.hasUncommittedChanges(rootDir, {
11
+ getGitStatus: (dir) => getGitStatus(dir, {runCommandCapture})
12
+ })
13
+ }
14
+
15
+ async function runLocalLaravelTests(rootDir, {runCommand, logProcessing, logSuccess, logWarning} = {}) {
16
+ if (!commandExists('php')) {
17
+ logWarning?.(
18
+ 'PHP is not available in PATH. Skipping local Laravel tests.\n' +
19
+ ' To run tests locally, ensure PHP is installed and added to your PATH.\n' +
20
+ ' On Windows with Laravel Herd, you may need to add Herd\'s PHP to your system PATH.'
21
+ )
22
+ return
23
+ }
24
+
25
+ logProcessing?.('Running Laravel tests locally...')
26
+
27
+ try {
28
+ await runCommand('php', ['artisan', 'test', '--compact'], {cwd: rootDir})
29
+ logSuccess?.('Local tests passed.')
30
+ } catch (error) {
31
+ if (error.code === 'ENOENT') {
32
+ throw new Error(
33
+ 'Failed to run Laravel tests: PHP executable not found.\n' +
34
+ 'Make sure PHP is installed and available in your PATH.'
35
+ )
36
+ }
37
+
38
+ throw new Error(`Local tests failed. Fix test failures before deploying.\n${error.message}`)
39
+ }
40
+ }
41
+
42
+ export async function runLocalDeploymentChecks({
43
+ rootDir,
44
+ isLaravel,
45
+ hasHook,
46
+ runCommand,
47
+ runCommandCapture,
48
+ logProcessing,
49
+ logSuccess,
50
+ logWarning
51
+ } = {}) {
52
+ if (hasHook) {
53
+ logProcessing?.('Pre-push git hook detected. Skipping local linting and test execution.')
54
+ return
55
+ }
56
+
57
+ const lintRan = await preflight.runLinting(rootDir, {
58
+ runCommand,
59
+ logProcessing,
60
+ logSuccess,
61
+ logWarning,
62
+ commandExists
63
+ })
64
+
65
+ if (lintRan) {
66
+ const hasChanges = await hasUncommittedChanges(rootDir, {runCommandCapture})
67
+ if (hasChanges) {
68
+ await preflight.commitLintingChanges(rootDir, {
69
+ getGitStatus: (dir) => getGitStatus(dir, {runCommandCapture}),
70
+ runCommand,
71
+ logProcessing,
72
+ logSuccess
73
+ })
74
+ }
75
+ }
76
+
77
+ if (isLaravel) {
78
+ await runLocalLaravelTests(rootDir, {runCommand, logProcessing, logSuccess, logWarning})
79
+ }
80
+ }
@@ -0,0 +1,340 @@
1
+ import {join} from 'node:path'
2
+ import {readFile} from 'node:fs/promises'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ import {writeStderr} from '../../utils/output.mjs'
8
+ import {
9
+ ensureCleanWorkingTree,
10
+ ensureReleaseBranchReady,
11
+ runReleaseCommand as runCommand,
12
+ validateReleaseDependencies
13
+ } from '../../release/shared.mjs'
14
+
15
+ async function readPackage(rootDir = process.cwd()) {
16
+ const packagePath = join(rootDir, 'package.json')
17
+ const raw = await readFile(packagePath, 'utf8')
18
+ return JSON.parse(raw)
19
+ }
20
+
21
+ function hasScript(pkg, scriptName) {
22
+ return pkg?.scripts?.[scriptName] !== undefined
23
+ }
24
+
25
+ async function runLint(skipLint, pkg, rootDir = process.cwd(), {logStep, logSuccess, logWarning} = {}) {
26
+ if (skipLint) {
27
+ logWarning?.('Skipping lint because --skip-lint flag was provided.')
28
+ return
29
+ }
30
+
31
+ if (!hasScript(pkg, 'lint')) {
32
+ logStep?.('Skipping lint (no lint script found in package.json).')
33
+ return
34
+ }
35
+
36
+ logStep?.('Running lint...')
37
+
38
+ try {
39
+ await runCommand('npm', ['run', 'lint'], {cwd: rootDir})
40
+ logSuccess?.('Lint passed.')
41
+ } catch (error) {
42
+ if (error.stdout) {
43
+ writeStderr(error.stdout)
44
+ }
45
+ if (error.stderr) {
46
+ writeStderr(error.stderr)
47
+ }
48
+ throw error
49
+ }
50
+ }
51
+
52
+ async function runTests(skipTests, pkg, rootDir = process.cwd(), {logStep, logSuccess, logWarning} = {}) {
53
+ if (skipTests) {
54
+ logWarning?.('Skipping tests because --skip-tests flag was provided.')
55
+ return
56
+ }
57
+
58
+ if (!hasScript(pkg, 'test:run') && !hasScript(pkg, 'test')) {
59
+ logStep?.('Skipping tests (no test or test:run script found in package.json).')
60
+ return
61
+ }
62
+
63
+ logStep?.('Running test suite...')
64
+
65
+ try {
66
+ const testRunScript = pkg?.scripts?.['test:run'] ?? ''
67
+ const testScript = pkg?.scripts?.test ?? ''
68
+ const usesNodeTest = (script) => /\bnode\b.*\s--test\b/.test(script)
69
+
70
+ if (hasScript(pkg, 'test:run')) {
71
+ if (usesNodeTest(testRunScript)) {
72
+ await runCommand('npm', ['run', 'test:run'], {cwd: rootDir})
73
+ } else {
74
+ await runCommand('npm', ['run', 'test:run', '--', '--reporter=dot'], {cwd: rootDir})
75
+ }
76
+ } else if (usesNodeTest(testScript)) {
77
+ await runCommand('npm', ['test'], {cwd: rootDir})
78
+ } else {
79
+ await runCommand('npm', ['test', '--', '--run', '--reporter=dot'], {cwd: rootDir})
80
+ }
81
+
82
+ logSuccess?.('Tests passed.')
83
+ } catch (error) {
84
+ if (error.stdout) {
85
+ writeStderr(error.stdout)
86
+ }
87
+ if (error.stderr) {
88
+ writeStderr(error.stderr)
89
+ }
90
+ throw error
91
+ }
92
+ }
93
+
94
+ async function runBuild(skipBuild, pkg, rootDir = process.cwd(), {logStep, logSuccess, logWarning} = {}) {
95
+ if (skipBuild) {
96
+ logWarning?.('Skipping build because --skip-build flag was provided.')
97
+ return
98
+ }
99
+
100
+ if (!hasScript(pkg, 'build')) {
101
+ logStep?.('Skipping build (no build script found in package.json).')
102
+ return
103
+ }
104
+
105
+ logStep?.('Building project...')
106
+
107
+ try {
108
+ await runCommand('npm', ['run', 'build'], {cwd: rootDir})
109
+ logSuccess?.('Build completed.')
110
+ } catch (error) {
111
+ if (error.stdout) {
112
+ writeStderr(error.stdout)
113
+ }
114
+ if (error.stderr) {
115
+ writeStderr(error.stderr)
116
+ }
117
+ throw error
118
+ }
119
+ }
120
+
121
+ async function runLibBuild(skipBuild, pkg, rootDir = process.cwd(), {logStep, logSuccess, logWarning} = {}) {
122
+ if (skipBuild) {
123
+ logWarning?.('Skipping library build because --skip-build flag was provided.')
124
+ return false
125
+ }
126
+
127
+ if (!hasScript(pkg, 'build:lib')) {
128
+ logStep?.('Skipping library build (no build:lib script found in package.json).')
129
+ return false
130
+ }
131
+
132
+ logStep?.('Building library...')
133
+
134
+ try {
135
+ await runCommand('npm', ['run', 'build:lib'], {cwd: rootDir})
136
+ logSuccess?.('Library built.')
137
+ } catch (error) {
138
+ if (error.stdout) {
139
+ writeStderr(error.stdout)
140
+ }
141
+ if (error.stderr) {
142
+ writeStderr(error.stderr)
143
+ }
144
+ throw error
145
+ }
146
+
147
+ const {stdout: statusAfterBuild} = await runCommand('git', ['status', '--porcelain'], {capture: true, cwd: rootDir})
148
+ const hasLibChanges = statusAfterBuild.split('\n').some((line) => {
149
+ const trimmed = line.trim()
150
+ return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
151
+ })
152
+
153
+ if (hasLibChanges) {
154
+ logStep?.('Committing lib build artifacts...')
155
+ await runCommand('git', ['add', 'lib/'], {capture: true, cwd: rootDir})
156
+ await runCommand('git', ['commit', '-m', 'chore: build lib artifacts'], {capture: true, cwd: rootDir})
157
+ logSuccess?.('Lib build artifacts committed.')
158
+ }
159
+
160
+ return hasLibChanges
161
+ }
162
+
163
+ async function bumpVersion(releaseType, rootDir = process.cwd(), {logStep, logSuccess} = {}) {
164
+ logStep?.('Bumping package version...')
165
+
166
+ const {stdout: statusBefore} = await runCommand('git', ['status', '--porcelain'], {capture: true, cwd: rootDir})
167
+ const hasLibChanges = statusBefore.split('\n').some((line) => {
168
+ const trimmed = line.trim()
169
+ return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
170
+ })
171
+
172
+ if (hasLibChanges) {
173
+ logStep?.('Stashing lib build artifacts...')
174
+ await runCommand('git', ['stash', 'push', '-u', '-m', 'temp: lib build artifacts', 'lib/'], {capture: true, cwd: rootDir})
175
+ }
176
+
177
+ try {
178
+ await runCommand('npm', ['version', releaseType], {capture: true, cwd: rootDir})
179
+ } finally {
180
+ if (hasLibChanges) {
181
+ logStep?.('Restoring lib build artifacts...')
182
+ await runCommand('git', ['stash', 'pop'], {capture: true, cwd: rootDir})
183
+ await runCommand('git', ['add', 'lib/'], {capture: true, cwd: rootDir})
184
+ const {stdout: statusAfter} = await runCommand('git', ['status', '--porcelain'], {capture: true, cwd: rootDir})
185
+ if (statusAfter.includes('lib/')) {
186
+ await runCommand('git', ['commit', '--amend', '--no-edit'], {capture: true, cwd: rootDir})
187
+ }
188
+ }
189
+ }
190
+
191
+ const pkg = await readPackage(rootDir)
192
+ const commitMessage = `chore: release ${pkg.version}`
193
+ await runCommand('git', ['commit', '--amend', '-m', commitMessage], {capture: true, cwd: rootDir})
194
+
195
+ logSuccess?.(`Version updated to ${pkg.version}.`)
196
+ return pkg
197
+ }
198
+
199
+ async function pushChanges(rootDir = process.cwd(), {logStep, logSuccess} = {}) {
200
+ logStep?.('Pushing commits and tags to origin...')
201
+ try {
202
+ await runCommand('git', ['push', '--follow-tags'], {capture: true, cwd: rootDir})
203
+ logSuccess?.('Git push completed.')
204
+ } catch (error) {
205
+ if (error.stdout) {
206
+ writeStderr(error.stdout)
207
+ }
208
+ if (error.stderr) {
209
+ writeStderr(error.stderr)
210
+ }
211
+ throw error
212
+ }
213
+ }
214
+
215
+ function extractDomainFromHomepage(homepage) {
216
+ if (!homepage) return null
217
+ try {
218
+ const url = new URL(homepage)
219
+ return url.hostname
220
+ } catch {
221
+ const match = homepage.match(/(?:https?:\/\/)?([^/]+)/)
222
+ return match ? match[1] : null
223
+ }
224
+ }
225
+
226
+ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd(), {logStep, logSuccess, logWarning} = {}) {
227
+ if (skipDeploy) {
228
+ logWarning?.('Skipping GitHub Pages deployment because --skip-deploy flag was provided.')
229
+ return
230
+ }
231
+
232
+ const distPath = path.join(rootDir, 'dist')
233
+ const distExists = await fs.promises
234
+ .stat(distPath)
235
+ .then((stats) => stats.isDirectory())
236
+ .catch(() => false)
237
+
238
+ if (!distExists) {
239
+ logStep?.('Skipping GitHub Pages deployment (no dist directory found).')
240
+ return
241
+ }
242
+
243
+ logStep?.('Deploying to GitHub Pages...')
244
+
245
+ const cnamePath = path.join(distPath, 'CNAME')
246
+ const homepage =
247
+ pkg &&
248
+ typeof pkg === 'object' &&
249
+ 'homepage' in pkg &&
250
+ typeof pkg.homepage === 'string'
251
+ ? pkg.homepage
252
+ : null
253
+
254
+ if (homepage) {
255
+ const domain = extractDomainFromHomepage(homepage)
256
+ if (domain) {
257
+ try {
258
+ await fs.promises.mkdir(distPath, {recursive: true})
259
+ await fs.promises.writeFile(cnamePath, domain)
260
+ } catch (error) {
261
+ logWarning?.(`Could not write CNAME file: ${error.message}`)
262
+ }
263
+ }
264
+ }
265
+
266
+ const worktreeDir = path.resolve(rootDir, '.gh-pages')
267
+
268
+ try {
269
+ try {
270
+ await runCommand('git', ['worktree', 'remove', worktreeDir, '-f'], {capture: true, cwd: rootDir})
271
+ } catch (_error) {
272
+ // Ignore if worktree doesn't exist
273
+ }
274
+
275
+ try {
276
+ await runCommand('git', ['worktree', 'add', worktreeDir, 'gh-pages'], {capture: true, cwd: rootDir})
277
+ } catch {
278
+ await runCommand('git', ['worktree', 'add', worktreeDir, '-b', 'gh-pages'], {capture: true, cwd: rootDir})
279
+ }
280
+
281
+ await runCommand('git', ['-C', worktreeDir, 'config', 'user.name', 'wyxos'], {capture: true})
282
+ await runCommand('git', ['-C', worktreeDir, 'config', 'user.email', 'github@wyxos.com'], {capture: true})
283
+
284
+ for (const entry of fs.readdirSync(worktreeDir)) {
285
+ if (entry === '.git') continue
286
+ const target = path.join(worktreeDir, entry)
287
+ fs.rmSync(target, {recursive: true, force: true})
288
+ }
289
+
290
+ fs.cpSync(distPath, worktreeDir, {recursive: true})
291
+
292
+ await runCommand('git', ['-C', worktreeDir, 'add', '-A'], {capture: true})
293
+ await runCommand('git', ['-C', worktreeDir, 'commit', '-m', `deploy: demo ${new Date().toISOString()}`, '--allow-empty'], {capture: true})
294
+ await runCommand('git', ['-C', worktreeDir, 'push', '-f', 'origin', 'gh-pages'], {capture: true})
295
+
296
+ logSuccess?.('GitHub Pages deployment completed.')
297
+ } catch (error) {
298
+ if (error.stdout) {
299
+ writeStderr(error.stdout)
300
+ }
301
+ if (error.stderr) {
302
+ writeStderr(error.stderr)
303
+ }
304
+ throw error
305
+ }
306
+ }
307
+
308
+ export async function releaseNodePackage({
309
+ releaseType,
310
+ skipTests = false,
311
+ skipLint = false,
312
+ skipBuild = false,
313
+ skipDeploy = false,
314
+ rootDir = process.cwd(),
315
+ logStep,
316
+ logSuccess,
317
+ logWarning
318
+ } = {}) {
319
+ logStep?.('Reading package metadata...')
320
+ const pkg = await readPackage(rootDir)
321
+
322
+ logStep?.('Validating dependencies...')
323
+ await validateReleaseDependencies(rootDir, {logSuccess})
324
+
325
+ logStep?.('Checking working tree status...')
326
+ await ensureCleanWorkingTree(rootDir, {runCommand})
327
+ await ensureReleaseBranchReady({rootDir, branchMethod: 'show-current', logStep, logWarning})
328
+
329
+ await runLint(skipLint, pkg, rootDir, {logStep, logSuccess, logWarning})
330
+ await runTests(skipTests, pkg, rootDir, {logStep, logSuccess, logWarning})
331
+ await runLibBuild(skipBuild, pkg, rootDir, {logStep, logSuccess, logWarning})
332
+
333
+ const updatedPkg = await bumpVersion(releaseType, rootDir, {logStep, logSuccess})
334
+ await runBuild(skipBuild, updatedPkg, rootDir, {logStep, logSuccess, logWarning})
335
+ await pushChanges(rootDir, {logStep, logSuccess})
336
+ await deployGHPages(skipDeploy, updatedPkg, rootDir, {logStep, logSuccess, logWarning})
337
+
338
+ logStep?.('Publishing will be handled by GitHub Actions via trusted publishing.')
339
+ logSuccess?.(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
340
+ }