@chalksurf/cli 0.2.2 → 0.2.3
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 +6 -8
- package/dist/commands/sheet.js +47 -4
- package/dist/lib/manifest.js +58 -0
- package/dist/lib/user-jobs.js +8 -0
- package/docs/examples/sheet-import-manifest.json +24 -0
- package/docs/manifest.md +24 -1
- package/package.json +1 -1
- package/schemas/sheet-import-manifest.schema.json +78 -22
package/README.md
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
# `@chalksurf/cli`
|
|
2
2
|
|
|
3
|
-
Publishable ChalkSurf CLI package.
|
|
4
|
-
|
|
5
|
-
Current internal release line: `0.2.2`. Expect breaking changes while the CLI is still only used internally.
|
|
3
|
+
Publishable ChalkSurf CLI package. Expect breaking changes while the CLI is still only used internally.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
7
|
Run the published CLI without a global install:
|
|
10
8
|
|
|
11
9
|
```bash
|
|
12
|
-
npx @chalksurf/cli@
|
|
10
|
+
npx @chalksurf/cli@ --help
|
|
13
11
|
```
|
|
14
12
|
|
|
15
13
|
Install it globally when you want a persistent local binary:
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
npm install -g @chalksurf/cli
|
|
16
|
+
npm install -g @chalksurf/cli
|
|
19
17
|
chalksurf --version
|
|
20
18
|
```
|
|
21
19
|
|
|
@@ -34,12 +32,12 @@ Headless or agent flow:
|
|
|
34
32
|
|
|
35
33
|
```bash
|
|
36
34
|
CHALKSURF_TOKEN=cs_cli_... \
|
|
37
|
-
printf '%s' "$CHALKSURF_TOKEN" | npx @chalksurf/cli
|
|
35
|
+
printf '%s' "$CHALKSURF_TOKEN" | npx @chalksurf/cli auth login \
|
|
38
36
|
--profile prod-codex \
|
|
39
37
|
--base-url https://chalksurf-api.fly.dev \
|
|
40
38
|
--with-token
|
|
41
39
|
|
|
42
|
-
npx @chalksurf/cli
|
|
40
|
+
npx @chalksurf/cli --profile prod-codex sheet import --manifest - --wait --json < import.json
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
Sheet import manifests use top-level `sheets[]`, where each sheet has one `targetFolderPath` and one or more ordered `sources[]`. See [the canonical example](./packages/cli/docs/examples/sheet-import-manifest.json).
|
|
@@ -60,7 +58,7 @@ Sheet import manifests use top-level `sheets[]`, where each sheet has one `targe
|
|
|
60
58
|
Use separate profiles so local, staging, production, human, and agent tokens do not overwrite each other:
|
|
61
59
|
|
|
62
60
|
```bash
|
|
63
|
-
npm run cli-dev -- auth login --profile dev-cztamas --base-url http://localhost:
|
|
61
|
+
npm run cli-dev -- auth login --profile dev-cztamas --base-url http://localhost:3101
|
|
64
62
|
npm run cli-dev -- profile use dev-cztamas
|
|
65
63
|
npm run cli-dev -- auth status --json
|
|
66
64
|
npm run cli-dev -- sheet import ./fixtures/algebra.pdf --wait --json
|
package/dist/commands/sheet.js
CHANGED
|
@@ -10,6 +10,7 @@ import { cleanupResolvedSources, resolveSources } from '../lib/source-resolver.j
|
|
|
10
10
|
import { resolveRequestedTranslateToLanguages, translationLanguages, } from '../lib/translation-languages.js';
|
|
11
11
|
import { formatCliJobSummary, getWaitError, getWaitExitCode, throwSilentExitCode, waitForCliJobs, } from '../lib/user-jobs.js';
|
|
12
12
|
const hasUniqueItems = (values) => new Set(values).size === values.length;
|
|
13
|
+
const sheetImportComponentIdPattern = /^[a-zA-Z0-9_-]{1,80}$/;
|
|
13
14
|
const isValidTargetFolderPath = (value) => {
|
|
14
15
|
const normalizedSegments = value
|
|
15
16
|
.replaceAll('\\', '/')
|
|
@@ -31,6 +32,32 @@ const assertValidSheetImportPlan = (plan) => {
|
|
|
31
32
|
if (!hasUniqueItems(sheet.sourceIndexes)) {
|
|
32
33
|
throw new CliCommandError('Invalid sheet import plan: sourceIndexes must be unique within a sheet.', 2);
|
|
33
34
|
}
|
|
35
|
+
if (sheet.components) {
|
|
36
|
+
if (sheet.targetFolderPath !== undefined || sheet.titleOverride !== undefined || sheet.translateToLanguages) {
|
|
37
|
+
throw new CliCommandError('Invalid sheet import plan: targetFolderPath, titleOverride, and translateToLanguages must be set on components when components are provided.', 2);
|
|
38
|
+
}
|
|
39
|
+
if (!hasUniqueItems(sheet.components.map((component) => component.componentId))) {
|
|
40
|
+
throw new CliCommandError('Invalid sheet import plan: componentId values must be unique within a sheet.', 2);
|
|
41
|
+
}
|
|
42
|
+
sheet.components.forEach((component) => {
|
|
43
|
+
if (!sheetImportComponentIdPattern.test(component.componentId)) {
|
|
44
|
+
throw new CliCommandError('Invalid sheet import plan: componentId must be 1-80 letters, numbers, underscores, or hyphens.', 2);
|
|
45
|
+
}
|
|
46
|
+
if (!component.description.trim()) {
|
|
47
|
+
throw new CliCommandError('Invalid sheet import plan: component descriptions must be non-empty.', 2);
|
|
48
|
+
}
|
|
49
|
+
if (component.targetFolderPath != null && !isValidTargetFolderPath(component.targetFolderPath)) {
|
|
50
|
+
throw new CliCommandError('Invalid sheet import plan: component targetFolderPath must be a normalized folder path without "." or ".." segments.', 2);
|
|
51
|
+
}
|
|
52
|
+
if (component.translateToLanguages && !hasUniqueItems(component.translateToLanguages)) {
|
|
53
|
+
throw new CliCommandError('Invalid sheet import plan: component translateToLanguages must be unique.', 2);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (sheet.targetFolderPath === undefined) {
|
|
59
|
+
throw new CliCommandError('Invalid sheet import plan: targetFolderPath is required.', 2);
|
|
60
|
+
}
|
|
34
61
|
if (sheet.targetFolderPath !== null && !isValidTargetFolderPath(sheet.targetFolderPath)) {
|
|
35
62
|
throw new CliCommandError('Invalid sheet import plan: targetFolderPath must be a normalized folder path without "." or ".." segments.', 2);
|
|
36
63
|
}
|
|
@@ -132,9 +159,21 @@ const resolveGroupedSourceIndexes = ({ groups, sourceIndexesByInputIndex, }) =>
|
|
|
132
159
|
});
|
|
133
160
|
return {
|
|
134
161
|
sourceIndexes,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
...(group.components
|
|
163
|
+
? {
|
|
164
|
+
components: group.components.map((component) => ({
|
|
165
|
+
componentId: component.componentId,
|
|
166
|
+
description: component.description,
|
|
167
|
+
targetFolderPath: component.targetFolderPath,
|
|
168
|
+
titleOverride: component.title,
|
|
169
|
+
translateToLanguages: component.translateTo,
|
|
170
|
+
})),
|
|
171
|
+
}
|
|
172
|
+
: {
|
|
173
|
+
targetFolderPath: group.targetFolderPath,
|
|
174
|
+
titleOverride: group.title,
|
|
175
|
+
translateToLanguages: group.translateTo,
|
|
176
|
+
}),
|
|
138
177
|
};
|
|
139
178
|
});
|
|
140
179
|
};
|
|
@@ -173,6 +212,7 @@ const buildSheetImportJobs = ({ importResultJobs, sources, waitedJobsById, }) =>
|
|
|
173
212
|
return importResultJobs.map((job, index) => {
|
|
174
213
|
const sourceIndexes = job.sourceIndexes ?? (sources[index] ? [sources[index].sourceIndex] : []);
|
|
175
214
|
const waitedJob = waitedJobsById?.get(job.jobId);
|
|
215
|
+
const importedSheetTranslationJobs = waitedJob?.sheets?.flatMap((sheet) => (sheet.status === 'imported' ? (sheet.translationJobs ?? []) : [])) ?? [];
|
|
176
216
|
return {
|
|
177
217
|
jobId: job.jobId,
|
|
178
218
|
sourceIndexes,
|
|
@@ -181,8 +221,10 @@ const buildSheetImportJobs = ({ importResultJobs, sources, waitedJobsById, }) =>
|
|
|
181
221
|
.filter((sourceId) => sourceId != null))),
|
|
182
222
|
status: waitedJob?.status ?? 'queued',
|
|
183
223
|
error: waitedJob?.error,
|
|
224
|
+
componentIds: job.componentIds,
|
|
184
225
|
exerciseSheetId: waitedJob?.exerciseSheetId,
|
|
185
|
-
|
|
226
|
+
sheets: waitedJob?.sheets,
|
|
227
|
+
translationJobs: waitedJob?.translationJobs ?? importedSheetTranslationJobs,
|
|
186
228
|
};
|
|
187
229
|
});
|
|
188
230
|
};
|
|
@@ -255,6 +297,7 @@ const formatWaitedImportOutput = ({ jobs, summary, }) => {
|
|
|
255
297
|
updatedAt: '',
|
|
256
298
|
error: job.error,
|
|
257
299
|
exerciseSheetId: job.exerciseSheetId,
|
|
300
|
+
sheets: job.sheets,
|
|
258
301
|
}),
|
|
259
302
|
...formatCliImportTranslationJobs(job.translationJobs),
|
|
260
303
|
]),
|
package/dist/lib/manifest.js
CHANGED
|
@@ -19,6 +19,7 @@ const translationLanguageListSchema = z
|
|
|
19
19
|
.refine((languages) => new Set(languages).size === languages.length, {
|
|
20
20
|
message: 'translateTo languages must be unique.',
|
|
21
21
|
});
|
|
22
|
+
const sheetImportComponentIdPattern = /^[a-zA-Z0-9_-]{1,80}$/;
|
|
22
23
|
const parseTranslateTo = ({ value, label }) => {
|
|
23
24
|
if (value == null) {
|
|
24
25
|
return undefined;
|
|
@@ -70,6 +71,52 @@ const parseTargetFolderPath = ({ value, label }) => {
|
|
|
70
71
|
}
|
|
71
72
|
return normalizedValue;
|
|
72
73
|
};
|
|
74
|
+
const parseOptionalTargetFolderPath = ({ value, label }) => {
|
|
75
|
+
if (value == null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parseTargetFolderPath({ value, label });
|
|
79
|
+
};
|
|
80
|
+
const parseSheetImportComponents = ({ value, sheetIndex, }) => {
|
|
81
|
+
if (value == null) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
85
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} components must be a non-empty array.`, 2);
|
|
86
|
+
}
|
|
87
|
+
const seenComponentIds = new Set();
|
|
88
|
+
return value.map((componentValue, componentIndex) => {
|
|
89
|
+
const label = `Manifest sheet at index ${sheetIndex} component at index ${componentIndex}`;
|
|
90
|
+
if (!isObject(componentValue)) {
|
|
91
|
+
throw new CliCommandError(`${label} must be an object.`, 2);
|
|
92
|
+
}
|
|
93
|
+
const componentId = normalizeOptionalString(componentValue.componentId);
|
|
94
|
+
if (!componentId || !sheetImportComponentIdPattern.test(componentId)) {
|
|
95
|
+
throw new CliCommandError(`${label} must include a componentId using 1-80 letters, numbers, underscores, or hyphens.`, 2);
|
|
96
|
+
}
|
|
97
|
+
if (seenComponentIds.has(componentId)) {
|
|
98
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} componentId "${componentId}" must be unique.`, 2);
|
|
99
|
+
}
|
|
100
|
+
seenComponentIds.add(componentId);
|
|
101
|
+
const description = normalizeOptionalString(componentValue.description);
|
|
102
|
+
if (!description) {
|
|
103
|
+
throw new CliCommandError(`${label} must include a non-empty description.`, 2);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
componentId,
|
|
107
|
+
description,
|
|
108
|
+
targetFolderPath: parseOptionalTargetFolderPath({
|
|
109
|
+
value: componentValue.targetFolderPath,
|
|
110
|
+
label,
|
|
111
|
+
}),
|
|
112
|
+
title: normalizeOptionalString(componentValue.title),
|
|
113
|
+
translateTo: parseTranslateTo({
|
|
114
|
+
value: componentValue.translateTo,
|
|
115
|
+
label,
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
};
|
|
73
120
|
const parseManifestMetadata = ({ parsedManifest, topLevelFields, }) => {
|
|
74
121
|
const wait = typeof parsedManifest.wait === 'boolean' ? parsedManifest.wait : undefined;
|
|
75
122
|
return {
|
|
@@ -215,6 +262,17 @@ const parseSheetImportManifest = (manifestText) => {
|
|
|
215
262
|
sources.push(parsedSource);
|
|
216
263
|
return flattenedSourceIndex;
|
|
217
264
|
});
|
|
265
|
+
const components = parseSheetImportComponents({ value: sheetValue.components, sheetIndex });
|
|
266
|
+
if (components) {
|
|
267
|
+
if ('targetFolderPath' in sheetValue || 'title' in sheetValue || 'translateTo' in sheetValue) {
|
|
268
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} must set targetFolderPath, title, and translateTo on components when components are provided.`, 2);
|
|
269
|
+
}
|
|
270
|
+
sheetGroups.push({
|
|
271
|
+
sourceInputIndexes,
|
|
272
|
+
components,
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
218
276
|
sheetGroups.push({
|
|
219
277
|
sourceInputIndexes,
|
|
220
278
|
targetFolderPath: parseTargetFolderPath({
|
package/dist/lib/user-jobs.js
CHANGED
|
@@ -17,6 +17,7 @@ export const serializeCliJob = (job) => {
|
|
|
17
17
|
eligibleExerciseCount: job.result?.eligibleExerciseCount,
|
|
18
18
|
nonUpdatableExerciseCount: job.result?.nonUpdatableExerciseCount,
|
|
19
19
|
resultCode: job.result?.resultCode,
|
|
20
|
+
sheets: job.result?.sheets,
|
|
20
21
|
skippedExerciseCount: job.result?.skippedExerciseCount,
|
|
21
22
|
translationJobs: job.result?.translationJobs,
|
|
22
23
|
unmatchedImportedSolutionCount: job.result?.unmatchedImportedSolutionCount,
|
|
@@ -28,6 +29,13 @@ export const formatCliJobSummary = (job) => {
|
|
|
28
29
|
if (job.exerciseSheetId) {
|
|
29
30
|
return `${job.id} completed -> ${job.exerciseSheetId}`;
|
|
30
31
|
}
|
|
32
|
+
const importedSheets = job.sheets?.filter((sheet) => sheet.status === 'imported') ?? [];
|
|
33
|
+
if (importedSheets.length === 1) {
|
|
34
|
+
return `${job.id} completed -> ${importedSheets[0].exerciseSheetId}`;
|
|
35
|
+
}
|
|
36
|
+
if (importedSheets.length > 1) {
|
|
37
|
+
return `${job.id} completed -> ${importedSheets.length} sheets`;
|
|
38
|
+
}
|
|
31
39
|
if (job.exerciseId) {
|
|
32
40
|
return `${job.id} completed -> ${job.exerciseId}`;
|
|
33
41
|
}
|
|
@@ -28,6 +28,30 @@
|
|
|
28
28
|
"path": "./practice-sheet.pdf"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"sources": [
|
|
34
|
+
{
|
|
35
|
+
"sourceId": "combined-archive-file",
|
|
36
|
+
"kind": "local",
|
|
37
|
+
"path": "./combined-archive-file.pdf"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"components": [
|
|
41
|
+
{
|
|
42
|
+
"componentId": "round_2_part_a",
|
|
43
|
+
"description": "The exercise sheet labeled Round 2, Part A.",
|
|
44
|
+
"targetFolderPath": "OKTV/2014",
|
|
45
|
+
"title": "OKTV 2014 Round 2 Part A",
|
|
46
|
+
"translateTo": ["english"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"componentId": "round_2_part_b",
|
|
50
|
+
"description": "The exercise sheet labeled Round 2, Part B.",
|
|
51
|
+
"targetFolderPath": "OKTV/2014",
|
|
52
|
+
"title": "OKTV 2014 Round 2 Part B"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
31
55
|
}
|
|
32
56
|
]
|
|
33
57
|
}
|
package/docs/manifest.md
CHANGED
|
@@ -60,7 +60,7 @@ Top-level fields:
|
|
|
60
60
|
| --- | --- | --- | --- |
|
|
61
61
|
| `sheets` | array | yes | One or more sheets to import. Each sheet may contain one or more source files. |
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Normal sheet fields:
|
|
64
64
|
|
|
65
65
|
| Field | Type | Required | Notes |
|
|
66
66
|
| --- | --- | --- | --- |
|
|
@@ -73,6 +73,27 @@ Sheet import metadata is sheet-level, not source-level. Do not put `title` or `t
|
|
|
73
73
|
|
|
74
74
|
Source-level `relativePath` is only a source label or filename override. It does not choose the destination folder for grouped sheet imports; `targetFolderPath` is the only destination field.
|
|
75
75
|
|
|
76
|
+
Declared-component sheet fields:
|
|
77
|
+
|
|
78
|
+
Use `components[]` when the same source group contains a known fixed set of component sheets. In this mode, do not put `targetFolderPath`, `title`, or `translateTo` on the sheet itself; put output metadata on each component.
|
|
79
|
+
|
|
80
|
+
| Field | Type | Required | Notes |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `sources` | array | yes | One or more source files that contain the declared components. |
|
|
83
|
+
| `components` | array | yes | The explicitly requested component sheets to extract from the shared source group. |
|
|
84
|
+
|
|
85
|
+
Component fields:
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Notes |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `componentId` | string | yes | Stable key for this requested component. Must match `/^[a-zA-Z0-9_-]{1,80}$/` and be unique within the sheet group. |
|
|
90
|
+
| `description` | string | yes | Matching instructions that identify this component in the source group. |
|
|
91
|
+
| `targetFolderPath` | string \| null | no | Destination folder for this component. Omit or use `null` for the root folder. |
|
|
92
|
+
| `title` | string | no | Overrides the imported component sheet title. |
|
|
93
|
+
| `translateTo` | string[] | no | Target translation languages for this component. Must be unique and non-empty when present. |
|
|
94
|
+
|
|
95
|
+
The model must return an explicit `import` or `not_present` decision for every declared `componentId`. Jobs complete when at least one component is imported and fail when all declared components are `not_present`.
|
|
96
|
+
|
|
76
97
|
Canonical example:
|
|
77
98
|
|
|
78
99
|
- [docs/examples/sheet-import-manifest.json](./examples/sheet-import-manifest.json)
|
|
@@ -142,6 +163,8 @@ The CLI validates more than the JSON schema can express on its own:
|
|
|
142
163
|
|
|
143
164
|
- `sourceId` values must be unique within one manifest.
|
|
144
165
|
- Sheet import `translateTo` values must be unique within one sheet.
|
|
166
|
+
- Sheet import component `componentId` values must be unique within one sheet group.
|
|
167
|
+
- Sheet import components require non-empty descriptions.
|
|
145
168
|
- Exercise import top-level `translateTo` values must be unique.
|
|
146
169
|
- `relativePath` cannot be empty or contain `..`.
|
|
147
170
|
- Sheet import `targetFolderPath` must be `null` or a normalized folder path without `.` or `..` segments.
|
package/package.json
CHANGED
|
@@ -40,6 +40,37 @@
|
|
|
40
40
|
"type": "string",
|
|
41
41
|
"enum": ["english", "hungarian", "german", "french", "spanish", "italian"]
|
|
42
42
|
},
|
|
43
|
+
"componentId": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"pattern": "^[a-zA-Z0-9_-]{1,80}$"
|
|
46
|
+
},
|
|
47
|
+
"component": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["componentId", "description"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"componentId": {
|
|
53
|
+
"$ref": "#/$defs/componentId"
|
|
54
|
+
},
|
|
55
|
+
"description": {
|
|
56
|
+
"$ref": "#/$defs/nonEmptyString"
|
|
57
|
+
},
|
|
58
|
+
"targetFolderPath": {
|
|
59
|
+
"$ref": "#/$defs/targetFolderPath"
|
|
60
|
+
},
|
|
61
|
+
"title": {
|
|
62
|
+
"$ref": "#/$defs/nonEmptyString"
|
|
63
|
+
},
|
|
64
|
+
"translateTo": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"minItems": 1,
|
|
67
|
+
"uniqueItems": true,
|
|
68
|
+
"items": {
|
|
69
|
+
"$ref": "#/$defs/translateLanguage"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
43
74
|
"localSource": {
|
|
44
75
|
"type": "object",
|
|
45
76
|
"additionalProperties": false,
|
|
@@ -111,32 +142,57 @@
|
|
|
111
142
|
]
|
|
112
143
|
},
|
|
113
144
|
"sheet": {
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
"oneOf": [
|
|
146
|
+
{
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"required": ["targetFolderPath", "sources"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"targetFolderPath": {
|
|
152
|
+
"$ref": "#/$defs/targetFolderPath"
|
|
153
|
+
},
|
|
154
|
+
"title": {
|
|
155
|
+
"$ref": "#/$defs/nonEmptyString"
|
|
156
|
+
},
|
|
157
|
+
"translateTo": {
|
|
158
|
+
"type": "array",
|
|
159
|
+
"minItems": 1,
|
|
160
|
+
"uniqueItems": true,
|
|
161
|
+
"items": {
|
|
162
|
+
"$ref": "#/$defs/translateLanguage"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"sources": {
|
|
166
|
+
"type": "array",
|
|
167
|
+
"minItems": 1,
|
|
168
|
+
"items": {
|
|
169
|
+
"$ref": "#/$defs/source"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
130
172
|
}
|
|
131
173
|
},
|
|
132
|
-
|
|
133
|
-
"type": "
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
|
|
174
|
+
{
|
|
175
|
+
"type": "object",
|
|
176
|
+
"additionalProperties": false,
|
|
177
|
+
"required": ["sources", "components"],
|
|
178
|
+
"properties": {
|
|
179
|
+
"sources": {
|
|
180
|
+
"type": "array",
|
|
181
|
+
"minItems": 1,
|
|
182
|
+
"items": {
|
|
183
|
+
"$ref": "#/$defs/source"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"components": {
|
|
187
|
+
"type": "array",
|
|
188
|
+
"minItems": 1,
|
|
189
|
+
"items": {
|
|
190
|
+
"$ref": "#/$defs/component"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
137
193
|
}
|
|
138
194
|
}
|
|
139
|
-
|
|
195
|
+
]
|
|
140
196
|
}
|
|
141
197
|
}
|
|
142
198
|
}
|