hammerkit 1.15.0 → 1.17.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.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cleanCache = exports.storeCache = exports.getArchivePaths = exports.restoreCache = void 0;
4
+ const resolve_git_source_1 = require("../includes/resolve-git-source");
4
5
  const remove_orphaned_containers_1 = require("../docker/remove-orphaned-containers");
5
6
  const execute_docker_1 = require("./execute-docker");
6
7
  const plan_work_tasks_1 = require("../planner/utils/plan-work-tasks");
@@ -92,6 +93,10 @@ async function cleanCache(workTree, environment, options) {
92
93
  await service.data.caching.backend.clear(service.id(), environment);
93
94
  }
94
95
  }
96
+ if (options?.cache) {
97
+ // cached remote includes are kept like stored results, so they go with them
98
+ await (0, resolve_git_source_1.purgeGitSources)();
99
+ }
95
100
  if (workTree.environment.type === 'docker') {
96
101
  const removed = await (0, remove_orphaned_containers_1.removeOrphanedContainers)((0, execute_docker_1.getContainerCli)(workTree.environment));
97
102
  for (const id of removed) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isWithin = exports.resolveGitSource = exports.describeGitSource = exports.getIncludesDirectory = void 0;
3
+ exports.isWithin = exports.purgeGitSources = exports.refreshGitSource = exports.resolveGitSource = exports.describeGitSource = exports.getIncludesDirectory = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const crypto_1 = require("crypto");
6
6
  const promises_1 = require("fs/promises");
@@ -34,6 +34,34 @@ async function resolveGitSource(source, environment) {
34
34
  if (cached) {
35
35
  return cached;
36
36
  }
37
+ return fetchIntoCache(source, directory, environment, false);
38
+ }
39
+ exports.resolveGitSource = resolveGitSource;
40
+ // `hammerkit includes pull`: fetch a source again and replace the cached copy
41
+ // with the ref's current commit. The old copy stays when the fetch fails.
42
+ async function refreshGitSource(source, environment) {
43
+ const directory = (0, path_1.join)(getIncludesDirectory(), getSourceKey(source));
44
+ const cached = await readCached(directory);
45
+ const pinned = isCommitSha(source.ref);
46
+ const resolved = pinned && cached ? cached : await fetchIntoCache(source, directory, environment, cached !== null);
47
+ return {
48
+ git: source.git,
49
+ ref: source.ref ?? null,
50
+ previous: cached?.commit ?? null,
51
+ commit: resolved.commit,
52
+ pinned,
53
+ };
54
+ }
55
+ exports.refreshGitSource = refreshGitSource;
56
+ // `hammerkit clean --cache`: the next run fetches every source again.
57
+ async function purgeGitSources() {
58
+ await (0, promises_1.rm)(getIncludesDirectory(), { recursive: true, force: true });
59
+ }
60
+ exports.purgeGitSources = purgeGitSources;
61
+ function isCommitSha(ref) {
62
+ return ref !== undefined && /^[0-9a-f]{40}$/i.test(ref);
63
+ }
64
+ async function fetchIntoCache(source, directory, environment, replace) {
37
65
  await (0, promises_1.mkdir)(getIncludesDirectory(), { recursive: true });
38
66
  const staging = `${directory}.${(0, crypto_1.randomUUID)()}`;
39
67
  try {
@@ -41,6 +69,9 @@ async function resolveGitSource(source, environment) {
41
69
  const commit = await fetchInto((0, path_1.join)(staging, 'repo'), source, environment);
42
70
  const stored = { git: source.git, ref: source.ref ?? null, commit };
43
71
  await (0, promises_1.writeFile)((0, path_1.join)(staging, 'source.json'), JSON.stringify(stored, null, 2));
72
+ if (replace) {
73
+ await (0, promises_1.rm)(directory, { recursive: true, force: true });
74
+ }
44
75
  try {
45
76
  await (0, promises_1.rename)(staging, directory);
46
77
  }
@@ -61,7 +92,6 @@ async function resolveGitSource(source, environment) {
61
92
  }
62
93
  return resolved;
63
94
  }
64
- exports.resolveGitSource = resolveGitSource;
65
95
  async function readCached(directory) {
66
96
  try {
67
97
  const stored = JSON.parse(await (0, promises_1.readFile)((0, path_1.join)(directory, 'source.json'), 'utf8'));
package/dist/program.js CHANGED
@@ -313,6 +313,43 @@ async function getProgram(environment, argv) {
313
313
  program.error('Explain was not successful', { exitCode: 1 });
314
314
  }
315
315
  });
316
+ program
317
+ .command('includes')
318
+ .description('manage remote includes (includes pull)')
319
+ .command('pull')
320
+ .description('fetch the git repositories of remote includes again, picking up new commits of branches and tags')
321
+ .action(async () => {
322
+ try {
323
+ const { ctx } = await (0, schema_parser_1.createParseContext)(fileName, environment, { refresh: true });
324
+ const refreshed = [...(ctx.refreshed?.values() ?? [])];
325
+ for (const source of refreshed) {
326
+ const name = `${source.git} at ${source.ref ?? 'HEAD'}`;
327
+ const commit = source.commit.slice(0, 12);
328
+ if (source.pinned) {
329
+ environment.stdout.write(`${name}: pinned to ${commit}\n`);
330
+ }
331
+ else if (source.previous === source.commit) {
332
+ environment.stdout.write(`${name}: up to date at ${commit}\n`);
333
+ }
334
+ else if (source.previous === null) {
335
+ environment.stdout.write(`${name}: fetched ${commit}\n`);
336
+ }
337
+ else {
338
+ environment.stdout.write(`${name}: ${source.previous.slice(0, 12)} -> ${commit}\n`);
339
+ }
340
+ }
341
+ if (refreshed.length === 0) {
342
+ environment.stdout.write('no remote includes\n');
343
+ }
344
+ }
345
+ catch (e) {
346
+ if (e instanceof commander_1.CommanderError) {
347
+ throw e;
348
+ }
349
+ environment.console.error((0, log_1.getErrorMessage)(e));
350
+ program.error('Includes pull was not successful', { exitCode: 1 });
351
+ }
352
+ });
316
353
  const cacheCommand = program.command('cache').description('inspect, prune and move cache entries');
317
354
  for (const direction of ['pull', 'push']) {
318
355
  cacheCommand
@@ -8,9 +8,10 @@ const default_build_file_1 = require("../parser/default-build-file");
8
8
  const parse_error_1 = require("./parse-error");
9
9
  const build_file_include_schema_1 = require("./build-file-include-schema");
10
10
  const resolve_git_source_1 = require("../includes/resolve-git-source");
11
- async function createParseContext(fileName, environment) {
11
+ async function createParseContext(fileName, environment, options) {
12
12
  const ctx = {
13
13
  files: {},
14
+ refreshed: options?.refresh ? new Map() : undefined,
14
15
  };
15
16
  const scope = await appendBuildFile((0, path_1.dirname)(fileName), ctx, environment, fileName, []);
16
17
  return { ctx, scope };
@@ -37,7 +38,7 @@ async function appendBuildFile(cwd, ctx, environment, fileName, namePrefix, remo
37
38
  ctx.files[key] = scope;
38
39
  if (scope.schema.references) {
39
40
  for (const referenceName of Object.keys(scope.schema.references)) {
40
- const target = await locateInclude(scope, scope.schema.references[referenceName],
41
+ const target = await locateInclude(ctx, scope, scope.schema.references[referenceName],
41
42
  // a remote file's paths resolve within its repository
42
43
  remote ? (0, path_1.dirname)(scope.fileName) : scope.cwd, environment);
43
44
  const referenceScope = await appendBuildFile((0, path_1.dirname)(target.fileName), ctx, environment, target.fileName, [...namePrefix, referenceName], target.remote, target.inputs);
@@ -49,7 +50,7 @@ async function appendBuildFile(cwd, ctx, environment, fileName, namePrefix, remo
49
50
  }
50
51
  if (scope.schema.includes) {
51
52
  for (const includeName of Object.keys(scope.schema.includes)) {
52
- const target = await locateInclude(scope, scope.schema.includes[includeName], (0, path_1.dirname)(scope.fileName), environment);
53
+ const target = await locateInclude(ctx, scope, scope.schema.includes[includeName], (0, path_1.dirname)(scope.fileName), environment);
53
54
  const includeScope = await appendBuildFile(cwd, ctx, environment, target.fileName, [...namePrefix, includeName], target.remote, target.inputs);
54
55
  if (scope.references[includeName]) {
55
56
  throw new Error(includeName + ' already exists');
@@ -67,9 +68,9 @@ exports.appendBuildFile = appendBuildFile;
67
68
  // A string is a local path below `base`; an object is a git source, fetched once
68
69
  // into the include cache. Inside a cached checkout a local path may not leave it:
69
70
  // a remote file contributes definitions from its own repository only.
70
- async function locateInclude(scope, include, base, environment) {
71
+ async function locateInclude(ctx, scope, include, base, environment) {
71
72
  if ((0, build_file_include_schema_1.isGitSource)(include)) {
72
- const remote = await (0, resolve_git_source_1.resolveGitSource)(include, environment);
73
+ const remote = await resolveRemote(ctx, include, environment);
73
74
  const path = (0, path_1.join)(remote.root, include.path ?? '');
74
75
  if (!(0, resolve_git_source_1.isWithin)(remote.root, path)) {
75
76
  throw new Error(`${include.path} in ${include.git} points outside the repository`);
@@ -82,6 +83,15 @@ async function locateInclude(scope, include, base, environment) {
82
83
  }
83
84
  return { fileName: await (0, default_build_file_1.getBuildFilename)(path, environment), remote: scope.remote };
84
85
  }
86
+ // `includes pull` fetches each source once, however often the build files name it.
87
+ async function resolveRemote(ctx, source, environment) {
88
+ const key = `${source.git}\n${source.ref ?? ''}`;
89
+ if (!ctx.refreshed || ctx.refreshed.has(key)) {
90
+ return (0, resolve_git_source_1.resolveGitSource)(source, environment);
91
+ }
92
+ ctx.refreshed.set(key, await (0, resolve_git_source_1.refreshGitSource)(source, environment));
93
+ return (0, resolve_git_source_1.resolveGitSource)(source, environment);
94
+ }
85
95
  // The top-level `envs` of an included file are its inputs: it declares each one
86
96
  // with a default, and `with` replaces the default. A name the file does not
87
97
  // declare is rejected, so a typo cannot silently do nothing.
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.15.0",
4
+ "version": "1.17.0",
5
5
  "keywords": [
6
6
  "build",
7
7
  "build-tool",