Skip to content

Commit

Permalink
chore: initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Sep 21, 2023
1 parent 3f9f0a8 commit de90d64
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 9 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# blank-ts
Template repository for TS projects
# tagtower
> Tag driven git meta
## Motivation
It does not seem possible [to get commits info w/o repo cloning](https://stackoverflow.com/questions/20055398/is-it-possible-to-get-commit-logs-messages-of-a-remote-git-repo-without-git-clon). This limitation brings a significant performance impact on conventional-commits driven release flows (especially if [git notes](https://git-scm.com/docs/git-notes) API is not supported by VSC). But what if we'd have a side index with web-hooks triggers instead. Let's find out.

## Usage
```ts
import {create, read, readAll, update, del} from 'tagtower'

const tag = {
hash: '3f9f0a88b411a8932bce289a3dd498d70a4dc96c',
author: 'Anton Golub <[email protected]>',
message: `feat: initial feat`
}

await create('some-tag', tag) // void
await read('some-tag') // tag | null
await readAll() // [tag]
await del('some-tag') // void
```

## License
[MIT](./LICENSE)
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@antongolub/blank-ts",
"name": "tagtower",
"version": "0.0.0",
"description": "Blank TS package",
"description": "Tag driven git meta",
"type": "module",
"main": "target/cjs/index.cjs",
"exports": {
Expand Down Expand Up @@ -32,14 +32,14 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/antongolub/blank-ts.git"
"url": "git+https://github.com/semrel-extra/tagtower.git"
},
"author": "Anton Golub <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/antongolub/blank-ts/issues"
"url": "https://github.com/semrel-extra/tagtower/issues"
},
"homepage": "https://github.com/antongolub/blank-ts#readme",
"homepage": "https://github.com/semrel-extra/tagtower#readme",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.6.3",
Expand Down
100 changes: 100 additions & 0 deletions src/main/ts/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import child_process from 'node:child_process'
import path from 'node:path'
import os from 'node:os'
import fs from 'node:fs/promises'

import {TAnnotatedTag, TTowerOpts} from './interface'

export const tempy = async () =>
fs.mkdtemp(path.join(os.tmpdir(), 'tempy-tagtower-'))

const _temp = await tempy()

export const exec = (cmd: string, args?: string[], opts?: any): Promise<{stdout: string, stderr: string, code?: number | null, duration: number}> => new Promise((resolve, reject) => {
const now = Date.now()
const p = child_process.spawn(cmd, args, opts)
let stdout = ''
let stderr = ''

p.stdout.on('data', (data) => {
stdout += data.toString()
})

p.stderr.on('data', (data) => {
stderr += data.toString()
})

p.on('close', (code) => {
resolve({
code,
stdout,
stderr,
duration: Date.now() - now
})
})
})

const getCwd = async ({url, branch = 'tagtower', temp}: TTowerOpts) => {
const base = temp || await tempy()
const id = `${url.replaceAll(/[@\/.:]/g, '-')}-${branch}`
const cwd = path.resolve(base, id)

try {
await fs.access(cwd)
} catch {
await fs.mkdir(cwd, { recursive: true })
await clone(url, branch, cwd)
}

return cwd
}

export const clone = async (url: string, branch = 'tagtower', _cwd?: string) => {
const cwd = _cwd || _temp
const opts = {cwd}
const remote = await exec('git', ['clone', '-b', branch, '--depth', '1', url, '.'], opts)

if (remote.code === 128) {
await exec('git', ['init'], opts)
await exec('git', ['remote', 'add', 'origin', url], opts)
await exec('git', ['commit', '--allow-empty', '-m', 'init tagtower'], opts)
await exec('git', ['push', 'origin', `HEAD:${branch}`], opts)
}
}

export const readTags = async (opts: TTowerOpts) => {
const cwd = await getCwd(opts)
await exec('git', ['fetch', 'origin', 'refs/tags/*:refs/tags/*'], {cwd})

const raw = await exec('git', ['log', '--decorate-refs=refs/tags/*','--no-walk', '--tags', '--pretty=%D+++%B---', '--decorate=full'], {cwd})

return raw.stdout.trim().split('---').slice(0, -1).map(e => {
const [ref, body] = e.split('+++')

return {
tag: ref.slice(ref.lastIndexOf('/') + 1),
body
}
})
}

export const pushTags = async (opts: TTowerOpts & {tags: TAnnotatedTag[]}) => {
const cwd = await getCwd(opts)

// const {stdout} = await exec('git', ['rev-parse', 'HEAD'], opts)
// return stdout.trim()
const errors: string[] = []
await Promise.all(opts.tags.map(async ({tag, body}) => {
const result = await exec('git', ['tag', '-a', tag, '-m', body], {cwd})

if (result.code !== 0) {
errors.push(result.stderr.trim())
}
}))

if (errors.length) {
console.warn(errors.join('\n'))
}

await exec('git', ['push', '--tags'], {cwd})
}
6 changes: 6 additions & 0 deletions src/main/ts/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type TTowerOpts = {url: string, branch?: string, temp?: string}

export type TAnnotatedTag = {
tag: string
body: string
}
2 changes: 1 addition & 1 deletion src/test/js/index.test.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const assert = require('node:assert')
const { describe, it } = require('node:test')
const { foo } = require('@antongolub/blank-ts')
const { foo } = require('tagtower')

describe('cjs foo()', () => {
it('is callable', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/test/js/index.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert'
import { describe, it } from 'node:test'
import { foo } from '@antongolub/blank-ts'
import { foo } from 'tagtower'

describe('mjs foo()', () => {
it('is callable', () => {
Expand Down
65 changes: 65 additions & 0 deletions src/test/ts/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as assert from 'node:assert'
import { describe, it } from 'node:test'
import path from 'node:path'
import process from 'node:process'
import {exec, readTags, pushTags} from '../../main/ts/git'
import crypto from 'node:crypto'

const temp = path.resolve(process.cwd(), 'temp')

describe('git', () => {
it('exec', async () => {
const {stdout, code, stderr} = await exec('echo', ['foo'])

assert.equal(stdout, 'foo\n')
assert.equal(stderr, '')
assert.equal(code, 0)
})

it.skip('readTags', async () => {
const tags = await readTags({
url: '[email protected]:antongolub/tsc-esm-fix.git',
branch: 'master',
temp
})

const first = tags[tags.length - 1]
assert.equal(first.tag, 'v1.0.0')
assert.ok(first.body.includes('chore(release): 1.0.0 [skip ci]\n'))
})

it.skip('pushTags', async () => {
await pushTags({
url: '[email protected]:semrel-extra/tagtower.git',
branch: 'testtower',
temp,
tags: [
{tag: 'test@1', body: 'test 1'},
{tag: 'test@2', body: 'test 2'}
]
})
})

it('pushTags (avalanche)', async () => {
const l = 2
const tags = [...Array(l)].map(() => ({
tag: `test@${Math.random().toString(36).slice(2)}`,
body: crypto.randomBytes(300).toString('hex')
}))

await pushTags({
url: '[email protected]:semrel-extra/tagtower.git',
branch: 'testtower',
temp,
tags
})

const _tags = await readTags({
url: '[email protected]:semrel-extra/tagtower.git',
branch: 'testtower',
temp,
})

console.log(_tags)
})
})

0 comments on commit de90d64

Please sign in to comment.