@wildo-ai/wildo-module-client 1.1.4 → 1.1.6
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/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/services/_http-client.d.ts +32 -3
- package/dist/esm/services/_http-client.d.ts.map +1 -1
- package/dist/esm/services/_http-client.js +100 -6
- package/dist/esm/services/_http-client.js.map +1 -1
- package/dist/esm/services/_zod-issue-formatting.d.ts.map +1 -1
- package/dist/esm/services/index.d.ts.map +1 -1
- package/dist/esm/services/lockfile-repair.service.d.ts +8 -23
- package/dist/esm/services/lockfile-repair.service.d.ts.map +1 -1
- package/dist/esm/services/lockfile-repair.service.js +10 -28
- package/dist/esm/services/lockfile-repair.service.js.map +1 -1
- package/dist/esm/services/module-adopter.service.d.ts +1 -1
- package/dist/esm/services/module-adopter.service.d.ts.map +1 -1
- package/dist/esm/services/module-adopter.service.js +7 -8
- package/dist/esm/services/module-adopter.service.js.map +1 -1
- package/dist/esm/services/module-packager.service.d.ts.map +1 -1
- package/dist/esm/services/module-packager.service.js +16 -19
- package/dist/esm/services/module-packager.service.js.map +1 -1
- package/dist/esm/services/provider-materialization-hook.d.ts.map +1 -1
- package/dist/esm/services/provider-sync-runner.d.ts +2 -3
- package/dist/esm/services/provider-sync-runner.d.ts.map +1 -1
- package/dist/esm/services/provider-sync-runner.js +14 -2
- package/dist/esm/services/provider-sync-runner.js.map +1 -1
- package/dist/esm/services/registry-companion-client.d.ts +10 -25
- package/dist/esm/services/registry-companion-client.d.ts.map +1 -1
- package/dist/esm/services/registry-companion-client.js +9 -83
- package/dist/esm/services/registry-companion-client.js.map +1 -1
- package/dist/esm/services/registry-lockfile.service.d.ts +7 -3
- package/dist/esm/services/registry-lockfile.service.d.ts.map +1 -1
- package/dist/esm/services/registry-lockfile.service.js +8 -3
- package/dist/esm/services/registry-lockfile.service.js.map +1 -1
- package/dist/esm/services/registry-server-client.d.ts +35 -9
- package/dist/esm/services/registry-server-client.d.ts.map +1 -1
- package/dist/esm/services/registry-server-client.js +115 -58
- package/dist/esm/services/registry-server-client.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/lockfile-repair.service.test.ts +2 -2
- package/src/__tests__/module-adopter.service.test.ts +6 -4
- package/src/__tests__/module-packager.service.test.ts +36 -34
- package/src/__tests__/provider-sync-runner.test.ts +12 -0
- package/src/__tests__/registry-lockfile.service.test.ts +3 -3
- package/src/__tests__/registry-server-client.test.ts +129 -0
- package/src/__tests__/registry-transfer-timeouts.test.ts +94 -0
- package/src/services/_http-client.ts +100 -6
- package/src/services/lockfile-repair.service.ts +9 -34
- package/src/services/module-adopter.service.ts +6 -9
- package/src/services/module-packager.service.ts +19 -16
- package/src/services/provider-sync-runner.ts +16 -5
- package/src/services/registry-companion-client.ts +10 -132
- package/src/services/registry-lockfile.service.ts +9 -3
- package/src/services/registry-server-client.ts +147 -57
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `LockfileRepairService` —
|
|
2
|
+
* `LockfileRepairService` — read-only lockfile diagnostics.
|
|
3
3
|
*
|
|
4
|
-
* Backs `wildo registry repair-lockfile
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `RegistryModulesLockSectionSchema`;
|
|
11
|
-
* - report which `installed[*].files` are missing on disk
|
|
12
|
-
* (drift between the lockfile and the source tree);
|
|
13
|
-
* - report which `installed[*].artifactDigest` strings are not
|
|
14
|
-
* `Sha256DigestSchema`-shaped (residue from pre-tightening
|
|
15
|
-
* publishes).
|
|
16
|
-
*
|
|
17
|
-
* Mutating repair is a Phase 7 deliverable — surfacing the gaps is
|
|
18
|
-
* what unblocks the operator now.
|
|
4
|
+
* Backs `wildo registry repair-lockfile`. Strict envelope and module-section
|
|
5
|
+
* validation runs before file inspection. Malformed records, including invalid
|
|
6
|
+
* artifact digests, require restoration from a trusted record; they are never
|
|
7
|
+
* admitted into the automatic repair path. Valid records can report missing
|
|
8
|
+
* source files, which the CLI can re-fetch at their recorded exact version.
|
|
9
|
+
* This inspector does not mutate files.
|
|
19
10
|
*
|
|
20
11
|
* `i18n` validation is intentionally NOT performed here (KD-15 +
|
|
21
12
|
* Review-pass-2 / MEDIUM-5): repair is the wrong place to
|
|
@@ -30,7 +21,6 @@ import { z } from 'zod';
|
|
|
30
21
|
import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';
|
|
31
22
|
import {
|
|
32
23
|
RegistryModulesLockSectionSchema,
|
|
33
|
-
Sha256DigestSchema,
|
|
34
24
|
getWildoSaasLockPath,
|
|
35
25
|
type RegistryModulesLockEntry,
|
|
36
26
|
type RegistryModulesLockSection,
|
|
@@ -66,12 +56,6 @@ export type LockfileRepairFinding =
|
|
|
66
56
|
kind: 'module-file-missing';
|
|
67
57
|
moduleId: string;
|
|
68
58
|
filePath: string;
|
|
69
|
-
}
|
|
70
|
-
| {
|
|
71
|
-
severity: 'warn';
|
|
72
|
-
kind: 'module-digest-not-sha256';
|
|
73
|
-
moduleId: string;
|
|
74
|
-
observedDigest: string;
|
|
75
59
|
};
|
|
76
60
|
|
|
77
61
|
export interface LockfileRepairReport {
|
|
@@ -163,7 +147,7 @@ export const LockfileRepairService = {
|
|
|
163
147
|
findings.push({
|
|
164
148
|
severity: 'error',
|
|
165
149
|
kind: 'modules-section-malformed',
|
|
166
|
-
message: '`sections.modules` failed schema validation.',
|
|
150
|
+
message: '`sections.modules` failed schema validation. Restore the affected record from a trusted lockfile revision; automatic repair does not infer missing or invalid metadata.',
|
|
167
151
|
detail: formatZodIssuesAsSingleLine(parsed.error),
|
|
168
152
|
});
|
|
169
153
|
return { lockfilePath, ok: false, findings };
|
|
@@ -179,8 +163,7 @@ export const LockfileRepairService = {
|
|
|
179
163
|
},
|
|
180
164
|
|
|
181
165
|
/**
|
|
182
|
-
*
|
|
183
|
-
* repair flows can reuse the same predicates.
|
|
166
|
+
* Missing-file diagnostics for an entry already validated by the strict section schema.
|
|
184
167
|
*/
|
|
185
168
|
async collectEntryFindings(
|
|
186
169
|
saasRoot: string,
|
|
@@ -188,14 +171,6 @@ export const LockfileRepairService = {
|
|
|
188
171
|
entry: RegistryModulesLockEntry,
|
|
189
172
|
findings: LockfileRepairFinding[],
|
|
190
173
|
): Promise<void> {
|
|
191
|
-
if (!Sha256DigestSchema.safeParse(entry.artifactDigest).success) {
|
|
192
|
-
findings.push({
|
|
193
|
-
severity: 'warn',
|
|
194
|
-
kind: 'module-digest-not-sha256',
|
|
195
|
-
moduleId,
|
|
196
|
-
observedDigest: entry.artifactDigest,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
174
|
for (const filePath of entry.files) {
|
|
200
175
|
const abs = path.resolve(saasRoot, filePath);
|
|
201
176
|
if (!(await fileExists(abs))) {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { promises as fs } from 'node:fs';
|
|
24
24
|
import * as path from 'node:path';
|
|
25
|
-
import type
|
|
25
|
+
import { isModuleArtifactPathAllowed, MODULE_BOUNDARY_LAYERS, type ModuleManifest } from '@wildo-ai/platform-config-lib';
|
|
26
26
|
import { ModulePackagerService, type StagingPaths } from './module-packager.service';
|
|
27
27
|
|
|
28
28
|
export interface AdoptionPlan {
|
|
@@ -65,6 +65,9 @@ async function walkBoundary(
|
|
|
65
65
|
const entries = await fs.readdir(path.resolve(saasRoot, cur), { withFileTypes: true });
|
|
66
66
|
for (const entry of entries) {
|
|
67
67
|
const rel = path.join(cur, entry.name);
|
|
68
|
+
// Skip what a module never ships, exactly as `wildo registry publish` does — otherwise the
|
|
69
|
+
// packager (which refuses those paths) throws on a module's own `.env` or `node_modules`.
|
|
70
|
+
if (!isModuleArtifactPathAllowed(rel.split(path.sep).join('/'))) continue;
|
|
68
71
|
if (entry.isDirectory()) {
|
|
69
72
|
queue.push(rel);
|
|
70
73
|
} else if (entry.isFile()) {
|
|
@@ -88,14 +91,8 @@ export const ModuleAdopterService = {
|
|
|
88
91
|
if (!boundaries) {
|
|
89
92
|
return { files: [], emptyBoundaries: [] };
|
|
90
93
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'shared',
|
|
94
|
-
'frontend',
|
|
95
|
-
'backend',
|
|
96
|
-
'minions',
|
|
97
|
-
'workers',
|
|
98
|
-
];
|
|
94
|
+
// Every layer the registry knows, not a hand-kept copy: a copy here missed `providers` (#2014).
|
|
95
|
+
const layers = MODULE_BOUNDARY_LAYERS;
|
|
99
96
|
const aggregate = new Set<string>();
|
|
100
97
|
const empty: string[] = [];
|
|
101
98
|
for (const layer of layers) {
|
|
@@ -31,14 +31,17 @@ import { createHash } from 'node:crypto';
|
|
|
31
31
|
import { promises as fs } from 'node:fs';
|
|
32
32
|
import * as path from 'node:path';
|
|
33
33
|
import {
|
|
34
|
+
flattenModuleBoundaries,
|
|
35
|
+
isModuleArtifactPathAllowed,
|
|
36
|
+
isModuleId,
|
|
34
37
|
ModuleManifestSchema,
|
|
35
38
|
RegistryBoundaryViolationError,
|
|
36
39
|
type ModuleManifest,
|
|
37
40
|
type ModuleManifestBoundaries,
|
|
41
|
+
MODULE_MANIFEST_FILE_NAME as MANIFEST_FILE_NAME,
|
|
38
42
|
} from '@wildo-ai/platform-config-lib';
|
|
39
43
|
|
|
40
44
|
export const MODULES_ARTIFACTS_RELATIVE_DIR = '.wildo-saas/generated/modules-artifacts';
|
|
41
|
-
const MANIFEST_FILE_NAME = 'wildo.module.json';
|
|
42
45
|
|
|
43
46
|
export interface StagingPaths {
|
|
44
47
|
/** Absolute path to the per-module staging root. */
|
|
@@ -52,7 +55,9 @@ export interface StagingPaths {
|
|
|
52
55
|
* fs side effects.
|
|
53
56
|
*/
|
|
54
57
|
export function getModuleStagingPaths(saasRoot: string, moduleId: string): StagingPaths {
|
|
55
|
-
|
|
58
|
+
// The full `<scope>/<name>` shape, not merely "contains a `/`": `stageFresh` runs a recursive delete
|
|
59
|
+
// on the path built here, and `a/../../../..` contained a slash.
|
|
60
|
+
if (!isModuleId(moduleId)) {
|
|
56
61
|
// C-L3 (4th-pass review): `TypeError` is Node's standard for
|
|
57
62
|
// argument-shape violations. The typed `RegistryError` subclasses
|
|
58
63
|
// model registry-runtime conditions (auth, integrity, version
|
|
@@ -62,10 +67,14 @@ export function getModuleStagingPaths(saasRoot: string, moduleId: string): Stagi
|
|
|
62
67
|
// and stops the typed-error vocabulary from absorbing pure
|
|
63
68
|
// argument-validation concerns.
|
|
64
69
|
throw new TypeError(
|
|
65
|
-
`Module id "${moduleId}" must be in <
|
|
70
|
+
`Module id "${moduleId}" must be in <scope>/<name> form (e.g. "acme/billing"), each half lowercase letters, digits, ".", "_" or "-".`,
|
|
66
71
|
);
|
|
67
72
|
}
|
|
68
|
-
const
|
|
73
|
+
const artifactsRoot = path.resolve(saasRoot, MODULES_ARTIFACTS_RELATIVE_DIR);
|
|
74
|
+
const stagingRoot = path.resolve(artifactsRoot, moduleId);
|
|
75
|
+
if (!isPathInside(stagingRoot, artifactsRoot) || stagingRoot === artifactsRoot) {
|
|
76
|
+
throw new TypeError(`Module id "${moduleId}" resolves outside ${MODULES_ARTIFACTS_RELATIVE_DIR}.`);
|
|
77
|
+
}
|
|
69
78
|
return {
|
|
70
79
|
stagingRoot,
|
|
71
80
|
manifestPath: path.join(stagingRoot, MANIFEST_FILE_NAME),
|
|
@@ -114,20 +123,14 @@ export function assertWithinBoundaries(
|
|
|
114
123
|
boundaries: ModuleManifestBoundaries | undefined,
|
|
115
124
|
): string {
|
|
116
125
|
const absolute = path.resolve(saasRoot, appRelativePath);
|
|
117
|
-
// Path must stay inside saasRoot (no `..` escapes)
|
|
118
|
-
|
|
126
|
+
// Path must stay inside saasRoot (no `..` escapes), and be a path a module artifact may carry at
|
|
127
|
+
// all — no dot-segment (`.env`, `.git`), no `node_modules` — whatever the boundaries say.
|
|
128
|
+
if (!isPathInside(absolute, path.resolve(saasRoot)) || !isModuleArtifactPathAllowed(appRelativePath.split(path.sep).join('/'))) {
|
|
119
129
|
throw new RegistryBoundaryViolationError(moduleId, appRelativePath);
|
|
120
130
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
...boundaries.shared,
|
|
125
|
-
...boundaries.frontend,
|
|
126
|
-
...boundaries.backend,
|
|
127
|
-
...boundaries.minions,
|
|
128
|
-
...boundaries.workers,
|
|
129
|
-
];
|
|
130
|
-
if (allowed.length === 0) return absolute;
|
|
131
|
+
// No declared boundary means the module may write NOWHERE. This used to return early and admit
|
|
132
|
+
// every path in the application — the fail-open reading of an absent declaration.
|
|
133
|
+
const allowed: readonly string[] = flattenModuleBoundaries(boundaries);
|
|
131
134
|
// The candidate must be inside at least one declared boundary.
|
|
132
135
|
const isInside = allowed.some((rel) => isPathInside(absolute, path.resolve(saasRoot, rel)));
|
|
133
136
|
if (!isInside) {
|
|
@@ -41,7 +41,16 @@ import {
|
|
|
41
41
|
} from '@wildo-ai/platform-config-lib';
|
|
42
42
|
import type { RegistryClientLogger } from './types';
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* How long the post-mutation `wildo config sync` may run before it is treated as wedged and killed.
|
|
46
|
+
*
|
|
47
|
+
* Five minutes, not two: the budget exists to stop a WEDGED sync, and a wall clock cannot tell wedged
|
|
48
|
+
* from slow. Measured 2026-09-27 on a scaffolded application (#1765): the same sync took 14.6 s on a
|
|
49
|
+
* quiet machine, 91 s while the framework's watchers were recompiling, and exceeded the old 120 s during
|
|
50
|
+
* an install under that load — so a healthy sync was killed part-way and the install reported a provider
|
|
51
|
+
* sync failure it did not have. The kill stays: a genuinely stuck sync still ends, five minutes on.
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
45
54
|
|
|
46
55
|
export interface ProviderSyncRunnerInput {
|
|
47
56
|
/** Absolute path to the SaaS application root. */
|
|
@@ -57,9 +66,8 @@ export interface ProviderSyncRunnerInput {
|
|
|
57
66
|
*/
|
|
58
67
|
wildoBinPath?: string;
|
|
59
68
|
/**
|
|
60
|
-
* Maximum wall-time before SIGTERM. Default
|
|
61
|
-
*
|
|
62
|
-
* but every additional second is operator-visible.
|
|
69
|
+
* Maximum wall-time before SIGTERM. Default {@link DEFAULT_TIMEOUT_MS} (five minutes): it only ever
|
|
70
|
+
* elapses on a wedged sync, and a healthy one under load has been measured past two minutes.
|
|
63
71
|
*/
|
|
64
72
|
timeoutMs?: number;
|
|
65
73
|
/**
|
|
@@ -178,7 +186,10 @@ function runWildoConfigSync(
|
|
|
178
186
|
const startedAt = Date.now();
|
|
179
187
|
const child = spawn(binPath, ['config', 'sync', '--domain', 'config'], {
|
|
180
188
|
cwd,
|
|
181
|
-
|
|
189
|
+
// stdout is IGNORED, not piped: nothing here reads it, and an unread pipe fills at the OS buffer
|
|
190
|
+
// (~64 KB) and blocks the child mid-write — a finished sync then looked like a wedged one and
|
|
191
|
+
// was killed at the timeout. stderr stays piped because the diagnostic reads it (bounded below).
|
|
192
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
182
193
|
});
|
|
183
194
|
let stderr = '';
|
|
184
195
|
let signaled = false;
|
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `RegistryCompanionClient` —
|
|
3
|
-
*
|
|
2
|
+
* `RegistryCompanionClient` — liveness probe for the local dev companion, used by
|
|
3
|
+
* `wildo registry ping` beside the registry-server probe.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* companion controller (under
|
|
13
|
-
* `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)
|
|
14
|
-
* implements them with the actual ts-morph + filesystem work in
|
|
15
|
-
* Phase 5+.
|
|
5
|
+
* That is all it is. It used to carry four mutation methods (install / update / remove / publish)
|
|
6
|
+
* against `/api/companion/registry/*` routes that only ever answered a "Phase 5/6 deliverable"
|
|
7
|
+
* not-implemented envelope and had no caller. Installation shipped CLI-direct instead
|
|
8
|
+
* (`wildo registry add` / `update` / `remove` / `publish` do the work themselves), so the routes,
|
|
9
|
+
* these methods and their wire schemas were deleted on 2026-09-25 rather than left describing a
|
|
10
|
+
* design that was never built. A future companion-owned mutation lane starts from the CLI commands,
|
|
11
|
+
* not from this file.
|
|
16
12
|
*/
|
|
17
13
|
|
|
18
|
-
import {
|
|
19
|
-
REGISTRY_COMPANION_ROUTES,
|
|
20
|
-
RegistryMalformedPayloadError,
|
|
21
|
-
RegistryRouteResultSchema,
|
|
22
|
-
RegistryUnexpectedStatusError,
|
|
23
|
-
type RegistryInstallRequest,
|
|
24
|
-
type RegistryPublishRequest,
|
|
25
|
-
type RegistryRemoveRequest,
|
|
26
|
-
type RegistryRouteResult,
|
|
27
|
-
type RegistryUpdateRequest,
|
|
28
|
-
} from '@wildo-ai/platform-config-lib';
|
|
29
14
|
import { fetchWithTimeout, joinUrl } from './_http-client';
|
|
30
|
-
import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';
|
|
31
15
|
|
|
32
16
|
const DIAGNOSTIC_LABEL = 'Companion registry';
|
|
33
17
|
|
|
@@ -36,47 +20,6 @@ export interface RegistryCompanionClientOptions {
|
|
|
36
20
|
timeoutMs?: number;
|
|
37
21
|
}
|
|
38
22
|
|
|
39
|
-
async function postJson<R>(
|
|
40
|
-
options: RegistryCompanionClientOptions,
|
|
41
|
-
routePath: string,
|
|
42
|
-
body: unknown,
|
|
43
|
-
): Promise<R> {
|
|
44
|
-
const url = joinUrl(options.baseUrl, routePath);
|
|
45
|
-
const res = await fetchWithTimeout(url, {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
headers: { 'content-type': 'application/json' },
|
|
48
|
-
body: JSON.stringify(body),
|
|
49
|
-
timeoutMs: options.timeoutMs,
|
|
50
|
-
diagnosticLabel: DIAGNOSTIC_LABEL,
|
|
51
|
-
});
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
// 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of
|
|
54
|
-
// bare `new Error(...)` so callers can `isRegistryError(err)` and
|
|
55
|
-
// discriminate by status code without re-parsing message text.
|
|
56
|
-
// The companion deliberately reuses the same error vocabulary
|
|
57
|
-
// as `RegistryServerClient` since the wire shape (status + URL +
|
|
58
|
-
// action) is identical even though the lane is different.
|
|
59
|
-
throw new RegistryUnexpectedStatusError(
|
|
60
|
-
options.baseUrl,
|
|
61
|
-
res.status,
|
|
62
|
-
res.statusText,
|
|
63
|
-
`companion ${routePath}`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
// 6th-pass M3: typed JSON-parse — a misconfigured proxy returning
|
|
67
|
-
// HTML at 2xx would otherwise throw a bare `SyntaxError` that
|
|
68
|
-
// bypasses the typed-error contract.
|
|
69
|
-
try {
|
|
70
|
-
return (await res.json()) as R;
|
|
71
|
-
} catch (err) {
|
|
72
|
-
throw new RegistryMalformedPayloadError(
|
|
73
|
-
options.baseUrl,
|
|
74
|
-
`companion ${routePath}`,
|
|
75
|
-
`response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
23
|
export const RegistryCompanionClient = {
|
|
81
24
|
/**
|
|
82
25
|
* Liveness probe against `/health` (the same endpoint the
|
|
@@ -95,69 +38,4 @@ export const RegistryCompanionClient = {
|
|
|
95
38
|
return false;
|
|
96
39
|
}
|
|
97
40
|
},
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Ask the companion to install a module: download bytes, copy
|
|
101
|
-
* source, mutate `wildo.saas.config.ts > moduleRegistries` +
|
|
102
|
-
* the three `modules-registry.*.ts` files, refresh
|
|
103
|
-
* `sections.modules`, run provider sync.
|
|
104
|
-
*/
|
|
105
|
-
async install(
|
|
106
|
-
options: RegistryCompanionClientOptions,
|
|
107
|
-
request: RegistryInstallRequest,
|
|
108
|
-
): Promise<RegistryRouteResult> {
|
|
109
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.install, request);
|
|
110
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
async update(
|
|
114
|
-
options: RegistryCompanionClientOptions,
|
|
115
|
-
request: RegistryUpdateRequest,
|
|
116
|
-
): Promise<RegistryRouteResult> {
|
|
117
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.update, request);
|
|
118
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
async remove(
|
|
122
|
-
options: RegistryCompanionClientOptions,
|
|
123
|
-
request: RegistryRemoveRequest,
|
|
124
|
-
): Promise<RegistryRouteResult> {
|
|
125
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.remove, request);
|
|
126
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
async publish(
|
|
130
|
-
options: RegistryCompanionClientOptions,
|
|
131
|
-
request: RegistryPublishRequest,
|
|
132
|
-
): Promise<RegistryRouteResult> {
|
|
133
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.publish, request);
|
|
134
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);
|
|
135
|
-
},
|
|
136
|
-
} as const;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that
|
|
140
|
-
* converts a Zod parse failure into the typed
|
|
141
|
-
* `RegistryMalformedPayloadError` instead of letting a bare `ZodError`
|
|
142
|
-
* bubble up. Keeps the typed-handler contract aligned with
|
|
143
|
-
* `RegistryServerClient`.
|
|
144
|
-
*/
|
|
145
|
-
function parseCompanionResult(
|
|
146
|
-
baseUrl: string,
|
|
147
|
-
routePath: string,
|
|
148
|
-
raw: unknown,
|
|
149
|
-
): RegistryRouteResult {
|
|
150
|
-
const parsed = RegistryRouteResultSchema.safeParse(raw);
|
|
151
|
-
if (!parsed.success) {
|
|
152
|
-
// 7th-pass C-L2: format the issues array for operator
|
|
153
|
-
// readability instead of using Zod 4's JSON-stringified
|
|
154
|
-
// `error.message`.
|
|
155
|
-
const detail = formatZodIssuesAsSingleLine(parsed.error);
|
|
156
|
-
throw new RegistryMalformedPayloadError(
|
|
157
|
-
baseUrl,
|
|
158
|
-
`companion ${routePath}`,
|
|
159
|
-
detail,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
return parsed.data;
|
|
163
|
-
}
|
|
41
|
+
};
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
withLockfileSectionMutate,
|
|
26
26
|
readWildoSaasLock,
|
|
27
27
|
type RegistryModulesLockSection,
|
|
28
|
-
|
|
28
|
+
RegistryModulesLockEntrySchema,
|
|
29
|
+
type RegistryModulesLockEntryInput,
|
|
29
30
|
} from '@wildo-ai/platform-config-lib';
|
|
30
31
|
// L6 (third-pass review): the package's barrel-discipline rule says
|
|
31
32
|
// "Never re-export (except in the root `index.ts` of each package)."
|
|
@@ -81,8 +82,13 @@ export const RegistryLockfileService = {
|
|
|
81
82
|
);
|
|
82
83
|
},
|
|
83
84
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Insert or replace one module's entry. The entry is parsed here, so its defaults (`distribution`,
|
|
87
|
+
* `files`) apply in one place and a malformed entry is refused naming the module, before the lockfile
|
|
88
|
+
* is touched (`mutate` then re-validates the whole section).
|
|
89
|
+
*/
|
|
90
|
+
async upsertEntry(saasRoot: string, input: RegistryModulesLockEntryInput): Promise<void> {
|
|
91
|
+
const entry = RegistryModulesLockEntrySchema.parse(input);
|
|
86
92
|
await this.mutate(saasRoot, (current) => ({
|
|
87
93
|
...current,
|
|
88
94
|
installed: { ...current.installed, [entry.moduleId]: entry },
|