@cloudcommerce/cli 0.0.50 → 0.0.53

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,5 +1,5 @@
1
- @cloudcommerce/cli:build: cache hit, replaying output 03aaa3a4a2c72327
2
- @cloudcommerce/cli:build: 
3
- @cloudcommerce/cli:build: > @cloudcommerce/cli@0.0.49 build /home/leo/code/ecomplus/cloud-commerce/packages/cli
4
- @cloudcommerce/cli:build: > sh ../../scripts/build-lib.sh
5
- @cloudcommerce/cli:build: 
1
+ @cloudcommerce/cli:build: cache hit, replaying output cc4c7d1f235b43da
2
+ @cloudcommerce/cli:build: 
3
+ @cloudcommerce/cli:build: > @cloudcommerce/cli@0.0.52 build /home/leo/code/ecomplus/cloud-commerce/packages/cli
4
+ @cloudcommerce/cli:build: > sh ../../scripts/build-lib.sh
5
+ @cloudcommerce/cli:build: 
@@ -36,7 +36,22 @@
36
36
  "firebase.json",
37
37
  "**/.*",
38
38
  "**/node_modules/**"
39
- ]
39
+ ],
40
+ "rewrites": [
41
+ {
42
+ "source": "/api/modules/**",
43
+ "function": "modules"
44
+ },
45
+ {
46
+ "source": "/api/passport/**",
47
+ "function": "passport"
48
+ },
49
+ {
50
+ "source": "**/!(*(*.)js|*(*.)css|*(*.)ico|*(*.)png|*(*.)gif|*(*.)jpg|*(*.)jpeg|*(*.)webp|*(*.)avif|*(*.)svg|*(*.)woff|*(*.)woff2|*(*.)otf|*(*.)ttf|*(*.)eot)",
51
+ "function": "ssr"
52
+ }
53
+ ],
54
+ "cleanUrls": true
40
55
  },
41
56
  "storage": {
42
57
  "rules": "storage.rules"
@@ -0,0 +1,94 @@
1
+ import path from 'path';
2
+ import { $, fs } from 'zx';
3
+
4
+ const serviceAccountId = 'cloud-commerce-gh-actions';
5
+ const getAccountEmail = (projectId) => {
6
+ return `${serviceAccountId}@${projectId}.iam.gserviceaccount.com`;
7
+ };
8
+ const checkServiceAccountExists = async (projectId) => {
9
+ let hasServiceAccount;
10
+ try {
11
+ const { stderr } = await $`gcloud iam service-accounts describe ${getAccountEmail(projectId)}`;
12
+ hasServiceAccount = !/not_?found/i.test(stderr);
13
+ } catch (e) {
14
+ return null;
15
+ }
16
+ return hasServiceAccount;
17
+ };
18
+ const siginGcloudAndSetIAM = async (projectId, pwd) => {
19
+ if (/no credential/i.test((await $`gcloud auth list`).stderr)) {
20
+ await $`gcloud auth login`;
21
+ }
22
+ await $`gcloud config set project ${projectId}`;
23
+ const roles = [
24
+ 'roles/firebase.admin',
25
+ 'roles/appengine.appAdmin',
26
+ 'roles/appengine.appCreator',
27
+ 'roles/artifactregistry.admin',
28
+ 'roles/cloudfunctions.admin',
29
+ 'roles/cloudscheduler.admin',
30
+ 'roles/iam.serviceAccountUser',
31
+ 'roles/run.viewer',
32
+ 'roles/serviceusage.apiKeysViewer',
33
+ 'roles/serviceusage.serviceUsageAdmin',
34
+ ];
35
+ const serviceAccount = await checkServiceAccountExists(projectId);
36
+ if (!serviceAccount) {
37
+ await $`gcloud iam service-accounts create ${serviceAccountId} \
38
+ --description="A service account with permission to deploy Cloud Commerce from the GitHub repository to Firebase" \
39
+ --display-name="Cloud Commerce GH Actions"`;
40
+ }
41
+ await fs.ensureDir(path.join(pwd, '.cloudcommerce'));
42
+ const pathPolicyIAM = path.join(pwd, '.cloudcommerce', 'policyIAM.json');
43
+ await $`gcloud projects get-iam-policy ${projectId} --format json > ${pathPolicyIAM}`;
44
+ const policyIAM = fs.readJSONSync(pathPolicyIAM);
45
+ const { bindings } = policyIAM;
46
+ let mustUpdatePolicy = false;
47
+ roles.forEach((role) => {
48
+ const roleFound = bindings.find((binding) => binding.role === role);
49
+ const memberServiceAccount = `serviceAccount:${getAccountEmail(projectId)}`;
50
+ if (!roleFound) {
51
+ const newBinding = {
52
+ members: [
53
+ memberServiceAccount,
54
+ ],
55
+ role,
56
+ };
57
+ if (role === 'roles/serviceusage.serviceUsageAdmin') {
58
+ const roleExpiration = Date.now() + 1000 * 60 * 60 * 12;
59
+ newBinding.condition = {
60
+ expression: `request.time < timestamp("${new Date(roleExpiration).toISOString()}")`,
61
+ title: 'Enable APIs on first deploy',
62
+ description: null,
63
+ };
64
+ }
65
+ bindings.push(newBinding);
66
+ mustUpdatePolicy = true;
67
+ } else {
68
+ const serviceAccountHavePermission = roleFound.members.find((account) => account === memberServiceAccount);
69
+ if (!serviceAccountHavePermission) {
70
+ roleFound.members.push(memberServiceAccount);
71
+ mustUpdatePolicy = true;
72
+ }
73
+ }
74
+ });
75
+ if (mustUpdatePolicy) {
76
+ fs.writeJSONSync(pathPolicyIAM, policyIAM);
77
+ return $`gcloud projects set-iam-policy ${projectId} ${pathPolicyIAM}`;
78
+ }
79
+ return null;
80
+ };
81
+ const createKeyServiceAccount = async (projectId, pwd) => {
82
+ try {
83
+ const pathFileKey = path.join(pwd, '.cloudcommerce', 'serviceAccountKey.json');
84
+ await $`gcloud iam service-accounts keys create ${pathFileKey} \
85
+ --iam-account=${getAccountEmail(projectId)}`;
86
+ return JSON.stringify(fs.readJSONSync(pathFileKey));
87
+ } catch (e) {
88
+ return null;
89
+ }
90
+ };
91
+
92
+ export default siginGcloudAndSetIAM;
93
+
94
+ export { siginGcloudAndSetIAM, createKeyServiceAccount };
@@ -26,15 +26,15 @@ export default async (storeId, accessToken) => {
26
26
  username: `cloudcomm${Date.now()}`,
27
27
  permissions: {
28
28
  applications: ['all'],
29
- brands: ['GET'],
30
- carts: ['all'],
31
- categories: ['GET'],
32
- collections: ['GET'],
29
+ brands: ['all'],
30
+ categories: ['all'],
31
+ collections: ['all'],
32
+ grids: ['all'],
33
+ products: ['all'],
33
34
  customers: ['all'],
34
- grids: ['GET'],
35
+ carts: ['all'],
35
36
  orders: ['GET', 'POST', 'PATCH'],
36
- products: ['GET', 'PATCH'],
37
- stores: ['GET'],
37
+ stores: ['GET', 'PATCH'],
38
38
  },
39
39
  }, apiConfig);
40
40
  authenticationId = _id;
package/lib/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from 'zx';
6
6
  import login from './login.js';
7
7
  import build from './build.js';
8
+ import { siginGcloudAndSetIAM, createKeyServiceAccount } from './config-gcloud.js';
8
9
 
9
10
  const { FIREBASE_PROJECT_ID, GOOGLE_APPLICATION_CREDENTIALS } = process.env;
10
11
  const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
@@ -28,7 +29,7 @@ if (projectId) {
28
29
  const firebaserc = fs.readJSONSync(path.join(pwd, '.firebaserc'));
29
30
  projectId = firebaserc.projects.default;
30
31
  } catch (e) {
31
- projectId = 'ecom2-hello';
32
+ projectId = 'ecom2-001';
32
33
  }
33
34
  }
34
35
  }
@@ -36,13 +37,16 @@ if (projectId) {
36
37
  export default async () => {
37
38
  await fs.copy(path.join(__dirname, '..', 'config'), pwd);
38
39
  const options = Object.keys(argv).reduce((opts, key) => {
39
- if (key !== '_' && key !== 'deploy' && key !== 'commit') {
40
+ if (key !== '_' && argv[key] !== false) {
40
41
  // eslint-disable-next-line no-param-reassign
41
42
  opts += ` --${key} ${argv[key]}`;
42
43
  }
43
44
  return opts;
44
45
  }, '');
45
46
  const $firebase = (cmd) => {
47
+ if (cmd === 'deploy') {
48
+ return $`firebase --project=${projectId} ${cmd}${options} --force`;
49
+ }
46
50
  return $`firebase --project=${projectId} ${cmd}${options}`;
47
51
  };
48
52
  if (argv._.includes('serve')) {
@@ -94,6 +98,15 @@ ECOM_STORE_ID=${storeId}
94
98
  //
95
99
  }
96
100
  }
101
+ let serviceAccountJSON = null;
102
+ if (argv.gcloud !== false) {
103
+ try {
104
+ await siginGcloudAndSetIAM(projectId, pwd);
105
+ serviceAccountJSON = await createKeyServiceAccount(projectId, pwd);
106
+ } catch (e) {
107
+ //
108
+ }
109
+ }
97
110
  return echo`
98
111
  ****
99
112
 
@@ -103,7 +116,7 @@ Finish by saving the following secrets to your GitHub repository:
103
116
 
104
117
  ${chalk.bold('ECOM_API_KEY')} = ${chalk.bgMagenta(apiKey)}
105
118
 
106
- ${chalk.bold('FIREBASE_SERVICE_ACCOUNT')} = {YOUR_SERVICE_ACCOUNT_JSON}
119
+ ${chalk.bold('FIREBASE_SERVICE_ACCOUNT')} = ${chalk.bgMagenta(serviceAccountJSON || '{YOUR_SERVICE_ACCOUNT_JSON}')}
107
120
 
108
121
  -- More info at https://github.com/ecomplus/store#getting-started
109
122
  `;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcommerce/cli",
3
3
  "type": "module",
4
- "version": "0.0.50",
4
+ "version": "0.0.53",
5
5
  "description": "E-Com Plus Cloud Commerce CLI tools",
6
6
  "bin": {
7
7
  "cloudcommerce": "./bin/run.mjs"
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "homepage": "https://github.com/ecomplus/cloud-commerce/tree/main/packages/cli#readme",
25
25
  "dependencies": {
26
- "@cloudcommerce/api": "0.0.50",
26
+ "@cloudcommerce/api": "0.0.53",
27
27
  "md5": "^2.3.0",
28
- "zx": "^7.0.7"
28
+ "zx": "^7.0.8"
29
29
  },
30
30
  "scripts": {
31
31
  "build": "sh ../../scripts/build-lib.sh"
@@ -0,0 +1,103 @@
1
+ import path from 'path';
2
+ import { $, fs } from 'zx';
3
+
4
+ const serviceAccountId = 'cloud-commerce-gh-actions';
5
+ const getAccountEmail = (projectId: string) => {
6
+ return `${serviceAccountId}@${projectId}.iam.gserviceaccount.com`;
7
+ };
8
+
9
+ const checkServiceAccountExists = async (projectId: string) => {
10
+ let hasServiceAccount: boolean;
11
+ try {
12
+ const { stderr } = await $`gcloud iam service-accounts describe ${getAccountEmail(projectId)}`;
13
+ hasServiceAccount = !/not_?found/i.test(stderr);
14
+ } catch (e) {
15
+ return null;
16
+ }
17
+ return hasServiceAccount;
18
+ };
19
+
20
+ const siginGcloudAndSetIAM = async (projectId: string, pwd: string) => {
21
+ if (/no credential/i.test((await $`gcloud auth list`).stderr)) {
22
+ await $`gcloud auth login`;
23
+ }
24
+ await $`gcloud config set project ${projectId}`;
25
+ const roles = [
26
+ 'roles/firebase.admin',
27
+ 'roles/appengine.appAdmin',
28
+ 'roles/appengine.appCreator',
29
+ 'roles/artifactregistry.admin',
30
+ 'roles/cloudfunctions.admin',
31
+ 'roles/cloudscheduler.admin',
32
+ 'roles/iam.serviceAccountUser',
33
+ 'roles/run.viewer',
34
+ 'roles/serviceusage.apiKeysViewer',
35
+ 'roles/serviceusage.serviceUsageAdmin',
36
+ ];
37
+ const serviceAccount = await checkServiceAccountExists(projectId);
38
+ if (!serviceAccount) {
39
+ await $`gcloud iam service-accounts create ${serviceAccountId} \
40
+ --description="A service account with permission to deploy Cloud Commerce from the GitHub repository to Firebase" \
41
+ --display-name="Cloud Commerce GH Actions"`;
42
+ }
43
+ await fs.ensureDir(path.join(pwd, '.cloudcommerce'));
44
+ const pathPolicyIAM = path.join(pwd, '.cloudcommerce', 'policyIAM.json');
45
+ await $`gcloud projects get-iam-policy ${projectId} --format json > ${pathPolicyIAM}`;
46
+ const policyIAM = fs.readJSONSync(pathPolicyIAM);
47
+ const { bindings } = policyIAM;
48
+
49
+ let mustUpdatePolicy = false;
50
+ roles.forEach((role) => {
51
+ const roleFound = bindings.find((binding) => binding.role === role);
52
+ const memberServiceAccount = `serviceAccount:${getAccountEmail(projectId)}`;
53
+ if (!roleFound) {
54
+ const newBinding: { [key: string]: any } = {
55
+ members: [
56
+ memberServiceAccount,
57
+ ],
58
+ role,
59
+ };
60
+ if (role === 'roles/serviceusage.serviceUsageAdmin') {
61
+ const roleExpiration = Date.now() + 1000 * 60 * 60 * 12;
62
+ newBinding.condition = {
63
+ expression: `request.time < timestamp("${new Date(roleExpiration).toISOString()}")`,
64
+ title: 'Enable APIs on first deploy',
65
+ description: null,
66
+ };
67
+ }
68
+ bindings.push(newBinding);
69
+ mustUpdatePolicy = true;
70
+ } else {
71
+ const serviceAccountHavePermission = roleFound.members.find(
72
+ (account: string) => account === memberServiceAccount,
73
+ );
74
+ if (!serviceAccountHavePermission) {
75
+ roleFound.members.push(memberServiceAccount);
76
+ mustUpdatePolicy = true;
77
+ }
78
+ }
79
+ });
80
+ if (mustUpdatePolicy) {
81
+ fs.writeJSONSync(pathPolicyIAM, policyIAM);
82
+ return $`gcloud projects set-iam-policy ${projectId} ${pathPolicyIAM}`;
83
+ }
84
+ return null;
85
+ };
86
+
87
+ const createKeyServiceAccount = async (projectId: string, pwd: string) => {
88
+ try {
89
+ const pathFileKey = path.join(pwd, '.cloudcommerce', 'serviceAccountKey.json');
90
+ await $`gcloud iam service-accounts keys create ${pathFileKey} \
91
+ --iam-account=${getAccountEmail(projectId)}`;
92
+ return JSON.stringify(fs.readJSONSync(pathFileKey));
93
+ } catch (e) {
94
+ return null;
95
+ }
96
+ };
97
+
98
+ export default siginGcloudAndSetIAM;
99
+
100
+ export {
101
+ siginGcloudAndSetIAM,
102
+ createKeyServiceAccount,
103
+ };
@@ -27,15 +27,15 @@ export default async (storeId: number, accessToken: string) => {
27
27
  username: `cloudcomm${Date.now()}`,
28
28
  permissions: {
29
29
  applications: ['all'],
30
- brands: ['GET'],
31
- carts: ['all'],
32
- categories: ['GET'],
33
- collections: ['GET'],
30
+ brands: ['all'],
31
+ categories: ['all'],
32
+ collections: ['all'],
33
+ grids: ['all'],
34
+ products: ['all'],
34
35
  customers: ['all'],
35
- grids: ['GET'],
36
+ carts: ['all'],
36
37
  orders: ['GET', 'POST', 'PATCH'],
37
- products: ['GET', 'PATCH'],
38
- stores: ['GET'],
38
+ stores: ['GET', 'PATCH'],
39
39
  },
40
40
  }, apiConfig);
41
41
  authenticationId = _id;
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  } from 'zx';
10
10
  import login from './login';
11
11
  import build from './build';
12
+ import { siginGcloudAndSetIAM, createKeyServiceAccount } from './config-gcloud';
12
13
 
13
14
  const {
14
15
  FIREBASE_PROJECT_ID,
@@ -40,7 +41,7 @@ if (projectId) {
40
41
  const firebaserc = fs.readJSONSync(path.join(pwd, '.firebaserc'));
41
42
  projectId = firebaserc.projects.default;
42
43
  } catch (e) {
43
- projectId = 'ecom2-hello';
44
+ projectId = 'ecom2-001';
44
45
  }
45
46
  }
46
47
  }
@@ -49,13 +50,16 @@ export default async () => {
49
50
  await fs.copy(path.join(__dirname, '..', 'config'), pwd);
50
51
 
51
52
  const options = Object.keys(argv).reduce((opts, key) => {
52
- if (key !== '_' && key !== 'deploy' && key !== 'commit') {
53
+ if (key !== '_' && argv[key] !== false) {
53
54
  // eslint-disable-next-line no-param-reassign
54
55
  opts += ` --${key} ${argv[key]}`;
55
56
  }
56
57
  return opts;
57
58
  }, '');
58
59
  const $firebase = (cmd: string) => {
60
+ if (cmd === 'deploy') {
61
+ return $`firebase --project=${projectId} ${cmd}${options} --force`;
62
+ }
59
63
  return $`firebase --project=${projectId} ${cmd}${options}`;
60
64
  };
61
65
 
@@ -116,6 +120,16 @@ ECOM_STORE_ID=${storeId}
116
120
  //
117
121
  }
118
122
  }
123
+ let serviceAccountJSON : string | null = null;
124
+ if (argv.gcloud !== false) {
125
+ try {
126
+ await siginGcloudAndSetIAM(projectId as string, pwd);
127
+ serviceAccountJSON = await createKeyServiceAccount(projectId as string, pwd);
128
+ } catch (e) {
129
+ //
130
+ }
131
+ }
132
+
119
133
  return echo`
120
134
  ****
121
135
 
@@ -125,11 +139,10 @@ Finish by saving the following secrets to your GitHub repository:
125
139
 
126
140
  ${chalk.bold('ECOM_API_KEY')} = ${chalk.bgMagenta(apiKey)}
127
141
 
128
- ${chalk.bold('FIREBASE_SERVICE_ACCOUNT')} = {YOUR_SERVICE_ACCOUNT_JSON}
142
+ ${chalk.bold('FIREBASE_SERVICE_ACCOUNT')} = ${chalk.bgMagenta(serviceAccountJSON || '{YOUR_SERVICE_ACCOUNT_JSON}')}
129
143
 
130
144
  -- More info at https://github.com/ecomplus/store#getting-started
131
145
  `;
132
146
  }
133
-
134
147
  return $`echo 'Hello from @cloudcommerce/cli'`;
135
148
  };