@wiztivi/dana-cli 0.0.8 → 0.0.10

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 (33) hide show
  1. package/dist/commands/addDevice/deviceConfig/androidtvConfig.d.ts +0 -4
  2. package/dist/commands/addDevice/deviceConfig/androidtvConfig.js +2 -8
  3. package/dist/commands/addDevice/deviceConfig/configTypes.d.ts +12 -1
  4. package/dist/commands/addDevice/deviceConfig/tizenConfig.js +34 -3
  5. package/dist/commands/addDevice/helper/addDeviceHelper.d.ts +12 -0
  6. package/dist/commands/addDevice/helper/addDeviceHelper.js +19 -0
  7. package/dist/commands/authentication/AuthenticationManager.d.ts +23 -0
  8. package/dist/commands/authentication/AuthenticationManager.js +107 -0
  9. package/dist/commands/authentication/authDefinition.js +17 -3
  10. package/dist/commands/authentication/authHook.d.ts +1 -1
  11. package/dist/commands/authentication/authHook.js +17 -4
  12. package/dist/commands/authentication/commands/get-packages-token.d.ts +6 -0
  13. package/dist/commands/authentication/commands/get-packages-token.js +30 -0
  14. package/dist/commands/authentication/commands/login.js +13 -89
  15. package/dist/commands/authentication/commands/logout.d.ts +5 -0
  16. package/dist/commands/authentication/commands/logout.js +20 -0
  17. package/dist/commands/authentication/helper/CommandHelper.d.ts +1 -0
  18. package/dist/commands/authentication/helper/CommandHelper.js +8 -0
  19. package/dist/commands/authentication/helper/CredentialsHelper.d.ts +18 -5
  20. package/dist/commands/authentication/helper/CredentialsHelper.js +55 -5
  21. package/dist/commands/createApp/helpers/CreateAppHelper.d.ts +0 -1
  22. package/dist/common/const/deviceConst.js +1 -1
  23. package/dist/common/const/exitCodeConst.d.ts +1 -0
  24. package/dist/common/const/exitCodeConst.js +1 -0
  25. package/dist/common/helpers/CreateFileHelper.js +1 -1
  26. package/dist/common/helpers/InstallHelper.d.ts +1 -0
  27. package/dist/common/helpers/InstallHelper.js +15 -3
  28. package/dist/common/helpers/UpdateFileHelper.js +11 -3
  29. package/dist/common/helpers/UserInputGetter.js +4 -1
  30. package/dist/common/helpers/stringHelper.d.ts +5 -0
  31. package/dist/common/helpers/stringHelper.js +12 -0
  32. package/dist/common/translation/en.json +4 -2
  33. package/package.json +2 -2
@@ -1,10 +1,6 @@
1
1
  import { AndroidConfig } from "./configTypes.js";
2
2
  declare const AndroidtvConfig: {
3
3
  new (): {};
4
- /**
5
- * Get the app icon url
6
- */
7
- readonly _getAppIcon: () => Promise<string>;
8
4
  /**
9
5
  * Ask the appId
10
6
  */
@@ -12,7 +12,6 @@
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import DeviceConfig from "./deviceConfig.js";
15
- import { DEFAULT_IMAGE_URL } from "../../createApp/const/setupConst.js";
16
15
  import ComponentHelper from "../../addComponent/helper/ComponentHelper.js";
17
16
  import * as prompts from "@clack/prompts";
18
17
  import colors from "picocolors";
@@ -24,13 +23,8 @@ import { CredentialsHelper } from "../../authentication/helper/CredentialsHelper
24
23
  import { ListPackageVersionsCommand } from "@aws-sdk/client-codeartifact";
25
24
  import { createCodeArtifactClient } from "../../authentication/helper/CodeArtifactHelper.js";
26
25
  import { DOMAIN, DOMAIN_OWNER } from "../../authentication/authentConst.js";
26
+ import addDeviceHelper from "../helper/addDeviceHelper.js";
27
27
  const AndroidtvConfig = class extends DeviceConfig {
28
- /**
29
- * Get the app icon url
30
- */
31
- static _getAppIcon = () => {
32
- return Promise.resolve(DEFAULT_IMAGE_URL);
33
- };
34
28
  /**
35
29
  * Ask the appId
36
30
  */
@@ -122,7 +116,7 @@ const AndroidtvConfig = class extends DeviceConfig {
122
116
  appName: () => this._askAppName(),
123
117
  appVersion: () => this._askAppVersion(),
124
118
  appVersionCode: () => this._askAppVersionCode(),
125
- icon: () => this._getAppIcon(),
119
+ icon: () => addDeviceHelper.getAppIcon(),
126
120
  wtvAndroidVersion: () => this._getWtvAndroidVersion(),
127
121
  }, {
128
122
  onCancel: () => {
@@ -9,8 +9,19 @@ interface AndroidDevice {
9
9
  interface AndroidConfig {
10
10
  device: AndroidDevice;
11
11
  }
12
+ interface TizenDevice {
13
+ type?: string;
14
+ profileName?: string;
15
+ icon: string;
16
+ application: {
17
+ requiredVersion: string;
18
+ package: string;
19
+ id: string;
20
+ };
21
+ }
12
22
  interface TizenConf {
13
23
  renderers: string[];
24
+ device: TizenDevice;
14
25
  }
15
26
  interface WebosConf {
16
27
  device: WebosDevice;
@@ -24,4 +35,4 @@ interface WebosDevice {
24
35
  largeIcon: string;
25
36
  disableBackHistoryAPI: boolean;
26
37
  }
27
- export { AndroidDevice, AndroidConfig, TizenConf, WebosConf, WebosDevice };
38
+ export { AndroidDevice, AndroidConfig, TizenConf, TizenDevice, WebosConf, WebosDevice };
@@ -13,6 +13,11 @@ import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import DeviceConfig from "./deviceConfig.js";
15
15
  import { LIGHTNING } from "../../../common/const/deviceConst.js";
16
+ import * as prompts from "@clack/prompts";
17
+ import colors from "picocolors";
18
+ import addDeviceHelper from "../helper/addDeviceHelper.js";
19
+ import { validAppVersion, validInputLength } from "../../../common/helpers/InputValidator.js";
20
+ import { clean } from "semver-ts";
16
21
  const TizenConfig = class extends DeviceConfig {
17
22
  static appConfig = {
18
23
  css: {
@@ -34,16 +39,42 @@ const TizenConfig = class extends DeviceConfig {
34
39
  },
35
40
  },
36
41
  };
37
- static getConfig = () => {
38
- const tizenConfig = {
42
+ static getConfig = async () => {
43
+ prompts.log.info(colors.bold("Let's configure Tizen !"));
44
+ const applicationConfig = await prompts.group({
45
+ requiredVersion: async () => {
46
+ const requiredVersion = await addDeviceHelper.promptTextFactory({
47
+ promptMessage: "What is the application requiredVersion ?",
48
+ validationFunction: (value) => validAppVersion(value),
49
+ placeholder: "1.0.0",
50
+ initialValue: "1.0.0",
51
+ });
52
+ return clean(requiredVersion);
53
+ },
54
+ package: async () => addDeviceHelper.promptTextFactory({
55
+ errorMessage: "Package name is required !",
56
+ promptMessage: "What is the package name ? Should be unique.",
57
+ validationFunction: (value) => validInputLength(value, "Package name is required !"),
58
+ }),
59
+ id: async () => addDeviceHelper.promptTextFactory({
60
+ errorMessage: "Package id is required !",
61
+ promptMessage: "What is the package id ?",
62
+ validationFunction: (value) => validInputLength(value, "Package id is required !"),
63
+ }),
64
+ });
65
+ return {
39
66
  renderers: [LIGHTNING],
67
+ device: {
68
+ icon: await addDeviceHelper.getAppIcon(),
69
+ application: { ...applicationConfig },
70
+ },
40
71
  };
41
- return Promise.resolve(tizenConfig);
42
72
  };
43
73
  static setConfig = ({ directory, config }) => {
44
74
  const filePath = path.join(directory, "profiles", "app.config.tizen.json");
45
75
  let jsonData = JSON.parse(fs.readFileSync(filePath).toString());
46
76
  jsonData = this.updateProfileJsonWithRenderers(jsonData, config.renderers);
77
+ jsonData.tizen.device = { ...jsonData.tizen.device, ...config.device };
47
78
  fs.writeFileSync(filePath, JSON.stringify(jsonData, null, 4));
48
79
  return Promise.resolve("Tizen configuration done");
49
80
  };
@@ -1,3 +1,10 @@
1
+ export interface PromptFactoryParams {
2
+ errorMessage?: string;
3
+ promptMessage: string;
4
+ validationFunction: (value: string, errorMessage?: string) => string | undefined;
5
+ placeholder?: string;
6
+ initialValue?: string;
7
+ }
1
8
  declare const AddDeviceHelper: {
2
9
  new (): {};
3
10
  /**
@@ -9,5 +16,10 @@ declare const AddDeviceHelper: {
9
16
  * We use script so we can get renderers for smartTv
10
17
  */
11
18
  readonly getInstalledDevices: (directory: string) => string[];
19
+ /**
20
+ * Get the app icon url
21
+ */
22
+ readonly getAppIcon: () => Promise<string>;
23
+ readonly promptTextFactory: ({ errorMessage, promptMessage, validationFunction, placeholder, initialValue, }: PromptFactoryParams) => Promise<string>;
12
24
  };
13
25
  export default AddDeviceHelper;
@@ -1,6 +1,9 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import * as prompts from "@clack/prompts";
5
+ import { DEFAULT_IMAGE_URL } from "../../createApp/const/setupConst.js";
6
+ import ComponentHelper from "../../addComponent/helper/ComponentHelper.js";
4
7
  const AddDeviceHelper = class {
5
8
  /**
6
9
  * clean on error
@@ -30,5 +33,21 @@ const AddDeviceHelper = class {
30
33
  }
31
34
  return installedDevices;
32
35
  };
36
+ /**
37
+ * Get the app icon url
38
+ */
39
+ static getAppIcon = () => {
40
+ return Promise.resolve(DEFAULT_IMAGE_URL);
41
+ };
42
+ static promptTextFactory = async ({ errorMessage, promptMessage, validationFunction, placeholder, initialValue, }) => {
43
+ const result = (await prompts.text({
44
+ message: promptMessage,
45
+ placeholder,
46
+ initialValue,
47
+ validate: (value) => validationFunction(value, errorMessage),
48
+ }));
49
+ ComponentHelper.handleCancellation(result);
50
+ return result;
51
+ };
33
52
  };
34
53
  export default AddDeviceHelper;
@@ -0,0 +1,23 @@
1
+ import { AwsCredentials, DanaToken } from "./helper/CredentialsHelper.js";
2
+ import { JwtPayload } from "jsonwebtoken";
3
+ export interface CognitoIdToken extends JwtPayload {
4
+ given_name: string;
5
+ family_name: string;
6
+ email: string;
7
+ }
8
+ export interface AuthCredentials {
9
+ aws: AwsCredentials;
10
+ dana: DanaToken;
11
+ org: string;
12
+ }
13
+ export declare class AuthenticationManager {
14
+ private static getAPIURL;
15
+ static login(organization: string): Promise<AuthCredentials>;
16
+ static logout(organization: string, creds?: Partial<DanaToken>): Promise<void>;
17
+ static refresh(organization: string, token: string): Promise<AuthCredentials>;
18
+ static isTokenExpired(token: string): boolean;
19
+ static loginNPM(organization: string, credentials: AwsCredentials): Promise<{
20
+ token: string;
21
+ expiration?: Date;
22
+ }>;
23
+ }
@@ -0,0 +1,107 @@
1
+ import getPort from "get-port";
2
+ import express from "express";
3
+ import { CredentialsHelper } from "./helper/CredentialsHelper.js";
4
+ import open from "open";
5
+ import jwt from "jsonwebtoken";
6
+ import { createCodeArtifactClient, getRepositoryEndpoint } from "./helper/CodeArtifactHelper.js";
7
+ import { GetAuthorizationTokenCommand } from "@aws-sdk/client-codeartifact";
8
+ import { DOMAIN, DOMAIN_OWNER } from "./authentConst.js";
9
+ import { execSync } from "node:child_process";
10
+ export class AuthenticationManager {
11
+ static getAPIURL(organization) {
12
+ return `https://api.${organization}.dana-framework.com`; // Or http://localhost:3001
13
+ }
14
+ static async login(organization) {
15
+ const app = express();
16
+ app.disable("x-powered-by");
17
+ const promise = new Promise((resolve, reject) => {
18
+ const timeout = setTimeout(() => {
19
+ reject(new Error("Login timed out."));
20
+ }, 5 * 60 * 1000);
21
+ app.get("/callback", (req, res) => {
22
+ clearTimeout(timeout);
23
+ try {
24
+ if (req.query.error) {
25
+ res.send("Login failed. Check the CLI for details.");
26
+ return reject(new Error(req.query.error));
27
+ }
28
+ const token = JSON.parse(req.query.token);
29
+ const awsCredentials = JSON.parse(req.query.aws);
30
+ const creds = { aws: awsCredentials, dana: token, org: organization };
31
+ res.send("Login successful! You can close this window.");
32
+ CredentialsHelper.store(creds);
33
+ resolve(creds);
34
+ }
35
+ catch (err) {
36
+ res.send("Login failed. Check the CLI for details.");
37
+ reject(err);
38
+ }
39
+ });
40
+ });
41
+ const port = await getPort();
42
+ const server = app.listen(port);
43
+ await open(`${AuthenticationManager.getAPIURL(organization)}/login?port=${port}`);
44
+ return promise.finally(() => {
45
+ server.closeAllConnections();
46
+ server.close();
47
+ });
48
+ }
49
+ static async logout(organization, creds) {
50
+ const url = new URL(`${AuthenticationManager.getAPIURL(organization)}/logout`);
51
+ if (creds?.id_token) {
52
+ url.searchParams.append("id_token", creds.id_token);
53
+ }
54
+ if (creds?.refresh_token) {
55
+ url.searchParams.append("refresh_token", creds.refresh_token);
56
+ }
57
+ await open(url.toString());
58
+ CredentialsHelper.delete(organization);
59
+ }
60
+ static async refresh(organization, token) {
61
+ const data = await fetch(`${AuthenticationManager.getAPIURL(organization)}/login?refresh_token=${token}`);
62
+ if (!data.ok) {
63
+ return AuthenticationManager.login(organization);
64
+ }
65
+ const refreshedCreds = (await data.json());
66
+ const creds = {
67
+ aws: refreshedCreds.aws,
68
+ dana: {
69
+ ...refreshedCreds.token,
70
+ refresh_token: token,
71
+ },
72
+ org: organization,
73
+ };
74
+ CredentialsHelper.store(creds);
75
+ return creds;
76
+ }
77
+ static isTokenExpired(token) {
78
+ const payload = jwt.decode(token);
79
+ if (!payload.exp) {
80
+ return true;
81
+ }
82
+ const expirationDate = new Date(payload.exp * 1000);
83
+ return expirationDate <= new Date();
84
+ }
85
+ static async loginNPM(organization, credentials) {
86
+ // Create CodeArtifact client with credentials
87
+ const client = createCodeArtifactClient(credentials);
88
+ // Get authorization token
89
+ const tokenCommand = new GetAuthorizationTokenCommand({
90
+ domain: DOMAIN,
91
+ domainOwner: DOMAIN_OWNER,
92
+ });
93
+ const { authorizationToken, expiration } = await client.send(tokenCommand);
94
+ if (!authorizationToken) {
95
+ throw new Error("Authorization token not found");
96
+ }
97
+ // Get repository endpoint
98
+ const repositoryEndpoint = await getRepositoryEndpoint({ organization, credentials, client, format: "npm" });
99
+ // Configure npm with the repository and token
100
+ execSync(`npm config set @dana:registry=${repositoryEndpoint}`);
101
+ execSync(`npm config set //${repositoryEndpoint.replace(/^https?:\/\//, "")}:_authToken=${authorizationToken}`);
102
+ return {
103
+ token: authorizationToken,
104
+ expiration,
105
+ };
106
+ }
107
+ }
@@ -1,19 +1,33 @@
1
- import { Command } from "commander";
1
+ import { Command, Option } from "commander";
2
2
  import translation from "../../common/translation/translation.js";
3
3
  import login from "./commands/login.js";
4
4
  import { statusAction } from "./commands/status.js";
5
- import { DEFAULT_ORG } from "./authentConst.js";
5
+ import logout from "./commands/logout.js";
6
+ import { getPackagesToken } from "./commands/get-packages-token.js";
7
+ import { authHook } from "./authHook.js";
6
8
  const addAuthDefinition = () => {
7
9
  const command = new Command("auth").description(translation["command.auth.description"]);
8
10
  const loginCmd = new Command("login")
9
11
  .description(translation["command.auth.login.description"])
10
- .option("-o, --org <organization>", translation["command.auth.login.option.org"], DEFAULT_ORG)
12
+ .option("-o, --org <organization>", translation["command.auth.login.option.org"])
11
13
  .action(login);
12
14
  command.addCommand(loginCmd);
15
+ const logoutCmd = new Command("logout")
16
+ .description(translation["command.auth.logout.description"])
17
+ .option("-o, --org <organization>", translation["command.auth.login.option.org"])
18
+ .action(logout);
19
+ command.addCommand(logoutCmd);
13
20
  const statusCmd = new Command("status")
14
21
  .description(translation["command.auth.status.description"])
15
22
  .action(statusAction);
16
23
  command.addCommand(statusCmd);
24
+ const getPackagesTokenCmd = new Command("get-packages-token")
25
+ .description(translation["command.auth.get-packages-token.description"])
26
+ .addOption(new Option("-t, --target <target>", "Packages target").makeOptionMandatory(true).choices(["android"]))
27
+ .option("-o, --org <organization>", translation["command.auth.login.option.org"])
28
+ .hook("preAction", authHook)
29
+ .action(getPackagesToken);
30
+ command.addCommand(getPackagesTokenCmd);
17
31
  return command;
18
32
  };
19
33
  export default addAuthDefinition;
@@ -1 +1 @@
1
- export declare const authHook: () => void;
1
+ export declare const authHook: () => Promise<void>;
@@ -9,11 +9,24 @@
9
9
  * This software MAY NOT be used, modified or rewritten without prior written permission from Wiztivi.
10
10
  *
11
11
  */
12
- import { status, LOGIN_STATUS } from "./commands/status.js";
12
+ import * as prompts from "@clack/prompts";
13
+ import { CredentialsHelper } from "./helper/CredentialsHelper.js";
14
+ import { AuthenticationManager } from "./AuthenticationManager.js";
13
15
  import { LOGIN_ERROR } from "../../common/const/exitCodeConst.js";
14
- export const authHook = () => {
15
- const isLogged = status() === LOGIN_STATUS.LOGGED;
16
- if (!isLogged) {
16
+ export const authHook = async () => {
17
+ const credentials = CredentialsHelper.get();
18
+ try {
19
+ if (!credentials.dana?.id_token || !credentials.dana?.refresh_token) {
20
+ await AuthenticationManager.login(credentials.org);
21
+ return;
22
+ }
23
+ if (AuthenticationManager.isTokenExpired(credentials.dana.id_token) ||
24
+ (credentials.aws && new Date(credentials.aws.expiration) < new Date())) {
25
+ await AuthenticationManager.refresh(credentials.org, credentials.dana.refresh_token);
26
+ }
27
+ }
28
+ catch (error) {
29
+ prompts.log.error(error.message);
17
30
  process.exit(LOGIN_ERROR);
18
31
  }
19
32
  };
@@ -0,0 +1,6 @@
1
+ interface GetPackageTokenOptions {
2
+ target: "android";
3
+ org?: string;
4
+ }
5
+ declare const getPackagesToken: (options: GetPackageTokenOptions) => Promise<void>;
6
+ export { getPackagesToken };
@@ -0,0 +1,30 @@
1
+ import { CredentialsHelper } from "../helper/CredentialsHelper.js";
2
+ import { createCodeArtifactClient } from "../helper/CodeArtifactHelper.js";
3
+ import { GetAuthorizationTokenCommand } from "@aws-sdk/client-codeartifact";
4
+ import * as prompts from "@clack/prompts";
5
+ import { GET_PACKAGES_TOKEN_ERROR } from "../../../common/const/exitCodeConst.js";
6
+ import { DOMAIN, DOMAIN_OWNER } from "../authentConst.js";
7
+ const getAndroidToken = async (creds) => {
8
+ const client = createCodeArtifactClient(creds);
9
+ const command = new GetAuthorizationTokenCommand({
10
+ domain: DOMAIN,
11
+ domainOwner: DOMAIN_OWNER,
12
+ });
13
+ try {
14
+ const result = await client.send(command);
15
+ console.log(result.authorizationToken);
16
+ }
17
+ catch (error) {
18
+ prompts.log.error(error.message);
19
+ process.exit(GET_PACKAGES_TOKEN_ERROR);
20
+ }
21
+ };
22
+ const getPackagesToken = async (options) => {
23
+ const creds = CredentialsHelper.get(options.org);
24
+ switch (options.target) {
25
+ case "android":
26
+ // authHook ensure credentials are valid
27
+ return getAndroidToken(creds.aws);
28
+ }
29
+ };
30
+ export { getPackagesToken };
@@ -1,103 +1,27 @@
1
- import { GetAuthorizationTokenCommand } from "@aws-sdk/client-codeartifact";
2
1
  import * as prompts from "@clack/prompts";
3
- import { execSync } from "node:child_process";
4
- import express from "express";
5
- import getPort from "get-port";
6
- import open from "open";
7
2
  import colors from "picocolors";
8
- import { LOGIN_ERROR } from "../../../common/const/exitCodeConst.js";
9
3
  import { CredentialsHelper } from "../helper/CredentialsHelper.js";
10
- import { DEFAULT_ORG, DOMAIN, DOMAIN_OWNER } from "../authentConst.js";
11
- import { getRepositoryEndpoint, createCodeArtifactClient } from "../helper/CodeArtifactHelper.js";
4
+ import { AuthenticationManager } from "../AuthenticationManager.js";
5
+ import { handleError } from "../helper/CommandHelper.js";
12
6
  /*
13
7
  Login to DANA backend (with aws and cognito) and connect to npm codeartifact
14
8
  More info: https://github.com/wiztivi-rd/dana-cli/wiki/Login
15
9
  */
16
- const login = async ({ org = DEFAULT_ORG }) => {
17
- const apiUrl = `https://api.${org}.dana-framework.com`; // Or http://localhost:3001
18
- const credentials = CredentialsHelper.get();
19
- if (credentials) {
20
- try {
21
- if (new Date(credentials.aws.expiration) < new Date()) {
22
- const data = await fetch(`${apiUrl}/login?refresh_token=${credentials.dana.refresh_token}`);
23
- if (!data.ok) {
24
- throw new Error("Unable to refresh token");
25
- }
26
- const refreshedCreds = (await data.json());
27
- credentials.aws = refreshedCreds.aws;
28
- credentials.dana = {
29
- ...refreshedCreds.token,
30
- refresh_token: credentials.dana.refresh_token,
31
- };
32
- CredentialsHelper.store(credentials);
33
- }
34
- prompts.log.info(`Retrieved credentials for ${org} from local cache`);
35
- await codeartifactLogin(org, credentials.aws);
36
- return;
37
- }
38
- catch (error) {
39
- // Log message and start nominal login process
40
- prompts.log.error(error.message);
41
- }
42
- }
43
- prompts.intro(`Login to ${org}...`);
10
+ const login = async ({ org }) => {
11
+ const credentials = CredentialsHelper.get(org);
44
12
  try {
45
- const port = await getPort();
46
- const app = express();
47
- app.disable("x-powered-by");
48
- const server = app.listen(port);
49
- app.get("/callback", async (req, res) => {
50
- try {
51
- if (req.query.error) {
52
- throw new Error(req.query.error);
53
- }
54
- const token = JSON.parse(req.query.token);
55
- const awsCredentials = JSON.parse(req.query.aws);
56
- CredentialsHelper.store({ aws: awsCredentials, dana: token, org });
57
- // Login to codeartifact repository
58
- await codeartifactLogin(org, awsCredentials);
59
- prompts.outro(colors.green("✓") + ` Login successfull`);
60
- res.send("Login successful! You can close this window.");
61
- }
62
- catch (err) {
63
- console.error(err);
64
- res.send("Login failed. Check the CLI for details.");
65
- handleError(err);
66
- }
67
- finally {
68
- server.close();
69
- }
70
- });
71
- await open(`${apiUrl}/login?port=${port}`);
13
+ prompts.intro(`Login to organization ${credentials.org} ...`);
14
+ const validCredentials = await AuthenticationManager.login(credentials.org);
15
+ prompts.outro(colors.green("") + ` Login successful`);
16
+ const npmToken = await AuthenticationManager.loginNPM(credentials.org, validCredentials.aws);
17
+ let stepText = "Successfully logged in to npm repository.";
18
+ if (npmToken.expiration) {
19
+ stepText += `\nLogin expires in 12 hours at: ${npmToken.expiration.toLocaleString()}`;
20
+ }
21
+ prompts.log.step(stepText);
72
22
  }
73
23
  catch (error) {
74
- console.error(error);
75
24
  handleError(error);
76
25
  }
77
26
  };
78
- const handleError = (error) => {
79
- const message = error instanceof Error ? error.message : error;
80
- prompts.outro(colors.red(`An issue occurred :` + message));
81
- process.exit(LOGIN_ERROR);
82
- };
83
- const codeartifactLogin = async (organization, credentials) => {
84
- // Create CodeArtifact client with credentials
85
- const client = createCodeArtifactClient(credentials);
86
- // Get authorization token
87
- const tokenCommand = new GetAuthorizationTokenCommand({
88
- domain: DOMAIN,
89
- domainOwner: DOMAIN_OWNER,
90
- });
91
- const { authorizationToken, expiration } = await client.send(tokenCommand);
92
- if (!authorizationToken) {
93
- throw new Error("Authorization token not found");
94
- }
95
- // Get repository endpoint
96
- const repositoryEndpoint = await getRepositoryEndpoint({ organization, credentials, client, format: "npm" });
97
- // Configure npm with the repository and token
98
- execSync(`npm config set @dana:registry=${repositoryEndpoint}`);
99
- execSync(`npm config set //${repositoryEndpoint.replace(/^https?:\/\//, "")}:_authToken=${authorizationToken}`);
100
- const expiresAt = expiration ? new Date(expiration).toLocaleString() : "unknown";
101
- prompts.log.step(`Successfully configured npm to use AWS CodeArtifact repository ${repositoryEndpoint}. \nLogin expires in 12 hours at: ${expiresAt}`);
102
- };
103
27
  export default login;
@@ -0,0 +1,5 @@
1
+ interface LogoutOptions {
2
+ org?: string;
3
+ }
4
+ declare const logout: ({ org }: LogoutOptions) => Promise<void>;
5
+ export default logout;
@@ -0,0 +1,20 @@
1
+ import * as prompts from "@clack/prompts";
2
+ import colors from "picocolors";
3
+ import { CredentialsHelper } from "../helper/CredentialsHelper.js";
4
+ import { AuthenticationManager } from "../AuthenticationManager.js";
5
+ import { handleError } from "../helper/CommandHelper.js";
6
+ /*
7
+ Logout from DANA backend
8
+ */
9
+ const logout = async ({ org }) => {
10
+ const credentials = CredentialsHelper.get(org);
11
+ try {
12
+ prompts.intro(`Logout from ${credentials.org}...`);
13
+ await AuthenticationManager.logout(credentials.org, credentials.dana);
14
+ prompts.outro(colors.green("✓") + ` Logout successful. You are now being logged out in your browser.`);
15
+ }
16
+ catch (error) {
17
+ handleError(error);
18
+ }
19
+ };
20
+ export default logout;
@@ -0,0 +1 @@
1
+ export declare const handleError: (error: unknown) => void;
@@ -0,0 +1,8 @@
1
+ import * as prompts from "@clack/prompts";
2
+ import colors from "picocolors";
3
+ import translation from "../../../common/translation/translation.js";
4
+ import { LOGIN_ERROR } from "../../../common/const/exitCodeConst.js";
5
+ export const handleError = (error) => {
6
+ prompts.outro(colors.red(error.message ?? translation["error.common.start.message"]));
7
+ process.exit(LOGIN_ERROR);
8
+ };
@@ -8,20 +8,33 @@ export interface AwsCredentials {
8
8
  export interface DanaToken {
9
9
  id_token: string;
10
10
  access_token: string;
11
- refresh_token?: string;
11
+ refresh_token: string;
12
12
  expires_in: number;
13
13
  token_type: string;
14
14
  }
15
+ export interface OrgCredentials {
16
+ aws: AwsCredentials;
17
+ dana: DanaToken;
18
+ }
19
+ export interface DanaConfig {
20
+ lastUsedOrg: string;
21
+ version: number;
22
+ credentials: Record<string, OrgCredentials>;
23
+ }
24
+ export declare const CURRENT_DANA_JSON_VERSION = 2;
15
25
  export declare class CredentialsHelper {
16
26
  static readonly filePath: string;
27
+ static getConfig(): DanaConfig | null;
28
+ private static migrateConfig;
17
29
  static store(credentials: {
18
30
  aws: AwsCredentials;
19
31
  dana: DanaToken;
20
32
  org: string;
21
33
  }): void;
22
- static get(): {
23
- aws: AwsCredentials;
24
- dana: DanaToken;
34
+ static get(org?: string): {
35
+ aws?: AwsCredentials;
36
+ dana?: DanaToken;
25
37
  org: string;
26
- } | null;
38
+ };
39
+ static delete(org: string): void;
27
40
  }
@@ -1,15 +1,65 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
+ import { DEFAULT_ORG } from "../authentConst.js";
5
+ export const CURRENT_DANA_JSON_VERSION = 2;
4
6
  export class CredentialsHelper {
5
7
  static filePath = path.join(homedir(), ".dana.json");
6
- static store(credentials) {
7
- writeFileSync(this.filePath, JSON.stringify(credentials, null, 4));
8
- }
9
- static get() {
8
+ static getConfig() {
10
9
  if (!existsSync(this.filePath)) {
11
10
  return null;
12
11
  }
13
- return JSON.parse(readFileSync(this.filePath).toString());
12
+ let config = JSON.parse(readFileSync(this.filePath, { encoding: "utf-8" }));
13
+ const configVersion = config.version ?? 1;
14
+ if (configVersion != CURRENT_DANA_JSON_VERSION) {
15
+ config = CredentialsHelper.migrateConfig(config, configVersion);
16
+ writeFileSync(this.filePath, JSON.stringify(config, null, 4));
17
+ }
18
+ return config;
19
+ }
20
+ static migrateConfig(config, from) {
21
+ if (from === 1) {
22
+ const v1 = config;
23
+ return {
24
+ lastUsedOrg: v1.org,
25
+ credentials: {
26
+ [v1.org]: {
27
+ dana: v1.dana,
28
+ aws: v1.aws,
29
+ },
30
+ },
31
+ version: CURRENT_DANA_JSON_VERSION,
32
+ };
33
+ }
34
+ return config;
35
+ }
36
+ static store(credentials) {
37
+ let config = this.getConfig();
38
+ config ??= { lastUsedOrg: credentials.org, credentials: {}, version: CURRENT_DANA_JSON_VERSION };
39
+ config.credentials ??= {};
40
+ config.lastUsedOrg = credentials.org;
41
+ config.credentials[credentials.org] = { aws: credentials.aws, dana: credentials.dana };
42
+ writeFileSync(this.filePath, JSON.stringify(config, null, 4));
43
+ }
44
+ static get(org) {
45
+ const config = this.getConfig();
46
+ const organization = org ?? config?.lastUsedOrg ?? DEFAULT_ORG;
47
+ const creds = {
48
+ org: organization,
49
+ };
50
+ if (config?.credentials?.[organization]?.aws) {
51
+ creds.aws = config?.credentials[organization]?.aws;
52
+ }
53
+ if (config?.credentials?.[organization]?.dana) {
54
+ creds.dana = config?.credentials[organization]?.dana;
55
+ }
56
+ return creds;
57
+ }
58
+ static delete(org) {
59
+ const config = this.getConfig();
60
+ if (config) {
61
+ delete config.credentials[org];
62
+ writeFileSync(this.filePath, JSON.stringify(config, null, 4));
63
+ }
14
64
  }
15
65
  }
@@ -89,7 +89,6 @@ declare const CreateAppHelper: {
89
89
  updateProfileJsonWithRenderers(jsonData: Record<string, unknown>, renderers: string[]): object;
90
90
  } | {
91
91
  new (): {};
92
- readonly _getAppIcon: () => Promise<string>;
93
92
  readonly _askAppId: () => Promise<string>;
94
93
  readonly _askAppName: () => Promise<string>;
95
94
  readonly _askAppVersion: () => Promise<string>;
@@ -29,7 +29,7 @@ const BASIC_SETUP = {
29
29
  [TIZEN]: {
30
30
  label: "Samsung",
31
31
  package: "@dana/vendor-tizen",
32
- dependencies: ["@dana/vendor-tizen", "@dana/vendor-shaka"],
32
+ dependencies: ["@dana/vendor-tizen", "@dana/vendor-shaka", "@dana/tools-smarttv-grunt"],
33
33
  },
34
34
  };
35
35
  //Devices that need a subscription to be added in project
@@ -5,3 +5,4 @@ export declare const DEVICE_ERROR = 5;
5
5
  export declare const LOGIN_ERROR = 6;
6
6
  export declare const COMMAND_ERROR = 7;
7
7
  export declare const SETUP_ERROR = 8;
8
+ export declare const GET_PACKAGES_TOKEN_ERROR = 9;
@@ -5,3 +5,4 @@ export const DEVICE_ERROR = 5;
5
5
  export const LOGIN_ERROR = 6;
6
6
  export const COMMAND_ERROR = 7;
7
7
  export const SETUP_ERROR = 8;
8
+ export const GET_PACKAGES_TOKEN_ERROR = 9;
@@ -60,7 +60,7 @@ class CreateFileHelper {
60
60
  credentials: credentials?.aws,
61
61
  format: "maven",
62
62
  });
63
- let gruntOptions = `--appUrl="${registryAccount}" `;
63
+ let gruntOptions = `--appUrl="${registryAccount}" --organization="${credentials.org}" `;
64
64
  const optionsToAdd = ["appId", "appName", "appVersion", "appVersionCode", "wtvAndroidVersion"];
65
65
  for (const option of optionsToAdd) {
66
66
  gruntOptions += `--${option}="${config.androidtv?.device[option]}" `;
@@ -11,6 +11,7 @@ declare class InstallHelper {
11
11
  }) => Promise<string>;
12
12
  private static getDependenciesList;
13
13
  private static readonly installSingleDependency;
14
+ private static readonly updateSemver;
14
15
  /**
15
16
  * Get a list of all dependencies needed by the different devices chosen by user
16
17
  */
@@ -12,6 +12,8 @@
12
12
  import { exec } from "node:child_process";
13
13
  import { BASIC_DEPENDENCIES, PLATFORM_CONFIG } from "../const/deviceConst.js";
14
14
  import translation from "../translation/translation.js";
15
+ import path from "node:path";
16
+ import fs from "node:fs";
15
17
  class InstallHelper {
16
18
  /**
17
19
  * Install dependencies with npm
@@ -22,9 +24,9 @@ class InstallHelper {
22
24
  return new Promise((resolve, reject) => {
23
25
  exec(`npm i --prefix ${directory} --silent`, {}, (async () => {
24
26
  try {
25
- for (const depName of devDependencies) {
26
- await InstallHelper.installSingleDependency(depName, directory);
27
- }
27
+ const dependenciesString = devDependencies.join(" ").trim();
28
+ await InstallHelper.installSingleDependency(dependenciesString, directory);
29
+ InstallHelper.updateSemver(directory);
28
30
  resolve("Dependencies updated");
29
31
  }
30
32
  catch (error) {
@@ -53,6 +55,16 @@ class InstallHelper {
53
55
  });
54
56
  });
55
57
  };
58
+ static updateSemver = (directory) => {
59
+ const filePath = path.join(directory, "package.json");
60
+ const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
61
+ packageJson.devDependencies = Object.entries(packageJson.devDependencies).reduce((acc, [key, value]) => {
62
+ acc[key] =
63
+ key.includes("@dana") || key.includes("@wiztivi") ? value.replace(/(\d+\.\d+\.)\d$/, "$1x") : value;
64
+ return acc;
65
+ }, {});
66
+ fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 4));
67
+ };
56
68
  /**
57
69
  * Get a list of all dependencies needed by the different devices chosen by user
58
70
  */
@@ -16,6 +16,7 @@ import { templateGenerator } from "./handlebarsHelper.js";
16
16
  import { CredentialsHelper } from "../../commands/authentication/helper/CredentialsHelper.js";
17
17
  import { getRepositoryEndpoint } from "../../commands/authentication/helper/CodeArtifactHelper.js";
18
18
  import { REGISTRY_KEY } from "../../commands/createApp/const/setupConst.js";
19
+ import { formatToCamelCase } from "./stringHelper.js";
19
20
  class UpdateFileHelper {
20
21
  /**
21
22
  * Update app.Config files with mandatory Data
@@ -54,7 +55,9 @@ class UpdateFileHelper {
54
55
  const filePath = path.join(directory, "package.json");
55
56
  const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
56
57
  if (name) {
57
- packageJson.name = name;
58
+ //https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true
59
+ const unsafeCharRegex = /[^A-Za-z0-9._-]/g;
60
+ packageJson.name = name.replaceAll(unsafeCharRegex, "").toLowerCase();
58
61
  }
59
62
  for (const device of selectedDevices) {
60
63
  const deviceConfig = config[device];
@@ -87,7 +90,7 @@ class UpdateFileHelper {
87
90
  };
88
91
  static applyTemplatesToFiles = function ({ directory, name, }) {
89
92
  const routesFilePath = path.join(directory, "scripts", "app");
90
- const appName = `${name[0].toUpperCase()}${name.substring(1)}`;
93
+ const appName = `${formatToCamelCase(name)}`;
91
94
  const filesToUpdate = [
92
95
  {
93
96
  fromPath: path.join(routesFilePath, "TemplateAppRoutes.js.hbs"),
@@ -117,7 +120,12 @@ class UpdateFileHelper {
117
120
  return Promise.resolve("Files templated");
118
121
  };
119
122
  static getConfigFileOutput = function (directory) {
120
- const configFiles = ["gitignoreTemplate.hbs", "editorConfigTemplate.hbs", "eslintrc.jsonTemplate.hbs"];
123
+ const configFiles = [
124
+ "gitignoreTemplate.hbs",
125
+ "editorconfigTemplate.hbs",
126
+ "eslintrc.jsonTemplate.hbs",
127
+ "nvmrcTemplate.hbs",
128
+ ];
121
129
  const filesToUpdate = [];
122
130
  for (const configFile of configFiles) {
123
131
  filesToUpdate.push({
@@ -44,7 +44,7 @@ class UserInputGetter {
44
44
  return UserInputGetter.formatAppName(name);
45
45
  }
46
46
  static formatAppName(input) {
47
- const separatorRegex = /[ \-_.]+/;
47
+ const separatorRegex = /[ /]+/;
48
48
  return input
49
49
  .trim()
50
50
  .split(separatorRegex)
@@ -60,6 +60,8 @@ class UserInputGetter {
60
60
  static getAvailableDevices() {
61
61
  const availableDevices = [];
62
62
  const unavailableDevices = [];
63
+ const spinner = prompts.spinner();
64
+ spinner.start();
63
65
  for (const [key, value] of Object.entries(EXTENDED_SETUP)) {
64
66
  try {
65
67
  execSync(`npm view ${value.package} --silent`, { encoding: "utf-8" });
@@ -69,6 +71,7 @@ class UserInputGetter {
69
71
  unavailableDevices.push(EXTENDED_SETUP[key].label);
70
72
  }
71
73
  }
74
+ spinner.stop("Available devices checked");
72
75
  return Promise.resolve({ availableDevices, unavailableDevices });
73
76
  }
74
77
  /**
@@ -1,3 +1,8 @@
1
1
  import { OptionList } from "../types/helperTypes.js";
2
2
  export declare const capitalizeFirstLetter: (input: string) => string;
3
3
  export declare const optionsListString: (input: OptionList) => string;
4
+ /**
5
+ * Remove separator characters from string and convert it to camelCase
6
+ */
7
+ export declare const formatToCamelCase: (input: string) => string;
8
+ export declare const formatWordToCamelCase: (input: string) => string;
@@ -4,3 +4,15 @@ export const capitalizeFirstLetter = (input) => {
4
4
  export const optionsListString = (input) => {
5
5
  return input.map((option) => option.command).join(", ");
6
6
  };
7
+ /**
8
+ * Remove separator characters from string and convert it to camelCase
9
+ */
10
+ export const formatToCamelCase = (input) => {
11
+ const separatorRegex = /[./_\-\\ ]/g;
12
+ return input
13
+ .trim()
14
+ .split(separatorRegex)
15
+ .map((el, index) => (index === 0 ? el.toLowerCase() : formatWordToCamelCase(el)))
16
+ .join("");
17
+ };
18
+ export const formatWordToCamelCase = (input) => `${input[0].toUpperCase()}${input.substring(1).toLowerCase()}`;
@@ -22,8 +22,10 @@
22
22
  "helper.git.status.error": "Git status is not clean or an error occurred. Try to commit your change.",
23
23
  "command.auth.description": "Authenticate to Dana framework",
24
24
  "command.auth.login.description": "Login to Dana",
25
- "command.auth.login.option.org": "Organization to login",
25
+ "command.auth.logout.description": "Logout from Dana",
26
+ "command.auth.login.option.org": "Organization to login/logout",
26
27
  "command.auth.status.description": "Get login status",
28
+ "command.auth.get-packages-token.description": "Get token for packages repository",
27
29
  "command.scrollView.description": "Add a scrollView of rails",
28
30
  "command.scrollView.option.itemView": "Item to be used in the scrollView. Default: pre-made rail",
29
31
  "command.scrollView.option.itemMargin": "Margin between items. Default: 550",
@@ -56,7 +58,7 @@
56
58
  "command.completion.argument": "Shell. Bash or zsh",
57
59
  "command.completion.success": "Completion installed to ",
58
60
  "login.info.token_expired": "Your token has expired.",
59
- "login.info.please_login": "Please run dana auth login command",
61
+ "login.info.please_login": "Please run 'dana auth login' command",
60
62
  "login.error.not_logged": "You're not logged in.",
61
63
  "login.info.logged": "You're currently logged in to Dana"
62
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wiztivi/dana-cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Dana create app CLI",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@aws-sdk/client-codeartifact": "^3.859.0",
30
30
  "@clack/prompts": "0.10.0",
31
- "@wiztivi/dana-templates": "^0.0.8",
31
+ "@wiztivi/dana-templates": "^0.0.10",
32
32
  "child_process": "1.0.x",
33
33
  "command-exists": "1.2.9",
34
34
  "commander": "11.1.x",