@audiofool/moondog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -0
- package/assets/brand/moondog-lunar-record/moondog-logo-1x1.png +0 -0
- package/contracts/v1/capability-policy.schema.json +572 -0
- package/contracts/v1/definitions.schema.json +365 -0
- package/contracts/v1/library-track-observation.schema.json +138 -0
- package/contracts/v1/listening-event.schema.json +111 -0
- package/contracts/v1/profile-evidence.schema.json +237 -0
- package/contracts/v1/taste-event.schema.json +258 -0
- package/contracts/v1/tool-invocation.schema.json +318 -0
- package/contracts/v1/tool-receipt.schema.json +1066 -0
- package/contracts/v1/track-ref.schema.json +275 -0
- package/contracts/v2/profile-evidence.schema.json +284 -0
- package/package.json +105 -0
- package/scripts/contract-lib.mjs +361 -0
- package/scripts/contract-semantics.mjs +2630 -0
- package/scripts/demo-studio.mjs +58 -0
- package/scripts/moondog +52 -0
- package/scripts/moondog.mjs +954 -0
- package/src/core/apple-data-migration.mjs +50 -0
- package/src/core/apple-library-source-status.mjs +220 -0
- package/src/core/apple-projection-domain-services.mjs +2356 -0
- package/src/core/capability-catalog.mjs +577 -0
- package/src/core/listening-profile-domain-services.mjs +1010 -0
- package/src/core/lyric-library.mjs +121 -0
- package/src/core/lyric-profile.mjs +50 -0
- package/src/core/lyric-service.mjs +41 -0
- package/src/core/moondog-application.mjs +1827 -0
- package/src/core/state-directory.mjs +19 -0
- package/src/core/synthetic-domain-services.mjs +1485 -0
- package/src/core/uuid-v5.mjs +42 -0
- package/src/demo/fictional-spotify-history.mjs +400 -0
- package/src/demo/moondog-demo.mjs +355 -0
- package/src/demo/moondog-interactive-tasteprint-demo.mjs +293 -0
- package/src/demo/moondog-tasteprint-demo.mjs +631 -0
- package/src/importers/apple-music-library/import-batch.mjs +290 -0
- package/src/importers/apple-music-library/index.mjs +76 -0
- package/src/importers/apple-music-library/normalize.mjs +1126 -0
- package/src/importers/apple-music-library/parse-plist.mjs +357 -0
- package/src/importers/apple-music-library/projection-records.mjs +557 -0
- package/src/importers/apple-music-library/read-batch.mjs +276 -0
- package/src/importers/apple-music-library/sqlite-projection.mjs +1307 -0
- package/src/importers/apple-music-library/stable-ids.mjs +19 -0
- package/src/importers/apple-music-library/track-snapshot.schema.json +136 -0
- package/src/integrations/apple-music/catalog.mjs +770 -0
- package/src/integrations/cross-catalog-artist-identity.mjs +222 -0
- package/src/integrations/listenbrainz/history-file.mjs +560 -0
- package/src/integrations/lyrics/lrclib.mjs +55 -0
- package/src/integrations/open-music-similarity/artist-radio.mjs +806 -0
- package/src/integrations/public-playlists.mjs +104 -0
- package/src/integrations/spotify/account-data-history.mjs +545 -0
- package/src/integrations/spotify/account-data-profile.mjs +918 -0
- package/src/integrations/spotify/authentication.mjs +791 -0
- package/src/integrations/spotify/catalog-resolver.mjs +657 -0
- package/src/integrations/spotify/connection.mjs +125 -0
- package/src/integrations/spotify/credential-store.mjs +26 -0
- package/src/integrations/spotify/extended-streaming-history.mjs +525 -0
- package/src/integrations/spotify/history-archive.mjs +59 -0
- package/src/integrations/spotify/index.mjs +80 -0
- package/src/integrations/spotify/recent-activity.mjs +198 -0
- package/src/integrations/spotify/resolution-cache.mjs +260 -0
- package/src/integrations/spotify/service.mjs +713 -0
- package/src/integrations/spotify/settings.mjs +89 -0
- package/src/integrations/spotify/web-api-client.mjs +761 -0
- package/src/integrations/web/codex-web.mjs +263 -0
- package/src/integrations/youtube-music/takeout.mjs +143 -0
- package/src/memory/local-memory-store.mjs +1705 -0
- package/src/memory/reflection-worker.mjs +87 -0
- package/src/profile/apple-library-import.mjs +28 -0
- package/src/profile/history-import.mjs +163 -0
- package/src/profile/listener-corrections.mjs +217 -0
- package/src/profile/listening-history-store.mjs +2055 -0
- package/src/profile/listening-profile-projection.mjs +2520 -0
- package/src/profile/local-music-data.mjs +812 -0
- package/src/profile/music-import-bundle.mjs +91 -0
- package/src/profile/music-providers.mjs +25 -0
- package/src/profile/spotify-archive-catalog-hints.mjs +210 -0
- package/src/profile/spotify-archive-taste.mjs +482 -0
- package/src/runtime/pi/agent-runtime.mjs +6444 -0
- package/src/runtime/pi/authentication.mjs +104 -0
- package/src/runtime/pi/configured-runtime.mjs +98 -0
- package/src/runtime/pi/memory-agent-runtime.mjs +337 -0
- package/src/runtime/pi/model-catalog.mjs +62 -0
- package/src/runtime/pi/model-transport.mjs +117 -0
- package/src/runtime/pi/persistent-credential-store.mjs +670 -0
- package/src/runtime/pi/provider-registry.mjs +74 -0
- package/src/runtime/pi/runtime-settings.mjs +81 -0
- package/src/surfaces/cli/auth-command.mjs +355 -0
- package/src/surfaces/cli/brand-components.mjs +390 -0
- package/src/surfaces/cli/brand-theme.mjs +231 -0
- package/src/surfaces/cli/catalog-command.mjs +509 -0
- package/src/surfaces/cli/command-completions.mjs +113 -0
- package/src/surfaces/cli/data-command.mjs +214 -0
- package/src/surfaces/cli/format-output.mjs +750 -0
- package/src/surfaces/cli/history-import-view.mjs +445 -0
- package/src/surfaces/cli/import-guides.mjs +9 -0
- package/src/surfaces/cli/listenbrainz-command.mjs +129 -0
- package/src/surfaces/cli/listening-menu.mjs +171 -0
- package/src/surfaces/cli/profile-command.mjs +290 -0
- package/src/surfaces/cli/spotify-command.mjs +849 -0
- package/src/surfaces/cli/taste-profile-model.mjs +306 -0
- package/src/surfaces/cli/taste-profile-view.mjs +272 -0
- package/src/surfaces/cli/terminal-art.mjs +295 -0
- package/src/surfaces/cli/tui.mjs +1714 -0
- package/src/surfaces/cli/web-command.mjs +52 -0
- package/src/surfaces/html/tasteprint-artifact.mjs +96 -0
- package/src/surfaces/html/tasteprint.mjs +3575 -0
- package/src/surfaces/web/showcase.mjs +272 -0
- package/src/surfaces/web/studio-page.mjs +4148 -0
- package/src/surfaces/web/studio.mjs +2288 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import Ajv2020 from "ajv/dist/2020.js";
|
|
6
|
+
import addFormats from "ajv-formats";
|
|
7
|
+
|
|
8
|
+
export const repositoryRoot = path.resolve(
|
|
9
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
10
|
+
"..",
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export const schemaDirectory = path.join(repositoryRoot, "contracts", "v1");
|
|
14
|
+
export const fixtureDirectory = path.join(
|
|
15
|
+
repositoryRoot,
|
|
16
|
+
"contracts",
|
|
17
|
+
"examples",
|
|
18
|
+
"v1",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const schemaVersions = [1, 2];
|
|
22
|
+
|
|
23
|
+
export const contractSchemaIdsByVersion = new Map([
|
|
24
|
+
["track-ref:1", "urn:moondog:contract:track-ref:1"],
|
|
25
|
+
["listening-event:1", "urn:moondog:contract:listening-event:1"],
|
|
26
|
+
["taste-event:1", "urn:moondog:contract:taste-event:1"],
|
|
27
|
+
["library-track-observation:1", "urn:moondog:contract:library-track-observation:1"],
|
|
28
|
+
["profile-evidence:1", "urn:moondog:contract:profile-evidence:1"],
|
|
29
|
+
["profile-evidence:2", "urn:moondog:contract:profile-evidence:2"],
|
|
30
|
+
["capability-policy:1", "urn:moondog:contract:capability-policy:1"],
|
|
31
|
+
["tool-invocation:1", "urn:moondog:contract:tool-invocation:1"],
|
|
32
|
+
["tool-receipt:1", "urn:moondog:contract:tool-receipt:1"],
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export const contractCurrentVersions = new Map([
|
|
36
|
+
["track-ref", 1],
|
|
37
|
+
["listening-event", 1],
|
|
38
|
+
["taste-event", 1],
|
|
39
|
+
["library-track-observation", 1],
|
|
40
|
+
["profile-evidence", 2],
|
|
41
|
+
["capability-policy", 1],
|
|
42
|
+
["tool-invocation", 1],
|
|
43
|
+
["tool-receipt", 1],
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
export function contractSchemaId(contractName, schemaVersion) {
|
|
47
|
+
const version = schemaVersion ?? contractCurrentVersions.get(contractName);
|
|
48
|
+
if (!Number.isInteger(version)) return undefined;
|
|
49
|
+
return contractSchemaIdsByVersion.get(`${contractName}:${version}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const contractSchemaIds = new Map(
|
|
53
|
+
[...contractCurrentVersions].map(([contractName, schemaVersion]) => [
|
|
54
|
+
contractName,
|
|
55
|
+
contractSchemaId(contractName, schemaVersion),
|
|
56
|
+
]),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
async function readJson(filePath) {
|
|
60
|
+
const source = await readFile(filePath, "utf8");
|
|
61
|
+
return JSON.parse(source);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function joinSchemaPath(basePath, segment) {
|
|
65
|
+
const escaped = segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
66
|
+
return `${basePath}/${escaped}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assertRequiredPropertiesDeclared(
|
|
70
|
+
schema,
|
|
71
|
+
schemaPath = "#",
|
|
72
|
+
inheritedProperties = new Set(),
|
|
73
|
+
) {
|
|
74
|
+
if (typeof schema === "boolean") return;
|
|
75
|
+
|
|
76
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
77
|
+
throw new TypeError(`${schemaPath} is not a JSON Schema object`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const visibleProperties = new Set(inheritedProperties);
|
|
81
|
+
|
|
82
|
+
for (const propertyName of Object.keys(schema.properties ?? {})) {
|
|
83
|
+
visibleProperties.add(propertyName);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const propertyName of schema.required ?? []) {
|
|
87
|
+
if (!visibleProperties.has(propertyName)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`${schemaPath}/required names undeclared property ${JSON.stringify(propertyName)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [triggerProperty, dependentProperties] of Object.entries(
|
|
95
|
+
schema.dependentRequired ?? {},
|
|
96
|
+
)) {
|
|
97
|
+
if (!visibleProperties.has(triggerProperty)) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`${schemaPath}/dependentRequired names undeclared trigger ${JSON.stringify(triggerProperty)}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
for (const dependentProperty of dependentProperties) {
|
|
103
|
+
if (!visibleProperties.has(dependentProperty)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`${schemaPath}/dependentRequired/${triggerProperty} names undeclared property ${JSON.stringify(dependentProperty)}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (schema.if?.properties) {
|
|
112
|
+
const guardedDiscriminators = new Set(schema.if.required ?? []);
|
|
113
|
+
for (const discriminator of Object.keys(schema.if.properties)) {
|
|
114
|
+
if (!guardedDiscriminators.has(discriminator)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`${schemaPath}/if/properties/${discriminator} is not guarded by if.required`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const sameInstanceKeywords = [
|
|
123
|
+
"allOf",
|
|
124
|
+
"anyOf",
|
|
125
|
+
"oneOf",
|
|
126
|
+
"not",
|
|
127
|
+
"if",
|
|
128
|
+
"then",
|
|
129
|
+
"else",
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const keyword of sameInstanceKeywords) {
|
|
133
|
+
const value = schema[keyword];
|
|
134
|
+
if (value === undefined) continue;
|
|
135
|
+
|
|
136
|
+
const children = Array.isArray(value) ? value : [value];
|
|
137
|
+
children.forEach((child, index) => {
|
|
138
|
+
const childPath = Array.isArray(value)
|
|
139
|
+
? joinSchemaPath(joinSchemaPath(schemaPath, keyword), String(index))
|
|
140
|
+
: joinSchemaPath(schemaPath, keyword);
|
|
141
|
+
assertRequiredPropertiesDeclared(child, childPath, visibleProperties);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const [propertyName, propertySchema] of Object.entries(
|
|
146
|
+
schema.properties ?? {},
|
|
147
|
+
)) {
|
|
148
|
+
assertRequiredPropertiesDeclared(
|
|
149
|
+
propertySchema,
|
|
150
|
+
joinSchemaPath(joinSchemaPath(schemaPath, "properties"), propertyName),
|
|
151
|
+
new Set(),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const keyword of ["patternProperties", "$defs"]) {
|
|
156
|
+
for (const [name, child] of Object.entries(schema[keyword] ?? {})) {
|
|
157
|
+
assertRequiredPropertiesDeclared(
|
|
158
|
+
child,
|
|
159
|
+
joinSchemaPath(joinSchemaPath(schemaPath, keyword), name),
|
|
160
|
+
new Set(),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const [name, child] of Object.entries(schema.dependentSchemas ?? {})) {
|
|
166
|
+
assertRequiredPropertiesDeclared(
|
|
167
|
+
child,
|
|
168
|
+
joinSchemaPath(joinSchemaPath(schemaPath, "dependentSchemas"), name),
|
|
169
|
+
visibleProperties,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const keyword of [
|
|
174
|
+
"additionalProperties",
|
|
175
|
+
"propertyNames",
|
|
176
|
+
"items",
|
|
177
|
+
"contains",
|
|
178
|
+
"unevaluatedProperties",
|
|
179
|
+
]) {
|
|
180
|
+
const child = schema[keyword];
|
|
181
|
+
if (child === undefined) continue;
|
|
182
|
+
assertRequiredPropertiesDeclared(
|
|
183
|
+
child,
|
|
184
|
+
joinSchemaPath(schemaPath, keyword),
|
|
185
|
+
new Set(),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
(schema.prefixItems ?? []).forEach((child, index) => {
|
|
190
|
+
assertRequiredPropertiesDeclared(
|
|
191
|
+
child,
|
|
192
|
+
joinSchemaPath(joinSchemaPath(schemaPath, "prefixItems"), String(index)),
|
|
193
|
+
new Set(),
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function loadSchemas() {
|
|
199
|
+
const schemas = [];
|
|
200
|
+
for (const schemaVersion of schemaVersions) {
|
|
201
|
+
const directory = path.join(repositoryRoot, "contracts", `v${schemaVersion}`);
|
|
202
|
+
let fileNames;
|
|
203
|
+
try {
|
|
204
|
+
fileNames = (await readdir(directory)).filter((fileName) =>
|
|
205
|
+
fileName.endsWith(".schema.json"),
|
|
206
|
+
);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error?.code === "ENOENT") continue;
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
for (const fileName of fileNames) {
|
|
212
|
+
const filePath = path.join(directory, fileName);
|
|
213
|
+
schemas.push({
|
|
214
|
+
fileName,
|
|
215
|
+
filePath,
|
|
216
|
+
relativePath: path.posix.join(`v${schemaVersion}`, fileName),
|
|
217
|
+
schemaVersion,
|
|
218
|
+
schema: await readJson(filePath),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return schemas.sort((left, right) => {
|
|
224
|
+
if (left.fileName === "definitions.schema.json") return -1;
|
|
225
|
+
if (right.fileName === "definitions.schema.json") return 1;
|
|
226
|
+
return left.relativePath.localeCompare(right.relativePath);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function createContractValidator() {
|
|
231
|
+
const ajv = new Ajv2020({
|
|
232
|
+
strict: true,
|
|
233
|
+
strictRequired: false,
|
|
234
|
+
allErrors: true,
|
|
235
|
+
validateSchema: true,
|
|
236
|
+
validateFormats: true,
|
|
237
|
+
allowUnionTypes: false,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
addFormats(ajv, { mode: "full" });
|
|
241
|
+
|
|
242
|
+
const schemas = await loadSchemas();
|
|
243
|
+
const seenIds = new Set();
|
|
244
|
+
|
|
245
|
+
for (const { fileName, schema } of schemas) {
|
|
246
|
+
if (!schema.$id) {
|
|
247
|
+
throw new Error(`${fileName} has no $id`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (seenIds.has(schema.$id)) {
|
|
251
|
+
throw new Error(`Duplicate schema $id: ${schema.$id}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
seenIds.add(schema.$id);
|
|
255
|
+
assertRequiredPropertiesDeclared(schema, `${fileName}#`);
|
|
256
|
+
ajv.addSchema(schema);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const schemaId of contractSchemaIdsByVersion.values()) {
|
|
260
|
+
if (!ajv.getSchema(schemaId)) {
|
|
261
|
+
throw new Error(`Schema did not compile: ${schemaId}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { ajv, schemas };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function loadFixtures() {
|
|
269
|
+
const fixtureDescriptors = [];
|
|
270
|
+
for (const schemaVersion of schemaVersions) {
|
|
271
|
+
const directory = path.join(
|
|
272
|
+
repositoryRoot,
|
|
273
|
+
"contracts",
|
|
274
|
+
"examples",
|
|
275
|
+
`v${schemaVersion}`,
|
|
276
|
+
);
|
|
277
|
+
let fileNames;
|
|
278
|
+
try {
|
|
279
|
+
fileNames = (await readdir(directory)).filter(
|
|
280
|
+
(fileName) =>
|
|
281
|
+
fileName.endsWith(".valid.json") ||
|
|
282
|
+
fileName.endsWith(".invalid.json"),
|
|
283
|
+
);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error?.code === "ENOENT") continue;
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
for (const fileName of fileNames) {
|
|
289
|
+
fixtureDescriptors.push({ directory, fileName, schemaVersion });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
fixtureDescriptors.sort((left, right) =>
|
|
293
|
+
`${left.schemaVersion}/${left.fileName}`.localeCompare(
|
|
294
|
+
`${right.schemaVersion}/${right.fileName}`,
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
return Promise.all(
|
|
299
|
+
fixtureDescriptors.map(async ({ directory, fileName, schemaVersion }) => {
|
|
300
|
+
const match = fileName.match(
|
|
301
|
+
/^([a-z0-9-]+?)(?:--([a-z0-9-]+))?\.(valid|invalid)\.json$/,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (!match) {
|
|
305
|
+
throw new Error(`Unrecognized fixture file name: ${fileName}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const [, contractName, caseName = "base", expectation] = match;
|
|
309
|
+
const schemaId = contractSchemaId(contractName, schemaVersion);
|
|
310
|
+
|
|
311
|
+
if (!schemaId) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`No schema mapping for fixture: v${schemaVersion}/${fileName}`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
fileName,
|
|
319
|
+
relativePath: path.posix.join(`v${schemaVersion}`, fileName),
|
|
320
|
+
contractName,
|
|
321
|
+
caseName,
|
|
322
|
+
schemaVersion,
|
|
323
|
+
schemaId,
|
|
324
|
+
expectedValid: expectation === "valid",
|
|
325
|
+
data: await readJson(path.join(directory, fileName)),
|
|
326
|
+
};
|
|
327
|
+
}),
|
|
328
|
+
).then((fixtures) => {
|
|
329
|
+
for (const versionKey of contractSchemaIdsByVersion.keys()) {
|
|
330
|
+
const separator = versionKey.lastIndexOf(":");
|
|
331
|
+
const contractName = versionKey.slice(0, separator);
|
|
332
|
+
const schemaVersion = Number(versionKey.slice(separator + 1));
|
|
333
|
+
const contractFixtures = fixtures.filter(
|
|
334
|
+
(fixture) =>
|
|
335
|
+
fixture.contractName === contractName &&
|
|
336
|
+
fixture.schemaVersion === schemaVersion,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (!contractFixtures.some((fixture) => fixture.expectedValid)) {
|
|
340
|
+
throw new Error(`${versionKey} has no valid fixture`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!contractFixtures.some((fixture) => !fixture.expectedValid)) {
|
|
344
|
+
throw new Error(`${versionKey} has no invalid fixture`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return fixtures;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function formatValidationErrors(errors) {
|
|
353
|
+
if (!errors || errors.length === 0) return "No validation errors were reported.";
|
|
354
|
+
|
|
355
|
+
return errors
|
|
356
|
+
.map((error) => {
|
|
357
|
+
const location = error.instancePath || "/";
|
|
358
|
+
return `${location} ${error.message}`;
|
|
359
|
+
})
|
|
360
|
+
.join("; ");
|
|
361
|
+
}
|