1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
#!/usr/bin/env node
import {
Either,
getRequiredEnvVars,
type IEither,
LogTraceable,
LogMetricTraceable,
Metric,
prependWith,
TraceUtil,
getStdoutMany,
} from '@emprespresso/pengueno';
import { Bitwarden, getPathOnHost, type SecureNote } from '@emprespresso/ci_worker';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { rmSync } from 'fs';
import { NpmPublishJob } from '@emprespresso/ci_model';
const eitherJob = getRequiredEnvVars(['source', 'registry']).mapRight(
(baseArgs) =>
<NpmPublishJob>{
type: 'npm_publish.js',
arguments: baseArgs,
},
);
const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config));
const READONLY_CREDENTIALS = { username: 'readonly', password: 'readonly' };
const REGISTRY = 'oci.liz.coffee';
const CI_PACKPUB_IMG = 'oci.liz.coffee/emprespresso/ci_packpub_npm:release';
const packPubMetric = Metric.fromName('npm_publish.packpub');
const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('npm_publish'));
await LogMetricTraceable.ofLogTraceable(_logJob)
.flatMap(TraceUtil.withMetricTrace(packPubMetric))
.peek((tEitherJob) => tEitherJob.trace.trace('starting npm packpub job! (⑅˘꒳˘)'))
.map((tEitherJob) =>
tEitherJob.get().flatMapAsync((job) =>
eitherVault.flatMapAsync(async (vault) => {
const eitherKey = await vault.unlock(tEitherJob);
tEitherJob.trace.trace('unlocked vault :3');
return eitherKey.mapRight((key) => ({ job, key, vault }));
}),
),
)
.map(async (tEitherJobVault) =>
(await tEitherJobVault.get()).flatMapAsync(({ job, key, vault }) =>
vault
.fetchSecret<SecureNote>(tEitherJobVault, key, 'npm_auth_token')
.then((e) =>
e
.mapRight(({ notes }) => notes)
.mapRight((token) =>
[
`//${job.arguments.registry}/:_authToken=${token.trim()}`,
`registry=https://${job.arguments.registry}/`,
`always-auth=true`,
].join('\n'),
)
.flatMapAsync((npmRc) => saveToTempFile(npmRc)),
)
.then((e) => e.mapRight((npmRc) => ({ npmRc, job })))
.finally(() => vault.lock(tEitherJobVault, key)),
),
)
.map(async (tEitherJobNpmRc) => {
const jobNpmRc = await tEitherJobNpmRc.get();
return jobNpmRc.flatMapAsync(async ({ job, npmRc }) => {
const [srcMount, npmRcMount] = await Promise.all(
[join(process.cwd(), job.arguments.source), npmRc].map((x) =>
getPathOnHost(x).then((e) => e.right().get()),
),
);
const volumes = [`${srcMount}:/src`, `${npmRcMount}:/etc/npmrc`];
const packPub = [
`docker login --username ${READONLY_CREDENTIALS.username} --password ${READONLY_CREDENTIALS.password} ${REGISTRY}`.split(
' ',
),
].concat([['docker', 'run', ...prependWith(volumes, '-v'), CI_PACKPUB_IMG]]);
tEitherJobNpmRc.trace.trace(`running packpub magic~ (◕ᴗ◕✿) ${packPub}`);
return tEitherJobNpmRc
.move(packPub)
.map((c) =>
getStdoutMany(c, { streamTraceable: ['stdout', 'stderr'] }).then((e) => {
rmSync(npmRcMount!);
return e;
}),
)
.get();
});
})
.map(async (tEitherJob) => {
const eitherJob = await tEitherJob.get();
return eitherJob.fold(
(e) => Promise.reject(e),
() => Promise.resolve(0),
);
})
.get();
async function saveToTempFile(text: string): Promise<IEither<Error, string>> {
const dir = join(process.cwd(), '.secrets', crypto.randomUUID());
const file = join(dir, 'secret');
return Either.fromFailableAsync(() =>
mkdir(dir, { recursive: true }).then(async () => {
await writeFile(file, text, { encoding: 'utf-8' });
return file;
}),
);
}
|