Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions dist/commands/hooks.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/commands/hooks.js.map

Large diffs are not rendered by default.

53 changes: 32 additions & 21 deletions dist/commitlore.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,34 @@ if ! mv "$dest_tmp" "$dest"; then
fi
dest_tmp=""

# A version-free path to the bundle, maintained beside the versioned checkouts
# (#693).
#
# `commitlore hooks install` records an absolute path to the bundle so a hook is
# independent of PATH and of any node_modules/.bin/commitlore above the
# repository. Recording the versioned checkout pins that repository to one
# release: three repositories on the first machine to upgrade were still
# validating commits with 0.8.2 and 0.8.0, and this repository was one of them.
#
# The wrapper cannot stand in for it -- that was tried and fails, because a hook
# runs where PATH may carry no node and a shell script cannot be launched with
# the recorded interpreter. A symlink to the checkout keeps both properties: an
# absolute path to a .mjs, and one that does not name a release.
#
# Symlink, then rename: an existing `current` cannot be replaced in place while
# something is reading through it.
current_link="$data_root/current"
current_tmp="$current_link.commitlore-install.$$"
if ln -sfn "$candidate" "$current_tmp" 2>/dev/null && mv -f "$current_tmp" "$current_link" 2>/dev/null; then
log "current -> $(basename "$candidate")"
else
rm -f "$current_tmp" 2>/dev/null || true
# Not fatal. A host without symlinks still has a working install; hooks there
# keep recording the versioned path and `commitlore hooks install` after an
# upgrade remains the repair, which `doctor` already names.
log "note: could not maintain $current_link -- hooks will record a versioned path"
fi

log "installed to $dest"
printf '%s\n' "$verified_version"

Expand Down
10 changes: 5 additions & 5 deletions installer/canonical-artifact.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"tsconfig.json",
"src"
],
"sha256": "064b3810c7c371bfa1cc838fbc3c2e8db8c856f22c0d07e8b2a4e61d3a660c11"
"sha256": "1d11f0bdafe983a545bf070830f32f1afcf6b437a7d154e6413f73651293fb75"
},
"artifact": {
"sha256": "c312755d70a4d15b2743f95b9999694ef0fbc433f9ee645acc903c5275ef5a5d",
"sha256": "997d93d4667f50c81db815da8f0a0858cd5f63752d1cd1bbb9e3a2464fffc433",
"files": [
{
"path": "dist/cli.d.ts",
Expand Down Expand Up @@ -422,11 +422,11 @@
},
{
"path": "dist/commands/hooks.js",
"sha256": "f6c02cae9c88838b3ee7eb99670593e4b99c6dc416908141712708f009482f32"
"sha256": "33d6ebc8e69684983b258b3253423eef7085a49ce44e7579116bed4f7644c5e2"
},
{
"path": "dist/commands/hooks.js.map",
"sha256": "fd48593e755a6c4efd0350219595cb62de56366d0aefc4ad91c0c7ecd2f22513"
"sha256": "21f2746932ed945e5260d4d688b5da6ee6d591ee95a5472fd17d3098681c8958"
},
{
"path": "dist/commands/index-cmd.d.ts",
Expand Down Expand Up @@ -586,7 +586,7 @@
},
{
"path": "dist/commitlore.mjs",
"sha256": "136348243607bca3c8f3130825d458a862444c440307d396f1977b34ea6da910"
"sha256": "7dc734bebd046ff5e30277d17807673107fba5254637113772ab834dd9d98b15"
},
{
"path": "dist/core/agent-configs.d.ts",
Expand Down
41 changes: 38 additions & 3 deletions src/commands/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
unlinkSync,
writeFileSync,
} from 'node:fs';
import { join, resolve } from 'node:path';
import { basename, dirname, join, resolve } from 'node:path';

import type { Command } from 'commander';

Expand Down Expand Up @@ -234,9 +234,44 @@ export const resolveEntryForRecord = (entry: string | undefined, cwd: string): s
return null;
};

/**
* The same bundle reached through a path that does not name a release (#693).
*
* `process.argv[1]` under a normal install is
* `<data-root>/v<version>/dist/commitlore.mjs`, and recording it pins the
* repository to that release: an upgrade leaves the hook validating commits
* with the old build while the CLI reports the new one. Three repositories on
* the first machine to upgrade were doing exactly that, this one among them,
* throughout two releases.
*
* `install.sh` maintains `<data-root>/current` beside the versioned checkouts.
* It is still an absolute path to a `.mjs`, so the recorded interpreter can
* launch it and the hook stays independent of PATH — the properties the
* versioned path was chosen for. The `bin` wrapper cannot serve here: it is a
* shell script, and a hook runs where PATH may carry no node at all.
*
* Only accepted when it resolves to the bundle that is running. A `current`
* pointing elsewhere belongs to another install, and sending a hook to code
* this one never verified is worse than pinning a version.
*/
const versionFreeEntryFor = (bundle: string): string | null => {
// Derived from the layout, not from a directory name: <root>/v<x>/dist/<file>.
// Matching on the literal "commitlore" would have made this depend on what the
// data root happens to be called.
const versionDir = dirname(dirname(bundle));
if (!/^v\d/.test(basename(versionDir))) return null;
const candidate = join(dirname(versionDir), 'current', 'dist', 'commitlore.mjs');
try {
return realpathSync(candidate) === realpathSync(bundle) ? candidate : null;
} catch {
return null;
}
};

const recordBinPath = (cwd: string): void => {
const resolvedEntry = resolveEntryForRecord(process.argv[1], cwd);
if (resolvedEntry === null) return;
const running = resolveEntryForRecord(process.argv[1], cwd);
if (running === null) return;
const resolvedEntry = versionFreeEntryFor(running) ?? running;
execGit(['config', '--local', 'commitlore.bin', resolvedEntry], { cwd });
// The interpreter as well: the branch that reads these back runs in a hook
// whose PATH may not carry node, which is the whole reason it exists. For a
Expand Down
Loading
Loading