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.
- package/.eslintignore +31 -0
- package/.eslintrc.json +54 -0
- package/LICENSE +200 -0
- package/README.md +101 -0
- package/babel.config.json +10 -0
- package/build/API/authorization.js +107 -0
- package/build/API/client.js +11 -0
- package/build/API/index.js +8 -0
- package/build/API/molecule/index.js +11 -0
- package/build/API/molecule/types.js +6 -0
- package/build/API/resource/device/index.js +12 -0
- package/build/API/resource/device/types.js +6 -0
- package/build/API/resource/device/utilities/confirmSshKey.js +20 -0
- package/build/API/resource/device/utilities/index.js +7 -0
- package/build/API/resource/device/utilities/sendSshKey.js +64 -0
- package/build/API/resource/index.js +10 -0
- package/build/API/resource/molecule/index.js +12 -0
- package/build/API/resource/molecule/types.js +6 -0
- package/build/API/resource/molecule/utilities/getPlan.js +15 -0
- package/build/API/resource/molecule/utilities/index.js +6 -0
- package/build/API/resource/payment/index.js +9 -0
- package/build/API/resource/payment/plans.js +28 -0
- package/build/API/resource/payment/platform.js +7 -0
- package/build/API/resource/payment/platforms/apple/index.js +21 -0
- package/build/API/resource/payment/platforms/apple/plans/appleMonthly.js +17 -0
- package/build/API/resource/payment/platforms/apple/plans/appleYearly.js +18 -0
- package/build/API/resource/payment/platforms/apple/plans/index.js +7 -0
- package/build/API/resource/payment/platforms/google/index.js +21 -0
- package/build/API/resource/payment/platforms/google/plans/googleMonthly.js +17 -0
- package/build/API/resource/payment/platforms/google/plans/googleYearly.js +18 -0
- package/build/API/resource/payment/platforms/google/plans/index.js +7 -0
- package/build/API/resource/payment/platforms/index.js +8 -0
- package/build/API/resource/payment/platforms/stripe/index.js +25 -0
- package/build/API/resource/payment/platforms/stripe/plans/index.js +11 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeBusinessMolecule.js +21 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeMonthly.js +18 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeMonthlyMolecule.js +18 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeSingleUseMolecule.js +19 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeUnlimitedUseMolecule.js +19 -0
- package/build/API/resource/payment/platforms/stripe/plans/stripeYearly.js +19 -0
- package/build/API/resource/payment/types.js +6 -0
- package/build/API/resource/types.js +6 -0
- package/build/API/resource/user/index.js +15 -0
- package/build/API/resource/user/types.js +6 -0
- package/build/API/resource/user/utilities/getPlan.js +12 -0
- package/build/API/resource/user/utilities/index.js +7 -0
- package/build/API/resource/user/utilities/logIn.js +60 -0
- package/build/cli.js +15 -0
- package/build/commands/add-ssh-key.js +24 -0
- package/build/commands/buy.js +70 -0
- package/build/commands/clone.js +329 -0
- package/build/commands/index.js +17 -0
- package/build/commands/info.js +33 -0
- package/build/commands/login.js +20 -0
- package/build/commands/logout.js +23 -0
- package/build/commands/molecule/index.js +11 -0
- package/build/commands/molecule/types.js +6 -0
- package/build/commands/remove-ssh-key.js +36 -0
- package/build/commands/select.js +98 -0
- package/build/commands/set-api-key.js +73 -0
- package/build/commands/subscribe.js +74 -0
- package/build/commands/unsubscribe.js +59 -0
- package/build/commands/utilities/asyncExec.js +6 -0
- package/build/commands/utilities/branchExists.js +13 -0
- package/build/commands/utilities/branchExistsSync.js +12 -0
- package/build/commands/utilities/checkoutSelectionBranch.js +49 -0
- package/build/commands/utilities/cherryPick.js +20 -0
- package/build/commands/utilities/cloneRepo.js +9 -0
- package/build/commands/utilities/convertNpmToYarn.js +60 -0
- package/build/commands/utilities/convertTypeDocToJSDoc.js +51 -0
- package/build/commands/utilities/convertTypeScriptToJavaScript.js +451 -0
- package/build/commands/utilities/enableMergeDrivers.js +50 -0
- package/build/commands/utilities/getCurrentBranch.js +11 -0
- package/build/commands/utilities/getCurrentBranchSync.js +7 -0
- package/build/commands/utilities/getRepoRootPath.js +7 -0
- package/build/commands/utilities/getRepoRootPathSync.js +13 -0
- package/build/commands/utilities/index.js +16 -0
- package/build/commands/utilities/npm-to-yarn.js +41 -0
- package/build/commands/utilities/ts-to-js.js +344 -0
- package/build/commands/version.js +14 -0
- package/build/commands/x-merge-json.js +33 -0
- package/build/config.js +5 -0
- package/build/environment.js +80 -0
- package/build/index.js +23 -0
- package/build/logger.js +12 -0
- package/build/pkg.js +5 -0
- package/build/repoConfig copy.js +5 -0
- package/build/repoConfig.js +5 -0
- package/build/types/global.js +6 -0
- package/build/types/index.js +6 -0
- package/build/userConfig.js +5 -0
- package/build/utilities/__tests__/getErrorMessage.js +26 -0
- package/build/utilities/__tests__/timeAgo.js +39 -0
- package/build/utilities/checkVersion.js +29 -0
- package/build/utilities/getErrorMessage.js +15 -0
- package/build/utilities/index.js +13 -0
- package/build/utilities/readConfig.js +19 -0
- package/build/utilities/readRepoConfig copy.js +20 -0
- package/build/utilities/readRepoConfig.js +19 -0
- package/build/utilities/readUserConfig.js +20 -0
- package/build/utilities/synchronizeUserConfig.js +25 -0
- package/build/utilities/timeAgo.js +42 -0
- package/build/utilities/userLoggedIn.js +24 -0
- package/build/utilities/writeConfig.js +8 -0
- package/build/utilities/writeRepoConfig copy.js +9 -0
- package/build/utilities/writeRepoConfig.js +10 -0
- package/build/utilities/writeUserConfig.js +11 -0
- package/jest.config.json +11 -0
- package/package.json +56 -0
- package/src/API/authorization.ts +143 -0
- package/src/API/client.ts +12 -0
- package/src/API/index.ts +10 -0
- package/src/API/resource/device/index.ts +27 -0
- package/src/API/resource/device/types.ts +80 -0
- package/src/API/resource/device/utilities/confirmSshKey.ts +25 -0
- package/src/API/resource/device/utilities/index.ts +8 -0
- package/src/API/resource/device/utilities/sendSshKey.ts +80 -0
- package/src/API/resource/index.ts +12 -0
- package/src/API/resource/molecule/index.ts +30 -0
- package/src/API/resource/molecule/types.ts +332 -0
- package/src/API/resource/molecule/utilities/getPlan.ts +21 -0
- package/src/API/resource/molecule/utilities/index.ts +7 -0
- package/src/API/resource/payment/index.ts +12 -0
- package/src/API/resource/payment/plans.ts +32 -0
- package/src/API/resource/payment/platform.ts +11 -0
- package/src/API/resource/payment/platforms/apple/index.ts +25 -0
- package/src/API/resource/payment/platforms/apple/plans/appleMonthly.ts +19 -0
- package/src/API/resource/payment/platforms/apple/plans/appleYearly.ts +20 -0
- package/src/API/resource/payment/platforms/apple/plans/index.ts +9 -0
- package/src/API/resource/payment/platforms/google/index.ts +25 -0
- package/src/API/resource/payment/platforms/google/plans/googleMonthly.ts +19 -0
- package/src/API/resource/payment/platforms/google/plans/googleYearly.ts +20 -0
- package/src/API/resource/payment/platforms/google/plans/index.ts +9 -0
- package/src/API/resource/payment/platforms/index.ts +10 -0
- package/src/API/resource/payment/platforms/stripe/index.ts +29 -0
- package/src/API/resource/payment/platforms/stripe/plans/index.ts +12 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeBusinessMolecule.ts +23 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeMonthly.ts +20 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeMonthlyMolecule.ts +20 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeSingleUseMolecule.ts +21 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeUnlimitedUseMolecule.ts +21 -0
- package/src/API/resource/payment/platforms/stripe/plans/stripeYearly.ts +21 -0
- package/src/API/resource/payment/types.ts +201 -0
- package/src/API/resource/types.ts +169 -0
- package/src/API/resource/user/index.ts +29 -0
- package/src/API/resource/user/types.ts +131 -0
- package/src/API/resource/user/utilities/getPlan.ts +16 -0
- package/src/API/resource/user/utilities/index.ts +8 -0
- package/src/API/resource/user/utilities/logIn.ts +71 -0
- package/src/commands/add-ssh-key.ts +30 -0
- package/src/commands/buy.ts +83 -0
- package/src/commands/clone.ts +363 -0
- package/src/commands/index.ts +19 -0
- package/src/commands/info.ts +40 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/logout.ts +27 -0
- package/src/commands/remove-ssh-key.ts +42 -0
- package/src/commands/subscribe.ts +84 -0
- package/src/commands/unsubscribe.ts +67 -0
- package/src/commands/utilities/asyncExec.ts +7 -0
- package/src/commands/utilities/branchExists.ts +15 -0
- package/src/commands/utilities/checkoutSelectionBranch.ts +62 -0
- package/src/commands/utilities/cherryPick.ts +23 -0
- package/src/commands/utilities/cloneRepo.ts +10 -0
- package/src/commands/utilities/convertNpmToYarn.ts +71 -0
- package/src/commands/utilities/convertTypeDocToJSDoc.ts +55 -0
- package/src/commands/utilities/convertTypeScriptToJavaScript.ts +526 -0
- package/src/commands/utilities/enableMergeDrivers.ts +55 -0
- package/src/commands/utilities/getCurrentBranch.ts +14 -0
- package/src/commands/utilities/getRepoRootPathSync.ts +13 -0
- package/src/commands/utilities/index.ts +17 -0
- package/src/commands/version.ts +22 -0
- package/src/commands/x-merge-json.ts +41 -0
- package/src/environment.ts +88 -0
- package/src/index.ts +30 -0
- package/src/logger.ts +29 -0
- package/src/pkg.ts +11 -0
- package/src/repoConfig.ts +6 -0
- package/src/types/global.ts +26 -0
- package/src/types/index.ts +53 -0
- package/src/types/process.env.d.ts +56 -0
- package/src/userConfig.ts +6 -0
- package/src/utilities/__tests__/getErrorMessage.ts +32 -0
- package/src/utilities/__tests__/timeAgo.ts +46 -0
- package/src/utilities/checkVersion.ts +31 -0
- package/src/utilities/getErrorMessage.ts +30 -0
- package/src/utilities/index.ts +14 -0
- package/src/utilities/readRepoConfig.ts +22 -0
- package/src/utilities/readUserConfig.ts +24 -0
- package/src/utilities/synchronizeUserConfig.ts +26 -0
- package/src/utilities/timeAgo.ts +39 -0
- package/src/utilities/userLoggedIn.ts +28 -0
- package/src/utilities/writeRepoConfig.ts +11 -0
- package/src/utilities/writeUserConfig.ts +13 -0
- 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
|
+
};
|
package/jest.config.json
ADDED
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
|
+
})
|
package/src/API/index.ts
ADDED
|
@@ -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
|
+
}
|