@salesforce/packaging
Version:
Packaging library for the Salesforce packaging platform
367 lines • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageDependencyNode = exports.DependencyDotProducer = exports.PackageVersionDependency = exports.VERSION_BEING_BUILT = 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 kit_1 = require("@salesforce/kit");
const versionNumber_1 = require("./versionNumber");
core_1.Messages.importMessagesDirectory(__dirname);
const messages = core_1.Messages.loadMessages('@salesforce/packaging', 'package_version_dependency');
exports.VERSION_BEING_BUILT = 'VERSION_BEING_BUILT';
const SELECT_PACKAGE_VERSION_DEPENDENCY = 'SELECT CalcTransitiveDependencies, DependencyGraphJson FROM Package2VersionCreateRequest';
class PackageVersionDependency extends kit_1.AsyncCreatable {
options;
connection;
project;
userPackageVersionId;
verbose;
edgeDirection;
resolvedPackageVersionId;
constructor(options) {
super(options);
this.options = options;
this.connection = options.connection;
this.project = options.project;
this.userPackageVersionId = options.packageVersionId;
this.verbose = options.verbose ?? false;
this.edgeDirection = options.edgeDirection ?? 'root-first';
this.resolvedPackageVersionId = '';
}
async init() {
await this.resolvePackageCreateRequestId();
}
/**
* Returns a DependencyDotProducer that can be used to produce a DOT code representation of the package dependency graph.
*/
async getDependencyDotProducer() {
const isValid = await this.validatePackageVersion();
if (!isValid) {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
const query = `${SELECT_PACKAGE_VERSION_DEPENDENCY} WHERE Id = '${this.resolvedPackageVersionId}'`;
const result = await this.connection.tooling.query(query);
const dependencyGraphJson = result.records[0].DependencyGraphJson;
// validatePackageVersion() should have already ensured this is not null
if (!dependencyGraphJson) {
throw messages.createError('invalidDependencyGraphError');
}
const producer = new DependencyDotProducer(this.connection, dependencyGraphJson, this.verbose, this.edgeDirection, this.resolvedPackageVersionId);
await producer.init();
return producer;
}
/**
* Resolves id input to a 08c. User can input a 08c or 04t.
* Currently a 04t is not supported in filtering the Package2VersionCreateRequest. So a user's input of 04t will be resolved to a 05i and then a 08c.
*/
async resolvePackageCreateRequestId() {
const versionId = this.project?.getPackageIdFromAlias(this.userPackageVersionId) ?? this.userPackageVersionId;
// User provided a Package2VersionCreateRequest ID (08c) and doesn't need to be resolved
if (versionId.startsWith('08c')) {
this.resolvedPackageVersionId = versionId;
}
// User provided a SubscriberPackageVersionId (04t) and needs to be resolved to a Package2VersionCreateRequest ID (08c)
else if (versionId.startsWith('04t')) {
const package2VersionId = await this.query05iFrom04t(versionId);
this.resolvedPackageVersionId = await this.query08cFrom05i(package2VersionId);
}
else {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
}
async query05iFrom04t(package04t) {
const query05i = `SELECT Id FROM Package2Version WHERE SubscriberPackageVersionId = '${package04t}'`;
const result05i = await this.connection.tooling.query(query05i);
if (result05i.records?.length !== 1) {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
return result05i.records[0].Id;
}
async query08cFrom05i(package05i) {
const query08c = `SELECT Id FROM Package2VersionCreateRequest WHERE Package2VersionId = '${package05i}'`;
const result08c = await this.connection.tooling.query(query08c);
if (result08c.records?.length !== 1) {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
return result08c.records[0].Id;
}
/**
* Checks that the given Package2VersionCreateRequest ID (08c) contains a valid DependencyGraphJson to generate DOT code
*/
async validatePackageVersion() {
if (!this.resolvedPackageVersionId) {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
if (await this.verifyTransitiveDependenciesCalculated()) {
return true;
}
return false;
}
async verifyCreateRequesIdExistsOnDevHub() {
const query = `${SELECT_PACKAGE_VERSION_DEPENDENCY} WHERE Id = '${this.resolvedPackageVersionId}'`;
const result = await this.connection.tooling.query(query);
if (result.records?.length === 0) {
throw messages.createError('invalidPackageVersionIdError', [this.userPackageVersionId]);
}
return true;
}
async verifyTransitiveDependenciesCalculated() {
if (await this.verifyCreateRequesIdExistsOnDevHub()) {
const query = `${SELECT_PACKAGE_VERSION_DEPENDENCY} WHERE Id = '${this.resolvedPackageVersionId}'`;
const result = await this.connection.tooling.query(query);
if (result.records?.length === 1) {
const record = result.records[0];
if (record.CalcTransitiveDependencies === true) {
if (record.DependencyGraphJson != null) {
return true;
}
else {
throw messages.createError('invalidDependencyGraphError');
}
}
else {
throw messages.createError('transitiveDependenciesRequiredError');
}
}
}
return false;
}
}
exports.PackageVersionDependency = PackageVersionDependency;
class DependencyDotProducer {
dependencyGraphString;
verbose;
edgeDirection;
resolvedPackageVersionId;
subscriberPackageVersionId;
connection;
dependencyGraphData;
selectedNodeIds = [];
constructor(connection, dependencyGraphString, verbose, edgeDirection, resolvedPackageVersionId) {
this.verbose = verbose;
this.edgeDirection = edgeDirection;
this.resolvedPackageVersionId = resolvedPackageVersionId;
this.connection = connection;
this.dependencyGraphString = dependencyGraphString;
this.subscriberPackageVersionId = exports.VERSION_BEING_BUILT;
}
static throwErrorOnInvalidRecord(record) {
if (!Object.values(record).every((value) => value !== null)) {
throw messages.createError('invalidDependencyGraphError');
}
}
async init() {
const dependencyGraphJson = JSON.parse(this.dependencyGraphString);
this.dependencyGraphData = {
creator: dependencyGraphJson.creator,
nodes: await this.createDependencyGraphNodes(dependencyGraphJson.nodes),
edges: this.createDependencyGraphEdges(dependencyGraphJson.edges),
};
this.selectedNodeIds = await this.addSelectedNodeIds();
}
produce() {
const dotLines = [];
for (const node of this.dependencyGraphData.nodes) {
dotLines.push(this.buildDotNode(node));
}
for (const edge of this.dependencyGraphData.edges) {
dotLines.push(this.buildDotEdge(edge));
}
return `strict digraph G {${node_os_1.EOL}${dotLines.join(node_os_1.EOL)}${node_os_1.EOL}}`;
}
async addSelectedNodeIds() {
const selectedNodes = [];
if (this.subscriberPackageVersionId === exports.VERSION_BEING_BUILT) {
selectedNodes.push(this.subscriberPackageVersionId);
}
else if (this.subscriberPackageVersionId.startsWith('04t')) {
selectedNodes.push(this.subscriberPackageVersionId);
const query = `SELECT Dependencies FROM SubscriberPackageVersion WHERE Id = '${this.subscriberPackageVersionId}'`;
try {
const result = await this.connection.tooling.query(query);
if (result.records?.length !== 1) {
return selectedNodes;
}
const dependencies = result.records[0].Dependencies;
if (!dependencies) {
return selectedNodes;
}
if (dependencies.ids && Array.isArray(dependencies.ids)) {
const dependencyIds = dependencies.ids
.map((dep) => dep.subscriberPackageVersionId)
.filter((id) => id && typeof id === 'string' && id.startsWith('04t'));
selectedNodes.push(...dependencyIds);
}
}
catch (error) {
throw messages.createError('invalidPackageVersionIdError', [this.subscriberPackageVersionId]);
}
}
return selectedNodes;
}
async createDependencyGraphNodes(jsonNodes) {
const nodePromises = jsonNodes.map(async (node) => this.createSingleDependencyGraphNode(node.id));
const resolvedNodes = await Promise.all(nodePromises);
return resolvedNodes;
}
/**
* Creates a single dependency graph node.
* All nodes in the json are 04t... or VERSION_BEING_BUILT and requires different queries to create the node
*/
async createSingleDependencyGraphNode(nodeId) {
const isVersionBeingCreatedNode = nodeId === exports.VERSION_BEING_BUILT;
const isSubscriberPackageVersionId = nodeId.startsWith('04t');
if (!isVersionBeingCreatedNode && !isSubscriberPackageVersionId) {
throw messages.createError('invalidDependencyGraphError');
}
if (isVersionBeingCreatedNode) {
return this.createVersionBeingBuiltNode(nodeId);
}
return this.createNormalDependencyNode(nodeId);
}
async createVersionBeingBuiltNode(nodeId) {
let subscriberPackageVersionId = nodeId;
let MajorVersion = 0;
let MinorVersion = 0;
let PatchVersion = 0;
let BuildNumber = 0;
const nodeQuery = `SELECT Package2Version.SubscriberPackageVersionId, Package2.Name,
Package2Version.MajorVersion, Package2Version.MinorVersion,
Package2Version.PatchVersion, Package2Version.BuildNumber
FROM Package2VersionCreateRequest WHERE Id = '${this.resolvedPackageVersionId}'`;
const nodeResult = await this.connection.tooling.query(nodeQuery);
if (nodeResult.records?.length !== 1) {
throw messages.createError('invalidDependencyGraphError');
}
const record = nodeResult.records[0];
if (!record.Package2?.Name) {
throw messages.createError('invalidDependencyGraphError');
}
const packageName = record.Package2.Name;
// sets the version number correctly and version id to 04t if it exists
if (record.Package2Version?.SubscriberPackageVersionId) {
DependencyDotProducer.throwErrorOnInvalidRecord(record.Package2Version);
subscriberPackageVersionId = record.Package2Version.SubscriberPackageVersionId;
this.subscriberPackageVersionId = subscriberPackageVersionId;
MajorVersion = record.Package2Version.MajorVersion;
MinorVersion = record.Package2Version.MinorVersion;
PatchVersion = record.Package2Version.PatchVersion;
BuildNumber = record.Package2Version.BuildNumber;
}
return {
subscriberPackageVersionId,
packageName,
version: new versionNumber_1.VersionNumber(MajorVersion, MinorVersion, PatchVersion, BuildNumber),
};
}
async createNormalDependencyNode(nodeId) {
const subscriberPackageVersionId = nodeId;
const nodeQuery = `SELECT SubscriberPackageVersionId, Package2.Name, MajorVersion, MinorVersion, PatchVersion, BuildNumber
FROM Package2Version WHERE SubscriberPackageVersionId = '${nodeId}'`;
const nodeResult = await this.connection.tooling.query(nodeQuery);
if (nodeResult.records?.length !== 1) {
throw messages.createError('invalidDependencyGraphError');
}
const record = nodeResult.records[0];
DependencyDotProducer.throwErrorOnInvalidRecord(record);
return {
subscriberPackageVersionId,
packageName: record.Package2.Name,
version: new versionNumber_1.VersionNumber(record.MajorVersion, record.MinorVersion, record.PatchVersion, record.BuildNumber),
};
}
createDependencyGraphEdges(jsonEdges) {
const edges = [];
for (const edge of jsonEdges) {
let source = edge.source;
let target = edge.target;
if (!source || !target) {
throw messages.createError('invalidDependencyGraphError');
}
if (source === exports.VERSION_BEING_BUILT) {
source = this.subscriberPackageVersionId;
}
if (target === exports.VERSION_BEING_BUILT) {
target = this.subscriberPackageVersionId;
}
edges.push({ source, target });
}
return edges;
}
/**
* Builds a DOT node with a label containing the package name and version
*
* @param node the node id and label
*/
buildDotNode(node) {
const nodeId = node.subscriberPackageVersionId;
let label;
// Include subscriber package version id in label based on verbose flag
if (node.subscriberPackageVersionId === exports.VERSION_BEING_BUILT) {
label = `${node.packageName}@${exports.VERSION_BEING_BUILT}`;
}
else {
label = `${node.packageName}@${node.version.toString()}`;
}
if (this.verbose) {
label += ` (${node.subscriberPackageVersionId})`;
}
const color = this.addColorToSelectedNode(node);
return `\t node_${nodeId} [label="${label}"${color}]`;
}
addColorToSelectedNode(node) {
if (this.selectedNodeIds.includes(node.subscriberPackageVersionId)) {
return ' color="green"';
}
return '';
}
/**
* Builds a DOT edge line of the form fromNode -> toNode
*
* @param edge the edge to build where the target node depends on the source node in the json
*/
buildDotEdge(edge) {
const sourceNodeId = edge.source;
const targetNodeId = edge.target;
if (!sourceNodeId || !targetNodeId) {
throw messages.createError('invalidDependencyGraphError');
}
// Handle edge direction based on the flag
if (this.edgeDirection === 'root-last') {
return `\t node_${sourceNodeId} -> node_${targetNodeId}`;
}
return `\t node_${targetNodeId} -> node_${sourceNodeId}`;
}
}
exports.DependencyDotProducer = DependencyDotProducer;
/**
* A class that represents a package dependency node.
* Given a PackageRequestId (08c), PackageName, SubscriberPackageVersionId (04t), and version (MajorVersion.MinorVersion.PatchVersion.BuildNumber) it will build a node of the package dependency.
*/
class PackageDependencyNode extends kit_1.AsyncCreatable {
options;
packageName;
subscriberPackageVersionId;
version;
constructor(options) {
super(options);
this.options = options;
this.packageName = options.packageName;
this.subscriberPackageVersionId = options.subscriberPackageVersionId;
this.version = new versionNumber_1.VersionNumber(this.options.version.major, this.options.version.minor, this.options.version.patch, this.options.version.build);
}
getVersion() {
return this.version.toString();
}
// eslint-disable-next-line class-methods-use-this
init() {
return Promise.resolve();
}
}
exports.PackageDependencyNode = PackageDependencyNode;
//# sourceMappingURL=packageVersionDependency.js.map