@leverege/build-tools 2.96.0 → 2.96.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/package.json +8 -3
- package/src/Utils.mjs +1 -1
- package/src/gcp-snapshots.mjs +182 -0
- package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +1 -2
- package/leverege-build-tools-2.21.13.tgz +0 -0
- package/lib/server/build-tools.js +0 -138
- package/lib/server/firebaseDeploy.js +0 -195
- package/lib/server/firebaseServe.js +0 -116
- package/lib/server/getfbcfg.js +0 -25
- package/lib/server/getjson.js +0 -70
- package/lib/server/overwhelm.js +0 -447
- package/lib/server/push-my-chart.js +0 -18
- package/lib/server/refresh-npm-token.js +0 -135
- package/lib/server/tag-release.js +0 -681
- package/lib/server/unleash.js +0 -50
- package/lib/web/build-tools.js +0 -138
- package/lib/web/firebaseDeploy.js +0 -195
- package/lib/web/firebaseServe.js +0 -116
- package/lib/web/getfbcfg.js +0 -25
- package/lib/web/getjson.js +0 -70
- package/lib/web/overwhelm.js +0 -447
- package/lib/web/push-my-chart.js +0 -18
- package/lib/web/refresh-npm-token.js +0 -135
- package/lib/web/tag-release.js +0 -681
- package/lib/web/unleash.js +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.96.
|
|
3
|
+
"version": "2.96.2",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"yarn-build": "src/yarn-build.mjs"
|
|
74
74
|
},
|
|
75
75
|
"author": "Leverege Devs",
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": "^22.11 || ^24.11",
|
|
78
|
+
"npm": ">=11.6"
|
|
79
|
+
},
|
|
76
80
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
77
81
|
"dependencies": {
|
|
78
82
|
"@leverege/jsdoc-template": "^1.0.1",
|
|
@@ -85,9 +89,10 @@
|
|
|
85
89
|
"execa": "^9.6.1",
|
|
86
90
|
"find-yarn-workspace-root": "^2.0.0",
|
|
87
91
|
"glob": "^13.0.0",
|
|
92
|
+
"googleapis": "^170.1.0",
|
|
88
93
|
"handlebars": "^4.7.8",
|
|
89
94
|
"ignore": "^7.0.5",
|
|
90
|
-
"inquirer": "^13.
|
|
95
|
+
"inquirer": "^13.2.0",
|
|
91
96
|
"js-yaml": "^4.1.1",
|
|
92
97
|
"jsdoc": "^4.0.5",
|
|
93
98
|
"ms": "^2.1.3",
|
|
@@ -106,7 +111,7 @@
|
|
|
106
111
|
"zx": "^8.8.5"
|
|
107
112
|
},
|
|
108
113
|
"devDependencies": {
|
|
109
|
-
"@leverege/eslint-config-leverege": "^5.1.
|
|
114
|
+
"@leverege/eslint-config-leverege": "^5.1.2",
|
|
110
115
|
"chai": "^6.2.2",
|
|
111
116
|
"mocha": "^11.7.5",
|
|
112
117
|
"npm": "^11.7.0"
|
package/src/Utils.mjs
CHANGED
|
@@ -581,7 +581,7 @@ export const getRepositoryDescr = async () => {
|
|
|
581
581
|
const gitRoot = await getGitRootDirectory()
|
|
582
582
|
const gitHooksPath = await getGitHooksPath()
|
|
583
583
|
const { branch : gitBranch, upstream : gitUpstream } = await getGitBranchAndUpstream()
|
|
584
|
-
const subPkgs = await glob( `${gitRoot}
|
|
584
|
+
const subPkgs = await glob( `${gitRoot}/{apps,packages}/*/**/package.json`, { ignore : '/**/node_modules/**' } )
|
|
585
585
|
const helmChart = await parseHelmChart()
|
|
586
586
|
const rootPackageJson = await parseJsonFile( `${gitRoot}/package.json` )
|
|
587
587
|
const rootPyProjectToml = await parsePyProjectToml( `${gitRoot}/pyproject.toml` )
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import { google } from 'googleapis'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
debug,
|
|
8
|
+
errorExit,
|
|
9
|
+
log,
|
|
10
|
+
err,
|
|
11
|
+
} from './Utils.mjs'
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name( 'gcp-snapshots' )
|
|
15
|
+
.description( 'Simple GCP wrapper to ease the pain of snapshot management' )
|
|
16
|
+
.requiredOption( '--project <id>', 'GCP project id (required)' )
|
|
17
|
+
.option( '--age <days>', 'Number of days (default: 180)', '180' )
|
|
18
|
+
.option( '--list', 'List snapshots older than --age days' )
|
|
19
|
+
.option( '--delete', 'Delete snapshots older than --age days' )
|
|
20
|
+
.parse()
|
|
21
|
+
|
|
22
|
+
const options = program.opts()
|
|
23
|
+
|
|
24
|
+
const parseAgeDays = ( value ) => {
|
|
25
|
+
const n = Number( value )
|
|
26
|
+
if ( !Number.isFinite( n ) || n <= 0 ) {
|
|
27
|
+
errorExit( `--age must be a positive number. Got: ${value}` )
|
|
28
|
+
}
|
|
29
|
+
return n
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cutoffDateFromAgeDays = ( days ) => {
|
|
33
|
+
return new Date( Date.now() - days * 24 * 60 * 60 * 1000 )
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const bytesToGb = ( bytes ) => {
|
|
37
|
+
if ( !Number.isFinite( bytes ) || bytes <= 0 ) return 0
|
|
38
|
+
return Math.round( ( bytes / ( 1024 ** 3 ) ) * 10 ) / 10 // 1 decimal GB
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const formatRow = ( cols, widths ) => {
|
|
42
|
+
return cols.map( ( c, i ) => {
|
|
43
|
+
const s = String( c ?? '' )
|
|
44
|
+
return s.length > widths[i] ?
|
|
45
|
+
`${s.slice( 0, widths[i] - 1 )}…` : s.padEnd( widths[i] )
|
|
46
|
+
} ).join( ' ' )
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const getComputeClient = async () => {
|
|
50
|
+
const auth = await google.auth.getClient( {
|
|
51
|
+
scopes : [ 'https://www.googleapis.com/auth/cloud-platform' ],
|
|
52
|
+
} )
|
|
53
|
+
|
|
54
|
+
return google.compute( { version : 'v1', auth } )
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fetchSnapshotsOlderThan = async ( { compute, projectId, cutoff } ) => {
|
|
58
|
+
const snapshots = []
|
|
59
|
+
let pageToken
|
|
60
|
+
|
|
61
|
+
debug( `Listing snapshots for project=${projectId}` )
|
|
62
|
+
debug( `Cutoff date=${cutoff.toISOString()}` )
|
|
63
|
+
|
|
64
|
+
do {
|
|
65
|
+
// eslint-disable-next-line no-await-in-loop
|
|
66
|
+
const res = await compute.snapshots.list( {
|
|
67
|
+
project : projectId,
|
|
68
|
+
maxResults : 500,
|
|
69
|
+
pageToken,
|
|
70
|
+
} )
|
|
71
|
+
|
|
72
|
+
const items = res.data.items ?? []
|
|
73
|
+
for ( const s of items ) {
|
|
74
|
+
if ( !s.creationTimestamp ) continue
|
|
75
|
+
const created = new Date( s.creationTimestamp )
|
|
76
|
+
if ( created < cutoff ) snapshots.push( s )
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pageToken = res.data.nextPageToken
|
|
80
|
+
} while ( pageToken )
|
|
81
|
+
|
|
82
|
+
// Oldest -> newest
|
|
83
|
+
snapshots.sort( ( a, b ) => (
|
|
84
|
+
new Date( a.creationTimestamp ) - new Date( b.creationTimestamp )
|
|
85
|
+
) )
|
|
86
|
+
|
|
87
|
+
return snapshots
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const printSnapshotTable = async ( { snapshots, ageDays, cutoff } ) => {
|
|
91
|
+
log(
|
|
92
|
+
`Snapshots older than ${ageDays} days ` +
|
|
93
|
+
`(cutoff ${cutoff.toISOString().slice( 0, 10 )}) — ${snapshots.length} found`
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const widths = [ 40, 20, 10, 8, 32 ]
|
|
97
|
+
log( formatRow(
|
|
98
|
+
[ 'NAME', 'CREATED', 'SIZE_GB', 'DISK_GB', 'SOURCE_DISK' ],
|
|
99
|
+
widths
|
|
100
|
+
) )
|
|
101
|
+
|
|
102
|
+
for ( const s of snapshots ) {
|
|
103
|
+
const sourceDisk = s.sourceDisk ? s.sourceDisk.split( '/' ).pop() : ''
|
|
104
|
+
|
|
105
|
+
log( formatRow(
|
|
106
|
+
[
|
|
107
|
+
s.name,
|
|
108
|
+
s.creationTimestamp.replace( 'T', ' ' ).replace( 'Z', '' ),
|
|
109
|
+
bytesToGb( Number( s.storageBytes ) ),
|
|
110
|
+
s.diskSizeGb ?? '',
|
|
111
|
+
sourceDisk,
|
|
112
|
+
],
|
|
113
|
+
widths
|
|
114
|
+
) )
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const deleteSnapshots = async ( { compute, projectId, snapshots } ) => {
|
|
119
|
+
if ( snapshots.length === 0 ) {
|
|
120
|
+
log( 'Nothing to delete.' )
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
log( `Deleting ${snapshots.length} snapshot(s)...` )
|
|
125
|
+
|
|
126
|
+
let ok = 0
|
|
127
|
+
let failed = 0
|
|
128
|
+
|
|
129
|
+
for ( const s of snapshots ) {
|
|
130
|
+
const name = s.name
|
|
131
|
+
if ( !name ) continue
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
log( `- deleting: ${name}` )
|
|
135
|
+
// eslint-disable-next-line no-await-in-loop
|
|
136
|
+
await compute.snapshots.delete( {
|
|
137
|
+
project : projectId,
|
|
138
|
+
snapshot : name,
|
|
139
|
+
} )
|
|
140
|
+
ok++
|
|
141
|
+
} catch ( e ) {
|
|
142
|
+
failed++
|
|
143
|
+
err( `! failed: ${name} — ${e?.message ?? e}` )
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
log( `Done. Deleted: ${ok}. Failed: ${failed}.` )
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const main = async () => {
|
|
151
|
+
if ( !options.list && !options.delete ) {
|
|
152
|
+
program.help( { error : false } )
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const ageDays = parseAgeDays( options.age )
|
|
157
|
+
const cutoff = cutoffDateFromAgeDays( ageDays )
|
|
158
|
+
|
|
159
|
+
const compute = await getComputeClient()
|
|
160
|
+
|
|
161
|
+
debug( `project=${options.project}` )
|
|
162
|
+
debug( `cutoff=${cutoff.toISOString()}` )
|
|
163
|
+
|
|
164
|
+
const snapshots = await fetchSnapshotsOlderThan( {
|
|
165
|
+
compute,
|
|
166
|
+
projectId : options.project,
|
|
167
|
+
cutoff,
|
|
168
|
+
} )
|
|
169
|
+
|
|
170
|
+
// Always show what we're about to act on.
|
|
171
|
+
await printSnapshotTable( { snapshots, ageDays, cutoff } )
|
|
172
|
+
|
|
173
|
+
if ( options.delete ) {
|
|
174
|
+
await deleteSnapshots( {
|
|
175
|
+
compute,
|
|
176
|
+
projectId : options.project,
|
|
177
|
+
snapshots,
|
|
178
|
+
} )
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await main()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
|
-
import { execa } from 'execa'
|
|
3
2
|
import inquirer from 'inquirer'
|
|
4
3
|
|
|
5
4
|
import { log, shellCmd, sleep } from '../../Utils.mjs'
|
|
@@ -121,7 +120,7 @@ export default class ServiceAccountManager {
|
|
|
121
120
|
async createServiceAccount( serviceName, k8sProject ) {
|
|
122
121
|
log( ' Creating Service Account => ', chalk.cyan( serviceName ) )
|
|
123
122
|
const command = `gcloud iam service-accounts create ${serviceName} --project ${k8sProject}`
|
|
124
|
-
const options = `--display-name
|
|
123
|
+
const options = `--display-name 'SvcMan SA => ${serviceName}' --description 'SvcMan SA'`
|
|
125
124
|
await shellCmd( `${command} ${options}` )
|
|
126
125
|
await this.waitForServiceAccount( serviceName, k8sProject )
|
|
127
126
|
}
|
|
Binary file
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const chalk = require('chalk');
|
|
7
|
-
const cliArgs = require('command-line-args');
|
|
8
|
-
const cliHelp = require('command-line-usage');
|
|
9
|
-
const npmRegistryFetch = require('npm-registry-fetch');
|
|
10
|
-
const semverLt = require('semver/functions/lt');
|
|
11
|
-
|
|
12
|
-
/* eslint-disable no-console */
|
|
13
|
-
|
|
14
|
-
const optionList = [
|
|
15
|
-
// Use optionList to tie into the Usage statements
|
|
16
|
-
{
|
|
17
|
-
name: 'root',
|
|
18
|
-
type: Boolean,
|
|
19
|
-
default: false,
|
|
20
|
-
description: 'returns the root of the build-tools installation'
|
|
21
|
-
}, {
|
|
22
|
-
name: 'latest',
|
|
23
|
-
type: Boolean,
|
|
24
|
-
default: false,
|
|
25
|
-
description: 'verifies the latest build-tools are installed'
|
|
26
|
-
}, {
|
|
27
|
-
name: 'bashfun',
|
|
28
|
-
type: Boolean,
|
|
29
|
-
default: false,
|
|
30
|
-
description: 'source the output to define common bash helper functions'
|
|
31
|
-
}, {
|
|
32
|
-
name: 'reporoot',
|
|
33
|
-
type: Boolean,
|
|
34
|
-
default: false,
|
|
35
|
-
description: 'the root of the build-tools repo - for finding other config files'
|
|
36
|
-
}, {
|
|
37
|
-
name: 'help',
|
|
38
|
-
type: Boolean,
|
|
39
|
-
default: false,
|
|
40
|
-
description: 'display this help screen'
|
|
41
|
-
}, {
|
|
42
|
-
name: 'version',
|
|
43
|
-
type: Boolean,
|
|
44
|
-
default: false,
|
|
45
|
-
description: 'returns the build-tools repo version'
|
|
46
|
-
}, {
|
|
47
|
-
name: 'verbose',
|
|
48
|
-
alias: 'v',
|
|
49
|
-
type: Boolean,
|
|
50
|
-
default: false,
|
|
51
|
-
description: 'emit additional info at run time'
|
|
52
|
-
}];
|
|
53
|
-
const sections = [{
|
|
54
|
-
header: 'A collection of build / support tools for all Leverege code',
|
|
55
|
-
content: `README: {green https://bitbucket.org/leverege/build-tools/src/development}
|
|
56
|
-
`
|
|
57
|
-
}, {
|
|
58
|
-
header: 'Options',
|
|
59
|
-
optionList
|
|
60
|
-
}];
|
|
61
|
-
const args = cliArgs(optionList, {
|
|
62
|
-
partial: true
|
|
63
|
-
});
|
|
64
|
-
const help = cliHelp(sections);
|
|
65
|
-
const repoRoot = path.dirname(__dirname);
|
|
66
|
-
const thisPackage = require(`${repoRoot}/package.json`);
|
|
67
|
-
const thisVersion = thisPackage.version;
|
|
68
|
-
if (args.root) {
|
|
69
|
-
console.log(repoRoot);
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
72
|
-
if (args.bashfun) {
|
|
73
|
-
console.log(`${repoRoot}/src/bash-funcs`);
|
|
74
|
-
process.exit(0);
|
|
75
|
-
}
|
|
76
|
-
if (args.reporoot) {
|
|
77
|
-
console.log(`${repoRoot}`);
|
|
78
|
-
process.exit(0);
|
|
79
|
-
}
|
|
80
|
-
const checkForLatest = async (pkg = '@leverege/build-tools') => {
|
|
81
|
-
const getToken = () => {
|
|
82
|
-
const tokenFile = `${process.env.HOME}/.npmrc`;
|
|
83
|
-
if (!fs.existsSync(tokenFile)) {
|
|
84
|
-
console.error(`Cannot find token file ${tokenFile}`);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const tokenLine = fs.readFileSync(tokenFile).toString();
|
|
89
|
-
const tokenRegX = new RegExp('.*registry.npmjs.org\\/:\\w+=+(.*)');
|
|
90
|
-
const tokenStr = tokenLine.match(tokenRegX);
|
|
91
|
-
if (!tokenStr) {
|
|
92
|
-
console.error(`\n***ERROR: malformed npm token in ${tokenFile}\n`);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
return tokenLine.match(tokenRegX)[1];
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.error(err);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
try {
|
|
101
|
-
const list = await npmRegistryFetch.json(pkg, {
|
|
102
|
-
'//registry.npmjs.org/:_authToken': getToken()
|
|
103
|
-
});
|
|
104
|
-
return list['dist-tags'].latest;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.log(err);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const checkAndAnnounce = () => {
|
|
110
|
-
checkForLatest().then(latestVersion => {
|
|
111
|
-
if (semverLt(thisVersion, latestVersion)) {
|
|
112
|
-
console.log(chalk.white`
|
|
113
|
-
Update available ${chalk.grey(thisVersion)} \u2b62 ${chalk.green(latestVersion)}
|
|
114
|
-
Run ${chalk.cyan('npm i -g @leverege/build-tools')} to update
|
|
115
|
-
`);
|
|
116
|
-
process.exit(1);
|
|
117
|
-
} else {
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
}).catch(err => {});
|
|
121
|
-
};
|
|
122
|
-
if (args.latest) {
|
|
123
|
-
checkAndAnnounce();
|
|
124
|
-
}
|
|
125
|
-
if (args.version) {
|
|
126
|
-
console.log(`build-tools version ${thisVersion}`);
|
|
127
|
-
checkAndAnnounce();
|
|
128
|
-
}
|
|
129
|
-
if (args.help) {
|
|
130
|
-
console.log(help);
|
|
131
|
-
process.exit(0);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/* eslint-disable no-underscore-dangle */
|
|
135
|
-
if (args._unknown) {
|
|
136
|
-
console.log(`\nUnrecognized argument [${args._unknown}] try --help\n`);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
// https://github.com/google/zx
|
|
4
|
-
/* eslint-disable max-len */
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
var _enquirer = _interopRequireDefault(require("enquirer"));
|
|
8
|
-
var _ansiColors = _interopRequireDefault(require("ansi-colors"));
|
|
9
|
-
var _zx = require("zx");
|
|
10
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
-
const {
|
|
12
|
-
prompt
|
|
13
|
-
} = _enquirer.default;
|
|
14
|
-
|
|
15
|
-
// This will preserve coloration from spawned child processes
|
|
16
|
-
process.env.FORCE_COLORS = 3;
|
|
17
|
-
process.env.FORCE_COLOR = '1';
|
|
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 look for a larger length of argv to decide whether to look for named paramters
|
|
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
|
-
|
|
35
|
-
// If target has not been defined on the command line,
|
|
36
|
-
// then enter interactive mode
|
|
37
|
-
const INTERACTIVE_MODE = !TARGET;
|
|
38
|
-
let exited = false;
|
|
39
|
-
const SECRETS_DIR = 'secrets';
|
|
40
|
-
const EXIT_EVENTS = ['SIGINT', 'exit', 'uncaughException', 'unhandledRejection'];
|
|
41
|
-
const [firebaserc, firebaseJson, gitBranch] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), '.firebaserc')), _zx.fs.exists(_zx.path.join(process.cwd(), 'firebase.json')), (0, _zx.$)`git rev-parse --abbrev-ref HEAD -C ${process.cwd()}`.then(({
|
|
42
|
-
stdout
|
|
43
|
-
}) => {
|
|
44
|
-
return stdout.split('\n')[0];
|
|
45
|
-
})]);
|
|
46
|
-
if (!firebaserc) {
|
|
47
|
-
console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
|
|
48
|
-
}
|
|
49
|
-
if (!firebaseJson) {
|
|
50
|
-
console.log(_zx.chalk.red('No firebase.json file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
|
|
51
|
-
}
|
|
52
|
-
if (!firebaserc || !firebaseJson) {
|
|
53
|
-
process.exit();
|
|
54
|
-
}
|
|
55
|
-
const firebasercContent = JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
|
|
56
|
-
encoding: 'utf-8'
|
|
57
|
-
}));
|
|
58
|
-
let maxSiteIdLength = 0;
|
|
59
|
-
const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
|
|
60
|
-
Object.entries(config.hosting).forEach(([siteId, aliases]) => {
|
|
61
|
-
aliases.forEach(alias => {
|
|
62
|
-
// Store the longest length of site id for formatting later on
|
|
63
|
-
maxSiteIdLength = Math.max(siteId.length, maxSiteIdLength);
|
|
64
|
-
prev[alias] = {
|
|
65
|
-
projectId,
|
|
66
|
-
siteId,
|
|
67
|
-
alias,
|
|
68
|
-
production: config.production
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
return prev;
|
|
73
|
-
}, {});
|
|
74
|
-
if (INTERACTIVE_MODE) {
|
|
75
|
-
const {
|
|
76
|
-
targetEnv
|
|
77
|
-
} = await prompt([{
|
|
78
|
-
type: 'autocomplete',
|
|
79
|
-
name: 'targetEnv',
|
|
80
|
-
message: 'To which environment would you like to deploy?',
|
|
81
|
-
choices: Object.values(targets).map(t => {
|
|
82
|
-
const productionLabel = t.production ? _ansiColors.default.bold.red('--PRODUCTION--') : '';
|
|
83
|
-
return {
|
|
84
|
-
message: `${_ansiColors.default.bold.cyan('Site: ')}${t.siteId.padEnd(maxSiteIdLength)} ${_ansiColors.default.bold.green('Project: ')}${t.projectId} ${productionLabel}`,
|
|
85
|
-
value: t.alias
|
|
86
|
-
};
|
|
87
|
-
})
|
|
88
|
-
}]);
|
|
89
|
-
TARGET = targets[targetEnv];
|
|
90
|
-
if (TARGET.production) {
|
|
91
|
-
const {
|
|
92
|
-
confirmProductionDeploy
|
|
93
|
-
} = await prompt([{
|
|
94
|
-
type: 'confirm',
|
|
95
|
-
name: 'confirmProductionDeploy',
|
|
96
|
-
message: `${_ansiColors.default.bold.red('WARNING: ')} This is a ${_ansiColors.default.bold.yellow('PRODUCTION')} environment. Are you sure you want to deploy here?`
|
|
97
|
-
}]);
|
|
98
|
-
if (!confirmProductionDeploy) {
|
|
99
|
-
console.log('Cancelling deployment...');
|
|
100
|
-
process.exit();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const {
|
|
104
|
-
useChannel
|
|
105
|
-
} = await prompt([{
|
|
106
|
-
type: 'confirm',
|
|
107
|
-
name: 'useChannel',
|
|
108
|
-
message: 'Do you want to deploy to a temporary preview channel?'
|
|
109
|
-
}]);
|
|
110
|
-
if (useChannel) {
|
|
111
|
-
const {
|
|
112
|
-
channelName,
|
|
113
|
-
channelExpiration
|
|
114
|
-
} = await prompt([{
|
|
115
|
-
type: 'input',
|
|
116
|
-
name: 'channelName',
|
|
117
|
-
message: 'To which channel would you like to deploy?',
|
|
118
|
-
initial: gitBranch ?? 'temp-deploy'
|
|
119
|
-
}, {
|
|
120
|
-
type: 'input',
|
|
121
|
-
name: 'channelExpiration',
|
|
122
|
-
message: 'When should the channel expire?',
|
|
123
|
-
initial: '7d'
|
|
124
|
-
}]);
|
|
125
|
-
CHANNEL = {
|
|
126
|
-
name: channelName,
|
|
127
|
-
expiration: channelExpiration
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
TARGET = targets[TARGET];
|
|
132
|
-
}
|
|
133
|
-
if (!TARGET) {
|
|
134
|
-
console.log(_zx.chalk.red('No such target exists in .firebaserc config.'));
|
|
135
|
-
process.exit();
|
|
136
|
-
}
|
|
137
|
-
const [aliasFileExists, siteIdFileExists, sharedFileExists] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.alias}.env`)), _zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env`)), _zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, 'shared.env'))]);
|
|
138
|
-
if (!aliasFileExists && !siteIdFileExists) {
|
|
139
|
-
console.log(_zx.chalk.red(`No env file for ${TARGET.alias} exists. Check your secrets directory and try again`));
|
|
140
|
-
process.exit();
|
|
141
|
-
}
|
|
142
|
-
const exec = [];
|
|
143
|
-
if (sharedFileExists) {
|
|
144
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, 'shared.env'), {
|
|
145
|
-
encoding: 'utf-8'
|
|
146
|
-
}));
|
|
147
|
-
}
|
|
148
|
-
if (aliasFileExists) {
|
|
149
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.alias}.env`), {
|
|
150
|
-
encoding: 'utf-8'
|
|
151
|
-
}));
|
|
152
|
-
} else {
|
|
153
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env`), {
|
|
154
|
-
encoding: 'utf-8'
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
|
-
console.log(`\n\n\n${_ansiColors.default.bold.green('====== Deployment Summary ======')}`);
|
|
158
|
-
console.log(`${_ansiColors.default.blue('Firebase project:')} ${TARGET.projectId} ${TARGET.production ? _ansiColors.default.yellow('--PRODUCTION--') : ''}`);
|
|
159
|
-
console.log(`${_ansiColors.default.blue('Site ID:')} ${TARGET.siteId}`);
|
|
160
|
-
console.log(`${_ansiColors.default.blue('Channel Name:')} ${CHANNEL?.name ?? _ansiColors.default.italic.gray('(none)')}`);
|
|
161
|
-
console.log(`${_ansiColors.default.blue('Channel Expiration date:')} ${CHANNEL?.name ? CHANNEL.expiration ?? _ansiColors.default.italic.gray('(none)') : _ansiColors.default.italic.gray('(n/a)')}`);
|
|
162
|
-
console.log(`${_ansiColors.default.blue('Shared .env file:')} ${sharedFileExists ? `${SECRETS_DIR}/shared.env` : _ansiColors.default.italic.gray('(none)')}`);
|
|
163
|
-
console.log(`${_ansiColors.default.blue('Additional .env overrides:')} ${aliasFileExists ? `${SECRETS_DIR}/${TARGET.alias}.env` : `${SECRETS_DIR}/${TARGET.siteId}.env`}`);
|
|
164
|
-
console.log(`${_ansiColors.default.bold.green('===============================')}`);
|
|
165
|
-
if (INTERACTIVE_MODE) {
|
|
166
|
-
const {
|
|
167
|
-
confirmSelections
|
|
168
|
-
} = await prompt([{
|
|
169
|
-
type: 'confirm',
|
|
170
|
-
name: 'confirmSelections',
|
|
171
|
-
message: 'Proceed with this deployment?'
|
|
172
|
-
}]);
|
|
173
|
-
if (!confirmSelections) {
|
|
174
|
-
console.log('Cancelling deployment...');
|
|
175
|
-
process.exit();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const envFileContent = (await Promise.all(exec)).join('\n');
|
|
179
|
-
EXIT_EVENTS.forEach(event => {
|
|
180
|
-
process.on(event, () => {
|
|
181
|
-
if (!exited) {
|
|
182
|
-
exited = true;
|
|
183
|
-
_zx.fs.removeSync(_zx.path.join(process.cwd(), '.env.temp'));
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
|
|
188
|
-
try {
|
|
189
|
-
// deploying to a channel requires slightly different parameter structure
|
|
190
|
-
const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}`.split(' ') : 'deploy';
|
|
191
|
-
const hostingPrefix = CHANNEL ? '' : 'hosting:';
|
|
192
|
-
await (0, _zx.$)`npm run clean && DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} DEPLOYMENT_TARGET=${TARGET.siteId} npm run build && firebase use ${TARGET.projectId} && firebase ${deployType} --only ${hostingPrefix}${TARGET.siteId}`;
|
|
193
|
-
} catch (err) {
|
|
194
|
-
console.error(err);
|
|
195
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// https://github.com/google/zx
|
|
3
|
-
/* eslint-disable max-len */
|
|
4
|
-
/* eslint-disable no-console */
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
var _enquirer = _interopRequireDefault(require("enquirer"));
|
|
8
|
-
var _zx = require("zx");
|
|
9
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
|
-
const {
|
|
11
|
-
prompt
|
|
12
|
-
} = _enquirer.default;
|
|
13
|
-
|
|
14
|
-
// This will preserve coloration from spawned child processes
|
|
15
|
-
process.env.FORCE_COLORS = 3;
|
|
16
|
-
process.env.FORCE_COLOR = '1';
|
|
17
|
-
let TARGET = _zx.argv._[0];
|
|
18
|
-
let exited = false;
|
|
19
|
-
const SECRETS_DIR = 'secrets';
|
|
20
|
-
const EXIT_EVENTS = ['SIGINT', 'exit', 'uncaughtException', 'unhandledRejection'];
|
|
21
|
-
const [firebaserc, firebaseJson, hasLocalConfig] = 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(), SECRETS_DIR, 'local.env'))]);
|
|
22
|
-
if (!hasLocalConfig) {
|
|
23
|
-
if (!firebaserc) {
|
|
24
|
-
console.log(_zx.chalk.red('No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
|
|
25
|
-
}
|
|
26
|
-
if (!firebaseJson) {
|
|
27
|
-
console.log(_zx.chalk.red('No firebase.json file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.'));
|
|
28
|
-
}
|
|
29
|
-
if (!firebaserc || !firebaseJson) {
|
|
30
|
-
process.exit();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const firebasercContent = firebaserc ? JSON.parse(await _zx.fs.readFile(_zx.path.join(process.cwd(), '.firebaserc'), {
|
|
34
|
-
encoding: 'utf-8'
|
|
35
|
-
})) : {
|
|
36
|
-
targets: {}
|
|
37
|
-
};
|
|
38
|
-
const defaultTargets = {};
|
|
39
|
-
if (hasLocalConfig) {
|
|
40
|
-
defaultTargets.local = {
|
|
41
|
-
siteId: 'local',
|
|
42
|
-
alias: 'local'
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const targets = Object.entries(firebasercContent.targets).reduce((prev, [projectId, config]) => {
|
|
46
|
-
Object.entries(config.hosting).forEach(([siteId, aliases]) => {
|
|
47
|
-
aliases.forEach(alias => {
|
|
48
|
-
prev[alias] = {
|
|
49
|
-
projectId,
|
|
50
|
-
siteId,
|
|
51
|
-
alias
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
return prev;
|
|
56
|
-
}, defaultTargets);
|
|
57
|
-
if (!TARGET) {
|
|
58
|
-
const choices = Object.keys(targets).map(t => ({
|
|
59
|
-
name: t,
|
|
60
|
-
value: t
|
|
61
|
-
}));
|
|
62
|
-
let targetEnv;
|
|
63
|
-
if (choices.length > 1) {
|
|
64
|
-
const res = await prompt({
|
|
65
|
-
type: 'autocomplete',
|
|
66
|
-
name: 'targetEnv',
|
|
67
|
-
message: 'Which environment would you like to serve?',
|
|
68
|
-
choices
|
|
69
|
-
});
|
|
70
|
-
targetEnv = res.targetEnv;
|
|
71
|
-
} else {
|
|
72
|
-
targetEnv = choices[0].value;
|
|
73
|
-
}
|
|
74
|
-
TARGET = targets[targetEnv];
|
|
75
|
-
} else {
|
|
76
|
-
TARGET = targets[TARGET];
|
|
77
|
-
}
|
|
78
|
-
if (!TARGET) {
|
|
79
|
-
console.log(_zx.chalk.red('No such target exists in .firebaserc config.'));
|
|
80
|
-
process.exit();
|
|
81
|
-
}
|
|
82
|
-
const [aliasFileExists, siteIdFileExists, sharedFileExists] = await Promise.all([_zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.alias}.env`)), _zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env`)), _zx.fs.exists(_zx.path.join(process.cwd(), SECRETS_DIR, 'shared.env'))]);
|
|
83
|
-
if (!aliasFileExists && !siteIdFileExists) {
|
|
84
|
-
console.log(_zx.chalk.red(`No env file for ${TARGET.alias} exists. Check your secrets directory and try again`));
|
|
85
|
-
process.exit();
|
|
86
|
-
}
|
|
87
|
-
const exec = [];
|
|
88
|
-
if (sharedFileExists) {
|
|
89
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, 'shared.env'), {
|
|
90
|
-
encoding: 'utf-8'
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
if (aliasFileExists) {
|
|
94
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.alias}.env`), {
|
|
95
|
-
encoding: 'utf-8'
|
|
96
|
-
}));
|
|
97
|
-
} else {
|
|
98
|
-
exec.push(_zx.fs.readFile(_zx.path.join(process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env`), {
|
|
99
|
-
encoding: 'utf-8'
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
const envFileContent = (await Promise.all(exec)).join('\n');
|
|
103
|
-
EXIT_EVENTS.forEach(event => {
|
|
104
|
-
process.on(event, () => {
|
|
105
|
-
if (!exited) {
|
|
106
|
-
exited = true;
|
|
107
|
-
_zx.fs.removeSync(_zx.path.join(process.cwd(), '.env.temp'));
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
await _zx.fs.writeFile(_zx.path.join(process.cwd(), '.env.temp'), envFileContent);
|
|
112
|
-
try {
|
|
113
|
-
await (0, _zx.$)`DOTENV_CONFIG_PATH=${_zx.path.join(process.cwd(), '.env.temp')} DEPLOYMENT_TARGET=${TARGET.siteId} npm run serve --colors`;
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.error(err);
|
|
116
|
-
}
|