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

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/.firebaserc 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-ui": [
28
+ "prd-indoor-zoning-ui"
29
+ ],
30
+ "prd-olive-indoor-zoning-ui": [
31
+ "prd-olive-indoor-zoning-ui"
32
+ ],
33
+ "prd-keys": [
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,26 @@ 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
+ }
17
34
  let exited = false;
18
35
  const SECRETS_DIR = 'secrets';
19
36
  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'))]);
37
+ 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
38
  if (!firebaserc) {
22
39
  console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
23
40
  }
@@ -30,13 +47,26 @@ if (!firebaserc || !firebaseJson) {
30
47
  const firebasercContent = JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
31
48
  encoding: 'utf-8'
32
49
  }));
33
- const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
34
- Object.entries(config.hosting).forEach(([siteId, aliases]) => {
50
+
51
+ // If in a git repo, fetch the name of the git branch to use as a default
52
+ // name in case a deployment channel is needed
53
+ let gitBranch;
54
+ if (git) {
55
+ const gitContent = (await _zx.fs.readFile(_zx.path.join(process.cwd(), '.git/HEAD'), {
56
+ encoding: 'utf-8'
57
+ }))?.split('/');
58
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd('\n');
59
+ }
60
+ let maxPadding = 0;
61
+ const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config, production]) => {
62
+ Object.entries(config.hosting).forEach(([siteId, aliases, production]) => {
35
63
  aliases.forEach(alias => {
64
+ maxPadding = Math.max(siteId.length, maxPadding);
36
65
  prev[alias] = {
37
66
  projectId,
38
67
  siteId,
39
- alias
68
+ alias,
69
+ production: config.production
40
70
  };
41
71
  });
42
72
  });
@@ -45,16 +75,59 @@ const targets = Object.entries(firebasercContent.targets).reduce((prev, [project
45
75
  if (!TARGET) {
46
76
  const {
47
77
  targetEnv
48
- } = await prompt({
49
- type: 'autocomplete',
78
+ } = await prompt([{
79
+ type: 'select',
50
80
  name: 'targetEnv',
51
81
  message: 'To which environment would you like to deploy?',
52
- choices: Object.keys(targets).map(t => ({
53
- name: t,
54
- value: t
55
- }))
56
- });
82
+ choices: Object.values(targets).map(t => {
83
+ const productionLabel = t.production ? _ansiColors.default.bold.red('--PRODUCTION--') : '';
84
+ return {
85
+ message: `${_ansiColors.default.bold.cyan('Site: ')}${t.siteId.padEnd(maxPadding)} ${_ansiColors.default.bold.green('Project: ')}${t.projectId} ${productionLabel}`,
86
+ value: t.siteId
87
+ };
88
+ })
89
+ }]);
57
90
  TARGET = targets[targetEnv];
91
+ if (TARGET.production) {
92
+ const {
93
+ confirmProductionDeploy
94
+ } = await prompt([{
95
+ type: 'confirm',
96
+ name: 'confirmProductionDeploy',
97
+ message: `${_ansiColors.default.bold.red('WARNING: ')} This is a ${_ansiColors.default.bold.yellow('PRODUCTION')} deployment target. Are you sure you want to proceed?`
98
+ }]);
99
+ if (!confirmProductionDeploy) {
100
+ console.log('Cancelling deployment...');
101
+ process.exit();
102
+ }
103
+ }
104
+ const {
105
+ useChannel
106
+ } = await prompt([{
107
+ type: 'confirm',
108
+ name: 'useChannel',
109
+ message: 'Do you want to deploy to a temporary preview channel?'
110
+ }]);
111
+ if (useChannel) {
112
+ const {
113
+ channelName,
114
+ channelExpiration
115
+ } = await prompt([{
116
+ type: 'input',
117
+ name: 'channelName',
118
+ message: 'To which channel would you like to deploy?',
119
+ initial: gitBranch ?? 'temp-deploy'
120
+ }, {
121
+ type: 'input',
122
+ name: 'channelExpiration',
123
+ message: 'When should the channel expire?',
124
+ initial: '7d'
125
+ }]);
126
+ CHANNEL = {
127
+ name: channelName,
128
+ expiration: channelExpiration
129
+ };
130
+ }
58
131
  } else {
59
132
  TARGET = targets[TARGET];
60
133
  }
@@ -93,7 +166,8 @@ EXIT_EVENTS.forEach(event => {
93
166
  });
94
167
  await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
95
168
  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}`;
169
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} --expires ${CHANNEL.expiration}` : 'deploy';
170
+ 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
171
  } catch (err) {
98
172
  console.error(err);
99
173
  }
@@ -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,26 @@ 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
+ }
17
34
  let exited = false;
18
35
  const SECRETS_DIR = 'secrets';
19
36
  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'))]);
37
+ 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
38
  if (!firebaserc) {
22
39
  console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
23
40
  }
@@ -30,13 +47,26 @@ if (!firebaserc || !firebaseJson) {
30
47
  const firebasercContent = JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
31
48
  encoding: 'utf-8'
32
49
  }));
33
- const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
34
- Object.entries(config.hosting).forEach(([siteId, aliases]) => {
50
+
51
+ // If in a git repo, fetch the name of the git branch to use as a default
52
+ // name in case a deployment channel is needed
53
+ let gitBranch;
54
+ if (git) {
55
+ const gitContent = (await _zx.fs.readFile(_zx.path.join(process.cwd(), '.git/HEAD'), {
56
+ encoding: 'utf-8'
57
+ }))?.split('/');
58
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd('\n');
59
+ }
60
+ let maxPadding = 0;
61
+ const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config, production]) => {
62
+ Object.entries(config.hosting).forEach(([siteId, aliases, production]) => {
35
63
  aliases.forEach(alias => {
64
+ maxPadding = Math.max(siteId.length, maxPadding);
36
65
  prev[alias] = {
37
66
  projectId,
38
67
  siteId,
39
- alias
68
+ alias,
69
+ production: config.production
40
70
  };
41
71
  });
42
72
  });
@@ -45,16 +75,59 @@ const targets = Object.entries(firebasercContent.targets).reduce((prev, [project
45
75
  if (!TARGET) {
46
76
  const {
47
77
  targetEnv
48
- } = await prompt({
49
- type: 'autocomplete',
78
+ } = await prompt([{
79
+ type: 'select',
50
80
  name: 'targetEnv',
51
81
  message: 'To which environment would you like to deploy?',
52
- choices: Object.keys(targets).map(t => ({
53
- name: t,
54
- value: t
55
- }))
56
- });
82
+ choices: Object.values(targets).map(t => {
83
+ const productionLabel = t.production ? _ansiColors.default.bold.red('--PRODUCTION--') : '';
84
+ return {
85
+ message: `${_ansiColors.default.bold.cyan('Site: ')}${t.siteId.padEnd(maxPadding)} ${_ansiColors.default.bold.green('Project: ')}${t.projectId} ${productionLabel}`,
86
+ value: t.siteId
87
+ };
88
+ })
89
+ }]);
57
90
  TARGET = targets[targetEnv];
91
+ if (TARGET.production) {
92
+ const {
93
+ confirmProductionDeploy
94
+ } = await prompt([{
95
+ type: 'confirm',
96
+ name: 'confirmProductionDeploy',
97
+ message: `${_ansiColors.default.bold.red('WARNING: ')} This is a ${_ansiColors.default.bold.yellow('PRODUCTION')} deployment target. Are you sure you want to proceed?`
98
+ }]);
99
+ if (!confirmProductionDeploy) {
100
+ console.log('Cancelling deployment...');
101
+ process.exit();
102
+ }
103
+ }
104
+ const {
105
+ useChannel
106
+ } = await prompt([{
107
+ type: 'confirm',
108
+ name: 'useChannel',
109
+ message: 'Do you want to deploy to a temporary preview channel?'
110
+ }]);
111
+ if (useChannel) {
112
+ const {
113
+ channelName,
114
+ channelExpiration
115
+ } = await prompt([{
116
+ type: 'input',
117
+ name: 'channelName',
118
+ message: 'To which channel would you like to deploy?',
119
+ initial: gitBranch ?? 'temp-deploy'
120
+ }, {
121
+ type: 'input',
122
+ name: 'channelExpiration',
123
+ message: 'When should the channel expire?',
124
+ initial: '7d'
125
+ }]);
126
+ CHANNEL = {
127
+ name: channelName,
128
+ expiration: channelExpiration
129
+ };
130
+ }
58
131
  } else {
59
132
  TARGET = targets[TARGET];
60
133
  }
@@ -93,7 +166,8 @@ EXIT_EVENTS.forEach(event => {
93
166
  });
94
167
  await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
95
168
  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}`;
169
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} --expires ${CHANNEL.expiration}` : 'deploy';
170
+ 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
171
  } catch (err) {
98
172
  console.error(err);
99
173
  }
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.1",
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",
@@ -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,24 @@ 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
+
14
33
  let exited = false
15
34
 
16
35
  const SECRETS_DIR = 'secrets'
@@ -18,10 +37,12 @@ const EXIT_EVENTS = [ 'SIGINT', 'exit', 'uncaughException', 'unhandledRejection'
18
37
 
19
38
  const [
20
39
  firebaserc,
21
- firebaseJson
40
+ firebaseJson,
41
+ git
22
42
  ] = await Promise.all( [
23
43
  fs.exists( path.join( process.cwd(), '.firebaserc' ) ),
24
- fs.exists( path.join( process.cwd(), 'firebase.json' ) )
44
+ fs.exists( path.join( process.cwd(), 'firebase.json' ) ),
45
+ fs.exists( path.join( process.cwd(), '.git/HEAD' ) )
25
46
  ] )
26
47
 
27
48
  if ( !firebaserc ) {
@@ -38,17 +59,29 @@ if ( !firebaserc || !firebaseJson ) {
38
59
 
39
60
  const firebasercContent = JSON.parse( await fs.readFile( path.join( process.cwd(), '.firebaserc' ), { encoding : 'utf-8' } ) )
40
61
 
62
+ // If in a git repo, fetch the name of the git branch to use as a default
63
+ // name in case a deployment channel is needed
64
+ let gitBranch
65
+ if ( git ) {
66
+ const gitContent = ( await fs.readFile( path.join( process.cwd(), '.git/HEAD' ), { encoding : 'utf-8' } ) )?.split( '/' )
67
+ gitBranch = gitContent[gitContent?.length - 1]?.trimEnd( '\n' )
68
+ }
69
+
70
+ let maxPadding = 0
71
+
41
72
  const targets = Object
42
73
  .entries( firebasercContent.targets )
43
- .reduce( ( prev, [ projectId, config ] ) => {
74
+ .reduce( ( prev, [ projectId, config, production ] ) => {
44
75
  Object
45
76
  .entries( config.hosting )
46
- .forEach( ( [ siteId, aliases ] ) => {
77
+ .forEach( ( [ siteId, aliases, production ] ) => {
47
78
  aliases.forEach( ( alias ) => {
79
+ maxPadding = Math.max( siteId.length, maxPadding )
48
80
  prev[alias] = {
49
81
  projectId,
50
82
  siteId,
51
- alias
83
+ alias,
84
+ production : config.production
52
85
  }
53
86
  } )
54
87
  } )
@@ -57,14 +90,52 @@ const targets = Object
57
90
  }, {} )
58
91
 
59
92
  if ( !TARGET ) {
60
- const { targetEnv } = await prompt( {
61
- type : 'autocomplete',
93
+ const { targetEnv } = await prompt( [ {
94
+ type : 'select',
62
95
  name : 'targetEnv',
63
96
  message : 'To which environment would you like to deploy?',
64
- choices : Object.keys( targets ).map( t => ( { name : t, value : t } ) )
65
- } )
97
+ choices : Object.values( targets ).map( ( t ) => {
98
+ const productionLabel = t.production ? colors.bold.red( '--PRODUCTION--' ) : ''
99
+ return { message : `${colors.bold.cyan( 'Site: ' )}${t.siteId.padEnd( maxPadding )} ${colors.bold.green( 'Project: ' )}${t.projectId} ${productionLabel}`, value : t.siteId }
100
+ } )
101
+ } ] )
66
102
 
67
103
  TARGET = targets[targetEnv]
104
+
105
+ if ( TARGET.production ) {
106
+ const { confirmProductionDeploy } = await prompt( [ {
107
+ type : 'confirm',
108
+ name : 'confirmProductionDeploy',
109
+ message : `${colors.bold.red( 'WARNING: ' )} This is a ${colors.bold.yellow( 'PRODUCTION' )} deployment target. Are you sure you want to proceed?`
110
+ } ] )
111
+ if ( !confirmProductionDeploy ) {
112
+ console.log( 'Cancelling deployment...' )
113
+ process.exit()
114
+ }
115
+ }
116
+
117
+ const { useChannel } = await prompt( [ {
118
+ type : 'confirm',
119
+ name : 'useChannel',
120
+ message : 'Do you want to deploy to a temporary preview channel?'
121
+ } ] )
122
+
123
+ if ( useChannel ) {
124
+ const { channelName, channelExpiration } = await prompt( [ {
125
+ type : 'input',
126
+ name : 'channelName',
127
+ message : 'To which channel would you like to deploy?',
128
+ initial : gitBranch ?? 'temp-deploy'
129
+ }, {
130
+ type : 'input',
131
+ name : 'channelExpiration',
132
+ message : 'When should the channel expire?',
133
+ initial : '7d'
134
+ } ] )
135
+
136
+ CHANNEL = { name : channelName, expiration : channelExpiration }
137
+ }
138
+
68
139
  } else {
69
140
  TARGET = targets[TARGET]
70
141
  }
@@ -112,7 +183,8 @@ EXIT_EVENTS
112
183
  await fs.writeFile( path.join( process.cwd(), '.env.temp' ), envFileContent )
113
184
 
114
185
  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}`
186
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} --expires ${CHANNEL.expiration}` : 'deploy'
187
+ 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
188
  } catch ( err ) {
117
189
  console.error( err )
118
190
  }