@rikalabs/logpoint 0.0.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.
@@ -0,0 +1,400 @@
1
+ import type { Language } from "./language.js";
2
+
3
+ export type GoTemplateRefs = {
4
+ bytesRef: string;
5
+ jsonRef: string;
6
+ httpRef: string;
7
+ timeRef: string;
8
+ };
9
+
10
+ export type TemplateArgs = {
11
+ readonly id: string;
12
+ readonly file: string;
13
+ readonly line: number;
14
+ readonly label: string;
15
+ readonly hypothesis: string;
16
+ readonly capture: readonly string[];
17
+ readonly maxHits: number;
18
+ readonly port: number;
19
+ readonly goRefs?: GoTemplateRefs;
20
+ };
21
+
22
+ export type TemplateOutput = {
23
+ readonly lines: readonly string[];
24
+ readonly goRefs?: GoTemplateRefs;
25
+ };
26
+
27
+ export const goImportPaths = {
28
+ bytes: "bytes",
29
+ json: "encoding/json",
30
+ http: "net/http",
31
+ time: "time",
32
+ } as const;
33
+
34
+ const jsonQuote = (value: string): string => JSON.stringify(value);
35
+
36
+ const sanitizeIdentifier = (value: string): string => value.replace(/[^a-zA-Z0-9_]/g, "_");
37
+
38
+ const jsTemplate = (args: TemplateArgs): readonly string[] => {
39
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
40
+ const vars = args.capture
41
+ .map((entry) => `${jsonQuote(entry)}:__lpVal(()=>(${entry}))`)
42
+ .join(",");
43
+
44
+ return [
45
+ `// LOGPOINT_START [${args.id}] - ${args.label}`,
46
+ `;(()=>{try{globalThis.${counter}=(globalThis.${counter}??0)+1;if(globalThis.${counter}>${args.maxHits})return;const __lpSeen=new WeakSet();const __lpSer=(v)=>{try{return JSON.stringify(v,(_k,x)=>{if(typeof x==="object"&&x!==null){if(__lpSeen.has(x))return"[Circular]";__lpSeen.add(x)}return x})}catch{return JSON.stringify(String(v))}};const __lpVal=(fn)=>{try{const __raw=fn();const __txt=__lpSer(__raw);return typeof __txt==="string"&&__txt.length>10240?__txt.slice(0,10240)+"...[truncated]":__txt}catch{return"__unavailable__"}};const __lp=JSON.stringify({id:${jsonQuote(args.id)},file:${jsonQuote(args.file)},line:${args.line},label:${jsonQuote(args.label)},hypothesis:${jsonQuote(args.hypothesis)},timestamp:new Date().toISOString(),hit:globalThis.${counter},maxHits:${args.maxHits},vars:{${vars}}});fetch("http://localhost:${args.port}",{method:"POST",headers:{"Content-Type":"application/json"},body:__lp}).catch(()=>{})}catch{}})();`,
47
+ `// LOGPOINT_END [${args.id}]`,
48
+ ];
49
+ };
50
+
51
+ const pythonTemplate = (args: TemplateArgs): readonly string[] => {
52
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
53
+ const lines: string[] = [
54
+ `# LOGPOINT_START [${args.id}] - ${args.label}`,
55
+ "try:",
56
+ ` globals()[${jsonQuote(counter)}] = int(globals().get(${jsonQuote(counter)}, 0)) + 1`,
57
+ ` if globals()[${jsonQuote(counter)}] <= ${args.maxHits}:`,
58
+ " import json as __lpj",
59
+ " import urllib.request as __lpr",
60
+ " from datetime import datetime as __lpdt",
61
+ " __lp_vars = {}",
62
+ ];
63
+
64
+ for (const capture of args.capture) {
65
+ lines.push(" try:");
66
+ lines.push(` __lp_raw = eval(${jsonQuote(capture)}, globals(), locals())`);
67
+ lines.push(" except Exception:");
68
+ lines.push(" __lp_raw = \"__unavailable__\"");
69
+ lines.push(" try:");
70
+ lines.push(" __lp_value = __lpj.dumps(__lp_raw, default=str)");
71
+ lines.push(" except Exception:");
72
+ lines.push(" __lp_value = str(__lp_raw)");
73
+ lines.push(` __lp_vars[${jsonQuote(capture)}] = __lp_value[:10240]`);
74
+ }
75
+
76
+ lines.push(
77
+ ` __lp_payload = {"id": ${jsonQuote(args.id)}, "file": ${jsonQuote(args.file)}, "line": ${args.line}, "label": ${jsonQuote(args.label)}, "hypothesis": ${jsonQuote(args.hypothesis)}, "timestamp": __lpdt.now().isoformat(), "hit": globals()[${jsonQuote(counter)}], "maxHits": ${args.maxHits}, "vars": __lp_vars}`,
78
+ );
79
+ lines.push(
80
+ ` __lp_req = __lpr.Request("http://localhost:${args.port}", data=__lpj.dumps(__lp_payload).encode(), headers={"Content-Type": "application/json"})`,
81
+ );
82
+ lines.push(" try:");
83
+ lines.push(" __lpr.urlopen(__lp_req, timeout=0.5)");
84
+ lines.push(" except Exception:");
85
+ lines.push(" pass");
86
+ lines.push("except Exception:");
87
+ lines.push(" pass");
88
+ lines.push(`# LOGPOINT_END [${args.id}]`);
89
+
90
+ return lines;
91
+ };
92
+
93
+ const goTemplate = (args: TemplateArgs): TemplateOutput => {
94
+ const refs: GoTemplateRefs =
95
+ args.goRefs ??
96
+ ({
97
+ bytesRef: "bytes",
98
+ jsonRef: "json",
99
+ httpRef: "http",
100
+ timeRef: "time",
101
+ } as const);
102
+
103
+ const vars = args.capture.length === 0 ? "" : `${args.capture.map((entry) => `"${entry}": ${entry}`).join(", ")}, `;
104
+
105
+ const lines = [
106
+ `// LOGPOINT_START [${args.id}] - ${args.label}`,
107
+ "func() {",
108
+ " defer func() { _ = recover() }()",
109
+ ` __lpVars := map[string]any{${vars}}`,
110
+ ` __lpPayload := map[string]any{"id": ${jsonQuote(args.id)}, "file": ${jsonQuote(args.file)}, "line": ${args.line}, "label": ${jsonQuote(args.label)}, "hypothesis": ${jsonQuote(args.hypothesis)}, "timestamp": ${refs.timeRef}.Now().Format(${refs.timeRef}.RFC3339Nano), "maxHits": ${args.maxHits}, "vars": __lpVars}`,
111
+ ` __lpBody, __lpErr := ${refs.jsonRef}.Marshal(__lpPayload)`,
112
+ " if __lpErr != nil {",
113
+ " return",
114
+ " }",
115
+ " go func(__lpBytes []byte) {",
116
+ ` _, _ = ${refs.httpRef}.Post("http://localhost:${args.port}", "application/json", ${refs.bytesRef}.NewBuffer(__lpBytes))`,
117
+ " }(__lpBody)",
118
+ "}()",
119
+ `// LOGPOINT_END [${args.id}]`,
120
+ ];
121
+
122
+ return { lines, goRefs: refs };
123
+ };
124
+
125
+ const rubyTemplate = (args: TemplateArgs): readonly string[] => {
126
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
127
+ const lines: string[] = [
128
+ `# LOGPOINT_START [${args.id}] - ${args.label}`,
129
+ "begin",
130
+ ` $${counter} = ($${counter} || 0) + 1`,
131
+ ` if $${counter} <= ${args.maxHits}`,
132
+ " require \"json\"",
133
+ " require \"net/http\"",
134
+ " require \"uri\"",
135
+ " __lp_vars = {}",
136
+ ];
137
+
138
+ for (const capture of args.capture) {
139
+ lines.push(" begin");
140
+ lines.push(` __lp_value = JSON.generate(${capture})`);
141
+ lines.push(` __lp_vars[${jsonQuote(capture)}] = __lp_value[0, 10240]`);
142
+ lines.push(" rescue StandardError");
143
+ lines.push(` __lp_vars[${jsonQuote(capture)}] = \"__unavailable__\"`);
144
+ lines.push(" end");
145
+ }
146
+
147
+ lines.push(
148
+ " __lp_payload = { " +
149
+ `id: ${jsonQuote(args.id)}, file: ${jsonQuote(args.file)}, line: ${args.line}, label: ${jsonQuote(args.label)}, hypothesis: ${jsonQuote(args.hypothesis)}, timestamp: Time.now.utc.iso8601(6), hit: $${counter}, maxHits: ${args.maxHits}, vars: __lp_vars ` +
150
+ "}",
151
+ );
152
+ lines.push(` __lp_uri = URI("http://localhost:${args.port}")`);
153
+ lines.push(
154
+ ' begin; Net::HTTP.post(__lp_uri, JSON.generate(__lp_payload), { "Content-Type" => "application/json" }); rescue StandardError; end',
155
+ );
156
+ lines.push(" end");
157
+ lines.push("rescue StandardError");
158
+ lines.push("end");
159
+ lines.push(`# LOGPOINT_END [${args.id}]`);
160
+
161
+ return lines;
162
+ };
163
+
164
+ const shellTemplate = (args: TemplateArgs): readonly string[] => {
165
+ const counter = `__LP_COUNT_${sanitizeIdentifier(args.id)}`;
166
+ const lines: string[] = [
167
+ `# LOGPOINT_START [${args.id}] - ${args.label}`,
168
+ "{",
169
+ ` : "\${${counter}:=0}"`,
170
+ ` ${counter}=$(( ${counter} + 1 ))`,
171
+ ` if [ "\${${counter}}" -le ${args.maxHits} ]; then`,
172
+ ];
173
+
174
+ const chunks: string[] = [];
175
+ for (let index = 0; index < args.capture.length; index += 1) {
176
+ const capture = args.capture[index];
177
+ const variable = `__lp_var_${index}`;
178
+ chunks.push(variable);
179
+ lines.push(
180
+ ` ${variable}=$(printf '%s' "\${${capture}-__unavailable__}" | tr '\\n' ' ' | cut -c1-10240 | sed 's/"/\\\\"/g')`,
181
+ );
182
+ }
183
+
184
+ const renderedVars =
185
+ chunks.length === 0
186
+ ? "{}"
187
+ : `{${chunks.map((chunk, index) => `${jsonQuote(args.capture[index] ?? "")}:"$${chunk}"`).join(",")}}`;
188
+
189
+ lines.push(
190
+ ` __lp_payload='{"id":${jsonQuote(args.id)},"file":${jsonQuote(args.file)},"line":${args.line},"label":${jsonQuote(args.label)},"hypothesis":${jsonQuote(args.hypothesis)},"timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)'"'","hit":"'"\${${counter}}"'","maxHits":${args.maxHits},"vars":${renderedVars}}'`,
191
+ );
192
+ lines.push(
193
+ ` curl -s -X POST "http://localhost:${args.port}" -H "Content-Type: application/json" --data "$__lp_payload" >/dev/null 2>&1 || true`,
194
+ );
195
+ lines.push(" fi");
196
+ lines.push("} || true");
197
+ lines.push(`# LOGPOINT_END [${args.id}]`);
198
+
199
+ return lines;
200
+ };
201
+
202
+ const javaTemplate = (args: TemplateArgs): readonly string[] => {
203
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
204
+ const lines: string[] = [
205
+ `// LOGPOINT_START [${args.id}] - ${args.label}`,
206
+ "try {",
207
+ ` int __lpCount = Integer.parseInt(System.getProperty(${jsonQuote(counter)}, "0")) + 1;`,
208
+ ` System.setProperty(${jsonQuote(counter)}, Integer.toString(__lpCount));`,
209
+ ` if (__lpCount <= ${args.maxHits}) {`,
210
+ " java.util.Map<String, Object> __lpVars = new java.util.HashMap<>();",
211
+ ];
212
+
213
+ for (const capture of args.capture) {
214
+ lines.push(` __lpVars.put(${jsonQuote(capture)}, String.valueOf(${capture}));`);
215
+ }
216
+
217
+ lines.push(" StringBuilder __lpVarsJson = new StringBuilder(\"{\");");
218
+ lines.push(" boolean __lpFirst = true;");
219
+ lines.push(" for (java.util.Map.Entry<String, Object> __lpEntry : __lpVars.entrySet()) {");
220
+ lines.push(" if (!__lpFirst) __lpVarsJson.append(\",\");");
221
+ lines.push(" __lpFirst = false;");
222
+ lines.push(' String __lpEsc = String.valueOf(__lpEntry.getValue()).replace("\\\\", "\\\\\\\\").replace("\\\"", "\\\\\\\"");');
223
+ lines.push(' __lpVarsJson.append("\\\"").append(__lpEntry.getKey()).append("\\\":\\\"").append(__lpEsc).append("\\\"");');
224
+ lines.push(" }");
225
+ lines.push(" __lpVarsJson.append(\"}\");");
226
+ lines.push(
227
+ ` String __lpPayload = "{\\\"id\\\":${jsonQuote(args.id)},\\\"file\\\":${jsonQuote(args.file)},\\\"line\\\":${args.line},\\\"label\\\":${jsonQuote(args.label)},\\\"hypothesis\\\":${jsonQuote(args.hypothesis)},\\\"timestamp\\\":\\\"" + java.time.Instant.now().toString() + "\\\",\\\"hit\\\":" + __lpCount + ",\\\"maxHits\\\":${args.maxHits},\\\"vars\\\":" + __lpVarsJson + "}";`,
228
+ );
229
+ lines.push(" java.net.http.HttpClient __lpClient = java.net.http.HttpClient.newHttpClient();");
230
+ lines.push(
231
+ ` java.net.http.HttpRequest __lpReq = java.net.http.HttpRequest.newBuilder(java.net.URI.create("http://localhost:${args.port}"))`,
232
+ );
233
+ lines.push(" .header(\"Content-Type\", \"application/json\")");
234
+ lines.push(" .POST(java.net.http.HttpRequest.BodyPublishers.ofString(__lpPayload))");
235
+ lines.push(" .build();");
236
+ lines.push(" __lpClient.sendAsync(__lpReq, java.net.http.HttpResponse.BodyHandlers.discarding());");
237
+ lines.push(" }");
238
+ lines.push("} catch (Throwable __lpErr) {}", `// LOGPOINT_END [${args.id}]`);
239
+
240
+ return lines;
241
+ };
242
+
243
+ const csharpTemplate = (args: TemplateArgs): readonly string[] => {
244
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
245
+ const lines: string[] = [
246
+ `// LOGPOINT_START [${args.id}] - ${args.label}`,
247
+ "try {",
248
+ ` var __lpCount = ((AppContext.GetData(${jsonQuote(counter)}) as int?) ?? 0) + 1;`,
249
+ ` AppContext.SetData(${jsonQuote(counter)}, __lpCount);`,
250
+ ` if (__lpCount <= ${args.maxHits}) {`,
251
+ " var __lpVars = new System.Collections.Generic.Dictionary<string, object?>();",
252
+ ];
253
+
254
+ for (const capture of args.capture) {
255
+ lines.push(` __lpVars[${jsonQuote(capture)}] = ${capture};`);
256
+ }
257
+
258
+ lines.push(" var __lpPayload = System.Text.Json.JsonSerializer.Serialize(new {");
259
+ lines.push(` id = ${jsonQuote(args.id)},`);
260
+ lines.push(` file = ${jsonQuote(args.file)},`);
261
+ lines.push(` line = ${args.line},`);
262
+ lines.push(` label = ${jsonQuote(args.label)},`);
263
+ lines.push(` hypothesis = ${jsonQuote(args.hypothesis)},`);
264
+ lines.push(" timestamp = System.DateTimeOffset.UtcNow.ToString(\"O\"),");
265
+ lines.push(" hit = __lpCount,");
266
+ lines.push(` maxHits = ${args.maxHits},`);
267
+ lines.push(" vars = __lpVars");
268
+ lines.push(" });");
269
+ lines.push(" using var __lpClient = new System.Net.Http.HttpClient();");
270
+ lines.push(
271
+ ` _ = __lpClient.PostAsync("http://localhost:${args.port}", new System.Net.Http.StringContent(__lpPayload, System.Text.Encoding.UTF8, "application/json"));`,
272
+ );
273
+ lines.push(" }");
274
+ lines.push("} catch {}", `// LOGPOINT_END [${args.id}]`);
275
+
276
+ return lines;
277
+ };
278
+
279
+ const phpCaptureExpr = (capture: string): string => (capture.startsWith("$") ? capture : `$${capture}`);
280
+
281
+ const phpTemplate = (args: TemplateArgs): readonly string[] => {
282
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
283
+ const lines: string[] = [
284
+ `# LOGPOINT_START [${args.id}] - ${args.label}`,
285
+ "try {",
286
+ ` $GLOBALS[${jsonQuote(counter)}] = ((int)($GLOBALS[${jsonQuote(counter)}] ?? 0)) + 1;`,
287
+ ` if ($GLOBALS[${jsonQuote(counter)}] <= ${args.maxHits}) {`,
288
+ " $__lpVars = [];",
289
+ ];
290
+
291
+ for (const capture of args.capture) {
292
+ const expr = phpCaptureExpr(capture);
293
+ lines.push(" try {");
294
+ lines.push(` $__lpVars[${jsonQuote(capture)}] = ${expr};`);
295
+ lines.push(" } catch (Throwable $__lpInner) {");
296
+ lines.push(` $__lpVars[${jsonQuote(capture)}] = "__unavailable__";`);
297
+ lines.push(" }");
298
+ }
299
+
300
+ lines.push(
301
+ ` $__lpPayload = json_encode(["id" => ${jsonQuote(args.id)}, "file" => ${jsonQuote(args.file)}, "line" => ${args.line}, "label" => ${jsonQuote(args.label)}, "hypothesis" => ${jsonQuote(args.hypothesis)}, "timestamp" => gmdate("c"), "hit" => $GLOBALS[${jsonQuote(counter)}], "maxHits" => ${args.maxHits}, "vars" => $__lpVars]);`,
302
+ );
303
+ lines.push(
304
+ ` @file_get_contents("http://localhost:${args.port}", false, stream_context_create(["http" => ["method" => "POST", "header" => "Content-Type: application/json\\r\\n", "content" => $__lpPayload, "timeout" => 0.5]]));`,
305
+ );
306
+ lines.push(" }");
307
+ lines.push("} catch (Throwable $__lpErr) {}", `# LOGPOINT_END [${args.id}]`);
308
+
309
+ return lines;
310
+ };
311
+
312
+ const rustTemplate = (args: TemplateArgs): readonly string[] => {
313
+ const lines: string[] = [`// LOGPOINT_START [${args.id}] - ${args.label}`, "{"];
314
+ lines.push(" let mut __lp_vars: Vec<String> = Vec::new();");
315
+
316
+ for (const capture of args.capture) {
317
+ lines.push(
318
+ ` __lp_vars.push(format!("\\\"${capture}\\\":\\\"{}\\\"", format!("{:?}", ${capture}).replace('\\\\', "\\\\\\\\").replace('\\\"', "\\\\\\\"")));`,
319
+ );
320
+ }
321
+
322
+ lines.push(" let __lp_ts = format!(\"{:?}\", std::time::SystemTime::now());");
323
+ lines.push(
324
+ ` let __lp_body = format!("{{\\\"id\\\":${jsonQuote(args.id)},\\\"file\\\":${jsonQuote(args.file)},\\\"line\\\":${args.line},\\\"label\\\":${jsonQuote(args.label)},\\\"hypothesis\\\":${jsonQuote(args.hypothesis)},\\\"timestamp\\\":\\\"{}\\\",\\\"maxHits\\\":${args.maxHits},\\\"vars\\\":{{{}}}}}", __lp_ts, __lp_vars.join(","));`,
325
+ );
326
+ lines.push(` if let Ok(mut __lp_stream) = std::net::TcpStream::connect("127.0.0.1:${args.port}") {`);
327
+ lines.push(
328
+ " let __lp_request = format!(\"POST / HTTP/1.1\\r\\nHost: localhost\\r\\nContent-Type: application/json\\r\\nContent-Length: {}\\r\\nConnection: close\\r\\n\\r\\n{}\", __lp_body.len(), __lp_body);",
329
+ );
330
+ lines.push(" let _ = std::io::Write::write_all(&mut __lp_stream, __lp_request.as_bytes());");
331
+ lines.push(" }");
332
+ lines.push("}", `// LOGPOINT_END [${args.id}]`);
333
+
334
+ return lines;
335
+ };
336
+
337
+ const kotlinTemplate = (args: TemplateArgs): readonly string[] => {
338
+ const counter = `__lp_count_${sanitizeIdentifier(args.id)}`;
339
+ const lines: string[] = [
340
+ `// LOGPOINT_START [${args.id}] - ${args.label}`,
341
+ "try {",
342
+ ` val __lpCount = ((System.getProperty(${jsonQuote(counter)}) ?: \"0\").toIntOrNull() ?: 0) + 1`,
343
+ ` System.setProperty(${jsonQuote(counter)}, __lpCount.toString())`,
344
+ ` if (__lpCount <= ${args.maxHits}) {`,
345
+ " val __lpVars = mutableMapOf<String, Any?>()",
346
+ ];
347
+
348
+ for (const capture of args.capture) {
349
+ lines.push(` __lpVars[${jsonQuote(capture)}] = ${capture}`);
350
+ }
351
+
352
+ lines.push(
353
+ ' val __lpVarsJson = __lpVars.entries.joinToString(",") { "\"${it.key}\":\"" + (it.value?.toString() ?: "__unavailable__").replace("\\", "\\\\").replace("\"", "\\\"") + "\"" }',
354
+ );
355
+ lines.push(
356
+ ` val __lpPayload = "{\\\"id\\\":${jsonQuote(args.id)},\\\"file\\\":${jsonQuote(args.file)},\\\"line\\\":${args.line},\\\"label\\\":${jsonQuote(args.label)},\\\"hypothesis\\\":${jsonQuote(args.hypothesis)},\\\"timestamp\\\":\\\"${'$'}{java.time.Instant.now()}\\\",\\\"hit\\\":${'$'}__lpCount,\\\"maxHits\\\":${args.maxHits},\\\"vars\\\":{${'$'}__lpVarsJson}}"`,
357
+ );
358
+ lines.push(` val __lpUrl = java.net.URL("http://localhost:${args.port}")`);
359
+ lines.push(" val __lpConn = (__lpUrl.openConnection() as java.net.HttpURLConnection).apply {");
360
+ lines.push(" requestMethod = \"POST\"");
361
+ lines.push(" setRequestProperty(\"Content-Type\", \"application/json\")");
362
+ lines.push(" doOutput = true");
363
+ lines.push(" }");
364
+ lines.push(" __lpConn.outputStream.use { it.write(__lpPayload.toByteArray()) }");
365
+ lines.push(" runCatching { __lpConn.inputStream.close() }");
366
+ lines.push(" __lpConn.disconnect()");
367
+ lines.push(" }");
368
+ lines.push("} catch (_: Throwable) {}", `// LOGPOINT_END [${args.id}]`);
369
+
370
+ return lines;
371
+ };
372
+
373
+ export const generateTemplate = (args: TemplateArgs, language: Language): TemplateOutput => {
374
+ switch (language) {
375
+ case "javascript":
376
+ return { lines: jsTemplate(args) };
377
+ case "typescript":
378
+ return { lines: jsTemplate(args) };
379
+ case "python":
380
+ return { lines: pythonTemplate(args) };
381
+ case "go":
382
+ return goTemplate(args);
383
+ case "ruby":
384
+ return { lines: rubyTemplate(args) };
385
+ case "shell":
386
+ return { lines: shellTemplate(args) };
387
+ case "java":
388
+ return { lines: javaTemplate(args) };
389
+ case "csharp":
390
+ return { lines: csharpTemplate(args) };
391
+ case "php":
392
+ return { lines: phpTemplate(args) };
393
+ case "rust":
394
+ return { lines: rustTemplate(args) };
395
+ case "kotlin":
396
+ return { lines: kotlinTemplate(args) };
397
+ default:
398
+ return { lines: jsTemplate(args) };
399
+ }
400
+ };
@@ -0,0 +1,53 @@
1
+ import { Effect } from "effect";
2
+ import { optionalStringOption, parseArgs } from "./lib/cli-args.js";
3
+ import {
4
+ ManifestError,
5
+ ValidationError,
6
+ type FileNotFound,
7
+ type FileReadError,
8
+ type ParseError,
9
+ } from "./lib/errors.js";
10
+ import { decodeManifestUnknown, parseJsonString } from "./lib/schema.js";
11
+ import { AppLive, FileSystem, Logger } from "./lib/services.js";
12
+
13
+ const parseManifestPath = (argv: readonly string[]): Effect.Effect<string, ValidationError, never> =>
14
+ Effect.gen(function* () {
15
+ const parsed = parseArgs(argv);
16
+ const manifest = (yield* optionalStringOption(parsed, "manifest")) ?? parsed.positionals[0];
17
+ if (manifest === undefined || manifest.length === 0) {
18
+ return yield* Effect.fail(new ValidationError({ message: "Manifest path is required" }));
19
+ }
20
+ return manifest;
21
+ });
22
+
23
+ export type ValidateRunError =
24
+ | ValidationError
25
+ | ManifestError
26
+ | FileNotFound
27
+ | FileReadError
28
+ | ParseError;
29
+
30
+ export const runValidateFromArgs = (
31
+ argv: readonly string[],
32
+ ): Effect.Effect<void, ValidateRunError, FileSystem | Logger> =>
33
+ Effect.gen(function* () {
34
+ const fs = yield* FileSystem;
35
+ const logger = yield* Logger;
36
+
37
+ const manifestPath = yield* parseManifestPath(argv);
38
+ const content = yield* fs.readFile(manifestPath);
39
+ const raw = yield* parseJsonString(content);
40
+ const manifest = yield* decodeManifestUnknown(raw);
41
+
42
+ yield* logger.info(JSON.stringify(manifest, null, 2));
43
+ });
44
+
45
+ const execute = (argv: readonly string[]): Promise<void> =>
46
+ Effect.runPromise(runValidateFromArgs(argv).pipe(Effect.provide(AppLive)));
47
+
48
+ if (import.meta.main) {
49
+ execute(Bun.argv.slice(2)).catch((error) => {
50
+ console.error(error);
51
+ process.exitCode = 1;
52
+ });
53
+ }