@livestore/utils-dev 0.4.0-dev.22 → 0.4.0-dev.23
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/dist/.tsbuildinfo +1 -0
- package/dist/node/DockerComposeService/DockerComposeService.d.ts +8 -3
- package/dist/node/DockerComposeService/DockerComposeService.d.ts.map +1 -1
- package/dist/node/DockerComposeService/DockerComposeService.js +47 -42
- package/dist/node/DockerComposeService/DockerComposeService.js.map +1 -1
- package/dist/node/DockerComposeService/DockerComposeService.test.js +2 -2
- package/dist/node/DockerComposeService/DockerComposeService.test.js.map +1 -1
- package/dist/node/FileLogger.d.ts.map +1 -1
- package/dist/node/FileLogger.js +3 -3
- package/dist/node/FileLogger.js.map +1 -1
- package/dist/node/cmd-log.d.ts +2 -2
- package/dist/node/cmd-log.d.ts.map +1 -1
- package/dist/node/cmd-log.js +6 -6
- package/dist/node/cmd-log.js.map +1 -1
- package/dist/node/cmd.d.ts +1 -1
- package/dist/node/cmd.d.ts.map +1 -1
- package/dist/node/cmd.js +25 -27
- package/dist/node/cmd.js.map +1 -1
- package/dist/node/cmd.test.js +2 -1
- package/dist/node/cmd.test.js.map +1 -1
- package/dist/node/mod.d.ts +2 -2
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +17 -16
- package/dist/node/mod.js.map +1 -1
- package/dist/node/workspace.d.ts.map +1 -1
- package/dist/node/workspace.js.map +1 -1
- package/dist/node-vitest/Vitest.d.ts.map +1 -1
- package/dist/node-vitest/Vitest.js +11 -11
- package/dist/node-vitest/Vitest.js.map +1 -1
- package/dist/node-vitest/Vitest.test.d.ts +8 -0
- package/dist/node-vitest/Vitest.test.d.ts.map +1 -1
- package/dist/node-vitest/Vitest.test.js +11 -7
- package/dist/node-vitest/Vitest.test.js.map +1 -1
- package/dist/wrangler/WranglerDevServer.d.ts +2 -2
- package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
- package/dist/wrangler/WranglerDevServer.js +7 -7
- package/dist/wrangler/WranglerDevServer.js.map +1 -1
- package/dist/wrangler/WranglerDevServer.test.js +2 -2
- package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
- package/package.json +68 -15
- package/src/node/DockerComposeService/DockerComposeService.test.ts +5 -2
- package/src/node/DockerComposeService/DockerComposeService.ts +105 -90
- package/src/node/DockerComposeService/test-fixtures/docker-compose.yml +1 -1
- package/src/node/FileLogger.ts +4 -3
- package/src/node/cmd-log.ts +52 -55
- package/src/node/cmd.test.ts +54 -50
- package/src/node/cmd.ts +54 -57
- package/src/node/mod.ts +20 -18
- package/src/node/workspace.ts +1 -0
- package/src/node-vitest/Vitest.test.ts +12 -7
- package/src/node-vitest/Vitest.ts +28 -24
- package/src/wrangler/WranglerDevServer.test.ts +4 -2
- package/src/wrangler/WranglerDevServer.ts +9 -8
- package/src/wrangler/fixtures/wrangler.toml +1 -1
- package/dist/.tsbuildinfo.json +0 -1
package/src/node/cmd.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
|
|
4
3
|
import { CommandExecutor, Duration, Effect, Layer } from '@livestore/utils/effect'
|
|
5
4
|
import { PlatformNode } from '@livestore/utils/node'
|
|
6
5
|
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
7
6
|
import { expect } from 'vitest'
|
|
7
|
+
|
|
8
8
|
import { cmd } from './cmd.ts'
|
|
9
9
|
import { CurrentWorkingDirectory } from './workspace.ts'
|
|
10
10
|
|
|
@@ -30,55 +30,59 @@ Vitest.describe('cmd helper', () => {
|
|
|
30
30
|
}).pipe(withNode(test)),
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expect(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
expect(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
33
|
+
/** TODO(#1020): Investigate CI timeout flakiness for cmd tests that spawn many child processes */
|
|
34
|
+
Vitest.scopedLive(
|
|
35
|
+
'supports logging with archive + retention',
|
|
36
|
+
(test) =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
const workspace = process.env.WORKSPACE_ROOT!
|
|
39
|
+
const logsDir = path.join(workspace, 'tmp', 'cmd-tests', String(Date.now()))
|
|
40
|
+
|
|
41
|
+
// first run
|
|
42
|
+
const exit1 = yield* cmd('printf first', { logDir: logsDir })
|
|
43
|
+
expect(exit1).toBe(CommandExecutor.ExitCode(0))
|
|
44
|
+
const current = path.join(logsDir, 'dev.log')
|
|
45
|
+
expect(fs.existsSync(current)).toBe(true)
|
|
46
|
+
const firstLog = fs.readFileSync(current, 'utf8')
|
|
47
|
+
const firstStdoutLines = firstLog.split('\n').filter((line) => line.includes('[stdout]'))
|
|
48
|
+
expect(firstStdoutLines.length).toBeGreaterThan(0)
|
|
49
|
+
for (const line of firstStdoutLines) {
|
|
50
|
+
expect(line).toContain('[stdout] first')
|
|
51
|
+
expect(line).toContain('INFO')
|
|
52
|
+
expect(line).toContain('printf first')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// second run — archives previous
|
|
56
|
+
const exit2 = yield* cmd('printf second', { logDir: logsDir })
|
|
57
|
+
expect(exit2).toBe(CommandExecutor.ExitCode(0))
|
|
58
|
+
const archiveDir = path.join(logsDir, 'archive')
|
|
59
|
+
const archives = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
|
|
60
|
+
expect(archives.length).toBe(1)
|
|
61
|
+
const archivedPath = path.join(archiveDir, archives[0]!)
|
|
62
|
+
const archivedLog = fs.readFileSync(archivedPath, 'utf8')
|
|
63
|
+
const archivedStdoutLines = archivedLog.split('\n').filter((line) => line.includes('[stdout]'))
|
|
64
|
+
expect(archivedStdoutLines.length).toBeGreaterThan(0)
|
|
65
|
+
for (const line of archivedStdoutLines) {
|
|
66
|
+
expect(line).toContain('[stdout] first')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const secondLog = fs.readFileSync(current, 'utf8')
|
|
70
|
+
const secondStdoutLines = secondLog.split('\n').filter((line) => line.includes('[stdout]'))
|
|
71
|
+
expect(secondStdoutLines.length).toBeGreaterThan(0)
|
|
72
|
+
for (const line of secondStdoutLines) {
|
|
73
|
+
expect(line).toContain('[stdout] second')
|
|
74
|
+
expect(line).toContain('INFO')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// generate many archives to exercise retention (keep 50)
|
|
78
|
+
for (let i = 0; i < 60; i++) {
|
|
79
|
+
// Use small unique payloads
|
|
80
|
+
yield* cmd(['printf', String(i)], { logDir: logsDir })
|
|
81
|
+
}
|
|
82
|
+
const archivesAfter = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
|
|
83
|
+
expect(archivesAfter.length).toBeLessThanOrEqual(50)
|
|
84
|
+
}).pipe(withNode(test)),
|
|
85
|
+
{ timeout: 30_000, retry: 3 },
|
|
82
86
|
)
|
|
83
87
|
|
|
84
88
|
Vitest.scopedLive('streams stdout and stderr with logger formatting', (test) =>
|
package/src/node/cmd.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
Schema,
|
|
18
18
|
Stream,
|
|
19
19
|
} from '@livestore/utils/effect'
|
|
20
|
+
|
|
20
21
|
import { applyLoggingToCommand } from './cmd-log.ts'
|
|
21
22
|
import * as FileLogger from './FileLogger.ts'
|
|
22
23
|
import { CurrentWorkingDirectory } from './workspace.ts'
|
|
@@ -26,23 +27,21 @@ const SUCCESS_EXIT_CODE: CommandExecutor.ExitCode = 0 as CommandExecutor.ExitCod
|
|
|
26
27
|
|
|
27
28
|
export const cmd: (
|
|
28
29
|
commandInput: string | (string | undefined)[],
|
|
29
|
-
options?:
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
| undefined,
|
|
30
|
+
options?: {
|
|
31
|
+
stderr?: 'inherit' | 'pipe'
|
|
32
|
+
stdout?: 'inherit' | 'pipe'
|
|
33
|
+
shell?: boolean
|
|
34
|
+
env?: Record<string, string | undefined>
|
|
35
|
+
/**
|
|
36
|
+
* When provided, streams command output to terminal AND to a canonical log file (`${logDir}/dev.log`) in this directory.
|
|
37
|
+
* Also archives the previous run to `${logDir}/archive/dev-<ISO>.log` and keeps only the latest 50 archives.
|
|
38
|
+
*/
|
|
39
|
+
logDir?: string
|
|
40
|
+
/** Optional basename for the canonical log file; defaults to 'dev.log' */
|
|
41
|
+
logFileName?: string
|
|
42
|
+
/** Optional number of archived logs to retain; defaults to 50 */
|
|
43
|
+
logRetention?: number
|
|
44
|
+
},
|
|
46
45
|
) => Effect.Effect<
|
|
47
46
|
CommandExecutor.ExitCode,
|
|
48
47
|
PlatformError.PlatformError | CmdError,
|
|
@@ -51,27 +50,26 @@ export const cmd: (
|
|
|
51
50
|
const cwd = yield* CurrentWorkingDirectory
|
|
52
51
|
|
|
53
52
|
const asArray = Array.isArray(commandInput)
|
|
54
|
-
const parts = asArray ?
|
|
55
|
-
const [command, ...args] = asArray ? (parts as string[]) :
|
|
53
|
+
const parts = asArray === true ? commandInput.filter(isNotUndefined) : undefined
|
|
54
|
+
const [command, ...args] = asArray === true ? (parts as string[]) : commandInput.split(' ')
|
|
56
55
|
|
|
57
56
|
const debugEnvStr = Object.entries(options?.env ?? {})
|
|
58
|
-
.map(([key, value]) => `${key}='${value}' `)
|
|
57
|
+
.map(([key, value]) => `${key}='${String(value)}' `)
|
|
59
58
|
.join('')
|
|
60
59
|
|
|
61
60
|
const loggingOpts = {
|
|
62
|
-
...(options?.logDir ? { logDir: options.logDir } : {}),
|
|
63
|
-
...(options?.logFileName ? { logFileName: options.logFileName } : {}),
|
|
64
|
-
...(options?.logRetention ? { logRetention: options.logRetention } : {}),
|
|
61
|
+
...(options?.logDir !== undefined ? { logDir: options.logDir } : {}),
|
|
62
|
+
...(options?.logFileName !== undefined ? { logFileName: options.logFileName } : {}),
|
|
63
|
+
...(options?.logRetention !== undefined ? { logRetention: options.logRetention } : {}),
|
|
65
64
|
} as const
|
|
66
65
|
const { input: finalInput, subshell: needsShell, logPath } = yield* applyLoggingToCommand(commandInput, loggingOpts)
|
|
67
66
|
|
|
68
67
|
const stdoutMode = options?.stdout ?? 'inherit'
|
|
69
68
|
const stderrMode = options?.stderr ?? 'inherit'
|
|
70
|
-
const useShell = (options?.shell ? true : false) || needsShell
|
|
69
|
+
const useShell = (options?.shell === true ? true : false) || needsShell
|
|
71
70
|
|
|
72
|
-
const commandDebugStr =
|
|
73
|
-
|
|
74
|
-
const subshellStr = useShell ? ' (in subshell)' : ''
|
|
71
|
+
const commandDebugStr = debugEnvStr + (Array.isArray(finalInput) === true ? finalInput.join(' ') : finalInput)
|
|
72
|
+
const subshellStr = useShell === true ? ' (in subshell)' : ''
|
|
75
73
|
|
|
76
74
|
yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
|
|
77
75
|
yield* Effect.annotateCurrentSpan({
|
|
@@ -91,7 +89,7 @@ export const cmd: (
|
|
|
91
89
|
useShell,
|
|
92
90
|
} as const
|
|
93
91
|
|
|
94
|
-
const exitCode = yield* isNotUndefined(logPath)
|
|
92
|
+
const exitCode = yield* isNotUndefined(logPath) === true
|
|
95
93
|
? Effect.gen(function* () {
|
|
96
94
|
yield* Effect.sync(() => console.log(`Logging output to ${logPath}`))
|
|
97
95
|
return yield* runWithLogging({ ...baseArgs, logPath, threadName: commandDebugStr })
|
|
@@ -99,15 +97,13 @@ export const cmd: (
|
|
|
99
97
|
: runWithoutLogging(baseArgs)
|
|
100
98
|
|
|
101
99
|
if (exitCode !== SUCCESS_EXIT_CODE) {
|
|
102
|
-
return yield*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}),
|
|
110
|
-
)
|
|
100
|
+
return yield* CmdError.make({
|
|
101
|
+
command: command!,
|
|
102
|
+
args,
|
|
103
|
+
cwd,
|
|
104
|
+
env: options?.env ?? {},
|
|
105
|
+
stderr: stderrMode,
|
|
106
|
+
})
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
return exitCode
|
|
@@ -123,15 +119,14 @@ export const cmdText: (
|
|
|
123
119
|
) => Effect.Effect<string, PlatformError.PlatformError, CommandExecutor.CommandExecutor | CurrentWorkingDirectory> =
|
|
124
120
|
Effect.fn('cmdText')(function* (commandInput, options) {
|
|
125
121
|
const cwd = yield* CurrentWorkingDirectory
|
|
126
|
-
const [command, ...args] =
|
|
127
|
-
? commandInput.filter(isNotUndefined)
|
|
128
|
-
: commandInput.split(' ')
|
|
122
|
+
const [command, ...args] =
|
|
123
|
+
Array.isArray(commandInput) === true ? commandInput.filter(isNotUndefined) : commandInput.split(' ')
|
|
129
124
|
const debugEnvStr = Object.entries(options?.env ?? {})
|
|
130
|
-
.map(([key, value]) => `${key}='${value}' `)
|
|
125
|
+
.map(([key, value]) => `${key}='${String(value)}' `)
|
|
131
126
|
.join('')
|
|
132
127
|
|
|
133
128
|
const commandDebugStr = debugEnvStr + [command, ...args].join(' ')
|
|
134
|
-
const subshellStr = options?.runInShell ? ' (in subshell)' : ''
|
|
129
|
+
const subshellStr = options?.runInShell === true ? ' (in subshell)' : ''
|
|
135
130
|
|
|
136
131
|
yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
|
|
137
132
|
yield* Effect.annotateCurrentSpan({ 'span.label': commandDebugStr, command, cwd })
|
|
@@ -140,13 +135,13 @@ export const cmdText: (
|
|
|
140
135
|
// inherit = Stream stderr to process.stderr, pipe = Stream stderr to process.stdout
|
|
141
136
|
Command.stderr(options?.stderr ?? 'inherit'),
|
|
142
137
|
Command.workingDirectory(cwd),
|
|
143
|
-
options?.runInShell ? Command.runInShell(true) : identity,
|
|
138
|
+
options?.runInShell === true ? Command.runInShell(true) : identity,
|
|
144
139
|
Command.env(options?.env ?? {}),
|
|
145
140
|
Command.string,
|
|
146
141
|
)
|
|
147
142
|
})
|
|
148
143
|
|
|
149
|
-
export class CmdError extends Schema.TaggedError<CmdError>()('CmdError', {
|
|
144
|
+
export class CmdError extends Schema.TaggedError<CmdError>('~@livestore/utils-dev/CmdError')('CmdError', {
|
|
150
145
|
command: Schema.String,
|
|
151
146
|
args: Schema.Array(Schema.String),
|
|
152
147
|
cwd: Schema.String,
|
|
@@ -169,7 +164,7 @@ const runWithoutLogging = ({ commandInput, cwd, env, stdoutMode, stderrMode, use
|
|
|
169
164
|
Command.stdout(stdoutMode),
|
|
170
165
|
Command.stderr(stderrMode),
|
|
171
166
|
Command.workingDirectory(cwd),
|
|
172
|
-
useShell ? Command.runInShell(true) : identity,
|
|
167
|
+
useShell === true ? Command.runInShell(true) : identity,
|
|
173
168
|
Command.env(env),
|
|
174
169
|
Command.exitCode,
|
|
175
170
|
)
|
|
@@ -228,14 +223,16 @@ const runWithLogging = ({
|
|
|
228
223
|
Command.stdout('pipe'),
|
|
229
224
|
Command.stderr('pipe'),
|
|
230
225
|
Command.workingDirectory(cwd),
|
|
231
|
-
useShell ? Command.runInShell(true) : identity,
|
|
226
|
+
useShell === true ? Command.runInShell(true) : identity,
|
|
232
227
|
Command.env(envWithColor),
|
|
233
228
|
)
|
|
234
229
|
|
|
235
230
|
// Acquire/start the command and make sure we kill it on interruption.
|
|
236
231
|
const runningProcess = yield* Effect.acquireRelease(command.pipe(Command.start), (proc) =>
|
|
237
232
|
proc.isRunning.pipe(
|
|
238
|
-
Effect.flatMap((running) =>
|
|
233
|
+
Effect.flatMap((running) =>
|
|
234
|
+
running === true ? proc.kill().pipe(Effect.catchAll(() => Effect.void)) : Effect.void,
|
|
235
|
+
),
|
|
239
236
|
Effect.ignore,
|
|
240
237
|
),
|
|
241
238
|
)
|
|
@@ -266,7 +263,7 @@ const runWithLogging = ({
|
|
|
266
263
|
// Dump any buffered data and finish both stream fibers before we return.
|
|
267
264
|
const flushOutputs = Effect.gen(function* () {
|
|
268
265
|
const stillRunning = yield* runningProcess.isRunning.pipe(Effect.catchAll(() => Effect.succeed(false)))
|
|
269
|
-
if (stillRunning) {
|
|
266
|
+
if (stillRunning === true) {
|
|
270
267
|
yield* Effect.ignore(runningProcess.kill())
|
|
271
268
|
}
|
|
272
269
|
yield* Effect.ignore(Fiber.join(stdoutFiber))
|
|
@@ -282,12 +279,12 @@ const runWithLogging = ({
|
|
|
282
279
|
)
|
|
283
280
|
|
|
284
281
|
const buildCommand = (input: string | string[], useShell: boolean) => {
|
|
285
|
-
if (Array.isArray(input)) {
|
|
282
|
+
if (Array.isArray(input) === true) {
|
|
286
283
|
const [c, ...a] = input
|
|
287
284
|
return Command.make(c!, ...a)
|
|
288
285
|
}
|
|
289
286
|
|
|
290
|
-
if (useShell) {
|
|
287
|
+
if (useShell === true) {
|
|
291
288
|
return Command.make(input)
|
|
292
289
|
}
|
|
293
290
|
|
|
@@ -298,8 +295,8 @@ const buildCommand = (input: string | string[], useShell: boolean) => {
|
|
|
298
295
|
type TLineTerminator = 'newline' | 'carriage-return' | 'none'
|
|
299
296
|
|
|
300
297
|
type TStreamHandler = {
|
|
301
|
-
readonly onChunk: (chunk: string) => Effect.Effect<void
|
|
302
|
-
readonly flush: () => Effect.Effect<void
|
|
298
|
+
readonly onChunk: (chunk: string) => Effect.Effect<void>
|
|
299
|
+
readonly flush: () => Effect.Effect<void>
|
|
303
300
|
}
|
|
304
301
|
|
|
305
302
|
const makeStreamHandler = ({
|
|
@@ -309,7 +306,7 @@ const makeStreamHandler = ({
|
|
|
309
306
|
}: {
|
|
310
307
|
readonly channel: 'stdout' | 'stderr'
|
|
311
308
|
readonly mirrorTarget?: NodeJS.WriteStream
|
|
312
|
-
readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void
|
|
309
|
+
readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void>
|
|
313
310
|
}): TStreamHandler => {
|
|
314
311
|
let buffer = ''
|
|
315
312
|
|
|
@@ -322,11 +319,11 @@ const makeStreamHandler = ({
|
|
|
322
319
|
channel,
|
|
323
320
|
content,
|
|
324
321
|
terminator,
|
|
325
|
-
...(mirrorTarget ? { mirrorTarget } : {}),
|
|
322
|
+
...(mirrorTarget !== undefined ? { mirrorTarget } : {}),
|
|
326
323
|
appendLog,
|
|
327
324
|
})
|
|
328
325
|
|
|
329
|
-
const consumeBuffer = (): Effect.Effect<void
|
|
326
|
+
const consumeBuffer = (): Effect.Effect<void> => {
|
|
330
327
|
if (buffer.length === 0) return Effect.void
|
|
331
328
|
|
|
332
329
|
const lastChar = buffer[buffer.length - 1]
|
|
@@ -390,10 +387,10 @@ const emitSegment = ({
|
|
|
390
387
|
readonly content: string
|
|
391
388
|
readonly terminator: TLineTerminator
|
|
392
389
|
readonly mirrorTarget?: NodeJS.WriteStream
|
|
393
|
-
readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void
|
|
390
|
+
readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void>
|
|
394
391
|
}) =>
|
|
395
392
|
Effect.gen(function* () {
|
|
396
|
-
if (mirrorTarget) {
|
|
393
|
+
if (mirrorTarget !== undefined) {
|
|
397
394
|
yield* Effect.sync(() => mirrorSegment(mirrorTarget, content, terminator))
|
|
398
395
|
}
|
|
399
396
|
|
package/src/node/mod.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks'
|
|
2
2
|
|
|
3
3
|
import * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
|
|
4
|
-
import { IS_BUN, isNonEmptyString } from '@livestore/utils'
|
|
5
|
-
import type { Tracer } from '@livestore/utils/effect'
|
|
6
|
-
import { Config, Effect, FiberRef, Layer, LogLevel, OtelTracer } from '@livestore/utils/effect'
|
|
7
|
-
import { OtelLiveDummy } from '@livestore/utils/node'
|
|
8
4
|
import * as otel from '@opentelemetry/api'
|
|
9
5
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
|
|
10
6
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
11
7
|
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
|
|
12
8
|
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
13
9
|
|
|
10
|
+
import { IS_BUN, isNonEmptyString } from '@livestore/utils'
|
|
11
|
+
import type { Tracer } from '@livestore/utils/effect'
|
|
12
|
+
import { Config, Effect, FiberRef, Layer, LogLevel, OtelTracer, Schema } from '@livestore/utils/effect'
|
|
13
|
+
import { OtelLiveDummy } from '@livestore/utils/node'
|
|
14
|
+
|
|
14
15
|
export { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
|
|
15
16
|
export { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
16
17
|
export * from './cmd.ts'
|
|
@@ -40,16 +41,16 @@ export const OtelLiveHttp = ({
|
|
|
40
41
|
rootSpanAttributes?: Record<string, unknown>
|
|
41
42
|
skipLogUrl?: boolean
|
|
42
43
|
traceNodeBootstrap?: boolean
|
|
43
|
-
} = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.ParentSpan
|
|
44
|
+
} = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.ParentSpan> =>
|
|
44
45
|
Effect.gen(function* () {
|
|
45
46
|
const configRes = yield* Config.all({
|
|
46
47
|
exporterUrl: Config.string('OTEL_EXPORTER_OTLP_ENDPOINT').pipe(
|
|
47
48
|
Config.validate({ message: 'OTEL_EXPORTER_OTLP_ENDPOINT must be set', validation: isNonEmptyString }),
|
|
48
49
|
),
|
|
49
|
-
serviceName: serviceName
|
|
50
|
+
serviceName: serviceName !== undefined
|
|
50
51
|
? Config.succeed(serviceName)
|
|
51
52
|
: Config.string('OTEL_SERVICE_NAME').pipe(Config.withDefault('livestore-utils-dev')),
|
|
52
|
-
rootSpanName: rootSpanName
|
|
53
|
+
rootSpanName: rootSpanName !== undefined
|
|
53
54
|
? Config.succeed(rootSpanName)
|
|
54
55
|
: Config.string('OTEL_ROOT_SPAN_NAME').pipe(Config.withDefault('RootSpan')),
|
|
55
56
|
}).pipe(Effect.option)
|
|
@@ -91,13 +92,13 @@ export const OtelLiveHttp = ({
|
|
|
91
92
|
|
|
92
93
|
const RootSpanLive = Layer.span(config.rootSpanName, {
|
|
93
94
|
attributes: { config, ...rootSpanAttributes },
|
|
94
|
-
onEnd: skipLogUrl ? undefined : (span: any) => logTraceUiUrlForSpan()(span.span),
|
|
95
|
+
onEnd: skipLogUrl === true ? undefined : (span: any) => logTraceUiUrlForSpan()(span.span),
|
|
95
96
|
parent: parentSpan,
|
|
96
97
|
})
|
|
97
98
|
|
|
98
99
|
const layer = yield* Layer.memoize(RootSpanLive.pipe(Layer.provideMerge(OtelLive)))
|
|
99
100
|
|
|
100
|
-
if (traceNodeBootstrap &&
|
|
101
|
+
if (traceNodeBootstrap === true && IS_BUN === false) {
|
|
101
102
|
/**
|
|
102
103
|
* Create a span representing the Node.js bootstrap duration.
|
|
103
104
|
* Note: Skipped in Bun since performance.nodeTiming is not properly supported.
|
|
@@ -136,7 +137,7 @@ export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (spa
|
|
|
136
137
|
if (url === undefined) {
|
|
137
138
|
return Effect.logWarning('No tracing backend url found')
|
|
138
139
|
} else {
|
|
139
|
-
if (printMsg) {
|
|
140
|
+
if (printMsg !== undefined) {
|
|
140
141
|
return Effect.log(printMsg(url))
|
|
141
142
|
} else {
|
|
142
143
|
return Effect.log(`Trace URL: ${url}`)
|
|
@@ -155,13 +156,14 @@ export const getTracingBackendUrl = (span: otel.Span) =>
|
|
|
155
156
|
// Grafana + Tempo
|
|
156
157
|
|
|
157
158
|
const grafanaEndpoint = endpoint.value
|
|
159
|
+
const left = yield* Schema.encode(Schema.parseJson())({
|
|
160
|
+
datasource: 'tempo',
|
|
161
|
+
queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
|
|
162
|
+
range: { from: 'now-1h', to: 'now' },
|
|
163
|
+
}).pipe(Effect.orDie)
|
|
158
164
|
const searchParams = new URLSearchParams({
|
|
159
165
|
orgId: '1',
|
|
160
|
-
left
|
|
161
|
-
datasource: 'tempo',
|
|
162
|
-
queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
|
|
163
|
-
range: { from: 'now-1h', to: 'now' },
|
|
164
|
-
}),
|
|
166
|
+
left,
|
|
165
167
|
})
|
|
166
168
|
|
|
167
169
|
// TODO make dynamic via env var
|
|
@@ -188,14 +190,14 @@ const computeBootstrapTiming = () => {
|
|
|
188
190
|
const nodeTiming = performance.nodeTiming
|
|
189
191
|
|
|
190
192
|
// Absolute start time in ms since epoch.
|
|
191
|
-
const startAbs = IS_BUN
|
|
193
|
+
const startAbs = IS_BUN === true
|
|
192
194
|
? typeof nodeTiming.nodeStart === 'number'
|
|
193
195
|
? nodeTiming.nodeStart
|
|
194
196
|
: performance.timeOrigin
|
|
195
197
|
: performance.timeOrigin + nodeTiming.nodeStart
|
|
196
198
|
|
|
197
199
|
// Absolute end time.
|
|
198
|
-
const endAbs = IS_BUN
|
|
200
|
+
const endAbs = IS_BUN === true
|
|
199
201
|
? (() => {
|
|
200
202
|
const { loopStart, bootstrapComplete } = nodeTiming
|
|
201
203
|
if (typeof loopStart === 'number' && loopStart > 0) return startAbs + loopStart
|
|
@@ -205,7 +207,7 @@ const computeBootstrapTiming = () => {
|
|
|
205
207
|
: startAbs + nodeTiming.duration
|
|
206
208
|
|
|
207
209
|
// Duration attribute value for the span.
|
|
208
|
-
const durationAttr = IS_BUN
|
|
210
|
+
const durationAttr = IS_BUN === true
|
|
209
211
|
? (() => {
|
|
210
212
|
const { loopStart } = nodeTiming
|
|
211
213
|
return typeof loopStart === 'number' && loopStart > 0 ? loopStart : 0
|
package/src/node/workspace.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { Effect, FastCheck } from '@livestore/utils/effect'
|
|
1
|
+
import { Effect, FastCheck, Schema } from '@livestore/utils/effect'
|
|
2
|
+
|
|
2
3
|
import * as Vitest from './Vitest.ts'
|
|
3
4
|
|
|
5
|
+
export class TestError extends Schema.TaggedError<TestError>()('TestError', {
|
|
6
|
+
message: Schema.String,
|
|
7
|
+
}) {}
|
|
8
|
+
|
|
4
9
|
// Demonstrate enhanced asProp functionality with clear shrinking progress
|
|
5
10
|
// This showcases the fix for the "Run 26/6" bug and accurate shrinking detection
|
|
6
11
|
|
|
@@ -16,7 +21,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
16
21
|
Effect.gen(function* () {
|
|
17
22
|
const [value] = properties
|
|
18
23
|
if (value === undefined) {
|
|
19
|
-
return yield*
|
|
24
|
+
return yield* new TestError({ message: 'Value is undefined' })
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
console.log(
|
|
@@ -43,7 +48,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
43
48
|
Effect.gen(function* () {
|
|
44
49
|
const [value] = properties
|
|
45
50
|
if (value === undefined) {
|
|
46
|
-
return yield*
|
|
51
|
+
return yield* new TestError({ message: 'Value is undefined' })
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const displayInfo =
|
|
@@ -59,10 +64,10 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
59
64
|
// Fail when value is greater than 80 to trigger shrinking
|
|
60
65
|
if (value > 80) {
|
|
61
66
|
alreadyFailed = true
|
|
62
|
-
return yield*
|
|
67
|
+
return yield* new TestError({ message: `Value ${value} is too large (> 80)` })
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
if (alreadyFailed && enhanced._tag === 'shrinking') {
|
|
70
|
+
if (alreadyFailed === true && enhanced._tag === 'shrinking') {
|
|
66
71
|
ctx.skip("For the sake of this test, we don't want to fail but want to skip")
|
|
67
72
|
return
|
|
68
73
|
}
|
|
@@ -81,7 +86,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
81
86
|
Effect.gen(function* () {
|
|
82
87
|
const [value] = properties
|
|
83
88
|
if (value === undefined) {
|
|
84
|
-
return yield*
|
|
89
|
+
return yield* new TestError({ message: 'Value is undefined' })
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
console.log(
|
|
@@ -91,7 +96,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
91
96
|
|
|
92
97
|
// This will fail but shrinking is disabled
|
|
93
98
|
if (value > 50) {
|
|
94
|
-
return yield*
|
|
99
|
+
return yield* new TestError({ message: `Value ${value} is too large (> 50) - but no shrinking!` })
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
return
|