Skip to content

Commit ef950fa

Browse files
committed
feat(tracker): the tracker is MIT; the server stays AGPL
apps/tracker gets its own MIT LICENSE and manifest field, the built oa.js opens with a /*! licence line (+69 B gzipped, +112 B raw, budgets stand), and README / SELF-HOSTING / CONTRIBUTING say the exception in one sentence each. The root LICENSE is untouched. A unit test pins the three places the split lives to each other.
1 parent a685424 commit ef950fa

8 files changed

Lines changed: 100 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ Releases before 0.6.0 have their notes on the
3636

3737
### Changed
3838

39+
- **The tracker is MIT.** `apps/tracker/` and the `oa.js` it builds now carry
40+
the MIT licence ([`apps/tracker/LICENSE`](apps/tracker/LICENSE)); the rest of
41+
the product stays AGPL-3.0. It is the one file that runs on your visitors'
42+
browsers rather than on the server, so embedding it should never have raised
43+
a copyleft question. The served bundle opens with a `/*! … MIT licensed … */`
44+
line saying so; +69 bytes gzipped.
3945
- The poster's chart is drawn on a smooth curve with the dashboard's own glow,
4046
and the two brands no longer share one line: the site is the headline, the
4147
period sits in the corner, and Open Analytics signs on a small tab at the

‎CONTRIBUTING.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ anywhere else. What it grants is permission to ship your work under terms other
2323
than the AGPL, which is what lets the hosted service and the open project be one
2424
codebase. [CLA.md](CLA.md) says so in full, including why.
2525

26+
One directory has its own licence: `apps/tracker/` is MIT, not AGPL, because it
27+
is the file that runs on other people's sites. A change there is released under
28+
MIT; the CLA already covers that.
29+
2630
[Discussions](https://github.com/OpenLabs-so/openanalytics/discussions) are for
2731
questions and for ideas you want to talk through first; issues are for something
2832
that is wrong or missing.

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ Security reports: [SECURITY.md](SECURITY.md) — please not a public issue.
166166
Code: [AGPL-3.0](LICENSE). If you run a modified OpenAnalytics as a network
167167
service, the AGPL requires you to offer your modified source to its users.
168168

169+
The browser tracker is the exception: `apps/tracker/` and the `oa.js` it builds
170+
are [MIT](apps/tracker/LICENSE), because it is the one file that runs on your
171+
site, not on the server. Embedding it creates no obligation under the AGPL.
172+
169173
The "OpenAnalytics" name and the hosted service's domain identify the instance
170174
its authors operate and are **not** part of the license grant. A self-hosted
171175
instance runs the software, not the brand.

‎SELF-HOSTING.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,11 @@ bug. A proxy or CDN in front of Caddy hands over its own address instead; see
874874
AGPL-3.0. If you run a modified OpenAnalytics as a network service, the license
875875
requires you to offer your modified source to its users.
876876

877+
One exception: the browser tracker — everything under `apps/tracker/`, and the
878+
`oa.js` it builds — is [MIT](apps/tracker/LICENSE). It is the file your visitors
879+
download and the only part of the product that runs on your pages, so it carries
880+
no copyleft. The bundle says so on its first line.
881+
877882
The "OpenAnalytics" name and the `getopen.so` domain identify the hosted service
878883
run by its operators and are **not** part of the license grant. Self-hosted
879884
instances run the software, not the brand.

‎apps/tracker/LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 OpenLabs LLC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎apps/tracker/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@openanalytics/tracker",
33
"version": "0.0.0",
44
"private": true,
5+
"license": "MIT",
56
"type": "module",
67
"description": "Dependency-free browser tracker source and build output.",
78
"exports": {

‎scripts/build-tracker.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ export const GZIP_BUDGET_BYTES = 9_728
4040
/** Uncompressed bytes. Bounds parse and compile cost, which gzip hides. */
4141
export const RAW_BUDGET_BYTES = 25_600
4242

43+
/**
44+
* The first line of the served bundle. The tracker is MIT while the rest of the
45+
* product is AGPL-3.0 (`apps/tracker/LICENSE`), and the file a site owner's
46+
* visitors download is the one place that fact has to be readable without
47+
* opening the repository. `legalComments: 'none'` strips every comment the
48+
* sources carry, so this is the only comment the bundle has, and it is added
49+
* after minification. The `!` keeps it through any minifier a customer runs
50+
* over their own assets.
51+
*/
52+
export const TRACKER_BANNER =
53+
'/*! OpenAnalytics tracker. MIT licensed: github.com/OpenLabs-so/openanalytics/blob/main/apps/tracker/LICENSE */'
54+
4355
const result = await build({
4456
entryPoints: [ENTRY],
4557
outfile: OUT_FILE,
@@ -50,6 +62,7 @@ const result = await build({
5062
// dragging in transpiler helpers for syntax we do not write.
5163
target: ['es2020', 'chrome80', 'firefox78', 'safari14', 'edge88'],
5264
legalComments: 'none',
65+
banner: { js: TRACKER_BANNER },
5366
write: false,
5467
})
5568

@@ -58,6 +71,10 @@ if (!output) {
5871
console.error('[tracker] esbuild produced no output')
5972
process.exit(1)
6073
}
74+
if (!output.text.startsWith(TRACKER_BANNER)) {
75+
console.error('[tracker] the bundle does not open with its licence line')
76+
process.exit(1)
77+
}
6178

6279
mkdirSync(OUT_DIR, { recursive: true })
6380
writeFileSync(OUT_FILE, output.contents)

‎tests/unit/tracker-licence.test.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { readFileSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
import { describe, expect, it } from 'vitest'
4+
5+
/**
6+
* `apps/tracker` is MIT while everything around it is AGPL-3.0 (README, "License
7+
* and trademark"). The
8+
* split lives in three files that nothing else reads — a LICENSE, a manifest
9+
* field and the banner the build script prepends — so this is the one place
10+
* that notices when one of them drifts from the other two.
11+
*/
12+
13+
const ROOT = join(import.meta.dirname, '..', '..')
14+
const read = (...parts: string[]) => readFileSync(join(ROOT, ...parts), 'utf8')
15+
16+
describe('the tracker licence', () => {
17+
it('ships an MIT LICENSE naming the copyright holder', () => {
18+
const licence = read('apps', 'tracker', 'LICENSE')
19+
expect(licence.startsWith('MIT License\n')).toBe(true)
20+
expect(licence).toContain('Copyright (c) 2026 OpenLabs LLC')
21+
expect(licence).toContain('Permission is hereby granted, free of charge')
22+
})
23+
24+
it('declares MIT in its manifest while the workspace root stays AGPL', () => {
25+
const tracker = JSON.parse(read('apps', 'tracker', 'package.json')) as { license?: string }
26+
const root = JSON.parse(read('package.json')) as { license?: string }
27+
expect(tracker.license).toBe('MIT')
28+
expect(root.license).toBe('AGPL-3.0-only')
29+
})
30+
31+
it('opens the served bundle with a licence line that survives minifiers', () => {
32+
// The build script is not importable (it builds on import), so its banner
33+
// is read as text. `/*!` is the marker minifiers keep.
34+
const script = read('scripts', 'build-tracker.mjs')
35+
const banner = /export const TRACKER_BANNER =\s*'([^']+)'/.exec(script)?.[1]
36+
expect(banner).toBeDefined()
37+
expect(banner?.startsWith('/*!')).toBe(true)
38+
expect(banner).toContain('MIT')
39+
expect(banner).toContain('apps/tracker/LICENSE')
40+
expect(script).toContain('banner: { js: TRACKER_BANNER }')
41+
})
42+
})

0 commit comments

Comments
 (0)