@le-space/node 0.1.10 → 0.1.12

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.
Files changed (3) hide show
  1. package/index.d.ts +11 -1
  2. package/index.js +199 -4
  3. package/package.json +4 -4
package/index.d.ts CHANGED
@@ -407,4 +407,14 @@ declare function runRootfsMode(env?: NodeJS.ProcessEnv, hooks?: {
407
407
  }): Promise<void>;
408
408
  declare function rootfsMain(): Promise<void>;
409
409
 
410
- export { type DeployConfigurationResult, type DeployExecutorDependencies, type DeployMetadataResult, type DeployOutputResult, type DeployPlan, type ParsedRootfsRunnerInputs, type PrivateKeyIdentity, actionLog, appendGithubOutput, appendGithubSummary, booleanEnv, buildScaffoldDeployResult, createPrivateKeyIdentity, createPrivateKeySigner, emitDeployOutputs, emitGeocodedCrnOutputs, emitRootfsOutputs, executeDeployPlan, integerEnv, jsonEnv, main, optionalEnv, parseDeployPlan, parseRootfsRunnerInputs, requiredEnv, rootfsMain, runActionMode, runLocalCommand, runRootfsMode };
410
+ interface RunResult {
411
+ stdout: string;
412
+ stderr: string;
413
+ exitCode: number;
414
+ }
415
+ declare function runSitePublishMode(env?: NodeJS.ProcessEnv): Promise<void>;
416
+ declare function runProbeMode(env?: NodeJS.ProcessEnv): Promise<void>;
417
+ declare function runBootstrapEnvMode(env?: NodeJS.ProcessEnv): Promise<void>;
418
+ declare function runSiteMode(env?: NodeJS.ProcessEnv): Promise<void>;
419
+
420
+ export { type DeployConfigurationResult, type DeployExecutorDependencies, type DeployMetadataResult, type DeployOutputResult, type DeployPlan, type ParsedRootfsRunnerInputs, type PrivateKeyIdentity, type RunResult, actionLog, appendGithubOutput, appendGithubSummary, booleanEnv, buildScaffoldDeployResult, createPrivateKeyIdentity, createPrivateKeySigner, emitDeployOutputs, emitGeocodedCrnOutputs, emitRootfsOutputs, executeDeployPlan, integerEnv, jsonEnv, main, optionalEnv, parseDeployPlan, parseRootfsRunnerInputs, requiredEnv, rootfsMain, runActionMode, runBootstrapEnvMode, runLocalCommand, runProbeMode, runRootfsMode, runSiteMode, runSitePublishMode };
package/index.js CHANGED
@@ -34,10 +34,20 @@ function jsonEnv(name, fallback, env = process.env) {
34
34
 
35
35
  // src/github-outputs.ts
36
36
  import { appendFile } from "fs/promises";
37
+ import { randomUUID } from "crypto";
37
38
  async function appendGithubOutput(name, value, env = process.env) {
38
39
  const outputFile = env.GITHUB_OUTPUT;
39
40
  if (!outputFile) return;
40
- await appendFile(outputFile, `${name}=${String(value ?? "")}
41
+ const normalized = String(value ?? "");
42
+ if (/\r|\n/.test(normalized)) {
43
+ const marker = `__ALEPH_OUTPUT_${randomUUID()}__`;
44
+ await appendFile(outputFile, `${name}<<${marker}
45
+ ${normalized}
46
+ ${marker}
47
+ `);
48
+ return;
49
+ }
50
+ await appendFile(outputFile, `${name}=${normalized}
41
51
  `);
42
52
  }
43
53
  async function appendGithubSummary(lines, env = process.env) {
@@ -607,6 +617,15 @@ function normalizeExecution(item, crnUrl) {
607
617
  }
608
618
 
609
619
  // ../core/src/deployment-inspection.ts
620
+ function insufficientBalanceMessage(details) {
621
+ const firstError = details && Array.isArray(details.errors) && details.errors[0] && typeof details.errors[0] === "object" ? details.errors[0] : null;
622
+ const accountBalance = firstError?.account_balance;
623
+ const requiredBalance = firstError?.required_balance;
624
+ if (accountBalance != null && requiredBalance != null) {
625
+ return `insufficient Aleph balance: account has ${accountBalance}, required is ${requiredBalance}`;
626
+ }
627
+ return null;
628
+ }
610
629
  async function fetchMessageEnvelope(itemHash, options) {
611
630
  const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST}/api/v0/messages/${itemHash}`, {
612
631
  cache: "no-cache"
@@ -615,6 +634,32 @@ async function fetchMessageEnvelope(itemHash, options) {
615
634
  if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
616
635
  return await response.json();
617
636
  }
637
+ async function inspectMessageResult(itemHash, options) {
638
+ const label = options.label ?? "Message";
639
+ const payload = await fetchMessageEnvelope(itemHash, options);
640
+ if (!payload) {
641
+ return {
642
+ status: "unknown",
643
+ errorCode: null,
644
+ details: null,
645
+ rejectionReason: `${label} ${itemHash} was not found on Aleph.`
646
+ };
647
+ }
648
+ const status = normalizeMessageStatus(payload.status);
649
+ const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
650
+ const details = payload.details && typeof payload.details === "object" ? payload.details : null;
651
+ let rejectionReason = null;
652
+ if (status === "rejected") {
653
+ const balanceMessage = errorCode === 5 ? insufficientBalanceMessage(details) : null;
654
+ rejectionReason = balanceMessage ? `${label} ${itemHash} was rejected by Aleph due to ${balanceMessage}.` : `${label} ${itemHash} was rejected by Aleph${errorCode ? ` (error ${errorCode})` : ""}.`;
655
+ }
656
+ return {
657
+ status,
658
+ errorCode,
659
+ details,
660
+ rejectionReason
661
+ };
662
+ }
618
663
  async function fetchReference(itemHash, options) {
619
664
  const payload = await fetchMessageEnvelope(itemHash, options);
620
665
  if (!payload) {
@@ -1399,11 +1444,11 @@ function proxyHostnameFromUrl(value) {
1399
1444
  return null;
1400
1445
  }
1401
1446
  }
1402
- async function defaultHttpProbe(fetch, url, timeoutMs = 1e4) {
1447
+ async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1403
1448
  try {
1404
1449
  const controller = new AbortController();
1405
1450
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
1406
- const response = await fetch(url, {
1451
+ const response = await fetch2(url, {
1407
1452
  method: "GET",
1408
1453
  redirect: "follow",
1409
1454
  signal: controller.signal
@@ -2780,6 +2825,152 @@ if (import.meta.url === pathToFileURL2(process.argv[1] ?? "").href) {
2780
2825
  process.exitCode = 1;
2781
2826
  });
2782
2827
  }
2828
+
2829
+ // src/site-runner.ts
2830
+ import process2 from "process";
2831
+ import { spawn as spawn2 } from "child_process";
2832
+ async function runCapture(command, args, options = {}) {
2833
+ return await new Promise((resolve, reject) => {
2834
+ const child = spawn2(command, args, {
2835
+ cwd: options.cwd,
2836
+ env: { ...process2.env, ...options.env },
2837
+ stdio: ["ignore", "pipe", "pipe"]
2838
+ });
2839
+ let stdout = "";
2840
+ let stderr = "";
2841
+ child.stdout.on("data", (chunk) => {
2842
+ stdout += String(chunk);
2843
+ });
2844
+ child.stderr.on("data", (chunk) => {
2845
+ stderr += String(chunk);
2846
+ });
2847
+ child.on("error", reject);
2848
+ child.on("exit", (exitCode) => resolve({ stdout, stderr, exitCode: exitCode ?? 1 }));
2849
+ });
2850
+ }
2851
+ function parseLastJsonObject(text) {
2852
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2853
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
2854
+ const line = lines[index];
2855
+ if (!line.startsWith("{")) continue;
2856
+ return JSON.parse(line);
2857
+ }
2858
+ throw new Error(`Could not parse JSON object from output: ${text}`);
2859
+ }
2860
+ async function waitForAlephMessage(itemHash, env = process2.env) {
2861
+ const apiHost = optionalEnv("ALEPH_SITE_ALEPH_API_HOST", "https://api2.aleph.im", env);
2862
+ const attempts = Number(optionalEnv("ALEPH_SITE_ALEPH_MESSAGE_WAIT_ATTEMPTS", "60", env));
2863
+ const delayMs = Number(optionalEnv("ALEPH_SITE_ALEPH_MESSAGE_WAIT_DELAY_MS", "5000", env));
2864
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
2865
+ const result = await inspectMessageResult(itemHash, {
2866
+ apiHost,
2867
+ fetch,
2868
+ label: "Aleph STORE message"
2869
+ });
2870
+ if (result.status === "processed") return;
2871
+ if (result.status === "rejected") {
2872
+ throw new Error(result.rejectionReason ?? `Aleph STORE message ${itemHash} was rejected.`);
2873
+ }
2874
+ if (attempt < attempts) {
2875
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2876
+ }
2877
+ }
2878
+ throw new Error(`Aleph STORE message ${itemHash} did not become processed in time.`);
2879
+ }
2880
+ function mergedAddrs(env = process2.env) {
2881
+ const combined = [];
2882
+ for (const key of ["PROBE_MULTIADDRS_JSON", "BROWSER_BOOTSTRAP_MULTIADDRS_JSON"]) {
2883
+ const raw = env[key] ?? "[]";
2884
+ for (const value of JSON.parse(raw)) {
2885
+ if (typeof value === "string") {
2886
+ const trimmed = value.trim();
2887
+ if (trimmed) combined.push(trimmed);
2888
+ }
2889
+ }
2890
+ }
2891
+ return Array.from(new Set(combined));
2892
+ }
2893
+ async function runSitePublishMode(env = process2.env) {
2894
+ const projectDir = requiredEnv("ALEPH_SITE_PROJECT_DIR", env);
2895
+ const publishScript = optionalEnv("ALEPH_SITE_PUBLISH_SCRIPT", "go-peer/aleph/publish-static-site.py", env);
2896
+ const siteDirectory = requiredEnv("ALEPH_SITE_DIRECTORY", env);
2897
+ const pythonBin = optionalEnv("ALEPH_SITE_PYTHON", "python3", env);
2898
+ const pin = optionalEnv("ALEPH_SITE_PIN", "true", env) === "true";
2899
+ const publish = await runCapture(pythonBin, [publishScript, siteDirectory], { cwd: projectDir });
2900
+ process2.stdout.write(publish.stdout);
2901
+ if (publish.stderr) process2.stderr.write(publish.stderr);
2902
+ if (publish.exitCode !== 0) {
2903
+ throw new Error(`${publishScript} failed with exit code ${publish.exitCode}`);
2904
+ }
2905
+ const payload = parseLastJsonObject(publish.stdout);
2906
+ const cidV0 = String(payload.cid_v0 ?? "");
2907
+ const cidV1 = String(payload.cid_v1 ?? "");
2908
+ if (!cidV0 || !cidV1 || cidV0 === "null" || cidV1 === "null") {
2909
+ throw new Error(`Failed to extract IPFS CIDs from publish result: ${JSON.stringify(payload)}`);
2910
+ }
2911
+ await appendGithubOutput("ipfs_cid_v0", cidV0, env);
2912
+ await appendGithubOutput("ipfs_cid_v1", cidV1, env);
2913
+ await appendGithubOutput("url", `https://${cidV1}.ipfs.aleph.sh`, env);
2914
+ let itemHash = "";
2915
+ if (pin) {
2916
+ const pinResult = await runCapture("aleph", ["file", "pin", cidV0], { cwd: projectDir });
2917
+ if (pinResult.stdout) process2.stdout.write(pinResult.stdout);
2918
+ if (pinResult.stderr) process2.stderr.write(pinResult.stderr);
2919
+ if (pinResult.exitCode !== 0) {
2920
+ throw new Error(`aleph file pin ${cidV0} failed with exit code ${pinResult.exitCode}`);
2921
+ }
2922
+ const pinPayload = parseLastJsonObject(pinResult.stdout);
2923
+ itemHash = String(pinPayload.item_hash ?? "");
2924
+ if (!itemHash) {
2925
+ throw new Error(`Aleph pin response did not include item_hash: ${JSON.stringify(pinPayload)}`);
2926
+ }
2927
+ await appendGithubOutput("item_hash", itemHash, env);
2928
+ await waitForAlephMessage(itemHash, env);
2929
+ }
2930
+ await appendGithubSummary([
2931
+ "## Shared Site Runner",
2932
+ "",
2933
+ `- Site directory: \`${siteDirectory}\``,
2934
+ `- IPFS CID v0: \`${cidV0}\``,
2935
+ `- IPFS CID v1: \`${cidV1}\``,
2936
+ `- Aleph item hash: \`${itemHash}\``
2937
+ ], env);
2938
+ }
2939
+ async function runProbeMode(env = process2.env) {
2940
+ const addrs = mergedAddrs(env);
2941
+ if (addrs.length === 0) throw new Error("No relay probe or browser bootstrap multiaddrs were supplied.");
2942
+ const workdir = requiredEnv("ALEPH_SITE_PROBE_WORKDIR", env);
2943
+ const scriptPath = requiredEnv("ALEPH_SITE_PROBE_SCRIPT", env);
2944
+ const nodeBin = optionalEnv("ALEPH_SITE_NODE", "node", env);
2945
+ const result = await runCapture(nodeBin, [scriptPath, ...addrs], { cwd: workdir });
2946
+ if (result.stdout) process2.stdout.write(result.stdout);
2947
+ if (result.stderr) process2.stderr.write(result.stderr);
2948
+ if (result.exitCode !== 0) {
2949
+ throw new Error(`relay probe failed with exit code ${result.exitCode}`);
2950
+ }
2951
+ const rows = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
2952
+ if (rows.length === 0) throw new Error("Relay probe produced no JSON output.");
2953
+ if (rows.some((row) => row.ok !== true)) throw new Error("At least one relay protocol probe failed.");
2954
+ await appendGithubOutput("ok", "true", env);
2955
+ await appendGithubOutput("json", result.stdout.trim(), env);
2956
+ await appendGithubOutput("merged_multiaddrs_json", JSON.stringify(addrs), env);
2957
+ }
2958
+ async function runBootstrapEnvMode(env = process2.env) {
2959
+ const raw = env.BROWSER_BOOTSTRAP_MULTIADDRS_JSON ?? "[]";
2960
+ const addrs = JSON.parse(raw).filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim());
2961
+ const csv = addrs.join(",");
2962
+ await appendGithubOutput("json", JSON.stringify(addrs), env);
2963
+ await appendGithubOutput("csv", csv, env);
2964
+ await appendGithubOutput("count", String(addrs.length), env);
2965
+ await appendGithubOutput("available", addrs.length > 0 ? "true" : "false", env);
2966
+ }
2967
+ async function runSiteMode(env = process2.env) {
2968
+ const mode = optionalEnv("ALEPH_VM_MODE", "site-publish", env);
2969
+ if (mode === "site-publish") return await runSitePublishMode(env);
2970
+ if (mode === "relay-probe") return await runProbeMode(env);
2971
+ if (mode === "bootstrap-env") return await runBootstrapEnvMode(env);
2972
+ throw new Error(`Unsupported ALEPH_VM_MODE "${mode}" in shared site runner.`);
2973
+ }
2783
2974
  export {
2784
2975
  actionLog,
2785
2976
  appendGithubOutput,
@@ -2801,6 +2992,10 @@ export {
2801
2992
  requiredEnv,
2802
2993
  rootfsMain,
2803
2994
  runActionMode,
2995
+ runBootstrapEnvMode,
2804
2996
  runLocalCommand,
2805
- runRootfsMode
2997
+ runProbeMode,
2998
+ runRootfsMode,
2999
+ runSiteMode,
3000
+ runSitePublishMode
2806
3001
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/node",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Node and GitHub Actions adapters for shared Aleph tooling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,9 +16,9 @@
16
16
  "access": "public"
17
17
  },
18
18
  "dependencies": {
19
- "@le-space/core": "0.1.10",
20
- "@le-space/shared-types": "0.1.10",
21
- "@le-space/rootfs": "0.1.10",
19
+ "@le-space/core": "0.1.12",
20
+ "@le-space/shared-types": "0.1.12",
21
+ "@le-space/rootfs": "0.1.12",
22
22
  "ethers": "^6.15.0"
23
23
  }
24
24
  }