@wyxos/zephyr 0.4.8 → 0.4.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/zephyr",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
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",
package/src/main.mjs CHANGED
@@ -17,6 +17,7 @@ import {selectDeploymentTarget} from './application/configuration/select-deploym
17
17
  import {resolvePendingSnapshot} from './application/deploy/resolve-pending-snapshot.mjs'
18
18
  import {runDeployment} from './application/deploy/run-deployment.mjs'
19
19
  import {SKIP_GIT_HOOKS_WARNING} from './utils/git-hooks.mjs'
20
+ import {notifyWorkflowResult} from './utils/notifications.mjs'
20
21
 
21
22
  const RELEASE_SCRIPT_NAME = 'release'
22
23
  const RELEASE_SCRIPT_COMMAND = 'npx @wyxos/zephyr@latest'
@@ -99,6 +100,7 @@ async function runRemoteTasks(config, options = {}) {
99
100
 
100
101
  async function main(optionsOrWorkflowType = null, versionArg = null) {
101
102
  const options = normalizeMainOptions(optionsOrWorkflowType, versionArg)
103
+ const rootDir = process.cwd()
102
104
 
103
105
  const executionMode = {
104
106
  interactive: !options.nonInteractive,
@@ -171,6 +173,14 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
171
173
  workflow: currentExecutionMode.workflow
172
174
  }
173
175
  })
176
+ if (!currentExecutionMode.json) {
177
+ await notifyWorkflowResult({
178
+ status: 'success',
179
+ workflow: currentExecutionMode.workflow,
180
+ presetName: currentExecutionMode.presetName,
181
+ rootDir
182
+ })
183
+ }
174
184
  return
175
185
  }
176
186
 
@@ -189,11 +199,17 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
189
199
  workflow: currentExecutionMode.workflow
190
200
  }
191
201
  })
202
+ if (!currentExecutionMode.json) {
203
+ await notifyWorkflowResult({
204
+ status: 'success',
205
+ workflow: currentExecutionMode.workflow,
206
+ presetName: currentExecutionMode.presetName,
207
+ rootDir
208
+ })
209
+ }
192
210
  return
193
211
  }
194
212
 
195
- const rootDir = process.cwd()
196
-
197
213
  await bootstrap.ensureGitignoreEntry(rootDir, {
198
214
  projectConfigDir: PROJECT_CONFIG_DIR,
199
215
  runCommand,
@@ -256,6 +272,14 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
256
272
  workflow: currentExecutionMode.workflow
257
273
  }
258
274
  })
275
+ if (!currentExecutionMode.json) {
276
+ await notifyWorkflowResult({
277
+ status: 'success',
278
+ workflow: currentExecutionMode.workflow,
279
+ presetName: currentExecutionMode.presetName,
280
+ rootDir
281
+ })
282
+ }
259
283
  } catch (error) {
260
284
  const errorCode = getErrorCode(error)
261
285
  emitEvent?.('run_failed', {
@@ -272,6 +296,13 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
272
296
  if (errorCode === 'ZEPHYR_FAILURE' && error.stack) {
273
297
  writeStderrLine(error.stack)
274
298
  }
299
+ await notifyWorkflowResult({
300
+ status: 'failure',
301
+ workflow: currentExecutionMode.workflow,
302
+ presetName: currentExecutionMode.presetName,
303
+ rootDir,
304
+ message: error.message
305
+ })
275
306
  }
276
307
 
277
308
  throw error
@@ -0,0 +1,101 @@
1
+ import path from 'node:path'
2
+ import process from 'node:process'
3
+
4
+ import {commandExists, runCommand as runCommandBase} from './command.mjs'
5
+
6
+ const MAX_NOTIFICATION_MESSAGE_LENGTH = 180
7
+
8
+ function escapeAppleScriptString(value = '') {
9
+ return String(value ?? '')
10
+ .replace(/\\/g, '\\\\')
11
+ .replace(/"/g, '\\"')
12
+ }
13
+
14
+ function humanizeWorkflow(workflow = 'deploy') {
15
+ if (workflow === 'release-node') {
16
+ return 'Node Release'
17
+ }
18
+
19
+ if (workflow === 'release-packagist') {
20
+ return 'Packagist Release'
21
+ }
22
+
23
+ return 'Deploy'
24
+ }
25
+
26
+ function truncateNotificationMessage(message = '') {
27
+ const normalized = String(message ?? '').replace(/\s+/g, ' ').trim()
28
+
29
+ if (normalized.length <= MAX_NOTIFICATION_MESSAGE_LENGTH) {
30
+ return normalized
31
+ }
32
+
33
+ return `${normalized.slice(0, MAX_NOTIFICATION_MESSAGE_LENGTH - 1).trimEnd()}…`
34
+ }
35
+
36
+ function buildNotificationPayload({
37
+ status = 'success',
38
+ workflow = 'deploy',
39
+ presetName = null,
40
+ rootDir = process.cwd(),
41
+ message = ''
42
+ } = {}) {
43
+ const isSuccess = status === 'success'
44
+ const repoName = path.basename(rootDir || process.cwd()) || 'project'
45
+ const title = isSuccess ? '🟢 Zephyr Passed' : '🔴 Zephyr Failed'
46
+ const subtitleParts = [humanizeWorkflow(workflow), repoName]
47
+
48
+ if (presetName) {
49
+ subtitleParts.push(presetName)
50
+ }
51
+
52
+ return {
53
+ title,
54
+ subtitle: subtitleParts.join(' • '),
55
+ message: isSuccess
56
+ ? 'Workflow completed successfully.'
57
+ : truncateNotificationMessage(message || 'Workflow failed.'),
58
+ soundName: isSuccess ? 'Glass' : 'Basso'
59
+ }
60
+ }
61
+
62
+ export async function notifyWorkflowResult({
63
+ status = 'success',
64
+ workflow = 'deploy',
65
+ presetName = null,
66
+ rootDir = process.cwd(),
67
+ message = ''
68
+ } = {}, {
69
+ processRef = process,
70
+ commandExistsImpl = commandExists,
71
+ runCommand = runCommandBase
72
+ } = {}) {
73
+ if (processRef.platform !== 'darwin' || !commandExistsImpl('osascript')) {
74
+ return false
75
+ }
76
+
77
+ const payload = buildNotificationPayload({
78
+ status,
79
+ workflow,
80
+ presetName,
81
+ rootDir,
82
+ message
83
+ })
84
+
85
+ const script = [
86
+ `display notification "${escapeAppleScriptString(payload.message)}"`,
87
+ `with title "${escapeAppleScriptString(payload.title)}"`,
88
+ `subtitle "${escapeAppleScriptString(payload.subtitle)}"`,
89
+ `sound name "${escapeAppleScriptString(payload.soundName)}"`
90
+ ].join(' ')
91
+
92
+ try {
93
+ await runCommand('osascript', ['-e', script], {
94
+ cwd: rootDir,
95
+ stdio: 'ignore'
96
+ })
97
+ return true
98
+ } catch {
99
+ return false
100
+ }
101
+ }