hammerkit 1.21.0 → 1.21.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.
@@ -108,20 +108,18 @@ class RegistryClient {
108
108
  this.authorization = `Bearer ${token}`;
109
109
  return true;
110
110
  }
111
- // Send a request, performing the auth handshake once on a 401. `body` is a
112
- // factory because a streamed body cannot be replayed after the challenge.
111
+ // Send a request, performing the auth handshake once on a 401. A string,
112
+ // buffer or blob body is replayable and fetch derives its content-length;
113
+ // never set that header by hand, the undici that serves `fetch` rejects it
114
+ // on some Node versions.
113
115
  async request(method, urlOrPath, init = {}) {
114
116
  const url = urlOrPath.startsWith('http') ? urlOrPath : this.url(urlOrPath);
115
- const send = () => {
116
- const body = init.body?.();
117
- return fetch(url, {
118
- method,
119
- headers: { ...(init.headers ?? {}), ...(this.authorization ? { authorization: this.authorization } : {}) },
120
- body: body instanceof stream_1.Readable ? stream_1.Readable.toWeb(body) : body,
121
- redirect: init.redirect ?? 'follow',
122
- ...(body instanceof stream_1.Readable ? { duplex: 'half' } : {}),
123
- });
124
- };
117
+ const send = () => fetch(url, {
118
+ method,
119
+ headers: { ...(init.headers ?? {}), ...(this.authorization ? { authorization: this.authorization } : {}) },
120
+ body: init.body?.(),
121
+ redirect: init.redirect ?? 'follow',
122
+ });
125
123
  let response = await send();
126
124
  if (response.status === 401 && (await this.authenticate(response.headers.get('www-authenticate')))) {
127
125
  await response.body?.cancel();
@@ -223,8 +221,10 @@ class RegistryClient {
223
221
  return true;
224
222
  }
225
223
  // Upload a blob from a buffer or a file (monolithic upload: POST to open the
226
- // session, PUT the content with its digest to commit it).
227
- async uploadBlob(digest, size, content) {
224
+ // session, PUT the content with its digest to commit it). A file is sent as a
225
+ // blob backed by the file, so it is read lazily and fetch sends its size as
226
+ // the content-length.
227
+ async uploadBlob(digest, content) {
228
228
  if (await this.blobExists(digest)) {
229
229
  return;
230
230
  }
@@ -238,9 +238,10 @@ class RegistryClient {
238
238
  }
239
239
  const uploadUrl = new URL(location, this.url(''));
240
240
  uploadUrl.searchParams.set('digest', digest);
241
+ const body = 'buffer' in content ? content.buffer : await (0, fs_1.openAsBlob)(content.file);
241
242
  const response = await this.request('PUT', uploadUrl.toString(), {
242
- headers: { 'content-type': 'application/octet-stream', 'content-length': `${size}` },
243
- body: () => ('buffer' in content ? content.buffer : (0, fs_1.createReadStream)(content.file)),
243
+ headers: { 'content-type': 'application/octet-stream' },
244
+ body: () => body,
244
245
  });
245
246
  if (response.status !== 201) {
246
247
  return this.fail(response, `uploading blob ${digest}`);
@@ -85,8 +85,8 @@ function createRegistryCacheBackend(spec) {
85
85
  config: { Labels: { 'dev.hammerkit.task-id': taskId, 'dev.hammerkit.state-key': stateKey } },
86
86
  }));
87
87
  const configDigest = (0, registry_client_1.digestBuffer)(config);
88
- await registry.uploadBlob(layer.digest, layer.size, { file: layerFile });
89
- await registry.uploadBlob(configDigest.digest, configDigest.size, { buffer: config });
88
+ await registry.uploadBlob(layer.digest, { file: layerFile });
89
+ await registry.uploadBlob(configDigest.digest, { buffer: config });
90
90
  const manifest = {
91
91
  schemaVersion: 2,
92
92
  mediaType: registry_client_1.OCI_MANIFEST,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.awaitRunningNeeds = exports.awaitRunningNeed = exports.awaitCompleted = exports.awaitCompletedDependencies = exports.awaitNoRequirements = exports.awaitDependentNeed = exports.awaitRequirement = void 0;
3
+ exports.awaitRunningNeeds = exports.awaitRunningNeed = exports.awaitCompleted = exports.awaitCompletedDependencies = exports.transitiveDeps = exports.awaitNoRequirements = exports.awaitDependentNeed = exports.awaitRequirement = void 0;
4
4
  const work_item_1 = require("../planner/work-item");
5
5
  const state_resolver_1 = require("./state-resolver");
6
6
  // Resolve true once any requirer is `ready` (about to run). With
@@ -42,8 +42,26 @@ async function awaitNoRequirements(svc, abort) {
42
42
  }));
43
43
  }
44
44
  exports.awaitNoRequirements = awaitNoRequirements;
45
+ // The dependencies and, recursively, theirs, each once.
46
+ function transitiveDeps(deps) {
47
+ const seen = new Set();
48
+ const visit = (items) => {
49
+ for (const item of items) {
50
+ if (!seen.has(item)) {
51
+ seen.add(item);
52
+ visit(item.deps);
53
+ }
54
+ }
55
+ };
56
+ visit(deps);
57
+ return [...seen];
58
+ }
59
+ exports.transitiveDeps = transitiveDeps;
60
+ // Waits for the whole dependency closure, not only the direct dependencies: a
61
+ // cached direct dependency completes at once, while the dependencies it
62
+ // skipped past are still being restored for the item about to run.
45
63
  async function awaitCompletedDependencies(work, deps, abort) {
46
- for (const dep of deps) {
64
+ for (const dep of transitiveDeps(deps)) {
47
65
  work.status.write('debug', `await completion of ${dep.name} with state ${dep.state.current.type}`);
48
66
  await awaitCompleted(work, dep, abort);
49
67
  }
@@ -5,6 +5,7 @@ const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
5
5
  const execute_work_service_1 = require("./execute-work-service");
6
6
  const execute_work_task_1 = require("./execute-work-task");
7
7
  const execute_init_task_1 = require("./execute-init-task");
8
+ const await_completed_dependencies_1 = require("./await-completed-dependencies");
8
9
  async function executeWorkTree(work, environment, options) {
9
10
  const itemPromises = [];
10
11
  const dependents = skippableDependents(work, options);
@@ -67,9 +68,13 @@ function skippableDependents(work, options) {
67
68
  result.set(task.name, []);
68
69
  }
69
70
  }
71
+ // A dependent also needs everything its dependencies need: a cached `build`
72
+ // does not restore the `install` a re-running `test` uses at run time. So a
73
+ // task is a dependent of its whole dependency closure, requested tasks
74
+ // included in the walk (they are cached or run on their own).
70
75
  const items = [...(0, plan_work_tasks_1.iterateWorkTasks)(work), ...(0, plan_work_tasks_1.iterateWorkServices)(work)];
71
76
  for (const item of items) {
72
- for (const dep of item.deps) {
77
+ for (const dep of (0, await_completed_dependencies_1.transitiveDeps)(item.deps)) {
73
78
  if (work.requested.includes(dep.name)) {
74
79
  continue;
75
80
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hammerkit",
3
3
  "description": "Containerized build tool with incremental caching that runs the same build file locally, in Docker and on Kubernetes.",
4
- "version": "1.21.0",
4
+ "version": "1.21.2",
5
5
  "keywords": [
6
6
  "build",
7
7
  "build-tool",