@hsuite/smart-engines-cli 1.5.0 → 1.5.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/README.md +5 -3
- package/dist/_lib/baas.d.ts +27 -4
- package/dist/_lib/baas.d.ts.map +1 -1
- package/dist/_lib/baas.js +76 -42
- package/dist/_lib/baas.js.map +1 -1
- package/dist/_lib/cluster-convergence.d.ts +214 -0
- package/dist/_lib/cluster-convergence.d.ts.map +1 -0
- package/dist/_lib/cluster-convergence.js +381 -0
- package/dist/_lib/cluster-convergence.js.map +1 -0
- package/dist/_lib/docker.d.ts +35 -23
- package/dist/_lib/docker.d.ts.map +1 -1
- package/dist/_lib/docker.js +58 -54
- package/dist/_lib/docker.js.map +1 -1
- package/dist/_lib/frontend-guard.d.ts +59 -0
- package/dist/_lib/frontend-guard.d.ts.map +1 -1
- package/dist/_lib/frontend-guard.js +148 -0
- package/dist/_lib/frontend-guard.js.map +1 -1
- package/dist/_lib/registry-fanout.d.ts +299 -9
- package/dist/_lib/registry-fanout.d.ts.map +1 -1
- package/dist/_lib/registry-fanout.js +557 -27
- package/dist/_lib/registry-fanout.js.map +1 -1
- package/dist/_lib/subscription-client.d.ts +14 -0
- package/dist/_lib/subscription-client.d.ts.map +1 -1
- package/dist/_lib/subscription-client.js +13 -0
- package/dist/_lib/subscription-client.js.map +1 -1
- package/dist/commands/deploy.d.ts +28 -4
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +157 -20
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/redeploy.d.ts +10 -0
- package/dist/commands/redeploy.d.ts.map +1 -1
- package/dist/commands/redeploy.js +140 -15
- package/dist/commands/redeploy.js.map +1 -1
- package/dist/commands/topup.d.ts +55 -0
- package/dist/commands/topup.d.ts.map +1 -0
- package/dist/commands/topup.js +180 -0
- package/dist/commands/topup.js.map +1 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +24 -1
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/_lib/docker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/_lib/docker.ts"],"names":[],"mappings":";;AAmEA,4BAEC;AAQD,kCAWC;AAaD,0CAIC;
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/_lib/docker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RunFn } from './baas';
|
|
1
2
|
/**
|
|
2
3
|
* Fail-closed guard shared by `deploy` and `redeploy`: refuse to ship a
|
|
3
4
|
* frontend build that still carries unfilled `REPLACE_BEFORE_DEPLOY:<key>`
|
|
@@ -10,4 +11,62 @@
|
|
|
10
11
|
* false-positive on the guard's own comparison literal (`REPLACE_BEFORE_DEPLOY:"`).
|
|
11
12
|
*/
|
|
12
13
|
export declare function assertNoDeploySentinels(dir: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* #2793: `resolveFrontendBundle` (`deploy.ts` / `redeploy.ts`) `mkdir -p`s the
|
|
16
|
+
* frontend `dir` BEFORE spawning the optional `frontend.build` hook — required
|
|
17
|
+
* (#2790, load-bearing): `execFileSync(..., { cwd: dir })` throws `ENOENT` on a
|
|
18
|
+
* missing cwd, verified empirically. Side effect: the sibling `existsSync(dir)`
|
|
19
|
+
* guard that used to catch a build that never produced its output dir can now
|
|
20
|
+
* NEVER fire once a hook is configured — `dir` always exists post-`mkdir`,
|
|
21
|
+
* whether or not the hook actually wrote into it.
|
|
22
|
+
*
|
|
23
|
+
* Nothing downstream catches an empty dir either: `assertNoDeploySentinels`'s
|
|
24
|
+
* `readdir` walk is a no-op on zero files, `tarFrontend` happily tars nothing
|
|
25
|
+
* into a syntactically valid tar, and the host only logs `size=<n>B` with no
|
|
26
|
+
* minimum. Net effect without this guard: a `frontend.build` hook that exits 0
|
|
27
|
+
* without populating `dir` ships an empty SPA bundle, silently, with no error
|
|
28
|
+
* at any layer.
|
|
29
|
+
*
|
|
30
|
+
* Assert `dir` is plausibly a built SPA: non-empty, and containing
|
|
31
|
+
* `index.html` at its root — the shape the browser-build output this CLI
|
|
32
|
+
* targets always has (`ng build`'s legacy `browser` builder — the one every
|
|
33
|
+
* shipped manifest uses — writes `index.html` directly under `outputPath`,
|
|
34
|
+
* not nested). Call this AFTER the build hook runs, so the hook remains what
|
|
35
|
+
* is allowed to populate `dir` (mirrors the existsSync timing already moved
|
|
36
|
+
* post-hook by #2788) — the failure then names the hook that ran, not the
|
|
37
|
+
* upload step three calls later that would otherwise be blamed.
|
|
38
|
+
*
|
|
39
|
+
* #2795: `frontend.build` is OPTIONAL (`ManifestFrontend.build?`), and a bare
|
|
40
|
+
* `frontend.dir` — "a pre-built/checked-in SPA dir" — is a first-class
|
|
41
|
+
* documented manifest shape. That branch has the SAME empty-bundle failure
|
|
42
|
+
* mode for a different reason: `existsSync(dir)` never checked
|
|
43
|
+
* empty-vs-populated, so an EXISTING but EMPTY dir (wiped, mis-`.gitignore`'d,
|
|
44
|
+
* stale checkout, a typo'd `dir` that resolves to an existing folder) shipped
|
|
45
|
+
* silently too. So `buildHook` is optional: pass `f.build` straight through
|
|
46
|
+
* and the remedy names whatever was actually responsible for populating `dir`
|
|
47
|
+
* on this branch — never a hook that was never configured.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertPlausibleSpaBundle(dir: string, buildHook?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* #2798 / #2791: the `frontend.bundlePath` branch of `resolveFrontendBundle`
|
|
52
|
+
* (deploy.ts / redeploy.ts) — "a pre-built tarball; takes precedence over
|
|
53
|
+
* `dir` when set" (deploy-manifest.ts) — returned BEFORE both dir-shaped
|
|
54
|
+
* guards, so an empty/truncated tarball, or one still carrying unfilled
|
|
55
|
+
* `REPLACE_BEFORE_DEPLOY:*` placeholders, uploaded unvalidated. Same silent
|
|
56
|
+
* broken-bundle symptom #2793/#2795 closed for `dir`, plus the sentinel
|
|
57
|
+
* exposure #2788 exists to prevent.
|
|
58
|
+
*
|
|
59
|
+
* The artifact here is a TAR, not a directory, so `fs.readdir` does not
|
|
60
|
+
* apply. Plausibility is checked from the archive LISTING (`tar -tf`): the
|
|
61
|
+
* bundle the host mounts must contain `index.html` at its root (`tarFrontend
|
|
62
|
+
* `-C <dir> .` puts it there; any pre-built bundle missing it has the same
|
|
63
|
+
* wrong-shape failure `assertPlausibleSpaBundle` rejects) and must be
|
|
64
|
+
* non-empty. A zero-byte file — the "build created the file then died" shape
|
|
65
|
+
* — fails both the size floor and the listing, and is named as such.
|
|
66
|
+
*
|
|
67
|
+
* Sentinels (#2791): `assertNoDeploySentinels` walks a directory tree, so the
|
|
68
|
+
* tarball is unpacked to a throwaway temp dir and the EXISTING guard runs
|
|
69
|
+
* unchanged against it — one sentinel matcher, not two that can drift.
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertPlausibleSpaTarball(tarPath: string, runFn?: RunFn): Promise<void>;
|
|
13
72
|
//# sourceMappingURL=frontend-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontend-guard.d.ts","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxE"}
|
|
1
|
+
{"version":3,"file":"frontend-guard.d.ts","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB7F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C7F"}
|
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.assertNoDeploySentinels = assertNoDeploySentinels;
|
|
37
|
+
exports.assertPlausibleSpaBundle = assertPlausibleSpaBundle;
|
|
38
|
+
exports.assertPlausibleSpaTarball = assertPlausibleSpaTarball;
|
|
4
39
|
const fs_1 = require("fs");
|
|
5
40
|
const path_1 = require("path");
|
|
41
|
+
const os_1 = require("os");
|
|
6
42
|
/**
|
|
7
43
|
* Fail-closed guard shared by `deploy` and `redeploy`: refuse to ship a
|
|
8
44
|
* frontend build that still carries unfilled `REPLACE_BEFORE_DEPLOY:<key>`
|
|
@@ -38,4 +74,116 @@ async function assertNoDeploySentinels(dir) {
|
|
|
38
74
|
(hits.length > 5 ? `, +${hits.length - 5} more` : ''));
|
|
39
75
|
}
|
|
40
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* #2793: `resolveFrontendBundle` (`deploy.ts` / `redeploy.ts`) `mkdir -p`s the
|
|
79
|
+
* frontend `dir` BEFORE spawning the optional `frontend.build` hook — required
|
|
80
|
+
* (#2790, load-bearing): `execFileSync(..., { cwd: dir })` throws `ENOENT` on a
|
|
81
|
+
* missing cwd, verified empirically. Side effect: the sibling `existsSync(dir)`
|
|
82
|
+
* guard that used to catch a build that never produced its output dir can now
|
|
83
|
+
* NEVER fire once a hook is configured — `dir` always exists post-`mkdir`,
|
|
84
|
+
* whether or not the hook actually wrote into it.
|
|
85
|
+
*
|
|
86
|
+
* Nothing downstream catches an empty dir either: `assertNoDeploySentinels`'s
|
|
87
|
+
* `readdir` walk is a no-op on zero files, `tarFrontend` happily tars nothing
|
|
88
|
+
* into a syntactically valid tar, and the host only logs `size=<n>B` with no
|
|
89
|
+
* minimum. Net effect without this guard: a `frontend.build` hook that exits 0
|
|
90
|
+
* without populating `dir` ships an empty SPA bundle, silently, with no error
|
|
91
|
+
* at any layer.
|
|
92
|
+
*
|
|
93
|
+
* Assert `dir` is plausibly a built SPA: non-empty, and containing
|
|
94
|
+
* `index.html` at its root — the shape the browser-build output this CLI
|
|
95
|
+
* targets always has (`ng build`'s legacy `browser` builder — the one every
|
|
96
|
+
* shipped manifest uses — writes `index.html` directly under `outputPath`,
|
|
97
|
+
* not nested). Call this AFTER the build hook runs, so the hook remains what
|
|
98
|
+
* is allowed to populate `dir` (mirrors the existsSync timing already moved
|
|
99
|
+
* post-hook by #2788) — the failure then names the hook that ran, not the
|
|
100
|
+
* upload step three calls later that would otherwise be blamed.
|
|
101
|
+
*
|
|
102
|
+
* #2795: `frontend.build` is OPTIONAL (`ManifestFrontend.build?`), and a bare
|
|
103
|
+
* `frontend.dir` — "a pre-built/checked-in SPA dir" — is a first-class
|
|
104
|
+
* documented manifest shape. That branch has the SAME empty-bundle failure
|
|
105
|
+
* mode for a different reason: `existsSync(dir)` never checked
|
|
106
|
+
* empty-vs-populated, so an EXISTING but EMPTY dir (wiped, mis-`.gitignore`'d,
|
|
107
|
+
* stale checkout, a typo'd `dir` that resolves to an existing folder) shipped
|
|
108
|
+
* silently too. So `buildHook` is optional: pass `f.build` straight through
|
|
109
|
+
* and the remedy names whatever was actually responsible for populating `dir`
|
|
110
|
+
* on this branch — never a hook that was never configured.
|
|
111
|
+
*/
|
|
112
|
+
async function assertPlausibleSpaBundle(dir, buildHook) {
|
|
113
|
+
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
114
|
+
const hasIndexHtml = entries.some((e) => e.isFile() && e.name === 'index.html');
|
|
115
|
+
if (entries.length > 0 && hasIndexHtml)
|
|
116
|
+
return;
|
|
117
|
+
const reason = entries.length === 0 ? 'is empty' : 'has no index.html at its root';
|
|
118
|
+
throw new Error(buildHook
|
|
119
|
+
? `frontend build produced no usable SPA bundle: ${dir} ${reason}. The ` +
|
|
120
|
+
`frontend.build hook (\`${buildHook}\`) exited 0 without writing a bundle ` +
|
|
121
|
+
`into its own output dir — verify it actually builds INTO ${dir} ` +
|
|
122
|
+
`(a mismatched --output-path / missing \`cd\` is the usual cause).`
|
|
123
|
+
: `frontend build produced no usable SPA bundle: ${dir} ${reason}, and ` +
|
|
124
|
+
`no \`frontend.build\` hook is configured — a bare frontend.dir must ` +
|
|
125
|
+
`ALREADY contain a built SPA. Build it before deploying (or add a ` +
|
|
126
|
+
`\`frontend.build\` hook to the manifest), and check frontend.dir points ` +
|
|
127
|
+
`at the real build output.`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* #2798 / #2791: the `frontend.bundlePath` branch of `resolveFrontendBundle`
|
|
131
|
+
* (deploy.ts / redeploy.ts) — "a pre-built tarball; takes precedence over
|
|
132
|
+
* `dir` when set" (deploy-manifest.ts) — returned BEFORE both dir-shaped
|
|
133
|
+
* guards, so an empty/truncated tarball, or one still carrying unfilled
|
|
134
|
+
* `REPLACE_BEFORE_DEPLOY:*` placeholders, uploaded unvalidated. Same silent
|
|
135
|
+
* broken-bundle symptom #2793/#2795 closed for `dir`, plus the sentinel
|
|
136
|
+
* exposure #2788 exists to prevent.
|
|
137
|
+
*
|
|
138
|
+
* The artifact here is a TAR, not a directory, so `fs.readdir` does not
|
|
139
|
+
* apply. Plausibility is checked from the archive LISTING (`tar -tf`): the
|
|
140
|
+
* bundle the host mounts must contain `index.html` at its root (`tarFrontend
|
|
141
|
+
* `-C <dir> .` puts it there; any pre-built bundle missing it has the same
|
|
142
|
+
* wrong-shape failure `assertPlausibleSpaBundle` rejects) and must be
|
|
143
|
+
* non-empty. A zero-byte file — the "build created the file then died" shape
|
|
144
|
+
* — fails both the size floor and the listing, and is named as such.
|
|
145
|
+
*
|
|
146
|
+
* Sentinels (#2791): `assertNoDeploySentinels` walks a directory tree, so the
|
|
147
|
+
* tarball is unpacked to a throwaway temp dir and the EXISTING guard runs
|
|
148
|
+
* unchanged against it — one sentinel matcher, not two that can drift.
|
|
149
|
+
*/
|
|
150
|
+
async function assertPlausibleSpaTarball(tarPath, runFn) {
|
|
151
|
+
const st = await fs_1.promises.stat(tarPath).catch(() => {
|
|
152
|
+
throw new Error(`frontend bundle not found: ${tarPath}`);
|
|
153
|
+
});
|
|
154
|
+
if (st.size === 0) {
|
|
155
|
+
throw new Error(`frontend bundle is a ZERO-BYTE file: ${tarPath}. A build that created ` +
|
|
156
|
+
`the tarball then died before writing it — rebuild it before deploying.`);
|
|
157
|
+
}
|
|
158
|
+
const run = runFn ?? (await Promise.resolve().then(() => __importStar(require('child_process')))).execFileSync;
|
|
159
|
+
let listing;
|
|
160
|
+
try {
|
|
161
|
+
listing = run('tar', ['-tf', tarPath], { encoding: 'utf8' });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw new Error(`frontend bundle is not a readable tar archive: ${tarPath}. Rebuild it ` +
|
|
165
|
+
`before deploying (a truncated or partially-uploaded tar fails here).`);
|
|
166
|
+
}
|
|
167
|
+
const entries = listing
|
|
168
|
+
.split('\n')
|
|
169
|
+
.map((l) => l.trim())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
const hasRootIndex = entries.some((e) => e === './index.html' || e === 'index.html');
|
|
172
|
+
if (!hasRootIndex) {
|
|
173
|
+
throw new Error(`frontend bundle has no index.html at its root: ${tarPath} (entries: ` +
|
|
174
|
+
`${entries.slice(0, 5).join(', ')}${entries.length > 5 ? `, +${entries.length - 5} more` : ''}). ` +
|
|
175
|
+
`The bundle root must contain the built SPA — the same shape ` +
|
|
176
|
+
`assertPlausibleSpaBundle enforces for frontend.dir.`);
|
|
177
|
+
}
|
|
178
|
+
// #2791: run the UNCHANGED dir-shaped sentinel walk over the unpacked
|
|
179
|
+
// bundle. Temp dir is under os.tmpdir() and best-effort removed after.
|
|
180
|
+
const probe = await fs_1.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), 'hsuite-bundlepath-'));
|
|
181
|
+
try {
|
|
182
|
+
run('tar', ['-xf', tarPath, '-C', probe], {});
|
|
183
|
+
await assertNoDeploySentinels(probe);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await fs_1.promises.rm(probe, { recursive: true, force: true }).catch(() => undefined);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
41
189
|
//# sourceMappingURL=frontend-guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontend-guard.js","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"frontend-guard.js","sourceRoot":"","sources":["../../src/_lib/frontend-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { Buffer } from 'buffer';
|
|
|
2
2
|
/** A request against one cluster's Harbor Registry-v2 API. `path` is always `/v2/...` (or `/token...`). */
|
|
3
3
|
export type RegistryRequest = {
|
|
4
4
|
ip: string;
|
|
5
|
-
method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH';
|
|
5
|
+
method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
6
6
|
path: string;
|
|
7
7
|
headers?: Record<string, string>;
|
|
8
8
|
body?: Buffer;
|
|
@@ -45,6 +45,17 @@ export type EnsureImageOpts = {
|
|
|
45
45
|
/** Test seam — defaults to {@link defaultTransport} bound to `host`. */
|
|
46
46
|
transport?: RegistryTransport;
|
|
47
47
|
};
|
|
48
|
+
/** Default per-request wall-clock budget for registry / cluster probes. */
|
|
49
|
+
export declare const DEFAULT_PROBE_TIMEOUT_MS = 10000;
|
|
50
|
+
/**
|
|
51
|
+
* Reject after `ms` instead of waiting on a request that may never settle.
|
|
52
|
+
*
|
|
53
|
+
* {@link defaultTransport} builds a bare `https.request` with no socket timeout,
|
|
54
|
+
* so a peer that completes the TCP handshake and then black-holes the response
|
|
55
|
+
* hangs its promise forever. Every probe that fans out across the cluster set
|
|
56
|
+
* wraps its requests in this.
|
|
57
|
+
*/
|
|
58
|
+
export declare function withRequestTimeout<T>(p: Promise<T>, ms: number, what: string): Promise<T>;
|
|
48
59
|
/** A parsed `WWW-Authenticate: Bearer ...` token challenge. `realm` is mandatory. */
|
|
49
60
|
export type AuthChallenge = {
|
|
50
61
|
realm: string;
|
|
@@ -91,6 +102,16 @@ export type ResolveRegistryDigestOpts = {
|
|
|
91
102
|
transport?: RegistryTransport;
|
|
92
103
|
/** Test seam — DNS A-record resolver (defaults to `dns.promises.resolve4`). */
|
|
93
104
|
resolve4?: (h: string) => Promise<string[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Per-request wall-clock budget for the manifest probe. Default 10s.
|
|
107
|
+
*
|
|
108
|
+
* Load-bearing since {@link probeServedDigests} must visit EVERY endpoint (the
|
|
109
|
+
* agreement logic needs all the answers) and so can no longer skip past a
|
|
110
|
+
* black-holing cluster the way the old first-responder read did. A node that
|
|
111
|
+
* completes the TCP handshake and then never answers would otherwise wedge the
|
|
112
|
+
* deploy indefinitely — `defaultTransport` sets no socket timeout.
|
|
113
|
+
*/
|
|
114
|
+
requestTimeoutMs?: number;
|
|
94
115
|
};
|
|
95
116
|
/**
|
|
96
117
|
* Resolve the CANONICAL manifest digest the registry serves for `<repo>:<tag>`
|
|
@@ -117,6 +138,31 @@ export type ResolveRegistryDigestOpts = {
|
|
|
117
138
|
* deployer still re-verifies).
|
|
118
139
|
*/
|
|
119
140
|
export declare function resolveRegistryDigest(opts: ResolveRegistryDigestOpts): Promise<string | null>;
|
|
141
|
+
/** What ONE cluster endpoint serves for a tag. `digest: null` ⇒ 404 / unreachable. */
|
|
142
|
+
export type ServedDigestAnswer = {
|
|
143
|
+
ip: string;
|
|
144
|
+
digest: string | null;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Ask EVERY cluster endpoint what it serves for `<repo>:<tag>` and return all
|
|
148
|
+
* the answers, in endpoint order.
|
|
149
|
+
*
|
|
150
|
+
* The federated registry name is round-robin over N INDEPENDENT Harbors, so
|
|
151
|
+
* "what does the registry serve for this tag" is a set of answers, not one
|
|
152
|
+
* answer. In the window between a `docker push` (which lands on exactly ONE
|
|
153
|
+
* cluster) and the fan-out, a re-used tag legitimately resolves to the NEW
|
|
154
|
+
* artifact on the origin and the PREVIOUS artifact on its peers. Collapsing
|
|
155
|
+
* that set to "whoever answered first" is what signed a stale digest in #2787.
|
|
156
|
+
*/
|
|
157
|
+
export declare function probeServedDigests(opts: ResolveRegistryDigestOpts): Promise<ServedDigestAnswer[]>;
|
|
158
|
+
/**
|
|
159
|
+
* Normalise a `registry.server` into the bare DNS name the endpoint resolvers
|
|
160
|
+
* and the push probe work with: scheme stripped, port stripped. One definition —
|
|
161
|
+
* this pair of regexes had accumulated four copies across three files, and a
|
|
162
|
+
* drift between them would silently change which host the fan-out resolves
|
|
163
|
+
* versus which one the push is pinned to.
|
|
164
|
+
*/
|
|
165
|
+
export declare function registryHostOf(server: string): string;
|
|
120
166
|
/** Registry coordinates a push produced (shape of `BaasInitResponse.registry`). */
|
|
121
167
|
export type FanOutRegistry = {
|
|
122
168
|
server: string;
|
|
@@ -127,8 +173,29 @@ export type FanOutRegistry = {
|
|
|
127
173
|
export type ResolveAttestationDigestOpts = {
|
|
128
174
|
registry: FanOutRegistry;
|
|
129
175
|
tag: string;
|
|
130
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* The `sha256:<64hex>` digest the push produced.
|
|
178
|
+
*
|
|
179
|
+
* On the build+push path this is NOT a local-daemon value: `buildAndPush`
|
|
180
|
+
* resolves it from the ORIGIN registry (`docker buildx imagetools inspect`
|
|
181
|
+
* against the pinned single-origin target) and hard-errors if the `docker
|
|
182
|
+
* push` summary disagrees with it. On `--skip-push` it is the round-robin
|
|
183
|
+
* registry's answer for a tag nobody is currently writing.
|
|
184
|
+
*/
|
|
131
185
|
pushDigest: string;
|
|
186
|
+
/**
|
|
187
|
+
* SINGLE-ORIGIN registry hostname the push was pinned to (`resolvePushOrigin`,
|
|
188
|
+
* #1679) — the one cluster that actually received the bytes this run.
|
|
189
|
+
*
|
|
190
|
+
* When set, the resolver returns {@link pushDigest} and nothing else: the
|
|
191
|
+
* origin's own live answer is a CROSS-CHECK (disagreement ⇒ throw), and a
|
|
192
|
+
* peer's differing answer is pre-fan-out divergence to converge, never a value
|
|
193
|
+
* to adopt (#2787). Omit it on `--skip-push` / an unpinned push: then no
|
|
194
|
+
* endpoint is privileged and the endpoints that answer must AGREE.
|
|
195
|
+
*/
|
|
196
|
+
originHost?: string;
|
|
197
|
+
/** Per-request probe timeout (ms). Default {@link DEFAULT_PROBE_TIMEOUT_MS}. */
|
|
198
|
+
requestTimeoutMs?: number;
|
|
132
199
|
/** Optional progress log sink (defaults to no-op). */
|
|
133
200
|
log?: (msg: string) => void;
|
|
134
201
|
/** Test seam — the registry transport. */
|
|
@@ -138,16 +205,100 @@ export type ResolveAttestationDigestOpts = {
|
|
|
138
205
|
};
|
|
139
206
|
/**
|
|
140
207
|
* Decide the digest to ATTEST (and to feed the fan-out): the digest Harbor
|
|
141
|
-
* SERVES for `<repo>:<tag>`, resolved the same way the deployer does.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
208
|
+
* SERVES for `<repo>:<tag>`, resolved the same way the deployer does.
|
|
209
|
+
*
|
|
210
|
+
* ## Which Harbor, though (#2787)
|
|
211
|
+
*
|
|
212
|
+
* `harbor.<domain>` is round-robin over N INDEPENDENT Harbors and a `docker
|
|
213
|
+
* push` lands on exactly ONE of them. Between the push and the fan-out, a
|
|
214
|
+
* re-used tag therefore resolves to the NEW artifact on the push origin and to
|
|
215
|
+
* the PREVIOUS artifact on its peers — both are "the registry-served digest".
|
|
216
|
+
* This function used to take whichever endpoint answered first, which on
|
|
217
|
+
* testnet signed `d889ff2a…` (a peer's stale `v12`) for a run that had just
|
|
218
|
+
* pushed `d6ae4a80…`. The deployer that received that attestation refused the
|
|
219
|
+
* image — correctly, it cannot tell a stale attestation from a swapped one —
|
|
220
|
+
* and stayed pinned to the old release while its two peers moved on.
|
|
221
|
+
*
|
|
222
|
+
* So:
|
|
223
|
+
* - **`originHost` set** (a pinned push landed there) → the result is ALWAYS
|
|
224
|
+
* {@link ResolveAttestationDigestOpts.pushDigest}, the digest `buildAndPush`
|
|
225
|
+
* read back from the origin registry after the upload. The origin's live
|
|
226
|
+
* answer is only a cross-check: if it answers something else we THROW (the
|
|
227
|
+
* tag moved underneath us and either value would be a guess), and if it does
|
|
228
|
+
* not answer at all we still attest `pushDigest` rather than fall through to
|
|
229
|
+
* the peers — the silent-origin case is exactly when the peers unanimously
|
|
230
|
+
* serve the PREVIOUS artifact. Peers serving something else are logged and
|
|
231
|
+
* left to the fan-out, which copies this manifest to them and re-verifies
|
|
232
|
+
* (throwing if it can't).
|
|
233
|
+
* - **no `originHost`** (`--skip-push` / unpinned push) → no endpoint is
|
|
234
|
+
* privileged, so the endpoints that answer MUST agree. A split mesh THROWS,
|
|
235
|
+
* naming every endpoint and its digest — #2766 is a live condition here and
|
|
236
|
+
* a coin flip is not an attestation. A served digest may only OVERRIDE
|
|
237
|
+
* `pushDigest` when the WHOLE endpoint set answered; a lone answer from a
|
|
238
|
+
* partially-reachable mesh also THROWS, because the fan-out would then copy
|
|
239
|
+
* that one endpoint's bytes everywhere and make it mesh truth.
|
|
146
240
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
241
|
+
* Falls back to `pushDigest` without complaint when NO endpoint can be probed
|
|
242
|
+
* (offline / dev registry / transient) — a fail-SOFT, never a fail-open: the
|
|
243
|
+
* deployer independently re-verifies the served digest against the attested one
|
|
244
|
+
* before it pins the pod.
|
|
245
|
+
*
|
|
246
|
+
* See {@link resolveRegistryDigest} for why a registry-served digest — not the
|
|
247
|
+
* `docker push` summary — is authoritative for the #1445 strict-equality gate
|
|
248
|
+
* (issue #1589).
|
|
149
249
|
*/
|
|
150
250
|
export declare function resolveAttestationDigest(opts: ResolveAttestationDigestOpts): Promise<string>;
|
|
251
|
+
/**
|
|
252
|
+
* Result of {@link assertTagFreeOnMesh}: what each cluster Harbor serves for
|
|
253
|
+
* the candidate tag, or `null` when the registry is not a federated mesh
|
|
254
|
+
* (nothing was checked — identical semantics to `fanOutPushedImage`'s null).
|
|
255
|
+
*/
|
|
256
|
+
export type MeshTagOccupancy = {
|
|
257
|
+
host: string;
|
|
258
|
+
tag: string;
|
|
259
|
+
/** One entry per cluster ingress: the digest it serves, or `null` when the tag is absent there. */
|
|
260
|
+
perEndpoint: Array<{
|
|
261
|
+
ip: string;
|
|
262
|
+
digest: string | null;
|
|
263
|
+
}>;
|
|
264
|
+
};
|
|
265
|
+
/** Options for {@link assertTagFreeOnMesh}. */
|
|
266
|
+
export type AssertTagFreeOnMeshOpts = {
|
|
267
|
+
registry: FanOutRegistry;
|
|
268
|
+
/** The candidate tag the deploy is about to `docker push` under. */
|
|
269
|
+
tag: string;
|
|
270
|
+
/** Test seam — DNS A-record resolver (defaults to `dns.promises.resolve4`). */
|
|
271
|
+
resolve4?: (h: string) => Promise<string[]>;
|
|
272
|
+
/** Test seam — the registry transport. */
|
|
273
|
+
transport?: RegistryTransport;
|
|
274
|
+
/** Optional progress log sink (defaults to no-op). */
|
|
275
|
+
log?: (msg: string) => void;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* #2766 — BEFORE a `docker push`, prove the candidate tag is unoccupied on
|
|
279
|
+
* EVERY cluster Harbor of the mesh. Tag immutability is enforced per-cluster,
|
|
280
|
+
* so it cannot prevent divergence BETWEEN registries: a tag that already
|
|
281
|
+
* exists on any one of them (at any digest — the about-to-be-pushed build has
|
|
282
|
+
* a fresh, unknowable digest) makes the deploy 412 mid-fan-out at best, and at
|
|
283
|
+
* worst leaves the tag permanently inconsistent across the mesh (sn1/sn2 at
|
|
284
|
+
* one digest, sn3 at another, all immutable — unrecoverable without operator
|
|
285
|
+
* action). This check moves that failure BEFORE the build/push, where the
|
|
286
|
+
* operator just picks a fresh tag, and lists the divergence it found.
|
|
287
|
+
*
|
|
288
|
+
* Semantics:
|
|
289
|
+
* - 0/1 endpoint, or DNS that does not fan out → `null` (single-target
|
|
290
|
+
* registry; a single origin's own immutability already protects it).
|
|
291
|
+
* - tag absent EVERYWHERE → the occupancy (all `digest: null`) — free to push.
|
|
292
|
+
* - tag present on ANY endpoint at ANY digest → THROWS, one line per cluster
|
|
293
|
+
* (`ip → sha256:… | absent`). A single-origin pre-check must never be
|
|
294
|
+
* treated as authoritative anywhere in this path — the exact check that
|
|
295
|
+
* produced the v10 incident.
|
|
296
|
+
*
|
|
297
|
+
* Fail-open is deliberate on TRANSPORT errors per endpoint? NO — a cluster we
|
|
298
|
+
* cannot interrogate is a cluster we cannot vouch for, so a non-404/200 answer
|
|
299
|
+
* is fatal too. Only the not-a-mesh case (above) returns `null`.
|
|
300
|
+
*/
|
|
301
|
+
export declare function assertTagFreeOnMesh(opts: AssertTagFreeOnMeshOpts): Promise<MeshTagOccupancy | null>;
|
|
151
302
|
export type FanOutPushedImageOpts = {
|
|
152
303
|
registry: FanOutRegistry;
|
|
153
304
|
tag: string;
|
|
@@ -175,4 +326,143 @@ export type FanOutPushedImageOpts = {
|
|
|
175
326
|
* resolves, so a failure here means a dev/local registry where fan-out is moot.
|
|
176
327
|
*/
|
|
177
328
|
export declare function fanOutPushedImage(opts: FanOutPushedImageOpts): Promise<FanOutReport | null>;
|
|
329
|
+
/** Options for {@link resolvePushOrigin}. */
|
|
330
|
+
export type ResolvePushOriginOpts = {
|
|
331
|
+
/** Federated (round-robin) Harbor host, e.g. `harbor.testnet.hsuite.network`. */
|
|
332
|
+
host: string;
|
|
333
|
+
/** Repository path the push will target, e.g. `hsuite-customers-<appId>/app`. */
|
|
334
|
+
repository: string;
|
|
335
|
+
username: string;
|
|
336
|
+
password: string;
|
|
337
|
+
/** Test seam — the registry transport (defaults to {@link defaultTransport}). */
|
|
338
|
+
transport?: RegistryTransport;
|
|
339
|
+
/** Test seam — DNS A-record resolver (defaults to `dns.promises.resolve4`). */
|
|
340
|
+
resolve4?: (h: string) => Promise<string[]>;
|
|
341
|
+
/**
|
|
342
|
+
* Already-resolved cluster ingress IPs for {@link ResolvePushOriginOpts.host}.
|
|
343
|
+
* Supplied by {@link requirePushOrigin} so ONE DNS answer decides both "is
|
|
344
|
+
* this registry federated?" and "which origins do we probe?" — two
|
|
345
|
+
* independent lookups can disagree, and the whole point of this function is
|
|
346
|
+
* that a single answer set is authoritative for one push.
|
|
347
|
+
*/
|
|
348
|
+
endpoints?: string[];
|
|
349
|
+
/** Optional progress log sink (defaults to no-op). */
|
|
350
|
+
log?: (msg: string) => void;
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Resolve the SINGLE-ORIGIN hostname a `docker push` must be aimed at, by
|
|
354
|
+
* asking the mesh which name it wants upload traffic on. Returns `null` when
|
|
355
|
+
* there is nothing to pin (single-target registry, no server-side pin, or a pin
|
|
356
|
+
* that fails validation) — the caller then pushes at the round-robin name, i.e.
|
|
357
|
+
* exactly today's behaviour.
|
|
358
|
+
*
|
|
359
|
+
* ## Why the push must be pinned at all (#1679)
|
|
360
|
+
*
|
|
361
|
+
* `harbor.<domain>` is public-DNS round-robin over three INDEPENDENT Harbors.
|
|
362
|
+
* A chunked blob upload (`POST` init → `PATCH` → `PUT` finalize) is stateful:
|
|
363
|
+
* the `_state` token is HMAC'd by the serving registry and the uploaded bytes
|
|
364
|
+
* sit on that cluster's own ReadWriteOnce PVC. When the client reconnects
|
|
365
|
+
* between requests and round-robin lands the next hop on a different cluster,
|
|
366
|
+
* the upload dies (`blob upload invalid` / `invalid secret`). Sharing
|
|
367
|
+
* `REGISTRY_HTTP_SECRET` and the token CA across the mesh (PR #2763) fixes the
|
|
368
|
+
* HMAC and the bearer token, but NOT the blob bytes — those are still local to
|
|
369
|
+
* one cluster. The push has to stay on one origin end to end; the fan-out then
|
|
370
|
+
* distributes the finished artifact ({@link fanOutPushedImage}).
|
|
371
|
+
*
|
|
372
|
+
* ## Why we ASK instead of deriving the name
|
|
373
|
+
*
|
|
374
|
+
* Each cluster's registry is already pinned server-side (`REGISTRY_HTTP_HOST`)
|
|
375
|
+
* and advertises that name in the `Location` of an upload-init. The server
|
|
376
|
+
* honours it; docker 29.5.2 does not — it keeps the host it DIALLED and reuses
|
|
377
|
+
* only the path+query of the `Location`, so the server-side pin never reaches
|
|
378
|
+
* the wire. Reading the advertised host and dialling it ourselves is therefore
|
|
379
|
+
* the client-side half of a pin the server already publishes. Deriving
|
|
380
|
+
* `harbor-sn1` from `harbor` by string surgery would instead bake a testnet DNS
|
|
381
|
+
* convention into a customer-facing CLI, and silently mis-aim any other mesh.
|
|
382
|
+
*
|
|
383
|
+
* ## Validation
|
|
384
|
+
*
|
|
385
|
+
* The advertised value is server-controlled, and the CLI hands it to
|
|
386
|
+
* `docker login` / `docker push` — so it gates the robot credential and the
|
|
387
|
+
* customer's image. Three guards, all fail-CLOSED (return `null`, never throw;
|
|
388
|
+
* the caller then pushes unpinned, exactly as it would have anyway):
|
|
389
|
+
*
|
|
390
|
+
* 1. Not an IP literal — no cert covers one here, and "does this address
|
|
391
|
+
* resolve to itself" is not an identity check.
|
|
392
|
+
* 2. Inside `host`'s own parent DNS zone. A registry may only nominate a name
|
|
393
|
+
* the OPERATOR already controls, never an arbitrary internet name.
|
|
394
|
+
* 3. Resolves to exactly ONE address, and that address is the very cluster
|
|
395
|
+
* that advertised it.
|
|
396
|
+
*
|
|
397
|
+
* RESIDUAL RISK (accepted, documented): validation and use are two different
|
|
398
|
+
* DNS lookups — ours via `dns.resolve4`, then docker's own, moments later. A
|
|
399
|
+
* DNS rebind between them is not detectable from here, because docker resolves
|
|
400
|
+
* the hostname itself and takes no IP pin. Guard 2 is what makes that
|
|
401
|
+
* uninteresting: a rebind would need control of the operator's own zone, and an
|
|
402
|
+
* attacker holding that already owns `harbor.<domain>` itself. Closing the
|
|
403
|
+
* window completely means not shelling out to `docker` for the push at all
|
|
404
|
+
* (doing the upload over the Registry-v2 transport in this file, which does pin
|
|
405
|
+
* IP + SNI) — a much larger change, tracked separately.
|
|
406
|
+
*
|
|
407
|
+
* The probe opens one blob-upload session per attempt and cancels it
|
|
408
|
+
* (`DELETE`), so it leaves no half-open upload behind.
|
|
409
|
+
*/
|
|
410
|
+
export declare function resolvePushOrigin(opts: ResolvePushOriginOpts): Promise<string | null>;
|
|
411
|
+
/**
|
|
412
|
+
* Options for {@link requirePushOrigin} — identical to
|
|
413
|
+
* {@link ResolvePushOriginOpts}; the wrapper differs in what it does with a
|
|
414
|
+
* failed pin, not in what it needs to attempt one. Named separately so the
|
|
415
|
+
* fail-closed entry point has its own referenceable contract.
|
|
416
|
+
*/
|
|
417
|
+
export type RequirePushOriginOpts = ResolvePushOriginOpts;
|
|
418
|
+
/**
|
|
419
|
+
* The pin every push goes through (#2887). Wraps {@link resolvePushOrigin} and
|
|
420
|
+
* turns its fail-OPEN `null` into a hard failure **when the registry name is
|
|
421
|
+
* federated** — i.e. when it resolves to more than one origin.
|
|
422
|
+
*
|
|
423
|
+
* ## Why the fail-open had to go
|
|
424
|
+
*
|
|
425
|
+
* `resolvePushOrigin` returning `null` means "I could not pin this push". Its
|
|
426
|
+
* callers then pushed at the round-robin name, which is documented as
|
|
427
|
+
* "exactly today's behaviour" — but on a federated mesh today's behaviour does
|
|
428
|
+
* not work. Measured on testnet 2026-08-27 against
|
|
429
|
+
* `harbor.testnet.hsuite.network` (three A records, three INDEPENDENT Harbors,
|
|
430
|
+
* each signing its own bearer tokens and holding blob-upload sessions on its
|
|
431
|
+
* own ReadWriteOnce PVC): `Login Succeeded`, then some layers reporting
|
|
432
|
+
* `Layer already exists` while others sat on `Waiting`, then
|
|
433
|
+
* `error from registry: unauthorized to access repository: …, action: push`
|
|
434
|
+
* — on all 6 retries. The identical push aimed at `harbor-sn2.…` succeeded on
|
|
435
|
+
* the first attempt, then again at sn1 and sn3.
|
|
436
|
+
*
|
|
437
|
+
* So the unpinned fall-back is not a degraded push, it is a push that cannot
|
|
438
|
+
* succeed, and taking it silently costs a full image build plus six retries
|
|
439
|
+
* before failing with an error message that blames permissions. Refusing up
|
|
440
|
+
* front — naming the mesh, its origins, and the server-side knob that fixes it
|
|
441
|
+
* — is strictly better information for the same outcome.
|
|
442
|
+
*
|
|
443
|
+
* ## What is still a legitimate no-pin (returns `null`, no throw)
|
|
444
|
+
*
|
|
445
|
+
* - ONE origin. A dev / local / single-VIP registry cannot split a push
|
|
446
|
+
* across clusters, so there is nothing to pin and nothing to fail.
|
|
447
|
+
* - DNS that cannot enumerate the name at all. `dns.resolve4` (this module's
|
|
448
|
+
* resolver) ignores `/etc/hosts` while docker's own resolution does not, so
|
|
449
|
+
* an unresolvable name here can still be a perfectly working single-origin
|
|
450
|
+
* registry on the developer's machine. Unproven federation is not proven
|
|
451
|
+
* federation; we log it and let the push proceed.
|
|
452
|
+
*
|
|
453
|
+
* RESIDUAL RISK (accepted, documented): the A-record count is the ONLY signal
|
|
454
|
+
* we have for "is this mesh federated". A resolver that answers with fewer
|
|
455
|
+
* records than the mesh really has — a spoofed or on-path answer, or simply a
|
|
456
|
+
* health-pruned view at query time — makes a three-Harbor mesh look
|
|
457
|
+
* single-origin, and the push falls back to unpinned. That is not a
|
|
458
|
+
* credential-disclosure risk (the three validation guards in
|
|
459
|
+
* {@link resolvePushOrigin} are untouched, and the fall-back still targets the
|
|
460
|
+
* operator's own round-robin name, resolved independently by docker); the cost
|
|
461
|
+
* is reinstating the failure mode above — a wasted build and six confusing
|
|
462
|
+
* retries. Removing it needs a source of mesh cardinality that is not DNS.
|
|
463
|
+
*
|
|
464
|
+
* Callers that genuinely want no pin have `--skip-push` (nothing is uploaded,
|
|
465
|
+
* so there is no upload session to keep on one origin).
|
|
466
|
+
*/
|
|
467
|
+
export declare function requirePushOrigin(opts: RequirePushOriginOpts): Promise<string | null>;
|
|
178
468
|
//# sourceMappingURL=registry-fanout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-fanout.d.ts","sourceRoot":"","sources":["../../src/_lib/registry-fanout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry-fanout.d.ts","sourceRoot":"","sources":["../../src/_lib/registry-fanout.ts"],"names":[],"mappings":"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"}
|