mlcl 1.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 (195) hide show
  1. package/.eslintignore +31 -0
  2. package/.eslintrc.json +54 -0
  3. package/LICENSE +200 -0
  4. package/README.md +101 -0
  5. package/babel.config.json +10 -0
  6. package/build/API/authorization.js +107 -0
  7. package/build/API/client.js +11 -0
  8. package/build/API/index.js +8 -0
  9. package/build/API/molecule/index.js +11 -0
  10. package/build/API/molecule/types.js +6 -0
  11. package/build/API/resource/device/index.js +12 -0
  12. package/build/API/resource/device/types.js +6 -0
  13. package/build/API/resource/device/utilities/confirmSshKey.js +20 -0
  14. package/build/API/resource/device/utilities/index.js +7 -0
  15. package/build/API/resource/device/utilities/sendSshKey.js +64 -0
  16. package/build/API/resource/index.js +10 -0
  17. package/build/API/resource/molecule/index.js +12 -0
  18. package/build/API/resource/molecule/types.js +6 -0
  19. package/build/API/resource/molecule/utilities/getPlan.js +15 -0
  20. package/build/API/resource/molecule/utilities/index.js +6 -0
  21. package/build/API/resource/payment/index.js +9 -0
  22. package/build/API/resource/payment/plans.js +28 -0
  23. package/build/API/resource/payment/platform.js +7 -0
  24. package/build/API/resource/payment/platforms/apple/index.js +21 -0
  25. package/build/API/resource/payment/platforms/apple/plans/appleMonthly.js +17 -0
  26. package/build/API/resource/payment/platforms/apple/plans/appleYearly.js +18 -0
  27. package/build/API/resource/payment/platforms/apple/plans/index.js +7 -0
  28. package/build/API/resource/payment/platforms/google/index.js +21 -0
  29. package/build/API/resource/payment/platforms/google/plans/googleMonthly.js +17 -0
  30. package/build/API/resource/payment/platforms/google/plans/googleYearly.js +18 -0
  31. package/build/API/resource/payment/platforms/google/plans/index.js +7 -0
  32. package/build/API/resource/payment/platforms/index.js +8 -0
  33. package/build/API/resource/payment/platforms/stripe/index.js +25 -0
  34. package/build/API/resource/payment/platforms/stripe/plans/index.js +11 -0
  35. package/build/API/resource/payment/platforms/stripe/plans/stripeBusinessMolecule.js +21 -0
  36. package/build/API/resource/payment/platforms/stripe/plans/stripeMonthly.js +18 -0
  37. package/build/API/resource/payment/platforms/stripe/plans/stripeMonthlyMolecule.js +18 -0
  38. package/build/API/resource/payment/platforms/stripe/plans/stripeSingleUseMolecule.js +19 -0
  39. package/build/API/resource/payment/platforms/stripe/plans/stripeUnlimitedUseMolecule.js +19 -0
  40. package/build/API/resource/payment/platforms/stripe/plans/stripeYearly.js +19 -0
  41. package/build/API/resource/payment/types.js +6 -0
  42. package/build/API/resource/types.js +6 -0
  43. package/build/API/resource/user/index.js +15 -0
  44. package/build/API/resource/user/types.js +6 -0
  45. package/build/API/resource/user/utilities/getPlan.js +12 -0
  46. package/build/API/resource/user/utilities/index.js +7 -0
  47. package/build/API/resource/user/utilities/logIn.js +60 -0
  48. package/build/cli.js +15 -0
  49. package/build/commands/add-ssh-key.js +24 -0
  50. package/build/commands/buy.js +70 -0
  51. package/build/commands/clone.js +329 -0
  52. package/build/commands/index.js +17 -0
  53. package/build/commands/info.js +33 -0
  54. package/build/commands/login.js +20 -0
  55. package/build/commands/logout.js +23 -0
  56. package/build/commands/molecule/index.js +11 -0
  57. package/build/commands/molecule/types.js +6 -0
  58. package/build/commands/remove-ssh-key.js +36 -0
  59. package/build/commands/select.js +98 -0
  60. package/build/commands/set-api-key.js +73 -0
  61. package/build/commands/subscribe.js +74 -0
  62. package/build/commands/unsubscribe.js +59 -0
  63. package/build/commands/utilities/asyncExec.js +6 -0
  64. package/build/commands/utilities/branchExists.js +13 -0
  65. package/build/commands/utilities/branchExistsSync.js +12 -0
  66. package/build/commands/utilities/checkoutSelectionBranch.js +49 -0
  67. package/build/commands/utilities/cherryPick.js +20 -0
  68. package/build/commands/utilities/cloneRepo.js +9 -0
  69. package/build/commands/utilities/convertNpmToYarn.js +60 -0
  70. package/build/commands/utilities/convertTypeDocToJSDoc.js +51 -0
  71. package/build/commands/utilities/convertTypeScriptToJavaScript.js +451 -0
  72. package/build/commands/utilities/enableMergeDrivers.js +50 -0
  73. package/build/commands/utilities/getCurrentBranch.js +11 -0
  74. package/build/commands/utilities/getCurrentBranchSync.js +7 -0
  75. package/build/commands/utilities/getRepoRootPath.js +7 -0
  76. package/build/commands/utilities/getRepoRootPathSync.js +13 -0
  77. package/build/commands/utilities/index.js +16 -0
  78. package/build/commands/utilities/npm-to-yarn.js +41 -0
  79. package/build/commands/utilities/ts-to-js.js +344 -0
  80. package/build/commands/version.js +14 -0
  81. package/build/commands/x-merge-json.js +33 -0
  82. package/build/config.js +5 -0
  83. package/build/environment.js +80 -0
  84. package/build/index.js +23 -0
  85. package/build/logger.js +12 -0
  86. package/build/pkg.js +5 -0
  87. package/build/repoConfig copy.js +5 -0
  88. package/build/repoConfig.js +5 -0
  89. package/build/types/global.js +6 -0
  90. package/build/types/index.js +6 -0
  91. package/build/userConfig.js +5 -0
  92. package/build/utilities/__tests__/getErrorMessage.js +26 -0
  93. package/build/utilities/__tests__/timeAgo.js +39 -0
  94. package/build/utilities/checkVersion.js +29 -0
  95. package/build/utilities/getErrorMessage.js +15 -0
  96. package/build/utilities/index.js +13 -0
  97. package/build/utilities/readConfig.js +19 -0
  98. package/build/utilities/readRepoConfig copy.js +20 -0
  99. package/build/utilities/readRepoConfig.js +19 -0
  100. package/build/utilities/readUserConfig.js +20 -0
  101. package/build/utilities/synchronizeUserConfig.js +25 -0
  102. package/build/utilities/timeAgo.js +42 -0
  103. package/build/utilities/userLoggedIn.js +24 -0
  104. package/build/utilities/writeConfig.js +8 -0
  105. package/build/utilities/writeRepoConfig copy.js +9 -0
  106. package/build/utilities/writeRepoConfig.js +10 -0
  107. package/build/utilities/writeUserConfig.js +11 -0
  108. package/jest.config.json +11 -0
  109. package/package.json +56 -0
  110. package/src/API/authorization.ts +143 -0
  111. package/src/API/client.ts +12 -0
  112. package/src/API/index.ts +10 -0
  113. package/src/API/resource/device/index.ts +27 -0
  114. package/src/API/resource/device/types.ts +80 -0
  115. package/src/API/resource/device/utilities/confirmSshKey.ts +25 -0
  116. package/src/API/resource/device/utilities/index.ts +8 -0
  117. package/src/API/resource/device/utilities/sendSshKey.ts +80 -0
  118. package/src/API/resource/index.ts +12 -0
  119. package/src/API/resource/molecule/index.ts +30 -0
  120. package/src/API/resource/molecule/types.ts +332 -0
  121. package/src/API/resource/molecule/utilities/getPlan.ts +21 -0
  122. package/src/API/resource/molecule/utilities/index.ts +7 -0
  123. package/src/API/resource/payment/index.ts +12 -0
  124. package/src/API/resource/payment/plans.ts +32 -0
  125. package/src/API/resource/payment/platform.ts +11 -0
  126. package/src/API/resource/payment/platforms/apple/index.ts +25 -0
  127. package/src/API/resource/payment/platforms/apple/plans/appleMonthly.ts +19 -0
  128. package/src/API/resource/payment/platforms/apple/plans/appleYearly.ts +20 -0
  129. package/src/API/resource/payment/platforms/apple/plans/index.ts +9 -0
  130. package/src/API/resource/payment/platforms/google/index.ts +25 -0
  131. package/src/API/resource/payment/platforms/google/plans/googleMonthly.ts +19 -0
  132. package/src/API/resource/payment/platforms/google/plans/googleYearly.ts +20 -0
  133. package/src/API/resource/payment/platforms/google/plans/index.ts +9 -0
  134. package/src/API/resource/payment/platforms/index.ts +10 -0
  135. package/src/API/resource/payment/platforms/stripe/index.ts +29 -0
  136. package/src/API/resource/payment/platforms/stripe/plans/index.ts +12 -0
  137. package/src/API/resource/payment/platforms/stripe/plans/stripeBusinessMolecule.ts +23 -0
  138. package/src/API/resource/payment/platforms/stripe/plans/stripeMonthly.ts +20 -0
  139. package/src/API/resource/payment/platforms/stripe/plans/stripeMonthlyMolecule.ts +20 -0
  140. package/src/API/resource/payment/platforms/stripe/plans/stripeSingleUseMolecule.ts +21 -0
  141. package/src/API/resource/payment/platforms/stripe/plans/stripeUnlimitedUseMolecule.ts +21 -0
  142. package/src/API/resource/payment/platforms/stripe/plans/stripeYearly.ts +21 -0
  143. package/src/API/resource/payment/types.ts +201 -0
  144. package/src/API/resource/types.ts +169 -0
  145. package/src/API/resource/user/index.ts +29 -0
  146. package/src/API/resource/user/types.ts +131 -0
  147. package/src/API/resource/user/utilities/getPlan.ts +16 -0
  148. package/src/API/resource/user/utilities/index.ts +8 -0
  149. package/src/API/resource/user/utilities/logIn.ts +71 -0
  150. package/src/commands/add-ssh-key.ts +30 -0
  151. package/src/commands/buy.ts +83 -0
  152. package/src/commands/clone.ts +363 -0
  153. package/src/commands/index.ts +19 -0
  154. package/src/commands/info.ts +40 -0
  155. package/src/commands/login.ts +24 -0
  156. package/src/commands/logout.ts +27 -0
  157. package/src/commands/remove-ssh-key.ts +42 -0
  158. package/src/commands/subscribe.ts +84 -0
  159. package/src/commands/unsubscribe.ts +67 -0
  160. package/src/commands/utilities/asyncExec.ts +7 -0
  161. package/src/commands/utilities/branchExists.ts +15 -0
  162. package/src/commands/utilities/checkoutSelectionBranch.ts +62 -0
  163. package/src/commands/utilities/cherryPick.ts +23 -0
  164. package/src/commands/utilities/cloneRepo.ts +10 -0
  165. package/src/commands/utilities/convertNpmToYarn.ts +71 -0
  166. package/src/commands/utilities/convertTypeDocToJSDoc.ts +55 -0
  167. package/src/commands/utilities/convertTypeScriptToJavaScript.ts +526 -0
  168. package/src/commands/utilities/enableMergeDrivers.ts +55 -0
  169. package/src/commands/utilities/getCurrentBranch.ts +14 -0
  170. package/src/commands/utilities/getRepoRootPathSync.ts +13 -0
  171. package/src/commands/utilities/index.ts +17 -0
  172. package/src/commands/version.ts +22 -0
  173. package/src/commands/x-merge-json.ts +41 -0
  174. package/src/environment.ts +88 -0
  175. package/src/index.ts +30 -0
  176. package/src/logger.ts +29 -0
  177. package/src/pkg.ts +11 -0
  178. package/src/repoConfig.ts +6 -0
  179. package/src/types/global.ts +26 -0
  180. package/src/types/index.ts +53 -0
  181. package/src/types/process.env.d.ts +56 -0
  182. package/src/userConfig.ts +6 -0
  183. package/src/utilities/__tests__/getErrorMessage.ts +32 -0
  184. package/src/utilities/__tests__/timeAgo.ts +46 -0
  185. package/src/utilities/checkVersion.ts +31 -0
  186. package/src/utilities/getErrorMessage.ts +30 -0
  187. package/src/utilities/index.ts +14 -0
  188. package/src/utilities/readRepoConfig.ts +22 -0
  189. package/src/utilities/readUserConfig.ts +24 -0
  190. package/src/utilities/synchronizeUserConfig.ts +26 -0
  191. package/src/utilities/timeAgo.ts +39 -0
  192. package/src/utilities/userLoggedIn.ts +28 -0
  193. package/src/utilities/writeRepoConfig.ts +11 -0
  194. package/src/utilities/writeUserConfig.ts +13 -0
  195. package/tsconfig.json +25 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Various utilities used throughout the application.
3
+ *
4
+ * @module
5
+ */
6
+ export * from './checkVersion.js';
7
+ export * from './getErrorMessage.js';
8
+ export * from './readRepoConfig.js';
9
+ export * from './readUserConfig.js';
10
+ export * from './synchronizeUserConfig.js';
11
+ export * from './timeAgo.js';
12
+ export * from './writeRepoConfig.js';
13
+ export * from './writeUserConfig.js';
@@ -0,0 +1,19 @@
1
+ import fs from 'fs';
2
+ /**
3
+ * Reads and returns the current repo's config.
4
+ */
5
+ export const readConfig = (path = `.mlclrc.json`) => {
6
+ try {
7
+ if (fs.existsSync(path)) {
8
+ const config = JSON.parse(fs.readFileSync(path, `utf8`));
9
+ return config;
10
+ }
11
+ else {
12
+ console.warn(`Config file (${path}) does not exist.`);
13
+ }
14
+ }
15
+ catch (error) {
16
+ console.error(error);
17
+ }
18
+ return {};
19
+ };
@@ -0,0 +1,20 @@
1
+ import fs from 'fs';
2
+ import { getRepoRootPath } from '../commands/utilities/getRepoRootPath.js';
3
+ /**
4
+ * Reads and returns the current repo's config.
5
+ */
6
+ export const readRepoConfig = (path = `${getRepoRootPath()}/.mlclrc.json`) => {
7
+ try {
8
+ if (fs.existsSync(path)) {
9
+ const config = JSON.parse(fs.readFileSync(path, `utf8`));
10
+ return config;
11
+ }
12
+ else {
13
+ console.warn(`Config file (${path}) does not exist.`);
14
+ }
15
+ }
16
+ catch (error) {
17
+ console.error(error);
18
+ }
19
+ return {};
20
+ };
@@ -0,0 +1,19 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getRepoRootPathSync } from '../commands/utilities/getRepoRootPathSync.js';
4
+ import { logger } from '../logger.js';
5
+ /**
6
+ * Reads and returns the current repo's config.
7
+ */
8
+ export const readRepoConfig = (repoConfigPath = path.join(getRepoRootPathSync(), `.mlclrc.json`)) => {
9
+ try {
10
+ if (fs.existsSync(repoConfigPath)) {
11
+ const config = JSON.parse(fs.readFileSync(repoConfigPath, `utf8`));
12
+ return config;
13
+ }
14
+ }
15
+ catch (error) {
16
+ logger.error(error);
17
+ }
18
+ return {};
19
+ };
@@ -0,0 +1,20 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { logger } from '../logger.js';
5
+ const homeDir = os.homedir();
6
+ /**
7
+ * Reads and returns the current user's config.
8
+ */
9
+ export const readUserConfig = (userConfigPath = path.join(homeDir, `.mlclrc.json`)) => {
10
+ try {
11
+ if (fs.existsSync(userConfigPath)) {
12
+ const config = JSON.parse(fs.readFileSync(userConfigPath, `utf8`));
13
+ return config;
14
+ }
15
+ }
16
+ catch (error) {
17
+ logger.error(error);
18
+ }
19
+ return {};
20
+ };
@@ -0,0 +1,25 @@
1
+ import { read as readUser } from '../API/resource/user/index.js';
2
+ import { userConfig } from '../userConfig.js';
3
+ import { writeUserConfig } from '../utilities/writeUserConfig.js';
4
+ import { logger } from '../logger.js';
5
+ import { getErrorMessage } from '../utilities/getErrorMessage.js';
6
+ export const synchronizeUserConfig = async () => {
7
+ if (userConfig.user) {
8
+ try {
9
+ const { props: user } = (await readUser(userConfig.user.id)).data;
10
+ if (!user) {
11
+ throw new Error(`No user was returned by the Molecule.dev API.`);
12
+ }
13
+ if (userConfig.user?.id === user.id) {
14
+ userConfig.user = user;
15
+ writeUserConfig();
16
+ }
17
+ else {
18
+ logger.warn(`The Molecule.dev API returned an unexpected response when synchronizing the user.`);
19
+ }
20
+ }
21
+ catch (error) {
22
+ logger.warn(`Error fetching user: ${getErrorMessage(error) || `Unknown error`}`);
23
+ }
24
+ }
25
+ };
@@ -0,0 +1,42 @@
1
+ const MINUTE = 60000;
2
+ const HOUR = 3600000;
3
+ const DAY = 86400000;
4
+ const WEEK = 604800000;
5
+ const YEAR = 31557600000;
6
+ /**
7
+ * Returns how long ago some time (date) was.
8
+ */
9
+ export const timeAgo = (time, abbreviate) => {
10
+ if (typeof time !== `number`) {
11
+ try {
12
+ time = new Date(time).getTime();
13
+ }
14
+ catch (error) {
15
+ }
16
+ }
17
+ const diff = Math.max(0, Date.now() - Number(time));
18
+ let count = 0;
19
+ if (diff < MINUTE) {
20
+ return `just now`;
21
+ }
22
+ else if (diff < HOUR) {
23
+ count = Math.round(diff / MINUTE);
24
+ return `${count}${abbreviate ? `m` : ` minute${count === 1 ? `` : `s`}`} ago`;
25
+ }
26
+ else if (diff < DAY) {
27
+ count = Math.round(diff / HOUR);
28
+ return `${count}${abbreviate ? `h` : ` hour${count === 1 ? `` : `s`}`} ago`;
29
+ }
30
+ else if (diff < WEEK) {
31
+ count = Math.round(diff / DAY);
32
+ return `${count}${abbreviate ? `d` : ` day${count === 1 ? `` : `s`}`} ago`;
33
+ }
34
+ else if (diff < YEAR) {
35
+ count = Math.round(diff / WEEK);
36
+ return `${count}${abbreviate ? `w` : ` week${count === 1 ? `` : `s`}`} ago`;
37
+ }
38
+ else {
39
+ count = Math.round(diff / YEAR);
40
+ return `${count}${abbreviate ? `y` : ` year${count === 1 ? `` : `s`}`} ago`;
41
+ }
42
+ };
@@ -0,0 +1,24 @@
1
+ import { userConfig } from '../userConfig.js';
2
+ import { synchronizeUserConfig } from './synchronizeUserConfig.js';
3
+ import * as API from '../API/index.js';
4
+ import { logger } from '../logger.js';
5
+ /**
6
+ * Ensures the user is logged in before continuing.
7
+ *
8
+ * If the user is not logged in, automatically runs the `login` command.
9
+ */
10
+ export const userLoggedIn = async () => {
11
+ await synchronizeUserConfig();
12
+ if (userConfig?.user?.id) {
13
+ logger.info(`Hello, ${userConfig.user.name || userConfig.user.username}!`);
14
+ }
15
+ else {
16
+ logger.info(`Not logged into \`mlcl\`. Logging in...`);
17
+ await API.resource.user.utilities.logIn();
18
+ if (!userConfig.user?.id) {
19
+ logger.warn(`Not logged in. Run \`mlcl login\` first.`);
20
+ return null;
21
+ }
22
+ }
23
+ return userConfig.user;
24
+ };
@@ -0,0 +1,8 @@
1
+ import fs from 'fs';
2
+ import { config as currentConfig } from '../config.js';
3
+ /**
4
+ * Writes the current repo's config to disk.
5
+ */
6
+ export const writeConfig = (config = currentConfig, path = `.mlclrc.json`) => {
7
+ fs.writeFileSync(path, JSON.stringify(config, null, 2));
8
+ };
@@ -0,0 +1,9 @@
1
+ import fs from 'fs';
2
+ import { repoConfig as currentRepoConfig } from '../repoConfig.js';
3
+ import { getRepoRootPath } from '../commands/utilities/getRepoRootPath.js';
4
+ /**
5
+ * Writes the current repo's config to disk.
6
+ */
7
+ export const writeRepoConfig = (repoConfig = currentRepoConfig, path = `${getRepoRootPath()}/.mlclrc.json`) => {
8
+ fs.writeFileSync(path, JSON.stringify(repoConfig, null, 2));
9
+ };
@@ -0,0 +1,10 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { repoConfig as currentRepoConfig } from '../repoConfig.js';
4
+ import { getRepoRootPathSync } from '../commands/utilities/getRepoRootPathSync.js';
5
+ /**
6
+ * Writes the current repo's config to disk.
7
+ */
8
+ export const writeRepoConfig = (repoConfig = currentRepoConfig, repoConfigPath = path.join(getRepoRootPathSync(), `.mlclrc.json`)) => {
9
+ fs.writeFileSync(repoConfigPath, JSON.stringify(repoConfig, null, 2));
10
+ };
@@ -0,0 +1,11 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { userConfig as currentUserConfig } from '../userConfig.js';
5
+ const homeDir = os.homedir();
6
+ /**
7
+ * Writes the current user's config to disk.
8
+ */
9
+ export const writeUserConfig = (userConfig = currentUserConfig, userConfigPath = path.join(homeDir, `.mlclrc.json`)) => {
10
+ fs.writeFileSync(userConfigPath, JSON.stringify(userConfig, null, 2));
11
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "roots": [
3
+ "<rootDir>/src/"
4
+ ],
5
+ "setupFiles": [
6
+ "<rootDir>/src/environment.ts"
7
+ ],
8
+ "moduleNameMapper": {
9
+ "^(\\.{1,2}/.*)\\.jsx?$": "$1"
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "mlcl",
3
+ "version": "1.0.0",
4
+ "description": "CLI (Command Line Interface) for Molecule.dev.",
5
+ "author": "molecule.dev",
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=16 <17"
10
+ },
11
+ "bin": {
12
+ "mlcl": "./build/index.js"
13
+ },
14
+ "devDependencies": {
15
+ "@babel/core": "^7.17.5",
16
+ "@babel/plugin-transform-runtime": "^7.17.0",
17
+ "@babel/preset-env": "^7.16.11",
18
+ "@babel/preset-typescript": "^7.16.7",
19
+ "@types/jest": "^27.4.1",
20
+ "@types/node": "^17.0.21",
21
+ "@types/semver": "^7.3.9",
22
+ "@typescript-eslint/eslint-plugin": "^5.13.0",
23
+ "@typescript-eslint/parser": "^5.13.0",
24
+ "babel-jest": "^27.5.1",
25
+ "babel-plugin-transform-import-meta": "^2.1.1",
26
+ "eslint": "^8.10.0",
27
+ "eslint-plugin-jest": "^26.1.1",
28
+ "jest": "^27.5.1",
29
+ "ts-node": "^10.7.0",
30
+ "typescript": "^4.6.2"
31
+ },
32
+ "dependencies": {
33
+ "@types/glob": "^7.2.0",
34
+ "axios": "^0.26.1",
35
+ "commander": "^9.0.0",
36
+ "detect-indent": "^7.0.0",
37
+ "dotenv": "^16.0.0",
38
+ "env-cmd": "^10.1.0",
39
+ "glob": "^8.0.1",
40
+ "jwt-decode": "^3.1.2",
41
+ "loglevel": "^1.8.0",
42
+ "open": "^8.4.0",
43
+ "prettier": "^2.6.2",
44
+ "semver": "^7.3.7",
45
+ "xdiff": "^0.2.11"
46
+ },
47
+ "scripts": {
48
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
49
+ "lint-autofix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
50
+ "test": "jest",
51
+ "test-watch": "jest --watch",
52
+ "dev": "tsc --watch",
53
+ "build": "tsc",
54
+ "start": "node build/cli.js"
55
+ }
56
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Methods for API authorization management.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { AxiosError, AxiosResponse } from 'axios'
8
+ import jwtDecode, { JwtPayload } from 'jwt-decode'
9
+ import { client } from './client.js'
10
+ import { userConfig } from '../userConfig.js'
11
+ import { writeUserConfig } from '../utilities/writeUserConfig.js'
12
+ import { logger } from '../logger.js'
13
+
14
+ /**
15
+ * Sets the `Authorization` header on the API client (`axios` instance)
16
+ * and saves it within the user config.
17
+ */
18
+ export const setHeader = (authorizationHeader = userConfig.authorizationHeader || ``) => {
19
+ if (authorizationHeader) {
20
+ client.defaults.headers.common.Authorization = authorizationHeader
21
+ userConfig.authorizationHeader = authorizationHeader
22
+ writeUserConfig()
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Removes the `Authorization` header from the API client (`axios` instance)
28
+ * and removes it from the user config.
29
+ */
30
+ export const removeHeader = () => {
31
+ if (client.defaults.headers) {
32
+ delete client.defaults.headers.common.Authorization
33
+ }
34
+
35
+ delete userConfig.authorizationHeader
36
+ writeUserConfig()
37
+ }
38
+
39
+ /**
40
+ * If multiple users log into the same device, we use this map of user IDs to device IDs
41
+ * to allow users to properly reidentify the device when switching between users.
42
+ */
43
+ export const getUserIdsToDeviceIds = (): { [userId: string]: string } => {
44
+ if (!userConfig.userIdsToDeviceIds) {
45
+ userConfig.userIdsToDeviceIds = {}
46
+ }
47
+
48
+ return userConfig.userIdsToDeviceIds
49
+ }
50
+
51
+ /**
52
+ * Gets the `userId` and `deviceId` from the decoded `Authorization` header
53
+ * and saves it within the config as a map of user IDs to device IDs.
54
+ */
55
+ export const setUserIdToDeviceId = (authorizationHeader: string) => {
56
+ try {
57
+ const [ bearer = ``, authorizationToken = `` ] = authorizationHeader.split(` `)
58
+ const { session } = jwtDecode<JwtPayload & { session: { userId: string; deviceId: string } }>(authorizationToken)
59
+ const { userId, deviceId } = session
60
+ const userIdsToDeviceIds = getUserIdsToDeviceIds()
61
+
62
+ if (bearer && userId && deviceId) {
63
+ userIdsToDeviceIds[userId] = deviceId
64
+ writeUserConfig()
65
+ }
66
+ } catch (error) {
67
+ logger.error(error)
68
+ }
69
+ }
70
+
71
+ export type interceptResponseProps = {
72
+ /**
73
+ * Called when the `Authorization` header is set.
74
+ */
75
+ onSet?: () => void
76
+ }
77
+
78
+ /**
79
+ * If the `Set-Authorization` header is included in the response,
80
+ * sets the header and user ID to device ID.
81
+ */
82
+ export const interceptResponse = ({ onSet }: interceptResponseProps = {}) => (response: AxiosResponse): AxiosResponse => {
83
+ if (response.headers[`set-authorization`]) {
84
+ setHeader(response.headers[`set-authorization`])
85
+ setUserIdToDeviceId(response.headers[`set-authorization`])
86
+
87
+ if (onSet) {
88
+ onSet()
89
+ }
90
+ }
91
+
92
+ return response
93
+ }
94
+
95
+ export type interceptErrorProps = {
96
+ /**
97
+ * Called when the `Authorization` header is set.
98
+ */
99
+ onSet?: () => void
100
+ /**
101
+ * Called when the `Authorization` header is removed.
102
+ */
103
+ onRemove?: () => void
104
+ }
105
+
106
+ /**
107
+ * Removes the `Authorization` header if the response status code is `401`,
108
+ * or if the `Set-Authorization` header is included in the error response,
109
+ * sets the header and user ID to device ID.
110
+ */
111
+ export const interceptError = ({ onSet, onRemove }: interceptErrorProps = {}) => (error: AxiosError): Promise<AxiosError> => {
112
+ if (error?.response?.status === 401) {
113
+ removeHeader()
114
+
115
+ if (onRemove) {
116
+ onRemove()
117
+ }
118
+ } else if (error?.response?.headers[`set-authorization`]) {
119
+ setHeader(error.response.headers[`set-authorization`])
120
+ setUserIdToDeviceId(error.response.headers[`set-authorization`])
121
+
122
+ if (onSet) {
123
+ onSet()
124
+ }
125
+ }
126
+
127
+ return Promise.reject(error)
128
+ }
129
+
130
+ // Set the locally stored authorization header, if it exists.
131
+ setHeader()
132
+
133
+ // Intercept all requests to set and/or remove the authorization header.
134
+ client.interceptors.response.use(
135
+ interceptResponse(), // Locally store authorization headers when provided.
136
+ interceptError({
137
+ onRemove: () => {
138
+ // Log out when authorization removed.
139
+ delete userConfig.user
140
+ writeUserConfig()
141
+ }
142
+ })
143
+ )
@@ -0,0 +1,12 @@
1
+ import axios from 'axios'
2
+
3
+ /**
4
+ * The API client (`axios` instance).
5
+ *
6
+ * @see https://www.npmjs.com/package/axios
7
+ */
8
+ export const client = axios.create({
9
+ baseURL: `${process.env.API_ORIGIN || ``}/api`,
10
+ headers: process.env.APP_ID ? { 'X-Requested-With': process.env.APP_ID } : {},
11
+ withCredentials: true
12
+ })
@@ -0,0 +1,10 @@
1
+ /**
2
+ * API client resources with authorization.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ export * as resource from './resource/index.js'
8
+
9
+ export * as authorization from './authorization.js'
10
+ export * from './client.js'
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The device API resource.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { client } from '../../client.js'
8
+ import * as types from './types.js'
9
+
10
+ export * as utilities from './utilities/index.js'
11
+ export * as types from './types.js'
12
+
13
+ export const query = (): Promise<types.QueryResponse> => (
14
+ client.get(`devices`)
15
+ )
16
+
17
+ export const read = (id: string): Promise<types.SuccessResponse> => (
18
+ client.get(`devices/${id}`)
19
+ )
20
+
21
+ export const update = (id: string, props: types.UpdateProps): Promise<types.SuccessPartialResponse> => (
22
+ client.patch(`devices/${id}`, props)
23
+ )
24
+
25
+ export const del = (id: string): Promise<types.SuccessPartialResponse> => (
26
+ client.delete(`devices/${id}`)
27
+ )
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Device type definitions.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import * as resourceTypes from '../types.js'
8
+
9
+ // Internal reference for easier default type arguments within this module.
10
+ type DeviceProps = Props
11
+
12
+ /**
13
+ * The device's properties returned by the API.
14
+ */
15
+ export interface Props extends resourceTypes.Props {
16
+ /**
17
+ * The device owner's `userId`.
18
+ */
19
+ userId: string
20
+ /**
21
+ * The name of the device.
22
+ *
23
+ * Usually determined using `navigator.userAgent`, `navigator.appVersion`, and `navigator.appName`, but user editable.
24
+ */
25
+ name?: string
26
+ /**
27
+ * An optional SSH key the device can use to access `git.molecule.dev`.
28
+ */
29
+ sshKey?: string
30
+ /**
31
+ * The platform when pushing notifications.
32
+ *
33
+ * No platform means the push subscription was set up via a service worker registration's `PushManager`, either via web browser or Electron app.
34
+ */
35
+ pushPlatform?: `fcm` | `apn`
36
+ /**
37
+ * True when the user has push notifications enabled for the device.
38
+ */
39
+ hasPushSubscription?: boolean
40
+ }
41
+
42
+ /**
43
+ * The properties when updating a device.
44
+ */
45
+ export interface UpdateProps {
46
+ name?: Props['name']
47
+ sshKey?: Props['sshKey']
48
+ }
49
+
50
+ /**
51
+ * The successful device resource response.
52
+ */
53
+ export type SuccessResponse<Props = DeviceProps> = resourceTypes.SuccessResponse<Props>
54
+
55
+ /**
56
+ * The successful device resource response returning partial props.
57
+ */
58
+ export type SuccessPartialResponse<Props = DeviceProps> = resourceTypes.SuccessPartialResponse<Props>
59
+
60
+ /**
61
+ * The response when querying devices.
62
+ *
63
+ * An extra `isCurrent` property is set to `true` when the device in the list matches the `session.deviceId`.
64
+ */
65
+ export type QueryResponse<Props = DeviceProps> = resourceTypes.QueryResponse<Props & { isCurrent?: boolean }>
66
+
67
+ /**
68
+ * Generic response with an optional body.
69
+ */
70
+ export type GenericResponse = resourceTypes.GenericResponse
71
+
72
+ /**
73
+ * The response containing a message.
74
+ */
75
+ export type MessageResponse = resourceTypes.MessageResponse
76
+
77
+ /**
78
+ * The response when an error has occurred when handling a device resource request.
79
+ */
80
+ export type ErrorResponse = resourceTypes.ErrorResponse
@@ -0,0 +1,25 @@
1
+ import { read } from '../index.js'
2
+ import { sendSshKey } from './sendSshKey.js'
3
+ import { logger } from '../../../../logger.js'
4
+
5
+ export const confirmSshKey = async (deviceId: string, name: string) => {
6
+ try {
7
+ const device = (await read(deviceId)).data.props
8
+
9
+ if (device.sshKey) {
10
+ return true
11
+ }
12
+
13
+ const sshKeyConfirmed = await sendSshKey(deviceId, name)
14
+
15
+ if (sshKeyConfirmed) {
16
+ return true
17
+ }
18
+ } catch (error) {
19
+ }
20
+
21
+ logger.warn(`The Molecule.dev API could not confirm your device SSH key.`)
22
+ logger.warn(`Use \`mlcl add-ssh-key\` or try logging out and in again.`)
23
+
24
+ return false
25
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Device resource utilities.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ export * from './confirmSshKey.js'
8
+ export * from './sendSshKey.js'