@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import inquirer from 'inquirer'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
|
|
4
|
+
import { validateLocalDependencies } from '../dependency-scanner.mjs'
|
|
5
|
+
import { runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase } from '../utils/command.mjs'
|
|
6
|
+
import {
|
|
7
|
+
ensureUpToDateWithUpstream,
|
|
8
|
+
getCurrentBranch,
|
|
9
|
+
getUpstreamRef
|
|
10
|
+
} from '../utils/git.mjs'
|
|
11
|
+
|
|
12
|
+
const RELEASE_TYPES = new Set([
|
|
13
|
+
'major',
|
|
14
|
+
'minor',
|
|
15
|
+
'patch',
|
|
16
|
+
'premajor',
|
|
17
|
+
'preminor',
|
|
18
|
+
'prepatch',
|
|
19
|
+
'prerelease'
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
function flagToKey(flag) {
|
|
23
|
+
return flag
|
|
24
|
+
.replace(/^--/, '')
|
|
25
|
+
.replace(/-([a-z])/g, (_match, character) => character.toUpperCase())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseReleaseArgs({
|
|
29
|
+
args = process.argv.slice(2),
|
|
30
|
+
booleanFlags = []
|
|
31
|
+
} = {}) {
|
|
32
|
+
const filteredArgs = args.filter((arg) => !arg.startsWith('--type='))
|
|
33
|
+
const positionals = filteredArgs.filter((arg) => !arg.startsWith('--'))
|
|
34
|
+
const presentFlags = new Set(filteredArgs.filter((arg) => arg.startsWith('--')))
|
|
35
|
+
const releaseType = positionals[0] ?? 'patch'
|
|
36
|
+
|
|
37
|
+
if (!RELEASE_TYPES.has(releaseType)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Invalid release type "${releaseType}". Use one of: ${Array.from(RELEASE_TYPES).join(', ')}.`
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parsedFlags = Object.fromEntries(
|
|
44
|
+
booleanFlags.map((flag) => [flagToKey(flag), presentFlags.has(flag)])
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return { releaseType, ...parsedFlags }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function runReleaseCommand(command, args, {
|
|
51
|
+
cwd = process.cwd(),
|
|
52
|
+
capture = false
|
|
53
|
+
} = {}) {
|
|
54
|
+
if (capture) {
|
|
55
|
+
const { stdout, stderr } = await runCommandCaptureBase(command, args, { cwd })
|
|
56
|
+
return { stdout: stdout.trim(), stderr: stderr.trim() }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await runCommandBase(command, args, { cwd })
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
64
|
+
runCommand = runReleaseCommand
|
|
65
|
+
} = {}) {
|
|
66
|
+
const { stdout } = await runCommand('git', ['status', '--porcelain'], {
|
|
67
|
+
capture: true,
|
|
68
|
+
cwd: rootDir
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (stdout.length > 0) {
|
|
72
|
+
throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function validateReleaseDependencies(rootDir = process.cwd(), {
|
|
77
|
+
prompt = (questions) => inquirer.prompt(questions),
|
|
78
|
+
logSuccess
|
|
79
|
+
} = {}) {
|
|
80
|
+
await validateLocalDependencies(rootDir, prompt, logSuccess)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function ensureReleaseBranchReady({
|
|
84
|
+
rootDir = process.cwd(),
|
|
85
|
+
branchMethod = 'show-current',
|
|
86
|
+
getCurrentBranchImpl = getCurrentBranch,
|
|
87
|
+
getUpstreamRefImpl = getUpstreamRef,
|
|
88
|
+
ensureUpToDateWithUpstreamImpl = ensureUpToDateWithUpstream,
|
|
89
|
+
logStep,
|
|
90
|
+
logWarning
|
|
91
|
+
} = {}) {
|
|
92
|
+
const branch = await getCurrentBranchImpl(rootDir, { method: branchMethod })
|
|
93
|
+
|
|
94
|
+
if (!branch) {
|
|
95
|
+
throw new Error('Unable to determine current branch.')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logStep?.(`Current branch: ${branch}`)
|
|
99
|
+
|
|
100
|
+
const upstreamRef = await getUpstreamRefImpl(rootDir)
|
|
101
|
+
await ensureUpToDateWithUpstreamImpl({ branch, upstreamRef, rootDir, logStep, logWarning })
|
|
102
|
+
|
|
103
|
+
return { branch, upstreamRef }
|
|
104
|
+
}
|
package/src/release-node.mjs
CHANGED
|
@@ -1,431 +1,27 @@
|
|
|
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
1
|
import process from 'node:process'
|
|
6
2
|
import chalk from 'chalk'
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
3
|
+
import {createChalkLogger} from './utils/output.mjs'
|
|
4
|
+
import {
|
|
5
|
+
parseReleaseArgs,
|
|
6
|
+
} from './release/shared.mjs'
|
|
7
|
+
import {releaseNodePackage} from './application/release/release-node-package.mjs'
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
writeStdoutLine(chalk.yellow(`→ ${message}`))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function logSuccess(message) {
|
|
18
|
-
writeStdoutLine(chalk.green(`✔ ${message}`))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function logWarning(message) {
|
|
22
|
-
writeStderrLine(chalk.yellow(`⚠ ${message}`))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function runCommand(command, args, { cwd = process.cwd(), capture = false } = {}) {
|
|
26
|
-
if (capture) {
|
|
27
|
-
const { stdout, stderr } = await runCommandCaptureBase(command, args, { cwd })
|
|
28
|
-
return { stdout: stdout.trim(), stderr: stderr.trim() }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await runCommandBase(command, args, { cwd })
|
|
32
|
-
return undefined
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function readPackage(rootDir = process.cwd()) {
|
|
36
|
-
const packagePath = join(rootDir, 'package.json')
|
|
37
|
-
const raw = await readFile(packagePath, 'utf8')
|
|
38
|
-
return JSON.parse(raw)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function hasScript(pkg, scriptName) {
|
|
42
|
-
return pkg?.scripts?.[scriptName] !== undefined
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function ensureCleanWorkingTree(rootDir = process.cwd()) {
|
|
46
|
-
const { stdout } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
47
|
-
|
|
48
|
-
if (stdout.length > 0) {
|
|
49
|
-
throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Git helpers imported from src/utils/git.mjs
|
|
54
|
-
|
|
55
|
-
function parseArgs() {
|
|
56
|
-
const args = process.argv.slice(2)
|
|
57
|
-
// Filter out --type flag as it's handled by zephyr CLI
|
|
58
|
-
const filteredArgs = args.filter((arg) => !arg.startsWith('--type='))
|
|
59
|
-
const positionals = filteredArgs.filter((arg) => !arg.startsWith('--'))
|
|
60
|
-
const flags = new Set(filteredArgs.filter((arg) => arg.startsWith('--')))
|
|
61
|
-
|
|
62
|
-
const releaseType = positionals[0] ?? 'patch'
|
|
63
|
-
const skipTests = flags.has('--skip-tests')
|
|
64
|
-
const skipLint = flags.has('--skip-lint')
|
|
65
|
-
const skipBuild = flags.has('--skip-build')
|
|
66
|
-
const skipDeploy = flags.has('--skip-deploy')
|
|
67
|
-
|
|
68
|
-
const allowedTypes = new Set([
|
|
69
|
-
'major',
|
|
70
|
-
'minor',
|
|
71
|
-
'patch',
|
|
72
|
-
'premajor',
|
|
73
|
-
'preminor',
|
|
74
|
-
'prepatch',
|
|
75
|
-
'prerelease'
|
|
76
|
-
])
|
|
77
|
-
|
|
78
|
-
if (!allowedTypes.has(releaseType)) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Invalid release type "${releaseType}". Use one of: ${Array.from(allowedTypes).join(', ')}.`
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return { releaseType, skipTests, skipLint, skipBuild, skipDeploy }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function runLint(skipLint, pkg, rootDir = process.cwd()) {
|
|
88
|
-
if (skipLint) {
|
|
89
|
-
logWarning('Skipping lint because --skip-lint flag was provided.')
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!hasScript(pkg, 'lint')) {
|
|
94
|
-
logStep('Skipping lint (no lint script found in package.json).')
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
logStep('Running lint...')
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
await runCommand('npm', ['run', 'lint'], { cwd: rootDir })
|
|
102
|
-
logSuccess('Lint passed.')
|
|
103
|
-
} catch (error) {
|
|
104
|
-
if (error.stdout) {
|
|
105
|
-
writeStderr(error.stdout)
|
|
106
|
-
}
|
|
107
|
-
if (error.stderr) {
|
|
108
|
-
writeStderr(error.stderr)
|
|
109
|
-
}
|
|
110
|
-
throw error
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function runTests(skipTests, pkg, rootDir = process.cwd()) {
|
|
115
|
-
if (skipTests) {
|
|
116
|
-
logWarning('Skipping tests because --skip-tests flag was provided.')
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check for test:run or test script
|
|
121
|
-
if (!hasScript(pkg, 'test:run') && !hasScript(pkg, 'test')) {
|
|
122
|
-
logStep('Skipping tests (no test or test:run script found in package.json).')
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
logStep('Running test suite...')
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const testRunScript = pkg?.scripts?.['test:run'] ?? ''
|
|
130
|
-
const testScript = pkg?.scripts?.test ?? ''
|
|
131
|
-
const usesNodeTest = (script) => /\bnode\b.*\s--test\b/.test(script)
|
|
132
|
-
|
|
133
|
-
// Prefer test:run if available, otherwise use test with --run and --reporter flags
|
|
134
|
-
if (hasScript(pkg, 'test:run')) {
|
|
135
|
-
if (usesNodeTest(testRunScript)) {
|
|
136
|
-
await runCommand('npm', ['run', 'test:run'], { cwd: rootDir })
|
|
137
|
-
} else {
|
|
138
|
-
// Pass reporter flag to test:run script
|
|
139
|
-
await runCommand('npm', ['run', 'test:run', '--', '--reporter=dot'], { cwd: rootDir })
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
if (usesNodeTest(testScript)) {
|
|
143
|
-
await runCommand('npm', ['test'], { cwd: rootDir })
|
|
144
|
-
} else {
|
|
145
|
-
// For test script, pass --run and --reporter flags (works with vitest)
|
|
146
|
-
await runCommand('npm', ['test', '--', '--run', '--reporter=dot'], { cwd: rootDir })
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
logSuccess('Tests passed.')
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (error.stdout) {
|
|
153
|
-
writeStderr(error.stdout)
|
|
154
|
-
}
|
|
155
|
-
if (error.stderr) {
|
|
156
|
-
writeStderr(error.stderr)
|
|
157
|
-
}
|
|
158
|
-
throw error
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function runBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
163
|
-
if (skipBuild) {
|
|
164
|
-
logWarning('Skipping build because --skip-build flag was provided.')
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!hasScript(pkg, 'build')) {
|
|
169
|
-
logStep('Skipping build (no build script found in package.json).')
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
logStep('Building project...')
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await runCommand('npm', ['run', 'build'], { cwd: rootDir })
|
|
177
|
-
logSuccess('Build completed.')
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (error.stdout) {
|
|
180
|
-
writeStderr(error.stdout)
|
|
181
|
-
}
|
|
182
|
-
if (error.stderr) {
|
|
183
|
-
writeStderr(error.stderr)
|
|
184
|
-
}
|
|
185
|
-
throw error
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
190
|
-
if (skipBuild) {
|
|
191
|
-
logWarning('Skipping library build because --skip-build flag was provided.')
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (!hasScript(pkg, 'build:lib')) {
|
|
196
|
-
logStep('Skipping library build (no build:lib script found in package.json).')
|
|
197
|
-
return false
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
logStep('Building library...')
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
await runCommand('npm', ['run', 'build:lib'], { cwd: rootDir })
|
|
204
|
-
logSuccess('Library built.')
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (error.stdout) {
|
|
207
|
-
writeStderr(error.stdout)
|
|
208
|
-
}
|
|
209
|
-
if (error.stderr) {
|
|
210
|
-
writeStderr(error.stderr)
|
|
211
|
-
}
|
|
212
|
-
throw error
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Check for lib changes and commit them if any
|
|
216
|
-
const { stdout: statusAfterBuild } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
217
|
-
const hasLibChanges = statusAfterBuild.split('\n').some(line => {
|
|
218
|
-
const trimmed = line.trim()
|
|
219
|
-
return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
if (hasLibChanges) {
|
|
223
|
-
logStep('Committing lib build artifacts...')
|
|
224
|
-
await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
|
|
225
|
-
await runCommand('git', ['commit', '-m', 'chore: build lib artifacts'], { capture: true, cwd: rootDir })
|
|
226
|
-
logSuccess('Lib build artifacts committed.')
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return hasLibChanges
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function bumpVersion(releaseType, rootDir = process.cwd()) {
|
|
233
|
-
logStep(`Bumping package version...`)
|
|
234
|
-
|
|
235
|
-
// Lib changes should already be committed by runLibBuild, but check anyway
|
|
236
|
-
const { stdout: statusBefore } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
237
|
-
const hasLibChanges = statusBefore.split('\n').some(line => {
|
|
238
|
-
const trimmed = line.trim()
|
|
239
|
-
return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
if (hasLibChanges) {
|
|
243
|
-
logStep('Stashing lib build artifacts...')
|
|
244
|
-
await runCommand('git', ['stash', 'push', '-u', '-m', 'temp: lib build artifacts', 'lib/'], { capture: true, cwd: rootDir })
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
// npm version will update package.json and create a commit with default message
|
|
249
|
-
const result = await runCommand('npm', ['version', releaseType], { capture: true, cwd: rootDir })
|
|
250
|
-
// Extract version from output (e.g., "v0.2.8" or "0.2.8")
|
|
251
|
-
if (result?.stdout) {
|
|
252
|
-
const versionMatch = result.stdout.match(/v?(\d+\.\d+\.\d+)/)
|
|
253
|
-
if (versionMatch) {
|
|
254
|
-
// Version is shown in the logSuccess message below, no need to show it here
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
} finally {
|
|
258
|
-
// Restore lib changes and ensure they're in the commit
|
|
259
|
-
if (hasLibChanges) {
|
|
260
|
-
logStep('Restoring lib build artifacts...')
|
|
261
|
-
await runCommand('git', ['stash', 'pop'], { capture: true, cwd: rootDir })
|
|
262
|
-
await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
|
|
263
|
-
const { stdout: statusAfter } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
264
|
-
if (statusAfter.includes('lib/')) {
|
|
265
|
-
await runCommand('git', ['commit', '--amend', '--no-edit'], { capture: true, cwd: rootDir })
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const pkg = await readPackage(rootDir)
|
|
271
|
-
const commitMessage = `chore: release ${pkg.version}`
|
|
272
|
-
|
|
273
|
-
// Amend the commit message to use our custom format
|
|
274
|
-
await runCommand('git', ['commit', '--amend', '-m', commitMessage], { capture: true, cwd: rootDir })
|
|
275
|
-
|
|
276
|
-
logSuccess(`Version updated to ${pkg.version}.`)
|
|
277
|
-
return pkg
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function pushChanges(rootDir = process.cwd()) {
|
|
281
|
-
logStep('Pushing commits and tags to origin...')
|
|
282
|
-
try {
|
|
283
|
-
await runCommand('git', ['push', '--follow-tags'], { capture: true, cwd: rootDir })
|
|
284
|
-
logSuccess('Git push completed.')
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (error.stdout) {
|
|
287
|
-
writeStderr(error.stdout)
|
|
288
|
-
}
|
|
289
|
-
if (error.stderr) {
|
|
290
|
-
writeStderr(error.stderr)
|
|
291
|
-
}
|
|
292
|
-
throw error
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function extractDomainFromHomepage(homepage) {
|
|
297
|
-
if (!homepage) return null
|
|
298
|
-
try {
|
|
299
|
-
const url = new URL(homepage)
|
|
300
|
-
return url.hostname
|
|
301
|
-
} catch {
|
|
302
|
-
// If it's not a valid URL, try to extract domain from string
|
|
303
|
-
const match = homepage.match(/(?:https?:\/\/)?([^/]+)/)
|
|
304
|
-
return match ? match[1] : null
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
|
|
309
|
-
if (skipDeploy) {
|
|
310
|
-
logWarning('Skipping GitHub Pages deployment because --skip-deploy flag was provided.')
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Check if dist directory exists (indicates build output for deployment)
|
|
315
|
-
const distPath = path.join(rootDir, 'dist')
|
|
316
|
-
let distExists = false
|
|
317
|
-
try {
|
|
318
|
-
const stats = await fs.promises.stat(distPath)
|
|
319
|
-
distExists = stats.isDirectory()
|
|
320
|
-
} catch {
|
|
321
|
-
distExists = false
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (!distExists) {
|
|
325
|
-
logStep('Skipping GitHub Pages deployment (no dist directory found).')
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
logStep('Deploying to GitHub Pages...')
|
|
330
|
-
|
|
331
|
-
// Write CNAME file to dist if homepage is set
|
|
332
|
-
const cnamePath = path.join(distPath, 'CNAME')
|
|
333
|
-
|
|
334
|
-
if (pkg.homepage) {
|
|
335
|
-
const domain = extractDomainFromHomepage(pkg.homepage)
|
|
336
|
-
if (domain) {
|
|
337
|
-
try {
|
|
338
|
-
await fs.promises.mkdir(distPath, { recursive: true })
|
|
339
|
-
await fs.promises.writeFile(cnamePath, domain)
|
|
340
|
-
} catch (error) {
|
|
341
|
-
logWarning(`Could not write CNAME file: ${error.message}`)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const worktreeDir = path.resolve(rootDir, '.gh-pages')
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
try {
|
|
350
|
-
await runCommand('git', ['worktree', 'remove', worktreeDir, '-f'], { capture: true, cwd: rootDir })
|
|
351
|
-
} catch (_error) {
|
|
352
|
-
// Ignore if worktree doesn't exist
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
try {
|
|
356
|
-
await runCommand('git', ['worktree', 'add', worktreeDir, 'gh-pages'], { capture: true, cwd: rootDir })
|
|
357
|
-
} catch {
|
|
358
|
-
await runCommand('git', ['worktree', 'add', worktreeDir, '-b', 'gh-pages'], { capture: true, cwd: rootDir })
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
await runCommand('git', ['-C', worktreeDir, 'config', 'user.name', 'wyxos'], { capture: true })
|
|
362
|
-
await runCommand('git', ['-C', worktreeDir, 'config', 'user.email', 'github@wyxos.com'], { capture: true })
|
|
363
|
-
|
|
364
|
-
// Clear worktree directory
|
|
365
|
-
for (const entry of fs.readdirSync(worktreeDir)) {
|
|
366
|
-
if (entry === '.git') continue
|
|
367
|
-
const target = path.join(worktreeDir, entry)
|
|
368
|
-
fs.rmSync(target, { recursive: true, force: true })
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Copy dist to worktree
|
|
372
|
-
fs.cpSync(distPath, worktreeDir, { recursive: true })
|
|
373
|
-
|
|
374
|
-
await runCommand('git', ['-C', worktreeDir, 'add', '-A'], { capture: true })
|
|
375
|
-
await runCommand('git', ['-C', worktreeDir, 'commit', '-m', `deploy: demo ${new Date().toISOString()}`, '--allow-empty'], { capture: true })
|
|
376
|
-
await runCommand('git', ['-C', worktreeDir, 'push', '-f', 'origin', 'gh-pages'], { capture: true })
|
|
377
|
-
|
|
378
|
-
logSuccess('GitHub Pages deployment completed.')
|
|
379
|
-
} catch (error) {
|
|
380
|
-
if (error.stdout) {
|
|
381
|
-
writeStderr(error.stdout)
|
|
382
|
-
}
|
|
383
|
-
if (error.stderr) {
|
|
384
|
-
writeStderr(error.stderr)
|
|
385
|
-
}
|
|
386
|
-
throw error
|
|
387
|
-
}
|
|
388
|
-
}
|
|
9
|
+
const {logProcessing: logStep, logSuccess, logWarning} = createChalkLogger(chalk)
|
|
389
10
|
|
|
390
11
|
export async function releaseNode() {
|
|
391
|
-
|
|
392
|
-
|
|
12
|
+
const {releaseType, skipTests, skipLint, skipBuild, skipDeploy} = parseReleaseArgs({
|
|
13
|
+
booleanFlags: ['--skip-tests', '--skip-lint', '--skip-build', '--skip-deploy']
|
|
14
|
+
})
|
|
393
15
|
const rootDir = process.cwd()
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (!branch) {
|
|
406
|
-
throw new Error('Unable to determine current branch.')
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
logStep(`Current branch: ${branch}`)
|
|
410
|
-
const upstreamRef = await getUpstreamRef(rootDir)
|
|
411
|
-
await ensureUpToDateWithUpstream({ branch, upstreamRef, rootDir, logStep, logWarning })
|
|
412
|
-
|
|
413
|
-
await runLint(skipLint, pkg, rootDir)
|
|
414
|
-
await runTests(skipTests, pkg, rootDir)
|
|
415
|
-
await runLibBuild(skipBuild, pkg, rootDir)
|
|
416
|
-
|
|
417
|
-
const updatedPkg = await bumpVersion(releaseType, rootDir)
|
|
418
|
-
await runBuild(skipBuild, updatedPkg, rootDir)
|
|
419
|
-
await pushChanges(rootDir)
|
|
420
|
-
await deployGHPages(skipDeploy, updatedPkg, rootDir)
|
|
421
|
-
|
|
422
|
-
logStep('Publishing will be handled by GitHub Actions via trusted publishing.')
|
|
423
|
-
|
|
424
|
-
logSuccess(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
|
|
425
|
-
} catch (error) {
|
|
426
|
-
writeStderrLine('\nRelease failed:')
|
|
427
|
-
writeStderrLine(error.message)
|
|
428
|
-
throw error
|
|
429
|
-
}
|
|
16
|
+
await releaseNodePackage({
|
|
17
|
+
releaseType,
|
|
18
|
+
skipTests,
|
|
19
|
+
skipLint,
|
|
20
|
+
skipBuild,
|
|
21
|
+
skipDeploy,
|
|
22
|
+
rootDir,
|
|
23
|
+
logStep,
|
|
24
|
+
logSuccess,
|
|
25
|
+
logWarning
|
|
26
|
+
})
|
|
430
27
|
}
|
|
431
|
-
|