@wyxos/zephyr 0.6.0 → 0.7.5
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 +27 -3
- package/package.json +1 -1
- package/src/application/configuration/preset-selection.mjs +0 -6
- package/src/application/configuration/select-deployment-target.mjs +110 -71
- package/src/application/deploy/build-remote-deployment-plan.mjs +13 -1
- package/src/application/deploy/plan-laravel-deployment-tasks.mjs +23 -2
- package/src/application/deploy/prepare-local-deployment.mjs +18 -9
- package/src/application/deploy/run-deployment.mjs +5 -1
- package/src/application/deploy/run-local-deployment-checks.mjs +38 -2
- package/src/application/release/release-node-package.mjs +68 -12
- package/src/application/release/release-packagist-package.mjs +56 -12
- package/src/cli/options.mjs +28 -1
- package/src/config/preset-options.mjs +89 -0
- package/src/config/project.mjs +45 -10
- package/src/deploy/local-repo.mjs +29 -16
- package/src/deploy/preflight.mjs +57 -3
- package/src/main.mjs +54 -4
- package/src/release/commit-message.mjs +8 -239
- package/src/release-node.mjs +8 -1
- package/src/release-packagist.mjs +10 -3
- package/src/runtime/app-context.mjs +10 -1
- package/src/targets/index.mjs +3 -2
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ zephyr minor --skip-checks
|
|
|
49
49
|
# Deploy a configured app non-interactively
|
|
50
50
|
zephyr --non-interactive --preset wyxos-release --maintenance off
|
|
51
51
|
|
|
52
|
+
# Deploy a configured app non-interactively and auto-commit dirty changes
|
|
53
|
+
zephyr --non-interactive --preset wyxos-release --auto-commit
|
|
54
|
+
|
|
55
|
+
# Deploy without mutating the local package version
|
|
56
|
+
zephyr --non-interactive --preset wyxos-release --skip-versioning
|
|
57
|
+
|
|
52
58
|
# Resume a pending non-interactive deployment
|
|
53
59
|
zephyr --non-interactive --preset wyxos-release --resume-pending --maintenance off
|
|
54
60
|
|
|
@@ -63,6 +69,10 @@ zephyr --type node minor
|
|
|
63
69
|
|
|
64
70
|
# Release a Packagist package
|
|
65
71
|
zephyr --type packagist patch
|
|
72
|
+
|
|
73
|
+
# Release the current package/composer version without bumping version files
|
|
74
|
+
zephyr --type node --skip-versioning
|
|
75
|
+
zephyr --type packagist --skip-versioning
|
|
66
76
|
```
|
|
67
77
|
|
|
68
78
|
When `--type node` or `--type vue` is used without a bump argument, Zephyr defaults to `patch`.
|
|
@@ -77,7 +87,7 @@ Non-interactive mode is strict and is intended for already-configured projects:
|
|
|
77
87
|
|
|
78
88
|
- `--non-interactive` fails instead of prompting
|
|
79
89
|
- app deployments require `--preset <name>`
|
|
80
|
-
- Laravel app deployments require `--maintenance on|off
|
|
90
|
+
- Laravel app deployments require either a saved preset maintenance preference, `--maintenance on|off`, or a resumable snapshot that already contains the choice
|
|
81
91
|
- pending deployment snapshots require either `--resume-pending` or `--discard-pending`
|
|
82
92
|
- stale remote locks are never auto-removed in non-interactive mode
|
|
83
93
|
- `--json` is only supported together with `--non-interactive`
|
|
@@ -94,7 +104,11 @@ If Zephyr would normally prompt to:
|
|
|
94
104
|
|
|
95
105
|
then non-interactive mode stops immediately with a clear error instead.
|
|
96
106
|
|
|
97
|
-
For Laravel app deployments, `--maintenance on|off` overrides the maintenance prompt when you want an explicit choice
|
|
107
|
+
For Laravel app deployments, `--maintenance on|off` overrides both the saved preset preference and the maintenance prompt when you want an explicit choice for the current run.
|
|
108
|
+
|
|
109
|
+
`--auto-commit` is available for app deployments and tells Zephyr to let local Codex inspect the repo and generate the dirty-tree commit message instead of prompting for one.
|
|
110
|
+
|
|
111
|
+
`--skip-versioning` keeps Zephyr from mutating `package.json` or `composer.json`. On app deploys it skips the local npm version bump step. On package release workflows it releases the version already present in the manifest and creates the release tag from the current `HEAD`.
|
|
98
112
|
|
|
99
113
|
## AI Agents and Automation
|
|
100
114
|
|
|
@@ -214,7 +228,15 @@ Deployment targets are stored per-project at `.zephyr/config.json`:
|
|
|
214
228
|
{
|
|
215
229
|
"name": "prod-main",
|
|
216
230
|
"appId": "app_def456",
|
|
217
|
-
"branch": "main"
|
|
231
|
+
"branch": "main",
|
|
232
|
+
"options": {
|
|
233
|
+
"maintenanceMode": true,
|
|
234
|
+
"skipGitHooks": false,
|
|
235
|
+
"skipTests": false,
|
|
236
|
+
"skipLint": false,
|
|
237
|
+
"skipVersioning": false,
|
|
238
|
+
"autoCommit": true
|
|
239
|
+
}
|
|
218
240
|
}
|
|
219
241
|
],
|
|
220
242
|
"apps": [
|
|
@@ -231,6 +253,8 @@ Deployment targets are stored per-project at `.zephyr/config.json`:
|
|
|
231
253
|
}
|
|
232
254
|
```
|
|
233
255
|
|
|
256
|
+
Preset `options` capture repeatable deploy behavior so Zephyr can reuse the same maintenance, dirty-tree, and deploy-check preferences on later runs.
|
|
257
|
+
|
|
234
258
|
### Project Directory Structure
|
|
235
259
|
|
|
236
260
|
Zephyr creates a `.zephyr/` directory in your project with:
|
package/package.json
CHANGED
|
@@ -23,12 +23,6 @@ export async function selectPreset({
|
|
|
23
23
|
const branch = preset.branch || app.branch || 'unknown'
|
|
24
24
|
displayName = `${preset.name} (${serverName} → ${app.projectPath} [${branch}])`
|
|
25
25
|
}
|
|
26
|
-
} else if (preset.key) {
|
|
27
|
-
const keyParts = preset.key.split(':')
|
|
28
|
-
const serverName = keyParts[0]
|
|
29
|
-
const projectPath = keyParts[1]
|
|
30
|
-
const branch = preset.branch || (keyParts.length === 3 ? keyParts[2] : 'unknown')
|
|
31
|
-
displayName = `${preset.name} (${serverName} → ${projectPath} [${branch}])`
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
return {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {writeStdoutLine} from '../../utils/output.mjs'
|
|
2
2
|
import {loadServers} from '../../config/servers.mjs'
|
|
3
3
|
import {loadProjectConfig, removePreset, saveProjectConfig} from '../../config/project.mjs'
|
|
4
|
+
import {
|
|
5
|
+
buildPresetOptionsFromExecutionMode,
|
|
6
|
+
normalizePresetOptions,
|
|
7
|
+
presetOptionsEqual
|
|
8
|
+
} from '../../config/preset-options.mjs'
|
|
4
9
|
import {ZephyrError} from '../../runtime/errors.mjs'
|
|
5
10
|
|
|
6
11
|
function findPresetByName(projectConfig, presetName) {
|
|
@@ -8,6 +13,56 @@ function findPresetByName(projectConfig, presetName) {
|
|
|
8
13
|
return presets.find((entry) => entry?.name === presetName) ?? null
|
|
9
14
|
}
|
|
10
15
|
|
|
16
|
+
function createPresetState(rootDir, projectConfig, preset, {
|
|
17
|
+
logSuccess
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (!preset) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
name: preset.name,
|
|
25
|
+
get options() {
|
|
26
|
+
return normalizePresetOptions(preset.options)
|
|
27
|
+
},
|
|
28
|
+
async saveOptions(nextOptions, {
|
|
29
|
+
message = null
|
|
30
|
+
} = {}) {
|
|
31
|
+
const normalizedOptions = normalizePresetOptions(nextOptions)
|
|
32
|
+
|
|
33
|
+
if (presetOptionsEqual(preset.options, normalizedOptions)) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
preset.options = normalizedOptions
|
|
38
|
+
await saveProjectConfig(rootDir, projectConfig)
|
|
39
|
+
|
|
40
|
+
if (message) {
|
|
41
|
+
logSuccess?.(message)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true
|
|
45
|
+
},
|
|
46
|
+
async applyExecutionMode(executionMode = {}) {
|
|
47
|
+
const nextOptions = buildPresetOptionsFromExecutionMode(executionMode, preset.options)
|
|
48
|
+
return await this.saveOptions(nextOptions)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function promptPresetAutoCommit(runPrompt, enabledByDefault = false) {
|
|
54
|
+
const {autoCommitPreference} = await runPrompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'autoCommitPreference',
|
|
58
|
+
message: 'Enable auto-commit for dirty changes on this preset? Leave blank for manual commit prompts.',
|
|
59
|
+
default: enabledByDefault ? 'enabled' : ''
|
|
60
|
+
}
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
return typeof autoCommitPreference === 'string' && autoCommitPreference.trim().length > 0
|
|
64
|
+
}
|
|
65
|
+
|
|
11
66
|
function resolvePresetNonInteractive(projectConfig, servers, preset, presetName) {
|
|
12
67
|
if (!preset) {
|
|
13
68
|
throw new ZephyrError(
|
|
@@ -18,8 +73,8 @@ function resolvePresetNonInteractive(projectConfig, servers, preset, presetName)
|
|
|
18
73
|
|
|
19
74
|
if (!preset.appId) {
|
|
20
75
|
throw new ZephyrError(
|
|
21
|
-
`Zephyr cannot run non-interactively because preset "${preset.name || presetName}"
|
|
22
|
-
{code: '
|
|
76
|
+
`Zephyr cannot run non-interactively because preset "${preset.name || presetName}" is invalid.`,
|
|
77
|
+
{code: 'ZEPHYR_PRESET_INVALID'}
|
|
23
78
|
)
|
|
24
79
|
}
|
|
25
80
|
|
|
@@ -54,6 +109,7 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
54
109
|
logSuccess,
|
|
55
110
|
logWarning,
|
|
56
111
|
emitEvent,
|
|
112
|
+
promptPresetOptions = true,
|
|
57
113
|
executionMode = {}
|
|
58
114
|
} = {}) {
|
|
59
115
|
const nonInteractive = executionMode?.interactive === false
|
|
@@ -72,6 +128,7 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
72
128
|
|
|
73
129
|
let server = null
|
|
74
130
|
let appConfig = null
|
|
131
|
+
let activePreset = null
|
|
75
132
|
let isCreatingNewPreset = false
|
|
76
133
|
|
|
77
134
|
const preset = nonInteractive
|
|
@@ -95,11 +152,11 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
95
152
|
}
|
|
96
153
|
|
|
97
154
|
if (nonInteractive) {
|
|
155
|
+
activePreset = preset
|
|
98
156
|
const resolved = resolvePresetNonInteractive(projectConfig, servers, preset, executionMode.presetName)
|
|
99
157
|
server = resolved.server
|
|
100
|
-
appConfig = resolved.appConfig
|
|
101
158
|
appConfig = {
|
|
102
|
-
...appConfig,
|
|
159
|
+
...resolved.appConfig,
|
|
103
160
|
branch: resolved.branch
|
|
104
161
|
}
|
|
105
162
|
} else if (preset === 'create') {
|
|
@@ -107,64 +164,29 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
107
164
|
server = await configurationService.selectServer(servers)
|
|
108
165
|
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
109
166
|
} else if (preset) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!appConfig) {
|
|
114
|
-
logWarning?.('Preset references an application that no longer exists. Creating a new configuration instead.')
|
|
115
|
-
await removeInvalidPreset()
|
|
116
|
-
server = await configurationService.selectServer(servers)
|
|
117
|
-
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
118
|
-
} else {
|
|
119
|
-
server = servers.find((entry) => entry.id === appConfig.serverId || entry.serverName === appConfig.serverName)
|
|
120
|
-
|
|
121
|
-
if (!server) {
|
|
122
|
-
logWarning?.('Preset references a server that no longer exists. Creating a new configuration instead.')
|
|
123
|
-
await removeInvalidPreset()
|
|
124
|
-
server = await configurationService.selectServer(servers)
|
|
125
|
-
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
126
|
-
} else if (preset.branch && appConfig.branch !== preset.branch) {
|
|
127
|
-
appConfig.branch = preset.branch
|
|
128
|
-
await saveProjectConfig(rootDir, projectConfig)
|
|
129
|
-
logSuccess?.(`Updated branch to ${preset.branch} from preset.`)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
} else if (preset.key) {
|
|
133
|
-
const keyParts = preset.key.split(':')
|
|
134
|
-
const serverName = keyParts[0]
|
|
135
|
-
const projectPath = keyParts[1]
|
|
136
|
-
const presetBranch = preset.branch || (keyParts.length === 3 ? keyParts[2] : null)
|
|
167
|
+
activePreset = preset
|
|
168
|
+
appConfig = projectConfig.apps?.find((app) => app.id === preset.appId)
|
|
137
169
|
|
|
138
|
-
|
|
170
|
+
if (!appConfig) {
|
|
171
|
+
logWarning?.('Preset references an application that no longer exists. Creating a new configuration instead.')
|
|
172
|
+
await removeInvalidPreset()
|
|
173
|
+
activePreset = null
|
|
174
|
+
server = await configurationService.selectServer(servers)
|
|
175
|
+
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
176
|
+
} else {
|
|
177
|
+
server = servers.find((entry) => entry.id === appConfig.serverId || entry.serverName === appConfig.serverName)
|
|
139
178
|
|
|
140
179
|
if (!server) {
|
|
141
|
-
logWarning?.(
|
|
180
|
+
logWarning?.('Preset references a server that no longer exists. Creating a new configuration instead.')
|
|
142
181
|
await removeInvalidPreset()
|
|
182
|
+
activePreset = null
|
|
143
183
|
server = await configurationService.selectServer(servers)
|
|
144
184
|
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
145
|
-
} else {
|
|
146
|
-
appConfig =
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if (!appConfig) {
|
|
151
|
-
logWarning?.('Preset references an application that no longer exists. Creating a new configuration instead.')
|
|
152
|
-
await removeInvalidPreset()
|
|
153
|
-
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
154
|
-
} else {
|
|
155
|
-
preset.appId = appConfig.id
|
|
156
|
-
if (presetBranch && appConfig.branch !== presetBranch) {
|
|
157
|
-
appConfig.branch = presetBranch
|
|
158
|
-
}
|
|
159
|
-
preset.branch = appConfig.branch
|
|
160
|
-
await saveProjectConfig(rootDir, projectConfig)
|
|
161
|
-
}
|
|
185
|
+
} else if (preset.branch && appConfig.branch !== preset.branch) {
|
|
186
|
+
appConfig.branch = preset.branch
|
|
187
|
+
await saveProjectConfig(rootDir, projectConfig)
|
|
188
|
+
logSuccess?.(`Updated branch to ${preset.branch} from preset.`)
|
|
162
189
|
}
|
|
163
|
-
} else {
|
|
164
|
-
logWarning?.('Preset format is invalid. Creating a new configuration instead.')
|
|
165
|
-
await removeInvalidPreset()
|
|
166
|
-
server = await configurationService.selectServer(servers)
|
|
167
|
-
appConfig = await configurationService.selectApp(projectConfig, server, rootDir)
|
|
168
190
|
}
|
|
169
191
|
} else {
|
|
170
192
|
server = await configurationService.selectServer(servers)
|
|
@@ -173,7 +195,7 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
173
195
|
|
|
174
196
|
if (nonInteractive && (!appConfig?.sshUser || !appConfig?.sshKey)) {
|
|
175
197
|
throw new ZephyrError(
|
|
176
|
-
`Zephyr cannot run non-interactively because preset "${
|
|
198
|
+
`Zephyr cannot run non-interactively because preset "${activePreset?.name || executionMode.presetName}" is missing SSH details.`,
|
|
177
199
|
{code: 'ZEPHYR_SSH_DETAILS_REQUIRED'}
|
|
178
200
|
)
|
|
179
201
|
}
|
|
@@ -207,7 +229,7 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
207
229
|
writeStdoutLine(JSON.stringify(deploymentConfig, null, 2))
|
|
208
230
|
}
|
|
209
231
|
|
|
210
|
-
if (!nonInteractive && (isCreatingNewPreset || !
|
|
232
|
+
if (!nonInteractive && (isCreatingNewPreset || !activePreset)) {
|
|
211
233
|
const {presetName} = await runPrompt([
|
|
212
234
|
{
|
|
213
235
|
type: 'input',
|
|
@@ -220,31 +242,48 @@ export async function selectDeploymentTarget(rootDir, {
|
|
|
220
242
|
const trimmedName = presetName?.trim()
|
|
221
243
|
|
|
222
244
|
if (trimmedName && trimmedName.length > 0) {
|
|
223
|
-
const presets = projectConfig.presets ?? []
|
|
224
245
|
const appId = appConfig.id
|
|
225
246
|
|
|
226
247
|
if (!appId) {
|
|
227
248
|
logWarning?.('Cannot save preset: app configuration missing ID.')
|
|
228
249
|
} else {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
const existingPreset = findPresetByName(projectConfig, trimmedName)
|
|
251
|
+
const nextPreset = existingPreset ?? {
|
|
252
|
+
name: trimmedName,
|
|
253
|
+
appId,
|
|
254
|
+
branch: deploymentConfig.branch,
|
|
255
|
+
options: normalizePresetOptions()
|
|
256
|
+
}
|
|
257
|
+
const nextOptions = buildPresetOptionsFromExecutionMode(executionMode, nextPreset.options)
|
|
258
|
+
|
|
259
|
+
if (promptPresetOptions) {
|
|
260
|
+
nextOptions.autoCommit = await promptPresetAutoCommit(
|
|
261
|
+
runPrompt,
|
|
262
|
+
executionMode.autoCommit === true || existingPreset?.options?.autoCommit === true
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
nextPreset.name = trimmedName
|
|
267
|
+
nextPreset.appId = appId
|
|
268
|
+
nextPreset.branch = deploymentConfig.branch
|
|
269
|
+
nextPreset.options = normalizePresetOptions(nextOptions)
|
|
270
|
+
|
|
271
|
+
if (!existingPreset) {
|
|
272
|
+
projectConfig.presets = [...(projectConfig.presets ?? []), nextPreset]
|
|
240
273
|
}
|
|
241
274
|
|
|
242
|
-
projectConfig.presets = presets
|
|
243
275
|
await saveProjectConfig(rootDir, projectConfig)
|
|
244
276
|
logSuccess?.(`Saved preset "${trimmedName}" to .zephyr/config.json`)
|
|
277
|
+
activePreset = nextPreset
|
|
245
278
|
}
|
|
246
279
|
}
|
|
247
280
|
}
|
|
248
281
|
|
|
249
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
deploymentConfig,
|
|
284
|
+
projectConfig,
|
|
285
|
+
presetState: createPresetState(rootDir, projectConfig, activePreset, {
|
|
286
|
+
logSuccess
|
|
287
|
+
})
|
|
288
|
+
}
|
|
250
289
|
}
|
|
@@ -235,6 +235,7 @@ async function resolveMaintenanceMode({
|
|
|
235
235
|
snapshot,
|
|
236
236
|
remoteIsLaravel,
|
|
237
237
|
runPrompt,
|
|
238
|
+
persistPresetOptions,
|
|
238
239
|
executionMode = {}
|
|
239
240
|
} = {}) {
|
|
240
241
|
if (!remoteIsLaravel) {
|
|
@@ -269,7 +270,14 @@ async function resolveMaintenanceMode({
|
|
|
269
270
|
}
|
|
270
271
|
])
|
|
271
272
|
|
|
272
|
-
|
|
273
|
+
const maintenanceModeEnabled = Boolean(answers?.enableMaintenanceMode)
|
|
274
|
+
await persistPresetOptions?.({
|
|
275
|
+
maintenanceMode: maintenanceModeEnabled
|
|
276
|
+
}, {
|
|
277
|
+
message: 'Saved maintenance mode preference to the selected preset.'
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
return maintenanceModeEnabled
|
|
273
281
|
}
|
|
274
282
|
|
|
275
283
|
async function resolveMaintenanceModePlan({
|
|
@@ -333,6 +341,7 @@ async function resolveMaintenanceModePlan({
|
|
|
333
341
|
export async function resolveRemoteDeploymentState({
|
|
334
342
|
snapshot,
|
|
335
343
|
executionMode = {},
|
|
344
|
+
persistPresetOptions,
|
|
336
345
|
ssh,
|
|
337
346
|
remoteCwd,
|
|
338
347
|
runPrompt,
|
|
@@ -351,6 +360,7 @@ export async function resolveRemoteDeploymentState({
|
|
|
351
360
|
snapshot,
|
|
352
361
|
remoteIsLaravel,
|
|
353
362
|
runPrompt,
|
|
363
|
+
persistPresetOptions,
|
|
354
364
|
executionMode
|
|
355
365
|
})
|
|
356
366
|
|
|
@@ -365,6 +375,7 @@ export async function buildRemoteDeploymentPlan({
|
|
|
365
375
|
snapshot = null,
|
|
366
376
|
requiredPhpVersion = null,
|
|
367
377
|
executionMode = {},
|
|
378
|
+
persistPresetOptions,
|
|
368
379
|
remoteIsLaravel = null,
|
|
369
380
|
maintenanceModeEnabled = null,
|
|
370
381
|
ssh,
|
|
@@ -384,6 +395,7 @@ export async function buildRemoteDeploymentPlan({
|
|
|
384
395
|
: await resolveRemoteDeploymentState({
|
|
385
396
|
snapshot,
|
|
386
397
|
executionMode,
|
|
398
|
+
persistPresetOptions,
|
|
387
399
|
ssh,
|
|
388
400
|
remoteCwd,
|
|
389
401
|
runPrompt,
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
const FRONTEND_BUILD_EXTENSIONS = [
|
|
2
|
+
'.vue',
|
|
3
|
+
'.css',
|
|
4
|
+
'.scss',
|
|
5
|
+
'.js',
|
|
6
|
+
'.jsx',
|
|
7
|
+
'.mjs',
|
|
8
|
+
'.cjs',
|
|
9
|
+
'.ts',
|
|
10
|
+
'.tsx',
|
|
11
|
+
'.less',
|
|
12
|
+
'.svg',
|
|
13
|
+
'.png',
|
|
14
|
+
'.jpg',
|
|
15
|
+
'.jpeg',
|
|
16
|
+
'.gif',
|
|
17
|
+
'.webp',
|
|
18
|
+
'.avif',
|
|
19
|
+
'.ico'
|
|
20
|
+
]
|
|
21
|
+
|
|
1
22
|
export function planLaravelDeploymentTasks({
|
|
2
23
|
branch,
|
|
3
24
|
isLaravel,
|
|
@@ -39,7 +60,7 @@ export function planLaravelDeploymentTasks({
|
|
|
39
60
|
const hasFrontendChanges =
|
|
40
61
|
isLaravel &&
|
|
41
62
|
safeChangedFiles.some((file) =>
|
|
42
|
-
|
|
63
|
+
FRONTEND_BUILD_EXTENSIONS.some((ext) => file.endsWith(ext))
|
|
43
64
|
)
|
|
44
65
|
|
|
45
66
|
const shouldRunBuild = isLaravel && (hasFrontendChanges || shouldRunNpmInstall)
|
|
@@ -116,4 +137,4 @@ export function planLaravelDeploymentTasks({
|
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
return steps
|
|
119
|
-
}
|
|
140
|
+
}
|
|
@@ -12,6 +12,8 @@ export async function prepareLocalDeployment(config, {
|
|
|
12
12
|
skipGitHooks = false,
|
|
13
13
|
skipTests = false,
|
|
14
14
|
skipLint = false,
|
|
15
|
+
skipVersioning = false,
|
|
16
|
+
autoCommit = false,
|
|
15
17
|
runPrompt,
|
|
16
18
|
runCommand,
|
|
17
19
|
runCommandCapture,
|
|
@@ -26,7 +28,8 @@ export async function prepareLocalDeployment(config, {
|
|
|
26
28
|
logProcessing,
|
|
27
29
|
logSuccess,
|
|
28
30
|
logWarning,
|
|
29
|
-
skipGitHooks
|
|
31
|
+
skipGitHooks,
|
|
32
|
+
autoCommit
|
|
30
33
|
})
|
|
31
34
|
|
|
32
35
|
const context = await resolveLocalDeploymentContext(rootDir)
|
|
@@ -53,17 +56,22 @@ export async function prepareLocalDeployment(config, {
|
|
|
53
56
|
logSuccess,
|
|
54
57
|
logWarning,
|
|
55
58
|
lintCommand: checkSupport.lintCommand,
|
|
59
|
+
buildCommand: checkSupport.buildCommand,
|
|
56
60
|
testCommand: checkSupport.testCommand
|
|
57
61
|
})
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
if (skipVersioning) {
|
|
64
|
+
logWarning?.('Skipping deployment version update because --skip-versioning flag was provided.')
|
|
65
|
+
} else {
|
|
66
|
+
await bumpLocalPackageVersion(rootDir, {
|
|
67
|
+
versionArg,
|
|
68
|
+
skipGitHooks,
|
|
69
|
+
runCommand,
|
|
70
|
+
logProcessing,
|
|
71
|
+
logSuccess,
|
|
72
|
+
logWarning
|
|
73
|
+
})
|
|
74
|
+
}
|
|
67
75
|
|
|
68
76
|
await ensureCommittedChangesPushed(config.branch, rootDir, {
|
|
69
77
|
runCommand,
|
|
@@ -90,6 +98,7 @@ export async function prepareLocalDeployment(config, {
|
|
|
90
98
|
logSuccess,
|
|
91
99
|
logWarning,
|
|
92
100
|
lintCommand: checkSupport.lintCommand,
|
|
101
|
+
buildCommand: checkSupport.buildCommand,
|
|
93
102
|
testCommand: checkSupport.testCommand
|
|
94
103
|
})
|
|
95
104
|
|
|
@@ -248,7 +248,8 @@ export async function runDeployment(config, options = {}) {
|
|
|
248
248
|
snapshot = null,
|
|
249
249
|
rootDir = process.cwd(),
|
|
250
250
|
versionArg = null,
|
|
251
|
-
context
|
|
251
|
+
context,
|
|
252
|
+
presetState = null
|
|
252
253
|
} = options
|
|
253
254
|
|
|
254
255
|
const {
|
|
@@ -368,6 +369,8 @@ export async function runDeployment(config, options = {}) {
|
|
|
368
369
|
skipGitHooks: executionMode?.skipGitHooks === true,
|
|
369
370
|
skipTests: executionMode?.skipTests === true,
|
|
370
371
|
skipLint: executionMode?.skipLint === true,
|
|
372
|
+
skipVersioning: executionMode?.skipVersioning === true,
|
|
373
|
+
autoCommit: executionMode?.autoCommit === true,
|
|
371
374
|
runPrompt,
|
|
372
375
|
runCommand,
|
|
373
376
|
runCommandCapture: context.runCommandCapture,
|
|
@@ -412,6 +415,7 @@ export async function runDeployment(config, options = {}) {
|
|
|
412
415
|
rootDir,
|
|
413
416
|
requiredPhpVersion,
|
|
414
417
|
executionMode,
|
|
418
|
+
persistPresetOptions: presetState?.saveOptions,
|
|
415
419
|
remoteIsLaravel: remoteState?.remoteIsLaravel,
|
|
416
420
|
maintenanceModeEnabled: remoteState?.maintenanceModeEnabled,
|
|
417
421
|
ssh,
|
|
@@ -66,16 +66,42 @@ export async function resolveLocalDeploymentCheckSupport({
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
const buildCommand = isLaravel && !skipTests
|
|
70
|
+
? await preflight.resolveSupportedBuildCommand(rootDir, {commandExists})
|
|
71
|
+
: null
|
|
72
|
+
|
|
69
73
|
const testCommand = isLaravel && !skipTests
|
|
70
74
|
? await resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture})
|
|
71
75
|
: null
|
|
72
76
|
|
|
73
77
|
return {
|
|
74
78
|
lintCommand,
|
|
79
|
+
buildCommand,
|
|
75
80
|
testCommand
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
async function runLocalLaravelBuild(rootDir, {runCommand, logProcessing, logSuccess, buildCommand} = {}) {
|
|
85
|
+
try {
|
|
86
|
+
await preflight.runBuild(rootDir, {
|
|
87
|
+
runCommand,
|
|
88
|
+
logProcessing,
|
|
89
|
+
logSuccess,
|
|
90
|
+
commandExists,
|
|
91
|
+
buildCommand
|
|
92
|
+
})
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.code === 'ENOENT') {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'Failed to run local frontend build: npm executable not found.\n' +
|
|
97
|
+
'Make sure npm is installed and available in your PATH.'
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`Local frontend build failed. Fix build failures before deploying.\n${error.message}`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
async function runLocalLaravelTests(rootDir, {runCommand, logProcessing, logSuccess, testCommand} = {}) {
|
|
80
106
|
logProcessing?.('Running Laravel tests locally...')
|
|
81
107
|
|
|
@@ -108,10 +134,11 @@ export async function runLocalDeploymentChecks({
|
|
|
108
134
|
logSuccess,
|
|
109
135
|
logWarning,
|
|
110
136
|
lintCommand = undefined,
|
|
137
|
+
buildCommand = undefined,
|
|
111
138
|
testCommand = undefined
|
|
112
139
|
} = {}) {
|
|
113
|
-
const support = lintCommand !== undefined || testCommand !== undefined
|
|
114
|
-
? {lintCommand, testCommand}
|
|
140
|
+
const support = lintCommand !== undefined || buildCommand !== undefined || testCommand !== undefined
|
|
141
|
+
? {lintCommand, buildCommand, testCommand}
|
|
115
142
|
: await resolveLocalDeploymentCheckSupport({
|
|
116
143
|
rootDir,
|
|
117
144
|
isLaravel,
|
|
@@ -176,6 +203,15 @@ export async function runLocalDeploymentChecks({
|
|
|
176
203
|
if (isLaravel && skipTests) {
|
|
177
204
|
logWarning?.('Skipping tests because --skip-tests flag was provided.')
|
|
178
205
|
} else if (isLaravel) {
|
|
206
|
+
if (support.buildCommand) {
|
|
207
|
+
await runLocalLaravelBuild(rootDir, {
|
|
208
|
+
runCommand,
|
|
209
|
+
logProcessing,
|
|
210
|
+
logSuccess,
|
|
211
|
+
buildCommand: support.buildCommand
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
179
215
|
await runLocalLaravelTests(rootDir, {
|
|
180
216
|
runCommand,
|
|
181
217
|
logProcessing,
|