ryugi-artifacts 1.7.0 → 1.7.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.
@@ -92,11 +92,59 @@ PID_FILE="$STATE_DIR/server.pid"
92
92
  LOG_FILE="$STATE_DIR/server.log"
93
93
  SERVER_ID_FILE="$STATE_DIR/server-instance-id"
94
94
  INFO_FILE="$STATE_DIR/server-info"
95
+ VERSION_FILE="$STATE_DIR/server-version"
95
96
  OWNERS_FILE="$STATE_DIR/owners"
96
97
  mkdir -p "$STATE_DIR"
97
98
  [[ -f "$STATE_DIR/.gitignore" ]] || printf '*\n' > "$STATE_DIR/.gitignore"
98
99
 
99
- # A local build (pnpm build:server) wins; otherwise run the release pinned in version.json,
100
+ RELEASES_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ryugi/artifacts"
101
+ LATEST_CHECK_MS=$((24 * 60 * 60 * 1000))
102
+ RETRY_CHECK_MS=$((60 * 60 * 1000))
103
+ SERVER_VERSION=""
104
+
105
+ # Modes: "cached" prints the release npm named within the last day; "checked <version>" records
106
+ # what npm just said. When npm or the install fails, "installed" falls back to the newest release
107
+ # already installed and records it so npm is asked again in an hour, not on every start.
108
+ latest_version() {
109
+ node -e '(() => {
110
+ const fs = require("node:fs");
111
+ const path = require("node:path");
112
+ const [releases, maxAge, retryAfter, mode, fresh] = process.argv.slice(1);
113
+ const file = path.join(releases, "latest.json");
114
+ const valid = (v) => /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(v || "");
115
+ const record = (version, checkedAt) => {
116
+ fs.mkdirSync(releases, { recursive: true });
117
+ fs.writeFileSync(`${file}.${process.pid}`, JSON.stringify({ version, checkedAt }));
118
+ fs.renameSync(`${file}.${process.pid}`, file);
119
+ };
120
+ if (mode === "cached") {
121
+ try {
122
+ const { version, checkedAt } = JSON.parse(fs.readFileSync(file, "utf8"));
123
+ if (valid(version) && Date.now() - checkedAt < Number(maxAge)) console.log(version);
124
+ } catch {}
125
+ return;
126
+ }
127
+ if (mode === "checked" && valid(fresh)) {
128
+ record(fresh, Date.now());
129
+ return console.log(fresh);
130
+ }
131
+ const parts = (v) => v.split(/[-+]/)[0].split(".").map(Number);
132
+ const newer = (a, b) => { const x = parts(a), y = parts(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i]; return 0; };
133
+ let installed = [];
134
+ try { installed = fs.readdirSync(releases).filter((v) => valid(v) && fs.existsSync(path.join(releases, v, "node_modules/ryugi-artifacts/dist/server.cjs"))); } catch {}
135
+ const newest = installed.sort(newer).pop();
136
+ if (!newest) return;
137
+ record(newest, Date.now() - Number(maxAge) + Number(retryAfter));
138
+ console.log(newest);
139
+ })()' "$RELEASES_DIR" "$LATEST_CHECK_MS" "$RETRY_CHECK_MS" "$@"
140
+ }
141
+
142
+ use_release() {
143
+ SERVER_VERSION="$1"
144
+ SERVER="$RELEASES_DIR/$1/node_modules/ryugi-artifacts/dist/server.cjs"
145
+ }
146
+
147
+ # A local build (pnpm build:server) wins; otherwise run the newest ryugi-artifacts release,
100
148
  # installed once per version. Runs before the project lock: a slow install must not outlive
101
149
  # the lock's stale threshold. npm installs into a staging dir that is renamed into place;
102
150
  # rename(2) refuses a non-empty target, so a concurrent start that finished first keeps its install.
@@ -105,16 +153,27 @@ resolve_server() {
105
153
  SERVER="$SCRIPT_DIR/server.cjs"
106
154
  return 0
107
155
  fi
108
- local version cache staging
109
- version="$(node -p 'require(process.argv[1]).version' "$SCRIPT_DIR/version.json")"
110
- cache="${XDG_CACHE_HOME:-$HOME/.cache}/ryugi/artifacts/$version"
111
- SERVER="$cache/node_modules/ryugi-artifacts/dist/server.cjs"
156
+ local version cache staging fallback
157
+ version="$(latest_version cached)"
158
+ [[ -n "$version" ]] || version="$(latest_version checked "$(npm view ryugi-artifacts version --prefer-online --fetch-retries=0 --fetch-timeout=10000 2>/dev/null | tail -1 | tr -d '[:space:]')")"
159
+ if [[ -z "$version" ]]; then
160
+ node -e 'console.log(JSON.stringify({ error: `Could not reach npm for the latest ryugi-artifacts; the first start needs network access and a writable ${process.argv[1]}, so rerun outside the sandbox.` }))' "$RELEASES_DIR"
161
+ return 1
162
+ fi
163
+ use_release "$version"
164
+ cache="$RELEASES_DIR/$version"
112
165
  [[ -f "$SERVER" ]] && return 0
113
166
  staging="$cache.install.$$"
114
167
  rm -rf "$staging"
115
168
  mkdir -p "$staging"
116
169
  if ! npm install --prefix "$staging" --no-save --no-package-lock --no-audit --no-fund --loglevel=error \
117
170
  "ryugi-artifacts@$version" > "$staging.log" 2>&1 || [[ ! -f "$staging/node_modules/ryugi-artifacts/dist/server.cjs" ]]; then
171
+ fallback="$(latest_version installed)"
172
+ if [[ -n "$fallback" ]]; then
173
+ rm -rf "$staging" "$staging.log"
174
+ use_release "$fallback"
175
+ return 0
176
+ fi
118
177
  node -e 'console.log(JSON.stringify({ error: `Could not install ${process.argv[1]} from npm; the first start of each version needs network access and a writable ${process.argv[2]}, so rerun outside the sandbox. npm: ${process.argv[3].trim().split("\n").pop() || "failed"}` }))' \
119
178
  "ryugi-artifacts@$version" "$(dirname "$cache")" "$(cat "$staging.log")"
120
179
  rm -rf "$staging" "$staging.log"
@@ -216,10 +275,29 @@ emit_info() {
216
275
  ' "$1" "$2" "$ARTIFACT"
217
276
  }
218
277
 
219
- if running_server_pid >/dev/null && [[ -f "$INFO_FILE" ]]; then
220
- register_owner
221
- emit_info "$(cat "$INFO_FILE")" true
222
- exit 0
278
+ # A server from an older release is replaced; the new one keeps its port and key, so open tabs reconnect.
279
+ if RUNNING_PID="$(running_server_pid)" && [[ -f "$INFO_FILE" ]]; then
280
+ if [[ -z "$SERVER_VERSION" || "$(cat "$VERSION_FILE" 2>/dev/null)" == "$SERVER_VERSION" ]]; then
281
+ register_owner
282
+ emit_info "$(cat "$INFO_FILE")" true
283
+ exit 0
284
+ fi
285
+ kill -TERM "$RUNNING_PID" 2>/dev/null
286
+ for _ in {1..50}; do
287
+ kill -0 "$RUNNING_PID" 2>/dev/null || break
288
+ sleep 0.1
289
+ done
290
+ if kill -0 "$RUNNING_PID" 2>/dev/null; then
291
+ kill -KILL "$RUNNING_PID" 2>/dev/null
292
+ for _ in {1..20}; do
293
+ kill -0 "$RUNNING_PID" 2>/dev/null || break
294
+ sleep 0.1
295
+ done
296
+ fi
297
+ if kill -0 "$RUNNING_PID" 2>/dev/null; then
298
+ echo "{\"error\": \"The running server (pid $RUNNING_PID) is on an older release and did not stop; stop it with stop-server.sh and retry\"}"
299
+ exit 1
300
+ fi
223
301
  fi
224
302
 
225
303
  register_owner
@@ -233,6 +311,7 @@ if ! [[ "$SERVER_ID" =~ ^[A-Za-z0-9_-]{32,64}$ ]]; then
233
311
  SERVER_ID="$(printf '%08x%08x%08x%08x' "$$" "$(date +%s)" "${RANDOM:-0}" "${RANDOM:-0}")"
234
312
  fi
235
313
  printf '%s\n' "$SERVER_ID" > "$SERVER_ID_FILE"
314
+ printf '%s\n' "$SERVER_VERSION" > "$VERSION_FILE"
236
315
 
237
316
  SERVER_ENV=(ARTIFACTS_ROOT="$ROOT_DIR" ARTIFACTS_HOST="$BIND_HOST" ARTIFACTS_URL_HOST="$URL_HOST" ARTIFACTS_OPEN="$OPEN")
238
317
  [[ -n "$ARTIFACT" ]] && SERVER_ENV+=(ARTIFACTS_OPEN_ARTIFACT="$ARTIFACT")
@@ -247,6 +326,7 @@ if [[ "$FOREGROUND" == "true" ]]; then
247
326
  sleep 0.1
248
327
  done
249
328
  release_lock
329
+ trap - EXIT
250
330
  wait "$SERVER_PID"
251
331
  exit $?
252
332
  fi