@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
298 lines • 11.3 kB
JavaScript
"use strict";
/*
* Copyright © 2020 Atomist, Inc.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.fileContent = exports.GitHubLazyProjectLoader = void 0;
const configuration_1 = require("@atomist/automation-client/lib/configuration");
const base64_1 = require("@atomist/automation-client/lib/internal/util/base64");
const GitHubRepoRef_1 = require("@atomist/automation-client/lib/operations/common/GitHubRepoRef");
const NodeFsLocalFile_1 = require("@atomist/automation-client/lib/project/local/NodeFsLocalFile");
const InMemoryFile_1 = require("@atomist/automation-client/lib/project/mem/InMemoryFile");
const AbstractProject_1 = require("@atomist/automation-client/lib/project/support/AbstractProject");
const httpClient_1 = require("@atomist/automation-client/lib/spi/http/httpClient");
const logger_1 = require("@atomist/automation-client/lib/util/logger");
const fg = require("fast-glob");
const stream = require("stream");
const CachingProjectLoader_1 = require("./CachingProjectLoader");
/**
* Create a lazy view of the given project GitHub, which will materialize
* the remote project (usually by cloning) only if needed.
*/
class GitHubLazyProjectLoader {
constructor(delegate) {
this.delegate = delegate;
}
doWithProject(params, action) {
const lazyProject = new GitHubLazyProject(params.id, this.delegate, params);
return action(lazyProject);
}
get isLazy() {
return true;
}
}
exports.GitHubLazyProjectLoader = GitHubLazyProjectLoader;
/**
* Lazy project that loads remote GitHub project and forwards to it only if necessary.
*/
class GitHubLazyProject extends AbstractProject_1.AbstractProject {
constructor(id, delegate, params) {
super(id);
this.delegate = delegate;
this.params = params;
this.release = () => Promise.resolve();
this.branch = this.id.branch;
this.newRepo = false;
this.remote = this.id.url;
}
materializing() {
return !!this.projectPromise;
}
materialized() {
return !!this.projectPromise && !!this.projectPromise.result();
}
materialize() {
this.materializeIfNecessary("materialize");
return this.projectPromise.then(mp => mp.gitStatus());
}
get provenance() {
return this.materialized() ? this.projectPromise.result().provenance : "unavailable";
}
get baseDir() {
if (!this.materialized()) {
throw new Error("baseDir not supported until materialized");
}
return this.projectPromise.result().baseDir;
}
addDirectory(path) {
this.materializeIfNecessary(`addDirectory${path}`);
return this.projectPromise.then(mp => mp.addDirectory(path));
}
hasDirectory(path) {
this.materializeIfNecessary(`hasDirectory${path}`);
return this.projectPromise.then(mp => mp.hasDirectory(path));
}
addFile(path, content) {
this.materializeIfNecessary(`addFile(${path})`);
return this.projectPromise.then(mp => mp.addFile(path, content));
}
addFileSync(path, content) {
throw new Error("sync methods not supported");
}
deleteDirectory(path) {
this.materializeIfNecessary(`deleteDirectory(${path})`);
return this.projectPromise.then(mp => mp.deleteDirectory(path));
}
deleteDirectorySync(path) {
throw new Error("sync methods not supported");
}
deleteFile(path) {
this.materializeIfNecessary(`deleteFile(${path})`);
return this.projectPromise.then(mp => mp.deleteFile(path));
}
deleteFileSync(path) {
throw new Error("sync methods not supported");
}
directoryExistsSync(path) {
throw new Error("sync methods not supported");
}
fileExistsSync(path) {
throw new Error("sync methods not supported");
}
async findFile(path) {
const file = await this.getFile(path);
if (!file) {
throw new Error(`No file found: '${path}'`);
}
return file;
}
findFileSync(path) {
throw new Error("sync methods not supported");
}
async getFile(path) {
if (this.materializing()) {
return this.projectPromise.then(mp => mp.getFile(path));
}
if (GitHubRepoRef_1.isGitHubRepoRef(this.id)) {
const content = await fileContent(this.params.credentials.token, this.id, path);
return !!content ? new InMemoryFile_1.InMemoryFile(path, content) : undefined;
}
this.materializeIfNecessary(`getFile(${path})`);
return this.projectPromise.then(mp => mp.getFile(path));
}
makeExecutable(path) {
this.materializeIfNecessary(`makeExecutable(${path})`);
return this.projectPromise.then(mp => mp.makeExecutable(path));
}
makeExecutableSync(path) {
throw new Error("sync methods not supported");
}
streamFilesRaw(globPatterns, opts) {
const resultStream = new stream.Transform({ objectMode: true });
resultStream._transform = function (chunk, encoding, done) {
this.push(chunk);
done();
};
this.materializeIfNecessary(`streamFilesRaw`)
.then(async () => {
const underlyingStream = await this.projectPromise.then(mp => mp.streamFilesRaw(globPatterns, opts));
// tslint:disable-next-line:no-floating-promises
underlyingStream.pipe(resultStream);
});
return resultStream;
}
checkout(sha) {
this.materializeIfNecessary(`checkout(${sha})`);
return this.projectPromise.then(mp => mp.checkout(sha));
}
commit(message) {
this.materializeIfNecessary(`commit(${message})`);
return this.projectPromise.then(mp => mp.commit(message));
}
configureFromRemote() {
this.materializeIfNecessary("configureFromRemote");
return this.projectPromise.then(mp => mp.configureFromRemote());
}
createAndSetRemote(gid, description, visibility) {
this.materializeIfNecessary("createAndSetRemote");
return this.projectPromise.then(mp => mp.createAndSetRemote(gid, description, visibility));
}
createBranch(name) {
this.materializeIfNecessary(`createBranch(${name})`);
return this.projectPromise.then(mp => mp.createBranch(name));
}
gitStatus() {
this.materializeIfNecessary("gitStatus");
return this.projectPromise.then(mp => mp.gitStatus());
}
hasBranch(name) {
this.materializeIfNecessary(`hasBranch(${name})`);
return this.projectPromise.then(mp => mp.hasBranch(name));
}
init() {
this.materializeIfNecessary("init");
return this.projectPromise.then(mp => mp.init());
}
isClean() {
this.materializeIfNecessary("isClean");
return this.projectPromise.then(mp => mp.isClean());
}
push(options) {
this.materializeIfNecessary("push");
return this.projectPromise.then(mp => mp.push(options));
}
raisePullRequest(title, body, targetBranch) {
this.materializeIfNecessary("raisePullRequest");
return this.projectPromise.then(mp => mp.raisePullRequest(title, body, targetBranch));
}
revert() {
this.materializeIfNecessary("revert");
return this.projectPromise.then(mp => mp.revert());
}
setRemote(remote) {
this.materializeIfNecessary("setRemote");
return this.projectPromise.then(mp => mp.setRemote(remote));
}
setUserConfig(user, email) {
this.materializeIfNecessary("setUserConfig");
return this.projectPromise.then(mp => mp.setUserConfig(user, email));
}
async getFilesInternal(globPatterns) {
await this.materializeIfNecessary("getFilesInternal");
const optsToUse = {
cwd: this.baseDir,
dot: true,
onlyFiles: true,
};
const paths = await fg(globPatterns, optsToUse);
const files = paths.map(path => new NodeFsLocalFile_1.NodeFsLocalFile(this.baseDir, path));
return files;
}
materializeIfNecessary(why) {
if (!this.materializing()) {
logger_1.logger.debug("Materializing project %j because of %s", this.id, why);
this.projectPromise = makeQueryablePromise(CachingProjectLoader_1.save(this.delegate, this.params));
}
return this.projectPromise;
}
}
/**
* Based on https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved
* This function allow you to modify a JS Promise by adding some status properties.
* Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved
* But modified according to the specs of promises : https://promisesaplus.com/
*/
function makeQueryablePromise(ppromise) {
const promise = ppromise;
// Don't modify any promise that has been already modified.
if (promise.isResolved) {
return promise;
}
// Set initial state
let isPending = true;
let isRejected = false;
let isFulfilled = false;
let result;
// Observe the promise, saving the fulfillment in a closure scope.
const qp = promise.then(v => {
isFulfilled = true;
isPending = false;
result = v;
return v;
}, e => {
isRejected = true;
isPending = false;
throw e;
});
qp.isFulfilled = () => {
return isFulfilled;
};
qp.isPending = () => {
return isPending;
};
qp.isRejected = () => {
return isRejected;
};
qp.result = () => {
return result;
};
return qp;
}
async function fileContent(token, rr, path) {
try {
const result = await filePromise(token, rr, path);
return base64_1.decode(result.body.content);
}
catch (e) {
logger_1.logger.debug(`File at '${path}' not available`);
return undefined;
}
}
exports.fileContent = fileContent;
async function filePromise(token, rr, path) {
const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/contents/${path}?ref=${rr.branch || "master"}`;
logger_1.logger.debug(`Requesting file from GitHub at '${url}'`);
const httpClient = configuration_1.configurationValue("http.client.factory", httpClient_1.defaultHttpClientFactory());
return httpClient.create(url).exchange(url, {
method: httpClient_1.HttpMethod.Get,
headers: {
Authorization: `token ${token}`,
},
retry: {
retries: 0,
},
});
}
//# sourceMappingURL=GitHubLazyProjectLoader.js.map