@red-hat-developer-hub/cli-module-install-dynamic-plugins 0.2.0
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/CHANGELOG.md +7 -0
- package/README.md +126 -0
- package/bin/install-dynamic-plugins +32 -0
- package/dist/catalog-index.cjs.js +242 -0
- package/dist/catalog-index.cjs.js.map +1 -0
- package/dist/command.cjs.js +12 -0
- package/dist/command.cjs.js.map +1 -0
- package/dist/concurrency.cjs.js +86 -0
- package/dist/concurrency.cjs.js.map +1 -0
- package/dist/errors.cjs.js +11 -0
- package/dist/errors.cjs.js.map +1 -0
- package/dist/image-cache.cjs.js +116 -0
- package/dist/image-cache.cjs.js.map +1 -0
- package/dist/image-resolver.cjs.js +24 -0
- package/dist/image-resolver.cjs.js.map +1 -0
- package/dist/index.cjs.js +20 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/installer-npm.cjs.js +111 -0
- package/dist/installer-npm.cjs.js.map +1 -0
- package/dist/installer-oci.cjs.js +106 -0
- package/dist/installer-oci.cjs.js.map +1 -0
- package/dist/installer.cjs.js +426 -0
- package/dist/installer.cjs.js.map +1 -0
- package/dist/integrity.cjs.js +79 -0
- package/dist/integrity.cjs.js.map +1 -0
- package/dist/lock-file.cjs.js +100 -0
- package/dist/lock-file.cjs.js.map +1 -0
- package/dist/log.cjs.js +9 -0
- package/dist/log.cjs.js.map +1 -0
- package/dist/merger.cjs.js +333 -0
- package/dist/merger.cjs.js.map +1 -0
- package/dist/npm-key.cjs.js +44 -0
- package/dist/npm-key.cjs.js.map +1 -0
- package/dist/oci-key.cjs.js +102 -0
- package/dist/oci-key.cjs.js.map +1 -0
- package/dist/package.json.cjs.js +104 -0
- package/dist/package.json.cjs.js.map +1 -0
- package/dist/plugin-hash.cjs.js +85 -0
- package/dist/plugin-hash.cjs.js.map +1 -0
- package/dist/run.cjs.js +37 -0
- package/dist/run.cjs.js.map +1 -0
- package/dist/skopeo.cjs.js +87 -0
- package/dist/skopeo.cjs.js.map +1 -0
- package/dist/tar-extract.cjs.js +155 -0
- package/dist/tar-extract.cjs.js.map +1 -0
- package/dist/types.cjs.js +45 -0
- package/dist/types.cjs.js.map +1 -0
- package/dist/util.cjs.js +56 -0
- package/dist/util.cjs.js.map +1 -0
- package/dist/which.cjs.js +45 -0
- package/dist/which.cjs.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var fs = require('node:fs/promises');
|
|
5
|
+
var errors = require('./errors.cjs.js');
|
|
6
|
+
var log = require('./log.cjs.js');
|
|
7
|
+
|
|
8
|
+
function _interopNamespaceCompat(e) {
|
|
9
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
27
|
+
|
|
28
|
+
const POLL_INTERVAL_MS = 1e3;
|
|
29
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
30
|
+
async function createLock(lockPath) {
|
|
31
|
+
const timeoutMs = parseLockTimeout(
|
|
32
|
+
process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS
|
|
33
|
+
);
|
|
34
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
|
+
for (; ; ) {
|
|
36
|
+
try {
|
|
37
|
+
await fs__namespace.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
38
|
+
log.log(`======= Created lock file: ${lockPath}`);
|
|
39
|
+
return;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code !== "EEXIST") throw err;
|
|
42
|
+
}
|
|
43
|
+
if (Date.now() >= deadline) {
|
|
44
|
+
throw new errors.InstallException(
|
|
45
|
+
`Timed out after ${timeoutMs}ms waiting for lock file ${lockPath}. Another install may be stuck \u2014 remove the file manually to proceed.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
log.log(`======= Waiting for lock to be released: ${lockPath}`);
|
|
49
|
+
await waitForPath(lockPath, deadline);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function parseLockTimeout(raw) {
|
|
53
|
+
if (!raw) return DEFAULT_LOCK_TIMEOUT_MS;
|
|
54
|
+
const n = Number.parseInt(raw, 10);
|
|
55
|
+
return Number.isFinite(n) && n >= 1 ? n : DEFAULT_LOCK_TIMEOUT_MS;
|
|
56
|
+
}
|
|
57
|
+
async function removeLock(lockPath) {
|
|
58
|
+
try {
|
|
59
|
+
await fs__namespace.unlink(lockPath);
|
|
60
|
+
log.log(`======= Removed lock file: ${lockPath}`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err.code !== "ENOENT") throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function registerLockCleanup(lockPath) {
|
|
66
|
+
const cleanup = () => {
|
|
67
|
+
try {
|
|
68
|
+
node_fs.unlinkSync(lockPath);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
process.on("exit", cleanup);
|
|
73
|
+
process.on("SIGTERM", () => {
|
|
74
|
+
cleanup();
|
|
75
|
+
process.exit(0);
|
|
76
|
+
});
|
|
77
|
+
process.on("SIGINT", () => {
|
|
78
|
+
cleanup();
|
|
79
|
+
process.exit(130);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function waitForPath(lockPath, deadline) {
|
|
83
|
+
for (; ; ) {
|
|
84
|
+
try {
|
|
85
|
+
await fs__namespace.access(lockPath);
|
|
86
|
+
} catch {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (Date.now() >= deadline) return;
|
|
90
|
+
await sleep(POLL_INTERVAL_MS);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
exports.createLock = createLock;
|
|
98
|
+
exports.registerLockCleanup = registerLockCleanup;
|
|
99
|
+
exports.removeLock = removeLock;
|
|
100
|
+
//# sourceMappingURL=lock-file.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-file.cjs.js","sources":["../src/lock-file.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { unlinkSync } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { InstallException } from './errors';\nimport { log } from './log';\n\nconst POLL_INTERVAL_MS = 1000;\nconst DEFAULT_LOCK_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Acquire an exclusive lock file. If the file exists we wait (polling every\n * second) until it disappears, then try to create it atomically with the\n * `wx` flag. Bounded by `DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS` (default 10 min)\n * so a stale lock from a `kill -9`'d process doesn't wedge the init container\n * forever — override via env var.\n */\nexport async function createLock(lockPath: string): Promise<void> {\n const timeoutMs = parseLockTimeout(\n process.env.DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS,\n );\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n await fs.writeFile(lockPath, String(process.pid), { flag: 'wx' });\n log(`======= Created lock file: ${lockPath}`);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n }\n if (Date.now() >= deadline) {\n throw new InstallException(\n `Timed out after ${timeoutMs}ms waiting for lock file ${lockPath}. ` +\n `Another install may be stuck — remove the file manually to proceed.`,\n );\n }\n log(`======= Waiting for lock to be released: ${lockPath}`);\n await waitForPath(lockPath, deadline);\n }\n}\n\nfunction parseLockTimeout(raw: string | undefined): number {\n if (!raw) return DEFAULT_LOCK_TIMEOUT_MS;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : DEFAULT_LOCK_TIMEOUT_MS;\n}\n\nexport async function removeLock(lockPath: string): Promise<void> {\n try {\n await fs.unlink(lockPath);\n log(`======= Removed lock file: ${lockPath}`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n}\n\n/** Register sync best-effort cleanup on process exit / SIGTERM / SIGINT. */\nexport function registerLockCleanup(lockPath: string): void {\n const cleanup = (): void => {\n try {\n unlinkSync(lockPath);\n } catch {\n /* lock already gone */\n }\n };\n process.on('exit', cleanup);\n process.on('SIGTERM', () => {\n cleanup();\n process.exit(0);\n });\n process.on('SIGINT', () => {\n cleanup();\n process.exit(130);\n });\n}\n\nasync function waitForPath(lockPath: string, deadline: number): Promise<void> {\n for (;;) {\n try {\n await fs.access(lockPath);\n } catch {\n return; // gone\n }\n if (Date.now() >= deadline) return;\n await sleep(POLL_INTERVAL_MS);\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"],"names":["fs","log","InstallException","unlinkSync"],"mappings":"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"}
|
package/dist/log.cjs.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.cjs.js","sources":["../src/log.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport function log(message: string): void {\n process.stdout.write(`${message}\\n`);\n}\n"],"names":[],"mappings":";;AAeO,SAAS,IAAI,OAAA,EAAuB;AACzC,EAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,OAAO;AAAA,CAAI,CAAA;AACrC;;;;"}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('node:fs/promises');
|
|
4
|
+
require('yaml');
|
|
5
|
+
var errors = require('./errors.cjs.js');
|
|
6
|
+
var log = require('./log.cjs.js');
|
|
7
|
+
var npmKey = require('./npm-key.cjs.js');
|
|
8
|
+
var ociKey = require('./oci-key.cjs.js');
|
|
9
|
+
var types = require('./types.cjs.js');
|
|
10
|
+
var util = require('./util.cjs.js');
|
|
11
|
+
|
|
12
|
+
function isForbiddenKey(key) {
|
|
13
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
14
|
+
}
|
|
15
|
+
function safeSet(dst, key, value) {
|
|
16
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype")
|
|
17
|
+
return;
|
|
18
|
+
Object.defineProperty(dst, key, {
|
|
19
|
+
value,
|
|
20
|
+
writable: true,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function deepMerge(src, dst, prefix = "") {
|
|
26
|
+
for (const [key, value] of Object.entries(src)) {
|
|
27
|
+
if (isForbiddenKey(key)) continue;
|
|
28
|
+
const dstRecord = dst;
|
|
29
|
+
if (util.isPlainObject(value)) {
|
|
30
|
+
const existing = dstRecord[key];
|
|
31
|
+
const node = util.isPlainObject(existing) ? existing : {};
|
|
32
|
+
safeSet(dstRecord, key, node);
|
|
33
|
+
deepMerge(value, node, `${prefix}${key}.`);
|
|
34
|
+
} else {
|
|
35
|
+
if (key in dst && !isEqual(dstRecord[key], value)) {
|
|
36
|
+
throw new errors.InstallException(
|
|
37
|
+
`Config key '${prefix}${key}' defined differently for 2 dynamic plugins`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
safeSet(dstRecord, key, value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return dst;
|
|
44
|
+
}
|
|
45
|
+
async function mergePlugin(plugin, allPlugins, configFile, level, imageCache) {
|
|
46
|
+
if (typeof plugin.package !== "string") {
|
|
47
|
+
throw new errors.InstallException(
|
|
48
|
+
`content of the 'plugins.package' field must be a string in ${configFile}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (plugin.package.startsWith(types.OCI_PROTO)) {
|
|
52
|
+
await mergeOciPlugin(plugin, allPlugins, configFile, level, imageCache);
|
|
53
|
+
} else {
|
|
54
|
+
mergeNpmPlugin(plugin, allPlugins, configFile, level);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function mergeNpmPlugin(plugin, allPlugins, configFile, level) {
|
|
58
|
+
const key = npmKey.npmPluginKey(plugin.package);
|
|
59
|
+
doMerge(key, plugin, allPlugins, configFile, level);
|
|
60
|
+
}
|
|
61
|
+
async function mergeOciPlugin(plugin, allPlugins, configFile, level, imageCache) {
|
|
62
|
+
let parsed = await ociKey.ociPluginKey(plugin.package, imageCache);
|
|
63
|
+
if (parsed.inherit && parsed.resolvedPath === null) {
|
|
64
|
+
parsed = resolveInherit(plugin, allPlugins, parsed);
|
|
65
|
+
} else if (!plugin.package.includes("!") && parsed.resolvedPath) {
|
|
66
|
+
plugin.package = `${plugin.package}!${parsed.resolvedPath}`;
|
|
67
|
+
}
|
|
68
|
+
plugin.version = parsed.version;
|
|
69
|
+
const existing = allPlugins[parsed.pluginKey];
|
|
70
|
+
if (!existing) {
|
|
71
|
+
if (parsed.inherit) {
|
|
72
|
+
throw new errors.InstallException(
|
|
73
|
+
`ERROR: {{inherit}} tag is set and there is currently no resolved tag or digest for ${plugin.package} in ${configFile}.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
log.log(
|
|
77
|
+
`
|
|
78
|
+
======= Adding new dynamic plugin configuration for version \`${parsed.version}\` of ${parsed.pluginKey}`
|
|
79
|
+
);
|
|
80
|
+
plugin.last_modified_level = level;
|
|
81
|
+
allPlugins[parsed.pluginKey] = plugin;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
log.log(`
|
|
85
|
+
======= Overriding dynamic plugin configuration ${parsed.pluginKey}`);
|
|
86
|
+
if (existing.last_modified_level === level) {
|
|
87
|
+
throw new errors.InstallException(
|
|
88
|
+
`Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (!parsed.inherit) {
|
|
92
|
+
existing.package = plugin.package;
|
|
93
|
+
if (existing.version !== parsed.version) {
|
|
94
|
+
log.log(
|
|
95
|
+
`INFO: Overriding version for ${parsed.pluginKey} from \`${existing.version ?? ""}\` to \`${parsed.version}\``
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
existing.version = parsed.version;
|
|
99
|
+
}
|
|
100
|
+
copyPluginFields(plugin, existing, [
|
|
101
|
+
"package",
|
|
102
|
+
"version",
|
|
103
|
+
"last_modified_level"
|
|
104
|
+
]);
|
|
105
|
+
existing.last_modified_level = level;
|
|
106
|
+
}
|
|
107
|
+
function resolveInherit(plugin, allPlugins, parsed) {
|
|
108
|
+
const prefix = `${parsed.pluginKey}:!`;
|
|
109
|
+
const matches = Object.keys(allPlugins).filter((k) => k.startsWith(prefix));
|
|
110
|
+
if (matches.length === 0) {
|
|
111
|
+
throw new errors.InstallException(
|
|
112
|
+
`Cannot use {{inherit}} for ${parsed.pluginKey}: no existing plugin configuration found. Ensure a plugin from this image is defined in an included file with an explicit version.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (matches.length > 1) {
|
|
116
|
+
const formatted = matches.map((m) => {
|
|
117
|
+
const baseVersion = allPlugins[m]?.version ?? "";
|
|
118
|
+
const registryPart2 = m.split(":!")[0] ?? "";
|
|
119
|
+
const pathPart = m.split(":!").at(-1) ?? "";
|
|
120
|
+
return ` - ${registryPart2}:${baseVersion}!${pathPart}`;
|
|
121
|
+
}).join("\n");
|
|
122
|
+
throw new errors.InstallException(
|
|
123
|
+
`Cannot use {{inherit}} for ${parsed.pluginKey}: multiple plugins from this image are defined in the included files:
|
|
124
|
+
${formatted}
|
|
125
|
+
Please specify which plugin configuration to inherit from using: ${parsed.pluginKey}:{{inherit}}!<plugin_path>`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const matchedKey = matches[0];
|
|
129
|
+
const basePlugin = allPlugins[matchedKey];
|
|
130
|
+
if (!basePlugin?.version) {
|
|
131
|
+
throw new errors.InstallException(
|
|
132
|
+
`Internal: inherited plugin ${matchedKey} has no version`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const version = basePlugin.version;
|
|
136
|
+
const resolvedPath = matchedKey.split(":!").at(-1) ?? "";
|
|
137
|
+
const registryPart = matchedKey.split(":!")[0] ?? "";
|
|
138
|
+
plugin.package = `${registryPart}:${version}!${resolvedPath}`;
|
|
139
|
+
log.log(
|
|
140
|
+
`
|
|
141
|
+
======= Inheriting version \`${version}\` and plugin path \`${resolvedPath}\` for ${matchedKey}`
|
|
142
|
+
);
|
|
143
|
+
return { pluginKey: matchedKey, version, inherit: true, resolvedPath };
|
|
144
|
+
}
|
|
145
|
+
function doMerge(key, plugin, allPlugins, configFile, level) {
|
|
146
|
+
const existing = allPlugins[key];
|
|
147
|
+
if (!existing) {
|
|
148
|
+
log.log(`
|
|
149
|
+
======= Adding new dynamic plugin configuration for ${key}`);
|
|
150
|
+
plugin.last_modified_level = level;
|
|
151
|
+
allPlugins[key] = plugin;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
log.log(`
|
|
155
|
+
======= Overriding dynamic plugin configuration ${key}`);
|
|
156
|
+
if (existing.last_modified_level === level) {
|
|
157
|
+
throw new errors.InstallException(
|
|
158
|
+
`Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
copyPluginFields(plugin, existing, ["last_modified_level"]);
|
|
162
|
+
existing.last_modified_level = level;
|
|
163
|
+
}
|
|
164
|
+
function copyPluginFields(src, dst, skip) {
|
|
165
|
+
const skipSet = new Set(skip);
|
|
166
|
+
for (const [k, v] of Object.entries(src)) {
|
|
167
|
+
if (skipSet.has(k) || isForbiddenKey(k)) continue;
|
|
168
|
+
safeSet(dst, k, v);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function isEqual(a, b) {
|
|
172
|
+
if (a === b) return true;
|
|
173
|
+
if (typeof a !== typeof b) return false;
|
|
174
|
+
if (Array.isArray(a) && Array.isArray(b)) return isArrayEqual(a, b);
|
|
175
|
+
if (util.isPlainObject(a) && util.isPlainObject(b)) return isObjectEqual(a, b);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
function isArrayEqual(a, b) {
|
|
179
|
+
if (a.length !== b.length) return false;
|
|
180
|
+
return a.every((v, i) => isEqual(v, b[i]));
|
|
181
|
+
}
|
|
182
|
+
function isObjectEqual(a, b) {
|
|
183
|
+
const keysA = Object.keys(a);
|
|
184
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
185
|
+
return keysA.every((k) => isEqual(a[k], b[k]));
|
|
186
|
+
}
|
|
187
|
+
function entryKeyOf(registry, path) {
|
|
188
|
+
return `${registry} ${path ?? ""}`;
|
|
189
|
+
}
|
|
190
|
+
function logInvalidOciFormat(pkg, sourceFile, disabled) {
|
|
191
|
+
if (!disabled) {
|
|
192
|
+
throw new errors.InstallException(
|
|
193
|
+
`oci package '${pkg}' is not in the expected format '${types.OCI_PROTO}<registry>:<tag>' or '${types.OCI_PROTO}<registry>@<algo>:<digest>' (optionally followed by '!<path>') in ${sourceFile} where <registry> may include a port (e.g. host:5000/path) and <algo> is one of ${types.RECOGNIZED_ALGORITHMS.join(", ")}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
log.log(
|
|
197
|
+
`WARNING: Skipping disabled OCI plugin with invalid format: '${pkg}' in ${sourceFile}. Expected format: '${types.OCI_PROTO}<registry>:<tag>' or '${types.OCI_PROTO}<registry>@<algo>:<digest>' (optionally followed by '!<path>') where <registry> may include a port (e.g. host:5000/path) and <algo> is one of ${types.RECOGNIZED_ALGORITHMS.join(", ")}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
function recordEntryState(state, registry, path, level, disabled, pkg, sourceFile) {
|
|
201
|
+
const key = entryKeyOf(registry, path);
|
|
202
|
+
const existing = state.perEntryState.get(key);
|
|
203
|
+
if (!existing) {
|
|
204
|
+
state.perEntryState.set(key, { disabled, level });
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (existing.level === level) {
|
|
208
|
+
const pathSuffix = path ? `!${path}` : "";
|
|
209
|
+
if (!disabled) {
|
|
210
|
+
throw new errors.InstallException(
|
|
211
|
+
`Duplicate OCI plugin configuration for ${registry}${pathSuffix} found at the same level in ${sourceFile}: ${pkg}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
log.log(
|
|
215
|
+
`WARNING: Skipping duplicate disabled OCI plugin configuration for ${registry}${pathSuffix} in ${sourceFile}`
|
|
216
|
+
);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (level > existing.level) state.perEntryState.set(key, { disabled, level });
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
function recordRegistryPath(state, registry, path, sourceFile) {
|
|
223
|
+
if (!path) {
|
|
224
|
+
state.pathlessRegistries.set(registry, sourceFile);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
let bucket = state.definedPaths.get(registry);
|
|
228
|
+
if (!bucket) {
|
|
229
|
+
bucket = /* @__PURE__ */ new Map();
|
|
230
|
+
state.definedPaths.set(registry, bucket);
|
|
231
|
+
}
|
|
232
|
+
bucket.set(path, sourceFile);
|
|
233
|
+
}
|
|
234
|
+
function processOciEntry(state, plugin, level, sourceFile) {
|
|
235
|
+
const pkg = plugin.package;
|
|
236
|
+
if (typeof pkg !== "string" || !pkg.startsWith(types.OCI_PROTO)) return;
|
|
237
|
+
const disabled = plugin.disabled === true;
|
|
238
|
+
const parsed = ociKey.tryParseOciRegistryAndPath(pkg);
|
|
239
|
+
if (!parsed) {
|
|
240
|
+
logInvalidOciFormat(pkg, sourceFile, disabled);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const { registry, path } = parsed;
|
|
244
|
+
if (!recordEntryState(state, registry, path, level, disabled, pkg, sourceFile))
|
|
245
|
+
return;
|
|
246
|
+
recordRegistryPath(state, registry, path, sourceFile);
|
|
247
|
+
}
|
|
248
|
+
function formatExplicitPaths(bucket) {
|
|
249
|
+
return [...bucket.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([p, src]) => `${p} (in ${src})`).join("\n - ");
|
|
250
|
+
}
|
|
251
|
+
function validateAmbiguousPathless(state) {
|
|
252
|
+
for (const [registry, pathlessSource] of state.pathlessRegistries) {
|
|
253
|
+
const bucket = state.definedPaths.get(registry);
|
|
254
|
+
if (!bucket || bucket.size <= 1) continue;
|
|
255
|
+
const formatted = formatExplicitPaths(bucket);
|
|
256
|
+
const pathlessState = state.perEntryState.get(entryKeyOf(registry, null));
|
|
257
|
+
if (pathlessState?.disabled) {
|
|
258
|
+
log.log(
|
|
259
|
+
`WARNING: Skipping disabled ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: multiple path-specific entries exist:
|
|
260
|
+
- ${formatted}
|
|
261
|
+
Cannot use path-less syntax for multi-plugin images. Please specify a !<plugin-path> suffix for the plugin`
|
|
262
|
+
);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
throw new errors.InstallException(
|
|
266
|
+
`Ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: multiple path-specific entries exist:
|
|
267
|
+
- ${formatted}
|
|
268
|
+
Cannot use path-less syntax for multi-plugin images. Please specify a !<plugin-path> suffix for the plugin.`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function effectiveRegistryDisabled(state, registry) {
|
|
273
|
+
const pathlessState = state.perEntryState.get(entryKeyOf(registry, null));
|
|
274
|
+
if (!pathlessState) return false;
|
|
275
|
+
const bucket = state.definedPaths.get(registry);
|
|
276
|
+
if (bucket?.size !== 1) return pathlessState.disabled;
|
|
277
|
+
const [singlePath] = bucket.keys();
|
|
278
|
+
if (singlePath === void 0) return pathlessState.disabled;
|
|
279
|
+
const definedState = state.perEntryState.get(
|
|
280
|
+
entryKeyOf(registry, singlePath)
|
|
281
|
+
);
|
|
282
|
+
if (definedState && definedState.level > pathlessState.level)
|
|
283
|
+
return definedState.disabled;
|
|
284
|
+
return pathlessState.disabled;
|
|
285
|
+
}
|
|
286
|
+
function computeDisabledRegistries(state) {
|
|
287
|
+
const out = /* @__PURE__ */ new Set();
|
|
288
|
+
for (const registry of state.pathlessRegistries.keys()) {
|
|
289
|
+
if (effectiveRegistryDisabled(state, registry)) out.add(registry);
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
function preMergeOciDisabledState(includePluginLists, mainPlugins, mainConfigFile) {
|
|
294
|
+
const state = {
|
|
295
|
+
perEntryState: /* @__PURE__ */ new Map(),
|
|
296
|
+
pathlessRegistries: /* @__PURE__ */ new Map(),
|
|
297
|
+
definedPaths: /* @__PURE__ */ new Map()
|
|
298
|
+
};
|
|
299
|
+
for (const [file, plugins] of includePluginLists) {
|
|
300
|
+
for (const plugin of plugins) processOciEntry(state, plugin, 0, file);
|
|
301
|
+
}
|
|
302
|
+
for (const plugin of mainPlugins)
|
|
303
|
+
processOciEntry(state, plugin, 1, mainConfigFile);
|
|
304
|
+
validateAmbiguousPathless(state);
|
|
305
|
+
return computeDisabledRegistries(state);
|
|
306
|
+
}
|
|
307
|
+
function filterDisabledOciPlugins(plugins, disabledRegistries) {
|
|
308
|
+
const out = [];
|
|
309
|
+
for (const plugin of plugins) {
|
|
310
|
+
const pkg = plugin.package;
|
|
311
|
+
if (typeof pkg === "string" && pkg.startsWith(types.OCI_PROTO)) {
|
|
312
|
+
const parsed = ociKey.tryParseOciRegistryAndPath(pkg);
|
|
313
|
+
if (parsed && disabledRegistries.has(parsed.registry)) {
|
|
314
|
+
log.log(`
|
|
315
|
+
======= Disabling OCI plugin ${pkg}`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (!parsed && plugin.disabled === true) {
|
|
319
|
+
log.log(`
|
|
320
|
+
======= Disabling OCI plugin ${pkg}`);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
out.push(plugin);
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
exports.deepMerge = deepMerge;
|
|
330
|
+
exports.filterDisabledOciPlugins = filterDisabledOciPlugins;
|
|
331
|
+
exports.mergePlugin = mergePlugin;
|
|
332
|
+
exports.preMergeOciDisabledState = preMergeOciDisabledState;
|
|
333
|
+
//# sourceMappingURL=merger.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merger.cjs.js","sources":["../src/merger.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport * as fs from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { InstallException } from './errors';\nimport { log } from './log';\nimport { type OciImageCache } from './image-cache';\nimport { npmPluginKey } from './npm-key';\nimport {\n ociPluginKey,\n type ParsedOciKey,\n tryParseOciRegistryAndPath,\n} from './oci-key';\nimport {\n type DynamicPluginsConfig,\n OCI_PROTO,\n type Plugin,\n type PluginMap,\n type PluginSpec,\n RECOGNIZED_ALGORITHMS,\n} from './types';\nimport { isPlainObject } from './util';\n\n/**\n * Reject `__proto__`, `constructor`, and `prototype` keys.\n *\n * Inlined string-literal comparisons (rather than a shared `Set.has()` call)\n * because CodeQL's `js/prototype-polluting-function` analysis only treats\n * this specific pattern as an exhaustive prototype-pollution sanitizer when\n * looking at the call site — a `Set` lookup gets flagged as \"not guarded\".\n */\nfunction isForbiddenKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype';\n}\n\n/**\n * Safely assign `value` to `dst[key]` without touching the prototype chain.\n *\n * Two layers of defense:\n * 1. Reject the three prototype-pollution keys outright.\n * 2. `Object.defineProperty` over `dst[key] = value` so that even if a\n * forbidden key somehow slipped through, the prototype chain is still\n * not mutated (`defineProperty` bypasses the `__proto__` setter).\n */\nfunction safeSet<T extends object>(dst: T, key: string, value: unknown): void {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype')\n return;\n Object.defineProperty(dst, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n}\n\n/**\n * Recursively merges `src` into `dst` in place and returns `dst`. Raises on\n * conflicting scalar values so duplicate plugin configs never silently\n * overwrite each other (matches the Python `merge()` contract).\n *\n * Skips `__proto__`, `constructor`, and `prototype` keys to prevent prototype\n * pollution via user-supplied YAML.\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n src: Record<string, unknown>,\n dst: T,\n prefix = '',\n): T {\n for (const [key, value] of Object.entries(src)) {\n if (isForbiddenKey(key)) continue;\n const dstRecord = dst as Record<string, unknown>;\n if (isPlainObject(value)) {\n const existing = dstRecord[key];\n const node = isPlainObject(existing) ? existing : {};\n safeSet(dstRecord, key, node);\n deepMerge(value, node, `${prefix}${key}.`);\n } else {\n if (key in dst && !isEqual(dstRecord[key], value)) {\n throw new InstallException(\n `Config key '${prefix}${key}' defined differently for 2 dynamic plugins`,\n );\n }\n safeSet(dstRecord, key, value);\n }\n }\n return dst;\n}\n\n/**\n * Read a dynamic-plugins config file (main or included), parse its `plugins`,\n * and merge each into `allPlugins` using the OCI or NPM merger as appropriate.\n */\nexport async function mergePluginsFromFile(\n configFile: string,\n allPlugins: PluginMap,\n level: number,\n imageCache?: OciImageCache,\n): Promise<void> {\n const content = parseYaml(await fs.readFile(configFile, 'utf8'));\n if (!isPlainObject(content)) {\n throw new InstallException(`${configFile} must contain a mapping`);\n }\n const plugins = (content as DynamicPluginsConfig).plugins;\n if (!Array.isArray(plugins)) {\n throw new InstallException(\n `${configFile} must contain a 'plugins' list (got ${typeof plugins})`,\n );\n }\n for (const plugin of plugins) {\n await mergePlugin(plugin, allPlugins, configFile, level, imageCache);\n }\n}\n\nexport async function mergePlugin(\n plugin: Plugin,\n allPlugins: PluginMap,\n configFile: string,\n level: number,\n imageCache?: OciImageCache,\n): Promise<void> {\n if (typeof plugin.package !== 'string') {\n throw new InstallException(\n `content of the 'plugins.package' field must be a string in ${configFile}`,\n );\n }\n if (plugin.package.startsWith(OCI_PROTO)) {\n await mergeOciPlugin(plugin, allPlugins, configFile, level, imageCache);\n } else {\n mergeNpmPlugin(plugin, allPlugins, configFile, level);\n }\n}\n\nfunction mergeNpmPlugin(\n plugin: Plugin,\n allPlugins: PluginMap,\n configFile: string,\n level: number,\n): void {\n const key = npmPluginKey(plugin.package);\n doMerge(key, plugin, allPlugins, configFile, level);\n}\n\nasync function mergeOciPlugin(\n plugin: Plugin,\n allPlugins: PluginMap,\n configFile: string,\n level: number,\n imageCache: OciImageCache | undefined,\n): Promise<void> {\n let parsed = await ociPluginKey(plugin.package, imageCache);\n\n if (parsed.inherit && parsed.resolvedPath === null) {\n parsed = resolveInherit(plugin, allPlugins, parsed);\n } else if (!plugin.package.includes('!') && parsed.resolvedPath) {\n plugin.package = `${plugin.package}!${parsed.resolvedPath}`;\n }\n\n plugin.version = parsed.version;\n\n const existing = allPlugins[parsed.pluginKey];\n if (!existing) {\n if (parsed.inherit) {\n throw new InstallException(\n `ERROR: {{inherit}} tag is set and there is currently no resolved tag or digest ` +\n `for ${plugin.package} in ${configFile}.`,\n );\n }\n log(\n `\\n======= Adding new dynamic plugin configuration for version \\`${parsed.version}\\` of ${parsed.pluginKey}`,\n );\n plugin.last_modified_level = level;\n allPlugins[parsed.pluginKey] = plugin;\n return;\n }\n\n log(`\\n======= Overriding dynamic plugin configuration ${parsed.pluginKey}`);\n if (existing.last_modified_level === level) {\n throw new InstallException(\n `Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`,\n );\n }\n\n if (!parsed.inherit) {\n existing.package = plugin.package;\n if (existing.version !== parsed.version) {\n log(\n `INFO: Overriding version for ${parsed.pluginKey} from \\`${existing.version ?? ''}\\` to \\`${parsed.version}\\``,\n );\n }\n existing.version = parsed.version;\n }\n copyPluginFields(plugin, existing, [\n 'package',\n 'version',\n 'last_modified_level',\n ]);\n existing.last_modified_level = level;\n}\n\n/**\n * Resolve `{{inherit}}` without a plugin path — finds a single previously-\n * merged plugin from the same image, adopts its version + path, and mutates\n * `plugin.package` in place. Throws with a helpful message when zero or\n * multiple matches are found.\n */\nfunction resolveInherit(\n plugin: Plugin,\n allPlugins: PluginMap,\n parsed: ParsedOciKey,\n): ParsedOciKey {\n const prefix = `${parsed.pluginKey}:!`;\n const matches = Object.keys(allPlugins).filter(k => k.startsWith(prefix));\n if (matches.length === 0) {\n throw new InstallException(\n `Cannot use {{inherit}} for ${parsed.pluginKey}: no existing plugin ` +\n `configuration found. Ensure a plugin from this image is defined in an ` +\n `included file with an explicit version.`,\n );\n }\n if (matches.length > 1) {\n const formatted = matches\n .map(m => {\n const baseVersion = allPlugins[m]?.version ?? '';\n const registryPart = m.split(':!')[0] ?? '';\n const pathPart = m.split(':!').at(-1) ?? '';\n return ` - ${registryPart}:${baseVersion}!${pathPart}`;\n })\n .join('\\n');\n throw new InstallException(\n `Cannot use {{inherit}} for ${parsed.pluginKey}: multiple plugins from ` +\n `this image are defined in the included files:\\n${formatted}\\n` +\n `Please specify which plugin configuration to inherit from using: ` +\n `${parsed.pluginKey}:{{inherit}}!<plugin_path>`,\n );\n }\n const matchedKey = matches[0] as string;\n const basePlugin = allPlugins[matchedKey];\n if (!basePlugin?.version) {\n throw new InstallException(\n `Internal: inherited plugin ${matchedKey} has no version`,\n );\n }\n const version = basePlugin.version;\n const resolvedPath = matchedKey.split(':!').at(-1) ?? '';\n const registryPart = matchedKey.split(':!')[0] ?? '';\n plugin.package = `${registryPart}:${version}!${resolvedPath}`;\n log(\n `\\n======= Inheriting version \\`${version}\\` and plugin path \\`${resolvedPath}\\` for ${matchedKey}`,\n );\n return { pluginKey: matchedKey, version, inherit: true, resolvedPath };\n}\n\nfunction doMerge(\n key: string,\n plugin: Plugin,\n allPlugins: PluginMap,\n configFile: string,\n level: number,\n): void {\n const existing = allPlugins[key];\n if (!existing) {\n log(`\\n======= Adding new dynamic plugin configuration for ${key}`);\n plugin.last_modified_level = level;\n allPlugins[key] = plugin;\n return;\n }\n log(`\\n======= Overriding dynamic plugin configuration ${key}`);\n if (existing.last_modified_level === level) {\n throw new InstallException(\n `Duplicate plugin configuration for ${plugin.package} found in ${configFile}.`,\n );\n }\n copyPluginFields(plugin, existing, ['last_modified_level']);\n existing.last_modified_level = level;\n}\n\nfunction copyPluginFields(\n src: Plugin,\n dst: Plugin,\n skip: ReadonlyArray<string>,\n): void {\n const skipSet = new Set<string>(skip);\n for (const [k, v] of Object.entries(src)) {\n if (skipSet.has(k) || isForbiddenKey(k)) continue;\n safeSet(dst, k, v);\n }\n}\n\nfunction isEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a) && Array.isArray(b)) return isArrayEqual(a, b);\n if (isPlainObject(a) && isPlainObject(b)) return isObjectEqual(a, b);\n return false;\n}\n\nfunction isArrayEqual(a: readonly unknown[], b: readonly unknown[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((v, i) => isEqual(v, b[i]));\n}\n\nfunction isObjectEqual(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n): boolean {\n const keysA = Object.keys(a);\n if (keysA.length !== Object.keys(b).length) return false;\n return keysA.every(k => isEqual(a[k], b[k]));\n}\n\ntype IncludePluginList = readonly [\n file: string,\n plugins: readonly PluginSpec[],\n];\n\ntype EntryState = { disabled: boolean; level: number };\n\ntype PreMergeState = {\n perEntryState: Map<string, EntryState>;\n pathlessRegistries: Map<string, string>;\n definedPaths: Map<string, Map<string, string>>;\n};\n\nfunction entryKeyOf(registry: string, path: string | null): string {\n return `${registry} ${path ?? ''}`;\n}\n\nfunction logInvalidOciFormat(\n pkg: string,\n sourceFile: string,\n disabled: boolean,\n): void {\n if (!disabled) {\n throw new InstallException(\n `oci package '${pkg}' is not in the expected format '${OCI_PROTO}<registry>:<tag>' ` +\n `or '${OCI_PROTO}<registry>@<algo>:<digest>' (optionally followed by '!<path>') in ${sourceFile} ` +\n `where <registry> may include a port (e.g. host:5000/path) ` +\n `and <algo> is one of ${RECOGNIZED_ALGORITHMS.join(', ')}`,\n );\n }\n log(\n `WARNING: Skipping disabled OCI plugin with invalid format: '${pkg}' in ${sourceFile}. ` +\n `Expected format: '${OCI_PROTO}<registry>:<tag>' or '${OCI_PROTO}<registry>@<algo>:<digest>' ` +\n `(optionally followed by '!<path>') where <registry> may include a port (e.g. host:5000/path) ` +\n `and <algo> is one of ${RECOGNIZED_ALGORITHMS.join(', ')}`,\n );\n}\n\n/**\n * Record the entry's disabled state at its level. Returns `false` when the\n * entry is a duplicate at the same level (warning logged for disabled-dups,\n * throws for enabled-dups) so the caller can skip recording its path/source.\n */\nfunction recordEntryState(\n state: PreMergeState,\n registry: string,\n path: string | null,\n level: number,\n disabled: boolean,\n pkg: string,\n sourceFile: string,\n): boolean {\n const key = entryKeyOf(registry, path);\n const existing = state.perEntryState.get(key);\n if (!existing) {\n state.perEntryState.set(key, { disabled, level });\n return true;\n }\n if (existing.level === level) {\n const pathSuffix = path ? `!${path}` : '';\n if (!disabled) {\n throw new InstallException(\n `Duplicate OCI plugin configuration for ${registry}${pathSuffix} ` +\n `found at the same level in ${sourceFile}: ${pkg}`,\n );\n }\n log(\n `WARNING: Skipping duplicate disabled OCI plugin configuration for ${registry}${pathSuffix} in ${sourceFile}`,\n );\n return false;\n }\n if (level > existing.level) state.perEntryState.set(key, { disabled, level });\n return true;\n}\n\nfunction recordRegistryPath(\n state: PreMergeState,\n registry: string,\n path: string | null,\n sourceFile: string,\n): void {\n if (!path) {\n state.pathlessRegistries.set(registry, sourceFile);\n return;\n }\n let bucket = state.definedPaths.get(registry);\n if (!bucket) {\n bucket = new Map<string, string>();\n state.definedPaths.set(registry, bucket);\n }\n bucket.set(path, sourceFile);\n}\n\nfunction processOciEntry(\n state: PreMergeState,\n plugin: PluginSpec,\n level: number,\n sourceFile: string,\n): void {\n const pkg = plugin.package;\n if (typeof pkg !== 'string' || !pkg.startsWith(OCI_PROTO)) return;\n const disabled = plugin.disabled === true;\n const parsed = tryParseOciRegistryAndPath(pkg);\n if (!parsed) {\n logInvalidOciFormat(pkg, sourceFile, disabled);\n return;\n }\n const { registry, path } = parsed;\n if (\n !recordEntryState(state, registry, path, level, disabled, pkg, sourceFile)\n )\n return;\n recordRegistryPath(state, registry, path, sourceFile);\n}\n\nfunction formatExplicitPaths(bucket: Map<string, string>): string {\n return [...bucket.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([p, src]) => `${p} (in ${src})`)\n .join('\\n - ');\n}\n\nfunction validateAmbiguousPathless(state: PreMergeState): void {\n for (const [registry, pathlessSource] of state.pathlessRegistries) {\n const bucket = state.definedPaths.get(registry);\n if (!bucket || bucket.size <= 1) continue;\n const formatted = formatExplicitPaths(bucket);\n const pathlessState = state.perEntryState.get(entryKeyOf(registry, null));\n if (pathlessState?.disabled) {\n log(\n `WARNING: Skipping disabled ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: ` +\n `multiple path-specific entries exist:\\n - ${formatted}\\n` +\n `Cannot use path-less syntax for multi-plugin images. ` +\n `Please specify a !<plugin-path> suffix for the plugin`,\n );\n continue;\n }\n throw new InstallException(\n `Ambiguous path-less OCI reference for ${registry} in ${pathlessSource}: ` +\n `multiple path-specific entries exist:\\n - ${formatted}\\n` +\n `Cannot use path-less syntax for multi-plugin images. ` +\n `Please specify a !<plugin-path> suffix for the plugin.`,\n );\n }\n}\n\nfunction effectiveRegistryDisabled(\n state: PreMergeState,\n registry: string,\n): boolean {\n const pathlessState = state.perEntryState.get(entryKeyOf(registry, null));\n if (!pathlessState) return false;\n const bucket = state.definedPaths.get(registry);\n if (bucket?.size !== 1) return pathlessState.disabled;\n const [singlePath] = bucket.keys();\n if (singlePath === undefined) return pathlessState.disabled;\n const definedState = state.perEntryState.get(\n entryKeyOf(registry, singlePath),\n );\n if (definedState && definedState.level > pathlessState.level)\n return definedState.disabled;\n return pathlessState.disabled;\n}\n\nfunction computeDisabledRegistries(state: PreMergeState): Set<string> {\n const out = new Set<string>();\n for (const registry of state.pathlessRegistries.keys()) {\n if (effectiveRegistryDisabled(state, registry)) out.add(registry);\n }\n return out;\n}\n\n/**\n * Pre-merge pass that walks every OCI plugin entry from the included files\n * (level 0) and the main config (level 1) and returns the set of OCI\n * registries that will be effectively disabled after the merge. Computed\n * BEFORE any skopeo work so disabled plugins never trigger a remote fetch.\n *\n * Ports `pre_merge_oci_disabled_state` from the Python installer\n * (`install-dynamic-plugins.py`). Only inspects `package` and `disabled` —\n * does NOT merge `pluginConfig`.\n *\n * Throws an `InstallException` for:\n * - invalid OCI package strings on enabled entries,\n * - duplicate enabled OCI entries declared at the same level,\n * - path-less enabled references that collide with multiple explicit-path\n * entries from the same image (ambiguous).\n *\n * Logs a warning (and skips the offending entry) for the equivalent\n * `disabled: true` scenarios — operators can still ship a disabled\n * descriptor without aborting the install.\n */\nexport function preMergeOciDisabledState(\n includePluginLists: ReadonlyArray<IncludePluginList>,\n mainPlugins: ReadonlyArray<PluginSpec>,\n mainConfigFile: string,\n): Set<string> {\n const state: PreMergeState = {\n perEntryState: new Map(),\n pathlessRegistries: new Map(),\n definedPaths: new Map(),\n };\n for (const [file, plugins] of includePluginLists) {\n for (const plugin of plugins) processOciEntry(state, plugin, 0, file);\n }\n for (const plugin of mainPlugins)\n processOciEntry(state, plugin, 1, mainConfigFile);\n\n validateAmbiguousPathless(state);\n return computeDisabledRegistries(state);\n}\n\n/**\n * Drop every OCI plugin whose registry is in the disabled set, plus invalid\n * OCI entries flagged `disabled: true` (a no-op the operator clearly intends\n * to remove). Non-OCI entries pass through unchanged.\n */\nexport function filterDisabledOciPlugins(\n plugins: ReadonlyArray<PluginSpec>,\n disabledRegistries: ReadonlySet<string>,\n): PluginSpec[] {\n const out: PluginSpec[] = [];\n for (const plugin of plugins) {\n const pkg = plugin.package;\n if (typeof pkg === 'string' && pkg.startsWith(OCI_PROTO)) {\n const parsed = tryParseOciRegistryAndPath(pkg);\n if (parsed && disabledRegistries.has(parsed.registry)) {\n log(`\\n======= Disabling OCI plugin ${pkg}`);\n continue;\n }\n if (!parsed && plugin.disabled === true) {\n log(`\\n======= Disabling OCI plugin ${pkg}`);\n continue;\n }\n }\n out.push(plugin);\n }\n return out;\n}\n"],"names":["isPlainObject","InstallException","OCI_PROTO","npmPluginKey","ociPluginKey","log","registryPart","RECOGNIZED_ALGORITHMS","tryParseOciRegistryAndPath"],"mappings":";;;;;;;;;;;AA4CA,SAAS,eAAe,GAAA,EAAsB;AAC5C,EAAA,OAAO,GAAA,KAAQ,WAAA,IAAe,GAAA,KAAQ,aAAA,IAAiB,GAAA,KAAQ,WAAA;AACjE;AAWA,SAAS,OAAA,CAA0B,GAAA,EAAQ,GAAA,EAAa,KAAA,EAAsB;AAC5E,EAAA,IAAI,GAAA,KAAQ,WAAA,IAAe,GAAA,KAAQ,aAAA,IAAiB,GAAA,KAAQ,WAAA;AAC1D,IAAA;AACF,EAAA,MAAA,CAAO,cAAA,CAAe,KAAK,GAAA,EAAK;AAAA,IAC9B,KAAA;AAAA,IACA,QAAA,EAAU,IAAA;AAAA,IACV,UAAA,EAAY,IAAA;AAAA,IACZ,YAAA,EAAc;AAAA,GACf,CAAA;AACH;AAUO,SAAS,SAAA,CACd,GAAA,EACA,GAAA,EACA,MAAA,GAAS,EAAA,EACN;AACH,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC9C,IAAA,IAAI,cAAA,CAAe,GAAG,CAAA,EAAG;AACzB,IAAA,MAAM,SAAA,GAAY,GAAA;AAClB,IAAA,IAAIA,kBAAA,CAAc,KAAK,CAAA,EAAG;AACxB,MAAA,MAAM,QAAA,GAAW,UAAU,GAAG,CAAA;AAC9B,MAAA,MAAM,IAAA,GAAOA,kBAAA,CAAc,QAAQ,CAAA,GAAI,WAAW,EAAC;AACnD,MAAA,OAAA,CAAQ,SAAA,EAAW,KAAK,IAAI,CAAA;AAC5B,MAAA,SAAA,CAAU,OAAO,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,EAAG,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,IAC3C,CAAA,MAAO;AACL,MAAA,IAAI,GAAA,IAAO,OAAO,CAAC,OAAA,CAAQ,UAAU,GAAG,CAAA,EAAG,KAAK,CAAA,EAAG;AACjD,QAAA,MAAM,IAAIC,uBAAA;AAAA,UACR,CAAA,YAAA,EAAe,MAAM,CAAA,EAAG,GAAG,CAAA,2CAAA;AAAA,SAC7B;AAAA,MACF;AACA,MAAA,OAAA,CAAQ,SAAA,EAAW,KAAK,KAAK,CAAA;AAAA,IAC/B;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AA2BA,eAAsB,WAAA,CACpB,MAAA,EACA,UAAA,EACA,UAAA,EACA,OACA,UAAA,EACe;AACf,EAAA,IAAI,OAAO,MAAA,CAAO,OAAA,KAAY,QAAA,EAAU;AACtC,IAAA,MAAM,IAAIA,uBAAA;AAAA,MACR,8DAA8D,UAAU,CAAA;AAAA,KAC1E;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,OAAA,CAAQ,UAAA,CAAWC,eAAS,CAAA,EAAG;AACxC,IAAA,MAAM,cAAA,CAAe,MAAA,EAAQ,UAAA,EAAY,UAAA,EAAY,OAAO,UAAU,CAAA;AAAA,EACxE,CAAA,MAAO;AACL,IAAA,cAAA,CAAe,MAAA,EAAQ,UAAA,EAAY,UAAA,EAAY,KAAK,CAAA;AAAA,EACtD;AACF;AAEA,SAAS,cAAA,CACP,MAAA,EACA,UAAA,EACA,UAAA,EACA,KAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAMC,mBAAA,CAAa,MAAA,CAAO,OAAO,CAAA;AACvC,EAAA,OAAA,CAAQ,GAAA,EAAK,MAAA,EAAQ,UAAA,EAAY,UAAA,EAAY,KAAK,CAAA;AACpD;AAEA,eAAe,cAAA,CACb,MAAA,EACA,UAAA,EACA,UAAA,EACA,OACA,UAAA,EACe;AACf,EAAA,IAAI,MAAA,GAAS,MAAMC,mBAAA,CAAa,MAAA,CAAO,SAAS,UAAU,CAAA;AAE1D,EAAA,IAAI,MAAA,CAAO,OAAA,IAAW,MAAA,CAAO,YAAA,KAAiB,IAAA,EAAM;AAClD,IAAA,MAAA,GAAS,cAAA,CAAe,MAAA,EAAQ,UAAA,EAAY,MAAM,CAAA;AAAA,EACpD,CAAA,MAAA,IAAW,CAAC,MAAA,CAAO,OAAA,CAAQ,SAAS,GAAG,CAAA,IAAK,OAAO,YAAA,EAAc;AAC/D,IAAA,MAAA,CAAO,UAAU,CAAA,EAAG,MAAA,CAAO,OAAO,CAAA,CAAA,EAAI,OAAO,YAAY,CAAA,CAAA;AAAA,EAC3D;AAEA,EAAA,MAAA,CAAO,UAAU,MAAA,CAAO,OAAA;AAExB,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,MAAA,CAAO,SAAS,CAAA;AAC5C,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,MAAM,IAAIH,uBAAA;AAAA,QACR,CAAA,mFAAA,EACS,MAAA,CAAO,OAAO,CAAA,IAAA,EAAO,UAAU,CAAA,CAAA;AAAA,OAC1C;AAAA,IACF;AACA,IAAAI,OAAA;AAAA,MACE;AAAA,8DAAA,EAAmE,MAAA,CAAO,OAAO,CAAA,MAAA,EAAS,MAAA,CAAO,SAAS,CAAA;AAAA,KAC5G;AACA,IAAA,MAAA,CAAO,mBAAA,GAAsB,KAAA;AAC7B,IAAA,UAAA,CAAW,MAAA,CAAO,SAAS,CAAA,GAAI,MAAA;AAC/B,IAAA;AAAA,EACF;AAEA,EAAAA,OAAA,CAAI;AAAA,gDAAA,EAAqD,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AAC3E,EAAA,IAAI,QAAA,CAAS,wBAAwB,KAAA,EAAO;AAC1C,IAAA,MAAM,IAAIJ,uBAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,MAAA,CAAO,OAAO,CAAA,UAAA,EAAa,UAAU,CAAA,CAAA;AAAA,KAC7E;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,QAAA,CAAS,UAAU,MAAA,CAAO,OAAA;AAC1B,IAAA,IAAI,QAAA,CAAS,OAAA,KAAY,MAAA,CAAO,OAAA,EAAS;AACvC,MAAAI,OAAA;AAAA,QACE,CAAA,6BAAA,EAAgC,OAAO,SAAS,CAAA,QAAA,EAAW,SAAS,OAAA,IAAW,EAAE,CAAA,QAAA,EAAW,MAAA,CAAO,OAAO,CAAA,EAAA;AAAA,OAC5G;AAAA,IACF;AACA,IAAA,QAAA,CAAS,UAAU,MAAA,CAAO,OAAA;AAAA,EAC5B;AACA,EAAA,gBAAA,CAAiB,QAAQ,QAAA,EAAU;AAAA,IACjC,SAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AACD,EAAA,QAAA,CAAS,mBAAA,GAAsB,KAAA;AACjC;AAQA,SAAS,cAAA,CACP,MAAA,EACA,UAAA,EACA,MAAA,EACc;AACd,EAAA,MAAM,MAAA,GAAS,CAAA,EAAG,MAAA,CAAO,SAAS,CAAA,EAAA,CAAA;AAClC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,UAAA,CAAW,MAAM,CAAC,CAAA;AACxE,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,MAAM,IAAIJ,uBAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,OAAO,SAAS,CAAA,kIAAA;AAAA,KAGhD;AAAA,EACF;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,IAAA,MAAM,SAAA,GAAY,OAAA,CACf,GAAA,CAAI,CAAA,CAAA,KAAK;AACR,MAAA,MAAM,WAAA,GAAc,UAAA,CAAW,CAAC,CAAA,EAAG,OAAA,IAAW,EAAA;AAC9C,MAAA,MAAMK,gBAAe,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA,CAAE,CAAC,CAAA,IAAK,EAAA;AACzC,MAAA,MAAM,WAAW,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA,CAAE,EAAA,CAAG,EAAE,CAAA,IAAK,EAAA;AACzC,MAAA,OAAO,CAAA,IAAA,EAAOA,aAAY,CAAA,CAAA,EAAI,WAAW,IAAI,QAAQ,CAAA,CAAA;AAAA,IACvD,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACZ,IAAA,MAAM,IAAIL,uBAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,OAAO,SAAS,CAAA;AAAA,EACM,SAAS;AAAA,iEAAA,EAExD,OAAO,SAAS,CAAA,0BAAA;AAAA,KACvB;AAAA,EACF;AACA,EAAA,MAAM,UAAA,GAAa,QAAQ,CAAC,CAAA;AAC5B,EAAA,MAAM,UAAA,GAAa,WAAW,UAAU,CAAA;AACxC,EAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AACxB,IAAA,MAAM,IAAIA,uBAAA;AAAA,MACR,8BAA8B,UAAU,CAAA,eAAA;AAAA,KAC1C;AAAA,EACF;AACA,EAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,EAAA,MAAM,eAAe,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA,CAAE,EAAA,CAAG,EAAE,CAAA,IAAK,EAAA;AACtD,EAAA,MAAM,eAAe,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA,CAAE,CAAC,CAAA,IAAK,EAAA;AAClD,EAAA,MAAA,CAAO,UAAU,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,OAAO,IAAI,YAAY,CAAA,CAAA;AAC3D,EAAAI,OAAA;AAAA,IACE;AAAA,6BAAA,EAAkC,OAAO,CAAA,qBAAA,EAAwB,YAAY,CAAA,OAAA,EAAU,UAAU,CAAA;AAAA,GACnG;AACA,EAAA,OAAO,EAAE,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,OAAA,EAAS,MAAM,YAAA,EAAa;AACvE;AAEA,SAAS,OAAA,CACP,GAAA,EACA,MAAA,EACA,UAAA,EACA,YACA,KAAA,EACM;AACN,EAAA,MAAM,QAAA,GAAW,WAAW,GAAG,CAAA;AAC/B,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAAA,OAAA,CAAI;AAAA,oDAAA,EAAyD,GAAG,CAAA,CAAE,CAAA;AAClE,IAAA,MAAA,CAAO,mBAAA,GAAsB,KAAA;AAC7B,IAAA,UAAA,CAAW,GAAG,CAAA,GAAI,MAAA;AAClB,IAAA;AAAA,EACF;AACA,EAAAA,OAAA,CAAI;AAAA,gDAAA,EAAqD,GAAG,CAAA,CAAE,CAAA;AAC9D,EAAA,IAAI,QAAA,CAAS,wBAAwB,KAAA,EAAO;AAC1C,IAAA,MAAM,IAAIJ,uBAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,MAAA,CAAO,OAAO,CAAA,UAAA,EAAa,UAAU,CAAA,CAAA;AAAA,KAC7E;AAAA,EACF;AACA,EAAA,gBAAA,CAAiB,MAAA,EAAQ,QAAA,EAAU,CAAC,qBAAqB,CAAC,CAAA;AAC1D,EAAA,QAAA,CAAS,mBAAA,GAAsB,KAAA;AACjC;AAEA,SAAS,gBAAA,CACP,GAAA,EACA,GAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAY,IAAI,CAAA;AACpC,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AACxC,IAAA,IAAI,QAAQ,GAAA,CAAI,CAAC,CAAA,IAAK,cAAA,CAAe,CAAC,CAAA,EAAG;AACzC,IAAA,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAC,CAAA;AAAA,EACnB;AACF;AAEA,SAAS,OAAA,CAAQ,GAAY,CAAA,EAAqB;AAChD,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,IAAA;AACpB,EAAA,IAAI,OAAO,CAAA,KAAM,OAAO,CAAA,EAAG,OAAO,KAAA;AAClC,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,YAAA,CAAa,CAAA,EAAG,CAAC,CAAA;AAClE,EAAA,IAAID,kBAAA,CAAc,CAAC,CAAA,IAAKA,kBAAA,CAAc,CAAC,CAAA,EAAG,OAAO,aAAA,CAAc,CAAA,EAAG,CAAC,CAAA;AACnE,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,YAAA,CAAa,GAAuB,CAAA,EAAgC;AAC3E,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,KAAM,QAAQ,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAC3C;AAEA,SAAS,aAAA,CACP,GACA,CAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA;AAC3B,EAAA,IAAI,MAAM,MAAA,KAAW,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,QAAQ,OAAO,KAAA;AACnD,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,CAAA,KAAK,OAAA,CAAQ,CAAA,CAAE,CAAC,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAC7C;AAeA,SAAS,UAAA,CAAW,UAAkB,IAAA,EAA6B;AACjE,EAAA,OAAO,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,IAAA,IAAQ,EAAE,CAAA,CAAA;AAClC;AAEA,SAAS,mBAAA,CACP,GAAA,EACA,UAAA,EACA,QAAA,EACM;AACN,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAIC,uBAAA;AAAA,MACR,CAAA,aAAA,EAAgB,GAAG,CAAA,iCAAA,EAAoCC,eAAS,CAAA,sBAAA,EACvDA,eAAS,CAAA,kEAAA,EAAqE,UAAU,CAAA,gFAAA,EAEvEK,2BAAA,CAAsB,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,KAC5D;AAAA,EACF;AACA,EAAAF,OAAA;AAAA,IACE,CAAA,4DAAA,EAA+D,GAAG,CAAA,KAAA,EAAQ,UAAU,CAAA,oBAAA,EAC7DH,eAAS,CAAA,sBAAA,EAAyBA,eAAS,CAAA,8IAAA,EAExCK,2BAAA,CAAsB,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,GAC5D;AACF;AAOA,SAAS,iBACP,KAAA,EACA,QAAA,EACA,MACA,KAAA,EACA,QAAA,EACA,KACA,UAAA,EACS;AACT,EAAA,MAAM,GAAA,GAAM,UAAA,CAAW,QAAA,EAAU,IAAI,CAAA;AACrC,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA;AAC5C,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,KAAA,CAAM,cAAc,GAAA,CAAI,GAAA,EAAK,EAAE,QAAA,EAAU,OAAO,CAAA;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,QAAA,CAAS,UAAU,KAAA,EAAO;AAC5B,IAAA,MAAM,UAAA,GAAa,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,GAAK,EAAA;AACvC,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAIN,uBAAA;AAAA,QACR,0CAA0C,QAAQ,CAAA,EAAG,UAAU,CAAA,4BAAA,EAC/B,UAAU,KAAK,GAAG,CAAA;AAAA,OACpD;AAAA,IACF;AACA,IAAAI,OAAA;AAAA,MACE,CAAA,kEAAA,EAAqE,QAAQ,CAAA,EAAG,UAAU,OAAO,UAAU,CAAA;AAAA,KAC7G;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,GAAQ,QAAA,CAAS,KAAA,EAAO,KAAA,CAAM,aAAA,CAAc,IAAI,GAAA,EAAK,EAAE,QAAA,EAAU,KAAA,EAAO,CAAA;AAC5E,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,kBAAA,CACP,KAAA,EACA,QAAA,EACA,IAAA,EACA,UAAA,EACM;AACN,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,KAAA,CAAM,kBAAA,CAAmB,GAAA,CAAI,QAAA,EAAU,UAAU,CAAA;AACjD,IAAA;AAAA,EACF;AACA,EAAA,IAAI,MAAA,GAAS,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA;AAC5C,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,uBAAa,GAAA,EAAoB;AACjC,IAAA,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,MAAM,CAAA;AAAA,EACzC;AACA,EAAA,MAAA,CAAO,GAAA,CAAI,MAAM,UAAU,CAAA;AAC7B;AAEA,SAAS,eAAA,CACP,KAAA,EACA,MAAA,EACA,KAAA,EACA,UAAA,EACM;AACN,EAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,CAAC,GAAA,CAAI,UAAA,CAAWH,eAAS,CAAA,EAAG;AAC3D,EAAA,MAAM,QAAA,GAAW,OAAO,QAAA,KAAa,IAAA;AACrC,EAAA,MAAM,MAAA,GAASM,kCAA2B,GAAG,CAAA;AAC7C,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,mBAAA,CAAoB,GAAA,EAAK,YAAY,QAAQ,CAAA;AAC7C,IAAA;AAAA,EACF;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,IAAA,EAAK,GAAI,MAAA;AAC3B,EAAA,IACE,CAAC,iBAAiB,KAAA,EAAO,QAAA,EAAU,MAAM,KAAA,EAAO,QAAA,EAAU,KAAK,UAAU,CAAA;AAEzE,IAAA;AACF,EAAA,kBAAA,CAAmB,KAAA,EAAO,QAAA,EAAU,IAAA,EAAM,UAAU,CAAA;AACtD;AAEA,SAAS,oBAAoB,MAAA,EAAqC;AAChE,EAAA,OAAO,CAAC,GAAG,MAAA,CAAO,OAAA,EAAS,CAAA,CACxB,IAAA,CAAK,CAAC,CAAC,CAAC,CAAA,EAAG,CAAC,CAAC,MAAM,CAAA,CAAE,aAAA,CAAc,CAAC,CAAC,CAAA,CACrC,GAAA,CAAI,CAAC,CAAC,GAAG,GAAG,CAAA,KAAM,CAAA,EAAG,CAAC,CAAA,KAAA,EAAQ,GAAG,CAAA,CAAA,CAAG,CAAA,CACpC,KAAK,QAAQ,CAAA;AAClB;AAEA,SAAS,0BAA0B,KAAA,EAA4B;AAC7D,EAAA,KAAA,MAAW,CAAC,QAAA,EAAU,cAAc,CAAA,IAAK,MAAM,kBAAA,EAAoB;AACjE,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA;AAC9C,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,IAAA,IAAQ,CAAA,EAAG;AACjC,IAAA,MAAM,SAAA,GAAY,oBAAoB,MAAM,CAAA;AAC5C,IAAA,MAAM,gBAAgB,KAAA,CAAM,aAAA,CAAc,IAAI,UAAA,CAAW,QAAA,EAAU,IAAI,CAAC,CAAA;AACxE,IAAA,IAAI,eAAe,QAAA,EAAU;AAC3B,MAAAH,OAAA;AAAA,QACE,CAAA,iEAAA,EAAoE,QAAQ,CAAA,IAAA,EAAO,cAAc,CAAA;AAAA,IAAA,EACjD,SAAS;AAAA,0GAAA;AAAA,OAG3D;AACA,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAIJ,uBAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,QAAQ,CAAA,IAAA,EAAO,cAAc,CAAA;AAAA,IAAA,EACtB,SAAS;AAAA,2GAAA;AAAA,KAG3D;AAAA,EACF;AACF;AAEA,SAAS,yBAAA,CACP,OACA,QAAA,EACS;AACT,EAAA,MAAM,gBAAgB,KAAA,CAAM,aAAA,CAAc,IAAI,UAAA,CAAW,QAAA,EAAU,IAAI,CAAC,CAAA;AACxE,EAAA,IAAI,CAAC,eAAe,OAAO,KAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA;AAC9C,EAAA,IAAI,MAAA,EAAQ,IAAA,KAAS,CAAA,EAAG,OAAO,aAAA,CAAc,QAAA;AAC7C,EAAA,MAAM,CAAC,UAAU,CAAA,GAAI,MAAA,CAAO,IAAA,EAAK;AACjC,EAAA,IAAI,UAAA,KAAe,MAAA,EAAW,OAAO,aAAA,CAAc,QAAA;AACnD,EAAA,MAAM,YAAA,GAAe,MAAM,aAAA,CAAc,GAAA;AAAA,IACvC,UAAA,CAAW,UAAU,UAAU;AAAA,GACjC;AACA,EAAA,IAAI,YAAA,IAAgB,YAAA,CAAa,KAAA,GAAQ,aAAA,CAAc,KAAA;AACrD,IAAA,OAAO,YAAA,CAAa,QAAA;AACtB,EAAA,OAAO,aAAA,CAAc,QAAA;AACvB;AAEA,SAAS,0BAA0B,KAAA,EAAmC;AACpE,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,EAAA,KAAA,MAAW,QAAA,IAAY,KAAA,CAAM,kBAAA,CAAmB,IAAA,EAAK,EAAG;AACtD,IAAA,IAAI,0BAA0B,KAAA,EAAO,QAAQ,CAAA,EAAG,GAAA,CAAI,IAAI,QAAQ,CAAA;AAAA,EAClE;AACA,EAAA,OAAO,GAAA;AACT;AAsBO,SAAS,wBAAA,CACd,kBAAA,EACA,WAAA,EACA,cAAA,EACa;AACb,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,aAAA,sBAAmB,GAAA,EAAI;AAAA,IACvB,kBAAA,sBAAwB,GAAA,EAAI;AAAA,IAC5B,YAAA,sBAAkB,GAAA;AAAI,GACxB;AACA,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,OAAO,CAAA,IAAK,kBAAA,EAAoB;AAChD,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS,eAAA,CAAgB,KAAA,EAAO,MAAA,EAAQ,GAAG,IAAI,CAAA;AAAA,EACtE;AACA,EAAA,KAAA,MAAW,MAAA,IAAU,WAAA;AACnB,IAAA,eAAA,CAAgB,KAAA,EAAO,MAAA,EAAQ,CAAA,EAAG,cAAc,CAAA;AAElD,EAAA,yBAAA,CAA0B,KAAK,CAAA;AAC/B,EAAA,OAAO,0BAA0B,KAAK,CAAA;AACxC;AAOO,SAAS,wBAAA,CACd,SACA,kBAAA,EACc;AACd,EAAA,MAAM,MAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,IAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,UAAA,CAAWC,eAAS,CAAA,EAAG;AACxD,MAAA,MAAM,MAAA,GAASM,kCAA2B,GAAG,CAAA;AAC7C,MAAA,IAAI,MAAA,IAAU,kBAAA,CAAmB,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAA,EAAG;AACrD,QAAAH,OAAA,CAAI;AAAA,6BAAA,EAAkC,GAAG,CAAA,CAAE,CAAA;AAC3C,QAAA;AAAA,MACF;AACA,MAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,QAAA,KAAa,IAAA,EAAM;AACvC,QAAAA,OAAA,CAAI;AAAA,6BAAA,EAAkC,GAAG,CAAA,CAAE,CAAA;AAC3C,QAAA;AAAA,MACF;AAAA,IACF;AACA,IAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,EACjB;AACA,EAAA,OAAO,GAAA;AACT;;;;;;;"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const NPM_PACKAGE_PATTERN = /^(@[^/]+\/)?([^@]+)(?:@(.+))?$/;
|
|
4
|
+
const NPM_ALIAS_PATTERN = /^([^@]+)@npm:(@[^/]+\/)?([^@]+)(?:@(.+))?$/;
|
|
5
|
+
const GITHUB_SHORTHAND_PATTERN = /^([^/@]+)\/([^/#]+)(?:#(.+))?$/;
|
|
6
|
+
const GIT_URL_PATTERNS = [
|
|
7
|
+
/^git\+https?:\/\/[^#]+(?:#(.+))?$/,
|
|
8
|
+
/^git\+ssh:\/\/[^#]+(?:#(.+))?$/,
|
|
9
|
+
/^git:\/\/[^#]+(?:#(.+))?$/,
|
|
10
|
+
/^https:\/\/github\.com\/[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/,
|
|
11
|
+
/^git@github\.com:[^/]+\/[^/#]+(?:\.git)?(?:#(.+))?$/,
|
|
12
|
+
/^github:([^/@]+)\/([^/#]+)(?:#(.+))?$/
|
|
13
|
+
];
|
|
14
|
+
function npmPluginKey(pkg) {
|
|
15
|
+
if (pkg.startsWith("./") || pkg.endsWith(".tgz")) return pkg;
|
|
16
|
+
const aliasKey = tryParseAlias(pkg);
|
|
17
|
+
if (aliasKey) return aliasKey;
|
|
18
|
+
if (isGitLikeSpec(pkg)) return stripRefSuffix(pkg);
|
|
19
|
+
return stripStandardNpmVersion(pkg);
|
|
20
|
+
}
|
|
21
|
+
function tryParseAlias(pkg) {
|
|
22
|
+
const m = NPM_ALIAS_PATTERN.exec(pkg);
|
|
23
|
+
if (!m) return null;
|
|
24
|
+
const [, aliasName, scope, name] = m;
|
|
25
|
+
return `${aliasName}@npm:${scope ?? ""}${name}`;
|
|
26
|
+
}
|
|
27
|
+
function isGitLikeSpec(pkg) {
|
|
28
|
+
if (GIT_URL_PATTERNS.some((re) => re.test(pkg))) return true;
|
|
29
|
+
if (pkg.includes("://") || pkg.startsWith("@")) return false;
|
|
30
|
+
return GITHUB_SHORTHAND_PATTERN.test(pkg);
|
|
31
|
+
}
|
|
32
|
+
function stripRefSuffix(pkg) {
|
|
33
|
+
const hash = pkg.indexOf("#");
|
|
34
|
+
return hash >= 0 ? pkg.slice(0, hash) : pkg;
|
|
35
|
+
}
|
|
36
|
+
function stripStandardNpmVersion(pkg) {
|
|
37
|
+
const m = NPM_PACKAGE_PATTERN.exec(pkg);
|
|
38
|
+
if (!m) return pkg;
|
|
39
|
+
const [, scope, name] = m;
|
|
40
|
+
return `${scope ?? ""}${name}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exports.npmPluginKey = npmPluginKey;
|
|
44
|
+
//# sourceMappingURL=npm-key.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-key.cjs.js","sources":["../src/npm-key.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// [@scope/]name[@version]\nconst NPM_PACKAGE_PATTERN = /^(@[^/]+\\/)?([^@]+)(?:@(.+))?$/;\n// alias@npm:[@scope/]name[@version]\nconst NPM_ALIAS_PATTERN = /^([^@]+)@npm:(@[^/]+\\/)?([^@]+)(?:@(.+))?$/;\n// user/repo\nconst GITHUB_SHORTHAND_PATTERN = /^([^/@]+)\\/([^/#]+)(?:#(.+))?$/;\n\nconst GIT_URL_PATTERNS: RegExp[] = [\n /^git\\+https?:\\/\\/[^#]+(?:#(.+))?$/,\n /^git\\+ssh:\\/\\/[^#]+(?:#(.+))?$/,\n /^git:\\/\\/[^#]+(?:#(.+))?$/,\n /^https:\\/\\/github\\.com\\/[^/]+\\/[^/#]+(?:\\.git)?(?:#(.+))?$/,\n /^git@github\\.com:[^/]+\\/[^/#]+(?:\\.git)?(?:#(.+))?$/,\n /^github:([^/@]+)\\/([^/#]+)(?:#(.+))?$/,\n];\n\nexport function npmPluginKey(pkg: string): string {\n // Local packages and tarballs have no version to strip.\n if (pkg.startsWith('./') || pkg.endsWith('.tgz')) return pkg;\n\n // Aliases: \"my-alias@npm:real-pkg@1.2.3\" -> \"my-alias@npm:real-pkg\"\n const aliasKey = tryParseAlias(pkg);\n if (aliasKey) return aliasKey;\n\n // Git URLs / GitHub shorthand: strip `#ref` suffix.\n if (isGitLikeSpec(pkg)) return stripRefSuffix(pkg);\n\n return stripStandardNpmVersion(pkg);\n}\n\nfunction tryParseAlias(pkg: string): string | null {\n const m = NPM_ALIAS_PATTERN.exec(pkg);\n if (!m) return null;\n const [, aliasName, scope, name] = m;\n return `${aliasName}@npm:${scope ?? ''}${name}`;\n}\n\nfunction isGitLikeSpec(pkg: string): boolean {\n if (GIT_URL_PATTERNS.some(re => re.test(pkg))) return true;\n // GitHub shorthand `user/repo#ref` — but not scoped packages or full URLs.\n if (pkg.includes('://') || pkg.startsWith('@')) return false;\n return GITHUB_SHORTHAND_PATTERN.test(pkg);\n}\n\nfunction stripRefSuffix(pkg: string): string {\n const hash = pkg.indexOf('#');\n return hash >= 0 ? pkg.slice(0, hash) : pkg;\n}\n\nfunction stripStandardNpmVersion(pkg: string): string {\n const m = NPM_PACKAGE_PATTERN.exec(pkg);\n if (!m) return pkg;\n const [, scope, name] = m;\n return `${scope ?? ''}${name}`;\n}\n"],"names":[],"mappings":";;AAiBA,MAAM,mBAAA,GAAsB,gCAAA;AAE5B,MAAM,iBAAA,GAAoB,4CAAA;AAE1B,MAAM,wBAAA,GAA2B,gCAAA;AAEjC,MAAM,gBAAA,GAA6B;AAAA,EACjC,mCAAA;AAAA,EACA,gCAAA;AAAA,EACA,2BAAA;AAAA,EACA,4DAAA;AAAA,EACA,qDAAA;AAAA,EACA;AACF,CAAA;AAEO,SAAS,aAAa,GAAA,EAAqB;AAEhD,EAAA,IAAI,GAAA,CAAI,WAAW,IAAI,CAAA,IAAK,IAAI,QAAA,CAAS,MAAM,GAAG,OAAO,GAAA;AAGzD,EAAA,MAAM,QAAA,GAAW,cAAc,GAAG,CAAA;AAClC,EAAA,IAAI,UAAU,OAAO,QAAA;AAGrB,EAAA,IAAI,aAAA,CAAc,GAAG,CAAA,EAAG,OAAO,eAAe,GAAG,CAAA;AAEjD,EAAA,OAAO,wBAAwB,GAAG,CAAA;AACpC;AAEA,SAAS,cAAc,GAAA,EAA4B;AACjD,EAAA,MAAM,CAAA,GAAI,iBAAA,CAAkB,IAAA,CAAK,GAAG,CAAA;AACpC,EAAA,IAAI,CAAC,GAAG,OAAO,IAAA;AACf,EAAA,MAAM,GAAG,SAAA,EAAW,KAAA,EAAO,IAAI,CAAA,GAAI,CAAA;AACnC,EAAA,OAAO,GAAG,SAAS,CAAA,KAAA,EAAQ,KAAA,IAAS,EAAE,GAAG,IAAI,CAAA,CAAA;AAC/C;AAEA,SAAS,cAAc,GAAA,EAAsB;AAC3C,EAAA,IAAI,gBAAA,CAAiB,KAAK,CAAA,EAAA,KAAM,EAAA,CAAG,KAAK,GAAG,CAAC,GAAG,OAAO,IAAA;AAEtD,EAAA,IAAI,GAAA,CAAI,SAAS,KAAK,CAAA,IAAK,IAAI,UAAA,CAAW,GAAG,GAAG,OAAO,KAAA;AACvD,EAAA,OAAO,wBAAA,CAAyB,KAAK,GAAG,CAAA;AAC1C;AAEA,SAAS,eAAe,GAAA,EAAqB;AAC3C,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC5B,EAAA,OAAO,QAAQ,CAAA,GAAI,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,GAAI,GAAA;AAC1C;AAEA,SAAS,wBAAwB,GAAA,EAAqB;AACpD,EAAA,MAAM,CAAA,GAAI,mBAAA,CAAoB,IAAA,CAAK,GAAG,CAAA;AACtC,EAAA,IAAI,CAAC,GAAG,OAAO,GAAA;AACf,EAAA,MAAM,GAAG,KAAA,EAAO,IAAI,CAAA,GAAI,CAAA;AACxB,EAAA,OAAO,CAAA,EAAG,KAAA,IAAS,EAAE,CAAA,EAAG,IAAI,CAAA,CAAA;AAC9B;;;;"}
|