@theholocron/holocron-plugin-sentry 3.24.3
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 +21 -0
- package/README.md +54 -0
- package/dist/index.d.mts +71 -0
- package/dist/index.mjs +110 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Newton Koumantzelis
|
|
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
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- editorconfig-checker-disable-file -->
|
|
2
|
+
|
|
3
|
+
# `@theholocron/holocron-plugin-sentry`
|
|
4
|
+
|
|
5
|
+
Sentry plugin for [Holocron](../cli). Implements the `observability`
|
|
6
|
+
capability against the [Sentry management API](https://docs.sentry.io/api/).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
<!-- prettier-ignore -->
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add -D @theholocron/holocron-plugin-sentry
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Auth
|
|
17
|
+
|
|
18
|
+
Token resolution order:
|
|
19
|
+
|
|
20
|
+
1. `--token <TOKEN>` flag on the holocron invocation
|
|
21
|
+
2. `HOLOCRON_SENTRY_TOKEN` env var
|
|
22
|
+
3. `SENTRY_AUTH_TOKEN` env var (the standard Sentry variable name)
|
|
23
|
+
|
|
24
|
+
Generate an auth token at **sentry.io/settings/account/api/auth-tokens/**
|
|
25
|
+
with `org:read`, `project:read`, and `project:write` scopes. Both
|
|
26
|
+
user-owned and org-owned tokens are accepted.
|
|
27
|
+
|
|
28
|
+
## Config
|
|
29
|
+
|
|
30
|
+
<!-- prettier-ignore -->
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"providers": {
|
|
34
|
+
"observability": ["sentry", { "org": "my-org-slug", "team": "my-team" }],
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `org` (required) — Sentry organization slug.
|
|
41
|
+
- `team` (optional) — team slug for project creation. Defaults to the
|
|
42
|
+
org slug when omitted.
|
|
43
|
+
|
|
44
|
+
## What's implemented
|
|
45
|
+
|
|
46
|
+
| Method | What it does |
|
|
47
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
48
|
+
| `describe` | Returns `{ provider: "sentry", envKeys: ["SENTRY_DSN", "NEXT_PUBLIC_SENTRY_DSN"] }` — the env vars the app reads at runtime. |
|
|
49
|
+
| `whoami` | Fetches the org by slug to verify the token and confirm the org exists. |
|
|
50
|
+
| `ensureProject` | Looks up the project by slug (derived from `name`); creates it under the configured team if absent. Returns the DSN and an `alreadyExists` flag. Idempotent. |
|
|
51
|
+
|
|
52
|
+
`holocron setup` calls `ensureProject` and pushes both `SENTRY_DSN` and
|
|
53
|
+
`NEXT_PUBLIC_SENTRY_DSN` to GitHub Secrets (same DSN value, both keys
|
|
54
|
+
required by the Sentry Next.js SDK).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AuthError, Observability, ResolveTokenInput } from "@theholocron/cli";
|
|
2
|
+
import { SentryClient, SentryClientOptions, createSentryClient } from "@theholocron/sentry-client";
|
|
3
|
+
//#region src/auth.d.ts
|
|
4
|
+
declare const resolveToken: (input?: ResolveTokenInput) => string;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/capabilities/observability.d.ts
|
|
7
|
+
interface SentryObservabilityOptions {
|
|
8
|
+
/** Sentry organization slug. Required. */
|
|
9
|
+
org: string;
|
|
10
|
+
/** Default team slug for project creation. Defaults to org slug. */
|
|
11
|
+
team?: string;
|
|
12
|
+
}
|
|
13
|
+
declare class SentryObservability implements Observability {
|
|
14
|
+
private readonly client;
|
|
15
|
+
private readonly opts;
|
|
16
|
+
readonly key: "observability";
|
|
17
|
+
readonly providerName = "sentry";
|
|
18
|
+
constructor(client: SentryClient, opts: SentryObservabilityOptions);
|
|
19
|
+
describe(): Promise<{
|
|
20
|
+
provider: string;
|
|
21
|
+
envKeys: string[];
|
|
22
|
+
}>;
|
|
23
|
+
whoami(): Promise<{
|
|
24
|
+
org: string;
|
|
25
|
+
}>;
|
|
26
|
+
ensureProject(input: {
|
|
27
|
+
name: string;
|
|
28
|
+
platform?: string;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
dsn: string;
|
|
31
|
+
alreadyExists: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/verify-token.d.ts
|
|
36
|
+
interface VerifyTokenSuccess {
|
|
37
|
+
ok: true;
|
|
38
|
+
subject: string;
|
|
39
|
+
}
|
|
40
|
+
interface VerifyTokenFailure {
|
|
41
|
+
ok: false;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
type VerifyTokenResult = VerifyTokenSuccess | VerifyTokenFailure;
|
|
45
|
+
interface VerifyTokenOptions {
|
|
46
|
+
baseUrl?: string;
|
|
47
|
+
fetch?: typeof fetch;
|
|
48
|
+
}
|
|
49
|
+
declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult>;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/index.d.ts
|
|
52
|
+
interface SentryPluginOptions extends ResolveTokenInput, SentryObservabilityOptions {
|
|
53
|
+
/** Override base URL for tests. */
|
|
54
|
+
baseUrl?: string;
|
|
55
|
+
fetch?: typeof fetch;
|
|
56
|
+
}
|
|
57
|
+
interface PluginContext {
|
|
58
|
+
options: SentryPluginOptions;
|
|
59
|
+
client: SentryClient;
|
|
60
|
+
}
|
|
61
|
+
declare function createContext(options: SentryPluginOptions): PluginContext;
|
|
62
|
+
declare function observability(ctx: PluginContext): Observability;
|
|
63
|
+
declare function createPlugin(options: SentryPluginOptions): {
|
|
64
|
+
name: string;
|
|
65
|
+
capabilities: {
|
|
66
|
+
observability: () => Observability;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
declare const AUTH_HINT: string;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { AUTH_HINT, AuthError, PluginContext, type ResolveTokenInput, type SentryClient, type SentryClientOptions, SentryObservability, type SentryObservabilityOptions, SentryPluginOptions, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, createSentryClient, observability, resolveToken, verifyToken };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AuthError, ProviderApiError, createResolveToken } from "@theholocron/cli";
|
|
2
|
+
import { createSentryClient } from "@theholocron/sentry-client";
|
|
3
|
+
//#region src/auth.ts
|
|
4
|
+
const resolveToken = createResolveToken({
|
|
5
|
+
envName: "HOLOCRON_SENTRY_TOKEN",
|
|
6
|
+
vendorEnvName: "SENTRY_AUTH_TOKEN",
|
|
7
|
+
keyringService: "sentry",
|
|
8
|
+
errorMessage: "no Sentry auth token found. Pass --token <TOKEN>, set HOLOCRON_SENTRY_TOKEN / SENTRY_AUTH_TOKEN, or run: holocron auth set sentry <TOKEN>"
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/capabilities/observability.ts
|
|
12
|
+
var SentryObservability = class {
|
|
13
|
+
client;
|
|
14
|
+
opts;
|
|
15
|
+
key = "observability";
|
|
16
|
+
providerName = "sentry";
|
|
17
|
+
constructor(client, opts) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
if (!opts.org) throw new Error("SentryObservability requires `org` in options");
|
|
21
|
+
}
|
|
22
|
+
async describe() {
|
|
23
|
+
return {
|
|
24
|
+
provider: "sentry",
|
|
25
|
+
envKeys: ["SENTRY_DSN", "NEXT_PUBLIC_SENTRY_DSN"]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async whoami() {
|
|
29
|
+
return { org: (await this.client.auth.getOrg(this.opts.org)).slug };
|
|
30
|
+
}
|
|
31
|
+
async ensureProject(input) {
|
|
32
|
+
const slug = toSlug(input.name);
|
|
33
|
+
try {
|
|
34
|
+
const existing = await this.client.projects.get(this.opts.org, slug);
|
|
35
|
+
const dsn = (await this.client.projects.keys(this.opts.org, existing.slug))[0]?.dsn.public;
|
|
36
|
+
/* c8 ignore next */
|
|
37
|
+
if (!dsn) throw new ProviderApiError(`Sentry project ${slug} has no keys`, 404, void 0);
|
|
38
|
+
return {
|
|
39
|
+
dsn,
|
|
40
|
+
alreadyExists: true
|
|
41
|
+
};
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (!(err instanceof ProviderApiError) || err.status !== 404) throw err;
|
|
44
|
+
}
|
|
45
|
+
const team = this.opts.team ?? this.opts.org;
|
|
46
|
+
const project = await this.client.projects.create(this.opts.org, team, {
|
|
47
|
+
name: input.name,
|
|
48
|
+
platform: input.platform ?? "node"
|
|
49
|
+
});
|
|
50
|
+
const dsn = (await this.client.projects.keys(this.opts.org, project.slug))[0]?.dsn.public;
|
|
51
|
+
/* c8 ignore next */
|
|
52
|
+
if (!dsn) throw new ProviderApiError(`Sentry project ${project.slug} has no keys after creation`, 500, void 0);
|
|
53
|
+
return {
|
|
54
|
+
dsn,
|
|
55
|
+
alreadyExists: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function toSlug(name) {
|
|
60
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/verify-token.ts
|
|
64
|
+
async function verifyToken(token, opts = {}) {
|
|
65
|
+
const client = createSentryClient({
|
|
66
|
+
token,
|
|
67
|
+
baseUrl: opts.baseUrl,
|
|
68
|
+
fetch: opts.fetch
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
subject: `org: ${(await client.auth.organizations())[0]?.slug ?? "unknown"}`
|
|
74
|
+
};
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
message: err instanceof Error ? err.message : String(err)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/index.ts
|
|
84
|
+
function createContext(options) {
|
|
85
|
+
if (!options.org) throw new Error("@theholocron/holocron-plugin-sentry requires `org` in options");
|
|
86
|
+
return {
|
|
87
|
+
options,
|
|
88
|
+
client: createSentryClient({
|
|
89
|
+
token: resolveToken(options),
|
|
90
|
+
baseUrl: options.baseUrl,
|
|
91
|
+
fetch: options.fetch
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function observability(ctx) {
|
|
96
|
+
return new SentryObservability(ctx.client, {
|
|
97
|
+
org: ctx.options.org,
|
|
98
|
+
team: ctx.options.team
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function createPlugin(options) {
|
|
102
|
+
const ctx = createContext(options);
|
|
103
|
+
return {
|
|
104
|
+
name: "@theholocron/holocron-plugin-sentry",
|
|
105
|
+
capabilities: { observability: () => observability(ctx) }
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const AUTH_HINT = "generate an auth token at https://sentry.io/settings/account/api/auth-tokens/ with project:read, project:write, and org:read scopes, then run: holocron auth set sentry <TOKEN>";
|
|
109
|
+
//#endregion
|
|
110
|
+
export { AUTH_HINT, AuthError, SentryObservability, createContext, createPlugin, createSentryClient, observability, resolveToken, verifyToken };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theholocron/holocron-plugin-sentry",
|
|
3
|
+
"version": "3.24.3",
|
|
4
|
+
"description": "Holocron plugin for Sentry. Implements the observability capability against Sentry's management API — project provisioning and DSN retrieval.",
|
|
5
|
+
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-sentry#readme",
|
|
6
|
+
"bugs": "https://github.com/theholocron/holocron/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/theholocron/holocron.git",
|
|
10
|
+
"directory": "packages/holocron-plugin-sentry"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Newton Koumantzelis",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.mjs",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@theholocron/sentry-client": "^1.7.1",
|
|
25
|
+
"@theholocron/cli": "3.24.4"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"@theholocron/sentry-client": {
|
|
29
|
+
"optional": false
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@theholocron/sentry-client": "^1.7.1",
|
|
34
|
+
"@theholocron/eslint-config": "^7.19.1",
|
|
35
|
+
"@theholocron/tsconfig": "^7.19.1",
|
|
36
|
+
"@theholocron/tsdown-config": "^7.19.1",
|
|
37
|
+
"@theholocron/vitest-config": "^7.19.1",
|
|
38
|
+
"@types/node": "^26",
|
|
39
|
+
"chalk": "^6.0.0",
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
41
|
+
"@vitest/eslint-plugin": "^1.6.27",
|
|
42
|
+
"eslint": "^10.8.1",
|
|
43
|
+
"eslint-plugin-n": "^18.3.0",
|
|
44
|
+
"globals": "^17.9.0",
|
|
45
|
+
"tsdown": "^0.22.14",
|
|
46
|
+
"tsx": "4.22.4",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^4.1.10",
|
|
49
|
+
"@theholocron/cli": "3.24.4"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md"
|
|
57
|
+
],
|
|
58
|
+
"types": "./dist/index.d.mts",
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsdown",
|
|
61
|
+
"lint": "eslint .",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"test:watch": "vitest",
|
|
65
|
+
"test:coverage": "vitest run --coverage",
|
|
66
|
+
"validate": "tsx scripts/validate.mjs"
|
|
67
|
+
}
|
|
68
|
+
}
|