@raidiant/notifai 10.1.1 → 10.1.2

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 (156) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/commands-core.d.ts +1 -1
  3. package/dist/commands-init.js +9 -6
  4. package/dist/commands-init.js.map +1 -1
  5. package/dist/commands-send-support.js +3 -0
  6. package/dist/commands-send-support.js.map +1 -1
  7. package/dist/commands-skill.js +72 -54
  8. package/dist/commands-skill.js.map +1 -1
  9. package/dist/invocation-context.d.ts +1 -0
  10. package/dist/invocation-context.js +9 -1
  11. package/dist/invocation-context.js.map +1 -1
  12. package/dist/native-skills.d.ts +13 -3
  13. package/dist/native-skills.js +51 -12
  14. package/dist/native-skills.js.map +1 -1
  15. package/dist/node-floor.d.ts +2 -1
  16. package/dist/node-floor.js +9 -5
  17. package/dist/node-floor.js.map +1 -1
  18. package/dist/orca-session-title.d.ts +5 -4
  19. package/dist/orca-session-title.js +32 -19
  20. package/dist/orca-session-title.js.map +1 -1
  21. package/dist/platform.d.ts +11 -17
  22. package/dist/platform.js +20 -23
  23. package/dist/platform.js.map +1 -1
  24. package/dist/release.d.ts +5 -5
  25. package/dist/release.js +5 -5
  26. package/dist/setup-destinations.js +1 -1
  27. package/dist/setup-destinations.js.map +1 -1
  28. package/dist/skill-integrity.d.ts +49 -0
  29. package/dist/skill-integrity.js +154 -0
  30. package/dist/skill-integrity.js.map +1 -0
  31. package/dist/skill-source/manifest.json +16 -0
  32. package/dist/skill-source/notifai/SKILL.md +390 -0
  33. package/dist/skill-source/notifai/references/harness-setup.md +220 -0
  34. package/package.json +11 -14
  35. package/src/atomic-file.ts +0 -198
  36. package/src/claude-wake.test.ts +0 -321
  37. package/src/claude-wake.ts +0 -499
  38. package/src/cli-bin.test.ts +0 -176
  39. package/src/cli-bin.ts +0 -233
  40. package/src/cli-release.test.ts +0 -46
  41. package/src/cli-release.ts +0 -78
  42. package/src/cli-update-contract.ts +0 -9
  43. package/src/cli-update.test.ts +0 -125
  44. package/src/client.test.ts +0 -390
  45. package/src/client.ts +0 -352
  46. package/src/codex-wake.test.ts +0 -254
  47. package/src/codex-wake.ts +0 -301
  48. package/src/command-session.ts +0 -38
  49. package/src/commands-agent-sessions.ts +0 -72
  50. package/src/commands-auth.ts +0 -238
  51. package/src/commands-config.ts +0 -336
  52. package/src/commands-core.ts +0 -312
  53. package/src/commands-devices.ts +0 -180
  54. package/src/commands-doctor.ts +0 -1501
  55. package/src/commands-guidance.ts +0 -181
  56. package/src/commands-harness-context.ts +0 -207
  57. package/src/commands-hook-shape.ts +0 -69
  58. package/src/commands-hooks.ts +0 -2318
  59. package/src/commands-init.ts +0 -1055
  60. package/src/commands-io.ts +0 -108
  61. package/src/commands-logs.ts +0 -243
  62. package/src/commands-project.ts +0 -47
  63. package/src/commands-send-support.test.ts +0 -156
  64. package/src/commands-send-support.ts +0 -311
  65. package/src/commands-send.ts +0 -875
  66. package/src/commands-setup-proof.test.ts +0 -72
  67. package/src/commands-setup-proof.ts +0 -124
  68. package/src/commands-skill.test.ts +0 -44
  69. package/src/commands-skill.ts +0 -204
  70. package/src/commands-sounds.test.ts +0 -106
  71. package/src/commands-sounds.ts +0 -49
  72. package/src/commands-update.ts +0 -180
  73. package/src/commands.test.ts +0 -8297
  74. package/src/commands.ts +0 -77
  75. package/src/config-schema.test.ts +0 -153
  76. package/src/config-schema.ts +0 -349
  77. package/src/config.test.ts +0 -581
  78. package/src/config.ts +0 -475
  79. package/src/credentials.test.ts +0 -403
  80. package/src/credentials.ts +0 -403
  81. package/src/file-lock-process-worker.test.ts +0 -64
  82. package/src/file-lock.test.ts +0 -320
  83. package/src/file-lock.ts +0 -393
  84. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
  85. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
  86. package/src/generated-session-label.ts +0 -155
  87. package/src/guidance-content.ts +0 -278
  88. package/src/guidance-render.ts +0 -56
  89. package/src/guidance.test.ts +0 -327
  90. package/src/guidance.ts +0 -233
  91. package/src/harness-integration.test.ts +0 -294
  92. package/src/harnesses.test.ts +0 -149
  93. package/src/harnesses.ts +0 -164
  94. package/src/hook-adapter.test.ts +0 -496
  95. package/src/hook-adapter.ts +0 -424
  96. package/src/hook-input.test.ts +0 -39
  97. package/src/hook-input.ts +0 -72
  98. package/src/hooks.test.ts +0 -5824
  99. package/src/hooks.ts +0 -4011
  100. package/src/install-hooks.test.ts +0 -1614
  101. package/src/install-hooks.ts +0 -1501
  102. package/src/interactive.test.ts +0 -387
  103. package/src/interactive.ts +0 -858
  104. package/src/invocation-context.test.ts +0 -352
  105. package/src/invocation-context.ts +0 -197
  106. package/src/logging-process-worker.test.ts +0 -45
  107. package/src/logging.test.ts +0 -544
  108. package/src/logging.ts +0 -698
  109. package/src/main-run.ts +0 -45
  110. package/src/main.ts +0 -12
  111. package/src/native-skills.test.ts +0 -115
  112. package/src/native-skills.ts +0 -162
  113. package/src/node-floor.test.ts +0 -27
  114. package/src/node-floor.ts +0 -37
  115. package/src/onboarding-docs.test.ts +0 -25
  116. package/src/openclaw-plugin.ts +0 -409
  117. package/src/opencode-plugin.ts +0 -298
  118. package/src/orca-session-title.test.ts +0 -54
  119. package/src/orca-session-title.ts +0 -103
  120. package/src/pack-feedback-slice.test.ts +0 -212
  121. package/src/pack-feedback-slice.ts +0 -187
  122. package/src/platform.test.ts +0 -152
  123. package/src/platform.ts +0 -173
  124. package/src/program.test.ts +0 -317
  125. package/src/program.ts +0 -842
  126. package/src/project-enablement.test.ts +0 -55
  127. package/src/project-enablement.ts +0 -99
  128. package/src/question-caps.test.ts +0 -49
  129. package/src/question-settlement-process.ts +0 -47
  130. package/src/question-timing.ts +0 -56
  131. package/src/readiness.test.ts +0 -25
  132. package/src/readiness.ts +0 -212
  133. package/src/release.ts +0 -51
  134. package/src/send-attempts.test.ts +0 -143
  135. package/src/send-attempts.ts +0 -188
  136. package/src/send.test.ts +0 -28
  137. package/src/send.ts +0 -436
  138. package/src/session-activation.ts +0 -61
  139. package/src/session-end-log-lock-worker.test.ts +0 -35
  140. package/src/session-labels.test.ts +0 -490
  141. package/src/session-labels.ts +0 -474
  142. package/src/setup-destinations.test.ts +0 -41
  143. package/src/setup-destinations.ts +0 -50
  144. package/src/skill.test.ts +0 -426
  145. package/src/sound-ref.test.ts +0 -31
  146. package/src/sound-ref.ts +0 -34
  147. package/src/trust-policy.test.ts +0 -701
  148. package/src/ui/banner.ts +0 -95
  149. package/src/ui/config-view.test.ts +0 -90
  150. package/src/ui/config-view.ts +0 -138
  151. package/src/ui/help.test.ts +0 -16
  152. package/src/ui/help.ts +0 -128
  153. package/src/ui/theme.test.ts +0 -92
  154. package/src/ui/theme.ts +0 -265
  155. package/src/url-policy.ts +0 -399
  156. package/tsconfig.json +0 -10
package/src/cli-bin.ts DELETED
@@ -1,233 +0,0 @@
1
- import {
2
- accessSync,
3
- constants,
4
- existsSync,
5
- lstatSync,
6
- readFileSync,
7
- realpathSync,
8
- } from 'node:fs'
9
- import path from 'node:path'
10
- import type { ReadinessState } from './readiness.js'
11
- import { packageVersion } from './release.js'
12
- import { cliUpdateRecoveryCommand } from './cli-update-contract.js'
13
-
14
- const POSIX_NAMES = ['notifai']
15
- const WINDOWS_NAMES = ['notifai.cmd', 'notifai.exe', 'notifai']
16
-
17
- export interface CliBinReadinessOptions {
18
- runningArtifactPath?: string
19
- currentVersion?: string | null
20
- }
21
-
22
- export interface CliPathEntry {
23
- command_path: string
24
- executable: boolean
25
- artifact_path: string | null
26
- version: string | null
27
- install_prefix: string | null
28
- }
29
-
30
- export interface CliInstallationInspection {
31
- current: { artifact_path: string; version: string | null }
32
- effective: CliPathEntry | null
33
- entries: CliPathEntry[]
34
- }
35
-
36
- export function pathNotifaiEntries(
37
- env: NodeJS.ProcessEnv,
38
- platform: NodeJS.Platform = process.platform,
39
- ): string[] {
40
- const raw = platform === 'win32' ? (env['Path'] ?? env['PATH'] ?? '') : (env['PATH'] ?? '')
41
- const delimiter = platform === 'win32' ? ';' : ':'
42
- const names = platform === 'win32' ? WINDOWS_NAMES : POSIX_NAMES
43
- const found: string[] = []
44
- for (const directory of raw.split(delimiter)) {
45
- if (directory === '') continue
46
- for (const name of names) {
47
- const candidate = path.join(directory, name)
48
- if (!existsSync(candidate)) continue
49
- if (!found.includes(candidate)) found.push(candidate)
50
- }
51
- }
52
- return found
53
- }
54
-
55
- export function isExecutablePath(file: string, platform: NodeJS.Platform = process.platform): boolean {
56
- if (!existsSync(file)) return false
57
- if (platform === 'win32') return true
58
- try {
59
- const target = lstatSync(file).isSymbolicLink() ? realpathSync(file) : file
60
- accessSync(target, constants.X_OK)
61
- return true
62
- } catch {
63
- return false
64
- }
65
- }
66
-
67
- function canonicalPath(file: string): string {
68
- try {
69
- return realpathSync(file)
70
- } catch {
71
- return path.resolve(file)
72
- }
73
- }
74
-
75
- function windowsShimArtifact(file: string): string | null {
76
- if (path.extname(file).toLowerCase() !== '.cmd') return null
77
- try {
78
- const source = readFileSync(file, 'utf8')
79
- const match = /(?:%dp0%|%~dp0)?([^"\r\n]*node_modules[\\/]@raidiant[\\/]notifai[\\/]dist[\\/]main\.js)/i.exec(source)
80
- if (match?.[0] === undefined) return null
81
- const expanded = match[0]
82
- .replace(/^%~?dp0%/i, `${path.dirname(file)}${path.sep}`)
83
- .replaceAll('\\', path.sep)
84
- return canonicalPath(expanded)
85
- } catch {
86
- return null
87
- }
88
- }
89
-
90
- function artifactForCommand(file: string, platform: NodeJS.Platform): string | null {
91
- const shim = platform === 'win32' ? windowsShimArtifact(file) : null
92
- if (shim !== null) return shim
93
- try {
94
- return canonicalPath(file)
95
- } catch {
96
- return null
97
- }
98
- }
99
-
100
- function artifactVersion(artifact: string | null): string | null {
101
- if (artifact === null) return null
102
- try {
103
- const parsed: unknown = JSON.parse(
104
- readFileSync(path.join(path.dirname(artifact), '..', 'package.json'), 'utf8'),
105
- )
106
- if (typeof parsed !== 'object' || parsed === null) return null
107
- const version = (parsed as { version?: unknown }).version
108
- return typeof version === 'string' && version !== '' ? version : null
109
- } catch {
110
- return null
111
- }
112
- }
113
-
114
- function installPrefix(artifact: string | null): string | null {
115
- if (artifact !== null) {
116
- const marker = /[\\/]lib[\\/]node_modules[\\/]@raidiant[\\/]notifai[\\/]/i.exec(artifact)
117
- ?? /[\\/]node_modules[\\/]@raidiant[\\/]notifai[\\/]/i.exec(artifact)
118
- if (marker?.index !== undefined) return artifact.slice(0, marker.index)
119
- }
120
- return null
121
- }
122
-
123
- export function inspectCliInstallations(
124
- env: NodeJS.ProcessEnv,
125
- platform: NodeJS.Platform = process.platform,
126
- options: CliBinReadinessOptions = {},
127
- ): CliInstallationInspection {
128
- const runningArtifact = canonicalPath(options.runningArtifactPath ?? process.argv[1] ?? 'notifai')
129
- const entries = pathNotifaiEntries(env, platform).map((command): CliPathEntry => {
130
- const artifact = artifactForCommand(command, platform)
131
- return {
132
- command_path: command,
133
- executable: isExecutablePath(command, platform),
134
- artifact_path: artifact,
135
- version: artifactVersion(artifact),
136
- install_prefix: installPrefix(artifact),
137
- }
138
- })
139
- return {
140
- current: {
141
- artifact_path: runningArtifact,
142
- version: options.currentVersion === undefined ? packageVersion() : options.currentVersion,
143
- },
144
- effective: entries.find((entry) => entry.executable) ?? null,
145
- entries,
146
- }
147
- }
148
-
149
- export function cliBinReadiness(
150
- env: NodeJS.ProcessEnv,
151
- platform: NodeJS.Platform = process.platform,
152
- options: CliBinReadinessOptions = {},
153
- ): ReadinessState {
154
- const inspection = inspectCliInstallations(env, platform, options)
155
- const { current, effective, entries } = inspection
156
- const updateCommand = cliUpdateRecoveryCommand()
157
- if (effective === null && entries.length > 0) {
158
- return {
159
- id: 'cli-bin',
160
- title: 'notifai command',
161
- status: 'gap',
162
- detail: 'the notifai command is on PATH but cannot run',
163
- technical: inspection,
164
- remedy: {
165
- by: 'user-here',
166
- summary: 'repair the global notifai command so it can run',
167
- command: updateCommand,
168
- },
169
- }
170
- }
171
- // A global install that landed outside PATH used to report `ready` with
172
- // "this process can run" — which is true of the running process and false of
173
- // every instruction it goes on to print. Hooks embed absolute paths, so
174
- // nothing visibly breaks until the reader types `notifai` themselves.
175
- //
176
- // Not a blocker: this process is already running, so the whole setup —
177
- // pairing, the app, the delivery proof — still completes. It is the later
178
- // `notifai …` lines that will not be found, and saying so is the fix.
179
- if (effective === null) {
180
- return {
181
- id: 'cli-bin',
182
- title: 'notifai command',
183
- status: 'optional-gap',
184
- detail:
185
- 'no `notifai` on PATH — this process runs, but a typed `notifai …` command will not be found',
186
- remedy: {
187
- by: 'user-here',
188
- summary: 'install notifai globally so the command is on your PATH',
189
- command: updateCommand,
190
- },
191
- }
192
- }
193
-
194
- const effectiveIsCurrent =
195
- effective.artifact_path === current.artifact_path ||
196
- (effective.version !== null && current.version !== null && effective.version === current.version)
197
- if (!effectiveIsCurrent) {
198
- return {
199
- id: 'cli-bin',
200
- title: 'notifai command',
201
- status: 'gap',
202
- detail: 'the notifai command resolves to a different installation than this current CLI',
203
- technical: inspection,
204
- remedy: {
205
- by: 'user-here',
206
- summary: 'update the notifai command that wins PATH',
207
- command: updateCommand,
208
- },
209
- }
210
- }
211
-
212
- const duplicateNeedsCleanup = entries.some(
213
- (entry) =>
214
- entry !== effective &&
215
- (!entry.executable || entry.artifact_path !== effective.artifact_path),
216
- )
217
- if (duplicateNeedsCleanup) {
218
- return {
219
- id: 'cli-bin',
220
- title: 'notifai command',
221
- status: 'optional-gap',
222
- detail: 'the notifai command is ready; another installation remains for cleanup',
223
- technical: inspection,
224
- }
225
- }
226
- return {
227
- id: 'cli-bin',
228
- title: 'notifai command',
229
- status: 'ready',
230
- detail: 'the notifai command is ready',
231
- technical: inspection,
232
- }
233
- }
@@ -1,46 +0,0 @@
1
- import { afterEach, describe, expect, it } from 'vitest'
2
- import {
3
- compareCliSemVer,
4
- latestPublishedCliVersion,
5
- newerPublishedCli,
6
- resetLatestPublishedCliVersionForTest,
7
- shouldConsultCliRegistry,
8
- } from './cli-release.js'
9
-
10
- afterEach(() => {
11
- resetLatestPublishedCliVersionForTest()
12
- })
13
-
14
- describe('CLI registry recommendation', () => {
15
- it('is suppressed for JSON, non-interactive, and CI callers', () => {
16
- expect(shouldConsultCliRegistry({ interactive: true })).toBe(true)
17
- expect(shouldConsultCliRegistry({ interactive: true, json: true })).toBe(false)
18
- expect(shouldConsultCliRegistry({ interactive: false })).toBe(false)
19
- expect(shouldConsultCliRegistry({ interactive: true, env: { CI: 'true' } })).toBe(false)
20
- })
21
-
22
- it('never throws when the registry is unreachable', async () => {
23
- const latest = await latestPublishedCliVersion(async () => {
24
- throw new Error('network down')
25
- })
26
- expect(latest).toBeNull()
27
- })
28
-
29
- it('caches a successful dist-tag read', async () => {
30
- let calls = 0
31
- const fetchImpl: typeof fetch = async () => {
32
- calls += 1
33
- return new Response(JSON.stringify({ latest: '9.1.0' }), { status: 200 })
34
- }
35
- expect(await latestPublishedCliVersion(fetchImpl)).toBe('9.1.0')
36
- expect(await latestPublishedCliVersion(fetchImpl)).toBe('9.1.0')
37
- expect(calls).toBe(1)
38
- })
39
-
40
- it('only recommends a strictly newer published version', () => {
41
- expect(newerPublishedCli('8.0.0', '8.0.1')).toBe('8.0.1')
42
- expect(newerPublishedCli('8.0.0', '8.0.0')).toBeNull()
43
- expect(newerPublishedCli('8.0.1', '8.0.0')).toBeNull()
44
- expect(compareCliSemVer('8.0.0', '7.0.2')).toBeGreaterThan(0)
45
- })
46
- })
@@ -1,78 +0,0 @@
1
- import { packageVersion } from './release.js'
2
-
3
- /**
4
- * The newest published CLI, read from the registry that publishes it.
5
- *
6
- * Which version is newest is a registry fact. Doctor consults it as a
7
- * best-effort, cached, short-timeout hint: never fatal, and never on a
8
- * non-interactive or CI invocation — those callers already get structured
9
- * support state from the server when they are signed in.
10
- */
11
- const DIST_TAGS_URL = 'https://registry.npmjs.org/-/package/@raidiant/notifai/dist-tags'
12
- const REQUEST_TIMEOUT_MS = 2_000
13
- const CACHE_TTL_MS = 60 * 60 * 1000
14
-
15
- let cached: { value: string | null; at: number } | null = null
16
-
17
- export function compareCliSemVer(left: string, right: string): number | null {
18
- const parse = (value: string): [number, number, number] | null => {
19
- const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value)
20
- if (!match) return null
21
- return [Number(match[1]), Number(match[2]), Number(match[3])]
22
- }
23
- const a = parse(left)
24
- const b = parse(right)
25
- if (a === null || b === null) return null
26
- for (let index = 0; index < 3; index += 1) {
27
- if (a[index] !== b[index]) return a[index]! - b[index]!
28
- }
29
- return 0
30
- }
31
-
32
- export function shouldConsultCliRegistry(input: {
33
- interactive?: boolean
34
- json?: boolean
35
- env?: NodeJS.ProcessEnv
36
- }): boolean {
37
- if (input.json === true) return false
38
- if (input.interactive !== true) return false
39
- const ci = input.env?.['CI']
40
- return ci !== '1' && ci !== 'true'
41
- }
42
-
43
- export async function latestPublishedCliVersion(
44
- fetchImpl: typeof fetch = fetch,
45
- ): Promise<string | null> {
46
- if (cached !== null && Date.now() - cached.at < CACHE_TTL_MS) return cached.value
47
- try {
48
- const response = await fetchImpl(DIST_TAGS_URL, {
49
- headers: { accept: 'application/json' },
50
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
51
- })
52
- if (!response.ok) return cached?.value ?? null
53
- const body: unknown = await response.json()
54
- if (typeof body !== 'object' || body === null) return cached?.value ?? null
55
- const latest = (body as { latest?: unknown }).latest
56
- if (typeof latest !== 'string' || compareCliSemVer(latest, '0.0.0') === null) {
57
- return cached?.value ?? null
58
- }
59
- cached = { value: latest, at: Date.now() }
60
- return latest
61
- } catch {
62
- return cached?.value ?? null
63
- }
64
- }
65
-
66
- export function newerPublishedCli(local: string | null, latest: string | null): string | null {
67
- if (local === null || latest === null) return null
68
- const comparison = compareCliSemVer(latest, local)
69
- return comparison !== null && comparison > 0 ? latest : null
70
- }
71
-
72
- export function thisCliVersion(): string | null {
73
- return packageVersion()
74
- }
75
-
76
- export function resetLatestPublishedCliVersionForTest(): void {
77
- cached = null
78
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Resolve the current published CLI independently of every installed
3
- * `notifai` command. The fetched build owns the locally derived prefix repair,
4
- * so this action neither invokes the stale PATH winner nor exposes local
5
- * installation paths.
6
- */
7
- export function cliUpdateRecoveryCommand(): string {
8
- return 'npx --yes @raidiant/notifai@latest update'
9
- }
@@ -1,125 +0,0 @@
1
- import { spawnSync } from 'node:child_process'
2
- import {
3
- chmodSync,
4
- mkdirSync,
5
- mkdtempSync,
6
- readFileSync,
7
- realpathSync,
8
- symlinkSync,
9
- writeFileSync,
10
- } from 'node:fs'
11
- import os from 'node:os'
12
- import path from 'node:path'
13
- import { describe, expect, it } from 'vitest'
14
- import type { CommandDeps, CommandIo } from './commands-core.js'
15
- import { cliUpdateCommand } from './commands-update.js'
16
- import { installHookAdapter, inspectHookAdapter } from './hook-adapter.js'
17
- import { packageVersion } from './release.js'
18
-
19
- class CapturedIo implements CommandIo {
20
- outLines: string[] = []
21
- errLines: string[] = []
22
- out(line: string) { this.outLines.push(line) }
23
- err(line: string) { this.errLines.push(line) }
24
- async confirm() { return false }
25
- openUrl() {}
26
- }
27
-
28
- function npmInstall(root: string, name: string, version: string) {
29
- const prefix = path.join(root, name)
30
- const packageRoot = path.join(prefix, 'lib', 'node_modules', '@raidiant', 'notifai')
31
- const artifact = path.join(packageRoot, 'dist', 'main.js')
32
- const command = path.join(prefix, 'bin', 'notifai')
33
- mkdirSync(path.dirname(artifact), { recursive: true })
34
- mkdirSync(path.dirname(command), { recursive: true })
35
- writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ version }))
36
- writeFileSync(artifact, `#!${process.execPath}\nprocess.stdout.write(${JSON.stringify(version)} + "\\n")\n`)
37
- chmodSync(artifact, 0o755)
38
- symlinkSync(path.relative(path.dirname(command), artifact), command)
39
- return { prefix, packageRoot, artifact, command }
40
- }
41
-
42
- describe('CLI update recovery', () => {
43
- it('updates the PATH winner prefix and retargets the shared hook adapter in one action', () => {
44
- const root = mkdtempSync(path.join(os.tmpdir(), 'notifai-update-loop-'))
45
- const currentVersion = packageVersion()
46
- if (currentVersion === null) throw new Error('test build has no package version')
47
- const stale = npmInstall(root, 'stale-prefix', '3.0.1')
48
- const current = npmInstall(root, 'current-prefix', currentVersion)
49
- const managerPrefix = path.join(root, 'manager-prefix')
50
- const managerBin = path.join(managerPrefix, 'bin')
51
- const npm = path.join(managerBin, 'npm')
52
- const calls = path.join(root, 'npm-calls.jsonl')
53
- mkdirSync(managerBin, { recursive: true })
54
- writeFileSync(
55
- npm,
56
- `#!${process.execPath}
57
- const fs = require('node:fs');
58
- const path = require('node:path');
59
- const args = process.argv.slice(2);
60
- fs.appendFileSync(${JSON.stringify(calls)}, JSON.stringify(args) + '\\n');
61
- if (args[0] === 'prefix') {
62
- process.stdout.write(${JSON.stringify(managerPrefix)} + '\\n');
63
- process.exit(0);
64
- }
65
- const prefix = args[args.indexOf('--prefix') + 1];
66
- const packageRoot = path.join(prefix, 'lib', 'node_modules', '@raidiant', 'notifai');
67
- const artifact = path.join(packageRoot, 'dist', 'main.js');
68
- fs.mkdirSync(path.dirname(artifact), { recursive: true });
69
- fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ version: ${JSON.stringify(currentVersion)} }));
70
- fs.writeFileSync(artifact, '#!${process.execPath}\\nprocess.stdout.write(${JSON.stringify(`${currentVersion}\\n`)})\\n', { mode: 0o755 });
71
- `,
72
- )
73
- chmodSync(npm, 0o755)
74
-
75
- const home = path.join(root, 'home')
76
- installHookAdapter({ execPath: process.execPath, scriptPath: stale.artifact }, home)
77
- const io = new CapturedIo()
78
- const deps: CommandDeps = {
79
- io,
80
- store: { load: () => null, save: () => {}, clear: () => {}, describe: () => 'test' },
81
- env: {
82
- PATH: [managerBin, path.dirname(stale.command), path.dirname(current.command)].join(':'),
83
- },
84
- cwd: root,
85
- hookAdapterHome: home,
86
- hookInstallTarget: { execPath: process.execPath, scriptPath: current.artifact },
87
- hookPlatform: 'darwin',
88
- }
89
-
90
- expect(cliUpdateCommand(deps, {})).toBe(0)
91
- expect(JSON.parse(readFileSync(path.join(stale.packageRoot, 'package.json'), 'utf8'))).toMatchObject({
92
- version: currentVersion,
93
- })
94
- expect(
95
- readFileSync(calls, 'utf8').trim().split('\n').map((line) => JSON.parse(line)),
96
- ).toEqual([
97
- ['prefix', '--global'],
98
- ['install', '--global', '--prefix', realpathSync(stale.prefix), '@raidiant/notifai'],
99
- ])
100
- expect(spawnSync(stale.command, ['--version'], { encoding: 'utf8' }).stdout.trim()).toBe(currentVersion)
101
- expect(inspectHookAdapter(home).target).toMatchObject({ scriptPath: realpathSync(stale.artifact) })
102
- expect(io.outLines).toEqual(['Notifai is updated. Re-run `notifai init` to continue setup.'])
103
- expect(io.outLines.join('\n')).not.toContain(root)
104
- expect(io.errLines).toEqual([])
105
-
106
- io.outLines = []
107
- expect(cliUpdateCommand(deps, { json: true })).toBe(0)
108
- const result = JSON.parse(io.outLines[0] ?? '{}') as Record<string, unknown>
109
- expect(result).toMatchObject({
110
- ok: true,
111
- package_manager_prefix: managerPrefix,
112
- update_prefix: realpathSync(stale.prefix),
113
- after: {
114
- effective: {
115
- command_path: stale.command,
116
- artifact_path: realpathSync(stale.artifact),
117
- version: currentVersion,
118
- },
119
- },
120
- hook_adapter: {
121
- target: { scriptPath: realpathSync(stale.artifact) },
122
- },
123
- })
124
- })
125
- })