@saeeol/core 7.3.2 → 7.3.5
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 +6 -2
- package/sst-env.d.ts +0 -10
- package/test/effect/cross-spawn-spawner.test.ts +0 -423
- package/test/effect/observability.test.ts +0 -46
- package/test/filesystem/filesystem.test.ts +0 -338
- package/test/fixture/effect-flock-worker.ts +0 -60
- package/test/fixture/flock-worker.ts +0 -72
- package/test/fixture/tmpdir.ts +0 -13
- package/test/global.test.ts +0 -16
- package/test/lib/effect.ts +0 -53
- package/test/npm-config.test.ts +0 -51
- package/test/npm.test.ts +0 -91
- package/test/saeeol/core-utils/filesystem-containment.test.ts +0 -13
- package/test/saeeol/filesystem-containment.test.ts +0 -13
- package/test/util/effect-flock.test.ts +0 -386
- package/test/util/flock.test.ts +0 -426
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test"
|
|
2
|
-
import { Effect, Layer, FileSystem } from "effect"
|
|
3
|
-
import { NodeFileSystem } from "@effect/platform-node"
|
|
4
|
-
import { AppFileSystem } from "@saeeol/core/filesystem"
|
|
5
|
-
import { testEffect } from "../lib/effect"
|
|
6
|
-
import path from "path"
|
|
7
|
-
|
|
8
|
-
const live = AppFileSystem.layer.pipe(Layer.provideMerge(NodeFileSystem.layer))
|
|
9
|
-
const { effect: it } = testEffect(live)
|
|
10
|
-
|
|
11
|
-
describe("AppFileSystem", () => {
|
|
12
|
-
describe("isDir", () => {
|
|
13
|
-
it(
|
|
14
|
-
"returns true for directories",
|
|
15
|
-
Effect.gen(function* () {
|
|
16
|
-
const fs = yield* AppFileSystem.Service
|
|
17
|
-
const filesys = yield* FileSystem.FileSystem
|
|
18
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
19
|
-
expect(yield* fs.isDir(tmp)).toBe(true)
|
|
20
|
-
}),
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
it(
|
|
24
|
-
"returns false for files",
|
|
25
|
-
Effect.gen(function* () {
|
|
26
|
-
const fs = yield* AppFileSystem.Service
|
|
27
|
-
const filesys = yield* FileSystem.FileSystem
|
|
28
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
29
|
-
const file = path.join(tmp, "test.txt")
|
|
30
|
-
yield* filesys.writeFileString(file, "hello")
|
|
31
|
-
expect(yield* fs.isDir(file)).toBe(false)
|
|
32
|
-
}),
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
it(
|
|
36
|
-
"returns false for non-existent paths",
|
|
37
|
-
Effect.gen(function* () {
|
|
38
|
-
const fs = yield* AppFileSystem.Service
|
|
39
|
-
expect(yield* fs.isDir("/tmp/nonexistent-" + Math.random())).toBe(false)
|
|
40
|
-
}),
|
|
41
|
-
)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
describe("isFile", () => {
|
|
45
|
-
it(
|
|
46
|
-
"returns true for files",
|
|
47
|
-
Effect.gen(function* () {
|
|
48
|
-
const fs = yield* AppFileSystem.Service
|
|
49
|
-
const filesys = yield* FileSystem.FileSystem
|
|
50
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
51
|
-
const file = path.join(tmp, "test.txt")
|
|
52
|
-
yield* filesys.writeFileString(file, "hello")
|
|
53
|
-
expect(yield* fs.isFile(file)).toBe(true)
|
|
54
|
-
}),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
it(
|
|
58
|
-
"returns false for directories",
|
|
59
|
-
Effect.gen(function* () {
|
|
60
|
-
const fs = yield* AppFileSystem.Service
|
|
61
|
-
const filesys = yield* FileSystem.FileSystem
|
|
62
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
63
|
-
expect(yield* fs.isFile(tmp)).toBe(false)
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe("readJson / writeJson", () => {
|
|
69
|
-
it(
|
|
70
|
-
"round-trips JSON data",
|
|
71
|
-
Effect.gen(function* () {
|
|
72
|
-
const fs = yield* AppFileSystem.Service
|
|
73
|
-
const filesys = yield* FileSystem.FileSystem
|
|
74
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
75
|
-
const file = path.join(tmp, "data.json")
|
|
76
|
-
const data = { name: "test", count: 42, nested: { ok: true } }
|
|
77
|
-
|
|
78
|
-
yield* fs.writeJson(file, data)
|
|
79
|
-
const result = yield* fs.readJson(file)
|
|
80
|
-
|
|
81
|
-
expect(result).toEqual(data)
|
|
82
|
-
}),
|
|
83
|
-
)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe("ensureDir", () => {
|
|
87
|
-
it(
|
|
88
|
-
"creates nested directories",
|
|
89
|
-
Effect.gen(function* () {
|
|
90
|
-
const fs = yield* AppFileSystem.Service
|
|
91
|
-
const filesys = yield* FileSystem.FileSystem
|
|
92
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
93
|
-
const nested = path.join(tmp, "a", "b", "c")
|
|
94
|
-
|
|
95
|
-
yield* fs.ensureDir(nested)
|
|
96
|
-
|
|
97
|
-
const info = yield* filesys.stat(nested)
|
|
98
|
-
expect(info.type).toBe("Directory")
|
|
99
|
-
}),
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
it(
|
|
103
|
-
"is idempotent",
|
|
104
|
-
Effect.gen(function* () {
|
|
105
|
-
const fs = yield* AppFileSystem.Service
|
|
106
|
-
const filesys = yield* FileSystem.FileSystem
|
|
107
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
108
|
-
const dir = path.join(tmp, "existing")
|
|
109
|
-
yield* filesys.makeDirectory(dir)
|
|
110
|
-
|
|
111
|
-
yield* fs.ensureDir(dir)
|
|
112
|
-
|
|
113
|
-
const info = yield* filesys.stat(dir)
|
|
114
|
-
expect(info.type).toBe("Directory")
|
|
115
|
-
}),
|
|
116
|
-
)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
describe("writeWithDirs", () => {
|
|
120
|
-
it(
|
|
121
|
-
"creates parent directories if missing",
|
|
122
|
-
Effect.gen(function* () {
|
|
123
|
-
const fs = yield* AppFileSystem.Service
|
|
124
|
-
const filesys = yield* FileSystem.FileSystem
|
|
125
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
126
|
-
const file = path.join(tmp, "deep", "nested", "file.txt")
|
|
127
|
-
|
|
128
|
-
yield* fs.writeWithDirs(file, "hello")
|
|
129
|
-
|
|
130
|
-
expect(yield* filesys.readFileString(file)).toBe("hello")
|
|
131
|
-
}),
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
it(
|
|
135
|
-
"writes directly when parent exists",
|
|
136
|
-
Effect.gen(function* () {
|
|
137
|
-
const fs = yield* AppFileSystem.Service
|
|
138
|
-
const filesys = yield* FileSystem.FileSystem
|
|
139
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
140
|
-
const file = path.join(tmp, "direct.txt")
|
|
141
|
-
|
|
142
|
-
yield* fs.writeWithDirs(file, "world")
|
|
143
|
-
|
|
144
|
-
expect(yield* filesys.readFileString(file)).toBe("world")
|
|
145
|
-
}),
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
it(
|
|
149
|
-
"writes Uint8Array content",
|
|
150
|
-
Effect.gen(function* () {
|
|
151
|
-
const fs = yield* AppFileSystem.Service
|
|
152
|
-
const filesys = yield* FileSystem.FileSystem
|
|
153
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
154
|
-
const file = path.join(tmp, "binary.bin")
|
|
155
|
-
const content = new Uint8Array([0x00, 0x01, 0x02, 0x03])
|
|
156
|
-
|
|
157
|
-
yield* fs.writeWithDirs(file, content)
|
|
158
|
-
|
|
159
|
-
const result = yield* filesys.readFile(file)
|
|
160
|
-
expect(new Uint8Array(result)).toEqual(content)
|
|
161
|
-
}),
|
|
162
|
-
)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe("findUp", () => {
|
|
166
|
-
it(
|
|
167
|
-
"finds target in start directory",
|
|
168
|
-
Effect.gen(function* () {
|
|
169
|
-
const fs = yield* AppFileSystem.Service
|
|
170
|
-
const filesys = yield* FileSystem.FileSystem
|
|
171
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
172
|
-
yield* filesys.writeFileString(path.join(tmp, "target.txt"), "found")
|
|
173
|
-
|
|
174
|
-
const result = yield* fs.findUp("target.txt", tmp)
|
|
175
|
-
expect(result).toEqual([path.join(tmp, "target.txt")])
|
|
176
|
-
}),
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
it(
|
|
180
|
-
"finds target in parent directories",
|
|
181
|
-
Effect.gen(function* () {
|
|
182
|
-
const fs = yield* AppFileSystem.Service
|
|
183
|
-
const filesys = yield* FileSystem.FileSystem
|
|
184
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
185
|
-
yield* filesys.writeFileString(path.join(tmp, "marker"), "root")
|
|
186
|
-
const child = path.join(tmp, "a", "b")
|
|
187
|
-
yield* filesys.makeDirectory(child, { recursive: true })
|
|
188
|
-
|
|
189
|
-
const result = yield* fs.findUp("marker", child, tmp)
|
|
190
|
-
expect(result).toEqual([path.join(tmp, "marker")])
|
|
191
|
-
}),
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
it(
|
|
195
|
-
"returns empty array when not found",
|
|
196
|
-
Effect.gen(function* () {
|
|
197
|
-
const fs = yield* AppFileSystem.Service
|
|
198
|
-
const filesys = yield* FileSystem.FileSystem
|
|
199
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
200
|
-
const result = yield* fs.findUp("nonexistent", tmp, tmp)
|
|
201
|
-
expect(result).toEqual([])
|
|
202
|
-
}),
|
|
203
|
-
)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
describe("up", () => {
|
|
207
|
-
it(
|
|
208
|
-
"finds multiple targets walking up",
|
|
209
|
-
Effect.gen(function* () {
|
|
210
|
-
const fs = yield* AppFileSystem.Service
|
|
211
|
-
const filesys = yield* FileSystem.FileSystem
|
|
212
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
213
|
-
yield* filesys.writeFileString(path.join(tmp, "a.txt"), "a")
|
|
214
|
-
yield* filesys.writeFileString(path.join(tmp, "b.txt"), "b")
|
|
215
|
-
const child = path.join(tmp, "sub")
|
|
216
|
-
yield* filesys.makeDirectory(child)
|
|
217
|
-
yield* filesys.writeFileString(path.join(child, "a.txt"), "a-child")
|
|
218
|
-
|
|
219
|
-
const result = yield* fs.up({ targets: ["a.txt", "b.txt"], start: child, stop: tmp })
|
|
220
|
-
|
|
221
|
-
expect(result).toContain(path.join(child, "a.txt"))
|
|
222
|
-
expect(result).toContain(path.join(tmp, "a.txt"))
|
|
223
|
-
expect(result).toContain(path.join(tmp, "b.txt"))
|
|
224
|
-
}),
|
|
225
|
-
)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
describe("glob", () => {
|
|
229
|
-
it(
|
|
230
|
-
"finds files matching pattern",
|
|
231
|
-
Effect.gen(function* () {
|
|
232
|
-
const fs = yield* AppFileSystem.Service
|
|
233
|
-
const filesys = yield* FileSystem.FileSystem
|
|
234
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
235
|
-
yield* filesys.writeFileString(path.join(tmp, "a.ts"), "a")
|
|
236
|
-
yield* filesys.writeFileString(path.join(tmp, "b.ts"), "b")
|
|
237
|
-
yield* filesys.writeFileString(path.join(tmp, "c.json"), "c")
|
|
238
|
-
|
|
239
|
-
const result = yield* fs.glob("*.ts", { cwd: tmp })
|
|
240
|
-
expect(result.sort()).toEqual(["a.ts", "b.ts"])
|
|
241
|
-
}),
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
it(
|
|
245
|
-
"supports absolute paths",
|
|
246
|
-
Effect.gen(function* () {
|
|
247
|
-
const fs = yield* AppFileSystem.Service
|
|
248
|
-
const filesys = yield* FileSystem.FileSystem
|
|
249
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
250
|
-
yield* filesys.writeFileString(path.join(tmp, "file.txt"), "hello")
|
|
251
|
-
|
|
252
|
-
const result = yield* fs.glob("*.txt", { cwd: tmp, absolute: true })
|
|
253
|
-
expect(result).toEqual([path.join(tmp, "file.txt")])
|
|
254
|
-
}),
|
|
255
|
-
)
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
describe("globMatch", () => {
|
|
259
|
-
it(
|
|
260
|
-
"matches patterns",
|
|
261
|
-
Effect.gen(function* () {
|
|
262
|
-
const fs = yield* AppFileSystem.Service
|
|
263
|
-
expect(fs.globMatch("*.ts", "foo.ts")).toBe(true)
|
|
264
|
-
expect(fs.globMatch("*.ts", "foo.json")).toBe(false)
|
|
265
|
-
expect(fs.globMatch("src/**", "src/a/b.ts")).toBe(true)
|
|
266
|
-
}),
|
|
267
|
-
)
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
describe("globUp", () => {
|
|
271
|
-
it(
|
|
272
|
-
"finds files walking up directories",
|
|
273
|
-
Effect.gen(function* () {
|
|
274
|
-
const fs = yield* AppFileSystem.Service
|
|
275
|
-
const filesys = yield* FileSystem.FileSystem
|
|
276
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
277
|
-
yield* filesys.writeFileString(path.join(tmp, "root.md"), "root")
|
|
278
|
-
const child = path.join(tmp, "a", "b")
|
|
279
|
-
yield* filesys.makeDirectory(child, { recursive: true })
|
|
280
|
-
yield* filesys.writeFileString(path.join(child, "leaf.md"), "leaf")
|
|
281
|
-
|
|
282
|
-
const result = yield* fs.globUp("*.md", child, tmp)
|
|
283
|
-
expect(result).toContain(path.join(child, "leaf.md"))
|
|
284
|
-
expect(result).toContain(path.join(tmp, "root.md"))
|
|
285
|
-
}),
|
|
286
|
-
)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
describe("built-in passthrough", () => {
|
|
290
|
-
it(
|
|
291
|
-
"exists works",
|
|
292
|
-
Effect.gen(function* () {
|
|
293
|
-
yield* AppFileSystem.Service
|
|
294
|
-
const filesys = yield* FileSystem.FileSystem
|
|
295
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
296
|
-
const file = path.join(tmp, "exists.txt")
|
|
297
|
-
yield* filesys.writeFileString(file, "yes")
|
|
298
|
-
|
|
299
|
-
expect(yield* filesys.exists(file)).toBe(true)
|
|
300
|
-
expect(yield* filesys.exists(file + ".nope")).toBe(false)
|
|
301
|
-
}),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
it(
|
|
305
|
-
"remove works",
|
|
306
|
-
Effect.gen(function* () {
|
|
307
|
-
yield* AppFileSystem.Service
|
|
308
|
-
const filesys = yield* FileSystem.FileSystem
|
|
309
|
-
const tmp = yield* filesys.makeTempDirectoryScoped()
|
|
310
|
-
const file = path.join(tmp, "delete-me.txt")
|
|
311
|
-
yield* filesys.writeFileString(file, "bye")
|
|
312
|
-
|
|
313
|
-
yield* filesys.remove(file)
|
|
314
|
-
|
|
315
|
-
expect(yield* filesys.exists(file)).toBe(false)
|
|
316
|
-
}),
|
|
317
|
-
)
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
describe("pure helpers", () => {
|
|
321
|
-
test("mimeType returns correct types", () => {
|
|
322
|
-
expect(AppFileSystem.mimeType("file.json")).toBe("application/json")
|
|
323
|
-
expect(AppFileSystem.mimeType("image.png")).toBe("image/png")
|
|
324
|
-
expect(AppFileSystem.mimeType("unknown.qzx")).toBe("application/octet-stream")
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
test("contains checks path containment", () => {
|
|
328
|
-
expect(AppFileSystem.contains("/a/b", "/a/b/c")).toBe(true)
|
|
329
|
-
expect(AppFileSystem.contains("/a/b", "/a/c")).toBe(false)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
test("overlaps detects overlapping paths", () => {
|
|
333
|
-
expect(AppFileSystem.overlaps("/a/b", "/a/b/c")).toBe(true)
|
|
334
|
-
expect(AppFileSystem.overlaps("/a/b/c", "/a/b")).toBe(true)
|
|
335
|
-
expect(AppFileSystem.overlaps("/a", "/b")).toBe(false)
|
|
336
|
-
})
|
|
337
|
-
})
|
|
338
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises"
|
|
2
|
-
import os from "os"
|
|
3
|
-
import { Effect, Layer } from "effect"
|
|
4
|
-
import { AppFileSystem } from "@saeeol/core/filesystem"
|
|
5
|
-
import { EffectFlock } from "@saeeol/core/util/effect-flock"
|
|
6
|
-
import { Global } from "@saeeol/core/global"
|
|
7
|
-
|
|
8
|
-
type Msg = {
|
|
9
|
-
key: string
|
|
10
|
-
dir: string
|
|
11
|
-
holdMs?: number
|
|
12
|
-
ready?: string
|
|
13
|
-
active?: string
|
|
14
|
-
done?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function sleep(ms: number) {
|
|
18
|
-
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const msg: Msg = JSON.parse(process.argv[2])
|
|
22
|
-
|
|
23
|
-
const testGlobal = Global.layerWith({
|
|
24
|
-
home: os.homedir(),
|
|
25
|
-
data: os.tmpdir(),
|
|
26
|
-
cache: os.tmpdir(),
|
|
27
|
-
config: os.tmpdir(),
|
|
28
|
-
state: os.tmpdir(),
|
|
29
|
-
bin: os.tmpdir(),
|
|
30
|
-
log: os.tmpdir(),
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const testLayer = EffectFlock.layer.pipe(Layer.provide(testGlobal), Layer.provide(AppFileSystem.defaultLayer))
|
|
34
|
-
|
|
35
|
-
async function job() {
|
|
36
|
-
if (msg.ready) await fs.writeFile(msg.ready, String(process.pid))
|
|
37
|
-
if (msg.active) await fs.writeFile(msg.active, String(process.pid), { flag: "wx" })
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
if (msg.holdMs && msg.holdMs > 0) await sleep(msg.holdMs)
|
|
41
|
-
if (msg.done) await fs.appendFile(msg.done, "1\n")
|
|
42
|
-
} finally {
|
|
43
|
-
if (msg.active) await fs.rm(msg.active, { force: true })
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
await Effect.runPromise(
|
|
48
|
-
Effect.gen(function* () {
|
|
49
|
-
const flock = yield* EffectFlock.Service
|
|
50
|
-
yield* flock.withLock(
|
|
51
|
-
Effect.promise(() => job()),
|
|
52
|
-
msg.key,
|
|
53
|
-
msg.dir,
|
|
54
|
-
)
|
|
55
|
-
}).pipe(Effect.provide(testLayer)),
|
|
56
|
-
).catch((err) => {
|
|
57
|
-
const text = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
|
58
|
-
process.stderr.write(text)
|
|
59
|
-
process.exit(1)
|
|
60
|
-
})
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises"
|
|
2
|
-
import { Flock } from "@saeeol/core/util/flock"
|
|
3
|
-
|
|
4
|
-
type Msg = {
|
|
5
|
-
key: string
|
|
6
|
-
dir: string
|
|
7
|
-
staleMs?: number
|
|
8
|
-
timeoutMs?: number
|
|
9
|
-
baseDelayMs?: number
|
|
10
|
-
maxDelayMs?: number
|
|
11
|
-
holdMs?: number
|
|
12
|
-
ready?: string
|
|
13
|
-
active?: string
|
|
14
|
-
done?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function sleep(ms: number) {
|
|
18
|
-
return new Promise<void>((resolve) => {
|
|
19
|
-
setTimeout(resolve, ms)
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function input() {
|
|
24
|
-
const raw = process.argv[2]
|
|
25
|
-
if (!raw) {
|
|
26
|
-
throw new Error("Missing flock worker input")
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return JSON.parse(raw) as Msg
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function job(input: Msg) {
|
|
33
|
-
if (input.ready) {
|
|
34
|
-
await fs.writeFile(input.ready, String(process.pid))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (input.active) {
|
|
38
|
-
await fs.writeFile(input.active, String(process.pid), { flag: "wx" })
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
if (input.holdMs && input.holdMs > 0) {
|
|
43
|
-
await sleep(input.holdMs)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (input.done) {
|
|
47
|
-
await fs.appendFile(input.done, "1\n")
|
|
48
|
-
}
|
|
49
|
-
} finally {
|
|
50
|
-
if (input.active) {
|
|
51
|
-
await fs.rm(input.active, { force: true })
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function main() {
|
|
57
|
-
const msg = input()
|
|
58
|
-
|
|
59
|
-
await Flock.withLock(msg.key, () => job(msg), {
|
|
60
|
-
dir: msg.dir,
|
|
61
|
-
staleMs: msg.staleMs,
|
|
62
|
-
timeoutMs: msg.timeoutMs,
|
|
63
|
-
baseDelayMs: msg.baseDelayMs,
|
|
64
|
-
maxDelayMs: msg.maxDelayMs,
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
await main().catch((err) => {
|
|
69
|
-
const text = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
|
70
|
-
process.stderr.write(text)
|
|
71
|
-
process.exit(1)
|
|
72
|
-
})
|
package/test/fixture/tmpdir.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises"
|
|
2
|
-
import { tmpdir as osTmpdir } from "os"
|
|
3
|
-
import path from "path"
|
|
4
|
-
|
|
5
|
-
export const tmpdir = async () => {
|
|
6
|
-
const dir = await fs.mkdtemp(path.join(osTmpdir(), "saeeol-core-test-"))
|
|
7
|
-
return {
|
|
8
|
-
path: dir,
|
|
9
|
-
async [Symbol.asyncDispose]() {
|
|
10
|
-
await fs.rm(dir, { recursive: true, force: true })
|
|
11
|
-
},
|
|
12
|
-
}
|
|
13
|
-
}
|
package/test/global.test.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import fs from "fs/promises"
|
|
3
|
-
import os from "os"
|
|
4
|
-
import path from "path"
|
|
5
|
-
import { Global } from "@saeeol/core/global"
|
|
6
|
-
|
|
7
|
-
describe("global paths", () => {
|
|
8
|
-
test("tmp path is under the system temp directory", () => {
|
|
9
|
-
expect(Global.Path.tmp).toBe(path.join(os.tmpdir(), "saeeol"))
|
|
10
|
-
expect(Global.make().tmp).toBe(Global.Path.tmp)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
test("tmp path is created on module load", async () => {
|
|
14
|
-
expect((await fs.stat(Global.Path.tmp)).isDirectory()).toBe(true)
|
|
15
|
-
})
|
|
16
|
-
})
|
package/test/lib/effect.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { test, type TestOptions } from "bun:test"
|
|
2
|
-
import { Cause, Effect, Exit, Layer } from "effect"
|
|
3
|
-
import type * as Scope from "effect/Scope"
|
|
4
|
-
import * as TestClock from "effect/testing/TestClock"
|
|
5
|
-
import * as TestConsole from "effect/testing/TestConsole"
|
|
6
|
-
|
|
7
|
-
type Body<A, E, R> = Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)
|
|
8
|
-
|
|
9
|
-
const body = <A, E, R>(value: Body<A, E, R>) => Effect.suspend(() => (typeof value === "function" ? value() : value))
|
|
10
|
-
|
|
11
|
-
const run = <A, E, R, E2>(value: Body<A, E, R | Scope.Scope>, layer: Layer.Layer<R, E2>) =>
|
|
12
|
-
Effect.gen(function* () {
|
|
13
|
-
const exit = yield* body(value).pipe(Effect.scoped, Effect.provide(layer), Effect.exit)
|
|
14
|
-
if (Exit.isFailure(exit)) {
|
|
15
|
-
for (const err of Cause.prettyErrors(exit.cause)) {
|
|
16
|
-
yield* Effect.logError(err)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return yield* exit
|
|
20
|
-
}).pipe(Effect.runPromise)
|
|
21
|
-
|
|
22
|
-
const make = <R, E>(testLayer: Layer.Layer<R, E>, liveLayer: Layer.Layer<R, E>) => {
|
|
23
|
-
const effect = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
24
|
-
test(name, () => run(value, testLayer), opts)
|
|
25
|
-
|
|
26
|
-
effect.only = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
27
|
-
test.only(name, () => run(value, testLayer), opts)
|
|
28
|
-
|
|
29
|
-
effect.skip = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
30
|
-
test.skip(name, () => run(value, testLayer), opts)
|
|
31
|
-
|
|
32
|
-
const live = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
33
|
-
test(name, () => run(value, liveLayer), opts)
|
|
34
|
-
|
|
35
|
-
live.only = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
36
|
-
test.only(name, () => run(value, liveLayer), opts)
|
|
37
|
-
|
|
38
|
-
live.skip = <A, E2>(name: string, value: Body<A, E2, R | Scope.Scope>, opts?: number | TestOptions) =>
|
|
39
|
-
test.skip(name, () => run(value, liveLayer), opts)
|
|
40
|
-
|
|
41
|
-
return { effect, live }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Test environment with TestClock and TestConsole
|
|
45
|
-
const testEnv = Layer.mergeAll(TestConsole.layer, TestClock.layer())
|
|
46
|
-
|
|
47
|
-
// Live environment - uses real clock, but keeps TestConsole for output capture
|
|
48
|
-
const liveEnv = TestConsole.layer
|
|
49
|
-
|
|
50
|
-
export const it = make(testEnv, liveEnv)
|
|
51
|
-
|
|
52
|
-
export const testEffect = <R, E>(layer: Layer.Layer<R, E>) =>
|
|
53
|
-
make(Layer.provideMerge(layer, testEnv), Layer.provideMerge(layer, liveEnv))
|
package/test/npm-config.test.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import path from "path"
|
|
2
|
-
import { describe, expect, test } from "bun:test"
|
|
3
|
-
import { Effect } from "effect"
|
|
4
|
-
import { NpmConfig } from "@saeeol/core/npm-config"
|
|
5
|
-
import { tmpdir } from "./fixture/tmpdir"
|
|
6
|
-
|
|
7
|
-
describe("NpmConfig.load", () => {
|
|
8
|
-
test("reads registry from project .npmrc", async () => {
|
|
9
|
-
await using tmp = await tmpdir()
|
|
10
|
-
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test/\n")
|
|
11
|
-
|
|
12
|
-
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
|
13
|
-
|
|
14
|
-
expect(config.registry).toBe("https://registry.example.test/")
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test("reads scoped registries from project .npmrc", async () => {
|
|
18
|
-
await using tmp = await tmpdir()
|
|
19
|
-
await Bun.write(path.join(tmp.path, ".npmrc"), "@acme:registry=https://npm.acme.test/\n")
|
|
20
|
-
|
|
21
|
-
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
|
22
|
-
|
|
23
|
-
expect(config["@acme:registry"]).toBe("https://npm.acme.test/")
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test("flattens boolean and list options", async () => {
|
|
27
|
-
await using tmp = await tmpdir()
|
|
28
|
-
await Bun.write(path.join(tmp.path, ".npmrc"), "ignore-scripts=true\nomit[]=dev\nomit[]=optional\n")
|
|
29
|
-
|
|
30
|
-
const config = await Effect.runPromise(NpmConfig.load(tmp.path))
|
|
31
|
-
|
|
32
|
-
expect(config.ignoreScripts).toBe(true)
|
|
33
|
-
expect(config.omit).toEqual(["dev", "optional"])
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe("NpmConfig.registry", () => {
|
|
38
|
-
test("normalizes configured registry without trailing slash", async () => {
|
|
39
|
-
await using tmp = await tmpdir()
|
|
40
|
-
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test/\n")
|
|
41
|
-
|
|
42
|
-
await expect(Effect.runPromise(NpmConfig.registry(tmp.path))).resolves.toBe("https://registry.example.test")
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test("leaves configured registry without trailing slash unchanged", async () => {
|
|
46
|
-
await using tmp = await tmpdir()
|
|
47
|
-
await Bun.write(path.join(tmp.path, ".npmrc"), "registry=https://registry.example.test\n")
|
|
48
|
-
|
|
49
|
-
await expect(Effect.runPromise(NpmConfig.registry(tmp.path))).resolves.toBe("https://registry.example.test")
|
|
50
|
-
})
|
|
51
|
-
})
|