@livestore/utils-dev 0.4.0-dev.18 → 0.4.0-dev.19
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.json +1 -1
- package/dist/node/cmd.d.ts +3 -4
- package/dist/node/cmd.d.ts.map +1 -1
- package/dist/node/cmd.js +4 -3
- package/dist/node/cmd.js.map +1 -1
- package/dist/node/cmd.test.js +3 -2
- package/dist/node/cmd.test.js.map +1 -1
- package/dist/node/mod.d.ts +1 -0
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +1 -0
- package/dist/node/mod.js.map +1 -1
- package/dist/node/workspace.d.ts +22 -0
- package/dist/node/workspace.d.ts.map +1 -0
- package/dist/node/workspace.js +26 -0
- package/dist/node/workspace.js.map +1 -0
- package/dist/wrangler/WranglerDevServer.d.ts +20 -3
- package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
- package/dist/wrangler/WranglerDevServer.js +18 -5
- package/dist/wrangler/WranglerDevServer.js.map +1 -1
- package/dist/wrangler/WranglerDevServer.test.js +1 -1
- package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
- package/package.json +2 -2
- package/src/node/cmd.test.ts +3 -2
- package/src/node/cmd.ts +73 -72
- package/src/node/mod.ts +1 -0
- package/src/node/workspace.ts +45 -0
- package/src/wrangler/WranglerDevServer.test.ts +1 -1
- package/src/wrangler/WranglerDevServer.ts +48 -8
package/src/node/cmd.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
|
|
3
|
-
import { isNotUndefined
|
|
3
|
+
import { isNotUndefined } from '@livestore/utils'
|
|
4
4
|
import {
|
|
5
5
|
Cause,
|
|
6
6
|
Command,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '@livestore/utils/effect'
|
|
20
20
|
import { applyLoggingToCommand } from './cmd-log.ts'
|
|
21
21
|
import * as FileLogger from './FileLogger.ts'
|
|
22
|
+
import { CurrentWorkingDirectory } from './workspace.ts'
|
|
22
23
|
|
|
23
24
|
// Branded zero value so we can compare exit codes without touching internals.
|
|
24
25
|
const SUCCESS_EXIT_CODE: CommandExecutor.ExitCode = 0 as CommandExecutor.ExitCode
|
|
@@ -27,7 +28,6 @@ export const cmd: (
|
|
|
27
28
|
commandInput: string | (string | undefined)[],
|
|
28
29
|
options?:
|
|
29
30
|
| {
|
|
30
|
-
cwd?: string
|
|
31
31
|
stderr?: 'inherit' | 'pipe'
|
|
32
32
|
stdout?: 'inherit' | 'pipe'
|
|
33
33
|
shell?: boolean
|
|
@@ -43,84 +43,86 @@ export const cmd: (
|
|
|
43
43
|
logRetention?: number
|
|
44
44
|
}
|
|
45
45
|
| undefined,
|
|
46
|
-
) => Effect.Effect<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
46
|
+
) => Effect.Effect<
|
|
47
|
+
CommandExecutor.ExitCode,
|
|
48
|
+
PlatformError.PlatformError | CmdError,
|
|
49
|
+
CommandExecutor.CommandExecutor | CurrentWorkingDirectory
|
|
50
|
+
> = Effect.fn('cmd')(function* (commandInput, options) {
|
|
51
|
+
const cwd = yield* CurrentWorkingDirectory
|
|
52
|
+
|
|
53
|
+
const asArray = Array.isArray(commandInput)
|
|
54
|
+
const parts = asArray ? (commandInput as (string | undefined)[]).filter(isNotUndefined) : undefined
|
|
55
|
+
const [command, ...args] = asArray ? (parts as string[]) : (commandInput as string).split(' ')
|
|
56
|
+
|
|
57
|
+
const debugEnvStr = Object.entries(options?.env ?? {})
|
|
58
|
+
.map(([key, value]) => `${key}='${value}' `)
|
|
59
|
+
.join('')
|
|
60
|
+
|
|
61
|
+
const loggingOpts = {
|
|
62
|
+
...(options?.logDir ? { logDir: options.logDir } : {}),
|
|
63
|
+
...(options?.logFileName ? { logFileName: options.logFileName } : {}),
|
|
64
|
+
...(options?.logRetention ? { logRetention: options.logRetention } : {}),
|
|
65
|
+
} as const
|
|
66
|
+
const { input: finalInput, subshell: needsShell, logPath } = yield* applyLoggingToCommand(commandInput, loggingOpts)
|
|
67
|
+
|
|
68
|
+
const stdoutMode = options?.stdout ?? 'inherit'
|
|
69
|
+
const stderrMode = options?.stderr ?? 'inherit'
|
|
70
|
+
const useShell = (options?.shell ? true : false) || needsShell
|
|
71
|
+
|
|
72
|
+
const commandDebugStr =
|
|
73
|
+
debugEnvStr + (Array.isArray(finalInput) ? (finalInput as string[]).join(' ') : (finalInput as string))
|
|
74
|
+
const subshellStr = useShell ? ' (in subshell)' : ''
|
|
75
|
+
|
|
76
|
+
yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
|
|
77
|
+
yield* Effect.annotateCurrentSpan({
|
|
78
|
+
'span.label': commandDebugStr,
|
|
79
|
+
cwd,
|
|
80
|
+
command,
|
|
81
|
+
args,
|
|
82
|
+
logDir: options?.logDir,
|
|
83
|
+
})
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
const baseArgs = {
|
|
86
|
+
commandInput: finalInput,
|
|
87
|
+
cwd,
|
|
88
|
+
env: options?.env ?? {},
|
|
89
|
+
stdoutMode,
|
|
90
|
+
stderrMode,
|
|
91
|
+
useShell,
|
|
92
|
+
} as const
|
|
93
|
+
|
|
94
|
+
const exitCode = yield* isNotUndefined(logPath)
|
|
95
|
+
? Effect.gen(function* () {
|
|
96
|
+
yield* Effect.sync(() => console.log(`Logging output to ${logPath}`))
|
|
97
|
+
return yield* runWithLogging({ ...baseArgs, logPath, threadName: commandDebugStr })
|
|
98
|
+
})
|
|
99
|
+
: runWithoutLogging(baseArgs)
|
|
100
|
+
|
|
101
|
+
if (exitCode !== SUCCESS_EXIT_CODE) {
|
|
102
|
+
return yield* Effect.fail(
|
|
103
|
+
CmdError.make({
|
|
104
|
+
command: command!,
|
|
105
|
+
args,
|
|
106
|
+
cwd,
|
|
107
|
+
env: options?.env ?? {},
|
|
108
|
+
stderr: stderrMode,
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
return exitCode
|
|
114
|
+
})
|
|
112
115
|
|
|
113
116
|
export const cmdText: (
|
|
114
117
|
commandInput: string | (string | undefined)[],
|
|
115
118
|
options?: {
|
|
116
|
-
cwd?: string
|
|
117
119
|
stderr?: 'inherit' | 'pipe'
|
|
118
120
|
runInShell?: boolean
|
|
119
121
|
env?: Record<string, string | undefined>
|
|
120
122
|
},
|
|
121
|
-
) => Effect.Effect<string, PlatformError.PlatformError, CommandExecutor.CommandExecutor> =
|
|
122
|
-
function* (commandInput, options) {
|
|
123
|
-
const cwd =
|
|
123
|
+
) => Effect.Effect<string, PlatformError.PlatformError, CommandExecutor.CommandExecutor | CurrentWorkingDirectory> =
|
|
124
|
+
Effect.fn('cmdText')(function* (commandInput, options) {
|
|
125
|
+
const cwd = yield* CurrentWorkingDirectory
|
|
124
126
|
const [command, ...args] = Array.isArray(commandInput)
|
|
125
127
|
? commandInput.filter(isNotUndefined)
|
|
126
128
|
: commandInput.split(' ')
|
|
@@ -142,8 +144,7 @@ export const cmdText: (
|
|
|
142
144
|
Command.env(options?.env ?? {}),
|
|
143
145
|
Command.string,
|
|
144
146
|
)
|
|
145
|
-
}
|
|
146
|
-
)
|
|
147
|
+
})
|
|
147
148
|
|
|
148
149
|
export class CmdError extends Schema.TaggedError<CmdError>()('CmdError', {
|
|
149
150
|
command: Schema.String,
|
package/src/node/mod.ts
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
3
|
+
import { Context, Effect, Layer } from '@livestore/utils/effect'
|
|
4
|
+
|
|
5
|
+
export type WorkspaceInfo = string
|
|
6
|
+
|
|
7
|
+
/** Current working directory. */
|
|
8
|
+
export class CurrentWorkingDirectory extends Context.Tag('CurrentWorkingDirectory')<
|
|
9
|
+
CurrentWorkingDirectory,
|
|
10
|
+
WorkspaceInfo
|
|
11
|
+
>() {
|
|
12
|
+
/** Layer that captures the process cwd once. */
|
|
13
|
+
static live = Layer.effect(
|
|
14
|
+
CurrentWorkingDirectory,
|
|
15
|
+
Effect.sync(() => process.cwd()),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
/** Override CWD for tests or nested invocations. */
|
|
19
|
+
static fromPath = (cwd: string) => Layer.succeed(CurrentWorkingDirectory, cwd)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Livestore workspace root (env required). */
|
|
23
|
+
export class LivestoreWorkspace extends Context.Tag('LivestoreWorkspace')<LivestoreWorkspace, WorkspaceInfo>() {
|
|
24
|
+
/** Resolve from WORKSPACE_ROOT env. */
|
|
25
|
+
static live = Layer.effect(
|
|
26
|
+
LivestoreWorkspace,
|
|
27
|
+
Effect.sync(() => {
|
|
28
|
+
const root = process.env.WORKSPACE_ROOT ?? shouldNeverHappen('WORKSPACE_ROOT is not set')
|
|
29
|
+
return root
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
/** Provide a fixed Livestore root. */
|
|
34
|
+
static fromPath = (root: string) => Layer.succeed(LivestoreWorkspace, root)
|
|
35
|
+
|
|
36
|
+
/** Derive a CurrentWorkingDirectory layer from the Livestore workspace root (with optional subpath) */
|
|
37
|
+
static toCwd = (/** Relative path to the Livestore workspace root */ subPath?: string) =>
|
|
38
|
+
Layer.effect(
|
|
39
|
+
CurrentWorkingDirectory,
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const root = yield* LivestoreWorkspace
|
|
42
|
+
return path.join(root, subPath ?? '')
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -59,7 +59,7 @@ Vitest.describe('WranglerDevServer', { timeout: testTimeout }, () => {
|
|
|
59
59
|
WranglerDevServerTest({
|
|
60
60
|
cwd: '/tmp',
|
|
61
61
|
wranglerConfigPath: '/dev/null',
|
|
62
|
-
connectTimeout: '500 millis',
|
|
62
|
+
readiness: { connectTimeout: '500 millis' },
|
|
63
63
|
}).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
|
|
64
64
|
),
|
|
65
65
|
Effect.flip,
|
|
@@ -24,17 +24,35 @@ export interface WranglerDevServer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Readiness and retry configuration for wrangler boot and HTTP health.
|
|
28
|
+
*
|
|
29
|
+
* Example: startupTimeout=20s, connectTimeout=5s, retrySchedule=recurs(1)
|
|
30
|
+
* - Give wrangler up to 20s to boot; if it succeeds, give the HTTP check up to 5s.
|
|
31
|
+
* - If wrangler fails/times out, retry boot once; each boot attempt gets its own 20s budget.
|
|
32
|
+
* connectTimeout should be shorter than startupTimeout because HTTP readiness should be fast after boot.
|
|
28
33
|
*/
|
|
34
|
+
export interface WranglerReadinessOptions {
|
|
35
|
+
/** Max time to wait for wrangler to report ready before retrying. */
|
|
36
|
+
startupTimeout?: Duration.DurationInput
|
|
37
|
+
/** Max time for the HTTP connectivity check after wrangler reports ready. */
|
|
38
|
+
connectTimeout?: Duration.DurationInput
|
|
39
|
+
/** Retry policy for startup attempts (applies when startupTimeout elapses or wrangler throws). */
|
|
40
|
+
retrySchedule?: Schedule.Schedule<unknown, unknown, never>
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
export interface StartWranglerDevServerArgs {
|
|
44
|
+
/** Path to wrangler.toml (defaults to cwd/wrangler.toml). */
|
|
30
45
|
wranglerConfigPath?: string
|
|
46
|
+
/** Working directory wrangler should use. */
|
|
31
47
|
cwd: string
|
|
32
48
|
/** The port to try first. The dev server may bind a different port if unavailable. */
|
|
33
49
|
preferredPort?: number
|
|
34
50
|
/** @default false */
|
|
35
51
|
showLogs?: boolean
|
|
52
|
+
/** Optional inspector port for wrangler dev. */
|
|
36
53
|
inspectorPort?: number
|
|
37
|
-
|
|
54
|
+
/** Readiness and retry configuration for bringing up wrangler and confirming connectivity. */
|
|
55
|
+
readiness?: WranglerReadinessOptions
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
/**
|
|
@@ -74,6 +92,8 @@ export class WranglerDevServerService extends Effect.Service<WranglerDevServerSe
|
|
|
74
92
|
)
|
|
75
93
|
const resolvedMainPath = yield* Effect.try(() => path.resolve(args.cwd, parsedConfig.main))
|
|
76
94
|
|
|
95
|
+
const readiness = args.readiness ?? {}
|
|
96
|
+
const startupTimeout = readiness.startupTimeout ?? Duration.seconds(IS_CI ? 30 : 10)
|
|
77
97
|
const devServer = yield* Effect.promise(() =>
|
|
78
98
|
wrangler.unstable_dev(resolvedMainPath, {
|
|
79
99
|
config: configPath,
|
|
@@ -85,6 +105,24 @@ export class WranglerDevServerService extends Effect.Service<WranglerDevServerSe
|
|
|
85
105
|
disableExperimentalWarning: true,
|
|
86
106
|
},
|
|
87
107
|
}),
|
|
108
|
+
).pipe(
|
|
109
|
+
Effect.timeout(startupTimeout),
|
|
110
|
+
Effect.mapError(
|
|
111
|
+
(cause) =>
|
|
112
|
+
new WranglerDevServerError({
|
|
113
|
+
cause,
|
|
114
|
+
message: `Failed to start wrangler dev server within ${Duration.format(startupTimeout)}`,
|
|
115
|
+
port: preferredPort,
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
Effect.tapError((error) =>
|
|
119
|
+
Effect.logError('Wrangler dev server failed to start', {
|
|
120
|
+
message: error.message,
|
|
121
|
+
preferredPort,
|
|
122
|
+
cwd: args.cwd,
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
Effect.retry(readiness.retrySchedule ?? Schedule.recurs(1)),
|
|
88
126
|
)
|
|
89
127
|
|
|
90
128
|
yield* Effect.addFinalizer(
|
|
@@ -111,10 +149,11 @@ export class WranglerDevServerService extends Effect.Service<WranglerDevServerSe
|
|
|
111
149
|
const actualHost = devServer.address
|
|
112
150
|
const url = `http://${actualHost}:${actualPort}`
|
|
113
151
|
|
|
114
|
-
// Use longer timeout in CI environments to account for slower
|
|
115
|
-
const
|
|
152
|
+
// Use longer timeout in CI environments to account for slower HTTP readiness
|
|
153
|
+
const defaultConnectivityTimeout = Duration.seconds(IS_CI ? 30 : 5)
|
|
154
|
+
const connectivityTimeout = readiness.connectTimeout ?? defaultConnectivityTimeout
|
|
116
155
|
|
|
117
|
-
yield* verifyHttpConnectivity({ url, showLogs, connectTimeout:
|
|
156
|
+
yield* verifyHttpConnectivity({ url, showLogs, connectTimeout: connectivityTimeout })
|
|
118
157
|
|
|
119
158
|
if (showLogs) {
|
|
120
159
|
yield* Effect.logDebug(
|
|
@@ -127,9 +166,10 @@ export class WranglerDevServerService extends Effect.Service<WranglerDevServerSe
|
|
|
127
166
|
url,
|
|
128
167
|
} satisfies WranglerDevServer
|
|
129
168
|
}).pipe(
|
|
130
|
-
Effect.mapError(
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
Effect.mapError((error) =>
|
|
170
|
+
error instanceof WranglerDevServerError
|
|
171
|
+
? error
|
|
172
|
+
: new WranglerDevServerError({ cause: error, message: 'Failed to start wrangler dev server', port: -1 }),
|
|
133
173
|
),
|
|
134
174
|
Effect.withSpan('WranglerDevServerService', {
|
|
135
175
|
attributes: { preferredPort: args.preferredPort ?? 'auto', cwd: args.cwd },
|