5 Commits
Author SHA1 Message Date
semantic-release-bot 8043247057 chore(release): 1.2.0
# [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))
2026-09-05 17:43:08 +00:00
asepharyana 0782d0f993 feat(health): expose app version in /health response
Reads application version from package.json via env.ts readPackageVersion()
and returns it alongside status so deploy verification is instant:
curl https://upload.asepharyana.my.id/health -> {status: ok, version: X.Y.Z}
2026-09-06 00:42:39 +07:00
asepharyana bdda2d9884 docs(readme): document auto semantic versioning 2026-09-06 00:40:11 +07:00
asepharyana 357a1ae41b ci(release): add workflow_dispatch for manual releases 2026-09-06 00:39:09 +07:00
asepharyana f16161d38a ci(semantic-release): auto versioning via semantic-release
- .releaserc.json: commit-analyzer + release-notes-generator + changelog
  + exec (prepare) + git + github plugins.
- .semrel/prepare.mjs: syncs next release version into package.json AND
  flake.nix before the release commit — guarantees a fresh Nix store path
  on every deploy (fixes the trap where a source change without a version
  bump reused the same store path and CI silently deployed stale code).
- release.yml: runs semantic-release on main push (GITHUB_TOKEN,
  isolated /tmp/sr-tools install so package.json/bun.lock stay clean).
- Release commit (no [skip ci]) triggers deploy.yml → auto build+deploy.
- Baseline tag v1.1.1 backfilled at current HEAD.
2026-09-06 00:36:01 +07:00
9 changed files with 166 additions and 3 deletions
+44
View File
@@ -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
+27
View File
@@ -0,0 +1,27 @@
{
"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"
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "flake.nix", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}
+36
View File
@@ -0,0 +1,36 @@
#!/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.
*/
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 replaced = flake.replace(/version = "\d+\.\d+\.\d+";/, `version = "${target}";`);
if (!replaced.includes(`version = "${target}";`)) {
throw new Error(`prepare.mjs: could not update version in ${flakePath} (pattern not found)`);
}
writeFileSync(flakePath, replaced);
console.log(`prepare.mjs: versions synced to ${target}`);
+6
View File
@@ -0,0 +1,6 @@
# [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))
+13
View File
@@ -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.
+1 -1
View File
@@ -14,7 +14,7 @@
overlays = [
(self: super: {
bun = super.bun.overrideAttrs (old: rec {
version = "1.3.14";
version = "1.2.0";
src = super.fetchzip {
url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-linux-x64.zip";
sha256 = "sha256:189kasncnfpq6q37q3w80dv2a92w79w20x2x6hf1fbkir5gyvzlb";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filedrop",
"version": "1.1.0",
"version": "1.2.0",
"description": "Telegram file uploader backend",
"type": "module",
"scripts": {
+36
View File
@@ -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 });