@sentry/junior 0.2.0 → 0.4.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.
@@ -1,30 +1,34 @@
1
1
  import {
2
2
  POST as POST2
3
- } from "../chunk-JDBWDYGR.js";
3
+ } from "../chunk-TEQ3UIS7.js";
4
4
  import {
5
5
  POST
6
- } from "../chunk-TQSDLJVE.js";
6
+ } from "../chunk-DGKNXMK4.js";
7
7
  import {
8
8
  escapeXml,
9
+ formatProviderLabel,
9
10
  generateAssistantReply,
10
- getOAuthProviderConfig,
11
11
  getSlackClient,
12
12
  getUserTokenStore,
13
13
  publishAppHomeView,
14
14
  resolveBaseUrl,
15
15
  truncateStatusText
16
- } from "../chunk-RXNMJQPY.js";
16
+ } from "../chunk-RFUE5VBK.js";
17
17
  import {
18
18
  GET
19
19
  } from "../chunk-4RBEYCOG.js";
20
20
  import {
21
21
  botConfig,
22
- getStateAdapter
23
- } from "../chunk-QHDDCUTN.js";
22
+ buildOAuthTokenRequest,
23
+ getPluginOAuthConfig,
24
+ getStateAdapter,
25
+ parseOAuthTokenResponse
26
+ } from "../chunk-OZFXD5IG.js";
24
27
  import {
25
28
  logException,
26
29
  logInfo
27
30
  } from "../chunk-PY4AI2GZ.js";
31
+ import "../chunk-Z5E25LRN.js";
28
32
 
29
33
  // src/handlers/oauth-callback.ts
30
34
  import { after } from "next/server";
@@ -49,13 +53,21 @@ function htmlErrorResponse(title, message, status) {
49
53
  }
50
54
  async function postSlackMessage(channelId, threadTs, text) {
51
55
  try {
52
- await getSlackClient().chat.postMessage({ channel: channelId, thread_ts: threadTs, text });
56
+ await getSlackClient().chat.postMessage({
57
+ channel: channelId,
58
+ thread_ts: threadTs,
59
+ text
60
+ });
53
61
  } catch {
54
62
  }
55
63
  }
56
64
  async function setAssistantStatus(channelId, threadTs, status) {
57
65
  try {
58
- await getSlackClient().assistant.threads.setStatus({ channel_id: channelId, thread_ts: threadTs, status });
66
+ await getSlackClient().assistant.threads.setStatus({
67
+ channel_id: channelId,
68
+ thread_ts: threadTs,
69
+ status
70
+ });
59
71
  } catch {
60
72
  }
61
73
  }
@@ -94,9 +106,12 @@ function createDebouncedStatusPoster(channelId, threadTs) {
94
106
  }
95
107
  pendingStatus = truncated;
96
108
  if (!pendingTimer) {
97
- pendingTimer = setTimeout(() => {
98
- void flush();
99
- }, Math.max(1, STATUS_DEBOUNCE_MS - elapsed));
109
+ pendingTimer = setTimeout(
110
+ () => {
111
+ void flush();
112
+ },
113
+ Math.max(1, STATUS_DEBOUNCE_MS - elapsed)
114
+ );
100
115
  }
101
116
  };
102
117
  post.stop = () => {
@@ -137,13 +152,16 @@ function createReadOnlyConfigService(values) {
137
152
  }
138
153
  async function resumePendingMessage(stored) {
139
154
  if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
140
- const providerLabel = stored.provider.charAt(0).toUpperCase() + stored.provider.slice(1);
155
+ const providerLabel = formatProviderLabel(stored.provider);
141
156
  await postSlackMessage(
142
157
  stored.channelId,
143
158
  stored.threadTs,
144
159
  `Your ${providerLabel} account is now connected. Processing your request...`
145
160
  );
146
- const postStatus = createDebouncedStatusPoster(stored.channelId, stored.threadTs);
161
+ const postStatus = createDebouncedStatusPoster(
162
+ stored.channelId,
163
+ stored.threadTs
164
+ );
147
165
  await setAssistantStatus(stored.channelId, stored.threadTs, "Thinking...");
148
166
  try {
149
167
  const reply = await generateAssistantReply(stored.pendingMessage, {
@@ -190,11 +208,15 @@ async function resumePendingMessage(stored) {
190
208
  }
191
209
  async function GET2(request, context) {
192
210
  const { provider } = await context.params;
193
- const providerConfig = getOAuthProviderConfig(provider);
211
+ const providerConfig = getPluginOAuthConfig(provider);
194
212
  if (!providerConfig) {
195
- return htmlErrorResponse("Unknown provider", "The OAuth provider in this link is not recognized.", 404);
213
+ return htmlErrorResponse(
214
+ "Unknown provider",
215
+ "The OAuth provider in this link is not recognized.",
216
+ 404
217
+ );
196
218
  }
197
- const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1);
219
+ const providerLabel = formatProviderLabel(provider);
198
220
  const url = new URL(request.url);
199
221
  const errorParam = url.searchParams.get("error");
200
222
  const code = url.searchParams.get("code");
@@ -218,7 +240,11 @@ async function GET2(request, context) {
218
240
  );
219
241
  }
220
242
  if (!code || !state) {
221
- return htmlErrorResponse("Invalid request", "This authorization link is missing required parameters.", 400);
243
+ return htmlErrorResponse(
244
+ "Invalid request",
245
+ "This authorization link is missing required parameters.",
246
+ 400
247
+ );
222
248
  }
223
249
  const stateAdapter = getStateAdapter();
224
250
  const stateKey = `oauth-state:${state}`;
@@ -231,51 +257,76 @@ async function GET2(request, context) {
231
257
  );
232
258
  }
233
259
  if (stored.provider !== provider) {
234
- return htmlErrorResponse("Provider mismatch", "This authorization link does not match the expected provider.", 400);
260
+ return htmlErrorResponse(
261
+ "Provider mismatch",
262
+ "This authorization link does not match the expected provider.",
263
+ 400
264
+ );
235
265
  }
236
266
  await stateAdapter.delete(stateKey);
237
267
  const clientId = process.env[providerConfig.clientIdEnv]?.trim();
238
268
  const clientSecret = process.env[providerConfig.clientSecretEnv]?.trim();
239
269
  if (!clientId || !clientSecret) {
240
- return htmlErrorResponse("Configuration error", "OAuth client credentials are not configured on the server.", 500);
270
+ return htmlErrorResponse(
271
+ "Configuration error",
272
+ "OAuth client credentials are not configured on the server.",
273
+ 500
274
+ );
241
275
  }
242
276
  const baseUrl = resolveBaseUrl();
243
277
  if (!baseUrl) {
244
- return htmlErrorResponse("Configuration error", "The server cannot determine its base URL.", 500);
278
+ return htmlErrorResponse(
279
+ "Configuration error",
280
+ "The server cannot determine its base URL.",
281
+ 500
282
+ );
245
283
  }
246
284
  const redirectUri = `${baseUrl}${providerConfig.callbackPath}`;
247
285
  let tokenResponse;
248
286
  try {
249
- tokenResponse = await fetch(providerConfig.tokenEndpoint, {
250
- method: "POST",
251
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
252
- body: new URLSearchParams({
287
+ const tokenRequest = buildOAuthTokenRequest({
288
+ clientId,
289
+ clientSecret,
290
+ payload: {
253
291
  grant_type: "authorization_code",
254
292
  code,
255
- client_id: clientId,
256
- client_secret: clientSecret,
257
293
  redirect_uri: redirectUri
258
- })
294
+ },
295
+ tokenAuthMethod: providerConfig.tokenAuthMethod,
296
+ tokenExtraHeaders: providerConfig.tokenExtraHeaders
297
+ });
298
+ tokenResponse = await fetch(providerConfig.tokenEndpoint, {
299
+ method: "POST",
300
+ headers: tokenRequest.headers,
301
+ body: tokenRequest.body
259
302
  });
260
303
  } catch {
261
- return htmlErrorResponse("Connection failed", "Failed to exchange the authorization code. Please try again.", 500);
304
+ return htmlErrorResponse(
305
+ "Connection failed",
306
+ "Failed to exchange the authorization code. Please try again.",
307
+ 500
308
+ );
262
309
  }
263
310
  if (!tokenResponse.ok) {
264
- return htmlErrorResponse("Connection failed", "The token exchange with the provider failed. Please try again.", 500);
311
+ return htmlErrorResponse(
312
+ "Connection failed",
313
+ "The token exchange with the provider failed. Please try again.",
314
+ 500
315
+ );
265
316
  }
266
317
  const tokenData = await tokenResponse.json();
267
- if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") {
268
- return htmlErrorResponse("Connection failed", "The provider returned an incomplete token response. Please try again.", 500);
318
+ let parsedTokenResponse;
319
+ try {
320
+ parsedTokenResponse = parseOAuthTokenResponse(tokenData);
321
+ } catch {
322
+ return htmlErrorResponse(
323
+ "Connection failed",
324
+ "The provider returned an incomplete token response. Please try again.",
325
+ 500
326
+ );
269
327
  }
270
- const accessToken = tokenData.access_token;
271
- const refreshToken = tokenData.refresh_token;
272
- const expiresAt = Date.now() + tokenData.expires_in * 1e3;
273
328
  const userTokenStore = getUserTokenStore();
274
- await userTokenStore.set(stored.userId, provider, {
275
- accessToken,
276
- refreshToken,
277
- expiresAt
278
- });
329
+ await userTokenStore.set(stored.userId, provider, parsedTokenResponse);
279
330
  after(async () => {
280
331
  try {
281
332
  await publishAppHomeView(getSlackClient(), stored.userId, userTokenStore);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  POST
3
- } from "../chunk-JDBWDYGR.js";
3
+ } from "../chunk-TEQ3UIS7.js";
4
4
  import "../chunk-PY4AI2GZ.js";
5
5
  export {
6
6
  POST
@@ -7,7 +7,7 @@ interface JuniorConfigOptions {
7
7
  dataDir?: string;
8
8
  skillsDir?: string;
9
9
  pluginsDir?: string;
10
- sentry?: boolean;
10
+ pluginPackages?: string[];
11
11
  }
12
12
  type NextConfigFactory = (phase: string, ctx: {
13
13
  defaultConfig: NextConfig;
@@ -17,6 +17,6 @@ type NextConfigFactory = (phase: string, ctx: {
17
17
  *
18
18
  * Supports both object and function-style Next config exports.
19
19
  */
20
- declare function withJunior(nextConfig?: NextConfig | NextConfigFactory, options?: JuniorConfigOptions): NextConfig | NextConfigFactory;
20
+ declare function withJunior(options?: JuniorConfigOptions, nextConfig?: NextConfig | NextConfigFactory): NextConfig | NextConfigFactory;
21
21
 
22
22
  export { type JuniorConfigOptions, withJunior };
@@ -1,102 +1,107 @@
1
+ import {
2
+ discoverInstalledPluginPackageContent,
3
+ discoverNodeModulesDirs,
4
+ isDirectory
5
+ } from "./chunk-Z5E25LRN.js";
6
+
1
7
  // src/next-config.ts
2
8
  import { createRequire } from "module";
3
- import { readFileSync, statSync } from "fs";
4
9
  import path from "path";
5
10
  var require2 = createRequire(import.meta.url);
6
- function isDirectory(targetPath) {
7
- try {
8
- return statSync(targetPath).isDirectory();
9
- } catch {
10
- return false;
11
- }
11
+ function unique(values) {
12
+ return [...new Set(values)];
12
13
  }
13
- function isFile(targetPath) {
14
- try {
15
- return statSync(targetPath).isFile();
16
- } catch {
17
- return false;
18
- }
14
+ function sentryConfigured() {
15
+ return Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN || process.env.SENTRY_DSN);
19
16
  }
20
- function discoverInstalledPluginPackageTracingIncludes(cwd = process.cwd()) {
21
- const rootPackageJsonPath = path.join(cwd, "package.json");
22
- let rootPackageJson;
23
- try {
24
- rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, "utf8"));
25
- } catch {
26
- return [];
27
- }
28
- const dependencies = [
29
- ...Object.keys(rootPackageJson.dependencies ?? {}),
30
- ...Object.keys(rootPackageJson.optionalDependencies ?? {})
31
- ];
32
- const tracingIncludes = [];
33
- for (const dependency of dependencies) {
34
- const packageDir = path.join(cwd, "node_modules", ...dependency.split("/"));
35
- if (!isDirectory(packageDir)) {
36
- continue;
37
- }
38
- const base = `./node_modules/${dependency}`;
39
- if (isFile(path.join(packageDir, "plugin.yaml"))) {
40
- tracingIncludes.push(`${base}/plugin.yaml`);
41
- }
42
- if (isDirectory(path.join(packageDir, "plugins"))) {
43
- tracingIncludes.push(`${base}/plugins/**/*`);
44
- }
45
- if (isDirectory(path.join(packageDir, "skills"))) {
46
- tracingIncludes.push(`${base}/skills/**/*`);
47
- }
48
- }
49
- return [...new Set(tracingIncludes)].sort((left, right) => left.localeCompare(right));
17
+ function isPackageInstalled(cwd, packageName) {
18
+ const nodeModulesDirs = discoverNodeModulesDirs(cwd);
19
+ return nodeModulesDirs.some(
20
+ (nodeModulesDir) => isDirectory(path.join(nodeModulesDir, ...packageName.split("/")))
21
+ );
50
22
  }
51
23
  function applyJuniorConfig(nextConfig, options) {
24
+ const existingEnv = nextConfig?.env ?? {};
52
25
  const dataDir = options?.dataDir ?? "./app/data";
53
26
  const skillsDir = options?.skillsDir ?? "./app/skills";
54
27
  const pluginsDir = options?.pluginsDir ?? "./app/plugins";
28
+ const configuredPluginPackages = unique(options?.pluginPackages ?? []);
29
+ const discoveredPlugins = discoverInstalledPluginPackageContent(
30
+ process.cwd(),
31
+ { packageNames: configuredPluginPackages }
32
+ );
33
+ const unresolvedConfiguredPackages = configuredPluginPackages.filter(
34
+ (packageName) => !discoveredPlugins.packageNames.includes(packageName)
35
+ );
36
+ const invalidPluginPackages = unresolvedConfiguredPackages.filter(
37
+ (packageName) => isPackageInstalled(process.cwd(), packageName)
38
+ );
39
+ const missingPluginPackages = unresolvedConfiguredPackages.filter(
40
+ (packageName) => !invalidPluginPackages.includes(packageName)
41
+ );
42
+ if (invalidPluginPackages.length > 0) {
43
+ throw new Error(
44
+ `withJunior pluginPackages contains installed packages that are not valid Junior plugins: ${invalidPluginPackages.join(", ")}`
45
+ );
46
+ }
47
+ if (missingPluginPackages.length > 0) {
48
+ throw new Error(
49
+ `withJunior pluginPackages contains unresolved packages: ${missingPluginPackages.join(", ")}`
50
+ );
51
+ }
55
52
  const defaultDataTracingIncludes = options?.dataDir ? [`${dataDir}/**/*`] : ["./app/SOUL.md", "./app/ABOUT.md"];
56
- const pluginPackageTracingIncludes = discoverInstalledPluginPackageTracingIncludes();
57
- const tracingIncludes = Array.from(/* @__PURE__ */ new Set([
58
- ...defaultDataTracingIncludes,
59
- `${skillsDir}/**/*`,
60
- `${pluginsDir}/**/*`,
61
- ...pluginPackageTracingIncludes
62
- ]));
53
+ const pluginPackageTracingIncludes = discoveredPlugins.tracingIncludes;
54
+ const tracingIncludes = Array.from(
55
+ /* @__PURE__ */ new Set([
56
+ ...defaultDataTracingIncludes,
57
+ `${skillsDir}/**/*`,
58
+ `${pluginsDir}/**/*`,
59
+ ...pluginPackageTracingIncludes
60
+ ])
61
+ );
63
62
  const existingGlobalTracingIncludes = nextConfig?.outputFileTracingIncludes?.["/*"] ?? [];
64
- const mergedGlobalTracingIncludes = Array.from(/* @__PURE__ */ new Set([
65
- ...existingGlobalTracingIncludes,
66
- ...tracingIncludes
67
- ]));
63
+ const mergedGlobalTracingIncludes = Array.from(
64
+ /* @__PURE__ */ new Set([...existingGlobalTracingIncludes, ...tracingIncludes])
65
+ );
68
66
  const config = {
69
67
  ...nextConfig,
70
- serverExternalPackages: Array.from(/* @__PURE__ */ new Set([
71
- ...nextConfig?.serverExternalPackages ?? [],
72
- "@vercel/sandbox",
73
- "bash-tool",
74
- "just-bash",
75
- "@mariozechner/pi-agent-core",
76
- "@mariozechner/pi-ai",
77
- "@chat-adapter/slack",
78
- "@slack/web-api"
79
- ])),
68
+ serverExternalPackages: Array.from(
69
+ /* @__PURE__ */ new Set([
70
+ ...nextConfig?.serverExternalPackages ?? [],
71
+ "@vercel/queue",
72
+ "@vercel/sandbox",
73
+ "bash-tool",
74
+ "just-bash",
75
+ "@mariozechner/pi-agent-core",
76
+ "@mariozechner/pi-ai",
77
+ "@chat-adapter/slack",
78
+ "@slack/web-api"
79
+ ])
80
+ ),
80
81
  outputFileTracingIncludes: {
81
82
  ...nextConfig?.outputFileTracingIncludes,
82
83
  "/*": mergedGlobalTracingIncludes
84
+ },
85
+ env: {
86
+ ...existingEnv,
87
+ JUNIOR_PLUGIN_PACKAGES: JSON.stringify(configuredPluginPackages)
83
88
  }
84
89
  };
85
- if (options?.sentry) {
86
- const { withSentryConfig } = require2("@sentry/nextjs");
87
- return withSentryConfig(config, {
88
- org: process.env.SENTRY_ORG,
89
- project: process.env.SENTRY_PROJECT,
90
- authToken: process.env.SENTRY_AUTH_TOKEN,
91
- silent: !process.env.CI,
92
- sourcemaps: {
93
- disable: false
94
- }
95
- });
90
+ if (!sentryConfigured()) {
91
+ return config;
96
92
  }
97
- return config;
93
+ const { withSentryConfig } = require2("@sentry/nextjs");
94
+ return withSentryConfig(config, {
95
+ org: process.env.SENTRY_ORG,
96
+ project: process.env.SENTRY_PROJECT,
97
+ authToken: process.env.SENTRY_AUTH_TOKEN,
98
+ silent: !process.env.CI,
99
+ sourcemaps: {
100
+ disable: false
101
+ }
102
+ });
98
103
  }
99
- function withJunior(nextConfig, options) {
104
+ function withJunior(options, nextConfig) {
100
105
  if (typeof nextConfig === "function") {
101
106
  return async (phase, ctx) => {
102
107
  const resolved = await nextConfig(phase, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"