UNPKG

6.48 kBPlain TextView Raw
1/*
2 * Copyright © 2019 Atomist, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {
18 GitHubRepoRef,
19 GitProject,
20 logger,
21 ProjectOperationCredentials,
22 RemoteRepoRef,
23} from "@atomist/automation-client";
24import {
25 ExecuteGoal,
26 ExecuteGoalResult,
27 GoalInvocation,
28 LoggingProgressLog,
29 ProgressLog,
30 spawnLog,
31} from "@atomist/sdm";
32import {
33 createTag,
34 createTagReference,
35 Tag,
36} from "../../../util/github/ghub";
37import { goalInvocationVersion } from "./local/projectVersioner";
38
39/**
40 * Options for creating a tag. If neither `name` or `release` are
41 * truthy, a prerelease, i.e., timestamped, version tag is created.
42 * If both `name` and `release` are truthy, `name` takes precedence.
43 */
44export interface ExecuteTagOptions {
45 /**
46 * Message to add to tag. If not provided, the push after commit
47 * message title is used.
48 */
49 message?: string;
50 /** Name of tag to create. */
51 name?: string;
52 /**
53 * If `true`, create a release semantic version tag, not a
54 * prerelease version tag.
55 */
56 release?: boolean;
57 /**
58 * Semantic version build metadata to append to tag, e.g.,
59 * "sdm.BUILD_NUMBER".
60 */
61 build?: string;
62}
63
64/**
65 * Create and return an execute goal object that creates a Git tag,
66 * suitable for use with the [[Tag]] goal.
67 *
68 * @param opts Options that determine the tag created
69 * @return Success if successful, Failure otherwise
70 */
71export function executeTag(opts: ExecuteTagOptions = {}): ExecuteGoal {
72 return async (goalInvocation: GoalInvocation): Promise<ExecuteGoalResult> => {
73 const { configuration, goalEvent, credentials, id, context, progressLog } = goalInvocation;
74
75 return configuration.sdm.projectLoader.doWithProject({ credentials, id, context, readOnly: false }, async project => {
76 try {
77 let tag: string;
78 let message: string;
79 if (opts.message) {
80 message = opts.message;
81 } else if (goalEvent.push.after && goalEvent.push.after.message) {
82 message = goalEvent.push.after.message.split("\n")[0];
83 }
84 if (opts.name) {
85 tag = opts.name;
86 message = message || `Tag ${opts.name}`;
87 } else {
88 const version = await goalInvocationVersion(goalInvocation);
89 if (opts.release) {
90 tag = version.replace(/[-+].*/, "");
91 message = message || `Release ${tag}`;
92 } else {
93 tag = version;
94 message = message || `Prerelease ${tag}`;
95 }
96 }
97 if (opts.build) {
98 tag += "+" + opts.build;
99 }
100 await createGitTag({ project, tag, message, log: progressLog });
101 return { code: 0, message: `Created tag '${tag}' for ${goalEvent.repo.owner}/${goalEvent.repo.name}` };
102 } catch (e) {
103 const message = `Failed to create tag for ${goalEvent.repo.owner}/${goalEvent.repo.name}: ${e.message}`;
104 logger.error(message);
105 progressLog.write(message);
106 return { code: 1, message };
107 }
108 });
109 };
110}
111
112/** [[createTag]] function arguments. */
113export interface CreateGitTagOptions {
114 /** Git repository project to operate on. */
115 project: GitProject;
116 /** Name of tag to create and push. */
117 tag: string;
118 /** Optional message to associate with Git tag. */
119 message?: string;
120 /** Optional progress log to write updates to. */
121 log?: ProgressLog;
122}
123
124/**
125 * Create and push a Git tag with optional message.
126 *
127 * @param opts Options for creating a Git tag.
128 */
129export async function createGitTag(opts: CreateGitTagOptions): Promise<void> {
130 if (!opts.tag) {
131 throw new Error("You must provide a valid Git tag");
132 }
133 if (!opts.project) {
134 throw new Error("You must provide a Git project");
135 }
136 if (!opts.log) {
137 opts.log = new LoggingProgressLog("logger");
138 }
139 const remote = opts.project.remote || "origin";
140 try {
141 const spawnOpts = { cwd: opts.project.baseDir, log: opts.log };
142 const tagArgs = ["tag", opts.tag];
143 if (opts.message) {
144 tagArgs.splice(1, 0, "-m", opts.message);
145 }
146 const tagResult = await spawnLog("git", tagArgs, spawnOpts);
147 if (tagResult.code) {
148 throw new Error(`git tag failed: ${tagResult.message}`);
149 }
150 const pushResult = await spawnLog("git", ["push", remote, opts.tag], spawnOpts);
151 if (pushResult.code) {
152 throw new Error(`git push failed: ${pushResult.message}`);
153 }
154 } catch (e) {
155 e.message = `Failed to create and push git tag '${opts.tag}': ${e.message}`;
156 throw e;
157 }
158}
159
160/**
161 * Create a GitHub tag using the GitHub API.
162 *
163 * @param id GitHub remote repository reference
164 * @param sha Commit SHA to tag
165 * @param message Tag message
166 * @param version Name of tag
167 * @param credentials GitHub token object
168 * @deprecated use createGitTag
169 */
170export async function createTagForStatus(id: RemoteRepoRef,
171 sha: string,
172 message: string,
173 version: string,
174 credentials: ProjectOperationCredentials): Promise<void> {
175 const tag: Tag = {
176 tag: version,
177 message,
178 object: sha,
179 type: "commit",
180 tagger: {
181 name: "Atomist",
182 email: "info@atomist.com",
183 date: new Date().toISOString(),
184 },
185 };
186
187 await createTag(credentials, id as GitHubRepoRef, tag);
188 await createTagReference(credentials, id as GitHubRepoRef, tag);
189}