@jwilleke/ngdpbase-types 5.35.0 → 5.37.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/packages/ngdpbase-types/testing.js +1 -1
- package/lib/src/ice/ledger.d.ts +8 -0
- package/lib/src/ice/runImport.d.ts +18 -2
- package/lib/src/ice/types.d.ts +14 -5
- package/lib/src/managers/AddonsManager.d.ts +59 -19
- package/lib/src/managers/ConfigurationManager.d.ts +5 -6
- package/lib/src/managers/ImportManager.d.ts +12 -0
- package/lib/src/utils/addonConfigLayer.d.ts +2 -4
- package/lib/src/utils/addonConfigLayer.js +10 -17
- package/lib/src/utils/addonInstaller.d.ts +90 -0
- package/lib/src/utils/addonLock.d.ts +21 -3
- package/lib/src/utils/addonLock.js +2 -0
- package/lib/src/utils/addonsPathResolver.d.ts +17 -48
- package/lib/src/utils/addonsPathResolver.js +23 -83
- package/lib/src/utils/auditEventNames.generated.d.ts +1 -1
- package/lib/src/utils/auditEventNames.generated.js +1 -1
- package/package.json +4 -5
|
@@ -48,7 +48,7 @@ export function openAddonTestDatabase(owner, options, declaredBy) {
|
|
|
48
48
|
checkAddonDatabaseRequest(owner, options);
|
|
49
49
|
if (declaredBy) {
|
|
50
50
|
const { slug, defaults } = declaredBy;
|
|
51
|
-
const layer = addonDatabaseLayer([{ slug, dir: '',
|
|
51
|
+
const layer = addonDatabaseLayer([{ slug, dir: '', enabled: true, defaults }], {});
|
|
52
52
|
const refused = layer.refused.get(slug);
|
|
53
53
|
if (refused)
|
|
54
54
|
throw new Error(`Add-on '${slug}' is not loaded: ${refused}`);
|
package/lib/src/ice/ledger.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { IceLedger, LedgerFileEntry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The ledger file form (#2013). A scope is bound to its receiver and user
|
|
4
|
+
* (`boundScope`); a file without this form was written before that binding,
|
|
5
|
+
* under a scope any caller could name, so it belongs to nobody: it is read
|
|
6
|
+
* as empty, never as anyone's history, and its files are imported once more.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LEDGER_FORM = 2;
|
|
2
9
|
interface ScopeFile {
|
|
10
|
+
form?: number;
|
|
3
11
|
scope: string;
|
|
4
12
|
files: LedgerFileEntry[];
|
|
5
13
|
items: string[];
|
|
@@ -21,8 +21,24 @@ export interface IceRunDeps {
|
|
|
21
21
|
}
|
|
22
22
|
/** The id of the receiver that keeps an original when the chosen receiver does not keep its own (D11). */
|
|
23
23
|
export declare const FILES_RECEIVER = "core.files";
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The engine's key for an item of a file it stored, so a retry of that file
|
|
26
|
+
* skips it (#2010): the file's fingerprint, the chain, and a hash of the
|
|
27
|
+
* item's content (kind, text, data, bytes). Never its position: a shim that
|
|
28
|
+
* changes or orders its output differently cannot make a retry skip the
|
|
29
|
+
* wrong item. `seen` is how many identical items came before it in this file.
|
|
30
|
+
*/
|
|
31
|
+
export declare function itemKeyOf(sha256: string, chain: string[], item: IceItem, seen: number): string;
|
|
32
|
+
/** The form of a bound scope (#2013). Scopes written before it never match one. */
|
|
33
|
+
export declare const SCOPE_FORM = "ice2";
|
|
34
|
+
/**
|
|
35
|
+
* The scope a run's fingerprints, per-file status and item keys live in
|
|
36
|
+
* (#2013): bound to the receiver and to the user the run is for, by their
|
|
37
|
+
* identity `@id` (else their username, #2041). The engine works it out from
|
|
38
|
+
* the caller's context; a request cannot name another user's. A caller's own
|
|
39
|
+
* `sub` (phr's source id) narrows it within that user's scope, never outside.
|
|
40
|
+
*/
|
|
41
|
+
export declare function boundScope(receiverId: string, owner: string, sub?: string): string;
|
|
26
42
|
export interface ChainLeaf {
|
|
27
43
|
item: IceItem;
|
|
28
44
|
chain: string[];
|
package/lib/src/ice/types.d.ts
CHANGED
|
@@ -201,7 +201,13 @@ export interface IceReceiver {
|
|
|
201
201
|
label?: string;
|
|
202
202
|
/** The kinds it takes, e.g. `['ncm']`, `['file']`, `['phr.health-record']`. */
|
|
203
203
|
accepts: IceKind[];
|
|
204
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* May this context use this receiver? Its existing permission (D5). The
|
|
206
|
+
* import screen shows only these, and the engine asks it before any work
|
|
207
|
+
* of a run (#2012): a no refuses every file before anything is unpacked,
|
|
208
|
+
* read by a shim, kept or fetched. Answer no whenever `receive` would
|
|
209
|
+
* refuse this context outright; `receive` still checks each item.
|
|
210
|
+
*/
|
|
205
211
|
mayUse(ctx: ActorContext): Promise<boolean>;
|
|
206
212
|
/** Hand one item to the receiver's own door. */
|
|
207
213
|
receive(item: IceItem, ctx: ActorContext, info: ReceiveInfo): Promise<ReceiveOutcome>;
|
|
@@ -252,10 +258,13 @@ export interface IceRunRequest {
|
|
|
252
258
|
/** D11. Default `ngdpbase.import.engine.default-original`. */
|
|
253
259
|
original?: OriginalChoice;
|
|
254
260
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
261
|
+
* A narrower ledger scope within the caller's own (#2013). The engine binds
|
|
262
|
+
* every run's scope to the receiver and to the user it runs as (their
|
|
263
|
+
* identity `@id`, else their username): the same file imported again by
|
|
264
|
+
* the same user into the same receiver is skipped. A caller with its own
|
|
265
|
+
* notion of a source (phr's source id) passes it to keep those apart; it
|
|
266
|
+
* can never reach another user's or another receiver's scope. Never pass a
|
|
267
|
+
* value a client sent.
|
|
259
268
|
*/
|
|
260
269
|
scope?: string;
|
|
261
270
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { type ActorContext } from '../context/ActorContext.js';
|
|
14
14
|
import type { BackupFolder } from './BaseManager.js';
|
|
15
|
+
import { type UpdateCheck, type UpdateOutcome } from '../utils/addonInstaller.js';
|
|
15
16
|
import BaseManager from './BaseManager.js';
|
|
16
17
|
import type { BackupData } from './BaseManager.js';
|
|
17
18
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
@@ -302,6 +303,9 @@ export interface AddonDashboardCard {
|
|
|
302
303
|
/** URL of the add-on's admin page */
|
|
303
304
|
adminUrl: string;
|
|
304
305
|
}
|
|
306
|
+
/** #2100: the PDP refused an administrator's add-on Update or Check. */
|
|
307
|
+
export declare class AddonUpdateForbiddenError extends Error {
|
|
308
|
+
}
|
|
305
309
|
/**
|
|
306
310
|
* Status information returned by getStatus()
|
|
307
311
|
*/
|
|
@@ -338,12 +342,14 @@ declare class AddonsManager extends BaseManager {
|
|
|
338
342
|
private addonsPaths;
|
|
339
343
|
/** Resolved absolute paths to addons directories */
|
|
340
344
|
private resolvedAddonsPaths;
|
|
341
|
-
/** #673: npm package globs (from `node_modules:` addons-path entries) to discover from node_modules. */
|
|
342
|
-
private npmAddonPatterns;
|
|
343
345
|
/** #1830: `<data>/addons`, where installed add-ons live; null when there is no data folder. */
|
|
344
346
|
private installedDir;
|
|
345
347
|
/** #1830: the installed add-ons verified this boot — the only ones loaded from `installedDir`. */
|
|
346
348
|
private verifiedInstalled;
|
|
349
|
+
/** #2100: installed add-ons this start switched to an Update's release; settled after the load pass. */
|
|
350
|
+
private updatedThisStart;
|
|
351
|
+
/** #2100: Updates run one at a time, so two never write the lock file at once. */
|
|
352
|
+
private updateQueue;
|
|
347
353
|
/** Stylesheets registered by add-ons via registerStylesheet() */
|
|
348
354
|
private registeredStylesheets;
|
|
349
355
|
/** Dashboard cards registered by add-ons via registerDashboardCard() */
|
|
@@ -374,6 +380,14 @@ declare class AddonsManager extends BaseManager {
|
|
|
374
380
|
* this boot, not the next.
|
|
375
381
|
*/
|
|
376
382
|
private installConfiguredAddons;
|
|
383
|
+
/**
|
|
384
|
+
* The installer's options for this site (#1830, #2100): the registry and the
|
|
385
|
+
* limits from configuration, and every request — registry, attestations, the
|
|
386
|
+
* Sigstore TUF repository — through the guarded client and the egress policy
|
|
387
|
+
* (#1139). `timeoutKey` is the `…install.<key>` that bounds each request: the
|
|
388
|
+
* boot install's, or the shorter one an admin page waits on.
|
|
389
|
+
*/
|
|
390
|
+
private installerOptions;
|
|
377
391
|
/**
|
|
378
392
|
* `addon-install` (#1830): written after every check passed and before
|
|
379
393
|
* anything is written to disk. on-failure: refuse — code does not join the
|
|
@@ -381,6 +395,47 @@ declare class AddonsManager extends BaseManager {
|
|
|
381
395
|
* (recordSystemAction throws, and the installer writes nothing).
|
|
382
396
|
*/
|
|
383
397
|
private recordAddonInstall;
|
|
398
|
+
/**
|
|
399
|
+
* #2100: after the load pass, settle each add-on this start switched to an
|
|
400
|
+
* Update's release. Loaded (or switched off, so there was nothing to try):
|
|
401
|
+
* the previous release's folder and tarball go. Not loaded: the previous
|
|
402
|
+
* release is restored on disk and in the lock, so the next start runs it.
|
|
403
|
+
* Core cannot unload an add-on that failed half-way (its modules, routes and
|
|
404
|
+
* jobs stay), so the restored release is not loaded in this process.
|
|
405
|
+
*/
|
|
406
|
+
private settleUpdatedAddons;
|
|
407
|
+
/**
|
|
408
|
+
* `addon-update` (#2100) for a fall-back: core moved an installed add-on back
|
|
409
|
+
* from `from` to `to` because `from` did not load. on-failure: continue for
|
|
410
|
+
* this record — the fall-back has already happened on disk, and a lost
|
|
411
|
+
* record must not undo it.
|
|
412
|
+
*/
|
|
413
|
+
private recordFallback;
|
|
414
|
+
/** #2100: the door for the admin's add-on questions. Throws when the PDP refuses. */
|
|
415
|
+
private requireAddonPermission;
|
|
416
|
+
/**
|
|
417
|
+
* #2100: for each add-on in `ngdpbase.addons.install`, the pinned release,
|
|
418
|
+
* the configured range, and the newest release in it that is newer — asked
|
|
419
|
+
* of the registry now. Called when an administrator opens the add-ons page
|
|
420
|
+
* or presses Check; never at start. Registry unreachable is an answer
|
|
421
|
+
* (`registryUnreachable`), not an error.
|
|
422
|
+
*/
|
|
423
|
+
checkInstalledUpdates(ctx: ActorContext): Promise<UpdateCheck[]>;
|
|
424
|
+
/**
|
|
425
|
+
* #2100: an administrator's Update. Downloads `version` of the installed
|
|
426
|
+
* add-on `slug`, checks it exactly as a first install is checked (sha512,
|
|
427
|
+
* provenance, contents, `ngdpbase.minVersion`) and stages it: it runs from
|
|
428
|
+
* the next start. Asks the PDP for `config-manage`. Recorded as
|
|
429
|
+
* `addon-update` (who, the add-on, from and to) before anything is written;
|
|
430
|
+
* a record that cannot be written stops the Update.
|
|
431
|
+
*/
|
|
432
|
+
updateInstalledAddon(ctx: ActorContext, slug: string, version: string): Promise<UpdateOutcome>;
|
|
433
|
+
/**
|
|
434
|
+
* `addon-update` (#2100): an administrator's Update, written after every
|
|
435
|
+
* check passed and before anything is written to disk. on-failure: refuse —
|
|
436
|
+
* a record that cannot be written stops the Update.
|
|
437
|
+
*/
|
|
438
|
+
private recordAddonUpdate;
|
|
384
439
|
/**
|
|
385
440
|
* `addon-verify-failed` (#1830): an integrity, provenance, tarball, manifest,
|
|
386
441
|
* lock or folder check refused (or, for a repaired folder, found it changed).
|
|
@@ -404,25 +459,10 @@ declare class AddonsManager extends BaseManager {
|
|
|
404
459
|
* Scan a single addons directory and register any add-ons found.
|
|
405
460
|
*/
|
|
406
461
|
private scanAddonsDirectory;
|
|
407
|
-
/**
|
|
408
|
-
* #673 (packaged model): discover addons published as npm packages, matching a
|
|
409
|
-
* `node_modules:<glob>` addons-path entry (e.g. `node_modules:@jwilleke/*-addon`).
|
|
410
|
-
* Each matching package is loaded through the same slug/module/register()
|
|
411
|
-
* contract as bundled and drop-in addons — only discovery differs.
|
|
412
|
-
*/
|
|
413
|
-
private scanNpmAddons;
|
|
414
|
-
/** Locate the node_modules directory (app root / cwd). */
|
|
415
|
-
private findNodeModules;
|
|
416
|
-
/** Expand a `@scope/glob` (or bare `glob`) pattern to package directories. */
|
|
417
|
-
private resolveNpmAddonDirs;
|
|
418
462
|
/**
|
|
419
463
|
* Load a single addon from its directory (index.js/ts + optional package.json
|
|
420
|
-
* `ngdpbase` manifest) and register it.
|
|
421
|
-
*
|
|
422
|
-
* no-slug identity fallback (#927): a directory addon's folder name is its
|
|
423
|
-
* identity verbatim, while an npm package strips the conventional `-addon`
|
|
424
|
-
* suffix. Duplicate ids are skipped — since npm discovery runs last, a
|
|
425
|
-
* bundled/drop-in addon wins.
|
|
464
|
+
* `ngdpbase` manifest) and register it. `sourceLabel` is for logging only.
|
|
465
|
+
* Duplicate ids are skipped, so the first directory scanned wins.
|
|
426
466
|
*/
|
|
427
467
|
private registerAddonFromDir;
|
|
428
468
|
/**
|
|
@@ -141,16 +141,15 @@ declare class ConfigurationManager extends BaseManager {
|
|
|
141
141
|
/**
|
|
142
142
|
* #672: refuse to start if any `ngdpbase.addons.<id>.enabled = true` key
|
|
143
143
|
* references an `<id>` that has no matching addon in any configured
|
|
144
|
-
* `addons-path`
|
|
145
|
-
*
|
|
144
|
+
* `addons-path` directory (bundled/drop-in) and is not named in
|
|
145
|
+
* `ngdpbase.addons.install`. A retired `node_modules:` entry is ignored (#2102).
|
|
146
146
|
* The discovery logic mirrors what `AddonsManager` does at runtime —
|
|
147
147
|
* directory/package present with `index.js` or `index.ts` — but without
|
|
148
148
|
* importing the modules (boot-time speed; `ConfigurationManager.initialize()`
|
|
149
|
-
* runs before any managers). The
|
|
150
|
-
*
|
|
149
|
+
* runs before any managers). The addons-path parsing AND the
|
|
150
|
+
* canonical-identity resolution are shared with
|
|
151
151
|
* `AddonsManager` via `utils/addonsPathResolver.ts` so the two can no
|
|
152
|
-
* longer drift out of sync (#924
|
|
153
|
-
* never boot as a result).
|
|
152
|
+
* longer drift out of sync (#924).
|
|
154
153
|
*
|
|
155
154
|
* Catches the failure mode that caused the 2026-05-10 geohazardwatch.com
|
|
156
155
|
* outage: the deploy configmap had `ngdpbase.addons.ve-geology.enabled =
|
|
@@ -382,6 +382,18 @@ declare class ImportManager extends BaseManager {
|
|
|
382
382
|
importRunsFor(ctx: ActorContext): IceRunReport[];
|
|
383
383
|
/** Use this ledger instead of the file one: a test's, or a database table's. */
|
|
384
384
|
setImportLedger(ledger: IceLedger): void;
|
|
385
|
+
/**
|
|
386
|
+
* The ledger scope a run uses (#2013): bound to the receiver and to the user
|
|
387
|
+
* `ctx` names, by their identity `@id`; their username only when the
|
|
388
|
+
* identity store answers that there is no record for them (#2041). Never a
|
|
389
|
+
* scope the caller names: a request's `scope` only narrows within this
|
|
390
|
+
* user's own.
|
|
391
|
+
*
|
|
392
|
+
* @throws IdentityStoreUnavailableError when the identity store cannot
|
|
393
|
+
* answer: the run does not start, and nothing is recorded, rather than
|
|
394
|
+
* land in a scope the user's other runs do not use
|
|
395
|
+
*/
|
|
396
|
+
private importScope;
|
|
385
397
|
private newRunReport;
|
|
386
398
|
private executeRun;
|
|
387
399
|
/** What a run, or keeping at upload, works with. */
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* shipped entry. The operator's file still wins over everything.
|
|
19
19
|
*
|
|
20
20
|
* Discovery follows `ngdpbase.managers.addons-manager.addons-path` exactly as
|
|
21
|
-
* AddonsManager does —
|
|
22
|
-
* and
|
|
21
|
+
* AddonsManager does — directory entries — so bundled
|
|
22
|
+
* and drop-in addons are treated alike. Only an addon whose
|
|
23
23
|
* `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
|
|
24
24
|
* contributes; a disabled addon's opinions do not reach the catalog.
|
|
25
25
|
*
|
|
@@ -63,7 +63,6 @@ export declare const ADDONS_MANAGER_ENABLED_KEY = "ngdpbase.managers.addons-mana
|
|
|
63
63
|
export interface AddonDefaults {
|
|
64
64
|
slug: string;
|
|
65
65
|
dir: string;
|
|
66
|
-
source: 'directory' | 'npm';
|
|
67
66
|
/**
|
|
68
67
|
* Whether the addon is switched on. A turned-off addon's file is read for its
|
|
69
68
|
* system-categories alone (#1559); none of its other keys reach the merge.
|
|
@@ -83,7 +82,6 @@ export declare function isAddonEnabled(base: Record<string, unknown>, slug: stri
|
|
|
83
82
|
*/
|
|
84
83
|
export declare function discoverAddonDirs(base: Record<string, unknown>, cwd?: string, dataFolder?: string): Array<{
|
|
85
84
|
dir: string;
|
|
86
|
-
source: 'directory' | 'npm';
|
|
87
85
|
}>;
|
|
88
86
|
/**
|
|
89
87
|
* The defaults of every installed addon, enabled or not, in discovery order,
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* shipped entry. The operator's file still wins over everything.
|
|
19
19
|
*
|
|
20
20
|
* Discovery follows `ngdpbase.managers.addons-manager.addons-path` exactly as
|
|
21
|
-
* AddonsManager does —
|
|
22
|
-
* and
|
|
21
|
+
* AddonsManager does — directory entries — so bundled
|
|
22
|
+
* and drop-in addons are treated alike. Only an addon whose
|
|
23
23
|
* `ngdpbase.addons.<slug>.enabled` is true in the shipped+custom view
|
|
24
24
|
* contributes; a disabled addon's opinions do not reach the catalog.
|
|
25
25
|
*
|
|
@@ -61,7 +61,7 @@ import fs from 'fs';
|
|
|
61
61
|
import path from 'path';
|
|
62
62
|
import { isDeepStrictEqual } from 'util';
|
|
63
63
|
import { configFilePaths, deepMergeConfigs, deepMergeObjects, instanceDataFolder, isPlainObject, readConfigFilesSync } from './configFiles.js';
|
|
64
|
-
import { configuredInstallSlugs,
|
|
64
|
+
import { configuredInstallSlugs, installedAddonsDir, resolveAddonSlug, splitAddonsPath } from './addonsPathResolver.js';
|
|
65
65
|
import { lockedAddonDirsSync } from './addonLock.js';
|
|
66
66
|
import { LEGACY_SECRET_KEYS_KEY, SENSITIVE_VALUES_KEY } from './sensitiveValues.js';
|
|
67
67
|
import { parsePrivatestore } from './privateStorePath.js';
|
|
@@ -81,7 +81,7 @@ export function isAddonEnabled(base, slug) {
|
|
|
81
81
|
export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
|
|
82
82
|
if (base[ADDONS_MANAGER_ENABLED_KEY] === false)
|
|
83
83
|
return [];
|
|
84
|
-
const { directories
|
|
84
|
+
const { directories } = splitAddonsPath(base[ADDONS_PATH_KEY] ?? './addons');
|
|
85
85
|
const found = [];
|
|
86
86
|
for (const raw of directories) {
|
|
87
87
|
const root = path.resolve(cwd, raw);
|
|
@@ -95,20 +95,13 @@ export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instan
|
|
|
95
95
|
for (const entry of entries) {
|
|
96
96
|
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'shared')
|
|
97
97
|
continue;
|
|
98
|
-
found.push({ dir: path.join(root, entry.name)
|
|
98
|
+
found.push({ dir: path.join(root, entry.name) });
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
const installed = installedAddonsDir(dataFolder, cwd);
|
|
102
102
|
if (!found.some((f) => path.dirname(f.dir) === installed)) {
|
|
103
103
|
for (const dir of lockedAddonDirsSync(installed, configuredInstallSlugs(base)))
|
|
104
|
-
found.push({ dir
|
|
105
|
-
}
|
|
106
|
-
const nmRoot = npmPatterns.length > 0 ? findNodeModulesDir(cwd) : null;
|
|
107
|
-
if (nmRoot) {
|
|
108
|
-
for (const pattern of npmPatterns) {
|
|
109
|
-
for (const dir of matchNpmPackageDirs(nmRoot, pattern))
|
|
110
|
-
found.push({ dir, source: 'npm' });
|
|
111
|
-
}
|
|
104
|
+
found.push({ dir });
|
|
112
105
|
}
|
|
113
106
|
return found;
|
|
114
107
|
}
|
|
@@ -122,8 +115,8 @@ export function discoverAddonDirs(base, cwd = process.cwd(), dataFolder = instan
|
|
|
122
115
|
*/
|
|
123
116
|
export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = instanceDataFolder()) {
|
|
124
117
|
const out = [];
|
|
125
|
-
for (const { dir
|
|
126
|
-
const slug = resolveAddonSlug(dir
|
|
118
|
+
for (const { dir } of discoverAddonDirs(base, cwd, dataFolder)) {
|
|
119
|
+
const slug = resolveAddonSlug(dir);
|
|
127
120
|
const enabled = isAddonEnabled(base, slug);
|
|
128
121
|
const file = path.join(dir, 'config', 'default-config.json');
|
|
129
122
|
if (!fs.existsSync(file))
|
|
@@ -136,10 +129,10 @@ export function discoverAddonDefaults(base, cwd = process.cwd(), dataFolder = in
|
|
|
136
129
|
for (const [key, value] of Object.entries(raw))
|
|
137
130
|
if (!key.startsWith('_'))
|
|
138
131
|
defaults[key] = value;
|
|
139
|
-
out.push({ slug, dir,
|
|
132
|
+
out.push({ slug, dir, enabled, defaults });
|
|
140
133
|
}
|
|
141
134
|
catch (err) {
|
|
142
|
-
out.push({ slug, dir,
|
|
135
|
+
out.push({ slug, dir, enabled, defaults: {}, error: err instanceof Error ? err.message : String(err) });
|
|
143
136
|
}
|
|
144
137
|
}
|
|
145
138
|
return out;
|
|
@@ -85,6 +85,12 @@ export type InstallStatus =
|
|
|
85
85
|
| 'verified'
|
|
86
86
|
/** Already installed; the folder differed from its verified tarball and was rewritten. */
|
|
87
87
|
| 'repaired'
|
|
88
|
+
/**
|
|
89
|
+
* #2100: switched to the release an administrator's Update staged; the
|
|
90
|
+
* previous release is kept until AddonsManager confirms this one loaded
|
|
91
|
+
* ({@link confirmUpdate}) or falls back to it ({@link rollBackUpdate}).
|
|
92
|
+
*/
|
|
93
|
+
| 'updated'
|
|
88
94
|
/** Not installed (and not loaded); `reason` says why. */
|
|
89
95
|
| 'skipped';
|
|
90
96
|
/** Which verification refused, for `addon-verify-failed`. */
|
|
@@ -99,6 +105,20 @@ export interface InstallOutcome {
|
|
|
99
105
|
registryUnreachable?: boolean;
|
|
100
106
|
/** Set when a verification refused, or (for `repaired`) found the folder changed. */
|
|
101
107
|
failedCheck?: FailedCheck;
|
|
108
|
+
/** For `updated`: the release it replaced. */
|
|
109
|
+
previousVersion?: string;
|
|
110
|
+
/**
|
|
111
|
+
* #2100: a switch an earlier start made and never confirmed (the new release
|
|
112
|
+
* did not load, or the process stopped before it could) was undone at this
|
|
113
|
+
* start, before anything loaded: `from` is the release given up, `to` the
|
|
114
|
+
* one restored.
|
|
115
|
+
*/
|
|
116
|
+
rolledBack?: {
|
|
117
|
+
from: string;
|
|
118
|
+
to: string;
|
|
119
|
+
};
|
|
120
|
+
/** #2100: something admins should hear about a staged Update, such as one discarded. */
|
|
121
|
+
notice?: string;
|
|
102
122
|
}
|
|
103
123
|
/** `sha512-<base64>` of `data`. */
|
|
104
124
|
export declare function sha512Integrity(data: Buffer): string;
|
|
@@ -107,3 +127,73 @@ export declare function sha512Integrity(data: Buffer): string;
|
|
|
107
127
|
* Never throws for one add-on's problem: each outcome says what happened.
|
|
108
128
|
*/
|
|
109
129
|
export declare function installConfiguredAddons(specs: Record<string, unknown>, opts: InstallerOptions): Promise<InstallOutcome[]>;
|
|
130
|
+
/**
|
|
131
|
+
* The switched-to release loaded (or the add-on is off, so there is nothing to
|
|
132
|
+
* try): the previous release's folder and tarball go. No-op when nothing waits.
|
|
133
|
+
*/
|
|
134
|
+
export declare function confirmUpdate(addonsDir: string, slug: string): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* The switched-to release did not load: restore the previous release on disk
|
|
137
|
+
* and in the lock, so the next start runs it. The new folder is set aside
|
|
138
|
+
* (`.rejected-…`) for inspection. Undefined when there is nothing to undo.
|
|
139
|
+
*/
|
|
140
|
+
export declare function rollBackUpdate(addonsDir: string, slug: string, now?: Date): Promise<{
|
|
141
|
+
from: string;
|
|
142
|
+
to: string;
|
|
143
|
+
} | undefined>;
|
|
144
|
+
/** Why an Update is refused while the last one is not settled (`previous` still in the lock). */
|
|
145
|
+
export declare const FINISH_LAST_UPDATE = "finish the last update first: restart the site";
|
|
146
|
+
/** What the admin page shows for one add-on in `ngdpbase.addons.install`. */
|
|
147
|
+
export interface UpdateCheck {
|
|
148
|
+
slug: string;
|
|
149
|
+
package?: string;
|
|
150
|
+
/** The configured range (`version` in the entry). */
|
|
151
|
+
range?: string;
|
|
152
|
+
/** The release the lock pins: what runs. Absent when not installed. */
|
|
153
|
+
pinned?: string;
|
|
154
|
+
/** A release an Update downloaded, used from the next start. */
|
|
155
|
+
pending?: string;
|
|
156
|
+
/** The newest release in the range, whether or not this core can run it. */
|
|
157
|
+
newest?: string;
|
|
158
|
+
/** The newest release in the range, newer than what is pinned or staged, that this core can run: what Update installs. */
|
|
159
|
+
available?: string;
|
|
160
|
+
/** The newest release in the range, newer than what is pinned or staged, when it needs a newer ngdpbase. */
|
|
161
|
+
needsCore?: {
|
|
162
|
+
version: string;
|
|
163
|
+
minVersion: string;
|
|
164
|
+
};
|
|
165
|
+
registryUnreachable?: boolean;
|
|
166
|
+
/** Why there is nothing to compare: not installed, or a configuration problem. */
|
|
167
|
+
problem?: string;
|
|
168
|
+
/** The last update is not settled (the lock still names the release it replaced): no Update until a restart. */
|
|
169
|
+
blocked?: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Ask the registry, for every installed add-on, which releases in its range
|
|
173
|
+
* are newer than the one pinned. Reads the packument only; writes nothing.
|
|
174
|
+
* Called only when an administrator opens the add-ons page or presses Check,
|
|
175
|
+
* never at start.
|
|
176
|
+
*/
|
|
177
|
+
export declare function checkForUpdates(specs: Record<string, unknown>, opts: InstallerOptions): Promise<UpdateCheck[]>;
|
|
178
|
+
/** What an Update did. */
|
|
179
|
+
export interface UpdateOutcome {
|
|
180
|
+
slug: string;
|
|
181
|
+
/** `staged`: verified and downloaded; it runs from the next start. `refused`: nothing changed; `reason` says why. */
|
|
182
|
+
status: 'staged' | 'refused';
|
|
183
|
+
package?: string;
|
|
184
|
+
version?: string;
|
|
185
|
+
/** The release that runs until the next start. */
|
|
186
|
+
previousVersion?: string;
|
|
187
|
+
reason?: string;
|
|
188
|
+
registryUnreachable?: boolean;
|
|
189
|
+
failedCheck?: FailedCheck;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Download `version` of an installed add-on and stage it for the next start
|
|
193
|
+
* (#2100). It must be a release in the configured range, newer than the
|
|
194
|
+
* pinned one, that this core can run, and it passes the same checks as a
|
|
195
|
+
* first install ({@link fetchVerified}): sha512, provenance, contents,
|
|
196
|
+
* package.json. `beforeInstall` (the audit record) runs after the checks and
|
|
197
|
+
* before anything is written. The running add-on's folder is not touched.
|
|
198
|
+
*/
|
|
199
|
+
export declare function stageUpdate(slug: string, rawSpec: unknown, version: string, opts: InstallerOptions): Promise<UpdateOutcome>;
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* folders in `<data>/addons` are installed add-ons at all: a folder the lock
|
|
8
8
|
* does not name is never an add-on, whatever it contains.
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
11
|
-
export interface
|
|
12
|
-
package: string;
|
|
10
|
+
/** One verified release of an add-on: what it is and what it was accepted on. */
|
|
11
|
+
export interface PinnedVersion {
|
|
13
12
|
version: string;
|
|
14
13
|
integrity: string;
|
|
15
14
|
installedAt: string;
|
|
@@ -19,6 +18,23 @@ export interface LockEntry {
|
|
|
19
18
|
verifiedAt: string;
|
|
20
19
|
};
|
|
21
20
|
}
|
|
21
|
+
/** What the lock records for one installed add-on. */
|
|
22
|
+
export interface LockEntry extends PinnedVersion {
|
|
23
|
+
package: string;
|
|
24
|
+
/**
|
|
25
|
+
* #2100: a newer release an administrator's Update downloaded and verified.
|
|
26
|
+
* Its tarball waits in `.packages/`; the next start switches to it. Nothing
|
|
27
|
+
* else reads it, so until then the site runs `version`.
|
|
28
|
+
*/
|
|
29
|
+
pending?: PinnedVersion;
|
|
30
|
+
/**
|
|
31
|
+
* #2100: the release this one replaced at the start that switched to it,
|
|
32
|
+
* kept (its folder as `.previous-<slug>`, its tarball in `.packages/`) until
|
|
33
|
+
* this one has loaded. Present at a later start only when that start never
|
|
34
|
+
* confirmed the switch, so the installer falls back to it.
|
|
35
|
+
*/
|
|
36
|
+
previous?: PinnedVersion;
|
|
37
|
+
}
|
|
22
38
|
export interface LockFile {
|
|
23
39
|
lockfileVersion: 1;
|
|
24
40
|
addons: Record<string, LockEntry>;
|
|
@@ -26,6 +42,8 @@ export interface LockFile {
|
|
|
26
42
|
export declare const LOCK_FILE_NAME = "addons-lock.json";
|
|
27
43
|
/** Verified tarballs, kept so a restart needs no registry. Dot-named: discovery skips it. */
|
|
28
44
|
export declare const PACKAGES_DIR_NAME = ".packages";
|
|
45
|
+
/** #2100: the folder a switched-from release waits in until the new one has loaded. Dot-named: discovery skips it. */
|
|
46
|
+
export declare const PREVIOUS_DIR_PREFIX = ".previous-";
|
|
29
47
|
export declare function lockFilePath(addonsDir: string): string;
|
|
30
48
|
/** The lock file, or an empty one when there is none. Throws when it exists and is not JSON. */
|
|
31
49
|
export declare function readLockSync(addonsDir: string): LockFile;
|
|
@@ -12,6 +12,8 @@ import path from 'path';
|
|
|
12
12
|
export const LOCK_FILE_NAME = 'addons-lock.json';
|
|
13
13
|
/** Verified tarballs, kept so a restart needs no registry. Dot-named: discovery skips it. */
|
|
14
14
|
export const PACKAGES_DIR_NAME = '.packages';
|
|
15
|
+
/** #2100: the folder a switched-from release waits in until the new one has loaded. Dot-named: discovery skips it. */
|
|
16
|
+
export const PREVIOUS_DIR_PREFIX = '.previous-';
|
|
15
17
|
export function lockFilePath(addonsDir) {
|
|
16
18
|
return path.join(addonsDir, LOCK_FILE_NAME);
|
|
17
19
|
}
|
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared parsing/discovery for the `ngdpbase.managers.addons-manager.addons-path`
|
|
3
|
-
* config value
|
|
3
|
+
* config value, used by `AddonsManager` (runtime discovery), `ConfigurationManager`
|
|
4
|
+
* (the #672 boot-time "does this enabled addon exist" check) and the add-on
|
|
5
|
+
* config layer, so the three cannot drift (#924).
|
|
4
6
|
*
|
|
5
|
-
* #
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* updated for #673's `node_modules:<glob>` packaged-addon syntax, but the
|
|
9
|
-
* validator's copy was not, so it resolved a `node_modules:` entry as a
|
|
10
|
-
* literal (non-existent) directory and treated every packaged addon as
|
|
11
|
-
* "unknown" — crashing boot the moment one was enabled. Consolidating the
|
|
12
|
-
* parsing/matching into one module removes the class of bug, not just this
|
|
13
|
-
* instance of it.
|
|
7
|
+
* #2102: the packaged model (`node_modules:<glob>` entries) is gone. A site
|
|
8
|
+
* config may still hold such an entry; it is ignored, never treated as a
|
|
9
|
+
* directory, and never stops the boot.
|
|
14
10
|
*/
|
|
15
|
-
/** Prefix marking an addons-path entry as an npm-package glob rather than a directory. */
|
|
16
|
-
export declare const NPM_ADDON_PREFIX = "node_modules:";
|
|
17
11
|
export interface SplitAddonsPath {
|
|
18
12
|
/** Plain directory entries (bundled/drop-in), not yet resolved to absolute. */
|
|
19
13
|
directories: string[];
|
|
20
|
-
/**
|
|
21
|
-
|
|
14
|
+
/** Retired `node_modules:` entries (#2102): ignored; AddonsManager warns about them once at start. */
|
|
15
|
+
retired: string[];
|
|
22
16
|
}
|
|
23
17
|
/**
|
|
24
18
|
* Coerce the raw config value (string or string[]) and split it into
|
|
25
|
-
* filesystem directory entries
|
|
19
|
+
* filesystem directory entries and retired `node_modules:` entries.
|
|
26
20
|
*/
|
|
27
21
|
export declare function splitAddonsPath(raw: unknown): SplitAddonsPath;
|
|
22
|
+
/** The warning for one retired `node_modules:` entry (#2102). */
|
|
23
|
+
export declare function retiredAddonsPathWarning(entry: string): string;
|
|
28
24
|
/** The config key naming the add-ons to install (#1830). */
|
|
29
25
|
export declare const ADDONS_INSTALL_KEY = "ngdpbase.addons.install";
|
|
30
26
|
/**
|
|
@@ -35,42 +31,15 @@ export declare const ADDONS_INSTALL_KEY = "ngdpbase.addons.install";
|
|
|
35
31
|
export declare function installedAddonsDir(dataFolder: string, cwd?: string): string;
|
|
36
32
|
/** The slugs `ngdpbase.addons.install` names, from a merged or shipped+custom view. */
|
|
37
33
|
export declare function configuredInstallSlugs(config: Record<string, unknown>): string[];
|
|
38
|
-
/** Locate the `node_modules` directory to search for packaged addons (resolved from cwd). */
|
|
39
|
-
export declare function findNodeModulesDir(cwd?: string): string | null;
|
|
40
|
-
/**
|
|
41
|
-
* Expand a `@scope/glob` (or bare `glob`) npm pattern to matching package
|
|
42
|
-
* directories under `nmRoot`.
|
|
43
|
-
*/
|
|
44
|
-
export declare function matchNpmPackageDirs(nmRoot: string, pattern: string): string[];
|
|
45
|
-
/**
|
|
46
|
-
* Best-effort addon identity for a packaged (npm) addon directory, derived
|
|
47
|
-
* from its package-directory name alone — no module import, matching the
|
|
48
|
-
* same boot-time-speed, no-module-loading design already accepted for the
|
|
49
|
-
* directory-scan case (see `ConfigurationManager.assertConfiguredAddonsExist`).
|
|
50
|
-
*
|
|
51
|
-
* Follows the documented packaged-addon naming convention
|
|
52
|
-
* (`@scope/<slug>-addon`, see docs/platform/deployment/addon-packaged.md):
|
|
53
|
-
* strips a trailing `-addon` suffix if present, else returns the folder name
|
|
54
|
-
* as-is.
|
|
55
|
-
*
|
|
56
|
-
* Caveat (same class as the existing directory-name heuristic): the addon's
|
|
57
|
-
* *real* identity is whatever its module exports as `name` at runtime. A
|
|
58
|
-
* package that doesn't follow the `-addon` naming convention, or whose
|
|
59
|
-
* module exports a different `name`, won't be matched precisely here — this
|
|
60
|
-
* is a validation-time approximation, not the source of truth.
|
|
61
|
-
*/
|
|
62
|
-
export declare function deriveAddonSlugFromPackageDirName(dirName: string): string;
|
|
63
34
|
/**
|
|
64
35
|
* Canonical identity for an addon directory (#927), resolved the SAME
|
|
65
36
|
* import-free way in every layer (discovery, `isEnabled`, the boot-time
|
|
66
|
-
* validator)
|
|
67
|
-
* config key, manifest slug) can no longer drift:
|
|
37
|
+
* validator):
|
|
68
38
|
*
|
|
69
|
-
* canonicalId = package.json `ngdpbase.slug` ??
|
|
39
|
+
* canonicalId = package.json `ngdpbase.slug` ?? the folder name verbatim
|
|
70
40
|
*
|
|
71
|
-
* Reads `package.json` statically — never imports the module
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* label validated against this at load time, not a source of identity.
|
|
41
|
+
* Reads `package.json` statically — never imports the module. The module's
|
|
42
|
+
* exported `name` is a display label validated against this at load time,
|
|
43
|
+
* not a source of identity.
|
|
75
44
|
*/
|
|
76
|
-
export declare function resolveAddonSlug(addonDir: string
|
|
45
|
+
export declare function resolveAddonSlug(addonDir: string): string;
|
|
@@ -1,36 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared parsing/discovery for the `ngdpbase.managers.addons-manager.addons-path`
|
|
3
|
-
* config value
|
|
3
|
+
* config value, used by `AddonsManager` (runtime discovery), `ConfigurationManager`
|
|
4
|
+
* (the #672 boot-time "does this enabled addon exist" check) and the add-on
|
|
5
|
+
* config layer, so the three cannot drift (#924).
|
|
4
6
|
*
|
|
5
|
-
* #
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* updated for #673's `node_modules:<glob>` packaged-addon syntax, but the
|
|
9
|
-
* validator's copy was not, so it resolved a `node_modules:` entry as a
|
|
10
|
-
* literal (non-existent) directory and treated every packaged addon as
|
|
11
|
-
* "unknown" — crashing boot the moment one was enabled. Consolidating the
|
|
12
|
-
* parsing/matching into one module removes the class of bug, not just this
|
|
13
|
-
* instance of it.
|
|
7
|
+
* #2102: the packaged model (`node_modules:<glob>` entries) is gone. A site
|
|
8
|
+
* config may still hold such an entry; it is ignored, never treated as a
|
|
9
|
+
* directory, and never stops the boot.
|
|
14
10
|
*/
|
|
15
11
|
import * as fs from 'fs';
|
|
16
12
|
import * as path from 'path';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export const NPM_ADDON_PREFIX = 'node_modules:';
|
|
13
|
+
/** The retired packaged-model prefix, recognised only so the entry can be ignored with a warning. */
|
|
14
|
+
const RETIRED_PACKAGED_PREFIX = 'node_modules:';
|
|
20
15
|
/**
|
|
21
16
|
* Coerce the raw config value (string or string[]) and split it into
|
|
22
|
-
* filesystem directory entries
|
|
17
|
+
* filesystem directory entries and retired `node_modules:` entries.
|
|
23
18
|
*/
|
|
24
19
|
export function splitAddonsPath(raw) {
|
|
25
20
|
const entries = Array.isArray(raw) ? raw.map(String) : [String(raw)];
|
|
26
21
|
return {
|
|
27
|
-
directories: entries.filter(p => !p.startsWith(
|
|
28
|
-
|
|
29
|
-
.filter(p => p.startsWith(NPM_ADDON_PREFIX))
|
|
30
|
-
.map(p => p.slice(NPM_ADDON_PREFIX.length).trim())
|
|
31
|
-
.filter(Boolean)
|
|
22
|
+
directories: entries.filter(p => !p.trim().startsWith(RETIRED_PACKAGED_PREFIX)),
|
|
23
|
+
retired: entries.filter(p => p.trim().startsWith(RETIRED_PACKAGED_PREFIX))
|
|
32
24
|
};
|
|
33
25
|
}
|
|
26
|
+
/** The warning for one retired `node_modules:` entry (#2102). */
|
|
27
|
+
export function retiredAddonsPathWarning(entry) {
|
|
28
|
+
return `addons-path entry '${entry}' is no longer supported and is ignored; install add-ons with ngdpbase.addons.install`;
|
|
29
|
+
}
|
|
34
30
|
/** The config key naming the add-ons to install (#1830). */
|
|
35
31
|
export const ADDONS_INSTALL_KEY = 'ngdpbase.addons.install';
|
|
36
32
|
/**
|
|
@@ -46,69 +42,18 @@ export function configuredInstallSlugs(config) {
|
|
|
46
42
|
const map = config[ADDONS_INSTALL_KEY];
|
|
47
43
|
return map && typeof map === 'object' && !Array.isArray(map) ? Object.keys(map) : [];
|
|
48
44
|
}
|
|
49
|
-
/** Locate the `node_modules` directory to search for packaged addons (resolved from cwd). */
|
|
50
|
-
export function findNodeModulesDir(cwd = process.cwd()) {
|
|
51
|
-
const nm = path.resolve(cwd, 'node_modules');
|
|
52
|
-
return fs.existsSync(nm) && fs.statSync(nm).isDirectory() ? nm : null;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Expand a `@scope/glob` (or bare `glob`) npm pattern to matching package
|
|
56
|
-
* directories under `nmRoot`.
|
|
57
|
-
*/
|
|
58
|
-
export function matchNpmPackageDirs(nmRoot, pattern) {
|
|
59
|
-
let baseDir = nmRoot;
|
|
60
|
-
let nameGlob = pattern;
|
|
61
|
-
if (pattern.startsWith('@')) {
|
|
62
|
-
const slash = pattern.indexOf('/');
|
|
63
|
-
const scope = slash > 0 ? pattern.slice(0, slash) : pattern;
|
|
64
|
-
nameGlob = slash > 0 ? pattern.slice(slash + 1) : '*';
|
|
65
|
-
baseDir = path.join(nmRoot, scope);
|
|
66
|
-
}
|
|
67
|
-
if (!fs.existsSync(baseDir))
|
|
68
|
-
return [];
|
|
69
|
-
const out = [];
|
|
70
|
-
for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
|
|
71
|
-
if (entry.isDirectory() && !entry.name.startsWith('.') && minimatch(entry.name, nameGlob)) {
|
|
72
|
-
out.push(path.join(baseDir, entry.name));
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Best-effort addon identity for a packaged (npm) addon directory, derived
|
|
79
|
-
* from its package-directory name alone — no module import, matching the
|
|
80
|
-
* same boot-time-speed, no-module-loading design already accepted for the
|
|
81
|
-
* directory-scan case (see `ConfigurationManager.assertConfiguredAddonsExist`).
|
|
82
|
-
*
|
|
83
|
-
* Follows the documented packaged-addon naming convention
|
|
84
|
-
* (`@scope/<slug>-addon`, see docs/platform/deployment/addon-packaged.md):
|
|
85
|
-
* strips a trailing `-addon` suffix if present, else returns the folder name
|
|
86
|
-
* as-is.
|
|
87
|
-
*
|
|
88
|
-
* Caveat (same class as the existing directory-name heuristic): the addon's
|
|
89
|
-
* *real* identity is whatever its module exports as `name` at runtime. A
|
|
90
|
-
* package that doesn't follow the `-addon` naming convention, or whose
|
|
91
|
-
* module exports a different `name`, won't be matched precisely here — this
|
|
92
|
-
* is a validation-time approximation, not the source of truth.
|
|
93
|
-
*/
|
|
94
|
-
export function deriveAddonSlugFromPackageDirName(dirName) {
|
|
95
|
-
const suffix = '-addon';
|
|
96
|
-
return dirName.endsWith(suffix) ? dirName.slice(0, -suffix.length) : dirName;
|
|
97
|
-
}
|
|
98
45
|
/**
|
|
99
46
|
* Canonical identity for an addon directory (#927), resolved the SAME
|
|
100
47
|
* import-free way in every layer (discovery, `isEnabled`, the boot-time
|
|
101
|
-
* validator)
|
|
102
|
-
* config key, manifest slug) can no longer drift:
|
|
48
|
+
* validator):
|
|
103
49
|
*
|
|
104
|
-
* canonicalId = package.json `ngdpbase.slug` ??
|
|
50
|
+
* canonicalId = package.json `ngdpbase.slug` ?? the folder name verbatim
|
|
105
51
|
*
|
|
106
|
-
* Reads `package.json` statically — never imports the module
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* label validated against this at load time, not a source of identity.
|
|
52
|
+
* Reads `package.json` statically — never imports the module. The module's
|
|
53
|
+
* exported `name` is a display label validated against this at load time,
|
|
54
|
+
* not a source of identity.
|
|
110
55
|
*/
|
|
111
|
-
export function resolveAddonSlug(addonDir
|
|
56
|
+
export function resolveAddonSlug(addonDir) {
|
|
112
57
|
const dirName = path.basename(addonDir);
|
|
113
58
|
const pkgPath = path.join(addonDir, 'package.json');
|
|
114
59
|
try {
|
|
@@ -118,12 +63,7 @@ export function resolveAddonSlug(addonDir, source) {
|
|
|
118
63
|
return slug.trim();
|
|
119
64
|
}
|
|
120
65
|
catch {
|
|
121
|
-
/* no/invalid package.json — fall back to the folder
|
|
66
|
+
/* no/invalid package.json — fall back to the folder name */
|
|
122
67
|
}
|
|
123
|
-
|
|
124
|
-
// `@scope/<slug>-addon` publishing convention, so strip the conventional
|
|
125
|
-
// trailing `-addon`. A plain directory (bundled/drop-in) addon's folder
|
|
126
|
-
// name IS its identity verbatim — an addon may legitimately be named
|
|
127
|
-
// `something-addon` on disk — so never strip there.
|
|
128
|
-
return source === 'npm' ? deriveAddonSlugFromPackageDirName(dirName) : dirName;
|
|
68
|
+
return dirName;
|
|
129
69
|
}
|
|
@@ -4,7 +4,7 @@ export declare const AUDIT_EVENT_NAME_PATTERN: RegExp;
|
|
|
4
4
|
export declare const AUDIT_EVENT: {
|
|
5
5
|
/** An add-on named in ngdpbase.addons.install installed into the data folder: package, version, sha512 and the repository its provenance names; recorded before it is written, which cannot proceed without it (#1830) */
|
|
6
6
|
readonly ADDON_INSTALL: "addon-install";
|
|
7
|
-
/**
|
|
7
|
+
/** The release an installed add-on runs changed: an administrator's Update (who, the add-on, from and to version), recorded before the release is written, which cannot proceed without it; or core falling back to the previous release because the new one did not load (#2100) */
|
|
8
8
|
readonly ADDON_UPDATE: "addon-update";
|
|
9
9
|
/** An add-on refused or set aside by a check: its sha512, provenance, tarball contents, package.json, the lock file, or a changed installed folder (#1830) */
|
|
10
10
|
readonly ADDON_VERIFY_FAILED: "addon-verify-failed";
|
|
@@ -11,7 +11,7 @@ export const AUDIT_EVENT_NAME_PATTERN = /^[a-z]+(-[a-z]+)+$/;
|
|
|
11
11
|
export const AUDIT_EVENT = {
|
|
12
12
|
/** An add-on named in ngdpbase.addons.install installed into the data folder: package, version, sha512 and the repository its provenance names; recorded before it is written, which cannot proceed without it (#1830) */
|
|
13
13
|
ADDON_INSTALL: 'addon-install',
|
|
14
|
-
/**
|
|
14
|
+
/** The release an installed add-on runs changed: an administrator's Update (who, the add-on, from and to version), recorded before the release is written, which cannot proceed without it; or core falling back to the previous release because the new one did not load (#2100) */
|
|
15
15
|
ADDON_UPDATE: 'addon-update',
|
|
16
16
|
/** An add-on refused or set aside by a check: its sha512, provenance, tarball contents, package.json, the lock file, or a changed installed folder (#1830) */
|
|
17
17
|
ADDON_VERIFY_FAILED: 'addon-verify-failed',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilleke/ngdpbase-types",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.37.0",
|
|
4
4
|
"description": "TypeScript types for ngdpbase add-ons, and a test kit of core's own values. A dev dependency, versioned with ngdpbase.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Jim Willeke",
|
|
@@ -41,15 +41,14 @@
|
|
|
41
41
|
"@types/multer": "^2.3.0",
|
|
42
42
|
"@types/node": "^24.19.2",
|
|
43
43
|
"better-sqlite3-multiple-ciphers": "^13.0.3",
|
|
44
|
-
"dotenv": "^18.0.
|
|
44
|
+
"dotenv": "^18.0.9",
|
|
45
45
|
"express": "^5.3.0",
|
|
46
46
|
"express-session": "^1.19.0",
|
|
47
47
|
"fs-extra": "^11.4.1",
|
|
48
48
|
"ipaddr.js": "^2.5.0",
|
|
49
|
-
"jose": "^6.2.
|
|
50
|
-
"linkedom": "^0.18.
|
|
49
|
+
"jose": "^6.2.13",
|
|
50
|
+
"linkedom": "^0.18.14",
|
|
51
51
|
"markdown-it": "^14.3.3",
|
|
52
|
-
"minimatch": "^10.2.6",
|
|
53
52
|
"minisearch": "7.2.0",
|
|
54
53
|
"multer": "^2.4.0",
|
|
55
54
|
"rrule-temporal": "2.2.8",
|