@olivervea/pipelines 2.0.0-beta.3 → 2.0.0-beta.31
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 +165 -78
- package/manifest.schema.json +300 -16
- package/package.json +6 -4
- package/src/assets.d.ts +2 -2
- package/src/build/build.ts +98 -64
- package/src/build/cli.ts +33 -23
- package/src/build/elf.ts +16 -7
- package/src/build/hashes.ts +40 -51
- package/src/build/project.ts +55 -52
- package/src/builder/pipeline.ts +348 -89
- package/src/builder/refs.ts +15 -5
- package/src/core/brands.ts +6 -5
- package/src/core/config-refs.ts +103 -0
- package/src/core/config.ts +224 -0
- package/src/core/external.ts +86 -0
- package/src/core/handlers.ts +62 -15
- package/src/core/matrix.ts +150 -0
- package/src/core/models.ts +37 -15
- package/src/core/secrets.ts +2 -2
- package/src/core/steps.ts +113 -38
- package/src/core/types.ts +5 -1
- package/src/core/webhooks.ts +21 -7
- package/src/ctx/files.ts +119 -42
- package/src/ctx/index.ts +110 -39
- package/src/ctx/links.ts +30 -0
- package/src/ctx/sh.ts +106 -44
- package/src/ctx/types.ts +24 -4
- package/src/describe/config-schema.ts +98 -0
- package/src/describe/context.ts +187 -0
- package/src/describe/describe.ts +63 -355
- package/src/describe/failure-handlers.ts +34 -0
- package/src/describe/manifest-schema.ts +304 -61
- package/src/describe/mapper-source.ts +252 -105
- package/src/describe/record.ts +67 -37
- package/src/describe/schemas.ts +71 -0
- package/src/describe/stages.ts +166 -0
- package/src/describe/step.ts +544 -0
- package/src/describe/triggers.ts +72 -0
- package/src/dev/cli.ts +62 -35
- package/src/harness/env.ts +47 -25
- package/src/harness/execute.ts +74 -30
- package/src/harness/exit.ts +1 -1
- package/src/harness/index.ts +19 -8
- package/src/harness/main.ts +245 -99
- package/src/harness/mask.ts +98 -0
- package/src/harness/result.ts +45 -24
- package/src/harness/validate.ts +82 -15
- package/src/index.ts +139 -10
- package/src/library/checks.ts +11 -6
- package/src/library/compose-deploy.ts +115 -0
- package/src/library/github-source.ts +100 -47
- package/src/library/helm-deploy-ssh.ts +426 -112
- package/src/library/helm-deploy.ts +529 -0
- package/src/library/http-health.ts +7 -5
- package/src/library/image-build.ts +510 -162
- package/src/library/index.ts +48 -14
- package/src/library/models.ts +35 -14
- package/src/library/ssh.ts +44 -18
- package/src/testing/index.ts +100 -35
- package/src/util/canonical.ts +7 -4
- package/src/util/code-hash.ts +17 -0
- package/src/util/hash.ts +5 -5
- package/src/util/tar.ts +83 -38
- package/src/library/script.ts +0 -85
- package/src/util/handler-hash.ts +0 -22
package/manifest.schema.json
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
{
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"properties": {
|
|
8
|
+
"cells": {
|
|
9
|
+
"const": true,
|
|
10
|
+
"description": "cells(Model): every cell of a matrix step whose output is the model, mapped with a whole-step ref; the value is [{cell, output}, …] in cell order. Omitted otherwise.",
|
|
11
|
+
"type": "boolean"
|
|
12
|
+
},
|
|
8
13
|
"model": {
|
|
9
14
|
"description": "Model name (key of `models`).",
|
|
10
15
|
"pattern": "^[A-Za-z][A-Za-z0-9_]*$",
|
|
@@ -47,6 +52,47 @@
|
|
|
47
52
|
"const": "2.0",
|
|
48
53
|
"type": "string"
|
|
49
54
|
},
|
|
55
|
+
"codeHash": {
|
|
56
|
+
"description": "Hash of the whole build: the pipeline and its code bundled, plus the harness. Any code change makes a new pipeline version.",
|
|
57
|
+
"pattern": "^sha256:[0-9a-f]{64}$",
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"config": {
|
|
61
|
+
"description": "Config entries used by steps, sorted by name (omitted when none). Fields of kind secretRef ({secret: NAME}) name a secret.",
|
|
62
|
+
"items": {
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"properties": {
|
|
65
|
+
"description": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
"name": {
|
|
69
|
+
"description": "Config entry name; a key of the pipeline's config document.",
|
|
70
|
+
"pattern": "^[a-z][a-zA-Z0-9]*$",
|
|
71
|
+
"type": "string"
|
|
72
|
+
},
|
|
73
|
+
"required": {
|
|
74
|
+
"description": "False when the schema accepts a missing value (optional, or a default).",
|
|
75
|
+
"type": "boolean"
|
|
76
|
+
},
|
|
77
|
+
"schema": {
|
|
78
|
+
"additionalProperties": {},
|
|
79
|
+
"description": "z.input view: validates the config document's value for this entry (defaults listed).",
|
|
80
|
+
"propertyNames": {
|
|
81
|
+
"type": "string"
|
|
82
|
+
},
|
|
83
|
+
"type": "object"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"required": [
|
|
87
|
+
"name",
|
|
88
|
+
"description",
|
|
89
|
+
"required",
|
|
90
|
+
"schema"
|
|
91
|
+
],
|
|
92
|
+
"type": "object"
|
|
93
|
+
},
|
|
94
|
+
"type": "array"
|
|
95
|
+
},
|
|
50
96
|
"description": {
|
|
51
97
|
"type": "string"
|
|
52
98
|
},
|
|
@@ -85,7 +131,7 @@
|
|
|
85
131
|
},
|
|
86
132
|
"harness": {
|
|
87
133
|
"additionalProperties": false,
|
|
88
|
-
"description": "Harness identity. Already folded into
|
|
134
|
+
"description": "Harness identity. Already folded into codeHash.",
|
|
89
135
|
"properties": {
|
|
90
136
|
"bun": {
|
|
91
137
|
"description": "Bun runtime version compiled into the binary.",
|
|
@@ -129,6 +175,28 @@
|
|
|
129
175
|
"minLength": 1,
|
|
130
176
|
"type": "string"
|
|
131
177
|
},
|
|
178
|
+
"retention": {
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"description": "Retention overrides for this pipeline (omitted when none). Each field replaces the instance setting of the same meaning.",
|
|
181
|
+
"properties": {
|
|
182
|
+
"buildGraceDays": {
|
|
183
|
+
"maximum": 9007199254740991,
|
|
184
|
+
"minimum": 0,
|
|
185
|
+
"type": "integer"
|
|
186
|
+
},
|
|
187
|
+
"keepLatestRuns": {
|
|
188
|
+
"maximum": 9007199254740991,
|
|
189
|
+
"minimum": 0,
|
|
190
|
+
"type": "integer"
|
|
191
|
+
},
|
|
192
|
+
"minRunAgeDays": {
|
|
193
|
+
"maximum": 9007199254740991,
|
|
194
|
+
"minimum": 0,
|
|
195
|
+
"type": "integer"
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"type": "object"
|
|
199
|
+
},
|
|
132
200
|
"secrets": {
|
|
133
201
|
"description": "All declared secrets (steps + poll triggers), sorted by name.",
|
|
134
202
|
"items": {
|
|
@@ -169,6 +237,7 @@
|
|
|
169
237
|
"name",
|
|
170
238
|
"input",
|
|
171
239
|
"harness",
|
|
240
|
+
"codeHash",
|
|
172
241
|
"models",
|
|
173
242
|
"secrets",
|
|
174
243
|
"triggers",
|
|
@@ -220,6 +289,84 @@
|
|
|
220
289
|
}
|
|
221
290
|
]
|
|
222
291
|
},
|
|
292
|
+
"Matrix": {
|
|
293
|
+
"additionalProperties": false,
|
|
294
|
+
"description": "A matrix step runs once per cell, named '<step>[<axis>=<value>,…]' in axis order; OLVE_CELL holds the cell as compact JSON.",
|
|
295
|
+
"properties": {
|
|
296
|
+
"axes": {
|
|
297
|
+
"description": "In order: the cells are the cross product, first axis outermost, minus the excludes (1 to 64 cells).",
|
|
298
|
+
"items": {
|
|
299
|
+
"additionalProperties": false,
|
|
300
|
+
"properties": {
|
|
301
|
+
"name": {
|
|
302
|
+
"pattern": "^[a-z][a-zA-Z0-9]{0,30}$",
|
|
303
|
+
"type": "string"
|
|
304
|
+
},
|
|
305
|
+
"values": {
|
|
306
|
+
"description": "Unique per axis.",
|
|
307
|
+
"items": {
|
|
308
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$",
|
|
309
|
+
"type": "string"
|
|
310
|
+
},
|
|
311
|
+
"minItems": 1,
|
|
312
|
+
"type": "array"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"required": [
|
|
316
|
+
"name",
|
|
317
|
+
"values"
|
|
318
|
+
],
|
|
319
|
+
"type": "object"
|
|
320
|
+
},
|
|
321
|
+
"maxItems": 4,
|
|
322
|
+
"minItems": 1,
|
|
323
|
+
"type": "array"
|
|
324
|
+
},
|
|
325
|
+
"exclude": {
|
|
326
|
+
"description": "Each entry names one or more axes with one of their values; a cell matching every named axis is left out (omitted when none).",
|
|
327
|
+
"items": {
|
|
328
|
+
"additionalProperties": {
|
|
329
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$",
|
|
330
|
+
"type": "string"
|
|
331
|
+
},
|
|
332
|
+
"propertyNames": {
|
|
333
|
+
"pattern": "^[a-z][a-zA-Z0-9]{0,30}$",
|
|
334
|
+
"type": "string"
|
|
335
|
+
},
|
|
336
|
+
"type": "object"
|
|
337
|
+
},
|
|
338
|
+
"type": "array"
|
|
339
|
+
},
|
|
340
|
+
"failure": {
|
|
341
|
+
"additionalProperties": false,
|
|
342
|
+
"description": "keepRunning: every cell runs to the end. cascadeFailure: a cell whose last allowed attempt failed cancels the step's other scheduled or running cells.",
|
|
343
|
+
"properties": {
|
|
344
|
+
"kind": {
|
|
345
|
+
"enum": [
|
|
346
|
+
"keepRunning",
|
|
347
|
+
"cascadeFailure"
|
|
348
|
+
],
|
|
349
|
+
"type": "string"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"required": [
|
|
353
|
+
"kind"
|
|
354
|
+
],
|
|
355
|
+
"type": "object"
|
|
356
|
+
},
|
|
357
|
+
"maxParallel": {
|
|
358
|
+
"description": "At most this many of the step's cells running at once in a run.",
|
|
359
|
+
"maximum": 9007199254740991,
|
|
360
|
+
"minimum": 1,
|
|
361
|
+
"type": "integer"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"required": [
|
|
365
|
+
"axes",
|
|
366
|
+
"failure"
|
|
367
|
+
],
|
|
368
|
+
"type": "object"
|
|
369
|
+
},
|
|
223
370
|
"ModelSchemas": {
|
|
224
371
|
"additionalProperties": false,
|
|
225
372
|
"properties": {
|
|
@@ -297,30 +444,170 @@
|
|
|
297
444
|
"stages"
|
|
298
445
|
],
|
|
299
446
|
"type": "object"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"additionalProperties": false,
|
|
450
|
+
"properties": {
|
|
451
|
+
"cases": {
|
|
452
|
+
"additionalProperties": {
|
|
453
|
+
"items": {
|
|
454
|
+
"$ref": "#/$defs/Stage"
|
|
455
|
+
},
|
|
456
|
+
"type": "array"
|
|
457
|
+
},
|
|
458
|
+
"description": "Discriminator value → stages. Exactly one case runs; a step name may repeat across cases, nowhere else.",
|
|
459
|
+
"propertyNames": {
|
|
460
|
+
"type": "string"
|
|
461
|
+
},
|
|
462
|
+
"type": "object"
|
|
463
|
+
},
|
|
464
|
+
"discriminator": {
|
|
465
|
+
"description": "The union's literal field whose value names the case.",
|
|
466
|
+
"minLength": 1,
|
|
467
|
+
"type": "string"
|
|
468
|
+
},
|
|
469
|
+
"select": {
|
|
470
|
+
"description": "Config reference (`<entry>.<field>…`) to a discriminated union; the run's config snapshot picks the case.",
|
|
471
|
+
"minLength": 1,
|
|
472
|
+
"type": "string"
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
"required": [
|
|
476
|
+
"select",
|
|
477
|
+
"discriminator",
|
|
478
|
+
"cases"
|
|
479
|
+
],
|
|
480
|
+
"type": "object"
|
|
300
481
|
}
|
|
301
482
|
]
|
|
302
483
|
},
|
|
303
484
|
"Step": {
|
|
304
485
|
"additionalProperties": false,
|
|
305
486
|
"properties": {
|
|
487
|
+
"clusterAccess": {
|
|
488
|
+
"const": true,
|
|
489
|
+
"description": "The step pod gets the instance's step ServiceAccount token (Kubernetes:StepServiceAccount), if the instance grants one. Omitted = no token.",
|
|
490
|
+
"type": "boolean"
|
|
491
|
+
},
|
|
306
492
|
"config": {
|
|
307
493
|
"additionalProperties": {
|
|
308
494
|
"$ref": "#/$defs/Json"
|
|
309
495
|
},
|
|
310
|
-
"description": "
|
|
496
|
+
"description": "The step's settings (display only). A config reference is recorded as {\"$config\": \"<entry>.<field>…\"}; the entry is in configEntries.",
|
|
311
497
|
"propertyNames": {
|
|
312
498
|
"type": "string"
|
|
313
499
|
},
|
|
314
500
|
"type": "object"
|
|
315
501
|
},
|
|
502
|
+
"configEntries": {
|
|
503
|
+
"description": "Config entries the step receives (sorted; omitted when none). The service delivers them in input.json under '$config'.",
|
|
504
|
+
"items": {
|
|
505
|
+
"description": "Config entry name; a key of the pipeline's config document.",
|
|
506
|
+
"pattern": "^[a-z][a-zA-Z0-9]*$",
|
|
507
|
+
"type": "string"
|
|
508
|
+
},
|
|
509
|
+
"type": "array"
|
|
510
|
+
},
|
|
316
511
|
"description": {
|
|
317
512
|
"description": "What the step does, shown in the UI (may be empty).",
|
|
318
513
|
"type": "string"
|
|
319
514
|
},
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
|
|
323
|
-
|
|
515
|
+
"externalInputs": {
|
|
516
|
+
"additionalProperties": {
|
|
517
|
+
"additionalProperties": false,
|
|
518
|
+
"properties": {
|
|
519
|
+
"description": {
|
|
520
|
+
"type": "string"
|
|
521
|
+
},
|
|
522
|
+
"from": {
|
|
523
|
+
"description": "Who or what may answer.",
|
|
524
|
+
"items": {
|
|
525
|
+
"oneOf": [
|
|
526
|
+
{
|
|
527
|
+
"additionalProperties": false,
|
|
528
|
+
"properties": {
|
|
529
|
+
"kind": {
|
|
530
|
+
"const": "person",
|
|
531
|
+
"type": "string"
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
"required": [
|
|
535
|
+
"kind"
|
|
536
|
+
],
|
|
537
|
+
"type": "object"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
"additionalProperties": false,
|
|
541
|
+
"properties": {
|
|
542
|
+
"kind": {
|
|
543
|
+
"const": "callback",
|
|
544
|
+
"type": "string"
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
"required": [
|
|
548
|
+
"kind"
|
|
549
|
+
],
|
|
550
|
+
"type": "object"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"additionalProperties": false,
|
|
554
|
+
"properties": {
|
|
555
|
+
"intervalSeconds": {
|
|
556
|
+
"maximum": 9007199254740991,
|
|
557
|
+
"minimum": 10,
|
|
558
|
+
"type": "integer"
|
|
559
|
+
},
|
|
560
|
+
"kind": {
|
|
561
|
+
"const": "poll",
|
|
562
|
+
"type": "string"
|
|
563
|
+
},
|
|
564
|
+
"url": {
|
|
565
|
+
"description": "GET while waiting; {run.id} and {run.number} are filled in.",
|
|
566
|
+
"type": "string"
|
|
567
|
+
},
|
|
568
|
+
"valuePath": {
|
|
569
|
+
"description": "Dot path to the value; '.' is the whole body.",
|
|
570
|
+
"minLength": 1,
|
|
571
|
+
"type": "string"
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
"required": [
|
|
575
|
+
"kind",
|
|
576
|
+
"url",
|
|
577
|
+
"valuePath",
|
|
578
|
+
"intervalSeconds"
|
|
579
|
+
],
|
|
580
|
+
"type": "object"
|
|
581
|
+
}
|
|
582
|
+
]
|
|
583
|
+
},
|
|
584
|
+
"minItems": 1,
|
|
585
|
+
"type": "array"
|
|
586
|
+
},
|
|
587
|
+
"model": {
|
|
588
|
+
"description": "The value is validated against this model's input view.",
|
|
589
|
+
"pattern": "^[A-Za-z][A-Za-z0-9_]*$",
|
|
590
|
+
"type": "string"
|
|
591
|
+
},
|
|
592
|
+
"title": {
|
|
593
|
+
"description": "What is asked, shown to whoever answers.",
|
|
594
|
+
"minLength": 1,
|
|
595
|
+
"type": "string"
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
"required": [
|
|
599
|
+
"model",
|
|
600
|
+
"title",
|
|
601
|
+
"description",
|
|
602
|
+
"from"
|
|
603
|
+
],
|
|
604
|
+
"type": "object"
|
|
605
|
+
},
|
|
606
|
+
"description": "Inputs answered from outside the run (omitted when none): the step waits until every one has a value, which the service writes into input.json under its key.",
|
|
607
|
+
"propertyNames": {
|
|
608
|
+
"type": "string"
|
|
609
|
+
},
|
|
610
|
+
"type": "object"
|
|
324
611
|
},
|
|
325
612
|
"image": {
|
|
326
613
|
"description": "Container image the step runs in.",
|
|
@@ -348,16 +635,14 @@
|
|
|
348
635
|
"type": "object"
|
|
349
636
|
},
|
|
350
637
|
"kind": {
|
|
351
|
-
"description": "'custom'
|
|
352
|
-
"
|
|
353
|
-
"custom",
|
|
354
|
-
"script",
|
|
355
|
-
"gitHubSource",
|
|
356
|
-
"imageBuild",
|
|
357
|
-
"helmDeployOverSsh"
|
|
358
|
-
],
|
|
638
|
+
"description": "A label for the step's kind: 'custom' unless the step names one.",
|
|
639
|
+
"minLength": 1,
|
|
359
640
|
"type": "string"
|
|
360
641
|
},
|
|
642
|
+
"matrix": {
|
|
643
|
+
"$ref": "#/$defs/Matrix",
|
|
644
|
+
"description": "Set on a matrix step (never on a failure handler); omitted otherwise."
|
|
645
|
+
},
|
|
361
646
|
"name": {
|
|
362
647
|
"description": "Step name: lower-case kebab, 1-63 chars. The step's identity.",
|
|
363
648
|
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$",
|
|
@@ -404,8 +689,7 @@
|
|
|
404
689
|
"inputTypes",
|
|
405
690
|
"input",
|
|
406
691
|
"output",
|
|
407
|
-
"secrets"
|
|
408
|
-
"handlerHash"
|
|
692
|
+
"secrets"
|
|
409
693
|
],
|
|
410
694
|
"type": "object"
|
|
411
695
|
},
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olivervea/pipelines",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.31",
|
|
4
4
|
"description": "Olve.Pipelines v2 SDK: typed pipeline authoring, the step harness, and the build tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Oliver Vea",
|
|
8
|
-
"homepage": "https://github.com/OliverVea/Olve.Pipelines/tree/
|
|
8
|
+
"homepage": "https://github.com/OliverVea/Olve.Pipelines/tree/main/sdk#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/OliverVea/Olve.Pipelines.git",
|
|
12
|
-
"directory": "sdk"
|
|
12
|
+
"directory": "src/sdk"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/OliverVea/Olve.Pipelines/issues"
|
|
@@ -50,13 +50,15 @@
|
|
|
50
50
|
"test": "bun test",
|
|
51
51
|
"check-compile-fail": "bun scripts/check-compile-fail.ts",
|
|
52
52
|
"gen-schema": "bun scripts/gen-manifest-schema.ts",
|
|
53
|
-
"check": "tsc --noEmit -p . && bun scripts/check-compile-fail.ts && bun test"
|
|
53
|
+
"check": "prettier --check . && tsc --noEmit -p . && bun scripts/check-compile-fail.ts && bun test",
|
|
54
|
+
"format": "prettier --write ."
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"zod": "4.6.7"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/bun": "1.4.3",
|
|
61
|
+
"prettier": "^3.5.0",
|
|
60
62
|
"typescript": "7.0.2"
|
|
61
63
|
}
|
|
62
64
|
}
|
package/src/assets.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// `import script from "./scripts/x.sh" with { type: "file" }` yields a path string
|
|
2
2
|
// (inside the compiled binary it points into the embedded /$bunfs filesystem).
|
|
3
|
-
declare module
|
|
3
|
+
declare module '*.sh' {
|
|
4
4
|
const path: string;
|
|
5
5
|
export default path;
|
|
6
6
|
}
|
|
7
|
-
declare module
|
|
7
|
+
declare module '*.bash' {
|
|
8
8
|
const path: string;
|
|
9
9
|
export default path;
|
|
10
10
|
}
|
package/src/build/build.ts
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
// olve-build: pipeline dir → single executable + step.tar.
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from
|
|
3
|
-
import { join, resolve } from
|
|
4
|
-
import { canonicalJson } from
|
|
5
|
-
import { sha256File } from
|
|
6
|
-
import type { HarnessInfo } from
|
|
7
|
-
import {
|
|
8
|
-
import { MANIFEST_BEGIN, MANIFEST_END, uploadFile } from
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { harnessInfo, loadProject, relPosix, type Project } from
|
|
1
|
+
// olve-build: pipeline dir → single executable + step.tar.zst + manifest.json.
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { canonicalJson } from '../util/canonical.ts';
|
|
5
|
+
import { sha256File } from '../util/hash.ts';
|
|
6
|
+
import type { HarnessInfo } from '../util/code-hash.ts';
|
|
7
|
+
import { writeTarZst } from '../util/tar.ts';
|
|
8
|
+
import { MANIFEST_BEGIN, MANIFEST_END, uploadFile } from '../harness/result.ts';
|
|
9
|
+
import { loaderProblem } from './elf.ts';
|
|
10
|
+
import { codeHash } from './hashes.ts';
|
|
11
|
+
import { harnessInfo, loadProject, relPosix, type Project } from './project.ts';
|
|
12
12
|
|
|
13
|
-
export const BUILD_FLAGS = [
|
|
14
|
-
|
|
13
|
+
export const BUILD_FLAGS = [
|
|
14
|
+
'--no-compile-autoload-dotenv',
|
|
15
|
+
'--no-compile-autoload-bunfig',
|
|
16
|
+
'--bytecode',
|
|
17
|
+
'--format=esm',
|
|
18
|
+
] as const;
|
|
19
|
+
export type Target = 'bun-linux-x64' | 'bun-linux-x64-musl';
|
|
15
20
|
|
|
16
21
|
export class BuildError extends Error {
|
|
17
|
-
override name =
|
|
18
|
-
constructor(
|
|
22
|
+
override name = 'BuildError';
|
|
23
|
+
constructor(
|
|
24
|
+
message: string,
|
|
25
|
+
readonly exitCode = 1,
|
|
26
|
+
) {
|
|
19
27
|
super(message);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
@@ -23,7 +31,7 @@ export class BuildError extends Error {
|
|
|
23
31
|
export interface BuildOptions {
|
|
24
32
|
out: string;
|
|
25
33
|
target?: Target;
|
|
26
|
-
/** Pre-signed PUT for step.
|
|
34
|
+
/** Pre-signed PUT for the step archive. */
|
|
27
35
|
uploadUrl?: string;
|
|
28
36
|
/** Print the manifest between sentinels on stdout (for the build Job's pod log). */
|
|
29
37
|
sentinels?: boolean;
|
|
@@ -36,90 +44,116 @@ export interface BuildResult {
|
|
|
36
44
|
archive: string;
|
|
37
45
|
manifestFile: string;
|
|
38
46
|
manifest: string;
|
|
39
|
-
|
|
47
|
+
codeHash: string;
|
|
40
48
|
harness: HarnessInfo;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
/** Writes `<dir>/.olve/gen/
|
|
44
|
-
export function generateEntry(
|
|
45
|
-
|
|
51
|
+
/** Writes `<dir>/.olve/gen/entry.ts`, the entry point of the step binary. */
|
|
52
|
+
export function generateEntry(
|
|
53
|
+
project: Project,
|
|
54
|
+
code: string,
|
|
55
|
+
harness: HarnessInfo,
|
|
56
|
+
): string {
|
|
57
|
+
const gen = join(project.dir, '.olve', 'gen');
|
|
46
58
|
mkdirSync(gen, { recursive: true });
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
join(gen, "handlers.ts"),
|
|
50
|
-
`// GENERATED by olve-build — do not edit.\n` +
|
|
51
|
-
`import type { HandlerRegistry, HarnessInfo } from "@olivervea/pipelines/harness";\n\n` +
|
|
52
|
-
`export const handlers: HandlerRegistry = {\n${registry}\n};\n\n` +
|
|
53
|
-
`export const handlerHashes: Record<string, string> = ${JSON.stringify(hashes, null, 2)};\n\n` +
|
|
54
|
-
`export const harness: HarnessInfo = ${JSON.stringify(harness, null, 2)};\n`,
|
|
55
|
-
);
|
|
56
|
-
const entry = join(gen, "entry.ts");
|
|
59
|
+
const entry = join(gen, 'entry.ts');
|
|
60
|
+
const config = `{ pipeline, codeHash: ${JSON.stringify(code)}, harness: ${JSON.stringify(harness)} }`;
|
|
57
61
|
writeFileSync(
|
|
58
62
|
entry,
|
|
59
63
|
`// GENERATED by olve-build — do not edit. Entry point of the step binary.\n` +
|
|
60
64
|
`import pipeline from ${JSON.stringify(relPosix(gen, project.pipelineFile))};\n` +
|
|
61
|
-
`import { runHarness } from "@olivervea/pipelines/harness";\n` +
|
|
62
|
-
`
|
|
63
|
-
`process.exit(await runHarness({ pipeline, handlers, handlerHashes, harness }, process.argv.slice(2)));\n`,
|
|
65
|
+
`import { runHarness } from "@olivervea/pipelines/harness";\n\n` +
|
|
66
|
+
`process.exit(await runHarness(${config}, process.argv.slice(2)));\n`,
|
|
64
67
|
);
|
|
65
68
|
return entry;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export async function build(dir: string, opts: BuildOptions): Promise<BuildResult> {
|
|
69
|
-
const log = opts.log ?? ((l: string) => process.stderr.write(l +
|
|
70
|
-
const stdout =
|
|
71
|
-
|
|
72
|
+
const log = opts.log ?? ((l: string) => process.stderr.write(l + '\n'));
|
|
73
|
+
const stdout =
|
|
74
|
+
opts.stdout ?? (async (t: string) => void (await Bun.write(Bun.stdout, t)));
|
|
75
|
+
const target = opts.target ?? 'bun-linux-x64';
|
|
72
76
|
const project = await loadProject(dir);
|
|
73
77
|
const out = resolve(opts.out);
|
|
74
78
|
mkdirSync(out, { recursive: true });
|
|
75
79
|
const harness = harnessInfo();
|
|
76
80
|
|
|
77
81
|
const t0 = performance.now();
|
|
78
|
-
const
|
|
79
|
-
const entry = generateEntry(project,
|
|
80
|
-
log(`olve-build:
|
|
82
|
+
const code = await codeHash(project, harness);
|
|
83
|
+
const entry = generateEntry(project, code, harness);
|
|
84
|
+
log(`olve-build: hashed the code in ${((performance.now() - t0) / 1000).toFixed(2)}s`);
|
|
81
85
|
|
|
82
86
|
// 1. compile
|
|
83
|
-
const binary = join(out,
|
|
87
|
+
const binary = join(out, 'step');
|
|
84
88
|
rmSync(binary, { force: true });
|
|
85
89
|
const t1 = performance.now();
|
|
86
|
-
const p = Bun.spawnSync(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
const p = Bun.spawnSync(
|
|
91
|
+
[
|
|
92
|
+
process.execPath,
|
|
93
|
+
'build',
|
|
94
|
+
'--compile',
|
|
95
|
+
`--target=${target}`,
|
|
96
|
+
...BUILD_FLAGS,
|
|
97
|
+
entry,
|
|
98
|
+
`--outfile=${binary}`,
|
|
99
|
+
],
|
|
100
|
+
{
|
|
101
|
+
cwd: project.dir,
|
|
102
|
+
stdout: 'pipe',
|
|
103
|
+
stderr: 'pipe',
|
|
104
|
+
env: { ...process.env, NO_COLOR: '1' },
|
|
105
|
+
},
|
|
106
|
+
);
|
|
92
107
|
if (p.exitCode !== 0 || !existsSync(binary))
|
|
93
|
-
throw new BuildError(
|
|
94
|
-
|
|
108
|
+
throw new BuildError(
|
|
109
|
+
`bun build --compile failed (exit ${p.exitCode}):\n${p.stdout.toString()}${p.stderr.toString()}`,
|
|
110
|
+
);
|
|
111
|
+
log(
|
|
112
|
+
`olve-build: compiled ${target} in ${((performance.now() - t1) / 1000).toFixed(2)}s`,
|
|
113
|
+
);
|
|
95
114
|
|
|
96
|
-
// 2. loader check:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const interp = readInterpreter(binary);
|
|
100
|
-
if (interp !== expected)
|
|
101
|
-
throw new BuildError(`compiled binary has PT_INTERP ${interp ?? "(none)"}, expected ${expected} — compile on a glibc Bun image (oven/bun:<version>-slim), not alpine`);
|
|
115
|
+
// 2. loader check: on alpine, bun-linux-x64 silently yields a musl binary, which would not start.
|
|
116
|
+
const problem = loaderProblem(binary, target);
|
|
117
|
+
if (problem) throw new BuildError(problem);
|
|
102
118
|
|
|
103
119
|
// 3. describe (runs the real binary: the manifest is exactly what the step pods will see)
|
|
104
|
-
const d = Bun.spawnSync([binary,
|
|
105
|
-
|
|
120
|
+
const d = Bun.spawnSync([binary, 'describe'], {
|
|
121
|
+
cwd: out,
|
|
122
|
+
stdout: 'pipe',
|
|
123
|
+
stderr: 'pipe',
|
|
124
|
+
env: { PATH: process.env.PATH ?? '/usr/bin:/bin' },
|
|
125
|
+
});
|
|
126
|
+
if (d.exitCode !== 0)
|
|
127
|
+
throw new BuildError(
|
|
128
|
+
`describe failed (exit ${d.exitCode}):\n${d.stderr.toString()}`,
|
|
129
|
+
d.exitCode ?? 1,
|
|
130
|
+
);
|
|
106
131
|
const manifest = d.stdout.toString();
|
|
107
|
-
const manifestFile = join(out,
|
|
132
|
+
const manifestFile = join(out, 'manifest.json');
|
|
108
133
|
writeFileSync(manifestFile, manifest);
|
|
109
134
|
|
|
110
|
-
// 4. step.tar.
|
|
111
|
-
const archive = join(out,
|
|
112
|
-
await
|
|
135
|
+
// 4. step.tar.zst (deterministic: `step` at the root, 0755, mtime 0)
|
|
136
|
+
const archive = join(out, 'step.tar.zst');
|
|
137
|
+
await writeTarZst(archive, [
|
|
138
|
+
{ path: 'step', type: 'file', mode: 0o755, source: binary },
|
|
139
|
+
]);
|
|
113
140
|
writeFileSync(
|
|
114
|
-
join(out,
|
|
115
|
-
canonicalJson({
|
|
141
|
+
join(out, 'build.json'),
|
|
142
|
+
canonicalJson({
|
|
143
|
+
target,
|
|
144
|
+
flags: BUILD_FLAGS,
|
|
145
|
+
harness,
|
|
146
|
+
codeHash: code,
|
|
147
|
+
binarySha256: sha256File(binary),
|
|
148
|
+
archiveSha256: sha256File(archive),
|
|
149
|
+
}),
|
|
116
150
|
);
|
|
117
151
|
log(`olve-build: wrote ${archive}`);
|
|
118
152
|
|
|
119
153
|
if (opts.uploadUrl) {
|
|
120
154
|
await uploadFile(opts.uploadUrl, archive, log);
|
|
121
|
-
log(
|
|
155
|
+
log('olve-build: uploaded step.tar.zst');
|
|
122
156
|
}
|
|
123
157
|
if (opts.sentinels) await stdout(`${MANIFEST_BEGIN}\n${manifest}${MANIFEST_END}\n`);
|
|
124
|
-
return { binary, archive, manifestFile, manifest,
|
|
158
|
+
return { binary, archive, manifestFile, manifest, codeHash: code, harness };
|
|
125
159
|
}
|