@adobe/ccweb-add-on-ssl 2.5.0 → 3.0.0

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.
Files changed (57) hide show
  1. package/.mocharc.json +9 -2
  2. package/README.md +5 -4
  3. package/bin/run.js +3 -2
  4. package/dist/AnalyticsMarkers.d.ts +5 -2
  5. package/dist/AnalyticsMarkers.d.ts.map +1 -1
  6. package/dist/AnalyticsMarkers.js +3 -0
  7. package/dist/app/CommandExecutor.d.ts +1 -1
  8. package/dist/app/CommandExecutor.d.ts.map +1 -1
  9. package/dist/app/PurgeCommandExecutor.d.ts +53 -0
  10. package/dist/app/PurgeCommandExecutor.d.ts.map +1 -0
  11. package/dist/app/PurgeCommandExecutor.js +125 -0
  12. package/dist/app/SSLReader.d.ts +2 -1
  13. package/dist/app/SSLReader.d.ts.map +1 -1
  14. package/dist/app/SetupCommandExecutor.d.ts.map +1 -1
  15. package/dist/app/SetupCommandExecutor.js +10 -7
  16. package/dist/app/WxpSSLReader.d.ts +8 -1
  17. package/dist/app/WxpSSLReader.d.ts.map +1 -1
  18. package/dist/app/WxpSSLReader.js +59 -5
  19. package/dist/app/index.d.ts +2 -1
  20. package/dist/app/index.d.ts.map +1 -1
  21. package/dist/app/index.js +2 -1
  22. package/dist/commands/purge.d.ts +41 -0
  23. package/dist/commands/purge.d.ts.map +1 -0
  24. package/dist/commands/purge.js +56 -0
  25. package/dist/commands/setup.d.ts +5 -10
  26. package/dist/commands/setup.d.ts.map +1 -1
  27. package/dist/commands/setup.js +5 -32
  28. package/dist/config/inversify.config.d.ts +2 -1
  29. package/dist/config/inversify.config.d.ts.map +1 -1
  30. package/dist/config/inversify.config.js +6 -1
  31. package/dist/config/inversify.types.d.ts.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -0
  35. package/dist/models/Types.d.ts +0 -1
  36. package/dist/models/Types.d.ts.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +14 -12
  39. package/src/AnalyticsMarkers.ts +5 -2
  40. package/src/app/CommandExecutor.ts +1 -1
  41. package/src/app/PurgeCommandExecutor.ts +144 -0
  42. package/src/app/SSLReader.ts +2 -1
  43. package/src/app/SetupCommandExecutor.ts +10 -7
  44. package/src/app/WxpSSLReader.ts +71 -5
  45. package/src/app/index.ts +2 -1
  46. package/src/commands/purge.ts +72 -0
  47. package/src/commands/setup.ts +12 -40
  48. package/src/config/inversify.config.ts +9 -2
  49. package/src/config/inversify.types.ts +5 -1
  50. package/src/index.ts +2 -0
  51. package/src/test/app/PurgeCommandExecutor.spec.ts +195 -0
  52. package/src/test/app/SetupCommandExecutor.spec.ts +141 -34
  53. package/src/test/app/WxpSSLReader.spec.ts +107 -33
  54. package/src/test/commands/command.spec.ts +108 -0
  55. package/src/test/commands/purge.spec.ts +132 -0
  56. package/src/test/commands/setup.spec.ts +28 -29
  57. package/tsconfig.json +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/ccweb-add-on-ssl",
3
- "version": "2.5.0",
3
+ "version": "3.0.0",
4
4
  "author": "Adobe",
5
5
  "license": "MIT",
6
6
  "description": "SSL scripts for Adobe Creative Cloud Web Add-on.",
@@ -16,18 +16,21 @@
16
16
  "bin": {
17
17
  "ccweb-add-on-ssl": "./bin/run.js"
18
18
  },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
19
22
  "oclif": {
20
23
  "bin": "ccweb-add-on-ssl",
21
24
  "commands": "./dist/commands",
22
- "plugins": [],
23
25
  "topicSeparator": " "
24
26
  },
25
27
  "repository": {
26
28
  "url": "https://github.com/adobe/create-ccweb-add-on"
27
29
  },
28
30
  "dependencies": {
29
- "@adobe/ccweb-add-on-devcert": "0.1.1",
30
- "@oclif/core": "2.8.0",
31
+ "@adobe/ccweb-add-on-devcert": "0.2.1",
32
+ "@oclif/core": "4.2.8",
33
+ "@swc/helpers": "0.5.12",
31
34
  "chalk": "4.1.1",
32
35
  "fs-extra": "10.0.1",
33
36
  "inversify": "6.0.1",
@@ -35,18 +38,17 @@
35
38
  "prompts": "2.4.2",
36
39
  "reflect-metadata": "0.1.13",
37
40
  "string-template": "1.0.0",
38
- "tslib": "2.6.2",
39
- "@swc/helpers": "0.5.12",
40
- "@adobe/ccweb-add-on-analytics": "2.5.0",
41
- "@adobe/ccweb-add-on-core": "2.5.0"
41
+ "tslib": "2.7.0",
42
+ "@adobe/ccweb-add-on-analytics": "3.0.0",
43
+ "@adobe/ccweb-add-on-core": "3.0.0"
42
44
  },
43
45
  "devDependencies": {
44
- "@oclif/test": "2.3.4",
46
+ "@oclif/test": "4.1.12",
45
47
  "@types/chai-as-promised": "7.1.3",
46
48
  "@types/chai": "4.2.14",
47
49
  "@types/fs-extra": "9.0.13",
48
50
  "@types/mocha": "9.1.1",
49
- "@types/node": "16.18.36",
51
+ "@types/node": "18.18.2",
50
52
  "@types/prompts": "2.0.14",
51
53
  "@types/sinon": "9.0.8",
52
54
  "@types/string-template": "1.0.2",
@@ -54,13 +56,13 @@
54
56
  "chai-as-promised": "7.1.1",
55
57
  "chai": "4.3.4",
56
58
  "mocha": "10.0.0",
57
- "oclif": "3.2.28",
59
+ "oclif": "4.17.34",
58
60
  "prettier": "2.8.0",
59
61
  "rimraf": "3.0.2",
60
62
  "sinon": "9.2.1",
61
63
  "ts-node": "10.9.2",
62
64
  "ts-sinon": "2.0.1",
63
- "typescript": "5.4.5"
65
+ "typescript": "5.7.3"
64
66
  },
65
67
  "scripts": {
66
68
  "clean": "rimraf dist coverage",
@@ -25,12 +25,15 @@
25
25
  export enum AnalyticsErrorMarkers {
26
26
  ERROR_SSL_INVALID_HOSTNAME = "ERROR_SSL_INVALID_HOSTNAME",
27
27
  ERROR_SSL_SETUP = "ERROR_SSL_SETUP",
28
- ERROR_SSL_REMOVE = "ERROR_SSL_REMOVE"
28
+ ERROR_SSL_REMOVE = "ERROR_SSL_REMOVE",
29
+ ERROR_SSL_PURGE = "ERROR_SSL_PURGE"
29
30
  }
30
31
 
31
32
  export enum AnalyticsSuccessMarkers {
32
33
  SUCCESSFUL_SSL_MANUAL_SETUP = "SUCCESSFUL_SSL_MANUAL_SETUP",
33
34
  SUCCESSFUL_SSL_AUTOMATIC_SETUP = "SUCCESSFUL_SSL_AUTOMATIC_SETUP",
34
35
  SUCCESSFUL_SSL_MANUAL_REMOVE = "SUCCESSFUL_SSL_MANUAL_REMOVE",
35
- SUCCESSFUL_SSL_AUTOMATIC_REMOVE = "SUCCESSFUL_SSL_AUTOMATIC_REMOVE"
36
+ SUCCESSFUL_SSL_AUTOMATIC_REMOVE = "SUCCESSFUL_SSL_AUTOMATIC_REMOVE",
37
+ SUCCESSFUL_SSL_MANUAL_PURGE = "SUCCESSFUL_SSL_MANUAL_PURGE",
38
+ SUCCESSFUL_SSL_AUTOMATIC_PURGE = "SUCCESSFUL_SSL_AUTOMATIC_PURGE"
36
39
  }
@@ -33,5 +33,5 @@ export interface CommandExecutor {
33
33
  * @param options - Command arguments entered by user represented as {@link CommandOptions}.
34
34
  * @returns Promise.
35
35
  */
36
- execute(options: CommandOptions): Promise<void>;
36
+ execute(options?: CommandOptions): Promise<void>;
37
37
  }
@@ -0,0 +1,144 @@
1
+ /********************************************************************************
2
+ * MIT License
3
+
4
+ * © Copyright 2025 Adobe. All rights reserved.
5
+
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ ********************************************************************************/
24
+
25
+ import type { AnalyticsService } from "@adobe/ccweb-add-on-analytics";
26
+ import { ITypes as IAnalyticsTypes } from "@adobe/ccweb-add-on-analytics";
27
+ import type { Logger, Preferences } from "@adobe/ccweb-add-on-core";
28
+ import { ITypes as ICoreTypes } from "@adobe/ccweb-add-on-core";
29
+ import devcert from "@adobe/ccweb-add-on-devcert";
30
+ import chalk from "chalk";
31
+ import fs from "fs-extra";
32
+ import { inject, injectable } from "inversify";
33
+ import prompts from "prompts";
34
+ import "reflect-metadata";
35
+ import { AnalyticsErrorMarkers, AnalyticsSuccessMarkers } from "../AnalyticsMarkers.js";
36
+ import { SSLRemoveOption } from "../models/Types.js";
37
+ import type { CommandExecutor } from "./CommandExecutor.js";
38
+
39
+ /**
40
+ * Purge command execution implementation class.
41
+ */
42
+ @injectable()
43
+ export class PurgeCommandExecutor implements CommandExecutor {
44
+ private readonly _preferences: Preferences;
45
+ private readonly _analyticsService: AnalyticsService;
46
+ private readonly _logger: Logger;
47
+
48
+ /**
49
+ * Instantiate {@link PurgeCommandExecutor}.
50
+ * @param preferences - {@link Preferences} reference.
51
+ * @param analyticsService - {@link AnalyticsService} reference.
52
+ * @param logger - {@link Logger} reference.
53
+ * @returns Reference to a new {@link PurgeCommandExecutor} instance.
54
+ */
55
+ constructor(
56
+ @inject(ICoreTypes.Preferences) preferences: Preferences,
57
+ @inject(IAnalyticsTypes.AnalyticsService) analyticsService: AnalyticsService,
58
+ @inject(ICoreTypes.Logger) logger: Logger
59
+ ) {
60
+ this._preferences = preferences;
61
+ this._analyticsService = analyticsService;
62
+ this._logger = logger;
63
+ }
64
+
65
+ /**
66
+ * Purge all SSL artifacts and invalidate the certificate authority from the trusted source.
67
+ * @returns Promise.
68
+ */
69
+ async execute(): Promise<void> {
70
+ const response = await prompts.prompt({
71
+ type: "select",
72
+ name: "purgeConfirmation",
73
+ message: this._promptMessage(LOGS.purgeConfirmation),
74
+ choices: [
75
+ { title: this._promptMessageOption(LOGS.keepSSL), value: SSLRemoveOption.Keep },
76
+ { title: this._promptMessageOption(LOGS.removeSSL), value: SSLRemoveOption.Remove }
77
+ ],
78
+ initial: 0
79
+ });
80
+
81
+ if (!response || !response.purgeConfirmation) {
82
+ this._analyticsService.postEvent(
83
+ AnalyticsErrorMarkers.ERROR_SSL_PURGE,
84
+ LOGS.sslPurgeOptionNotSpecified,
85
+ false
86
+ );
87
+
88
+ return;
89
+ }
90
+
91
+ if (response.purgeConfirmation === SSLRemoveOption.Keep) {
92
+ return;
93
+ }
94
+
95
+ this._logger.information(LOGS.removingAndInvalidatingSSL, { prefix: LOGS.newLine });
96
+ this._logger.message(LOGS.requireSystemPassword);
97
+ this._logger.message(LOGS.requireSystemPasswordReason);
98
+ this._logger.message(LOGS.retryRunningIfTakingLonger);
99
+
100
+ this._purgeCustomSSL();
101
+ this._purgeWxpSSL();
102
+
103
+ this._logger.success(LOGS.removedAllSSL, { prefix: LOGS.newLine, postfix: LOGS.newLine });
104
+ }
105
+
106
+ private _purgeCustomSSL(): void {
107
+ const userPreference = this._preferences.get();
108
+ if (userPreference.ssl !== undefined) {
109
+ userPreference.ssl = undefined;
110
+ this._preferences.set(userPreference);
111
+
112
+ this._analyticsService.postEvent(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_MANUAL_PURGE, "", true);
113
+ }
114
+ }
115
+
116
+ private _purgeWxpSSL(): void {
117
+ if (fs.existsSync(devcert.location())) {
118
+ devcert.removeAll();
119
+ this._analyticsService.postEvent(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_AUTOMATIC_PURGE, "", true);
120
+ }
121
+ }
122
+
123
+ private _promptMessage(message: string): string {
124
+ return chalk.hex("#E59400")(message);
125
+ }
126
+
127
+ private _promptMessageOption(message: string): string {
128
+ return chalk.green.bold(message);
129
+ }
130
+ }
131
+
132
+ const LOGS = {
133
+ newLine: "\n",
134
+ sslPurgeOptionNotSpecified: "SSL purge option is not specified.",
135
+ purgeConfirmation: "Are you sure you want to remove all SSL artifacts from your system",
136
+ keepSSL: "No, keep existing SSL artifacts",
137
+ removeSSL: "Yes, remove all SSL artifacts",
138
+ removingAndInvalidatingSSL: "Removing and invalidating all SSL artifacts from your system ...",
139
+ requireSystemPassword: "This may require you to enter your system's password,",
140
+ requireSystemPasswordReason:
141
+ "so that the SSL certificate can be removed from your system's trusted certificate path.",
142
+ retryRunningIfTakingLonger: "[If this takes longer than expected, please break and retry]",
143
+ removedAllSSL: "Removed all SSL artifacts."
144
+ };
@@ -45,7 +45,8 @@ export interface SSLReader {
45
45
  /**
46
46
  * Read the SSL artifacts.
47
47
  * @param hostname - Hostname in the SSL certificate.
48
+ * @param port - Port where the add-on is being hosted.
48
49
  * @returns Promise of {@link SSLData}.
49
50
  */
50
- read(hostname: string): Promise<SSLData>;
51
+ read(hostname: string, port: number): Promise<SSLData>;
51
52
  }
@@ -26,8 +26,8 @@ import type { AnalyticsService } from "@adobe/ccweb-add-on-analytics";
26
26
  import { ITypes as IAnalyticsTypes } from "@adobe/ccweb-add-on-analytics";
27
27
  import type { Logger, Preferences } from "@adobe/ccweb-add-on-core";
28
28
  import { ITypes as ICoreTypes, isFile } from "@adobe/ccweb-add-on-core";
29
- import chalk from "chalk";
30
29
  import devcert from "@adobe/ccweb-add-on-devcert";
30
+ import chalk from "chalk";
31
31
  import { inject, injectable } from "inversify";
32
32
  import path from "path";
33
33
  import process from "process";
@@ -147,8 +147,6 @@ export class SetupCommandExecutor implements CommandExecutor {
147
147
  prefix: LOGS.newLine
148
148
  });
149
149
 
150
- // Consider updating the signature of `_removeExistingSSL` to accept only `options`
151
- // when we introduce the @oclf command to `remove` an existing SSL.
152
150
  return await this._removeExistingSSL(options, isCustomSSL, isWxpSSL);
153
151
  }
154
152
 
@@ -229,7 +227,12 @@ export class SetupCommandExecutor implements CommandExecutor {
229
227
  }
230
228
 
231
229
  if (isWxpSSL) {
232
- await devcert.removeDomain(options.hostname);
230
+ if (devcert.caExpiryInDays() <= 0) {
231
+ devcert.removeAll();
232
+ } else {
233
+ await devcert.removeDomain(options.hostname);
234
+ }
235
+
233
236
  this._analyticsService.postEvent(
234
237
  AnalyticsSuccessMarkers.SUCCESSFUL_SSL_AUTOMATIC_REMOVE,
235
238
  options.hostname,
@@ -237,7 +240,7 @@ export class SetupCommandExecutor implements CommandExecutor {
237
240
  );
238
241
  }
239
242
 
240
- this._logger.success(LOGS.removed, { postfix: LOGS.newLine });
243
+ this._logger.success(LOGS.removedExistingSSL, { prefix: LOGS.newLine, postfix: LOGS.newLine });
241
244
  return true;
242
245
  }
243
246
 
@@ -295,7 +298,7 @@ const LOGS = {
295
298
  automatically: "Automatically, set it up for me",
296
299
  manually: "Manually, I already have an SSL certificate and key",
297
300
  settingUpSSL: "Setting up self-signed SSL certificate ...",
298
- requireSystemPassword: "This is only a one time step and may require you to enter your system's password",
301
+ requireSystemPassword: "This is only a one time step and may require you to enter your system's password,",
299
302
  requireSystemPasswordReason: "so that the SSL certificate can be added to your system's trusted certificate path.",
300
303
  retryRunningIfTakingLonger: "[If this takes longer than expected, please break and retry]",
301
304
  sslSetupComplete: "SSL setup complete!",
@@ -306,5 +309,5 @@ const LOGS = {
306
309
  invalidPathSpecified: "Invalid {asset} path specified.",
307
310
  sslSetupOptionNotSpecified: "SSL setup option is not specified.",
308
311
  sslRemoveOptionNotSpecified: "SSL remove option is not specified.",
309
- removed: "Removed."
312
+ removedExistingSSL: "Removed existing SSL certificate."
310
313
  };
@@ -28,6 +28,7 @@ import devcert from "@adobe/ccweb-add-on-devcert";
28
28
  import fs from "fs-extra";
29
29
  import { inject, injectable } from "inversify";
30
30
  import "reflect-metadata";
31
+ import format from "string-template";
31
32
  import type { SSLData } from "../models/index.js";
32
33
 
33
34
  /**
@@ -70,24 +71,27 @@ export class WxpSSLReader {
70
71
  /**
71
72
  * Read the SSL artifacts.
72
73
  * @param hostname - Hostname in the SSL certificate.
74
+ * @param port - Port where the add-on is being hosted.
73
75
  * @returns Promise of {@link SSLData}.
74
76
  */
75
- async read(hostname: string): Promise<SSLData> {
77
+ async read(hostname: string, port: number): Promise<SSLData> {
76
78
  const sslSettings = this._getUserDefinedSSL(hostname);
77
79
 
78
80
  // When SSL is set up manuually by the `user`.
79
81
  if (sslSettings !== undefined) {
80
82
  const { certificatePath, keyPath } = sslSettings;
81
83
  if (!certificatePath || !isFile(certificatePath)) {
82
- this._logger.error(LOGS.invalidCertificatePath);
84
+ this._handleInvalidUserSSLCertificate(LOGS.invalidCertificatePath);
83
85
  return process.exit(1);
84
86
  }
85
87
 
86
88
  if (!keyPath || !isFile(keyPath)) {
87
- this._logger.error(LOGS.invalidKeyPath);
89
+ this._handleInvalidUserSSLCertificate(LOGS.invalidKeyPath);
88
90
  return process.exit(1);
89
91
  }
90
92
 
93
+ this._handleUnknownExpirySSLCertificate(hostname, port);
94
+
91
95
  return {
92
96
  cert: fs.readFileSync(certificatePath),
93
97
  key: fs.readFileSync(keyPath)
@@ -96,10 +100,23 @@ export class WxpSSLReader {
96
100
 
97
101
  // When SSL is set up automatically by `devcert`.
98
102
  if (this.isWxpSSL(hostname)) {
103
+ const caExpiry = devcert.caExpiryInDays();
104
+ const certificateExpiry = devcert.certificateExpiryInDays(hostname);
105
+
106
+ const expiry = Math.min(certificateExpiry, caExpiry);
107
+ if (expiry <= 0) {
108
+ this._handleExpiredSSLCertificate();
109
+ return process.exit(1);
110
+ }
111
+
112
+ if (expiry <= 7) {
113
+ this._handleNearingExpirySSLCertificate(expiry);
114
+ }
115
+
99
116
  return await devcert.certificateFor(hostname);
100
117
  }
101
118
 
102
- this._logger.error(LOGS.noSSLDataFound, { postfix: LOGS.newLine });
119
+ this._handleNoSSLCertificateFound();
103
120
  return process.exit(1);
104
121
  }
105
122
 
@@ -111,11 +128,60 @@ export class WxpSSLReader {
111
128
 
112
129
  return ssl.get(hostname);
113
130
  }
131
+
132
+ private _handleExpiredSSLCertificate() {
133
+ this._logger.error(LOGS.noValidSSLCertificateFound);
134
+ this._logger.error(LOGS.expiredSSLCertificate);
135
+ this._recreateSSLCertificate();
136
+ }
137
+
138
+ private _handleNearingExpirySSLCertificate(expiry: number) {
139
+ this._logger.warning(format(LOGS.nearingExpirySSLCertificate, { expiry }));
140
+ this._recreateSSLCertificate();
141
+ }
142
+
143
+ private _handleNoSSLCertificateFound() {
144
+ this._logger.error(LOGS.noValidSSLCertificateFound);
145
+ this._logger.error(LOGS.invalidatedSSLCertificate);
146
+ this._recreateSSLCertificate();
147
+ }
148
+
149
+ private _handleInvalidUserSSLCertificate(errorMessage: string) {
150
+ this._logger.error(errorMessage);
151
+ this._recreateSSLCertificate();
152
+ }
153
+
154
+ private _handleUnknownExpirySSLCertificate(hostname: string, port: number) {
155
+ this._logger.warning(LOGS.undeterminedExpirySSLCertificate);
156
+ this._logger.warning(LOGS.unableToSideloadAddOn);
157
+ this._logger.warning(format(LOGS.checkCertificateValidity, { hostname, port }));
158
+ this._recreateSSLCertificate();
159
+ }
160
+
161
+ private _recreateSSLCertificate() {
162
+ this._logger.warning(LOGS.recreateSSLCertificate, { prefix: LOGS.newLine });
163
+ this._logger.information(LOGS.setupSSLCommand, { prefix: LOGS.tab });
164
+
165
+ this._logger.warning(LOGS.example, { prefix: LOGS.newLine });
166
+ this._logger.information(LOGS.setupSSLCommandExample, { prefix: LOGS.tab, postfix: LOGS.newLine });
167
+ }
114
168
  }
115
169
 
116
170
  const LOGS = {
117
171
  newLine: "\n",
172
+ tab: " ",
118
173
  invalidCertificatePath: "Invalid SSL certificate file path.",
119
174
  invalidKeyPath: "Invalid SSL key file path.",
120
- noSSLDataFound: "No SSL related certificate or key files were found. Please retry after setting them up."
175
+ noValidSSLCertificateFound: "Could not locate a valid SSL certificate to host the add-on.",
176
+ expiredSSLCertificate: "The SSL certificate has expired.",
177
+ nearingExpirySSLCertificate: "Your SSL certificate will expire in {expiry} days.",
178
+ invalidatedSSLCertificate:
179
+ "If you had previously set it up, it may have been invalidated due to a version upgrade.",
180
+ undeterminedExpirySSLCertificate: "Could not determine the expiry of your SSL certificate.",
181
+ unableToSideloadAddOn: "If you are unable to sideload your add-on, please check the validity of:",
182
+ checkCertificateValidity: "https://{hostname}:{port} certificate in your browser.",
183
+ recreateSSLCertificate: "To re-create the SSL certificate, you may run:",
184
+ setupSSLCommand: "npx @adobe/ccweb-add-on-ssl setup --hostname [hostname]",
185
+ example: "Example:",
186
+ setupSSLCommandExample: "npx @adobe/ccweb-add-on-ssl setup --hostname localhost"
121
187
  };
package/src/app/index.ts CHANGED
@@ -23,6 +23,7 @@
23
23
  ********************************************************************************/
24
24
 
25
25
  export * from "./CommandExecutor.js";
26
- export * from "./SSLReader.js";
26
+ export * from "./PurgeCommandExecutor.js";
27
27
  export * from "./SetupCommandExecutor.js";
28
+ export * from "./SSLReader.js";
28
29
  export * from "./WxpSSLReader.js";
@@ -0,0 +1,72 @@
1
+ /********************************************************************************
2
+ * MIT License
3
+
4
+ * © Copyright 2025 Adobe. All rights reserved.
5
+
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ ********************************************************************************/
24
+
25
+ import { BaseCommand, CLIProgram } from "@adobe/ccweb-add-on-analytics";
26
+ import { UncaughtExceptionHandler } from "@adobe/ccweb-add-on-core";
27
+ import type { Config } from "@oclif/core";
28
+ import process from "process";
29
+ import "reflect-metadata";
30
+ import { AnalyticsErrorMarkers } from "../AnalyticsMarkers.js";
31
+ import type { CommandExecutor } from "../app/CommandExecutor.js";
32
+ import { IContainer, ITypes } from "../config/index.js";
33
+ import { PROGRAM_NAME } from "../constants.js";
34
+
35
+ /**
36
+ * SSL Purge command.
37
+ */
38
+ export class Purge extends BaseCommand {
39
+ private readonly _commandExecutor: CommandExecutor;
40
+
41
+ static description = "Remove and invalidate all add-on related SSL artifacts from your system.";
42
+
43
+ static args = {};
44
+
45
+ static flags = {};
46
+
47
+ constructor(argv: string[], config: Config) {
48
+ super(argv, config, new CLIProgram(PROGRAM_NAME, config.name + "@" + config.version));
49
+
50
+ this._commandExecutor = IContainer.getNamed<CommandExecutor>(ITypes.CommandExecutor, "purge");
51
+ }
52
+
53
+ async run(): Promise<void> {
54
+ UncaughtExceptionHandler.registerExceptionHandler(PROGRAM_NAME);
55
+
56
+ const rootDirectory = process.cwd();
57
+ process.chdir(rootDirectory);
58
+
59
+ const {
60
+ flags: { analytics }
61
+ } = await this.parse(Purge);
62
+
63
+ await this._seekAnalyticsConsent(analytics);
64
+
65
+ await this._commandExecutor.execute();
66
+ }
67
+
68
+ async catch(error: { message: string }): Promise<void> {
69
+ this._analyticsService.postEvent(AnalyticsErrorMarkers.ERROR_SSL_PURGE, error.message, false);
70
+ throw error;
71
+ }
72
+ }
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  /********************************************************************************
4
2
  * MIT License
5
3
 
@@ -24,11 +22,11 @@
24
22
  * SOFTWARE.
25
23
  ********************************************************************************/
26
24
 
27
- import type { AnalyticsConsent, AnalyticsService } from "@adobe/ccweb-add-on-analytics";
28
- import { CLIProgram, ITypes as IAnalyticsTypes } from "@adobe/ccweb-add-on-analytics";
25
+ import { BaseCommand, CLIProgram } from "@adobe/ccweb-add-on-analytics";
29
26
  import { UncaughtExceptionHandler } from "@adobe/ccweb-add-on-core";
30
27
  import type { Config } from "@oclif/core";
31
- import { Command, Flags } from "@oclif/core";
28
+ import { Flags } from "@oclif/core";
29
+ import type { BooleanFlag, CustomOptions, OptionFlag } from "@oclif/core/lib/interfaces/parser.js";
32
30
  import process from "process";
33
31
  import "reflect-metadata";
34
32
  import { AnalyticsErrorMarkers } from "../AnalyticsMarkers.js";
@@ -41,18 +39,18 @@ import type { CommandValidator } from "../validators/CommandValidator.js";
41
39
  /**
42
40
  * SSL Setup command.
43
41
  */
44
- export class Setup extends Command {
45
- private readonly _analyticsConsent: AnalyticsConsent;
46
- private readonly _analyticsService: AnalyticsService;
47
-
42
+ export class Setup extends BaseCommand {
48
43
  private readonly _commandValidator: CommandValidator;
49
44
  private readonly _commandExecutor: CommandExecutor;
50
45
 
51
- static description = "Setup a trusted SSL certificate for hosting an add-on.";
46
+ static description = "Setup a locally trusted SSL certificate for hosting an add-on.";
52
47
 
53
48
  static args = {};
54
49
 
55
- static flags = {
50
+ static flags: {
51
+ hostname: OptionFlag<string, CustomOptions>;
52
+ useExisting: BooleanFlag<boolean>;
53
+ } = {
56
54
  hostname: Flags.string({
57
55
  char: "h",
58
56
  description: "Hostname in the SSL certificate.",
@@ -64,35 +62,17 @@ export class Setup extends Command {
64
62
  required: false,
65
63
  default: false,
66
64
  hidden: true
67
- }),
68
- analytics: Flags.string({
69
- char: "a",
70
- description: "Turn on/off sending analytics to Adobe.",
71
- options: ["on", "off"],
72
- required: false
73
- }),
74
- verbose: Flags.boolean({
75
- char: "v",
76
- description: "Print detailed logs.",
77
- required: false,
78
- default: false
79
65
  })
80
66
  };
81
67
 
82
68
  constructor(argv: string[], config: Config) {
83
- super(argv, config);
84
-
85
- this._analyticsConsent = IContainer.get<AnalyticsConsent>(IAnalyticsTypes.AnalyticsConsent);
86
-
87
- this._analyticsService = IContainer.get<AnalyticsService>(IAnalyticsTypes.AnalyticsService);
88
- this._analyticsService.program = new CLIProgram(PROGRAM_NAME, this.config.name + "@" + this.config.version);
89
- this._analyticsService.startTime = Date.now();
69
+ super(argv, config, new CLIProgram(PROGRAM_NAME, config.name + "@" + config.version));
90
70
 
91
71
  this._commandValidator = IContainer.getNamed<CommandValidator>(ITypes.CommandValidator, "setup");
92
72
  this._commandExecutor = IContainer.getNamed<CommandExecutor>(ITypes.CommandExecutor, "setup");
93
73
  }
94
74
 
95
- async run() {
75
+ async run(): Promise<void> {
96
76
  UncaughtExceptionHandler.registerExceptionHandler(PROGRAM_NAME);
97
77
 
98
78
  const rootDirectory = process.cwd();
@@ -109,16 +89,8 @@ export class Setup extends Command {
109
89
  await this._commandExecutor.execute(options);
110
90
  }
111
91
 
112
- async catch(error: { message: string }) {
92
+ async catch(error: { message: string }): Promise<void> {
113
93
  this._analyticsService.postEvent(AnalyticsErrorMarkers.ERROR_SSL_SETUP, error.message, false);
114
94
  throw error;
115
95
  }
116
-
117
- private async _seekAnalyticsConsent(analytics: string | undefined): Promise<void> {
118
- if (analytics === undefined) {
119
- await this._analyticsConsent.get();
120
- } else {
121
- await this._analyticsConsent.set(analytics === "on");
122
- }
123
- }
124
96
  }
@@ -23,14 +23,15 @@
23
23
  ********************************************************************************/
24
24
 
25
25
  import { IContainer as ICoreContainer } from "@adobe/ccweb-add-on-core";
26
+ import { Container } from "inversify";
26
27
  import "reflect-metadata";
27
28
  import type { CommandExecutor, SSLReader } from "../app/index.js";
28
- import { SetupCommandExecutor, WxpSSLReader } from "../app/index.js";
29
+ import { PurgeCommandExecutor, SetupCommandExecutor, WxpSSLReader } from "../app/index.js";
29
30
  import type { CommandValidator } from "../validators/index.js";
30
31
  import { SetupCommandValidator } from "../validators/index.js";
31
32
  import { ITypes } from "./inversify.types.js";
32
33
 
33
- const container = ICoreContainer;
34
+ const container: Container = ICoreContainer;
34
35
 
35
36
  container
36
37
  .bind<CommandExecutor>(ITypes.CommandExecutor)
@@ -38,6 +39,12 @@ container
38
39
  .inSingletonScope()
39
40
  .whenTargetNamed("setup");
40
41
 
42
+ container
43
+ .bind<CommandExecutor>(ITypes.CommandExecutor)
44
+ .to(PurgeCommandExecutor)
45
+ .inSingletonScope()
46
+ .whenTargetNamed("purge");
47
+
41
48
  container
42
49
  .bind<CommandValidator>(ITypes.CommandValidator)
43
50
  .to(SetupCommandValidator)
@@ -22,7 +22,11 @@
22
22
  * SOFTWARE.
23
23
  ********************************************************************************/
24
24
 
25
- export const ITypes = {
25
+ export const ITypes: {
26
+ CommandExecutor: symbol;
27
+ CommandValidator: symbol;
28
+ SSLReader: symbol;
29
+ } = {
26
30
  CommandExecutor: Symbol.for("CommandExecutor"),
27
31
  CommandValidator: Symbol.for("CommandValidator"),
28
32
  SSLReader: Symbol.for("SSLReader")
package/src/index.ts CHANGED
@@ -26,3 +26,5 @@ export * from "./app/index.js";
26
26
  export * from "./config/index.js";
27
27
  export * from "./constants.js";
28
28
  export * from "./models/index.js";
29
+
30
+ export { run } from "@oclif/core";