@enspirit/emb 0.0.9 → 0.1.0
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 +50 -28
- package/dist/src/cli/abstract/BaseCommand.d.ts +6 -0
- package/dist/src/cli/abstract/BaseCommand.js +33 -0
- package/dist/src/cli/abstract/FlavouredCommand.d.ts +2 -1
- package/dist/src/cli/abstract/FlavouredCommand.js +4 -3
- package/dist/src/cli/abstract/index.d.ts +1 -0
- package/dist/src/cli/abstract/index.js +1 -0
- package/dist/src/cli/commands/clean.d.ts +2 -2
- package/dist/src/cli/commands/clean.js +7 -6
- package/dist/src/cli/commands/components/index.d.ts +5 -4
- package/dist/src/cli/commands/components/index.js +13 -15
- package/dist/src/cli/commands/config/print.d.ts +2 -2
- package/dist/src/cli/commands/containers/index.d.ts +2 -2
- package/dist/src/cli/commands/containers/index.js +3 -3
- package/dist/src/cli/commands/containers/prune.d.ts +2 -2
- package/dist/src/cli/commands/containers/prune.js +2 -2
- package/dist/src/cli/commands/images/delete.d.ts +2 -2
- package/dist/src/cli/commands/images/delete.js +3 -2
- package/dist/src/cli/commands/images/index.d.ts +2 -2
- package/dist/src/cli/commands/images/index.js +3 -3
- package/dist/src/cli/commands/images/prune.d.ts +2 -2
- package/dist/src/cli/commands/images/prune.js +3 -2
- package/dist/src/cli/commands/{components → resources}/build.d.ts +3 -3
- package/dist/src/cli/commands/{components → resources}/build.js +11 -10
- package/dist/src/cli/commands/resources/index.d.ts +9 -0
- package/dist/src/cli/commands/resources/index.js +28 -0
- package/dist/src/cli/commands/tasks/index.d.ts +2 -2
- package/dist/src/cli/commands/tasks/index.js +4 -4
- package/dist/src/cli/commands/tasks/run.d.ts +2 -2
- package/dist/src/cli/commands/tasks/run.js +12 -5
- package/dist/src/cli/commands/up.js +5 -4
- package/dist/src/cli/hooks/init.js +1 -26
- package/dist/src/cli/utils.d.ts +1 -0
- package/dist/src/cli/utils.js +26 -0
- package/dist/src/config/convert.d.ts +2 -4
- package/dist/src/config/convert.js +12 -35
- package/dist/src/config/index.d.ts +3 -4
- package/dist/src/config/index.js +2 -3
- package/dist/src/config/schema.d.ts +69 -48
- package/dist/src/config/schema.json +185 -99
- package/dist/src/config/types.d.ts +13 -46
- package/dist/src/config/types.js +1 -1
- package/dist/src/config/validation.d.ts +3 -3
- package/dist/src/config/validation.js +9 -9
- package/dist/src/context.d.ts +1 -1
- package/dist/src/context.js +1 -0
- package/dist/src/docker/images/index.d.ts +0 -1
- package/dist/src/docker/images/index.js +0 -1
- package/dist/src/docker/index.d.ts +1 -1
- package/dist/src/docker/index.js +1 -1
- package/dist/src/docker/operations/containers/ExecContainerOperation.js +2 -1
- package/dist/src/docker/operations/images/BuildImageOperation.d.ts +7 -8
- package/dist/src/docker/operations/images/BuildImageOperation.js +16 -10
- package/dist/src/docker/resources/DockerImageResource.js +56 -0
- package/dist/src/docker/resources/index.d.ts +1 -0
- package/dist/src/docker/resources/index.js +1 -0
- package/dist/src/docker/utils.d.ts +1 -7
- package/dist/src/docker/utils.js +3 -7
- package/dist/src/errors.d.ts +9 -6
- package/dist/src/errors.js +15 -9
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/monorepo/component.d.ts +14 -19
- package/dist/src/monorepo/component.js +42 -58
- package/dist/src/monorepo/config.d.ts +13 -15
- package/dist/src/monorepo/config.js +15 -46
- package/dist/src/monorepo/index.d.ts +1 -0
- package/dist/src/monorepo/index.js +1 -0
- package/dist/src/monorepo/monorepo.d.ts +13 -10
- package/dist/src/monorepo/monorepo.js +78 -19
- package/dist/src/monorepo/operations/components/index.d.ts +0 -1
- package/dist/src/monorepo/operations/components/index.js +0 -1
- package/dist/src/monorepo/operations/fs/CreateFileOperation.d.ts +11 -0
- package/dist/src/monorepo/operations/fs/CreateFileOperation.js +31 -0
- package/dist/src/monorepo/operations/fs/index.d.ts +1 -0
- package/dist/src/monorepo/operations/fs/index.js +1 -0
- package/dist/src/monorepo/operations/index.d.ts +1 -0
- package/dist/src/monorepo/operations/index.js +1 -0
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.d.ts +27 -0
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.js +146 -0
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +1 -1
- package/dist/src/monorepo/plugins/{ComponentDiscoverPlugin.d.ts → AutoDockerPlugin.d.ts} +4 -4
- package/dist/src/monorepo/plugins/AutoDockerPlugin.js +46 -0
- package/dist/src/monorepo/plugins/EmbfileLoaderPlugin.js +9 -2
- package/dist/src/monorepo/plugins/index.d.ts +1 -1
- package/dist/src/monorepo/plugins/index.js +3 -3
- package/dist/src/monorepo/resources/FileResource.d.ts +1 -0
- package/dist/src/monorepo/resources/FileResource.js +13 -0
- package/dist/src/monorepo/resources/ResourceFactory.d.ts +23 -0
- package/dist/src/monorepo/resources/ResourceFactory.js +16 -0
- package/dist/src/monorepo/resources/index.d.ts +1 -0
- package/dist/src/monorepo/resources/index.js +1 -0
- package/dist/src/monorepo/store/index.d.ts +1 -1
- package/dist/src/monorepo/store/index.js +10 -2
- package/dist/src/monorepo/taskManagerFactory.d.ts +2 -2
- package/dist/src/monorepo/taskManagerFactory.js +1 -2
- package/dist/src/monorepo/types.d.ts +18 -5
- package/dist/src/monorepo/utils/{findRunOrder.d.ts → EMBCollection.d.ts} +3 -9
- package/dist/src/monorepo/utils/EMBCollection.js +101 -0
- package/dist/src/monorepo/utils/graph.d.ts +15 -0
- package/dist/src/monorepo/utils/graph.js +84 -0
- package/dist/src/monorepo/utils/index.d.ts +7 -1
- package/dist/src/monorepo/utils/index.js +14 -1
- package/dist/src/monorepo/utils/types.d.ts +2 -0
- package/dist/src/monorepo/utils/types.js +1 -0
- package/dist/src/operations/abstract/AbstractOperation.d.ts +3 -1
- package/dist/src/operations/abstract/AbstractOperation.js +3 -0
- package/dist/src/operations/types.d.ts +1 -1
- package/dist/src/prerequisites/GitPrerequisitePlugin.js +2 -2
- package/dist/src/utils/TemplateExpander.d.ts +2 -1
- package/dist/src/utils/TemplateExpander.js +3 -1
- package/oclif.manifest.json +105 -68
- package/package.json +7 -2
- package/dist/src/docker/images/buildImage.d.ts +0 -19
- package/dist/src/docker/images/buildImage.js +0 -64
- package/dist/src/docker/types.d.ts +0 -14
- package/dist/src/monorepo/operations/components/BuildComponentsOperation.d.ts +0 -23
- package/dist/src/monorepo/operations/components/BuildComponentsOperation.js +0 -157
- package/dist/src/monorepo/plugins/ComponentDiscoverPlugin.js +0 -44
- package/dist/src/monorepo/project.d.ts +0 -6
- package/dist/src/monorepo/project.js +0 -8
- package/dist/src/monorepo/utils/findRunOrder.js +0 -165
- /package/dist/src/docker/{types.js → resources/DockerImageResource.d.ts} +0 -0
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { getContext } from '../../index.js';
|
|
2
|
-
import { decodeBuildkitStatusResponse, getSentinelFile, } from '../index.js';
|
|
3
|
-
import { FilePrerequisitePlugin } from '../../prerequisites/index.js';
|
|
4
|
-
export const buildDockerImage = async (component, opts = {}, progress) => {
|
|
5
|
-
const cmp = await component.toDockerBuild();
|
|
6
|
-
const files = (cmp.prerequisites || []).map((f) => f.path);
|
|
7
|
-
const { docker, monorepo } = getContext();
|
|
8
|
-
/** SENTINEL LOGIC */
|
|
9
|
-
// TODO: make configurable
|
|
10
|
-
const prereqPlugin = new FilePrerequisitePlugin();
|
|
11
|
-
const preBuildMeta = await prereqPlugin.meta(component, cmp.prerequisites, 'pre');
|
|
12
|
-
const sentinelFile = getSentinelFile(component);
|
|
13
|
-
let lastValue;
|
|
14
|
-
try {
|
|
15
|
-
lastValue = (await monorepo.store.readFile(sentinelFile)).toString();
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
lastValue = undefined;
|
|
19
|
-
}
|
|
20
|
-
if (lastValue) {
|
|
21
|
-
const diff = await prereqPlugin.diff(component, cmp.prerequisites, lastValue, preBuildMeta);
|
|
22
|
-
if (!diff) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const stream = await docker.buildImage({
|
|
27
|
-
context: cmp.context,
|
|
28
|
-
src: [...files],
|
|
29
|
-
}, {
|
|
30
|
-
buildargs: cmp.buildArgs,
|
|
31
|
-
dockerfile: cmp.dockerfile,
|
|
32
|
-
labels: cmp.labels,
|
|
33
|
-
t: cmp.name + ':' + (cmp.tag || 'latest'),
|
|
34
|
-
target: cmp.target,
|
|
35
|
-
version: '2',
|
|
36
|
-
});
|
|
37
|
-
if (opts.output) {
|
|
38
|
-
stream.pipe(opts.output);
|
|
39
|
-
}
|
|
40
|
-
stream.on('close', async () => {
|
|
41
|
-
const sentinelValue = await prereqPlugin.meta(component, cmp.prerequisites, 'post');
|
|
42
|
-
await monorepo.store.writeFile(sentinelFile, sentinelValue);
|
|
43
|
-
});
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
docker.modem.followProgress(stream, (err, traces) => {
|
|
46
|
-
return err ? reject(err) : resolve({ ...cmp, traces });
|
|
47
|
-
}, async (trace) => {
|
|
48
|
-
if (trace.error) {
|
|
49
|
-
reject(new Error(trace.error));
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
try {
|
|
53
|
-
const { vertexes } = await decodeBuildkitStatusResponse(trace.aux);
|
|
54
|
-
vertexes.forEach((v) => {
|
|
55
|
-
progress?.(v);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
console.error(error);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { FilePrerequisite } from '../prerequisites/index.js';
|
|
2
|
-
export type EnvVariable = {
|
|
3
|
-
name: string;
|
|
4
|
-
};
|
|
5
|
-
export interface DockerComponentBuild {
|
|
6
|
-
buildArgs?: Record<string, string>;
|
|
7
|
-
context: string;
|
|
8
|
-
dockerfile: string;
|
|
9
|
-
labels?: Record<string, string>;
|
|
10
|
-
name: string;
|
|
11
|
-
prerequisites: Array<FilePrerequisite>;
|
|
12
|
-
tag: string;
|
|
13
|
-
target?: string;
|
|
14
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import * as z from 'zod';
|
|
2
|
-
import { DockerComponentBuild } from '../../../docker/index.js';
|
|
3
|
-
import { AbstractOperation } from '../../../operations/index.js';
|
|
4
|
-
export type BuildComponentMeta = {
|
|
5
|
-
dryRun?: boolean;
|
|
6
|
-
cacheHit?: boolean;
|
|
7
|
-
force?: boolean;
|
|
8
|
-
build: DockerComponentBuild;
|
|
9
|
-
preBuildMeta?: string;
|
|
10
|
-
sentinelFile: string;
|
|
11
|
-
};
|
|
12
|
-
declare const schema: z.ZodObject<{
|
|
13
|
-
components: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
-
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
-
silent: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
-
force: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
-
}, z.core.$strip>;
|
|
18
|
-
export declare class BuildComponentsOperation extends AbstractOperation<typeof schema, Record<string, BuildComponentMeta>> {
|
|
19
|
-
constructor();
|
|
20
|
-
protected _run(input: z.input<typeof schema>): Promise<Record<string, BuildComponentMeta>>;
|
|
21
|
-
private buildComponent;
|
|
22
|
-
}
|
|
23
|
-
export {};
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { getContext } from '../../../index.js';
|
|
2
|
-
import * as z from 'zod';
|
|
3
|
-
import { BuildImageOperation, getSentinelFile, } from '../../../docker/index.js';
|
|
4
|
-
import { EMBCollection, findRunOrder, taskManagerFactory, } from '../../index.js';
|
|
5
|
-
import { AbstractOperation } from '../../../operations/index.js';
|
|
6
|
-
import { FilePrerequisitePlugin, PrerequisiteType } from '../../../prerequisites/index.js';
|
|
7
|
-
const schema = z.object({
|
|
8
|
-
components: z
|
|
9
|
-
.array(z.string())
|
|
10
|
-
.describe('The list of components to build')
|
|
11
|
-
.optional(),
|
|
12
|
-
dryRun: z
|
|
13
|
-
.boolean()
|
|
14
|
-
.optional()
|
|
15
|
-
.describe('Do not build but return the config that would be used to build the images'),
|
|
16
|
-
silent: z
|
|
17
|
-
.boolean()
|
|
18
|
-
.optional()
|
|
19
|
-
.describe('Do not produce any output on the terminal'),
|
|
20
|
-
force: z
|
|
21
|
-
.boolean()
|
|
22
|
-
.optional()
|
|
23
|
-
.describe('Bypass the cache and force the build'),
|
|
24
|
-
});
|
|
25
|
-
export class BuildComponentsOperation extends AbstractOperation {
|
|
26
|
-
constructor() {
|
|
27
|
-
super(schema);
|
|
28
|
-
}
|
|
29
|
-
async _run(input) {
|
|
30
|
-
const { monorepo } = getContext();
|
|
31
|
-
const manager = taskManagerFactory();
|
|
32
|
-
const selection = (input.components || []).map((t) => monorepo.component(t));
|
|
33
|
-
const collection = new EMBCollection(this.context.monorepo.components, {
|
|
34
|
-
idField: 'name',
|
|
35
|
-
depField: 'dependencies',
|
|
36
|
-
forbidIdNameCollision: true,
|
|
37
|
-
});
|
|
38
|
-
const ordered = findRunOrder(selection.map((s) => s.name), collection);
|
|
39
|
-
const tasks = ordered.map((cmp) => {
|
|
40
|
-
return {
|
|
41
|
-
task: async (context, task) => {
|
|
42
|
-
return this.buildComponent(cmp, task, context, {
|
|
43
|
-
dryRun: input.dryRun,
|
|
44
|
-
force: input.force,
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
title: `Building ${cmp.name}`,
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
manager.add([
|
|
51
|
-
{
|
|
52
|
-
title: 'Build images',
|
|
53
|
-
async task(ctx, task) {
|
|
54
|
-
return task.newListr([...tasks], {
|
|
55
|
-
rendererOptions: {
|
|
56
|
-
collapseSubtasks: true,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
], {
|
|
62
|
-
rendererOptions: {
|
|
63
|
-
collapseSkips: false,
|
|
64
|
-
collapseSubtasks: false,
|
|
65
|
-
},
|
|
66
|
-
ctx: {},
|
|
67
|
-
});
|
|
68
|
-
const results = await manager.runAll();
|
|
69
|
-
return results;
|
|
70
|
-
}
|
|
71
|
-
async buildComponent(cmp, parentTask, parentContext, options) {
|
|
72
|
-
const prereqPlugin = new FilePrerequisitePlugin();
|
|
73
|
-
const list = parentTask.newListr([
|
|
74
|
-
// Collect all the prerequisites and other build infos
|
|
75
|
-
// (This is when variables are expanded etc)
|
|
76
|
-
{
|
|
77
|
-
async task(ctx) {
|
|
78
|
-
// Install the context for this specific component build chain
|
|
79
|
-
ctx.cacheHit = false;
|
|
80
|
-
ctx.force = options?.force;
|
|
81
|
-
ctx.sentinelFile = getSentinelFile(cmp);
|
|
82
|
-
ctx.build = await cmp.toDockerBuild();
|
|
83
|
-
},
|
|
84
|
-
title: 'Prepare build context',
|
|
85
|
-
},
|
|
86
|
-
// Check for sentinal information to see if the build can be skipped
|
|
87
|
-
{
|
|
88
|
-
skip(ctx) {
|
|
89
|
-
return Boolean(ctx.force);
|
|
90
|
-
},
|
|
91
|
-
task: async (ctx) => {
|
|
92
|
-
ctx.preBuildMeta = await prereqPlugin.meta(cmp, ctx.build.prerequisites, 'pre');
|
|
93
|
-
let lastValue;
|
|
94
|
-
try {
|
|
95
|
-
lastValue = (await this.context.monorepo.store.readFile(ctx.sentinelFile)).toString();
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
lastValue = undefined;
|
|
99
|
-
}
|
|
100
|
-
if (lastValue) {
|
|
101
|
-
const diff = await prereqPlugin.diff(cmp, ctx.build.prerequisites, lastValue, ctx.preBuildMeta);
|
|
102
|
-
if (!ctx.force && !diff) {
|
|
103
|
-
ctx.cacheHit = true;
|
|
104
|
-
parentTask.skip(`${parentTask.title} (cache hit)`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
title: 'Checking prerequisites',
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
skip: (ctx) => !ctx.force && (Boolean(ctx.cacheHit) || Boolean(ctx.dryRun)),
|
|
112
|
-
task: async (ctx, task) => {
|
|
113
|
-
const title = `Building image ${ctx.build.name}:${ctx.build.tag}`;
|
|
114
|
-
task.title = title;
|
|
115
|
-
const op = new BuildImageOperation((progress) => {
|
|
116
|
-
task.title = progress;
|
|
117
|
-
});
|
|
118
|
-
await this.context.monorepo.run(op, {
|
|
119
|
-
...ctx.build,
|
|
120
|
-
src: ctx.build.prerequisites
|
|
121
|
-
.filter((p) => {
|
|
122
|
-
return p.type === PrerequisiteType.file;
|
|
123
|
-
})
|
|
124
|
-
.map((p) => p.path),
|
|
125
|
-
tag: ctx.build.name + ':' + ctx.build.tag,
|
|
126
|
-
});
|
|
127
|
-
// Restore title
|
|
128
|
-
task.title = title;
|
|
129
|
-
},
|
|
130
|
-
title: 'Building image',
|
|
131
|
-
},
|
|
132
|
-
// Update sentinel file
|
|
133
|
-
{
|
|
134
|
-
skip: (ctx) => !ctx.force && (Boolean(ctx.cacheHit) || Boolean(ctx.dryRun)),
|
|
135
|
-
task: async (ctx) => {
|
|
136
|
-
const sentinelValue = await prereqPlugin.meta(cmp, ctx.build.prerequisites, 'post');
|
|
137
|
-
await this.context.monorepo.store.writeFile(ctx.sentinelFile, sentinelValue);
|
|
138
|
-
},
|
|
139
|
-
title: 'Dumping cache info',
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
// Return build meta data
|
|
143
|
-
async task(ctx) {
|
|
144
|
-
parentContext[cmp.name] = ctx;
|
|
145
|
-
if (!ctx.force && ctx.dryRun) {
|
|
146
|
-
parentTask.skip(`${parentTask.title} (dry run)`);
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
], {
|
|
151
|
-
ctx: {
|
|
152
|
-
...options,
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
return list;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import deepmerge from '@fastify/deepmerge';
|
|
2
|
-
import { glob } from 'glob';
|
|
3
|
-
import { dirname } from 'node:path';
|
|
4
|
-
import { MonorepoConfig } from '../index.js';
|
|
5
|
-
import { AbstractPlugin } from './plugin.js';
|
|
6
|
-
export const ComponentDiscoverPluginDefaultOptions = {
|
|
7
|
-
glob: '*/Dockerfile',
|
|
8
|
-
};
|
|
9
|
-
export class ComponentDiscoverPlugin extends AbstractPlugin {
|
|
10
|
-
monorepo;
|
|
11
|
-
static name = 'autodiscover';
|
|
12
|
-
constructor(config, monorepo) {
|
|
13
|
-
super({
|
|
14
|
-
...ComponentDiscoverPluginDefaultOptions,
|
|
15
|
-
...config,
|
|
16
|
-
}, monorepo);
|
|
17
|
-
this.monorepo = monorepo;
|
|
18
|
-
}
|
|
19
|
-
async extendConfig(config) {
|
|
20
|
-
const files = await glob(this.config.glob || ComponentDiscoverPluginDefaultOptions.glob, {
|
|
21
|
-
...this.config,
|
|
22
|
-
cwd: config.project.rootDir,
|
|
23
|
-
});
|
|
24
|
-
const overrides = files.map((path) => {
|
|
25
|
-
const name = dirname(path);
|
|
26
|
-
const component = config.components.find((cmp) => cmp.name === name);
|
|
27
|
-
const cfg = {
|
|
28
|
-
name,
|
|
29
|
-
docker: {
|
|
30
|
-
context: name,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
return component ? deepmerge()(component, cfg) : cfg;
|
|
34
|
-
});
|
|
35
|
-
const untouched = config.components.filter((c) => !overrides.find((o) => {
|
|
36
|
-
return o.name === c.name;
|
|
37
|
-
}));
|
|
38
|
-
const components = [...overrides, ...untouched];
|
|
39
|
-
return new MonorepoConfig({
|
|
40
|
-
...config,
|
|
41
|
-
components,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import graphlib from 'graphlib';
|
|
2
|
-
import { AmbiguousTaskError, CircularDependencyError, TaskNameCollisionError, UnkownReferenceError, } from '../../errors.js';
|
|
3
|
-
export class EMBCollection {
|
|
4
|
-
items;
|
|
5
|
-
idField;
|
|
6
|
-
depField;
|
|
7
|
-
byId;
|
|
8
|
-
byName;
|
|
9
|
-
constructor(items, cfg) {
|
|
10
|
-
this.items = [];
|
|
11
|
-
this.idField = cfg.idField;
|
|
12
|
-
this.depField = cfg.depField;
|
|
13
|
-
this.byId = new Map();
|
|
14
|
-
this.byName = new Map();
|
|
15
|
-
// single-pass validation state
|
|
16
|
-
const seenIds = new Set();
|
|
17
|
-
const seenNames = new Set();
|
|
18
|
-
const dupIdReports = [];
|
|
19
|
-
const collisions = [];
|
|
20
|
-
for (const t of items) {
|
|
21
|
-
const id = t[this.idField];
|
|
22
|
-
const { name } = t;
|
|
23
|
-
// duplicate id?
|
|
24
|
-
if (seenIds.has(id)) {
|
|
25
|
-
const firstOwner = this.byId.get(id); // first occurrence already stored
|
|
26
|
-
dupIdReports.push(`id "${id}" used by "${firstOwner.name}" and "${name}"`);
|
|
27
|
-
// keep the first owner in byId; do not overwrite
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
this.byId.set(id, t);
|
|
31
|
-
seenIds.add(id);
|
|
32
|
-
}
|
|
33
|
-
// --- Optional validation: forbid id <-> name collisions ---
|
|
34
|
-
const checkCollisions = cfg.forbidIdNameCollision && this.idField !== 'name';
|
|
35
|
-
if (checkCollisions) {
|
|
36
|
-
if (seenNames.has(id)) {
|
|
37
|
-
const nameOwners = this.byName.get(id) ?? [];
|
|
38
|
-
const ownerIds = nameOwners.map((o) => o[this.idField]).join(', ');
|
|
39
|
-
collisions.push(`value "${id}" is an id of "${name}" and also a name of item(s) with id(s): [${ownerIds}]`);
|
|
40
|
-
}
|
|
41
|
-
if (seenIds.has(name)) {
|
|
42
|
-
const idOwner = this.byId.get(name);
|
|
43
|
-
collisions.push(`value "${name}" is a name of "${t.name}" and also an id of "${idOwner.name}"`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// byName index
|
|
47
|
-
const list = this.byName.get(name);
|
|
48
|
-
if (list)
|
|
49
|
-
list.push(t);
|
|
50
|
-
else
|
|
51
|
-
this.byName.set(name, [t]);
|
|
52
|
-
// keep item list (stable order)
|
|
53
|
-
this.items.push(t);
|
|
54
|
-
// record name after checks so current name won’t collide with itself
|
|
55
|
-
seenNames.add(name);
|
|
56
|
-
}
|
|
57
|
-
if (dupIdReports.length > 0 || collisions.length > 0) {
|
|
58
|
-
const parts = [];
|
|
59
|
-
if (dupIdReports.length > 0) {
|
|
60
|
-
parts.push(`Duplicate ${String(this.idField)} values (${dupIdReports.length}):\n` +
|
|
61
|
-
dupIdReports.join('\n'));
|
|
62
|
-
}
|
|
63
|
-
if (collisions.length > 0) {
|
|
64
|
-
parts.push(`id↔name collisions (${collisions.length}):\n` +
|
|
65
|
-
collisions.join('\n'));
|
|
66
|
-
}
|
|
67
|
-
throw new TaskNameCollisionError('Collision between task names and ids', parts);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/** All items (stable array iteration) */
|
|
71
|
-
get all() {
|
|
72
|
-
return this.items;
|
|
73
|
-
}
|
|
74
|
-
idOf(t) {
|
|
75
|
-
return t[this.idField];
|
|
76
|
-
}
|
|
77
|
-
depsOf(t) {
|
|
78
|
-
return (t[this.depField] ?? []);
|
|
79
|
-
}
|
|
80
|
-
matches(ref, opts) {
|
|
81
|
-
const idHit = this.byId.get(ref);
|
|
82
|
-
if (idHit)
|
|
83
|
-
return opts?.multiple ? [idHit] : idHit;
|
|
84
|
-
const nameHits = this.byName.get(ref) ?? [];
|
|
85
|
-
if (nameHits.length === 0) {
|
|
86
|
-
throw new UnkownReferenceError(`Unknown reference "${ref}"`);
|
|
87
|
-
}
|
|
88
|
-
if (opts?.multiple)
|
|
89
|
-
return nameHits;
|
|
90
|
-
if (nameHits.length > 1) {
|
|
91
|
-
const ids = nameHits.map((t) => this.idOf(t));
|
|
92
|
-
throw new AmbiguousTaskError(`Ambiguous reference "${ref}" matches multiple id/name`, ids);
|
|
93
|
-
}
|
|
94
|
-
return nameHits[0];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/* ----------------- run-order helpers unchanged (for completeness) ---------------- */
|
|
98
|
-
function resolveRefSet(col, ref, policy) {
|
|
99
|
-
if (policy === 'runAll')
|
|
100
|
-
return col.matches(ref, { multiple: true }).map((t) => col.idOf(t));
|
|
101
|
-
return [col.idOf(col.matches(ref))];
|
|
102
|
-
}
|
|
103
|
-
function collectPredecessorClosure(g, seeds) {
|
|
104
|
-
const seen = new Set();
|
|
105
|
-
const q = [];
|
|
106
|
-
for (const s of seeds) {
|
|
107
|
-
if (!seen.has(s)) {
|
|
108
|
-
seen.add(s);
|
|
109
|
-
q.push(s);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
while (q.length > 0) {
|
|
113
|
-
const cur = q.shift();
|
|
114
|
-
for (const p of g.predecessors(cur) ?? [])
|
|
115
|
-
if (!seen.has(p)) {
|
|
116
|
-
seen.add(p);
|
|
117
|
-
q.push(p);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return seen;
|
|
121
|
-
}
|
|
122
|
-
function buildGraph(col, policy) {
|
|
123
|
-
const g = new graphlib.Graph({ directed: true });
|
|
124
|
-
for (const t of col.all)
|
|
125
|
-
g.setNode(col.idOf(t));
|
|
126
|
-
for (const t of col.all) {
|
|
127
|
-
const toId = col.idOf(t);
|
|
128
|
-
for (const ref of col.depsOf(t)) {
|
|
129
|
-
for (const fromId of resolveRefSet(col, ref, policy))
|
|
130
|
-
g.setEdge(fromId, toId);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return g;
|
|
134
|
-
}
|
|
135
|
-
export function findRunOrder(selection, collection, { onAmbiguous = 'error' } = {}) {
|
|
136
|
-
const g = buildGraph(collection, onAmbiguous);
|
|
137
|
-
const cycles = graphlib.alg.findCycles(g);
|
|
138
|
-
if (cycles.length > 0) {
|
|
139
|
-
throw new CircularDependencyError(`Circular dependencies detected: ${JSON.stringify(cycles)}`);
|
|
140
|
-
}
|
|
141
|
-
const selectedIds = new Set();
|
|
142
|
-
for (const ref of selection)
|
|
143
|
-
for (const id of resolveRefSet(collection, ref, onAmbiguous))
|
|
144
|
-
selectedIds.add(id);
|
|
145
|
-
if (selectedIds.size === 0)
|
|
146
|
-
throw new Error('Selection resolved to no items.');
|
|
147
|
-
const include = collectPredecessorClosure(g, selectedIds.values());
|
|
148
|
-
const sub = new graphlib.Graph({ directed: true });
|
|
149
|
-
for (const id of include)
|
|
150
|
-
sub.setNode(id);
|
|
151
|
-
for (const id of include)
|
|
152
|
-
for (const p of g.predecessors(id) ?? [])
|
|
153
|
-
if (include.has(p))
|
|
154
|
-
sub.setEdge(p, id);
|
|
155
|
-
const ids = graphlib.alg.topsort(sub);
|
|
156
|
-
const byId = new Map();
|
|
157
|
-
for (const t of collection.all)
|
|
158
|
-
byId.set(collection.idOf(t), t);
|
|
159
|
-
return ids.map((id) => {
|
|
160
|
-
const t = byId.get(id);
|
|
161
|
-
if (!t)
|
|
162
|
-
throw new Error(`Internal error: missing item for id "${id}"`);
|
|
163
|
-
return t;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
File without changes
|