overlay-factory-worker 0.1.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/README.md +215 -0
- package/package.json +64 -0
- package/public/fonts/Handjet-variable.woff2 +0 -0
- package/remotion.config.ts +14 -0
- package/scripts/check-legibility.ts +284 -0
- package/scripts/check-safe-area.ts +148 -0
- package/scripts/custom-fonts.ts +114 -0
- package/scripts/export.sh +31 -0
- package/scripts/field-images.ts +93 -0
- package/scripts/ig-probe.ts +83 -0
- package/scripts/ig-sync.ts +204 -0
- package/scripts/ingest.sh +34 -0
- package/scripts/library.ts +143 -0
- package/scripts/look-card.ts +107 -0
- package/scripts/look-store.ts +176 -0
- package/scripts/make-card.ts +237 -0
- package/scripts/merge-index.ts +74 -0
- package/scripts/new-episode.ts +149 -0
- package/scripts/overlay-worker.ts +1119 -0
- package/scripts/place-overlay.ts +359 -0
- package/scripts/prep-card.ts +73 -0
- package/scripts/quality.ts +0 -0
- package/scripts/render-overlay.ts +150 -0
- package/scripts/report.ts +127 -0
- package/scripts/rerender-cards.ts +116 -0
- package/scripts/series.ts +816 -0
- package/scripts/set-difficulty.ts +62 -0
- package/scripts/state-dir.ts +102 -0
- package/scripts/stock.ts +254 -0
- package/scripts/verify.ts +149 -0
- package/scripts/wp-restock.ts +281 -0
- package/src/Root.tsx +112 -0
- package/src/index.css +1 -0
- package/src/index.ts +4 -0
- package/src/lab/FontLab.tsx +50 -0
- package/src/lab/FontSheet.tsx +188 -0
- package/src/lab/PillLab.tsx +121 -0
- package/src/overlay/Composition.tsx +297 -0
- package/src/overlay/DifficultyMeter.tsx +86 -0
- package/src/overlay/PixelText.tsx +134 -0
- package/src/overlay/Title.tsx +75 -0
- package/src/overlay/brandFonts.ts +58 -0
- package/src/overlay/cardLayout.ts +94 -0
- package/src/overlay/fonts.ts +19 -0
- package/src/overlay/look.ts +155 -0
- package/src/overlay/safeArea.ts +89 -0
- package/src/overlay/types.ts +134 -0
- package/src/series/what-prints/CodeCard.tsx +107 -0
- package/src/series/what-prints/Composition.tsx +106 -0
- package/src/series/what-prints/codeCardTypes.ts +105 -0
- package/src/series/what-prints/types.ts +22 -0
- package/tsconfig.json +17 -0
- package/worker/cli.mjs +151 -0
- package/worker/service.mjs +404 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
// Background-service install/uninstall for the Overlay Factory worker.
|
|
2
|
+
//
|
|
3
|
+
// Modelled on goosetools-worker's service.js so all four workers behave the
|
|
4
|
+
// same way: the token lands in ~/.goosetools/env (shared — one connected
|
|
5
|
+
// computer, one token), a stable copy of this package is installed under
|
|
6
|
+
// ~/.goosetools/overlay-app, and a launchd LaunchAgent runs it whenever the
|
|
7
|
+
// machine is on.
|
|
8
|
+
//
|
|
9
|
+
// Two deliberate differences from the other workers:
|
|
10
|
+
//
|
|
11
|
+
// 1. macOS only. The composite pass encodes with hevc_videotoolbox, which
|
|
12
|
+
// is an Apple framework — there is no Windows path to fall back to yet.
|
|
13
|
+
// 2. `uninstall` does NOT remove ~/.goosetools. The carousel worker can:
|
|
14
|
+
// everything it keeps there is re-derivable from the server. This one
|
|
15
|
+
// keeps the puzzle library, the learned looks, and the used-card record
|
|
16
|
+
// in ~/.goosetools/overlay, and none of that exists anywhere else.
|
|
17
|
+
|
|
18
|
+
import { execSync } from "node:child_process";
|
|
19
|
+
import {
|
|
20
|
+
chmodSync,
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
readdirSync,
|
|
24
|
+
readFileSync,
|
|
25
|
+
rmSync,
|
|
26
|
+
statSync,
|
|
27
|
+
writeFileSync,
|
|
28
|
+
} from "node:fs";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
import { dirname, join } from "node:path";
|
|
31
|
+
|
|
32
|
+
export const GOOSE_DIR = join(homedir(), ".goosetools");
|
|
33
|
+
const ENV_FILE = join(GOOSE_DIR, "env");
|
|
34
|
+
const APP_PREFIX = join(GOOSE_DIR, "overlay-app");
|
|
35
|
+
const STATE_DIR = join(GOOSE_DIR, "overlay");
|
|
36
|
+
const PKG = "overlay-factory-worker";
|
|
37
|
+
const INSTALLED_CLI = join(APP_PREFIX, "node_modules", PKG, "worker", "cli.mjs");
|
|
38
|
+
|
|
39
|
+
const MAC_LABEL = "com.goosetools.overlay";
|
|
40
|
+
const MAC_PLIST = join(homedir(), "Library", "LaunchAgents", `${MAC_LABEL}.plist`);
|
|
41
|
+
const LOG = join(GOOSE_DIR, "overlay-worker.log");
|
|
42
|
+
const ERR_LOG = join(GOOSE_DIR, "overlay-worker.err.log");
|
|
43
|
+
|
|
44
|
+
function sh(cmd, opts = {}) {
|
|
45
|
+
return execSync(cmd, { stdio: ["ignore", "pipe", "pipe"], ...opts })
|
|
46
|
+
.toString()
|
|
47
|
+
.trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function quietSh(cmd) {
|
|
51
|
+
try {
|
|
52
|
+
sh(cmd);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isEmptyDir(dir) {
|
|
60
|
+
try {
|
|
61
|
+
return readdirSync(dir).length === 0;
|
|
62
|
+
} catch {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function hasFfmpeg() {
|
|
68
|
+
return quietSh("ffmpeg -version") && quietSh("ffprobe -version");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function npmGlobalBin() {
|
|
72
|
+
try {
|
|
73
|
+
return join(sh("npm prefix -g", { shell: true }), "bin");
|
|
74
|
+
} catch {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// launchd starts an agent with an almost empty environment. The render shells
|
|
80
|
+
// out to ffmpeg, npx and claude, so PATH is spelled out here — inheriting the
|
|
81
|
+
// interactive shell's PATH is exactly what does not happen, and it's the usual
|
|
82
|
+
// way one of these agents ends up running but failing every job.
|
|
83
|
+
function servicePath() {
|
|
84
|
+
const parts = [
|
|
85
|
+
dirname(process.execPath),
|
|
86
|
+
npmGlobalBin(),
|
|
87
|
+
"/opt/homebrew/bin",
|
|
88
|
+
"/usr/local/bin",
|
|
89
|
+
`${process.env.HOME ?? ""}/.local/bin`,
|
|
90
|
+
"/usr/bin",
|
|
91
|
+
"/bin",
|
|
92
|
+
"/usr/sbin",
|
|
93
|
+
"/sbin",
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
return [...new Set(parts)].join(":");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readEnvFile() {
|
|
99
|
+
if (!existsSync(ENV_FILE)) return {};
|
|
100
|
+
return Object.fromEntries(
|
|
101
|
+
readFileSync(ENV_FILE, "utf8")
|
|
102
|
+
.split("\n")
|
|
103
|
+
.filter((l) => l.includes("=") && !l.trim().startsWith("#"))
|
|
104
|
+
.map((l) => [
|
|
105
|
+
l.slice(0, l.indexOf("=")).trim(),
|
|
106
|
+
l.slice(l.indexOf("=") + 1).trim(),
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// One token file for every worker on this machine. Whichever installer runs
|
|
112
|
+
// last writes it, and they all write the same two keys — so `goosetools
|
|
113
|
+
// install --token gt_…` leaves every worker pointing at the same credential.
|
|
114
|
+
function writeEnvFile({ url, token }) {
|
|
115
|
+
mkdirSync(GOOSE_DIR, { recursive: true });
|
|
116
|
+
writeFileSync(ENV_FILE, `GOOSETOOLS_URL=${url}\nWORKER_TOKEN=${token}\n`);
|
|
117
|
+
chmodSync(ENV_FILE, 0o600);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function installStableCopy(root) {
|
|
121
|
+
console.log("Setting up the background copy (this can take a few minutes)…");
|
|
122
|
+
mkdirSync(APP_PREFIX, { recursive: true });
|
|
123
|
+
// From a git checkout (development), install the checkout itself; normal
|
|
124
|
+
// npx users get the published package.
|
|
125
|
+
const source = existsSync(join(root, ".git")) ? `"${root}"` : `${PKG}@latest`;
|
|
126
|
+
execSync(
|
|
127
|
+
`npm install --no-fund --no-audit --loglevel=error --prefix "${APP_PREFIX}" ${source}`,
|
|
128
|
+
{ stdio: ["ignore", "inherit", "inherit"], shell: true },
|
|
129
|
+
);
|
|
130
|
+
if (!existsSync(INSTALLED_CLI)) {
|
|
131
|
+
throw new Error("background copy did not install where expected");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── macOS (launchd) ─────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
function macPlist() {
|
|
138
|
+
const esc = (s) =>
|
|
139
|
+
s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
140
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
141
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
142
|
+
<plist version="1.0">
|
|
143
|
+
<dict>
|
|
144
|
+
<key>Label</key>
|
|
145
|
+
<string>${MAC_LABEL}</string>
|
|
146
|
+
<key>ProgramArguments</key>
|
|
147
|
+
<array>
|
|
148
|
+
<string>${esc(process.execPath)}</string>
|
|
149
|
+
<string>${esc(INSTALLED_CLI)}</string>
|
|
150
|
+
<string>run</string>
|
|
151
|
+
</array>
|
|
152
|
+
<key>WorkingDirectory</key>
|
|
153
|
+
<string>${esc(join(APP_PREFIX, "node_modules", PKG))}</string>
|
|
154
|
+
<key>RunAtLoad</key>
|
|
155
|
+
<true/>
|
|
156
|
+
<key>KeepAlive</key>
|
|
157
|
+
<true/>
|
|
158
|
+
<key>ThrottleInterval</key>
|
|
159
|
+
<integer>30</integer>
|
|
160
|
+
<key>StandardOutPath</key>
|
|
161
|
+
<string>${esc(LOG)}</string>
|
|
162
|
+
<key>StandardErrorPath</key>
|
|
163
|
+
<string>${esc(ERR_LOG)}</string>
|
|
164
|
+
<key>EnvironmentVariables</key>
|
|
165
|
+
<dict>
|
|
166
|
+
<key>PATH</key>
|
|
167
|
+
<string>${esc(servicePath())}</string>
|
|
168
|
+
<key>HOME</key>
|
|
169
|
+
<string>${esc(homedir())}</string>
|
|
170
|
+
</dict>
|
|
171
|
+
</dict>
|
|
172
|
+
</plist>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function macInstall() {
|
|
177
|
+
mkdirSync(dirname(MAC_PLIST), { recursive: true });
|
|
178
|
+
writeFileSync(MAC_PLIST, macPlist());
|
|
179
|
+
const uid = process.getuid();
|
|
180
|
+
quietSh(`launchctl bootout gui/${uid}/${MAC_LABEL}`);
|
|
181
|
+
if (!quietSh(`launchctl bootstrap gui/${uid} "${MAC_PLIST}"`)) {
|
|
182
|
+
quietSh(`launchctl unload "${MAC_PLIST}"`);
|
|
183
|
+
execSync(`launchctl load -w "${MAC_PLIST}"`, { stdio: "ignore" });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function macUninstall() {
|
|
188
|
+
quietSh(`launchctl bootout gui/${process.getuid()}/${MAC_LABEL}`);
|
|
189
|
+
quietSh(`launchctl unload "${MAC_PLIST}"`);
|
|
190
|
+
rmSync(MAC_PLIST, { force: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function requireMac() {
|
|
194
|
+
if (process.platform === "darwin") return;
|
|
195
|
+
console.error(
|
|
196
|
+
"\nThe Overlay Factory worker runs on macOS only — the render encodes with\n" +
|
|
197
|
+
"hevc_videotoolbox, an Apple framework. The other Goose Tools workers\n" +
|
|
198
|
+
"(Carousel, Caption, Brand) work here; only reels need a Mac.\n",
|
|
199
|
+
);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Public commands ─────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
export function install({ url, token, root }) {
|
|
206
|
+
requireMac();
|
|
207
|
+
if (!token) {
|
|
208
|
+
console.error(
|
|
209
|
+
`Missing worker token. Run: npx ${PKG} install --url ${url} --token gt_…`,
|
|
210
|
+
);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
writeEnvFile({ url, token });
|
|
214
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
215
|
+
installStableCopy(root);
|
|
216
|
+
macInstall();
|
|
217
|
+
console.log(
|
|
218
|
+
"\n✓ Overlay Factory connected. This computer renders reels in the\n" +
|
|
219
|
+
" background whenever it's on, even after a restart.\n\n" +
|
|
220
|
+
` Logs: ${LOG}\n` +
|
|
221
|
+
` Your work: ${STATE_DIR} (puzzles, looks, cards — kept across updates)\n` +
|
|
222
|
+
` Turn off: npx --yes ${PKG} uninstall\n`,
|
|
223
|
+
);
|
|
224
|
+
// Anyone who ran this worker from a reel-factory checkout has a puzzle
|
|
225
|
+
// library and learned looks sitting in that repo. The packaged worker reads
|
|
226
|
+
// the state dir instead, so say where to bring them across rather than
|
|
227
|
+
// letting it look like the work vanished.
|
|
228
|
+
if (isEmptyDir(STATE_DIR)) {
|
|
229
|
+
console.log(
|
|
230
|
+
" Ran this from a reel-factory checkout before? Bring your work over:\n\n" +
|
|
231
|
+
` cp -R ~/code/reel-factory/{episodes,puzzles,series} ${STATE_DIR}/\n` +
|
|
232
|
+
` cp -R ~/code/reel-factory/public/assets ${STATE_DIR}/assets\n`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (!hasFfmpeg()) {
|
|
236
|
+
console.log(
|
|
237
|
+
" One thing left — reels need ffmpeg, which isn't installed yet:\n\n" +
|
|
238
|
+
" brew install ffmpeg\n\n" +
|
|
239
|
+
" Everything else on this computer works without it. Renders will\n" +
|
|
240
|
+
" fail with that same message until it's there.\n",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function uninstall() {
|
|
246
|
+
macUninstall();
|
|
247
|
+
rmSync(APP_PREFIX, { recursive: true, force: true });
|
|
248
|
+
console.log(
|
|
249
|
+
"✓ Overlay Factory worker removed — this computer no longer renders reels.\n" +
|
|
250
|
+
` Kept: ${STATE_DIR} (puzzles, looks, used-card record). Delete it by hand\n` +
|
|
251
|
+
" if you want it gone; nothing else has a copy.",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// The daemon runs the COPY under ~/.goosetools/overlay-app, not wherever this
|
|
256
|
+
// was run from — so new code only reaches it by reinstalling that copy.
|
|
257
|
+
// Re-registering the service restarts it, which is what actually loads the new
|
|
258
|
+
// code (Node reads every module once, at startup).
|
|
259
|
+
export function update({ root }) {
|
|
260
|
+
requireMac();
|
|
261
|
+
const saved = readEnvFile();
|
|
262
|
+
if (!saved.WORKER_TOKEN) {
|
|
263
|
+
console.error(
|
|
264
|
+
"No saved worker token — this computer hasn't been connected yet.\n" +
|
|
265
|
+
"Get your connect command at https://goosetools.com/dashboard/setup",
|
|
266
|
+
);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
const before = installedVersion();
|
|
270
|
+
installStableCopy(root);
|
|
271
|
+
macInstall();
|
|
272
|
+
const after = installedVersion();
|
|
273
|
+
console.log(
|
|
274
|
+
after && before && after !== before
|
|
275
|
+
? `\n✓ Updated ${before} → ${after} and restarted. Nothing else to do.\n`
|
|
276
|
+
: `\n✓ Worker reinstalled (version ${after ?? "unknown"}) and restarted.\n`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── status ──────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
function installedVersion() {
|
|
283
|
+
try {
|
|
284
|
+
const pkg = join(APP_PREFIX, "node_modules", PKG, "package.json");
|
|
285
|
+
return JSON.parse(readFileSync(pkg, "utf8")).version ?? null;
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function latestVersion() {
|
|
292
|
+
try {
|
|
293
|
+
return sh(`npm view ${PKG} version`, { shell: true, timeout: 15000 });
|
|
294
|
+
} catch {
|
|
295
|
+
return null; // offline, or npm unreachable — not worth failing on
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function compareVersions(a, b) {
|
|
300
|
+
const parts = (v) =>
|
|
301
|
+
String(v)
|
|
302
|
+
.split(".")
|
|
303
|
+
.map((n) => Number.parseInt(n, 10) || 0);
|
|
304
|
+
const [pa, pb] = [parts(a), parts(b)];
|
|
305
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
306
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) < (pb[i] ?? 0) ? -1 : 1;
|
|
307
|
+
}
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// launchctl prints "<pid>\t<last exit>\t<label>"; a "-" pid means registered
|
|
312
|
+
// but not currently running.
|
|
313
|
+
function servicePid() {
|
|
314
|
+
try {
|
|
315
|
+
const line = sh(`launchctl list | grep ${MAC_LABEL}`, { shell: true });
|
|
316
|
+
const pid = line.split(/\s+/)[0];
|
|
317
|
+
return pid === "-" ? null : pid;
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function tail(file, n) {
|
|
324
|
+
try {
|
|
325
|
+
return readFileSync(file, "utf8").trimEnd().split("\n").slice(-n).join("\n");
|
|
326
|
+
} catch {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// How long ago a log was last written. Without this, days-old errors read as
|
|
332
|
+
// a live outage — the single most misleading thing in a log tail.
|
|
333
|
+
function lastWritten(file) {
|
|
334
|
+
try {
|
|
335
|
+
const mins = Math.round((Date.now() - statSync(file).mtimeMs) / 60000);
|
|
336
|
+
if (mins < 1) return "just now";
|
|
337
|
+
if (mins < 60) return `${mins} min ago`;
|
|
338
|
+
const hours = Math.round(mins / 60);
|
|
339
|
+
if (hours < 24) return `${hours} hr ago`;
|
|
340
|
+
return `${Math.round(hours / 24)} days ago`;
|
|
341
|
+
} catch {
|
|
342
|
+
return "unknown";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function status() {
|
|
347
|
+
const ok = (b) => (b ? "✓" : "✗");
|
|
348
|
+
const env = readEnvFile();
|
|
349
|
+
const installed = installedVersion();
|
|
350
|
+
const latest = latestVersion();
|
|
351
|
+
const pid = servicePid();
|
|
352
|
+
const url = env.GOOSETOOLS_URL ?? "https://goosetools.com";
|
|
353
|
+
const ffmpeg = hasFfmpeg();
|
|
354
|
+
|
|
355
|
+
console.log("\nOverlay Factory worker — status\n");
|
|
356
|
+
console.log(` ${ok(installed)} Installed ${installed ?? "not installed"}`);
|
|
357
|
+
const cmp = installed && latest ? compareVersions(installed, latest) : 0;
|
|
358
|
+
const stale = cmp < 0;
|
|
359
|
+
if (latest) {
|
|
360
|
+
const note = stale
|
|
361
|
+
? " ← update available"
|
|
362
|
+
: cmp > 0
|
|
363
|
+
? " (you're ahead — local dev build)"
|
|
364
|
+
: "";
|
|
365
|
+
console.log(` ${ok(!stale)} Latest on npm ${latest}${note}`);
|
|
366
|
+
} else {
|
|
367
|
+
console.log(" · Latest on npm couldn't check (offline?)");
|
|
368
|
+
}
|
|
369
|
+
console.log(` ${ok(pid)} Running ${pid ? `yes (pid ${pid})` : "no"}`);
|
|
370
|
+
console.log(
|
|
371
|
+
` ${ok(env.WORKER_TOKEN)} Token saved ${
|
|
372
|
+
env.WORKER_TOKEN ? "yes" : "no — reconnect at goosetools.com/dashboard/setup"
|
|
373
|
+
}`,
|
|
374
|
+
);
|
|
375
|
+
console.log(
|
|
376
|
+
` ${ok(ffmpeg)} ffmpeg ${
|
|
377
|
+
ffmpeg ? "found" : "missing — renders fail without it: brew install ffmpeg"
|
|
378
|
+
}`,
|
|
379
|
+
);
|
|
380
|
+
console.log(` · Server ${url}`);
|
|
381
|
+
console.log(` · Your work ${STATE_DIR}`);
|
|
382
|
+
console.log(` · Logs ${LOG}`);
|
|
383
|
+
|
|
384
|
+
const errors = tail(ERR_LOG, 15);
|
|
385
|
+
if (errors) {
|
|
386
|
+
console.log(
|
|
387
|
+
`\nErrors (last written ${lastWritten(ERR_LOG)}):\n${errors.replace(/^/gm, " ")}`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const log = tail(LOG, 15);
|
|
391
|
+
if (log) {
|
|
392
|
+
console.log(
|
|
393
|
+
`\nActivity (last written ${lastWritten(LOG)}):\n${log.replace(/^/gm, " ")}`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (stale) {
|
|
398
|
+
console.log(`\nTo update: npx --yes ${PKG}@latest update\n`);
|
|
399
|
+
} else if (!pid && installed) {
|
|
400
|
+
console.log(`\nNot running. Restart it with: npx --yes ${PKG}@latest update\n`);
|
|
401
|
+
} else {
|
|
402
|
+
console.log("");
|
|
403
|
+
}
|
|
404
|
+
}
|