@sjohansson/astro-plausible 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Stefan Johansson
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.
package/README.md CHANGED
@@ -1,3 +1,206 @@
1
- # Temporary Holding Version
1
+ # Astro Plausible
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ First-party [Plausible](https://plausible.io) analytics for Astro sites.
4
+
5
+ - The official Plausible tracker (the `@plausible-analytics/tracker` package) is bundled into your site's own
6
+ JavaScript. There is no third-party script tag for a blocker to recognise.
7
+ - Events go to `/api/event` on your own domain. A small relay there forwards them to Plausible with the visitor's
8
+ IP and user agent. Plausible drops events that arrive without a real visitor IP, so the relay is not optional.
9
+ - `astro-plausible init` writes that relay for Azure Static Web Apps. On other hosts it is a few lines around
10
+ `createRelay()`.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @sjohansson/astro-plausible
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### 1. Add the integration
21
+
22
+ ```js
23
+ // astro.config.mjs
24
+ import { defineConfig } from "astro/config";
25
+ import plausible from "@sjohansson/astro-plausible/integration";
26
+
27
+ export default defineConfig({
28
+ integrations: [plausible({ domain: "example.com" })],
29
+ });
30
+ ```
31
+
32
+ It adds one script to every page that starts the tracker. Options:
33
+
34
+ | Option | Default | Notes |
35
+ | ---------------------- | -------------- | ------------------------------------------------------------------------- |
36
+ | `domain` | | Required. The site domain as entered in Plausible. |
37
+ | `endpoint` | `"/api/event"` | Where events go. Set `https://plausible.io/api/event` to skip the relay. |
38
+ | `enabled` | | Defaults to on for `astro build` and off for `astro dev`. |
39
+ | `autoCapturePageviews` | `true` | Send a pageview on load and on history navigation. |
40
+ | `hashBasedRouting` | `false` | Track `#` paths as separate pages. |
41
+ | `outboundLinks` | `false` | Track clicks on links to other sites. |
42
+ | `fileDownloads` | `false` | `true` for Plausible's default file types, or `{ fileExtensions: [...] }`. |
43
+ | `formSubmissions` | `false` | Track form submissions. |
44
+ | `captureOnLocalhost` | `false` | Also track on localhost. |
45
+ | `logging` | `true` | Log ignored events to the console. |
46
+ | `customProperties` | | Custom properties added to every event. |
47
+ | `bindToWindow` | `true` | Expose `window.plausible`. Plausible's installation check looks for it. |
48
+
49
+ ### 2. Add the relay
50
+
51
+ #### Azure Static Web Apps
52
+
53
+ ```bash
54
+ pnpm exec astro-plausible init --domain example.com
55
+ ```
56
+
57
+ This writes `api/event/index.mjs` and `api/event/function.json`, plus `api/host.json` and `api/package.json` when
58
+ they are missing. The function is one file with no imports, so the `api/` folder has no dependencies and the deploy
59
+ can skip the API build. Commit the files. Re-run the command after upgrading the package; it rewrites the function
60
+ and leaves the other files alone.
61
+
62
+ Deploy settings:
63
+
64
+ ```json
65
+ // public/staticwebapp.config.json
66
+ { "platform": { "apiRuntime": "node:22" } }
67
+ ```
68
+
69
+ ```yaml
70
+ # The deploy step
71
+ - uses: Azure/static-web-apps-deploy@v1
72
+ with:
73
+ app_location: "dist"
74
+ api_location: "api"
75
+ skip_app_build: true
76
+ skip_api_build: true
77
+ ```
78
+
79
+ CLI options:
80
+
81
+ ```text
82
+ --target <name> Hosting target. "swa" is the only one today.
83
+ --domain <domain> Site domain as entered in Plausible (or PLAUSIBLE_DOMAIN). Repeat it for several sites.
84
+ --upstream <url> Plausible host, default https://plausible.io. Self-hosted: https://plausible.example.com
85
+ --dir <path> API folder, default "api"
86
+ --rule <spec> Validate a custom event: <event>:<property>=<regex>. Repeatable.
87
+ --max-body <bytes> Reject bodies larger than this, default 16384
88
+ --dry-run Print the files instead of writing them
89
+ ```
90
+
91
+ #### Other hosts
92
+
93
+ The relay works on the web standard `Request` and `Response`. Give it the visitor IP when the platform provides one;
94
+ otherwise it reads the usual headers (`x-azure-clientip`, `cf-connecting-ip`, `x-real-ip`, `x-forwarded-for`, and
95
+ so on). A client can send any of those headers, and the first one present wins, so on a platform that sets only
96
+ one of them, name it with `ipHeaders`.
97
+
98
+ ```ts
99
+ // src/pages/api/event.ts, an Astro server endpoint
100
+ import { createRelay } from "@sjohansson/astro-plausible/relay";
101
+ import type { APIRoute } from "astro";
102
+
103
+ export const prerender = false;
104
+
105
+ const relay = createRelay({ domain: "example.com" });
106
+
107
+ export const POST: APIRoute = ({ request, clientAddress }) => relay.handle(request, { clientIp: clientAddress });
108
+ ```
109
+
110
+ ```ts
111
+ // functions/api/event.ts, a Cloudflare Pages function
112
+ import { createRelay } from "@sjohansson/astro-plausible/relay";
113
+
114
+ // Cloudflare sets cf-connecting-ip; any other IP header could come from the client.
115
+ const relay = createRelay({ domain: "example.com", ipHeaders: ["cf-connecting-ip"] });
116
+
117
+ export const onRequestPost = ({ request }) => relay.handle(request);
118
+ ```
119
+
120
+ Relay options:
121
+
122
+ | Option | Default | Notes |
123
+ | --------------- | ------------------------ | ------------------------------------------------------------------------- |
124
+ | `domain` | | Required. One domain or several. Events for other domains get a 400. |
125
+ | `upstream` | `"https://plausible.io"` | Events are posted to `<upstream>/api/event`. |
126
+ | `ipHeaders` | | Headers to read the visitor IP from, in order, when none is passed in. |
127
+ | `maxBodyLength` | `16384` | Largest request body accepted, in bytes. |
128
+ | `events` | | Validation for custom events, see below. |
129
+ | `onError` | | Called when Plausible cannot be reached. The response is a 502 either way. |
130
+
131
+ The relay answers 405 for anything but POST, 413 for a body over the limit, 400 for anything it will not forward,
132
+ and otherwise passes Plausible's status and body through. The visitor IP goes in `X-Forwarded-For`.
133
+
134
+ ### Validating custom events
135
+
136
+ If you build anything on custom events, say vote counts, the relay can make sure only well-formed ones get through.
137
+ A rule lists the properties an event may carry and a pattern for each. Events with a rule are forwarded only with
138
+ those properties, each matching its pattern. Events without a rule pass through untouched.
139
+
140
+ ```ts
141
+ import { voteRelayRule } from "@sjohansson/astro-votes";
142
+
143
+ const relay = createRelay({
144
+ domain: "example.com",
145
+ events: {
146
+ ...voteRelayRule(),
147
+ signup: { plan: /^(free|pro)$/ },
148
+ },
149
+ });
150
+ ```
151
+
152
+ The same for the CLI:
153
+
154
+ ```bash
155
+ pnpm exec astro-plausible init --domain example.com \
156
+ --rule "vote:vote=^[A-Za-z0-9._/-]{1,200}:(?:up|down)(?:-undo)?$" \
157
+ --rule "signup:plan=^(free|pro)$"
158
+ ```
159
+
160
+ ### Tracking events
161
+
162
+ `window.plausible("signup")` works as in any Plausible setup. The typed way is the tracker's own `track`:
163
+
164
+ ```ts
165
+ import { track } from "@sjohansson/astro-plausible/client";
166
+
167
+ track("signup", { props: { plan: "pro" } });
168
+ ```
169
+
170
+ ### Without the integration
171
+
172
+ Start the tracker yourself when you want a function for `customProperties`, a `transformRequest` hook, or control
173
+ over where it runs.
174
+
175
+ ```ts
176
+ import { startPlausible } from "@sjohansson/astro-plausible/client";
177
+
178
+ startPlausible({
179
+ domain: "example.com",
180
+ transformRequest: (payload) => (payload.u.includes("/admin") ? null : payload),
181
+ });
182
+ ```
183
+
184
+ The first call wins; later calls do nothing. The tracker itself allows a single `init()`.
185
+
186
+ ### A path other than `/api/event`
187
+
188
+ Set `endpoint` to wherever the relay answers, for example `/stats/api/event`. Static Web Apps managed functions
189
+ always live under `/api`; a rewrite from another path to `/api/event` in `staticwebapp.config.json` may do it, but
190
+ that is untested and the Static Web Apps documentation says route rules for the API only support redirects and
191
+ roles.
192
+
193
+ ### Local development
194
+
195
+ The integration leaves the tracker out of `astro dev`, and the tracker ignores localhost unless
196
+ `captureOnLocalhost` is set. To test the whole chain locally, set both and run the site through something that
197
+ serves the relay too, such as the Static Web Apps CLI.
198
+
199
+ ## Keeping the tracker current
200
+
201
+ The tracker is a normal dependency, so Renovate or a routine upgrade brings new Plausible releases to your site.
202
+ Nothing is vendored.
203
+
204
+ ## License
205
+
206
+ MIT
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/bin.js ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ import { DEFAULT_MAX_BODY_LENGTH, DEFAULT_UPSTREAM } from "./relay.js";
3
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { parseArgs } from "node:util";
6
+ // src/cli.ts
7
+ /**
8
+ * astro-plausible - writes the relay that forwards tracker events to Plausible.
9
+ *
10
+ * Static Web Apps managed functions must be real files in the repo, and nothing in node_modules is picked up, so
11
+ * the relay is written out as one self-contained file. Re-run the command to update it after a package upgrade.
12
+ */
13
+ const HELP = `Usage: astro-plausible init [options]
14
+
15
+ Writes a relay function that forwards Plausible tracker events from your domain to Plausible.
16
+
17
+ Options:
18
+ --target <name> Hosting target. "swa" (Azure Static Web Apps managed functions) is the only one today.
19
+ --domain <domain> Site domain as entered in Plausible (or PLAUSIBLE_DOMAIN). Repeat it for several sites.
20
+ --upstream <url> Plausible host, default ${DEFAULT_UPSTREAM}. Self-hosted: https://plausible.example.com
21
+ --dir <path> API folder, default "api"
22
+ --rule <spec> Validate a custom event: <event>:<property>=<regex>. Repeatable. Events with a rule are
23
+ forwarded only with the listed properties, each matching its pattern.
24
+ --max-body <bytes> Reject bodies larger than this, default ${DEFAULT_MAX_BODY_LENGTH}
25
+ --dry-run Print the files instead of writing them
26
+ -h, --help Show this help
27
+
28
+ Writes <dir>/event/index.mjs and <dir>/event/function.json every time, and <dir>/host.json and <dir>/package.json
29
+ when they are missing. Deploy with api_location set to <dir>.
30
+ `;
31
+ const CONFIG_PLACEHOLDER = "__ASTRO_PLAUSIBLE_RELAY_CONFIG__";
32
+ const RULE_PATTERN = /^([\w.-]+):([\w.-]+)=(.+)$/s;
33
+ /** Parses `<event>:<property>=<regex>` specs into relay rules. Throws on a bad spec or pattern. */
34
+ function parseRules(specs) {
35
+ const events = {};
36
+ for (const spec of specs) {
37
+ const match = spec.match(RULE_PATTERN);
38
+ if (!match) throw new Error(`--rule must look like <event>:<property>=<regex>, got "${spec}"`);
39
+ const [, event = "", property = "", pattern = ""] = match;
40
+ try {
41
+ new RegExp(pattern);
42
+ } catch (error) {
43
+ throw new Error(`--rule pattern for ${event}:${property} is not a valid regex: ${error}`);
44
+ }
45
+ events[event] = {
46
+ ...events[event],
47
+ [property]: pattern
48
+ };
49
+ }
50
+ return events;
51
+ }
52
+ /** The files `init --target swa` produces for a relay config. */
53
+ function swaFiles(template, config, dir, context) {
54
+ if (!template.includes(CONFIG_PLACEHOLDER)) throw new Error("The relay template is missing its config placeholder.");
55
+ const banner = [
56
+ `// Written by astro-plausible init (@sjohansson/astro-plausible ${context.version}) on ${context.now.toISOString().slice(0, 10)}.`,
57
+ "// Forwards Plausible tracker events to Plausible so the tracker runs first party and is not dropped by ad blockers.",
58
+ "// Do not edit: re-run the command to change the config or pick up a new package version.",
59
+ ""
60
+ ].join("\n");
61
+ const code = template.replace(/^\/\/# sourceMappingURL=.*$/m, "").replaceAll(CONFIG_PLACEHOLDER, () => JSON.stringify(config, null, 2)).trimEnd();
62
+ return [
63
+ {
64
+ path: join(dir, "event", "index.mjs"),
65
+ content: `${banner}${code}\n`,
66
+ keepExisting: false
67
+ },
68
+ {
69
+ path: join(dir, "event", "function.json"),
70
+ content: `${JSON.stringify({
71
+ scriptFile: "index.mjs",
72
+ entryPoint: "default",
73
+ bindings: [{
74
+ authLevel: "anonymous",
75
+ type: "httpTrigger",
76
+ direction: "in",
77
+ name: "req",
78
+ methods: ["post"]
79
+ }, {
80
+ type: "http",
81
+ direction: "out",
82
+ name: "res"
83
+ }]
84
+ }, null, 2)}\n`,
85
+ keepExisting: false
86
+ },
87
+ {
88
+ path: join(dir, "host.json"),
89
+ content: "{\n \"version\": \"2.0\"\n}\n",
90
+ keepExisting: true
91
+ },
92
+ {
93
+ path: join(dir, "package.json"),
94
+ content: `${JSON.stringify({
95
+ name: "site-api",
96
+ private: true,
97
+ description: "Static Web Apps managed functions. No dependencies on purpose, so the deploy skips the API build."
98
+ }, null, 2)}\n`,
99
+ keepExisting: true
100
+ }
101
+ ];
102
+ }
103
+ async function exists(path) {
104
+ try {
105
+ await access(path);
106
+ return true;
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+ async function packageVersion() {
112
+ try {
113
+ const pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
114
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
115
+ } catch {
116
+ return "unknown";
117
+ }
118
+ }
119
+ async function main(argv, env, context = {}) {
120
+ const { values, positionals } = parseArgs({
121
+ args: argv,
122
+ allowPositionals: true,
123
+ options: {
124
+ target: { type: "string" },
125
+ domain: {
126
+ type: "string",
127
+ multiple: true
128
+ },
129
+ upstream: { type: "string" },
130
+ dir: { type: "string" },
131
+ rule: {
132
+ type: "string",
133
+ multiple: true
134
+ },
135
+ "max-body": { type: "string" },
136
+ "dry-run": { type: "boolean" },
137
+ help: {
138
+ type: "boolean",
139
+ short: "h"
140
+ }
141
+ }
142
+ });
143
+ if (values.help || positionals.length === 0) {
144
+ process.stdout.write(HELP);
145
+ return values.help ? 0 : 1;
146
+ }
147
+ if (positionals[0] !== "init") return fail(`Unknown command "${positionals[0]}". Only "init" exists.`);
148
+ const target = values.target ?? "swa";
149
+ if (target !== "swa") return fail(`Unknown target "${target}". Only "swa" exists.`);
150
+ const domains = values.domain ?? (env["PLAUSIBLE_DOMAIN"] ? [env["PLAUSIBLE_DOMAIN"]] : []);
151
+ if (domains.length === 0) return fail("No domain. Pass --domain or set PLAUSIBLE_DOMAIN.");
152
+ const maxBodyLength = values["max-body"] === void 0 ? void 0 : Number(values["max-body"]);
153
+ if (maxBodyLength !== void 0 && !(Number.isInteger(maxBodyLength) && maxBodyLength > 0)) return fail(`--max-body must be a positive whole number, got "${values["max-body"]}".`);
154
+ let events;
155
+ try {
156
+ events = parseRules(values.rule ?? []);
157
+ } catch (error) {
158
+ return fail(error instanceof Error ? error.message : String(error));
159
+ }
160
+ const config = {
161
+ domain: domains.length === 1 ? domains[0] : domains,
162
+ ...values.upstream ? { upstream: values.upstream } : {},
163
+ ...maxBodyLength ? { maxBodyLength } : {},
164
+ ...Object.keys(events).length > 0 ? { events } : {}
165
+ };
166
+ const templateDir = context.templateDir ?? new URL("./templates/", import.meta.url);
167
+ const files = swaFiles(await readFile(new URL("swa.mjs", templateDir), "utf8"), config, values.dir ?? "api", {
168
+ version: context.version ?? await packageVersion(),
169
+ now: context.now ?? /* @__PURE__ */ new Date()
170
+ });
171
+ for (const file of files) {
172
+ if (values["dry-run"]) {
173
+ process.stdout.write(`--- ${file.path}\n${file.content}\n`);
174
+ continue;
175
+ }
176
+ const present = await exists(file.path);
177
+ if (present && file.keepExisting) {
178
+ process.stdout.write(`kept ${file.path}\n`);
179
+ continue;
180
+ }
181
+ await mkdir(join(file.path, ".."), { recursive: true });
182
+ await writeFile(file.path, file.content);
183
+ process.stdout.write(`${present ? "updated" : "created"} ${file.path}\n`);
184
+ }
185
+ return 0;
186
+ }
187
+ function fail(message) {
188
+ process.stderr.write(`astro-plausible: ${message}\n`);
189
+ return 1;
190
+ }
191
+ // src/bin.ts
192
+ main(process.argv.slice(2), process.env).then((code) => {
193
+ process.exitCode = code;
194
+ }, (error) => {
195
+ process.exitCode = fail(error instanceof Error ? error.message : String(error));
196
+ });
197
+
198
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","names":[],"sources":["../src/cli.ts","../src/bin.ts"],"sourcesContent":["/**\n * astro-plausible - writes the relay that forwards tracker events to Plausible.\n *\n * Static Web Apps managed functions must be real files in the repo, and nothing in node_modules is picked up, so\n * the relay is written out as one self-contained file. Re-run the command to update it after a package upgrade.\n */\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { DEFAULT_MAX_BODY_LENGTH, DEFAULT_UPSTREAM, type RelayOptions, type RelayRule } from \"./relay\";\n\nconst HELP = `Usage: astro-plausible init [options]\n\nWrites a relay function that forwards Plausible tracker events from your domain to Plausible.\n\nOptions:\n --target <name> Hosting target. \"swa\" (Azure Static Web Apps managed functions) is the only one today.\n --domain <domain> Site domain as entered in Plausible (or PLAUSIBLE_DOMAIN). Repeat it for several sites.\n --upstream <url> Plausible host, default ${DEFAULT_UPSTREAM}. Self-hosted: https://plausible.example.com\n --dir <path> API folder, default \"api\"\n --rule <spec> Validate a custom event: <event>:<property>=<regex>. Repeatable. Events with a rule are\n forwarded only with the listed properties, each matching its pattern.\n --max-body <bytes> Reject bodies larger than this, default ${DEFAULT_MAX_BODY_LENGTH}\n --dry-run Print the files instead of writing them\n -h, --help Show this help\n\nWrites <dir>/event/index.mjs and <dir>/event/function.json every time, and <dir>/host.json and <dir>/package.json\nwhen they are missing. Deploy with api_location set to <dir>.\n`;\n\nconst CONFIG_PLACEHOLDER = \"__ASTRO_PLAUSIBLE_RELAY_CONFIG__\";\nconst RULE_PATTERN = /^([\\w.-]+):([\\w.-]+)=(.+)$/s;\n\nexport interface CliContext {\n /** Where the built templates live, as a directory URL. Defaults to the package's own dist/templates. */\n templateDir?: URL;\n version?: string;\n now?: Date;\n}\n\nexport interface ScaffoldFile {\n path: string;\n content: string;\n /** Written only when the file does not exist yet. */\n keepExisting: boolean;\n}\n\n/** Parses `<event>:<property>=<regex>` specs into relay rules. Throws on a bad spec or pattern. */\nexport function parseRules(specs: string[]): Record<string, RelayRule> {\n const events: Record<string, RelayRule> = {};\n for (const spec of specs) {\n const match = spec.match(RULE_PATTERN);\n if (!match) throw new Error(`--rule must look like <event>:<property>=<regex>, got \"${spec}\"`);\n const [, event = \"\", property = \"\", pattern = \"\"] = match;\n try {\n new RegExp(pattern);\n } catch (error) {\n throw new Error(`--rule pattern for ${event}:${property} is not a valid regex: ${error}`);\n }\n events[event] = { ...events[event], [property]: pattern };\n }\n return events;\n}\n\n/** The files `init --target swa` produces for a relay config. */\nexport function swaFiles(\n template: string,\n config: RelayOptions,\n dir: string,\n context: Required<Pick<CliContext, \"version\" | \"now\">>,\n): ScaffoldFile[] {\n if (!template.includes(CONFIG_PLACEHOLDER)) throw new Error(\"The relay template is missing its config placeholder.\");\n const banner = [\n `// Written by astro-plausible init (@sjohansson/astro-plausible ${context.version}) on ${context.now.toISOString().slice(0, 10)}.`,\n \"// Forwards Plausible tracker events to Plausible so the tracker runs first party and is not dropped by ad blockers.\",\n \"// Do not edit: re-run the command to change the config or pick up a new package version.\",\n \"\",\n ].join(\"\\n\");\n const code = template\n .replace(/^\\/\\/# sourceMappingURL=.*$/m, \"\")\n // A replacement function, so `$` sequences in the patterns are inserted as is.\n .replaceAll(CONFIG_PLACEHOLDER, () => JSON.stringify(config, null, 2))\n .trimEnd();\n\n const functionJson = {\n scriptFile: \"index.mjs\",\n entryPoint: \"default\",\n bindings: [\n { authLevel: \"anonymous\", type: \"httpTrigger\", direction: \"in\", name: \"req\", methods: [\"post\"] },\n { type: \"http\", direction: \"out\", name: \"res\" },\n ],\n };\n const packageJson = {\n name: \"site-api\",\n private: true,\n description: \"Static Web Apps managed functions. No dependencies on purpose, so the deploy skips the API build.\",\n };\n\n return [\n { path: join(dir, \"event\", \"index.mjs\"), content: `${banner}${code}\\n`, keepExisting: false },\n {\n path: join(dir, \"event\", \"function.json\"),\n content: `${JSON.stringify(functionJson, null, 2)}\\n`,\n keepExisting: false,\n },\n { path: join(dir, \"host.json\"), content: '{\\n \"version\": \"2.0\"\\n}\\n', keepExisting: true },\n { path: join(dir, \"package.json\"), content: `${JSON.stringify(packageJson, null, 2)}\\n`, keepExisting: true },\n ];\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function packageVersion(): Promise<string> {\n try {\n const pkg = JSON.parse(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nexport async function main(\n argv: string[],\n env: Record<string, string | undefined>,\n context: CliContext = {},\n): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n target: { type: \"string\" },\n domain: { type: \"string\", multiple: true },\n upstream: { type: \"string\" },\n dir: { type: \"string\" },\n rule: { type: \"string\", multiple: true },\n \"max-body\": { type: \"string\" },\n \"dry-run\": { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n });\n\n if (values.help || positionals.length === 0) {\n process.stdout.write(HELP);\n return values.help ? 0 : 1;\n }\n if (positionals[0] !== \"init\") return fail(`Unknown command \"${positionals[0]}\". Only \"init\" exists.`);\n\n const target = values.target ?? \"swa\";\n if (target !== \"swa\") return fail(`Unknown target \"${target}\". Only \"swa\" exists.`);\n\n const domains = values.domain ?? (env[\"PLAUSIBLE_DOMAIN\"] ? [env[\"PLAUSIBLE_DOMAIN\"]] : []);\n if (domains.length === 0) return fail(\"No domain. Pass --domain or set PLAUSIBLE_DOMAIN.\");\n\n const maxBodyLength = values[\"max-body\"] === undefined ? undefined : Number(values[\"max-body\"]);\n if (maxBodyLength !== undefined && !(Number.isInteger(maxBodyLength) && maxBodyLength > 0)) {\n return fail(`--max-body must be a positive whole number, got \"${values[\"max-body\"]}\".`);\n }\n\n let events: Record<string, RelayRule>;\n try {\n events = parseRules(values.rule ?? []);\n } catch (error) {\n return fail(error instanceof Error ? error.message : String(error));\n }\n\n const config: RelayOptions = {\n domain: domains.length === 1 ? (domains[0] as string) : domains,\n ...(values.upstream ? { upstream: values.upstream } : {}),\n ...(maxBodyLength ? { maxBodyLength } : {}),\n ...(Object.keys(events).length > 0 ? { events } : {}),\n };\n\n const templateDir = context.templateDir ?? new URL(\"./templates/\", import.meta.url);\n const template = await readFile(new URL(\"swa.mjs\", templateDir), \"utf8\");\n const files = swaFiles(template, config, values.dir ?? \"api\", {\n version: context.version ?? (await packageVersion()),\n now: context.now ?? new Date(),\n });\n\n for (const file of files) {\n if (values[\"dry-run\"]) {\n process.stdout.write(`--- ${file.path}\\n${file.content}\\n`);\n continue;\n }\n const present = await exists(file.path);\n if (present && file.keepExisting) {\n process.stdout.write(`kept ${file.path}\\n`);\n continue;\n }\n await mkdir(join(file.path, \"..\"), { recursive: true });\n await writeFile(file.path, file.content);\n process.stdout.write(`${present ? \"updated\" : \"created\"} ${file.path}\\n`);\n }\n return 0;\n}\n\nexport function fail(message: string): number {\n process.stderr.write(`astro-plausible: ${message}\\n`);\n return 1;\n}\n","#!/usr/bin/env node\nimport { fail, main } from \"./cli\";\n\nmain(process.argv.slice(2), process.env).then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.exitCode = fail(error instanceof Error ? error.message : String(error));\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { n as PlausibleOptions } from "./options-C15OXXsk.js";
2
+ import { CustomProperties, PlausibleConfig, PlausibleEventOptions, PlausibleRequestPayload, track } from "@plausible-analytics/tracker";
3
+ export interface PlausibleClientOptions extends Omit<PlausibleOptions, "customProperties"> {
4
+ /** Custom properties for every event, or a function of the event name that returns them. */
5
+ customProperties?: PlausibleConfig["customProperties"];
6
+ /** Change or drop a request before it is sent. Return null to drop it. */
7
+ transformRequest?: PlausibleConfig["transformRequest"];
8
+ }
9
+ /**
10
+ * Starts the tracker. The first call wins; later calls do nothing, so it is safe in a script that runs on every
11
+ * page. The tracker itself allows a single `init()`.
12
+ */
13
+ export declare function startPlausible(options: PlausibleClientOptions): void;
14
+ export { type CustomProperties, type PlausibleEventOptions, type PlausibleRequestPayload, track };
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;iBAUiB,+BAA+B,KAAK;;EAEnD,mBAAmB;;EAEnB,mBAAmB;;;;;;wBASL,eAAe,SAAS"}
package/dist/client.js ADDED
@@ -0,0 +1,27 @@
1
+ import { n as plausibleConfig } from "./options-B-SB8PRR.js";
2
+ import { init, track } from "@plausible-analytics/tracker";
3
+ // src/client.ts
4
+ /**
5
+ * Browser side. Starts the official Plausible tracker (the `@plausible-analytics/tracker` package, bundled into the
6
+ * site) pointed at the relay on your own domain.
7
+ */
8
+ let started = false;
9
+ /**
10
+ * Starts the tracker. The first call wins; later calls do nothing, so it is safe in a script that runs on every
11
+ * page. The tracker itself allows a single `init()`.
12
+ */
13
+ function startPlausible(options) {
14
+ if (started || typeof window === "undefined") return;
15
+ started = true;
16
+ const { customProperties, transformRequest, ...rest } = options;
17
+ const config = plausibleConfig(typeof customProperties === "function" || customProperties === void 0 ? rest : {
18
+ ...rest,
19
+ customProperties
20
+ });
21
+ if (typeof customProperties === "function") config.customProperties = customProperties;
22
+ if (transformRequest) config.transformRequest = transformRequest;
23
+ init(config);
24
+ }
25
+ export { startPlausible, track };
26
+
27
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * Browser side. Starts the official Plausible tracker (the `@plausible-analytics/tracker` package, bundled into the\n * site) pointed at the relay on your own domain.\n */\nimport { init, type PlausibleConfig, track } from \"@plausible-analytics/tracker\";\nimport { type PlausibleOptions, plausibleConfig } from \"./options\";\n\nexport type { CustomProperties, PlausibleEventOptions, PlausibleRequestPayload } from \"@plausible-analytics/tracker\";\nexport { track };\n\nexport interface PlausibleClientOptions extends Omit<PlausibleOptions, \"customProperties\"> {\n /** Custom properties for every event, or a function of the event name that returns them. */\n customProperties?: PlausibleConfig[\"customProperties\"];\n /** Change or drop a request before it is sent. Return null to drop it. */\n transformRequest?: PlausibleConfig[\"transformRequest\"];\n}\n\nlet started = false;\n\n/**\n * Starts the tracker. The first call wins; later calls do nothing, so it is safe in a script that runs on every\n * page. The tracker itself allows a single `init()`.\n */\nexport function startPlausible(options: PlausibleClientOptions): void {\n if (started || typeof window === \"undefined\") return;\n started = true;\n\n const { customProperties, transformRequest, ...rest } = options;\n const config = plausibleConfig(\n typeof customProperties === \"function\" || customProperties === undefined ? rest : { ...rest, customProperties },\n );\n if (typeof customProperties === \"function\") config.customProperties = customProperties;\n if (transformRequest) config.transformRequest = transformRequest;\n init(config);\n}\n"],"mappings":";;;;;;;AAiBA,IAAI,UAAU;;;;;AAMd,SAAgB,eAAe,SAAuC;CACpE,IAAI,WAAW,OAAO,WAAW,aAAa;CAC9C,UAAU;CAEV,MAAM,EAAE,kBAAkB,kBAAkB,GAAG,SAAS;CACxD,MAAM,SAAS,gBACb,OAAO,qBAAqB,cAAc,qBAAqB,KAAA,IAAY,OAAO;EAAE,GAAG;EAAM;CAAiB,CAChH;CACA,IAAI,OAAO,qBAAqB,YAAY,OAAO,mBAAmB;CACtE,IAAI,kBAAkB,OAAO,mBAAmB;CAChD,KAAK,MAAM;AACb"}
@@ -0,0 +1,3 @@
1
+ import { n as PlausibleOptions, r as plausibleConfig, t as DEFAULT_ENDPOINT } from "./options-C15OXXsk.js";
2
+ import { PlausibleIntegrationOptions, clientScript } from "./integration.js";
3
+ export { DEFAULT_ENDPOINT, type PlausibleIntegrationOptions, type PlausibleOptions, clientScript, plausibleConfig };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { n as plausibleConfig, t as DEFAULT_ENDPOINT } from "./options-B-SB8PRR.js";
2
+ import { clientScript } from "./integration.js";
3
+ export { DEFAULT_ENDPOINT, clientScript, plausibleConfig };
@@ -0,0 +1,29 @@
1
+ import { n as PlausibleOptions } from "./options-C15OXXsk.js";
2
+ import { AstroIntegration } from "astro";
3
+ export interface PlausibleIntegrationOptions extends PlausibleOptions {
4
+ /**
5
+ * Whether to add the tracker to pages. Default is to add it for `astro build` and leave it out of `astro dev`,
6
+ * so local browsing does not show up in the dashboard. Set `true` to track in dev too (with
7
+ * `captureOnLocalhost: true`, or the tracker ignores localhost anyway).
8
+ */
9
+ enabled?: boolean;
10
+ }
11
+ /** The client script injected on every page. Exported for tests. */
12
+ export declare function clientScript(options: PlausibleOptions): string;
13
+ /**
14
+ * Astro integration. Bundles the Plausible tracker into every page and points it at `endpoint`, which defaults to
15
+ * `/api/event` on the site itself. Pair it with the relay from `astro-plausible init` so events reach Plausible
16
+ * from your own domain.
17
+ *
18
+ * @example
19
+ * ```js
20
+ * // astro.config.mjs
21
+ * import plausible from "@sjohansson/astro-plausible/integration";
22
+ *
23
+ * export default defineConfig({
24
+ * integrations: [plausible({ domain: "example.com" })],
25
+ * });
26
+ * ```
27
+ */
28
+ export default function plausibleIntegration(options: PlausibleIntegrationOptions): AstroIntegration;
29
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","names":[],"sources":["../src/integration.ts"],"mappings":";;iBAGiB,oCAAoC;;;;;;EAMnD;;;wBAIc,aAAa,SAAS;;;;;;;;;;;;;;;;wBAsBd,qBAAqB,SAAS,8BAA8B"}
@@ -0,0 +1,40 @@
1
+ import { n as plausibleConfig } from "./options-B-SB8PRR.js";
2
+ // src/integration.ts
3
+ /** The client script injected on every page. Exported for tests. */
4
+ function clientScript(options) {
5
+ return ["import { startPlausible } from \"@sjohansson/astro-plausible/client\";", `startPlausible(${JSON.stringify(plausibleConfig(options))});`].join("\n");
6
+ }
7
+ /**
8
+ * Astro integration. Bundles the Plausible tracker into every page and points it at `endpoint`, which defaults to
9
+ * `/api/event` on the site itself. Pair it with the relay from `astro-plausible init` so events reach Plausible
10
+ * from your own domain.
11
+ *
12
+ * @example
13
+ * ```js
14
+ * // astro.config.mjs
15
+ * import plausible from "@sjohansson/astro-plausible/integration";
16
+ *
17
+ * export default defineConfig({
18
+ * integrations: [plausible({ domain: "example.com" })],
19
+ * });
20
+ * ```
21
+ */
22
+ function plausibleIntegration(options) {
23
+ const { enabled, ...tracker } = options ?? {};
24
+ if (enabled !== void 0 && typeof enabled !== "boolean") throw new Error("[@sjohansson/astro-plausible] enabled must be a boolean.");
25
+ plausibleConfig(tracker);
26
+ return {
27
+ name: "@sjohansson/astro-plausible",
28
+ hooks: { "astro:config:setup": ({ command, injectScript, logger }) => {
29
+ if (!(enabled ?? command === "build")) {
30
+ logger.info(`Plausible tracker left out of this ${command}. Set enabled: true to include it.`);
31
+ return;
32
+ }
33
+ injectScript("page", clientScript(tracker));
34
+ logger.debug(`Plausible tracker added, sending to ${tracker.endpoint ?? "/api/event"}`);
35
+ } }
36
+ };
37
+ }
38
+ export { clientScript, plausibleIntegration as default };
39
+
40
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","names":[],"sources":["../src/integration.ts"],"sourcesContent":["import type { AstroIntegration } from \"astro\";\nimport { type PlausibleOptions, plausibleConfig } from \"./options\";\n\nexport interface PlausibleIntegrationOptions extends PlausibleOptions {\n /**\n * Whether to add the tracker to pages. Default is to add it for `astro build` and leave it out of `astro dev`,\n * so local browsing does not show up in the dashboard. Set `true` to track in dev too (with\n * `captureOnLocalhost: true`, or the tracker ignores localhost anyway).\n */\n enabled?: boolean;\n}\n\n/** The client script injected on every page. Exported for tests. */\nexport function clientScript(options: PlausibleOptions): string {\n return [\n 'import { startPlausible } from \"@sjohansson/astro-plausible/client\";',\n `startPlausible(${JSON.stringify(plausibleConfig(options))});`,\n ].join(\"\\n\");\n}\n\n/**\n * Astro integration. Bundles the Plausible tracker into every page and points it at `endpoint`, which defaults to\n * `/api/event` on the site itself. Pair it with the relay from `astro-plausible init` so events reach Plausible\n * from your own domain.\n *\n * @example\n * ```js\n * // astro.config.mjs\n * import plausible from \"@sjohansson/astro-plausible/integration\";\n *\n * export default defineConfig({\n * integrations: [plausible({ domain: \"example.com\" })],\n * });\n * ```\n */\nexport default function plausibleIntegration(options: PlausibleIntegrationOptions): AstroIntegration {\n const { enabled, ...tracker } = options ?? {};\n if (enabled !== undefined && typeof enabled !== \"boolean\") {\n throw new Error(\"[@sjohansson/astro-plausible] enabled must be a boolean.\");\n }\n // Validate now so a bad config fails at startup, not on the first page.\n plausibleConfig(tracker);\n\n return {\n name: \"@sjohansson/astro-plausible\",\n hooks: {\n \"astro:config:setup\": ({ command, injectScript, logger }) => {\n const on = enabled ?? command === \"build\";\n if (!on) {\n logger.info(`Plausible tracker left out of this ${command}. Set enabled: true to include it.`);\n return;\n }\n injectScript(\"page\", clientScript(tracker));\n logger.debug(`Plausible tracker added, sending to ${tracker.endpoint ?? \"/api/event\"}`);\n },\n },\n };\n}\n"],"mappings":";;;AAaA,SAAgB,aAAa,SAAmC;CAC9D,OAAO,CACL,0EACA,kBAAkB,KAAK,UAAU,gBAAgB,OAAO,CAAC,EAAE,GAC7D,CAAC,CAAC,KAAK,IAAI;AACb;;;;;;;;;;;;;;;;AAiBA,SAAwB,qBAAqB,SAAwD;CACnG,MAAM,EAAE,SAAS,GAAG,YAAY,WAAW,CAAC;CAC5C,IAAI,YAAY,KAAA,KAAa,OAAO,YAAY,WAC9C,MAAM,IAAI,MAAM,0DAA0D;CAG5E,gBAAgB,OAAO;CAEvB,OAAO;EACL,MAAM;EACN,OAAO,EACL,uBAAuB,EAAE,SAAS,cAAc,aAAa;GAE3D,IAAI,EADO,WAAW,YAAY,UACzB;IACP,OAAO,KAAK,sCAAsC,QAAQ,mCAAmC;IAC7F;GACF;GACA,aAAa,QAAQ,aAAa,OAAO,CAAC;GAC1C,OAAO,MAAM,uCAAuC,QAAQ,YAAY,cAAc;EACxF,EACF;CACF;AACF"}
@@ -0,0 +1,50 @@
1
+ // src/options.ts
2
+ const DEFAULT_ENDPOINT = "/api/event";
3
+ const DOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*(\/[\w.-]+)*$/i;
4
+ const BOOLEAN_KEYS = [
5
+ "autoCapturePageviews",
6
+ "hashBasedRouting",
7
+ "outboundLinks",
8
+ "formSubmissions",
9
+ "captureOnLocalhost",
10
+ "logging",
11
+ "bindToWindow"
12
+ ];
13
+ function fail(message) {
14
+ throw new Error(`[@sjohansson/astro-plausible] ${message}`);
15
+ }
16
+ /** Validates the options and returns the config for the tracker's `init()`. Only keys that were set are included. */
17
+ function plausibleConfig(options) {
18
+ if (typeof options?.domain !== "string" || options.domain.length > 253 || !DOMAIN_PATTERN.test(options.domain)) fail("domain must be the site domain as entered in Plausible, like example.com.");
19
+ const endpoint = options.endpoint ?? "/api/event";
20
+ if (typeof endpoint !== "string" || endpoint.length === 0) fail("endpoint must be a non-empty string.");
21
+ const config = {
22
+ domain: options.domain,
23
+ endpoint
24
+ };
25
+ for (const key of BOOLEAN_KEYS) {
26
+ const value = options[key];
27
+ if (value === void 0) continue;
28
+ if (typeof value !== "boolean") fail(`${key} must be a boolean.`);
29
+ config[key] = value;
30
+ }
31
+ if (options.fileDownloads !== void 0) {
32
+ const value = options.fileDownloads;
33
+ const custom = typeof value === "object" && value !== null && Array.isArray(value.fileExtensions);
34
+ if (typeof value !== "boolean" && !custom) fail("fileDownloads must be a boolean or { fileExtensions: [...] }.");
35
+ config.fileDownloads = value;
36
+ }
37
+ if (options.customProperties !== void 0) {
38
+ const value = options.customProperties;
39
+ if (!(typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((v) => [
40
+ "string",
41
+ "number",
42
+ "boolean"
43
+ ].includes(typeof v)))) fail("customProperties must be an object of strings, numbers and booleans.");
44
+ config.customProperties = value;
45
+ }
46
+ return config;
47
+ }
48
+ export { plausibleConfig as n, DEFAULT_ENDPOINT as t };
49
+
50
+ //# sourceMappingURL=options-B-SB8PRR.js.map