@wildo-ai/wildo-module-client 1.1.5 → 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/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/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-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 -47
- package/dist/esm/services/registry-companion-client.d.ts.map +1 -1
- package/dist/esm/services/registry-companion-client.js +9 -97
- 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__/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/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 -166
- package/src/services/registry-lockfile.service.ts +9 -3
- package/src/services/registry-server-client.ts +147 -57
- package/dist/esm/__tests__/lockfile-repair.service.test.d.ts +0 -0
- package/dist/esm/__tests__/lockfile-repair.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/lockfile-repair.service.test.js.map +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.d.ts +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.js.map +0 -0
- package/dist/esm/__tests__/module-packager.service.test.d.ts +0 -0
- package/dist/esm/__tests__/module-packager.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/module-packager.service.test.js.map +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.d.ts +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.d.ts.map +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.js.map +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.d.ts +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.d.ts.map +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.js.map +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.d.ts +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.js.map +0 -0
- package/dist/esm/__tests__/registry-server-client.test.d.ts +0 -0
- package/dist/esm/__tests__/registry-server-client.test.d.ts.map +0 -0
- package/dist/esm/__tests__/registry-server-client.test.js.map +0 -0
|
@@ -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,116 +1,25 @@
|
|
|
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+.
|
|
16
|
-
*
|
|
17
|
-
* The four mutations are GATED: the companion refuses any non-`GET` route that does not present the
|
|
18
|
-
* machine-local token, so they take `RegistryCompanionMutationOptions` and `ping` — a `GET` — does
|
|
19
|
-
* not. See that type for why the credential is required rather than optional.
|
|
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.
|
|
20
12
|
*/
|
|
21
13
|
|
|
22
|
-
import {
|
|
23
|
-
REGISTRY_COMPANION_ROUTES,
|
|
24
|
-
RegistryMalformedPayloadError,
|
|
25
|
-
RegistryRouteResultSchema,
|
|
26
|
-
RegistryUnexpectedStatusError,
|
|
27
|
-
type RegistryInstallRequest,
|
|
28
|
-
type RegistryPublishRequest,
|
|
29
|
-
type RegistryRemoveRequest,
|
|
30
|
-
type RegistryRouteResult,
|
|
31
|
-
type RegistryUpdateRequest,
|
|
32
|
-
} from '@wildo-ai/platform-config-lib';
|
|
33
14
|
import { fetchWithTimeout, joinUrl } from './_http-client';
|
|
34
|
-
import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';
|
|
35
15
|
|
|
36
16
|
const DIAGNOSTIC_LABEL = 'Companion registry';
|
|
37
17
|
|
|
38
|
-
/**
|
|
39
|
-
* The header the companion's P-29b token floor reads on a mutating route.
|
|
40
|
-
*
|
|
41
|
-
* A wire contract between two processes that share no package, so it is named at both ends rather
|
|
42
|
-
* than at neither. Its server twin is `COMPANION_TOKEN_HEADER` in the companion's
|
|
43
|
-
* `core/auth/companion-auth-floor.companion.service.ts`; the CLI's is in
|
|
44
|
-
* `wildo-cli/src/services/companion/companion-local-token.utils.ts`. Move one, move all three.
|
|
45
|
-
*/
|
|
46
|
-
// semantic-literal-boundary: private companion HTTP protocol, translated only at this client edge.
|
|
47
|
-
const COMPANION_TOKEN_HEADER = 'x-wildo-companion-token';
|
|
48
|
-
|
|
49
18
|
export interface RegistryCompanionClientOptions {
|
|
50
19
|
baseUrl: string;
|
|
51
20
|
timeoutMs?: number;
|
|
52
21
|
}
|
|
53
22
|
|
|
54
|
-
/**
|
|
55
|
-
* What a MUTATING companion registry call needs, over and above an address.
|
|
56
|
-
*
|
|
57
|
-
* `companionToken` is REQUIRED, and that is the whole point of this second type. Every non-`GET`
|
|
58
|
-
* route under `/api/companion/**` is refused without it, and the four methods below have no caller
|
|
59
|
-
* yet (the routes answer a not-implemented envelope until Phase 5) — so making it required now costs
|
|
60
|
-
* nothing and means the caller Phase 5 writes cannot be written without the credential. An optional
|
|
61
|
-
* field would have been a rule to remember; a required one is a compile error.
|
|
62
|
-
*
|
|
63
|
-
* The value is the content of `<appRoot>/.wildo-saas/state/companion.token` (mode 0600), which the
|
|
64
|
-
* companion mints on every boot. This package has no notion of an application root, so reading it is
|
|
65
|
-
* deliberately the caller's job: `wildo-cli` already owns a hardened no-follow reader
|
|
66
|
-
* (`readCompanionTokenNoFollow`), and a second copy of a permission-checked read is precisely where
|
|
67
|
-
* the checks quietly diverge.
|
|
68
|
-
*/
|
|
69
|
-
export interface RegistryCompanionMutationOptions extends RegistryCompanionClientOptions {
|
|
70
|
-
companionToken: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function postJson<R>(
|
|
74
|
-
options: RegistryCompanionMutationOptions,
|
|
75
|
-
routePath: string,
|
|
76
|
-
body: unknown,
|
|
77
|
-
): Promise<R> {
|
|
78
|
-
const url = joinUrl(options.baseUrl, routePath);
|
|
79
|
-
const res = await fetchWithTimeout(url, {
|
|
80
|
-
method: 'POST',
|
|
81
|
-
headers: { 'content-type': 'application/json', [COMPANION_TOKEN_HEADER]: options.companionToken },
|
|
82
|
-
body: JSON.stringify(body),
|
|
83
|
-
timeoutMs: options.timeoutMs,
|
|
84
|
-
diagnosticLabel: DIAGNOSTIC_LABEL,
|
|
85
|
-
});
|
|
86
|
-
if (!res.ok) {
|
|
87
|
-
// 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of
|
|
88
|
-
// bare `new Error(...)` so callers can `isRegistryError(err)` and
|
|
89
|
-
// discriminate by status code without re-parsing message text.
|
|
90
|
-
// The companion deliberately reuses the same error vocabulary
|
|
91
|
-
// as `RegistryServerClient` since the wire shape (status + URL +
|
|
92
|
-
// action) is identical even though the lane is different.
|
|
93
|
-
throw new RegistryUnexpectedStatusError(
|
|
94
|
-
options.baseUrl,
|
|
95
|
-
res.status,
|
|
96
|
-
res.statusText,
|
|
97
|
-
`companion ${routePath}`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
// 6th-pass M3: typed JSON-parse — a misconfigured proxy returning
|
|
101
|
-
// HTML at 2xx would otherwise throw a bare `SyntaxError` that
|
|
102
|
-
// bypasses the typed-error contract.
|
|
103
|
-
try {
|
|
104
|
-
return (await res.json()) as R;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
throw new RegistryMalformedPayloadError(
|
|
107
|
-
options.baseUrl,
|
|
108
|
-
`companion ${routePath}`,
|
|
109
|
-
`response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
23
|
export const RegistryCompanionClient = {
|
|
115
24
|
/**
|
|
116
25
|
* Liveness probe against `/health` (the same endpoint the
|
|
@@ -129,69 +38,4 @@ export const RegistryCompanionClient = {
|
|
|
129
38
|
return false;
|
|
130
39
|
}
|
|
131
40
|
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Ask the companion to install a module: download bytes, copy
|
|
135
|
-
* source, mutate `wildo.saas.config.ts > moduleRegistries` +
|
|
136
|
-
* the three `modules-registry.*.ts` files, refresh
|
|
137
|
-
* `sections.modules`, run provider sync.
|
|
138
|
-
*/
|
|
139
|
-
async install(
|
|
140
|
-
options: RegistryCompanionMutationOptions,
|
|
141
|
-
request: RegistryInstallRequest,
|
|
142
|
-
): Promise<RegistryRouteResult> {
|
|
143
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.install, request);
|
|
144
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
async update(
|
|
148
|
-
options: RegistryCompanionMutationOptions,
|
|
149
|
-
request: RegistryUpdateRequest,
|
|
150
|
-
): Promise<RegistryRouteResult> {
|
|
151
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.update, request);
|
|
152
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
async remove(
|
|
156
|
-
options: RegistryCompanionMutationOptions,
|
|
157
|
-
request: RegistryRemoveRequest,
|
|
158
|
-
): Promise<RegistryRouteResult> {
|
|
159
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.remove, request);
|
|
160
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
async publish(
|
|
164
|
-
options: RegistryCompanionMutationOptions,
|
|
165
|
-
request: RegistryPublishRequest,
|
|
166
|
-
): Promise<RegistryRouteResult> {
|
|
167
|
-
const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.publish, request);
|
|
168
|
-
return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);
|
|
169
|
-
},
|
|
170
|
-
} as const;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that
|
|
174
|
-
* converts a Zod parse failure into the typed
|
|
175
|
-
* `RegistryMalformedPayloadError` instead of letting a bare `ZodError`
|
|
176
|
-
* bubble up. Keeps the typed-handler contract aligned with
|
|
177
|
-
* `RegistryServerClient`.
|
|
178
|
-
*/
|
|
179
|
-
function parseCompanionResult(
|
|
180
|
-
baseUrl: string,
|
|
181
|
-
routePath: string,
|
|
182
|
-
raw: unknown,
|
|
183
|
-
): RegistryRouteResult {
|
|
184
|
-
const parsed = RegistryRouteResultSchema.safeParse(raw);
|
|
185
|
-
if (!parsed.success) {
|
|
186
|
-
// 7th-pass C-L2: format the issues array for operator
|
|
187
|
-
// readability instead of using Zod 4's JSON-stringified
|
|
188
|
-
// `error.message`.
|
|
189
|
-
const detail = formatZodIssuesAsSingleLine(parsed.error);
|
|
190
|
-
throw new RegistryMalformedPayloadError(
|
|
191
|
-
baseUrl,
|
|
192
|
-
`companion ${routePath}`,
|
|
193
|
-
detail,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return parsed.data;
|
|
197
|
-
}
|
|
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 },
|