Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab955afad4 | ||
|
|
f442797a86 | ||
|
|
6ca92fcc21 | ||
|
|
8043247057 | ||
|
|
0782d0f993 | ||
|
|
bdda2d9884 | ||
|
|
357a1ae41b | ||
|
|
f16161d38a |
@@ -0,0 +1,44 @@
|
||||
name: Semantic Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# semantic-release needs the full history + tags to analyse commits
|
||||
persist-credentials: true
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.14"
|
||||
|
||||
- name: Install semantic-release (isolated, no package.json pollution)
|
||||
run: |
|
||||
npm install --prefix /tmp/sr-tools \
|
||||
semantic-release@^24 \
|
||||
@semantic-release/changelog@^6 \
|
||||
@semantic-release/exec@^6 \
|
||||
@semantic-release/git@^10
|
||||
echo "/tmp/sr-tools/node_modules/.bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Run semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: semantic-release
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"branches": ["main"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
"changelogFile": "CHANGELOG.md"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "node ./.semrel/prepare.mjs '${nextRelease.version}' && bunx biome check package.json flake.nix CHANGELOG.md 2>/dev/null || true",
|
||||
"successCmd": "node ./.semrel/dispatch.mjs '${nextRelease.version}'"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": ["package.json", "flake.nix", "CHANGELOG.md"],
|
||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* successCmd hook for semantic-release: dispatch the deploy workflow so the
|
||||
* freshly released version is built and deployed to the VPS.
|
||||
*
|
||||
* GitHub disables workflow re-triggering for pushes made by GITHUB_TOKEN
|
||||
* (which is what @semantic-release/git uses to commit the release), so the
|
||||
* plain `push: branches: [main]` trigger in deploy.yml never fires for the
|
||||
* release commit. We dispatch deploy.yml explicitly instead.
|
||||
*/
|
||||
|
||||
const version = (process.argv[2] || '').trim();
|
||||
|
||||
async function main() {
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
console.error('dispatch.mjs: GITHUB_TOKEN not set, skipping deploy dispatch');
|
||||
process.exit(0);
|
||||
}
|
||||
if (!version) {
|
||||
console.error('dispatch.mjs: no version argument, skipping');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const repo = process.env.GITHUB_REPOSITORY || 'asepharyana/TeleUploader';
|
||||
const url = `https://api.github.com/repos/${repo}/actions/workflows/deploy.yml/dispatches`;
|
||||
|
||||
// dispatch with branch ref main; deploy builds #teleuploader from the
|
||||
// release commit whose package.json/flake.nix already carry `version`
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'semantic-release-dispatch',
|
||||
},
|
||||
body: JSON.stringify({ ref: 'main' }),
|
||||
});
|
||||
|
||||
if (res.status === 204) {
|
||||
console.log(`dispatch.mjs: dispatched deploy for v${version} to ${repo} main`);
|
||||
} else {
|
||||
const body = await res.text().catch(() => '');
|
||||
console.error(`dispatch.mjs: deploy dispatch failed (HTTP ${res.status}): ${body}`);
|
||||
// Non-fatal: a user can still run the deploy workflow manually.
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('dispatch.mjs: unexpected error:', err);
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Prepare script for semantic-release: synchronises the package version
|
||||
* across package.json and flake.nix before the release commit.
|
||||
*
|
||||
* getVersion() keeps package.json as the source of truth when semver has
|
||||
* not run yet (e.g. first bootstrap) — after that, nextRelease.version
|
||||
* is passed as argv[2] and everything is aligned to it.
|
||||
*
|
||||
* Fixes the TeleUploader Nix deploy trap: a source change without a
|
||||
* version bump made Nix reuse the same store path, so CI reported success
|
||||
* while the VPS profile stayed on the old build. By bumping flake.nix's
|
||||
* `version` on every release, the Nix derivation always changes and the
|
||||
* store path is always fresh.
|
||||
*
|
||||
* IMPORTANT: only bump the TeleUploader package version (the block whose
|
||||
* `pname = "teleuploader"` precedes it), never the Bun overlay version that
|
||||
* appears earlier in the file. Store path is derived from the
|
||||
* teleuploader package's `version`.
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const nextVersion = process.argv[2];
|
||||
|
||||
// --- package.json ---
|
||||
const pkgPath = 'package.json';
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||
if (nextVersion) pkg.version = nextVersion;
|
||||
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
|
||||
// --- flake.nix ---
|
||||
const flakePath = 'flake.nix';
|
||||
let flake = readFileSync(flakePath, 'utf8');
|
||||
const target = nextVersion ?? pkg.version;
|
||||
|
||||
const anchor = 'pname = "teleuploader";';
|
||||
const anchorIdx = flake.indexOf(anchor);
|
||||
if (anchorIdx === -1) {
|
||||
throw new Error(`prepare.mjs: TeleUploader pname anchor not found in ${flakePath}`);
|
||||
}
|
||||
const rest = flake.slice(anchorIdx);
|
||||
const verMatch = rest.match(/(\s*)version = "\d+\.\d+\.\d+";/);
|
||||
if (!verMatch) {
|
||||
throw new Error(`prepare.mjs: could not locate TeleUploader version block in ${flakePath}`);
|
||||
}
|
||||
const replaced =
|
||||
flake.slice(0, anchorIdx) +
|
||||
rest.replace(/(\s*)version = "\d+\.\d+\.\d+";/, `$1version = "${target}";`);
|
||||
if (!replaced.includes(`version = "${target}";`)) {
|
||||
throw new Error(`prepare.mjs: replacement did not land in ${flakePath}`);
|
||||
}
|
||||
writeFileSync(flakePath, replaced);
|
||||
|
||||
console.log(`prepare.mjs: versions synced to ${target}`);
|
||||
@@ -0,0 +1,13 @@
|
||||
## [1.2.1](https://github.com/asepharyana/TeleUploader/compare/v1.2.0...v1.2.1) (2026-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** bump only TeleUploader flake version, not Bun overlay ([f442797](https://github.com/asepharyana/TeleUploader/commit/f442797a86e845853c28dc22432a530560434488))
|
||||
|
||||
# [1.2.0](https://github.com/asepharyana/TeleUploader/compare/v1.1.1...v1.2.0) (2026-09-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **health:** expose app version in /health response ([0782d0f](https://github.com/asepharyana/TeleUploader/commit/0782d0f993a033bb9d84fa1f876e29486751889d))
|
||||
@@ -53,3 +53,16 @@ Ini berarti URL service Anda fix, bukan jaminan file Telegram abadi.
|
||||
## Testing
|
||||
|
||||
Gunakan bot Telegram untuk upload, atau upload API langsung via HTTP.
|
||||
|
||||
## Versioning
|
||||
|
||||
This repository uses auto semantic versioning via [semantic-release](https://github.com/semantic-release/semantic-release):
|
||||
|
||||
- `fix(...)` commits → patch bump (v1.1.1 → v1.1.2)
|
||||
- `feat(...)` commits → minor bump (v1.2.0)
|
||||
- breaking changes → major bump (v2.0.0)
|
||||
- `chore/ci/docs/refactor/test` commits → no release
|
||||
|
||||
On every release, `prepare.mjs` syncs the version across `package.json` and
|
||||
`flake.nix`, guaranteeing a fresh Nix store path and true auto-deploy via
|
||||
the `Build & Deploy (Nix)` workflow.
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
# TeleUploader package
|
||||
teleuploader = pkgs.stdenvNoCC.mkDerivation rec {
|
||||
pname = "teleuploader";
|
||||
version = "1.1.1";
|
||||
version = "1.2.1";
|
||||
|
||||
src = ./.;
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filedrop",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.1",
|
||||
"description": "Telegram file uploader backend",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+36
@@ -2,6 +2,8 @@ import logger from './shared/logger/index';
|
||||
import { TELEGRAM_CHUNK_SIZE_MAX_BYTES } from './shared/utils/validation';
|
||||
|
||||
interface AppConfig {
|
||||
/** Application version (read from package.json, kept in sync by semantic-release prepare.mjs) */
|
||||
appVersion: string;
|
||||
/** All bot tokens merged from BOT_TOKENS (or BOT_TOKEN + ADDITIONAL_BOT_TOKENS fallback) */
|
||||
botTokens: string[];
|
||||
/** Per-bot concurrency for Telegram API calls (default 1). */
|
||||
@@ -131,7 +133,41 @@ if (telegramChunkSizeBytes > TELEGRAM_CHUNK_SIZE_MAX_BYTES) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the application version from package.json.
|
||||
*
|
||||
* Prefers the resolved node_modules/teleuploader package version (set by
|
||||
* semantic-release when the repo is installed as a published artifact) and
|
||||
* falls back to reading the repo's own package.json when running from a
|
||||
* checkout. Both files are updated by prepare.mjs on every release, so the
|
||||
* value always matches the deployed build.
|
||||
*/
|
||||
const readPackageVersion = (): string => {
|
||||
try {
|
||||
const candidates = [
|
||||
// node_modules resolution is only available when installed as a dep
|
||||
import.meta.dir ? `${import.meta.dir}/../package.json` : undefined,
|
||||
`${process.cwd()}/package.json`,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate) continue;
|
||||
try {
|
||||
const pkg = JSON.parse(import.meta.require(candidate).default ?? '{}') as {
|
||||
version?: string;
|
||||
};
|
||||
if (pkg.version) return pkg.version;
|
||||
} catch {
|
||||
// try next candidate
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
return '0.0.0';
|
||||
};
|
||||
|
||||
export const config: AppConfig = {
|
||||
appVersion: readPackageVersion(),
|
||||
botTokens: parseTokens(botTokensRaw),
|
||||
telegramBotConcurrency: parseNumber(process.env.TELEGRAM_BOT_CONCURRENCY, 1),
|
||||
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID!, 10),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { config } from '../../../env';
|
||||
import { db } from '../../../infrastructure/persistence/drizzle/index';
|
||||
import logger from '../../../shared/logger/index';
|
||||
import { getErrorMessage } from '../../../shared/utils/file';
|
||||
@@ -16,7 +17,7 @@ import { getErrorMessage } from '../../../shared/utils/file';
|
||||
export const handleHealth = async (_req: Request): Promise<Response> => {
|
||||
try {
|
||||
await db.execute(sql`SELECT 1`);
|
||||
return Response.json({ status: 'ok' }, { status: 200 });
|
||||
return Response.json({ status: 'ok', version: config.appVersion }, { status: 200 });
|
||||
} catch (error: unknown) {
|
||||
const message = getErrorMessage(error);
|
||||
logger.error('Health check failed', { error: message });
|
||||
|
||||
Reference in New Issue
Block a user