@enspirit/emb 0.4.0 → 0.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/README.md +1 -1
- package/dist/src/cli/commands/up.js +0 -3
- package/dist/src/docker/operations/images/BuildImageOperation.d.ts +7 -2
- package/dist/src/docker/operations/images/BuildImageOperation.js +61 -9
- package/dist/src/docker/operations/images/PushImagesOperation.d.ts +14 -0
- package/dist/src/docker/operations/images/PushImagesOperation.js +22 -0
- package/dist/src/docker/resources/DockerImageResource.js +24 -25
- package/dist/src/errors.d.ts +5 -0
- package/dist/src/errors.js +9 -0
- package/dist/src/monorepo/monorepo.d.ts +3 -1
- package/dist/src/monorepo/monorepo.js +5 -2
- package/dist/src/monorepo/operations/fs/CreateFileOperation.d.ts +3 -1
- package/dist/src/monorepo/operations/fs/CreateFileOperation.js +3 -1
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.d.ts +3 -6
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.js +20 -31
- package/dist/src/monorepo/operations/shell/ExecuteLocalCommandOperation.d.ts +2 -2
- package/dist/src/monorepo/operations/shell/ExecuteLocalCommandOperation.js +3 -1
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +0 -2
- package/dist/src/monorepo/resources/FileResourceBuilder.d.ts +15 -0
- package/dist/src/monorepo/resources/FileResourceBuilder.js +19 -0
- package/dist/src/monorepo/resources/ResourceFactory.d.ts +7 -29
- package/dist/src/monorepo/resources/ResourceFactory.js +5 -5
- package/dist/src/monorepo/resources/abstract/AbstractResourceBuilder.d.ts +23 -0
- package/dist/src/monorepo/resources/abstract/AbstractResourceBuilder.js +18 -0
- package/dist/src/monorepo/resources/abstract/SentinelFileBasedBuilder.d.ts +27 -0
- package/dist/src/monorepo/resources/abstract/SentinelFileBasedBuilder.js +46 -0
- package/dist/src/monorepo/resources/abstract/index.d.ts +2 -0
- package/dist/src/monorepo/resources/abstract/index.js +2 -0
- package/dist/src/monorepo/resources/index.d.ts +3 -1
- package/dist/src/monorepo/resources/index.js +3 -1
- package/dist/src/monorepo/resources/types.d.ts +36 -0
- package/dist/src/monorepo/types.d.ts +3 -1
- package/dist/src/operations/abstract/AbstractOperation.d.ts +5 -5
- package/dist/src/operations/types.d.ts +1 -1
- package/oclif.manifest.json +117 -117
- package/package.json +1 -1
- package/dist/src/monorepo/resources/FileResource.js +0 -17
- /package/dist/src/monorepo/resources/{FileResource.d.ts → types.js} +0 -0
package/README.md
CHANGED
|
@@ -20,9 +20,6 @@ export default class UpCommand extends FlavoredCommand {
|
|
|
20
20
|
if (flags.force) {
|
|
21
21
|
buildFlags.push('--force');
|
|
22
22
|
}
|
|
23
|
-
if (flags.flavor) {
|
|
24
|
-
buildFlags.push('--flavor', flags.flavor);
|
|
25
|
-
}
|
|
26
23
|
await this.config.runCommand('resources:build', buildFlags);
|
|
27
24
|
await monorepo.run(new ComposeUpOperation(), {
|
|
28
25
|
forceRecreate: flags.force,
|
|
@@ -13,8 +13,13 @@ export declare const BuildImageOperationInputSchema: z.ZodObject<{
|
|
|
13
13
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
14
14
|
target: z.ZodOptional<z.ZodString>;
|
|
15
15
|
}, z.core.$strip>;
|
|
16
|
-
export declare class BuildImageOperation extends AbstractOperation<typeof BuildImageOperationInputSchema,
|
|
16
|
+
export declare class BuildImageOperation extends AbstractOperation<typeof BuildImageOperationInputSchema, void> {
|
|
17
17
|
private out?;
|
|
18
18
|
constructor(out?: Writable | undefined);
|
|
19
|
-
protected _run(input: z.input<typeof BuildImageOperationInputSchema>): Promise<
|
|
19
|
+
protected _run(input: z.input<typeof BuildImageOperationInputSchema>): Promise<void>;
|
|
20
|
+
protected _buildWithDockerCLI(input: z.input<typeof BuildImageOperationInputSchema>): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Experimental with dockerode and the docker API directly
|
|
23
|
+
*/
|
|
24
|
+
protected _buildWithDockerode(input: z.input<typeof BuildImageOperationInputSchema>): Promise<void>;
|
|
20
25
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { CommandExecError } from '../../../index.js';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
1
4
|
import { PassThrough } from 'node:stream';
|
|
2
5
|
import * as z from 'zod';
|
|
3
6
|
import { decodeBuildkitStatusResponse } from '../../index.js';
|
|
@@ -37,13 +40,61 @@ export class BuildImageOperation extends AbstractOperation {
|
|
|
37
40
|
this.out = out;
|
|
38
41
|
}
|
|
39
42
|
async _run(input) {
|
|
40
|
-
|
|
43
|
+
return this._buildWithDockerCLI(input);
|
|
44
|
+
}
|
|
45
|
+
async _buildWithDockerCLI(input) {
|
|
46
|
+
const labels = Object.entries(input.labels || {})
|
|
47
|
+
.reduce((arr, [key, value]) => {
|
|
48
|
+
arr.push(`${key.trim()}=${value.trim()}`);
|
|
49
|
+
return arr;
|
|
50
|
+
}, [])
|
|
51
|
+
.join(',');
|
|
52
|
+
const args = [
|
|
53
|
+
'build',
|
|
54
|
+
input.context,
|
|
55
|
+
'-f',
|
|
56
|
+
join(input.context, input.dockerfile || 'Dockerfile'),
|
|
57
|
+
'--label',
|
|
58
|
+
labels,
|
|
59
|
+
];
|
|
60
|
+
if (input.tag) {
|
|
61
|
+
args.push('--tag', input.tag);
|
|
62
|
+
}
|
|
63
|
+
if (input.target) {
|
|
64
|
+
args.push('--target', input.target);
|
|
65
|
+
}
|
|
66
|
+
Object.entries(input.buildArgs || []).forEach(([key, value]) => {
|
|
67
|
+
args.push('--build-arg', `${key.trim()}=${value.trim()}`);
|
|
68
|
+
});
|
|
41
69
|
const logFile = await this.context.monorepo.store.createWriteStream(`logs/docker/build/${input.tag}.log`);
|
|
70
|
+
const tee = new PassThrough();
|
|
42
71
|
tee.pipe(logFile);
|
|
43
72
|
if (this.out) {
|
|
44
73
|
tee.pipe(this.out);
|
|
45
74
|
}
|
|
46
|
-
tee.write('
|
|
75
|
+
tee.write('Building image with opts: ' + JSON.stringify(args));
|
|
76
|
+
const child = await spawn('docker', args);
|
|
77
|
+
child.stderr.pipe(tee);
|
|
78
|
+
child.stdout.pipe(tee);
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
child.on('close', () => {
|
|
81
|
+
resolve();
|
|
82
|
+
});
|
|
83
|
+
child.on('exit', (code, signal) => {
|
|
84
|
+
if (code !== 0) {
|
|
85
|
+
reject(new CommandExecError('Docker build failed', code || -1, signal));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
child.on('error', (err) => {
|
|
89
|
+
reject(err);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Experimental with dockerode and the docker API directly
|
|
95
|
+
*/
|
|
96
|
+
async _buildWithDockerode(input) {
|
|
97
|
+
const logFile = await this.context.monorepo.store.createWriteStream(`logs/docker/build/${input.tag}.log`);
|
|
47
98
|
const stream = await this.context.docker.buildImage({
|
|
48
99
|
context: input.context,
|
|
49
100
|
src: [...input.src],
|
|
@@ -55,21 +106,22 @@ export class BuildImageOperation extends AbstractOperation {
|
|
|
55
106
|
target: input.target,
|
|
56
107
|
version: '2',
|
|
57
108
|
});
|
|
58
|
-
tee.write('Starting build\n');
|
|
59
109
|
return new Promise((resolve, reject) => {
|
|
60
|
-
this.context.docker.modem.followProgress(stream, (err,
|
|
61
|
-
|
|
62
|
-
return err ? reject(err) : resolve(traces);
|
|
110
|
+
this.context.docker.modem.followProgress(stream, (err, _traces) => {
|
|
111
|
+
return err ? reject(err) : resolve();
|
|
63
112
|
}, async (trace) => {
|
|
64
113
|
if (trace.error) {
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
logFile.write(trace.error + '\n');
|
|
115
|
+
this.out?.write(trace.error + '\n');
|
|
116
|
+
reject(trace.error);
|
|
67
117
|
}
|
|
68
118
|
else {
|
|
69
119
|
try {
|
|
70
120
|
const { vertexes } = await decodeBuildkitStatusResponse(trace.aux);
|
|
71
121
|
vertexes.forEach((v) => {
|
|
72
|
-
|
|
122
|
+
// logStream.write(JSON.stringify(v) + '\n');
|
|
123
|
+
logFile.write(v.name + '\n');
|
|
124
|
+
this.out?.write(v.name + '\n');
|
|
73
125
|
});
|
|
74
126
|
}
|
|
75
127
|
catch (error) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Image/operation/ImagePush
|
|
5
|
+
*/
|
|
6
|
+
declare const schema: z.ZodObject<{
|
|
7
|
+
images: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
tag: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare class PushImagesOperation extends AbstractOperation<typeof schema, void> {
|
|
11
|
+
constructor();
|
|
12
|
+
protected _run(_input: z.input<typeof schema>): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Image/operation/ImagePush
|
|
5
|
+
*/
|
|
6
|
+
const schema = z.object({
|
|
7
|
+
images: z
|
|
8
|
+
.array(z.string())
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('The names of images to push (The name should be provided without tag. Use the `tag` parameter to specify why tag to push)'),
|
|
11
|
+
tag: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.default('latest')
|
|
15
|
+
.describe('Tag of the images to push'),
|
|
16
|
+
});
|
|
17
|
+
export class PushImagesOperation extends AbstractOperation {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(schema);
|
|
20
|
+
}
|
|
21
|
+
async _run(_input) { }
|
|
22
|
+
}
|
|
@@ -2,15 +2,17 @@ import { fdir as Fdir } from 'fdir';
|
|
|
2
2
|
import { stat, statfs } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import pMap from 'p-map';
|
|
5
|
+
import { SentinelFileBasedBuilder } from '../../monorepo/index.js';
|
|
5
6
|
import { GitPrerequisitePlugin } from '../../prerequisites/index.js';
|
|
6
7
|
import { ResourceFactory, } from '../../monorepo/resources/ResourceFactory.js';
|
|
7
8
|
import { BuildImageOperation } from '../operations/index.js';
|
|
8
|
-
class DockerImageResourceBuilder {
|
|
9
|
+
class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
|
|
9
10
|
buildContext;
|
|
10
|
-
|
|
11
|
+
dockerContext;
|
|
11
12
|
constructor(buildContext) {
|
|
13
|
+
super(buildContext);
|
|
12
14
|
this.buildContext = buildContext;
|
|
13
|
-
this.
|
|
15
|
+
this.dockerContext = this.config?.context
|
|
14
16
|
? this.config.context[0] === '/'
|
|
15
17
|
? buildContext.monorepo.join(this.config.context)
|
|
16
18
|
: buildContext.component.join(this.config.context)
|
|
@@ -20,72 +22,69 @@ class DockerImageResourceBuilder {
|
|
|
20
22
|
return this.buildContext.monorepo;
|
|
21
23
|
}
|
|
22
24
|
get config() {
|
|
23
|
-
return
|
|
25
|
+
return this.buildContext.config.params;
|
|
24
26
|
}
|
|
25
27
|
get component() {
|
|
26
28
|
return this.buildContext.component;
|
|
27
29
|
}
|
|
28
|
-
async
|
|
30
|
+
async _build(_resource, out) {
|
|
29
31
|
// Ensure the folder exists
|
|
30
|
-
await statfs(this.
|
|
32
|
+
await statfs(this.dockerContext);
|
|
31
33
|
const imageName = [
|
|
32
34
|
this.monorepo.name,
|
|
33
|
-
this.config
|
|
35
|
+
this.config?.tag || this.component.name,
|
|
34
36
|
].join('/');
|
|
35
|
-
const tagName = this.config
|
|
37
|
+
const tagName = this.config?.tag || this.monorepo.defaults.docker?.tag || 'latest';
|
|
36
38
|
const crawler = new Fdir();
|
|
37
39
|
const sources = await crawler
|
|
38
40
|
.withRelativePaths()
|
|
39
|
-
.crawl(this.
|
|
41
|
+
.crawl(this.dockerContext)
|
|
40
42
|
.withPromise();
|
|
41
43
|
const buildParams = {
|
|
42
|
-
context: this.
|
|
43
|
-
dockerfile: this.config
|
|
44
|
+
context: this.dockerContext,
|
|
45
|
+
dockerfile: this.config?.dockerfile || 'Dockerfile',
|
|
44
46
|
src: sources,
|
|
45
47
|
buildArgs: await this.monorepo.expand({
|
|
46
48
|
...this.monorepo.defaults.docker?.buildArgs,
|
|
47
|
-
...this.config
|
|
49
|
+
...this.config?.buildArgs,
|
|
48
50
|
}),
|
|
49
51
|
tag: await this.monorepo.expand(`${imageName}:${tagName}`),
|
|
50
52
|
labels: await this.monorepo.expand({
|
|
51
|
-
...this.config
|
|
53
|
+
...this.config?.labels,
|
|
52
54
|
'emb/project': this.monorepo.name,
|
|
53
55
|
'emb/component': this.component.name,
|
|
54
56
|
'emb/flavor': this.monorepo.currentFlavor,
|
|
55
57
|
}),
|
|
56
|
-
target: this.config
|
|
58
|
+
target: this.config?.target,
|
|
57
59
|
};
|
|
58
60
|
return {
|
|
59
61
|
input: buildParams,
|
|
60
62
|
operation: new BuildImageOperation(out),
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
|
-
async
|
|
65
|
+
async _mustBuild() {
|
|
64
66
|
const plugin = new GitPrerequisitePlugin();
|
|
65
|
-
const sources = await plugin.collect(this.
|
|
67
|
+
const sources = await plugin.collect(this.dockerContext);
|
|
66
68
|
const lastUpdated = await this.lastUpdatedInfo(sources);
|
|
67
|
-
if (!
|
|
68
|
-
return
|
|
69
|
+
if (!lastUpdated) {
|
|
70
|
+
return;
|
|
69
71
|
}
|
|
70
|
-
return
|
|
71
|
-
? lastUpdated
|
|
72
|
-
: undefined;
|
|
72
|
+
return { mtime: lastUpdated.time.getTime() };
|
|
73
73
|
}
|
|
74
74
|
async lastUpdatedInfo(sources) {
|
|
75
75
|
const stats = await pMap(sources, async (s) => {
|
|
76
|
-
const stats = await stat(join(this.
|
|
76
|
+
const stats = await stat(join(this.dockerContext, s.path));
|
|
77
77
|
return {
|
|
78
78
|
time: stats.mtime,
|
|
79
79
|
path: s.path,
|
|
80
80
|
};
|
|
81
81
|
}, { concurrency: 30 });
|
|
82
82
|
if (stats.length === 0) {
|
|
83
|
-
return
|
|
83
|
+
return;
|
|
84
84
|
}
|
|
85
85
|
return stats.reduce((last, entry) => {
|
|
86
86
|
return last.time > entry.time ? last : entry;
|
|
87
87
|
}, stats[0]);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
ResourceFactory.register('docker/image', async (context) => new DockerImageResourceBuilder(context));
|
|
90
|
+
ResourceFactory.register('docker/image', DockerImageResourceBuilder);
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ export declare class MultipleContainersFoundError extends EMBError {
|
|
|
72
72
|
component: string;
|
|
73
73
|
constructor(msg: string, component: string);
|
|
74
74
|
}
|
|
75
|
+
export declare class CommandExecError extends EMBError {
|
|
76
|
+
exitCode: number;
|
|
77
|
+
signal?: (NodeJS.Signals | null) | undefined;
|
|
78
|
+
constructor(msg: string, exitCode: number, signal?: (NodeJS.Signals | null) | undefined);
|
|
79
|
+
}
|
|
75
80
|
export declare class ComposeExecError extends EMBError {
|
|
76
81
|
exitCode: number;
|
|
77
82
|
signal?: (NodeJS.Signals | null) | undefined;
|
package/dist/src/errors.js
CHANGED
|
@@ -95,6 +95,15 @@ export class MultipleContainersFoundError extends EMBError {
|
|
|
95
95
|
this.component = component;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
export class CommandExecError extends EMBError {
|
|
99
|
+
exitCode;
|
|
100
|
+
signal;
|
|
101
|
+
constructor(msg, exitCode, signal) {
|
|
102
|
+
super('COMMAND_EXEC_ERR', msg);
|
|
103
|
+
this.exitCode = exitCode;
|
|
104
|
+
this.signal = signal;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
98
107
|
export class ComposeExecError extends EMBError {
|
|
99
108
|
exitCode;
|
|
100
109
|
signal;
|
|
@@ -30,7 +30,9 @@ export declare class Monorepo {
|
|
|
30
30
|
private installEnv;
|
|
31
31
|
init(): Promise<Monorepo>;
|
|
32
32
|
join(...paths: string[]): string;
|
|
33
|
-
run<I, O>(operation: IOperation<I, O
|
|
33
|
+
run<I extends void, O>(operation: IOperation<I, O>): Promise<O>;
|
|
34
|
+
run<I extends void, O>(operation: IOperation<I, O>): Promise<O>;
|
|
35
|
+
run<I, O>(operation: IOperation<I, O>, input: I): Promise<O>;
|
|
34
36
|
private expandPatches;
|
|
35
37
|
withFlavor(flavorName: string): Promise<Monorepo>;
|
|
36
38
|
}
|
|
@@ -161,8 +161,11 @@ export class Monorepo {
|
|
|
161
161
|
join(...paths) {
|
|
162
162
|
return join(this.rootDir, ...paths);
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
run(operation, input = undefined) {
|
|
165
|
+
if (input === undefined) {
|
|
166
|
+
return operation.run();
|
|
167
|
+
}
|
|
168
|
+
return operation.run(input);
|
|
166
169
|
}
|
|
167
170
|
async expandPatches(patches) {
|
|
168
171
|
const expanded = Promise.all(patches.map(async (patch) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
1
2
|
import * as z from 'zod';
|
|
2
3
|
import { AbstractOperation } from '../../../operations/index.js';
|
|
3
4
|
declare const schema: z.ZodObject<{
|
|
@@ -5,7 +6,8 @@ declare const schema: z.ZodObject<{
|
|
|
5
6
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
6
7
|
}, z.core.$strip>;
|
|
7
8
|
export declare class CreateFileOperation extends AbstractOperation<typeof schema, unknown> {
|
|
8
|
-
|
|
9
|
+
protected out?: Writable | undefined;
|
|
10
|
+
constructor(out?: Writable | undefined);
|
|
9
11
|
protected _run(input: z.input<typeof schema>): Promise<void>;
|
|
10
12
|
}
|
|
11
13
|
export {};
|
|
@@ -10,8 +10,10 @@ const schema = z.object({
|
|
|
10
10
|
.describe("Update 'atime' and 'mtime' if the file already exists"),
|
|
11
11
|
});
|
|
12
12
|
export class CreateFileOperation extends AbstractOperation {
|
|
13
|
-
|
|
13
|
+
out;
|
|
14
|
+
constructor(out) {
|
|
14
15
|
super(schema);
|
|
16
|
+
this.out = out;
|
|
15
17
|
}
|
|
16
18
|
async _run(input) {
|
|
17
19
|
try {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
|
-
import { ResourceInfo } from '../../index.js';
|
|
3
|
-
import { ResourceBuilderInfo } from '../../resources/ResourceFactory.js';
|
|
2
|
+
import { IResourceBuilder, ResourceInfo } from '../../index.js';
|
|
4
3
|
import { AbstractOperation } from '../../../operations/index.js';
|
|
5
4
|
export type BuildResourceMeta = {
|
|
6
5
|
dryRun?: boolean;
|
|
7
6
|
force?: boolean;
|
|
8
7
|
resource?: ResourceInfo;
|
|
9
|
-
builder?:
|
|
8
|
+
builder?: IResourceBuilder<unknown, unknown, unknown>;
|
|
10
9
|
builderInput?: unknown;
|
|
11
10
|
sentinelData?: unknown;
|
|
12
11
|
cacheHit?: boolean;
|
|
@@ -18,11 +17,9 @@ declare const schema: z.ZodObject<{
|
|
|
18
17
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
19
18
|
}, z.core.$strip>;
|
|
20
19
|
export declare class BuildResourcesOperation extends AbstractOperation<typeof schema, Record<string, BuildResourceMeta>> {
|
|
20
|
+
private built;
|
|
21
21
|
constructor();
|
|
22
22
|
protected _run(input: z.input<typeof schema>): Promise<Record<string, BuildResourceMeta>>;
|
|
23
23
|
private buildResource;
|
|
24
|
-
private sentinelFilePath;
|
|
25
|
-
private storeSentinelData;
|
|
26
|
-
private readSentinelFile;
|
|
27
24
|
}
|
|
28
25
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PRESET_TIMER, } from 'listr2';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import { EMBCollection, findRunOrder, taskManagerFactory, } from '../../index.js';
|
|
4
|
-
import { ResourceFactory
|
|
4
|
+
import { ResourceFactory } from '../../resources/ResourceFactory.js';
|
|
5
5
|
import { AbstractOperation } from '../../../operations/index.js';
|
|
6
6
|
const schema = z.object({
|
|
7
7
|
resources: z
|
|
@@ -22,6 +22,9 @@ const schema = z.object({
|
|
|
22
22
|
.describe('Bypass the cache and force the build'),
|
|
23
23
|
});
|
|
24
24
|
export class BuildResourcesOperation extends AbstractOperation {
|
|
25
|
+
// keep track of what has been built
|
|
26
|
+
// to ensure depedencies cannot ignore their turn
|
|
27
|
+
built = [];
|
|
25
28
|
constructor() {
|
|
26
29
|
super(schema);
|
|
27
30
|
}
|
|
@@ -92,16 +95,19 @@ export class BuildResourcesOperation extends AbstractOperation {
|
|
|
92
95
|
/** Skip the build if the builder knows it can be skipped */
|
|
93
96
|
task: async (ctx) => {
|
|
94
97
|
if (ctx.builder?.mustBuild) {
|
|
95
|
-
|
|
96
|
-
ctx.sentinelData =
|
|
97
|
-
await ctx.builder.mustBuild(previousSentinelData);
|
|
98
|
+
ctx.sentinelData = await ctx.builder.mustBuild(ctx.resource);
|
|
98
99
|
ctx.cacheHit = !ctx.sentinelData;
|
|
100
|
+
// If one of our dependency was built, we force the re-build
|
|
101
|
+
// despite the cache-hit
|
|
102
|
+
const found = ctx.resource.dependencies?.find((d) => Boolean(this.built.find((r) => r.id === d)));
|
|
103
|
+
ctx.force = ctx.force || Boolean(found);
|
|
99
104
|
}
|
|
100
105
|
},
|
|
101
106
|
},
|
|
102
107
|
{
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
rendererOptions: { persistentOutput: true },
|
|
109
|
+
title: `Build ${resource.id}`,
|
|
110
|
+
task: async (ctx, task) => {
|
|
105
111
|
const skip = (prefix) => {
|
|
106
112
|
parentTask.title = `${prefix} ${resource.id}`;
|
|
107
113
|
task.skip();
|
|
@@ -110,26 +116,28 @@ export class BuildResourcesOperation extends AbstractOperation {
|
|
|
110
116
|
if (ctx.cacheHit && !ctx.force && !ctx.dryRun) {
|
|
111
117
|
return skip('[cache hit]');
|
|
112
118
|
}
|
|
113
|
-
const { input, operation } = await ctx.builder.build(task.stdout());
|
|
119
|
+
const { input, operation } = await ctx.builder.build(ctx.resource, task.stdout());
|
|
114
120
|
ctx.builderInput = input;
|
|
121
|
+
this.built.push(ctx.resource);
|
|
115
122
|
if (ctx.dryRun) {
|
|
116
123
|
return skip('[dry run]');
|
|
117
124
|
}
|
|
118
|
-
|
|
125
|
+
const output = await operation.run(ctx.builderInput);
|
|
126
|
+
if (ctx.sentinelData) {
|
|
127
|
+
ctx.builder.commit?.(ctx.resource, output, ctx.sentinelData);
|
|
128
|
+
}
|
|
129
|
+
return output;
|
|
119
130
|
},
|
|
120
131
|
},
|
|
121
132
|
{
|
|
122
133
|
// Return build meta data and dump
|
|
123
134
|
// cache data into sentinel file
|
|
124
|
-
|
|
135
|
+
async task(ctx) {
|
|
125
136
|
if (ctx.builder) {
|
|
126
137
|
delete ctx.builder;
|
|
127
138
|
}
|
|
128
139
|
//
|
|
129
140
|
parentContext[resource.id] = ctx;
|
|
130
|
-
if (ctx.sentinelData && !ctx.dryRun) {
|
|
131
|
-
await this.storeSentinelData(resource, ctx.sentinelData);
|
|
132
|
-
}
|
|
133
141
|
},
|
|
134
142
|
},
|
|
135
143
|
], {
|
|
@@ -142,23 +150,4 @@ export class BuildResourcesOperation extends AbstractOperation {
|
|
|
142
150
|
});
|
|
143
151
|
return list;
|
|
144
152
|
}
|
|
145
|
-
sentinelFilePath(resource) {
|
|
146
|
-
const { monorepo } = this.context;
|
|
147
|
-
return `sentinels/flavors/${monorepo.currentFlavor}/${resource.component}/${resource.name}.built`;
|
|
148
|
-
}
|
|
149
|
-
async storeSentinelData(resource, data) {
|
|
150
|
-
await this.context.monorepo.store.writeFile(this.sentinelFilePath(resource), JSON.stringify(data));
|
|
151
|
-
}
|
|
152
|
-
async readSentinelFile(resource) {
|
|
153
|
-
const path = this.sentinelFilePath(resource);
|
|
154
|
-
const stats = await this.context.monorepo.store.stat(path, false);
|
|
155
|
-
if (!stats) {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
const data = await this.context.monorepo.store.readFile(path, false);
|
|
159
|
-
return {
|
|
160
|
-
data,
|
|
161
|
-
mtime: stats.mtime.getTime(),
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
153
|
}
|
|
@@ -10,8 +10,8 @@ declare const schema: z.ZodObject<{
|
|
|
10
10
|
workingDir: z.ZodOptional<z.ZodString>;
|
|
11
11
|
}, z.core.$strip>;
|
|
12
12
|
export declare class ExecuteLocalCommandOperation extends AbstractOperation<typeof schema, Readable> {
|
|
13
|
-
protected out
|
|
14
|
-
constructor(out
|
|
13
|
+
protected out?: Writable | undefined;
|
|
14
|
+
constructor(out?: Writable | undefined);
|
|
15
15
|
protected _run(input: z.input<typeof schema>): Promise<Readable>;
|
|
16
16
|
}
|
|
17
17
|
export {};
|
|
@@ -59,8 +59,6 @@ export class RunTasksOperation {
|
|
|
59
59
|
async runDocker(task, out) {
|
|
60
60
|
const { monorepo } = getContext();
|
|
61
61
|
return monorepo.run(new ComposeExecOperation(out), {
|
|
62
|
-
attachStderr: true,
|
|
63
|
-
attachStdout: true,
|
|
64
62
|
service: task.component,
|
|
65
63
|
command: task.script,
|
|
66
64
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { CreateFileOperation, IResourceBuilder, ResourceInfo } from '../index.js';
|
|
3
|
+
import { OpInput, OpOutput } from '../../operations/index.js';
|
|
4
|
+
import { ResourceBuildContext } from './ResourceFactory.js';
|
|
5
|
+
export declare class FileResourceBuilder implements IResourceBuilder<OpInput<CreateFileOperation>, OpOutput<CreateFileOperation>, void> {
|
|
6
|
+
protected context: ResourceBuildContext<OpInput<CreateFileOperation>>;
|
|
7
|
+
constructor(context: ResourceBuildContext<OpInput<CreateFileOperation>>);
|
|
8
|
+
build(resource: ResourceInfo<OpInput<CreateFileOperation>>, out?: Writable): Promise<{
|
|
9
|
+
input: {
|
|
10
|
+
path: string;
|
|
11
|
+
force?: boolean | undefined;
|
|
12
|
+
};
|
|
13
|
+
operation: CreateFileOperation;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CreateFileOperation, } from '../index.js';
|
|
2
|
+
import { ResourceFactory } from './ResourceFactory.js';
|
|
3
|
+
export class FileResourceBuilder {
|
|
4
|
+
context;
|
|
5
|
+
constructor(context) {
|
|
6
|
+
this.context = context;
|
|
7
|
+
}
|
|
8
|
+
async build(resource, out) {
|
|
9
|
+
const input = {
|
|
10
|
+
path: this.context.component.join(resource.params?.path || resource.name),
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
input,
|
|
14
|
+
operation: new CreateFileOperation(out),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Bring better abstraction and register as part of the plugin initialization
|
|
19
|
+
ResourceFactory.register('file', FileResourceBuilder);
|
|
@@ -1,34 +1,12 @@
|
|
|
1
|
-
import { Component, Monorepo, ResourceInfo } from '../../index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export type ResourceBuildContext = {
|
|
5
|
-
config: ResourceInfo;
|
|
1
|
+
import { Component, IResourceBuilder, Monorepo, ResourceInfo } from '../../index.js';
|
|
2
|
+
export type ResourceBuildContext<I> = {
|
|
3
|
+
config: ResourceInfo<I>;
|
|
6
4
|
component: Component;
|
|
7
5
|
monorepo: Monorepo;
|
|
8
6
|
};
|
|
9
|
-
export type
|
|
10
|
-
mtime: number;
|
|
11
|
-
data: T;
|
|
12
|
-
};
|
|
13
|
-
export type ResourceBuilderInfo<I, O, D = unknown> = {
|
|
14
|
-
/**
|
|
15
|
-
* Returns input and operation required to actually
|
|
16
|
-
* build the resources.
|
|
17
|
-
* This allows the dry-run mechanism to be implemented outside
|
|
18
|
-
* resource builder implementations
|
|
19
|
-
*
|
|
20
|
-
* @param out The Writable to use to write logs
|
|
21
|
-
*/
|
|
22
|
-
build(out?: Writable): Promise<{
|
|
23
|
-
input: I;
|
|
24
|
-
operation: IOperation<I, O>;
|
|
25
|
-
}>;
|
|
26
|
-
mustBuild?: (previousSentinelData: SentinelData<D> | undefined) => Promise<undefined | unknown>;
|
|
27
|
-
};
|
|
28
|
-
export type ResourceFactoryOutput<I, O> = Promise<ResourceBuilderInfo<I, O>>;
|
|
29
|
-
export type ResourceOperationFactory<I, O> = (context: ResourceBuildContext) => ResourceFactoryOutput<I, O>;
|
|
7
|
+
export type ResourceBuilderConstructor<I, O, R> = new (context: ResourceBuildContext<I>) => IResourceBuilder<I, O, R>;
|
|
30
8
|
export declare class ResourceFactory {
|
|
31
|
-
protected static types: Record<string,
|
|
32
|
-
static register<I, O>(type: string,
|
|
33
|
-
static factor<I, O>(type: string, context: ResourceBuildContext):
|
|
9
|
+
protected static types: Record<string, ResourceBuilderConstructor<any, any, any>>;
|
|
10
|
+
static register<I, O, R>(type: string, constructor: ResourceBuilderConstructor<I, O, R>): void;
|
|
11
|
+
static factor<I, O, R>(type: string, context: ResourceBuildContext<I>): IResourceBuilder<I, O, R>;
|
|
34
12
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export class ResourceFactory {
|
|
2
2
|
static types = {};
|
|
3
|
-
static register(type,
|
|
3
|
+
static register(type, constructor) {
|
|
4
4
|
if (this.types[type]) {
|
|
5
5
|
throw new Error(`Resource type \`${type}\` already registered`);
|
|
6
6
|
}
|
|
7
|
-
this.types[type] =
|
|
7
|
+
this.types[type] = constructor;
|
|
8
8
|
}
|
|
9
9
|
static factor(type, context) {
|
|
10
|
-
const
|
|
11
|
-
if (!
|
|
10
|
+
const BuilderClass = this.types[type];
|
|
11
|
+
if (!BuilderClass) {
|
|
12
12
|
throw new Error(`Unknown resource type \`${type}\``);
|
|
13
13
|
}
|
|
14
|
-
return
|
|
14
|
+
return new BuilderClass(context);
|
|
15
15
|
}
|
|
16
16
|
}
|