UNPKG

@salesforce/packaging

Version:

Packaging library for the Salesforce packaging platform

558 lines 22.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PackageAncestryNode = exports.AncestryDotProducer = exports.AncestryJsonProducer = exports.AncestryTreeProducer = exports.PackageAncestry = void 0; /* * Copyright (c) 2022, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ const node_os_1 = require("node:os"); const core_1 = require("@salesforce/core"); const graphology_1 = require("graphology"); const kit_1 = require("@salesforce/kit"); const graphology_traversal_1 = require("graphology-traversal"); // @ts-expect-error because object-treeify v2 is not typed const object_treeify_1 = __importDefault(require("object-treeify")); const pkgUtils = __importStar(require("../utils/packageUtils")); const packageVersion_1 = require("./packageVersion"); const package_1 = require("./package"); const versionNumber_1 = require("./versionNumber"); core_1.Messages.importMessagesDirectory(__dirname); const messages = core_1.Messages.loadMessages('@salesforce/packaging', 'package_ancestry'); const SELECT_PACKAGE_VERSION = 'SELECT AncestorId, SubscriberPackageVersionId, MajorVersion, MinorVersion, PatchVersion, BuildNumber FROM Package2Version'; // Add this to query calls to only show released package versions in the output const releasedOnlyFilter = ' AND IsReleased = true'; const sortAncestryNodeData = (a, b) => { if (!a.options || !b.options) { return 0; } if (!a.options.packageNode && b.options.packageNode) { return -1; } if (a.options.packageNode && !b.options.packageNode) { return 1; } if (a.options.packageNode && b.options.packageNode) { const aVersion = new versionNumber_1.VersionNumber(a.options.packageNode.MajorVersion, a.options.packageNode.MinorVersion, a.options.packageNode.PatchVersion, a.options.packageNode.BuildNumber); const bVersion = new versionNumber_1.VersionNumber(b.options.packageNode.MajorVersion, b.options.packageNode.MinorVersion, b.options.packageNode.PatchVersion, b.options.packageNode.BuildNumber); return aVersion.compareTo(bVersion); } return 0; }; /** * A class that represents the package ancestry graph. * Given a package Id (0Ho) or a package version Id (04t), it will build a graph of the package's ancestors. */ class PackageAncestry extends kit_1.AsyncCreatable { options; roots = []; graph = new graphology_1.DirectedGraph(); packageId; constructor(options) { super(options); this.options = options; this.packageId = options.packageId; } get requestedPackageId() { return this.packageId; } static createAttributes(node) { return { AncestorId: node.AncestorId, BuildNumber: node.BuildNumber, MajorVersion: node.MajorVersion, MinorVersion: node.MinorVersion, PatchVersion: node.PatchVersion, SubscriberPackageVersionId: node.SubscriberPackageVersionId, depthCounter: node.depthCounter, node, }; } async init() { await this.buildAncestryTree(); } /** * Returns the internal representation of the requested package ancestry graph. */ getAncestryGraph() { return this.graph; } /** * Convenience method to get the json representation of the package ancestry graph. */ getJsonProducer() { return this.getRepresentationProducer((opts) => new AncestryJsonProducer(opts), this.requestedPackageId); } /** * Convenience method to get the CliUx.Tree representation of the package ancestry graph. */ getTreeProducer(verbose) { return this.getRepresentationProducer((opts) => new AncestryTreeProducer({ ...opts, verbose }), this.requestedPackageId); } /** * Convenience method to get the dot representation of the package ancestry graph. */ getDotProducer() { return this.getRepresentationProducer((opts) => new AncestryDotProducer(opts), this.requestedPackageId); } /** * Returns the producer representation of the package ancestry graph. * * @param producerCtor - function that returns a new instance of the producer * @param rootPackageId - the subscriber package version id of the root node */ getRepresentationProducer(producerCtor, rootPackageId) { const treeRootKey = rootPackageId ? this.graph.findNode((node, attributes) => attributes.SubscriberPackageVersionId === rootPackageId) : undefined; const treeRoot = treeRootKey ? this.graph.getNodeAttributes(treeRootKey).node : undefined; const tree = producerCtor({ packageNode: treeRoot, depth: 0 }); const treeStack = []; function handleNode(node, attr, depth) { if (treeStack.length > depth) { treeStack.splice(depth); } let t = treeStack[depth]; if (!t) { t = producerCtor({ packageNode: attr.node, depth }); treeStack.push(t); } if (depth === 0) { tree.addNode(t); } else { treeStack[depth - 1].addNode(t); } } if (treeRoot) { (0, graphology_traversal_1.dfsFromNode)(this.graph, treeRoot.getVersion(), handleNode); } else { (0, graphology_traversal_1.dfs)(this.graph, handleNode); } return tree; } /** * Returns a list of ancestry nodes that represent the path from subscriber package version id to the root of the * package ancestry tree. * * @param subscriberPackageVersionId */ getLeafPathToRoot(subscriberPackageVersionId) { const root = this.graph.findNode((node, attributes) => attributes.AncestorId === null); const paths = []; let path = []; let previousDepth = 0; (0, graphology_traversal_1.dfsFromNode)(this.graph, root, (node, attr, depth) => { if (depth === 0) { paths.push(path); path = []; } else if (depth <= previousDepth) { paths.push(path); path = path.slice(0, depth); } previousDepth = depth; path.push(attr.node); }); // push remaining path paths.push(path); const filteredPaths = paths.filter((nodePath) => nodePath.length > 0 && // don't care about zero length paths (!subscriberPackageVersionId || nodePath.some((node) => node.SubscriberPackageVersionId === subscriberPackageVersionId))); return filteredPaths .map((nodePath) => nodePath.reverse()) .map((nodePath) => { const subscriberPackageVersionIdIndex = nodePath.findIndex((node) => node.SubscriberPackageVersionId === subscriberPackageVersionId); return nodePath.slice(subscriberPackageVersionIdIndex === -1 ? 0 : subscriberPackageVersionIdIndex); }); } async buildAncestryTree() { this.roots = await this.getRootsFromRequestedId(); await this.buildAncestryTreeFromRoots(this.roots); } async getRootsFromRequestedId() { let roots = []; this.packageId = this.options.project ? this.options.project.getPackageIdFromAlias(this.options.packageId) ?? this.options.packageId : this.options.packageId; switch (this.requestedPackageId?.slice(0, 3)) { case '0Ho': pkgUtils.validateId(pkgUtils.BY_LABEL.PACKAGE_ID, this.requestedPackageId); roots = await this.findRootsForPackage(); break; case '04t': pkgUtils.validateId(pkgUtils.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID, this.requestedPackageId); roots = await this.findRootsForPackageVersion(); break; default: throw messages.createError('idOrAliasNotFound', [this.requestedPackageId]); } await this.validatePackageType(); return roots; } async findRootsForPackageVersion() { // Start with the node, and shoot up let node = await this.getPackageVersion(this.requestedPackageId); while (node.AncestorId !== null) { // eslint-disable-next-line no-await-in-loop const ancestor = await this.getPackageVersion(node.AncestorId); this.addToGraph(ancestor, node); node = ancestor; } return [node]; } async validatePackageType() { // Check to see if the package version is part of an unlocked package // if so, throw an error since ancestry only applies to managed packages let packageType; if (this.requestedPackageId) { const prefix = this.requestedPackageId?.slice(0, 3); switch (prefix) { case '04t': // eslint-disable-next-line no-case-declarations const packageVersion = new packageVersion_1.PackageVersion({ idOrAlias: this.requestedPackageId, project: this.options.project, connection: this.options.connection, }); packageType = await packageVersion.getPackageType(); break; case '0Ho': // eslint-disable-next-line no-case-declarations const pkg = new package_1.Package({ packageAliasOrId: this.requestedPackageId, project: this.options.project, connection: this.options.connection, }); packageType = await pkg.getType(); break; default: throw new Error(`Not implemented yet: ${prefix} case`); } } else { throw new Error('Not implemented yet: undefined case'); } if (packageType !== 'Managed') { throw messages.createError('unlockedPackageError'); } } async getPackageVersion(nodeId) { if (!nodeId) { throw new Error('nodeId is undefined'); } const query = `${SELECT_PACKAGE_VERSION} WHERE SubscriberPackageVersionId = '${nodeId}'`; try { const results = await this.options.connection.singleRecordQuery(query, { tooling: true, }); return new PackageAncestryNode(results); } catch (err) { if (err instanceof Error && err.message.includes('No record found for')) { throw messages.createError('versionNotFound', [nodeId]); } throw err; } } async findRootsForPackage() { // Check to see if the package is an unlocked package // if so, throw and error since ancestry only applies to managed packages await this.validatePackageType(); const normalQuery = `${SELECT_PACKAGE_VERSION} WHERE AncestorId = NULL AND Package2Id = '${this.requestedPackageId ?? ''}' ${releasedOnlyFilter}`; const subscriberPackageVersions = (await this.options.connection.tooling.query(normalQuery)).records?.map((record) => new PackageAncestryNode(record)); // The package exists, but there are no versions for the provided package if (subscriberPackageVersions.length === 0) { throw messages.createError('noVersionsError'); } return subscriberPackageVersions; } async buildAncestryTreeFromRoots(roots) { while (roots.length > 0) { const subscriberPackageVersion = roots.shift(); // eslint-disable-next-line no-await-in-loop if (subscriberPackageVersion) { // eslint-disable-next-line no-await-in-loop const descendants = await this.addDescendantsFromPackageVersion(subscriberPackageVersion); roots.push(...descendants); } } } async addDescendantsFromPackageVersion(subscriberPackageVersion) { const descendants = await this.getDescendants(subscriberPackageVersion); descendants.forEach((descendant) => this.addToGraph(subscriberPackageVersion, descendant)); return descendants; } addToGraph(ancestor, descendant) { if (!this.graph.hasNode(ancestor.getVersion())) { this.graph.addNode(ancestor.getVersion(), PackageAncestry.createAttributes(ancestor)); } if (!this.graph.hasNode(descendant.getVersion())) { this.graph.addNode(descendant.getVersion(), PackageAncestry.createAttributes(descendant)); } if (!this.graph.hasEdge(ancestor.getVersion(), descendant.getVersion())) { this.graph.addDirectedEdgeWithKey(`${ancestor.getVersion()}->${descendant.getVersion()}`, ancestor.getVersion(), descendant.getVersion(), { from: ancestor.getVersion(), to: descendant.getVersion(), }); } } async getDescendants(ancestor) { const query = `${SELECT_PACKAGE_VERSION} WHERE AncestorId = '${ancestor.SubscriberPackageVersionId}' ${releasedOnlyFilter}`; const results = await this.options.connection.tooling.query(query); return results.records.map((result) => new PackageAncestryNode(result)); } } exports.PackageAncestry = PackageAncestry; class Tree { nodes = {}; // eslint-disable-next-line no-console display(logger = console.log) { const addNodes = function (nodes) { const tree = {}; for (const p of Object.keys(nodes)) { tree[p] = addNodes(nodes[p].nodes); } return tree; }; const tree = addNodes(this.nodes); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call logger((0, object_treeify_1.default)(tree)); } insert(child, value = new Tree()) { this.nodes[child] = value; return this; } search(key) { for (const child of Object.keys(this.nodes)) { if (child === key) { return this.nodes[child]; } const c = this.nodes[child].search(key); if (c) return c; } } } class AncestryTreeProducer extends Tree { label; options; verbose; constructor(options) { super(); this.options = options; this.label = this.options?.packageNode?.getVersion() ?? 'root'; this.verbose = this.options?.verbose ?? false; } addNode(node) { const label = this.createLabel(node); this.insert(label, node); } produce() { const sortAncestors = (a, b) => { if (a.options?.packageNode?.version && b.options?.packageNode?.version) { return a.options?.packageNode.version?.compareTo(b.options?.packageNode.version); } return 0; }; const producers = []; producers.push(this); while (producers.length > 0) { const producer = producers.shift(); if (producer) { Object.values(producer.nodes ?? {}) // as AncestryTreeProducer is needed to sort and gain access producer functions. oclif Tree does not support generic for nodes .map((node) => node) .sort(sortAncestors) .forEach((child) => { delete producer.nodes[this.createLabel(child)]; producer.addNode(child); producers.push(child); }); } } this.display(this.options ? this.options['logger'] : undefined); } createLabel(node) { const subscriberId = this.verbose && node?.options?.packageNode?.SubscriberPackageVersionId ? ` (${node.options.packageNode.SubscriberPackageVersionId})` : ''; return node?.label ? `${node.label}${subscriberId}` : 'root'; } } exports.AncestryTreeProducer = AncestryTreeProducer; class AncestryJsonProducer { label; options; children = []; data; constructor(options) { this.options = options; this.label = this.options?.packageNode?.getVersion() ?? 'root'; this.data = { children: [], data: { SubscriberPackageVersionId: this.options.packageNode?.SubscriberPackageVersionId ?? 'unknown', MajorVersion: this.options.packageNode?.MajorVersion ?? 0, MinorVersion: this.options.packageNode?.MinorVersion ?? 0, PatchVersion: this.options.packageNode?.PatchVersion ?? 0, BuildNumber: this.options.packageNode?.BuildNumber ?? 0, depthCounter: this.options.depth, }, }; } addNode(node) { this.data.children.push(node.data); this.children.push(node); } produce() { const producers = []; producers.push(this); while (producers.length > 0) { const producer = producers.shift(); if (producer) { producer.children.sort(sortAncestryNodeData); producers.push(...producer.children); } } return this.data.children[0]; } } exports.AncestryJsonProducer = AncestryJsonProducer; class AncestryDotProducer { label; options; children = []; constructor(options) { this.options = options; this.label = this.options?.packageNode?.getVersion() ?? 'root'; } /** * Builds a node line in DOT, of the form nodeID [label="MAJOR.MINOR.PATCH"] * * @param currentNode */ static buildDotNode(currentNode) { const subscriberId = currentNode.options?.packageNode?.SubscriberPackageVersionId ?? 'unknown'; return `\t node${subscriberId} [label="${currentNode.label}"]`; } /** * Builds an edge line in DOT, of the form fromNode -- toNode * * @param fromNode * @param toNode */ static buildDotEdge(fromNode, toNode) { const fromSubscriberId = fromNode.options?.packageNode?.SubscriberPackageVersionId ?? 'unknown'; const toSubscriberId = toNode.options?.packageNode?.SubscriberPackageVersionId ?? 'unknown'; return `\t node${fromSubscriberId} -- node${toSubscriberId}`; } addNode(node) { this.children.push(node); } produce() { const producers = []; producers.push(this); const dotLines = []; while (producers.length > 0) { const producer = producers.shift(); if (producer) { if (producer.options) { dotLines.push(AncestryDotProducer.buildDotNode(producer)); } producer?.children.sort(sortAncestryNodeData); producers.push(...producer.children); } } producers.push(this); while (producers.length > 0) { const producer = producers.shift(); if (producer) { if (producer.options) { producer.children.forEach((child) => dotLines.push(AncestryDotProducer.buildDotEdge(producer, child))); } producers.push(...producer.children); } } return `strict graph G {${node_os_1.EOL}${dotLines.join(node_os_1.EOL)}${node_os_1.EOL}}`; } } exports.AncestryDotProducer = AncestryDotProducer; class PackageAncestryNode extends kit_1.AsyncCreatable { options; version; MajorVersion; MinorVersion; PatchVersion; BuildNumber; AncestorId; SubscriberPackageVersionId; depthCounter = 0; constructor(options) { super(options); this.options = options; this.version = new versionNumber_1.VersionNumber(this.options.MajorVersion, this.options.MinorVersion, this.options.PatchVersion, this.options.BuildNumber); this.AncestorId = this.options.AncestorId; this.SubscriberPackageVersionId = this.options.SubscriberPackageVersionId; this.MajorVersion = typeof this.options.MajorVersion === 'number' ? this.options.MajorVersion : parseInt(this.options.MajorVersion, 10); this.MinorVersion = typeof this.options.MinorVersion === 'number' ? this.options.MinorVersion : parseInt(this.options.MinorVersion, 10); this.PatchVersion = typeof this.options.PatchVersion === 'number' ? this.options.PatchVersion : parseInt(this.options?.PatchVersion, 10); this.BuildNumber = this.options?.BuildNumber; } getVersion() { return this.version.toString(); } // eslint-disable-next-line class-methods-use-this init() { return Promise.resolve(); } } exports.PackageAncestryNode = PackageAncestryNode; //# sourceMappingURL=packageAncestry.js.map