Skip to content

Commit 35a825c

Browse files
authored
feat(templates): add the possibility to select a specific git branch on the template repo
Feature/select branch
2 parents 82a28ae + 1ff334a commit 35a825c

File tree

13 files changed

+234
-25
lines changed

13 files changed

+234
-25
lines changed

vscode-extension/packages/core-lib/src/__tests__/unit/templateService.test.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe("TemplateService", () => {
4545

4646
const data = await getTemplateTitles(
4747
"https://example.com/templates.json",
48+
"main",
4849
);
4950

5051
expect(data.list.length).toBe(2);
@@ -53,14 +54,16 @@ describe("TemplateService", () => {
5354
expect(data.list[1].name).toBe("sqsToLambda");
5455
expect(data.list[1].path).toBe("some/path/to/sqs.png");
5556
expect(axios.get).toHaveBeenCalledWith(
56-
"https://example.com/templates.json",
57+
"https://example.com/templates.json/main/cookiecutter.json",
5758
);
5859

5960
axiosGetSpy.mockRestore();
6061
});
6162

6263
it("should throw TemplateFetchError when URL is not defined", async () => {
63-
await expect(getTemplateTitles("")).rejects.toThrow(TemplateFetchError);
64+
await expect(getTemplateTitles("", "")).rejects.toThrow(
65+
TemplateFetchError,
66+
);
6467
});
6568

6669
it("should throw TemplateFetchError when request fails", async () => {
@@ -70,7 +73,7 @@ describe("TemplateService", () => {
7073
.mockRejectedValueOnce(new Error(errorMessage));
7174

7275
await expect(
73-
getTemplateTitles("https://example.com/templates.json"),
76+
getTemplateTitles("https://example.com/templates.json", "main"),
7477
).rejects.toThrow(TemplateFetchError);
7578

7679
axiosGetSpy.mockRestore();

vscode-extension/packages/core-lib/src/config/config.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
//===----------------------------------------------------------------------===//
1515

1616
export const config = {
17-
TEMPLATES_JSON_URL:
18-
process.env.TEMPLATES_JSON_URL ||
19-
"https://raw.githubusercontent.com/swift-server-community/aws-lambda-swift-sam-template/main/cookiecutter.json",
17+
RAW_TEMPLATES_REPO_URL:
18+
process.env.RAW_TEMPLATES_REPO_URL ||
19+
"https://raw.githubusercontent.com/swift-server-community/aws-lambda-swift-sam-template",
2020
TEMPLATES_REPO_URL:
2121
process.env.TEMPLATES_REPO_URL ||
22-
"https://github.com/swift-server-community/aws-lambda-swift-sam-template.git",
22+
"https://github.com/swift-server-community/aws-lambda-swift-sam-template",
2323
CLONED_TEMPLATES_DIR:
2424
process.env.CLONED_TEMPLATES_DIR || "/tmp/template-repo",
2525
EVENTS_DIR: process.env.EVENTS_DIR || "/events",

vscode-extension/packages/core-lib/src/facade.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
//===----------------------------------------------------------------------===//
1515

1616
/* eslint-disable no-unused-vars */
17-
import { getTemplateTitles } from "./services/templateServices";
17+
import {
18+
getTemplateTitles,
19+
getTemplateBranches,
20+
} from "./services/templateServices";
1821
import logger from "./logger";
1922
import { config } from "./config/config";
2023
import * as Sam from "./sam/sam";
@@ -43,6 +46,20 @@ export async function getFunctions(path: string): Promise<string[]> {
4346
return await Sam.getFunctions(path);
4447
}
4548

49+
/**
50+
* Gets all the branches from the template repository
51+
* @async
52+
* @example
53+
* const branches = await getBranches();
54+
* console.log("Branches:", branches);
55+
* @throws {Error} If the branches cannot be fetched
56+
* @returns {Promise<string[]>} A list of branches
57+
*/
58+
export async function getBranches(): Promise<string[]> {
59+
logger.debug("Getting branches...");
60+
return await getTemplateBranches(config.TEMPLATES_REPO_URL);
61+
}
62+
4663
/**
4764
* Gets all the events
4865
* @param {string} path The path to the project to get the events from
@@ -150,9 +167,11 @@ export function unsubscribeFromStderr(listener: (data: string) => void) {
150167
* @throws {Error} If the templates cannot be retrieved
151168
* @returns {Promise<string[]>} A list of available template titles
152169
*/
153-
export async function getTemplates(): Promise<TemplatesResult> {
170+
export async function getTemplates(
171+
branch: string = "main",
172+
): Promise<TemplatesResult> {
154173
logger.debug("Getting templates...");
155-
return await getTemplateTitles(config.TEMPLATES_JSON_URL);
174+
return await getTemplateTitles(config.RAW_TEMPLATES_REPO_URL, branch);
156175
}
157176

158177
/**
@@ -170,11 +189,12 @@ export async function initializeProject(
170189
name: string,
171190
template: string,
172191
path: string,
192+
branch: string = "main",
173193
): Promise<CommandResult> {
174194
logger.debug(
175-
`Initializing project with template ${template} and name ${name} in directory ${path}...`,
195+
`Initializing project with template ${template} and name ${name} in directory ${path}... from branch ${branch}`,
176196
);
177-
return await Sam.runInitializeProject(name, template, path);
197+
return await Sam.runInitializeProject(name, template, path, branch);
178198
}
179199

180200
/**

vscode-extension/packages/core-lib/src/sam/sam.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export async function runInitializeProject(
151151
name: string,
152152
template: string,
153153
path: string,
154+
branch: string,
154155
): Promise<CommandResult> {
155156
try {
156157
if (!name) {
@@ -181,7 +182,7 @@ export async function runInitializeProject(
181182
const exitCode = 0;
182183

183184
await cleanupTempDirectory();
184-
const cloneResult = await cloneTemplate();
185+
const cloneResult = await cloneTemplate(branch);
185186
output.stdout += cloneResult.output.stdout;
186187
output.stderr += cloneResult.output.stderr;
187188
if (cloneResult.exitCode !== 0) {

vscode-extension/packages/core-lib/src/services/templateServices.ts

+46-3
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,30 @@ import { TemplatesResult } from "../utils/types";
1919

2020
/** Get the titles of the templates from the JSON file.
2121
* @param {string} URL The URL of the JSON file
22+
* @param {string} branch The branch of the repository
2223
* @throws {Error} If the URL is not defined in the environment
2324
* @async
2425
* @example
25-
* const titles = await getTemplateTitles("https://example.com/templates.json");
26+
* const titles = await getTemplateTitles("https://example.com/templates.json", "main");
2627
* console.log("Template titles:", titles);
2728
* @returns {Promise<TemplatesResult>} The titles of the templates with their images
2829
*/
29-
export async function getTemplateTitles(URL: string): Promise<TemplatesResult> {
30+
export async function getTemplateTitles(
31+
URL: string,
32+
branch: string,
33+
): Promise<TemplatesResult> {
3034
if (!URL || URL === "") {
3135
throw new TemplateFetchError(
3236
"TEMPLATES_JSON_URL is not defined in the environment.",
3337
);
3438
}
3539

40+
if (!branch || branch === "") {
41+
throw new TemplateFetchError("Branch is not defined.");
42+
}
43+
3644
try {
37-
const response = await axios.get(URL);
45+
const response = await axios.get(`${URL}/${branch}/cookiecutter.json`);
3846

3947
const result = Object.entries(response.data.templates).map(
4048
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -52,3 +60,38 @@ export async function getTemplateTitles(URL: string): Promise<TemplatesResult> {
5260
);
5361
}
5462
}
63+
64+
/**
65+
* Get the branches of the template repository.
66+
* @param {string} repoUrl The URL of the GitHub repository
67+
* @throws {Error} If the URL is not defined or if there's an error fetching the branches
68+
* @async
69+
* @example
70+
* const branches = await getTemplateBranches("https://github.com/username/repository");
71+
* console.log("Branches:", branches);
72+
* @returns {Promise<string[]>} The branches of the repository
73+
*/
74+
export async function getTemplateBranches(repoUrl: string): Promise<string[]> {
75+
if (!repoUrl || repoUrl === "") {
76+
throw new TemplateFetchError("Repository URL is not defined.");
77+
}
78+
79+
const apiUrl = `${repoUrl}/branches/all`;
80+
81+
try {
82+
const response = await axios.get(apiUrl, {
83+
headers: {
84+
Accept: "application/json",
85+
},
86+
});
87+
return response.data.payload.branches.map(
88+
(branch: { name: string }) => branch.name,
89+
);
90+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
91+
} catch (error: any) {
92+
throw new TemplateFetchError(
93+
`Error fetching template branches: ${error.message}`,
94+
error,
95+
);
96+
}
97+
}

vscode-extension/packages/core-lib/src/utils/initUtils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ const commandRunner = CommandRunner.getInstance();
2828
* @throws {GitCloneFailedError} If the template repository cannot be cloned
2929
* @returns {Promise<CommandResult>}
3030
*/
31-
export async function cloneTemplate(): Promise<CommandResult> {
31+
export async function cloneTemplate(branch: string): Promise<CommandResult> {
3232
const cloneArgs: string[] = [
3333
"clone",
34+
"--single-branch",
35+
"--branch",
36+
branch,
3437
"--depth",
3538
"1",
3639
config.TEMPLATES_REPO_URL,

vscode-extension/src/commands/Commands.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ export async function getEvents(data: any) {
5151
return { events, success: true };
5252
}
5353

54+
/**
55+
* Fetches templates from the specified branch.
56+
* @param {any} data - Branch information.
57+
* @returns {Promise<{ templates: { list: [{ name: string, path: string }] }, success: boolean }>} An object containing fetched templates and success status.
58+
* @async
59+
* @example
60+
* const templates = await getTemplates({ branch: "main" });
61+
* console.log("Templates:", templates);
62+
*/
63+
export async function getTemplates(data: any) {
64+
try {
65+
const branch = data.branch;
66+
const templates = await Sam.getTemplates(branch);
67+
return { templates, success: true };
68+
} catch (error: any) {
69+
window.showErrorMessage(error.message);
70+
}
71+
}
72+
5473
/**
5574
* Fetches necessary data to initialize the project.
5675
* @returns {Promise<{ templates: any[], path: string, regions: any[], locale: string, theme: string }>} Object with templates, path, regions, locale, and theme.
@@ -82,7 +101,8 @@ export async function getReady() {
82101
}
83102
const locale = env.language || "en";
84103
const theme = window.activeColorTheme.kind || "dark";
85-
return { templates, path, regions, locale, theme };
104+
const branches = await Sam.getBranches();
105+
return { templates, path, regions, locale, theme, branches };
86106
} catch (error: any) {
87107
window.showErrorMessage(error.message);
88108
}
@@ -137,13 +157,19 @@ export async function initializeProject(data: any) {
137157
const name = data.name;
138158
const selectedTemplate = data.template;
139159
let path = data.path;
160+
const branch = data.branch;
140161

141162
if (!path) {
142163
const workspaceFolders = workspace.workspaceFolders;
143164
path = workspaceFolders?.[0]?.uri.fsPath;
144165
}
145166

146-
const result = await Sam.initializeProject(name, selectedTemplate, path);
167+
const result = await Sam.initializeProject(
168+
name,
169+
selectedTemplate,
170+
path,
171+
branch || "main",
172+
);
147173
return { success: result.exitCode === 0 };
148174
} catch (error: any) {
149175
window.showErrorMessage(error.message);

vscode-extension/src/panels/MainPanel.ts

+13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
getEvents,
3838
getFunctions,
3939
checkFolderExists,
40+
getTemplates,
4041
} from "../commands/Commands";
4142

4243
/**
@@ -211,6 +212,18 @@ export class MainPanel {
211212
const data = message.data;
212213

213214
switch (command) {
215+
case "templates":
216+
getTemplates(data)
217+
.then((response) => {
218+
webview.postMessage({ command: "templates", data: response });
219+
})
220+
.catch((error) => {
221+
webview.postMessage({
222+
command: "templates",
223+
data: { success: false, error },
224+
});
225+
});
226+
return;
214227
case "checkExists":
215228
checkFolderExists(data)
216229
.then((response) => {

vscode-extension/webview-ui/src/api/markdownApi.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616
// This file contains the API for fetching Markdown files from the specified template repository.
1717
const GITHUB_API_URL =
1818
process.env.GITHUB_API_URL ||
19-
"https://raw.githubusercontent.com/swift-server-community/aws-lambda-swift-sam-template/main/";
19+
"https://raw.githubusercontent.com/swift-server-community/aws-lambda-swift-sam-template";
2020

2121
/**
2222
* Fetches a Markdown file from the specified template repository.
2323
* @param {string} template - The name of the template repository.
2424
* @returns {Promise<string>} - A promise that resolves to the content of the Markdown file.
2525
* @throws {Error} - If fetching the Markdown file fails.
2626
*/
27-
export async function fetchMarkdownFile(template: string): Promise<string> {
27+
export async function fetchMarkdownFile(
28+
template: string,
29+
branch: string = "main",
30+
): Promise<string> {
2831
// Constructing the URL of the Markdown file
29-
const url = `${GITHUB_API_URL}/${template}/doc/INFO.md`;
32+
const url = `${GITHUB_API_URL}/${branch}/${template}/doc/INFO.md`;
3033

3134
try {
3235
// Fetching the Markdown file

vscode-extension/webview-ui/src/components/initialize/Initialize.css

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
*
1414
*===----------------------------------------------------------------------===//
1515
*/
16+
17+
.loading-ring-container, .loading-ring {
18+
height: 100%;
19+
}
1620

1721
.markdown-container {
1822
display: flex;

0 commit comments

Comments
 (0)