@sdeverywhere/plugin-deploy 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,242 +1,252 @@
1
- // src/plugin.ts
2
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2 } from "fs";
3
- import { isAbsolute as isAbsolute2, join as joinPath3 } from "path";
4
-
5
- // src/copy-products.ts
6
- import { cpSync, existsSync, mkdirSync } from "fs";
7
- import { isAbsolute, join as joinPath } from "path";
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ //#region src/copy-products.ts
5
+ /**
6
+ * Copy the build products to the deployment directory.
7
+ *
8
+ * @param context The build context.
9
+ * @param options The resolved plugin options.
10
+ */
8
11
  function copyProducts(context, options) {
9
- function copyToDeployDir(src, dst) {
10
- let fullSrcPath;
11
- if (isAbsolute(src)) {
12
- fullSrcPath = src;
13
- } else {
14
- fullSrcPath = joinPath(context.config.rootDir, src);
15
- }
16
- const skipIfNotExists = options.defaultProducts;
17
- if (skipIfNotExists && !existsSync(fullSrcPath)) {
18
- return;
19
- }
20
- context.log("verbose", `Copying '${src}' to '${dst}'...`);
21
- const fullDstPath = joinPath(options.deployDir, dst);
22
- if (existsSync(fullDstPath)) {
23
- mkdirSync(fullDstPath, { recursive: true });
24
- }
25
- cpSync(fullSrcPath, fullDstPath, { recursive: true });
26
- }
27
- for (const product of Object.values(options.products)) {
28
- copyToDeployDir(product.srcPath, product.dstPath);
29
- }
12
+ function copyToDeployDir(src, dst) {
13
+ let fullSrcPath;
14
+ if (isAbsolute(src)) fullSrcPath = src;
15
+ else fullSrcPath = join(context.config.rootDir, src);
16
+ if (options.defaultProducts && !existsSync(fullSrcPath)) return;
17
+ context.log("verbose", `Copying '${src}' to '${dst}'...`);
18
+ const fullDstPath = join(options.deployDir, dst);
19
+ if (existsSync(fullDstPath)) mkdirSync(fullDstPath, { recursive: true });
20
+ cpSync(fullSrcPath, fullDstPath, { recursive: true });
21
+ }
22
+ for (const product of Object.values(options.products)) copyToDeployDir(product.srcPath, product.dstPath);
30
23
  }
31
-
32
- // src/store-artifacts.ts
33
- import { execSync } from "child_process";
34
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync as cpSync2, readFileSync, rmSync, writeFileSync } from "fs";
35
- import { join as joinPath2 } from "path";
36
- var artifactsBranchName = "artifacts";
37
- var artifactsDir = "artifacts";
24
+ //#endregion
25
+ //#region src/store-artifacts.ts
26
+ const artifactsBranchName = "artifacts";
27
+ const artifactsDir = "artifacts";
28
+ /**
29
+ * Store build artifacts in the `artifacts` orphan branch.
30
+ *
31
+ * The `artifacts` directory contains build artifacts for all branches that
32
+ * have been built. The directory structure is as follows:
33
+ * ```
34
+ * artifacts/
35
+ * ├── index.html # Top-level index.html file
36
+ * ├── latest/
37
+ * | ├── index.html # Main branch app
38
+ * | ├── assets/ # Main branch assets
39
+ * ├── branch/
40
+ * | ├── main/
41
+ * | │ ├── app/ # Main branch app files
42
+ * | │ └── extras/ # Main branch check bundles and reports
43
+ * | ├── chris/1234-test/
44
+ * | │ ├── app/ # Feature branch app files
45
+ * | │ └── extras/ # Feature branch check bundles and reports
46
+ * | └── feature/new-ui/
47
+ * | ├── app/ # Feature branch app files
48
+ * | └── extras/ # Feature branch check bundles and reports
49
+ * └── metadata/
50
+ * ├── bundles.json # Listing of available bundles
51
+ * └── index.json # Listing of available branch builds
52
+ * ```
53
+ *
54
+ * @param context The build context.
55
+ * @param options The resolved plugin options.
56
+ * @param currentBranchName The current (sanitized/validated) git branch name.
57
+ */
38
58
  function storeArtifacts(context, options, currentBranchName) {
39
- const deployDir = options.deployDir;
40
- if (!existsSync2(deployDir)) {
41
- context.log("error", `ERROR: Deployment directory '${deployDir}' does not exist`);
42
- return false;
43
- }
44
- try {
45
- if (!checkGitHubPagesConfiguration(context)) {
46
- return false;
47
- }
48
- context.log("info", `Storing artifacts for branch '${currentBranchName}'...`);
49
- const githubActor = process.env.GITHUB_ACTOR;
50
- if (!githubActor) {
51
- context.log(
52
- "error",
53
- "Failed to get the GitHub username associated with the latest push (GITHUB_ACTOR is not defined); the artifacts branch will not be updated"
54
- );
55
- return false;
56
- }
57
- execSync(`git config user.name "${githubActor}"`, { stdio: "inherit" });
58
- execSync(`git config user.email "${githubActor}@users.noreply.github.com"`, { stdio: "inherit" });
59
- let artifactsExists;
60
- try {
61
- const branchRef = `refs/remotes/origin/${artifactsBranchName}`;
62
- execSync(`git show-ref --verify --quiet ${branchRef}`, { stdio: "ignore" });
63
- artifactsExists = true;
64
- } catch (_) {
65
- artifactsExists = false;
66
- }
67
- if (!artifactsExists) {
68
- context.log("verbose", `Creating orphan '${artifactsBranchName}' branch...`);
69
- execSync(`git checkout --orphan ${artifactsBranchName}`, { stdio: "inherit" });
70
- execSync("git rm -rf --cached .", { stdio: "inherit" });
71
- const ignoredFiles = ["*", "!artifacts", "!artifacts/**", "!.gitignore"];
72
- writeFileSync(".gitignore", ignoredFiles.join("\n"));
73
- execSync("git add .gitignore", { stdio: "inherit" });
74
- context.log("verbose", `Created .gitignore file for '${artifactsBranchName}' branch`);
75
- } else {
76
- context.log("verbose", `Switching to existing '${artifactsBranchName}' branch...`);
77
- execSync(`git checkout ${artifactsBranchName}`, { stdio: "inherit" });
78
- }
79
- const currentBranchDir = joinPath2(artifactsDir, "branch", currentBranchName);
80
- if (existsSync2(currentBranchDir)) {
81
- context.log("verbose", `Removing existing branch directory '${currentBranchDir}'...`);
82
- rmSync(currentBranchDir, { recursive: true });
83
- }
84
- context.log("verbose", `Copying staged files from '${deployDir}' to '${currentBranchDir}'...`);
85
- cpSync2(deployDir, currentBranchDir, { recursive: true });
86
- const currentBranchUrlPath = `branch/${currentBranchName}`;
87
- const productSpecs = {};
88
- for (const [name, product] of Object.entries(options.products || {})) {
89
- productSpecs[name] = {
90
- displayName: product.displayName,
91
- path: `${currentBranchUrlPath}/${product.dstPath}`
92
- };
93
- }
94
- const currentBranchSpec = {
95
- name: currentBranchName,
96
- path: currentBranchUrlPath,
97
- lastModified: (/* @__PURE__ */ new Date()).toISOString(),
98
- products: productSpecs
99
- };
100
- updateMetadata(context, options.baseUrl, currentBranchSpec);
101
- if (currentBranchName === "main") {
102
- const stagedAppSrcDir = joinPath2(deployDir, "app");
103
- if (existsSync2(stagedAppSrcDir)) {
104
- context.log("verbose", `Copying main branch app files to 'latest' directory...`);
105
- if (existsSync2("latest")) {
106
- context.log("verbose", `Removing existing 'latest' directory...`);
107
- rmSync("latest", { recursive: true });
108
- }
109
- const stagedAppDstDir = joinPath2(artifactsDir, "latest");
110
- cpSync2(stagedAppSrcDir, stagedAppDstDir, { recursive: true });
111
- }
112
- }
113
- try {
114
- const commitCount = parseInt(execSync("git rev-list --count HEAD", { encoding: "utf8" }).trim());
115
- if (commitCount > 5) {
116
- context.log(
117
- "verbose",
118
- `Found ${commitCount} commits, squashing older commits to reduce size of '${artifactsBranchName}' branch...`
119
- );
120
- const fifthCommitHash = execSync("git rev-list --skip=4 --max-count=1 HEAD", { encoding: "utf8" }).trim();
121
- execSync(`git reset --soft ${fifthCommitHash}`, { stdio: "inherit" });
122
- execSync("git commit --amend --no-edit", { stdio: "inherit" });
123
- context.log("verbose", "Successfully squashed older commits");
124
- }
125
- } catch (_) {
126
- context.log("verbose", "No commits found or error checking commit history, continuing...");
127
- }
128
- execSync(`git add ${artifactsDir}`, { stdio: "inherit" });
129
- try {
130
- execSync("git diff --cached --quiet", { stdio: "ignore" });
131
- context.log("verbose", "No changes to commit");
132
- } catch (_) {
133
- const commitMessage = `build: update artifacts for branch ${currentBranchName}`;
134
- execSync(`git commit -m "${commitMessage}"`, { stdio: "inherit" });
135
- context.log("verbose", `Committed artifacts for branch '${currentBranchName}'...`);
136
- }
137
- context.log("info", `Pushing '${artifactsBranchName}' branch to remote...`);
138
- execSync(`git push --force origin ${artifactsBranchName}`, { stdio: "inherit" });
139
- context.log("info", `\u2705 Successfully stored artifacts for branch '${currentBranchName}'`);
140
- return true;
141
- } catch (error) {
142
- context.log("error", "\u274C Error storing artifacts:");
143
- console.error(error);
144
- return false;
145
- } finally {
146
- context.log("verbose", `Switching to original '${currentBranchName}' branch...`);
147
- execSync(`git checkout ${currentBranchName}`, { stdio: "inherit" });
148
- }
59
+ const deployDir = options.deployDir;
60
+ if (!existsSync(deployDir)) {
61
+ context.log("error", `ERROR: Deployment directory '${deployDir}' does not exist`);
62
+ return false;
63
+ }
64
+ try {
65
+ if (!checkGitHubPagesConfiguration(context)) return false;
66
+ context.log("info", `Storing artifacts for branch '${currentBranchName}'...`);
67
+ const githubActor = process.env.GITHUB_ACTOR;
68
+ if (!githubActor) {
69
+ context.log("error", "Failed to get the GitHub username associated with the latest push (GITHUB_ACTOR is not defined); the artifacts branch will not be updated");
70
+ return false;
71
+ }
72
+ execSync(`git config user.name "${githubActor}"`, { stdio: "inherit" });
73
+ execSync(`git config user.email "${githubActor}@users.noreply.github.com"`, { stdio: "inherit" });
74
+ let artifactsExists;
75
+ try {
76
+ execSync(`git show-ref --verify --quiet ${`refs/remotes/origin/${artifactsBranchName}`}`, { stdio: "ignore" });
77
+ artifactsExists = true;
78
+ } catch (_) {
79
+ artifactsExists = false;
80
+ }
81
+ if (!artifactsExists) {
82
+ context.log("verbose", `Creating orphan '${artifactsBranchName}' branch...`);
83
+ execSync(`git checkout --orphan ${artifactsBranchName}`, { stdio: "inherit" });
84
+ execSync("git rm -rf --cached .", { stdio: "inherit" });
85
+ writeFileSync(".gitignore", [
86
+ "*",
87
+ "!artifacts",
88
+ "!artifacts/**",
89
+ "!.gitignore"
90
+ ].join("\n"));
91
+ execSync("git add .gitignore", { stdio: "inherit" });
92
+ context.log("verbose", `Created .gitignore file for '${artifactsBranchName}' branch`);
93
+ } else {
94
+ context.log("verbose", `Switching to existing '${artifactsBranchName}' branch...`);
95
+ execSync(`git checkout ${artifactsBranchName}`, { stdio: "inherit" });
96
+ }
97
+ const currentBranchDir = join(artifactsDir, "branch", currentBranchName);
98
+ if (existsSync(currentBranchDir)) {
99
+ context.log("verbose", `Removing existing branch directory '${currentBranchDir}'...`);
100
+ rmSync(currentBranchDir, { recursive: true });
101
+ }
102
+ context.log("verbose", `Copying staged files from '${deployDir}' to '${currentBranchDir}'...`);
103
+ cpSync(deployDir, currentBranchDir, { recursive: true });
104
+ const currentBranchUrlPath = `branch/${currentBranchName}`;
105
+ const productSpecs = {};
106
+ for (const [name, product] of Object.entries(options.products || {})) productSpecs[name] = {
107
+ displayName: product.displayName,
108
+ path: `${currentBranchUrlPath}/${product.dstPath}`
109
+ };
110
+ const currentBranchSpec = {
111
+ name: currentBranchName,
112
+ path: currentBranchUrlPath,
113
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
114
+ products: productSpecs
115
+ };
116
+ updateMetadata(context, options.baseUrl, currentBranchSpec);
117
+ if (currentBranchName === "main") {
118
+ const stagedAppSrcDir = join(deployDir, "app");
119
+ if (existsSync(stagedAppSrcDir)) {
120
+ context.log("verbose", `Copying main branch app files to 'latest' directory...`);
121
+ if (existsSync("latest")) {
122
+ context.log("verbose", `Removing existing 'latest' directory...`);
123
+ rmSync("latest", { recursive: true });
124
+ }
125
+ const stagedAppDstDir = join(artifactsDir, "latest");
126
+ cpSync(stagedAppSrcDir, stagedAppDstDir, { recursive: true });
127
+ }
128
+ }
129
+ try {
130
+ const commitCount = parseInt(execSync("git rev-list --count HEAD", { encoding: "utf8" }).trim());
131
+ if (commitCount > 5) {
132
+ context.log("verbose", `Found ${commitCount} commits, squashing older commits to reduce size of '${artifactsBranchName}' branch...`);
133
+ const fifthCommitHash = execSync("git rev-list --skip=4 --max-count=1 HEAD", { encoding: "utf8" }).trim();
134
+ execSync(`git reset --soft ${fifthCommitHash}`, { stdio: "inherit" });
135
+ execSync("git commit --amend --no-edit", { stdio: "inherit" });
136
+ context.log("verbose", "Successfully squashed older commits");
137
+ }
138
+ } catch (_) {
139
+ context.log("verbose", "No commits found or error checking commit history, continuing...");
140
+ }
141
+ execSync(`git add ${artifactsDir}`, { stdio: "inherit" });
142
+ try {
143
+ execSync("git diff --cached --quiet", { stdio: "ignore" });
144
+ context.log("verbose", "No changes to commit");
145
+ } catch (_) {
146
+ const commitMessage = `build: update artifacts for branch ${currentBranchName}`;
147
+ execSync(`git commit -m "${commitMessage}"`, { stdio: "inherit" });
148
+ context.log("verbose", `Committed artifacts for branch '${currentBranchName}'...`);
149
+ }
150
+ context.log("info", `Pushing '${artifactsBranchName}' branch to remote...`);
151
+ execSync(`git push --force origin ${artifactsBranchName}`, { stdio: "inherit" });
152
+ context.log("info", `✅ Successfully stored artifacts for branch '${currentBranchName}'`);
153
+ return true;
154
+ } catch (error) {
155
+ context.log("error", "❌ Error storing artifacts:");
156
+ console.error(error);
157
+ return false;
158
+ } finally {
159
+ context.log("verbose", `Switching to original '${currentBranchName}' branch...`);
160
+ execSync(`git checkout ${currentBranchName}`, { stdio: "inherit" });
161
+ }
149
162
  }
163
+ /**
164
+ * Check if GitHub Pages is configured for this repo.
165
+ *
166
+ * @param context The build context.
167
+ * @returns true if GitHub Pages is configured, false otherwise.
168
+ */
150
169
  function checkGitHubPagesConfiguration(context) {
151
- context.log("verbose", "Checking if GitHub Pages is configured for this repo...");
152
- const ownerAndRepo = process.env.GITHUB_REPOSITORY;
153
- const ghAccept = '"Accept: application/vnd.github+json"';
154
- const ghApiVersion = '"X-GitHub-Api-Version: 2022-11-28"';
155
- const ghPagesApiPath = `/repos/${ownerAndRepo}/pages`;
156
- let isGitHubPagesSetup;
157
- try {
158
- const pagesResponse = execSync(`gh api -H ${ghAccept} -H ${ghApiVersion} ${ghPagesApiPath}`);
159
- const pagesMetadata = JSON.parse(pagesResponse.toString());
160
- isGitHubPagesSetup = pagesMetadata.build_type === "workflow";
161
- if (!isGitHubPagesSetup) {
162
- context.log(
163
- "verbose",
164
- "GitHub Pages is not configured to use workflow builds for this repo, but found existing configuration:"
165
- );
166
- context.log("verbose", " build_type: " + pagesMetadata.build_type);
167
- context.log("verbose", " source.branch: " + pagesMetadata.source?.branch);
168
- context.log("verbose", " source.path: " + pagesMetadata.source?.path);
169
- }
170
- } catch (_) {
171
- context.log("verbose", "No existing GitHub Pages configuration found");
172
- isGitHubPagesSetup = false;
173
- }
174
- if (isGitHubPagesSetup) {
175
- context.log("verbose", "GitHub Pages is already configured for this repo");
176
- return true;
177
- } else {
178
- const msg = [];
179
- msg.push("GitHub Pages is not configured for this repo");
180
- msg.push("For now, you must manually enable GitHub Pages as follows:");
181
- msg.push(" 1. Go to the GitHub repository settings");
182
- msg.push(' 2. In the sidebar, select "Pages"');
183
- msg.push(' 3. Under "Build and deployment", change "Source" to "GitHub Actions"');
184
- msg.push(' 4. In the tab bar, select "Actions"');
185
- msg.push(" 5. Click on the most recent failed workflow run");
186
- msg.push(' 6. In the upper right corner, click "Re-run jobs" then "Re-run all jobs"');
187
- context.log("error", msg.join("\n"));
188
- return false;
189
- }
170
+ context.log("verbose", "Checking if GitHub Pages is configured for this repo...");
171
+ const ownerAndRepo = process.env.GITHUB_REPOSITORY;
172
+ const ghAccept = "\"Accept: application/vnd.github+json\"";
173
+ const ghApiVersion = "\"X-GitHub-Api-Version: 2022-11-28\"";
174
+ const ghPagesApiPath = `/repos/${ownerAndRepo}/pages`;
175
+ let isGitHubPagesSetup;
176
+ try {
177
+ const pagesResponse = execSync(`gh api -H ${ghAccept} -H ${ghApiVersion} ${ghPagesApiPath}`);
178
+ const pagesMetadata = JSON.parse(pagesResponse.toString());
179
+ isGitHubPagesSetup = pagesMetadata.build_type === "workflow";
180
+ if (!isGitHubPagesSetup) {
181
+ context.log("verbose", "GitHub Pages is not configured to use workflow builds for this repo, but found existing configuration:");
182
+ context.log("verbose", " build_type: " + pagesMetadata.build_type);
183
+ context.log("verbose", " source.branch: " + pagesMetadata.source?.branch);
184
+ context.log("verbose", " source.path: " + pagesMetadata.source?.path);
185
+ }
186
+ } catch (_) {
187
+ context.log("verbose", "No existing GitHub Pages configuration found");
188
+ isGitHubPagesSetup = false;
189
+ }
190
+ if (isGitHubPagesSetup) {
191
+ context.log("verbose", "GitHub Pages is already configured for this repo");
192
+ return true;
193
+ } else {
194
+ const msg = [];
195
+ msg.push("GitHub Pages is not configured for this repo");
196
+ msg.push("For now, you must manually enable GitHub Pages as follows:");
197
+ msg.push(" 1. Go to the GitHub repository settings");
198
+ msg.push(" 2. In the sidebar, select \"Pages\"");
199
+ msg.push(" 3. Under \"Build and deployment\", change \"Source\" to \"GitHub Actions\"");
200
+ msg.push(" 4. In the tab bar, select \"Actions\"");
201
+ msg.push(" 5. Click on the most recent failed workflow run");
202
+ msg.push(" 6. In the upper right corner, click \"Re-run jobs\" then \"Re-run all jobs\"");
203
+ context.log("error", msg.join("\n"));
204
+ return false;
205
+ }
190
206
  }
207
+ /**
208
+ * Update the `metadata/index.json` file with the URL paths for the branch and its artifacts,
209
+ * update the `metadata/bundles.json` file with the available bundles, and generate a top-level
210
+ * `index.html` file that lists all available branch builds.
211
+ */
191
212
  function updateMetadata(context, baseUrl, currentBranchSpec) {
192
- const metadataDir = joinPath2(artifactsDir, "metadata");
193
- const indexJsonFile = joinPath2(metadataDir, "index.json");
194
- const bundlesJsonFile = joinPath2(metadataDir, "bundles.json");
195
- mkdirSync2(metadataDir, { recursive: true });
196
- let branchSpecs = [];
197
- if (existsSync2(indexJsonFile)) {
198
- try {
199
- branchSpecs = JSON.parse(readFileSync(indexJsonFile, "utf8"));
200
- } catch (_) {
201
- context.log("info", "\u26A0\uFE0F Could not parse existing index.json file, starting fresh");
202
- branchSpecs = [];
203
- }
204
- }
205
- branchSpecs = branchSpecs.filter((spec) => spec.name !== currentBranchSpec.name);
206
- branchSpecs.push(currentBranchSpec);
207
- branchSpecs.sort((a, b) => b.lastModified.localeCompare(a.lastModified));
208
- writeFileSync(indexJsonFile, JSON.stringify(branchSpecs, null, 2));
209
- const bundleSpecs = [];
210
- for (const branchSpec of branchSpecs) {
211
- if (branchSpec.products?.checkBundle) {
212
- bundleSpecs.push({
213
- name: branchSpec.name,
214
- // TODO: For now we store the full URL to the bundle in the `bundles.json` file using
215
- // the `baseUrl` value from the plugin options. Ideally we could use a relative URL
216
- // here instead of encoding the full URL in the `bundles.json` file, but
217
- // `@sdeverywhere/plugin-check` currently doesn't understand relative URLs.
218
- url: `${baseUrl}/${branchSpec.products.checkBundle.path}`,
219
- lastModified: branchSpec.lastModified
220
- });
221
- }
222
- }
223
- writeFileSync(bundlesJsonFile, JSON.stringify(bundleSpecs, null, 2));
224
- context.log("verbose", `Updated metadata for branch '${currentBranchSpec.name}'`);
225
- generateIndexHtml(context, branchSpecs);
213
+ const metadataDir = join(artifactsDir, "metadata");
214
+ const indexJsonFile = join(metadataDir, "index.json");
215
+ const bundlesJsonFile = join(metadataDir, "bundles.json");
216
+ mkdirSync(metadataDir, { recursive: true });
217
+ let branchSpecs = [];
218
+ if (existsSync(indexJsonFile)) try {
219
+ branchSpecs = JSON.parse(readFileSync(indexJsonFile, "utf8"));
220
+ } catch (_) {
221
+ context.log("info", "⚠️ Could not parse existing index.json file, starting fresh");
222
+ branchSpecs = [];
223
+ }
224
+ branchSpecs = branchSpecs.filter((spec) => spec.name !== currentBranchSpec.name);
225
+ branchSpecs.push(currentBranchSpec);
226
+ branchSpecs.sort((a, b) => b.lastModified.localeCompare(a.lastModified));
227
+ writeFileSync(indexJsonFile, JSON.stringify(branchSpecs, null, 2));
228
+ const bundleSpecs = [];
229
+ for (const branchSpec of branchSpecs) if (branchSpec.products?.checkBundle) bundleSpecs.push({
230
+ name: branchSpec.name,
231
+ url: `${baseUrl}/${branchSpec.products.checkBundle.path}`,
232
+ lastModified: branchSpec.lastModified
233
+ });
234
+ writeFileSync(bundlesJsonFile, JSON.stringify(bundleSpecs, null, 2));
235
+ context.log("verbose", `Updated metadata for branch '${currentBranchSpec.name}'`);
236
+ generateIndexHtml(context, branchSpecs);
226
237
  }
238
+ /**
239
+ * Generate a top-level index.html file that lists all available branch builds.
240
+ */
227
241
  function generateIndexHtml(context, branchSpecs) {
228
- const indexHtmlPath = joinPath2(artifactsDir, "index.html");
229
- const latestBuildDate = branchSpecs.find((branch) => branch.name === "main")?.lastModified || "";
230
- function getBranchLinksHtml(branch) {
231
- const links = [];
232
- for (const product of Object.values(branch.products || {})) {
233
- if (product.displayName) {
234
- links.push(`<a href="${product.path}">${product.displayName}</a>`);
235
- }
236
- }
237
- return links.join('<span class="separator">|</span>');
238
- }
239
- const htmlContent = `<!DOCTYPE html>
242
+ const indexHtmlPath = join(artifactsDir, "index.html");
243
+ const latestBuildDate = branchSpecs.find((branch) => branch.name === "main")?.lastModified || "";
244
+ function getBranchLinksHtml(branch) {
245
+ const links = [];
246
+ for (const product of Object.values(branch.products || {})) if (product.displayName) links.push(`<a href="${product.path}">${product.displayName}</a>`);
247
+ return links.join("<span class=\"separator\">|</span>");
248
+ }
249
+ const htmlContent = `<!DOCTYPE html>
240
250
  <html lang="en">
241
251
  <head>
242
252
  <meta charset="UTF-8">
@@ -306,7 +316,7 @@ function generateIndexHtml(context, branchSpecs) {
306
316
  }
307
317
  })
308
318
  })
309
- </script>
319
+ <\/script>
310
320
  </head>
311
321
  <body>
312
322
  <div class="container">
@@ -317,8 +327,7 @@ function generateIndexHtml(context, branchSpecs) {
317
327
  <div class="grid">
318
328
  <div class="header">Branch</div>
319
329
  <div class="header">Last Updated</div>
320
- ${branchSpecs.map(
321
- (branch) => `
330
+ ${branchSpecs.map((branch) => `
322
331
  <div class="row">
323
332
  <div class="cell">
324
333
  <span class="branch-name">${branch.name}</span>
@@ -329,152 +338,132 @@ ${branchSpecs.map(
329
338
  <div class="cell">
330
339
  <span class="date" data-timestamp="${branch.lastModified}"></span>
331
340
  </div>
332
- </div>`
333
- ).join("")}
341
+ </div>`).join("")}
334
342
  </div>
335
343
  </div>
336
344
  </body>
337
345
  </html>`;
338
- writeFileSync(indexHtmlPath, htmlContent);
339
- context.log("verbose", `Generated top-level index.html`);
346
+ writeFileSync(indexHtmlPath, htmlContent);
347
+ context.log("verbose", `Generated top-level index.html`);
340
348
  }
341
-
342
- // src/validate-branch-name.ts
349
+ //#endregion
350
+ //#region src/validate-branch-name.ts
351
+ /**
352
+ * Validate the given branch name to ensure it meets requirements for URL-safe paths.
353
+ *
354
+ * Allows: /, -, _, a-z, A-Z, 0-9
355
+ *
356
+ * @param branchName The branch name to check.
357
+ * @throws Error if the branch name is invalid.
358
+ */
343
359
  function validateBranchName(branchName) {
344
- const validBranchPattern = /^[/\-_a-zA-Z0-9]+$/;
345
- if (!validBranchPattern.test(branchName)) {
346
- throw new Error(
347
- `Branch name '${branchName}' contains invalid characters; branch names must only contain: /, -, _, a-z, A-Z, 0-9`
348
- );
349
- }
350
- if (branchName.startsWith("/") || branchName.endsWith("/")) {
351
- throw new Error(`Branch name '${branchName}' cannot start or end with "/"`);
352
- }
353
- if (branchName.includes("//")) {
354
- throw new Error(`Branch name '${branchName}' cannot contain consecutive slashes "//"`);
355
- }
360
+ if (!/^[/\-_a-zA-Z0-9]+$/.test(branchName)) throw new Error(`Branch name '${branchName}' contains invalid characters; branch names must only contain: /, -, _, a-z, A-Z, 0-9`);
361
+ if (branchName.startsWith("/") || branchName.endsWith("/")) throw new Error(`Branch name '${branchName}' cannot start or end with "/"`);
362
+ if (branchName.includes("//")) throw new Error(`Branch name '${branchName}' cannot contain consecutive slashes "//"`);
356
363
  }
357
-
358
- // src/plugin.ts
364
+ //#endregion
365
+ //#region src/plugin.ts
359
366
  function deployPlugin(options) {
360
- return new DeployPlugin(options ?? {});
367
+ return new DeployPlugin(options ?? {});
361
368
  }
362
369
  var DeployPlugin = class {
363
- constructor(userOptions) {
364
- this.userOptions = userOptions;
365
- }
366
- async init() {
367
- const branchName = getCurrentBranchName();
368
- if (branchName) {
369
- validateBranchName(branchName);
370
- }
371
- }
372
- async postBuild(context) {
373
- if (context.config.mode !== "production") {
374
- return true;
375
- }
376
- context.log("info", "\nPreparing to deploy build products...");
377
- const resolvedOptions = resolveOptions(context, this.userOptions);
378
- const deployDir = resolvedOptions.deployDir;
379
- if (existsSync3(deployDir)) {
380
- context.log("verbose", "Removing existing deploy directory...");
381
- rmSync2(deployDir, { recursive: true, force: true });
382
- }
383
- context.log("verbose", "Creating deploy directory...");
384
- mkdirSync3(deployDir, { recursive: true });
385
- context.log("verbose", "Copying build products to deploy directory...");
386
- copyProducts(context, resolvedOptions);
387
- if (process.env.VITEST === "true") {
388
- context.log("info", "Skipping `storeArtifacts` step in test mode...");
389
- return true;
390
- }
391
- const currentBranchName = getCurrentBranchName();
392
- if (!currentBranchName) {
393
- context.log(
394
- "info",
395
- "Failed to get the name of the current branch (GITHUB_REF_NAME is not defined); artifacts branch will not be updated"
396
- );
397
- return true;
398
- }
399
- context.log("verbose", "Updating artifacts branch with build products...");
400
- return storeArtifacts(context, resolvedOptions, currentBranchName);
401
- }
370
+ constructor(userOptions) {
371
+ this.userOptions = userOptions;
372
+ }
373
+ async init() {
374
+ const branchName = getCurrentBranchName();
375
+ if (branchName) validateBranchName(branchName);
376
+ }
377
+ async postBuild(context) {
378
+ if (context.config.mode !== "production") return true;
379
+ context.log("info", "\nPreparing to deploy build products...");
380
+ const resolvedOptions = resolveOptions(context, this.userOptions);
381
+ const deployDir = resolvedOptions.deployDir;
382
+ if (existsSync(deployDir)) {
383
+ context.log("verbose", "Removing existing deploy directory...");
384
+ rmSync(deployDir, {
385
+ recursive: true,
386
+ force: true
387
+ });
388
+ }
389
+ context.log("verbose", "Creating deploy directory...");
390
+ mkdirSync(deployDir, { recursive: true });
391
+ context.log("verbose", "Copying build products to deploy directory...");
392
+ copyProducts(context, resolvedOptions);
393
+ if (process.env.VITEST === "true") {
394
+ context.log("info", "Skipping `storeArtifacts` step in test mode...");
395
+ return true;
396
+ }
397
+ const currentBranchName = getCurrentBranchName();
398
+ if (!currentBranchName) {
399
+ context.log("info", "Failed to get the name of the current branch (GITHUB_REF_NAME is not defined); artifacts branch will not be updated");
400
+ return true;
401
+ }
402
+ context.log("verbose", "Updating artifacts branch with build products...");
403
+ return storeArtifacts(context, resolvedOptions, currentBranchName);
404
+ }
402
405
  };
403
406
  function getRepoOwnerAndName() {
404
- const ownerAndRepo = process.env.GITHUB_REPOSITORY;
405
- if (ownerAndRepo) {
406
- const [owner, repo] = ownerAndRepo.split("/");
407
- return [owner, repo];
408
- } else {
409
- return void 0;
410
- }
407
+ const ownerAndRepo = process.env.GITHUB_REPOSITORY;
408
+ if (ownerAndRepo) {
409
+ const [owner, repo] = ownerAndRepo.split("/");
410
+ return [owner, repo];
411
+ } else return;
411
412
  }
412
413
  function getCurrentBranchName() {
413
- if (process.env.VITEST) {
414
- return process.env.TEST_BRANCH_NAME;
415
- } else {
416
- return process.env.GITHUB_REF_NAME;
417
- }
414
+ if (process.env.VITEST) return process.env.TEST_BRANCH_NAME;
415
+ else return process.env.GITHUB_REF_NAME;
418
416
  }
419
417
  function resolveOptions(context, userOptions) {
420
- let baseUrl;
421
- if (userOptions.baseUrl) {
422
- baseUrl = userOptions.baseUrl;
423
- } else {
424
- const repoOwnerAndName = getRepoOwnerAndName();
425
- if (repoOwnerAndName) {
426
- const [owner, repo] = repoOwnerAndName;
427
- baseUrl = `https://${owner}.github.io/${repo}`;
428
- } else {
429
- context.log(
430
- "info",
431
- "Failed to get the name of the repository (GITHUB_REPOSITORY is not defined); the deploy directory will be populated, but the artifacts branch will not be updated"
432
- );
433
- baseUrl = void 0;
434
- }
435
- }
436
- let deployDir;
437
- if (userOptions.deployDir) {
438
- if (isAbsolute2(userOptions.deployDir)) {
439
- deployDir = userOptions.deployDir;
440
- } else {
441
- deployDir = joinPath3(context.config.rootDir, userOptions.deployDir);
442
- }
443
- } else {
444
- deployDir = joinPath3(context.config.prepDir, "deploy");
445
- }
446
- let products;
447
- let defaultProducts;
448
- if (userOptions.products) {
449
- products = userOptions.products;
450
- defaultProducts = false;
451
- } else {
452
- products = {
453
- app: {
454
- displayName: "app",
455
- srcPath: "packages/app/public",
456
- dstPath: "app"
457
- },
458
- checkReport: {
459
- displayName: "checks",
460
- srcPath: "sde-prep/check-report",
461
- dstPath: "extras/check-compare-to-base"
462
- },
463
- checkBundle: {
464
- srcPath: "sde-prep/check-bundle.js",
465
- dstPath: "extras/check-bundle.js"
466
- }
467
- };
468
- defaultProducts = true;
469
- }
470
- return {
471
- baseUrl,
472
- deployDir,
473
- products,
474
- defaultProducts
475
- };
418
+ let baseUrl;
419
+ if (userOptions.baseUrl) baseUrl = userOptions.baseUrl;
420
+ else {
421
+ const repoOwnerAndName = getRepoOwnerAndName();
422
+ if (repoOwnerAndName) {
423
+ const [owner, repo] = repoOwnerAndName;
424
+ baseUrl = `https://${owner}.github.io/${repo}`;
425
+ } else {
426
+ context.log("info", "Failed to get the name of the repository (GITHUB_REPOSITORY is not defined); the deploy directory will be populated, but the artifacts branch will not be updated");
427
+ baseUrl = void 0;
428
+ }
429
+ }
430
+ let deployDir;
431
+ if (userOptions.deployDir) {
432
+ if (isAbsolute(userOptions.deployDir)) deployDir = userOptions.deployDir;
433
+ else deployDir = join(context.config.rootDir, userOptions.deployDir);
434
+ } else deployDir = join(context.config.prepDir, "deploy");
435
+ let products;
436
+ let defaultProducts;
437
+ if (userOptions.products) {
438
+ products = userOptions.products;
439
+ defaultProducts = false;
440
+ } else {
441
+ products = {
442
+ app: {
443
+ displayName: "app",
444
+ srcPath: "packages/app/public",
445
+ dstPath: "app"
446
+ },
447
+ checkReport: {
448
+ displayName: "checks",
449
+ srcPath: "sde-prep/check-report",
450
+ dstPath: "extras/check-compare-to-base"
451
+ },
452
+ checkBundle: {
453
+ srcPath: "sde-prep/check-bundle.js",
454
+ dstPath: "extras/check-bundle.js"
455
+ }
456
+ };
457
+ defaultProducts = true;
458
+ }
459
+ return {
460
+ baseUrl,
461
+ deployDir,
462
+ products,
463
+ defaultProducts
464
+ };
476
465
  }
477
- export {
478
- deployPlugin
479
- };
466
+ //#endregion
467
+ export { deployPlugin };
468
+
480
469
  //# sourceMappingURL=index.js.map