UNPKG

npm-template-sync

Version:

Keep npm package in sync with its template

254 lines (214 loc) 6.14 kB
import { createContext } from "expression-expander"; import { LogLevelMixin } from "loglevel-mixin"; import { Package } from "./mergers/package.mjs"; import { Template } from "./template.mjs"; import { jspath, asArray, log } from "./util.mjs"; export { Template }; /** * context prepared to execute one package * @param {string} targetBranchName * * @property {Object} ctx * @property {Map<string,Object>} files */ export class Context extends LogLevelMixin(class _Context {}) { static async from(provider, targetBranchName, options) { const pc = new Context(provider, targetBranchName, options); await pc.initialize(); return pc; } constructor(provider, targetBranchName, options = {}) { super(); Object.defineProperties(this, { options: { value: options }, templateSources: { value: asArray(options.template) }, track: { value: options.track || false }, dry: { value: options.dry || false }, provider: { value: provider }, properties: { value: { date: { year: new Date().getFullYear() }, license: {}, templateSources: [], ...options.properties } }, ctx: { value: createContext({ properties: Object.assign({}, this.properties), keepUndefinedValues: true, leftMarker: "{{", rightMarker: "}}", markerRegexp: "{{([^}]+)}}", evaluate: (expression, context, path) => jspath(this.properties, expression) }) }, targetBranchName: { value: targetBranchName } }); this.logLevel = options.logLevel; } expand(...args) { return this.ctx.expand(...args); } evaluate(expression) { return jspath(this.properties, expression); } async initialize() { const targetBranch = await this.provider.branch(this.targetBranchName); if (targetBranch === undefined) { throw new Error(`Unable to find branch ${this.targetBranchName}`); } if (targetBranch.provider.name === "GithubProvider") { this.properties.github = { user: targetBranch.owner.name, repo: targetBranch.repository.name }; } if ( targetBranch.repository.owner !== undefined && this.properties.license.owner === undefined ) { Object.assign(this.properties.license, { owner: targetBranch.owner.name }); } if ( targetBranch.repository.description !== undefined && this.properties.description === undefined ) { this.properties.description = targetBranch.repository.description; } try { const entry = await targetBranch.entry("package.json"); Object.assign(this.properties, await Package.properties(entry)); } catch {} this.templateSources.push(...this.properties.templateSources); const template = await Template.templateFor(this, this.templateSources, { logLevel: this.logLevel }); if (template === undefined) { throw new Error( `Unable to extract template repo url from ${targetBranch.name} ${pkg.name}` ); } Object.assign(this.properties, await template.properties()); this.debug({ message: "detected properties", properties: this.properties }); Object.defineProperties(this, { targetBranch: { value: targetBranch }, template: { value: template } }); this.debug({ message: "initialized for", targetBranch }); } async execute() { if (this.properties.usedBy !== undefined) { const pullRequests = []; for (const r of this.properties.usedBy) { try { const context = await Context.from(this.provider, r, this.options); pullRequests.push(...(await context.execute())); } catch (e) { this.error(e); } } return pullRequests; } else { return this.executeSingleRepo(); } } /** * @return {[Promise<PullRequest>]} */ async executeSingleRepo() { const targetBranch = this.targetBranch; const pullRequests = []; this.debug({ message: "executeSingleRepo", targetBranch }); const template = this.template; if (this.track && !this.dry) { pullRequests.push(await template.addUsedPackage(targetBranch)); } const mergers = await template.mergers(); /* const targetEntries = new Map(); await Promise.all( mergers.map(async ([name]) => { name = this.expand(name); targetEntries.set( name, (await targetBranch.entry(name)) || new EmptyContentEntry(name) ); }) );*/ const commits = ( await Promise.all( mergers.map(async ([name, merger, options]) => { const targetName = this.expand(name); const targetEntry = (await targetBranch.entry(targetName)) || new EmptyContentEntry(targetName); return merger.merge( this, targetEntry, await template.entry(name), options ); }) ) ).filter(c => c !== undefined); if (commits.length === 0) { this.info("-"); return pullRequests; } this.info(commits.map(c => `${c.message}`).join(",")); if (this.dry) { return pullRequests; } const prBranch = await targetBranch.createBranch( `npm-template-sync/${template.name}` ); for (const commit of commits) { await prBranch.commit(commit.message, [commit.entry]); } try { const pullRequest = await targetBranch.createPullRequest(prBranch, { title: `merge from ${template.name}`, body: commits .map( c => `${c.entry.name} --- - ${c.message} ` ) .join("\n") }); this.info({ message: "PR", pr: pullRequest }); pullRequests.push(pullRequest); } catch (err) { this.error(err); } return pullRequests; } log(level, ...args) { log(level, ...args); } }