@le-space/node 0.1.11 → 0.1.13

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 +12 -1
  2. package/index.js +193 -3
  3. package/package.json +4 -4
package/index.d.ts CHANGED
@@ -407,4 +407,15 @@ 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 parseLastJsonObject(text: string): Record<string, unknown>;
416
+ declare function runSitePublishMode(env?: NodeJS.ProcessEnv): Promise<void>;
417
+ declare function runProbeMode(env?: NodeJS.ProcessEnv): Promise<void>;
418
+ declare function runBootstrapEnvMode(env?: NodeJS.ProcessEnv): Promise<void>;
419
+ declare function runSiteMode(env?: NodeJS.ProcessEnv): Promise<void>;
420
+
421
+ 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, parseLastJsonObject, parseRootfsRunnerInputs, requiredEnv, rootfsMain, runActionMode, runBootstrapEnvMode, runLocalCommand, runProbeMode, runRootfsMode, runSiteMode, runSitePublishMode };
package/index.js CHANGED
@@ -617,6 +617,15 @@ function normalizeExecution(item, crnUrl) {
617
617
  }
618
618
 
619
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
+ }
620
629
  async function fetchMessageEnvelope(itemHash, options) {
621
630
  const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST}/api/v0/messages/${itemHash}`, {
622
631
  cache: "no-cache"
@@ -625,6 +634,32 @@ async function fetchMessageEnvelope(itemHash, options) {
625
634
  if (!response.ok) throw new Error(`Message lookup failed: ${response.status}`);
626
635
  return await response.json();
627
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
+ }
628
663
  async function fetchReference(itemHash, options) {
629
664
  const payload = await fetchMessageEnvelope(itemHash, options);
630
665
  if (!payload) {
@@ -1409,11 +1444,11 @@ function proxyHostnameFromUrl(value) {
1409
1444
  return null;
1410
1445
  }
1411
1446
  }
1412
- async function defaultHttpProbe(fetch, url, timeoutMs = 1e4) {
1447
+ async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1413
1448
  try {
1414
1449
  const controller = new AbortController();
1415
1450
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
1416
- const response = await fetch(url, {
1451
+ const response = await fetch2(url, {
1417
1452
  method: "GET",
1418
1453
  redirect: "follow",
1419
1454
  signal: controller.signal
@@ -2790,6 +2825,156 @@ if (import.meta.url === pathToFileURL2(process.argv[1] ?? "").href) {
2790
2825
  process.exitCode = 1;
2791
2826
  });
2792
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/).filter((line) => line.trim().length > 0);
2853
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
2854
+ const candidate = lines[index]?.trimStart() ?? "";
2855
+ if (!candidate.startsWith("{")) continue;
2856
+ const suffix = lines.slice(index).join("\n");
2857
+ try {
2858
+ return JSON.parse(suffix);
2859
+ } catch {
2860
+ }
2861
+ }
2862
+ throw new Error(`Could not parse JSON object from output: ${text}`);
2863
+ }
2864
+ async function waitForAlephMessage(itemHash, env = process2.env) {
2865
+ const apiHost = optionalEnv("ALEPH_SITE_ALEPH_API_HOST", "https://api2.aleph.im", env);
2866
+ const attempts = Number(optionalEnv("ALEPH_SITE_ALEPH_MESSAGE_WAIT_ATTEMPTS", "60", env));
2867
+ const delayMs = Number(optionalEnv("ALEPH_SITE_ALEPH_MESSAGE_WAIT_DELAY_MS", "5000", env));
2868
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
2869
+ const result = await inspectMessageResult(itemHash, {
2870
+ apiHost,
2871
+ fetch,
2872
+ label: "Aleph STORE message"
2873
+ });
2874
+ if (result.status === "processed") return;
2875
+ if (result.status === "rejected") {
2876
+ throw new Error(result.rejectionReason ?? `Aleph STORE message ${itemHash} was rejected.`);
2877
+ }
2878
+ if (attempt < attempts) {
2879
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2880
+ }
2881
+ }
2882
+ throw new Error(`Aleph STORE message ${itemHash} did not become processed in time.`);
2883
+ }
2884
+ function mergedAddrs(env = process2.env) {
2885
+ const combined = [];
2886
+ for (const key of ["PROBE_MULTIADDRS_JSON", "BROWSER_BOOTSTRAP_MULTIADDRS_JSON"]) {
2887
+ const raw = env[key] ?? "[]";
2888
+ for (const value of JSON.parse(raw)) {
2889
+ if (typeof value === "string") {
2890
+ const trimmed = value.trim();
2891
+ if (trimmed) combined.push(trimmed);
2892
+ }
2893
+ }
2894
+ }
2895
+ return Array.from(new Set(combined));
2896
+ }
2897
+ async function runSitePublishMode(env = process2.env) {
2898
+ const projectDir = requiredEnv("ALEPH_SITE_PROJECT_DIR", env);
2899
+ const publishScript = optionalEnv("ALEPH_SITE_PUBLISH_SCRIPT", "go-peer/aleph/publish-static-site.py", env);
2900
+ const siteDirectory = requiredEnv("ALEPH_SITE_DIRECTORY", env);
2901
+ const pythonBin = optionalEnv("ALEPH_SITE_PYTHON", "python3", env);
2902
+ const pin = optionalEnv("ALEPH_SITE_PIN", "true", env) === "true";
2903
+ const publish = await runCapture(pythonBin, [publishScript, siteDirectory], { cwd: projectDir });
2904
+ process2.stdout.write(publish.stdout);
2905
+ if (publish.stderr) process2.stderr.write(publish.stderr);
2906
+ if (publish.exitCode !== 0) {
2907
+ throw new Error(`${publishScript} failed with exit code ${publish.exitCode}`);
2908
+ }
2909
+ const payload = parseLastJsonObject(publish.stdout);
2910
+ const cidV0 = String(payload.cid_v0 ?? "");
2911
+ const cidV1 = String(payload.cid_v1 ?? "");
2912
+ if (!cidV0 || !cidV1 || cidV0 === "null" || cidV1 === "null") {
2913
+ throw new Error(`Failed to extract IPFS CIDs from publish result: ${JSON.stringify(payload)}`);
2914
+ }
2915
+ await appendGithubOutput("ipfs_cid_v0", cidV0, env);
2916
+ await appendGithubOutput("ipfs_cid_v1", cidV1, env);
2917
+ await appendGithubOutput("url", `https://${cidV1}.ipfs.aleph.sh`, env);
2918
+ let itemHash = "";
2919
+ if (pin) {
2920
+ const pinResult = await runCapture("aleph", ["file", "pin", cidV0], { cwd: projectDir });
2921
+ if (pinResult.stdout) process2.stdout.write(pinResult.stdout);
2922
+ if (pinResult.stderr) process2.stderr.write(pinResult.stderr);
2923
+ if (pinResult.exitCode !== 0) {
2924
+ throw new Error(`aleph file pin ${cidV0} failed with exit code ${pinResult.exitCode}`);
2925
+ }
2926
+ const pinPayload = parseLastJsonObject(pinResult.stdout);
2927
+ itemHash = String(pinPayload.item_hash ?? "");
2928
+ if (!itemHash) {
2929
+ throw new Error(`Aleph pin response did not include item_hash: ${JSON.stringify(pinPayload)}`);
2930
+ }
2931
+ await appendGithubOutput("item_hash", itemHash, env);
2932
+ await waitForAlephMessage(itemHash, env);
2933
+ }
2934
+ await appendGithubSummary([
2935
+ "## Shared Site Runner",
2936
+ "",
2937
+ `- Site directory: \`${siteDirectory}\``,
2938
+ `- IPFS CID v0: \`${cidV0}\``,
2939
+ `- IPFS CID v1: \`${cidV1}\``,
2940
+ `- Aleph item hash: \`${itemHash}\``
2941
+ ], env);
2942
+ }
2943
+ async function runProbeMode(env = process2.env) {
2944
+ const addrs = mergedAddrs(env);
2945
+ if (addrs.length === 0) throw new Error("No relay probe or browser bootstrap multiaddrs were supplied.");
2946
+ const workdir = requiredEnv("ALEPH_SITE_PROBE_WORKDIR", env);
2947
+ const scriptPath = requiredEnv("ALEPH_SITE_PROBE_SCRIPT", env);
2948
+ const nodeBin = optionalEnv("ALEPH_SITE_NODE", "node", env);
2949
+ const result = await runCapture(nodeBin, [scriptPath, ...addrs], { cwd: workdir });
2950
+ if (result.stdout) process2.stdout.write(result.stdout);
2951
+ if (result.stderr) process2.stderr.write(result.stderr);
2952
+ if (result.exitCode !== 0) {
2953
+ throw new Error(`relay probe failed with exit code ${result.exitCode}`);
2954
+ }
2955
+ const rows = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
2956
+ if (rows.length === 0) throw new Error("Relay probe produced no JSON output.");
2957
+ if (rows.some((row) => row.ok !== true)) throw new Error("At least one relay protocol probe failed.");
2958
+ await appendGithubOutput("ok", "true", env);
2959
+ await appendGithubOutput("json", result.stdout.trim(), env);
2960
+ await appendGithubOutput("merged_multiaddrs_json", JSON.stringify(addrs), env);
2961
+ }
2962
+ async function runBootstrapEnvMode(env = process2.env) {
2963
+ const raw = env.BROWSER_BOOTSTRAP_MULTIADDRS_JSON ?? "[]";
2964
+ const addrs = JSON.parse(raw).filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim());
2965
+ const csv = addrs.join(",");
2966
+ await appendGithubOutput("json", JSON.stringify(addrs), env);
2967
+ await appendGithubOutput("csv", csv, env);
2968
+ await appendGithubOutput("count", String(addrs.length), env);
2969
+ await appendGithubOutput("available", addrs.length > 0 ? "true" : "false", env);
2970
+ }
2971
+ async function runSiteMode(env = process2.env) {
2972
+ const mode = optionalEnv("ALEPH_VM_MODE", "site-publish", env);
2973
+ if (mode === "site-publish") return await runSitePublishMode(env);
2974
+ if (mode === "relay-probe") return await runProbeMode(env);
2975
+ if (mode === "bootstrap-env") return await runBootstrapEnvMode(env);
2976
+ throw new Error(`Unsupported ALEPH_VM_MODE "${mode}" in shared site runner.`);
2977
+ }
2793
2978
  export {
2794
2979
  actionLog,
2795
2980
  appendGithubOutput,
@@ -2807,10 +2992,15 @@ export {
2807
2992
  main,
2808
2993
  optionalEnv,
2809
2994
  parseDeployPlan,
2995
+ parseLastJsonObject,
2810
2996
  parseRootfsRunnerInputs,
2811
2997
  requiredEnv,
2812
2998
  rootfsMain,
2813
2999
  runActionMode,
3000
+ runBootstrapEnvMode,
2814
3001
  runLocalCommand,
2815
- runRootfsMode
3002
+ runProbeMode,
3003
+ runRootfsMode,
3004
+ runSiteMode,
3005
+ runSitePublishMode
2816
3006
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/node",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
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.11",
20
- "@le-space/shared-types": "0.1.11",
21
- "@le-space/rootfs": "0.1.11",
19
+ "@le-space/core": "0.1.13",
20
+ "@le-space/shared-types": "0.1.13",
21
+ "@le-space/rootfs": "0.1.13",
22
22
  "ethers": "^6.15.0"
23
23
  }
24
24
  }