@norskvideo/ctl-dev-kit 0.2.39 → 0.2.40
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/package.json +1 -1
- package/publish/product-channel.ts +64 -16
package/package.json
CHANGED
|
@@ -16,16 +16,72 @@
|
|
|
16
16
|
* are not reliable yet: it skips only the guide half of the gate, must give a
|
|
17
17
|
* reason, and the reason travels into the published row, so nobody reads
|
|
18
18
|
* "passed" off an image whose guides never ran.
|
|
19
|
+
*
|
|
20
|
+
* Self-contained on purpose: it imports nothing outside node and this package.
|
|
21
|
+
* Products resolve the dev-kit's siblings from their OWN lockfiles, so a sibling
|
|
22
|
+
* import here bound the writer to whatever schema version a product happened to
|
|
23
|
+
* lock -- the first gated publish in every product died on a missing export.
|
|
24
|
+
* The object's contract is @norskvideo/ctl-product-template-schema's
|
|
25
|
+
* product-channels; product-channel.test.ts parses this module's output with it.
|
|
19
26
|
*/
|
|
20
27
|
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
-
import {
|
|
22
|
-
type ProductChannelRow,
|
|
23
|
-
ProductChannelSchema,
|
|
24
|
-
parseProductChannels,
|
|
25
|
-
withChannelRow,
|
|
26
|
-
} from "@norskvideo/ctl-product-template-schema/product-channels";
|
|
27
28
|
import { builtImageName } from "../licence/check-template.ts";
|
|
28
29
|
|
|
30
|
+
const CHANNELS = ["nightly", "rc", "latest"] as const;
|
|
31
|
+
type Channel = (typeof CHANNELS)[number];
|
|
32
|
+
|
|
33
|
+
export interface ProductChannelRow {
|
|
34
|
+
image: string;
|
|
35
|
+
sha: string;
|
|
36
|
+
publishedAt: string;
|
|
37
|
+
runtimeImages: string[];
|
|
38
|
+
gate: { checks: "passed"; guides: "passed" | { skipped: string }; run?: string };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ProductChannels {
|
|
42
|
+
schemaVersion: 1;
|
|
43
|
+
product: string;
|
|
44
|
+
imageRef: string;
|
|
45
|
+
updated: string;
|
|
46
|
+
channels: Partial<Record<Channel, unknown>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The current object, checked only as far as the merge needs: other channels'
|
|
50
|
+
* rows are carried through untouched, so a newer writer's fields survive. */
|
|
51
|
+
function parseCurrent(text: string): ProductChannels {
|
|
52
|
+
const raw = JSON.parse(text) as Partial<ProductChannels>;
|
|
53
|
+
if (raw.schemaVersion !== 1) throw new Error(`channels object has schemaVersion ${raw.schemaVersion}, not 1`);
|
|
54
|
+
if (typeof raw.product !== "string" || typeof raw.imageRef !== "string") {
|
|
55
|
+
throw new Error("channels object has no product/imageRef");
|
|
56
|
+
}
|
|
57
|
+
if (typeof raw.channels !== "object" || raw.channels === null) throw new Error("channels object has no channels");
|
|
58
|
+
return raw as ProductChannels;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `current` with `channel` set to `row`; every other channel is kept. */
|
|
62
|
+
export function withChannelRow(
|
|
63
|
+
current: ProductChannels | null,
|
|
64
|
+
opts: { product: string; imageRef: string; channel: Channel; row: ProductChannelRow; updated: string },
|
|
65
|
+
): ProductChannels {
|
|
66
|
+
if (current && (current.product !== opts.product || current.imageRef !== opts.imageRef)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`channels object belongs to ${current.product} (${current.imageRef}), not ${opts.product} (${opts.imageRef})`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
product: opts.product,
|
|
74
|
+
imageRef: opts.imageRef,
|
|
75
|
+
updated: opts.updated,
|
|
76
|
+
channels: { ...(current?.channels ?? {}), [opts.channel]: opts.row },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseChannel(value: string): Channel {
|
|
81
|
+
if (!(CHANNELS as readonly string[]).includes(value)) throw new Error(`not a channel: ${value}`);
|
|
82
|
+
return value as Channel;
|
|
83
|
+
}
|
|
84
|
+
|
|
29
85
|
export type GuideGate = { run: true } | { run: false; reason: string };
|
|
30
86
|
|
|
31
87
|
/** Whether the guides gate this product's publish, from its opt-out file. */
|
|
@@ -106,20 +162,12 @@ if (import.meta.main) {
|
|
|
106
162
|
console.log(productForImage(readFileSync(need("template"), "utf8"), need("image")));
|
|
107
163
|
} else if (cmd === "row") {
|
|
108
164
|
const currentText = readIfPresent(flag("current"));
|
|
109
|
-
|
|
110
|
-
if (currentText !== undefined) {
|
|
111
|
-
const parsed = parseProductChannels(JSON.parse(currentText));
|
|
112
|
-
if (!parsed.ok) {
|
|
113
|
-
console.error(`product-channel row: the current object does not parse:\n${parsed.error}`);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
current = parsed.value;
|
|
117
|
-
}
|
|
165
|
+
const current = currentText === undefined ? null : parseCurrent(currentText);
|
|
118
166
|
const skipped = flag("skipped");
|
|
119
167
|
const merged = withChannelRow(current, {
|
|
120
168
|
product: need("product"),
|
|
121
169
|
imageRef: need("image-ref"),
|
|
122
|
-
channel:
|
|
170
|
+
channel: parseChannel(need("channel")),
|
|
123
171
|
updated: need("published-at"),
|
|
124
172
|
row: gateRow({
|
|
125
173
|
image: need("image"),
|