ffc-pay-etl-framework 1.4.2-alpha.6 → 1.4.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/.npmignore +26 -0
- package/app/database-connections/index.js +7 -0
- package/app/database-connections/postgres-database-connection.js +47 -0
- package/app/database-connections/provided-connection.js +19 -0
- package/app/destinations/README.md +107 -0
- package/app/destinations/console-destination.js +38 -0
- package/app/destinations/csv-file-destination.js +94 -0
- package/app/destinations/index.js +12 -0
- package/app/destinations/postgres-destination.js +141 -0
- package/app/destinations/sql-file-destination.js +68 -0
- package/app/lib/index.js +103 -0
- package/app/lib/row-meta-data.js +24 -0
- package/app/loaders/csv-loader.js +58 -0
- package/app/loaders/index.js +5 -0
- package/app/misc/README.md +55 -0
- package/app/misc/index.js +5 -0
- package/app/misc/postgres-sql-task.js +65 -0
- package/app/transformers/README.md +102 -0
- package/app/transformers/faker-transformer.js +48 -0
- package/app/transformers/index.js +9 -0
- package/app/transformers/string-replace-transformer.js +38 -0
- package/app/transformers/to-upper-case-transformer.js +29 -0
- package/app/validators/README.md +81 -0
- package/app/validators/index.js +9 -0
- package/app/validators/multi-tool-validator.js +50 -0
- package/app/validators/required-validator.js +35 -0
- package/app/validators/unique-validator.js +46 -0
- package/azure-pipelines.yml +45 -0
- package/examples/additional-tasks.js +127 -0
- package/examples/csv-faker.js +30 -0
- package/examples/csv-find-replace.js +97 -0
- package/examples/csv-multi-tool-validator.js +38 -0
- package/examples/csv-required-validator.js +27 -0
- package/examples/csv-to-csv.js +24 -0
- package/examples/csv-to-postgres.js +36 -0
- package/examples/csv-to-sqlfile.js +31 -0
- package/examples/csv-to-upper-case.js +25 -0
- package/examples/csv-unique-validator.js +27 -0
- package/examples/index.js +26 -0
- package/examples/sql-return-values.js +37 -0
- package/index.js +3 -1
- package/jest.config.js +42 -0
- package/jest.setup.js +2 -0
- package/package-lock.json +7992 -0
- package/package.json +22 -66
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const fs = require('node:fs')
|
|
3
|
+
const { Transform } = require('node:stream')
|
|
4
|
+
const { parse } = require('csv-parse')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {String} [options.path]
|
|
10
|
+
* @param {ReadableStream} [options.stream]
|
|
11
|
+
* @param {Number} [options.startingLine]
|
|
12
|
+
* @param {Boolean} [options.relax]
|
|
13
|
+
* @returns StreamReader
|
|
14
|
+
*/
|
|
15
|
+
function csvLoader (options) {
|
|
16
|
+
let loader
|
|
17
|
+
if (options.path) {
|
|
18
|
+
loader = fs.createReadStream(options.path)
|
|
19
|
+
} else {
|
|
20
|
+
loader = options.stream
|
|
21
|
+
}
|
|
22
|
+
let lineCount = 1
|
|
23
|
+
const fromLine = options.startingLine ?? 2
|
|
24
|
+
const relaxQuotes = options.relax ?? false
|
|
25
|
+
loader._columns = options.columns
|
|
26
|
+
loader.pump = () => {
|
|
27
|
+
const parser = parse({ delimiter: ',', from_line: fromLine, relax_quotes: relaxQuotes })
|
|
28
|
+
const transformer = new Transform({
|
|
29
|
+
readableObjectMode: true,
|
|
30
|
+
writableObjectMode: true,
|
|
31
|
+
emitClose: true,
|
|
32
|
+
transform (chunk, _, callback) {
|
|
33
|
+
chunk['_columns'] = options.columns
|
|
34
|
+
chunk['_linecount'] = lineCount
|
|
35
|
+
lineCount += 1
|
|
36
|
+
|
|
37
|
+
// remove non-printable characters
|
|
38
|
+
options.columns.forEach((_column, index) => {
|
|
39
|
+
if (chunk[index]) {
|
|
40
|
+
// eslint-disable-next-line no-control-regex
|
|
41
|
+
chunk[index] = chunk[index].replaceAll(/[\x00-\x1F\x7F-\x9F]/g, '')
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
callback(null, chunk)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
return loader
|
|
49
|
+
.pipe(parser)
|
|
50
|
+
.pipe(transformer)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return loader
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
CSVLoader: csvLoader
|
|
58
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Miscellanous pipeline items
|
|
2
|
+
|
|
3
|
+
## PostgresSQLTask
|
|
4
|
+
|
|
5
|
+
Executes an arbitrary sql task either as part of another transformation or as an event
|
|
6
|
+
|
|
7
|
+
### Example
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const {
|
|
11
|
+
Connections,
|
|
12
|
+
Destinations,
|
|
13
|
+
Etl,
|
|
14
|
+
Loaders,
|
|
15
|
+
Misc,
|
|
16
|
+
Validators,
|
|
17
|
+
Transformers,
|
|
18
|
+
} = require("ffc-pay-etl-framework");
|
|
19
|
+
|
|
20
|
+
const etl = new Etl.Etl();
|
|
21
|
+
|
|
22
|
+
etl
|
|
23
|
+
.connections(
|
|
24
|
+
new Connections.PostgresSQLConnection({
|
|
25
|
+
connectionname: "MyConnection",
|
|
26
|
+
database: "MyDB",
|
|
27
|
+
username: "MyUserName",
|
|
28
|
+
password: "MyPassword",
|
|
29
|
+
host: "localhost",
|
|
30
|
+
port: 5432,
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
.beforeETL(
|
|
34
|
+
new Misc.PostgresSQLTask({
|
|
35
|
+
connection: "MyConnection",
|
|
36
|
+
sql: "INSERT INTO MyTable (column1, column2) VALUES ('apples','oranges');",
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
.loader(new Loaders.CSVLoader({ path: csvFile, columns: columns }))
|
|
40
|
+
.destination(
|
|
41
|
+
new Destinations.PostgresDestination({
|
|
42
|
+
connection: "MyConnection",
|
|
43
|
+
table: "target",
|
|
44
|
+
mapping: [
|
|
45
|
+
{
|
|
46
|
+
column: "Dist Code",
|
|
47
|
+
targetColumn: "dist_code",
|
|
48
|
+
targetType: "string",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
includeErrors: false,
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
.pump();
|
|
55
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const { PassThrough } = require('node:stream')
|
|
3
|
+
const startPosOffset = 3
|
|
4
|
+
const endPosOffset = 1
|
|
5
|
+
|
|
6
|
+
function getPlaceHolders (sql) {
|
|
7
|
+
const columnNamePlaceholderRegex = /(?<=\$\{)(.*?)(?=})/g
|
|
8
|
+
const matches = Array.from(sql.matchAll(columnNamePlaceholderRegex))
|
|
9
|
+
return matches.map(match => ({
|
|
10
|
+
match: match[0],
|
|
11
|
+
value: match[0].split('.')[1],
|
|
12
|
+
collection: match[0].split('.')[0],
|
|
13
|
+
startPos: match.index - startPosOffset,
|
|
14
|
+
endPos: match.index + match[0].length - endPosOffset
|
|
15
|
+
}))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function doPlaceHolderValueInterpolations (chunk, sql, placeholders) {
|
|
19
|
+
placeholders.forEach(placeholder => {
|
|
20
|
+
sql = sql.replace(new RegExp(`\\$\{${placeholder.match}}`),
|
|
21
|
+
chunk[chunk[`_${placeholder.collection}`].indexOf(placeholder.value)])
|
|
22
|
+
})
|
|
23
|
+
return sql
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function postgresSQLTask (options) {
|
|
27
|
+
const passthrough = new PassThrough({
|
|
28
|
+
readableObjectMode: true,
|
|
29
|
+
writableObjectMode: true,
|
|
30
|
+
decodeStrings: false,
|
|
31
|
+
construct (callback) {
|
|
32
|
+
this.sql = options.sql
|
|
33
|
+
this.connectionname = options.connectionname
|
|
34
|
+
callback()
|
|
35
|
+
},
|
|
36
|
+
transform (chunk, _, callback) {
|
|
37
|
+
const placeholders = getPlaceHolders(this.sql)
|
|
38
|
+
if (placeholders.length === 0) {
|
|
39
|
+
this.connection.db.query(this.sql)
|
|
40
|
+
} else {
|
|
41
|
+
const interpolatedSql = doPlaceHolderValueInterpolations(chunk, this.sql, placeholders)
|
|
42
|
+
this.connection.db.query(interpolatedSql)
|
|
43
|
+
}
|
|
44
|
+
callback(null, chunk)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
Object.assign(passthrough, {
|
|
48
|
+
setConnection: function (connection) {
|
|
49
|
+
this.connection = connection
|
|
50
|
+
}.bind(passthrough),
|
|
51
|
+
getConnectionName: function () {
|
|
52
|
+
return this.connection.name
|
|
53
|
+
}.bind(passthrough),
|
|
54
|
+
setETL: function (etl) {
|
|
55
|
+
this.etl = etl
|
|
56
|
+
}.bind(passthrough)
|
|
57
|
+
})
|
|
58
|
+
return passthrough
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
PostgresSQLTask: postgresSQLTask,
|
|
63
|
+
getPlaceHolders,
|
|
64
|
+
doPlaceHolderValueInterpolations
|
|
65
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Transformers
|
|
2
|
+
|
|
3
|
+
Transformers manipulate row data and either replace the existing values or add new columns to each row depending on the type of transformer and the configuration.
|
|
4
|
+
|
|
5
|
+
Currently supported transformers are :
|
|
6
|
+
|
|
7
|
+
- ToUpperCaseTransformer
|
|
8
|
+
- FakerTransformer
|
|
9
|
+
|
|
10
|
+
## FakerTransformer
|
|
11
|
+
|
|
12
|
+
### Options
|
|
13
|
+
|
|
14
|
+
| option | description |
|
|
15
|
+
| ------- | ------------------------------------------------------------ |
|
|
16
|
+
| columns | Array of column transformations |
|
|
17
|
+
| locale | See https://fakerjs.dev/guide/localization for valid locales |
|
|
18
|
+
|
|
19
|
+
### Usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
FakerTransformer({
|
|
23
|
+
columns: [
|
|
24
|
+
{
|
|
25
|
+
name: "column2",
|
|
26
|
+
faker: "company.name",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
faker is a string value that can be any of the currently supported [apis](https://fakerjs.dev/api/)
|
|
33
|
+
|
|
34
|
+
e.g.
|
|
35
|
+
|
|
36
|
+
- finance.accountName
|
|
37
|
+
- person.firstName
|
|
38
|
+
- randomizer.next
|
|
39
|
+
|
|
40
|
+
To create UK post code
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
FakerTransformer({
|
|
44
|
+
columns: [
|
|
45
|
+
{
|
|
46
|
+
name: "column2",
|
|
47
|
+
faker: "location.zipCode",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
locale: "en_GB",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Example
|
|
55
|
+
|
|
56
|
+
examples/csv-faker.js
|
|
57
|
+
|
|
58
|
+
## ToUpperCaseTransformer
|
|
59
|
+
|
|
60
|
+
### Options
|
|
61
|
+
|
|
62
|
+
| option | description |
|
|
63
|
+
| ------ | ------------------------------- |
|
|
64
|
+
| column | source column to be transformed |
|
|
65
|
+
|
|
66
|
+
### Usage
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
ToUpperCaseTransformer({
|
|
70
|
+
column: "column2",
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Example
|
|
75
|
+
|
|
76
|
+
examples/csv-to-upper-case.js
|
|
77
|
+
|
|
78
|
+
## StringReplaceTransformer
|
|
79
|
+
|
|
80
|
+
### Options
|
|
81
|
+
|
|
82
|
+
| option | description |
|
|
83
|
+
| ------- | --------------------------------------- |
|
|
84
|
+
| column | source column to be transformed |
|
|
85
|
+
| find | search string to be replaced |
|
|
86
|
+
| replace | value to replace the search string with |
|
|
87
|
+
|
|
88
|
+
### Usage
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
StringReplaceTransformer([
|
|
92
|
+
{
|
|
93
|
+
column: "column2",
|
|
94
|
+
find: "'",
|
|
95
|
+
replace: "''",
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Example
|
|
101
|
+
|
|
102
|
+
examples/csv-find-replace.js
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { Transform } = require('node:stream')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} options
|
|
6
|
+
* @param {Array} options.columns {
|
|
7
|
+
* columns: [
|
|
8
|
+
* {
|
|
9
|
+
* name: "column-name",
|
|
10
|
+
* faker: "company.name"
|
|
11
|
+
* }
|
|
12
|
+
* ]
|
|
13
|
+
* }
|
|
14
|
+
* @param {String} options.locale
|
|
15
|
+
* @returns StreamReader
|
|
16
|
+
*/
|
|
17
|
+
function fakerTransformer (options) {
|
|
18
|
+
const columns = options.columns
|
|
19
|
+
let faker
|
|
20
|
+
if (options.locale) {
|
|
21
|
+
faker = require(`@faker-js/faker/locale/${options.locale}`).faker
|
|
22
|
+
} else {
|
|
23
|
+
faker = require('@faker-js/faker').faker
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getFaker (fakerType) {
|
|
27
|
+
return fakerType.split('.').reduce((a, b) => {
|
|
28
|
+
return Object.keys(a).length === 0 ? faker[b] : a[b]
|
|
29
|
+
}, {})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Transform({
|
|
33
|
+
readableObjectMode: true,
|
|
34
|
+
writableObjectMode: true,
|
|
35
|
+
transform (chunk, _, callback) {
|
|
36
|
+
const { _columns } = chunk
|
|
37
|
+
columns.forEach(column => {
|
|
38
|
+
const colIndex = _columns.indexOf(column.name)
|
|
39
|
+
chunk[colIndex] = getFaker(column.faker)()
|
|
40
|
+
})
|
|
41
|
+
callback(null, chunk)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
FakerTransformer: fakerTransformer
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { ToUpperCaseTransformer } = require('./to-upper-case-transformer')
|
|
2
|
+
const { FakerTransformer } = require('./faker-transformer')
|
|
3
|
+
const { StringReplaceTransformer } = require('./string-replace-transformer')
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
StringReplaceTransformer,
|
|
7
|
+
ToUpperCaseTransformer,
|
|
8
|
+
FakerTransformer
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { Transform } = require('node:stream')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} options
|
|
6
|
+
* @param {String} options.column
|
|
7
|
+
* @returns Writable
|
|
8
|
+
*/
|
|
9
|
+
function stringReplaceTransformer (options) {
|
|
10
|
+
const replacements = options
|
|
11
|
+
|
|
12
|
+
return new Transform({
|
|
13
|
+
readableObjectMode: true,
|
|
14
|
+
writableObjectMode: true,
|
|
15
|
+
transform (chunk, _, callback) {
|
|
16
|
+
const { _columns } = chunk
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
replacements.forEach(r => {
|
|
19
|
+
const colIndex = _columns.indexOf(r.column)
|
|
20
|
+
if (colIndex === -1) {
|
|
21
|
+
// column not found, skip
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
if (r.all) {
|
|
25
|
+
chunk[colIndex] = chunk[colIndex].replaceAll(r.find, r.replace)
|
|
26
|
+
} else {
|
|
27
|
+
chunk[colIndex] = chunk[colIndex].replace(r.find, r.replace)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
callback(null, chunk)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
StringReplaceTransformer: stringReplaceTransformer
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { Transform } = require('node:stream')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} options
|
|
6
|
+
* @param {String} options.column
|
|
7
|
+
* @returns Writable
|
|
8
|
+
*/
|
|
9
|
+
function toUpperCaseTransformer (options) {
|
|
10
|
+
const column = options.column
|
|
11
|
+
|
|
12
|
+
return new Transform({
|
|
13
|
+
readableObjectMode: true,
|
|
14
|
+
writableObjectMode: true,
|
|
15
|
+
transform (chunk, _, callback) {
|
|
16
|
+
const { _columns } = chunk
|
|
17
|
+
const colIndex = _columns.indexOf(column)
|
|
18
|
+
|
|
19
|
+
if (colIndex !== -1 && chunk[colIndex] != null) {
|
|
20
|
+
chunk[colIndex] = chunk[colIndex].toUpperCase()
|
|
21
|
+
}
|
|
22
|
+
callback(null, chunk)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
ToUpperCaseTransformer: toUpperCaseTransformer
|
|
29
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Validators
|
|
2
|
+
|
|
3
|
+
Validators are expected to check specific columns within each row against specific validation rules
|
|
4
|
+
|
|
5
|
+
Currently supported destinations are :
|
|
6
|
+
|
|
7
|
+
- RequiredValidator
|
|
8
|
+
- UniqueValidator
|
|
9
|
+
- MultiToolValidator
|
|
10
|
+
|
|
11
|
+
## RequiredValidator
|
|
12
|
+
|
|
13
|
+
### Options
|
|
14
|
+
|
|
15
|
+
| option | description |
|
|
16
|
+
| -------------- | ------------------------------------------------------------- |
|
|
17
|
+
| columns | Array of column indexes |
|
|
18
|
+
|
|
19
|
+
### Usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
Validators.RequiredValidator({
|
|
23
|
+
columns: [0]
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Example
|
|
28
|
+
|
|
29
|
+
examples/csv-required-validator.js
|
|
30
|
+
|
|
31
|
+
## UniqueValidator
|
|
32
|
+
|
|
33
|
+
### Options
|
|
34
|
+
|
|
35
|
+
| option | description |
|
|
36
|
+
| -------------- | ------------------------------------------------------------- |
|
|
37
|
+
| column | Column to be tested for uniqueness |
|
|
38
|
+
|
|
39
|
+
### Usage
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
Validators.UniqueValidator(
|
|
43
|
+
{ column: "field_one" }
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Example
|
|
48
|
+
|
|
49
|
+
examples/csv-unique-validator.js
|
|
50
|
+
|
|
51
|
+
## MultiToolValidator
|
|
52
|
+
|
|
53
|
+
### Options
|
|
54
|
+
|
|
55
|
+
| option | description |
|
|
56
|
+
| -------------- | ------------------------------------------------------------- |
|
|
57
|
+
| column | Column to be tested for validity and validator specification |
|
|
58
|
+
|
|
59
|
+
NB Any validators specified [here](https://github.com/validatorjs/validator.js) can be used
|
|
60
|
+
|
|
61
|
+
### Usage
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
Validators.MultiToolValidator( { columns: [
|
|
65
|
+
{
|
|
66
|
+
name: "SOIL TYPE PERCENT (Percent)",
|
|
67
|
+
validator: "isAlphanumeric",
|
|
68
|
+
args: {
|
|
69
|
+
locale: "en-GB",
|
|
70
|
+
options: {
|
|
71
|
+
ignore: "-;%/ "
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Example
|
|
80
|
+
|
|
81
|
+
examples/csv-multi-tool-validator.js
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { RequiredValidator } = require('./required-validator')
|
|
2
|
+
const { UniqueValidator } = require('./unique-validator')
|
|
3
|
+
const { MultiToolValidator } = require('./multi-tool-validator')
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
RequiredValidator,
|
|
7
|
+
UniqueValidator,
|
|
8
|
+
MultiToolValidator
|
|
9
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const { Transform } = require('node:stream')
|
|
3
|
+
const validator = require('validator')
|
|
4
|
+
|
|
5
|
+
function getValidator (validatorType) {
|
|
6
|
+
return validator[validatorType]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {Array} options.columns { columns: [
|
|
13
|
+
* {
|
|
14
|
+
* name: "column-name",
|
|
15
|
+
* validator: "isAlphanumeric",
|
|
16
|
+
* args: {
|
|
17
|
+
* locale: "en-GB",
|
|
18
|
+
* options: {
|
|
19
|
+
* ignore: "-;%/ "
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ]
|
|
24
|
+
* }
|
|
25
|
+
* @returns Writable
|
|
26
|
+
*/
|
|
27
|
+
function multiToolValidator (options) {
|
|
28
|
+
return new Transform({
|
|
29
|
+
readableObjectMode: true,
|
|
30
|
+
writableObjectMode: true,
|
|
31
|
+
decodeStrings: false,
|
|
32
|
+
construct (callback) {
|
|
33
|
+
this.columns = options.columns
|
|
34
|
+
callback()
|
|
35
|
+
},
|
|
36
|
+
transform (chunk, _, callback) {
|
|
37
|
+
const { _columns } = chunk
|
|
38
|
+
this.columns.forEach(column => {
|
|
39
|
+
const colIndex = _columns.indexOf(column.name)
|
|
40
|
+
const vtr = getValidator(column.validator)
|
|
41
|
+
if (!vtr(chunk[colIndex], ...Object.values(column.args))) { chunk.errors.push(`${column.validator} returned invalid status in column ${colIndex}`) }
|
|
42
|
+
})
|
|
43
|
+
callback(null, chunk)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
MultiToolValidator: multiToolValidator
|
|
50
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const { Transform } = require('node:stream')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} options
|
|
7
|
+
* @param {String} options.message
|
|
8
|
+
* @param {Array} options.columns
|
|
9
|
+
* @returns Transform
|
|
10
|
+
*/
|
|
11
|
+
function requiredValidator (options) {
|
|
12
|
+
return new Transform({
|
|
13
|
+
readableObjectMode: true,
|
|
14
|
+
writableObjectMode: true,
|
|
15
|
+
decodeStrings: false,
|
|
16
|
+
construct (callback) {
|
|
17
|
+
this.message = options.message ? options.message : 'Required value is null'
|
|
18
|
+
this.requiredColumns = options.columns
|
|
19
|
+
callback()
|
|
20
|
+
},
|
|
21
|
+
transform (chunk, _, callback) {
|
|
22
|
+
this.requiredColumns.forEach(column => {
|
|
23
|
+
const requiredVal = chunk[column]
|
|
24
|
+
if (requiredVal === undefined || requiredVal === '') {
|
|
25
|
+
chunk.errors.push(`${this.message} in column ${column}`)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
callback(null, chunk)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
RequiredValidator: requiredValidator
|
|
35
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { Transform } = require('node:stream')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} options
|
|
6
|
+
* @param {String} options.message
|
|
7
|
+
* @param {String} options.column
|
|
8
|
+
* @returns Transform
|
|
9
|
+
*/
|
|
10
|
+
function uniqueValidator (options) {
|
|
11
|
+
const column = options.column
|
|
12
|
+
const message = options.message || 'Expected unique value is not unique'
|
|
13
|
+
const uniqueColumnRows = new Set()
|
|
14
|
+
|
|
15
|
+
function checkIsUnique (data) {
|
|
16
|
+
return !uniqueColumnRows.has(data)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return new Transform({
|
|
20
|
+
readableObjectMode: true,
|
|
21
|
+
writableObjectMode: true,
|
|
22
|
+
decodeStrings: false,
|
|
23
|
+
construct (callback) {
|
|
24
|
+
callback()
|
|
25
|
+
},
|
|
26
|
+
transform (chunk, _, callback) {
|
|
27
|
+
const { _columns } = chunk
|
|
28
|
+
const colIndex = _columns.indexOf(column)
|
|
29
|
+
const uniqueValue = chunk[colIndex]
|
|
30
|
+
|
|
31
|
+
if (checkIsUnique(uniqueValue)) {
|
|
32
|
+
uniqueColumnRows.add(uniqueValue)
|
|
33
|
+
} else {
|
|
34
|
+
if (!chunk.errors) {
|
|
35
|
+
chunk.errors = []
|
|
36
|
+
}
|
|
37
|
+
chunk.errors.push(`${message} ${column}`)
|
|
38
|
+
}
|
|
39
|
+
callback(null, chunk)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
UniqueValidator: uniqueValidator
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Pipeline name convention
|
|
2
|
+
name: '$(Build.SourceBranchName)$(System.PullRequest.SourceBranch)$(Rev:.r)'
|
|
3
|
+
|
|
4
|
+
# Trigger configuration
|
|
5
|
+
trigger:
|
|
6
|
+
- main
|
|
7
|
+
- feature/*
|
|
8
|
+
- release/*
|
|
9
|
+
|
|
10
|
+
pr:
|
|
11
|
+
- main
|
|
12
|
+
- releases/*
|
|
13
|
+
|
|
14
|
+
# Parameters
|
|
15
|
+
parameters:
|
|
16
|
+
- name: prDeployment
|
|
17
|
+
type: boolean
|
|
18
|
+
default: true
|
|
19
|
+
- name: branchDeployment
|
|
20
|
+
type: boolean
|
|
21
|
+
default: true
|
|
22
|
+
- name: sndDeployment
|
|
23
|
+
type: boolean
|
|
24
|
+
default: true
|
|
25
|
+
|
|
26
|
+
# Resources
|
|
27
|
+
resources:
|
|
28
|
+
repositories:
|
|
29
|
+
- repository: templates
|
|
30
|
+
endpoint: DEFRA
|
|
31
|
+
type: github
|
|
32
|
+
name: defra/fcp-pipeline-common
|
|
33
|
+
# ref: pl-709 # Optional: build specific pipeline branch
|
|
34
|
+
|
|
35
|
+
# Pipeline template
|
|
36
|
+
extends:
|
|
37
|
+
template: fcp-common-pipeline.yaml@templates
|
|
38
|
+
parameters:
|
|
39
|
+
appFrameworkType: nodejs # Optional: nodejs (default), dotnet, java
|
|
40
|
+
Type: npm # Optional: kubernetes (default), npm, functions, nuget
|
|
41
|
+
npmLegacyPeerDeps: false # Optional: default is false
|
|
42
|
+
failOnThreshold: moderate # Optional: moderate (default), low, high, critical
|
|
43
|
+
prDeployment: ${{ parameters.prDeployment }}
|
|
44
|
+
branchDeployment: ${{ parameters.branchDeployment }}
|
|
45
|
+
sndDeployment: ${{ parameters.sndDeployment }}
|