]> Git Repo - pico-vscode.git/blob - src/utils/examplesUtil.mts
Ensure examples clone is up to date
[pico-vscode.git] / src / utils / examplesUtil.mts
1 import { join as joinPosix } from "path/posix";
2 import Logger from "../logger.mjs";
3 import { existsSync, readFileSync, rmSync } from "fs";
4 import { homedir } from "os";
5 import { getGit, sparseCheckout, sparseCloneRepository, execAsync } from "./gitUtil.mjs";
6 import Settings from "../settings.mjs";
7 import { checkForInstallationRequirements } from "./requirementsUtil.mjs";
8 import { cp } from "fs/promises";
9 import { get } from "https";
10 import {
11   isInternetConnected, CURRENT_DATA_VERSION, getDataRoot
12 } from "./downloadHelpers.mjs";
13
14 const EXAMPLES_REPOSITORY_URL =
15   "https://github.com/raspberrypi/pico-examples.git";
16 const EXAMPLES_JSON_URL =
17   "https://raspberrypi.github.io/pico-vscode/" +
18   `${CURRENT_DATA_VERSION}/examples.json`;
19 const EXAMPLES_GITREF = 
20   "7fe60d6b4027771e45d97f207532c41b1d8c5418";
21 const EXAMPLES_TAG = 
22   "sdk-2.0.0";
23
24 export interface Example {
25   path: string;
26   name: string;
27   searchKey: string;
28 }
29
30 interface ExamplesFile {
31   [key: string]: {
32     path: string;
33     name: string;
34   };
35 }
36
37 function buildExamplesPath(): string {
38   return joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "examples");
39 }
40
41 function parseExamplesJson(data: string): Example[] {
42   try {
43     const examples = JSON.parse(data.toString()) as ExamplesFile;
44
45     return Object.keys(examples).map(key => ({
46       path: examples[key].path,
47       name: examples[key].name,
48       searchKey: key,
49     }));
50   } catch {
51     Logger.log("Failed to parse examples.json");
52
53     return [];
54   }
55 }
56
57 export async function loadExamples(): Promise<Example[]> {
58   try {
59     if (!(await isInternetConnected())) {
60       throw new Error(
61         "Error while downloading examples list. " +
62           "No internet connection"
63       );
64     }
65     const result = await new Promise<Example[]>((resolve, reject) => {
66       // Download the INI file
67       get(EXAMPLES_JSON_URL, response => {
68         if (response.statusCode !== 200) {
69           reject(
70             new Error(
71               "Error while downloading examples list. " +
72                 `Status code: ${response.statusCode}`
73             )
74           );
75         }
76         let data = "";
77
78         // Append data as it arrives
79         response.on("data", chunk => {
80           data += chunk;
81         });
82
83         // Parse the INI data when the download is complete
84         response.on("end", () => {
85           // Resolve with the array of SupportedToolchainVersion
86           resolve(parseExamplesJson(data));
87         });
88
89         // Handle errors
90         response.on("error", error => {
91           reject(error);
92         });
93       });
94     });
95
96     // TODO: Logger.debug
97     Logger.log(`Successfully downloaded examples list from the internet.`);
98
99     return result;
100   } catch (error) {
101     Logger.log(error instanceof Error ? error.message : (error as string));
102
103     try {
104       const examplesFile = readFileSync(
105         joinPosix(getDataRoot(), "examples.json")
106       );
107
108       return parseExamplesJson(examplesFile.toString("utf-8"));
109     } catch (e) {
110       Logger.log("Failed to load examples.json");
111
112       return [];
113     }
114   }
115 }
116
117 export async function setupExample(
118   example: Example,
119   targetPath: string
120 ): Promise<boolean> {
121   const examplesRepoPath = buildExamplesPath();
122   const absoluteExamplePath = joinPosix(examplesRepoPath, example.path);
123
124   const settings = Settings.getInstance();
125   if (settings === undefined) {
126     Logger.log("Error: Settings not initialized.");
127
128     return false;
129   }
130
131   // TODO: this does take about 2s - may be reduced
132   const requirementsCheck = await checkForInstallationRequirements(
133     settings
134   );
135   if (!requirementsCheck) {
136     return false;
137   }
138
139   const gitPath = await getGit(settings);
140
141   if (existsSync(examplesRepoPath)) {
142     let ref = await execAsync(
143       `cd "${examplesRepoPath}" && ${
144         process.env.ComSpec === "powershell.exe" ? "&" : ""
145       }"${gitPath}" rev-parse HEAD`
146     );
147     Logger.log(`Examples git ref is ${ref.stdout}\n`);
148     if (ref.stdout.trim() !== EXAMPLES_GITREF) {
149       Logger.log(`Removing old examples repo\n`);
150       rmSync(examplesRepoPath, { recursive: true, force: true });
151     }
152   }
153
154   if (!existsSync(examplesRepoPath)) {
155     const result = await sparseCloneRepository(
156       EXAMPLES_REPOSITORY_URL,
157       EXAMPLES_TAG,
158       examplesRepoPath,
159       gitPath
160     );
161
162     if (!result) {
163       return result;
164     }
165   }
166
167   Logger.log(`Spare-checkout selected example: ${example.name}`);
168   const result = await sparseCheckout(
169     examplesRepoPath,
170     example.path,
171     gitPath
172   );
173   if (!result) {
174     return result;
175   }
176
177   Logger.log(`Copying example from ${absoluteExamplePath} to ${targetPath}`);
178   // TODO: use example.name or example.search key for project folder name?
179   await cp(absoluteExamplePath, joinPosix(targetPath, example.searchKey), {
180     recursive: true,
181   });
182   Logger.log("Done copying example.");
183
184   return result;
185 }
This page took 0.033699 seconds and 4 git commands to generate.