@leverege/build-tools 2.21.8 → 2.21.9-nick.2

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/.env.temp ADDED
@@ -0,0 +1,18 @@
1
+ THEFT_RECOVERY_URL='theft.recovermycar.com'
2
+ MAPBOX_APIKEY=pk.eyJ1IjoibGV2ZXJlZ2UiLCJhIjoiY2p1Y3E0cWpkMG15azQzbnFzaDY2dmhiMSJ9.7N25h5jsjO2Aq_YmiLrAJw
3
+ APPEARANCE_PROJECT_ID=7b2GTbmsZj3f5VdFpjGM8O
4
+ INTERCOM_APP_ID='ec0qbuwi'
5
+ LOCATOR_ALIAS_KEY='serialNumber'
6
+ RECOVR_BILLING_MODULE='recovrBilling'
7
+ IMAGINE_HOST=https://leverege-sandbox-imagine-api.leverege.com
8
+ RECOVR_HOST=https://dev-recovr-notify.leverege.com
9
+ # RECOVR_HOST=http://localhost:8296
10
+ SYSTEM_ID=0UTmPFvRHXYLMlT4Kl2z1K
11
+ PROJECT_ID=08HBeHEYGxu5Bg4TCCnAAV
12
+ VEHICLE_NETWORK_ID='dev-dealership-vehicles'
13
+ CONSUMER_VEHICLE_NETWORK_ID='dev-customer-vehicles'
14
+ LOCATOR_NETWORK_ID='dev-recovr-tracker'
15
+ BEACON_NETWORK_ID='dev-dealership-beacons'
16
+ SALE_LOCATION_PARENT_PATH=sales
17
+ CONSUMER_LOCATION_ID=1KceIcZlVWrOBvCzsEXYud
18
+ IS_DEV=true
package/.firebaserc ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "projects": {
3
+ "default": "leverege-sandbox",
4
+ "stg-recovr-auto": "stg-recovr-auto",
5
+ "prd-recovr-auto":"prd-recovr-auto"
6
+ },
7
+ "targets": {
8
+ "leverege-sandbox": {
9
+ "hosting": {
10
+ "recovr": [
11
+ "dev"
12
+ ],
13
+ "dev-obd-recovr": [
14
+ "dev-obd"
15
+ ]
16
+ }
17
+ },
18
+ "stg-recovr-auto": {
19
+ "hosting": {
20
+ "stg-recovr": [
21
+ "stg"
22
+ ],
23
+ "stg-obd-recovr": [
24
+ "stg-obd"
25
+ ]
26
+ }
27
+ },
28
+ "prd-recovr-auto": {
29
+ "hosting": {
30
+ "recovr-auto": [
31
+ "prd"
32
+ ],
33
+ "prd-obd-recovr":[
34
+ "prd-obd"
35
+ ]
36
+ },
37
+ "production": true
38
+ }
39
+ }
40
+ }
package/.firebasercizg ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "projects": {
3
+ "default": "leverege-sandbox",
4
+ "dev-keys": "leverege-dev-nick",
5
+ "prd-indoor-zoning" : "prd-indoor-zoning"
6
+ },
7
+ "targets": {
8
+ "leverege-sandbox": {
9
+ "hosting": {
10
+ "dev-izg": [
11
+ "dev-izg"
12
+ ],
13
+ "dev-olive-indoor-zoning": [
14
+ "dev-olive-indoor-zoning"
15
+ ]
16
+ }
17
+ },
18
+ "leverege-dev-nick": {
19
+ "hosting": {
20
+ "dev-keys": [
21
+ "dev-keys"
22
+ ]
23
+ }
24
+ },
25
+ "prd-indoor-zoning": {
26
+ "hosting": {
27
+ "prd-indoor-zoning-ui222": [
28
+ "prd-indoor-zoning-ui"
29
+ ],
30
+ "prd-olive-indoor-zoning-ui222": [
31
+ "prd-olive-indoor-zoning-ui"
32
+ ],
33
+ "prd-keys222": [
34
+ "prd-keys"
35
+ ]
36
+ },
37
+ "production": true
38
+ },
39
+ "stg-indoor-zoning-pen-test": {
40
+ "hosting": {
41
+ "stg-indoor-zoning-ui": [
42
+ "stg-indoor-zoning-ui"
43
+ ]
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "node",
9
+ "request": "launch",
10
+ "name": "firebase deploy",
11
+ "skipFiles": [
12
+ "<node_internals>/**"
13
+ ],
14
+ "program": "${workspaceFolder}/src/firebaseDeploy.mjs",
15
+ "console": "integratedTerminal"
16
+ }
17
+ ]
18
+ }
package/firebase.json ADDED
@@ -0,0 +1,180 @@
1
+ {
2
+ "hosting": [
3
+ {
4
+ "site":"dev-izg",
5
+ "public": "dist",
6
+ "ignore": [
7
+ "firebase.json",
8
+ "**/.*",
9
+ "**/node_modules/**"
10
+ ],
11
+ "rewrites": [
12
+ {
13
+ "source": "**",
14
+ "destination": "/index.html"
15
+ }
16
+ ],
17
+ "headers": {
18
+ "X-Content-Type-Options": "nosniff",
19
+ "X-XSS-Protection": "1; mode=block",
20
+ "X-Frame-Options": "deny",
21
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
22
+ "Referrer-Policy":"no-referrer"
23
+ }
24
+ },
25
+ {
26
+ "site":"dev-keys",
27
+ "public": "dist",
28
+ "ignore": [
29
+ "firebase.json",
30
+ "**/.*",
31
+ "**/node_modules/**"
32
+ ],
33
+ "rewrites": [
34
+ {
35
+ "source": "**",
36
+ "destination": "/index.html"
37
+ }
38
+ ],
39
+ "headers": {
40
+ "X-Content-Type-Options": "nosniff",
41
+ "X-XSS-Protection": "1; mode=block",
42
+ "X-Frame-Options": "deny",
43
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
44
+ "Referrer-Policy":"no-referrer"
45
+ }
46
+ },
47
+ {
48
+ "site":"dev-keys",
49
+ "public": "dist",
50
+ "ignore": [
51
+ "firebase.json",
52
+ "**/.*",
53
+ "**/node_modules/**"
54
+ ],
55
+ "rewrites": [
56
+ {
57
+ "source": "**",
58
+ "destination": "/index.html"
59
+ }
60
+ ],
61
+ "headers": {
62
+ "X-Content-Type-Options": "nosniff",
63
+ "X-XSS-Protection": "1; mode=block",
64
+ "X-Frame-Options": "deny",
65
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
66
+ "Referrer-Policy":"no-referrer"
67
+ }
68
+ },
69
+ {
70
+ "site":"dev-olive-indoor-zoning",
71
+ "public": "dist",
72
+ "ignore": [
73
+ "firebase.json",
74
+ "**/.*",
75
+ "**/node_modules/**"
76
+ ],
77
+ "rewrites": [
78
+ {
79
+ "source": "**",
80
+ "destination": "/index.html"
81
+ }
82
+ ],
83
+ "headers": {
84
+ "X-Content-Type-Options": "nosniff",
85
+ "X-XSS-Protection": "1; mode=block",
86
+ "X-Frame-Options": "deny",
87
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
88
+ "Referrer-Policy":"no-referrer"
89
+ }
90
+ },
91
+ {
92
+ "site":"prd-indoor-zoning-ui",
93
+ "public": "dist",
94
+ "ignore": [
95
+ "firebase.json",
96
+ "**/.*",
97
+ "**/node_modules/**"
98
+ ],
99
+ "rewrites": [
100
+ {
101
+ "source": "**",
102
+ "destination": "/index.html"
103
+ }
104
+ ],
105
+ "headers": {
106
+ "X-Content-Type-Options": "nosniff",
107
+ "X-XSS-Protection": "1; mode=block",
108
+ "X-Frame-Options": "deny",
109
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
110
+ "Referrer-Policy":"no-referrer"
111
+ }
112
+ },
113
+ {
114
+ "site":"prd-keys",
115
+ "public": "dist",
116
+ "ignore": [
117
+ "firebase.json",
118
+ "**/.*",
119
+ "**/node_modules/**"
120
+ ],
121
+ "rewrites": [
122
+ {
123
+ "source": "**",
124
+ "destination": "/index.html"
125
+ }
126
+ ],
127
+ "headers": {
128
+ "X-Content-Type-Options": "nosniff",
129
+ "X-XSS-Protection": "1; mode=block",
130
+ "X-Frame-Options": "deny",
131
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
132
+ "Referrer-Policy":"no-referrer"
133
+ }
134
+ },
135
+ {
136
+ "site":"prd-olive-indoor-zoning-ui",
137
+ "public": "dist",
138
+ "ignore": [
139
+ "firebase.json",
140
+ "**/.*",
141
+ "**/node_modules/**"
142
+ ],
143
+ "rewrites": [
144
+ {
145
+ "source": "**",
146
+ "destination": "/index.html"
147
+ }
148
+ ],
149
+ "headers": {
150
+ "X-Content-Type-Options": "nosniff",
151
+ "X-XSS-Protection": "1; mode=block",
152
+ "X-Frame-Options": "deny",
153
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
154
+ "Referrer-Policy":"no-referrer"
155
+ }
156
+ },
157
+ {
158
+ "site":"stg-indoor-zoning-ui",
159
+ "public": "dist",
160
+ "ignore": [
161
+ "firebase.json",
162
+ "**/.*",
163
+ "**/node_modules/**"
164
+ ],
165
+ "rewrites": [
166
+ {
167
+ "source": "**",
168
+ "destination": "/index.html"
169
+ }
170
+ ],
171
+ "headers": {
172
+ "X-Content-Type-Options": "nosniff",
173
+ "X-XSS-Protection": "1; mode=block",
174
+ "X-Frame-Options": "deny",
175
+ "Content-Security-Policy": "default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: leverege-sandbox-imagine-api.leverege.com *.mapbox.com *.firebaseio.com wss://*.firebaseio.com *.googleapis.com *.intercom.io wss://*.intercom.io maxcdn.bootstrapcdn.com *.intercomcdn.com *.gstatic.com;",
176
+ "Referrer-Policy":"no-referrer"
177
+ }
178
+ }
179
+ ]
180
+ }
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console */
2
3
  // https://github.com/google/zx
3
4
  /* eslint-disable max-len */
4
5
  "use strict";
5
6
 
6
7
  var _enquirer = _interopRequireDefault(require("enquirer"));
8
+ var _ansiColors = _interopRequireDefault(require("ansi-colors"));
7
9
  var _zx = require("zx");
8
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
11
  const {
@@ -13,11 +15,27 @@ const {
13
15
  // This will preserve coloration from spawned child processes
14
16
  process.env.FORCE_COLORS = 3;
15
17
  process.env.FORCE_COLOR = '1';
16
- let TARGET = _zx.argv._[0];
18
+ let TARGET;
19
+ let CHANNEL;
20
+
21
+ // A single unnamed parameter is assumed to be a deploy target.
22
+ // If named parameters exist, they may not be stored in argv,
23
+ // so don't use the length of argv to decide whether to look for them
24
+ if (_zx.argv._.length === 1) {
25
+ TARGET = _zx.argv._[0];
26
+ } else {
27
+ TARGET = _zx.argv.target;
28
+ if (_zx.argv.channelName) {
29
+ CHANNEL = {};
30
+ CHANNEL.name = _zx.argv.channelName;
31
+ CHANNEL.expiration = _zx.argv.channelExpiration ?? '7d';
32
+ }
33
+ }
34
+ const INTERACTIVE_MODE = !TARGET;
17
35
  let exited = false;
18
36
  const SECRETS_DIR = 'secrets';
19
37
  const EXIT_EVENTS = ['SIGINT', 'exit', 'uncaughException', 'unhandledRejection'];
20
- const [firebaserc, firebaseJson] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), '.firebaserc')), _zx.fs.exists(_zx.path.join(process.cwd(), 'firebase.json'))]);
38
+ const [firebaserc, firebaseJson, git] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), '.firebaserc')), _zx.fs.exists(_zx.path.join(process.cwd(), 'firebase.json')), _zx.fs.exists(_zx.path.join(process.cwd(), '.git/HEAD'))]);
21
39
  if (!firebaserc) {
22
40
  console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
23
41
  }
@@ -30,31 +48,87 @@ if (!firebaserc || !firebaseJson) {
30
48
  const firebasercContent = JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
31
49
  encoding: 'utf-8'
32
50
  }));
33
- const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
34
- Object.entries(config.hosting).forEach(([siteId, aliases]) => {
51
+
52
+ // If in a git repo, fetch the name of the git branch to use as a default
53
+ // name in case a deployment channel is needed
54
+ let gitBranch;
55
+ if (git) {
56
+ const gitContent = (await _zx.fs.readFile(_zx.path.join(process.cwd(), '.git/HEAD'), {
57
+ encoding: 'utf-8'
58
+ }))?.split('/');
59
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd('\n');
60
+ }
61
+ let maxPadding = 0;
62
+ const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config, production]) => {
63
+ Object.entries(config.hosting).forEach(([siteId, aliases, production]) => {
35
64
  aliases.forEach(alias => {
65
+ maxPadding = Math.max(siteId.length, maxPadding);
36
66
  prev[alias] = {
37
67
  projectId,
38
68
  siteId,
39
- alias
69
+ alias,
70
+ production: config.production
40
71
  };
41
72
  });
42
73
  });
43
74
  return prev;
44
75
  }, {});
45
- if (!TARGET) {
76
+ if (INTERACTIVE_MODE) {
46
77
  const {
47
78
  targetEnv
48
- } = await prompt({
49
- type: 'autocomplete',
79
+ } = await prompt([{
80
+ type: 'select',
50
81
  name: 'targetEnv',
51
82
  message: 'To which environment would you like to deploy?',
52
- choices: Object.keys(targets).map(t => ({
53
- name: t,
54
- value: t
55
- }))
56
- });
83
+ choices: Object.values(targets).map(t => {
84
+ const productionLabel = t.production ? _ansiColors.default.bold.red('--PRODUCTION--') : '';
85
+ return {
86
+ message: `${_ansiColors.default.bold.cyan('Site: ')}${t.siteId.padEnd(maxPadding)} ${_ansiColors.default.bold.green('Project: ')}${t.projectId} ${productionLabel}`,
87
+ value: t.alias
88
+ };
89
+ })
90
+ }]);
57
91
  TARGET = targets[targetEnv];
92
+ if (TARGET.production) {
93
+ const {
94
+ confirmProductionDeploy
95
+ } = await prompt([{
96
+ type: 'confirm',
97
+ name: 'confirmProductionDeploy',
98
+ message: `${_ansiColors.default.bold.red('WARNING: ')} This is a ${_ansiColors.default.bold.yellow('PRODUCTION')} environment. Are you sure you want to deploy here?`
99
+ }]);
100
+ if (!confirmProductionDeploy) {
101
+ console.log('Cancelling deployment...');
102
+ process.exit();
103
+ }
104
+ }
105
+ const {
106
+ useChannel
107
+ } = await prompt([{
108
+ type: 'confirm',
109
+ name: 'useChannel',
110
+ message: 'Do you want to deploy to a temporary preview channel?'
111
+ }]);
112
+ if (useChannel) {
113
+ const {
114
+ channelName,
115
+ channelExpiration
116
+ } = await prompt([{
117
+ type: 'input',
118
+ name: 'channelName',
119
+ message: 'To which channel would you like to deploy?',
120
+ initial: gitBranch ?? 'temp-deploy'
121
+ }, {
122
+ type: 'input',
123
+ name: 'channelExpiration',
124
+ message: 'When should the channel expire?',
125
+ initial: '7d'
126
+ }]);
127
+ CHANNEL = {
128
+ name: channelName,
129
+ expiration: channelExpiration
130
+ };
131
+ }
58
132
  } else {
59
133
  TARGET = targets[TARGET];
60
134
  }
@@ -82,6 +156,26 @@ if (aliasFileExists) {
82
156
  encoding: 'utf-8'
83
157
  }));
84
158
  }
159
+ if (INTERACTIVE_MODE) {
160
+ console.log(`\n\n\n${_ansiColors.default.bold.green('====== Deployment Summary ======')}`);
161
+ console.log(`${_ansiColors.default.blue('Firebase project:')} ${TARGET.projectId} ${TARGET.production ? _ansiColors.default.yellow('--PRODUCTION--') : ''}`);
162
+ console.log(`${_ansiColors.default.blue('Site ID:')} ${TARGET.siteId}`);
163
+ console.log(`${_ansiColors.default.blue('Channel Name:')} ${CHANNEL?.name ?? _ansiColors.default.italic.gray('(none)')}`);
164
+ console.log(`${_ansiColors.default.blue('Channel Expiration date:')} ${CHANNEL?.name ? CHANNEL.expiration ?? _ansiColors.default.italic.gray('(none)') : _ansiColors.default.italic.gray('(n/a)')}`);
165
+ console.log(`${_ansiColors.default.blue('Shared .env file:')} ${sharedFileExists ? `${SECRETS_DIR}/shared.env` : _ansiColors.default.italic.gray('(none)')}`);
166
+ console.log(`${_ansiColors.default.blue('Additional .env overrides:')} ${aliasFileExists ? `${SECRETS_DIR}/${TARGET.alias}.env` : `${SECRETS_DIR}/${TARGET.siteId}.env`}`);
167
+ const {
168
+ confirmSelections
169
+ } = await prompt([{
170
+ type: 'confirm',
171
+ name: 'confirmSelections',
172
+ message: 'Proceed with this deployment?'
173
+ }]);
174
+ if (!confirmSelections) {
175
+ console.log('Cancelling deployment...');
176
+ process.exit();
177
+ }
178
+ }
85
179
  const envFileContent = (await Promise.all(exec)).join('\n');
86
180
  EXIT_EVENTS.forEach(event => {
87
181
  process.on(event, () => {
@@ -93,7 +187,8 @@ EXIT_EVENTS.forEach(event => {
93
187
  });
94
188
  await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
95
189
  try {
96
- await (0, _zx.$)`npm run clean && DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} npm run build && firebase use ${TARGET.projectId} && firebase deploy --only hosting:${TARGET.siteId}`;
190
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}` : 'deploy';
191
+ await (0, _zx.$)`npm run clean && DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} npm run build && firebase use ${TARGET.projectId} && firebase ${deployType} --only hosting:${TARGET.siteId}`;
97
192
  } catch (err) {
98
193
  console.error(err);
99
194
  }
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console */
2
3
  // https://github.com/google/zx
3
4
  /* eslint-disable max-len */
4
5
  "use strict";
5
6
 
6
7
  var _enquirer = _interopRequireDefault(require("enquirer"));
8
+ var _ansiColors = _interopRequireDefault(require("ansi-colors"));
7
9
  var _zx = require("zx");
8
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
11
  const {
@@ -13,11 +15,27 @@ const {
13
15
  // This will preserve coloration from spawned child processes
14
16
  process.env.FORCE_COLORS = 3;
15
17
  process.env.FORCE_COLOR = '1';
16
- let TARGET = _zx.argv._[0];
18
+ let TARGET;
19
+ let CHANNEL;
20
+
21
+ // A single unnamed parameter is assumed to be a deploy target.
22
+ // If named parameters exist, they may not be stored in argv,
23
+ // so don't use the length of argv to decide whether to look for them
24
+ if (_zx.argv._.length === 1) {
25
+ TARGET = _zx.argv._[0];
26
+ } else {
27
+ TARGET = _zx.argv.target;
28
+ if (_zx.argv.channelName) {
29
+ CHANNEL = {};
30
+ CHANNEL.name = _zx.argv.channelName;
31
+ CHANNEL.expiration = _zx.argv.channelExpiration ?? '7d';
32
+ }
33
+ }
34
+ const INTERACTIVE_MODE = !TARGET;
17
35
  let exited = false;
18
36
  const SECRETS_DIR = 'secrets';
19
37
  const EXIT_EVENTS = ['SIGINT', 'exit', 'uncaughException', 'unhandledRejection'];
20
- const [firebaserc, firebaseJson] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), '.firebaserc')), _zx.fs.exists(_zx.path.join(process.cwd(), 'firebase.json'))]);
38
+ const [firebaserc, firebaseJson, git] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), '.firebaserc')), _zx.fs.exists(_zx.path.join(process.cwd(), 'firebase.json')), _zx.fs.exists(_zx.path.join(process.cwd(), '.git/HEAD'))]);
21
39
  if (!firebaserc) {
22
40
  console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
23
41
  }
@@ -30,31 +48,87 @@ if (!firebaserc || !firebaseJson) {
30
48
  const firebasercContent = JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
31
49
  encoding: 'utf-8'
32
50
  }));
33
- const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
34
- Object.entries(config.hosting).forEach(([siteId, aliases]) => {
51
+
52
+ // If in a git repo, fetch the name of the git branch to use as a default
53
+ // name in case a deployment channel is needed
54
+ let gitBranch;
55
+ if (git) {
56
+ const gitContent = (await _zx.fs.readFile(_zx.path.join(process.cwd(), '.git/HEAD'), {
57
+ encoding: 'utf-8'
58
+ }))?.split('/');
59
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd('\n');
60
+ }
61
+ let maxPadding = 0;
62
+ const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config, production]) => {
63
+ Object.entries(config.hosting).forEach(([siteId, aliases, production]) => {
35
64
  aliases.forEach(alias => {
65
+ maxPadding = Math.max(siteId.length, maxPadding);
36
66
  prev[alias] = {
37
67
  projectId,
38
68
  siteId,
39
- alias
69
+ alias,
70
+ production: config.production
40
71
  };
41
72
  });
42
73
  });
43
74
  return prev;
44
75
  }, {});
45
- if (!TARGET) {
76
+ if (INTERACTIVE_MODE) {
46
77
  const {
47
78
  targetEnv
48
- } = await prompt({
49
- type: 'autocomplete',
79
+ } = await prompt([{
80
+ type: 'select',
50
81
  name: 'targetEnv',
51
82
  message: 'To which environment would you like to deploy?',
52
- choices: Object.keys(targets).map(t => ({
53
- name: t,
54
- value: t
55
- }))
56
- });
83
+ choices: Object.values(targets).map(t => {
84
+ const productionLabel = t.production ? _ansiColors.default.bold.red('--PRODUCTION--') : '';
85
+ return {
86
+ message: `${_ansiColors.default.bold.cyan('Site: ')}${t.siteId.padEnd(maxPadding)} ${_ansiColors.default.bold.green('Project: ')}${t.projectId} ${productionLabel}`,
87
+ value: t.alias
88
+ };
89
+ })
90
+ }]);
57
91
  TARGET = targets[targetEnv];
92
+ if (TARGET.production) {
93
+ const {
94
+ confirmProductionDeploy
95
+ } = await prompt([{
96
+ type: 'confirm',
97
+ name: 'confirmProductionDeploy',
98
+ message: `${_ansiColors.default.bold.red('WARNING: ')} This is a ${_ansiColors.default.bold.yellow('PRODUCTION')} environment. Are you sure you want to deploy here?`
99
+ }]);
100
+ if (!confirmProductionDeploy) {
101
+ console.log('Cancelling deployment...');
102
+ process.exit();
103
+ }
104
+ }
105
+ const {
106
+ useChannel
107
+ } = await prompt([{
108
+ type: 'confirm',
109
+ name: 'useChannel',
110
+ message: 'Do you want to deploy to a temporary preview channel?'
111
+ }]);
112
+ if (useChannel) {
113
+ const {
114
+ channelName,
115
+ channelExpiration
116
+ } = await prompt([{
117
+ type: 'input',
118
+ name: 'channelName',
119
+ message: 'To which channel would you like to deploy?',
120
+ initial: gitBranch ?? 'temp-deploy'
121
+ }, {
122
+ type: 'input',
123
+ name: 'channelExpiration',
124
+ message: 'When should the channel expire?',
125
+ initial: '7d'
126
+ }]);
127
+ CHANNEL = {
128
+ name: channelName,
129
+ expiration: channelExpiration
130
+ };
131
+ }
58
132
  } else {
59
133
  TARGET = targets[TARGET];
60
134
  }
@@ -82,6 +156,26 @@ if (aliasFileExists) {
82
156
  encoding: 'utf-8'
83
157
  }));
84
158
  }
159
+ if (INTERACTIVE_MODE) {
160
+ console.log(`\n\n\n${_ansiColors.default.bold.green('====== Deployment Summary ======')}`);
161
+ console.log(`${_ansiColors.default.blue('Firebase project:')} ${TARGET.projectId} ${TARGET.production ? _ansiColors.default.yellow('--PRODUCTION--') : ''}`);
162
+ console.log(`${_ansiColors.default.blue('Site ID:')} ${TARGET.siteId}`);
163
+ console.log(`${_ansiColors.default.blue('Channel Name:')} ${CHANNEL?.name ?? _ansiColors.default.italic.gray('(none)')}`);
164
+ console.log(`${_ansiColors.default.blue('Channel Expiration date:')} ${CHANNEL?.name ? CHANNEL.expiration ?? _ansiColors.default.italic.gray('(none)') : _ansiColors.default.italic.gray('(n/a)')}`);
165
+ console.log(`${_ansiColors.default.blue('Shared .env file:')} ${sharedFileExists ? `${SECRETS_DIR}/shared.env` : _ansiColors.default.italic.gray('(none)')}`);
166
+ console.log(`${_ansiColors.default.blue('Additional .env overrides:')} ${aliasFileExists ? `${SECRETS_DIR}/${TARGET.alias}.env` : `${SECRETS_DIR}/${TARGET.siteId}.env`}`);
167
+ const {
168
+ confirmSelections
169
+ } = await prompt([{
170
+ type: 'confirm',
171
+ name: 'confirmSelections',
172
+ message: 'Proceed with this deployment?'
173
+ }]);
174
+ if (!confirmSelections) {
175
+ console.log('Cancelling deployment...');
176
+ process.exit();
177
+ }
178
+ }
85
179
  const envFileContent = (await Promise.all(exec)).join('\n');
86
180
  EXIT_EVENTS.forEach(event => {
87
181
  process.on(event, () => {
@@ -93,7 +187,8 @@ EXIT_EVENTS.forEach(event => {
93
187
  });
94
188
  await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
95
189
  try {
96
- await (0, _zx.$)`npm run clean && DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} npm run build && firebase use ${TARGET.projectId} && firebase deploy --only hosting:${TARGET.siteId}`;
190
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}` : 'deploy';
191
+ await (0, _zx.$)`npm run clean && DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} npm run build && firebase use ${TARGET.projectId} && firebase ${deployType} --only hosting:${TARGET.siteId}`;
97
192
  } catch (err) {
98
193
  console.error(err);
99
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.21.8",
3
+ "version": "2.21.9-nick.2",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -49,6 +49,7 @@
49
49
  "author": "Leverege Devs",
50
50
  "license": "SEE LICENSE IN LICENSE.md",
51
51
  "dependencies": {
52
+ "ansi-colors": "^4.1.3",
52
53
  "chalk": "^4.1.2",
53
54
  "command-line-args": "^5.2.1",
54
55
  "command-line-usage": "^6.1.3",
@@ -0,0 +1,13 @@
1
+ IMAGINE_HOST=https://leverege-sandbox-imagine-api.leverege.com
2
+ RECOVR_HOST=https://dev-recovr-notify.leverege.com
3
+ # RECOVR_HOST=http://localhost:8296
4
+ SYSTEM_ID=0UTmPFvRHXYLMlT4Kl2z1K
5
+ PROJECT_ID=08HBeHEYGxu5Bg4TCCnAAV
6
+ VEHICLE_NETWORK_ID='dev-dealership-vehicles'
7
+ CONSUMER_VEHICLE_NETWORK_ID='dev-customer-vehicles'
8
+ LOCATOR_NETWORK_ID='dev-recovr-tracker'
9
+ BEACON_NETWORK_ID='dev-dealership-beacons'
10
+ SALE_LOCATION_PARENT_PATH=sales
11
+ CONSUMER_LOCATION_ID=1KceIcZlVWrOBvCzsEXYud
12
+ userManagementUiName='obd'
13
+ IS_DEV=true
@@ -0,0 +1,12 @@
1
+ IMAGINE_HOST=https://leverege-sandbox-imagine-api.leverege.com
2
+ RECOVR_HOST=https://dev-recovr-notify.leverege.com
3
+ # RECOVR_HOST=http://localhost:8296
4
+ SYSTEM_ID=0UTmPFvRHXYLMlT4Kl2z1K
5
+ PROJECT_ID=08HBeHEYGxu5Bg4TCCnAAV
6
+ VEHICLE_NETWORK_ID='dev-dealership-vehicles'
7
+ CONSUMER_VEHICLE_NETWORK_ID='dev-customer-vehicles'
8
+ LOCATOR_NETWORK_ID='dev-recovr-tracker'
9
+ BEACON_NETWORK_ID='dev-dealership-beacons'
10
+ SALE_LOCATION_PARENT_PATH=sales
11
+ CONSUMER_LOCATION_ID=1KceIcZlVWrOBvCzsEXYud
12
+ IS_DEV=true
@@ -0,0 +1,12 @@
1
+ IMAGINE_HOST=https://prd-recovr-auto-imagine-api.leverege.com
2
+ RECOVR_HOST=https://recovr-auto-notify.leverege.com
3
+ SYSTEM_ID=2ExK7rl76meCVCCctjOJEY
4
+ PROJECT_ID=1Je8Y7dNlfthv7ixV8oEfZ
5
+ VEHICLE_NETWORK_ID='prd-auto-vehicles'
6
+ CONSUMER_VEHICLE_NETWORK_ID='prd-customer-vehicles'
7
+ BEACON_NETWORK_ID='prd-auto-beacons'
8
+ GOOGLE_ANALYTICS_TRACKER_ID='G-LBTT0JT9V6'
9
+ SALE_LOCATION_PARENT_PATH=location
10
+ CONSUMER_LOCATION_ID=26OsDlsRqBTIOxMjyHg9S5
11
+ userManagementUiName='obd'
12
+ IS_DEV=true
@@ -0,0 +1,10 @@
1
+ IMAGINE_HOST=https://prd-recovr-auto-imagine-api.leverege.com
2
+ RECOVR_HOST=https://recovr-auto-notify.leverege.com
3
+ SYSTEM_ID=2ExK7rl76meCVCCctjOJEY
4
+ PROJECT_ID=1Je8Y7dNlfthv7ixV8oEfZ
5
+ VEHICLE_NETWORK_ID='prd-auto-vehicles'
6
+ CONSUMER_VEHICLE_NETWORK_ID='prd-customer-vehicles'
7
+ BEACON_NETWORK_ID='prd-auto-beacons'
8
+ GOOGLE_ANALYTICS_TRACKER_ID='G-LBTT0JT9V6'
9
+ SALE_LOCATION_PARENT_PATH=location
10
+ CONSUMER_LOCATION_ID=26OsDlsRqBTIOxMjyHg9S5
@@ -0,0 +1,6 @@
1
+ THEFT_RECOVERY_URL='theft.recovermycar.com'
2
+ MAPBOX_APIKEY=pk.eyJ1IjoibGV2ZXJlZ2UiLCJhIjoiY2p1Y3E0cWpkMG15azQzbnFzaDY2dmhiMSJ9.7N25h5jsjO2Aq_YmiLrAJw
3
+ APPEARANCE_PROJECT_ID=7b2GTbmsZj3f5VdFpjGM8O
4
+ INTERCOM_APP_ID='ec0qbuwi'
5
+ LOCATOR_ALIAS_KEY='serialNumber'
6
+ RECOVR_BILLING_MODULE='recovrBilling'
@@ -0,0 +1,10 @@
1
+ IMAGINE_HOST=https://stg-recovr-auto-imagine-api.leverege.com
2
+ # RECOVR_HOST=http://localhost:8296
3
+ SYSTEM_ID=6CxONnaSOnbGFBS5gLnZWl
4
+ PROJECT_ID=3vo2u8m9GEXPaQgMnJ45rh
5
+ VEHICLE_NETWORK_ID='stg-auto-vehicles'
6
+ CONSUMER_VEHICLE_NETWORK_ID='stg-customer-vehicles'
7
+ BEACON_NETWORK_ID='stg-auto-beacons'
8
+ CONSUMER_LOCATION_ID=1SRDkIuPWDpiAy4dsuy0JT
9
+ userManagementUiName='obd'
10
+ IS_DEV=false
@@ -0,0 +1,9 @@
1
+ IMAGINE_HOST=https://stg-recovr-auto-imagine-api.leverege.com
2
+ # RECOVR_HOST=http://localhost:8296
3
+ SYSTEM_ID=6CxONnaSOnbGFBS5gLnZWl
4
+ PROJECT_ID=3vo2u8m9GEXPaQgMnJ45rh
5
+ VEHICLE_NETWORK_ID='stg-auto-vehicles'
6
+ CONSUMER_VEHICLE_NETWORK_ID='stg-customer-vehicles'
7
+ BEACON_NETWORK_ID='stg-auto-beacons'
8
+ CONSUMER_LOCATION_ID=1SRDkIuPWDpiAy4dsuy0JT
9
+ IS_DEV=false
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console */
2
3
  // https://github.com/google/zx
3
4
  /* eslint-disable max-len */
4
5
  import pkg from 'enquirer'
6
+ import colors from 'ansi-colors'
5
7
  import { $, fs, argv, path, chalk } from 'zx'
6
8
 
7
9
  const { prompt } = pkg
@@ -10,7 +12,26 @@ const { prompt } = pkg
10
12
  process.env.FORCE_COLORS = 3
11
13
  process.env.FORCE_COLOR = '1'
12
14
 
13
- let TARGET = argv._[0]
15
+ let TARGET
16
+ let CHANNEL
17
+
18
+ // A single unnamed parameter is assumed to be a deploy target.
19
+ // If named parameters exist, they may not be stored in argv,
20
+ // so don't use the length of argv to decide whether to look for them
21
+ if ( argv._.length === 1 ) {
22
+ TARGET = argv._[0]
23
+ } else {
24
+ TARGET = argv.target
25
+
26
+ if ( argv.channelName ) {
27
+ CHANNEL = {}
28
+ CHANNEL.name = argv.channelName
29
+ CHANNEL.expiration = argv.channelExpiration ?? '7d'
30
+ }
31
+ }
32
+
33
+ const INTERACTIVE_MODE = !TARGET
34
+
14
35
  let exited = false
15
36
 
16
37
  const SECRETS_DIR = 'secrets'
@@ -18,10 +39,12 @@ const EXIT_EVENTS = [ 'SIGINT', 'exit', 'uncaughException', 'unhandledRejection'
18
39
 
19
40
  const [
20
41
  firebaserc,
21
- firebaseJson
42
+ firebaseJson,
43
+ git
22
44
  ] = await Promise.all( [
23
45
  fs.exists( path.join( process.cwd(), '.firebaserc' ) ),
24
- fs.exists( path.join( process.cwd(), 'firebase.json' ) )
46
+ fs.exists( path.join( process.cwd(), 'firebase.json' ) ),
47
+ fs.exists( path.join( process.cwd(), '.git/HEAD' ) )
25
48
  ] )
26
49
 
27
50
  if ( !firebaserc ) {
@@ -38,17 +61,29 @@ if ( !firebaserc || !firebaseJson ) {
38
61
 
39
62
  const firebasercContent = JSON.parse( await fs.readFile( path.join( process.cwd(), '.firebaserc' ), { encoding : 'utf-8' } ) )
40
63
 
64
+ // If in a git repo, fetch the name of the git branch to use as a default
65
+ // name in case a deployment channel is needed
66
+ let gitBranch
67
+ if ( git ) {
68
+ const gitContent = ( await fs.readFile( path.join( process.cwd(), '.git/HEAD' ), { encoding : 'utf-8' } ) )?.split( '/' )
69
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd( '\n' )
70
+ }
71
+
72
+ let maxPadding = 0
73
+
41
74
  const targets = Object
42
75
  .entries( firebasercContent.targets )
43
- .reduce( ( prev, [ projectId, config ] ) => {
76
+ .reduce( ( prev, [ projectId, config, production ] ) => {
44
77
  Object
45
78
  .entries( config.hosting )
46
- .forEach( ( [ siteId, aliases ] ) => {
79
+ .forEach( ( [ siteId, aliases, production ] ) => {
47
80
  aliases.forEach( ( alias ) => {
81
+ maxPadding = Math.max( siteId.length, maxPadding )
48
82
  prev[alias] = {
49
83
  projectId,
50
84
  siteId,
51
- alias
85
+ alias,
86
+ production : config.production
52
87
  }
53
88
  } )
54
89
  } )
@@ -56,15 +91,53 @@ const targets = Object
56
91
  return prev
57
92
  }, {} )
58
93
 
59
- if ( !TARGET ) {
60
- const { targetEnv } = await prompt( {
61
- type : 'autocomplete',
94
+ if ( INTERACTIVE_MODE ) {
95
+ const { targetEnv } = await prompt( [ {
96
+ type : 'select',
62
97
  name : 'targetEnv',
63
98
  message : 'To which environment would you like to deploy?',
64
- choices : Object.keys( targets ).map( t => ( { name : t, value : t } ) )
65
- } )
99
+ choices : Object.values( targets ).map( ( t ) => {
100
+ const productionLabel = t.production ? colors.bold.red( '--PRODUCTION--' ) : ''
101
+ return { message : `${colors.bold.cyan( 'Site: ' )}${t.siteId.padEnd( maxPadding )} ${colors.bold.green( 'Project: ' )}${t.projectId} ${productionLabel}`, value : t.alias }
102
+ } )
103
+ } ] )
66
104
 
67
105
  TARGET = targets[targetEnv]
106
+
107
+ if ( TARGET.production ) {
108
+ const { confirmProductionDeploy } = await prompt( [ {
109
+ type : 'confirm',
110
+ name : 'confirmProductionDeploy',
111
+ message : `${colors.bold.red( 'WARNING: ' )} This is a ${colors.bold.yellow( 'PRODUCTION' )} environment. Are you sure you want to deploy here?`
112
+ } ] )
113
+ if ( !confirmProductionDeploy ) {
114
+ console.log( 'Cancelling deployment...' )
115
+ process.exit()
116
+ }
117
+ }
118
+
119
+ const { useChannel } = await prompt( [ {
120
+ type : 'confirm',
121
+ name : 'useChannel',
122
+ message : 'Do you want to deploy to a temporary preview channel?'
123
+ } ] )
124
+
125
+ if ( useChannel ) {
126
+ const { channelName, channelExpiration } = await prompt( [ {
127
+ type : 'input',
128
+ name : 'channelName',
129
+ message : 'To which channel would you like to deploy?',
130
+ initial : gitBranch ?? 'temp-deploy'
131
+ }, {
132
+ type : 'input',
133
+ name : 'channelExpiration',
134
+ message : 'When should the channel expire?',
135
+ initial : '7d'
136
+ } ] )
137
+
138
+ CHANNEL = { name : channelName, expiration : channelExpiration }
139
+ }
140
+
68
141
  } else {
69
142
  TARGET = targets[TARGET]
70
143
  }
@@ -97,6 +170,26 @@ if ( aliasFileExists ) {
97
170
  exec.push( fs.readFile( path.join( process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env` ), { encoding : 'utf-8' } ) )
98
171
  }
99
172
 
173
+ if ( INTERACTIVE_MODE ) {
174
+ console.log( `\n\n\n${colors.bold.green( '====== Deployment Summary ======' )}` )
175
+ console.log( `${colors.blue( 'Firebase project:' )} ${TARGET.projectId} ${TARGET.production ? colors.yellow( '--PRODUCTION--' ) : ''}` )
176
+ console.log( `${colors.blue( 'Site ID:' )} ${TARGET.siteId}` )
177
+ console.log( `${colors.blue( 'Channel Name:' )} ${CHANNEL?.name ?? colors.italic.gray( '(none)' )}` )
178
+ console.log( `${colors.blue( 'Channel Expiration date:' )} ${CHANNEL?.name ? CHANNEL.expiration ?? colors.italic.gray( '(none)' ) : colors.italic.gray( '(n/a)' )}` )
179
+ console.log( `${colors.blue( 'Shared .env file:' )} ${sharedFileExists ? `${SECRETS_DIR}/shared.env` : colors.italic.gray( '(none)' )}` )
180
+ console.log( `${colors.blue( 'Additional .env overrides:' )} ${aliasFileExists ? `${SECRETS_DIR}/${TARGET.alias}.env` : `${SECRETS_DIR}/${TARGET.siteId}.env`}` )
181
+
182
+ const { confirmSelections } = await prompt( [ {
183
+ type : 'confirm',
184
+ name : 'confirmSelections',
185
+ message : 'Proceed with this deployment?'
186
+ } ] )
187
+
188
+ if ( !confirmSelections ) {
189
+ console.log( 'Cancelling deployment...' )
190
+ process.exit()
191
+ }
192
+ }
100
193
  const envFileContent = ( await Promise.all( exec ) ).join( '\n' )
101
194
 
102
195
  EXIT_EVENTS
@@ -112,7 +205,8 @@ EXIT_EVENTS
112
205
  await fs.writeFile( path.join( process.cwd(), '.env.temp' ), envFileContent )
113
206
 
114
207
  try {
115
- await $`npm run clean && DOTENV_CONFIG_PATH=${path.join( process.cwd(), '.env.temp' )} npm run build && firebase use ${TARGET.projectId} && firebase deploy --only hosting:${TARGET.siteId}`
208
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}` : 'deploy'
209
+ await $`npm run clean && DOTENV_CONFIG_PATH=${path.join( process.cwd(), '.env.temp' )} npm run build && firebase use ${TARGET.projectId} && firebase ${deployType} --only hosting:${TARGET.siteId}`
116
210
  } catch ( err ) {
117
211
  console.error( err )
118
212
  }