@saeeol/core 7.3.2 → 7.3.4

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
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "7.3.2",
3
+ "version": "7.3.4",
4
4
  "name": "@saeeol/core",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -48,5 +48,8 @@
48
48
  "overrides": {
49
49
  "drizzle-orm": "1.0.0-beta.19-d95b7a4"
50
50
  },
51
- "peerDependencies": {}
51
+ "peerDependencies": {},
52
+ "files": [
53
+ "src"
54
+ ]
52
55
  }
package/sst-env.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /* This file is auto-generated by SST. Do not edit. */
2
- /* tslint:disable */
3
- /* eslint-disable */
4
- /* deno-fmt-ignore-file */
5
- /* biome-ignore-all lint: auto-generated */
6
-
7
- /// <reference path="../../sst-env.d.ts" />
8
-
9
- import "sst"
10
- export {}
@@ -1,423 +0,0 @@
1
- import { describe, expect } from "bun:test"
2
- import fs from "node:fs/promises"
3
- import os from "node:os"
4
- import path from "node:path"
5
- import { Effect, Exit, Stream } from "effect"
6
- import type * as PlatformError from "effect/PlatformError"
7
- import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
8
- import { CrossSpawnSpawner } from "@saeeol/core/cross-spawn-spawner"
9
- import { testEffect } from "../lib/effect"
10
-
11
- const live = CrossSpawnSpawner.defaultLayer
12
- const fx = testEffect(live)
13
-
14
- function js(code: string, opts?: ChildProcess.CommandOptions) {
15
- return ChildProcess.make("node", ["-e", code], opts)
16
- }
17
-
18
- function decodeByteStream(stream: Stream.Stream<Uint8Array, PlatformError.PlatformError>) {
19
- return Stream.runCollect(stream).pipe(
20
- Effect.map((chunks) => {
21
- const total = chunks.reduce((acc, x) => acc + x.length, 0)
22
- const out = new Uint8Array(total)
23
- let off = 0
24
- for (const chunk of chunks) {
25
- out.set(chunk, off)
26
- off += chunk.length
27
- }
28
- return new TextDecoder("utf-8").decode(out).trim()
29
- }),
30
- )
31
- }
32
-
33
- function alive(pid: number) {
34
- try {
35
- process.kill(pid, 0)
36
- return true
37
- } catch {
38
- return false
39
- }
40
- }
41
-
42
- async function tmpdir() {
43
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), "saeeol-core-test-"))
44
- return {
45
- path: dir,
46
- async [Symbol.asyncDispose]() {
47
- await fs.rm(dir, { recursive: true, force: true })
48
- },
49
- }
50
- }
51
-
52
- async function gone(pid: number, timeout = 5_000) {
53
- const end = Date.now() + timeout
54
- while (Date.now() < end) {
55
- if (!alive(pid)) return true
56
- await new Promise((resolve) => setTimeout(resolve, 50))
57
- }
58
- return !alive(pid)
59
- }
60
-
61
- describe("cross-spawn spawner", () => {
62
- describe("basic spawning", () => {
63
- fx.effect(
64
- "captures stdout",
65
- Effect.gen(function* () {
66
- const out = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) =>
67
- svc.string(ChildProcess.make(process.execPath, ["-e", 'process.stdout.write("ok")'])),
68
- )
69
- expect(out).toBe("ok")
70
- }),
71
- )
72
-
73
- fx.effect(
74
- "captures multiple lines",
75
- Effect.gen(function* () {
76
- const handle = yield* js('console.log("line1"); console.log("line2"); console.log("line3")')
77
- const out = yield* decodeByteStream(handle.stdout)
78
- expect(out).toBe("line1\nline2\nline3")
79
- }),
80
- )
81
-
82
- fx.effect(
83
- "returns exit code",
84
- Effect.gen(function* () {
85
- const handle = yield* js("process.exit(0)")
86
- const code = yield* handle.exitCode
87
- expect(code).toBe(ChildProcessSpawner.ExitCode(0))
88
- }),
89
- )
90
-
91
- fx.effect(
92
- "returns non-zero exit code",
93
- Effect.gen(function* () {
94
- const handle = yield* js("process.exit(42)")
95
- const code = yield* handle.exitCode
96
- expect(code).toBe(ChildProcessSpawner.ExitCode(42))
97
- }),
98
- )
99
- })
100
-
101
- describe("cwd option", () => {
102
- fx.effect(
103
- "uses cwd when spawning commands",
104
- Effect.gen(function* () {
105
- const tmp = yield* Effect.acquireRelease(
106
- Effect.promise(() => tmpdir()),
107
- (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
108
- )
109
- const out = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) =>
110
- svc.string(
111
- ChildProcess.make(process.execPath, ["-e", "process.stdout.write(process.cwd())"], { cwd: tmp.path }),
112
- ),
113
- )
114
- expect(out).toBe(tmp.path)
115
- }),
116
- )
117
-
118
- fx.effect(
119
- "fails for invalid cwd",
120
- Effect.gen(function* () {
121
- const exit = yield* Effect.exit(
122
- ChildProcess.make("echo", ["test"], { cwd: "/nonexistent/directory/path" }).asEffect(),
123
- )
124
- expect(Exit.isFailure(exit)).toBe(true)
125
- }),
126
- )
127
- })
128
-
129
- describe("env option", () => {
130
- fx.effect(
131
- "passes environment variables with extendEnv",
132
- Effect.gen(function* () {
133
- const handle = yield* js('process.stdout.write(process.env.TEST_VAR ?? "")', {
134
- env: { TEST_VAR: "test_value" },
135
- extendEnv: true,
136
- })
137
- const out = yield* decodeByteStream(handle.stdout)
138
- expect(out).toBe("test_value")
139
- }),
140
- )
141
-
142
- fx.effect(
143
- "passes multiple environment variables",
144
- Effect.gen(function* () {
145
- const handle = yield* js(
146
- "process.stdout.write(`${process.env.VAR1}-${process.env.VAR2}-${process.env.VAR3}`)",
147
- {
148
- env: { VAR1: "one", VAR2: "two", VAR3: "three" },
149
- extendEnv: true,
150
- },
151
- )
152
- const out = yield* decodeByteStream(handle.stdout)
153
- expect(out).toBe("one-two-three")
154
- }),
155
- )
156
- })
157
-
158
- describe("stderr", () => {
159
- fx.effect(
160
- "captures stderr output",
161
- Effect.gen(function* () {
162
- const handle = yield* js('process.stderr.write("error message")')
163
- const err = yield* decodeByteStream(handle.stderr)
164
- expect(err).toBe("error message")
165
- }),
166
- )
167
-
168
- fx.effect(
169
- "captures both stdout and stderr",
170
- Effect.gen(function* () {
171
- const handle = yield* js(
172
- [
173
- "let pending = 2",
174
- "const done = () => {",
175
- " pending -= 1",
176
- " if (pending === 0) setTimeout(() => process.exit(0), 0)",
177
- "}",
178
- 'process.stdout.write("stdout\\n", done)',
179
- 'process.stderr.write("stderr\\n", done)',
180
- ].join("\n"),
181
- )
182
- const [stdout, stderr] = yield* Effect.all([decodeByteStream(handle.stdout), decodeByteStream(handle.stderr)], {
183
- concurrency: 2,
184
- })
185
- expect(stdout).toBe("stdout")
186
- expect(stderr).toBe("stderr")
187
- }),
188
- )
189
- })
190
-
191
- describe("combined output (all)", () => {
192
- fx.effect(
193
- "captures stdout via .all when no stderr",
194
- Effect.gen(function* () {
195
- const handle = yield* ChildProcess.make("echo", ["hello from stdout"])
196
- const all = yield* decodeByteStream(handle.all)
197
- expect(all).toBe("hello from stdout")
198
- }),
199
- )
200
-
201
- fx.effect(
202
- "captures stderr via .all when no stdout",
203
- Effect.gen(function* () {
204
- const handle = yield* js('process.stderr.write("hello from stderr")')
205
- const all = yield* decodeByteStream(handle.all)
206
- expect(all).toBe("hello from stderr")
207
- }),
208
- )
209
- })
210
-
211
- describe("stdin", () => {
212
- fx.effect(
213
- "allows providing standard input to a command",
214
- Effect.gen(function* () {
215
- const input = "a b c"
216
- const stdin = Stream.make(Buffer.from(input, "utf-8"))
217
- const handle = yield* js(
218
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out))',
219
- { stdin },
220
- )
221
- const out = yield* decodeByteStream(handle.stdout)
222
- yield* handle.exitCode
223
- expect(out).toBe("a b c")
224
- }),
225
- )
226
- })
227
-
228
- describe("process control", () => {
229
- fx.effect(
230
- "kills a running process",
231
- Effect.gen(function* () {
232
- const exit = yield* Effect.exit(
233
- Effect.gen(function* () {
234
- const handle = yield* js("setTimeout(() => {}, 10_000)")
235
- yield* handle.kill()
236
- return yield* handle.exitCode
237
- }),
238
- )
239
- expect(Exit.isFailure(exit) ? true : exit.value !== ChildProcessSpawner.ExitCode(0)).toBe(true)
240
- }),
241
- )
242
-
243
- fx.effect(
244
- "kills a child when scope exits",
245
- Effect.gen(function* () {
246
- const pid = yield* Effect.scoped(
247
- Effect.gen(function* () {
248
- const handle = yield* js("setInterval(() => {}, 10_000)")
249
- return Number(handle.pid)
250
- }),
251
- )
252
- const done = yield* Effect.promise(() => gone(pid))
253
- expect(done).toBe(true)
254
- }),
255
- )
256
-
257
- fx.effect(
258
- "forceKillAfter escalates for stubborn processes",
259
- Effect.gen(function* () {
260
- if (process.platform === "win32") return
261
-
262
- const started = Date.now()
263
- const exit = yield* Effect.exit(
264
- Effect.gen(function* () {
265
- const handle = yield* js('process.on("SIGTERM", () => {}); setInterval(() => {}, 10_000)')
266
- yield* handle.kill({ forceKillAfter: 100 })
267
- return yield* handle.exitCode
268
- }),
269
- )
270
-
271
- expect(Date.now() - started).toBeLessThan(1_000)
272
- expect(Exit.isFailure(exit) ? true : exit.value !== ChildProcessSpawner.ExitCode(0)).toBe(true)
273
- }),
274
- )
275
-
276
- fx.effect(
277
- "isRunning reflects process state",
278
- Effect.gen(function* () {
279
- const handle = yield* js('process.stdout.write("done")')
280
- yield* handle.exitCode
281
- const running = yield* handle.isRunning
282
- expect(running).toBe(false)
283
- }),
284
- )
285
- })
286
-
287
- describe("error handling", () => {
288
- fx.effect(
289
- "fails for invalid command",
290
- Effect.gen(function* () {
291
- const exit = yield* Effect.exit(
292
- Effect.gen(function* () {
293
- const handle = yield* ChildProcess.make("nonexistent-command-12345")
294
- return yield* handle.exitCode
295
- }),
296
- )
297
- expect(Exit.isFailure(exit) ? true : exit.value !== ChildProcessSpawner.ExitCode(0)).toBe(true)
298
- }),
299
- )
300
- })
301
-
302
- describe("pipeline", () => {
303
- fx.effect(
304
- "pipes stdout of one command to stdin of another",
305
- Effect.gen(function* () {
306
- const handle = yield* js('process.stdout.write("hello world")').pipe(
307
- ChildProcess.pipeTo(
308
- js(
309
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out.toUpperCase()))',
310
- ),
311
- ),
312
- )
313
- const out = yield* decodeByteStream(handle.stdout)
314
- yield* handle.exitCode
315
- expect(out).toBe("HELLO WORLD")
316
- }),
317
- )
318
-
319
- fx.effect(
320
- "three-stage pipeline",
321
- Effect.gen(function* () {
322
- const handle = yield* js('process.stdout.write("hello world")').pipe(
323
- ChildProcess.pipeTo(
324
- js(
325
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out.toUpperCase()))',
326
- ),
327
- ),
328
- ChildProcess.pipeTo(
329
- js(
330
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out.replaceAll(" ", "-")))',
331
- ),
332
- ),
333
- )
334
- const out = yield* decodeByteStream(handle.stdout)
335
- yield* handle.exitCode
336
- expect(out).toBe("HELLO-WORLD")
337
- }),
338
- )
339
-
340
- fx.effect(
341
- "pipes stderr with { from: 'stderr' }",
342
- Effect.gen(function* () {
343
- const handle = yield* js('process.stderr.write("error")').pipe(
344
- ChildProcess.pipeTo(
345
- js(
346
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out))',
347
- ),
348
- { from: "stderr" },
349
- ),
350
- )
351
- const out = yield* decodeByteStream(handle.stdout)
352
- yield* handle.exitCode
353
- expect(out).toBe("error")
354
- }),
355
- )
356
-
357
- fx.effect(
358
- "pipes combined output with { from: 'all' }",
359
- Effect.gen(function* () {
360
- const handle = yield* js('process.stdout.write("stdout\\n"); process.stderr.write("stderr\\n")').pipe(
361
- ChildProcess.pipeTo(
362
- js(
363
- 'process.stdin.setEncoding("utf8"); let out = ""; process.stdin.on("data", (chunk) => out += chunk); process.stdin.on("end", () => process.stdout.write(out))',
364
- ),
365
- { from: "all" },
366
- ),
367
- )
368
- const out = yield* decodeByteStream(handle.stdout)
369
- yield* handle.exitCode
370
- expect(out).toContain("stdout")
371
- expect(out).toContain("stderr")
372
- }),
373
- )
374
- })
375
-
376
- describe("Windows-specific", () => {
377
- fx.effect(
378
- "uses shell routing on Windows",
379
- Effect.gen(function* () {
380
- if (process.platform !== "win32") return
381
-
382
- const out = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) =>
383
- svc.string(
384
- ChildProcess.make("set", ["SAEEOL_TEST_SHELL"], {
385
- shell: true,
386
- extendEnv: true,
387
- env: { SAEEOL_TEST_SHELL: "ok" },
388
- }),
389
- ),
390
- )
391
- expect(out).toContain("SAEEOL_TEST_SHELL=ok")
392
- }),
393
- )
394
-
395
- fx.effect(
396
- "runs cmd scripts with spaces on Windows without shell",
397
- Effect.gen(function* () {
398
- if (process.platform !== "win32") return
399
-
400
- const tmp = yield* Effect.acquireRelease(
401
- Effect.promise(() => tmpdir()),
402
- (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
403
- )
404
- const dir = path.join(tmp.path, "with space")
405
- const file = path.join(dir, "echo cmd.cmd")
406
-
407
- yield* Effect.promise(() => fs.mkdir(dir, { recursive: true }))
408
- yield* Effect.promise(() => fs.writeFile(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n"))
409
-
410
- const code = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) =>
411
- svc.exitCode(
412
- ChildProcess.make(file, ["--stdio"], {
413
- stdin: "pipe",
414
- stdout: "pipe",
415
- stderr: "pipe",
416
- }),
417
- ),
418
- )
419
- expect(code).toBe(ChildProcessSpawner.ExitCode(0))
420
- }),
421
- )
422
- })
423
- })
@@ -1,46 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test"
2
- import { resource } from "@saeeol/core/effect/observability"
3
-
4
- const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES
5
- const saeeolClient = process.env.SAEEOL_CLIENT
6
-
7
- afterEach(() => {
8
- if (otelResourceAttributes === undefined) delete process.env.OTEL_RESOURCE_ATTRIBUTES
9
- else process.env.OTEL_RESOURCE_ATTRIBUTES = otelResourceAttributes
10
-
11
- if (saeeolClient === undefined) delete process.env.SAEEOL_CLIENT
12
- else process.env.SAEEOL_CLIENT = saeeolClient
13
- })
14
-
15
- describe("resource", () => {
16
- test("parses and decodes OTEL resource attributes", () => {
17
- process.env.OTEL_RESOURCE_ATTRIBUTES =
18
- "service.namespace=saeeol,team=platform%2Cobservability,label=hello%3Dworld,key%2Fname=value%20here"
19
-
20
- expect(resource().attributes).toMatchObject({
21
- "service.namespace": "saeeol",
22
- team: "platform,observability",
23
- label: "hello=world",
24
- "key/name": "value here",
25
- })
26
- })
27
-
28
- test("drops OTEL resource attributes when any entry is invalid", () => {
29
- process.env.OTEL_RESOURCE_ATTRIBUTES = "service.namespace=saeeol,broken"
30
-
31
- expect(resource().attributes["service.namespace"]).toBeUndefined()
32
- expect(resource().attributes["saeeol.client"]).toBeDefined()
33
- })
34
-
35
- test("keeps built-in attributes when env values conflict", () => {
36
- process.env.SAEEOL_CLIENT = "cli"
37
- process.env.OTEL_RESOURCE_ATTRIBUTES =
38
- "saeeol.client=web,service.instance.id=override,service.namespace=saeeol"
39
-
40
- expect(resource().attributes).toMatchObject({
41
- "saeeol.client": "cli",
42
- "service.namespace": "saeeol",
43
- })
44
- expect(resource().attributes["service.instance.id"]).not.toBe("override")
45
- })
46
- })