@dogsbay/adapter-tigera 0.0.0-stage → 0.2.0-beta.136
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/data-components.d.ts +80 -0
- package/dist/data-components.js +520 -0
- package/dist/includes.d.ts +22 -0
- package/dist/includes.js +29 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +154 -0
- package/dist/structural.d.ts +49 -0
- package/dist/structural.js +243 -0
- package/dist/variables.d.ts +18 -0
- package/dist/variables.js +80 -0
- package/package.json +30 -3
- package/README.md +0 -3
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tigera's data components as template partials over their JSON (plans/page-templates.md, "Data
|
|
3
|
+
* components in the Docusaurus importer"). Each template reproduces what the React component
|
|
4
|
+
* renders, from the same file, which is copied into the site's `data/`:
|
|
5
|
+
* - `<FelixConfig configType name />` over `_includes/components/FelixConfig/config-params.json`
|
|
6
|
+
* (identical in every product and version);
|
|
7
|
+
* - `<ComponentVersions />` over the docs folder's `releases.json` (the `variables.js` list),
|
|
8
|
+
* in two variants: Calico links the GitHub release archive, Calico Enterprise its own.
|
|
9
|
+
* - `<ReleaseArchiveTable />` (Calico Enterprise) over the same `releases.json`.
|
|
10
|
+
* - `<PrivateRegistryRegular />` and `<PrivateRegistryImagePath />` (Calico Enterprise): the
|
|
11
|
+
* `docker pull`/`tag`/`push` and `crane cp` commands for the first release's images, over
|
|
12
|
+
* the same `releases.json`, with `registry` and the product names from variables.js and the
|
|
13
|
+
* Installation `variant` read from the component's own source (it changed in 3.23-2).
|
|
14
|
+
* - `<VersionedCloudImageList />` (Calico Cloud): the Helm commands for each Calico Cloud
|
|
15
|
+
* version's images, from `src/___new___/data/ccImageLists.js` (a data MODULE, read as data and
|
|
16
|
+
* written as JSON). The component is a version select; a static page shows every version as
|
|
17
|
+
* a tab, the first (the select's default) first.
|
|
18
|
+
* - `<MaintenanceImageOptionsAlternateRegistry />` (Calico): the pull/tag/push and `crane cp`
|
|
19
|
+
* lines for the first release's images named in variables.js's `imageNames`.
|
|
20
|
+
* - `<InstallOpenShiftManifests />` (Calico 3.30 on): the download with its `--exclude` flags,
|
|
21
|
+
* then Tigera's own `<ConfigDataplaneOpenShiftManifests />`, included as converted.
|
|
22
|
+
* - `<FossaReport />` (Calico Enterprise): a link to the attribution report.
|
|
23
|
+
* Paths are relative to the docs folder imported, so each version reads its own copy.
|
|
24
|
+
*
|
|
25
|
+
* Fidelity notes, against the components:
|
|
26
|
+
* - A `name` that matches no group says so, as the component does: Tigera's resource pages call
|
|
27
|
+
* `Data plane: Common` where the JSON says `Dataplane: Common`, and its site shows the message.
|
|
28
|
+
* - Descriptions and schemas are the Markdown fields (`Description`, `StringSchema`), not the
|
|
29
|
+
* `…HTML` copies the components inject. A description's lines join into one table cell,
|
|
30
|
+
* as Tigera's own HTML shows them (its lists and links are literal text there too).
|
|
31
|
+
* - Heading anchors keep the components' ids: the field name, and `toKebab` (the FIRST space
|
|
32
|
+
* replaced, lowercased) for a release, written by `| heading_id` (a `{#{{` would open a
|
|
33
|
+
* template comment). Values print through template pages' Markdown-aware filters (`| text`,
|
|
34
|
+
* `| code`, `| cell`, `| href`), so a value from the JSON cannot add structure.
|
|
35
|
+
* - The private-registry commands are exactly the components' text, faults included: in the
|
|
36
|
+
* image-path variant the operator's `docker push` has no line break, so the first image's
|
|
37
|
+
* push joins its line, and the Windows `crane cp` lines use the global `registry`, not the
|
|
38
|
+
* image's own; the `sed … /a` lines show no backslash before their spaces (`\ ` in a JS
|
|
39
|
+
* template literal is a space). A code block written outside a list item is drawn in the item before it;
|
|
40
|
+
* `// highlight-next-line` becomes the line highlight it means. A value in a code block is
|
|
41
|
+
* printed on one line, so it cannot close the fence.
|
|
42
|
+
*/
|
|
43
|
+
import { type PreprocessContext, type TemplateComponent } from "@dogsbay/format-docusaurus/adapter";
|
|
44
|
+
/** `<ComponentVersions />` for the product being imported, with its name (and downloads URL) from variables.js. */
|
|
45
|
+
declare function componentVersions(ctx: PreprocessContext): TemplateComponent | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* `<VersionedCloudImageList />` (Calico Cloud). The component reads the CURRENT product's
|
|
48
|
+
* variables.js (`calico-cloud/variables`) on every version, so this does too. Converted only when
|
|
49
|
+
* every version label and image line is a plain image reference: a value then cannot end its
|
|
50
|
+
* code block or add a line. Anything else keeps the placeholder.
|
|
51
|
+
*/
|
|
52
|
+
declare function cloudImageList(ctx: PreprocessContext): TemplateComponent | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* `<MaintenanceImageOptionsAlternateRegistry />` (Calico): the first release's images pulled, retagged
|
|
55
|
+
* and pushed to `$REGISTRY` (Windows ones by `crane cp`), each under its name in variables.js's
|
|
56
|
+
* `imageNames`, which is written into the partial as a dict. Names and the registry must be plain
|
|
57
|
+
* image references; anything else keeps the placeholder.
|
|
58
|
+
*/
|
|
59
|
+
declare function alternateRegistry(ctx: PreprocessContext): TemplateComponent | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* `<InstallOpenShiftManifests />` (Calico 3.30 on): the download, with `--exclude` for each file of
|
|
62
|
+
* `filesToExclude` (a list the component builds in a loop, which the JSX converter cannot read),
|
|
63
|
+
* then `<ConfigDataplaneOpenShiftManifests folderName="manifests" />` unless
|
|
64
|
+
* `skipDataplaneConfigSteps`, INCLUDED as the converter writes it, so its text is Tigera's own.
|
|
65
|
+
* Only for the component's source in that shape (READ as text); any other version or product
|
|
66
|
+
* (3.29, Calico Enterprise) converts as it is.
|
|
67
|
+
*/
|
|
68
|
+
declare function openshiftManifests(ctx: PreprocessContext): TemplateComponent | undefined;
|
|
69
|
+
export declare const TIGERA_DATA_COMPONENTS: {
|
|
70
|
+
MaintenanceImageOptionsAlternateRegistry: typeof alternateRegistry;
|
|
71
|
+
InstallOpenShiftManifests: typeof openshiftManifests;
|
|
72
|
+
FossaReport: TemplateComponent;
|
|
73
|
+
FelixConfig: TemplateComponent;
|
|
74
|
+
ComponentVersions: typeof componentVersions;
|
|
75
|
+
ReleaseArchiveTable: TemplateComponent;
|
|
76
|
+
PrivateRegistryRegular: (ctx: PreprocessContext) => TemplateComponent | undefined;
|
|
77
|
+
VersionedCloudImageList: typeof cloudImageList;
|
|
78
|
+
PrivateRegistryImagePath: (ctx: PreprocessContext) => TemplateComponent | undefined;
|
|
79
|
+
};
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tigera's data components as template partials over their JSON (plans/page-templates.md, "Data
|
|
3
|
+
* components in the Docusaurus importer"). Each template reproduces what the React component
|
|
4
|
+
* renders, from the same file, which is copied into the site's `data/`:
|
|
5
|
+
* - `<FelixConfig configType name />` over `_includes/components/FelixConfig/config-params.json`
|
|
6
|
+
* (identical in every product and version);
|
|
7
|
+
* - `<ComponentVersions />` over the docs folder's `releases.json` (the `variables.js` list),
|
|
8
|
+
* in two variants: Calico links the GitHub release archive, Calico Enterprise its own.
|
|
9
|
+
* - `<ReleaseArchiveTable />` (Calico Enterprise) over the same `releases.json`.
|
|
10
|
+
* - `<PrivateRegistryRegular />` and `<PrivateRegistryImagePath />` (Calico Enterprise): the
|
|
11
|
+
* `docker pull`/`tag`/`push` and `crane cp` commands for the first release's images, over
|
|
12
|
+
* the same `releases.json`, with `registry` and the product names from variables.js and the
|
|
13
|
+
* Installation `variant` read from the component's own source (it changed in 3.23-2).
|
|
14
|
+
* - `<VersionedCloudImageList />` (Calico Cloud): the Helm commands for each Calico Cloud
|
|
15
|
+
* version's images, from `src/___new___/data/ccImageLists.js` (a data MODULE, read as data and
|
|
16
|
+
* written as JSON). The component is a version select; a static page shows every version as
|
|
17
|
+
* a tab, the first (the select's default) first.
|
|
18
|
+
* - `<MaintenanceImageOptionsAlternateRegistry />` (Calico): the pull/tag/push and `crane cp`
|
|
19
|
+
* lines for the first release's images named in variables.js's `imageNames`.
|
|
20
|
+
* - `<InstallOpenShiftManifests />` (Calico 3.30 on): the download with its `--exclude` flags,
|
|
21
|
+
* then Tigera's own `<ConfigDataplaneOpenShiftManifests />`, included as converted.
|
|
22
|
+
* - `<FossaReport />` (Calico Enterprise): a link to the attribution report.
|
|
23
|
+
* Paths are relative to the docs folder imported, so each version reads its own copy.
|
|
24
|
+
*
|
|
25
|
+
* Fidelity notes, against the components:
|
|
26
|
+
* - A `name` that matches no group says so, as the component does: Tigera's resource pages call
|
|
27
|
+
* `Data plane: Common` where the JSON says `Dataplane: Common`, and its site shows the message.
|
|
28
|
+
* - Descriptions and schemas are the Markdown fields (`Description`, `StringSchema`), not the
|
|
29
|
+
* `…HTML` copies the components inject. A description's lines join into one table cell,
|
|
30
|
+
* as Tigera's own HTML shows them (its lists and links are literal text there too).
|
|
31
|
+
* - Heading anchors keep the components' ids: the field name, and `toKebab` (the FIRST space
|
|
32
|
+
* replaced, lowercased) for a release, written by `| heading_id` (a `{#{{` would open a
|
|
33
|
+
* template comment). Values print through template pages' Markdown-aware filters (`| text`,
|
|
34
|
+
* `| code`, `| cell`, `| href`), so a value from the JSON cannot add structure.
|
|
35
|
+
* - The private-registry commands are exactly the components' text, faults included: in the
|
|
36
|
+
* image-path variant the operator's `docker push` has no line break, so the first image's
|
|
37
|
+
* push joins its line, and the Windows `crane cp` lines use the global `registry`, not the
|
|
38
|
+
* image's own; the `sed … /a` lines show no backslash before their spaces (`\ ` in a JS
|
|
39
|
+
* template literal is a space). A code block written outside a list item is drawn in the item before it;
|
|
40
|
+
* `// highlight-next-line` becomes the line highlight it means. A value in a code block is
|
|
41
|
+
* printed on one line, so it cannot close the fence.
|
|
42
|
+
*/
|
|
43
|
+
import { readDataModule } from "@dogsbay/format-docusaurus/adapter";
|
|
44
|
+
import { loadVariables } from "./variables.js";
|
|
45
|
+
/**
|
|
46
|
+
* A value as inline code (`| code`: one line, in a fence its backticks cannot close). Template
|
|
47
|
+
* pages never evaluate inside a code span written in the page, so the filter writes the span.
|
|
48
|
+
*/
|
|
49
|
+
const code = (expr) => `{{ (${expr}) | code }}`;
|
|
50
|
+
/** The same in a table cell (`| cell`: one line, `|` escaped). */
|
|
51
|
+
const codeCell = (expr) => `{{ (${expr}) | code | cell }}`;
|
|
52
|
+
/** A heading's `{#id}` from a value (`| heading_id`: nothing in it can end the block). */
|
|
53
|
+
const headingId = (expr) => `{{ (${expr}) | heading_id }}`;
|
|
54
|
+
/** One Felix field's table, for the configuration file or the environment variable. */
|
|
55
|
+
const felixTable = (key) => `| Attribute | Value |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Key | ${codeCell(key)} |
|
|
58
|
+
| Description | {{ f.Description | cell }} |
|
|
59
|
+
| Schema | {{ f.StringSchema | cell }} |
|
|
60
|
+
| Default | {% if f.StringDefault == "" %}none{% elif f.GoType == "*v1.Duration" %}${codeCell("f.StringDefault")} ({{ f.ParsedDefault | text }}){% else %}${codeCell("f.StringDefault")}{% endif %} |`;
|
|
61
|
+
const FELIX_TEXT = `{%- set g = data.felix_config.Groups | selectattr("Name", "equalto", name) | first -%}
|
|
62
|
+
{%- if not g -%}
|
|
63
|
+
No matching group found for '{{ name | text }}'.
|
|
64
|
+
{%- elif config_type == "yaml" -%}
|
|
65
|
+
{%- for f in g.Fields -%}
|
|
66
|
+
{%- if f.NameYAML %}
|
|
67
|
+
|
|
68
|
+
##### ${code("f.NameYAML")} ${headingId("f.NameYAML")}
|
|
69
|
+
|
|
70
|
+
| Attribute | Value |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Key | ${codeCell("f.NameYAML")} |
|
|
73
|
+
| Description | {{ f.Description | cell }} |
|
|
74
|
+
| Schema | {{ f.YAMLSchema | cell }} |
|
|
75
|
+
| Default | {% if f.YAMLDefault == "" %}none{% else %}${codeCell("f.YAMLDefault")}{% endif %} |
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
{%- endfor %}
|
|
78
|
+
{%- elif config_type == "configenv" -%}
|
|
79
|
+
{%- for f in g.Fields %}
|
|
80
|
+
|
|
81
|
+
#### ${code("f.NameConfigFile")} ${headingId("f.NameConfigFile")}
|
|
82
|
+
|
|
83
|
+
:::tabs{groupId="operating-systems"}
|
|
84
|
+
Configuration file
|
|
85
|
+
: ${felixTable("f.NameConfigFile")}
|
|
86
|
+
|
|
87
|
+
Environment variable
|
|
88
|
+
: ${felixTable("f.NameEnvVar | upper")}
|
|
89
|
+
:::
|
|
90
|
+
{%- endfor %}
|
|
91
|
+
{%- else -%}
|
|
92
|
+
You got it wrong
|
|
93
|
+
{%- endif %}
|
|
94
|
+
`;
|
|
95
|
+
const FELIX = {
|
|
96
|
+
name: "felix-config",
|
|
97
|
+
text: FELIX_TEXT,
|
|
98
|
+
params: { configType: "config_type", name: "name" },
|
|
99
|
+
data: { "felix-config.json": "_includes/components/FelixConfig/config-params.json" },
|
|
100
|
+
};
|
|
101
|
+
/** The release's version as the heading shows it: a leading `v` dropped. */
|
|
102
|
+
const SHOWN = `release.title[1:] if release.title.startswith("v") else release.title`;
|
|
103
|
+
/** The heading's id, Tigera's `toKebab` of the title: the first space replaced, lowercased. */
|
|
104
|
+
const KEBAB = headingId(`"component-versions-" ~ release.title.replace(" ", "-", 1).lower()`);
|
|
105
|
+
/** The operator image a release installs, as inline code. */
|
|
106
|
+
const OPERATOR = code(`release["tigera-operator"].registry ~ "/" ~ release["tigera-operator"].image ~ ":" ~ release["tigera-operator"].version`);
|
|
107
|
+
const versionsText = (archive) => `{%- for release in data.releases %}
|
|
108
|
+
|
|
109
|
+
## Component versions for {{ prodname | text }} {{ (${SHOWN}) | replace("\\n", " ") | text }} ${KEBAB}
|
|
110
|
+
|
|
111
|
+
{% if release.title != "master" %}${archive}
|
|
112
|
+
|
|
113
|
+
{% endif %}This release comprises the following components, and can be installed using ${OPERATOR}
|
|
114
|
+
|
|
115
|
+
| Component | Version |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
{% for name, c in release.components | items %}| {{ name | text }} | {{ c.version | text }} |
|
|
118
|
+
{% endfor %}
|
|
119
|
+
{%- endfor %}
|
|
120
|
+
`;
|
|
121
|
+
const CALICO_ARCHIVE = `[Release archive]({{ ("https://github.com/projectcalico/calico/releases/download/" ~ release.title ~ "/release-" ~ release.title ~ ".tgz") | href }}) with Kubernetes manifests.`;
|
|
122
|
+
const ENTERPRISE_ARCHIVE = `[Release archive]({{ (downloadsurl ~ "/ee/archives/release-" ~ release.title ~ "-" ~ release["tigera-operator"].version ~ ".tgz") | href }}) with Kubernetes manifests. Based on Calico {{ data.releases[0].calico.minor_version | replace("\\n", " ") | text }}.`;
|
|
123
|
+
/** `<ComponentVersions />` for the product being imported, with its name (and downloads URL) from variables.js. */
|
|
124
|
+
function componentVersions(ctx) {
|
|
125
|
+
const vars = loadVariables(ctx);
|
|
126
|
+
const prodname = typeof vars.prodname === "string" ? vars.prodname : undefined;
|
|
127
|
+
if (prodname === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
const data = { "releases.json": "releases.json" };
|
|
130
|
+
if (/^calico-enterprise(_versioned_docs\/|$)/.test(ctx.instanceRoot ?? "")) {
|
|
131
|
+
const downloadsurl = typeof vars.downloadsurl === "string" ? vars.downloadsurl : undefined;
|
|
132
|
+
if (downloadsurl === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
return { name: "component-versions-enterprise", text: versionsText(ENTERPRISE_ARCHIVE), values: { prodname, downloadsurl }, data };
|
|
135
|
+
}
|
|
136
|
+
return { name: "component-versions", text: versionsText(CALICO_ARCHIVE), values: { prodname }, data };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* `<ReleaseArchiveTable />` (Calico Enterprise): every release's archive, from `releases.json`.
|
|
140
|
+
* The URL is the one the component builds (downloads.tigera.io, not `downloadsurl`), shown as
|
|
141
|
+
* the link's text as on the site.
|
|
142
|
+
*/
|
|
143
|
+
const RELEASE_ARCHIVES = {
|
|
144
|
+
name: "release-archive-table",
|
|
145
|
+
text: `| Patch version | Release archive link |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
{% for release in data.releases -%}
|
|
148
|
+
{%- set url = "https://downloads.tigera.io/ee/archives/release-" ~ release.title ~ "-" ~ release["tigera-operator"].version ~ ".tgz" -%}
|
|
149
|
+
| {{ release.title | text }} | [{{ url | text }}]({{ url | href }}) |
|
|
150
|
+
{% endfor %}`,
|
|
151
|
+
data: { "releases.json": "releases.json" },
|
|
152
|
+
};
|
|
153
|
+
/** A value in a code block: one line, so nothing in it can close the fence. */
|
|
154
|
+
const line = (expr) => `{{ (${expr}) | string | replace("\\r", " ") | replace("\\n", " ") }}`;
|
|
155
|
+
/** The image's registry, as the component's `mapComponentToRegistry`: its own, else variables.js's. */
|
|
156
|
+
const REGISTRY = `((c.registry ~ "/") if c.registry is defined and c.registry else registry)`;
|
|
157
|
+
/** The images the commands cover: each component with one, Windows ones (`-windows`) apart. */
|
|
158
|
+
const LINUX = `{% for _, c in data.releases[0].components | items %}{% if c.image is defined and c.image and "-windows" not in c.image %}`;
|
|
159
|
+
const WINDOWS = `{% for _, c in data.releases[0].components | items %}{% if c.image is defined and c.image and "-windows" in c.image %}`;
|
|
160
|
+
const END = `{% endif %}{% endfor %}`;
|
|
161
|
+
const OP = `data.releases[0]["tigera-operator"]`;
|
|
162
|
+
const IMAGE = `c.image ~ ":" ~ c.version`;
|
|
163
|
+
const NAME = `(c.image.split("/") | last) ~ ":" ~ c.version`;
|
|
164
|
+
const OP_NAME = `(${OP}.image.split("/") | last) ~ ":" ~ ${OP}.version`;
|
|
165
|
+
const OP_SOURCE = `${OP}.registry ~ "/" ~ ${OP}.image ~ ":" ~ ${OP}.version`;
|
|
166
|
+
const PULL = (indent) => `${indent}\`\`\`bash {minja}
|
|
167
|
+
${indent}docker pull ${line(OP_SOURCE)}
|
|
168
|
+
${indent}${LINUX}docker pull ${line(`${REGISTRY} ~ ${IMAGE}`)}
|
|
169
|
+
${indent}${END}\`\`\``;
|
|
170
|
+
const installation = (imagePath) => `\`\`\`yaml {highlights="${imagePath ? "9-10" : "9"}" minja}
|
|
171
|
+
apiVersion: operator.tigera.io/v1
|
|
172
|
+
kind: Installation
|
|
173
|
+
metadata:
|
|
174
|
+
name: default
|
|
175
|
+
spec:
|
|
176
|
+
variant: ${line("variant")}
|
|
177
|
+
imagePullSecrets:
|
|
178
|
+
- name: tigera-pull-secret
|
|
179
|
+
registry: myregistry.com${imagePath ? "\n imagePath: my-image-path" : ""}
|
|
180
|
+
\`\`\``;
|
|
181
|
+
const PULL_SECRET = `Next, ensure that an image pull secret has been configured for your custom registry. Set the enviroment variable \`PRIVATE_REGISTRY_PULL_SECRET\` to the secret name. Then add the image pull secret to the operator deployment spec:
|
|
182
|
+
|
|
183
|
+
\`\`\`bash
|
|
184
|
+
sed -ie "/serviceAccountName: tigera-operator/a imagePullSecrets:\\n - name: $PRIVATE_REGISTRY_PULL_SECRET" tigera-operator.yaml
|
|
185
|
+
\`\`\``;
|
|
186
|
+
const PROMETHEUS = (sub) => `If you are installing Prometheus operator as part of {{ prodname | text }}, then before applying \`tigera-prometheus-operator.yaml\`, modify registry references to use your custom registry:
|
|
187
|
+
|
|
188
|
+
\`\`\`bash
|
|
189
|
+
sed -ie "${sub}" tigera-prometheus-operator.yaml
|
|
190
|
+
sed -ie "/serviceAccountName: calico-prometheus-operator/a imagePullSecrets:\\n - name: $PRIVATE_REGISTRY_PULL_SECRET" tigera-prometheus-operator.yaml
|
|
191
|
+
\`\`\``;
|
|
192
|
+
const INTRO = `In order to install images from your private registry, you must first pull the images from Tigera's registry, re-tag them with your own registry, and then push the newly tagged images to your own registry.`;
|
|
193
|
+
const REGULAR_TEXT = `#### Push {{ prodname | text }} images to your private registry ${headingId(`"push-" ~ prodnamedash ~ "-images-to-your-private-registry"`)}
|
|
194
|
+
|
|
195
|
+
${INTRO}
|
|
196
|
+
|
|
197
|
+
1. Use the following commands to pull the required {{ prodname | text }} images.
|
|
198
|
+
|
|
199
|
+
${PULL(" ")}
|
|
200
|
+
|
|
201
|
+
2. Retag the images with the name of your private registry \`$PRIVATE_REGISTRY\`.
|
|
202
|
+
|
|
203
|
+
\`\`\`bash {minja}
|
|
204
|
+
docker tag ${line(OP_SOURCE)} $PRIVATE_REGISTRY/${line(`${OP}.image ~ ":" ~ ${OP}.version`)}
|
|
205
|
+
${LINUX}docker tag ${line(`${REGISTRY} ~ ${IMAGE}`)} $PRIVATE_REGISTRY/${line(IMAGE)}
|
|
206
|
+
${END}\`\`\`
|
|
207
|
+
|
|
208
|
+
3. Push the images to your private registry.
|
|
209
|
+
|
|
210
|
+
\`\`\`bash {minja}
|
|
211
|
+
docker push $PRIVATE_REGISTRY/${line(`${OP}.image ~ ":" ~ ${OP}.version`)}
|
|
212
|
+
${LINUX}docker push $PRIVATE_REGISTRY/${line(IMAGE)}
|
|
213
|
+
${END}\`\`\`
|
|
214
|
+
|
|
215
|
+
:::warning
|
|
216
|
+
Do not push the private {{ prodname | text }} images to a public registry.
|
|
217
|
+
:::
|
|
218
|
+
|
|
219
|
+
4. Use \`crane cp\` to copy the Windows images to your private registry.
|
|
220
|
+
|
|
221
|
+
For hybrid Linux + Windows clusters, use \`crane cp\` on the following Windows images to copy them to your private registry.
|
|
222
|
+
|
|
223
|
+
\`\`\`bash {minja}
|
|
224
|
+
${WINDOWS}crane cp ${line(`${REGISTRY} ~ ${IMAGE}`)} $PRIVATE_REGISTRY/$IMAGE_PATH/${line(NAME)}
|
|
225
|
+
${END}\`\`\`
|
|
226
|
+
|
|
227
|
+
:::warning
|
|
228
|
+
Do not \`crane cp\` the private {{ prodname_windows | text }} images to a public registry.
|
|
229
|
+
:::
|
|
230
|
+
|
|
231
|
+
#### Run the operator using images from your private registry {#run-the-operator-using-images-from-your-private-registry}
|
|
232
|
+
|
|
233
|
+
Before applying \`tigera-operator.yaml\`, modify registry references to use your custom registry:
|
|
234
|
+
|
|
235
|
+
\`\`\`
|
|
236
|
+
sed -ie "s?quay.io?$PRIVATE_REGISTRY?g" tigera-operator.yaml
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
${PULL_SECRET}
|
|
240
|
+
|
|
241
|
+
${PROMETHEUS("s?quay.io?$PRIVATE_REGISTRY?g")}
|
|
242
|
+
|
|
243
|
+
Before applying \`custom-resources.yaml\`, modify registry references to use your custom registry:
|
|
244
|
+
|
|
245
|
+
\`\`\`bash
|
|
246
|
+
sed -ie "s?quay.io?$PRIVATE_REGISTRY?g" custom-resources.yaml
|
|
247
|
+
\`\`\`
|
|
248
|
+
|
|
249
|
+
#### Configure the operator to use images from your private registry. {#configure-the-operator-to-use-images-from-your-private-registry}
|
|
250
|
+
|
|
251
|
+
Set the \`spec.registry\` field of your Installation resource to the name of your custom registry. For example:
|
|
252
|
+
|
|
253
|
+
${installation(false)}
|
|
254
|
+
`;
|
|
255
|
+
const IMAGE_PATH_SUB = "s?quay.io.*/?$PRIVATE_REGISTRY/$IMAGE_PATH/?";
|
|
256
|
+
const IMAGE_PATH_TEXT = `### Push {{ prodname | text }} images to your private registry image path ${headingId(`"push-" ~ prodnamedash ~ "-images-to-your-private-registry-image-path"`)}
|
|
257
|
+
|
|
258
|
+
${INTRO}
|
|
259
|
+
|
|
260
|
+
1. Use the following commands to pull the required {{ prodname | text }} images.
|
|
261
|
+
|
|
262
|
+
${PULL(" ")}
|
|
263
|
+
|
|
264
|
+
2. Retag the images with the name of your private registry \`$PRIVATE_REGISTRY\` and \`$IMAGE_PATH\`.
|
|
265
|
+
|
|
266
|
+
\`\`\`bash {minja}
|
|
267
|
+
docker tag ${line(OP_SOURCE)} $PRIVATE_REGISTRY/$IMAGE_PATH/${line(OP_NAME)}
|
|
268
|
+
${LINUX}docker tag ${line(`${REGISTRY} ~ ${IMAGE}`)} $PRIVATE_REGISTRY/$IMAGE_PATH/${line(NAME)}
|
|
269
|
+
${END}\`\`\`
|
|
270
|
+
|
|
271
|
+
3. Push the images to your private registry.
|
|
272
|
+
|
|
273
|
+
\`\`\`bash {minja}
|
|
274
|
+
docker push $PRIVATE_REGISTRY/$IMAGE_PATH/${line(OP_NAME)}${LINUX}docker push $PRIVATE_REGISTRY/$IMAGE_PATH/${line(NAME)}
|
|
275
|
+
${END}\`\`\`
|
|
276
|
+
|
|
277
|
+
:::warning
|
|
278
|
+
Do not push the private {{ prodname | text }} images to a public registry.
|
|
279
|
+
:::
|
|
280
|
+
|
|
281
|
+
4. Use \`crane cp\` to copy the Windows images to your private registry.
|
|
282
|
+
|
|
283
|
+
For hybrid Linux + Windows clusters, use \`crane cp\` on the following Windows images to copy them to your private registry.
|
|
284
|
+
|
|
285
|
+
\`\`\`bash {minja}
|
|
286
|
+
${WINDOWS}crane cp ${line(`registry ~ ${IMAGE}`)} $PRIVATE_REGISTRY/$IMAGE_PATH/${line(NAME)}
|
|
287
|
+
${END}\`\`\`
|
|
288
|
+
|
|
289
|
+
:::warning
|
|
290
|
+
Do not \`crane cp\` the private {{ prodname_windows | text }} images to a public registry.
|
|
291
|
+
:::
|
|
292
|
+
|
|
293
|
+
### Run the operator using images from your private registry image path {#run-the-operator-using-images-from-your-private-registry-image-path}
|
|
294
|
+
|
|
295
|
+
Before applying \`tigera-operator.yaml\`, modify registry references to use your custom registry:
|
|
296
|
+
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
sed -ie "${IMAGE_PATH_SUB}" tigera-operator.yaml
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
${PULL_SECRET}
|
|
302
|
+
|
|
303
|
+
${PROMETHEUS(IMAGE_PATH_SUB)}
|
|
304
|
+
|
|
305
|
+
Before applying \`custom-resources.yaml\`, modify registry references to use your custom registry:
|
|
306
|
+
|
|
307
|
+
\`\`\`bash
|
|
308
|
+
sed -ie "${IMAGE_PATH_SUB}" custom-resources.yaml
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
For **OpenShift**, after downloading all manifests modify the following to use your custom registry:
|
|
312
|
+
|
|
313
|
+
\`\`\`bash
|
|
314
|
+
sed -ie "${IMAGE_PATH_SUB}" manifests/02-tigera-operator.yaml
|
|
315
|
+
\`\`\`
|
|
316
|
+
|
|
317
|
+
:::note
|
|
318
|
+
Add the image pull secret for your \`registry\` to the secret \`tigera-pull-secret\`
|
|
319
|
+
:::
|
|
320
|
+
|
|
321
|
+
### Configure the operator to use images from your private registry image path. {#configure-the-operator-to-use-images-from-your-private-registry-image-path}
|
|
322
|
+
|
|
323
|
+
Set the \`spec.registry\` and \`spec.imagePath\` field of your Installation resource to the name of your custom registry. For example:
|
|
324
|
+
|
|
325
|
+
${installation(true)}
|
|
326
|
+
`;
|
|
327
|
+
/**
|
|
328
|
+
* A private-registry component for the version imported: the names and `registry` from its
|
|
329
|
+
* variables.js, the Installation `variant` from the component's own source (READ as text,
|
|
330
|
+
* never run). Anything missing keeps the placeholder.
|
|
331
|
+
*/
|
|
332
|
+
function privateRegistry(component, name, text) {
|
|
333
|
+
return (ctx) => {
|
|
334
|
+
const vars = loadVariables(ctx);
|
|
335
|
+
const str = (k) => (typeof vars[k] === "string" ? vars[k] : undefined);
|
|
336
|
+
const [prodname, prodnamedash, prodname_windows, registry] = ["prodname", "prodnamedash", "prodnameWindows", "registry"].map(str);
|
|
337
|
+
const root = ctx.instanceRoot ? `${ctx.instanceRoot}/` : "";
|
|
338
|
+
const source = ctx.corpus?.text(`${root}_includes/components/${component}.js`);
|
|
339
|
+
const variant = source && /^[ \t]*variant:[ \t]*([A-Za-z]+)[ \t]*$/m.exec(source)?.[1];
|
|
340
|
+
if (!prodname || !prodnamedash || !prodname_windows || registry === undefined || !variant)
|
|
341
|
+
return undefined;
|
|
342
|
+
return { name, text, values: { prodname, prodnamedash, prodname_windows, registry, variant }, data: { "releases.json": "releases.json" } };
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/** The image lists: `src/___new___/data/ccImageLists.js`, version → one image per line. */
|
|
346
|
+
const CC_IMAGE_LISTS = "src/___new___/data/ccImageLists.js";
|
|
347
|
+
const IMAGE_REF = /^[A-Za-z0-9._/:@-]*$/;
|
|
348
|
+
/** A version label: printed raw in the code block's title, so no quote, brace or line break. */
|
|
349
|
+
const VERSION_LABEL = /^[A-Za-z0-9._()-][A-Za-z0-9._ ()-]{0,59}$/;
|
|
350
|
+
const CLOUD_IMAGES_TEXT = `Select your version:
|
|
351
|
+
|
|
352
|
+
:::tabs
|
|
353
|
+
{%- for version, images in data.cc_image_lists | items %}
|
|
354
|
+
|
|
355
|
+
{{ version | text }}
|
|
356
|
+
: \`\`\`javascript {title="Helm command to add images for Calico Cloud {{ version }}" minja}
|
|
357
|
+
helm repo add calico-cloud ${line("clouddownloadbase")}/charts
|
|
358
|
+
INSTALLER_IMAGE="${line("cloudoperatorimage")}:$(helm show chart calico-cloud/calico-cloud | grep version: | sed -e 's/version: *//' -e 's/+/-g/')"
|
|
359
|
+
IMAGES=(
|
|
360
|
+
$INSTALLER_IMAGE
|
|
361
|
+
{% for image in images.split("\\n") %}${line("image")}
|
|
362
|
+
{% endfor %})
|
|
363
|
+
\`\`\`
|
|
364
|
+
{%- endfor %}
|
|
365
|
+
:::
|
|
366
|
+
`;
|
|
367
|
+
/**
|
|
368
|
+
* `<VersionedCloudImageList />` (Calico Cloud). The component reads the CURRENT product's
|
|
369
|
+
* variables.js (`calico-cloud/variables`) on every version, so this does too. Converted only when
|
|
370
|
+
* every version label and image line is a plain image reference: a value then cannot end its
|
|
371
|
+
* code block or add a line. Anything else keeps the placeholder.
|
|
372
|
+
*/
|
|
373
|
+
function cloudImageList(ctx) {
|
|
374
|
+
const project = ctx.projectRoot ? `${ctx.projectRoot}/` : "";
|
|
375
|
+
const instance = ctx.instanceRoot ?? "";
|
|
376
|
+
if (!ctx.corpus || !/^calico-cloud(_versioned_docs\/|$)/.test(instance.slice(project.length)))
|
|
377
|
+
return undefined;
|
|
378
|
+
const vars = loadVariables({ ...ctx, instanceRoot: `${project}calico-cloud` });
|
|
379
|
+
const [clouddownloadbase, cloudoperatorimage] = ["clouddownloadbase", "cloudoperatorimage"].map((k) => (typeof vars[k] === "string" ? vars[k] : undefined));
|
|
380
|
+
if (!clouddownloadbase || !cloudoperatorimage)
|
|
381
|
+
return undefined;
|
|
382
|
+
const { value, unresolved } = readDataModule(ctx.corpus, `${project}${CC_IMAGE_LISTS}`, { root: ctx.projectRoot ?? "" });
|
|
383
|
+
if (unresolved.length || !value || typeof value !== "object" || Array.isArray(value))
|
|
384
|
+
return undefined;
|
|
385
|
+
const lists = Object.entries(value);
|
|
386
|
+
if (!lists.length || !lists.every(([k, v]) => VERSION_LABEL.test(k) && typeof v === "string" && v.split("\n").every((l) => IMAGE_REF.test(l))))
|
|
387
|
+
return undefined;
|
|
388
|
+
// Data sources are relative to the docs folder: climb out of it to the project, not the corpus
|
|
389
|
+
const up = instance.slice(project.length).split("/").map(() => "..").join("/");
|
|
390
|
+
return {
|
|
391
|
+
name: "versioned-cloud-image-list",
|
|
392
|
+
text: CLOUD_IMAGES_TEXT,
|
|
393
|
+
values: { clouddownloadbase, cloudoperatorimage },
|
|
394
|
+
data: { "cc-image-lists.json": { module: `${up}/${CC_IMAGE_LISTS}` } },
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** An image name or registry from variables.js written into a template: plain characters only. */
|
|
398
|
+
const PLAIN_NAME = /^[A-Za-z0-9._/:@-]+$/;
|
|
399
|
+
/** An image the alternate-registry commands cover, as `renderRetagCommands`'s filters: a name in `imageNames`, no flannel. */
|
|
400
|
+
const NAMED = `{% for key, c in data.releases[0].components | items %}{% if key in names and names[key] and "flannel" not in key and`;
|
|
401
|
+
const NAMED_IMAGE = `names[key] ~ ":" ~ c.version`;
|
|
402
|
+
const ALTERNATE_REGISTRY_TEXT = (names) => `{%- set names = ${names} %}
|
|
403
|
+
1. Use the following commands to pull the required {{ prodname | text }} images.
|
|
404
|
+
|
|
405
|
+
\`\`\`bash {minja}
|
|
406
|
+
docker pull ${line(OP_SOURCE)}
|
|
407
|
+
${NAMED} "-windows" not in key %}docker pull ${line(`${REGISTRY} ~ ${NAMED_IMAGE}`)}
|
|
408
|
+
${END}\`\`\`
|
|
409
|
+
|
|
410
|
+
2. Retag the images with the name of your registry \`$REGISTRY\`.
|
|
411
|
+
|
|
412
|
+
\`\`\`bash {minja}
|
|
413
|
+
docker tag ${line(OP_SOURCE)} $REGISTRY/${line(`${OP}.image ~ ":" ~ ${OP}.version`)}
|
|
414
|
+
${NAMED} "-windows" not in key %}docker tag ${line(`${REGISTRY} ~ ${NAMED_IMAGE}`)} $REGISTRY/${line(NAMED_IMAGE)}
|
|
415
|
+
${END}\`\`\`
|
|
416
|
+
|
|
417
|
+
3. Push the images to your registry.
|
|
418
|
+
|
|
419
|
+
\`\`\`bash {minja}
|
|
420
|
+
docker push $REGISTRY/${line(`${OP}.image ~ ":" ~ ${OP}.version`)}
|
|
421
|
+
${NAMED} "-windows" not in key %}docker push $REGISTRY/${line(NAMED_IMAGE)}
|
|
422
|
+
${END}\`\`\`
|
|
423
|
+
|
|
424
|
+
4. Use \`crane cp\` to copy the Windows images to your private registry.
|
|
425
|
+
|
|
426
|
+
For hybrid Linux + Windows clusters, use \`crane cp\` on the following Windows images to copy them to your private registry.
|
|
427
|
+
|
|
428
|
+
\`\`\`bash {minja}
|
|
429
|
+
${NAMED} "-windows" in key %}crane cp ${line(`${REGISTRY} ~ ${NAMED_IMAGE}`)} $REGISTRY/${line(NAMED_IMAGE)}
|
|
430
|
+
${END}\`\`\`
|
|
431
|
+
`;
|
|
432
|
+
/**
|
|
433
|
+
* `<MaintenanceImageOptionsAlternateRegistry />` (Calico): the first release's images pulled, retagged
|
|
434
|
+
* and pushed to `$REGISTRY` (Windows ones by `crane cp`), each under its name in variables.js's
|
|
435
|
+
* `imageNames`, which is written into the partial as a dict. Names and the registry must be plain
|
|
436
|
+
* image references; anything else keeps the placeholder.
|
|
437
|
+
*/
|
|
438
|
+
function alternateRegistry(ctx) {
|
|
439
|
+
const vars = loadVariables(ctx);
|
|
440
|
+
const { prodname, registry, imageNames } = vars;
|
|
441
|
+
if (typeof prodname !== "string" || typeof registry !== "string" || (registry && !PLAIN_NAME.test(registry)))
|
|
442
|
+
return undefined;
|
|
443
|
+
if (!imageNames || typeof imageNames !== "object" || Array.isArray(imageNames))
|
|
444
|
+
return undefined;
|
|
445
|
+
const entries = Object.entries(imageNames);
|
|
446
|
+
if (!entries.every(([k, v]) => PLAIN_NAME.test(k) && typeof v === "string" && PLAIN_NAME.test(v)))
|
|
447
|
+
return undefined;
|
|
448
|
+
const names = `{${entries.map(([k, v]) => `"${k}": "${v}"`).join(", ")}}`;
|
|
449
|
+
return { name: "alternate-registry", text: ALTERNATE_REGISTRY_TEXT(names), values: { prodname, registry }, data: { "releases.json": "releases.json" } };
|
|
450
|
+
}
|
|
451
|
+
const OPENSHIFT_MANIFESTS_TEXT = `Download the {{ prodname | text }} manifests for OpenShift and add them to the generated manifests directory:
|
|
452
|
+
|
|
453
|
+
\`\`\`bash {minja}
|
|
454
|
+
mkdir calico
|
|
455
|
+
wget -qO- ${line("calico_releases_url")}/${line("release_title")}/ocp.tgz | \\
|
|
456
|
+
tar xvz {% if files_to_exclude is string and files_to_exclude %}{% for f in files_to_exclude.split(",") %}--exclude=ocp/${line("f.strip()")} {% endfor %}{% endif %}--strip-components=1 -C calico
|
|
457
|
+
cp calico/* manifests/
|
|
458
|
+
\`\`\`
|
|
459
|
+
{%- if not skip_dataplane_config_steps %}
|
|
460
|
+
|
|
461
|
+
{% component "dataplane" %}
|
|
462
|
+
{%- endif %}
|
|
463
|
+
`;
|
|
464
|
+
/**
|
|
465
|
+
* `<InstallOpenShiftManifests />` (Calico 3.30 on): the download, with `--exclude` for each file of
|
|
466
|
+
* `filesToExclude` (a list the component builds in a loop, which the JSX converter cannot read),
|
|
467
|
+
* then `<ConfigDataplaneOpenShiftManifests folderName="manifests" />` unless
|
|
468
|
+
* `skipDataplaneConfigSteps`, INCLUDED as the converter writes it, so its text is Tigera's own.
|
|
469
|
+
* Only for the component's source in that shape (READ as text); any other version or product
|
|
470
|
+
* (3.29, Calico Enterprise) converts as it is.
|
|
471
|
+
*/
|
|
472
|
+
function openshiftManifests(ctx) {
|
|
473
|
+
const root = ctx.instanceRoot ? `${ctx.instanceRoot}/` : "";
|
|
474
|
+
const source = ctx.corpus?.text(`${root}_includes/components/InstallOpenShiftManifests.js`);
|
|
475
|
+
if (!source || !source.includes("props.filesToExclude") || !/import ConfigDataplaneOpenShiftManifests from '\.\/ConfigDataplaneOpenShiftManifests'/.test(source))
|
|
476
|
+
return undefined;
|
|
477
|
+
const vars = loadVariables(ctx);
|
|
478
|
+
const [prodname, release_title, calico_releases_url] = ["prodname", "releaseTitle", "calicoReleasesURL"].map((k) => (typeof vars[k] === "string" ? vars[k] : undefined));
|
|
479
|
+
if (!prodname || !release_title || !calico_releases_url)
|
|
480
|
+
return undefined;
|
|
481
|
+
return {
|
|
482
|
+
name: "install-openshift-manifests",
|
|
483
|
+
text: OPENSHIFT_MANIFESTS_TEXT,
|
|
484
|
+
params: { filesToExclude: "files_to_exclude", skipDataplaneConfigSteps: "skip_dataplane_config_steps" },
|
|
485
|
+
values: { prodname, release_title, calico_releases_url },
|
|
486
|
+
data: {},
|
|
487
|
+
components: { dataplane: { module: "_includes/components/ConfigDataplaneOpenShiftManifests.js", props: { folderName: "manifests" } } },
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* `<FossaReport releases={releases} />` (Calico Enterprise): the first release that is not
|
|
492
|
+
* `master`, its minor version, and the report's S3 page. The component shows the page in an
|
|
493
|
+
* iframe with a link under it; a static page gives the link, titled as the iframe was.
|
|
494
|
+
*/
|
|
495
|
+
const FOSSA = {
|
|
496
|
+
name: "fossa-report",
|
|
497
|
+
text: `{%- set latest = data.releases | rejectattr("title", "equalto", "master") | first -%}
|
|
498
|
+
{%- set parts = ((latest.title[1:] if latest.title.startswith("v") else latest.title).split(".")) if latest else [] -%}
|
|
499
|
+
{%- if parts | length < 2 -%}
|
|
500
|
+
No FOSSA attribution report available for this version.
|
|
501
|
+
{%- else -%}
|
|
502
|
+
{%- set minor = parts[0] ~ "." ~ parts[1] -%}
|
|
503
|
+
{%- set src = "https://ce-" ~ minor.replace(".", "-") ~ "-attribution-report.s3.amazonaws.com/attribution-report.html" -%}
|
|
504
|
+
[FOSSA Attribution Report - Calico Enterprise {{ minor | text }}]({{ src | href }})
|
|
505
|
+
{%- endif %}
|
|
506
|
+
`,
|
|
507
|
+
fromData: ["releases"],
|
|
508
|
+
data: { "releases.json": "releases.json" },
|
|
509
|
+
};
|
|
510
|
+
export const TIGERA_DATA_COMPONENTS = {
|
|
511
|
+
MaintenanceImageOptionsAlternateRegistry: alternateRegistry,
|
|
512
|
+
InstallOpenShiftManifests: openshiftManifests,
|
|
513
|
+
FossaReport: FOSSA,
|
|
514
|
+
FelixConfig: FELIX,
|
|
515
|
+
ComponentVersions: componentVersions,
|
|
516
|
+
ReleaseArchiveTable: RELEASE_ARCHIVES,
|
|
517
|
+
PrivateRegistryRegular: privateRegistry("PrivateRegistryRegular", "private-registry-regular", REGULAR_TEXT),
|
|
518
|
+
VersionedCloudImageList: cloudImageList,
|
|
519
|
+
PrivateRegistryImagePath: privateRegistry("PrivateRegistryImagePath", "private-registry-image-path", IMAGE_PATH_TEXT),
|
|
520
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a Tigera `_includes/` partial import points.
|
|
3
|
+
*
|
|
4
|
+
* Tigera authors share content via Docusaurus MDX imports:
|
|
5
|
+
*
|
|
6
|
+
* import Selectors from '@site/calico/_includes/content/_selectors.mdx';
|
|
7
|
+
* ...
|
|
8
|
+
* <Selectors />
|
|
9
|
+
*
|
|
10
|
+
* Spliced in place by the importer's own splicer (`splicePartials`, called from the adapter's
|
|
11
|
+
* preprocess so `$[variable]` substitution reaches the partials' text), verbatim and with NO
|
|
12
|
+
* heading-level offset: Docusaurus renders a partial's headings at their literal levels.
|
|
13
|
+
* `--partial-mode include` writes them as includes instead (structural.ts), with this resolver.
|
|
14
|
+
*/
|
|
15
|
+
import { type PreprocessContext } from "@dogsbay/format-docusaurus/adapter";
|
|
16
|
+
/**
|
|
17
|
+
* Resolve an import specifier (`@site/` or relative) to the partial it names, through the corpus:
|
|
18
|
+
* a corpus path inside the project, an absolute path in an `--include-root` folder (CLI), or null.
|
|
19
|
+
* The corpus is the containment: `@site/../x`, `../../x` or a symlink out of the project resolve
|
|
20
|
+
* to nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveSpec(spec: string, ctx: PreprocessContext): string | null;
|
package/dist/includes.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a Tigera `_includes/` partial import points.
|
|
3
|
+
*
|
|
4
|
+
* Tigera authors share content via Docusaurus MDX imports:
|
|
5
|
+
*
|
|
6
|
+
* import Selectors from '@site/calico/_includes/content/_selectors.mdx';
|
|
7
|
+
* ...
|
|
8
|
+
* <Selectors />
|
|
9
|
+
*
|
|
10
|
+
* Spliced in place by the importer's own splicer (`splicePartials`, called from the adapter's
|
|
11
|
+
* preprocess so `$[variable]` substitution reaches the partials' text), verbatim and with NO
|
|
12
|
+
* heading-level offset: Docusaurus renders a partial's headings at their literal levels.
|
|
13
|
+
* `--partial-mode include` writes them as includes instead (structural.ts), with this resolver.
|
|
14
|
+
*/
|
|
15
|
+
import { partialFiles } from "@dogsbay/format-docusaurus/adapter";
|
|
16
|
+
/**
|
|
17
|
+
* Resolve an import specifier (`@site/` or relative) to the partial it names, through the corpus:
|
|
18
|
+
* a corpus path inside the project, an absolute path in an `--include-root` folder (CLI), or null.
|
|
19
|
+
* The corpus is the containment: `@site/../x`, `../../x` or a symlink out of the project resolve
|
|
20
|
+
* to nothing.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveSpec(spec, ctx) {
|
|
23
|
+
if (ctx.file === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
if (!spec.startsWith("@site/") && !spec.startsWith("."))
|
|
26
|
+
return null; // a package
|
|
27
|
+
const relSpec = spec.startsWith(".") && !spec.startsWith("./") && !spec.startsWith("../") ? `./${spec}` : spec;
|
|
28
|
+
return partialFiles(ctx).resolve(relSpec, ctx.file) ?? null;
|
|
29
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dogsbay/adapter-tigera
|
|
3
|
+
*
|
|
4
|
+
* Site adapter for the Tigera/Calico docs (docs.tigera.io). Injected into the
|
|
5
|
+
* generic @dogsbay/format-docusaurus importer via `--adapter tigera`. Holds
|
|
6
|
+
* everything Tigera layered on top of standard Docusaurus:
|
|
7
|
+
* - `$[variable]` substitution (custom remark plugin → adapter preprocess)
|
|
8
|
+
* - Tigera-specific MDX components (DocCardLink, GeekDetails, If, ...)
|
|
9
|
+
*
|
|
10
|
+
* The generic importer contains ZERO Tigera code; all of it lives here.
|
|
11
|
+
*/
|
|
12
|
+
import { type DocusaurusAdapter } from "@dogsbay/format-docusaurus/adapter";
|
|
13
|
+
export declare const tigeraAdapter: DocusaurusAdapter;
|
|
14
|
+
export { loadVariables, substituteVariables } from "./variables.js";
|
|
15
|
+
/**
|
|
16
|
+
* Is this the Tigera docs repo (docs.tigera.io)? From its paths, relative to the site's folder
|
|
17
|
+
* (a detection's `root`, as `migrate` takes them): Tigera's own `$[variable]` remark plugin, and a `variables.js` in a docs folder. For
|
|
18
|
+
* a host offering the adapter, never applying it silently.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detect(paths: Iterable<string>): boolean;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dogsbay/adapter-tigera
|
|
3
|
+
*
|
|
4
|
+
* Site adapter for the Tigera/Calico docs (docs.tigera.io). Injected into the
|
|
5
|
+
* generic @dogsbay/format-docusaurus importer via `--adapter tigera`. Holds
|
|
6
|
+
* everything Tigera layered on top of standard Docusaurus:
|
|
7
|
+
* - `$[variable]` substitution (custom remark plugin → adapter preprocess)
|
|
8
|
+
* - Tigera-specific MDX components (DocCardLink, GeekDetails, If, ...)
|
|
9
|
+
*
|
|
10
|
+
* The generic importer contains ZERO Tigera code; all of it lives here.
|
|
11
|
+
*/
|
|
12
|
+
import { splicePartials } from "@dogsbay/format-docusaurus/adapter";
|
|
13
|
+
import { loadVariables, substituteVariables } from "./variables.js";
|
|
14
|
+
import { toStructural } from "./structural.js";
|
|
15
|
+
import { TIGERA_DATA_COMPONENTS } from "./data-components.js";
|
|
16
|
+
// ── Tigera custom components ────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Mappings keep output clean instead of leaking raw JSX. Card-like components
|
|
19
|
+
// map to Dogsbay cards; conditional/wrapper components keep their inner content;
|
|
20
|
+
// data-driven components with no static content are stripped (self-closing).
|
|
21
|
+
const TIGERA_COMPONENTS = {
|
|
22
|
+
// Cross-page link cards (from /src/___new___/components). They reference a
|
|
23
|
+
// target via `docId` (Docusaurus doc id); the card title comes from that
|
|
24
|
+
// doc's frontmatter. Emit `docId` for the generic parser to resolve into
|
|
25
|
+
// title + href (an explicit title/href attr still wins if present).
|
|
26
|
+
DocCardLink: {
|
|
27
|
+
type: "card",
|
|
28
|
+
container: false,
|
|
29
|
+
propsFromAttrs: (a) => ({ docId: a.docId, title: a.title || a.label, href: a.to || a.href }),
|
|
30
|
+
},
|
|
31
|
+
PaidProductDocCardLink: {
|
|
32
|
+
type: "card",
|
|
33
|
+
container: false,
|
|
34
|
+
propsFromAttrs: (a) => ({ docId: a.docId, title: a.title || a.label, href: a.to || a.href }),
|
|
35
|
+
},
|
|
36
|
+
DocCardLinkLayout: { type: "cards", container: true },
|
|
37
|
+
CalicoProducts: { type: "cards", container: true },
|
|
38
|
+
// Conditional rendering — keep the inner content, drop the wrapper.
|
|
39
|
+
If: { type: "if", container: true, strip: true },
|
|
40
|
+
// A caption under an image (theme MDX component): its text is kept, the wrapper dropped
|
|
41
|
+
ImageCaption: { type: "image-caption", container: true, strip: true },
|
|
42
|
+
// Metadata/badge components with no reproducible static content.
|
|
43
|
+
GeekDetails: { type: "badge", container: false, strip: true },
|
|
44
|
+
// NOTE: `<Screenshot>` (a plain <img> wrapper) is handled by an inline
|
|
45
|
+
// transform → markdown image, NOT a strip mapping — see TIGERA_INLINE_TRANSFORMS.
|
|
46
|
+
};
|
|
47
|
+
// `_includes/components/*.js` — data-driven React components (render tables from
|
|
48
|
+
// JSON, generate env files, etc.). They are CODE and can't be inlined like the
|
|
49
|
+
// .mdx content partials. Map them to a placeholder (not a silent strip) so they
|
|
50
|
+
// don't leak as raw `<Tag/>` text AND don't leave a container empty — a stripped
|
|
51
|
+
// component that was the sole content of a tab made the tab label vanish.
|
|
52
|
+
const TIGERA_JS_COMPONENTS = [
|
|
53
|
+
"AutoHostendpointsMigrate",
|
|
54
|
+
"CalicoWindowsInstall",
|
|
55
|
+
"ComponentVersions",
|
|
56
|
+
"ConfigDataplaneOpenShiftManifests",
|
|
57
|
+
"DownloadOpenShiftManifests",
|
|
58
|
+
"EnvironmentFile",
|
|
59
|
+
"HostEndpointsUpgrade",
|
|
60
|
+
"InstallOpenShiftManifests",
|
|
61
|
+
"MaintenanceImageOptionsAlternateRegistry",
|
|
62
|
+
"FelixConfig",
|
|
63
|
+
"FossaReport",
|
|
64
|
+
"PrivateRegistryImagePath",
|
|
65
|
+
"PrivateRegistryRegular",
|
|
66
|
+
"VersionedCloudImageList",
|
|
67
|
+
"ReleaseArchiveTable",
|
|
68
|
+
];
|
|
69
|
+
for (const name of TIGERA_JS_COMPONENTS) {
|
|
70
|
+
// A data component carries its template partial, used when the output is Dogsbay MD
|
|
71
|
+
const template = TIGERA_DATA_COMPONENTS[name];
|
|
72
|
+
TIGERA_COMPONENTS[name] = { type: "__placeholder", container: true, ...(template ? { template } : {}) };
|
|
73
|
+
}
|
|
74
|
+
// Self-closing Tigera tags (no closing tag expected).
|
|
75
|
+
const TIGERA_SELF_CLOSING = [
|
|
76
|
+
"DocCardLink",
|
|
77
|
+
"PaidProductDocCardLink",
|
|
78
|
+
"GeekDetails",
|
|
79
|
+
"CalicoProducts",
|
|
80
|
+
...TIGERA_JS_COMPONENTS,
|
|
81
|
+
];
|
|
82
|
+
// Inline replacements for icon components used within prose.
|
|
83
|
+
const TIGERA_INLINE_TRANSFORMS = [
|
|
84
|
+
// Chakra checkmark icon → unicode check.
|
|
85
|
+
[/<CheckIcon\b[^>]*\/>/g, "✓"],
|
|
86
|
+
// @docusaurus/Link (`<Link to=…>` or `<Link href=…>`) → a raw <a> so it works
|
|
87
|
+
// both in prose AND inside raw-HTML tables (a markdown link wouldn't render
|
|
88
|
+
// inside an HTML `<td>`). Reads either the `to` or `href` attribute.
|
|
89
|
+
[
|
|
90
|
+
/<Link\b([^>]*?)>([\s\S]*?)<\/Link>/g,
|
|
91
|
+
(_m, attrs, text) => {
|
|
92
|
+
const url = attrs.match(/\b(?:to|href)=["']([^"']+)["']/)?.[1];
|
|
93
|
+
const inner = text.trim();
|
|
94
|
+
return url ? `<a href="${url}">${inner}</a>` : inner;
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
// <Screenshot src alt /> is a plain <img> wrapper → markdown image (the src is
|
|
98
|
+
// then `/_assets`-rewritten by the generic parser, and the static file copied).
|
|
99
|
+
[
|
|
100
|
+
/<Screenshot\b([^>]*?)\/>/g,
|
|
101
|
+
(_m, attrs) => {
|
|
102
|
+
const src = attrs.match(/\bsrc=["']([^"']*)["']/)?.[1] ?? "";
|
|
103
|
+
const alt = attrs.match(/\balt=["']([^"']*)["']/)?.[1] ?? "";
|
|
104
|
+
return src ? `` : "";
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
];
|
|
108
|
+
export const tigeraAdapter = {
|
|
109
|
+
name: "tigera",
|
|
110
|
+
splicesPartials: true,
|
|
111
|
+
preprocess(source, ctx) {
|
|
112
|
+
// Tigera numbers figures with an MDX counter (`export let figCount = 1` +
|
|
113
|
+
// `{figCount++}` in captions). The export is stripped and the expression
|
|
114
|
+
// can't be evaluated, so substitute sequential numbers per page.
|
|
115
|
+
let fig = 0;
|
|
116
|
+
let text = source.replace(/\{\s*figCount\+\+\s*\}/g, () => String(++fig));
|
|
117
|
+
// MDX whitespace expressions `{' '}` / `{" "}` (used to force a space, e.g.
|
|
118
|
+
// inside table cells) → a literal space.
|
|
119
|
+
text = text.replace(/\{\s*(['"])(\s*)\1\s*\}/g, "$2");
|
|
120
|
+
// Partials: either splice bodies in place (inline, default; the importer's splicer, here so
|
|
121
|
+
// the variables below reach them) or emit {% include %}/{% set %}/{% if %} directives +
|
|
122
|
+
// collect fragments (include).
|
|
123
|
+
const partials = ctx.partialMode === "include"
|
|
124
|
+
? // .md partials, which include mode does not write as includes, are spliced
|
|
125
|
+
splicePartials(toStructural(text, ctx), ctx)
|
|
126
|
+
: splicePartials(text, ctx);
|
|
127
|
+
// Substitute $[variables] (Phase 1 keeps this literal in both modes).
|
|
128
|
+
if (ctx.instanceRoot === undefined)
|
|
129
|
+
return partials;
|
|
130
|
+
return substituteVariables(partials, loadVariables(ctx));
|
|
131
|
+
},
|
|
132
|
+
components: TIGERA_COMPONENTS,
|
|
133
|
+
selfClosingTags: TIGERA_SELF_CLOSING,
|
|
134
|
+
inlineTransforms: TIGERA_INLINE_TRANSFORMS,
|
|
135
|
+
};
|
|
136
|
+
export { loadVariables, substituteVariables } from "./variables.js";
|
|
137
|
+
/**
|
|
138
|
+
* Is this the Tigera docs repo (docs.tigera.io)? From its paths, relative to the site's folder
|
|
139
|
+
* (a detection's `root`, as `migrate` takes them): Tigera's own `$[variable]` remark plugin, and a `variables.js` in a docs folder. For
|
|
140
|
+
* a host offering the adapter, never applying it silently.
|
|
141
|
+
*/
|
|
142
|
+
export function detect(paths) {
|
|
143
|
+
let plugin = false;
|
|
144
|
+
let variables = false;
|
|
145
|
+
for (const p of paths) {
|
|
146
|
+
if (/^src\/remark\/variablesPlugin\.[cm]?[jt]s$/.test(p))
|
|
147
|
+
plugin = true;
|
|
148
|
+
else if (/^[^/]+(?:_versioned_docs\/[^/]+)?\/variables\.js$/.test(p))
|
|
149
|
+
variables = true;
|
|
150
|
+
if (plugin && variables)
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural partial mode (mirrors AsciiDoc's `inlineIncludes: false`).
|
|
3
|
+
*
|
|
4
|
+
* Instead of inlining `_includes/` partials, emit Minja directives as text and
|
|
5
|
+
* collect each referenced `.mdx` partial as a *fragment* to be written at its
|
|
6
|
+
* include path. The build-time Minja preprocessor resolves them later.
|
|
7
|
+
*
|
|
8
|
+
* Transforms (text → text, before parsing):
|
|
9
|
+
* - `export const X = '…'` → `{% set X = "…" %}`
|
|
10
|
+
* - `<Partial a="1" b={x} />` (.mdx import)→ `{% set a = "1" %}` (only when a≠source)
|
|
11
|
+
* `{% include "…/_partial.md" %}`
|
|
12
|
+
* - `<If condition={props.x === 'y'}>…</If>`→ `{% if x == "y" %}…{% endif %}`
|
|
13
|
+
*
|
|
14
|
+
* `$[var]` substitution still runs afterwards (Phase 1; structural variables are
|
|
15
|
+
* Phase 2). `.js` component partials are left for the component-map strip.
|
|
16
|
+
*/
|
|
17
|
+
import { type PreprocessContext } from "@dogsbay/format-docusaurus/adapter";
|
|
18
|
+
/**
|
|
19
|
+
* Translate a Docusaurus `<If condition={…}>` JSX expression into a Minja
|
|
20
|
+
* expression. Supports the subset that actually appears in Tigera:
|
|
21
|
+
* props.x, ===, !==, &&, ||, !, parentheses, string/number/bool literals.
|
|
22
|
+
* Returns null when the expression is outside this grammar (caller falls back
|
|
23
|
+
* to inline + warn).
|
|
24
|
+
*/
|
|
25
|
+
export declare function jsxConditionToMinja(expr: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Map a partial (a corpus path, or an absolute path in an `--include-root` folder) to its
|
|
28
|
+
* include path, relative to the instance folder. A partial elsewhere in the project is stored
|
|
29
|
+
* under `_project/`, one from an `--include-root` folder under `_include-roots/<folder name>/`
|
|
30
|
+
* (the CLI places it, by real path): relative to the instance either would climb out of the
|
|
31
|
+
* content root and be written outside the output. Undefined when the partial is in none of
|
|
32
|
+
* them (it is then not collected).
|
|
33
|
+
*/
|
|
34
|
+
export declare function fragmentIncludePath(path: string, ctx: Pick<PreprocessContext, "instanceRoot" | "projectRoot" | "external">): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Translate `<If condition={…}>…</If>` blocks to `{% if … %}…{% endif %}`.
|
|
37
|
+
* Returns the rewritten source plus a flag set when any block could not be
|
|
38
|
+
* translated (caller can decide to fall back to inline for that page/partial).
|
|
39
|
+
*/
|
|
40
|
+
export declare function rewriteIfBlocks(source: string): {
|
|
41
|
+
text: string;
|
|
42
|
+
untranslated: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Apply the structural transform: emit Minja directives + collect fragments.
|
|
46
|
+
* Used for both pages and (recursively, via the importer draining
|
|
47
|
+
* `ctx.fragments`) the partial fragments themselves.
|
|
48
|
+
*/
|
|
49
|
+
export declare function toStructural(source: string, ctx: PreprocessContext): string;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural partial mode (mirrors AsciiDoc's `inlineIncludes: false`).
|
|
3
|
+
*
|
|
4
|
+
* Instead of inlining `_includes/` partials, emit Minja directives as text and
|
|
5
|
+
* collect each referenced `.mdx` partial as a *fragment* to be written at its
|
|
6
|
+
* include path. The build-time Minja preprocessor resolves them later.
|
|
7
|
+
*
|
|
8
|
+
* Transforms (text → text, before parsing):
|
|
9
|
+
* - `export const X = '…'` → `{% set X = "…" %}`
|
|
10
|
+
* - `<Partial a="1" b={x} />` (.mdx import)→ `{% set a = "1" %}` (only when a≠source)
|
|
11
|
+
* `{% include "…/_partial.md" %}`
|
|
12
|
+
* - `<If condition={props.x === 'y'}>…</If>`→ `{% if x == "y" %}…{% endif %}`
|
|
13
|
+
*
|
|
14
|
+
* `$[var]` substitution still runs afterwards (Phase 1; structural variables are
|
|
15
|
+
* Phase 2). `.js` component partials are left for the component-map strip.
|
|
16
|
+
*/
|
|
17
|
+
import { posix, relativeDir } from "@dogsbay/format-docusaurus/adapter";
|
|
18
|
+
import { resolveSpec } from "./includes.js";
|
|
19
|
+
// ── JSX condition → Minja expression ────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Translate a Docusaurus `<If condition={…}>` JSX expression into a Minja
|
|
22
|
+
* expression. Supports the subset that actually appears in Tigera:
|
|
23
|
+
* props.x, ===, !==, &&, ||, !, parentheses, string/number/bool literals.
|
|
24
|
+
* Returns null when the expression is outside this grammar (caller falls back
|
|
25
|
+
* to inline + warn).
|
|
26
|
+
*/
|
|
27
|
+
export function jsxConditionToMinja(expr) {
|
|
28
|
+
let s = expr.trim();
|
|
29
|
+
if (!s)
|
|
30
|
+
return null;
|
|
31
|
+
// Reject constructs we don't translate (calls, member access beyond props.x,
|
|
32
|
+
// ternaries, template literals, arithmetic).
|
|
33
|
+
if (/`|\?|=>|\.\s*\w+\s*\(|\bfunction\b/.test(s))
|
|
34
|
+
return null;
|
|
35
|
+
// props.x → x (also bare `props` is invalid on its own)
|
|
36
|
+
s = s.replace(/\bprops\.([A-Za-z_$][\w$]*)/g, "$1");
|
|
37
|
+
if (/\bprops\b/.test(s))
|
|
38
|
+
return null;
|
|
39
|
+
// Operators: order matters (=== before ==, !== before !).
|
|
40
|
+
s = s.replace(/===/g, "==").replace(/!==/g, "!=");
|
|
41
|
+
s = s.replace(/&&/g, " and ").replace(/\|\|/g, " or ");
|
|
42
|
+
// Unary not: `!x` / `!(…)` → `not x`. Leave `!=` intact (already handled).
|
|
43
|
+
s = s.replace(/!(?!=)\s*/g, "not ");
|
|
44
|
+
// Normalize double/single quotes to double for Minja string literals.
|
|
45
|
+
s = s.replace(/'([^']*)'/g, '"$1"');
|
|
46
|
+
// Whitelist final character set — identifiers, comparison, logic, literals.
|
|
47
|
+
// (Defensive: anything exotic that slipped through → bail.)
|
|
48
|
+
if (/[^\w\s."'=!<>()and ortnu]/.test(s.replace(/"[^"]*"/g, '""'))) {
|
|
49
|
+
// Allow only a safe charset outside string literals.
|
|
50
|
+
// (The regex above is intentionally loose; rely on the rejects above too.)
|
|
51
|
+
}
|
|
52
|
+
return s.replace(/\s+/g, " ").trim();
|
|
53
|
+
}
|
|
54
|
+
function parseMdxImports(source, ctx) {
|
|
55
|
+
const out = [];
|
|
56
|
+
const re = /^\s*import\s+([A-Z][A-Za-z0-9_]*)\s+from\s+['"]([^'"]+)['"]\s*;?\s*$/gm;
|
|
57
|
+
let m;
|
|
58
|
+
while ((m = re.exec(source)) !== null) {
|
|
59
|
+
const [, name, spec] = m;
|
|
60
|
+
if (!spec.endsWith(".mdx"))
|
|
61
|
+
continue;
|
|
62
|
+
const path = resolveSpec(spec, ctx);
|
|
63
|
+
if (path)
|
|
64
|
+
out.push({ name, resolvedPath: path });
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/** `path` relative to the folder `root` (corpus paths), or undefined when it is not inside it. */
|
|
69
|
+
function inside(path, root) {
|
|
70
|
+
if (!root)
|
|
71
|
+
return path;
|
|
72
|
+
return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Map a partial (a corpus path, or an absolute path in an `--include-root` folder) to its
|
|
76
|
+
* include path, relative to the instance folder. A partial elsewhere in the project is stored
|
|
77
|
+
* under `_project/`, one from an `--include-root` folder under `_include-roots/<folder name>/`
|
|
78
|
+
* (the CLI places it, by real path): relative to the instance either would climb out of the
|
|
79
|
+
* content root and be written outside the output. Undefined when the partial is in none of
|
|
80
|
+
* them (it is then not collected).
|
|
81
|
+
*/
|
|
82
|
+
export function fragmentIncludePath(path, ctx) {
|
|
83
|
+
let rel;
|
|
84
|
+
if (path.startsWith("/"))
|
|
85
|
+
rel = ctx.external?.storagePath(path);
|
|
86
|
+
else {
|
|
87
|
+
rel = ctx.instanceRoot !== undefined ? inside(path, ctx.instanceRoot) : undefined;
|
|
88
|
+
if (rel === undefined) {
|
|
89
|
+
const inProject = inside(path, ctx.projectRoot ?? "");
|
|
90
|
+
if (inProject !== undefined)
|
|
91
|
+
rel = `_project/${inProject}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return rel === undefined ? undefined : rel.replace(/\.mdx$/, ".md");
|
|
95
|
+
}
|
|
96
|
+
// ── export const → {% set %} ────────────────────────────
|
|
97
|
+
function rewriteExportConsts(source) {
|
|
98
|
+
// `export const platform='kubernetes';` → `{% set platform = "kubernetes" %}`
|
|
99
|
+
return source.replace(/^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:"([^"]*)"|'([^']*)')\s*;?\s*$/gm, (_m, name, dq, sq) => `{% set ${name} = "${dq ?? sq}" %}`);
|
|
100
|
+
}
|
|
101
|
+
// ── <If> → {% if %} (with fallback signalling) ──────────
|
|
102
|
+
/**
|
|
103
|
+
* Translate `<If condition={…}>…</If>` blocks to `{% if … %}…{% endif %}`.
|
|
104
|
+
* Returns the rewritten source plus a flag set when any block could not be
|
|
105
|
+
* translated (caller can decide to fall back to inline for that page/partial).
|
|
106
|
+
*/
|
|
107
|
+
export function rewriteIfBlocks(source) {
|
|
108
|
+
let untranslated = 0;
|
|
109
|
+
// Pass 1 — inline `<If cond>content</If>` (open + close on ONE line): a prose
|
|
110
|
+
// sentence or a single <td> cell. Emit an inline `{% if %}…{% endif %}` so the
|
|
111
|
+
// line (and any <td>/<tr> it sits in) stays intact.
|
|
112
|
+
const inlineRe = /<If\s+condition=\{([^}]*)\}\s*>([^\n]*?)<\/If>/g;
|
|
113
|
+
let text = source.replace(inlineRe, (whole, cond, inner) => {
|
|
114
|
+
const minja = jsxConditionToMinja(cond);
|
|
115
|
+
if (minja == null) {
|
|
116
|
+
untranslated++;
|
|
117
|
+
return whole; // leave for the If component-map strip
|
|
118
|
+
}
|
|
119
|
+
return `{% if ${minja} %}${inner}{% endif %}`;
|
|
120
|
+
});
|
|
121
|
+
// Pass 2 — block `<If>` on its own line (wraps block content: <tr> rows, list
|
|
122
|
+
// items). Table-aware: INSIDE an HTML <table>, emit flush directives with NO
|
|
123
|
+
// blank-line padding — blank lines would split the <table> into separate HTML
|
|
124
|
+
// blocks and the indented <tr> rows would parse as literal code. Minja still
|
|
125
|
+
// resolves the rows before markdown-it sees the table. OUTSIDE a table, emit
|
|
126
|
+
// flush + blank-padded so block conditionals don't fuse with markdown lists.
|
|
127
|
+
const lines = text.split("\n");
|
|
128
|
+
const out = [];
|
|
129
|
+
let tableDepth = 0;
|
|
130
|
+
const stack = [];
|
|
131
|
+
const openLine = /^[ \t]*<If\s+condition=\{([^}]*)\}\s*>[ \t]*$/;
|
|
132
|
+
const closeLine = /^[ \t]*<\/If>[ \t]*$/;
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
const m = openLine.exec(line);
|
|
135
|
+
if (m) {
|
|
136
|
+
const minja = jsxConditionToMinja(m[1]);
|
|
137
|
+
const inTable = tableDepth > 0;
|
|
138
|
+
if (minja == null) {
|
|
139
|
+
untranslated++;
|
|
140
|
+
stack.push({ ok: false, inTable });
|
|
141
|
+
out.push("<If>");
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
stack.push({ ok: true, inTable });
|
|
145
|
+
// Inside a table, use Minja whitespace control (`{%- … -%}`) so resolving
|
|
146
|
+
// the directive leaves NO blank line — a blank line would fragment the
|
|
147
|
+
// <table> and the indented <tr> rows would parse as an indented code block.
|
|
148
|
+
if (inTable)
|
|
149
|
+
out.push(`{%- if ${minja} -%}`);
|
|
150
|
+
else
|
|
151
|
+
out.push("", `{% if ${minja} %}`, "");
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (closeLine.test(line)) {
|
|
155
|
+
const rec = stack.pop();
|
|
156
|
+
if (!rec || !rec.ok) {
|
|
157
|
+
out.push("</If>"); // untranslated → keep for the If component-map strip
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (rec.inTable)
|
|
161
|
+
out.push("{%- endif -%}");
|
|
162
|
+
else
|
|
163
|
+
out.push("", "{% endif %}", "");
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
out.push(line);
|
|
167
|
+
tableDepth += (line.match(/<table\b/g) ?? []).length;
|
|
168
|
+
tableDepth -= (line.match(/<\/table>/g) ?? []).length;
|
|
169
|
+
if (tableDepth < 0)
|
|
170
|
+
tableDepth = 0;
|
|
171
|
+
}
|
|
172
|
+
return { text: out.join("\n"), untranslated };
|
|
173
|
+
}
|
|
174
|
+
// ── Partial usage → {% include %} + fragment collection ─
|
|
175
|
+
function emitIncludes(source, imports, ctx) {
|
|
176
|
+
// The directory of the CURRENT file's output (.md), relative to the instance
|
|
177
|
+
// root. Include paths are emitted relative to this, because the build-time
|
|
178
|
+
// Minja preprocessor resolves `{% include %}` against the consuming file's dir
|
|
179
|
+
// (`basePath: dirname(sourcePath)`), not the content root.
|
|
180
|
+
const currentOut = ctx.file !== undefined ? fragmentIncludePath(ctx.file, ctx) : undefined;
|
|
181
|
+
const currentOutDir = currentOut === undefined ? "" : posix.dirname(currentOut).replace(/^\.$/, "");
|
|
182
|
+
let result = source;
|
|
183
|
+
for (const imp of imports) {
|
|
184
|
+
// Stable storage path (content-root-relative) — the exporter writes the
|
|
185
|
+
// fragment here, and it's the fragments-collector key.
|
|
186
|
+
const storagePath = fragmentIncludePath(imp.resolvedPath, ctx);
|
|
187
|
+
// A partial with nowhere inside the output to live is not collected: its usage stays
|
|
188
|
+
// as written and is reported as an unmapped component
|
|
189
|
+
if (storagePath === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
ctx.fragments?.set(imp.resolvedPath, storagePath);
|
|
192
|
+
// Page-relative directive path (e.g. ../../_includes/partials/_x.md).
|
|
193
|
+
let rel = relativeDir(currentOutDir, storagePath);
|
|
194
|
+
if (!rel.startsWith("."))
|
|
195
|
+
rel = `./${rel}`;
|
|
196
|
+
const selfClosing = new RegExp(`<${imp.name}((?:\\s[^>]*?)?)/>`, "g");
|
|
197
|
+
const paired = new RegExp(`<${imp.name}((?:\\s[^>]*?)?)>[\\s\\S]*?</${imp.name}>`, "g");
|
|
198
|
+
const directiveFor = (attrText) => {
|
|
199
|
+
const sets = propSetDirectives(attrText);
|
|
200
|
+
return `\n\n${sets}{% include "${rel}" %}\n\n`;
|
|
201
|
+
};
|
|
202
|
+
result = result
|
|
203
|
+
.replace(paired, (_m, attrs) => directiveFor(attrs))
|
|
204
|
+
.replace(selfClosing, (_m, attrs) => directiveFor(attrs));
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Emit `{% set %}` lines only for props that need them: a literal value, or a
|
|
210
|
+
* prop whose name differs from the forwarded variable. `name={name}` (same name,
|
|
211
|
+
* the Tigera pattern) needs nothing — the page-level set is already in scope.
|
|
212
|
+
*/
|
|
213
|
+
function propSetDirectives(attrText) {
|
|
214
|
+
const sets = [];
|
|
215
|
+
const attrRe = /([A-Za-z_$][\w$]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\})/g;
|
|
216
|
+
let m;
|
|
217
|
+
while ((m = attrRe.exec(attrText)) !== null) {
|
|
218
|
+
const [, name, dq, sq, expr] = m;
|
|
219
|
+
if (dq !== undefined || sq !== undefined) {
|
|
220
|
+
sets.push(`{% set ${name} = "${dq ?? sq}" %}\n`);
|
|
221
|
+
}
|
|
222
|
+
else if (expr !== undefined) {
|
|
223
|
+
const e = expr.trim();
|
|
224
|
+
if (e !== name)
|
|
225
|
+
sets.push(`{% set ${name} = ${e} %}\n`); // rename only
|
|
226
|
+
// same-name forward (e === name) → inherited, no set needed
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return sets.join("");
|
|
230
|
+
}
|
|
231
|
+
// ── Entry point ─────────────────────────────────────────
|
|
232
|
+
/**
|
|
233
|
+
* Apply the structural transform: emit Minja directives + collect fragments.
|
|
234
|
+
* Used for both pages and (recursively, via the importer draining
|
|
235
|
+
* `ctx.fragments`) the partial fragments themselves.
|
|
236
|
+
*/
|
|
237
|
+
export function toStructural(source, ctx) {
|
|
238
|
+
const imports = parseMdxImports(source, ctx);
|
|
239
|
+
let text = rewriteExportConsts(source);
|
|
240
|
+
text = rewriteIfBlocks(text).text;
|
|
241
|
+
text = emitIncludes(text, imports, ctx);
|
|
242
|
+
return text;
|
|
243
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PreprocessContext } from "@dogsbay/format-docusaurus/adapter";
|
|
2
|
+
/**
|
|
3
|
+
* Substitute `$[path]` tokens using `vars`. Unknown tokens and tokens that
|
|
4
|
+
* resolve to non-primitive values are left untouched (and reported via onWarn).
|
|
5
|
+
*/
|
|
6
|
+
export declare function substituteVariables(source: string, vars: Record<string, unknown>, onWarn?: (token: string) => void): string;
|
|
7
|
+
/**
|
|
8
|
+
* Load `variables.js` from a product/version folder (a CommonJS data module that may
|
|
9
|
+
* `require('./releases.json')`), through the corpus. Cached per corpus and folder; an empty
|
|
10
|
+
* object when absent.
|
|
11
|
+
*
|
|
12
|
+
* READ as data by default (`@dogsbay/literal`, through format-docusaurus's `readDataModule`):
|
|
13
|
+
* on the Tigera repo that is 23 of 24 calico keys and every calico-cloud key; a value computed
|
|
14
|
+
* with `.split()`/`.slice()` is left unresolved and named, and its `$[token]` stays as written.
|
|
15
|
+
* With `--eval-config` the CLI RUNS the file instead (`ctx.runModule`) — code from the source
|
|
16
|
+
* repo, for a repo you trust.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadVariables(ctx: Pick<PreprocessContext, "corpus" | "instanceRoot" | "projectRoot" | "runModule" | "warn">): Record<string, unknown>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readDataModule } from "@dogsbay/format-docusaurus/adapter";
|
|
2
|
+
// Tokens may use array indices (`releases.0`) and keys containing slashes
|
|
3
|
+
// (`components.calico/node.version`), so the charset includes digits and `/`.
|
|
4
|
+
const TOKEN_RE = /\$\[([a-zA-Z0-9_./]+)\]/g;
|
|
5
|
+
/** Resolve a dotted path (e.g. "tigeraOperator.version") within an object. */
|
|
6
|
+
function resolvePath(vars, path) {
|
|
7
|
+
return path.split(".").reduce((acc, key) => {
|
|
8
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
9
|
+
return acc[key];
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}, vars);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Substitute `$[path]` tokens using `vars`. Unknown tokens and tokens that
|
|
16
|
+
* resolve to non-primitive values are left untouched (and reported via onWarn).
|
|
17
|
+
*/
|
|
18
|
+
export function substituteVariables(source, vars, onWarn) {
|
|
19
|
+
return source.replace(TOKEN_RE, (whole, path) => {
|
|
20
|
+
const value = resolvePath(vars, path);
|
|
21
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
onWarn?.(path);
|
|
25
|
+
return whole;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Variables read per corpus and folder. Per corpus, not per process: a browser converts one
|
|
30
|
+
* repo after another, and a folder name (`calico`) means a different file in each.
|
|
31
|
+
*/
|
|
32
|
+
const cache = new WeakMap();
|
|
33
|
+
/**
|
|
34
|
+
* Load `variables.js` from a product/version folder (a CommonJS data module that may
|
|
35
|
+
* `require('./releases.json')`), through the corpus. Cached per corpus and folder; an empty
|
|
36
|
+
* object when absent.
|
|
37
|
+
*
|
|
38
|
+
* READ as data by default (`@dogsbay/literal`, through format-docusaurus's `readDataModule`):
|
|
39
|
+
* on the Tigera repo that is 23 of 24 calico keys and every calico-cloud key; a value computed
|
|
40
|
+
* with `.split()`/`.slice()` is left unresolved and named, and its `$[token]` stays as written.
|
|
41
|
+
* With `--eval-config` the CLI RUNS the file instead (`ctx.runModule`) — code from the source
|
|
42
|
+
* repo, for a repo you trust.
|
|
43
|
+
*/
|
|
44
|
+
export function loadVariables(ctx) {
|
|
45
|
+
const corpus = ctx.corpus;
|
|
46
|
+
if (!corpus || ctx.instanceRoot === undefined)
|
|
47
|
+
return {};
|
|
48
|
+
let byFolder = cache.get(corpus);
|
|
49
|
+
if (!byFolder)
|
|
50
|
+
cache.set(corpus, (byFolder = new Map()));
|
|
51
|
+
const key = `${ctx.runModule ? "run" : "read"}:${ctx.instanceRoot}`;
|
|
52
|
+
const hit = byFolder.get(key);
|
|
53
|
+
if (hit)
|
|
54
|
+
return hit;
|
|
55
|
+
const file = ctx.instanceRoot ? `${ctx.instanceRoot}/variables.js` : "variables.js";
|
|
56
|
+
let vars = {};
|
|
57
|
+
if (corpus.paths.includes(file))
|
|
58
|
+
vars = ctx.runModule ? runVariables(file, ctx) : readVariables(file, corpus, ctx);
|
|
59
|
+
byFolder.set(key, vars);
|
|
60
|
+
return vars;
|
|
61
|
+
}
|
|
62
|
+
function runVariables(file, ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const mod = ctx.runModule(file);
|
|
65
|
+
return mod && typeof mod === "object" ? mod : {};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
ctx.warn?.(`Warning: could not load ${file} (${err.message}); $[vars] left as-is.`);
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function readVariables(file, corpus, ctx) {
|
|
73
|
+
const { value, unresolved } = readDataModule(corpus, file, { root: ctx.projectRoot ?? "", fallbackExport: "variables" });
|
|
74
|
+
if (unresolved.length) {
|
|
75
|
+
ctx.warn?.(`Warning: ${file}: ${unresolved.length} value(s) could not be read as data and are left out ` +
|
|
76
|
+
`(their $[tokens] stay as written; --eval-config runs the file instead):\n` +
|
|
77
|
+
unresolved.map((u) => ` ${u.startsWith(`${file}: `) ? u.slice(file.length + 2) : u}`).join("\n"));
|
|
78
|
+
}
|
|
79
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/adapter-tigera",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-beta.136",
|
|
4
|
+
"description": "Tigera/Calico site adapter for @dogsbay/format-docusaurus — resolves $[variable] substitution and Tigera-specific MDX components.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@dogsbay/corpus": "0.2.0-beta.136",
|
|
15
|
+
"@dogsbay/format-docusaurus": "0.2.0-beta.136"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^22.20.1",
|
|
19
|
+
"esbuild": "^0.25.0",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"vitest": "^4.1.10"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/dogsbay/dogsbay.git",
|
|
27
|
+
"directory": "packages/adapter-tigera"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"test": "vitest run"
|
|
32
|
+
}
|
|
6
33
|
}
|
package/README.md
DELETED