@backstage/cli-node
Advanced tools
| /* | ||
| * Copyright 2024 The Backstage Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| const { pathToFileURL } = require('node:url'); | ||
| const { transformSync } = require('@swc/core'); | ||
| const { addHook } = require('pirates'); | ||
| const { Module } = require('node:module'); | ||
| // This hooks into module resolution and overrides imports of packages that | ||
| // exist in the linked workspace to instead be resolved from the linked workspace. | ||
| if (process.env.BACKSTAGE_CLI_LINKED_WORKSPACE) { | ||
| const { join: joinPath } = require('node:path'); | ||
| const { getPackagesSync } = require('@manypkg/get-packages'); | ||
| const { packages: linkedPackages, root: linkedRoot } = getPackagesSync( | ||
| process.env.BACKSTAGE_CLI_LINKED_WORKSPACE, | ||
| ); | ||
| // Matches all packages in the linked workspaces, as well as sub-path exports from them | ||
| const replacementRegex = new RegExp( | ||
| `^(?:${linkedPackages | ||
| .map(pkg => pkg.packageJson.name) | ||
| .join('|')})(?:/.*)?$`, | ||
| ); | ||
| const origLoad = Module._load; | ||
| Module._load = function requireHook(request, parent) { | ||
| if (!replacementRegex.test(request)) { | ||
| return origLoad.call(this, request, parent); | ||
| } | ||
| // The package import that we're overriding will always existing in the root | ||
| // node_modules of the linked workspace, so it's enough to override the | ||
| // parent paths with that single entry | ||
| return origLoad.call(this, request, { | ||
| ...parent, | ||
| paths: [joinPath(linkedRoot.dir, 'node_modules')], | ||
| }); | ||
| }; | ||
| } | ||
| addHook( | ||
| (code, filename) => { | ||
| const transformed = transformSync(code, { | ||
| filename, | ||
| sourceMaps: 'inline', | ||
| module: { | ||
| type: 'commonjs', | ||
| ignoreDynamic: true, | ||
| }, | ||
| jsc: { | ||
| target: 'es2023', | ||
| parser: { | ||
| syntax: 'typescript', | ||
| }, | ||
| }, | ||
| }); | ||
| process.send?.({ type: 'watch', path: filename }); | ||
| return transformed.code; | ||
| }, | ||
| { extensions: ['.ts', '.cts'], ignoreNodeModules: true }, | ||
| ); | ||
| addHook( | ||
| (code, filename) => { | ||
| process.send?.({ type: 'watch', path: filename }); | ||
| return code; | ||
| }, | ||
| { extensions: ['.js', '.cjs'], ignoreNodeModules: true }, | ||
| ); | ||
| // Register module hooks, used by "type": "module" in package.json, .mjs and | ||
| // .mts files, as well as dynamic import(...)s, although dynamic imports will be | ||
| // handled be the CommonJS hooks in this file if what it points to is CommonJS. | ||
| Module.register('./nodeTransformHooks.mjs', pathToFileURL(__filename)); |
| /* | ||
| * Copyright 2024 The Backstage Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| import { dirname, extname, resolve as resolvePath } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { transformFile } from '@swc/core'; | ||
| import { isBuiltin } from 'node:module'; | ||
| import { readFile } from 'node:fs/promises'; | ||
| import { existsSync } from 'node:fs'; | ||
| // @ts-check | ||
| // No explicit file extension, no type in package.json | ||
| const DEFAULT_MODULE_FORMAT = 'commonjs'; | ||
| // Source file extensions to look for when using bundle resolution strategy | ||
| const SRC_EXTS = ['.ts', '.js']; | ||
| const TS_EXTS = ['.ts', '.mts', '.cts']; | ||
| const moduleTypeTable = { | ||
| '.mjs': 'module', | ||
| '.mts': 'module', | ||
| '.cjs': 'commonjs', | ||
| '.cts': 'commonjs', | ||
| '.ts': undefined, | ||
| '.js': undefined, | ||
| }; | ||
| /** @type {import('module').ResolveHook} */ | ||
| export async function resolve(specifier, context, nextResolve) { | ||
| // Built-in modules are handled by the default resolver | ||
| if (isBuiltin(specifier)) { | ||
| return nextResolve(specifier, context); | ||
| } | ||
| const ext = extname(specifier); | ||
| // Unless there's an explicit import attribute, JSON files are loaded with our custom loader that's defined below. | ||
| if (ext === '.json' && !context.importAttributes?.type) { | ||
| const jsonResult = await nextResolve(specifier, context); | ||
| return { | ||
| ...jsonResult, | ||
| format: 'commonjs', | ||
| importAttributes: { type: 'json' }, | ||
| }; | ||
| } | ||
| // Anything else with an explicit extension is handled by the default | ||
| // resolver, except that we help determine the module type where needed. | ||
| if (ext !== '') { | ||
| return withDetectedModuleType(await nextResolve(specifier, context)); | ||
| } | ||
| // Other external modules are handled by the default resolver, but again we | ||
| // help determine the module type where needed. | ||
| if (!specifier.startsWith('.')) { | ||
| return withDetectedModuleType(await nextResolve(specifier, context)); | ||
| } | ||
| // The rest of this function handles the case of resolving imports that do not | ||
| // specify any extension and might point to a directory with an `index.*` | ||
| // file. We resolve those using the same logic as most JS bundlers would, with | ||
| // the addition of checking if there's an explicit module format listed in the | ||
| // closest `package.json` file. | ||
| // | ||
| // We use a bundle resolution strategy in order to keep code consistent across | ||
| // Backstage codebases that contains code both for Web and Node.js, and to | ||
| // support packages with common code that can be used in both environments. | ||
| try { | ||
| // This is expected to throw, but in the event that this module specifier is | ||
| // supported we prefer to use the default resolver. | ||
| return await nextResolve(specifier, context); | ||
| } catch (error) { | ||
| if (error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { | ||
| const spec = `${specifier}${specifier.endsWith('/') ? '' : '/'}index`; | ||
| const resolved = await resolveWithoutExt(spec, context, nextResolve); | ||
| if (resolved) { | ||
| return withDetectedModuleType(resolved); | ||
| } | ||
| } else if (error.code === 'ERR_MODULE_NOT_FOUND') { | ||
| const resolved = await resolveWithoutExt(specifier, context, nextResolve); | ||
| if (resolved) { | ||
| return withDetectedModuleType(resolved); | ||
| } | ||
| } | ||
| // Unexpected error or no resolution found | ||
| throw error; | ||
| } | ||
| } | ||
| /** | ||
| * Populates the `format` field in the resolved object based on the closest `package.json` file. | ||
| * | ||
| * @param {import('module').ResolveFnOutput} resolved | ||
| * @returns {Promise<import('module').ResolveFnOutput>} | ||
| */ | ||
| async function withDetectedModuleType(resolved) { | ||
| // Already has an explicit format | ||
| if (resolved.format) { | ||
| return resolved; | ||
| } | ||
| // Happens in Node.js v22 when there's a package.json without an explicit "type" field. Use the default. | ||
| if (resolved.format === null) { | ||
| return { ...resolved, format: DEFAULT_MODULE_FORMAT }; | ||
| } | ||
| const ext = extname(resolved.url); | ||
| const explicitFormat = moduleTypeTable[ext]; | ||
| if (explicitFormat) { | ||
| return { | ||
| ...resolved, | ||
| format: explicitFormat, | ||
| }; | ||
| } | ||
| // Under normal circumstances .js files should reliably have a format and so | ||
| // we should only reach this point for .ts files. However, if additional | ||
| // custom loaders are being used the format may not be detected for .js files | ||
| // either. As such we don't restrict the file format at this point. | ||
| // TODO(Rugvip): Does this need caching? kept it simple for now but worth exploring | ||
| const packageJsonPath = await findPackageJSON(fileURLToPath(resolved.url)); | ||
| if (!packageJsonPath) { | ||
| return resolved; | ||
| } | ||
| const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); | ||
| return { | ||
| ...resolved, | ||
| format: packageJson.type ?? DEFAULT_MODULE_FORMAT, | ||
| }; | ||
| } | ||
| /** | ||
| * Find the closest package.json file from the given path. | ||
| * | ||
| * TODO(Rugvip): This can be replaced with the Node.js built-in with the same name once it is stable. | ||
| * @param {string} startPath | ||
| * @returns {Promise<string | undefined>} | ||
| */ | ||
| async function findPackageJSON(startPath) { | ||
| let path = startPath; | ||
| // Some confidence check to avoid infinite loop | ||
| for (let i = 0; i < 1000; i++) { | ||
| const packagePath = resolvePath(path, 'package.json'); | ||
| if (existsSync(packagePath)) { | ||
| return packagePath; | ||
| } | ||
| const newPath = dirname(path); | ||
| if (newPath === path) { | ||
| return undefined; | ||
| } | ||
| path = newPath; | ||
| } | ||
| throw new Error( | ||
| `Iteration limit reached when searching for package.json at ${startPath}`, | ||
| ); | ||
| } | ||
| /** @type {import('module').ResolveHook} */ | ||
| async function resolveWithoutExt(specifier, context, nextResolve) { | ||
| for (const tryExt of SRC_EXTS) { | ||
| try { | ||
| const resolved = await nextResolve(specifier + tryExt, { | ||
| ...context, | ||
| format: 'commonjs', | ||
| }); | ||
| return { | ||
| ...resolved, | ||
| format: moduleTypeTable[tryExt] ?? resolved.format, | ||
| }; | ||
| } catch { | ||
| /* ignore */ | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
| /** @type {import('module').LoadHook} */ | ||
| export async function load(url, context, nextLoad) { | ||
| // Non-file URLs are handled by the default loader | ||
| if (!url.startsWith('file://')) { | ||
| return nextLoad(url, context); | ||
| } | ||
| // JSON files loaded as CommonJS are handled by this custom loader, because | ||
| // the default one doesn't work. For JSON loading to work we'd need the | ||
| // synchronous hooks that aren't supported yet, or avoid using the CommonJS | ||
| // compatibility. | ||
| if ( | ||
| context.format === 'commonjs' && | ||
| context.importAttributes?.type === 'json' | ||
| ) { | ||
| try { | ||
| // TODO(Rugvip): Make sure this is valid JSON | ||
| const content = await readFile(fileURLToPath(url), 'utf8'); | ||
| return { | ||
| source: `module.exports = (${content})`, | ||
| format: 'commonjs', | ||
| shortCircuit: true, | ||
| }; | ||
| } catch { | ||
| // Let the default loader generate the error | ||
| return nextLoad(url, context); | ||
| } | ||
| } | ||
| const ext = extname(url); | ||
| // Non-TS files are handled by the default loader | ||
| if (!TS_EXTS.includes(ext)) { | ||
| return nextLoad(url, context); | ||
| } | ||
| const format = context.format ?? DEFAULT_MODULE_FORMAT; | ||
| // We have two choices at this point, we can either transform CommonJS files | ||
| // and return the transformed source code, or let the default loader handle | ||
| // them. If we transform them ourselves we will enter CommonJS compatibility | ||
| // mode in the new module system in Node.js, this effectively means all | ||
| // CommonJS loaded via `require` calls from this point will all be treated as | ||
| // if it was loaded via `import` calls from modules. | ||
| // | ||
| // The CommonJS compatibility layer will try to identify named exports and | ||
| // make them available directly, which is convenient as it avoids things like | ||
| // `import(...).then(m => m.default.foo)`, allowing you to instead write | ||
| // `import(...).then(m => m.foo)`. The compatibility layer doesn't always work | ||
| // all that well though, and can lead to module loading issues in many cases, | ||
| // especially for older code. | ||
| // This `if` block opts-out of using CommonJS compatibility mode by default, | ||
| // and instead leaves it to our existing loader to transform CommonJS. We do | ||
| // however use compatibility mode for the more explicit .cts file extension, | ||
| // allows for a way to opt-in to the new behavior. | ||
| // | ||
| // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead | ||
| if (format === 'commonjs' && ext !== '.cts') { | ||
| return nextLoad(url, { ...context, format }); | ||
| } | ||
| // If the Node.js version we're running supports TypeScript, i.e. type | ||
| // stripping, we hand over to the default loader. This is done for all cases | ||
| // except if we're loading a .ts file that's been resolved to CommonJS format. | ||
| // This is because these files aren't actually CommonJS in the Backstage build | ||
| // system, and need to be transformed to CommonJS. | ||
| if ( | ||
| format === 'module-typescript' || | ||
| (format === 'module-commonjs' && ext !== '.ts') | ||
| ) { | ||
| return nextLoad(url, { ...context, format }); | ||
| } | ||
| const transformed = await transformFile(fileURLToPath(url), { | ||
| sourceMaps: 'inline', | ||
| module: { | ||
| type: format === 'module' ? 'es6' : 'commonjs', | ||
| ignoreDynamic: true, | ||
| // This helps the Node.js CommonJS compat layer identify named exports. | ||
| exportInteropAnnotation: true, | ||
| }, | ||
| jsc: { | ||
| target: 'es2023', | ||
| parser: { | ||
| syntax: 'typescript', | ||
| }, | ||
| }, | ||
| }); | ||
| return { | ||
| ...context, | ||
| shortCircuit: true, | ||
| source: transformed.code, | ||
| format, | ||
| responseURL: url, | ||
| }; | ||
| } |
| 'use strict'; | ||
| function getAuthInstanceService(instanceName) { | ||
| return `backstage-cli:auth-instance:${instanceName}`; | ||
| } | ||
| exports.getAuthInstanceService = getAuthInstanceService; | ||
| //# sourceMappingURL=authIdentifiers.cjs.js.map |
| {"version":3,"file":"authIdentifiers.cjs.js","sources":["../../src/auth/authIdentifiers.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** @internal */\nexport function getAuthInstanceService(instanceName: string): string {\n return `backstage-cli:auth-instance:${instanceName}`;\n}\n"],"names":[],"mappings":";;AAiBO,SAAS,uBAAuB,YAAA,EAA8B;AACnE,EAAA,OAAO,+BAA+B,YAAY,CAAA,CAAA;AACpD;;;;"} |
| 'use strict'; | ||
| var storage = require('./storage.cjs.js'); | ||
| var secretStore = require('./secretStore.cjs.js'); | ||
| var authIdentifiers = require('./authIdentifiers.cjs.js'); | ||
| var httpJson = require('./httpJson.cjs.js'); | ||
| var v3 = require('zod/v3'); | ||
| const TokenResponseSchema = v3.z.object({ | ||
| access_token: v3.z.string().min(1), | ||
| token_type: v3.z.string().min(1), | ||
| expires_in: v3.z.number().positive().finite(), | ||
| refresh_token: v3.z.string().min(1).optional() | ||
| }); | ||
| class CliAuth { | ||
| #secretStore; | ||
| #instance; | ||
| /** | ||
| * Resolve the current auth instance and return a ready-to-use | ||
| * {@link CliAuth} object. Throws when no instance can be found. | ||
| */ | ||
| static async create(options) { | ||
| const instance = await storage.getSelectedInstance(options?.instanceName); | ||
| const secretStore$1 = await secretStore.getSecretStore(); | ||
| return new CliAuth(instance, secretStore$1); | ||
| } | ||
| constructor(instance, secretStore) { | ||
| this.#instance = instance; | ||
| this.#secretStore = secretStore; | ||
| } | ||
| /** Returns the name of the resolved auth instance. */ | ||
| getInstanceName() { | ||
| return this.#instance.name; | ||
| } | ||
| /** Returns the base URL of the resolved auth instance. */ | ||
| getBaseUrl() { | ||
| return this.#instance.baseUrl; | ||
| } | ||
| /** | ||
| * Returns a valid access token, refreshing it first if the current | ||
| * token is expired or about to expire. | ||
| */ | ||
| async getAccessToken() { | ||
| if (storage.accessTokenNeedsRefresh(this.#instance)) { | ||
| await this.#refreshAccessToken(); | ||
| } | ||
| const service = authIdentifiers.getAuthInstanceService(this.#instance.name); | ||
| const token = await this.#secretStore.get(service, "accessToken"); | ||
| if (!token) { | ||
| throw new Error( | ||
| 'No access token found. Run "auth login" to authenticate.' | ||
| ); | ||
| } | ||
| return token; | ||
| } | ||
| /** | ||
| * Reads a per-instance metadata value previously stored by the | ||
| * auth module (e.g. `pluginSources`). | ||
| */ | ||
| async getMetadata(key) { | ||
| return storage.getInstanceMetadata(this.#instance.name, key); | ||
| } | ||
| /** | ||
| * Writes a per-instance metadata value to the on-disk instance store. | ||
| */ | ||
| async setMetadata(key, value) { | ||
| return storage.updateInstanceMetadata(this.#instance.name, key, value); | ||
| } | ||
| async #refreshAccessToken() { | ||
| const service = authIdentifiers.getAuthInstanceService(this.#instance.name); | ||
| const refreshToken = await this.#secretStore.get(service, "refreshToken") ?? ""; | ||
| if (!refreshToken) { | ||
| throw new Error( | ||
| "Access token is expired and no refresh token is available" | ||
| ); | ||
| } | ||
| const response = await httpJson.httpJson( | ||
| `${this.#instance.baseUrl}/api/auth/v1/token`, | ||
| { | ||
| method: "POST", | ||
| body: { | ||
| grant_type: "refresh_token", | ||
| refresh_token: refreshToken | ||
| }, | ||
| signal: AbortSignal.timeout(3e4) | ||
| } | ||
| ); | ||
| const parsed = TokenResponseSchema.safeParse(response); | ||
| if (!parsed.success) { | ||
| throw new Error(`Invalid token response: ${parsed.error.message}`); | ||
| } | ||
| const token = parsed.data; | ||
| await this.#secretStore.set(service, "accessToken", token.access_token); | ||
| if (token.refresh_token) { | ||
| await this.#secretStore.set(service, "refreshToken", token.refresh_token); | ||
| } | ||
| const issuedAt = Date.now(); | ||
| const accessTokenExpiresAt = Date.now() + token.expires_in * 1e3; | ||
| this.#instance = { ...this.#instance, issuedAt, accessTokenExpiresAt }; | ||
| await storage.updateInstance(this.#instance.name, { | ||
| issuedAt, | ||
| accessTokenExpiresAt | ||
| }); | ||
| } | ||
| } | ||
| exports.CliAuth = CliAuth; | ||
| //# sourceMappingURL=CliAuth.cjs.js.map |
| {"version":3,"file":"CliAuth.cjs.js","sources":["../../src/auth/CliAuth.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type StoredInstance,\n getSelectedInstance,\n getInstanceMetadata,\n updateInstanceMetadata,\n updateInstance,\n accessTokenNeedsRefresh,\n} from './storage';\nimport { getSecretStore, type SecretStore } from './secretStore';\nimport { getAuthInstanceService } from './authIdentifiers';\nimport { httpJson } from './httpJson';\nimport { z } from 'zod/v3';\n\nconst TokenResponseSchema = z.object({\n access_token: z.string().min(1),\n token_type: z.string().min(1),\n expires_in: z.number().positive().finite(),\n refresh_token: z.string().min(1).optional(),\n});\n\n/**\n * Options for creating a {@link CliAuth} instance.\n *\n * @public\n */\nexport interface CliAuthCreateOptions {\n /**\n * An explicit instance name to resolve. When omitted the currently\n * selected instance is used.\n */\n instanceName?: string;\n}\n\n/**\n * Manages authentication state for Backstage CLI commands.\n *\n * Reads the currently selected (or explicitly named) auth instance from\n * the on-disk instance store, transparently refreshes expired access\n * tokens, and exposes helpers that other CLI modules need to talk to a\n * Backstage backend.\n *\n * @public\n */\nexport class CliAuth {\n readonly #secretStore: SecretStore;\n #instance: StoredInstance;\n\n /**\n * Resolve the current auth instance and return a ready-to-use\n * {@link CliAuth} object. Throws when no instance can be found.\n */\n static async create(options?: CliAuthCreateOptions): Promise<CliAuth> {\n const instance = await getSelectedInstance(options?.instanceName);\n const secretStore = await getSecretStore();\n return new CliAuth(instance, secretStore);\n }\n\n private constructor(instance: StoredInstance, secretStore: SecretStore) {\n this.#instance = instance;\n this.#secretStore = secretStore;\n }\n\n /** Returns the name of the resolved auth instance. */\n getInstanceName(): string {\n return this.#instance.name;\n }\n\n /** Returns the base URL of the resolved auth instance. */\n getBaseUrl(): string {\n return this.#instance.baseUrl;\n }\n\n /**\n * Returns a valid access token, refreshing it first if the current\n * token is expired or about to expire.\n */\n async getAccessToken(): Promise<string> {\n if (accessTokenNeedsRefresh(this.#instance)) {\n await this.#refreshAccessToken();\n }\n\n const service = getAuthInstanceService(this.#instance.name);\n const token = await this.#secretStore.get(service, 'accessToken');\n if (!token) {\n throw new Error(\n 'No access token found. Run \"auth login\" to authenticate.',\n );\n }\n return token;\n }\n\n /**\n * Reads a per-instance metadata value previously stored by the\n * auth module (e.g. `pluginSources`).\n */\n async getMetadata(key: string): Promise<unknown> {\n return getInstanceMetadata(this.#instance.name, key);\n }\n\n /**\n * Writes a per-instance metadata value to the on-disk instance store.\n */\n async setMetadata(key: string, value: unknown): Promise<void> {\n return updateInstanceMetadata(this.#instance.name, key, value);\n }\n\n async #refreshAccessToken(): Promise<void> {\n const service = getAuthInstanceService(this.#instance.name);\n const refreshToken =\n (await this.#secretStore.get(service, 'refreshToken')) ?? '';\n if (!refreshToken) {\n throw new Error(\n 'Access token is expired and no refresh token is available',\n );\n }\n\n const response = await httpJson<unknown>(\n `${this.#instance.baseUrl}/api/auth/v1/token`,\n {\n method: 'POST',\n body: {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n },\n signal: AbortSignal.timeout(30_000),\n },\n );\n\n const parsed = TokenResponseSchema.safeParse(response);\n if (!parsed.success) {\n throw new Error(`Invalid token response: ${parsed.error.message}`);\n }\n const token = parsed.data;\n\n await this.#secretStore.set(service, 'accessToken', token.access_token);\n if (token.refresh_token) {\n await this.#secretStore.set(service, 'refreshToken', token.refresh_token);\n }\n const issuedAt = Date.now();\n const accessTokenExpiresAt = Date.now() + token.expires_in * 1000;\n this.#instance = { ...this.#instance, issuedAt, accessTokenExpiresAt };\n await updateInstance(this.#instance.name, {\n issuedAt,\n accessTokenExpiresAt,\n });\n }\n}\n"],"names":["z","getSelectedInstance","secretStore","getSecretStore","accessTokenNeedsRefresh","getAuthInstanceService","getInstanceMetadata","updateInstanceMetadata","httpJson","updateInstance"],"mappings":"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"} |
| 'use strict'; | ||
| var errors = require('@backstage/errors'); | ||
| async function httpJson(url, init) { | ||
| const res = await fetch(url, { | ||
| ...init, | ||
| body: init?.body ? JSON.stringify(init.body) : void 0, | ||
| headers: { | ||
| ...init?.body ? { "Content-Type": "application/json" } : {}, | ||
| ...init?.headers | ||
| } | ||
| }); | ||
| if (!res.ok) { | ||
| throw await errors.ResponseError.fromResponse(res); | ||
| } | ||
| return await res.json(); | ||
| } | ||
| exports.httpJson = httpJson; | ||
| //# sourceMappingURL=httpJson.cjs.js.map |
| {"version":3,"file":"httpJson.cjs.js","sources":["../../src/auth/httpJson.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\n\n/** @internal */\nexport type HttpInit = {\n headers?: Record<string, string>;\n method?: string;\n body?: any;\n signal?: AbortSignal;\n};\n\n/** @internal */\nexport async function httpJson<T>(url: string, init?: HttpInit): Promise<T> {\n const res = await fetch(url, {\n ...init,\n body: init?.body ? JSON.stringify(init.body) : undefined,\n headers: {\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n ...init?.headers,\n },\n });\n if (!res.ok) {\n throw await ResponseError.fromResponse(res);\n }\n return (await res.json()) as T;\n}\n"],"names":["ResponseError"],"mappings":";;;;AA2BA,eAAsB,QAAA,CAAY,KAAa,IAAA,EAA6B;AAC1E,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAC3B,GAAG,IAAA;AAAA,IACH,MAAM,IAAA,EAAM,IAAA,GAAO,KAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA;AAAA,IAC/C,OAAA,EAAS;AAAA,MACP,GAAI,IAAA,EAAM,IAAA,GAAO,EAAE,cAAA,EAAgB,kBAAA,KAAuB,EAAC;AAAA,MAC3D,GAAG,IAAA,EAAM;AAAA;AACX,GACD,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,MAAMA,oBAAA,CAAc,YAAA,CAAa,GAAG,CAAA;AAAA,EAC5C;AACA,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;;;;"} |
| 'use strict'; | ||
| var node_fs = require('node:fs'); | ||
| var os = require('node:os'); | ||
| var path = require('node:path'); | ||
| function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } | ||
| var os__default = /*#__PURE__*/_interopDefaultCompat(os); | ||
| var path__default = /*#__PURE__*/_interopDefaultCompat(path); | ||
| async function loadKeytar() { | ||
| try { | ||
| const keytar = require("keytar"); | ||
| if (keytar && typeof keytar.getPassword === "function") { | ||
| return keytar; | ||
| } | ||
| } catch { | ||
| } | ||
| return void 0; | ||
| } | ||
| class KeytarSecretStore { | ||
| keytar; | ||
| constructor(keytar) { | ||
| this.keytar = keytar; | ||
| } | ||
| async get(service, account) { | ||
| const result = await this.keytar.getPassword(service, account); | ||
| return result ?? void 0; | ||
| } | ||
| async set(service, account, secret) { | ||
| await this.keytar.setPassword(service, account, secret); | ||
| } | ||
| async delete(service, account) { | ||
| await this.keytar.deletePassword(service, account); | ||
| } | ||
| } | ||
| async function pathExists(p) { | ||
| try { | ||
| await node_fs.promises.stat(p); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| class FileSecretStore { | ||
| baseDir; | ||
| constructor() { | ||
| const root = process.env.XDG_DATA_HOME || (process.platform === "win32" ? process.env.APPDATA || path__default.default.join(os__default.default.homedir(), "AppData", "Roaming") : path__default.default.join(os__default.default.homedir(), ".local", "share")); | ||
| this.baseDir = path__default.default.join(root, "backstage-cli", "auth-secrets"); | ||
| } | ||
| filePath(service, account) { | ||
| return path__default.default.join( | ||
| this.baseDir, | ||
| encodeURIComponent(service), | ||
| `${encodeURIComponent(account)}.secret` | ||
| ); | ||
| } | ||
| async get(service, account) { | ||
| const file = this.filePath(service, account); | ||
| if (!await pathExists(file)) { | ||
| return void 0; | ||
| } | ||
| return await node_fs.promises.readFile(file, "utf8"); | ||
| } | ||
| async set(service, account, secret) { | ||
| const file = this.filePath(service, account); | ||
| await node_fs.promises.mkdir(path__default.default.dirname(file), { recursive: true }); | ||
| await node_fs.promises.writeFile(file, secret, { encoding: "utf8", mode: 384 }); | ||
| } | ||
| async delete(service, account) { | ||
| const file = this.filePath(service, account); | ||
| try { | ||
| await node_fs.promises.unlink(file); | ||
| } catch (err) { | ||
| if (err.code !== "ENOENT") { | ||
| throw err; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| let singleton; | ||
| async function getSecretStore() { | ||
| if (!singleton) { | ||
| const keytar = await loadKeytar(); | ||
| if (keytar) { | ||
| singleton = new KeytarSecretStore(keytar); | ||
| } else { | ||
| singleton = new FileSecretStore(); | ||
| } | ||
| } | ||
| return singleton; | ||
| } | ||
| exports.getSecretStore = getSecretStore; | ||
| //# sourceMappingURL=secretStore.cjs.js.map |
| {"version":3,"file":"secretStore.cjs.js","sources":["../../src/auth/secretStore.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** @internal */\nexport type SecretStore = {\n get(service: string, account: string): Promise<string | undefined>;\n set(service: string, account: string, secret: string): Promise<void>;\n delete(service: string, account: string): Promise<void>;\n};\n\nasync function loadKeytar(): Promise<typeof import('keytar') | undefined> {\n try {\n // eslint-disable-next-line import/no-extraneous-dependencies, @backstage/no-undeclared-imports\n const keytar = require('keytar') as typeof import('keytar');\n if (keytar && typeof keytar.getPassword === 'function') {\n return keytar;\n }\n } catch {\n // keytar not available\n }\n return undefined;\n}\n\nclass KeytarSecretStore implements SecretStore {\n private readonly keytar: typeof import('keytar');\n constructor(keytar: typeof import('keytar')) {\n this.keytar = keytar;\n }\n async get(service: string, account: string): Promise<string | undefined> {\n const result = await this.keytar.getPassword(service, account);\n return result ?? undefined;\n }\n async set(service: string, account: string, secret: string): Promise<void> {\n await this.keytar.setPassword(service, account, secret);\n }\n async delete(service: string, account: string): Promise<void> {\n await this.keytar.deletePassword(service, account);\n }\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nclass FileSecretStore implements SecretStore {\n private readonly baseDir: string;\n constructor() {\n const root =\n process.env.XDG_DATA_HOME ||\n (process.platform === 'win32'\n ? process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')\n : path.join(os.homedir(), '.local', 'share'));\n this.baseDir = path.join(root, 'backstage-cli', 'auth-secrets');\n }\n private filePath(service: string, account: string): string {\n return path.join(\n this.baseDir,\n encodeURIComponent(service),\n `${encodeURIComponent(account)}.secret`,\n );\n }\n async get(service: string, account: string): Promise<string | undefined> {\n const file = this.filePath(service, account);\n if (!(await pathExists(file))) {\n return undefined;\n }\n return await fs.readFile(file, 'utf8');\n }\n async set(service: string, account: string, secret: string): Promise<void> {\n const file = this.filePath(service, account);\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, secret, { encoding: 'utf8', mode: 0o600 });\n }\n async delete(service: string, account: string): Promise<void> {\n const file = this.filePath(service, account);\n try {\n await fs.unlink(file);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err;\n }\n }\n }\n}\n\nlet singleton: SecretStore | undefined;\n\n/** @internal */\nexport async function getSecretStore(): Promise<SecretStore> {\n if (!singleton) {\n const keytar = await loadKeytar();\n if (keytar) {\n singleton = new KeytarSecretStore(keytar);\n } else {\n singleton = new FileSecretStore();\n }\n }\n return singleton;\n}\n\n/**\n * Reset the singleton instance (for testing purposes only)\n * @internal\n */\nexport function resetSecretStore(): void {\n singleton = undefined;\n}\n"],"names":["fs","path","os"],"mappings":"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"} |
| 'use strict'; | ||
| var errors = require('@backstage/errors'); | ||
| var node_fs = require('node:fs'); | ||
| var os = require('node:os'); | ||
| var path = require('node:path'); | ||
| var lockfile = require('proper-lockfile'); | ||
| var YAML = require('yaml'); | ||
| var v3 = require('zod/v3'); | ||
| function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } | ||
| var os__default = /*#__PURE__*/_interopDefaultCompat(os); | ||
| var path__default = /*#__PURE__*/_interopDefaultCompat(path); | ||
| var lockfile__default = /*#__PURE__*/_interopDefaultCompat(lockfile); | ||
| var YAML__default = /*#__PURE__*/_interopDefaultCompat(YAML); | ||
| const METADATA_FILE = "auth-instances.yaml"; | ||
| const INSTANCE_NAME_PATTERN = /^[a-zA-Z0-9._:@-]+$/; | ||
| const storedInstanceSchema = v3.z.object({ | ||
| name: v3.z.string().min(1).regex(INSTANCE_NAME_PATTERN, "Instance name contains invalid characters"), | ||
| baseUrl: v3.z.string().url(), | ||
| clientId: v3.z.string().min(1), | ||
| issuedAt: v3.z.number().int().nonnegative(), | ||
| accessTokenExpiresAt: v3.z.number().int().nonnegative(), | ||
| selected: v3.z.boolean().optional(), | ||
| metadata: v3.z.record(v3.z.string(), v3.z.unknown()).optional() | ||
| }); | ||
| const authYamlSchema = v3.z.object({ | ||
| instances: v3.z.array(storedInstanceSchema).default([]) | ||
| }); | ||
| async function pathExists(p) { | ||
| try { | ||
| await node_fs.promises.stat(p); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function getMetadataFilePath() { | ||
| const root = process.env.XDG_CONFIG_HOME || (process.platform === "win32" ? process.env.APPDATA || path__default.default.join(os__default.default.homedir(), "AppData", "Roaming") : path__default.default.join(os__default.default.homedir(), ".config")); | ||
| return path__default.default.join(root, "backstage-cli", METADATA_FILE); | ||
| } | ||
| async function readAll() { | ||
| const file = getMetadataFilePath(); | ||
| if (!await pathExists(file)) { | ||
| return { instances: [] }; | ||
| } | ||
| const text = await node_fs.promises.readFile(file, "utf8"); | ||
| if (!text.trim()) { | ||
| return { instances: [] }; | ||
| } | ||
| try { | ||
| const doc = YAML__default.default.parse(text); | ||
| const parsed = authYamlSchema.safeParse(doc); | ||
| if (parsed.success) { | ||
| return parsed.data; | ||
| } | ||
| return { instances: [] }; | ||
| } catch { | ||
| return { instances: [] }; | ||
| } | ||
| } | ||
| async function writeAll(data) { | ||
| const file = getMetadataFilePath(); | ||
| await node_fs.promises.mkdir(path__default.default.dirname(file), { recursive: true }); | ||
| const yaml = YAML__default.default.stringify(authYamlSchema.parse(data), { indentSeq: false }); | ||
| await node_fs.promises.writeFile(file, yaml, { encoding: "utf8", mode: 384 }); | ||
| } | ||
| async function withMetadataLock(fn) { | ||
| const file = getMetadataFilePath(); | ||
| await node_fs.promises.mkdir(path__default.default.dirname(file), { recursive: true }); | ||
| if (!await pathExists(file)) { | ||
| await node_fs.promises.writeFile(file, "", { encoding: "utf8", mode: 384 }); | ||
| } | ||
| const release = await lockfile__default.default.lock(file, { | ||
| retries: { retries: 5, factor: 1.5, minTimeout: 100, maxTimeout: 1e3 } | ||
| }); | ||
| try { | ||
| return await fn(); | ||
| } finally { | ||
| await release(); | ||
| } | ||
| } | ||
| async function getAllInstances() { | ||
| const { instances } = await readAll(); | ||
| if (instances.length === 0) { | ||
| return { instances: [], selected: void 0 }; | ||
| } | ||
| const selected = instances.find((i) => i.selected) ?? instances[0]; | ||
| return { | ||
| instances: instances.map((i) => ({ | ||
| ...i, | ||
| selected: i.name === selected.name | ||
| })), | ||
| selected | ||
| }; | ||
| } | ||
| async function getSelectedInstance(instanceName) { | ||
| if (instanceName) { | ||
| return await getInstanceByName(instanceName); | ||
| } | ||
| const { selected } = await getAllInstances(); | ||
| if (!selected) { | ||
| throw new Error( | ||
| 'No instances found. Run "auth login" to authenticate first.' | ||
| ); | ||
| } | ||
| return selected; | ||
| } | ||
| async function getInstanceByName(name) { | ||
| const { instances } = await readAll(); | ||
| const instance = instances.find((i) => i.name === name); | ||
| if (!instance) { | ||
| throw new errors.NotFoundError(`Instance '${name}' not found`); | ||
| } | ||
| return instance; | ||
| } | ||
| async function getInstanceMetadata(instanceName, key) { | ||
| const instance = await getInstanceByName(instanceName); | ||
| return instance.metadata?.[key]; | ||
| } | ||
| async function updateInstanceMetadata(instanceName, key, value) { | ||
| return withMetadataLock(async () => { | ||
| const data = await readAll(); | ||
| const idx = data.instances.findIndex((i) => i.name === instanceName); | ||
| if (idx === -1) { | ||
| throw new errors.NotFoundError(`Instance '${instanceName}' not found`); | ||
| } | ||
| data.instances[idx] = { | ||
| ...data.instances[idx], | ||
| metadata: { ...data.instances[idx].metadata, [key]: value } | ||
| }; | ||
| await writeAll(data); | ||
| }); | ||
| } | ||
| async function updateInstance(instanceName, updates) { | ||
| return withMetadataLock(async () => { | ||
| const data = await readAll(); | ||
| const idx = data.instances.findIndex((i) => i.name === instanceName); | ||
| if (idx === -1) { | ||
| throw new errors.NotFoundError(`Instance '${instanceName}' not found`); | ||
| } | ||
| data.instances[idx] = { ...data.instances[idx], ...updates }; | ||
| await writeAll(data); | ||
| }); | ||
| } | ||
| function accessTokenNeedsRefresh(instance) { | ||
| return instance.accessTokenExpiresAt <= Date.now() + 2 * 6e4; | ||
| } | ||
| exports.accessTokenNeedsRefresh = accessTokenNeedsRefresh; | ||
| exports.getAllInstances = getAllInstances; | ||
| exports.getInstanceByName = getInstanceByName; | ||
| exports.getInstanceMetadata = getInstanceMetadata; | ||
| exports.getMetadataFilePath = getMetadataFilePath; | ||
| exports.getSelectedInstance = getSelectedInstance; | ||
| exports.readAll = readAll; | ||
| exports.updateInstance = updateInstance; | ||
| exports.updateInstanceMetadata = updateInstanceMetadata; | ||
| //# sourceMappingURL=storage.cjs.js.map |
| {"version":3,"file":"storage.cjs.js","sources":["../../src/auth/storage.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NotFoundError } from '@backstage/errors';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport lockfile from 'proper-lockfile';\nimport YAML from 'yaml';\nimport { z } from 'zod/v3';\n\nconst METADATA_FILE = 'auth-instances.yaml';\n\nconst INSTANCE_NAME_PATTERN = /^[a-zA-Z0-9._:@-]+$/;\n\nconst storedInstanceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(INSTANCE_NAME_PATTERN, 'Instance name contains invalid characters'),\n baseUrl: z.string().url(),\n clientId: z.string().min(1),\n issuedAt: z.number().int().nonnegative(),\n accessTokenExpiresAt: z.number().int().nonnegative(),\n selected: z.boolean().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst authYamlSchema = z.object({\n instances: z.array(storedInstanceSchema).default([]),\n});\n\nexport type StoredInstance = {\n name: string;\n baseUrl: string;\n clientId: string;\n issuedAt: number;\n accessTokenExpiresAt: number;\n selected?: boolean;\n metadata?: Record<string, unknown>;\n};\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/** @internal */\nexport function getMetadataFilePath(): string {\n const root =\n process.env.XDG_CONFIG_HOME ||\n (process.platform === 'win32'\n ? process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')\n : path.join(os.homedir(), '.config'));\n\n return path.join(root, 'backstage-cli', METADATA_FILE);\n}\n\n/** @internal */\nexport async function readAll(): Promise<{ instances: StoredInstance[] }> {\n const file = getMetadataFilePath();\n if (!(await pathExists(file))) {\n return { instances: [] };\n }\n const text = await fs.readFile(file, 'utf8');\n if (!text.trim()) {\n return { instances: [] };\n }\n try {\n const doc = YAML.parse(text);\n const parsed = authYamlSchema.safeParse(doc);\n if (parsed.success) {\n return parsed.data;\n }\n return { instances: [] };\n } catch {\n return { instances: [] };\n }\n}\n\nasync function writeAll(data: { instances: StoredInstance[] }): Promise<void> {\n const file = getMetadataFilePath();\n await fs.mkdir(path.dirname(file), { recursive: true });\n const yaml = YAML.stringify(authYamlSchema.parse(data), { indentSeq: false });\n await fs.writeFile(file, yaml, { encoding: 'utf8', mode: 0o600 });\n}\n\nasync function withMetadataLock<T>(fn: () => Promise<T>): Promise<T> {\n const file = getMetadataFilePath();\n await fs.mkdir(path.dirname(file), { recursive: true });\n if (!(await pathExists(file))) {\n await fs.writeFile(file, '', { encoding: 'utf8', mode: 0o600 });\n }\n const release = await lockfile.lock(file, {\n retries: { retries: 5, factor: 1.5, minTimeout: 100, maxTimeout: 1000 },\n });\n try {\n return await fn();\n } finally {\n await release();\n }\n}\n\n/** @internal */\nexport async function getAllInstances(): Promise<{\n instances: StoredInstance[];\n selected: StoredInstance | undefined;\n}> {\n const { instances } = await readAll();\n if (instances.length === 0) {\n return { instances: [], selected: undefined };\n }\n const selected = instances.find(i => i.selected) ?? instances[0];\n return {\n instances: instances.map(i => ({\n ...i,\n selected: i.name === selected.name,\n })),\n selected,\n };\n}\n\n/** @internal */\nexport async function getSelectedInstance(\n instanceName?: string,\n): Promise<StoredInstance> {\n if (instanceName) {\n return await getInstanceByName(instanceName);\n }\n const { selected } = await getAllInstances();\n if (!selected) {\n throw new Error(\n 'No instances found. Run \"auth login\" to authenticate first.',\n );\n }\n return selected;\n}\n\n/** @internal */\nexport async function getInstanceByName(name: string): Promise<StoredInstance> {\n const { instances } = await readAll();\n const instance = instances.find(i => i.name === name);\n if (!instance) {\n throw new NotFoundError(`Instance '${name}' not found`);\n }\n return instance;\n}\n\n/** @internal */\nexport async function getInstanceMetadata(\n instanceName: string,\n key: string,\n): Promise<unknown> {\n const instance = await getInstanceByName(instanceName);\n return instance.metadata?.[key];\n}\n\n/** @internal */\nexport async function updateInstanceMetadata(\n instanceName: string,\n key: string,\n value: unknown,\n): Promise<void> {\n return withMetadataLock(async () => {\n const data = await readAll();\n const idx = data.instances.findIndex(i => i.name === instanceName);\n if (idx === -1) {\n throw new NotFoundError(`Instance '${instanceName}' not found`);\n }\n data.instances[idx] = {\n ...data.instances[idx],\n metadata: { ...data.instances[idx].metadata, [key]: value },\n };\n await writeAll(data);\n });\n}\n\n/** @internal */\nexport async function updateInstance(\n instanceName: string,\n updates: Partial<Pick<StoredInstance, 'issuedAt' | 'accessTokenExpiresAt'>>,\n): Promise<void> {\n return withMetadataLock(async () => {\n const data = await readAll();\n const idx = data.instances.findIndex(i => i.name === instanceName);\n if (idx === -1) {\n throw new NotFoundError(`Instance '${instanceName}' not found`);\n }\n data.instances[idx] = { ...data.instances[idx], ...updates };\n await writeAll(data);\n });\n}\n\n/** @internal */\nexport function accessTokenNeedsRefresh(instance: StoredInstance): boolean {\n // 2 minutes before expiration\n return instance.accessTokenExpiresAt <= Date.now() + 2 * 60_000;\n}\n"],"names":["z","fs","path","os","YAML","lockfile","NotFoundError"],"mappings":"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"} |
| 'use strict'; | ||
| var OpaqueType = require('../../opaque-internal/src/OpaqueType.cjs.js'); | ||
| const OpaqueCliModule = OpaqueType.OpaqueType.create({ | ||
| type: "@backstage/CliModule", | ||
| versions: ["v1"] | ||
| }); | ||
| exports.OpaqueCliModule = OpaqueCliModule; | ||
| //# sourceMappingURL=InternalCliModule.cjs.js.map |
| {"version":3,"file":"InternalCliModule.cjs.js","sources":["../../../../cli-internal/src/InternalCliModule.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CliCommand, CliModule } from '@backstage/cli-node';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueCliModule = OpaqueType.create<{\n public: CliModule;\n versions: {\n readonly version: 'v1';\n readonly packageName: string;\n readonly commands: Promise<ReadonlyArray<CliCommand>>;\n };\n}>({\n type: '@backstage/CliModule',\n versions: ['v1'],\n});\n"],"names":["OpaqueType"],"mappings":";;;;AAmBO,MAAM,eAAA,GAAkBA,sBAAW,MAAA,CAOvC;AAAA,EACD,IAAA,EAAM,sBAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"} |
| 'use strict'; | ||
| var OpaqueType = require('../../opaque-internal/src/OpaqueType.cjs.js'); | ||
| const OpaqueCommandTreeNode = OpaqueType.OpaqueType.create({ | ||
| type: "@backstage/CommandTreeNode", | ||
| versions: ["v1"] | ||
| }); | ||
| const OpaqueCommandLeafNode = OpaqueType.OpaqueType.create({ | ||
| type: "@backstage/CommandLeafNode", | ||
| versions: ["v1"] | ||
| }); | ||
| function isCommandNodeHidden(node) { | ||
| if (OpaqueCommandLeafNode.isType(node)) { | ||
| const { command } = OpaqueCommandLeafNode.toInternal(node); | ||
| return !!command.deprecated || !!command.experimental; | ||
| } | ||
| const { children } = OpaqueCommandTreeNode.toInternal(node); | ||
| return children.every((child) => isCommandNodeHidden(child)); | ||
| } | ||
| exports.OpaqueCommandLeafNode = OpaqueCommandLeafNode; | ||
| exports.OpaqueCommandTreeNode = OpaqueCommandTreeNode; | ||
| exports.isCommandNodeHidden = isCommandNodeHidden; | ||
| //# sourceMappingURL=InternalCommandNode.cjs.js.map |
| {"version":3,"file":"InternalCommandNode.cjs.js","sources":["../../../../cli-internal/src/InternalCommandNode.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CliCommand, CliModule } from '@backstage/cli-node';\nimport { OpaqueType } from '@internal/opaque';\n\n/** @internal */\nexport interface CommandTreeNode {\n readonly $$type: '@backstage/CommandTreeNode';\n}\n\n/** @internal */\nexport interface CommandLeafNode {\n readonly $$type: '@backstage/CommandLeafNode';\n}\n\nexport type CommandNode = CommandTreeNode | CommandLeafNode;\n\nexport const OpaqueCommandTreeNode = OpaqueType.create<{\n public: CommandTreeNode;\n versions: {\n readonly version: 'v1';\n readonly name: string;\n readonly children: CommandNode[];\n };\n}>({\n type: '@backstage/CommandTreeNode',\n versions: ['v1'],\n});\n\nexport const OpaqueCommandLeafNode = OpaqueType.create<{\n public: CommandLeafNode;\n versions: {\n readonly version: 'v1';\n readonly name: string;\n readonly command: CliCommand;\n readonly module?: CliModule;\n };\n}>({\n type: '@backstage/CommandLeafNode',\n versions: ['v1'],\n});\n\n/**\n * Checks whether a command node should be hidden from help output.\n * Leaf nodes are hidden if they are deprecated or experimental.\n * Tree nodes are hidden if all of their children are hidden.\n */\nexport function isCommandNodeHidden(node: CommandNode): boolean {\n if (OpaqueCommandLeafNode.isType(node)) {\n const { command } = OpaqueCommandLeafNode.toInternal(node);\n return !!command.deprecated || !!command.experimental;\n }\n const { children } = OpaqueCommandTreeNode.toInternal(node);\n return children.every(child => isCommandNodeHidden(child));\n}\n"],"names":["OpaqueType"],"mappings":";;;;AA+BO,MAAM,qBAAA,GAAwBA,sBAAW,MAAA,CAO7C;AAAA,EACD,IAAA,EAAM,4BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;AAEM,MAAM,qBAAA,GAAwBA,sBAAW,MAAA,CAQ7C;AAAA,EACD,IAAA,EAAM,4BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;AAOM,SAAS,oBAAoB,IAAA,EAA4B;AAC9D,EAAA,IAAI,qBAAA,CAAsB,MAAA,CAAO,IAAI,CAAA,EAAG;AACtC,IAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,qBAAA,CAAsB,WAAW,IAAI,CAAA;AACzD,IAAA,OAAO,CAAC,CAAC,OAAA,CAAQ,UAAA,IAAc,CAAC,CAAC,OAAA,CAAQ,YAAA;AAAA,EAC3C;AACA,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,qBAAA,CAAsB,WAAW,IAAI,CAAA;AAC1D,EAAA,OAAO,QAAA,CAAS,KAAA,CAAM,CAAA,KAAA,KAAS,mBAAA,CAAoB,KAAK,CAAC,CAAA;AAC3D;;;;;;"} |
| 'use strict'; | ||
| const knownBackendPluginIds = [ | ||
| "app", | ||
| "auth", | ||
| "catalog", | ||
| "events", | ||
| "kubernetes", | ||
| "notifications", | ||
| "permission", | ||
| "proxy", | ||
| "scaffolder", | ||
| "search", | ||
| "signals", | ||
| "techdocs" | ||
| ]; | ||
| const knownFrontendPluginIds = [ | ||
| "app", | ||
| "auth", | ||
| "catalog", | ||
| "kubernetes", | ||
| "notifications", | ||
| "scaffolder", | ||
| "search", | ||
| "signals", | ||
| "techdocs" | ||
| ]; | ||
| Object.fromEntries( | ||
| knownBackendPluginIds.map((pluginId) => [ | ||
| pluginId, | ||
| `@backstage/plugin-${pluginId}-backend` | ||
| ]) | ||
| ); | ||
| Object.fromEntries( | ||
| knownFrontendPluginIds.map((pluginId) => [ | ||
| pluginId, | ||
| `@backstage/plugin-${pluginId}` | ||
| ]) | ||
| ); | ||
| //# sourceMappingURL=knownPluginPackages.cjs.js.map |
| {"version":3,"file":"knownPluginPackages.cjs.js","sources":["../../../../cli-internal/src/knownPluginPackages.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst knownBackendPluginIds = [\n 'app',\n 'auth',\n 'catalog',\n 'events',\n 'kubernetes',\n 'notifications',\n 'permission',\n 'proxy',\n 'scaffolder',\n 'search',\n 'signals',\n 'techdocs',\n];\n\n// Only includes plugin IDs that have a corresponding frontend package; some plugins are backend-only and not listed here.\nconst knownFrontendPluginIds = [\n 'app',\n 'auth',\n 'catalog',\n 'kubernetes',\n 'notifications',\n 'scaffolder',\n 'search',\n 'signals',\n 'techdocs',\n];\n\n/**\n * Maps known plugin IDs to their corresponding backend package names.\n */\nexport const knownBackendPluginPackageNameByPluginId: Record<string, string> =\n Object.fromEntries(\n knownBackendPluginIds.map(pluginId => [\n pluginId,\n `@backstage/plugin-${pluginId}-backend`,\n ]),\n );\n\n/**\n * Maps known plugin IDs to their corresponding frontend package names.\n */\nexport const knownFrontendPluginPackageNameByPluginId: Record<string, string> =\n Object.fromEntries(\n knownFrontendPluginIds.map(pluginId => [\n pluginId,\n `@backstage/plugin-${pluginId}`,\n ]),\n );\n"],"names":[],"mappings":";;AAgBA,MAAM,qBAAA,GAAwB;AAAA,EAC5B,KAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,eAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA;AAGA,MAAM,sBAAA,GAAyB;AAAA,EAC7B,KAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,eAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA;AAME,MAAA,CAAO,WAAA;AAAA,EACL,qBAAA,CAAsB,IAAI,CAAA,QAAA,KAAY;AAAA,IACpC,QAAA;AAAA,IACA,qBAAqB,QAAQ,CAAA,QAAA;AAAA,GAC9B;AACH;AAMA,MAAA,CAAO,WAAA;AAAA,EACL,sBAAA,CAAuB,IAAI,CAAA,QAAA,KAAY;AAAA,IACrC,QAAA;AAAA,IACA,qBAAqB,QAAQ,CAAA;AAAA,GAC9B;AACH;;"} |
| 'use strict'; | ||
| var InternalCliModule = require('../cli-internal/src/InternalCliModule.cjs.js'); | ||
| require('../cli-internal/src/InternalCommandNode.cjs.js'); | ||
| require('node:fs'); | ||
| require('node:os'); | ||
| require('node:path'); | ||
| require('../cli-internal/src/knownPluginPackages.cjs.js'); | ||
| function createCliModule(options) { | ||
| if (!options.packageJson.name) { | ||
| throw new Error( | ||
| "The packageJson provided to createCliModule must have a name" | ||
| ); | ||
| } | ||
| const commands = []; | ||
| const commandsPromise = Promise.resolve().then(() => options.init({ addCommand: (command) => commands.push(command) })).then(() => commands); | ||
| return InternalCliModule.OpaqueCliModule.createInstance("v1", { | ||
| packageName: options.packageJson.name, | ||
| commands: commandsPromise | ||
| }); | ||
| } | ||
| exports.createCliModule = createCliModule; | ||
| //# sourceMappingURL=createCliModule.cjs.js.map |
| {"version":3,"file":"createCliModule.cjs.js","sources":["../../src/cli-module/createCliModule.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpaqueCliModule } from '@internal/cli';\nimport { CliCommand, CliModule } from './types';\n\n/**\n * Creates a new CLI plugin that provides commands to the Backstage CLI.\n *\n * The `init` callback is invoked immediately at creation time and is used\n * to register commands via the provided registry. The commands are then\n * made available to the CLI host once the returned promise resolves.\n *\n * @example\n * ```\n * import { createCliModule } from '@backstage/cli-node';\n * import packageJson from '../package.json';\n *\n * export default createCliModule({\n * packageJson,\n * init: async reg => {\n * reg.addCommand({\n * path: ['repo', 'test'],\n * description: 'Run tests across the repository',\n * execute: { loader: () => import('./commands/test') },\n * });\n * },\n * });\n * ```\n *\n * @public\n */\nexport function createCliModule(options: {\n /** The `package.json` contents of the plugin package, used to identify the plugin. */\n packageJson: { name: string };\n /**\n * An initialization callback that registers commands with the CLI.\n * Called immediately when the plugin is created.\n */\n init: (registry: {\n /** Registers a new command with the CLI. */\n addCommand: (command: CliCommand) => void;\n }) => Promise<void>;\n}): CliModule {\n if (!options.packageJson.name) {\n throw new Error(\n 'The packageJson provided to createCliModule must have a name',\n );\n }\n\n const commands: CliCommand[] = [];\n const commandsPromise = Promise.resolve()\n .then(() => options.init({ addCommand: command => commands.push(command) }))\n .then(() => commands);\n\n return OpaqueCliModule.createInstance('v1', {\n packageName: options.packageJson.name,\n commands: commandsPromise,\n });\n}\n"],"names":["OpaqueCliModule"],"mappings":";;;;;;;;;AA6CO,SAAS,gBAAgB,OAAA,EAWlB;AACZ,EAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,CAAY,IAAA,EAAM;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,WAAyB,EAAC;AAChC,EAAA,MAAM,eAAA,GAAkB,QAAQ,OAAA,EAAQ,CACrC,KAAK,MAAM,OAAA,CAAQ,KAAK,EAAE,UAAA,EAAY,aAAW,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA,EAAG,CAAC,CAAA,CAC1E,IAAA,CAAK,MAAM,QAAQ,CAAA;AAEtB,EAAA,OAAOA,iCAAA,CAAgB,eAAe,IAAA,EAAM;AAAA,IAC1C,WAAA,EAAa,QAAQ,WAAA,CAAY,IAAA;AAAA,IACjC,QAAA,EAAU;AAAA,GACX,CAAA;AACH;;;;"} |
| 'use strict'; | ||
| var InternalCliModule = require('../cli-internal/src/InternalCliModule.cjs.js'); | ||
| var InternalCommandNode = require('../cli-internal/src/InternalCommandNode.cjs.js'); | ||
| require('node:fs'); | ||
| require('node:os'); | ||
| require('node:path'); | ||
| require('../cli-internal/src/knownPluginPackages.cjs.js'); | ||
| var commander = require('commander'); | ||
| var chalk = require('chalk'); | ||
| var errors = require('@backstage/errors'); | ||
| function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } | ||
| var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk); | ||
| function buildCommandGraph(commands) { | ||
| const graph = []; | ||
| for (const command of commands) { | ||
| const { path } = command; | ||
| let current = graph; | ||
| for (let i = 0; i < path.length - 1; i++) { | ||
| const name = path[i]; | ||
| let next = current.find( | ||
| (n) => InternalCommandNode.OpaqueCommandTreeNode.isType(n) && InternalCommandNode.OpaqueCommandTreeNode.toInternal(n).name === name | ||
| ); | ||
| if (!next) { | ||
| next = InternalCommandNode.OpaqueCommandTreeNode.createInstance("v1", { | ||
| name, | ||
| children: [] | ||
| }); | ||
| current.push(next); | ||
| } | ||
| current = InternalCommandNode.OpaqueCommandTreeNode.toInternal(next).children; | ||
| } | ||
| current.push( | ||
| InternalCommandNode.OpaqueCommandLeafNode.createInstance("v1", { | ||
| name: path[path.length - 1], | ||
| command | ||
| }) | ||
| ); | ||
| } | ||
| return graph; | ||
| } | ||
| function exitWithError(error) { | ||
| process.stderr.write(` | ||
| ${chalk__default.default.red(errors.stringifyError(error))} | ||
| `); | ||
| process.exit( | ||
| errors.isError(error) && "code" in error && typeof error.code === "number" ? error.code : 1 | ||
| ); | ||
| } | ||
| function registerCommands(graph, program, programName) { | ||
| const queue = graph.map((node) => ({ node, argParser: program })); | ||
| while (queue.length) { | ||
| const { node, argParser } = queue.shift(); | ||
| if (InternalCommandNode.OpaqueCommandTreeNode.isType(node)) { | ||
| const internal = InternalCommandNode.OpaqueCommandTreeNode.toInternal(node); | ||
| const treeParser = argParser.command(`${internal.name} [command]`, { | ||
| hidden: InternalCommandNode.isCommandNodeHidden(node) | ||
| }).description(internal.name); | ||
| queue.push( | ||
| ...internal.children.map((child) => ({ | ||
| node: child, | ||
| argParser: treeParser | ||
| })) | ||
| ); | ||
| } else { | ||
| const internal = InternalCommandNode.OpaqueCommandLeafNode.toInternal(node); | ||
| argParser.command(internal.name, { | ||
| hidden: !!internal.command.deprecated || !!internal.command.experimental | ||
| }).description(internal.command.description).helpOption(false).allowUnknownOption(true).allowExcessArguments(true).action(async () => { | ||
| try { | ||
| const args = program.parseOptions(process.argv); | ||
| const nonProcessArgs = args.operands.slice(2); | ||
| const positionalArgs = []; | ||
| let index = 0; | ||
| for (let argIndex = 0; argIndex < nonProcessArgs.length; argIndex++) { | ||
| if (argIndex === index && internal.command.path[argIndex] === nonProcessArgs[argIndex]) { | ||
| index += 1; | ||
| continue; | ||
| } | ||
| positionalArgs.push(nonProcessArgs[argIndex]); | ||
| } | ||
| const context = { | ||
| args: [...positionalArgs, ...args.unknown], | ||
| info: { | ||
| usage: [programName, ...internal.command.path].join(" "), | ||
| name: internal.command.path.join(" ") | ||
| } | ||
| }; | ||
| if (typeof internal.command.execute === "function") { | ||
| await internal.command.execute(context); | ||
| } else { | ||
| const mod = await internal.command.execute.loader(); | ||
| const fn = typeof mod.default === "function" ? mod.default : mod.default.default; | ||
| await fn(context); | ||
| } | ||
| process.exit(0); | ||
| } catch (error) { | ||
| exitWithError(error); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| async function runCliModule(options) { | ||
| const { module: cliModule, name, version } = options; | ||
| if (!InternalCliModule.OpaqueCliModule.isType(cliModule)) { | ||
| throw new Error( | ||
| `Invalid CLI module: expected a module created with createCliModule` | ||
| ); | ||
| } | ||
| const internal = InternalCliModule.OpaqueCliModule.toInternal(cliModule); | ||
| const commands = await internal.commands; | ||
| const graph = buildCommandGraph(commands); | ||
| const program = new commander.Command(); | ||
| program.name(name).allowUnknownOption(true).allowExcessArguments(true); | ||
| if (version) { | ||
| program.version(version); | ||
| } | ||
| registerCommands(graph, program, name); | ||
| program.on("command:*", () => { | ||
| console.log(); | ||
| console.log(chalk__default.default.red(`Invalid command: ${program.args.join(" ")}`)); | ||
| console.log(); | ||
| program.outputHelp(); | ||
| process.exit(1); | ||
| }); | ||
| process.on("unhandledRejection", (rejection) => { | ||
| exitWithError(rejection); | ||
| }); | ||
| await program.parseAsync(process.argv); | ||
| } | ||
| exports.runCliModule = runCliModule; | ||
| //# sourceMappingURL=runCliModule.cjs.js.map |
| {"version":3,"file":"runCliModule.cjs.js","sources":["../../src/cli-module/runCliModule.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n OpaqueCliModule,\n OpaqueCommandTreeNode,\n OpaqueCommandLeafNode,\n isCommandNodeHidden,\n} from '@internal/cli';\nimport type { CommandNode } from '@internal/cli';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { isError, stringifyError } from '@backstage/errors';\nimport type { CliModule, CliCommand } from './types';\n\nfunction buildCommandGraph(commands: ReadonlyArray<CliCommand>): CommandNode[] {\n const graph: CommandNode[] = [];\n\n for (const command of commands) {\n const { path } = command;\n let current = graph;\n\n for (let i = 0; i < path.length - 1; i++) {\n const name = path[i];\n let next = current.find(\n n =>\n OpaqueCommandTreeNode.isType(n) &&\n OpaqueCommandTreeNode.toInternal(n).name === name,\n );\n if (!next) {\n next = OpaqueCommandTreeNode.createInstance('v1', {\n name,\n children: [],\n });\n current.push(next);\n }\n current = OpaqueCommandTreeNode.toInternal(next).children;\n }\n\n current.push(\n OpaqueCommandLeafNode.createInstance('v1', {\n name: path[path.length - 1],\n command,\n }),\n );\n }\n\n return graph;\n}\n\nfunction exitWithError(error: unknown): never {\n process.stderr.write(`\\n${chalk.red(stringifyError(error))}\\n\\n`);\n process.exit(\n isError(error) && 'code' in error && typeof error.code === 'number'\n ? error.code\n : 1,\n );\n}\n\nfunction registerCommands(\n graph: CommandNode[],\n program: Command,\n programName: string,\n): void {\n const queue = graph.map(node => ({ node, argParser: program }));\n\n while (queue.length) {\n const { node, argParser } = queue.shift()!;\n\n if (OpaqueCommandTreeNode.isType(node)) {\n const internal = OpaqueCommandTreeNode.toInternal(node);\n const treeParser = argParser\n .command(`${internal.name} [command]`, {\n hidden: isCommandNodeHidden(node),\n })\n .description(internal.name);\n\n queue.push(\n ...internal.children.map(child => ({\n node: child,\n argParser: treeParser,\n })),\n );\n } else {\n const internal = OpaqueCommandLeafNode.toInternal(node);\n argParser\n .command(internal.name, {\n hidden:\n !!internal.command.deprecated || !!internal.command.experimental,\n })\n .description(internal.command.description)\n .helpOption(false)\n .allowUnknownOption(true)\n .allowExcessArguments(true)\n .action(async () => {\n try {\n const args = program.parseOptions(process.argv);\n\n const nonProcessArgs = args.operands.slice(2);\n const positionalArgs = [];\n let index = 0;\n for (\n let argIndex = 0;\n argIndex < nonProcessArgs.length;\n argIndex++\n ) {\n if (\n argIndex === index &&\n internal.command.path[argIndex] === nonProcessArgs[argIndex]\n ) {\n index += 1;\n continue;\n }\n positionalArgs.push(nonProcessArgs[argIndex]);\n }\n const context = {\n args: [...positionalArgs, ...args.unknown],\n info: {\n usage: [programName, ...internal.command.path].join(' '),\n name: internal.command.path.join(' '),\n },\n };\n\n if (typeof internal.command.execute === 'function') {\n await internal.command.execute(context);\n } else {\n const mod = await internal.command.execute.loader();\n const fn =\n typeof mod.default === 'function'\n ? mod.default\n : (mod.default as any).default;\n await fn(context);\n }\n process.exit(0);\n } catch (error: unknown) {\n exitWithError(error);\n }\n });\n }\n }\n}\n\n/**\n * Runs a CLI module as a standalone program.\n *\n * This helper extracts the commands from a {@link CliModule} and exposes\n * them as a fully functional CLI with help output and argument parsing.\n * It is intended to be called from a module package's `bin` entry point\n * so that the module can be executed directly without being wired into\n * a larger CLI host.\n *\n * @example\n * ```ts\n * #!/usr/bin/env node\n * import { runCliModule } from '@backstage/cli-node';\n * import cliModule from './index';\n *\n * runCliModule({\n * module: cliModule,\n * name: 'backstage-auth',\n * version: require('../package.json').version,\n * });\n * ```\n *\n * @public\n */\nexport async function runCliModule(options: {\n /** The CLI module to run. */\n module: CliModule;\n /** The program name shown in help output and usage strings. */\n name: string;\n /** The version string shown when `--version` is passed. */\n version?: string;\n}): Promise<void> {\n const { module: cliModule, name, version } = options;\n\n if (!OpaqueCliModule.isType(cliModule)) {\n throw new Error(\n `Invalid CLI module: expected a module created with createCliModule`,\n );\n }\n\n const internal = OpaqueCliModule.toInternal(cliModule);\n const commands = await internal.commands;\n const graph = buildCommandGraph(commands);\n\n const program = new Command();\n program.name(name).allowUnknownOption(true).allowExcessArguments(true);\n\n if (version) {\n program.version(version);\n }\n\n registerCommands(graph, program, name);\n\n program.on('command:*', () => {\n console.log();\n console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`));\n console.log();\n program.outputHelp();\n process.exit(1);\n });\n\n process.on('unhandledRejection', rejection => {\n exitWithError(rejection);\n });\n\n await program.parseAsync(process.argv);\n}\n"],"names":["OpaqueCommandTreeNode","OpaqueCommandLeafNode","chalk","stringifyError","isError","isCommandNodeHidden","OpaqueCliModule","Command"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAS,kBAAkB,QAAA,EAAoD;AAC7E,EAAA,MAAM,QAAuB,EAAC;AAE9B,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,EAAE,MAAK,GAAI,OAAA;AACjB,IAAA,IAAI,OAAA,GAAU,KAAA;AAEd,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA,EAAA,EAAK;AACxC,MAAA,MAAM,IAAA,GAAO,KAAK,CAAC,CAAA;AACnB,MAAA,IAAI,OAAO,OAAA,CAAQ,IAAA;AAAA,QACjB,CAAA,CAAA,KACEA,0CAAsB,MAAA,CAAO,CAAC,KAC9BA,yCAAA,CAAsB,UAAA,CAAW,CAAC,CAAA,CAAE,IAAA,KAAS;AAAA,OACjD;AACA,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,IAAA,GAAOA,yCAAA,CAAsB,eAAe,IAAA,EAAM;AAAA,UAChD,IAAA;AAAA,UACA,UAAU;AAAC,SACZ,CAAA;AACD,QAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,MACnB;AACA,MAAA,OAAA,GAAUA,yCAAA,CAAsB,UAAA,CAAW,IAAI,CAAA,CAAE,QAAA;AAAA,IACnD;AAEA,IAAA,OAAA,CAAQ,IAAA;AAAA,MACNC,yCAAA,CAAsB,eAAe,IAAA,EAAM;AAAA,QACzC,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AAAA,QAC1B;AAAA,OACD;AAAA,KACH;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,cAAc,KAAA,EAAuB;AAC5C,EAAA,OAAA,CAAQ,OAAO,KAAA,CAAM;AAAA,EAAKC,sBAAA,CAAM,GAAA,CAAIC,qBAAA,CAAe,KAAK,CAAC,CAAC;;AAAA,CAAM,CAAA;AAChE,EAAA,OAAA,CAAQ,IAAA;AAAA,IACNC,cAAA,CAAQ,KAAK,CAAA,IAAK,MAAA,IAAU,KAAA,IAAS,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GACvD,KAAA,CAAM,IAAA,GACN;AAAA,GACN;AACF;AAEA,SAAS,gBAAA,CACP,KAAA,EACA,OAAA,EACA,WAAA,EACM;AACN,EAAA,MAAM,KAAA,GAAQ,MAAM,GAAA,CAAI,CAAA,IAAA,MAAS,EAAE,IAAA,EAAM,SAAA,EAAW,SAAQ,CAAE,CAAA;AAE9D,EAAA,OAAO,MAAM,MAAA,EAAQ;AACnB,IAAA,MAAM,EAAE,IAAA,EAAM,SAAA,EAAU,GAAI,MAAM,KAAA,EAAM;AAExC,IAAA,IAAIJ,yCAAA,CAAsB,MAAA,CAAO,IAAI,CAAA,EAAG;AACtC,MAAA,MAAM,QAAA,GAAWA,yCAAA,CAAsB,UAAA,CAAW,IAAI,CAAA;AACtD,MAAA,MAAM,aAAa,SAAA,CAChB,OAAA,CAAQ,CAAA,EAAG,QAAA,CAAS,IAAI,CAAA,UAAA,CAAA,EAAc;AAAA,QACrC,MAAA,EAAQK,wCAAoB,IAAI;AAAA,OACjC,CAAA,CACA,WAAA,CAAY,QAAA,CAAS,IAAI,CAAA;AAE5B,MAAA,KAAA,CAAM,IAAA;AAAA,QACJ,GAAG,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,CAAA,KAAA,MAAU;AAAA,UACjC,IAAA,EAAM,KAAA;AAAA,UACN,SAAA,EAAW;AAAA,SACb,CAAE;AAAA,OACJ;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,QAAA,GAAWJ,yCAAA,CAAsB,UAAA,CAAW,IAAI,CAAA;AACtD,MAAA,SAAA,CACG,OAAA,CAAQ,SAAS,IAAA,EAAM;AAAA,QACtB,MAAA,EACE,CAAC,CAAC,QAAA,CAAS,QAAQ,UAAA,IAAc,CAAC,CAAC,QAAA,CAAS,OAAA,CAAQ;AAAA,OACvD,CAAA,CACA,WAAA,CAAY,QAAA,CAAS,OAAA,CAAQ,WAAW,CAAA,CACxC,UAAA,CAAW,KAAK,CAAA,CAChB,mBAAmB,IAAI,CAAA,CACvB,qBAAqB,IAAI,CAAA,CACzB,OAAO,YAAY;AAClB,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,YAAA,CAAa,OAAA,CAAQ,IAAI,CAAA;AAE9C,UAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AAC5C,UAAA,MAAM,iBAAiB,EAAC;AACxB,UAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,UAAA,KAAA,IACM,QAAA,GAAW,CAAA,EACf,QAAA,GAAW,cAAA,CAAe,QAC1B,QAAA,EAAA,EACA;AACA,YAAA,IACE,QAAA,KAAa,SACb,QAAA,CAAS,OAAA,CAAQ,KAAK,QAAQ,CAAA,KAAM,cAAA,CAAe,QAAQ,CAAA,EAC3D;AACA,cAAA,KAAA,IAAS,CAAA;AACT,cAAA;AAAA,YACF;AACA,YAAA,cAAA,CAAe,IAAA,CAAK,cAAA,CAAe,QAAQ,CAAC,CAAA;AAAA,UAC9C;AACA,UAAA,MAAM,OAAA,GAAU;AAAA,YACd,MAAM,CAAC,GAAG,cAAA,EAAgB,GAAG,KAAK,OAAO,CAAA;AAAA,YACzC,IAAA,EAAM;AAAA,cACJ,KAAA,EAAO,CAAC,WAAA,EAAa,GAAG,SAAS,OAAA,CAAQ,IAAI,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAAA,cACvD,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,KAAK,GAAG;AAAA;AACtC,WACF;AAEA,UAAA,IAAI,OAAO,QAAA,CAAS,OAAA,CAAQ,OAAA,KAAY,UAAA,EAAY;AAClD,YAAA,MAAM,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AAAA,UACxC,CAAA,MAAO;AACL,YAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,OAAA,CAAQ,QAAQ,MAAA,EAAO;AAClD,YAAA,MAAM,EAAA,GACJ,OAAO,GAAA,CAAI,OAAA,KAAY,aACnB,GAAA,CAAI,OAAA,GACH,IAAI,OAAA,CAAgB,OAAA;AAC3B,YAAA,MAAM,GAAG,OAAO,CAAA;AAAA,UAClB;AACA,UAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,QAChB,SAAS,KAAA,EAAgB;AACvB,UAAA,aAAA,CAAc,KAAK,CAAA;AAAA,QACrB;AAAA,MACF,CAAC,CAAA;AAAA,IACL;AAAA,EACF;AACF;AA0BA,eAAsB,aAAa,OAAA,EAOjB;AAChB,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAW,IAAA,EAAM,SAAQ,GAAI,OAAA;AAE7C,EAAA,IAAI,CAACK,iCAAA,CAAgB,MAAA,CAAO,SAAS,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,kEAAA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAWA,iCAAA,CAAgB,UAAA,CAAW,SAAS,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,QAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,kBAAkB,QAAQ,CAAA;AAExC,EAAA,MAAM,OAAA,GAAU,IAAIC,iBAAA,EAAQ;AAC5B,EAAA,OAAA,CAAQ,KAAK,IAAI,CAAA,CAAE,mBAAmB,IAAI,CAAA,CAAE,qBAAqB,IAAI,CAAA;AAErE,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAA,CAAQ,QAAQ,OAAO,CAAA;AAAA,EACzB;AAEA,EAAA,gBAAA,CAAiB,KAAA,EAAO,SAAS,IAAI,CAAA;AAErC,EAAA,OAAA,CAAQ,EAAA,CAAG,aAAa,MAAM;AAC5B,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,GAAA,CAAIL,sBAAA,CAAM,GAAA,CAAI,CAAA,iBAAA,EAAoB,OAAA,CAAQ,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,CAAC,CAAA;AACnE,IAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,IAAA,OAAA,CAAQ,UAAA,EAAW;AACnB,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB,CAAC,CAAA;AAED,EAAA,OAAA,CAAQ,EAAA,CAAG,sBAAsB,CAAA,SAAA,KAAa;AAC5C,IAAA,aAAA,CAAc,SAAS,CAAA;AAAA,EACzB,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,CAAQ,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA;AACvC;;;;"} |
| 'use strict'; | ||
| class OpaqueType { | ||
| /** | ||
| * Creates a new opaque type. | ||
| * | ||
| * @param options.type The type identifier of the opaque type | ||
| * @param options.versions The available versions of the opaque type | ||
| * @returns A new opaque type helper | ||
| */ | ||
| static create(options) { | ||
| return new OpaqueType(options.type, new Set(options.versions)); | ||
| } | ||
| #type; | ||
| #versions; | ||
| constructor(type, versions) { | ||
| this.#type = type; | ||
| this.#versions = versions; | ||
| } | ||
| /** | ||
| * The internal version of the opaque type, used like this: `typeof MyOpaqueType.TPublic` | ||
| * | ||
| * @remarks | ||
| * | ||
| * This property is only useful for type checking, its runtime value is `undefined`. | ||
| */ | ||
| TPublic = void 0; | ||
| /** | ||
| * The internal version of the opaque type, used like this: `typeof MyOpaqueType.TInternal` | ||
| * | ||
| * @remarks | ||
| * | ||
| * This property is only useful for type checking, its runtime value is `undefined`. | ||
| */ | ||
| TInternal = void 0; | ||
| /** | ||
| * @param value Input value expected to be an instance of this opaque type | ||
| * @returns True if the value matches this opaque type | ||
| */ | ||
| isType = (value) => { | ||
| return this.#isThisInternalType(value); | ||
| }; | ||
| /** | ||
| * @param value Input value expected to be an instance of this opaque type | ||
| * @throws If the value is not an instance of this opaque type or is of an unsupported version | ||
| * @returns The internal version of the opaque type | ||
| */ | ||
| toInternal = (value) => { | ||
| if (!this.#isThisInternalType(value)) { | ||
| throw new TypeError( | ||
| `Invalid opaque type, expected '${this.#type}', but got '${this.#stringifyUnknown(value)}'` | ||
| ); | ||
| } | ||
| if (!this.#versions.has(value.version)) { | ||
| const versions = Array.from(this.#versions).map(this.#stringifyVersion); | ||
| if (versions.length > 1) { | ||
| versions[versions.length - 1] = `or ${versions[versions.length - 1]}`; | ||
| } | ||
| const expected = versions.length > 2 ? versions.join(", ") : versions.join(" "); | ||
| throw new TypeError( | ||
| `Invalid opaque type instance, got version ${this.#stringifyVersion( | ||
| value.version | ||
| )}, expected ${expected}` | ||
| ); | ||
| } | ||
| return value; | ||
| }; | ||
| /** | ||
| * Creates an instance of the opaque type, returning the public type. | ||
| * | ||
| * @param version The version of the instance to create | ||
| * @param value The remaining public and internal properties of the instance | ||
| * @returns An instance of the opaque type | ||
| */ | ||
| createInstance(version, props) { | ||
| return Object.assign(props, { | ||
| $$type: this.#type, | ||
| ...version && { version } | ||
| }); | ||
| } | ||
| #isThisInternalType(value) { | ||
| if (value === null || typeof value !== "object") { | ||
| return false; | ||
| } | ||
| return value.$$type === this.#type; | ||
| } | ||
| #stringifyUnknown(value) { | ||
| if (typeof value !== "object") { | ||
| return `<${typeof value}>`; | ||
| } | ||
| if (value === null) { | ||
| return "<null>"; | ||
| } | ||
| if ("$$type" in value) { | ||
| return String(value.$$type); | ||
| } | ||
| return String(value); | ||
| } | ||
| #stringifyVersion = (version) => { | ||
| return version ? `'${version}'` : "undefined"; | ||
| }; | ||
| } | ||
| exports.OpaqueType = OpaqueType; | ||
| //# sourceMappingURL=OpaqueType.cjs.js.map |
| {"version":3,"file":"OpaqueType.cjs.js","sources":["../../../../opaque-internal/src/OpaqueType.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// TODO(Rugvip): This lives here temporarily, but should be moved to a more\n// central location. It's useful for backend packages too so we'll need to have\n// it in a common package, but it might also be that we want to make it\n// available publicly too in which case it would make sense to have this be part\n// of @backstage/version-bridge. The problem with exporting it from there is\n// that it would need to be very stable at that point, so it might be a bit\n// early to put it there already.\n\n/**\n * A helper for working with opaque types.\n */\nexport class OpaqueType<\n T extends {\n public: { $$type: string };\n versions: { version: string | undefined };\n },\n> {\n /**\n * Creates a new opaque type.\n *\n * @param options.type The type identifier of the opaque type\n * @param options.versions The available versions of the opaque type\n * @returns A new opaque type helper\n */\n static create<\n T extends {\n public: { $$type: string };\n versions: { version: string | undefined };\n },\n >(options: {\n type: T['public']['$$type'];\n versions: Array<T['versions']['version']>;\n }) {\n return new OpaqueType<T>(options.type, new Set(options.versions));\n }\n\n #type: string;\n #versions: Set<string | undefined>;\n\n private constructor(type: string, versions: Set<string | undefined>) {\n this.#type = type;\n this.#versions = versions;\n }\n\n /**\n * The internal version of the opaque type, used like this: `typeof MyOpaqueType.TPublic`\n *\n * @remarks\n *\n * This property is only useful for type checking, its runtime value is `undefined`.\n */\n TPublic: T['public'] = undefined as any;\n\n /**\n * The internal version of the opaque type, used like this: `typeof MyOpaqueType.TInternal`\n *\n * @remarks\n *\n * This property is only useful for type checking, its runtime value is `undefined`.\n */\n TInternal: T['public'] & T['versions'] = undefined as any;\n\n /**\n * @param value Input value expected to be an instance of this opaque type\n * @returns True if the value matches this opaque type\n */\n isType = (value: unknown): value is T['public'] => {\n return this.#isThisInternalType(value);\n };\n\n /**\n * @param value Input value expected to be an instance of this opaque type\n * @throws If the value is not an instance of this opaque type or is of an unsupported version\n * @returns The internal version of the opaque type\n */\n toInternal = (value: unknown): T['public'] & T['versions'] => {\n if (!this.#isThisInternalType(value)) {\n throw new TypeError(\n `Invalid opaque type, expected '${\n this.#type\n }', but got '${this.#stringifyUnknown(value)}'`,\n );\n }\n\n if (!this.#versions.has(value.version)) {\n const versions = Array.from(this.#versions).map(this.#stringifyVersion);\n if (versions.length > 1) {\n versions[versions.length - 1] = `or ${versions[versions.length - 1]}`;\n }\n const expected =\n versions.length > 2 ? versions.join(', ') : versions.join(' ');\n throw new TypeError(\n `Invalid opaque type instance, got version ${this.#stringifyVersion(\n value.version,\n )}, expected ${expected}`,\n );\n }\n\n return value;\n };\n\n /**\n * Creates an instance of the opaque type, returning the public type.\n *\n * @param version The version of the instance to create\n * @param value The remaining public and internal properties of the instance\n * @returns An instance of the opaque type\n */\n createInstance<\n TVersion extends T['versions']['version'],\n TPublic extends T['public'],\n >(\n version: TVersion,\n props: Omit<T['public'], '$$type'> &\n (T['versions'] extends infer UVersion\n ? UVersion extends { version: TVersion }\n ? Omit<UVersion, 'version'>\n : never\n : never) &\n Object, // & Object to allow for object properties too, e.g. toString()\n ): TPublic {\n return Object.assign(props as object, {\n $$type: this.#type,\n ...(version && { version }),\n }) as unknown as TPublic;\n }\n\n #isThisInternalType(value: unknown): value is T['public'] & T['versions'] {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n return (value as T['public']).$$type === this.#type;\n }\n\n #stringifyUnknown(value: unknown) {\n if (typeof value !== 'object') {\n return `<${typeof value}>`;\n }\n if (value === null) {\n return '<null>';\n }\n if ('$$type' in value) {\n return String(value.$$type);\n }\n return String(value);\n }\n\n #stringifyVersion = (version: string | undefined) => {\n return version ? `'${version}'` : 'undefined';\n };\n}\n"],"names":[],"mappings":";;AA2BO,MAAM,UAAA,CAKX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,OAKL,OAAA,EAGC;AACD,IAAA,OAAO,IAAI,WAAc,OAAA,CAAQ,IAAA,EAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAAA,EAClE;AAAA,EAEA,KAAA;AAAA,EACA,SAAA;AAAA,EAEQ,WAAA,CAAY,MAAc,QAAA,EAAmC;AACnE,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAA,GAAuB,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASvB,SAAA,GAAyC,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzC,MAAA,GAAS,CAAC,KAAA,KAAyC;AACjD,IAAA,OAAO,IAAA,CAAK,oBAAoB,KAAK,CAAA;AAAA,EACvC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA,GAAa,CAAC,KAAA,KAAgD;AAC5D,IAAA,IAAI,CAAC,IAAA,CAAK,mBAAA,CAAoB,KAAK,CAAA,EAAG;AACpC,MAAA,MAAM,IAAI,SAAA;AAAA,QACR,kCACE,IAAA,CAAK,KACP,eAAe,IAAA,CAAK,iBAAA,CAAkB,KAAK,CAAC,CAAA,CAAA;AAAA,OAC9C;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG;AACtC,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA,CAAE,GAAA,CAAI,KAAK,iBAAiB,CAAA;AACtE,MAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,QAAA,QAAA,CAAS,QAAA,CAAS,SAAS,CAAC,CAAA,GAAI,MAAM,QAAA,CAAS,QAAA,CAAS,MAAA,GAAS,CAAC,CAAC,CAAA,CAAA;AAAA,MACrE;AACA,MAAA,MAAM,QAAA,GACJ,QAAA,CAAS,MAAA,GAAS,CAAA,GAAI,QAAA,CAAS,KAAK,IAAI,CAAA,GAAI,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAC/D,MAAA,MAAM,IAAI,SAAA;AAAA,QACR,6CAA6C,IAAA,CAAK,iBAAA;AAAA,UAChD,KAAA,CAAM;AAAA,SACP,cAAc,QAAQ,CAAA;AAAA,OACzB;AAAA,IACF;AAEA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAA,CAIE,SACA,KAAA,EAOS;AACT,IAAA,OAAO,MAAA,CAAO,OAAO,KAAA,EAAiB;AAAA,MACpC,QAAQ,IAAA,CAAK,KAAA;AAAA,MACb,GAAI,OAAA,IAAW,EAAE,OAAA;AAAQ,KAC1B,CAAA;AAAA,EACH;AAAA,EAEA,oBAAoB,KAAA,EAAsD;AACxE,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAQ,KAAA,CAAsB,WAAW,IAAA,CAAK,KAAA;AAAA,EAChD;AAAA,EAEA,kBAAkB,KAAA,EAAgB;AAChC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,OAAO,CAAA,CAAA,EAAI,OAAO,KAAK,CAAA,CAAA,CAAA;AAAA,IACzB;AACA,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,OAAO,QAAA;AAAA,IACT;AACA,IAAA,IAAI,YAAY,KAAA,EAAO;AACrB,MAAA,OAAO,MAAA,CAAO,MAAM,MAAM,CAAA;AAAA,IAC5B;AACA,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AAAA,EAEA,iBAAA,GAAoB,CAAC,OAAA,KAAgC;AACnD,IAAA,OAAO,OAAA,GAAU,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAA,GAAM,WAAA;AAAA,EACpC,CAAA;AACF;;;;"} |
+19
-0
| # @backstage/cli-node | ||
| ## 0.3.0 | ||
| ### Minor Changes | ||
| - 7d055ef: Added `createCliModule` API and related types for building Backstage CLI plugins. | ||
| ### Patch Changes | ||
| - 94a885a: Added a new `cli-module` package role for packages that provide CLI plugin extensions. | ||
| - 12fa965: Added `CliAuth` class for managing CLI authentication state. This provides a class-based API with a static `create` method that resolves the currently selected (or explicitly named) auth instance, transparently refreshes expired access tokens, and exposes helpers for other CLI modules to authenticate with a Backstage backend. | ||
| - 61cb976: Added `toString()` method to `Lockfile` for serializing lockfiles back to string format. | ||
| - 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code. | ||
| - 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`. | ||
| - 3c811bf: Added `hasBackstageYarnPlugin` and `SuccessCache` exports, moved from `@backstage/cli`. | ||
| - a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports. | ||
| - a9d23c4: Properly support `package.json` `workspaces` field | ||
| - Updated dependencies | ||
| - @backstage/cli-common@0.2.0 | ||
| ## 0.2.19-next.1 | ||
@@ -4,0 +23,0 @@ |
| 'use strict'; | ||
| var CliAuth = require('./auth/CliAuth.cjs.js'); | ||
| var SuccessCache = require('./cache/SuccessCache.cjs.js'); | ||
| var createCliModule = require('./cli-module/createCliModule.cjs.js'); | ||
| var runCliModule = require('./cli-module/runCliModule.cjs.js'); | ||
| var runConcurrentTasks = require('./concurrency/runConcurrentTasks.cjs.js'); | ||
@@ -15,3 +18,6 @@ var runWorkerQueueThreads = require('./concurrency/runWorkerQueueThreads.cjs.js'); | ||
| exports.CliAuth = CliAuth.CliAuth; | ||
| exports.SuccessCache = SuccessCache.SuccessCache; | ||
| exports.createCliModule = createCliModule.createCliModule; | ||
| exports.runCliModule = runCliModule.runCliModule; | ||
| exports.runConcurrentTasks = runConcurrentTasks.runConcurrentTasks; | ||
@@ -18,0 +24,0 @@ exports.runWorkerQueueThreads = runWorkerQueueThreads.runWorkerQueueThreads; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"} | ||
| {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} |
+226
-3
@@ -5,2 +5,52 @@ import { Package } from '@manypkg/get-packages'; | ||
| /** | ||
| * Options for creating a {@link CliAuth} instance. | ||
| * | ||
| * @public | ||
| */ | ||
| interface CliAuthCreateOptions { | ||
| /** | ||
| * An explicit instance name to resolve. When omitted the currently | ||
| * selected instance is used. | ||
| */ | ||
| instanceName?: string; | ||
| } | ||
| /** | ||
| * Manages authentication state for Backstage CLI commands. | ||
| * | ||
| * Reads the currently selected (or explicitly named) auth instance from | ||
| * the on-disk instance store, transparently refreshes expired access | ||
| * tokens, and exposes helpers that other CLI modules need to talk to a | ||
| * Backstage backend. | ||
| * | ||
| * @public | ||
| */ | ||
| declare class CliAuth { | ||
| #private; | ||
| /** | ||
| * Resolve the current auth instance and return a ready-to-use | ||
| * {@link CliAuth} object. Throws when no instance can be found. | ||
| */ | ||
| static create(options?: CliAuthCreateOptions): Promise<CliAuth>; | ||
| private constructor(); | ||
| /** Returns the name of the resolved auth instance. */ | ||
| getInstanceName(): string; | ||
| /** Returns the base URL of the resolved auth instance. */ | ||
| getBaseUrl(): string; | ||
| /** | ||
| * Returns a valid access token, refreshing it first if the current | ||
| * token is expired or about to expire. | ||
| */ | ||
| getAccessToken(): Promise<string>; | ||
| /** | ||
| * Reads a per-instance metadata value previously stored by the | ||
| * auth module (e.g. `pluginSources`). | ||
| */ | ||
| getMetadata(key: string): Promise<unknown>; | ||
| /** | ||
| * Writes a per-instance metadata value to the on-disk instance store. | ||
| */ | ||
| setMetadata(key: string, value: unknown): Promise<void>; | ||
| } | ||
| /** | ||
| * A file-system-based cache that tracks successful operations by storing | ||
@@ -29,2 +79,175 @@ * timestamped marker files. | ||
| /** | ||
| * The context provided to a CLI command at the time of execution. | ||
| * | ||
| * Contains the parsed arguments and metadata about the command being run. | ||
| * | ||
| * @public | ||
| */ | ||
| interface CliCommandContext { | ||
| /** | ||
| * The remaining arguments passed to the command after the command path | ||
| * has been resolved. This includes both positional arguments and flags. | ||
| * | ||
| * For example, running `backstage-cli repo test --verbose src/` would | ||
| * result in `args` being `['--verbose', 'src/']`. | ||
| */ | ||
| args: string[]; | ||
| /** | ||
| * Metadata about the command being executed. | ||
| */ | ||
| info: { | ||
| /** | ||
| * The full usage string of the command including the program name, | ||
| * for example `"backstage-cli repo test"`. | ||
| */ | ||
| usage: string; | ||
| /** | ||
| * The name of the command as defined by its path, | ||
| * for example `"repo test"`. | ||
| */ | ||
| name: string; | ||
| }; | ||
| } | ||
| /** | ||
| * A command definition for a Backstage CLI plugin. | ||
| * | ||
| * Each command is identified by a `path` that determines its position in | ||
| * the command tree. For example, a path of `['repo', 'test']` registers | ||
| * the command as `backstage-cli repo test`. | ||
| * | ||
| * Commands can either provide an `execute` function directly, or use a | ||
| * `loader` for deferred loading of the implementation. The loader pattern | ||
| * is recommended for commands with heavy dependencies, as it avoids | ||
| * loading the implementation until the command is actually invoked. | ||
| * | ||
| * @public | ||
| */ | ||
| interface CliCommand { | ||
| /** | ||
| * The path segments that define the command's position in the CLI tree. | ||
| * For example, `['repo', 'test']` maps to `backstage-cli repo test`. | ||
| */ | ||
| path: string[]; | ||
| /** | ||
| * A short description of the command, displayed in help output. | ||
| */ | ||
| description: string; | ||
| /** | ||
| * If `true`, the command is deprecated and will be hidden from help output | ||
| * but can still be invoked. | ||
| */ | ||
| deprecated?: boolean; | ||
| /** | ||
| * If `true`, the command is experimental and will be hidden from help | ||
| * output but can still be invoked. | ||
| */ | ||
| experimental?: boolean; | ||
| /** | ||
| * The command implementation, either as a direct function or as a loader | ||
| * that returns the implementation as a default export. The loader form | ||
| * is useful for deferring heavy imports until the command is invoked. | ||
| * | ||
| * @example | ||
| * Direct execution: | ||
| * ``` | ||
| * execute: async ({ args }) => { ... } | ||
| * ``` | ||
| * | ||
| * @example | ||
| * Deferred loading: | ||
| * ``` | ||
| * execute: { loader: () => import('./my-command') } | ||
| * ``` | ||
| */ | ||
| execute: ((context: CliCommandContext) => Promise<void>) | { | ||
| loader: () => Promise<{ | ||
| default: (context: CliCommandContext) => Promise<void>; | ||
| }>; | ||
| }; | ||
| } | ||
| /** | ||
| * An opaque representation of a Backstage CLI plugin, created | ||
| * using {@link createCliModule}. | ||
| * | ||
| * @public | ||
| */ | ||
| interface CliModule { | ||
| readonly $$type: '@backstage/CliModule'; | ||
| } | ||
| /** | ||
| * Creates a new CLI plugin that provides commands to the Backstage CLI. | ||
| * | ||
| * The `init` callback is invoked immediately at creation time and is used | ||
| * to register commands via the provided registry. The commands are then | ||
| * made available to the CLI host once the returned promise resolves. | ||
| * | ||
| * @example | ||
| * ``` | ||
| * import { createCliModule } from '@backstage/cli-node'; | ||
| * import packageJson from '../package.json'; | ||
| * | ||
| * export default createCliModule({ | ||
| * packageJson, | ||
| * init: async reg => { | ||
| * reg.addCommand({ | ||
| * path: ['repo', 'test'], | ||
| * description: 'Run tests across the repository', | ||
| * execute: { loader: () => import('./commands/test') }, | ||
| * }); | ||
| * }, | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @public | ||
| */ | ||
| declare function createCliModule(options: { | ||
| /** The `package.json` contents of the plugin package, used to identify the plugin. */ | ||
| packageJson: { | ||
| name: string; | ||
| }; | ||
| /** | ||
| * An initialization callback that registers commands with the CLI. | ||
| * Called immediately when the plugin is created. | ||
| */ | ||
| init: (registry: { | ||
| /** Registers a new command with the CLI. */ | ||
| addCommand: (command: CliCommand) => void; | ||
| }) => Promise<void>; | ||
| }): CliModule; | ||
| /** | ||
| * Runs a CLI module as a standalone program. | ||
| * | ||
| * This helper extracts the commands from a {@link CliModule} and exposes | ||
| * them as a fully functional CLI with help output and argument parsing. | ||
| * It is intended to be called from a module package's `bin` entry point | ||
| * so that the module can be executed directly without being wired into | ||
| * a larger CLI host. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * #!/usr/bin/env node | ||
| * import { runCliModule } from '@backstage/cli-node'; | ||
| * import cliModule from './index'; | ||
| * | ||
| * runCliModule({ | ||
| * module: cliModule, | ||
| * name: 'backstage-auth', | ||
| * version: require('../package.json').version, | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @public | ||
| */ | ||
| declare function runCliModule(options: { | ||
| /** The CLI module to run. */ | ||
| module: CliModule; | ||
| /** The program name shown in help output and usage strings. */ | ||
| name: string; | ||
| /** The version string shown when `--version` is passed. */ | ||
| version?: string; | ||
| }): Promise<void>; | ||
| /** | ||
| * Options for {@link runConcurrentTasks}. | ||
@@ -119,3 +342,3 @@ * | ||
| */ | ||
| type PackageRole = 'frontend' | 'backend' | 'cli' | 'web-library' | 'node-library' | 'common-library' | 'frontend-plugin' | 'frontend-plugin-module' | 'backend-plugin' | 'backend-plugin-module'; | ||
| type PackageRole = 'frontend' | 'backend' | 'cli' | 'cli-module' | 'web-library' | 'node-library' | 'common-library' | 'frontend-plugin' | 'frontend-plugin-module' | 'backend-plugin' | 'backend-plugin-module'; | ||
| /** | ||
@@ -412,3 +635,3 @@ * A type of platform that a package can be built for. | ||
| export { GitUtils, Lockfile, PackageGraph, PackageRoles, SuccessCache, hasBackstageYarnPlugin, isMonoRepo, packageFeatureType, runConcurrentTasks, runWorkerQueueThreads }; | ||
| export type { BackstagePackage, BackstagePackageFeatureType, BackstagePackageJson, ConcurrentTasksOptions, LockfileDiff, LockfileDiffEntry, LockfileQueryEntry, PackageGraphNode, PackageOutputType, PackagePlatform, PackageRole, PackageRoleInfo, WorkerQueueThreadsOptions }; | ||
| export { CliAuth, GitUtils, Lockfile, PackageGraph, PackageRoles, SuccessCache, createCliModule, hasBackstageYarnPlugin, isMonoRepo, packageFeatureType, runCliModule, runConcurrentTasks, runWorkerQueueThreads }; | ||
| export type { BackstagePackage, BackstagePackageFeatureType, BackstagePackageJson, CliAuthCreateOptions, CliCommand, CliCommandContext, CliModule, ConcurrentTasksOptions, LockfileDiff, LockfileDiffEntry, LockfileQueryEntry, PackageGraphNode, PackageOutputType, PackagePlatform, PackageRole, PackageRoleInfo, WorkerQueueThreadsOptions }; |
| 'use strict'; | ||
| var z = require('zod'); | ||
| var v3 = require('zod/v3'); | ||
@@ -22,2 +22,7 @@ const packageRoleInfos = [ | ||
| { | ||
| role: "cli-module", | ||
| platform: "node", | ||
| output: ["types", "cjs"] | ||
| }, | ||
| { | ||
| role: "web-library", | ||
@@ -64,22 +69,22 @@ platform: "web", | ||
| ]; | ||
| const readSchema = z.z.object({ | ||
| name: z.z.string().optional(), | ||
| backstage: z.z.object({ | ||
| role: z.z.string().optional() | ||
| const readSchema = v3.z.object({ | ||
| name: v3.z.string().optional(), | ||
| backstage: v3.z.object({ | ||
| role: v3.z.string().optional() | ||
| }).optional() | ||
| }); | ||
| const detectionSchema = z.z.object({ | ||
| name: z.z.string().optional(), | ||
| scripts: z.z.object({ | ||
| start: z.z.string().optional(), | ||
| build: z.z.string().optional() | ||
| const detectionSchema = v3.z.object({ | ||
| name: v3.z.string().optional(), | ||
| scripts: v3.z.object({ | ||
| start: v3.z.string().optional(), | ||
| build: v3.z.string().optional() | ||
| }).optional(), | ||
| publishConfig: z.z.object({ | ||
| main: z.z.string().optional(), | ||
| types: z.z.string().optional(), | ||
| module: z.z.string().optional() | ||
| publishConfig: v3.z.object({ | ||
| main: v3.z.string().optional(), | ||
| types: v3.z.string().optional(), | ||
| module: v3.z.string().optional() | ||
| }).optional(), | ||
| main: z.z.string().optional(), | ||
| types: z.z.string().optional(), | ||
| module: z.z.string().optional() | ||
| main: v3.z.string().optional(), | ||
| types: v3.z.string().optional(), | ||
| module: v3.z.string().optional() | ||
| }); | ||
@@ -86,0 +91,0 @@ class PackageRoles { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"PackageRoles.cjs.js","sources":["../../src/roles/PackageRoles.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { z } from 'zod';\nimport { PackageRole, PackageRoleInfo } from './types';\n\nconst packageRoleInfos: PackageRoleInfo[] = [\n {\n role: 'frontend',\n platform: 'web',\n output: ['bundle'],\n },\n {\n role: 'backend',\n platform: 'node',\n output: ['bundle'],\n },\n {\n role: 'cli',\n platform: 'node',\n output: ['cjs'],\n },\n {\n role: 'web-library',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'node-library',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n {\n role: 'common-library',\n platform: 'common',\n output: ['types', 'esm', 'cjs'],\n },\n {\n role: 'frontend-plugin',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'frontend-plugin-module',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'frontend-dynamic-container' as PackageRole, // experimental\n platform: 'web',\n output: ['bundle'],\n },\n {\n role: 'backend-plugin',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n {\n role: 'backend-plugin-module',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n];\n\nconst readSchema = z.object({\n name: z.string().optional(),\n backstage: z\n .object({\n role: z.string().optional(),\n })\n .optional(),\n});\n\nconst detectionSchema = z.object({\n name: z.string().optional(),\n scripts: z\n .object({\n start: z.string().optional(),\n build: z.string().optional(),\n })\n .optional(),\n publishConfig: z\n .object({\n main: z.string().optional(),\n types: z.string().optional(),\n module: z.string().optional(),\n })\n .optional(),\n main: z.string().optional(),\n types: z.string().optional(),\n module: z.string().optional(),\n});\n\n/**\n * Utilities for working with Backstage package roles.\n *\n * @public\n */\nexport class PackageRoles {\n /**\n * Get the associated info for a package role.\n */\n static getRoleInfo(role: string): PackageRoleInfo {\n const roleInfo = packageRoleInfos.find(r => r.role === role);\n if (!roleInfo) {\n throw new Error(`Unknown package role '${role}'`);\n }\n return roleInfo;\n }\n\n /**\n * Given package JSON data, get the package role.\n */\n static getRoleFromPackage(pkgJson: unknown): PackageRole | undefined {\n const pkg = readSchema.parse(pkgJson);\n\n if (pkg.backstage) {\n const { role } = pkg.backstage;\n if (!role) {\n throw new Error(\n `Package ${pkg.name} must specify a role in the \"backstage\" field`,\n );\n }\n\n return this.getRoleInfo(role).role;\n }\n\n return undefined;\n }\n\n /**\n * Attempt to detect the role of a package from its package.json.\n */\n static detectRoleFromPackage(pkgJson: unknown): PackageRole | undefined {\n const pkg = detectionSchema.parse(pkgJson);\n\n if (pkg.scripts?.start?.includes('app:serve')) {\n return 'frontend';\n }\n if (pkg.scripts?.build?.includes('backend:bundle')) {\n return 'backend';\n }\n if (\n pkg.name?.includes('plugin-') &&\n pkg.name?.includes('-backend-module-')\n ) {\n return 'backend-plugin-module';\n }\n if (pkg.name?.includes('plugin-') && pkg.name?.includes('-module-')) {\n return 'frontend-plugin-module';\n }\n if (pkg.scripts?.start?.includes('plugin:serve')) {\n return 'frontend-plugin';\n }\n if (pkg.scripts?.start?.includes('backend:dev')) {\n return 'backend-plugin';\n }\n\n const mainEntry = pkg.publishConfig?.main || pkg.main;\n const moduleEntry = pkg.publishConfig?.module || pkg.module;\n const typesEntry = pkg.publishConfig?.types || pkg.types;\n if (typesEntry) {\n if (mainEntry && moduleEntry) {\n return 'common-library';\n }\n if (moduleEntry || mainEntry?.endsWith('.esm.js')) {\n return 'web-library';\n }\n if (mainEntry) {\n return 'node-library';\n }\n } else if (mainEntry) {\n return 'cli';\n }\n\n return undefined;\n }\n}\n"],"names":["z"],"mappings":";;;;AAmBA,MAAM,gBAAA,GAAsC;AAAA,EAC1C;AAAA,IACE,IAAA,EAAM,UAAA;AAAA,IACN,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,CAAC,QAAQ;AAAA,GACnB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,SAAA;AAAA,IACN,QAAA,EAAU,MAAA;AAAA,IACV,MAAA,EAAQ,CAAC,QAAQ;AAAA,GACnB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,KAAA;AAAA,IACN,QAAA,EAAU,MAAA;AAAA,IACV,MAAA,EAAQ,CAAC,KAAK;AAAA,GAChB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,aAAA;AAAA,IACN,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA,GACzB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,cAAA;AAAA,IACN,QAAA,EAAU,MAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA,GACzB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,gBAAA;AAAA,IACN,QAAA,EAAU,QAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAA,EAAO,KAAK;AAAA,GAChC;AAAA,EACA;AAAA,IACE,IAAA,EAAM,iBAAA;AAAA,IACN,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA,GACzB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,wBAAA;AAAA,IACN,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA,GACzB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,4BAAA;AAAA;AAAA,IACN,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,CAAC,QAAQ;AAAA,GACnB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,gBAAA;AAAA,IACN,QAAA,EAAU,MAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA,GACzB;AAAA,EACA;AAAA,IACE,IAAA,EAAM,uBAAA;AAAA,IACN,QAAA,EAAU,MAAA;AAAA,IACV,MAAA,EAAQ,CAAC,OAAA,EAAS,KAAK;AAAA;AAE3B,CAAA;AAEA,MAAM,UAAA,GAAaA,IAAE,MAAA,CAAO;AAAA,EAC1B,IAAA,EAAMA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,SAAA,EAAWA,IACR,MAAA,CAAO;AAAA,IACN,IAAA,EAAMA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAAS,GAC3B,EACA,QAAA;AACL,CAAC,CAAA;AAED,MAAM,eAAA,GAAkBA,IAAE,MAAA,CAAO;AAAA,EAC/B,IAAA,EAAMA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,OAAA,EAASA,IACN,MAAA,CAAO;AAAA,IACN,KAAA,EAAOA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC3B,KAAA,EAAOA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAAS,GAC5B,EACA,QAAA,EAAS;AAAA,EACZ,aAAA,EAAeA,IACZ,MAAA,CAAO;AAAA,IACN,IAAA,EAAMA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC1B,KAAA,EAAOA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC3B,MAAA,EAAQA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAAS,GAC7B,EACA,QAAA,EAAS;AAAA,EACZ,IAAA,EAAMA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,KAAA,EAAOA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC3B,MAAA,EAAQA,GAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AACrB,CAAC,CAAA;AAOM,MAAM,YAAA,CAAa;AAAA;AAAA;AAAA;AAAA,EAIxB,OAAO,YAAY,IAAA,EAA+B;AAChD,IAAA,MAAM,WAAW,gBAAA,CAAiB,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,IAAI,CAAA;AAC3D,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,IAClD;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,mBAAmB,OAAA,EAA2C;AACnE,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,KAAA,CAAM,OAAO,CAAA;AAEpC,IAAA,IAAI,IAAI,SAAA,EAAW;AACjB,MAAA,MAAM,EAAE,IAAA,EAAK,GAAI,GAAA,CAAI,SAAA;AACrB,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,QAAA,EAAW,IAAI,IAAI,CAAA,6CAAA;AAAA,SACrB;AAAA,MACF;AAEA,MAAA,OAAO,IAAA,CAAK,WAAA,CAAY,IAAI,CAAA,CAAE,IAAA;AAAA,IAChC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,sBAAsB,OAAA,EAA2C;AACtE,IAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,KAAA,CAAM,OAAO,CAAA;AAEzC,IAAA,IAAI,GAAA,CAAI,OAAA,EAAS,KAAA,EAAO,QAAA,CAAS,WAAW,CAAA,EAAG;AAC7C,MAAA,OAAO,UAAA;AAAA,IACT;AACA,IAAA,IAAI,GAAA,CAAI,OAAA,EAAS,KAAA,EAAO,QAAA,CAAS,gBAAgB,CAAA,EAAG;AAClD,MAAA,OAAO,SAAA;AAAA,IACT;AACA,IAAA,IACE,GAAA,CAAI,MAAM,QAAA,CAAS,SAAS,KAC5B,GAAA,CAAI,IAAA,EAAM,QAAA,CAAS,kBAAkB,CAAA,EACrC;AACA,MAAA,OAAO,uBAAA;AAAA,IACT;AACA,IAAA,IAAI,GAAA,CAAI,MAAM,QAAA,CAAS,SAAS,KAAK,GAAA,CAAI,IAAA,EAAM,QAAA,CAAS,UAAU,CAAA,EAAG;AACnE,MAAA,OAAO,wBAAA;AAAA,IACT;AACA,IAAA,IAAI,GAAA,CAAI,OAAA,EAAS,KAAA,EAAO,QAAA,CAAS,cAAc,CAAA,EAAG;AAChD,MAAA,OAAO,iBAAA;AAAA,IACT;AACA,IAAA,IAAI,GAAA,CAAI,OAAA,EAAS,KAAA,EAAO,QAAA,CAAS,aAAa,CAAA,EAAG;AAC/C,MAAA,OAAO,gBAAA;AAAA,IACT;AAEA,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,aAAA,EAAe,IAAA,IAAQ,GAAA,CAAI,IAAA;AACjD,IAAA,MAAM,WAAA,GAAc,GAAA,CAAI,aAAA,EAAe,MAAA,IAAU,GAAA,CAAI,MAAA;AACrD,IAAA,MAAM,UAAA,GAAa,GAAA,CAAI,aAAA,EAAe,KAAA,IAAS,GAAA,CAAI,KAAA;AACnD,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,IAAI,aAAa,WAAA,EAAa;AAC5B,QAAA,OAAO,gBAAA;AAAA,MACT;AACA,MAAA,IAAI,WAAA,IAAe,SAAA,EAAW,QAAA,CAAS,SAAS,CAAA,EAAG;AACjD,QAAA,OAAO,aAAA;AAAA,MACT;AACA,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,OAAO,cAAA;AAAA,MACT;AAAA,IACF,WAAW,SAAA,EAAW;AACpB,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;"} | ||
| {"version":3,"file":"PackageRoles.cjs.js","sources":["../../src/roles/PackageRoles.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { z } from 'zod/v3';\nimport { PackageRole, PackageRoleInfo } from './types';\n\nconst packageRoleInfos: PackageRoleInfo[] = [\n {\n role: 'frontend',\n platform: 'web',\n output: ['bundle'],\n },\n {\n role: 'backend',\n platform: 'node',\n output: ['bundle'],\n },\n {\n role: 'cli',\n platform: 'node',\n output: ['cjs'],\n },\n {\n role: 'cli-module',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n {\n role: 'web-library',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'node-library',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n {\n role: 'common-library',\n platform: 'common',\n output: ['types', 'esm', 'cjs'],\n },\n {\n role: 'frontend-plugin',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'frontend-plugin-module',\n platform: 'web',\n output: ['types', 'esm'],\n },\n {\n role: 'frontend-dynamic-container' as PackageRole, // experimental\n platform: 'web',\n output: ['bundle'],\n },\n {\n role: 'backend-plugin',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n {\n role: 'backend-plugin-module',\n platform: 'node',\n output: ['types', 'cjs'],\n },\n];\n\nconst readSchema = z.object({\n name: z.string().optional(),\n backstage: z\n .object({\n role: z.string().optional(),\n })\n .optional(),\n});\n\nconst detectionSchema = z.object({\n name: z.string().optional(),\n scripts: z\n .object({\n start: z.string().optional(),\n build: z.string().optional(),\n })\n .optional(),\n publishConfig: z\n .object({\n main: z.string().optional(),\n types: z.string().optional(),\n module: z.string().optional(),\n })\n .optional(),\n main: z.string().optional(),\n types: z.string().optional(),\n module: z.string().optional(),\n});\n\n/**\n * Utilities for working with Backstage package roles.\n *\n * @public\n */\nexport class PackageRoles {\n /**\n * Get the associated info for a package role.\n */\n static getRoleInfo(role: string): PackageRoleInfo {\n const roleInfo = packageRoleInfos.find(r => r.role === role);\n if (!roleInfo) {\n throw new Error(`Unknown package role '${role}'`);\n }\n return roleInfo;\n }\n\n /**\n * Given package JSON data, get the package role.\n */\n static getRoleFromPackage(pkgJson: unknown): PackageRole | undefined {\n const pkg = readSchema.parse(pkgJson);\n\n if (pkg.backstage) {\n const { role } = pkg.backstage;\n if (!role) {\n throw new Error(\n `Package ${pkg.name} must specify a role in the \"backstage\" field`,\n );\n }\n\n return this.getRoleInfo(role).role;\n }\n\n return undefined;\n }\n\n /**\n * Attempt to detect the role of a package from its package.json.\n */\n static detectRoleFromPackage(pkgJson: unknown): PackageRole | undefined {\n const pkg = detectionSchema.parse(pkgJson);\n\n if (pkg.scripts?.start?.includes('app:serve')) {\n return 'frontend';\n }\n if (pkg.scripts?.build?.includes('backend:bundle')) {\n return 'backend';\n }\n if (\n pkg.name?.includes('plugin-') &&\n pkg.name?.includes('-backend-module-')\n ) {\n return 'backend-plugin-module';\n }\n if (pkg.name?.includes('plugin-') && pkg.name?.includes('-module-')) {\n return 'frontend-plugin-module';\n }\n if (pkg.scripts?.start?.includes('plugin:serve')) {\n return 'frontend-plugin';\n }\n if (pkg.scripts?.start?.includes('backend:dev')) {\n return 'backend-plugin';\n }\n\n const mainEntry = pkg.publishConfig?.main || pkg.main;\n const moduleEntry = pkg.publishConfig?.module || pkg.module;\n const typesEntry = pkg.publishConfig?.types || pkg.types;\n if (typesEntry) {\n if (mainEntry && moduleEntry) {\n return 'common-library';\n }\n if (moduleEntry || mainEntry?.endsWith('.esm.js')) {\n return 'web-library';\n }\n if (mainEntry) {\n return 'node-library';\n }\n } else if (mainEntry) {\n return 'cli';\n }\n\n return undefined;\n }\n}\n"],"names":["z"],"mappings":"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"} |
@@ -5,4 +5,4 @@ 'use strict'; | ||
| var path = require('node:path'); | ||
| var yaml = require('yaml'); | ||
| var z = require('zod'); | ||
| var YAML = require('yaml'); | ||
| var v3 = require('zod/v3'); | ||
| var cliCommon = require('@backstage/cli-common'); | ||
@@ -13,9 +13,8 @@ | ||
| var fs__default = /*#__PURE__*/_interopDefaultCompat(fs); | ||
| var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml); | ||
| var z__default = /*#__PURE__*/_interopDefaultCompat(z); | ||
| var YAML__default = /*#__PURE__*/_interopDefaultCompat(YAML); | ||
| const yarnRcSchema = z__default.default.object({ | ||
| plugins: z__default.default.array( | ||
| z__default.default.object({ | ||
| path: z__default.default.string() | ||
| const yarnRcSchema = v3.z.object({ | ||
| plugins: v3.z.array( | ||
| v3.z.object({ | ||
| path: v3.z.string() | ||
| }) | ||
@@ -38,3 +37,3 @@ ).optional() | ||
| } | ||
| const parseResult = yarnRcSchema.safeParse(yaml__default.default.parse(yarnRcContent)); | ||
| const parseResult = yarnRcSchema.safeParse(YAML__default.default.parse(yarnRcContent)); | ||
| if (!parseResult.success) { | ||
@@ -41,0 +40,0 @@ throw new Error( |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"yarnPlugin.cjs.js","sources":["../../src/yarn/yarnPlugin.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport { resolve as resolvePath } from 'node:path';\nimport yaml from 'yaml';\nimport z from 'zod';\nimport { targetPaths } from '@backstage/cli-common';\n\nconst yarnRcSchema = z.object({\n plugins: z\n .array(\n z.object({\n path: z.string(),\n }),\n )\n .optional(),\n});\n\n/**\n * Detects whether the Backstage Yarn plugin is installed in the given workspace directory.\n *\n * @param workspaceDir - The workspace root directory to check. Defaults to the target root.\n * @returns Promise resolving to true if the plugin is installed, false otherwise\n * @public\n */\nexport async function hasBackstageYarnPlugin(\n workspaceDir?: string,\n): Promise<boolean> {\n const yarnRcPath = resolvePath(\n workspaceDir ?? targetPaths.rootDir,\n '.yarnrc.yml',\n );\n const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => {\n if (e.code === 'ENOENT') {\n return '';\n }\n throw e;\n });\n\n if (!yarnRcContent) {\n return false;\n }\n\n const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent));\n\n if (!parseResult.success) {\n throw new Error(\n `Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`,\n );\n }\n\n const yarnRc = parseResult.data;\n\n const backstagePlugin = yarnRc.plugins?.some(\n plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs',\n );\n\n return Boolean(backstagePlugin);\n}\n"],"names":["z","resolvePath","targetPaths","fs","yaml"],"mappings":";;;;;;;;;;;;;;AAsBA,MAAM,YAAA,GAAeA,mBAAE,MAAA,CAAO;AAAA,EAC5B,SAASA,kBAAA,CACN,KAAA;AAAA,IACCA,mBAAE,MAAA,CAAO;AAAA,MACP,IAAA,EAAMA,mBAAE,MAAA;AAAO,KAChB;AAAA,IAEF,QAAA;AACL,CAAC,CAAA;AASD,eAAsB,uBACpB,YAAA,EACkB;AAClB,EAAA,MAAM,UAAA,GAAaC,YAAA;AAAA,IACjB,gBAAgBC,qBAAA,CAAY,OAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,aAAA,GAAgB,MAAMC,mBAAA,CAAG,QAAA,CAAS,YAAY,OAAO,CAAA,CAAE,MAAM,CAAA,CAAA,KAAK;AACtE,IAAA,IAAI,CAAA,CAAE,SAAS,QAAA,EAAU;AACvB,MAAA,OAAO,EAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAA;AAAA,EACR,CAAC,CAAA;AAED,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAc,YAAA,CAAa,SAAA,CAAUC,qBAAA,CAAK,KAAA,CAAM,aAAa,CAAC,CAAA;AAEpE,EAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,WAAA,CAAY,KAAA,CAAM,QAAA,EAAU,CAAA;AAAA,KACpE;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,WAAA,CAAY,IAAA;AAE3B,EAAA,MAAM,eAAA,GAAkB,OAAO,OAAA,EAAS,IAAA;AAAA,IACtC,CAAA,MAAA,KAAU,OAAO,IAAA,KAAS;AAAA,GAC5B;AAEA,EAAA,OAAO,QAAQ,eAAe,CAAA;AAChC;;;;"} | ||
| {"version":3,"file":"yarnPlugin.cjs.js","sources":["../../src/yarn/yarnPlugin.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport { resolve as resolvePath } from 'node:path';\nimport yaml from 'yaml';\nimport { z } from 'zod/v3';\nimport { targetPaths } from '@backstage/cli-common';\n\nconst yarnRcSchema = z.object({\n plugins: z\n .array(\n z.object({\n path: z.string(),\n }),\n )\n .optional(),\n});\n\n/**\n * Detects whether the Backstage Yarn plugin is installed in the given workspace directory.\n *\n * @param workspaceDir - The workspace root directory to check. Defaults to the target root.\n * @returns Promise resolving to true if the plugin is installed, false otherwise\n * @public\n */\nexport async function hasBackstageYarnPlugin(\n workspaceDir?: string,\n): Promise<boolean> {\n const yarnRcPath = resolvePath(\n workspaceDir ?? targetPaths.rootDir,\n '.yarnrc.yml',\n );\n const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => {\n if (e.code === 'ENOENT') {\n return '';\n }\n throw e;\n });\n\n if (!yarnRcContent) {\n return false;\n }\n\n const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent));\n\n if (!parseResult.success) {\n throw new Error(\n `Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`,\n );\n }\n\n const yarnRc = parseResult.data;\n\n const backstagePlugin = yarnRc.plugins?.some(\n plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs',\n );\n\n return Boolean(backstagePlugin);\n}\n"],"names":["z","resolvePath","targetPaths","fs","yaml"],"mappings":";;;;;;;;;;;;;AAsBA,MAAM,YAAA,GAAeA,KAAE,MAAA,CAAO;AAAA,EAC5B,SAASA,IAAA,CACN,KAAA;AAAA,IACCA,KAAE,MAAA,CAAO;AAAA,MACP,IAAA,EAAMA,KAAE,MAAA;AAAO,KAChB;AAAA,IAEF,QAAA;AACL,CAAC,CAAA;AASD,eAAsB,uBACpB,YAAA,EACkB;AAClB,EAAA,MAAM,UAAA,GAAaC,YAAA;AAAA,IACjB,gBAAgBC,qBAAA,CAAY,OAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,aAAA,GAAgB,MAAMC,mBAAA,CAAG,QAAA,CAAS,YAAY,OAAO,CAAA,CAAE,MAAM,CAAA,CAAA,KAAK;AACtE,IAAA,IAAI,CAAA,CAAE,SAAS,QAAA,EAAU;AACvB,MAAA,OAAO,EAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAA;AAAA,EACR,CAAC,CAAA;AAED,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAc,YAAA,CAAa,SAAA,CAAUC,qBAAA,CAAK,KAAA,CAAM,aAAa,CAAC,CAAA;AAEpE,EAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,WAAA,CAAY,KAAA,CAAM,QAAA,EAAU,CAAA;AAAA,KACpE;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,WAAA,CAAY,IAAA;AAE3B,EAAA,MAAM,eAAA,GAAkB,OAAO,OAAA,EAAS,IAAA;AAAA,IACtC,CAAA,MAAA,KAAU,OAAO,IAAA,KAAS;AAAA,GAC5B;AAEA,EAAA,OAAO,QAAQ,eAAe,CAAA;AAChC;;;;"} |
+26
-9
| { | ||
| "name": "@backstage/cli-node", | ||
| "version": "0.2.19-next.1", | ||
| "version": "0.3.0", | ||
| "description": "Node.js library for Backstage CLIs", | ||
@@ -23,3 +23,4 @@ "backstage": { | ||
| "files": [ | ||
| "dist" | ||
| "dist", | ||
| "config" | ||
| ], | ||
@@ -35,19 +36,35 @@ "scripts": { | ||
| "dependencies": { | ||
| "@backstage/cli-common": "0.2.0-next.1", | ||
| "@backstage/errors": "1.2.7", | ||
| "@backstage/types": "1.2.2", | ||
| "@backstage/cli-common": "^0.2.0", | ||
| "@backstage/errors": "^1.2.7", | ||
| "@backstage/types": "^1.2.2", | ||
| "@manypkg/get-packages": "^1.1.3", | ||
| "@yarnpkg/lockfile": "^1.1.0", | ||
| "@yarnpkg/parsers": "^3.0.0", | ||
| "chalk": "^4.0.0", | ||
| "commander": "^12.0.0", | ||
| "fs-extra": "^11.2.0", | ||
| "pirates": "^4.0.6", | ||
| "proper-lockfile": "^4.1.2", | ||
| "semver": "^7.5.3", | ||
| "yaml": "^2.0.0", | ||
| "zod": "^3.25.76" | ||
| "zod": "^3.25.76 || ^4.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@backstage/backend-test-utils": "1.11.1-next.1", | ||
| "@backstage/cli": "0.36.0-next.1", | ||
| "@backstage/test-utils": "1.7.16-next.0", | ||
| "@backstage/backend-test-utils": "^1.11.1", | ||
| "@backstage/cli": "^0.36.0", | ||
| "@backstage/test-utils": "^1.7.16", | ||
| "@types/proper-lockfile": "^4", | ||
| "@types/yarnpkg__lockfile": "^1.1.4" | ||
| }, | ||
| "optionalDependencies": { | ||
| "keytar": "^7.9.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "@swc/core": "^1.15.6" | ||
| }, | ||
| "peerDependenciesMeta": { | ||
| "@swc/core": { | ||
| "optional": true | ||
| } | ||
| }, | ||
| "typesVersions": { | ||
@@ -54,0 +71,0 @@ "*": { |
Network access
Supply chain riskThis module accesses the network.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
239421
77.37%50
92.31%2617
94.57%16
60%5
25%15
87.5%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated