@intentius/chant 0.34.1 → 0.37.2
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/cli/commands/check-lexicon-docs.d.ts +42 -0
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +2 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/codegen/docs-sidebar.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +27 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +0 -10
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/lifecycle/rollback.d.ts +18 -0
- package/dist/lifecycle/rollback.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +53 -0
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/check-lexicon-docs.test.ts +90 -0
- package/src/cli/commands/check-lexicon-docs.ts +71 -0
- package/src/cli/commands/check-lexicon.ts +15 -0
- package/src/cli/handlers/components.ts +54 -3
- package/src/cli/handlers/graph.test.ts +61 -0
- package/src/cli/handlers/lifecycle.ts +8 -1
- package/src/cli/main.ts +2 -0
- package/src/cli/registry.ts +2 -0
- package/src/codegen/docs-rule-scanning.ts +12 -3
- package/src/codegen/docs-sections.ts +6 -4
- package/src/codegen/docs-sidebar.ts +8 -2
- package/src/codegen/docs.ts +141 -5
- package/src/codegen/fetch.test.ts +24 -5
- package/src/codegen/fetch.ts +19 -1
- package/src/deep-observation.test.ts +151 -0
- package/src/deep-observation.ts +66 -2
- package/src/lifecycle/rollback.test.ts +78 -1
- package/src/lifecycle/rollback.ts +41 -2
- package/src/lifecycle/status.test.ts +90 -5
- package/src/lifecycle/status.ts +85 -2
- package/src/yaml.test.ts +89 -0
- package/src/yaml.ts +66 -9
package/src/lifecycle/status.ts
CHANGED
|
@@ -86,6 +86,19 @@ export interface ComponentStatusRow {
|
|
|
86
86
|
* present-but-not-healthy amber (mid-deploy) / red (rollback/failed).
|
|
87
87
|
*/
|
|
88
88
|
stack?: LiveStackInfo;
|
|
89
|
+
/**
|
|
90
|
+
* How this component's own resources answered (behold#98). Present whenever
|
|
91
|
+
* `--live` gathered evidence across a live-name mapping.
|
|
92
|
+
*
|
|
93
|
+
* `stack` above only exists where the substrate has a deploy object to read,
|
|
94
|
+
* which is AWS and nowhere else — floci-az and floci-gcp have none, so a
|
|
95
|
+
* consumer painting component status off `stack` has nothing to paint from
|
|
96
|
+
* there. These counts are the substrate-neutral source for the same job:
|
|
97
|
+
* they aggregate observations, which every lexicon produces, rather than a
|
|
98
|
+
* provider-specific grouping object. `stack` stays as the richer enrichment
|
|
99
|
+
* where it exists.
|
|
100
|
+
*/
|
|
101
|
+
resources?: ComponentResourceRollup;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
/** A component's owning deploy unit and its provider-native status. */
|
|
@@ -129,6 +142,14 @@ export interface LiveComponentEvidence {
|
|
|
129
142
|
/** The owning deploy unit's raw status, when observed (AWS: the component's own
|
|
130
143
|
* CFN stack). Surfaced onto `ComponentStatusRow.stack` for a richer palette. */
|
|
131
144
|
stack?: LiveStackInfo;
|
|
145
|
+
/**
|
|
146
|
+
* How the component's own resources answered, before any merge collapsed
|
|
147
|
+
* them (behold#98). Always present when evidence exists — a component that
|
|
148
|
+
* maps to a single entity by identity gets a rollup of one, so a consumer
|
|
149
|
+
* never has to branch on whether a live-name mapping happened to be
|
|
150
|
+
* configured.
|
|
151
|
+
*/
|
|
152
|
+
rollup?: ComponentResourceRollup;
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
/**
|
|
@@ -141,6 +162,16 @@ export interface LiveComponentEvidence {
|
|
|
141
162
|
* is authoritative for **presence** (`live`) and **ownership**; the change-set's
|
|
142
163
|
* `action` is kept, since drift is still assessed from the diff. A component in
|
|
143
164
|
* only one map passes through unchanged.
|
|
165
|
+
*
|
|
166
|
+
* The change-set's `rollup` is kept too (behold#100). This merge rebuilds the
|
|
167
|
+
* evidence object field by field, so anything not named here is dropped — and
|
|
168
|
+
* `describeStackStatus` reports a stack, never per-resource counts, so the
|
|
169
|
+
* supplement has no rollup to contribute. Before this, every component on a
|
|
170
|
+
* lexicon that implements `describeStackStatus` lost the counts #1300 had just
|
|
171
|
+
* computed. That is AWS and only AWS, which made the rollup absent on exactly
|
|
172
|
+
* the substrate it was meant to be verified against: behold#98 shipped its
|
|
173
|
+
* consumer against floci-az/floci-gcp rows, where no stack observer runs and
|
|
174
|
+
* the field survived.
|
|
144
175
|
*/
|
|
145
176
|
export function mergeLiveEvidence(
|
|
146
177
|
base: Map<string, LiveComponentEvidence> | undefined,
|
|
@@ -158,6 +189,9 @@ export function mergeLiveEvidence(
|
|
|
158
189
|
ownership: sup.ownership ?? b?.ownership,
|
|
159
190
|
action: b?.action,
|
|
160
191
|
stack: sup.stack ?? b?.stack,
|
|
192
|
+
// Base first: the counts come from the change set, and a stack
|
|
193
|
+
// observation has none to offer.
|
|
194
|
+
...(b?.rollup ?? sup.rollup ? { rollup: b?.rollup ?? sup.rollup } : {}),
|
|
161
195
|
});
|
|
162
196
|
}
|
|
163
197
|
return merged;
|
|
@@ -180,6 +214,42 @@ export function resolveLiveNames(component: string, mapping?: LiveNameMapping):
|
|
|
180
214
|
return mapped && mapped.length > 0 ? mapped : [component];
|
|
181
215
|
}
|
|
182
216
|
|
|
217
|
+
/**
|
|
218
|
+
* How a component's own resources answered, one count per tri-state verdict.
|
|
219
|
+
*
|
|
220
|
+
* The merged verdict above is deliberately lossy — it answers "is this
|
|
221
|
+
* component deployed" and nothing else. A consumer painting component status
|
|
222
|
+
* without a deploy object to read (behold#98: floci-az and floci-gcp have no
|
|
223
|
+
* CloudFormation stack, so `stack` below is absent and there is nothing to
|
|
224
|
+
* colour from) needs the shape underneath: how many of the component's
|
|
225
|
+
* resources were seen, how many were confirmed gone, how many nobody could
|
|
226
|
+
* look at. Substrate-neutral by construction — it counts observations, not
|
|
227
|
+
* provider objects.
|
|
228
|
+
*/
|
|
229
|
+
export interface ComponentResourceRollup {
|
|
230
|
+
/** Resources this component owns, per the live-name mapping. */
|
|
231
|
+
total: number;
|
|
232
|
+
/** Observed present. */
|
|
233
|
+
present: number;
|
|
234
|
+
/** Looked for, reported missing. Never includes a resource nobody could read. */
|
|
235
|
+
absent: number;
|
|
236
|
+
/** NOT-OBSERVED (#1089) — a hole, never counted as absence. */
|
|
237
|
+
unobserved: number;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Count a component's entity verdicts without collapsing them (behold#98). */
|
|
241
|
+
function rollUp(entries: LiveComponentEvidence[]): ComponentResourceRollup {
|
|
242
|
+
let present = 0;
|
|
243
|
+
let absent = 0;
|
|
244
|
+
let unobserved = 0;
|
|
245
|
+
for (const e of entries) {
|
|
246
|
+
if (e.unobserved) unobserved += 1;
|
|
247
|
+
else if (e.live) present += 1;
|
|
248
|
+
else absent += 1;
|
|
249
|
+
}
|
|
250
|
+
return { total: entries.length, present, absent, unobserved };
|
|
251
|
+
}
|
|
252
|
+
|
|
183
253
|
/**
|
|
184
254
|
* Merge live evidence for several entity/resource names owned by one
|
|
185
255
|
* component into a single verdict. `live` and `ownership` favor the
|
|
@@ -187,10 +257,14 @@ export function resolveLiveNames(component: string, mapping?: LiveNameMapping):
|
|
|
187
257
|
* favors `update` so that drift on *any* owned entity surfaces as drift for
|
|
188
258
|
* the component as a whole, matching `reconcileStatus`'s single check for
|
|
189
259
|
* `action === "update"`.
|
|
260
|
+
*
|
|
261
|
+
* The per-entity counts survive as {@link LiveComponentEvidence.rollup}, so a
|
|
262
|
+
* consumer that needs the shape rather than the verdict is not forced to redo
|
|
263
|
+
* this join on the far side of a CLI boundary.
|
|
190
264
|
*/
|
|
191
265
|
function mergeEvidence(entries: LiveComponentEvidence[]): LiveComponentEvidence | undefined {
|
|
192
266
|
if (entries.length === 0) return undefined;
|
|
193
|
-
if (entries.length === 1) return entries[0];
|
|
267
|
+
if (entries.length === 1) return { ...entries[0], rollup: rollUp(entries) };
|
|
194
268
|
|
|
195
269
|
const live = entries.some((e) => e.live);
|
|
196
270
|
const ownership = entries.some((e) => e.ownership === "owned")
|
|
@@ -206,7 +280,7 @@ function mergeEvidence(entries: LiveComponentEvidence[]): LiveComponentEvidence
|
|
|
206
280
|
// actually seen live, which already answers "is this deployed".
|
|
207
281
|
const unobserved = live ? undefined : entries.find((e) => e.unobserved)?.unobserved;
|
|
208
282
|
|
|
209
|
-
return { live, ownership, action, ...(unobserved ? { unobserved } : {}) };
|
|
283
|
+
return { live, ownership, action, ...(unobserved ? { unobserved } : {}), rollup: rollUp(entries) };
|
|
210
284
|
}
|
|
211
285
|
|
|
212
286
|
/**
|
|
@@ -228,6 +302,14 @@ export function liveEvidenceFromChangeSet(
|
|
|
228
302
|
const evidenceByName = new Map<string, LiveComponentEvidence>();
|
|
229
303
|
for (const entry of cs.entries) {
|
|
230
304
|
evidenceByName.set(entry.name, {
|
|
305
|
+
rollup: rollUp([
|
|
306
|
+
{
|
|
307
|
+
live: entry.evidence.live,
|
|
308
|
+
...(entry.action === "unobserved" && entry.unobservedReason
|
|
309
|
+
? { unobserved: { reason: entry.unobservedReason } }
|
|
310
|
+
: {}),
|
|
311
|
+
},
|
|
312
|
+
]),
|
|
231
313
|
live: entry.evidence.live,
|
|
232
314
|
action: entry.action,
|
|
233
315
|
ownership: entry.ownership,
|
|
@@ -352,6 +434,7 @@ export function reconcileStatus(
|
|
|
352
434
|
...(liveEvidence && !evidence?.unobserved ? { live: !!evidence?.live } : {}),
|
|
353
435
|
...(evidence?.unobserved ? { unobserved: evidence.unobserved } : {}),
|
|
354
436
|
...(evidence?.stack ? { stack: evidence.stack } : {}),
|
|
437
|
+
...(evidence?.rollup ? { resources: evidence.rollup } : {}),
|
|
355
438
|
});
|
|
356
439
|
}
|
|
357
440
|
|
package/src/yaml.test.ts
CHANGED
|
@@ -244,3 +244,92 @@ describe("parseYAML block scalars (#910)", () => {
|
|
|
244
244
|
expect(parseYAML("msg: >\n a\n b\n\n c\n")).toEqual({ msg: "a b\nc\n" });
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
// #1311 — a sequence item's sibling keys survive a nested block, whichever key
|
|
249
|
+
// comes first. The bug was purely positional: the same keys in the other order
|
|
250
|
+
// parsed correctly, so nothing about the keys themselves was at fault.
|
|
251
|
+
describe("parseYAML — sibling keys after a nested block in a sequence item (#1311)", () => {
|
|
252
|
+
test("a sibling key after a nested MAPPING is not swallowed", () => {
|
|
253
|
+
expect(parseYAML("items:\n- context:\n cluster: c1\n name: n1\n")).toEqual({
|
|
254
|
+
items: [{ context: { cluster: "c1" }, name: "n1" }],
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("the same keys in the other order still parse — the ordering is what mattered", () => {
|
|
259
|
+
expect(parseYAML("items:\n- name: n1\n context:\n cluster: c1\n")).toEqual({
|
|
260
|
+
items: [{ name: "n1", context: { cluster: "c1" } }],
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("several siblings after a nested mapping", () => {
|
|
265
|
+
expect(parseYAML("items:\n- context:\n cluster: c1\n name: n1\n user: u1\n")).toEqual({
|
|
266
|
+
items: [{ context: { cluster: "c1" }, name: "n1", user: "u1" }],
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("back-to-back nested mappings", () => {
|
|
271
|
+
expect(parseYAML("items:\n- a:\n x: 1\n b:\n y: 2\n")).toEqual({ items: [{ a: { x: 1 }, b: { y: 2 } }] });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("every item in a multi-item sequence keeps its siblings", () => {
|
|
275
|
+
expect(parseYAML("items:\n- context:\n cluster: c1\n name: n1\n- context:\n cluster: c2\n name: n2\n")).toEqual({
|
|
276
|
+
items: [
|
|
277
|
+
{ context: { cluster: "c1" }, name: "n1" },
|
|
278
|
+
{ context: { cluster: "c2" }, name: "n2" },
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("a sibling key after a SAME-COLUMN nested sequence — valid YAML, and what kubectl emits", () => {
|
|
284
|
+
expect(parseYAML("items:\n- ports:\n - 80\n - 443\n name: n1\n")).toEqual({
|
|
285
|
+
items: [{ ports: [80, 443], name: "n1" }],
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("a key with no value stays null when the next line is a sibling, not its content", () => {
|
|
290
|
+
// The reason a nested MAPPING must be indented PAST its key while a
|
|
291
|
+
// sequence may share its column: `other` here belongs to the item, not to
|
|
292
|
+
// `meta`. Reading both against the same threshold breaks one or the other.
|
|
293
|
+
expect(parseYAML("items:\n- name: a\n meta:\n other: b\n")).toEqual({
|
|
294
|
+
items: [{ name: "a", meta: null, other: "b" }],
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("a real Kubernetes container: same-column sequences between scalar keys", () => {
|
|
299
|
+
expect(
|
|
300
|
+
parseYAML('containers:\n- name: web\n ports:\n - containerPort: 80\n env:\n - name: X\n value: "1"\n image: nginx\n'),
|
|
301
|
+
).toEqual({
|
|
302
|
+
containers: [
|
|
303
|
+
{
|
|
304
|
+
name: "web",
|
|
305
|
+
ports: [{ containerPort: 80 }],
|
|
306
|
+
env: [{ name: "X", value: "1" }],
|
|
307
|
+
image: "nginx",
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("a real kubeconfig context block — the shape that surfaced this", () => {
|
|
314
|
+
const kubeconfig = [
|
|
315
|
+
"contexts:",
|
|
316
|
+
"- context:",
|
|
317
|
+
" cluster: arn:aws:eks:us-east-1:000000000000:cluster/cc-eks",
|
|
318
|
+
" user: arn:aws:eks:us-east-1:000000000000:cluster/cc-eks",
|
|
319
|
+
" name: arn:aws:eks:us-east-1:000000000000:cluster/cc-eks",
|
|
320
|
+
"current-context: arn:aws:eks:us-east-1:000000000000:cluster/cc-eks",
|
|
321
|
+
"",
|
|
322
|
+
].join("\n");
|
|
323
|
+
const arn = "arn:aws:eks:us-east-1:000000000000:cluster/cc-eks";
|
|
324
|
+
expect(parseYAML(kubeconfig)).toEqual({
|
|
325
|
+
contexts: [{ context: { cluster: arn, user: arn }, name: arn }],
|
|
326
|
+
"current-context": arn,
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("a GitHub Actions step with `with:` before its sibling keys", () => {
|
|
331
|
+
expect(parseYAML("steps:\n- with:\n fetch-depth: 0\n name: checkout\n uses: actions/checkout@v4\n")).toEqual({
|
|
332
|
+
steps: [{ with: { "fetch-depth": 0 }, name: "checkout", uses: "actions/checkout@v4" }],
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
package/src/yaml.ts
CHANGED
|
@@ -329,7 +329,7 @@ function parseArrayItemValue(
|
|
|
329
329
|
inlineValue: string,
|
|
330
330
|
lines: string[],
|
|
331
331
|
currentIndex: number,
|
|
332
|
-
|
|
332
|
+
keyIndent: number,
|
|
333
333
|
): unknown {
|
|
334
334
|
if (inlineValue !== "" && !inlineValue.startsWith("#")) {
|
|
335
335
|
const header = blockScalarHeader(inlineValue);
|
|
@@ -349,16 +349,28 @@ function parseArrayItemValue(
|
|
|
349
349
|
}
|
|
350
350
|
return parseScalar(inlineValue);
|
|
351
351
|
}
|
|
352
|
-
// Empty inline value — check for nested block
|
|
352
|
+
// Empty inline value — check for a nested block. `keyIndent` is the key's own
|
|
353
|
+
// column, and the two nested shapes do NOT share a threshold (#1311):
|
|
354
|
+
//
|
|
355
|
+
// - a SEQUENCE may sit at the key's own column (valid YAML, and what
|
|
356
|
+
// kubectl and Kubernetes manifests emit);
|
|
357
|
+
// - a MAPPING must be indented past it, otherwise the next line is a
|
|
358
|
+
// sibling key and this key's value is null:
|
|
359
|
+
//
|
|
360
|
+
// - name: a
|
|
361
|
+
// meta: <- no value
|
|
362
|
+
// other: b <- a sibling, NOT meta's content
|
|
363
|
+
//
|
|
364
|
+
// Testing both against `>= keyIndent` would swallow that sibling; testing
|
|
365
|
+
// both against `> keyIndent` loses the same-column sequence.
|
|
353
366
|
const nextIdx = currentIndex + 1;
|
|
354
367
|
if (nextIdx < lines.length) {
|
|
355
368
|
const nextLine = lines[nextIdx];
|
|
356
369
|
if (nextLine.trim() !== "" && !nextLine.trim().startsWith("#")) {
|
|
357
370
|
const ni = nextLine.search(/\S/);
|
|
358
|
-
if (
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
}
|
|
371
|
+
if (nextLine.trimStart().startsWith("- ")) {
|
|
372
|
+
if (ni >= keyIndent) return parseYAMLArray(lines, nextIdx, ni).value;
|
|
373
|
+
} else if (ni > keyIndent) {
|
|
362
374
|
return parseYAMLLines(lines, nextIdx, ni).value;
|
|
363
375
|
}
|
|
364
376
|
}
|
|
@@ -366,6 +378,37 @@ function parseArrayItemValue(
|
|
|
366
378
|
return null;
|
|
367
379
|
}
|
|
368
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Skip past the value block belonging to a key at column `keyIndent` inside a
|
|
383
|
+
* sequence item, returning the first line that is NOT part of it (#1311).
|
|
384
|
+
*
|
|
385
|
+
* Two shapes, and only the first is a matter of indentation:
|
|
386
|
+
*
|
|
387
|
+
* - a nested MAPPING is indented past its key, so anything further right
|
|
388
|
+
* belongs to it and anything at the key's own column is a sibling;
|
|
389
|
+
* - a nested SEQUENCE may sit at the SAME column as its key, which is valid
|
|
390
|
+
* YAML and what kubectl and Kubernetes manifests both emit:
|
|
391
|
+
*
|
|
392
|
+
* - name: web
|
|
393
|
+
* ports:
|
|
394
|
+
* - containerPort: 80
|
|
395
|
+
* env: <- a sibling, at `ports`' own column
|
|
396
|
+
*
|
|
397
|
+
* An indent rule cannot separate those, so the sequence is re-parsed to
|
|
398
|
+
* find where it ends. `parseYAMLArray` already reports that as `endIndex`.
|
|
399
|
+
*/
|
|
400
|
+
function skipValueBlock(lines: string[], startIndex: number, keyIndent: number): number {
|
|
401
|
+
let k = startIndex;
|
|
402
|
+
while (k < lines.length && (lines[k].trim() === "" || lines[k].trim().startsWith("#"))) k++;
|
|
403
|
+
if (k < lines.length) {
|
|
404
|
+
const ni = lines[k].search(/\S/);
|
|
405
|
+
if (ni >= keyIndent && lines[k].trimStart().startsWith("- ")) {
|
|
406
|
+
return parseYAMLArray(lines, k, ni).endIndex;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return skipNestedBlock(lines, startIndex, keyIndent + 1);
|
|
410
|
+
}
|
|
411
|
+
|
|
369
412
|
/**
|
|
370
413
|
* Skip past a nested block (object or array) starting at startIndex with the given indent.
|
|
371
414
|
* Returns the index of the first line that is NOT part of the nested block.
|
|
@@ -420,7 +463,19 @@ export function parseYAMLArray(
|
|
|
420
463
|
const nextIndent = indent + 2;
|
|
421
464
|
const firstVal = kvMatch[2].trim();
|
|
422
465
|
let j = firstVal === "" || firstVal.startsWith("#")
|
|
423
|
-
|
|
466
|
+
// The nested block belongs to THIS key and is indented past it, so
|
|
467
|
+
// skip lines indented more than the key's own column (#1311). Using
|
|
468
|
+
// the key's column itself also swallowed the item's sibling keys,
|
|
469
|
+
// which sit at exactly that column:
|
|
470
|
+
//
|
|
471
|
+
// - context: <- key at column 2
|
|
472
|
+
// cluster: c1 <- its block, column 4
|
|
473
|
+
// name: n1 <- a SIBLING at column 2, was skipped
|
|
474
|
+
//
|
|
475
|
+
// Only the item's first key was affected: the sibling loop below
|
|
476
|
+
// already skips past its own key's column, and the block-scalar
|
|
477
|
+
// branch immediately below has always used `+ 1` for this reason.
|
|
478
|
+
? skipValueBlock(lines, i + 1, nextIndent)
|
|
424
479
|
: blockScalarHeader(firstVal)
|
|
425
480
|
// Block body is indented past the key (nextIndent); skip it (#910).
|
|
426
481
|
? skipNestedBlock(lines, i + 1, nextIndent + 1)
|
|
@@ -437,9 +492,11 @@ export function parseYAMLArray(
|
|
|
437
492
|
const nextKV = nextLine.match(/^(\s*)([^\s:][^:]*?):\s*(.*)$/);
|
|
438
493
|
if (nextKV) {
|
|
439
494
|
const nextVal = nextKV[3].trim();
|
|
440
|
-
obj[nextKV[2].trim()] = parseArrayItemValue(nextVal, lines, j, ni
|
|
495
|
+
obj[nextKV[2].trim()] = parseArrayItemValue(nextVal, lines, j, ni);
|
|
441
496
|
if (nextVal === "" || nextVal.startsWith("#")) {
|
|
442
|
-
|
|
497
|
+
// Same rule as the first key above: past this key's own column,
|
|
498
|
+
// or to the end of a same-column sequence (#1311).
|
|
499
|
+
j = skipValueBlock(lines, j + 1, ni);
|
|
443
500
|
} else if (blockScalarHeader(nextVal)) {
|
|
444
501
|
// Skip the block body (indented past this key at `ni`) (#910).
|
|
445
502
|
j = skipNestedBlock(lines, j + 1, ni + 1);
|