@red-hat-developer-hub/cli-module-install-dynamic-plugins 0.2.0 → 0.3.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 +6 -0
- package/dist/installer-npm.cjs.js +1 -1
- package/dist/installer-npm.cjs.js.map +1 -1
- package/dist/installer-oci.cjs.js +1 -1
- package/dist/installer-oci.cjs.js.map +1 -1
- package/dist/installer.cjs.js +1 -1
- package/dist/installer.cjs.js.map +1 -1
- package/dist/merger.cjs.js +6 -2
- package/dist/merger.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/dist/types.cjs.js +24 -0
- package/dist/types.cjs.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -32,7 +32,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
|
32
32
|
var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
33
33
|
|
|
34
34
|
async function installNpmPlugin(plugin, destination, skipIntegrity, installed) {
|
|
35
|
-
if (plugin
|
|
35
|
+
if (types.isPluginDisabled(plugin)) {
|
|
36
36
|
return { pluginPath: null, pluginConfig: {} };
|
|
37
37
|
}
|
|
38
38
|
const hash = plugin.plugin_hash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer-npm.cjs.js","sources":["../src/installer-npm.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 * as path from 'node:path';\nimport { InstallException } from './errors';\nimport { verifyIntegrity } from './integrity';\nimport { log } from './log';\nimport { run } from './run';\nimport { extractNpmPackage } from './tar-extract';\nimport { CONFIG_HASH_FILE, type Plugin } from './types';\nimport { markAsFresh } from './util';\n\nexport type NpmInstallResult = {\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\n/**\n * Install a single NPM-packaged (or local) plugin into `destination`.\n * Runs `npm pack` to produce the tarball, verifies integrity for remote\n * packages (unless skipped), then extracts.\n *\n * Concurrency is the caller's responsibility — `installNpm` in `index.ts`\n * runs a bounded `mapConcurrent` (default 3 workers via `getNpmWorkers()`)\n * over a list of plugins that have already passed the `definitelyNoOp`\n * pre-pass, so by the time this function is called the plugin definitely\n * needs work.\n */\nexport async function installNpmPlugin(\n plugin: Plugin,\n destination: string,\n skipIntegrity: boolean,\n installed: Map<string, string>,\n): Promise<NpmInstallResult> {\n if (plugin
|
|
1
|
+
{"version":3,"file":"installer-npm.cjs.js","sources":["../src/installer-npm.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 * as path from 'node:path';\nimport { InstallException } from './errors';\nimport { verifyIntegrity } from './integrity';\nimport { log } from './log';\nimport { run } from './run';\nimport { extractNpmPackage } from './tar-extract';\nimport { CONFIG_HASH_FILE, isPluginDisabled, type Plugin } from './types';\nimport { markAsFresh } from './util';\n\nexport type NpmInstallResult = {\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\n/**\n * Install a single NPM-packaged (or local) plugin into `destination`.\n * Runs `npm pack` to produce the tarball, verifies integrity for remote\n * packages (unless skipped), then extracts.\n *\n * Concurrency is the caller's responsibility — `installNpm` in `index.ts`\n * runs a bounded `mapConcurrent` (default 3 workers via `getNpmWorkers()`)\n * over a list of plugins that have already passed the `definitelyNoOp`\n * pre-pass, so by the time this function is called the plugin definitely\n * needs work.\n */\nexport async function installNpmPlugin(\n plugin: Plugin,\n destination: string,\n skipIntegrity: boolean,\n installed: Map<string, string>,\n): Promise<NpmInstallResult> {\n if (isPluginDisabled(plugin)) {\n return { pluginPath: null, pluginConfig: {} };\n }\n const hash = plugin.plugin_hash;\n if (!hash) {\n throw new InstallException(\n `Internal error: plugin ${plugin.package} missing plugin_hash`,\n );\n }\n const pkg = plugin.package;\n const config: Record<string, unknown> = plugin.pluginConfig ?? {};\n\n const isLocal = pkg.startsWith('./');\n const actualPkg = isLocal ? path.join(process.cwd(), pkg.slice(2)) : pkg;\n\n const verifyRemoteIntegrity = !isLocal && !skipIntegrity;\n if (verifyRemoteIntegrity && !plugin.integrity) {\n throw new InstallException(\n `No integrity hash provided for Package ${pkg}. This is an insecure installation. ` +\n `To ignore this error, set the SKIP_INTEGRITY_CHECK environment variable to 'true'.`,\n );\n }\n\n log('\\t==> Running npm pack');\n const archiveName = await npmPack(actualPkg, destination);\n if (!isSafeArchiveName(archiveName)) {\n throw new InstallException(\n `npm pack returned an unsafe filename for ${pkg}: '${archiveName}'`,\n );\n }\n const archive = path.join(destination, archiveName);\n\n if (verifyRemoteIntegrity) {\n log('\\t==> Verifying package integrity');\n // `plugin.integrity` is guaranteed present — the check above throws otherwise.\n await verifyIntegrity(pkg, archive, plugin.integrity as string);\n }\n\n const pluginPath = await extractNpmPackage(archive);\n await fs.writeFile(\n path.join(destination, pluginPath, CONFIG_HASH_FILE),\n hash,\n );\n\n markAsFresh(installed, pluginPath);\n return { pluginPath, pluginConfig: config };\n}\n\n/**\n * Run `npm pack --json` and extract the archive filename from the structured\n * output. The text form of `npm pack` intermixes warnings with the filename\n * (last-line parsing is fragile); `--json` gives `[{ filename, ... }]`.\n */\nasync function npmPack(\n actualPkg: string,\n destination: string,\n): Promise<string> {\n // `--ignore-scripts` blocks `preinstall` / `prepack` / `prepare` lifecycle\n // hooks that NPM packages can declare. Dynamic plugins are not expected\n // to ship build steps that need to run at install time, and skipping the\n // hooks both removes a code-execution-on-install attack surface and\n // shaves a fork+exec per package off the wall clock.\n const { stdout } = await run(\n ['npm', 'pack', '--json', '--ignore-scripts', actualPkg],\n `npm pack failed for ${actualPkg}`,\n { cwd: destination },\n );\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout);\n } catch (err) {\n throw new InstallException(\n `npm pack produced invalid JSON for ${actualPkg}: ${(err as Error).message}`,\n );\n }\n if (!Array.isArray(parsed) || parsed.length === 0) {\n throw new InstallException(\n `npm pack produced no archives for ${actualPkg}`,\n );\n }\n const first = parsed[0];\n if (!isNpmPackJsonEntry(first)) {\n throw new InstallException(\n `npm pack output missing 'filename' for ${actualPkg}`,\n );\n }\n return first.filename;\n}\n\nfunction isNpmPackJsonEntry(value: unknown): value is { filename: string } {\n return (\n !!value &&\n typeof value === 'object' &&\n typeof (value as { filename?: unknown }).filename === 'string'\n );\n}\n\n/**\n * Reject any filename that would let `npm pack` escape `destination` once\n * passed to `path.join` — directory separators, leading `..`, or empty.\n * `npm pack` is expected to emit a flat `<name>-<version>.tgz`, so any\n * non-flat name is treated as adversarial.\n */\nfunction isSafeArchiveName(name: string): boolean {\n if (!name || name === '.' || name === '..') return false;\n if (name.startsWith('..')) return false;\n return !/[/\\\\]/.test(name);\n}\n"],"names":["isPluginDisabled","InstallException","path","log","verifyIntegrity","extractNpmPackage","fs","CONFIG_HASH_FILE","markAsFresh","run"],"mappings":"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"}
|
|
@@ -38,7 +38,7 @@ function splitOciPackage(pkg) {
|
|
|
38
38
|
return { imagePart, pluginPath };
|
|
39
39
|
}
|
|
40
40
|
async function installOciPlugin(plugin, destination, imageCache, installed) {
|
|
41
|
-
if (plugin
|
|
41
|
+
if (types.isPluginDisabled(plugin)) {
|
|
42
42
|
return { pluginPath: null, pluginConfig: {} };
|
|
43
43
|
}
|
|
44
44
|
const hash = plugin.plugin_hash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer-oci.cjs.js","sources":["../src/installer-oci.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 * as path from 'node:path';\nimport { InstallException } from './errors';\nimport { type OciImageCache } from './image-cache';\nimport { log } from './log';\nimport { extractOciPlugin } from './tar-extract';\nimport {\n CONFIG_HASH_FILE,\n effectivePullPolicy,\n IMAGE_HASH_FILE,\n type Plugin,\n PullPolicy,\n} from './types';\nimport { fileExists, markAsFresh } from './util';\n\n/**\n * Split an OCI package spec into `<image-part>!<plugin-path>`. Uses\n * `indexOf` so plugin paths containing `!` (legal per the OCI grammar) are\n * preserved on the right side instead of being silently truncated by\n * `String#split`.\n */\nfunction splitOciPackage(\n pkg: string,\n): { imagePart: string; pluginPath: string } | null {\n const bang = pkg.indexOf('!');\n if (bang === -1) return null;\n const imagePart = pkg.slice(0, bang);\n const pluginPath = pkg.slice(bang + 1);\n if (!imagePart || !pluginPath) return null;\n return { imagePart, pluginPath };\n}\n\nexport type OciInstallResult = {\n /** The installed plugin's directory name (relative to destination), or null when skipped. */\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\n/**\n * Install a single OCI-packaged plugin into `destination`. Returns the\n * on-disk directory name and the plugin's own config (for merging into the\n * global app-config).\n */\nexport async function installOciPlugin(\n plugin: Plugin,\n destination: string,\n imageCache: OciImageCache,\n installed: Map<string, string>,\n): Promise<OciInstallResult> {\n if (plugin
|
|
1
|
+
{"version":3,"file":"installer-oci.cjs.js","sources":["../src/installer-oci.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 * as path from 'node:path';\nimport { InstallException } from './errors';\nimport { type OciImageCache } from './image-cache';\nimport { log } from './log';\nimport { extractOciPlugin } from './tar-extract';\nimport {\n CONFIG_HASH_FILE,\n effectivePullPolicy,\n IMAGE_HASH_FILE,\n isPluginDisabled,\n type Plugin,\n PullPolicy,\n} from './types';\nimport { fileExists, markAsFresh } from './util';\n\n/**\n * Split an OCI package spec into `<image-part>!<plugin-path>`. Uses\n * `indexOf` so plugin paths containing `!` (legal per the OCI grammar) are\n * preserved on the right side instead of being silently truncated by\n * `String#split`.\n */\nfunction splitOciPackage(\n pkg: string,\n): { imagePart: string; pluginPath: string } | null {\n const bang = pkg.indexOf('!');\n if (bang === -1) return null;\n const imagePart = pkg.slice(0, bang);\n const pluginPath = pkg.slice(bang + 1);\n if (!imagePart || !pluginPath) return null;\n return { imagePart, pluginPath };\n}\n\nexport type OciInstallResult = {\n /** The installed plugin's directory name (relative to destination), or null when skipped. */\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\n/**\n * Install a single OCI-packaged plugin into `destination`. Returns the\n * on-disk directory name and the plugin's own config (for merging into the\n * global app-config).\n */\nexport async function installOciPlugin(\n plugin: Plugin,\n destination: string,\n imageCache: OciImageCache,\n installed: Map<string, string>,\n): Promise<OciInstallResult> {\n if (isPluginDisabled(plugin)) {\n return { pluginPath: null, pluginConfig: {} };\n }\n const hash = plugin.plugin_hash;\n if (!hash) {\n throw new InstallException(\n `Internal error: plugin ${plugin.package} missing plugin_hash`,\n );\n }\n const pkg = plugin.package;\n const config: Record<string, unknown> = plugin.pluginConfig ?? {};\n const pullPolicy = effectivePullPolicy(plugin);\n\n if (\n await isAlreadyInstalled(\n pkg,\n hash,\n pullPolicy,\n destination,\n imageCache,\n installed,\n )\n ) {\n installed.delete(hash);\n return { pluginPath: null, pluginConfig: config };\n }\n\n if (!plugin.version) {\n throw new InstallException(`No version for ${pkg}`);\n }\n const parts = splitOciPackage(pkg);\n if (!parts) {\n throw new InstallException(\n `OCI package ${pkg} missing !plugin-path suffix`,\n );\n }\n const { imagePart, pluginPath } = parts;\n\n const tarball = await imageCache.getTarball(imagePart);\n await extractOciPlugin(tarball, pluginPath, destination);\n\n const pluginDir = path.join(destination, pluginPath);\n await fs.mkdir(pluginDir, { recursive: true });\n await fs.writeFile(\n path.join(pluginDir, IMAGE_HASH_FILE),\n await imageCache.getDigest(imagePart),\n );\n await fs.writeFile(path.join(pluginDir, CONFIG_HASH_FILE), hash);\n\n markAsFresh(installed, pluginPath);\n return { pluginPath, pluginConfig: config };\n}\n\n/**\n * Returns true when the plugin is already installed and can be skipped:\n * - IfNotPresent policy → skip unconditionally\n * - Always policy → skip only when the remote digest matches what's on disk\n */\nasync function isAlreadyInstalled(\n pkg: string,\n hash: string,\n pullPolicy: PullPolicy,\n destination: string,\n imageCache: OciImageCache,\n installed: Map<string, string>,\n): Promise<boolean> {\n const pathInstalled = installed.get(hash);\n if (pathInstalled === undefined) return false;\n\n if (pullPolicy === PullPolicy.IF_NOT_PRESENT) {\n log(`\\t==> ${pkg}: already installed, skipping`);\n return true;\n }\n\n if (pullPolicy !== PullPolicy.ALWAYS) return false;\n\n const digestFile = path.join(destination, pathInstalled, IMAGE_HASH_FILE);\n if (!(await fileExists(digestFile))) return false;\n\n const localDigest = (await fs.readFile(digestFile, 'utf8')).trim();\n const parts = splitOciPackage(pkg);\n if (!parts) return false;\n const remoteDigest = await imageCache.getDigest(parts.imagePart);\n if (localDigest !== remoteDigest) return false;\n\n log(`\\t==> ${pkg}: digest unchanged, skipping`);\n return true;\n}\n"],"names":["isPluginDisabled","InstallException","effectivePullPolicy","extractOciPlugin","path","fs","IMAGE_HASH_FILE","CONFIG_HASH_FILE","markAsFresh","PullPolicy","log","fileExists"],"mappings":"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"}
|
package/dist/installer.cjs.js
CHANGED
|
@@ -257,7 +257,7 @@ function categorize(allPlugins) {
|
|
|
257
257
|
const npm = [];
|
|
258
258
|
const skipped = [];
|
|
259
259
|
for (const plugin of Object.values(allPlugins)) {
|
|
260
|
-
if (plugin.
|
|
260
|
+
if (types.isPluginDisabled(plugin, log.log)) {
|
|
261
261
|
log.log(`
|
|
262
262
|
======= Skipping disabled plugin ${plugin.package}`);
|
|
263
263
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.cjs.js","sources":["../src/installer.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 { existsSync } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { cli } from 'cleye';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport {\n cleanupCatalogIndexTemp,\n extractCatalogIndex,\n extractExtraCatalogIndex,\n parseExtraCatalogIndexImages,\n} from './catalog-index';\nimport {\n getNpmWorkers,\n getWorkers,\n mapConcurrent,\n type Outcome,\n} from './concurrency';\nimport { InstallException } from './errors';\nimport { OciImageCache } from './image-cache';\nimport { installNpmPlugin } from './installer-npm';\nimport { installOciPlugin } from './installer-oci';\nimport { createLock, registerLockCleanup, removeLock } from './lock-file';\nimport { log } from './log';\nimport {\n deepMerge,\n filterDisabledOciPlugins,\n mergePlugin,\n preMergeOciDisabledState,\n} from './merger';\nimport { computePluginHash } from './plugin-hash';\nimport { Skopeo } from './skopeo';\nimport {\n CONFIG_HASH_FILE,\n DPDY_FILENAME,\n type DynamicPluginsConfig,\n effectivePullPolicy,\n GLOBAL_CONFIG_FILENAME,\n LOCK_FILENAME,\n OCI_PROTO,\n type Plugin,\n type PluginMap,\n type PluginSpec,\n PullPolicy,\n} from './types';\nimport { fileExists, isPlainObject } from './util';\n\nconst CONFIG_FILE = 'dynamic-plugins.yaml';\n\n/**\n * Entry point for the install command. `args` is the argv slice after the\n * command path; `programName` overrides the `--help` usage line so it matches\n * the actual invocation (e.g. `install-dynamic-plugins install` when called\n * via the cli-module discovery path). Exported for the cli-module command\n * loader and unit tests; not part of the package's public API.\n *\n * @internal\n */\nexport async function main(\n args: string[] = process.argv.slice(2),\n programName = 'install-dynamic-plugins',\n): Promise<void> {\n const argv = cli(\n {\n name: programName,\n parameters: ['<dynamic-plugins-root>'],\n help: {\n description:\n 'Install RHDH dynamic plugins listed in dynamic-plugins.yaml into the given directory.',\n },\n },\n undefined,\n args,\n );\n const root = path.resolve(argv._.dynamicPluginsRoot);\n const lockPath = path.join(root, LOCK_FILENAME);\n registerLockCleanup(lockPath);\n await fs.mkdir(root, { recursive: true });\n await createLock(lockPath);\n\n let exitCode = 0;\n try {\n exitCode = await runInstaller(root);\n } finally {\n await cleanupCatalogIndexTemp(root).catch(() => undefined);\n await removeLock(lockPath).catch(() => undefined);\n }\n process.exit(exitCode);\n}\n\nasync function runInstaller(root: string): Promise<number> {\n const skopeo = new Skopeo();\n const workers = getWorkers();\n log(`======= Workers: ${workers} (CPUs: ${os.cpus().length})`);\n\n // Resolve the config file path against CWD at startup so the dependency on\n // CWD is explicit in the operator log; includes are resolved relative to\n // the config file's directory (matches the Python installer).\n const configFileAbs = path.resolve(CONFIG_FILE);\n const configDir = path.dirname(configFileAbs);\n const globalConfigFile = path.join(root, GLOBAL_CONFIG_FILENAME);\n log(`======= Config file: ${configFileAbs}`);\n\n const entitiesDir =\n process.env.CATALOG_ENTITIES_EXTRACT_DIR ??\n path.join(os.tmpdir(), 'extensions');\n const catalogDpdy = await maybeExtractCatalogIndex(skopeo, root, entitiesDir);\n await maybeExtractExtraCatalogIndexes(skopeo, entitiesDir);\n const content = await loadDynamicPluginsConfig(\n configFileAbs,\n globalConfigFile,\n );\n if (!content) return 0;\n\n const imageCache = new OciImageCache(\n skopeo,\n await fs.mkdtemp(path.join(os.tmpdir(), 'rhdh-oci-cache-')),\n );\n\n const allPlugins = await loadAllPlugins(\n content,\n configFileAbs,\n configDir,\n catalogDpdy,\n imageCache,\n );\n const installed = await readInstalledPluginHashes(root);\n const globalConfig: Record<string, unknown> = {\n dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' },\n };\n const { oci, npm, skipped } = categorize(allPlugins);\n handleSkippedLocals(skipped, globalConfig);\n\n const skipIntegrity =\n (process.env.SKIP_INTEGRITY_CHECK ?? '').toLowerCase() === 'true';\n const errors: string[] = [];\n await installOci(\n oci,\n root,\n imageCache,\n installed,\n workers,\n globalConfig,\n errors,\n );\n await installNpm(npm, root, skipIntegrity, installed, globalConfig, errors);\n\n return finalizeInstall(\n errors,\n globalConfigFile,\n globalConfig,\n root,\n installed,\n );\n}\n\n/** Optional `CATALOG_INDEX_IMAGE` extraction — returns the path to the\n * extracted `dynamic-plugins.default.yaml`, or `null` when the env var is\n * unset. */\nasync function maybeExtractCatalogIndex(\n skopeo: Skopeo,\n root: string,\n entitiesDir: string,\n): Promise<string | null> {\n const catalogImage = process.env.CATALOG_INDEX_IMAGE ?? '';\n if (!catalogImage) return null;\n return extractCatalogIndex(skopeo, catalogImage, root, entitiesDir);\n}\n\n/**\n * Optional `EXTRA_CATALOG_INDEX_IMAGES` extraction. Each entry is extracted\n * into an isolated subdirectory under `<entitiesDir>/extra/` so multiple\n * indexes can coexist without clobbering the primary index's\n * `catalog-entities/`. Duplicate subdirectory names within the same env-var\n * value emit an overwrite warning (handled by `extractExtraCatalogIndex`).\n */\nasync function maybeExtractExtraCatalogIndexes(\n skopeo: Skopeo,\n entitiesDir: string,\n): Promise<void> {\n const raw = process.env.EXTRA_CATALOG_INDEX_IMAGES ?? '';\n if (!raw) return;\n const extraParent = path.join(entitiesDir, 'extra');\n const seen = new Map<string, string>();\n for (const [name, imageRef] of parseExtraCatalogIndexImages(raw)) {\n const prev = seen.get(name) ?? null;\n seen.set(name, imageRef);\n await extractExtraCatalogIndex(skopeo, imageRef, name, extraParent, prev);\n }\n}\n\n/** Read and parse `dynamic-plugins.yaml`. Writes an empty global config and\n * returns `null` for the two short-circuit cases (file missing, file empty)\n * so the caller can early-exit with code 0. */\nasync function loadDynamicPluginsConfig(\n configFileAbs: string,\n globalConfigFile: string,\n): Promise<DynamicPluginsConfig | null> {\n if (!(await fileExists(configFileAbs))) {\n log(`No ${CONFIG_FILE} found at ${configFileAbs}. Skipping.`);\n await fs.writeFile(globalConfigFile, '');\n return null;\n }\n const rawContent = await fs.readFile(configFileAbs, 'utf8');\n const content = parseYaml(rawContent) as DynamicPluginsConfig | null;\n if (!content) {\n log(`${configFileAbs} is empty. Skipping.`);\n await fs.writeFile(globalConfigFile, '');\n return null;\n }\n if (!isPlainObject(content)) {\n throw new InstallException(\n `${configFileAbs} must contain a YAML mapping, got ${typeof content}`,\n );\n }\n return content;\n}\n\n/** Resolve include paths, substitute the catalog-index placeholder, merge\n * everything into a single `PluginMap`, and compute change-detection hashes.\n *\n * Two-phase to match the Python pre-merge OCI-disable pass: load every\n * include file's plugin list into memory FIRST, compute the effectively\n * disabled OCI registries, then filter those entries out of every list\n * before merging. Without this pass an OCI plugin marked `disabled: true`\n * at level 1 would still trigger a `skopeo` round-trip during the level-0\n * merge — wasted work and a footgun in restricted-network init containers.\n */\nasync function loadAllPlugins(\n content: DynamicPluginsConfig,\n configFileAbs: string,\n configDir: string,\n catalogDpdy: string | null,\n imageCache: OciImageCache,\n): Promise<PluginMap> {\n const allPlugins: PluginMap = {};\n const includes = resolveIncludes(\n content.includes ?? [],\n configDir,\n catalogDpdy,\n );\n\n const includeLists: Array<[string, PluginSpec[]]> = [];\n for (const inc of includes) {\n if (!(await fileExists(inc))) {\n log(`WARNING: include file ${inc} not found, skipping`);\n continue;\n }\n log(`\\n======= Including plugins from ${inc}`);\n const parsed = parseYaml(\n await fs.readFile(inc, 'utf8'),\n ) as DynamicPluginsConfig | null;\n if (parsed && !isPlainObject(parsed)) {\n throw new InstallException(`${inc} must contain a mapping`);\n }\n const plugins = parsed?.plugins ?? [];\n if (!Array.isArray(plugins)) {\n throw new InstallException(\n `${inc} must contain a 'plugins' list (got ${typeof plugins})`,\n );\n }\n includeLists.push([inc, plugins]);\n }\n const mainPlugins = content.plugins ?? [];\n\n const disabledRegistries = preMergeOciDisabledState(\n includeLists,\n mainPlugins,\n configFileAbs,\n );\n\n for (const [inc, plugins] of includeLists) {\n for (const plugin of filterDisabledOciPlugins(\n plugins,\n disabledRegistries,\n )) {\n await mergePlugin(plugin, allPlugins, inc, /* level */ 0, imageCache);\n }\n }\n for (const plugin of filterDisabledOciPlugins(\n mainPlugins,\n disabledRegistries,\n )) {\n await mergePlugin(\n plugin,\n allPlugins,\n configFileAbs,\n /* level */ 1,\n imageCache,\n );\n }\n\n for (const p of Object.values(allPlugins)) {\n p.plugin_hash = computePluginHash(p);\n }\n return allPlugins;\n}\n\nfunction resolveIncludes(\n rawIncludes: readonly string[],\n configDir: string,\n catalogDpdy: string | null,\n): string[] {\n const includes = rawIncludes.map(inc =>\n path.isAbsolute(inc) ? inc : path.resolve(configDir, inc),\n );\n if (catalogDpdy) {\n const idx = includes.findIndex(inc => path.basename(inc) === DPDY_FILENAME);\n if (idx !== -1) includes[idx] = catalogDpdy;\n }\n return includes;\n}\n\n/**\n * Write the global config, prune obsolete plugin dirs, and compute the exit\n * code. Exported for unit tests; not part of the package's public API.\n *\n * @internal\n */\nexport async function finalizeInstall(\n errors: string[],\n globalConfigFile: string,\n globalConfig: Record<string, unknown>,\n root: string,\n installed: Map<string, string>,\n): Promise<number> {\n if (errors.length > 0) {\n log(`\\n======= ${errors.length} plugin(s) failed:`);\n for (const err of errors) log(` - ${err}`);\n // Skip writing the global config and cleaning up previously-installed\n // plugin dirs so the filesystem does not end up in an \"almost valid\"\n // state. Exit 1 is enough for init containers to block startup, but a\n // manual restart of the main container (or a deployment that does not\n // enforce init-container success) could otherwise pick up a partial\n // config — e.g. a frontend plugin without its backend dep, yielding a\n // broken UI. Preserving the prior state makes the next run a clean retry.\n log(\n `\\n======= Skipping ${GLOBAL_CONFIG_FILENAME} write and cleanup because of install failures. ` +\n `Fix the errors above and re-run; the previous successful state is preserved.`,\n );\n return 1;\n }\n\n await fs.writeFile(globalConfigFile, stringifyYaml(globalConfig));\n await cleanupRemoved(root, installed);\n\n log('\\n======= All plugins installed successfully');\n return 0;\n}\n\ntype Categorized = {\n oci: Plugin[];\n npm: Plugin[];\n skipped: Plugin[];\n};\n\nfunction categorize(allPlugins: PluginMap): Categorized {\n const oci: Plugin[] = [];\n const npm: Plugin[] = [];\n const skipped: Plugin[] = [];\n for (const plugin of Object.values(allPlugins)) {\n if (plugin.disabled) {\n log(`\\n======= Skipping disabled plugin ${plugin.package}`);\n continue;\n }\n if (plugin.package.startsWith(OCI_PROTO)) {\n oci.push(plugin);\n continue;\n }\n if (plugin.package.startsWith('./')) {\n const localPath = path.join(process.cwd(), plugin.package.slice(2));\n if (existsSync(localPath)) npm.push(plugin);\n else skipped.push(plugin);\n continue;\n }\n npm.push(plugin);\n }\n return { oci, npm, skipped };\n}\n\nfunction handleSkippedLocals(\n skipped: Plugin[],\n globalConfig: Record<string, unknown>,\n): void {\n if (skipped.length === 0) return;\n log(\n `\\n======= Skipping ${skipped.length} local plugins (directories not found)`,\n );\n for (const plugin of skipped) {\n const abs = path.join(process.cwd(), plugin.package.slice(2));\n log(`\\t==> ${plugin.package} (not found at ${abs})`);\n if (isPlainObject(plugin.pluginConfig)) {\n deepMerge(plugin.pluginConfig, globalConfig);\n }\n }\n}\n\ntype InstallOutcome = {\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\nasync function installOci(\n plugins: Plugin[],\n root: string,\n imageCache: OciImageCache,\n installed: Map<string, string>,\n workers: number,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Promise<void> {\n await runInstallPipeline({\n plugins,\n workers,\n label: 'OCI',\n installFn: plugin => installOciPlugin(plugin, root, imageCache, installed),\n installed,\n globalConfig,\n errors,\n });\n}\n\nasync function installNpm(\n plugins: Plugin[],\n root: string,\n skipIntegrity: boolean,\n installed: Map<string, string>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Promise<void> {\n // `npm pack` writes the tarball to `cwd` with a package-derived filename\n // (`<name>-<version>.tgz`), so concurrent invocations against different\n // packages don't clash on the filename. The npm CLI cache\n // (`~/.npm/_cacache`) handles its own locking. Cap defaults to 3 to keep\n // the registry happy — override with `DYNAMIC_PLUGINS_NPM_WORKERS=1` to\n // restore the original sequential behaviour.\n await runInstallPipeline({\n plugins,\n workers: getNpmWorkers(),\n label: 'NPM',\n installFn: plugin =>\n installNpmPlugin(plugin, root, skipIntegrity, installed),\n installed,\n globalConfig,\n errors,\n });\n}\n\ntype RunInstallPipelineArgs = {\n plugins: Plugin[];\n workers: number;\n label: 'OCI' | 'NPM';\n installFn: (plugin: Plugin) => Promise<InstallOutcome>;\n installed: Map<string, string>;\n globalConfig: Record<string, unknown>;\n errors: string[];\n};\n\n/**\n * Shared install pipeline used by both `installOci` and `installNpm`:\n * 1. Synchronous pre-pass that short-circuits \"definitely no-op\" plugins\n * (hash present, no force, pull policy not Always) without spinning\n * up the worker pool — avoids the parallel-skopeo / parallel-npm-pack\n * overhead in the steady-state restart case.\n * 2. `mapConcurrent` over the plugins that actually need work, capped by\n * `workers`.\n * 3. Single-pass over the outcomes that records errors and merges plugin\n * configs into the global config.\n *\n * Keeping both categories on this shared body so a behaviour change (a new\n * fast-path filter, a different log format, an extra error pathway) does\n * not have to be made twice in two slightly-divergent copies.\n */\nasync function runInstallPipeline(args: RunInstallPipelineArgs): Promise<void> {\n const {\n plugins,\n workers,\n label,\n installFn,\n installed,\n globalConfig,\n errors,\n } = args;\n if (plugins.length === 0) return;\n\n const needsWork = partitionDefinitelyNoOp(\n plugins,\n installed,\n globalConfig,\n errors,\n );\n if (needsWork.length === 0) return;\n\n const workerSuffix = workers === 1 ? '' : 's';\n log(\n `\\n======= Installing ${needsWork.length} ${label} plugin(s) (${workers} worker${workerSuffix})`,\n );\n\n const results = await mapConcurrent(needsWork, workers, async plugin => {\n log(`\\n======= Installing ${label} plugin ${plugin.package}`);\n return installFn(plugin);\n });\n\n applyInstallOutcomes(results, globalConfig, errors);\n}\n\n/**\n * Synchronous pre-pass: drop plugins that are definitely a no-op (hash on\n * disk, not forced, not Always-pull) without paying the worker-pool /\n * Promise overhead, and return the remaining plugins that actually need\n * installation work. Side-effect: removes the no-op plugins from\n * `installed` and merges their `pluginConfig` into `globalConfig`.\n */\nfunction partitionDefinitelyNoOp(\n plugins: Plugin[],\n installed: Map<string, string>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Plugin[] {\n const needsWork: Plugin[] = [];\n for (const plugin of plugins) {\n if (definitelyNoOp(plugin, installed)) {\n log(`\\t==> ${plugin.package}: already installed, skipping`);\n installed.delete(plugin.plugin_hash);\n mergeConfigSafely(\n plugin.pluginConfig,\n globalConfig,\n plugin.package,\n errors,\n );\n } else {\n needsWork.push(plugin);\n }\n }\n return needsWork;\n}\n\n/**\n * Drain a `mapConcurrent` outcome list: record errors, merge configs into\n * the global config, log success lines. Pulled out of `runInstallPipeline`\n * to keep that orchestrator small enough to read top-to-bottom.\n */\nfunction applyInstallOutcomes(\n results: ReadonlyArray<Outcome<InstallOutcome, Plugin>>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): void {\n for (const outcome of results) {\n if (!outcome.ok) {\n errors.push(`${outcome.item.package}: ${outcome.error.message}`);\n log(`\\t==> ERROR: ${outcome.item.package}: ${outcome.error.message}`);\n continue;\n }\n const { value, item } = outcome;\n const merged = mergeConfigSafely(\n value.pluginConfig,\n globalConfig,\n item.package,\n errors,\n );\n if (merged && value.pluginPath) {\n log(`\\t==> Installed ${item.package}`);\n }\n }\n}\n\n/**\n * Merge `pluginConfig` into `globalConfig` if it is a plain object. Returns\n * `false` and pushes onto `errors` when `deepMerge` raises a key conflict —\n * the caller uses this to skip the \"Installed\" success log so the operator\n * sees only the error line for the affected plugin.\n */\nfunction mergeConfigSafely(\n pluginConfig: Record<string, unknown> | undefined,\n globalConfig: Record<string, unknown>,\n pkg: string,\n errors: string[],\n): boolean {\n if (!isPlainObject(pluginConfig)) return true;\n try {\n deepMerge(pluginConfig, globalConfig);\n return true;\n } catch (err) {\n errors.push(`${pkg}: ${(err as Error).message}`);\n return false;\n }\n}\n\n/**\n * Cheap synchronous check: a plugin is \"definitely\" a no-op when its hash\n * is already on disk, the user did not force a re-download, and the pull\n * policy doesn't demand a remote-digest comparison. ALWAYS-pull plugins\n * still go through the regular install path because they need a\n * `skopeo inspect` to compare local vs remote digest.\n *\n * Type guard: narrows `plugin.plugin_hash` to a non-undefined `string`\n * inside the `if (definitelyNoOp(...))` branch so the caller does not\n * need a `as string` cast on the subsequent `installed.delete` call.\n */\nfunction definitelyNoOp(\n plugin: Plugin,\n installed: Map<string, string>,\n): plugin is Plugin & { plugin_hash: string } {\n if (!plugin.plugin_hash || !installed.has(plugin.plugin_hash)) return false;\n if (plugin.forceDownload) return false;\n return effectivePullPolicy(plugin) !== PullPolicy.ALWAYS;\n}\n\nasync function cleanupRemoved(\n root: string,\n installed: Map<string, string>,\n): Promise<void> {\n for (const [, dir] of installed) {\n const pluginDir = path.join(root, dir);\n log(`\\n======= Removing obsolete plugin ${dir}`);\n await fs.rm(pluginDir, { recursive: true, force: true });\n }\n}\n\nasync function readInstalledPluginHashes(\n root: string,\n): Promise<Map<string, string>> {\n const installed = new Map<string, string>();\n let entries: string[];\n try {\n entries = await fs.readdir(root);\n } catch {\n return installed;\n }\n for (const entry of entries) {\n const hashFile = path.join(root, entry, CONFIG_HASH_FILE);\n try {\n const hash = (await fs.readFile(hashFile, 'utf8')).trim();\n if (hash) installed.set(hash, entry);\n } catch {\n /* not a plugin dir */\n }\n }\n return installed;\n}\n"],"names":["cli","path","LOCK_FILENAME","registerLockCleanup","fs","createLock","cleanupCatalogIndexTemp","removeLock","skopeo","Skopeo","getWorkers","log","os","GLOBAL_CONFIG_FILENAME","imageCache","OciImageCache","extractCatalogIndex","parseExtraCatalogIndexImages","extractExtraCatalogIndex","fileExists","parseYaml","isPlainObject","InstallException","preMergeOciDisabledState","filterDisabledOciPlugins","mergePlugin","computePluginHash","DPDY_FILENAME","stringifyYaml","OCI_PROTO","existsSync","deepMerge","installOciPlugin","getNpmWorkers","installNpmPlugin","mapConcurrent","effectivePullPolicy","PullPolicy","CONFIG_HASH_FILE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"installer.cjs.js","sources":["../src/installer.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 { existsSync } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { cli } from 'cleye';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport {\n cleanupCatalogIndexTemp,\n extractCatalogIndex,\n extractExtraCatalogIndex,\n parseExtraCatalogIndexImages,\n} from './catalog-index';\nimport {\n getNpmWorkers,\n getWorkers,\n mapConcurrent,\n type Outcome,\n} from './concurrency';\nimport { InstallException } from './errors';\nimport { OciImageCache } from './image-cache';\nimport { installNpmPlugin } from './installer-npm';\nimport { installOciPlugin } from './installer-oci';\nimport { createLock, registerLockCleanup, removeLock } from './lock-file';\nimport { log } from './log';\nimport {\n deepMerge,\n filterDisabledOciPlugins,\n mergePlugin,\n preMergeOciDisabledState,\n} from './merger';\nimport { computePluginHash } from './plugin-hash';\nimport { Skopeo } from './skopeo';\nimport {\n CONFIG_HASH_FILE,\n DPDY_FILENAME,\n type DynamicPluginsConfig,\n effectivePullPolicy,\n GLOBAL_CONFIG_FILENAME,\n isPluginDisabled,\n LOCK_FILENAME,\n OCI_PROTO,\n type Plugin,\n type PluginMap,\n type PluginSpec,\n PullPolicy,\n} from './types';\nimport { fileExists, isPlainObject } from './util';\n\nconst CONFIG_FILE = 'dynamic-plugins.yaml';\n\n/**\n * Entry point for the install command. `args` is the argv slice after the\n * command path; `programName` overrides the `--help` usage line so it matches\n * the actual invocation (e.g. `install-dynamic-plugins install` when called\n * via the cli-module discovery path). Exported for the cli-module command\n * loader and unit tests; not part of the package's public API.\n *\n * @internal\n */\nexport async function main(\n args: string[] = process.argv.slice(2),\n programName = 'install-dynamic-plugins',\n): Promise<void> {\n const argv = cli(\n {\n name: programName,\n parameters: ['<dynamic-plugins-root>'],\n help: {\n description:\n 'Install RHDH dynamic plugins listed in dynamic-plugins.yaml into the given directory.',\n },\n },\n undefined,\n args,\n );\n const root = path.resolve(argv._.dynamicPluginsRoot);\n const lockPath = path.join(root, LOCK_FILENAME);\n registerLockCleanup(lockPath);\n await fs.mkdir(root, { recursive: true });\n await createLock(lockPath);\n\n let exitCode = 0;\n try {\n exitCode = await runInstaller(root);\n } finally {\n await cleanupCatalogIndexTemp(root).catch(() => undefined);\n await removeLock(lockPath).catch(() => undefined);\n }\n process.exit(exitCode);\n}\n\nasync function runInstaller(root: string): Promise<number> {\n const skopeo = new Skopeo();\n const workers = getWorkers();\n log(`======= Workers: ${workers} (CPUs: ${os.cpus().length})`);\n\n // Resolve the config file path against CWD at startup so the dependency on\n // CWD is explicit in the operator log; includes are resolved relative to\n // the config file's directory (matches the Python installer).\n const configFileAbs = path.resolve(CONFIG_FILE);\n const configDir = path.dirname(configFileAbs);\n const globalConfigFile = path.join(root, GLOBAL_CONFIG_FILENAME);\n log(`======= Config file: ${configFileAbs}`);\n\n const entitiesDir =\n process.env.CATALOG_ENTITIES_EXTRACT_DIR ??\n path.join(os.tmpdir(), 'extensions');\n const catalogDpdy = await maybeExtractCatalogIndex(skopeo, root, entitiesDir);\n await maybeExtractExtraCatalogIndexes(skopeo, entitiesDir);\n const content = await loadDynamicPluginsConfig(\n configFileAbs,\n globalConfigFile,\n );\n if (!content) return 0;\n\n const imageCache = new OciImageCache(\n skopeo,\n await fs.mkdtemp(path.join(os.tmpdir(), 'rhdh-oci-cache-')),\n );\n\n const allPlugins = await loadAllPlugins(\n content,\n configFileAbs,\n configDir,\n catalogDpdy,\n imageCache,\n );\n const installed = await readInstalledPluginHashes(root);\n const globalConfig: Record<string, unknown> = {\n dynamicPlugins: { rootDirectory: 'dynamic-plugins-root' },\n };\n const { oci, npm, skipped } = categorize(allPlugins);\n handleSkippedLocals(skipped, globalConfig);\n\n const skipIntegrity =\n (process.env.SKIP_INTEGRITY_CHECK ?? '').toLowerCase() === 'true';\n const errors: string[] = [];\n await installOci(\n oci,\n root,\n imageCache,\n installed,\n workers,\n globalConfig,\n errors,\n );\n await installNpm(npm, root, skipIntegrity, installed, globalConfig, errors);\n\n return finalizeInstall(\n errors,\n globalConfigFile,\n globalConfig,\n root,\n installed,\n );\n}\n\n/** Optional `CATALOG_INDEX_IMAGE` extraction — returns the path to the\n * extracted `dynamic-plugins.default.yaml`, or `null` when the env var is\n * unset. */\nasync function maybeExtractCatalogIndex(\n skopeo: Skopeo,\n root: string,\n entitiesDir: string,\n): Promise<string | null> {\n const catalogImage = process.env.CATALOG_INDEX_IMAGE ?? '';\n if (!catalogImage) return null;\n return extractCatalogIndex(skopeo, catalogImage, root, entitiesDir);\n}\n\n/**\n * Optional `EXTRA_CATALOG_INDEX_IMAGES` extraction. Each entry is extracted\n * into an isolated subdirectory under `<entitiesDir>/extra/` so multiple\n * indexes can coexist without clobbering the primary index's\n * `catalog-entities/`. Duplicate subdirectory names within the same env-var\n * value emit an overwrite warning (handled by `extractExtraCatalogIndex`).\n */\nasync function maybeExtractExtraCatalogIndexes(\n skopeo: Skopeo,\n entitiesDir: string,\n): Promise<void> {\n const raw = process.env.EXTRA_CATALOG_INDEX_IMAGES ?? '';\n if (!raw) return;\n const extraParent = path.join(entitiesDir, 'extra');\n const seen = new Map<string, string>();\n for (const [name, imageRef] of parseExtraCatalogIndexImages(raw)) {\n const prev = seen.get(name) ?? null;\n seen.set(name, imageRef);\n await extractExtraCatalogIndex(skopeo, imageRef, name, extraParent, prev);\n }\n}\n\n/** Read and parse `dynamic-plugins.yaml`. Writes an empty global config and\n * returns `null` for the two short-circuit cases (file missing, file empty)\n * so the caller can early-exit with code 0. */\nasync function loadDynamicPluginsConfig(\n configFileAbs: string,\n globalConfigFile: string,\n): Promise<DynamicPluginsConfig | null> {\n if (!(await fileExists(configFileAbs))) {\n log(`No ${CONFIG_FILE} found at ${configFileAbs}. Skipping.`);\n await fs.writeFile(globalConfigFile, '');\n return null;\n }\n const rawContent = await fs.readFile(configFileAbs, 'utf8');\n const content = parseYaml(rawContent) as DynamicPluginsConfig | null;\n if (!content) {\n log(`${configFileAbs} is empty. Skipping.`);\n await fs.writeFile(globalConfigFile, '');\n return null;\n }\n if (!isPlainObject(content)) {\n throw new InstallException(\n `${configFileAbs} must contain a YAML mapping, got ${typeof content}`,\n );\n }\n return content;\n}\n\n/** Resolve include paths, substitute the catalog-index placeholder, merge\n * everything into a single `PluginMap`, and compute change-detection hashes.\n *\n * Two-phase to match the Python pre-merge OCI-disable pass: load every\n * include file's plugin list into memory FIRST, compute the effectively\n * disabled OCI registries, then filter those entries out of every list\n * before merging. Without this pass an OCI plugin marked `disabled: true`\n * at level 1 would still trigger a `skopeo` round-trip during the level-0\n * merge — wasted work and a footgun in restricted-network init containers.\n */\nasync function loadAllPlugins(\n content: DynamicPluginsConfig,\n configFileAbs: string,\n configDir: string,\n catalogDpdy: string | null,\n imageCache: OciImageCache,\n): Promise<PluginMap> {\n const allPlugins: PluginMap = {};\n const includes = resolveIncludes(\n content.includes ?? [],\n configDir,\n catalogDpdy,\n );\n\n const includeLists: Array<[string, PluginSpec[]]> = [];\n for (const inc of includes) {\n if (!(await fileExists(inc))) {\n log(`WARNING: include file ${inc} not found, skipping`);\n continue;\n }\n log(`\\n======= Including plugins from ${inc}`);\n const parsed = parseYaml(\n await fs.readFile(inc, 'utf8'),\n ) as DynamicPluginsConfig | null;\n if (parsed && !isPlainObject(parsed)) {\n throw new InstallException(`${inc} must contain a mapping`);\n }\n const plugins = parsed?.plugins ?? [];\n if (!Array.isArray(plugins)) {\n throw new InstallException(\n `${inc} must contain a 'plugins' list (got ${typeof plugins})`,\n );\n }\n includeLists.push([inc, plugins]);\n }\n const mainPlugins = content.plugins ?? [];\n\n const disabledRegistries = preMergeOciDisabledState(\n includeLists,\n mainPlugins,\n configFileAbs,\n );\n\n for (const [inc, plugins] of includeLists) {\n for (const plugin of filterDisabledOciPlugins(\n plugins,\n disabledRegistries,\n )) {\n await mergePlugin(plugin, allPlugins, inc, /* level */ 0, imageCache);\n }\n }\n for (const plugin of filterDisabledOciPlugins(\n mainPlugins,\n disabledRegistries,\n )) {\n await mergePlugin(\n plugin,\n allPlugins,\n configFileAbs,\n /* level */ 1,\n imageCache,\n );\n }\n\n for (const p of Object.values(allPlugins)) {\n p.plugin_hash = computePluginHash(p);\n }\n return allPlugins;\n}\n\nfunction resolveIncludes(\n rawIncludes: readonly string[],\n configDir: string,\n catalogDpdy: string | null,\n): string[] {\n const includes = rawIncludes.map(inc =>\n path.isAbsolute(inc) ? inc : path.resolve(configDir, inc),\n );\n if (catalogDpdy) {\n const idx = includes.findIndex(inc => path.basename(inc) === DPDY_FILENAME);\n if (idx !== -1) includes[idx] = catalogDpdy;\n }\n return includes;\n}\n\n/**\n * Write the global config, prune obsolete plugin dirs, and compute the exit\n * code. Exported for unit tests; not part of the package's public API.\n *\n * @internal\n */\nexport async function finalizeInstall(\n errors: string[],\n globalConfigFile: string,\n globalConfig: Record<string, unknown>,\n root: string,\n installed: Map<string, string>,\n): Promise<number> {\n if (errors.length > 0) {\n log(`\\n======= ${errors.length} plugin(s) failed:`);\n for (const err of errors) log(` - ${err}`);\n // Skip writing the global config and cleaning up previously-installed\n // plugin dirs so the filesystem does not end up in an \"almost valid\"\n // state. Exit 1 is enough for init containers to block startup, but a\n // manual restart of the main container (or a deployment that does not\n // enforce init-container success) could otherwise pick up a partial\n // config — e.g. a frontend plugin without its backend dep, yielding a\n // broken UI. Preserving the prior state makes the next run a clean retry.\n log(\n `\\n======= Skipping ${GLOBAL_CONFIG_FILENAME} write and cleanup because of install failures. ` +\n `Fix the errors above and re-run; the previous successful state is preserved.`,\n );\n return 1;\n }\n\n await fs.writeFile(globalConfigFile, stringifyYaml(globalConfig));\n await cleanupRemoved(root, installed);\n\n log('\\n======= All plugins installed successfully');\n return 0;\n}\n\ntype Categorized = {\n oci: Plugin[];\n npm: Plugin[];\n skipped: Plugin[];\n};\n\nfunction categorize(allPlugins: PluginMap): Categorized {\n const oci: Plugin[] = [];\n const npm: Plugin[] = [];\n const skipped: Plugin[] = [];\n for (const plugin of Object.values(allPlugins)) {\n if (isPluginDisabled(plugin, log)) {\n log(`\\n======= Skipping disabled plugin ${plugin.package}`);\n continue;\n }\n if (plugin.package.startsWith(OCI_PROTO)) {\n oci.push(plugin);\n continue;\n }\n if (plugin.package.startsWith('./')) {\n const localPath = path.join(process.cwd(), plugin.package.slice(2));\n if (existsSync(localPath)) npm.push(plugin);\n else skipped.push(plugin);\n continue;\n }\n npm.push(plugin);\n }\n return { oci, npm, skipped };\n}\n\nfunction handleSkippedLocals(\n skipped: Plugin[],\n globalConfig: Record<string, unknown>,\n): void {\n if (skipped.length === 0) return;\n log(\n `\\n======= Skipping ${skipped.length} local plugins (directories not found)`,\n );\n for (const plugin of skipped) {\n const abs = path.join(process.cwd(), plugin.package.slice(2));\n log(`\\t==> ${plugin.package} (not found at ${abs})`);\n if (isPlainObject(plugin.pluginConfig)) {\n deepMerge(plugin.pluginConfig, globalConfig);\n }\n }\n}\n\ntype InstallOutcome = {\n pluginPath: string | null;\n pluginConfig: Record<string, unknown>;\n};\n\nasync function installOci(\n plugins: Plugin[],\n root: string,\n imageCache: OciImageCache,\n installed: Map<string, string>,\n workers: number,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Promise<void> {\n await runInstallPipeline({\n plugins,\n workers,\n label: 'OCI',\n installFn: plugin => installOciPlugin(plugin, root, imageCache, installed),\n installed,\n globalConfig,\n errors,\n });\n}\n\nasync function installNpm(\n plugins: Plugin[],\n root: string,\n skipIntegrity: boolean,\n installed: Map<string, string>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Promise<void> {\n // `npm pack` writes the tarball to `cwd` with a package-derived filename\n // (`<name>-<version>.tgz`), so concurrent invocations against different\n // packages don't clash on the filename. The npm CLI cache\n // (`~/.npm/_cacache`) handles its own locking. Cap defaults to 3 to keep\n // the registry happy — override with `DYNAMIC_PLUGINS_NPM_WORKERS=1` to\n // restore the original sequential behaviour.\n await runInstallPipeline({\n plugins,\n workers: getNpmWorkers(),\n label: 'NPM',\n installFn: plugin =>\n installNpmPlugin(plugin, root, skipIntegrity, installed),\n installed,\n globalConfig,\n errors,\n });\n}\n\ntype RunInstallPipelineArgs = {\n plugins: Plugin[];\n workers: number;\n label: 'OCI' | 'NPM';\n installFn: (plugin: Plugin) => Promise<InstallOutcome>;\n installed: Map<string, string>;\n globalConfig: Record<string, unknown>;\n errors: string[];\n};\n\n/**\n * Shared install pipeline used by both `installOci` and `installNpm`:\n * 1. Synchronous pre-pass that short-circuits \"definitely no-op\" plugins\n * (hash present, no force, pull policy not Always) without spinning\n * up the worker pool — avoids the parallel-skopeo / parallel-npm-pack\n * overhead in the steady-state restart case.\n * 2. `mapConcurrent` over the plugins that actually need work, capped by\n * `workers`.\n * 3. Single-pass over the outcomes that records errors and merges plugin\n * configs into the global config.\n *\n * Keeping both categories on this shared body so a behaviour change (a new\n * fast-path filter, a different log format, an extra error pathway) does\n * not have to be made twice in two slightly-divergent copies.\n */\nasync function runInstallPipeline(args: RunInstallPipelineArgs): Promise<void> {\n const {\n plugins,\n workers,\n label,\n installFn,\n installed,\n globalConfig,\n errors,\n } = args;\n if (plugins.length === 0) return;\n\n const needsWork = partitionDefinitelyNoOp(\n plugins,\n installed,\n globalConfig,\n errors,\n );\n if (needsWork.length === 0) return;\n\n const workerSuffix = workers === 1 ? '' : 's';\n log(\n `\\n======= Installing ${needsWork.length} ${label} plugin(s) (${workers} worker${workerSuffix})`,\n );\n\n const results = await mapConcurrent(needsWork, workers, async plugin => {\n log(`\\n======= Installing ${label} plugin ${plugin.package}`);\n return installFn(plugin);\n });\n\n applyInstallOutcomes(results, globalConfig, errors);\n}\n\n/**\n * Synchronous pre-pass: drop plugins that are definitely a no-op (hash on\n * disk, not forced, not Always-pull) without paying the worker-pool /\n * Promise overhead, and return the remaining plugins that actually need\n * installation work. Side-effect: removes the no-op plugins from\n * `installed` and merges their `pluginConfig` into `globalConfig`.\n */\nfunction partitionDefinitelyNoOp(\n plugins: Plugin[],\n installed: Map<string, string>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): Plugin[] {\n const needsWork: Plugin[] = [];\n for (const plugin of plugins) {\n if (definitelyNoOp(plugin, installed)) {\n log(`\\t==> ${plugin.package}: already installed, skipping`);\n installed.delete(plugin.plugin_hash);\n mergeConfigSafely(\n plugin.pluginConfig,\n globalConfig,\n plugin.package,\n errors,\n );\n } else {\n needsWork.push(plugin);\n }\n }\n return needsWork;\n}\n\n/**\n * Drain a `mapConcurrent` outcome list: record errors, merge configs into\n * the global config, log success lines. Pulled out of `runInstallPipeline`\n * to keep that orchestrator small enough to read top-to-bottom.\n */\nfunction applyInstallOutcomes(\n results: ReadonlyArray<Outcome<InstallOutcome, Plugin>>,\n globalConfig: Record<string, unknown>,\n errors: string[],\n): void {\n for (const outcome of results) {\n if (!outcome.ok) {\n errors.push(`${outcome.item.package}: ${outcome.error.message}`);\n log(`\\t==> ERROR: ${outcome.item.package}: ${outcome.error.message}`);\n continue;\n }\n const { value, item } = outcome;\n const merged = mergeConfigSafely(\n value.pluginConfig,\n globalConfig,\n item.package,\n errors,\n );\n if (merged && value.pluginPath) {\n log(`\\t==> Installed ${item.package}`);\n }\n }\n}\n\n/**\n * Merge `pluginConfig` into `globalConfig` if it is a plain object. Returns\n * `false` and pushes onto `errors` when `deepMerge` raises a key conflict —\n * the caller uses this to skip the \"Installed\" success log so the operator\n * sees only the error line for the affected plugin.\n */\nfunction mergeConfigSafely(\n pluginConfig: Record<string, unknown> | undefined,\n globalConfig: Record<string, unknown>,\n pkg: string,\n errors: string[],\n): boolean {\n if (!isPlainObject(pluginConfig)) return true;\n try {\n deepMerge(pluginConfig, globalConfig);\n return true;\n } catch (err) {\n errors.push(`${pkg}: ${(err as Error).message}`);\n return false;\n }\n}\n\n/**\n * Cheap synchronous check: a plugin is \"definitely\" a no-op when its hash\n * is already on disk, the user did not force a re-download, and the pull\n * policy doesn't demand a remote-digest comparison. ALWAYS-pull plugins\n * still go through the regular install path because they need a\n * `skopeo inspect` to compare local vs remote digest.\n *\n * Type guard: narrows `plugin.plugin_hash` to a non-undefined `string`\n * inside the `if (definitelyNoOp(...))` branch so the caller does not\n * need a `as string` cast on the subsequent `installed.delete` call.\n */\nfunction definitelyNoOp(\n plugin: Plugin,\n installed: Map<string, string>,\n): plugin is Plugin & { plugin_hash: string } {\n if (!plugin.plugin_hash || !installed.has(plugin.plugin_hash)) return false;\n if (plugin.forceDownload) return false;\n return effectivePullPolicy(plugin) !== PullPolicy.ALWAYS;\n}\n\nasync function cleanupRemoved(\n root: string,\n installed: Map<string, string>,\n): Promise<void> {\n for (const [, dir] of installed) {\n const pluginDir = path.join(root, dir);\n log(`\\n======= Removing obsolete plugin ${dir}`);\n await fs.rm(pluginDir, { recursive: true, force: true });\n }\n}\n\nasync function readInstalledPluginHashes(\n root: string,\n): Promise<Map<string, string>> {\n const installed = new Map<string, string>();\n let entries: string[];\n try {\n entries = await fs.readdir(root);\n } catch {\n return installed;\n }\n for (const entry of entries) {\n const hashFile = path.join(root, entry, CONFIG_HASH_FILE);\n try {\n const hash = (await fs.readFile(hashFile, 'utf8')).trim();\n if (hash) installed.set(hash, entry);\n } catch {\n /* not a plugin dir */\n }\n }\n return installed;\n}\n"],"names":["cli","path","LOCK_FILENAME","registerLockCleanup","fs","createLock","cleanupCatalogIndexTemp","removeLock","skopeo","Skopeo","getWorkers","log","os","GLOBAL_CONFIG_FILENAME","imageCache","OciImageCache","extractCatalogIndex","parseExtraCatalogIndexImages","extractExtraCatalogIndex","fileExists","parseYaml","isPlainObject","InstallException","preMergeOciDisabledState","filterDisabledOciPlugins","mergePlugin","computePluginHash","DPDY_FILENAME","stringifyYaml","isPluginDisabled","OCI_PROTO","existsSync","deepMerge","installOciPlugin","getNpmWorkers","installNpmPlugin","mapConcurrent","effectivePullPolicy","PullPolicy","CONFIG_HASH_FILE"],"mappings":"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"}
|
package/dist/merger.cjs.js
CHANGED
|
@@ -167,6 +167,10 @@ function copyPluginFields(src, dst, skip) {
|
|
|
167
167
|
if (skipSet.has(k) || isForbiddenKey(k)) continue;
|
|
168
168
|
safeSet(dst, k, v);
|
|
169
169
|
}
|
|
170
|
+
if (typeof src.enabled === "boolean" && "disabled" in dst)
|
|
171
|
+
delete dst.disabled;
|
|
172
|
+
if (typeof src.disabled === "boolean" && "enabled" in dst)
|
|
173
|
+
delete dst.enabled;
|
|
170
174
|
}
|
|
171
175
|
function isEqual(a, b) {
|
|
172
176
|
if (a === b) return true;
|
|
@@ -234,7 +238,7 @@ function recordRegistryPath(state, registry, path, sourceFile) {
|
|
|
234
238
|
function processOciEntry(state, plugin, level, sourceFile) {
|
|
235
239
|
const pkg = plugin.package;
|
|
236
240
|
if (typeof pkg !== "string" || !pkg.startsWith(types.OCI_PROTO)) return;
|
|
237
|
-
const disabled = plugin
|
|
241
|
+
const disabled = types.isPluginDisabled(plugin);
|
|
238
242
|
const parsed = ociKey.tryParseOciRegistryAndPath(pkg);
|
|
239
243
|
if (!parsed) {
|
|
240
244
|
logInvalidOciFormat(pkg, sourceFile, disabled);
|
|
@@ -315,7 +319,7 @@ function filterDisabledOciPlugins(plugins, disabledRegistries) {
|
|
|
315
319
|
======= Disabling OCI plugin ${pkg}`);
|
|
316
320
|
continue;
|
|
317
321
|
}
|
|
318
|
-
if (!parsed && plugin
|
|
322
|
+
if (!parsed && types.isPluginDisabled(plugin)) {
|
|
319
323
|
log.log(`
|
|
320
324
|
======= Disabling OCI plugin ${pkg}`);
|
|
321
325
|
continue;
|
package/dist/merger.cjs.js.map
CHANGED
|
@@ -1 +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":"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"}
|
|
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 isPluginDisabled,\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 // When the override introduces a valid boolean activation field, clear the\n // opposite so the merged record never carries both (which would trigger a\n // spurious \"specifies both\" warning in isPluginDisabled). Only act on\n // actual booleans — a non-boolean value is treated as \"unset\" by\n // isPluginDisabled and should not displace a valid value on dst.\n if (typeof src.enabled === 'boolean' && 'disabled' in dst)\n delete (dst as Record<string, unknown>).disabled;\n if (typeof src.disabled === 'boolean' && 'enabled' in dst)\n delete (dst as Record<string, unknown>).enabled;\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 = isPluginDisabled(plugin);\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 && isPluginDisabled(plugin)) {\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","isPluginDisabled","tryParseOciRegistryAndPath"],"mappings":"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"}
|
package/dist/package.json.cjs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var name = "@red-hat-developer-hub/cli-module-install-dynamic-plugins";
|
|
6
|
-
var version = "0.
|
|
6
|
+
var version = "0.3.0";
|
|
7
7
|
var description = "Backstage CLI module that installs RHDH dynamic plugins from a dynamic-plugins.yaml config (OCI, NPM, local).";
|
|
8
8
|
var license = "Apache-2.0";
|
|
9
9
|
var backstage = {
|
package/dist/types.cjs.js
CHANGED
|
@@ -26,6 +26,29 @@ function effectivePullPolicy(plugin) {
|
|
|
26
26
|
if (plugin.pullPolicy) return plugin.pullPolicy;
|
|
27
27
|
return plugin.package.includes(LATEST_TAG_MARKER) ? PullPolicy.ALWAYS : PullPolicy.IF_NOT_PRESENT;
|
|
28
28
|
}
|
|
29
|
+
function isPluginDisabled(plugin, warn) {
|
|
30
|
+
const hasEnabled = typeof plugin.enabled === "boolean";
|
|
31
|
+
const hasDisabled = typeof plugin.disabled === "boolean";
|
|
32
|
+
if (plugin.enabled !== void 0 && !hasEnabled) {
|
|
33
|
+
warn?.(
|
|
34
|
+
`WARNING: Plugin ${plugin.package} has non-boolean 'enabled: ${String(plugin.enabled)}'. Expected true or false; ignoring the field.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (plugin.disabled !== void 0 && !hasDisabled) {
|
|
38
|
+
warn?.(
|
|
39
|
+
`WARNING: Plugin ${plugin.package} has non-boolean 'disabled: ${String(plugin.disabled)}'. Expected true or false; ignoring the field.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (hasEnabled && hasDisabled) {
|
|
43
|
+
warn?.(
|
|
44
|
+
`WARNING: Plugin ${plugin.package} specifies both 'enabled' and 'disabled'. The 'enabled' field takes precedence; please use only 'enabled'.`
|
|
45
|
+
);
|
|
46
|
+
return !plugin.enabled;
|
|
47
|
+
}
|
|
48
|
+
if (hasEnabled) return !plugin.enabled;
|
|
49
|
+
if (hasDisabled) return plugin.disabled === true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
29
52
|
|
|
30
53
|
exports.CONFIG_HASH_FILE = CONFIG_HASH_FILE;
|
|
31
54
|
exports.DOCKER_PROTO = DOCKER_PROTO;
|
|
@@ -41,5 +64,6 @@ exports.RECOGNIZED_ALGORITHMS = RECOGNIZED_ALGORITHMS;
|
|
|
41
64
|
exports.RHDH_FALLBACK = RHDH_FALLBACK;
|
|
42
65
|
exports.RHDH_REGISTRY = RHDH_REGISTRY;
|
|
43
66
|
exports.effectivePullPolicy = effectivePullPolicy;
|
|
67
|
+
exports.isPluginDisabled = isPluginDisabled;
|
|
44
68
|
exports.parseMaxEntrySize = parseMaxEntrySize;
|
|
45
69
|
//# sourceMappingURL=types.cjs.js.map
|
package/dist/types.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.cjs.js","sources":["../src/types.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 const PullPolicy = {\n IF_NOT_PRESENT: 'IfNotPresent',\n ALWAYS: 'Always',\n} as const;\n\n// PullPolicy is intentionally both a const (runtime values) and a type\n// (string union) so callers can use `PullPolicy.ALWAYS` and `PullPolicy` as\n// a type. The no-redeclare rule doesn't recognise this enum-like pattern.\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport type PullPolicy = (typeof PullPolicy)[keyof typeof PullPolicy];\n\n/**\n * External schema — the fields a user may declare in `dynamic-plugins.yaml`.\n * Keep this in sync with RHDH documentation.\n */\nexport type PluginSpec = {\n package: string;\n disabled?: boolean;\n pullPolicy?: PullPolicy;\n forceDownload?: boolean;\n integrity?: string;\n pluginConfig?: Record<string, unknown>;\n};\n\n/**\n * Internal plugin record. Extends the YAML schema with fields populated at\n * runtime (`version` from the package string, `plugin_hash` for change\n * detection, `last_modified_level` to track include-file precedence).\n *\n * The field name `last_modified_level` matches the Python implementation so\n * the install hashes computed by `plugin-hash.ts` stay byte-compatible\n * across the Python ↔ TS migration. Renaming it would force every existing\n * dynamic-plugins-root to be re-installed on the first TS run.\n */\nexport type Plugin = PluginSpec & {\n version?: string;\n plugin_hash?: string;\n last_modified_level?: number;\n};\n\nexport type PluginMap = Record<string, Plugin>;\n\nexport type DynamicPluginsConfig = {\n includes?: string[];\n plugins?: PluginSpec[];\n};\n\nexport const DOCKER_PROTO = 'docker://';\nexport const OCI_PROTO = 'oci://';\n/**\n * Tag suffix that, by convention, opts an OCI plugin into `pullPolicy: Always`\n * when no explicit policy is set — mirrors the Python script's behaviour and\n * keeps the two implementations swappable. Always parsed in combination with\n * the `!plugin-path` separator so a plugin tagged `:latest` (no plugin path)\n * does not accidentally trigger.\n */\nexport const LATEST_TAG_MARKER = ':latest!';\nexport const RHDH_REGISTRY = 'registry.access.redhat.com/rhdh/';\nexport const RHDH_FALLBACK = 'quay.io/rhdh/';\nexport const CONFIG_HASH_FILE = 'dynamic-plugin-config.hash';\nexport const IMAGE_HASH_FILE = 'dynamic-plugin-image.hash';\nexport const DPDY_FILENAME = 'dynamic-plugins.default.yaml';\nexport const LOCK_FILENAME = 'install-dynamic-plugins.lock';\nexport const GLOBAL_CONFIG_FILENAME = 'app-config.dynamic-plugins.yaml';\n\nconst DEFAULT_MAX_ENTRY_SIZE = 40_000_000;\n\n/**\n * Parse the MAX_ENTRY_SIZE env var, falling back to the default when unset,\n * non-numeric, or < 1. Exported for unit tests — the `MAX_ENTRY_SIZE` constant\n * below is the module-level value used by the extractors.\n */\nexport function parseMaxEntrySize(\n raw: string | undefined = process.env.MAX_ENTRY_SIZE,\n): number {\n if (!raw) return DEFAULT_MAX_ENTRY_SIZE;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : DEFAULT_MAX_ENTRY_SIZE;\n}\n\nexport const MAX_ENTRY_SIZE = parseMaxEntrySize();\nexport const RECOGNIZED_ALGORITHMS = ['sha512', 'sha384', 'sha256'] as const;\nexport type Algorithm = (typeof RECOGNIZED_ALGORITHMS)[number];\n\n/**\n * Resolve the effective `pullPolicy` for an OCI plugin: an explicit policy\n * wins, otherwise the convention is `Always` for `:latest!` images and\n * `IfNotPresent` for everything else. Shared by the install pipeline and the\n * \"definitely no-op\" pre-pass so the `:latest!` semantics live in one place.\n */\nexport function effectivePullPolicy(plugin: {\n pullPolicy?: PullPolicy;\n package: string;\n}): PullPolicy {\n if (plugin.pullPolicy) return plugin.pullPolicy;\n return plugin.package.includes(LATEST_TAG_MARKER)\n ? PullPolicy.ALWAYS\n : PullPolicy.IF_NOT_PRESENT;\n}\n"],"names":[],"mappings":";;AAeO,MAAM,UAAA,GAAa;AAAA,EACxB,cAAA,EAAgB,cAAA;AAAA,EAChB,MAAA,EAAQ;AACV;
|
|
1
|
+
{"version":3,"file":"types.cjs.js","sources":["../src/types.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 const PullPolicy = {\n IF_NOT_PRESENT: 'IfNotPresent',\n ALWAYS: 'Always',\n} as const;\n\n// PullPolicy is intentionally both a const (runtime values) and a type\n// (string union) so callers can use `PullPolicy.ALWAYS` and `PullPolicy` as\n// a type. The no-redeclare rule doesn't recognise this enum-like pattern.\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport type PullPolicy = (typeof PullPolicy)[keyof typeof PullPolicy];\n\n/**\n * External schema — the fields a user may declare in `dynamic-plugins.yaml`.\n * Keep this in sync with RHDH documentation.\n */\nexport type PluginSpec = {\n package: string;\n /**\n * Recommended: Use `enabled` instead.\n * When both `enabled` and `disabled` are present, `enabled` takes precedence.\n */\n disabled?: boolean;\n /**\n * Whether the plugin is active. Preferred over `disabled` (positive logic).\n * When both `enabled` and `disabled` are present, `enabled` takes precedence\n * and a warning is logged.\n */\n enabled?: boolean;\n pullPolicy?: PullPolicy;\n forceDownload?: boolean;\n integrity?: string;\n pluginConfig?: Record<string, unknown>;\n};\n\n/**\n * Internal plugin record. Extends the YAML schema with fields populated at\n * runtime (`version` from the package string, `plugin_hash` for change\n * detection, `last_modified_level` to track include-file precedence).\n *\n * The field name `last_modified_level` matches the Python implementation so\n * the install hashes computed by `plugin-hash.ts` stay byte-compatible\n * across the Python ↔ TS migration. Renaming it would force every existing\n * dynamic-plugins-root to be re-installed on the first TS run.\n */\nexport type Plugin = PluginSpec & {\n version?: string;\n plugin_hash?: string;\n last_modified_level?: number;\n};\n\nexport type PluginMap = Record<string, Plugin>;\n\nexport type DynamicPluginsConfig = {\n includes?: string[];\n plugins?: PluginSpec[];\n};\n\nexport const DOCKER_PROTO = 'docker://';\nexport const OCI_PROTO = 'oci://';\n/**\n * Tag suffix that, by convention, opts an OCI plugin into `pullPolicy: Always`\n * when no explicit policy is set — mirrors the Python script's behaviour and\n * keeps the two implementations swappable. Always parsed in combination with\n * the `!plugin-path` separator so a plugin tagged `:latest` (no plugin path)\n * does not accidentally trigger.\n */\nexport const LATEST_TAG_MARKER = ':latest!';\nexport const RHDH_REGISTRY = 'registry.access.redhat.com/rhdh/';\nexport const RHDH_FALLBACK = 'quay.io/rhdh/';\nexport const CONFIG_HASH_FILE = 'dynamic-plugin-config.hash';\nexport const IMAGE_HASH_FILE = 'dynamic-plugin-image.hash';\nexport const DPDY_FILENAME = 'dynamic-plugins.default.yaml';\nexport const LOCK_FILENAME = 'install-dynamic-plugins.lock';\nexport const GLOBAL_CONFIG_FILENAME = 'app-config.dynamic-plugins.yaml';\n\nconst DEFAULT_MAX_ENTRY_SIZE = 40_000_000;\n\n/**\n * Parse the MAX_ENTRY_SIZE env var, falling back to the default when unset,\n * non-numeric, or < 1. Exported for unit tests — the `MAX_ENTRY_SIZE` constant\n * below is the module-level value used by the extractors.\n */\nexport function parseMaxEntrySize(\n raw: string | undefined = process.env.MAX_ENTRY_SIZE,\n): number {\n if (!raw) return DEFAULT_MAX_ENTRY_SIZE;\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : DEFAULT_MAX_ENTRY_SIZE;\n}\n\nexport const MAX_ENTRY_SIZE = parseMaxEntrySize();\nexport const RECOGNIZED_ALGORITHMS = ['sha512', 'sha384', 'sha256'] as const;\nexport type Algorithm = (typeof RECOGNIZED_ALGORITHMS)[number];\n\n/**\n * Resolve the effective `pullPolicy` for an OCI plugin: an explicit policy\n * wins, otherwise the convention is `Always` for `:latest!` images and\n * `IfNotPresent` for everything else. Shared by the install pipeline and the\n * \"definitely no-op\" pre-pass so the `:latest!` semantics live in one place.\n */\nexport function effectivePullPolicy(plugin: {\n pullPolicy?: PullPolicy;\n package: string;\n}): PullPolicy {\n if (plugin.pullPolicy) return plugin.pullPolicy;\n return plugin.package.includes(LATEST_TAG_MARKER)\n ? PullPolicy.ALWAYS\n : PullPolicy.IF_NOT_PRESENT;\n}\n\n/**\n * Resolve the effective disabled state from the `enabled` and `disabled`\n * fields on a plugin spec. Precedence rules (per RHIDP-11983):\n *\n * 1. When only `enabled` is set → `disabled = !enabled`.\n * 2. When only `disabled` is set → use it directly (backward compat).\n * 3. When both are set → `enabled` wins and a warning is emitted\n * via the optional `warn` callback.\n * 4. When neither is set → default to `false` (not disabled).\n *\n * Non-boolean values (e.g. the quoted string `enabled: 'false'` or\n * `enabled: null`) are treated as unset and a warning is emitted,\n * preventing JS truthiness from silently flipping activation state.\n *\n * The `warn` callback receives the warning message string. Pass `log` or\n * leave it out for silent resolution (unit tests, hashing).\n */\nexport function isPluginDisabled(\n plugin: { package: string; disabled?: boolean; enabled?: boolean },\n warn?: (msg: string) => void,\n): boolean {\n const hasEnabled = typeof plugin.enabled === 'boolean';\n const hasDisabled = typeof plugin.disabled === 'boolean';\n\n if (plugin.enabled !== undefined && !hasEnabled) {\n warn?.(\n `WARNING: Plugin ${plugin.package} has non-boolean 'enabled: ${String(plugin.enabled)}'. ` +\n `Expected true or false; ignoring the field.`,\n );\n }\n if (plugin.disabled !== undefined && !hasDisabled) {\n warn?.(\n `WARNING: Plugin ${plugin.package} has non-boolean 'disabled: ${String(plugin.disabled)}'. ` +\n `Expected true or false; ignoring the field.`,\n );\n }\n\n if (hasEnabled && hasDisabled) {\n warn?.(\n `WARNING: Plugin ${plugin.package} specifies both 'enabled' and 'disabled'. ` +\n `The 'enabled' field takes precedence; please use only 'enabled'.`,\n );\n return !plugin.enabled;\n }\n if (hasEnabled) return !plugin.enabled;\n if (hasDisabled) return plugin.disabled === true;\n return false;\n}\n"],"names":[],"mappings":";;AAeO,MAAM,UAAA,GAAa;AAAA,EACxB,cAAA,EAAgB,cAAA;AAAA,EAChB,MAAA,EAAQ;AACV;AAsDO,MAAM,YAAA,GAAe;AACrB,MAAM,SAAA,GAAY;AAQlB,MAAM,iBAAA,GAAoB;AAC1B,MAAM,aAAA,GAAgB;AACtB,MAAM,aAAA,GAAgB;AACtB,MAAM,gBAAA,GAAmB;AACzB,MAAM,eAAA,GAAkB;AACxB,MAAM,aAAA,GAAgB;AACtB,MAAM,aAAA,GAAgB;AACtB,MAAM,sBAAA,GAAyB;AAEtC,MAAM,sBAAA,GAAyB,GAAA;AAOxB,SAAS,iBAAA,CACd,GAAA,GAA0B,OAAA,CAAQ,GAAA,CAAI,cAAA,EAC9B;AACR,EAAA,IAAI,CAAC,KAAK,OAAO,sBAAA;AACjB,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AACjC,EAAA,OAAO,OAAO,QAAA,CAAS,CAAC,CAAA,IAAK,CAAA,IAAK,IAAI,CAAA,GAAI,sBAAA;AAC5C;AAEO,MAAM,iBAAiB,iBAAA;AACvB,MAAM,qBAAA,GAAwB,CAAC,QAAA,EAAU,QAAA,EAAU,QAAQ;AAS3D,SAAS,oBAAoB,MAAA,EAGrB;AACb,EAAA,IAAI,MAAA,CAAO,UAAA,EAAY,OAAO,MAAA,CAAO,UAAA;AACrC,EAAA,OAAO,OAAO,OAAA,CAAQ,QAAA,CAAS,iBAAiB,CAAA,GAC5C,UAAA,CAAW,SACX,UAAA,CAAW,cAAA;AACjB;AAmBO,SAAS,gBAAA,CACd,QACA,IAAA,EACS;AACT,EAAA,MAAM,UAAA,GAAa,OAAO,MAAA,CAAO,OAAA,KAAY,SAAA;AAC7C,EAAA,MAAM,WAAA,GAAc,OAAO,MAAA,CAAO,QAAA,KAAa,SAAA;AAE/C,EAAA,IAAI,MAAA,CAAO,OAAA,KAAY,MAAA,IAAa,CAAC,UAAA,EAAY;AAC/C,IAAA,IAAA;AAAA,MACE,mBAAmB,MAAA,CAAO,OAAO,8BAA8B,MAAA,CAAO,MAAA,CAAO,OAAO,CAAC,CAAA,8CAAA;AAAA,KAEvF;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,KAAa,MAAA,IAAa,CAAC,WAAA,EAAa;AACjD,IAAA,IAAA;AAAA,MACE,mBAAmB,MAAA,CAAO,OAAO,+BAA+B,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAC,CAAA,8CAAA;AAAA,KAEzF;AAAA,EACF;AAEA,EAAA,IAAI,cAAc,WAAA,EAAa;AAC7B,IAAA,IAAA;AAAA,MACE,CAAA,gBAAA,EAAmB,OAAO,OAAO,CAAA,0GAAA;AAAA,KAEnC;AACA,IAAA,OAAO,CAAC,MAAA,CAAO,OAAA;AAAA,EACjB;AACA,EAAA,IAAI,UAAA,EAAY,OAAO,CAAC,MAAA,CAAO,OAAA;AAC/B,EAAA,IAAI,WAAA,EAAa,OAAO,MAAA,CAAO,QAAA,KAAa,IAAA;AAC5C,EAAA,OAAO,KAAA;AACT;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/cli-module-install-dynamic-plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Backstage CLI module that installs RHDH dynamic plugins from a dynamic-plugins.yaml config (OCI, NPM, local).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"backstage": {
|