UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

298 lines 11.3 kB
"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