@tahminator/pipeline 1.0.59-beta.4cc29c8c → 1.0.59-beta.7832b36f
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/README.md +83 -0
- package/dist/postgres/client.d.ts +26 -0
- package/dist/postgres/client.js +47 -15
- package/dist/redis/client.d.ts +26 -0
- package/dist/redis/client.js +47 -15
- package/dist/utils/client.d.ts +2 -0
- package/dist/utils/client.js +4 -0
- package/dist/utils/wait.d.ts +5 -0
- package/dist/utils/wait.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,9 +42,30 @@ import {
|
|
|
42
42
|
SonarScannerClient,
|
|
43
43
|
Utils,
|
|
44
44
|
VersioningClient,
|
|
45
|
+
LocalPostgresClient,
|
|
46
|
+
LocalRedisClient,
|
|
45
47
|
} from "@tahminator/pipeline";
|
|
46
48
|
```
|
|
47
49
|
|
|
50
|
+
Jump to client documentation
|
|
51
|
+
|
|
52
|
+
<!-- toc -->
|
|
53
|
+
|
|
54
|
+
- [`GitHubClient`](#githubclient)
|
|
55
|
+
- [`DockerClient`](#dockerclient)
|
|
56
|
+
- [`NPMClient`](#npmclient)
|
|
57
|
+
- [`SonarScannerClient`](#sonarscannerclient)
|
|
58
|
+
- [`Utils`](#utils)
|
|
59
|
+
- [`EnvClient`](#envclient)
|
|
60
|
+
- [`PulumiClient`](#pulumiclient)
|
|
61
|
+
- [`VersioningClient`](#versioningclient)
|
|
62
|
+
- [`LocalPostgresClient`](#localpostgresclient)
|
|
63
|
+
- [`LocalRedisClient`](#localredisclient)
|
|
64
|
+
|
|
65
|
+
<!-- tocstop -->
|
|
66
|
+
|
|
67
|
+
<!-- if toc does not update automatically, `markdown-toc -i README.md` -->
|
|
68
|
+
|
|
48
69
|
> [!NOTE]
|
|
49
70
|
> While there is documentation below, each API should be relatively well commented and be more up to date than what is seen below.
|
|
50
71
|
|
|
@@ -328,3 +349,65 @@ if (!Utils.SemVer.validate(beta)) throw new Error("invalid beta version");
|
|
|
328
349
|
|
|
329
350
|
await versioning.update(beta);
|
|
330
351
|
```
|
|
352
|
+
|
|
353
|
+
### `LocalPostgresClient`
|
|
354
|
+
|
|
355
|
+
Spin up a disposable local Postgres instance via Docker. Useful for running migrations or acceptance tests against a real database in CI / local dev.
|
|
356
|
+
|
|
357
|
+
Each call to `create()` gets a unique container name and a Docker-assigned host port, so multiple instances can coexist without conflict.
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { $ } from "bun";
|
|
361
|
+
|
|
362
|
+
async function main() {
|
|
363
|
+
await using pgClient = await LocalPostgresClient.create({
|
|
364
|
+
database: "instalock-server-acceptance",
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// get credentials
|
|
368
|
+
const { database, host, port, password, user } = pgClient.state;
|
|
369
|
+
|
|
370
|
+
await $.env({
|
|
371
|
+
...process.env,
|
|
372
|
+
DB_HOST: host,
|
|
373
|
+
DB_PORT: String(port),
|
|
374
|
+
DB_NAME: database,
|
|
375
|
+
DB_USERNAME: user,
|
|
376
|
+
DB_PASSWORD: password,
|
|
377
|
+
})`just migrate`;
|
|
378
|
+
|
|
379
|
+
// `pgClient` will automatically be cleaned up when `main` returns.
|
|
380
|
+
// Set DEBUG=true in the env to dump container logs on cleanup.
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await main();
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### `LocalRedisClient`
|
|
387
|
+
|
|
388
|
+
Spin up a disposable local Redis instance via Docker.
|
|
389
|
+
|
|
390
|
+
Each call to `create()` gets a unique container name and a Docker-assigned host port, so multiple instances can coexist without conflict.
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
import { $ } from "bun";
|
|
394
|
+
|
|
395
|
+
async function main() {
|
|
396
|
+
await using redisClient = await LocalRedisClient.create();
|
|
397
|
+
|
|
398
|
+
// get credentials
|
|
399
|
+
const { port, password, host } = redisClient.state;
|
|
400
|
+
|
|
401
|
+
await $.env({
|
|
402
|
+
...process.env,
|
|
403
|
+
REDIS_HOST: host,
|
|
404
|
+
REDIS_PORT: String(port),
|
|
405
|
+
REDIS_PASSWORD: password,
|
|
406
|
+
})`./gradlew bootRun`;
|
|
407
|
+
|
|
408
|
+
// `redisClient` will automatically be cleaned up when `main` returns.
|
|
409
|
+
// Set DEBUG=true in the env to dump container logs on cleanup.
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await main();
|
|
413
|
+
```
|
|
@@ -2,6 +2,32 @@ import type { PostgresInternalState, PostgresState } from "./types";
|
|
|
2
2
|
export declare class LocalPostgresClient {
|
|
3
3
|
private readonly iState;
|
|
4
4
|
private constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Spin up a local Postgres instance via Docker.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
async function main() {
|
|
11
|
+
await using pgClient = await LocalPostgresClient.create({
|
|
12
|
+
database: "instalock-server-acceptance",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// get credentials
|
|
16
|
+
const { database, host, port, password, user } = pgClient.state;
|
|
17
|
+
|
|
18
|
+
$.env({
|
|
19
|
+
...process.env,
|
|
20
|
+
DB_HOST: host,
|
|
21
|
+
DB_PORT: String(port),
|
|
22
|
+
DB_NAME: database,
|
|
23
|
+
DB_USERNAME: user,
|
|
24
|
+
DB_PASSWORD: password,
|
|
25
|
+
})`just migrate`
|
|
26
|
+
|
|
27
|
+
// `pgClient` will be automatically cleaned up at the end of the scope.
|
|
28
|
+
}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
5
31
|
static create(state: PostgresState): Promise<LocalPostgresClient>;
|
|
6
32
|
private static launch;
|
|
7
33
|
private static waitUntilReady;
|
package/dist/postgres/client.js
CHANGED
|
@@ -5,6 +5,32 @@ export class LocalPostgresClient {
|
|
|
5
5
|
constructor(iState) {
|
|
6
6
|
this.iState = iState;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Spin up a local Postgres instance via Docker.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
async function main() {
|
|
14
|
+
await using pgClient = await LocalPostgresClient.create({
|
|
15
|
+
database: "instalock-server-acceptance",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// get credentials
|
|
19
|
+
const { database, host, port, password, user } = pgClient.state;
|
|
20
|
+
|
|
21
|
+
$.env({
|
|
22
|
+
...process.env,
|
|
23
|
+
DB_HOST: host,
|
|
24
|
+
DB_PORT: String(port),
|
|
25
|
+
DB_NAME: database,
|
|
26
|
+
DB_USERNAME: user,
|
|
27
|
+
DB_PASSWORD: password,
|
|
28
|
+
})`just migrate`
|
|
29
|
+
|
|
30
|
+
// `pgClient` will be automatically cleaned up at the end of the scope.
|
|
31
|
+
}
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
8
34
|
static async create(state) {
|
|
9
35
|
const iState = {
|
|
10
36
|
...state,
|
|
@@ -37,20 +63,26 @@ export class LocalPostgresClient {
|
|
|
37
63
|
static async waitUntilReady(iState) {
|
|
38
64
|
console.log(`Waiting for ${iState.dockerName} to become ready.`);
|
|
39
65
|
const attempts = 30;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
const ready = await Utils.waitUntil({
|
|
67
|
+
attempts,
|
|
68
|
+
intervalMs: 2000,
|
|
69
|
+
predicate: async (attempt) => {
|
|
70
|
+
const check = await $ `docker exec ${iState.dockerName} pg_isready -U ${iState.user}`
|
|
71
|
+
.quiet()
|
|
72
|
+
.nothrow();
|
|
73
|
+
if (check.exitCode === 0) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
console.log(`Waiting for ${iState.dockerName}... (${attempt}/${attempts})`);
|
|
77
|
+
return false;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (!ready) {
|
|
81
|
+
const msg = `${iState.dockerName} failed to launch`;
|
|
82
|
+
console.error(msg);
|
|
83
|
+
throw new Error(msg);
|
|
50
84
|
}
|
|
51
|
-
|
|
52
|
-
console.error(msg);
|
|
53
|
-
throw new Error(msg);
|
|
85
|
+
console.log(`${iState.dockerName} is ready`);
|
|
54
86
|
}
|
|
55
87
|
get state() {
|
|
56
88
|
return this.iState;
|
|
@@ -61,13 +93,13 @@ export class LocalPostgresClient {
|
|
|
61
93
|
async cleanup() {
|
|
62
94
|
console.log(`Stopping and removing ${this.iState.dockerName} container...`);
|
|
63
95
|
if (Utils.Log.isDebug) {
|
|
64
|
-
console.log(Utils.Colors.brightMagenta(
|
|
96
|
+
console.log(Utils.Colors.brightMagenta(`=== ${this.iState.dockerName} LOGS ===`));
|
|
65
97
|
const logs = await $ `docker logs ${this.iState.dockerName}`.text();
|
|
66
98
|
logs
|
|
67
99
|
.split("\n")
|
|
68
100
|
.filter((s) => s.length > 0)
|
|
69
101
|
.forEach((line) => console.log(Utils.Colors.brightMagenta(line)));
|
|
70
|
-
console.log(Utils.Colors.brightMagenta(
|
|
102
|
+
console.log(Utils.Colors.brightMagenta(`=== ${this.iState.dockerName} LOGS END ===`));
|
|
71
103
|
}
|
|
72
104
|
await $ `docker stop ${this.iState.dockerName}`.quiet().nothrow();
|
|
73
105
|
await $ `docker rm ${this.iState.dockerName}`.quiet().nothrow();
|
package/dist/redis/client.d.ts
CHANGED
|
@@ -2,6 +2,32 @@ import type { RedisInternalState, RedisState } from "./types";
|
|
|
2
2
|
export declare class LocalRedisClient {
|
|
3
3
|
private readonly iState;
|
|
4
4
|
private constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Spin up a local Redis instance via Docker.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
async function main() {
|
|
11
|
+
await using redisClient = await LocalRedisClient.create();
|
|
12
|
+
|
|
13
|
+
// get credentials
|
|
14
|
+
const {
|
|
15
|
+
port,
|
|
16
|
+
password,
|
|
17
|
+
host,
|
|
18
|
+
} = redisClient.state;
|
|
19
|
+
|
|
20
|
+
$.env({
|
|
21
|
+
...process.env,
|
|
22
|
+
REDISHOST: host,
|
|
23
|
+
REDIS_PORT: String(port),
|
|
24
|
+
REDIS_PASSWORD: password,
|
|
25
|
+
})`./gradlew bootRun`
|
|
26
|
+
|
|
27
|
+
// `redisClient` will be automatically cleaned up at the end of the scope.
|
|
28
|
+
}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
5
31
|
static create(state?: RedisState): Promise<LocalRedisClient>;
|
|
6
32
|
private static launch;
|
|
7
33
|
private static waitUntilReady;
|
package/dist/redis/client.js
CHANGED
|
@@ -5,6 +5,32 @@ export class LocalRedisClient {
|
|
|
5
5
|
constructor(iState) {
|
|
6
6
|
this.iState = iState;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Spin up a local Redis instance via Docker.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
async function main() {
|
|
14
|
+
await using redisClient = await LocalRedisClient.create();
|
|
15
|
+
|
|
16
|
+
// get credentials
|
|
17
|
+
const {
|
|
18
|
+
port,
|
|
19
|
+
password,
|
|
20
|
+
host,
|
|
21
|
+
} = redisClient.state;
|
|
22
|
+
|
|
23
|
+
$.env({
|
|
24
|
+
...process.env,
|
|
25
|
+
REDISHOST: host,
|
|
26
|
+
REDIS_PORT: String(port),
|
|
27
|
+
REDIS_PASSWORD: password,
|
|
28
|
+
})`./gradlew bootRun`
|
|
29
|
+
|
|
30
|
+
// `redisClient` will be automatically cleaned up at the end of the scope.
|
|
31
|
+
}
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
8
34
|
static async create(state = {}) {
|
|
9
35
|
const iState = {
|
|
10
36
|
...state,
|
|
@@ -34,20 +60,26 @@ export class LocalRedisClient {
|
|
|
34
60
|
static async waitUntilReady(iState) {
|
|
35
61
|
console.log(`Waiting for ${iState.dockerName} to become ready.`);
|
|
36
62
|
const attempts = 30;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
const ready = await Utils.waitUntil({
|
|
64
|
+
attempts,
|
|
65
|
+
intervalMs: 2000,
|
|
66
|
+
predicate: async (attempt) => {
|
|
67
|
+
const check = await $ `docker exec ${iState.dockerName} redis-cli -a ${iState.password} ping`
|
|
68
|
+
.quiet()
|
|
69
|
+
.nothrow();
|
|
70
|
+
if (check.exitCode === 0) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
console.log(`Waiting for ${iState.dockerName}... (${attempt}/${attempts})`);
|
|
74
|
+
return false;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
if (!ready) {
|
|
78
|
+
const msg = `${iState.dockerName} failed to launch`;
|
|
79
|
+
console.error(msg);
|
|
80
|
+
throw new Error(msg);
|
|
47
81
|
}
|
|
48
|
-
|
|
49
|
-
console.error(msg);
|
|
50
|
-
throw new Error(msg);
|
|
82
|
+
console.log(`${iState.dockerName} is ready`);
|
|
51
83
|
}
|
|
52
84
|
get state() {
|
|
53
85
|
return this.iState;
|
|
@@ -58,13 +90,13 @@ export class LocalRedisClient {
|
|
|
58
90
|
async cleanup() {
|
|
59
91
|
console.log(`Stopping and removing ${this.iState.dockerName} container...`);
|
|
60
92
|
if (Utils.Log.isDebug) {
|
|
61
|
-
console.log(Utils.Colors.brightMagenta(
|
|
93
|
+
console.log(Utils.Colors.brightMagenta(`=== ${this.iState.dockerName} LOGS ===`));
|
|
62
94
|
const logs = await $ `docker logs ${this.iState.dockerName}`.text();
|
|
63
95
|
logs
|
|
64
96
|
.split("\n")
|
|
65
97
|
.filter((s) => s.length > 0)
|
|
66
98
|
.forEach((line) => console.log(Utils.Colors.brightMagenta(line)));
|
|
67
|
-
console.log(Utils.Colors.brightMagenta(
|
|
99
|
+
console.log(Utils.Colors.brightMagenta(`=== ${this.iState.dockerName} LOGS END ===`));
|
|
68
100
|
}
|
|
69
101
|
await $ `docker stop ${this.iState.dockerName}`.quiet().nothrow();
|
|
70
102
|
await $ `docker rm ${this.iState.dockerName}`.quiet().nothrow();
|
package/dist/utils/client.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Colors } from "./colors";
|
|
|
4
4
|
import { Log } from "./log";
|
|
5
5
|
import { SemVer } from "./semver";
|
|
6
6
|
import { generateShortId } from "./short";
|
|
7
|
+
import { waitUntil } from "./wait";
|
|
7
8
|
export declare class Utils {
|
|
8
9
|
static Colors: typeof Colors;
|
|
9
10
|
static Log: typeof Log;
|
|
@@ -11,4 +12,5 @@ export declare class Utils {
|
|
|
11
12
|
static generateShortId(...args: Parameters<typeof generateShortId>): string;
|
|
12
13
|
static isCmdAvailable(...args: Parameters<typeof isCmdAvailable>): Promise<boolean>;
|
|
13
14
|
static decodeBase64EncodedString(...args: Parameters<typeof decodeBase64EncodedString>): Promise<string>;
|
|
15
|
+
static waitUntil(...args: Parameters<typeof waitUntil>): Promise<boolean>;
|
|
14
16
|
}
|
package/dist/utils/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Colors } from "./colors";
|
|
|
4
4
|
import { Log } from "./log";
|
|
5
5
|
import { SemVer } from "./semver";
|
|
6
6
|
import { generateShortId } from "./short";
|
|
7
|
+
import { waitUntil } from "./wait";
|
|
7
8
|
export class Utils {
|
|
8
9
|
// hoist
|
|
9
10
|
static Colors = Colors;
|
|
@@ -18,4 +19,7 @@ export class Utils {
|
|
|
18
19
|
static async decodeBase64EncodedString(...args) {
|
|
19
20
|
return decodeBase64EncodedString(...args);
|
|
20
21
|
}
|
|
22
|
+
static async waitUntil(...args) {
|
|
23
|
+
return waitUntil(...args);
|
|
24
|
+
}
|
|
21
25
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"author": "Tahmid Ahmed",
|
|
5
5
|
"description": "A collection of Bun shell scripts that can be re-used in various CICD pipelines.",
|
|
6
|
-
"version": "1.0.59-beta.
|
|
6
|
+
"version": "1.0.59-beta.7832b36f",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "git+https://github.com/tahminator/pipeline.git"
|
|
9
9
|
},
|