@salesforce/packaging
Version:
Packaging library for the Salesforce packaging platform
558 lines • 22.9 kB
JavaScript
"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