@leverege/build-tools 2.92.0 → 2.93.0-beta.11
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/.vscode/settings.json +3 -0
- package/package.json +8 -3
- package/src/Utils.mjs +9 -0
- package/src/artifacts/ArtifactPuller.mjs +148 -0
- package/src/artifacts/ArtifactPusher.mjs +78 -0
- package/src/artifacts/Structs.mjs +91 -0
- package/src/artifacts/Utils.mjs +113 -0
- package/src/artifacts/pull-artifact.mjs +68 -0
- package/src/artifacts/push-artifact.mjs +68 -0
- 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.
|
|
3
|
+
"version": "2.93.0-beta.11",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"clean": "rm -rf lib",
|
|
12
12
|
"lint": "find ./src -name \\*.\\*js | xargs npx eslint --report-unused-disable-directives",
|
|
13
13
|
"prepare": "[ $SKIP_PREPARE ] && exit 0 || ./src/hook-and-release.mjs",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "mocha -t 60000 --exit 'test/**/*.mjs'",
|
|
15
15
|
"prepack": "npm exec prepack"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
"pkglint": "src/pkglint.sh",
|
|
55
55
|
"prepack": "src/prepack.mjs",
|
|
56
56
|
"prune-git": "src/prune-git.sh",
|
|
57
|
+
"pull-artifact": "src/artifacts/pull-artifact.mjs",
|
|
57
58
|
"pull-git": "src/pull-git.sh",
|
|
59
|
+
"push-artifact": "src/artifacts/push-artifact.mjs",
|
|
58
60
|
"push-my-chart": "src/push-my-chart.mjs",
|
|
59
61
|
"refresh-npm-token": "src/refresh-npm-token.mjs",
|
|
60
62
|
"refresh-py-idx": "src/refresh-py-idx.sh",
|
|
@@ -91,11 +93,14 @@
|
|
|
91
93
|
"shell-quote": "^1.8.3",
|
|
92
94
|
"simple-git": "^3.30.0",
|
|
93
95
|
"sloc": "^0.3.2",
|
|
96
|
+
"superstruct": "^2.0.2",
|
|
94
97
|
"toml": "^3.0.0",
|
|
95
98
|
"zx": "^8.8.5"
|
|
96
99
|
},
|
|
97
100
|
"devDependencies": {
|
|
98
101
|
"@leverege/eslint-config-leverege": "^5.1.1",
|
|
102
|
+
"chai": "^6.2.1",
|
|
103
|
+
"mocha": "^11.7.5",
|
|
99
104
|
"npm": "^11.6.2"
|
|
100
105
|
}
|
|
101
|
-
}
|
|
106
|
+
}
|
package/src/Utils.mjs
CHANGED
|
@@ -224,6 +224,15 @@ export const gitRepoIsDirty = async () => {
|
|
|
224
224
|
return cleanliness?.length > 0
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
export const parseYamlFile = async ( filePath ) => {
|
|
228
|
+
try {
|
|
229
|
+
const fileContent = await fs.promises.readFile( filePath, 'utf8' )
|
|
230
|
+
return YAML.load( fileContent )
|
|
231
|
+
} catch ( error ) {
|
|
232
|
+
throw new Error( `Failed to parse YAML file ${filePath}: ${error.message}` )
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
227
236
|
export const parseJsonFile = async ( jsonFile ) => {
|
|
228
237
|
// TODO: rework this file exists logic / throw
|
|
229
238
|
if ( !fs.existsSync( jsonFile ) ) { return undefined } // eslint-disable-line security/detect-non-literal-fs-filename
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
|
|
3
|
+
import { toExplicitPath } from './Utils.mjs'
|
|
4
|
+
|
|
5
|
+
class ArtifactPuller {
|
|
6
|
+
constructor( artifact ) {
|
|
7
|
+
this.artifact = artifact
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async pull() {
|
|
11
|
+
throw new Error( 'Not implemented' )
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class GenericArtifactPuller extends ArtifactPuller {
|
|
16
|
+
|
|
17
|
+
async downloadFile( remoteFilePath, localFilePath ) {
|
|
18
|
+
const command = `gcloud artifacts generic download \
|
|
19
|
+
--project=${this.artifact.gcp_project_id} \
|
|
20
|
+
--location=${this.artifact.gcp_location} \
|
|
21
|
+
--repository=${this.artifact.gcp_artifact_registry_repository_name} \
|
|
22
|
+
--package=${this.artifact.gcp_artifact_registry_package_name} \
|
|
23
|
+
--version=${this.artifact.gcp_artifact_registry_package_version} \
|
|
24
|
+
--name="${remoteFilePath}" \
|
|
25
|
+
--destination=/tmp`
|
|
26
|
+
|
|
27
|
+
const oldPath = `/tmp/${remoteFilePath.split( '/' ).pop()}`
|
|
28
|
+
const newPath = localFilePath
|
|
29
|
+
|
|
30
|
+
// if file exists in the old path, delete it
|
|
31
|
+
if ( fs.existsSync( oldPath ) ) {
|
|
32
|
+
fs.unlinkSync( oldPath )
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// if file exists in the new path, delete it
|
|
36
|
+
if ( fs.existsSync( newPath ) ) {
|
|
37
|
+
fs.unlinkSync( newPath )
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Ensure the directory exists
|
|
41
|
+
fs.mkdirSync( localFilePath.split( '/' ).slice( 0, -1 ).join( '/' ), { recursive : true } )
|
|
42
|
+
|
|
43
|
+
console.log( 'Command:', command.replace( /\s+/g, ' ' ).trim() )
|
|
44
|
+
|
|
45
|
+
const { execSync } = await import( 'child_process' )
|
|
46
|
+
const result = execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
|
|
47
|
+
console.log( 'Result:', result )
|
|
48
|
+
|
|
49
|
+
// Check if file exists in the old path
|
|
50
|
+
if ( !fs.existsSync( oldPath ) ) {
|
|
51
|
+
console.error( `File does not exist in the old path: ${oldPath}` )
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log( `Copying file from ${oldPath} to ${newPath}` )
|
|
56
|
+
|
|
57
|
+
await fs.copyFileSync( oldPath, newPath )
|
|
58
|
+
|
|
59
|
+
// Check if file exists in the new path
|
|
60
|
+
if ( !fs.existsSync( newPath ) ) {
|
|
61
|
+
console.error( `File does not exist in the new path: ${newPath}` )
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async downloadDirectory( localPath ) {
|
|
69
|
+
const command = `gcloud artifacts generic download \
|
|
70
|
+
--project=${this.artifact.gcp_project_id} \
|
|
71
|
+
--location=${this.artifact.gcp_location} \
|
|
72
|
+
--repository=${this.artifact.gcp_artifact_registry_repository_name} \
|
|
73
|
+
--package=${this.artifact.gcp_artifact_registry_package_name} \
|
|
74
|
+
--version=${this.artifact.gcp_artifact_registry_package_version} \
|
|
75
|
+
--destination=${localPath}`
|
|
76
|
+
|
|
77
|
+
console.log( 'Command:', command.replace( /\s+/g, ' ' ).trim() )
|
|
78
|
+
|
|
79
|
+
const { execSync } = await import( 'child_process' )
|
|
80
|
+
execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
|
|
81
|
+
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async downloadNestedDirectory( remotePath, localPath ) {
|
|
86
|
+
const files = await this.listFiles( remotePath )
|
|
87
|
+
await Promise.all(
|
|
88
|
+
files
|
|
89
|
+
.filter( file => file.name.split( ':' ).pop().replaceAll( '%2F', '/' ).startsWith( remotePath ) )
|
|
90
|
+
.map( async ( file ) => {
|
|
91
|
+
const remoteFilePath = file.name.split( ':' ).pop().replaceAll( '%2F', '/' )
|
|
92
|
+
const localFilePath = `${localPath}${remoteFilePath.replaceAll( remotePath, '' )}`
|
|
93
|
+
await this.downloadFile( remoteFilePath, localFilePath )
|
|
94
|
+
} )
|
|
95
|
+
)
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async listFiles( ) {
|
|
100
|
+
const command = `gcloud artifacts files list \
|
|
101
|
+
--project=${this.artifact.gcp_project_id} \
|
|
102
|
+
--location=${this.artifact.gcp_location} \
|
|
103
|
+
--repository=${this.artifact.gcp_artifact_registry_repository_name} \
|
|
104
|
+
--package=${this.artifact.gcp_artifact_registry_package_name} \
|
|
105
|
+
--version ${this.artifact.gcp_artifact_registry_package_version} \
|
|
106
|
+
--format="json"`
|
|
107
|
+
|
|
108
|
+
console.log( 'Command:', command.replace( /\s+/g, ' ' ).trim() )
|
|
109
|
+
|
|
110
|
+
const { execSync } = await import( 'child_process' )
|
|
111
|
+
const result = execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
|
|
112
|
+
|
|
113
|
+
// Parse the JSON response
|
|
114
|
+
const files = JSON.parse( result )
|
|
115
|
+
|
|
116
|
+
return files
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async pull() {
|
|
120
|
+
console.log( 'Pulling artifact from registry...' )
|
|
121
|
+
|
|
122
|
+
if ( this.artifact.remote_file_path && this.artifact.local_file_path ) {
|
|
123
|
+
return this.downloadFile( this.artifact.remote_file_path, toExplicitPath( this.artifact.local_file_path ) )
|
|
124
|
+
}
|
|
125
|
+
if ( this.artifact.remote_file_path && this.artifact.local_path ) {
|
|
126
|
+
return this.downloadFile( this.artifact.remote_file_path, toExplicitPath( `${this.artifact.local_path}/${this.artifact.remote_file_path.split( '/' ).pop()}` ) )
|
|
127
|
+
}
|
|
128
|
+
if ( this.artifact.remote_path && this.artifact.local_path ) {
|
|
129
|
+
return this.downloadNestedDirectory( this.artifact.remote_path, toExplicitPath( this.artifact.local_path ) )
|
|
130
|
+
}
|
|
131
|
+
if ( this.artifact.remote_path && !this.artifact.local_path && !this.artifact.local_file_path ) {
|
|
132
|
+
return this.downloadDirectory( this.artifact.remote_path, '.' )
|
|
133
|
+
}
|
|
134
|
+
if ( !this.artifact.remote_path && !this.artifact.remote_file_path && this.artifact.local_path ) {
|
|
135
|
+
return this.downloadDirectory( toExplicitPath( this.artifact.local_path ) )
|
|
136
|
+
}
|
|
137
|
+
// if ( !this.artifact.remote_path && !this.artifact.remote_file_path && this.local_file_path ) {
|
|
138
|
+
// throw new Error( '' )
|
|
139
|
+
// }
|
|
140
|
+
// if ( this.artifact.remote_path && this.artifact.local_file_path ) {
|
|
141
|
+
// throw new Error( '' )
|
|
142
|
+
// }
|
|
143
|
+
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { ArtifactPuller, GenericArtifactPuller }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
class ArtifactPusher {
|
|
2
|
+
constructor( artifact ) {
|
|
3
|
+
this.artifact = artifact
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async push() {
|
|
7
|
+
throw new Error( 'Not implemented' )
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class GenericArtifactPusher extends ArtifactPusher {
|
|
12
|
+
|
|
13
|
+
async listFiles( ) {
|
|
14
|
+
const command = `gcloud artifacts files list \
|
|
15
|
+
--project=${this.artifact.gcp_project_id} \
|
|
16
|
+
--location=${this.artifact.gcp_location} \
|
|
17
|
+
--repository=${this.artifact.gcp_artifact_registry_repository_name} \
|
|
18
|
+
--package=${this.artifact.gcp_artifact_registry_package_name} \
|
|
19
|
+
--version ${this.artifact.gcp_artifact_registry_package_version} \
|
|
20
|
+
--format="json"`
|
|
21
|
+
|
|
22
|
+
console.log( 'Command:', command.replace( /\s+/g, ' ' ).trim() )
|
|
23
|
+
|
|
24
|
+
const { execSync } = await import( 'child_process' )
|
|
25
|
+
const result = execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
|
|
26
|
+
|
|
27
|
+
// Parse the JSON response
|
|
28
|
+
const files = JSON.parse( result )
|
|
29
|
+
|
|
30
|
+
return files
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async push() {
|
|
34
|
+
console.log( 'Pushing artifact to registry...' )
|
|
35
|
+
|
|
36
|
+
if ( this.artifact.remote_file_path ) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log( 'Uploading artifact to registry...' )
|
|
41
|
+
|
|
42
|
+
let command = ''
|
|
43
|
+
|
|
44
|
+
command = `gcloud artifacts generic upload \
|
|
45
|
+
--project=${this.artifact.gcp_project_id} \
|
|
46
|
+
--location=${this.artifact.gcp_location} \
|
|
47
|
+
--repository=${this.artifact.gcp_artifact_registry_repository_name} \
|
|
48
|
+
--package=${this.artifact.gcp_artifact_registry_package_name} \
|
|
49
|
+
--version=${this.artifact.gcp_artifact_registry_package_version}`
|
|
50
|
+
|
|
51
|
+
if ( this.artifact.local_path ) {
|
|
52
|
+
command += ` --source-directory=${this.artifact.local_path}`
|
|
53
|
+
command += ' --skip-existing'
|
|
54
|
+
} else if ( this.artifact.local_file_path ) {
|
|
55
|
+
const files = await this.listFiles( this.artifact.remote_path )
|
|
56
|
+
if ( files.some( file => file.name.split( ':' ).pop().replaceAll( '%2F', '/' ) === `${this.artifact.remote_path}/${this.artifact.remote_file_path.split( '/' ).pop()}` ) ) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
command += ` --source=${this.artifact.local_file_path}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
command += ` --destination-path=${this.artifact.remote_path}`
|
|
63
|
+
|
|
64
|
+
console.log( 'Command:', command.replace( /\s+/g, ' ' ).trim() )
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const { execSync } = await import( 'child_process' )
|
|
68
|
+
execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
|
|
69
|
+
console.log( `Successfully published artifact: ${this.artifact.gcp_artifact_registry_package_name}:${this.artifact.gcp_artifact_registry_package_version}` )
|
|
70
|
+
return true
|
|
71
|
+
} catch ( error ) {
|
|
72
|
+
console.error( 'Error publishing artifact:', error.message )
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { ArtifactPusher, GenericArtifactPusher }
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { assign, object, string, array, optional, enums, union, refine } from 'superstruct'
|
|
2
|
+
|
|
3
|
+
const ArtifactTypeEnum = {
|
|
4
|
+
GENERIC : 'generic',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Superstruct schema for individual artifact configuration
|
|
8
|
+
const PartialArtifactSchema = object( {
|
|
9
|
+
name : string(),
|
|
10
|
+
type : optional( enums( Object.values( ArtifactTypeEnum ) ) ),
|
|
11
|
+
gcp_project_id : optional( string() ),
|
|
12
|
+
gcp_location : optional( string() ),
|
|
13
|
+
gcp_artifact_registry_repository_name : optional( string() ),
|
|
14
|
+
gcp_artifact_registry_package_name : optional( string() ),
|
|
15
|
+
gcp_artifact_registry_package_version : optional( string() )
|
|
16
|
+
} )
|
|
17
|
+
|
|
18
|
+
const ArtifactSchema = assign( PartialArtifactSchema, object( {
|
|
19
|
+
name : string(),
|
|
20
|
+
type : enums( Object.values( ArtifactTypeEnum ) ),
|
|
21
|
+
gcp_project_id : string(),
|
|
22
|
+
gcp_location : string(),
|
|
23
|
+
gcp_artifact_registry_repository_name : string(),
|
|
24
|
+
gcp_artifact_registry_package_name : string(),
|
|
25
|
+
gcp_artifact_registry_package_version : string()
|
|
26
|
+
} ) )
|
|
27
|
+
|
|
28
|
+
const PartialGenericArtifactSchema = refine(
|
|
29
|
+
assign( PartialArtifactSchema, object( {
|
|
30
|
+
type : enums( [ ArtifactTypeEnum.GENERIC ] ),
|
|
31
|
+
local_file_path : optional( string() ),
|
|
32
|
+
local_path : optional( string() ),
|
|
33
|
+
remote_file_path : optional( string() ),
|
|
34
|
+
remote_path : optional( string() )
|
|
35
|
+
} ) ),
|
|
36
|
+
'ConflictingPaths',
|
|
37
|
+
( value ) => {
|
|
38
|
+
if ( value.local_file_path && value.local_path ) {
|
|
39
|
+
return 'Only one of local_file_path or local_path must be provided'
|
|
40
|
+
}
|
|
41
|
+
if ( value.remote_file_path && value.remote_path ) {
|
|
42
|
+
return 'Only one of remote_file_path or remote_path must be provided'
|
|
43
|
+
}
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const GenericArtifactSchema = refine(
|
|
49
|
+
assign( ArtifactSchema, object( {
|
|
50
|
+
local_file_path : optional( string() ),
|
|
51
|
+
local_path : optional( string() ),
|
|
52
|
+
remote_file_path : optional( string() ),
|
|
53
|
+
remote_path : optional( string() )
|
|
54
|
+
} ) ),
|
|
55
|
+
'ConflictingPaths',
|
|
56
|
+
( value ) => {
|
|
57
|
+
if ( value.local_file_path && value.local_path ) {
|
|
58
|
+
return 'Only one of local_file_path or local_path must be provided'
|
|
59
|
+
}
|
|
60
|
+
if ( value.remote_file_path && value.remote_path ) {
|
|
61
|
+
return 'Only one of remote_file_path or remote_path must be provided'
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Superstruct schema for defaults configuration
|
|
68
|
+
const DefaultsSchema = object( {
|
|
69
|
+
gcp_project_id : optional( string() ),
|
|
70
|
+
gcp_location : optional( string() ),
|
|
71
|
+
gcp_artifact_registry_repository_name : optional( string() ),
|
|
72
|
+
gcp_artifact_registry_package_name : optional( string() ),
|
|
73
|
+
gcp_artifact_registry_package_version : optional( string() )
|
|
74
|
+
} )
|
|
75
|
+
|
|
76
|
+
// Superstruct schema for the complete artifacts configuration with defaults
|
|
77
|
+
const ArtifactsSchema = object( {
|
|
78
|
+
defaults : optional( DefaultsSchema ),
|
|
79
|
+
artifacts : array( union( [ PartialArtifactSchema, PartialGenericArtifactSchema ] ) )
|
|
80
|
+
} )
|
|
81
|
+
|
|
82
|
+
const FlattenedArtifactsSchema = array( assign( union( [ ArtifactSchema, GenericArtifactSchema ] ) ) )
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
ArtifactTypeEnum,
|
|
86
|
+
ArtifactsSchema,
|
|
87
|
+
FlattenedArtifactsSchema,
|
|
88
|
+
ArtifactSchema,
|
|
89
|
+
DefaultsSchema,
|
|
90
|
+
GenericArtifactSchema,
|
|
91
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
|
|
4
|
+
import { validate } from 'superstruct'
|
|
5
|
+
|
|
6
|
+
import { parseYamlFile, warning } from '../Utils.mjs'
|
|
7
|
+
|
|
8
|
+
import { ArtifactTypeEnum, ArtifactsSchema, GenericArtifactSchema } from './Structs.mjs'
|
|
9
|
+
|
|
10
|
+
const getArtifactsSchema = async ( artifactsFilePath = './artifacts.yml' ) => {
|
|
11
|
+
const artifactsConfig = await parseYamlFile( artifactsFilePath )
|
|
12
|
+
const [ error, result ] = validate( artifactsConfig, ArtifactsSchema )
|
|
13
|
+
if ( error ) {
|
|
14
|
+
throw new Error( `Invalid artifacts.yml file: ${error.message}` )
|
|
15
|
+
}
|
|
16
|
+
return result
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const flattenArtifactsSchema = ( artifactsSchema ) => {
|
|
20
|
+
return artifactsSchema.artifacts.reduce( ( flattenedArtifacts, artifact ) => {
|
|
21
|
+
const mergedArtifact = {
|
|
22
|
+
...artifactsSchema.defaults,
|
|
23
|
+
...artifact
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let schema
|
|
27
|
+
switch ( mergedArtifact.type ) {
|
|
28
|
+
case ArtifactTypeEnum.GENERIC:
|
|
29
|
+
schema = GenericArtifactSchema
|
|
30
|
+
break
|
|
31
|
+
default:
|
|
32
|
+
warning( `Unsupported artifact type "${mergedArtifact.type}"` )
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ( !schema ) {
|
|
37
|
+
warning( `Invalid artifact type "${mergedArtifact.type}"` )
|
|
38
|
+
return flattenedArtifacts
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [ error, validatedArtifact ] = validate( mergedArtifact, schema )
|
|
42
|
+
|
|
43
|
+
if ( error ) {
|
|
44
|
+
warning( `Invalid artifact "${mergedArtifact.name}": ${error.message}` )
|
|
45
|
+
return flattenedArtifacts
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return [ ...flattenedArtifacts, validatedArtifact ]
|
|
49
|
+
}, [] )
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const toImplicitPath = ( path ) => {
|
|
53
|
+
let implicitPath = path
|
|
54
|
+
// Remove leading './' if present
|
|
55
|
+
if ( implicitPath.startsWith( './' ) ) {
|
|
56
|
+
implicitPath = implicitPath.slice( 2 )
|
|
57
|
+
}
|
|
58
|
+
// Remove trailing slash
|
|
59
|
+
implicitPath = implicitPath.replace( /\/+$/, '' )
|
|
60
|
+
return implicitPath
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const toExplicitPath = ( path ) => {
|
|
64
|
+
let explicitPath = path
|
|
65
|
+
// Keep absolute paths and paths already starting with '.' as-is
|
|
66
|
+
if ( !path.startsWith( '/' ) && !path.startsWith( '.' ) ) {
|
|
67
|
+
// Add './' to relative paths
|
|
68
|
+
explicitPath = `./${explicitPath}`
|
|
69
|
+
}
|
|
70
|
+
return explicitPath
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const buildGenericArtifactName = ( artifact ) => {
|
|
74
|
+
const baseElements = [
|
|
75
|
+
'projects',
|
|
76
|
+
artifact.gcp_project_id,
|
|
77
|
+
'locations',
|
|
78
|
+
artifact.gcp_location,
|
|
79
|
+
'repositories',
|
|
80
|
+
artifact.gcp_artifact_registry_repository_name,
|
|
81
|
+
'files',
|
|
82
|
+
artifact.gcp_artifact_registry_package_name,
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const versionElement = artifact.gcp_artifact_registry_package_version
|
|
86
|
+
|
|
87
|
+
const remotePathElements = []
|
|
88
|
+
|
|
89
|
+
if ( artifact.remote_path || artifact.remote_file_path ) {
|
|
90
|
+
const explicitRemotePath = toExplicitPath( artifact.remote_path || artifact.remote_file_path )
|
|
91
|
+
const implicitRemotePath = toImplicitPath( explicitRemotePath )
|
|
92
|
+
if ( implicitRemotePath !== '.' ) {
|
|
93
|
+
remotePathElements.push( implicitRemotePath )
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const name = [ baseElements.join( '/' ), versionElement, encodeURIComponent( remotePathElements.join( '/' ) ) ].join( ':' )
|
|
98
|
+
|
|
99
|
+
return name
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const calculateHash = ( path ) => {
|
|
103
|
+
try {
|
|
104
|
+
const hash = crypto.createHash( 'sha256' )
|
|
105
|
+
hash.update( fs.readFileSync( path ) )
|
|
106
|
+
return hash.digest( 'base64url' )
|
|
107
|
+
} catch ( error ) {
|
|
108
|
+
console.error( error )
|
|
109
|
+
throw new Error( `Failed to calculate hash for artifact ${path}: ${error.message}` )
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { calculateHash, getArtifactsSchema, flattenArtifactsSchema, toImplicitPath, toExplicitPath, buildGenericArtifactName }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { warning } from '../Utils.mjs'
|
|
4
|
+
|
|
5
|
+
import { getArtifactsSchema, flattenArtifactsSchema } from './Utils.mjs'
|
|
6
|
+
import { ArtifactTypeEnum } from './Structs.mjs'
|
|
7
|
+
import { GenericArtifactPuller } from './ArtifactPuller.mjs'
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
|
|
11
|
+
let artifactsYmlPath = null
|
|
12
|
+
let artifactName = null
|
|
13
|
+
let artifactPackageName = null
|
|
14
|
+
let artifactPackageVersion = null
|
|
15
|
+
let filteredArtifactSchema = null
|
|
16
|
+
|
|
17
|
+
if ( process.argv.length === 3 ) {
|
|
18
|
+
artifactsYmlPath = process.argv[2]
|
|
19
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
20
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
21
|
+
filteredArtifactSchema = flattenedArtifactsSchema
|
|
22
|
+
} else if ( process.argv.length === 4 ) {
|
|
23
|
+
artifactsYmlPath = process.argv[2]
|
|
24
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
25
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
26
|
+
filteredArtifactSchema = flattenedArtifactsSchema.find(
|
|
27
|
+
artifact => artifact.name === artifactName )
|
|
28
|
+
} else if ( process.argv.length === 5 ) {
|
|
29
|
+
artifactsYmlPath = process.argv[2]
|
|
30
|
+
artifactName = process.argv[3]
|
|
31
|
+
artifactPackageName = process.argv[4]
|
|
32
|
+
artifactPackageVersion = process.argv[5]
|
|
33
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
34
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
35
|
+
filteredArtifactSchema = flattenedArtifactsSchema.filter(
|
|
36
|
+
artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName &&
|
|
37
|
+
artifact.gcp_artifact_registry_package_version === artifactPackageVersion )
|
|
38
|
+
} else {
|
|
39
|
+
console.error( 'Usage:' )
|
|
40
|
+
console.error( ' pull-artifact.mjs <artifacts.yml>' )
|
|
41
|
+
console.error( ' pull-artifact.mjs <artifacts.yml> <artifact name>' )
|
|
42
|
+
console.error( ' pull-artifact.mjs <artifacts.yml> <artifact package name> <artifact package version>' )
|
|
43
|
+
process.exit( 1 )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// TODO: Use concurrency to pull artifacts and provide summary of results.
|
|
47
|
+
for ( const artifactSchema of filteredArtifactSchema ) {
|
|
48
|
+
let puller
|
|
49
|
+
switch ( artifactSchema.type ) {
|
|
50
|
+
case ArtifactTypeEnum.GENERIC:
|
|
51
|
+
puller = new GenericArtifactPuller( artifactSchema )
|
|
52
|
+
break
|
|
53
|
+
default:
|
|
54
|
+
warning( `Unsupported artifact type "${artifactSchema.type}"` )
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ( !puller ) {
|
|
59
|
+
warning( `Unsupported artifact type "${artifactSchema.type}"` )
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
await puller.pull()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { warning } from '../Utils.mjs'
|
|
4
|
+
|
|
5
|
+
import { getArtifactsSchema, flattenArtifactsSchema } from './Utils.mjs'
|
|
6
|
+
import { ArtifactTypeEnum } from './Structs.mjs'
|
|
7
|
+
import { GenericArtifactPusher } from './ArtifactPusher.mjs'
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
|
|
11
|
+
let artifactsYmlPath = null
|
|
12
|
+
let artifactName = null
|
|
13
|
+
let artifactPackageName = null
|
|
14
|
+
let artifactPackageVersion = null
|
|
15
|
+
let filteredArtifactSchema = null
|
|
16
|
+
|
|
17
|
+
if ( process.argv.length === 3 ) {
|
|
18
|
+
artifactsYmlPath = process.argv[2]
|
|
19
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
20
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
21
|
+
filteredArtifactSchema = flattenedArtifactsSchema
|
|
22
|
+
} else if ( process.argv.length === 4 ) {
|
|
23
|
+
artifactsYmlPath = process.argv[2]
|
|
24
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
25
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
26
|
+
filteredArtifactSchema = flattenedArtifactsSchema.find(
|
|
27
|
+
artifact => artifact.name === artifactName )
|
|
28
|
+
} else if ( process.argv.length === 5 ) {
|
|
29
|
+
artifactsYmlPath = process.argv[2]
|
|
30
|
+
artifactName = process.argv[3]
|
|
31
|
+
artifactPackageName = process.argv[4]
|
|
32
|
+
artifactPackageVersion = process.argv[5]
|
|
33
|
+
filteredArtifactSchema = await getArtifactsSchema( artifactsYmlPath )
|
|
34
|
+
const flattenedArtifactsSchema = flattenArtifactsSchema( filteredArtifactSchema )
|
|
35
|
+
filteredArtifactSchema = flattenedArtifactsSchema.filter(
|
|
36
|
+
artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName &&
|
|
37
|
+
artifact.gcp_artifact_registry_package_version === artifactPackageVersion )
|
|
38
|
+
} else {
|
|
39
|
+
console.error( 'Usage:' )
|
|
40
|
+
console.error( ' push-artifact.mjs <artifacts.yml>' )
|
|
41
|
+
console.error( ' push-artifact.mjs <artifacts.yml> <artifact name>' )
|
|
42
|
+
console.error( ' push-artifact.mjs <artifacts.yml> <artifact package name> <artifact package version>' )
|
|
43
|
+
process.exit( 1 )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// TODO: Use concurrency to push artifacts and provide summary of results.
|
|
47
|
+
for ( const artifactSchema of filteredArtifactSchema ) {
|
|
48
|
+
let pusher
|
|
49
|
+
switch ( artifactSchema.type ) {
|
|
50
|
+
case ArtifactTypeEnum.GENERIC:
|
|
51
|
+
pusher = new GenericArtifactPusher( artifactSchema )
|
|
52
|
+
break
|
|
53
|
+
default:
|
|
54
|
+
warning( `Unsupported artifact type "${artifactSchema.type}"` )
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ( !pusher ) {
|
|
59
|
+
warning( `Unsupported artifact type "${artifactSchema.type}"` )
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
await pusher.push()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main()
|
|
Binary file
|