Skip to content

Commit

Permalink
fix(hook): Windows support (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber committed Apr 2, 2023
1 parent f6d43f2 commit 2255583
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 23 deletions.
49 changes: 38 additions & 11 deletions src/commands/hook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { fileURLToPath, pathToFileURL } from 'url';
import { green, red } from 'kolorist';
import { command } from 'cleye';
import { assertGitRepo } from '../utils/git.js';
Expand All @@ -10,14 +10,24 @@ import { KnownError, handleCliError } from '../utils/error.js';
const hookName = 'prepare-commit-msg';
const symlinkPath = `.git/hooks/${hookName}`;

export const isCalledFromGitHook = process.argv[1].endsWith(`/${symlinkPath}`);
const hookPath = fileURLToPath(new URL('cli.mjs', import.meta.url));

export const isCalledFromGitHook = (
process.argv[1]
.replace(/\\/g, '/') // Replace Windows back slashes with forward slashes
.endsWith(`/${symlinkPath}`)
);

const isWindows = process.platform === 'win32';
const windowsHook = `
#!/usr/bin/env node
import(${JSON.stringify(pathToFileURL(hookPath))})
`.trim();

export default command({
name: 'hook',
parameters: ['<install/uninstall>'],
}, (argv) => {
const hookPath = fileURLToPath(new URL('cli.mjs', import.meta.url));

(async () => {
await assertGitRepo();

Expand All @@ -29,16 +39,24 @@ export default command({
// If the symlink is broken, it will throw an error
// eslint-disable-next-line @typescript-eslint/no-empty-function
const realpath = await fs.realpath(symlinkPath).catch(() => {});
if (realpath === hookPath) {
if (realpath === hookPath) {
console.warn('The hook is already installed');

Check warning on line 43 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 43 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement

Check warning on line 43 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 43 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement
return;
}
throw new KnownError(`A different ${hookName} hook seems to be installed. Please remove it before installing aicommits.`);
}

await fs.mkdir(path.dirname(symlinkPath), { recursive: true });
await fs.symlink(hookPath, symlinkPath, 'file');
await fs.chmod(symlinkPath, 0o755);

if (isWindows) {
await fs.writeFile(
symlinkPath,
windowsHook,
);
} else {
await fs.symlink(hookPath, symlinkPath, 'file');
await fs.chmod(symlinkPath, 0o755);
}
console.log(`${green('✔')} Hook installed`);

Check warning on line 60 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 60 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement

Check warning on line 60 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 60 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement
return;
}
Expand All @@ -48,10 +66,19 @@ export default command({
console.warn('Hook is not installed');

Check warning on line 66 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 66 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement

Check warning on line 66 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 66 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement
return;
}
const realpath = await fs.realpath(symlinkPath);
if (realpath !== hookPath) {
console.warn('Hook is not installed');
return;

if (isWindows) {
const scriptContent = await fs.readFile(symlinkPath, 'utf8');
if (scriptContent !== windowsHook) {
console.warn('Hook is not installed');

Check warning on line 73 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 73 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement

Check warning on line 73 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 73 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement
return;
}
} else {
const realpath = await fs.realpath(symlinkPath);
if (realpath !== hookPath) {
console.warn('Hook is not installed');

Check warning on line 79 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 79 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement

Check warning on line 79 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

Unexpected console statement

Check warning on line 79 in src/commands/hook.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Unexpected console statement
return;
}
}

await fs.rm(symlinkPath);
Expand Down
24 changes: 21 additions & 3 deletions src/commands/prepare-commit-msg-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,32 @@ export default () => (async () => {
} finally {
s.stop('Changes analyzed');
}

/**
* When `--no-edit` is passed in, the base commit message is empty,
* and even when you use pass in comments via #, they are ignored.
*
* Note: `--no-edit` cannot be detected in argvs so this is the only way to check
*/
const baseMessage = await fs.readFile(messageFilePath, 'utf8');
const supportsComments = baseMessage !== '';
const hasMultipleMessages = messages.length > 1;
let instructions = `# 🤖 AI generated commit${hasMultipleMessages ? 's' : ''}\n`;

let instructions = '';

if (supportsComments) {
instructions = `# 🤖 AI generated commit${hasMultipleMessages ? 's' : ''}\n`;
}

if (hasMultipleMessages) {
instructions += '# Select one of the following messages by uncommeting:\n';
if (supportsComments) {
instructions += '# Select one of the following messages by uncommeting:\n';
}
instructions += `\n${messages.map(message => `# ${message}`).join('\n')}`;
} else {
instructions += '# Edit the message below and commit:\n';
if (supportsComments) {
instructions += '# Edit the message below and commit:\n';
}
instructions += `\n${messages[0]}\n`;
}

Expand Down
1 change: 1 addition & 0 deletions tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { describe } from 'manten';
describe('aicommits', ({ runTestSuite }) => {
runTestSuite(import('./specs/cli/index.js'));
runTestSuite(import('./specs/config.js'));
runTestSuite(import('./specs/git-hook.js'));
});
11 changes: 2 additions & 9 deletions tests/specs/cli/commits.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { testSuite, expect } from 'manten';
import { createFixture, createGit } from '../../utils.js';

const { OPENAI_KEY } = process.env;
import { createFixture, createGit, files } from '../../utils.js';

export default testSuite(({ describe }) => {
if (process.platform === 'win32') {
Expand All @@ -10,17 +8,12 @@ export default testSuite(({ describe }) => {
return;
}

if (!OPENAI_KEY) {
if (!process.env.OPENAI_KEY) {
console.warn('⚠️ process.env.OPENAI_KEY is necessary to run these tests. Skipping...');
return;
}

describe('CLI', async ({ test, describe }) => {
const files = {
'.aicommits': `OPENAI_KEY=${OPENAI_KEY}`,
'data.json': 'Lorem ipsum dolor sit amet '.repeat(10),
} as const;

test('Excludes files', async () => {
const { fixture, aicommits } = await createFixture(files);
const git = await createGit(fixture.path);
Expand Down
40 changes: 40 additions & 0 deletions tests/specs/git-hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { testSuite, expect } from 'manten';
import { createFixture, createGit, files } from '../utils.js';

export default testSuite(({ describe }) => {
describe('Git hook', ({ test }) => {
test('errors when not in Git repo', async () => {
const { fixture, aicommits } = await createFixture(files);
const { exitCode, stderr } = await aicommits(['hook', 'install'], {
reject: false,
});

expect(exitCode).toBe(1);
expect(stderr).toMatch('The current directory must be a Git repository');

await fixture.rm();
});

test('Commits', async () => {
const { fixture, aicommits } = await createFixture(files);
const git = await createGit(fixture.path);

const { stdout } = await aicommits(['hook', 'install']);
expect(stdout).toMatch('Hook installed');

await git('add', ['data.json']);
await git('commit', ['--no-edit'], {
env: {
HOME: fixture.path,
USERPROFILE: fixture.path,
},
});

const { stdout: commitMessage } = await git('log', ['--pretty=%B']);
console.log('Committed with:', commitMessage);
expect(commitMessage.startsWith('# ')).not.toBe(true);

await fixture.rm();
});
});
});
5 changes: 5 additions & 0 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ export const createFixture = async (
aicommits,
};
};

export const files = Object.freeze({
'.aicommits': `OPENAI_KEY=${process.env.OPENAI_KEY}`,
'data.json': 'Lorem ipsum dolor sit amet '.repeat(10),
});

0 comments on commit 2255583

Please sign in to comment.