@wiztivi/dana-cli 0.0.8 → 0.0.9

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.
@@ -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,22 @@
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 refresh(organization: string, token: string): Promise<AuthCredentials>;
17
+ static isTokenExpired(token: string): boolean;
18
+ static loginNPM(organization: string, credentials: AwsCredentials): Promise<{
19
+ token: string;
20
+ expiration?: Date;
21
+ }>;
22
+ }
@@ -0,0 +1,92 @@
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
+ app.get("/callback", (req, res) => {
19
+ try {
20
+ if (req.query.error) {
21
+ res.send("Login failed. Check the CLI for details.");
22
+ return reject(new Error(req.query.error));
23
+ }
24
+ const token = JSON.parse(req.query.token);
25
+ const awsCredentials = JSON.parse(req.query.aws);
26
+ const creds = { aws: awsCredentials, dana: token, org: organization };
27
+ res.send("Login successful! You can close this window.");
28
+ CredentialsHelper.store(creds);
29
+ resolve(creds);
30
+ }
31
+ catch (err) {
32
+ res.send("Login failed. Check the CLI for details.");
33
+ reject(err);
34
+ }
35
+ });
36
+ });
37
+ const port = await getPort();
38
+ const server = app.listen(port);
39
+ await open(`${AuthenticationManager.getAPIURL(organization)}/login?port=${port}`);
40
+ return promise.finally(() => {
41
+ server.closeAllConnections();
42
+ server.close();
43
+ });
44
+ }
45
+ static async refresh(organization, token) {
46
+ const data = await fetch(`${AuthenticationManager.getAPIURL(organization)}/login?refresh_token=${token}`);
47
+ if (!data.ok) {
48
+ return AuthenticationManager.login(organization);
49
+ }
50
+ const refreshedCreds = (await data.json());
51
+ const creds = {
52
+ aws: refreshedCreds.aws,
53
+ dana: {
54
+ ...refreshedCreds.token,
55
+ refresh_token: token,
56
+ },
57
+ org: organization,
58
+ };
59
+ CredentialsHelper.store(creds);
60
+ return creds;
61
+ }
62
+ static isTokenExpired(token) {
63
+ const payload = jwt.decode(token);
64
+ if (!payload.exp) {
65
+ return true;
66
+ }
67
+ const expirationDate = new Date(payload.exp * 1000);
68
+ return expirationDate <= new Date();
69
+ }
70
+ static async loginNPM(organization, credentials) {
71
+ // Create CodeArtifact client with credentials
72
+ const client = createCodeArtifactClient(credentials);
73
+ // Get authorization token
74
+ const tokenCommand = new GetAuthorizationTokenCommand({
75
+ domain: DOMAIN,
76
+ domainOwner: DOMAIN_OWNER,
77
+ });
78
+ const { authorizationToken, expiration } = await client.send(tokenCommand);
79
+ if (!authorizationToken) {
80
+ throw new Error("Authorization token not found");
81
+ }
82
+ // Get repository endpoint
83
+ const repositoryEndpoint = await getRepositoryEndpoint({ organization, credentials, client, format: "npm" });
84
+ // Configure npm with the repository and token
85
+ execSync(`npm config set @dana:registry=${repositoryEndpoint}`);
86
+ execSync(`npm config set //${repositoryEndpoint.replace(/^https?:\/\//, "")}:_authToken=${authorizationToken}`);
87
+ return {
88
+ token: authorizationToken,
89
+ expiration,
90
+ };
91
+ }
92
+ }
@@ -2,12 +2,11 @@ import { Command } 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";
6
5
  const addAuthDefinition = () => {
7
6
  const command = new Command("auth").description(translation["command.auth.description"]);
8
7
  const loginCmd = new Command("login")
9
8
  .description(translation["command.auth.login.description"])
10
- .option("-o, --org <organization>", translation["command.auth.login.option.org"], DEFAULT_ORG)
9
+ .option("-o, --org <organization>", translation["command.auth.login.option.org"])
11
10
  .action(login);
12
11
  command.addCommand(loginCmd);
13
12
  const statusCmd = new Command("status")
@@ -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
  };
@@ -1,77 +1,26 @@
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
3
  import { LOGIN_ERROR } from "../../../common/const/exitCodeConst.js";
9
4
  import { CredentialsHelper } from "../helper/CredentialsHelper.js";
10
- import { DEFAULT_ORG, DOMAIN, DOMAIN_OWNER } from "../authentConst.js";
11
- import { getRepositoryEndpoint, createCodeArtifactClient } from "../helper/CodeArtifactHelper.js";
5
+ import { AuthenticationManager } from "../AuthenticationManager.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
  };
@@ -80,24 +29,4 @@ const handleError = (error) => {
80
29
  prompts.outro(colors.red(`An issue occurred :` + message));
81
30
  process.exit(LOGIN_ERROR);
82
31
  };
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
32
  export default login;
@@ -8,20 +8,29 @@ 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
+ credentials: Record<string, OrgCredentials>;
22
+ }
15
23
  export declare class CredentialsHelper {
16
24
  static readonly filePath: string;
25
+ private static getConfig;
17
26
  static store(credentials: {
18
27
  aws: AwsCredentials;
19
28
  dana: DanaToken;
20
29
  org: string;
21
30
  }): void;
22
- static get(): {
23
- aws: AwsCredentials;
24
- dana: DanaToken;
31
+ static get(org?: string): {
32
+ aws?: AwsCredentials;
33
+ dana?: DanaToken;
25
34
  org: string;
26
- } | null;
35
+ };
27
36
  }
@@ -1,15 +1,35 @@
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";
4
5
  export class CredentialsHelper {
5
6
  static filePath = path.join(homedir(), ".dana.json");
6
- static store(credentials) {
7
- writeFileSync(this.filePath, JSON.stringify(credentials, null, 4));
8
- }
9
- static get() {
7
+ static getConfig() {
10
8
  if (!existsSync(this.filePath)) {
11
9
  return null;
12
10
  }
13
- return JSON.parse(readFileSync(this.filePath).toString());
11
+ return JSON.parse(readFileSync(this.filePath, { encoding: "utf-8" }));
12
+ }
13
+ static store(credentials) {
14
+ let config = this.getConfig();
15
+ config ??= { lastUsedOrg: credentials.org, credentials: {} };
16
+ config.credentials ??= {};
17
+ config.lastUsedOrg = credentials.org;
18
+ config.credentials[credentials.org] = { aws: credentials.aws, dana: credentials.dana };
19
+ writeFileSync(this.filePath, JSON.stringify(config, null, 4));
20
+ }
21
+ static get(org) {
22
+ const config = this.getConfig();
23
+ const organization = org ?? config?.lastUsedOrg ?? DEFAULT_ORG;
24
+ const creds = {
25
+ org: organization,
26
+ };
27
+ if (config?.credentials?.[organization]?.aws) {
28
+ creds.aws = config?.credentials[organization]?.aws;
29
+ }
30
+ if (config?.credentials?.[organization]?.dana) {
31
+ creds.dana = config?.credentials[organization]?.dana;
32
+ }
33
+ return creds;
14
34
  }
15
35
  }
@@ -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
@@ -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()}`;
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.9",
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.9",
32
32
  "child_process": "1.0.x",
33
33
  "command-exists": "1.2.9",
34
34
  "commander": "11.1.x",