@openmeet/snowglobe 0.3.0-rc.1 → 0.3.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/lib/apps.mjs CHANGED
@@ -15,7 +15,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
15
15
  import { join } from 'node:path';
16
16
  import { ROOT, globeFacts, paths } from './env.mjs';
17
17
 
18
- const HEADER = '# Written by bin/snowglobe.mjs app from https/apps; rerun it instead of editing.\n';
18
+ const HEADER = '# Written by snowglobe app from https/apps; rerun it instead of editing.\n';
19
19
 
20
20
  /**
21
21
  * The first saved route whose ending is `app`, or null. app.<domain> is where the apps are, so an
@@ -25,7 +25,7 @@ export const appEnding = (apps) => apps.find((a) => a.ending === 'app') ?? null;
25
25
 
26
26
  export const appEndingMessage = (file, a) =>
27
27
  `${file}: the line "${formatApps([a]).trim()}" claims the ending app, which is where the apps are (${a.name}.app.<domain>); ` +
28
- `remove the route (bin/snowglobe.mjs app --remove ${a.name}) or give it another ending`;
28
+ `remove the route (snowglobe app --remove ${a.name}) or give it another ending`;
29
29
 
30
30
  /** The routes in an apps file's text: [{ name, port, ending }], ending null when none. */
31
31
  export function parseApps(text) {
@@ -85,7 +85,7 @@ export const writeApps = (p, apps) => put(p.apps, formatApps(apps));
85
85
  export function renderRoutes({ root = ROOT, state, env = process.env } = {}) {
86
86
  const p = paths(root, state);
87
87
  const pageJson = join(p.globe, 'page.json');
88
- if (!existsSync(pageJson)) throw new Error(`No ${pageJson}: run bin/snowglobe.mjs up, which renders it`);
88
+ if (!existsSync(pageJson)) throw new Error(`No ${pageJson}: run snowglobe up, which renders it`);
89
89
  const { domain } = globeFacts({ root, state, env });
90
90
  const apps = readApps(p);
91
91
  const bad = appEnding(apps);
package/lib/render.mjs CHANGED
@@ -452,14 +452,14 @@ export function renderGlobe(globe, base, { stateDir, globeDir = join(stateDir ??
452
452
  }
453
453
 
454
454
  const header = (c) =>
455
- `${c} Written by bin/snowglobe.mjs render from ${source}; render again instead of editing.\n`;
455
+ `${c} Written by snowglobe render from ${source}; render again instead of editing.\n`;
456
456
 
457
457
  const compose =
458
458
  header('#') +
459
459
  `# The https globe "${globe.globe}" at ${D}: ${globe.pds.map((p) => p.name).join(', ')}` +
460
460
  (globe.grouphosts.length ? `; group hosts ${globe.grouphosts.map((g) => g.name).join(', ')}` : '') +
461
461
  '.\n' +
462
- '# Run it with bin/snowglobe.mjs up, which renders this file first.\n\n' +
462
+ '# Run it with snowglobe up, which renders this file first.\n\n' +
463
463
  // YAML 1.1 quoting, so a value such as restart's "no" stays a string to compose.
464
464
  stringify({ services, volumes, networks: { [NETWORK]: null } }, { lineWidth: 0, nullStr: '', aliasDuplicateObjects: false, version: '1.1' });
465
465
 
@@ -514,15 +514,18 @@ export function renderGlobe(globe, base, { stateDir, globeDir = join(stateDir ??
514
514
  const named = Object.entries(IMAGE_NAMES).find(([, ref]) => ref === image)?.[0];
515
515
  return named ?? image.split('/').pop().replace(/@sha256:([0-9a-f]{12})[0-9a-f]+$/, '@sha256:$1…');
516
516
  };
517
+ // What the globe is made of, as the page's first line names it: only what runs.
518
+ const parts = [globe.pds.length === 1 ? 'PDS' : 'PDSs', 'PLC directory', on.relay && 'relay', on.jetstream && 'Jetstream'].filter(Boolean);
517
519
  const page = {
518
520
  globe: globe.globe,
519
521
  domain: D,
520
522
  about:
521
- 'An atproto network on this machine: its own PDSs, PLC directory, relay and Jetstream. Its accounts live in its own directory, not the public one, so Bluesky cannot find anything here. An app is handed this network\'s addresses (snowglobe env), not Bluesky\'s.',
523
+ `An atproto network on this machine: its own ${parts.slice(0, -1).join(', ')} and ${parts.at(-1)}. Its accounts live in its own directory, not the public one, so Bluesky cannot find anything here. An app is handed this network's addresses (snowglobe env), not Bluesky's.`,
522
524
  accountsNote:
523
525
  `Every account on the network, on each of its ${globe.pds.length} PDS${globe.pds.length === 1 ? '' : 's'}` +
524
526
  (globe.grouphosts.length ? `, and every group on its group host${globe.grouphosts.length === 1 ? '' : 's'}.` : '.'),
525
- ...(on.jetstream ? { feed: '/subscribe' } : {}),
527
+ // Without Jetstream the page has no feed to follow, and says so instead of reconnecting.
528
+ feed: on.jetstream ? '/subscribe' : false,
526
529
  services: [
527
530
  ...globe.pds.map((p) => [
528
531
  'PDS',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmeet/snowglobe",
3
- "version": "0.3.0-rc.1",
3
+ "version": "0.3.0",
4
4
  "description": "Standalone, testable ATProto development network for local development and CI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,11 @@
1
1
  # The default snowglobe: one current PDS, a PLC directory and Jetstream, all at https names, with
2
2
  # Slingshot and PDSls (the record browser) beside them.
3
- # Copy this file to snowglobe.yml (here, or in your app's repo) and run bin/snowglobe.mjs up.
4
- # The commented sections below add more; docs/snowglobe.md says what each one does, and
5
- # docs/examples/atmo.yml is the whole thing: three PDSes, a relay and a lexicon authority.
3
+ # Copy this file to snowglobe.yml (here, or in your app's repo) and run snowglobe up
4
+ # (bin/snowglobe.mjs up from a checkout of snowglobe). The commented sections below add more.
5
+ # What each one does:
6
+ # https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/snowglobe.md
7
+ # The whole thing, three PDSes, a relay and a lexicon authority:
8
+ # https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/examples/atmo.yml
6
9
 
7
10
  globe: snowglobe
8
11
  domain: local.snowglobe.at # every name below is under it
@@ -26,7 +29,7 @@ services: # these are the defaults, each true or false
26
29
  jetstream: true
27
30
  slingshot: true # answers a handle's DID and PDS inside the network; needs jetstream
28
31
  pdsls: true # https://pdsls.local.snowglobe.at, a browser for the network's records
29
- tap: false # sends an app the events of one collection (true, or a map; see docs/snowglobe.md)
32
+ tap: false # sends an app the events of one collection (true, or a map; see snowglobe.md above)
30
33
  # relay: auto # auto: on when there is more than one PDS
31
34
 
32
35
  # Where every PDS resolves lexicons (NSIDs) from. Name a PDS that is in this file.
@@ -45,7 +48,8 @@ accounts: # <name>@<pds>; up makes each one
45
48
  - bob@main
46
49
 
47
50
  # Your app, reached at https://myapp.app.local.snowglobe.at, with settings from the snowglobe.
48
- # `snowglobe env` hands them over; docs/your-app.md has the names.
51
+ # `snowglobe env` hands them over. The names:
52
+ # https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/your-app.md
49
53
  # apps:
50
54
  # - name: myapp
51
55
  # local: { port: 5173 }
@@ -96,7 +96,7 @@ server {
96
96
  listen 443 ssl;
97
97
  server_name *.app.${SG_DOMAIN};
98
98
  if ($snowglobe_app = "") {
99
- return 404 "No app is registered at $host (bin/snowglobe.mjs app).\n";
99
+ return 404 "No app is registered at $host (snowglobe app).\n";
100
100
  }
101
101
  location / {
102
102
  proxy_pass http://$snowglobe_app;
@@ -233,7 +233,8 @@
233
233
  if (tally.people != null) parts.push(plural(tally.people, "person", "people"));
234
234
  if (tally.groups) parts.push(plural(tally.groups, "group", "groups"));
235
235
  var line = parts.length ? parts.join(" and ") + ". " : "";
236
- document.getElementById("tally").textContent = line + plural(tally.writes, "write", "writes") + " in the last 24 hours.";
236
+ // writes is null on a network with no feed to count them from.
237
+ document.getElementById("tally").textContent = line + (tally.writes == null ? "" : plural(tally.writes, "write", "writes") + " in the last 24 hours.");
237
238
  }
238
239
 
239
240
  function health(domain, services) {
@@ -380,6 +381,15 @@
380
381
  connect();
381
382
  }
382
383
 
384
+ // A network with no Jetstream has no feed: the panel says so instead of reconnecting forever.
385
+ function noActivity() {
386
+ document.getElementById("stream").style.display = "none";
387
+ document.getElementById("count").parentNode.style.display = "none";
388
+ var body = document.getElementById("events");
389
+ body.textContent = "";
390
+ add(body, el("p", { "class": "empty" }, "This network runs no Jetstream, so its writes are not followed here."));
391
+ }
392
+
383
393
  getJSON("/snowglobe.json").then(function (g) {
384
394
  document.title = g.globe + " · Snowglobe network";
385
395
  document.getElementById("title").textContent = g.globe;
@@ -396,12 +406,14 @@
396
406
  }
397
407
  if (g.about) document.getElementById("about").textContent = g.about;
398
408
  if (g.accountsNote) document.getElementById("accounts-note").textContent = g.accountsNote;
399
- var feed = g.feed || "wss://jetstream." + g.domain + "/subscribe";
400
- if (feed.charAt(0) === "/") feed = "wss://" + location.host + feed;
409
+ // feed false: the network runs no Jetstream. Unset, Jetstream is at its usual public name.
410
+ var feed = g.feed === false ? null : g.feed || "wss://jetstream." + g.domain + "/subscribe";
411
+ if (feed && feed.charAt(0) === "/") feed = "wss://" + location.host + feed;
412
+ if (!feed) { tally.writes = null; noActivity(); }
401
413
  showTally();
402
414
  health(g.domain, g.services || SERVICES);
403
415
  // The feed names accounts by handle, so it waits for the account list.
404
- accounts(g.accounts || ACCOUNTS).catch(function () {}).then(function () { activity(feed); });
416
+ accounts(g.accounts || ACCOUNTS).catch(function () {}).then(function () { if (feed) activity(feed); });
405
417
  });
406
418
  })();
407
419
  </script>