@withbuddi/buddi 0.1.0-pre.38 → 0.1.0-pre.39
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/extension/background.js +635 -21
- package/extension/manifest.json +2 -2
- package/node_modules/@buddi/cli/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.d.ts +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/access-cmd.js +23 -3
- package/node_modules/@buddi/cli/dist/access-cmd.js.map +1 -1
- package/node_modules/@buddi/cli/dist/args.d.ts +2 -2
- package/node_modules/@buddi/cli/dist/args.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/args.js +7 -2
- package/node_modules/@buddi/cli/dist/args.js.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/commands.js +11 -0
- package/node_modules/@buddi/cli/dist/commands.js.map +1 -1
- package/node_modules/@buddi/cli/dist/main.d.ts.map +1 -1
- package/node_modules/@buddi/cli/dist/main.js +2 -1
- package/node_modules/@buddi/cli/dist/main.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts +26 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.d.ts.map +1 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.js +83 -0
- package/node_modules/@buddi/gateway/dist/missions/ask-cap.js.map +1 -0
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts +12 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/execute.js +73 -11
- package/node_modules/@buddi/gateway/dist/missions/execute.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.d.ts +6 -0
- package/node_modules/@buddi/gateway/dist/missions/parked.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.js +7 -1
- package/node_modules/@buddi/gateway/dist/missions/parked.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/serve.js +1 -1
- package/node_modules/@buddi/gateway/dist/serve.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/system-context.d.ts +7 -0
- package/node_modules/@buddi/gateway/dist/system-context.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/system-context.js +8 -0
- package/node_modules/@buddi/gateway/dist/system-context.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/api-routes.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/api-routes.js +11 -3
- package/node_modules/@buddi/gateway/dist/web/api-routes.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/extension.d.ts +13 -1
- package/node_modules/@buddi/gateway/dist/web/extension.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/extension.js +125 -5
- package/node_modules/@buddi/gateway/dist/web/extension.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/remote-hand.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/remote-hand.js +45 -2
- package/node_modules/@buddi/gateway/dist/web/remote-hand.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.d.ts +26 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/secrets.js +66 -3
- package/node_modules/@buddi/gateway/dist/web/secrets.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/server.js +63 -7
- package/node_modules/@buddi/gateway/dist/web/server.js.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/version.d.ts +2 -0
- package/node_modules/@buddi/gateway/dist/web/version.d.ts.map +1 -1
- package/node_modules/@buddi/gateway/dist/web/version.js +5 -1
- package/node_modules/@buddi/gateway/dist/web/version.js.map +1 -1
- package/node_modules/@buddi/install/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/install/dist/app-layout.d.ts +127 -0
- package/node_modules/@buddi/install/dist/app-layout.d.ts.map +1 -0
- package/node_modules/@buddi/install/dist/app-layout.js +323 -0
- package/node_modules/@buddi/install/dist/app-layout.js.map +1 -0
- package/node_modules/@buddi/install/dist/index.d.ts +1 -0
- package/node_modules/@buddi/install/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/index.js +1 -0
- package/node_modules/@buddi/install/dist/index.js.map +1 -1
- package/node_modules/@buddi/install/dist/launcher.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/launcher.js +18 -4
- package/node_modules/@buddi/install/dist/launcher.js.map +1 -1
- package/node_modules/@buddi/install/dist/provenance.d.ts +34 -0
- package/node_modules/@buddi/install/dist/provenance.d.ts.map +1 -0
- package/node_modules/@buddi/install/dist/provenance.js +200 -0
- package/node_modules/@buddi/install/dist/provenance.js.map +1 -0
- package/node_modules/@buddi/install/dist/supervisor.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/supervisor.js +9 -4
- package/node_modules/@buddi/install/dist/supervisor.js.map +1 -1
- package/node_modules/@buddi/install/dist/upgrade.d.ts +77 -3
- package/node_modules/@buddi/install/dist/upgrade.d.ts.map +1 -1
- package/node_modules/@buddi/install/dist/upgrade.js +325 -43
- package/node_modules/@buddi/install/dist/upgrade.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/.tsbuildinfo +1 -1
- package/node_modules/@buddi/tool-browser/dist/controller.d.ts +6 -0
- package/node_modules/@buddi/tool-browser/dist/controller.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/controller.js +80 -6
- package/node_modules/@buddi/tool-browser/dist/controller.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/driver.d.ts +3 -1
- package/node_modules/@buddi/tool-browser/dist/driver.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/driver.js +82 -1
- package/node_modules/@buddi/tool-browser/dist/driver.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/extension.d.ts +60 -7
- package/node_modules/@buddi/tool-browser/dist/extension.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/extension.js +90 -13
- package/node_modules/@buddi/tool-browser/dist/extension.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.d.ts +6 -4
- package/node_modules/@buddi/tool-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/index.js +7 -4
- package/node_modules/@buddi/tool-browser/dist/index.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/logins.d.ts +150 -0
- package/node_modules/@buddi/tool-browser/dist/logins.d.ts.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/logins.js +476 -0
- package/node_modules/@buddi/tool-browser/dist/logins.js.map +1 -0
- package/node_modules/@buddi/tool-browser/dist/routes.d.ts +22 -1
- package/node_modules/@buddi/tool-browser/dist/routes.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/routes.js +51 -3
- package/node_modules/@buddi/tool-browser/dist/routes.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts +3 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/secrets.js +10 -6
- package/node_modules/@buddi/tool-browser/dist/secrets.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.d.ts +26 -2
- package/node_modules/@buddi/tool-browser/dist/service.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/service.js +117 -3
- package/node_modules/@buddi/tool-browser/dist/service.js.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.d.ts +88 -0
- package/node_modules/@buddi/tool-browser/dist/types.d.ts.map +1 -1
- package/node_modules/@buddi/tool-browser/dist/types.js +18 -0
- package/node_modules/@buddi/tool-browser/dist/types.js.map +1 -1
- package/node_modules/hono/dist/cjs/hono-base.js +2 -0
- package/node_modules/hono/dist/cjs/middleware/language/language.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/mount/index.js +71 -0
- package/node_modules/hono/dist/cjs/request.js +4 -4
- package/node_modules/hono/dist/hono-base.js +2 -0
- package/node_modules/hono/dist/middleware/mount/index.js +70 -0
- package/node_modules/hono/dist/request.js +4 -4
- package/node_modules/hono/dist/types/hono-base.d.ts +2 -0
- package/node_modules/hono/dist/types/jsx/base.d.ts +1 -1
- package/node_modules/hono/dist/types/middleware/mount/index.d.ts +46 -0
- package/node_modules/hono/dist/types/request.d.ts +4 -4
- package/node_modules/hono/package.json +9 -1
- package/package.json +2 -2
- package/packages/cli/dist/.tsbuildinfo +1 -1
- package/packages/cli/dist/access-cmd.d.ts +1 -1
- package/packages/cli/dist/access-cmd.d.ts.map +1 -1
- package/packages/cli/dist/access-cmd.js +23 -3
- package/packages/cli/dist/access-cmd.js.map +1 -1
- package/packages/cli/dist/args.d.ts +2 -2
- package/packages/cli/dist/args.d.ts.map +1 -1
- package/packages/cli/dist/args.js +7 -2
- package/packages/cli/dist/args.js.map +1 -1
- package/packages/cli/dist/commands.d.ts.map +1 -1
- package/packages/cli/dist/commands.js +11 -0
- package/packages/cli/dist/commands.js.map +1 -1
- package/packages/cli/dist/main.d.ts.map +1 -1
- package/packages/cli/dist/main.js +2 -1
- package/packages/cli/dist/main.js.map +1 -1
- package/packages/gateway/dist/.tsbuildinfo +1 -1
- package/packages/gateway/dist/missions/ask-cap.d.ts +26 -0
- package/packages/gateway/dist/missions/ask-cap.d.ts.map +1 -0
- package/packages/gateway/dist/missions/ask-cap.js +83 -0
- package/packages/gateway/dist/missions/ask-cap.js.map +1 -0
- package/packages/gateway/dist/missions/execute.d.ts +12 -1
- package/packages/gateway/dist/missions/execute.d.ts.map +1 -1
- package/packages/gateway/dist/missions/execute.js +73 -11
- package/packages/gateway/dist/missions/execute.js.map +1 -1
- package/packages/gateway/dist/missions/parked.d.ts +6 -0
- package/packages/gateway/dist/missions/parked.d.ts.map +1 -1
- package/packages/gateway/dist/missions/parked.js +7 -1
- package/packages/gateway/dist/missions/parked.js.map +1 -1
- package/packages/gateway/dist/serve.js +1 -1
- package/packages/gateway/dist/serve.js.map +1 -1
- package/packages/gateway/dist/system-context.d.ts +7 -0
- package/packages/gateway/dist/system-context.d.ts.map +1 -1
- package/packages/gateway/dist/system-context.js +8 -0
- package/packages/gateway/dist/system-context.js.map +1 -1
- package/packages/gateway/dist/web/api-routes.d.ts.map +1 -1
- package/packages/gateway/dist/web/api-routes.js +11 -3
- package/packages/gateway/dist/web/api-routes.js.map +1 -1
- package/packages/gateway/dist/web/extension.d.ts +13 -1
- package/packages/gateway/dist/web/extension.d.ts.map +1 -1
- package/packages/gateway/dist/web/extension.js +125 -5
- package/packages/gateway/dist/web/extension.js.map +1 -1
- package/packages/gateway/dist/web/remote-hand.d.ts.map +1 -1
- package/packages/gateway/dist/web/remote-hand.js +45 -2
- package/packages/gateway/dist/web/remote-hand.js.map +1 -1
- package/packages/gateway/dist/web/secrets.d.ts +26 -1
- package/packages/gateway/dist/web/secrets.d.ts.map +1 -1
- package/packages/gateway/dist/web/secrets.js +66 -3
- package/packages/gateway/dist/web/secrets.js.map +1 -1
- package/packages/gateway/dist/web/server.d.ts.map +1 -1
- package/packages/gateway/dist/web/server.js +63 -7
- package/packages/gateway/dist/web/server.js.map +1 -1
- package/packages/gateway/dist/web/version.d.ts +2 -0
- package/packages/gateway/dist/web/version.d.ts.map +1 -1
- package/packages/gateway/dist/web/version.js +5 -1
- package/packages/gateway/dist/web/version.js.map +1 -1
- package/packages/install/dist/.tsbuildinfo +1 -1
- package/packages/install/dist/app-layout.d.ts +127 -0
- package/packages/install/dist/app-layout.d.ts.map +1 -0
- package/packages/install/dist/app-layout.js +323 -0
- package/packages/install/dist/app-layout.js.map +1 -0
- package/packages/install/dist/index.d.ts +1 -0
- package/packages/install/dist/index.d.ts.map +1 -1
- package/packages/install/dist/index.js +1 -0
- package/packages/install/dist/index.js.map +1 -1
- package/packages/install/dist/launcher.d.ts.map +1 -1
- package/packages/install/dist/launcher.js +18 -4
- package/packages/install/dist/launcher.js.map +1 -1
- package/packages/install/dist/provenance.d.ts +34 -0
- package/packages/install/dist/provenance.d.ts.map +1 -0
- package/packages/install/dist/provenance.js +200 -0
- package/packages/install/dist/provenance.js.map +1 -0
- package/packages/install/dist/supervisor.d.ts.map +1 -1
- package/packages/install/dist/supervisor.js +9 -4
- package/packages/install/dist/supervisor.js.map +1 -1
- package/packages/install/dist/upgrade.d.ts +77 -3
- package/packages/install/dist/upgrade.d.ts.map +1 -1
- package/packages/install/dist/upgrade.js +325 -43
- package/packages/install/dist/upgrade.js.map +1 -1
- package/packages/tools/browser/dist/.tsbuildinfo +1 -1
- package/packages/tools/browser/dist/controller.d.ts +6 -0
- package/packages/tools/browser/dist/controller.d.ts.map +1 -1
- package/packages/tools/browser/dist/controller.js +80 -6
- package/packages/tools/browser/dist/controller.js.map +1 -1
- package/packages/tools/browser/dist/driver.d.ts +3 -1
- package/packages/tools/browser/dist/driver.d.ts.map +1 -1
- package/packages/tools/browser/dist/driver.js +82 -1
- package/packages/tools/browser/dist/driver.js.map +1 -1
- package/packages/tools/browser/dist/extension.d.ts +60 -7
- package/packages/tools/browser/dist/extension.d.ts.map +1 -1
- package/packages/tools/browser/dist/extension.js +90 -13
- package/packages/tools/browser/dist/extension.js.map +1 -1
- package/packages/tools/browser/dist/index.d.ts +6 -4
- package/packages/tools/browser/dist/index.d.ts.map +1 -1
- package/packages/tools/browser/dist/index.js +7 -4
- package/packages/tools/browser/dist/index.js.map +1 -1
- package/packages/tools/browser/dist/logins.d.ts +150 -0
- package/packages/tools/browser/dist/logins.d.ts.map +1 -0
- package/packages/tools/browser/dist/logins.js +476 -0
- package/packages/tools/browser/dist/logins.js.map +1 -0
- package/packages/tools/browser/dist/routes.d.ts +22 -1
- package/packages/tools/browser/dist/routes.d.ts.map +1 -1
- package/packages/tools/browser/dist/routes.js +51 -3
- package/packages/tools/browser/dist/routes.js.map +1 -1
- package/packages/tools/browser/dist/secrets.d.ts +3 -1
- package/packages/tools/browser/dist/secrets.d.ts.map +1 -1
- package/packages/tools/browser/dist/secrets.js +10 -6
- package/packages/tools/browser/dist/secrets.js.map +1 -1
- package/packages/tools/browser/dist/service.d.ts +26 -2
- package/packages/tools/browser/dist/service.d.ts.map +1 -1
- package/packages/tools/browser/dist/service.js +117 -3
- package/packages/tools/browser/dist/service.js.map +1 -1
- package/packages/tools/browser/dist/types.d.ts +88 -0
- package/packages/tools/browser/dist/types.d.ts.map +1 -1
- package/packages/tools/browser/dist/types.js +18 -0
- package/packages/tools/browser/dist/types.js.map +1 -1
- package/packages/web/dist/assets/index-DYfeUW-0.css +1 -0
- package/packages/web/dist/assets/index-vpc904_b.js +146 -0
- package/packages/web/dist/assets/{lottie_light-CuiSMqnl.js → lottie_light-BFAMYgLA.js} +1 -1
- package/packages/web/dist/build.json +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/precache.json +4 -4
- package/packages/web/dist/assets/index-Bac-CdD-.css +0 -1
- package/packages/web/dist/assets/index-DI8bp_D7.js +0 -146
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
import { execFile } from 'node:child_process';
|
|
40
40
|
import { existsSync, readFileSync } from 'node:fs';
|
|
41
41
|
import { request } from 'node:http';
|
|
42
|
-
import { open, readFile } from 'node:fs/promises';
|
|
42
|
+
import { open, readFile, realpath, rm } from 'node:fs/promises';
|
|
43
43
|
import path from 'node:path';
|
|
44
44
|
import { promisify } from 'node:util';
|
|
45
45
|
/*
|
|
@@ -53,6 +53,7 @@ import { compareSemver, parseSemver } from '@buddi/core/semver';
|
|
|
53
53
|
import { atomicJson, launchAgentLabel, SERVICE_UNIT_VAR } from './environment.js';
|
|
54
54
|
import { JobStore } from './backup.js';
|
|
55
55
|
import { createPostgresCheck } from './postgres-check.js';
|
|
56
|
+
import { APP_BUNDLE, appLayout, insideAppBundle, placeRelease, pruneReleases, releaseDir, stageRelease, switchCurrent } from './app-layout.js';
|
|
56
57
|
const run = promisify(execFile);
|
|
57
58
|
/** Where `buddi` is published, and where the check and the install both look. */
|
|
58
59
|
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
@@ -177,6 +178,7 @@ export async function readUpgradeState(data, current) {
|
|
|
177
178
|
...(typeof check.latest === 'string' ? { latest: check.latest } : {}),
|
|
178
179
|
...(typeof check.latest === 'string' && typeof check.latestNotes === 'string' ? { latestNotes: check.latestNotes } : {}),
|
|
179
180
|
...(typeof check.error === 'string' ? { error: check.error } : {}),
|
|
181
|
+
...(typeof check.processing === 'string' && check.processing === check.latest ? { processing: check.processing } : {}),
|
|
180
182
|
},
|
|
181
183
|
current,
|
|
182
184
|
...(typeof parsed.registry === 'string' ? { registry: parsed.registry } : {}),
|
|
@@ -188,17 +190,177 @@ export async function writeUpgradeState(data, state) {
|
|
|
188
190
|
await atomicJson(upgradeStateFile(data), { ...state, history: state.history.slice(-HISTORY_LIMIT) });
|
|
189
191
|
}
|
|
190
192
|
export function versionView(state) {
|
|
193
|
+
const newer = isNewer(state.check.latest, state.current);
|
|
194
|
+
const processing = newer && state.check.processing !== undefined && state.check.processing === state.check.latest ? state.check.processing : undefined;
|
|
191
195
|
return {
|
|
192
196
|
current: state.current,
|
|
193
197
|
...(state.check.latest === undefined ? {} : { latest: state.check.latest }),
|
|
194
198
|
...(state.check.latest === undefined || state.check.latestNotes === undefined ? {} : { latestNotes: state.check.latestNotes }),
|
|
195
199
|
...(state.check.lastAt === undefined ? {} : { checkedAt: state.check.lastAt }),
|
|
196
200
|
checkEnabled: state.check.enabled,
|
|
197
|
-
updateAvailable:
|
|
201
|
+
updateAvailable: newer && processing === undefined,
|
|
198
202
|
...(state.check.error === undefined ? {} : { error: state.check.error }),
|
|
203
|
+
...(processing === undefined ? {} : { processing: { version: processing, message: processingSentence(processing) } }),
|
|
199
204
|
history: state.history,
|
|
200
205
|
};
|
|
201
206
|
}
|
|
207
|
+
/** What the sheet and a failed install say while npm has not served a fresh version's tarball yet. */
|
|
208
|
+
export function processingSentence(version) {
|
|
209
|
+
return `npm is still processing ${version}; try again in a few minutes.`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Where npm serves a version's tarball by convention: `<registry>/@withbuddi/buddi/-/buddi-<version>.tgz`.
|
|
213
|
+
* Only the fallback: the version document's `dist.tarball` is what npm itself downloads.
|
|
214
|
+
*/
|
|
215
|
+
export function tarballUrl(registry, version) {
|
|
216
|
+
return `${registry.replace(/\/+$/, '')}/${PACKAGE_NAME}/-/${PACKAGE_NAME.split('/').pop()}-${version}.tgz`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The credentials npm itself would send to this registry, read from the
|
|
220
|
+
* owner's npmrc (`NPM_CONFIG_USERCONFIG`, else `~/.npmrc`): `_authToken` as a
|
|
221
|
+
* bearer, `_auth` or `username` + `_password` as basic. The longest
|
|
222
|
+
* `//host/path/:` key the registry URL starts with wins, as in npm, and
|
|
223
|
+
* `${VAR}` is read from the environment. Nothing found is no headers: the
|
|
224
|
+
* public registry needs none.
|
|
225
|
+
*
|
|
226
|
+
* Read so the check and the install see the same registry: a mirror that wants
|
|
227
|
+
* a token answered the anonymous check 401, which once read as "still
|
|
228
|
+
* processing" for ever.
|
|
229
|
+
*/
|
|
230
|
+
export function registryAuth(registry, env, read = file => readFileSync(file, 'utf8')) {
|
|
231
|
+
const file = env.NPM_CONFIG_USERCONFIG ?? env.npm_config_userconfig ?? (env.HOME ? path.join(env.HOME, '.npmrc') : undefined);
|
|
232
|
+
if (file === undefined)
|
|
233
|
+
return {};
|
|
234
|
+
let text;
|
|
235
|
+
try {
|
|
236
|
+
text = read(file);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
let url;
|
|
242
|
+
try {
|
|
243
|
+
url = new URL(registry.replace(/\/+$/, '') + '/');
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return {};
|
|
247
|
+
}
|
|
248
|
+
const nerf = `//${url.host}${url.pathname}`;
|
|
249
|
+
const entries = new Map();
|
|
250
|
+
for (const line of text.split(/\r?\n/)) {
|
|
251
|
+
const trimmed = line.trim();
|
|
252
|
+
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith(';'))
|
|
253
|
+
continue;
|
|
254
|
+
const eq = trimmed.indexOf('=');
|
|
255
|
+
if (eq <= 0)
|
|
256
|
+
continue;
|
|
257
|
+
const value = trimmed.slice(eq + 1).trim().replace(/^"(.*)"$/, '$1').replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? '');
|
|
258
|
+
entries.set(trimmed.slice(0, eq).trim(), value);
|
|
259
|
+
}
|
|
260
|
+
const scopes = [...new Set([...entries.keys()].filter(k => k.startsWith('//') && k.includes('/:')).map(k => k.slice(0, k.lastIndexOf('/:') + 1)))]
|
|
261
|
+
.filter(scope => nerf.startsWith(scope))
|
|
262
|
+
.sort((a, b) => b.length - a.length);
|
|
263
|
+
for (const scope of scopes) {
|
|
264
|
+
const token = entries.get(`${scope}:_authToken`);
|
|
265
|
+
if (token)
|
|
266
|
+
return { authorization: `Bearer ${token}` };
|
|
267
|
+
const auth = entries.get(`${scope}:_auth`);
|
|
268
|
+
if (auth)
|
|
269
|
+
return { authorization: `Basic ${auth}` };
|
|
270
|
+
const user = entries.get(`${scope}:username`), password = entries.get(`${scope}:_password`);
|
|
271
|
+
if (user && password)
|
|
272
|
+
return { authorization: `Basic ${Buffer.from(`${user}:${Buffer.from(password, 'base64').toString('utf8')}`).toString('base64')}` };
|
|
273
|
+
}
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
/** Credentials go to the registry's own origin only, never to a host a document names. */
|
|
277
|
+
export function authFor(url, registry, auth) {
|
|
278
|
+
if (auth === undefined || Object.keys(auth).length === 0)
|
|
279
|
+
return {};
|
|
280
|
+
try {
|
|
281
|
+
return new URL(url).origin === new URL(registry).origin ? auth : {};
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Does npm serve this version's tarball yet?
|
|
289
|
+
*
|
|
290
|
+
* The tarball is the one the version document names (`dist.tarball`), asked
|
|
291
|
+
* with the registry's credentials. Only a 404 (of the document or the
|
|
292
|
+
* tarball) is "still processing": that is what npmjs answers in the minutes
|
|
293
|
+
* after a publish. A registry that refuses HEAD (405, 501) is asked for one
|
|
294
|
+
* byte instead. Anything else — a 401 or 403 from a mirror, a 5xx — is not a
|
|
295
|
+
* publish in progress and is not reported as one: the install that follows
|
|
296
|
+
* says what is actually wrong. A transport failure throws.
|
|
297
|
+
*/
|
|
298
|
+
export async function tarballServed(registry, version, http, opts = {}) {
|
|
299
|
+
const base = registry.replace(/\/+$/, '');
|
|
300
|
+
let url = opts.tarball;
|
|
301
|
+
if (url === undefined || url === '') {
|
|
302
|
+
const doc = await http(`${base}/${PACKAGE_PATH}/${version}`, {
|
|
303
|
+
method: 'GET',
|
|
304
|
+
headers: { accept: 'application/json', ...authFor(base, base, opts.auth) },
|
|
305
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
|
|
306
|
+
});
|
|
307
|
+
if (doc.status === 404)
|
|
308
|
+
return false;
|
|
309
|
+
if (doc.status === 200) {
|
|
310
|
+
const body = await doc.json().catch(() => undefined);
|
|
311
|
+
if (typeof body?.dist?.tarball === 'string' && body.dist.tarball !== '')
|
|
312
|
+
url = body.dist.tarball;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (url === undefined || url === '')
|
|
316
|
+
url = tarballUrl(base, version);
|
|
317
|
+
const headers = authFor(url, base, opts.auth);
|
|
318
|
+
const head = await http(url, { method: 'HEAD', headers, signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
|
|
319
|
+
if (head.status >= 200 && head.status < 300)
|
|
320
|
+
return true;
|
|
321
|
+
if (head.status === 404)
|
|
322
|
+
return false;
|
|
323
|
+
if (head.status === 405 || head.status === 501) {
|
|
324
|
+
try {
|
|
325
|
+
const ranged = await http(url, { method: 'GET', headers: { ...headers, range: 'bytes=0-0' }, signal: AbortSignal.timeout(CHECK_TIMEOUT_MS), maxBytes: 64 * 1024 });
|
|
326
|
+
return ranged.status !== 404;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// A server that ignored the range and started sending the whole tarball is serving it.
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* npm's own words for *this package's* tarball not being served (yet): `E404`
|
|
337
|
+
* and a 404 line naming `@withbuddi/buddi`. A 404 for anything else — a
|
|
338
|
+
* dependency, a mirror's missing path — is not a publish in progress, and
|
|
339
|
+
* keeps npm's own words.
|
|
340
|
+
*/
|
|
341
|
+
export function isNotFound(error) {
|
|
342
|
+
// The "npm install <spec> failed:" this module puts in front names the package itself.
|
|
343
|
+
const said = error.replace(/npm (install|cache add) \S+ failed:/g, '');
|
|
344
|
+
if (!/\bE404\b/.test(said))
|
|
345
|
+
return false;
|
|
346
|
+
return said.split('\n').some(line => /\b404\b/.test(line) && /@withbuddi(\/|%2f)buddi\b/i.test(line));
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* What npm said, safe to keep in the history and show on the dashboard:
|
|
350
|
+
* credentials in URLs and npmrc-style tokens masked, terminal escapes and
|
|
351
|
+
* other control characters dropped, the last eight lines.
|
|
352
|
+
*/
|
|
353
|
+
export function sanitizeNpmOutput(text) {
|
|
354
|
+
return text
|
|
355
|
+
// eslint-disable-next-line no-control-regex
|
|
356
|
+
.replace(/\u001b\[[0-9;]*[A-Za-z]/g, '')
|
|
357
|
+
// eslint-disable-next-line no-control-regex
|
|
358
|
+
.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, '')
|
|
359
|
+
.replace(/(\w+:\/\/)[^\s/@:]+(:[^\s/@]*)?@/g, '$1***@')
|
|
360
|
+
.replace(/(_authToken|_auth|_password)(\s*[=:]\s*)\S+/gi, '$1$2***')
|
|
361
|
+
.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/g, '$1 ***')
|
|
362
|
+
.trim().split('\n').slice(-8).join('\n');
|
|
363
|
+
}
|
|
202
364
|
/** Release notes longer than this are cut; `build.mjs` keeps them to 8 KB. */
|
|
203
365
|
export const NOTES_LIMIT = 8 * 1024;
|
|
204
366
|
/**
|
|
@@ -210,10 +372,11 @@ export const NOTES_LIMIT = 8 * 1024;
|
|
|
210
372
|
* same document carries the release notes (`buddi.notes`, written by
|
|
211
373
|
* `scripts/release/build.mjs`), so they cost no second request.
|
|
212
374
|
*/
|
|
213
|
-
export async function fetchLatestRelease(registry, http) {
|
|
214
|
-
const
|
|
375
|
+
export async function fetchLatestRelease(registry, http, auth) {
|
|
376
|
+
const base = registry.replace(/\/+$/, '');
|
|
377
|
+
const response = await http(`${base}/${PACKAGE_PATH}/latest`, {
|
|
215
378
|
method: 'GET',
|
|
216
|
-
headers: { accept: 'application/json' },
|
|
379
|
+
headers: { accept: 'application/json', ...authFor(base, base, auth) },
|
|
217
380
|
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
|
|
218
381
|
});
|
|
219
382
|
if (response.status !== 200)
|
|
@@ -225,10 +388,11 @@ export async function fetchLatestRelease(registry, http) {
|
|
|
225
388
|
// and a number on the dashboard. It gets to name a version, and nothing else.
|
|
226
389
|
if (!isVersion(body.version))
|
|
227
390
|
throw new Error(`the registry named "${body.version}", which is not a version`);
|
|
391
|
+
const tarball = typeof body.dist?.tarball === 'string' && body.dist.tarball !== '' ? { tarball: body.dist.tarball } : {};
|
|
228
392
|
const notes = typeof body.buddi === 'object' && body.buddi !== null ? body.buddi.notes : undefined;
|
|
229
393
|
if (typeof notes !== 'string' || notes.trim() === '')
|
|
230
|
-
return { version: body.version };
|
|
231
|
-
return { version: body.version, notes: notes.trim().slice(0, NOTES_LIMIT) };
|
|
394
|
+
return { version: body.version, ...tarball };
|
|
395
|
+
return { version: body.version, notes: notes.trim().slice(0, NOTES_LIMIT), ...tarball };
|
|
232
396
|
}
|
|
233
397
|
/**
|
|
234
398
|
* Read the layout back off the install root rather than trusting npm's config.
|
|
@@ -278,6 +442,10 @@ export const NOT_GLOBAL = 'This buddi was installed inside another project; upgr
|
|
|
278
442
|
*/
|
|
279
443
|
export function upgradeTarget(root, opts = {}) {
|
|
280
444
|
const platform = opts.platform ?? process.platform;
|
|
445
|
+
// The bundle is signed: npm must never write there. buddi.app upgrades it
|
|
446
|
+
// through its own layout (`app-layout.ts`) when it is the one running buddi.
|
|
447
|
+
if (insideAppBundle(root))
|
|
448
|
+
return { error: APP_BUNDLE };
|
|
281
449
|
const target = installTarget(root, platform);
|
|
282
450
|
if (target.global)
|
|
283
451
|
return target;
|
|
@@ -354,7 +522,7 @@ export function createInstaller(opts = {}) {
|
|
|
354
522
|
const failed = (verb, spec, err) => {
|
|
355
523
|
const stderr = err?.stderr;
|
|
356
524
|
const text = (stderr ?? (err instanceof Error ? err.message : String(err))).toString().trim();
|
|
357
|
-
return new Error(`npm ${verb} ${spec} failed: ${text
|
|
525
|
+
return new Error(`npm ${verb} ${spec} failed: ${sanitizeNpmOutput(text)}`);
|
|
358
526
|
};
|
|
359
527
|
return async (spec, where) => {
|
|
360
528
|
if (!isLocalTarball(spec)) {
|
|
@@ -402,6 +570,11 @@ export function createInstaller(opts = {}) {
|
|
|
402
570
|
* into nothing, leaving the installation down until the next login.
|
|
403
571
|
*/
|
|
404
572
|
export function restartPlan(facts) {
|
|
573
|
+
// buddi.app is this supervisor's parent: it starts the launcher `current`
|
|
574
|
+
// points at again as soon as this exits with APP_RESTART_EXIT (app-layout.ts).
|
|
575
|
+
if (facts.appLayout === true) {
|
|
576
|
+
return { mode: 'app', reason: 'buddi.app starts the release `current` points at again' };
|
|
577
|
+
}
|
|
405
578
|
if (facts.platform === 'darwin' && facts.label !== '' && facts.xpcServiceName === facts.label) {
|
|
406
579
|
return { mode: 'launchd', reason: 'launchd keeps this job alive and runs the launcher from the install root' };
|
|
407
580
|
}
|
|
@@ -424,9 +597,9 @@ export function restartPlan(facts) {
|
|
|
424
597
|
* owner actually asked for.
|
|
425
598
|
*/
|
|
426
599
|
export async function handOver(opts) {
|
|
427
|
-
const plan = opts.plan ?? restartPlan({ platform: process.platform, label: launchAgentLabel(opts.ctx.data), serviceUnit: process.env[SERVICE_UNIT_VAR] ?? opts.env[SERVICE_UNIT_VAR], xpcServiceName: process.env.XPC_SERVICE_NAME ?? opts.env.XPC_SERVICE_NAME });
|
|
600
|
+
const plan = opts.plan ?? restartPlan({ platform: process.platform, label: launchAgentLabel(opts.ctx.data), serviceUnit: process.env[SERVICE_UNIT_VAR] ?? opts.env[SERVICE_UNIT_VAR], xpcServiceName: process.env.XPC_SERVICE_NAME ?? opts.env.XPC_SERVICE_NAME, appLayout: appLayout(opts.env) !== undefined });
|
|
428
601
|
const log = opts.log ?? ((line) => console.error(line));
|
|
429
|
-
if (plan.mode === 'launchd' || plan.mode === 'systemd') {
|
|
602
|
+
if (plan.mode === 'launchd' || plan.mode === 'systemd' || plan.mode === 'app') {
|
|
430
603
|
log(`supervisor: exiting for the upgrade; ${plan.reason}.`);
|
|
431
604
|
return { plan, ok: true, attempts: 0 };
|
|
432
605
|
}
|
|
@@ -505,11 +678,15 @@ export async function statusOnSocket(socket, timeoutMs = 2_000) {
|
|
|
505
678
|
* dashboard is not available in that state — the gateway is deliberately not
|
|
506
679
|
* started — so this has to be complete on its own.
|
|
507
680
|
*/
|
|
508
|
-
export function recoverySentence(entry) {
|
|
681
|
+
export function recoverySentence(entry, opts = {}) {
|
|
509
682
|
const where = entry.step === 'starting' ? 'while starting' : 'while migrating';
|
|
510
|
-
|
|
511
|
-
`The backup taken first is ${entry.backup ?? 'not available'}.
|
|
512
|
-
|
|
683
|
+
const head = `Upgrade to ${entry.to} failed ${where}: ${entry.error ?? 'unknown error'}. ` +
|
|
684
|
+
`The backup taken first is ${entry.backup ?? 'not available'}. `;
|
|
685
|
+
if (opts.app === true) {
|
|
686
|
+
// buddi.app keeps the release that ran before; its menu goes back to it.
|
|
687
|
+
return head + `In the buddi menu, choose Advanced → Restart with the Previous Version (${entry.from}), then restore ${entry.backup ?? 'that backup'} in Settings → Backups.`;
|
|
688
|
+
}
|
|
689
|
+
return head + `Reinstall with \`npm install -g ${PACKAGE_NAME}@${entry.from}\` and run \`buddi backup restore ${entry.backup ?? '<backup>'}\`.`;
|
|
513
690
|
}
|
|
514
691
|
function message(err) {
|
|
515
692
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -519,10 +696,16 @@ export function createUpgradeService(opts) {
|
|
|
519
696
|
const log = opts.log ?? ((line) => console.error(line));
|
|
520
697
|
const jobs = new JobStore();
|
|
521
698
|
const registry = registryFor(ctx.env);
|
|
699
|
+
/** What npm would send this registry; the check asks with the same. */
|
|
700
|
+
const auth = registryAuth(registry, ctx.env);
|
|
522
701
|
const checkInterval = opts.checkIntervalMs ?? (Number(ctx.env.BUDDI_UPGRADE_CHECK_INTERVAL_MS) || CHECK_INTERVAL_MS);
|
|
523
702
|
const install = opts.install ?? createInstaller();
|
|
524
703
|
const checkPostgres = opts.checkPostgres ?? createPostgresCheck({ env: ctx.env });
|
|
525
704
|
const backupWait = opts.backupWaitMs ?? BACKUP_WAIT_MS;
|
|
705
|
+
/** buddi.app's releases folder, when the app runs this installation. */
|
|
706
|
+
const releases = appLayout(ctx.env);
|
|
707
|
+
const npm = opts.npm ?? (async (bin, argv, where) => await run(bin, argv, where));
|
|
708
|
+
const dressed = (view) => releases === undefined ? view : { ...view, app: true };
|
|
526
709
|
let running;
|
|
527
710
|
/** Has the code under this process already been replaced? See `perform`. */
|
|
528
711
|
let replaced = false;
|
|
@@ -539,13 +722,17 @@ export function createUpgradeService(opts) {
|
|
|
539
722
|
return await save({ ...state, check: { ...state.check, lastAt: at, error: 'this installation has no outbound transport' } });
|
|
540
723
|
}
|
|
541
724
|
try {
|
|
542
|
-
const latest = await fetchLatestRelease(registry, opts.http);
|
|
725
|
+
const latest = await fetchLatestRelease(registry, opts.http, auth);
|
|
726
|
+
// npm names a fresh version minutes before it serves the tarball; until
|
|
727
|
+
// it does, the version is known but not offered (Upgrade would 404).
|
|
728
|
+
const served = !isNewer(latest.version, current) || await tarballServed(registry, latest.version, opts.http, { tarball: latest.tarball, auth });
|
|
543
729
|
// A check that answered clears the error a check that did not left, and
|
|
544
730
|
// the notes are always the ones this answer carried: notes kept from an
|
|
545
731
|
// older `latest` would describe the wrong release.
|
|
546
732
|
return await save({ ...state, check: {
|
|
547
733
|
enabled: state.check.enabled, lastAt: at, latest: latest.version,
|
|
548
734
|
...(latest.notes === undefined ? {} : { latestNotes: latest.notes }),
|
|
735
|
+
...(served ? {} : { processing: latest.version }),
|
|
549
736
|
} });
|
|
550
737
|
}
|
|
551
738
|
catch (err) {
|
|
@@ -586,10 +773,12 @@ export function createUpgradeService(opts) {
|
|
|
586
773
|
if (asked !== undefined) {
|
|
587
774
|
return isVersion(asked) ? { version: asked } : { error: `"${asked}" is not a version this can install.` };
|
|
588
775
|
}
|
|
589
|
-
const
|
|
590
|
-
if (isNewer(
|
|
591
|
-
return { version:
|
|
776
|
+
const kept = (await read()).check;
|
|
777
|
+
if (isNewer(kept.latest, current) && kept.processing === undefined)
|
|
778
|
+
return { version: kept.latest };
|
|
592
779
|
const state = await runCheck();
|
|
780
|
+
if (state.check.processing !== undefined)
|
|
781
|
+
return { error: processingSentence(state.check.processing) };
|
|
593
782
|
if (isVersion(state.check.latest))
|
|
594
783
|
return { version: state.check.latest };
|
|
595
784
|
return { error: state.check.error ?? 'the registry did not name a version to upgrade to' };
|
|
@@ -615,6 +804,8 @@ export function createUpgradeService(opts) {
|
|
|
615
804
|
else
|
|
616
805
|
log(`upgrade: ${step} failed: ${error}`);
|
|
617
806
|
};
|
|
807
|
+
if (releases !== undefined)
|
|
808
|
+
return await performInApp(job, version, releases, give, startedAt, (next) => { to = next; }, (taken) => { archive = taken; });
|
|
618
809
|
// Where npm may write, decided before anything is stopped or archived.
|
|
619
810
|
const target = upgradeTarget(ctx.root);
|
|
620
811
|
if ('error' in target)
|
|
@@ -626,27 +817,18 @@ export function createUpgradeService(opts) {
|
|
|
626
817
|
return await give('checking', resolved.error, false);
|
|
627
818
|
to = resolved.version;
|
|
628
819
|
spec = `${PACKAGE_NAME}@${resolved.version}`;
|
|
820
|
+
// Asked before the backup and the stop: a version npm is still
|
|
821
|
+
// processing would only reach npm's 404 after both.
|
|
822
|
+
if (opts.http !== undefined) {
|
|
823
|
+
const served = await tarballServed(registry, to, opts.http, { auth }).catch(() => undefined);
|
|
824
|
+
if (served === false)
|
|
825
|
+
return await give('checking', processingSentence(to), false);
|
|
826
|
+
}
|
|
629
827
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
*/
|
|
635
|
-
const schedule = await backup.schedule().catch(() => undefined);
|
|
636
|
-
const encrypt = schedule?.encryptLocal !== false;
|
|
637
|
-
if (encrypt && !backup.hasVault()) {
|
|
638
|
-
return await give('backup', 'This installation has no vault, so the backup an upgrade takes first cannot be encrypted. Turn off "Encrypt local backups" in Settings, or set up a vault.', false);
|
|
639
|
-
}
|
|
640
|
-
jobs.phase(job, 'backup', 'taking a backup before anything changes');
|
|
641
|
-
const started = backup.create(encrypt);
|
|
642
|
-
if ('status' in started)
|
|
643
|
-
return await give('backup', started.error, false);
|
|
644
|
-
const done = await settled(started, backupWait);
|
|
645
|
-
if (done === undefined)
|
|
646
|
-
return await give('backup', 'the backup did not finish within twenty minutes', false);
|
|
647
|
-
if (done.phase !== 'done')
|
|
648
|
-
return await give('backup', done.error ?? 'the backup did not finish', false);
|
|
649
|
-
archive = done.report?.archive;
|
|
828
|
+
const taken = await takeBackup(job);
|
|
829
|
+
if ('error' in taken)
|
|
830
|
+
return await give('backup', taken.error, false);
|
|
831
|
+
archive = taken.archive;
|
|
650
832
|
jobs.phase(job, 'stopping', 'stopping the gateway; the database stays up');
|
|
651
833
|
await opts.stopGateway();
|
|
652
834
|
jobs.phase(job, 'installing', `installing ${spec}`);
|
|
@@ -654,7 +836,9 @@ export function createUpgradeService(opts) {
|
|
|
654
836
|
await install(spec, { registry, root: ctx.root, target });
|
|
655
837
|
}
|
|
656
838
|
catch (err) {
|
|
657
|
-
|
|
839
|
+
// npm's 404 wall for a version it has not served yet is one sentence here.
|
|
840
|
+
const said = message(err);
|
|
841
|
+
return await give('installing', tarball === undefined && isNotFound(said) ? processingSentence(to) : said, true);
|
|
658
842
|
}
|
|
659
843
|
/*
|
|
660
844
|
* What is on disk now, rather than what was asked for. npm can be pointed
|
|
@@ -698,6 +882,104 @@ export function createUpgradeService(opts) {
|
|
|
698
882
|
return await give('verifying', `${refused} It is still running on ${current}.`, true);
|
|
699
883
|
}
|
|
700
884
|
}
|
|
885
|
+
await handOverTo(job, to, startedAt, archive);
|
|
886
|
+
};
|
|
887
|
+
/**
|
|
888
|
+
* The backup every upgrade takes first. Encrypted exactly as the schedule
|
|
889
|
+
* says, because that setting is the owner's answer to "may an archive of
|
|
890
|
+
* everything sit on this disk in the clear", and an upgrade is not an
|
|
891
|
+
* exception to it.
|
|
892
|
+
*/
|
|
893
|
+
const takeBackup = async (job) => {
|
|
894
|
+
const schedule = await backup.schedule().catch(() => undefined);
|
|
895
|
+
const encrypt = schedule?.encryptLocal !== false;
|
|
896
|
+
if (encrypt && !backup.hasVault()) {
|
|
897
|
+
return { error: 'This installation has no vault, so the backup an upgrade takes first cannot be encrypted. Turn off "Encrypt local backups" in Settings, or set up a vault.' };
|
|
898
|
+
}
|
|
899
|
+
jobs.phase(job, 'backup', 'taking a backup before anything changes');
|
|
900
|
+
const started = backup.create(encrypt);
|
|
901
|
+
if ('status' in started)
|
|
902
|
+
return { error: started.error };
|
|
903
|
+
const done = await settled(started, backupWait);
|
|
904
|
+
if (done === undefined)
|
|
905
|
+
return { error: 'the backup did not finish within twenty minutes' };
|
|
906
|
+
if (done.phase !== 'done')
|
|
907
|
+
return { error: done.error ?? 'the backup did not finish' };
|
|
908
|
+
return { archive: done.report?.archive };
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* buddi.app's upgrade: the new release goes into `<releases>/buddi-<v>`,
|
|
912
|
+
* verified and checked while the gateway still runs, and only then is there
|
|
913
|
+
* a backup, a stop and the switch of `current`. See `app-layout.ts`.
|
|
914
|
+
*/
|
|
915
|
+
const performInApp = async (job, version, dir, give, startedAt, setTo, setArchive) => {
|
|
916
|
+
if (ctx.env.BUDDI_UPGRADE_SOURCE?.trim())
|
|
917
|
+
return await give('checking', 'buddi.app upgrades from the registry only; BUDDI_UPGRADE_SOURCE is for npm installations.', false);
|
|
918
|
+
const resolved = await resolveVersion(version);
|
|
919
|
+
if ('error' in resolved)
|
|
920
|
+
return await give('checking', resolved.error, false);
|
|
921
|
+
const to = resolved.version;
|
|
922
|
+
setTo(to);
|
|
923
|
+
if (opts.http === undefined)
|
|
924
|
+
return await give('checking', 'this installation has no outbound transport', false);
|
|
925
|
+
const served = await tarballServed(registry, to, opts.http, { auth }).catch(() => undefined);
|
|
926
|
+
if (served === false)
|
|
927
|
+
return await give('checking', processingSentence(to), false);
|
|
928
|
+
const base = await realpath(dir).catch(() => dir);
|
|
929
|
+
if ((path.resolve(ctx.root) + path.sep).startsWith(releaseDir(base, to) + path.sep)) {
|
|
930
|
+
return await give('checking', `${to} is the version running.`, false);
|
|
931
|
+
}
|
|
932
|
+
/*
|
|
933
|
+
* Everything is checked in a staging folder of its own, and only a release
|
|
934
|
+
* that passed every check is renamed into `buddi-<v>`: a failed retry never
|
|
935
|
+
* touches a `buddi-<v>` that `previous` (after a rollback) still points at.
|
|
936
|
+
*/
|
|
937
|
+
jobs.phase(job, 'installing', `downloading and verifying ${to}`);
|
|
938
|
+
let staged;
|
|
939
|
+
try {
|
|
940
|
+
staged = await stageRelease({ releases: dir, version: to, registry, http: opts.http, exec: npm, log, auth, ...(opts.provenanceChain === undefined ? {} : { chain: opts.provenanceChain }) });
|
|
941
|
+
}
|
|
942
|
+
catch (err) {
|
|
943
|
+
const said = message(err);
|
|
944
|
+
return await give('installing', isNotFound(said) ? processingSentence(to) : said, false);
|
|
945
|
+
}
|
|
946
|
+
const dropStaging = async () => { await rm(staged.staging, { recursive: true, force: true }).catch(() => { }); };
|
|
947
|
+
if (ctx.state.database !== 'external') {
|
|
948
|
+
jobs.phase(job, 'verifying', `checking that ${to} can start its database`);
|
|
949
|
+
const verdict = await checkPostgres(staged.root);
|
|
950
|
+
if (!verdict.ok) {
|
|
951
|
+
await dropStaging();
|
|
952
|
+
return await give('verifying', `${verdict.error} buddi did not switch to ${to}. It is still running on ${current}.`, false);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
let placed;
|
|
956
|
+
try {
|
|
957
|
+
placed = await placeRelease(dir, to, staged.staging, log);
|
|
958
|
+
}
|
|
959
|
+
catch (err) {
|
|
960
|
+
await dropStaging();
|
|
961
|
+
return await give('installing', `could not put ${to} in place: ${message(err)}`, false);
|
|
962
|
+
}
|
|
963
|
+
// Only what neither link uses goes: a kept `buddi-<v>` that `previous` names stays.
|
|
964
|
+
const discard = async () => { await pruneReleases(dir).catch(() => []); };
|
|
965
|
+
const taken = await takeBackup(job);
|
|
966
|
+
if ('error' in taken) {
|
|
967
|
+
await discard();
|
|
968
|
+
return await give('backup', taken.error, false);
|
|
969
|
+
}
|
|
970
|
+
setArchive(taken.archive);
|
|
971
|
+
jobs.phase(job, 'stopping', 'stopping the gateway; the database stays up');
|
|
972
|
+
await opts.stopGateway();
|
|
973
|
+
try {
|
|
974
|
+
await switchCurrent(dir, placed.root, ctx.root);
|
|
975
|
+
}
|
|
976
|
+
catch (err) {
|
|
977
|
+
return await give('installing', `could not switch to ${to}: ${message(err)}`, true);
|
|
978
|
+
}
|
|
979
|
+
await handOverTo(job, to, startedAt, taken.archive);
|
|
980
|
+
};
|
|
981
|
+
/** The point of no return, shared by both layouts. */
|
|
982
|
+
const handOverTo = async (job, to, startedAt, archive) => {
|
|
701
983
|
replaced = true;
|
|
702
984
|
jobs.phase(job, 'restarting', `handing over to ${to}`);
|
|
703
985
|
/*
|
|
@@ -733,14 +1015,14 @@ export function createUpgradeService(opts) {
|
|
|
733
1015
|
return {
|
|
734
1016
|
current,
|
|
735
1017
|
async view() {
|
|
736
|
-
return versionView(await read());
|
|
1018
|
+
return dressed(versionView(await read()));
|
|
737
1019
|
},
|
|
738
1020
|
async check() {
|
|
739
|
-
return versionView(await runCheck());
|
|
1021
|
+
return dressed(versionView(await runCheck()));
|
|
740
1022
|
},
|
|
741
1023
|
async setCheckEnabled(enabled) {
|
|
742
1024
|
const state = await read();
|
|
743
|
-
return versionView(await save({ ...state, check: { ...state.check, enabled } }));
|
|
1025
|
+
return dressed(versionView(await save({ ...state, check: { ...state.check, enabled } })));
|
|
744
1026
|
},
|
|
745
1027
|
start(version) {
|
|
746
1028
|
if (version !== undefined && !isVersion(version)) {
|
|
@@ -852,7 +1134,7 @@ export function upgradeDoctorLines(view, phase) {
|
|
|
852
1134
|
if (last !== undefined) {
|
|
853
1135
|
lines.push(`Last upgrade: ${last.from} to ${last.to}, ${last.outcome}${last.step === undefined ? '' : ` at ${last.step}`}`);
|
|
854
1136
|
if (phase === 'upgrade-failed' && last.outcome === 'failed')
|
|
855
|
-
lines.push(recoverySentence(last));
|
|
1137
|
+
lines.push(recoverySentence(last, { app: view.app === true }));
|
|
856
1138
|
}
|
|
857
1139
|
return lines;
|
|
858
1140
|
}
|