@le-space/node 0.1.16 → 0.1.18

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/index.d.ts CHANGED
@@ -231,6 +231,20 @@ declare function retainSuccessfulDeployments(args: {
231
231
  hash: string;
232
232
  result: Awaited<ReturnType<typeof forgetAlephMessages>>;
233
233
  }[];
234
+ forgetStageResults: {
235
+ stage: "instances" | "dependents";
236
+ hashes: string[];
237
+ skipped?: boolean;
238
+ skippedReason?: string;
239
+ forgottenHashes: string[];
240
+ outstandingForgetHashes: string[];
241
+ statuses: Record<string, string>;
242
+ forgetResult: Awaited<ReturnType<typeof forgetAlephMessages>> | null;
243
+ followUpForgetResults: Array<{
244
+ hash: string;
245
+ result: Awaited<ReturnType<typeof forgetAlephMessages>>;
246
+ }>;
247
+ }[];
234
248
  }>;
235
249
 
236
250
  declare function buildScaffoldDeployResult(env?: NodeJS.ProcessEnv): DeployOutputResult;
package/index.js CHANGED
@@ -896,6 +896,26 @@ function retentionRecordId(record) {
896
896
  function hashesFromRetentionRecord(record) {
897
897
  return [record.instance_item_hash, record.rootfs_item_hash, record.site_item_hash].filter(Boolean);
898
898
  }
899
+ function uniqueHashes(hashes) {
900
+ return [...new Set(hashes.filter(Boolean))];
901
+ }
902
+ function dependentHashesFromRetentionRecord(record) {
903
+ return [record.rootfs_item_hash, record.site_item_hash].filter(Boolean);
904
+ }
905
+ function splitForgetStages(args) {
906
+ const instanceForgetHashes = uniqueHashes(args.prunedRecords.map((record) => record.instance_item_hash)).filter(
907
+ (hash) => !args.retainedHashes.has(hash)
908
+ );
909
+ const dependentForgetHashes = uniqueHashes([
910
+ ...args.prunedRecords.flatMap(dependentHashesFromRetentionRecord),
911
+ ...args.extraForgetHashes
912
+ ]).filter((hash) => !args.retainedHashes.has(hash) && !instanceForgetHashes.includes(hash));
913
+ return {
914
+ instanceForgetHashes,
915
+ dependentForgetHashes,
916
+ orderedForgetHashes: [...instanceForgetHashes, ...dependentForgetHashes]
917
+ };
918
+ }
899
919
  async function fetchAggregateKey(args) {
900
920
  const requestUrl = new URL(`/api/v0/aggregates/${args.address}.json`, args.apiHost ?? "https://api2.aleph.im");
901
921
  requestUrl.searchParams.set("keys", args.key);
@@ -973,6 +993,73 @@ async function classifyForgetHashes(args) {
973
993
  statuses
974
994
  };
975
995
  }
996
+ async function executeForgetStage(args) {
997
+ const stageHashes = uniqueHashes(args.hashes);
998
+ let forgetResult = null;
999
+ const followUpForgetResults = [];
1000
+ if (stageHashes.length === 0) {
1001
+ return {
1002
+ hashes: stageHashes,
1003
+ forgottenHashes: [],
1004
+ outstandingForgetHashes: [],
1005
+ statuses: {},
1006
+ forgetResult,
1007
+ followUpForgetResults
1008
+ };
1009
+ }
1010
+ let classified = await classifyForgetHashes({
1011
+ hashes: stageHashes,
1012
+ fetch: args.fetch,
1013
+ apiHost: args.apiHost
1014
+ });
1015
+ if (classified.outstandingForgetHashes.length > 0) {
1016
+ forgetResult = await forgetAlephMessages({
1017
+ sender: args.sender,
1018
+ hashes: classified.outstandingForgetHashes,
1019
+ reason: args.reason,
1020
+ signer: args.signer,
1021
+ hasher: args.hasher,
1022
+ fetch: args.fetch,
1023
+ channel: args.channel,
1024
+ apiHost: args.apiHost,
1025
+ now: args.now
1026
+ });
1027
+ classified = await classifyForgetHashes({
1028
+ hashes: stageHashes,
1029
+ fetch: args.fetch,
1030
+ apiHost: args.apiHost
1031
+ });
1032
+ if (classified.outstandingForgetHashes.length > 0) {
1033
+ for (const hash of classified.outstandingForgetHashes) {
1034
+ const result = await forgetAlephMessages({
1035
+ sender: args.sender,
1036
+ hashes: [hash],
1037
+ reason: args.reason,
1038
+ signer: args.signer,
1039
+ hasher: args.hasher,
1040
+ fetch: args.fetch,
1041
+ channel: args.channel,
1042
+ apiHost: args.apiHost,
1043
+ now: args.now
1044
+ });
1045
+ followUpForgetResults.push({ hash, result });
1046
+ }
1047
+ classified = await classifyForgetHashes({
1048
+ hashes: stageHashes,
1049
+ fetch: args.fetch,
1050
+ apiHost: args.apiHost
1051
+ });
1052
+ }
1053
+ }
1054
+ return {
1055
+ hashes: stageHashes,
1056
+ forgottenHashes: classified.forgottenHashes,
1057
+ outstandingForgetHashes: classified.outstandingForgetHashes,
1058
+ statuses: classified.statuses,
1059
+ forgetResult,
1060
+ followUpForgetResults
1061
+ };
1062
+ }
976
1063
  async function retainSuccessfulDeployments(args) {
977
1064
  const keepCount = Math.max(0, Number.parseInt(String(args.keepCount ?? 0), 10) || 0);
978
1065
  const aggregateKey = asString3(args.aggregateKey) ?? SUCCESSFUL_DEPLOYMENTS_AGGREGATE_KEY;
@@ -999,10 +1086,13 @@ async function retainSuccessfulDeployments(args) {
999
1086
  const retainedRecords = keepCount > 0 ? uniqueRecords.slice(0, keepCount) : [];
1000
1087
  const prunedRecords = keepCount > 0 ? uniqueRecords.slice(keepCount) : uniqueRecords;
1001
1088
  const retainedHashes = new Set(retainedRecords.flatMap(hashesFromRetentionRecord));
1002
- const extraForgetHashes = [...new Set((args.extraForgetHashes ?? []).filter(Boolean))];
1003
- const forgetHashes = [.../* @__PURE__ */ new Set([...prunedRecords.flatMap(hashesFromRetentionRecord), ...extraForgetHashes])].filter(
1004
- (hash) => !retainedHashes.has(hash)
1005
- );
1089
+ const extraForgetHashes = uniqueHashes(args.extraForgetHashes ?? []);
1090
+ const { instanceForgetHashes, dependentForgetHashes, orderedForgetHashes } = splitForgetStages({
1091
+ prunedRecords,
1092
+ extraForgetHashes,
1093
+ retainedHashes
1094
+ });
1095
+ const forgetHashes = orderedForgetHashes;
1006
1096
  const aggregateContent = {
1007
1097
  keep: keepCount,
1008
1098
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1019,25 +1109,40 @@ async function retainSuccessfulDeployments(args) {
1019
1109
  apiHost: args.apiHost,
1020
1110
  now: args.now
1021
1111
  });
1022
- let forgetResult = null;
1023
- let followUpForgetResults = [];
1112
+ const forgetReason = args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`;
1113
+ const forgetStageResults = [];
1024
1114
  let forgottenHashes = [];
1025
1115
  let outstandingForgetHashes = [];
1026
1116
  let forgetStatuses = {};
1027
- if (forgetHashes.length > 0) {
1028
- const initiallyOutstanding = await classifyForgetHashes({
1029
- hashes: forgetHashes,
1117
+ let forgetResult = null;
1118
+ const followUpForgetResults = [];
1119
+ if (instanceForgetHashes.length > 0) {
1120
+ const instanceStage = await executeForgetStage({
1121
+ sender: args.sender,
1122
+ hashes: instanceForgetHashes,
1123
+ reason: forgetReason,
1124
+ signer: args.signer,
1125
+ hasher: args.hasher,
1030
1126
  fetch: args.fetch,
1031
- apiHost: args.apiHost
1127
+ channel: args.channel,
1128
+ apiHost: args.apiHost,
1129
+ now: args.now
1032
1130
  });
1033
- forgottenHashes = initiallyOutstanding.forgottenHashes;
1034
- outstandingForgetHashes = initiallyOutstanding.outstandingForgetHashes;
1035
- forgetStatuses = initiallyOutstanding.statuses;
1036
- if (outstandingForgetHashes.length > 0) {
1037
- forgetResult = await forgetAlephMessages({
1131
+ forgetStageResults.push({ stage: "instances", ...instanceStage });
1132
+ forgottenHashes.push(...instanceStage.forgottenHashes);
1133
+ outstandingForgetHashes.push(...instanceStage.outstandingForgetHashes);
1134
+ forgetStatuses = { ...forgetStatuses, ...instanceStage.statuses };
1135
+ if (!forgetResult && instanceStage.forgetResult) {
1136
+ forgetResult = instanceStage.forgetResult;
1137
+ }
1138
+ followUpForgetResults.push(...instanceStage.followUpForgetResults);
1139
+ }
1140
+ if (dependentForgetHashes.length > 0) {
1141
+ if (outstandingForgetHashes.length === 0) {
1142
+ const dependentStage = await executeForgetStage({
1038
1143
  sender: args.sender,
1039
- hashes: outstandingForgetHashes,
1040
- reason: args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`,
1144
+ hashes: dependentForgetHashes,
1145
+ reason: forgetReason,
1041
1146
  signer: args.signer,
1042
1147
  hasher: args.hasher,
1043
1148
  fetch: args.fetch,
@@ -1045,40 +1150,38 @@ async function retainSuccessfulDeployments(args) {
1045
1150
  apiHost: args.apiHost,
1046
1151
  now: args.now
1047
1152
  });
1048
- let postBatch = await classifyForgetHashes({
1049
- hashes: forgetHashes,
1153
+ forgetStageResults.push({ stage: "dependents", ...dependentStage });
1154
+ forgottenHashes.push(...dependentStage.forgottenHashes);
1155
+ outstandingForgetHashes.push(...dependentStage.outstandingForgetHashes);
1156
+ forgetStatuses = { ...forgetStatuses, ...dependentStage.statuses };
1157
+ if (!forgetResult && dependentStage.forgetResult) {
1158
+ forgetResult = dependentStage.forgetResult;
1159
+ }
1160
+ followUpForgetResults.push(...dependentStage.followUpForgetResults);
1161
+ } else {
1162
+ const dependentStageStatus = await classifyForgetHashes({
1163
+ hashes: dependentForgetHashes,
1050
1164
  fetch: args.fetch,
1051
1165
  apiHost: args.apiHost
1052
1166
  });
1053
- forgottenHashes = postBatch.forgottenHashes;
1054
- outstandingForgetHashes = postBatch.outstandingForgetHashes;
1055
- forgetStatuses = postBatch.statuses;
1056
- if (outstandingForgetHashes.length > 0) {
1057
- for (const hash of outstandingForgetHashes) {
1058
- const result = await forgetAlephMessages({
1059
- sender: args.sender,
1060
- hashes: [hash],
1061
- reason: args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`,
1062
- signer: args.signer,
1063
- hasher: args.hasher,
1064
- fetch: args.fetch,
1065
- channel: args.channel,
1066
- apiHost: args.apiHost,
1067
- now: args.now
1068
- });
1069
- followUpForgetResults.push({ hash, result });
1070
- }
1071
- postBatch = await classifyForgetHashes({
1072
- hashes: forgetHashes,
1073
- fetch: args.fetch,
1074
- apiHost: args.apiHost
1075
- });
1076
- forgottenHashes = postBatch.forgottenHashes;
1077
- outstandingForgetHashes = postBatch.outstandingForgetHashes;
1078
- forgetStatuses = postBatch.statuses;
1079
- }
1167
+ forgetStageResults.push({
1168
+ stage: "dependents",
1169
+ hashes: dependentForgetHashes,
1170
+ skipped: true,
1171
+ skippedReason: "Waiting for instance forget stage to complete before pruning dependent store items.",
1172
+ forgottenHashes: dependentStageStatus.forgottenHashes,
1173
+ outstandingForgetHashes: dependentStageStatus.outstandingForgetHashes,
1174
+ statuses: dependentStageStatus.statuses,
1175
+ forgetResult: null,
1176
+ followUpForgetResults: []
1177
+ });
1178
+ forgottenHashes.push(...dependentStageStatus.forgottenHashes);
1179
+ outstandingForgetHashes.push(...dependentStageStatus.outstandingForgetHashes);
1180
+ forgetStatuses = { ...forgetStatuses, ...dependentStageStatus.statuses };
1080
1181
  }
1081
1182
  }
1183
+ forgottenHashes = uniqueHashes(forgottenHashes);
1184
+ outstandingForgetHashes = uniqueHashes(outstandingForgetHashes);
1082
1185
  return {
1083
1186
  sender: args.sender,
1084
1187
  aggregateKey,
@@ -1091,7 +1194,8 @@ async function retainSuccessfulDeployments(args) {
1091
1194
  outstandingForgetHashes,
1092
1195
  forgetStatuses,
1093
1196
  forgetResult,
1094
- followUpForgetResults
1197
+ followUpForgetResults,
1198
+ forgetStageResults
1095
1199
  };
1096
1200
  }
1097
1201
 
@@ -2905,6 +3009,7 @@ if (import.meta.url === pathToFileURL2(process.argv[1] ?? "").href) {
2905
3009
  // src/site-runner.ts
2906
3010
  import process2 from "process";
2907
3011
  import { spawn as spawn2 } from "child_process";
3012
+ import { fileURLToPath as fileURLToPath2 } from "url";
2908
3013
  async function runCapture(command, args, options = {}) {
2909
3014
  return await new Promise((resolve, reject) => {
2910
3015
  const child = spawn2(command, args, {
@@ -2957,6 +3062,9 @@ async function waitForAlephMessage(itemHash, env = process2.env) {
2957
3062
  }
2958
3063
  throw new Error(`Aleph STORE message ${itemHash} did not become processed in time.`);
2959
3064
  }
3065
+ function defaultSitePublishScriptPath() {
3066
+ return fileURLToPath2(new URL("../reference/publish-static-site.py", import.meta.url));
3067
+ }
2960
3068
  function mergedAddrs(env = process2.env) {
2961
3069
  const combined = [];
2962
3070
  for (const key of ["PROBE_MULTIADDRS_JSON", "BROWSER_BOOTSTRAP_MULTIADDRS_JSON"]) {
@@ -2972,7 +3080,7 @@ function mergedAddrs(env = process2.env) {
2972
3080
  }
2973
3081
  async function runSitePublishMode(env = process2.env) {
2974
3082
  const projectDir = requiredEnv("ALEPH_SITE_PROJECT_DIR", env);
2975
- const publishScript = optionalEnv("ALEPH_SITE_PUBLISH_SCRIPT", "go-peer/aleph/publish-static-site.py", env);
3083
+ const publishScript = optionalEnv("ALEPH_SITE_PUBLISH_SCRIPT", defaultSitePublishScriptPath(), env);
2976
3084
  const siteDirectory = requiredEnv("ALEPH_SITE_DIRECTORY", env);
2977
3085
  const pythonBin = optionalEnv("ALEPH_SITE_PYTHON", "python3", env);
2978
3086
  const alephBin = optionalEnv("ALEPH_SITE_ALEPH_BIN", "aleph", env);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/node",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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.16",
20
- "@le-space/shared-types": "0.1.16",
21
- "@le-space/rootfs": "0.1.16",
19
+ "@le-space/core": "0.1.18",
20
+ "@le-space/shared-types": "0.1.18",
21
+ "@le-space/rootfs": "0.1.18",
22
22
  "ethers": "^6.15.0"
23
23
  }
24
24
  }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import requests
7
+ from cid import make_cid
8
+
9
+
10
+ def upload_directory(folder: Path, gateway: str) -> dict[str, str]:
11
+ url = f"{gateway.rstrip('/')}/api/v0/add"
12
+ params = {
13
+ "recursive": "true",
14
+ "wrap-with-directory": "true",
15
+ }
16
+
17
+ handles = []
18
+ files = []
19
+ try:
20
+ for path in sorted(folder.rglob("*")):
21
+ if not path.is_file():
22
+ continue
23
+ relative_path = path.relative_to(folder)
24
+ handle = path.open("rb")
25
+ handles.append(handle)
26
+ files.append(("file", (str(relative_path), handle)))
27
+
28
+ if not files:
29
+ raise RuntimeError(f"No files found under {folder}")
30
+
31
+ response = requests.post(url, params=params, files=files, timeout=300)
32
+ response.raise_for_status()
33
+
34
+ cid_v0 = None
35
+ for line in response.text.strip().splitlines():
36
+ entry = json.loads(line)
37
+ cid_v0 = entry.get("Hash") or cid_v0
38
+
39
+ if not cid_v0:
40
+ raise RuntimeError("CID not found in IPFS response")
41
+
42
+ cid_v1 = make_cid(cid_v0).to_v1().encode("base32").decode()
43
+ return {"cid_v0": cid_v0, "cid_v1": cid_v1}
44
+ finally:
45
+ for handle in handles:
46
+ handle.close()
47
+
48
+
49
+ def main() -> int:
50
+ if len(sys.argv) != 2:
51
+ print("usage: publish-static-site.py <directory>", file=sys.stderr)
52
+ return 2
53
+
54
+ directory = Path(sys.argv[1]).resolve()
55
+ if not directory.is_dir():
56
+ print(f"Error: path must be a directory: {directory}", file=sys.stderr)
57
+ return 1
58
+
59
+ result = upload_directory(directory, "https://ipfs-2.aleph.im")
60
+ print(json.dumps(result))
61
+ return 0
62
+
63
+
64
+ if __name__ == "__main__":
65
+ raise SystemExit(main())