salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
257 lines (255 loc) • 10.2 kB
JavaScript
"use strict";
/*
* Copyright (c) 2020, 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Workspace = void 0;
const path = require("path");
const core_1 = require("@salesforce/core");
const kit_1 = require("@salesforce/kit");
const Messages = require("../messages");
const sourcePathStatusManager_1 = require("./sourcePathStatusManager");
const messages = Messages();
const Package2ConfigFileNames = ['package2-descriptor.json', 'package2-manifest.json'];
// eslint-disable-next-line no-redeclare
class Workspace extends core_1.ConfigFile {
constructor(options) {
super(options);
this.pathInfos = new Map();
this.trackedPackages = [];
this.org = options.org;
this.forceIgnore = options.forceIgnore;
this.isStateless = options.isStateless;
this.workspacePath = options.org.config.getProjectPath();
}
async init() {
this.logger = await core_1.Logger.child(this.constructor.name);
this.options.filePath = path.join('orgs', this.org.name);
this.options.filename = Workspace.getFileName();
await super.init();
this.backupPath = `${this.getPath()}.bak`;
if (!this.isStateless) {
await this.initializeCached();
const pathInfos = this.getContents();
if (kit_1.isEmpty(pathInfos)) {
await this.initializeStateFull();
}
}
else {
this.initializeStateless();
}
}
async initializeCached() {
this.logger.debug('Reading workspace from cache');
let workspacePathChanged;
const trackedPackages = [];
try {
const oldSourcePathInfos = [...this.values()];
let oldWorkspacePath;
for (const sourcePathInfoObj of oldSourcePathInfos) {
if (!sourcePathInfoObj.package) {
const packagePath = core_1.SfdxProject.getInstance().getPackageNameFromPath(sourcePathInfoObj.sourcePath);
if (packagePath) {
sourcePathInfoObj.package = packagePath;
}
}
if (sourcePathInfoObj.isWorkspace) {
oldWorkspacePath = sourcePathInfoObj.sourcePath;
}
if (sourcePathInfoObj.isArtifactRoot) {
trackedPackages.push(sourcePathInfoObj.sourcePath);
}
}
this.trackedPackages = trackedPackages;
workspacePathChanged = !!oldWorkspacePath && this.workspacePath !== oldWorkspacePath;
for (const sourcePathInfoObj of oldSourcePathInfos) {
const sourcePathInfo = await sourcePathStatusManager_1.SourcePathInfo.create(sourcePathInfoObj);
if (workspacePathChanged) {
const oldPath = sourcePathInfo.sourcePath;
sourcePathInfo.sourcePath = path.join(this.workspacePath, path.relative(oldWorkspacePath, sourcePathInfo.sourcePath));
this.unset(oldPath);
}
this.set(sourcePathInfo.sourcePath, sourcePathInfo);
}
}
catch (e) {
// Do nothing if the file can't be read, which will cause the workspace to be initialized
}
if (workspacePathChanged) {
await this.write();
}
}
async initializeStateFull() {
this.logger.debug('Initializing statefull workspace');
const packages = core_1.SfdxProject.getInstance()
.getUniquePackageDirectories()
.map((p) => stripTrailingSlash(p.fullPath));
this.trackedPackages = packages;
await this.walkDirectories(packages);
await this.write();
}
initializeStateless() {
this.logger.debug('Initializing stateless workspace');
this.trackedPackages = core_1.SfdxProject.getInstance()
.getUniquePackageDirectories()
.map((p) => stripTrailingSlash(p.fullPath));
this.setContents({});
}
getContents() {
return this['contents'];
}
entries() {
// override entries and cast here to avoid casting every entries() call
return super.entries();
}
static getFileName() {
return 'sourcePathInfos.json';
}
async rewriteInfos() {
await this.initializeStateFull();
}
async walkDirectories(directories) {
for (const directory of directories) {
const exists = await core_1.fs.fileExists(directory);
if (!exists) {
const error = new Error(messages.getMessage('InvalidPackageDirectory', directory));
error['name'] = 'InvalidProjectWorkspace';
throw error;
}
await this.walk(directory);
}
}
/**
* Walks the directory using native fs.readdir
*/
async walk(directory, recur) {
if (!recur) {
await this.handleArtifact(directory, directory);
}
const files = await core_1.fs.readdir(directory);
for (const filename of files) {
const sourcePath = path.join(directory, filename);
const sourcePathInfo = await this.handleArtifact(sourcePath, directory);
if (sourcePathInfo.isDirectory) {
await this.walk(sourcePath, true);
}
}
}
async handleArtifact(sourcePath, parentDirectory) {
const isWorkspace = false;
const isArtifactRoot = parentDirectory ? sourcePath === parentDirectory : false;
const sourcePathInfo = await sourcePathStatusManager_1.SourcePathInfo.create({
sourcePath,
deferContentHash: false,
isWorkspace,
isArtifactRoot,
});
if (this.isValidSourcePath(sourcePathInfo)) {
this.set(sourcePath, sourcePathInfo);
}
return sourcePathInfo;
}
/**
* Check if the given sourcePath should be ignored
*/
isValidSourcePath(sourcePathInfo) {
const sourcePath = sourcePathInfo.sourcePath;
let isValid = this.forceIgnore.accepts(sourcePath);
const basename = path.basename(sourcePath);
const isPackage2ConfigFile = Package2ConfigFileNames.includes(basename);
isValid = !basename.startsWith('.') && !basename.endsWith('.dup') && isValid && !isPackage2ConfigFile;
if (isValid && !!sourcePathStatusManager_1.SourcePathStatusManager.metadataRegistry) {
if (!sourcePathInfo.isDirectory) {
if (!sourcePathStatusManager_1.SourcePathStatusManager.metadataRegistry.isValidSourceFilePath(sourcePath)) {
const error = new Error(`Unexpected file found in package directory: ${sourcePath}`);
error['name'] = 'UnexpectedFileFound';
throw error;
}
}
}
// Skip directories/files beginning with '.', end with .dup, and that should be ignored
return isValid;
}
async write() {
if (!this.has(this.workspacePath)) {
const workspaceSourcePathInfo = await sourcePathStatusManager_1.SourcePathInfo.create({
sourcePath: this.workspacePath,
deferContentHash: false,
isWorkspace: true,
isArtifactRoot: false,
});
this.set(this.workspacePath, workspaceSourcePathInfo);
}
return super.write();
}
get(key) {
return this.getContents()[key];
}
async getInitializedValue(key) {
const value = this.get(key);
return sourcePathStatusManager_1.SourcePathInfo.create(value);
}
has(key) {
return !!this.get(key);
}
values() {
return super.values();
}
async getInitializedValues() {
const values = this.values();
const initialized = [];
for (const value of values) {
initialized.push(await sourcePathStatusManager_1.SourcePathInfo.create(value));
}
return initialized;
}
// @ts-ignore because typescript expects value to be a SourcePathInfo.Json but we want to do the
// conversion from a SourcePathInfo instance to SourcePathInfo.Json here instead of relying on whoever
// calls this method to do it first.
set(key, value) {
return super.set(key, value.toJson());
}
setMethod(contents, key, value) {
contents[key] = value;
}
async revert() {
if (await core_1.fs.fileExists(this.backupPath)) {
const backedupContents = await core_1.fs.readFile(this.backupPath, 'UTF-8');
this.setContentsFromObject(JSON.parse(backedupContents));
await this.write();
await core_1.fs.unlink(this.backupPath);
}
}
async backup() {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (this.exists()) {
await core_1.fs.writeFile(this.backupPath, JSON.stringify(this.getContents()));
}
}
async read() {
try {
return await super.read();
}
catch (err) {
if (err.name === 'JsonDataFormatError') {
// This error means that the old sourcePathInfos format is still
// in use and so we need to convert it to the new format.
const contents = await core_1.fs.readFile(this.getPath(), 'utf-8');
const map = new Map(JSON.parse(contents));
const obj = {};
map.forEach((value, key) => (obj[key] = value));
this.setContentsFromObject(obj);
await this.write();
return this.getContents();
}
}
}
}
exports.Workspace = Workspace;
function stripTrailingSlash(str) {
return str.endsWith(path.sep) ? str.slice(0, -1) : str;
}
//# sourceMappingURL=workspace.js.map