generated from antongolub/blank-ts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3f9f0a8
commit de90d64
Showing
7 changed files
with
199 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": { | ||
|
@@ -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", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) |