Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version Utils #1

Merged
merged 1 commit into from
Sep 30, 2024
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
14 changes: 14 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

RED='\033[1;31m'
GREEN='\033[1;32m'
NC='\033[0m'

PREFIX="${GREEN}[HUSKY]${NC} "

if ! OUTPUT=$(npx ts-node ../src/version.ts 2>&1); then
echo -e "${RED}${PREFIX}${OUTPUT}${NC}"
echo -e "${RED}${PREFIX}Aborting commit.${NC}"
echo -e "${RED}${PREFIX}Run commit with '-n' to skip pre-commit hooks.${NC}"
exit 1
fi
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Typescript Core
A TypeScript Repository that stands as a starting point for all other TypeScript repositories.
# Utils
A versatile TypeScript utility package packed with reusable, type-safe functions, scripts useful for all kinds of TypeScript projects, and precommit scripts to streamline your development workflow.

## Husky

Expand All @@ -11,6 +11,27 @@ Pre-commit hooks run scripts before a commit is finalized to catch issues or enf

This project uses a custom pre-commit hook to run `npm run bundle`. This ensures that our bundled assets are always up to date before any commit (which is especially important for TypeScript GitHub Actions). Husky automates this, so no commits will go through without a fresh bundle, keeping everything streamlined.

### Using Utils as Pre-Commit Hooks

```sh
./husky/pre-commit
#!/bin/sh

RED='\033[1;31m'
GREEN='\033[1;32m'
NC='\033[0m'

PREFIX="${GREEN}[HUSKY]${NC} "

if ! OUTPUT=$(npx ts-node ./node_modules/@krauters/utils/src/version.ts 2>&1); then
echo -e "${RED}${PREFIX}${OUTPUT}${NC}"
echo -e "${RED}${PREFIX}Aborting commit.${NC}"
echo -e "${RED}${PREFIX}Run commit with '-n' to skip pre-commit hooks.${NC}"
exit 1
fi

```

## Contributing

The goal of this project is to continually evolve and improve its core features, making it more efficient and easier to use. Development happens openly here on GitHub, and we’re thankful to the community for contributing bug fixes, enhancements, and fresh ideas. Whether you're fixing a small bug or suggesting a major improvement, your input is invaluable.
Expand All @@ -23,6 +44,6 @@ This project is licensed under the ISC License. Please see the [LICENSE](./LICEN

Thanks for spending time on this project.

<a href="https://github.com/krauters/typescript-core/graphs/contributors">
<img src="https://contrib.rocks/image?repo=krauters/typescript-core" />
<a href="https://github.com/krauters/utils/graphs/contributors">
<img src="https://contrib.rocks/image?repo=krauters/utils" />
</a>
8 changes: 4 additions & 4 deletions package-lock.json

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

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "@krauters/typescript-core",
"description": "A TypeScript Repository that stands as a starting point for all other TypeScript repositories.",
"version": "1.0.0",
"main": "app.ts",
"name": "@krauters/utils",
"description": "A versatile TypeScript utility package packed with reusable, type-safe functions, scripts useful for all kinds of TypeScript projects, and precommit scripts to streamline your development workflow.",
"version": "0.0.1",
"main": "index.ts",
"type": "commonjs",
"scripts": {
"build": "ts-node ./src/app.ts",
"build": "ts-node ./src/index.ts",
"example-1": "ts-node ./example/1.ts",
"fix": "npm run lint -- --fix",
"lint": "npx eslint src/**",
Expand Down
1 change: 0 additions & 1 deletion src/app.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './version'
23 changes: 23 additions & 0 deletions src/scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Scripts

This folder contains reusable TypeScript scripts intended for pre-commit checks and other automation tasks, designed to be used across multiple repositories.

## Purpose

- **Centralized Automation:** Provides a consistent way to enforce checks, such as version validation, before committing changes.
- **Ease of Integration:** Ensures all consuming repositories maintain uniform pre-commit behavior.

## Key Script

- **`pre-commit.ts`**: This script handles pre-commit checks (like version validation). It’s meant to be executed via a bash script.

## Usage

1. **Install Dependencies**:
```
npm install --save-dev @krauters/utils ts-node
```
2. **Set Up Your Pre-Commit Hook**:
- Use a bash script to call `pre-commit.ts` (or other specific scripst) in your repo.

For a detailed example, see [example.sh](./example.sh).
36 changes: 36 additions & 0 deletions src/scripts/example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh

# This script is an example of how a pre-commit hook might be set up
# to execute pre-commit scripts from @krauters/utils.
#
# Prerequisite: Make sure to install @krauters/utils and ts-node in your project
# npm install --save-dev @krauters/utils ts-node

RED='\033[1;31m'
GREEN='\033[1;32m'
NC='\033[0m'

PREFIX="[@krauters/utils]"

log() {
echo "${GREEN}${PREFIX}${NC} $1"
}

error() {
echo "${RED}${PREFIX}${NC} $1$"
}

log "Running pre-commit scripts..."

# Adjust the path based on whether it's run from node_modules or locally as shown
# if ! OUTPUT=$(npx ts-node ./node_modules/@krauters/utils/scripts/pre-commit.ts 2>&1); then
if ! OUTPUT=$(npx ts-node ./pre-commit.ts 2>&1); then
error "Pre-commit check failed:\n\n${OUTPUT}\n"
error "Aborting commit."
error "Run commit with '-n' to skip pre-commit hooks."
exit 1
fi

log "Script output...\n\n${OUTPUT}\n"
log "Pre-commit checks passed."
exit 0
1 change: 1 addition & 0 deletions src/scripts/pre-commit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './version'
3 changes: 3 additions & 0 deletions src/scripts/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { compareVersions } = require('../version')

compareVersions()
65 changes: 65 additions & 0 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'

interface PackageJson {
version: string
}

/**
* Retrieves the version from the previous commit's package.json.
*
* @returns {string} The previous version string.
* @throws {Error} If fetching fails.
*/
function getPreviousVersion(): string {
try {
const previousPackageJson: string = execSync('git show HEAD~1:package.json', { encoding: 'utf8' })
const data: PackageJson = JSON.parse(previousPackageJson)

return data.version
} catch (error: unknown) {
throw new Error(`Failed fetching previous package.json with error [${error}]`)
}
}

/**
* Retrieves the current version from package.json.
*
* @returns {string} The current version string.
* @throws {Error} If reading fails.
*/
function getCurrentVersion(): string {
try {
const packageJsonPath: string = path.resolve(process.cwd(), 'package.json')
const packageJson: string = fs.readFileSync(packageJsonPath, 'utf8')
const data: PackageJson = JSON.parse(packageJson)

return data.version
} catch (error: unknown) {
throw new Error(`Failed reading current package.json with error [${error}]`)
}
}

/**
* Compares the previous and current package.json versions.
*
* @returns {boolean} True if versions differ, false otherwise.
* @throws {Error} If fetching or reading package.json fails.
*/
function compareVersions(): boolean {
const previous: string = getPreviousVersion()
const current: string = getCurrentVersion()

if (previous !== current) {
console.log(`Version changed from [${previous}] to [${current}].`)

return true
}

console.error('Version has not been changed. Please update the version before committing.')

return false
}

export { compareVersions, getCurrentVersion, getPreviousVersion }
19 changes: 0 additions & 19 deletions test/basic.test.ts

This file was deleted.

74 changes: 74 additions & 0 deletions test/version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-empty-function */

import { beforeEach, describe, expect, it, jest } from '@jest/globals'
import { execSync } from 'child_process'
import fs from 'fs'

import { compareVersions } from '../src/version'

jest.mock('child_process')
jest.mock('fs')

const mockedExecSync = execSync as jest.Mock
const mockedFs = fs as jest.Mocked<typeof fs>

describe('compareVersions', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should return true when versions differ', () => {
mockedExecSync.mockReturnValue(JSON.stringify({ version: '1.0.0' }))
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.1' }))

const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})

const result = compareVersions()

expect(result).toBe(true)
expect(mockedExecSync).toHaveBeenCalledWith('git show HEAD~1:package.json', { encoding: 'utf8' })
expect(mockedFs.readFileSync).toHaveBeenCalledWith(expect.stringContaining('package.json'), 'utf8')
expect(consoleLogSpy).toHaveBeenCalledWith('Version changed from 1.0.0 to 1.0.1.')

consoleLogSpy.mockRestore()
})

it('should return false when versions are the same', () => {
mockedExecSync.mockReturnValue(JSON.stringify({ version: '1.0.0' }))
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: '1.0.0' }))

const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})

const result = compareVersions()

expect(result).toBe(false)
expect(mockedExecSync).toHaveBeenCalledWith('git show HEAD~1:package.json', { encoding: 'utf8' })
expect(mockedFs.readFileSync).toHaveBeenCalledWith(expect.stringContaining('package.json'), 'utf8')
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Version has not been changed. Please update the version before committing.',
)

consoleErrorSpy.mockRestore()
})

it('should throw an error when fetching previous package.json fails', () => {
mockedExecSync.mockImplementation(() => {
throw new Error('Git command failed')
})

expect(() => compareVersions()).toThrow(
'Failed fetching previous package.json with error [Error: Git command failed]',
)
})

it('should throw an error when reading current package.json fails', () => {
mockedExecSync.mockReturnValue(JSON.stringify({ version: '1.0.0' }))
mockedFs.readFileSync.mockImplementation(() => {
throw new Error('Failed to read package.json')
})

expect(() => compareVersions()).toThrow(
'Failed reading current package.json with error [Error: Failed to read package.json]',
)
})
})