mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-13 19:20:06 +00:00
ci: add lint job, npm publish pipeline, and update goreleaser config
- CI: add golangci-lint job, target master branch, use Go 1.26 - Release: add workflow_dispatch for npm-only releases, Discord notifications, and npm-publish job for @mark3labs/kit - Goreleaser: upgrade to v2 schema, lowercase archive names, structured changelog groups, release header with install instructions - npm: add package scaffolding with postinstall binary downloader
This commit is contained in:
@@ -1,17 +1,32 @@
|
||||
name: go
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.26"
|
||||
|
||||
- uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.8.0
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- run: go test ./... -race
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.26"
|
||||
|
||||
- run: go test -race ./...
|
||||
|
||||
@@ -4,30 +4,98 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to use for npm publish'
|
||||
required: true
|
||||
skip_goreleaser:
|
||||
description: 'Skip goreleaser job (npm-only release)'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write # Required for npm trusted publishing (OIDC)
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'push' || !inputs.skip_goreleaser }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.21.0"
|
||||
cache: true
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
go-version: "1.26"
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Send Discord Notification
|
||||
if: success()
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.RELEASES_WEBHOOK }}
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
RELEASE_URL: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}
|
||||
run: |
|
||||
curl -H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{
|
||||
\"embeds\": [{
|
||||
\"title\": \"New Release: $TAG_NAME\",
|
||||
\"description\": \"A new version of kit has been released!\",
|
||||
\"color\": 5814783,
|
||||
\"fields\": [
|
||||
{
|
||||
\"name\": \"Version\",
|
||||
\"value\": \"$TAG_NAME\",
|
||||
\"inline\": true
|
||||
},
|
||||
{
|
||||
\"name\": \"Repository\",
|
||||
\"value\": \"[kit](https://github.com/${{ github.repository }})\",
|
||||
\"inline\": true
|
||||
}
|
||||
],
|
||||
\"footer\": {
|
||||
\"text\": \"Released via GitHub Actions\"
|
||||
},
|
||||
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",
|
||||
\"url\": \"$RELEASE_URL\"
|
||||
}]
|
||||
}" \
|
||||
$DISCORD_WEBHOOK
|
||||
|
||||
npm-publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: goreleaser
|
||||
if: ${{ always() && (needs.goreleaser.result == 'success' || needs.goreleaser.result == 'skipped') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Set version from tag
|
||||
working-directory: npm
|
||||
run: |
|
||||
TAG=${{ inputs.tag || github.ref_name }}
|
||||
VERSION=${TAG#v}
|
||||
echo "Setting npm version to $VERSION"
|
||||
npm version $VERSION --no-git-tag-version
|
||||
|
||||
- name: Publish to npm
|
||||
working-directory: npm
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
+48
-25
@@ -1,47 +1,70 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- id: kit
|
||||
main: ./cmd/kit
|
||||
- main: ./cmd/kit
|
||||
binary: kit
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
binary: kit
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}}
|
||||
- -s -w
|
||||
- -X main.version={{.Version}}
|
||||
- -X main.commit={{.Commit}}
|
||||
- -X main.date={{.Date}}
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
- formats: [tar.gz]
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: [zip]
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
name_template: checksums.txt
|
||||
|
||||
release:
|
||||
header: |
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Bun / npm
|
||||
bun add -g @mark3labs/kit
|
||||
# or: npm install -g @mark3labs/kit
|
||||
|
||||
# Go install (requires Go 1.26+)
|
||||
go install github.com/mark3labs/kit/cmd/kit@latest
|
||||
|
||||
# Or download from assets below
|
||||
```
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^ci:"
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
- "^Merge"
|
||||
- "^merge"
|
||||
- "^wip"
|
||||
- "^WIP"
|
||||
groups:
|
||||
- title: "Features"
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: "Bug Fixes"
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: "Documentation"
|
||||
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
|
||||
order: 2
|
||||
- title: "Maintenance"
|
||||
regexp: '^.*?(chore|refactor|style|ci|build)(\([[:word:]]+\))??!?:.+$'
|
||||
order: 3
|
||||
- title: "Other Changes"
|
||||
order: 999
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Downloaded binaries (only wrapper script should be committed)
|
||||
bin/kit-bin
|
||||
bin/kit.exe
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawnSync } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const https = require("https");
|
||||
|
||||
const REPO = "mark3labs/kit";
|
||||
const BINARY = "kit";
|
||||
|
||||
const binDir = __dirname;
|
||||
const isWindows = process.platform === "win32";
|
||||
const binaryName = isWindows ? "kit.exe" : "kit-bin";
|
||||
const binaryPath = path.join(binDir, binaryName);
|
||||
|
||||
// Platform/arch mapping
|
||||
const PLATFORM_MAP = { darwin: "darwin", linux: "linux", win32: "windows" };
|
||||
const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
|
||||
|
||||
function download(url, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const follow = (url, redirects = 0) => {
|
||||
if (redirects > 10) return reject(new Error("Too many redirects"));
|
||||
https.get(url, (res) => {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
return follow(res.headers.location, redirects + 1);
|
||||
}
|
||||
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
||||
const file = fs.createWriteStream(dest);
|
||||
res.pipe(file);
|
||||
file.on("finish", () => { file.close(); resolve(); });
|
||||
file.on("error", (err) => { fs.unlinkSync(dest); reject(err); });
|
||||
}).on("error", reject);
|
||||
};
|
||||
follow(url);
|
||||
});
|
||||
}
|
||||
|
||||
async function install() {
|
||||
const platform = PLATFORM_MAP[process.platform];
|
||||
const arch = ARCH_MAP[process.arch];
|
||||
if (!platform || !arch) {
|
||||
console.error(`Unsupported platform: ${process.platform}/${process.arch}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const packageJson = require("../package.json");
|
||||
const version = packageJson.version;
|
||||
const ext = platform === "windows" ? "zip" : "tar.gz";
|
||||
const filename = `${BINARY}_${version}_${platform}_${arch}.${ext}`;
|
||||
const url = `https://github.com/${REPO}/releases/download/v${version}/${filename}`;
|
||||
const archivePath = path.join(binDir, filename);
|
||||
// Extract to temp dir to avoid overwriting this JS wrapper script
|
||||
const tempDir = path.join(binDir, ".tmp-extract");
|
||||
|
||||
console.log(`Installing ${BINARY} v${version} (${platform}/${arch})...`);
|
||||
|
||||
await download(url, archivePath);
|
||||
|
||||
// Create temp extraction directory
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
if (platform === "windows") {
|
||||
spawnSync("powershell", ["-Command", `Expand-Archive -Path "${archivePath}" -DestinationPath "${tempDir}" -Force`]);
|
||||
} else {
|
||||
spawnSync("tar", ["-xzf", archivePath, "-C", tempDir]);
|
||||
}
|
||||
|
||||
// Move extracted binary to final location (from temp dir to avoid overwriting wrapper)
|
||||
const extractedPath = path.join(tempDir, platform === "windows" ? `${BINARY}.exe` : BINARY);
|
||||
if (fs.existsSync(extractedPath)) {
|
||||
fs.renameSync(extractedPath, binaryPath);
|
||||
}
|
||||
|
||||
if (platform !== "windows") fs.chmodSync(binaryPath, 0o755);
|
||||
fs.unlinkSync(archivePath);
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
console.log(`Installed ${BINARY} successfully`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Install binary if missing
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
await install();
|
||||
}
|
||||
|
||||
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
shell: false,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Failed to run kit: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
main();
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync, spawnSync } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const https = require("https");
|
||||
const { createWriteStream, mkdirSync, chmodSync, unlinkSync } = fs;
|
||||
|
||||
const REPO = "mark3labs/kit";
|
||||
const BINARY = "kit";
|
||||
|
||||
// Get version from package.json
|
||||
const packageJson = require("./package.json");
|
||||
const VERSION = packageJson.version;
|
||||
|
||||
// Platform mapping
|
||||
const PLATFORM_MAP = {
|
||||
darwin: "darwin",
|
||||
linux: "linux",
|
||||
win32: "windows",
|
||||
};
|
||||
|
||||
// Arch mapping
|
||||
const ARCH_MAP = {
|
||||
x64: "amd64",
|
||||
arm64: "arm64",
|
||||
};
|
||||
|
||||
function getPlatform() {
|
||||
const platform = PLATFORM_MAP[process.platform];
|
||||
if (!platform) {
|
||||
throw new Error(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
|
||||
function getArch() {
|
||||
const arch = ARCH_MAP[process.arch];
|
||||
if (!arch) {
|
||||
throw new Error(`Unsupported architecture: ${process.arch}`);
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
|
||||
function getExtension(platform) {
|
||||
return platform === "windows" ? "zip" : "tar.gz";
|
||||
}
|
||||
|
||||
function getBinaryName(platform) {
|
||||
// Use different name to avoid conflict with JS wrapper on Unix
|
||||
return platform === "windows" ? `${BINARY}.exe` : `${BINARY}-bin`;
|
||||
}
|
||||
|
||||
function download(url, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const follow = (url, redirects = 0) => {
|
||||
if (redirects > 10) {
|
||||
reject(new Error("Too many redirects"));
|
||||
return;
|
||||
}
|
||||
|
||||
https
|
||||
.get(url, (response) => {
|
||||
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
||||
follow(response.headers.location, redirects + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Failed to download: ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const file = createWriteStream(dest);
|
||||
response.pipe(file);
|
||||
file.on("finish", () => {
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
file.on("error", (err) => {
|
||||
unlinkSync(dest);
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.on("error", reject);
|
||||
};
|
||||
|
||||
follow(url);
|
||||
});
|
||||
}
|
||||
|
||||
function extract(archivePath, destDir, platform) {
|
||||
if (platform === "windows") {
|
||||
// Use PowerShell to extract zip on Windows
|
||||
spawnSync("powershell", [
|
||||
"-Command",
|
||||
`Expand-Archive -Path "${archivePath}" -DestinationPath "${destDir}" -Force`,
|
||||
]);
|
||||
} else {
|
||||
// Use tar for Unix systems
|
||||
spawnSync("tar", ["-xzf", archivePath, "-C", destDir]);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const platform = getPlatform();
|
||||
const arch = getArch();
|
||||
const ext = getExtension(platform);
|
||||
const binaryName = getBinaryName(platform);
|
||||
|
||||
const filename = `${BINARY}_${VERSION}_${platform}_${arch}.${ext}`;
|
||||
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${filename}`;
|
||||
|
||||
const binDir = path.join(__dirname, "bin");
|
||||
const archivePath = path.join(__dirname, filename);
|
||||
const binaryPath = path.join(binDir, binaryName);
|
||||
|
||||
console.log(`Installing ${BINARY} v${VERSION} (${platform}/${arch})...`);
|
||||
console.log(`Downloading from ${url}...`);
|
||||
|
||||
try {
|
||||
// Ensure bin directory exists
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
|
||||
// Download archive
|
||||
await download(url, archivePath);
|
||||
|
||||
// Extract
|
||||
extract(archivePath, binDir, platform);
|
||||
|
||||
// Rename binary (archive contains "kit", we want "kit-bin" on Unix)
|
||||
const extractedName = platform === "windows" ? `${BINARY}.exe` : BINARY;
|
||||
const extractedPath = path.join(binDir, extractedName);
|
||||
if (extractedPath !== binaryPath && fs.existsSync(extractedPath)) {
|
||||
fs.renameSync(extractedPath, binaryPath);
|
||||
}
|
||||
|
||||
// Make executable on Unix
|
||||
if (platform !== "windows") {
|
||||
chmodSync(binaryPath, 0o755);
|
||||
}
|
||||
|
||||
// Clean up archive
|
||||
unlinkSync(archivePath);
|
||||
|
||||
console.log(`Successfully installed ${BINARY} to ${binaryPath}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to install ${BINARY}: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@mark3labs/kit",
|
||||
"version": "0.0.0",
|
||||
"description": "AI-powered CLI tool",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/mark3labs/kit.git"
|
||||
},
|
||||
"homepage": "https://github.com/mark3labs/kit",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mark3labs/kit/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"kit": "bin/kit"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node install.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"install.js"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32"
|
||||
],
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
],
|
||||
"keywords": [
|
||||
"cli",
|
||||
"kit",
|
||||
"ai"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user