querysub 0.710.0 → 0.712.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.710.0",
3
+ "version": "0.712.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -79,7 +79,7 @@
79
79
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
80
80
  "pako": "^2.1.0",
81
81
  "peggy": "^5.0.6",
82
- "sliftutils": "^1.7.149",
82
+ "sliftutils": "^1.8.20",
83
83
  "socket-function": "^1.3.1",
84
84
  "terser": "^5.31.0",
85
85
  "typenode": "^6.6.1",
@@ -102,8 +102,8 @@ export class ServicesListPage extends qreact.Component {
102
102
  parameters: {
103
103
  key: newServiceId,
104
104
  machineIds: [],
105
- repoUrl: gitInfo.repoUrl,
106
- gitRef: gitInfo.latestRef,
105
+ repoUrl: gitInfo?.repoUrl || "",
106
+ gitRef: gitInfo?.latestRef || "",
107
107
  command: "ping 1.1.1.1",
108
108
  deploy: false,
109
109
  overlapTime: DEFAULT_OVERLAP_TIME,
@@ -32,7 +32,7 @@ async function main() {
32
32
  let controller = new MachineServiceControllerBase();
33
33
  let commitMessage = `emergency update ${formatDateTime(Date.now())}`;
34
34
 
35
- let gitInfo = await controller.getGitInfo();
35
+ let gitInfo = await getGitInfoAssert(controller);
36
36
  console.log(magenta(`Starting emergency update of ${gitInfo.repoUrl}`), {
37
37
  commitMessage,
38
38
  querysubUncommittedFiles: gitInfo.querysubUncommitted.length,
@@ -47,7 +47,7 @@ async function main() {
47
47
  }
48
48
 
49
49
  // Re-read, as publishing querysub updates our package.json, which then needs to be committed here
50
- gitInfo = await controller.getGitInfo();
50
+ gitInfo = await getGitInfoAssert(controller);
51
51
  if (gitInfo.uncommitted.length > 0) {
52
52
  console.log(magenta(`Committing and pushing the service repo (${gitInfo.uncommitted.length} files changed)`));
53
53
  await controller.commitPushService(commitMessage);
@@ -56,7 +56,7 @@ async function main() {
56
56
  }
57
57
 
58
58
  // Re-read, as latestRef must include the commit we just pushed
59
- gitInfo = await controller.getGitInfo();
59
+ gitInfo = await getGitInfoAssert(controller);
60
60
 
61
61
  let serviceIds = await controller.getServiceList();
62
62
  let configs: ServiceConfig[] = [];
@@ -92,4 +92,12 @@ async function main() {
92
92
  console.log(green(`Archives initialized`));
93
93
  }
94
94
 
95
+ async function getGitInfoAssert(controller: MachineServiceControllerBase) {
96
+ let gitInfo = await controller.getGitInfo();
97
+ if (!gitInfo) {
98
+ throw new Error(`Expected git info for the emergency update, but getGitInfo returned undefined because this process is public (isPublic() is true). Run the emergency update without --public, from the repo you deploy from.`);
99
+ }
100
+ return gitInfo;
101
+ }
102
+
95
103
  main().catch(logErrors).finally(() => process.exit());
@@ -9,7 +9,8 @@ import { runPromise } from "../functional/runCommand";
9
9
  import { getExternalIP } from "socket-function/src/networking";
10
10
  import { errorToUndefined, errorToUndefinedSilent } from "../errors";
11
11
  import { getDomain } from "../config";
12
- import { formatTime } from "socket-function/src/formatting/format";
12
+ import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
13
+ import { httpsRequest, HttpsResponseInfo } from "socket-function/src/https";
13
14
  import { sort, timeInMinute, timeInSecond, watchSlowPromise } from "socket-function/src/misc";
14
15
  import { isDefined, setPreciseTimeout } from "../misc";
15
16
  import { logLoadTime } from "../logModuleLoadTimes";
@@ -247,6 +248,30 @@ function checkGitSyncedAndClean(gitFolder: string, gitRef: string): Promise<bool
247
248
  return cached;
248
249
  }
249
250
 
251
+ async function recordMissingDependency(config: { gitFolder: string; screenName: string; error: unknown }) {
252
+ let { gitFolder, screenName } = config;
253
+ let match = String((config.error as Error)?.stack || config.error).match(/(https?:\/\/\S+?\.tgz): Request failed "404 Not Found"/);
254
+ if (!match) return;
255
+ let failedUrl = new URL(match[1]);
256
+ let tarballUrl = NPM_REGISTRY_ORIGIN + failedUrl.pathname;
257
+ let outResponse: HttpsResponseInfo = { headers: {} };
258
+ try {
259
+ await httpsRequest(tarballUrl, undefined, "HEAD", false, { outResponse });
260
+ } catch { }
261
+ if (outResponse.statusCode !== 404) {
262
+ console.warn(`Yarn install for ${screenName} failed on ${match[1]} returning 404, but npm returned ${outResponse.statusCode} for ${tarballUrl}, so it is not missing and we are not backing off`);
263
+ return;
264
+ }
265
+ let failures = (missingDependencyRetries.get(gitFolder)?.failures || 0) + 1;
266
+ let nextAttemptTime = failures >= MISSING_DEPENDENCY_MAX_FAILURES && Number.POSITIVE_INFINITY || Date.now() + MISSING_DEPENDENCY_RETRY_DELAY;
267
+ missingDependencyRetries.set(gitFolder, { failures, nextAttemptTime, tarballUrl });
268
+ if (failures >= MISSING_DEPENDENCY_MAX_FAILURES) {
269
+ console.error(`Giving up yarn installing for ${screenName}, ${tarballUrl} is still missing from npm after ${failures} failed installs. Retrying only on a config change.`);
270
+ return;
271
+ }
272
+ console.warn(`Yarn install for ${screenName} failed because ${tarballUrl} is missing from npm (failure ${failures}/${MISSING_DEPENDENCY_MAX_FAILURES}), retrying at ${formatDateTime(nextAttemptTime)}`);
273
+ }
274
+
250
275
  async function getQuerysubVersionMismatch(gitFolder: string): Promise<string> {
251
276
  let packageJson = JSON.parse(await fs.promises.readFile(gitFolder + "package.json", "utf8")) as { dependencies?: { [name: string]: string } };
252
277
  let requested = packageJson.dependencies?.querysub;
@@ -322,6 +347,11 @@ const RESYNC_INTERVAL = timeInSecond * 30;
322
347
  const CONFIG_REFRESH_INTERVAL = RESYNC_INTERVAL * 6;
323
348
 
324
349
  const LAUNCH_VERIFY_DELAY = 2000;
350
+
351
+ const NPM_REGISTRY_ORIGIN = "https://registry.npmjs.org";
352
+ const MISSING_DEPENDENCY_RETRY_DELAY = timeInMinute * 5;
353
+ const MISSING_DEPENDENCY_MAX_FAILURES = 5;
354
+ let missingDependencyRetries = new Map<string, { failures: number; nextAttemptTime: number; tarballUrl: string }>();
325
355
  // How far a re-read start time may drift from the recorded one and still be the same process. Well under any interval a pid could be reused in, and wide enough for sources that only resolve to the second.
326
356
  const START_TIME_TOLERANCE = timeInSecond * 5;
327
357
 
@@ -503,9 +533,22 @@ async function ensureScreen(config: {
503
533
  let afterGitRef = await getGitRefLive(gitFolder);
504
534
  let nodeModulesMissing = !await fsExistsAsync(gitFolder + "node_modules");
505
535
  let querysubMismatch = await getQuerysubVersionMismatch(gitFolder);
506
- if (afterGitRef !== prevGitRef || nodeModulesMissing || querysubMismatch) {
507
- console.log(green(`Yarn installing for ${magenta(screenName)} (ref ${prevGitRef} -> ${afterGitRef}, nodeModulesMissing=${nodeModulesMissing}, querysubMismatch=${querysubMismatch || "none"})`));
508
- await runPromise(`yarn install --mutex network`, { cwd: gitFolder });
536
+ let missingRetry = missingDependencyRetries.get(gitFolder);
537
+ if (afterGitRef !== prevGitRef || nodeModulesMissing || querysubMismatch || missingRetry) {
538
+ if (missingRetry && Date.now() < missingRetry.nextAttemptTime) {
539
+ throw new Error(`Not yarn installing for ${screenName}, ${missingRetry.tarballUrl} was missing from npm after ${missingRetry.failures} failed installs. ${missingRetry.failures >= MISSING_DEPENDENCY_MAX_FAILURES && "Gave up, waiting for a config change to retry" || `Next attempt at ${formatDateTime(missingRetry.nextAttemptTime)}`}`);
540
+ }
541
+ console.log(green(`Yarn installing for ${magenta(screenName)} (ref ${prevGitRef} -> ${afterGitRef}, nodeModulesMissing=${nodeModulesMissing}, querysubMismatch=${querysubMismatch || "none"}, missingDependencyFailures=${missingRetry?.failures || 0})`));
542
+ try {
543
+ await runPromise(`yarn install --mutex network`, { cwd: gitFolder });
544
+ } catch (e) {
545
+ await recordMissingDependency({ gitFolder, screenName, error: e });
546
+ throw e;
547
+ }
548
+ if (missingRetry) {
549
+ console.log(green(`Yarn install for ${magenta(screenName)} succeeded after ${missingRetry.failures} failures from ${missingRetry.tarballUrl} missing from npm`));
550
+ missingDependencyRetries.delete(gitFolder);
551
+ }
509
552
  }
510
553
  }
511
554
 
@@ -654,6 +697,10 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
654
697
  /** A resync that re-reads the configs, however recently they were last read - for when something tells us they changed. */
655
698
  async function resyncServicesWithFreshConfigs() {
656
699
  configRefreshRequested = true;
700
+ if (missingDependencyRetries.size) {
701
+ console.log(`Config changed, clearing the missing dependency backoff for ${Array.from(missingDependencyRetries.keys()).join(", ")}`);
702
+ missingDependencyRetries.clear();
703
+ }
657
704
  await resyncServices();
658
705
  }
659
706
  async function resyncServices() {
@@ -1,6 +1,6 @@
1
1
  import { isNodeTrue, list, timeInDay, timeInMinute, timeInSecond } from "socket-function/src/misc";
2
2
  import { getArchives2 } from "../-a-archives/archives2";
3
- import { getDomain } from "../config";
3
+ import { getDomain, isPublic } from "../config";
4
4
  import { archiveJSONT } from "../-a-archives/archivesJSONT";
5
5
  import { SocketFunction } from "socket-function/SocketFunction";
6
6
  import { assertIsManagementUser } from "../diagnostics/managementPages";
@@ -442,6 +442,7 @@ export class MachineServiceControllerBase {
442
442
  return extractType(module, "ServiceConfig");
443
443
  }
444
444
  public async getGitInfo() {
445
+ if (isPublic()) return undefined;
445
446
  let repoUrl = await getGitURLLive();
446
447
  let uncommitted = await getGitUncommitted(".");
447
448
  let latestRef = await getLatestRefOnUpstreamBranch(".");