diff --git a/source-archive.ts b/source-archive.ts
deleted file mode 100644
index 0ef6d4b..0000000
--- a/source-archive.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2026 Coffey Labs
- *
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-/*
- * The AGPL's offer, for this build: the exact source it was built from,
- * written next to the app as `source.tar.gz`, and an identity for it that the
- * interface shows with the download link.
- *
- * "Exact" includes uncommitted work, new files too: every file git doesn't
- * ignore is written into a throwaway index, never the real one, and the tree
- * that makes is what gets archived. A clean tree is HEAD's tree. Outside a git
- * checkout (a release tarball, say), the project files are packed as they are.
- */
-import { execFileSync } from 'node:child_process';
-import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import path from 'node:path';
-import type { Plugin } from 'vite';
-
-const EXCLUDE = ['node_modules', 'dist', '.git', '.ignore', 'coverage'];
-
-function git(args: string[], cwd: string, env?: NodeJS.ProcessEnv): string {
- return execFileSync('git', args, { cwd, env: env ?? process.env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
-}
-
-/** The tree this build is made from: its git tree id, and a name that says whether it has uncommitted work. */
-export function sourceIdentity(root: string): { ref: string | null; id: string } {
- try {
- git(['rev-parse', '--is-inside-work-tree'], root);
- } catch {
- return { ref: null, id: 'unversioned' };
- }
- const dir = mkdtempSync(path.join(tmpdir(), 'inbuxa-source-'));
- try {
- const env = { ...process.env, GIT_INDEX_FILE: path.join(dir, 'index') };
- git(['read-tree', 'HEAD'], root, env);
- git(['add', '--all', '.'], root, env);
- const tree = git(['write-tree'], root, env);
- const headTree = git(['rev-parse', 'HEAD^{tree}'], root);
- const head = git(['rev-parse', '--short=12', 'HEAD'], root);
- return tree === headTree ? { ref: tree, id: head } : { ref: tree, id: `${head}+local-${tree.slice(0, 12)}` };
- } finally {
- rmSync(dir, { recursive: true, force: true });
- }
-}
-
-export function sourceArchive(root: string, name: string, identity: { ref: string | null; id: string }): Plugin {
- return {
- name: 'inbuxa-source-archive',
- apply: 'build',
- closeBundle() {
- const outDir = path.join(root, 'dist');
- if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
- const out = path.join(outDir, 'source.tar.gz');
- const prefix = `${name}-${identity.id}/`;
- if (identity.ref) {
- execFileSync('git', ['archive', '--format=tar.gz', `--prefix=${prefix}`, '-o', out, identity.ref], { cwd: root });
- } else {
- execFileSync(
- 'tar',
- [...EXCLUDE.map((e) => `--exclude=./${e}`), `--transform=s,^\\.,${prefix.slice(0, -1)},`, '-czf', out, '.'],
- { cwd: root },
- );
- }
- },
- };
-}
diff --git a/src/components/common/SourceLink.tsx b/src/components/common/SourceLink.tsx
index cdb79e4..5344a10 100644
--- a/src/components/common/SourceLink.tsx
+++ b/src/components/common/SourceLink.tsx
@@ -5,17 +5,17 @@
*/
import { useTranslation } from 'react-i18next';
-import { sourceDownloadUrl } from '@/lib/sourceDownload';
+import { SOURCE_URL } from '@/lib/sourceDownload';
/**
- * The AGPL's offer to everyone using this interface over the network: the
- * exact source of the version running, with that version named.
+ * The AGPL's offer to everyone using this interface over the network: where
+ * the source is, with the running version named beside it.
*/
export function SourceLink({ className }: { className?: string }) {
const { t } = useTranslation();
return (
-
- {t('source.download', 'Source code of this version ({{id}}), AGPL-3.0', { id: __SOURCE_ID__ })}
+
+ {t('source.download', 'Source code ({{version}}), AGPL-3.0', { version: __APP_VERSION__ })}
);
}
diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx
index 25ecf9f..fc091c3 100644
--- a/src/components/layout/TopBar.tsx
+++ b/src/components/layout/TopBar.tsx
@@ -31,7 +31,7 @@ import { isPaletteId, PALETTES } from '@/lib/palettes';
import Logo from '@/components/common/Logo';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell';
-import { sourceDownloadUrl } from '@/lib/sourceDownload';
+import { SOURCE_URL } from '@/lib/sourceDownload';
import { visibleLayouts } from '@/lib/layout';
import { sectionLandingLink } from '@/lib/lastVisited';
import { cn } from '@/lib/utils';
@@ -104,7 +104,7 @@ export function TopBar() {
- {t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} ยท {__SOURCE_ID__}
+ {t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })}
@@ -286,7 +286,7 @@ export function TopBar() {
-
+
{t('source.menu', 'Source code (AGPL-3.0)')}
diff --git a/src/lib/sourceDownload.ts b/src/lib/sourceDownload.ts
index 93ad322..7a3a33e 100644
--- a/src/lib/sourceDownload.ts
+++ b/src/lib/sourceDownload.ts
@@ -4,9 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { getBasePath } from '@/lib/basePath';
-
-/** Where this build's own source is: written next to the app at build time (see source-archive.ts). */
-export function sourceDownloadUrl(): string {
- return `${getBasePath()}/source.tar.gz`;
-}
+/**
+ * Where to point someone who wants this interface's source.
+ *
+ * INBUXA Admin is a modified Stalwart web interface, so the AGPL's offer is
+ * this fork. The version shown beside the link names the build, which is what
+ * makes the offer something a person can act on.
+ */
+export const SOURCE_URL = 'https://github.com/inbuxa/inbuxa-admin';
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 490be7b..a9576d6 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -22,4 +22,3 @@ interface ImportMeta {
}
declare const __APP_VERSION__: string;
-declare const __SOURCE_ID__: string;
diff --git a/vite.config.ts b/vite.config.ts
index a111d13..c90ea9e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -9,18 +9,13 @@ import { version } from './package.json' with { type: 'json' }
// INBUXA's own dated version lives apart from package.json, whose version
// follows upstream WebUI so its bumps merge without conflicts.
import inbuxa from './inbuxa-version.json' with { type: 'json' }
-import { sourceArchive, sourceIdentity } from './source-archive'
-
-const source = sourceIdentity(import.meta.dirname)
export default defineConfig({
base: './',
define: {
__APP_VERSION__: JSON.stringify(`${inbuxa.version} (base ${version})`),
- // The tree this build came from; source.tar.gz next to the app holds it.
- __SOURCE_ID__: JSON.stringify(source.id),
},
- plugins: [react(), tailwindcss(), sourceArchive(import.meta.dirname, 'inbuxa-admin', source)],
+ plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(import.meta.dirname, './src'),