@teambit/builder 1.0.107 → 1.0.108

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.
Files changed (55) hide show
  1. package/build-pipe.ts +216 -0
  2. package/build-pipeline-order.ts +192 -0
  3. package/build-pipeline-result-list.ts +97 -0
  4. package/build-task.ts +151 -0
  5. package/build.cmd.ts +157 -0
  6. package/builder-env-type.ts +16 -0
  7. package/builder.aspect.ts +5 -0
  8. package/builder.graphql.ts +185 -0
  9. package/builder.main.runtime.ts +493 -0
  10. package/builder.route.ts +95 -0
  11. package/dist/artifact/artifact-definition.d.ts +2 -2
  12. package/dist/artifact/artifact-extractor.d.ts +3 -3
  13. package/dist/artifact/artifact-factory.d.ts +1 -1
  14. package/dist/artifact/artifact-factory.js +1 -2
  15. package/dist/artifact/artifact-factory.js.map +1 -1
  16. package/dist/artifact/artifact-list.d.ts +1 -1
  17. package/dist/artifact/artifact.d.ts +1 -1
  18. package/dist/artifact/artifacts.cmd.d.ts +1 -1
  19. package/dist/build-pipe.d.ts +3 -3
  20. package/dist/build-pipe.js +5 -12
  21. package/dist/build-pipe.js.map +1 -1
  22. package/dist/build-pipeline-result-list.d.ts +2 -2
  23. package/dist/build-pipeline-result-list.js +1 -2
  24. package/dist/build-pipeline-result-list.js.map +1 -1
  25. package/dist/build-task.d.ts +1 -1
  26. package/dist/build.cmd.d.ts +1 -1
  27. package/dist/builder.composition.d.ts +2 -2
  28. package/dist/builder.graphql.d.ts +17 -17
  29. package/dist/builder.graphql.js +4 -8
  30. package/dist/builder.graphql.js.map +1 -1
  31. package/dist/builder.main.runtime.d.ts +5 -5
  32. package/dist/builder.main.runtime.js +11 -14
  33. package/dist/builder.main.runtime.js.map +1 -1
  34. package/dist/builder.route.d.ts +1 -1
  35. package/dist/builder.route.js +3 -3
  36. package/dist/builder.route.js.map +1 -1
  37. package/dist/builder.service.d.ts +9 -9
  38. package/dist/builder.service.js +4 -4
  39. package/dist/builder.service.js.map +1 -1
  40. package/dist/pipeline.d.ts +1 -1
  41. package/dist/{preview-1703590665075.js → preview-1703647408454.js} +2 -2
  42. package/dist/storage/storage-resolver.d.ts +2 -2
  43. package/dist/task-results-list.js +2 -8
  44. package/dist/task-results-list.js.map +1 -1
  45. package/dist/tasks-queue.d.ts +1 -1
  46. package/dist/types.d.ts +2 -2
  47. package/index.ts +20 -0
  48. package/package.json +26 -33
  49. package/pipeline.ts +104 -0
  50. package/task-results-list.ts +68 -0
  51. package/task.ts +50 -0
  52. package/tasks-queue.ts +40 -0
  53. package/tsconfig.json +16 -21
  54. package/types/asset.d.ts +15 -3
  55. package/types.ts +37 -0
@@ -0,0 +1,493 @@
1
+ import { cloneDeep } from 'lodash';
2
+ import { ArtifactVinyl } from '@teambit/legacy/dist/consumer/component/sources/artifact';
3
+ import { ArtifactFiles, ArtifactObject } from '@teambit/legacy/dist/consumer/component/sources/artifact-files';
4
+ import { AspectLoaderAspect, AspectLoaderMain } from '@teambit/aspect-loader';
5
+ import { CLIAspect, CLIMain, MainRuntime } from '@teambit/cli';
6
+ import { Component, ComponentMap, IComponent, ComponentAspect, ComponentMain, ComponentID } from '@teambit/component';
7
+ import { EnvsAspect, EnvsMain } from '@teambit/envs';
8
+ import { GraphqlAspect, GraphqlMain } from '@teambit/graphql';
9
+ import { Slot, SlotRegistry } from '@teambit/harmony';
10
+ import GlobalConfigAspect, { GlobalConfigMain } from '@teambit/global-config';
11
+ import { Logger, LoggerAspect, LoggerMain } from '@teambit/logger';
12
+ import AspectAspect from '@teambit/aspect';
13
+ import { ScopeAspect, ScopeMain } from '@teambit/scope';
14
+ import { Workspace, WorkspaceAspect } from '@teambit/workspace';
15
+ import { IsolateComponentsOptions, IsolatorAspect, IsolatorMain } from '@teambit/isolator';
16
+ import { getHarmonyVersion } from '@teambit/legacy/dist/bootstrap';
17
+ import findDuplications from '@teambit/legacy/dist/utils/array/find-duplications';
18
+ import { GeneratorAspect, GeneratorMain } from '@teambit/generator';
19
+ import { UIAspect, UiMain, BundleUiTask } from '@teambit/ui';
20
+ import { Artifact, ArtifactList, FsArtifact } from './artifact';
21
+ import { ArtifactFactory } from './artifact/artifact-factory'; // it gets undefined when importing it from './artifact'
22
+ import { BuilderAspect } from './builder.aspect';
23
+ import { builderSchema } from './builder.graphql';
24
+ import { BuilderService, BuilderServiceOptions } from './builder.service';
25
+ import { BuilderCmd } from './build.cmd';
26
+ import { BuildTask, BuildTaskHelper } from './build-task';
27
+ import { TaskResults } from './build-pipe';
28
+ import { TaskResultsList } from './task-results-list';
29
+ import { ArtifactStorageError } from './exceptions';
30
+ import { BuildPipelineResultList, AspectData, PipelineReport } from './build-pipeline-result-list';
31
+ import { TaskMetadata } from './types';
32
+ import { ArtifactsCmd } from './artifact/artifacts.cmd';
33
+ import { buildTaskTemplate } from './templates/build-task';
34
+ import { BuilderRoute } from './builder.route';
35
+
36
+ export type TaskSlot = SlotRegistry<BuildTask[]>;
37
+ export type OnTagResults = { builderDataMap: ComponentMap<RawBuilderData>; pipeResults: TaskResultsList[] };
38
+ export type OnTagOpts = {
39
+ disableTagAndSnapPipelines?: boolean;
40
+ throwOnError?: boolean; // on the CI it helps to save the results on failure so this is set to false
41
+ forceDeploy?: boolean; // whether run the deploy-pipeline although the build-pipeline has failed
42
+ populateArtifactsFrom?: ComponentID[]; // helpful for tagging from scope where we want to use the build-artifacts of previous snap.
43
+ isSnap?: boolean;
44
+ };
45
+ export const FILE_PATH_PARAM_DELIM = '~';
46
+
47
+ /**
48
+ * builder data format for the bit object store
49
+ */
50
+ export type RawBuilderData = {
51
+ pipeline: PipelineReport[];
52
+ artifacts?: ArtifactObject[];
53
+ aspectsData: AspectData[];
54
+ bitVersion?: string;
55
+ };
56
+ /**
57
+ * builder data mapped to an ArtifactList instance
58
+ */
59
+ export type BuilderData = Omit<RawBuilderData, 'artifacts'> & {
60
+ artifacts: ArtifactList<Artifact>;
61
+ };
62
+
63
+ export class BuilderMain {
64
+ constructor(
65
+ private envs: EnvsMain,
66
+ private workspace: Workspace,
67
+ private buildService: BuilderService,
68
+ private tagService: BuilderService,
69
+ private snapService: BuilderService,
70
+ private scope: ScopeMain,
71
+ private isolator: IsolatorMain,
72
+ private aspectLoader: AspectLoaderMain,
73
+ private componentAspect: ComponentMain,
74
+ private globalConfig: GlobalConfigMain,
75
+ private buildTaskSlot: TaskSlot,
76
+ private tagTaskSlot: TaskSlot,
77
+ private snapTaskSlot: TaskSlot,
78
+ private logger: Logger
79
+ ) {}
80
+
81
+ private async storeArtifacts(tasksResults: TaskResults[]) {
82
+ const artifacts = tasksResults.flatMap((t) => (t.artifacts ? [t.artifacts] : []));
83
+ const storeP = artifacts.map(async (artifactMap: ComponentMap<ArtifactList<FsArtifact>>) => {
84
+ return Promise.all(
85
+ artifactMap.toArray().map(async ([component, artifactList]) => {
86
+ try {
87
+ return await artifactList.store(component);
88
+ } catch (err: any) {
89
+ throw new ArtifactStorageError(err, component);
90
+ }
91
+ })
92
+ );
93
+ });
94
+ await Promise.all(storeP);
95
+ }
96
+
97
+ private pipelineResultsToBuilderData(
98
+ components: Component[],
99
+ buildPipelineResults: TaskResults[]
100
+ ): ComponentMap<RawBuilderData> {
101
+ const buildPipelineResultList = new BuildPipelineResultList(buildPipelineResults, components);
102
+ return ComponentMap.as<RawBuilderData>(components, (component) => {
103
+ const aspectsData = buildPipelineResultList.getDataOfComponent(component.id);
104
+ const pipelineReport = buildPipelineResultList.getPipelineReportOfComponent(component.id);
105
+ const artifacts = buildPipelineResultList.getArtifactsDataOfComponent(component.id);
106
+ return { pipeline: pipelineReport, artifacts, aspectsData, bitVersion: getHarmonyVersion(true) };
107
+ });
108
+ }
109
+
110
+ async tagListener(
111
+ components: Component[],
112
+ options: OnTagOpts = {},
113
+ isolateOptions: IsolateComponentsOptions = {},
114
+ builderOptions: BuilderServiceOptions = {}
115
+ ): Promise<OnTagResults> {
116
+ const pipeResults: TaskResultsList[] = [];
117
+ const allTasksResults: TaskResults[] = [];
118
+ const { throwOnError, forceDeploy, disableTagAndSnapPipelines, isSnap, populateArtifactsFrom } = options;
119
+ if (populateArtifactsFrom) isolateOptions.populateArtifactsFrom = populateArtifactsFrom;
120
+ const buildEnvsExecutionResults = await this.build(
121
+ components,
122
+ { emptyRootDir: true, ...isolateOptions },
123
+ {
124
+ ...builderOptions,
125
+ // even when build is skipped (in case of tag-from-scope), the pre-build/post-build and teambit.harmony/aspect tasks are needed
126
+ tasks: populateArtifactsFrom ? [AspectAspect.id] : undefined,
127
+ }
128
+ );
129
+ if (throwOnError && !forceDeploy) buildEnvsExecutionResults.throwErrorsIfExist();
130
+ allTasksResults.push(...buildEnvsExecutionResults.tasksResults);
131
+ pipeResults.push(buildEnvsExecutionResults);
132
+
133
+ if (forceDeploy || (!disableTagAndSnapPipelines && !buildEnvsExecutionResults?.hasErrors())) {
134
+ const builderOptionsForTagSnap: BuilderServiceOptions = {
135
+ ...builderOptions,
136
+ seedersOnly: isolateOptions.seedersOnly,
137
+ previousTasksResults: buildEnvsExecutionResults?.tasksResults,
138
+ };
139
+ const deployEnvsExecutionResults = isSnap
140
+ ? await this.runSnapTasks(components, builderOptionsForTagSnap)
141
+ : await this.runTagTasks(components, builderOptionsForTagSnap);
142
+ if (throwOnError && !forceDeploy) deployEnvsExecutionResults.throwErrorsIfExist();
143
+ allTasksResults.push(...deployEnvsExecutionResults.tasksResults);
144
+ pipeResults.push(deployEnvsExecutionResults);
145
+ }
146
+ await this.storeArtifacts(allTasksResults);
147
+ const builderDataMap = this.pipelineResultsToBuilderData(components, allTasksResults);
148
+ if (populateArtifactsFrom) await this.combineBuildDataFrom(builderDataMap, populateArtifactsFrom);
149
+ this.validateBuilderDataMap(builderDataMap);
150
+
151
+ return { builderDataMap, pipeResults };
152
+ }
153
+
154
+ private validateBuilderDataMap(builderDataMap: ComponentMap<RawBuilderData>) {
155
+ builderDataMap.forEach((buildData: RawBuilderData, component) => {
156
+ const taskSerializedIds = buildData.pipeline.map((t) =>
157
+ BuildTaskHelper.serializeId({ aspectId: t.taskId, name: t.taskName })
158
+ );
159
+ const duplications = findDuplications(taskSerializedIds);
160
+ if (duplications.length) {
161
+ throw new Error(
162
+ `build-task-results validation has failed. the following task(s) of "${component.id.toString()}" are duplicated: ${duplications.join(
163
+ ', '
164
+ )}`
165
+ );
166
+ }
167
+ });
168
+ }
169
+
170
+ private async combineBuildDataFrom(
171
+ builderDataMap: ComponentMap<RawBuilderData>,
172
+ populateArtifactsFrom: ComponentID[]
173
+ ) {
174
+ const promises = builderDataMap.map(async (builderData, component) => {
175
+ const populateFrom = populateArtifactsFrom.find((id) => id.isEqual(component.id, { ignoreVersion: true }));
176
+ const idStr = component.id.toString();
177
+ if (!populateFrom) {
178
+ throw new Error(`combineBuildDataFromParent: unable to find where to populate the artifacts from for ${idStr}`);
179
+ }
180
+ const populateFromComp = await this.componentAspect.getHost().get(populateFrom);
181
+ if (!populateFromComp)
182
+ throw new Error(
183
+ `combineBuildDataFromParent, unable to load parent component of ${idStr}. hash: ${populateFrom.version}`
184
+ );
185
+ const populateFromBuilderData = this.getBuilderData(populateFromComp);
186
+ if (!populateFromBuilderData) throw new Error(`parent of ${idStr} was not built yet. unable to continue`);
187
+ populateFromBuilderData.artifacts.forEach((artifact) => {
188
+ const artifactObj = artifact.toObject();
189
+ if (!builderData.artifacts) builderData.artifacts = [];
190
+ if (
191
+ builderData.artifacts.find((a) => a.task.id === artifactObj.task.id && a.task.name === artifactObj.task.name)
192
+ ) {
193
+ return;
194
+ }
195
+ builderData.artifacts.push(artifactObj);
196
+ });
197
+ populateFromBuilderData.aspectsData.forEach((aspectData) => {
198
+ if (builderData.aspectsData.find((a) => a.aspectId === aspectData.aspectId)) return;
199
+ builderData.aspectsData.push(aspectData);
200
+ });
201
+ populateFromBuilderData.pipeline.forEach((pipeline) => {
202
+ if (builderData.pipeline.find((p) => p.taskId === pipeline.taskId && p.taskName === pipeline.taskName)) return;
203
+ builderData.pipeline.push(pipeline);
204
+ });
205
+ });
206
+
207
+ await Promise.all(promises.flattenValue());
208
+ }
209
+
210
+ // TODO: merge with getArtifactsVinylByExtensionAndName by getting aspect name and name as object with optional props
211
+ async getArtifactsVinylByAspect(component: Component, aspectName: string): Promise<ArtifactVinyl[]> {
212
+ const artifacts = this.getArtifactsByAspect(component, aspectName);
213
+ const vinyls = await artifacts.getVinylsAndImportIfMissing(component.id, this.scope.legacyScope);
214
+ return vinyls;
215
+ }
216
+
217
+ async getArtifactsVinylByAspectAndName(
218
+ component: Component,
219
+ aspectName: string,
220
+ name: string
221
+ ): Promise<ArtifactVinyl[]> {
222
+ const artifacts = this.getArtifactsByAspectAndName(component, aspectName, name);
223
+ const vinyls = await artifacts.getVinylsAndImportIfMissing(component.id, this.scope.legacyScope);
224
+ return vinyls;
225
+ }
226
+
227
+ async getArtifactsVinylByAspectAndTaskName(
228
+ component: Component,
229
+ aspectName: string,
230
+ name: string
231
+ ): Promise<ArtifactVinyl[]> {
232
+ const artifacts = this.getArtifactsbyAspectAndTaskName(component, aspectName, name);
233
+ const vinyls = await artifacts.getVinylsAndImportIfMissing(component.id, this.scope.legacyScope);
234
+ return vinyls;
235
+ }
236
+
237
+ getArtifactsByName(component: Component, name: string): ArtifactList<Artifact> {
238
+ const artifacts = this.getArtifacts(component).byAspectNameAndName(undefined, name);
239
+ return artifacts;
240
+ }
241
+
242
+ getArtifactsByAspect(component: Component, aspectName: string): ArtifactList<Artifact> {
243
+ const artifacts = this.getArtifacts(component).byAspectNameAndName(aspectName);
244
+ return artifacts;
245
+ }
246
+
247
+ getArtifactsByAspectAndName(component: Component, aspectName: string, name: string): ArtifactList<Artifact> {
248
+ const artifacts = this.getArtifacts(component).byAspectNameAndName(aspectName, name);
249
+ return artifacts;
250
+ }
251
+
252
+ getArtifactsbyAspectAndTaskName(component: IComponent, aspectName: string, taskName: string): ArtifactList<Artifact> {
253
+ const artifacts = this.getArtifacts(component).byAspectNameAndTaskName(aspectName, taskName);
254
+ return artifacts;
255
+ }
256
+
257
+ /**
258
+ * this is the aspect's data that was generated as "metadata" of the task component-result during the build process
259
+ * and saved by the builder aspect in the "aspectsData" property.
260
+ * (not to be confused with the data saved in the aspect itself, which is saved in the "data" property of the aspect).
261
+ */
262
+ getDataByAspect(component: IComponent, aspectName: string): TaskMetadata | undefined {
263
+ const aspectsData = this.getBuilderData(component)?.aspectsData;
264
+ const data = aspectsData?.find((aspectData) => aspectData.aspectId === aspectName);
265
+ return data?.data;
266
+ }
267
+
268
+ getArtifacts(component: IComponent): ArtifactList<Artifact> {
269
+ const artifacts = this.getBuilderData(component)?.artifacts || ArtifactList.fromArray([]);
270
+ return artifacts;
271
+ }
272
+
273
+ getBuilderData(component: IComponent): BuilderData | undefined {
274
+ const data = component.get(BuilderAspect.id)?.data;
275
+ if (!data) return undefined;
276
+ const clonedData = cloneDeep(data) as BuilderData;
277
+ let artifactFiles: ArtifactFiles;
278
+ const artifacts = clonedData.artifacts?.map((artifact) => {
279
+ if (!(artifact.files instanceof ArtifactFiles)) {
280
+ artifactFiles = ArtifactFiles.fromObject(artifact.files);
281
+ } else {
282
+ artifactFiles = artifact.files;
283
+ }
284
+ if (artifact instanceof Artifact) {
285
+ return artifact;
286
+ }
287
+ Object.assign(artifact, { files: artifactFiles });
288
+ return Artifact.fromArtifactObject(artifact);
289
+ });
290
+ clonedData.artifacts = ArtifactList.fromArray(artifacts || []);
291
+ return clonedData;
292
+ }
293
+
294
+ async build(
295
+ components: Component[],
296
+ isolateOptions?: IsolateComponentsOptions,
297
+ builderOptions?: BuilderServiceOptions,
298
+ extraOptions?: { includeTag?: boolean; includeSnap?: boolean }
299
+ ): Promise<TaskResultsList> {
300
+ const ids = components.map((c) => c.id);
301
+ const capsulesBaseDir = this.buildService.getComponentsCapsulesBaseDir();
302
+ const baseIsolateOpts = {
303
+ baseDir: capsulesBaseDir,
304
+ useHash: !capsulesBaseDir,
305
+ };
306
+ const mergedIsolateOpts = {
307
+ ...baseIsolateOpts,
308
+ ...isolateOptions,
309
+ };
310
+
311
+ const network = await this.isolator.isolateComponents(ids, mergedIsolateOpts, this.scope.legacyScope);
312
+ const envs = await this.envs.createEnvironment(network.graphCapsules.getAllComponents());
313
+ const builderServiceOptions = {
314
+ seedersOnly: isolateOptions?.seedersOnly,
315
+ originalSeeders: ids,
316
+ capsulesBaseDir,
317
+ ...(builderOptions || {}),
318
+ };
319
+ this.logger.consoleTitle(`Total ${components.length} components to build`);
320
+ const buildResult: TaskResultsList = await envs.runOnce(this.buildService, builderServiceOptions);
321
+
322
+ if (extraOptions?.includeSnap || extraOptions?.includeTag) {
323
+ const builderOptionsForTagSnap: BuilderServiceOptions = {
324
+ ...builderServiceOptions,
325
+ previousTasksResults: buildResult.tasksResults,
326
+ };
327
+ const deployEnvsExecutionResults = extraOptions?.includeSnap
328
+ ? await this.runSnapTasks(components, builderOptionsForTagSnap)
329
+ : await this.runTagTasks(components, builderOptionsForTagSnap);
330
+ buildResult.tasksResults.push(...deployEnvsExecutionResults.tasksResults);
331
+ }
332
+
333
+ return buildResult;
334
+ }
335
+
336
+ async runTagTasks(components: Component[], builderOptions: BuilderServiceOptions): Promise<TaskResultsList> {
337
+ const envs = await this.envs.createEnvironment(components);
338
+ const buildResult = await envs.runOnce(this.tagService, builderOptions);
339
+
340
+ return buildResult;
341
+ }
342
+
343
+ async runSnapTasks(components: Component[], builderOptions: BuilderServiceOptions): Promise<TaskResultsList> {
344
+ const envs = await this.envs.createEnvironment(components);
345
+ const buildResult = await envs.runOnce(this.snapService, builderOptions);
346
+
347
+ return buildResult;
348
+ }
349
+
350
+ listTasks(component: Component) {
351
+ const compEnv = this.envs.getEnv(component);
352
+ const buildTasks = this.buildService.getCurrentPipeTasks(compEnv);
353
+ const tagTasks = this.tagService.getCurrentPipeTasks(compEnv);
354
+ const snapTasks = this.snapService.getCurrentPipeTasks(compEnv);
355
+ return { id: component.id, envId: compEnv.id, buildTasks, tagTasks, snapTasks };
356
+ }
357
+
358
+ /**
359
+ * register a build task to apply on all component build pipelines.
360
+ * build happens on `bit build` and as part of `bit tag --persist`.
361
+ */
362
+ registerBuildTasks(tasks: BuildTask[]) {
363
+ this.buildTaskSlot.register(tasks);
364
+ return this;
365
+ }
366
+
367
+ /**
368
+ * @deprecated use registerTagTasks or registerSnapTasks
369
+ */
370
+ registerDeployTasks(tasks: BuildTask[]) {
371
+ this.tagTaskSlot.register(tasks);
372
+ return this;
373
+ }
374
+
375
+ /**
376
+ * tag tasks that don't get executed on `bit build`, only on `bit tag'.
377
+ * this pipeline is running once the build-pipeline has completed.
378
+ */
379
+ registerTagTasks(tasks: BuildTask[]) {
380
+ this.tagTaskSlot.register(tasks);
381
+ return this;
382
+ }
383
+
384
+ /**
385
+ * tag tasks that don't get executed on `bit build`, only on `bit snap'.
386
+ * this pipeline is running once the build-pipeline has completed.
387
+ */
388
+ registerSnapTasks(tasks: BuildTask[]) {
389
+ this.snapTaskSlot.register(tasks);
390
+ return this;
391
+ }
392
+
393
+ getDownloadUrlForArtifact(componentId: ComponentID, taskId: string, path?: string) {
394
+ return `/api/${componentId}/~aspect/builder/${taskId}/${path ? `${FILE_PATH_PARAM_DELIM}${path}` : ''}`;
395
+ }
396
+
397
+ static slots = [Slot.withType<BuildTask>(), Slot.withType<BuildTask>(), Slot.withType<BuildTask>()];
398
+
399
+ static runtime = MainRuntime;
400
+ static dependencies = [
401
+ CLIAspect,
402
+ EnvsAspect,
403
+ WorkspaceAspect,
404
+ ScopeAspect,
405
+ IsolatorAspect,
406
+ LoggerAspect,
407
+ AspectLoaderAspect,
408
+ GraphqlAspect,
409
+ GeneratorAspect,
410
+ ComponentAspect,
411
+ UIAspect,
412
+ GlobalConfigAspect,
413
+ ];
414
+
415
+ static async provider(
416
+ [cli, envs, workspace, scope, isolator, loggerExt, aspectLoader, graphql, generator, component, ui, globalConfig]: [
417
+ CLIMain,
418
+ EnvsMain,
419
+ Workspace,
420
+ ScopeMain,
421
+ IsolatorMain,
422
+ LoggerMain,
423
+ AspectLoaderMain,
424
+ GraphqlMain,
425
+ GeneratorMain,
426
+ ComponentMain,
427
+ UiMain,
428
+ GlobalConfigMain
429
+ ],
430
+ config,
431
+ [buildTaskSlot, tagTaskSlot, snapTaskSlot]: [TaskSlot, TaskSlot, TaskSlot]
432
+ ) {
433
+ const artifactFactory = new ArtifactFactory();
434
+ const logger = loggerExt.createLogger(BuilderAspect.id);
435
+ const buildService = new BuilderService(
436
+ isolator,
437
+ logger,
438
+ buildTaskSlot,
439
+ 'getBuildPipe',
440
+ 'build',
441
+ artifactFactory,
442
+ scope,
443
+ globalConfig
444
+ );
445
+ envs.registerService(buildService);
446
+ const tagService = new BuilderService(
447
+ isolator,
448
+ logger,
449
+ tagTaskSlot,
450
+ 'getTagPipe',
451
+ 'tag',
452
+ artifactFactory,
453
+ scope,
454
+ globalConfig
455
+ );
456
+ const snapService = new BuilderService(
457
+ isolator,
458
+ logger,
459
+ snapTaskSlot,
460
+ 'getSnapPipe',
461
+ 'snap',
462
+ artifactFactory,
463
+ scope,
464
+ globalConfig
465
+ );
466
+ const builder = new BuilderMain(
467
+ envs,
468
+ workspace,
469
+ buildService,
470
+ tagService,
471
+ snapService,
472
+ scope,
473
+ isolator,
474
+ aspectLoader,
475
+ component,
476
+ globalConfig,
477
+ buildTaskSlot,
478
+ tagTaskSlot,
479
+ snapTaskSlot,
480
+ logger
481
+ );
482
+ builder.registerBuildTasks([new BundleUiTask(ui, logger)]);
483
+ component.registerRoute([new BuilderRoute(builder, scope, logger)]);
484
+ graphql.register(builderSchema(builder, logger));
485
+ if (generator) generator.registerComponentTemplate([buildTaskTemplate]);
486
+ const commands = [new BuilderCmd(builder, workspace, logger), new ArtifactsCmd(builder, component)];
487
+ cli.register(...commands);
488
+
489
+ return builder;
490
+ }
491
+ }
492
+
493
+ BuilderAspect.addRuntime(BuilderMain);
@@ -0,0 +1,95 @@
1
+ import { Request, Response, Route } from '@teambit/express';
2
+ import { Component } from '@teambit/component';
3
+ import archiver from 'archiver';
4
+ import { Logger } from '@teambit/logger';
5
+ import { ScopeMain } from '@teambit/scope';
6
+ import mime from 'mime';
7
+ import { BuilderMain } from './builder.main.runtime';
8
+
9
+ export const routePath = `builder`;
10
+
11
+ export type BuilderUrlParams = {
12
+ aspectId?: string;
13
+ filePath?: string;
14
+ };
15
+ export const defaultExtension = '.tgz';
16
+ export class BuilderRoute implements Route {
17
+ constructor(private builder: BuilderMain, private scope: ScopeMain, private logger: Logger) {}
18
+ route = `/${routePath}/*`;
19
+ method = 'get';
20
+
21
+ middlewares = [
22
+ async (req: Request<BuilderUrlParams>, res: Response) => {
23
+ // @ts-ignore TODO: @guy please fix.
24
+ const component = req.component as Component;
25
+ const { params } = req;
26
+ const [aspectIdStr, filePath] = params[1].split('~');
27
+ // remove trailing slash
28
+ const aspectId = aspectIdStr.replace(/\/$/, '');
29
+ const artifacts = aspectId
30
+ ? this.builder.getArtifactsByAspect(component, aspectId)
31
+ : this.builder.getArtifacts(component);
32
+ if (!artifacts)
33
+ return res
34
+ .status(404)
35
+ .jsonp({ error: `no artifacts found for component ${component.id} by aspect ${aspectId}` });
36
+ const extensionsWithArtifacts = await Promise.all(
37
+ artifacts.map(async (artifact) => {
38
+ const files = await artifact.files.getVinylsAndImportIfMissing(component.id, this.scope.legacyScope);
39
+ if (!filePath) return { extensionId: artifact.task.aspectId, files };
40
+ return { extensionId: artifact.task.aspectId, files: files.filter((file) => file.path === filePath) };
41
+ })
42
+ );
43
+
44
+ const artifactFilesCount = extensionsWithArtifacts.reduce((accum, next) => accum + next.files.length, 0);
45
+
46
+ if (artifactFilesCount === 0)
47
+ return res
48
+ .status(404)
49
+ .jsonp({ error: `no artifacts found for component ${component.id} by aspect ${aspectId}` });
50
+
51
+ if (artifactFilesCount === 1) {
52
+ const extensionWithArtifact = extensionsWithArtifacts.find((e) => e.files.length > 0);
53
+ const fileName = `${extensionWithArtifact?.extensionId}_${extensionWithArtifact?.files[0].path}`;
54
+ const fileContent = extensionWithArtifact?.files[0].contents;
55
+ const fileExt = extensionWithArtifact?.files[0].extname || defaultExtension;
56
+ const contentType = mime.getType(fileExt);
57
+ res.set('Content-disposition', `attachment; filename=${fileName}`);
58
+ if (contentType) res.set('Content-Type', contentType);
59
+ return res.send(fileContent);
60
+ }
61
+
62
+ /**
63
+ * if more than 1 file requested, zip them before sending
64
+ */
65
+ const archive = archiver('tar', { gzip: true });
66
+
67
+ archive.on('warning', (warn) => {
68
+ this.logger.warn(warn.message);
69
+ });
70
+
71
+ archive.on('error', (err) => {
72
+ this.logger.error(err.message);
73
+ });
74
+
75
+ extensionsWithArtifacts.forEach((extensionWithArtifacts) => {
76
+ extensionWithArtifacts.files.forEach((artifact) => {
77
+ archive.append(artifact.contents, { name: `${extensionWithArtifacts.extensionId}_${artifact.path}` });
78
+ });
79
+ });
80
+
81
+ try {
82
+ archive.pipe(res);
83
+ /**
84
+ * promise that is returned from the await zip.finalize(); is resolved before the archive is actually finalized
85
+ * resolving it results in setting the headers before the stream is finished
86
+ */
87
+ // eslint-disable-next-line no-void
88
+ void archive.finalize();
89
+ return res.attachment(`${aspectId.replace('/', '_')}.tar`);
90
+ } catch (e: any) {
91
+ return res.send({ error: e.toString() });
92
+ }
93
+ },
94
+ ];
95
+ }
@@ -1,5 +1,5 @@
1
1
  import { ArtifactStorageResolver } from '..';
2
- export declare type ArtifactDefinition = {
2
+ export type ArtifactDefinition = {
3
3
  /**
4
4
  * name of the artifact.
5
5
  * e.g. a project might utilize two different artifacts for the same typescript compiler, one
@@ -60,6 +60,6 @@ export declare type ArtifactDefinition = {
60
60
  */
61
61
  storageResolver?: ArtifactStorageResolver;
62
62
  };
63
- export declare type ArtifactModelDefinition = Omit<ArtifactDefinition, 'storageResolver'> & {
63
+ export type ArtifactModelDefinition = Omit<ArtifactDefinition, 'storageResolver'> & {
64
64
  storageResolver?: string;
65
65
  };
@@ -2,17 +2,17 @@ import { ComponentMain } from '@teambit/component';
2
2
  import { ComponentID } from '@teambit/component-id';
3
3
  import { BuilderMain } from '../builder.main.runtime';
4
4
  import { ArtifactsOpts } from './artifacts.cmd';
5
- export declare type ExtractorResult = {
5
+ export type ExtractorResult = {
6
6
  id: ComponentID;
7
7
  artifacts: ExtractorArtifactResult[];
8
8
  };
9
- export declare type ExtractorArtifactResult = {
9
+ export type ExtractorArtifactResult = {
10
10
  artifactName: string;
11
11
  aspectId: string;
12
12
  taskName: string;
13
13
  files: string[];
14
14
  };
15
- export declare type ExtractorResultGrouped = {
15
+ export type ExtractorResultGrouped = {
16
16
  id: ComponentID;
17
17
  artifacts: {
18
18
  [aspectId: string]: ExtractorArtifactResult[];
@@ -4,7 +4,7 @@ import { ArtifactList } from './artifact-list';
4
4
  import type { BuildContext, BuildTask } from '../build-task';
5
5
  import { FsArtifact } from './fs-artifact';
6
6
  export declare const DEFAULT_CONTEXT = "component";
7
- export declare type ArtifactMap = ComponentMap<ArtifactList<FsArtifact>>;
7
+ export type ArtifactMap = ComponentMap<ArtifactList<FsArtifact>>;
8
8
  export declare class ArtifactFactory {
9
9
  resolvePaths(root: string, def: ArtifactDefinition): string[];
10
10
  private getArtifactContextPath;
@@ -80,8 +80,7 @@ class ArtifactFactory {
80
80
  getArtifactContextPath(context, component, def) {
81
81
  const artifactContext = this.getArtifactContext(def);
82
82
  if (artifactContext === 'component') {
83
- var _context$capsuleNetwo;
84
- const capsulePath = (_context$capsuleNetwo = context.capsuleNetwork.graphCapsules.getCapsule(component.id)) === null || _context$capsuleNetwo === void 0 ? void 0 : _context$capsuleNetwo.path;
83
+ const capsulePath = context.capsuleNetwork.graphCapsules.getCapsule(component.id)?.path;
85
84
  if (!capsulePath) throw new (_exceptions().CapsuleNotFound)(component.id);
86
85
  return capsulePath;
87
86
  }