@sentry/junior 0.4.1 → 0.6.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.
@@ -0,0 +1,303 @@
1
+ import {
2
+ parseSkillFile
3
+ } from "../chunk-KT5HARSN.js";
4
+ import {
5
+ parsePluginManifest
6
+ } from "../chunk-VW26MOSO.js";
7
+
8
+ // src/cli/check.ts
9
+ import fs from "fs/promises";
10
+ import path from "path";
11
+ var DEFAULT_IO = {
12
+ info: console.log,
13
+ warn: console.warn,
14
+ error: console.error
15
+ };
16
+ var ANSI = {
17
+ reset: "\x1B[0m",
18
+ bold: "\x1B[1m",
19
+ dim: "\x1B[2m",
20
+ green: "\x1B[32m",
21
+ yellow: "\x1B[33m",
22
+ red: "\x1B[31m",
23
+ cyan: "\x1B[36m"
24
+ };
25
+ function supportsColor(stream) {
26
+ if (process.env.NO_COLOR !== void 0) {
27
+ return false;
28
+ }
29
+ if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") {
30
+ return true;
31
+ }
32
+ return Boolean(stream?.isTTY);
33
+ }
34
+ var COLOR_ENABLED = supportsColor(process.stdout) || supportsColor(process.stderr);
35
+ function color(text, ...codes) {
36
+ if (!COLOR_ENABLED || codes.length === 0) {
37
+ return text;
38
+ }
39
+ return `${codes.join("")}${text}${ANSI.reset}`;
40
+ }
41
+ function contentRoot(rootDir, subdir) {
42
+ return path.resolve(rootDir, "app", subdir);
43
+ }
44
+ async function pathIsDirectory(targetPath) {
45
+ try {
46
+ return (await fs.stat(targetPath)).isDirectory();
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ async function pathIsFile(targetPath) {
52
+ try {
53
+ return (await fs.stat(targetPath)).isFile();
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ async function validateSkillDirectory(skillDir, duplicateNames) {
59
+ const skillFile = path.join(skillDir, "SKILL.md");
60
+ const errors = [];
61
+ const warnings = [];
62
+ let raw;
63
+ try {
64
+ raw = await fs.readFile(skillFile, "utf8");
65
+ } catch {
66
+ errors.push(`${skillFile}: missing SKILL.md`);
67
+ return { skillFile, errors, warnings };
68
+ }
69
+ const parsed = parseSkillFile(raw, path.basename(skillDir));
70
+ if (!parsed.ok) {
71
+ errors.push(`${skillFile}: ${parsed.error}`);
72
+ return { skillFile, errors, warnings };
73
+ }
74
+ const name = parsed.skill.name;
75
+ const firstSeen = duplicateNames.get(name);
76
+ if (firstSeen) {
77
+ errors.push(
78
+ `${skillFile}: duplicate skill name "${name}" (already defined in ${firstSeen})`
79
+ );
80
+ } else {
81
+ duplicateNames.set(name, skillFile);
82
+ }
83
+ if (!parsed.skill.body) {
84
+ warnings.push(`${skillFile}: no skill instructions after frontmatter`);
85
+ }
86
+ return { skillFile, skill: parsed.skill, errors, warnings };
87
+ }
88
+ async function validatePluginDirectory(pluginDir, duplicatePluginNames) {
89
+ const manifestPath = path.join(pluginDir, "plugin.yaml");
90
+ try {
91
+ const raw = await fs.readFile(manifestPath, "utf8");
92
+ const manifest = parsePluginManifest(raw, pluginDir);
93
+ const firstSeen = duplicatePluginNames.get(manifest.name);
94
+ if (firstSeen) {
95
+ return {
96
+ manifestPath,
97
+ manifest,
98
+ errors: [
99
+ `${manifestPath}: duplicate plugin name "${manifest.name}" (already defined in ${firstSeen})`
100
+ ]
101
+ };
102
+ }
103
+ duplicatePluginNames.set(manifest.name, manifestPath);
104
+ return { manifestPath, manifest, errors: [] };
105
+ } catch (error) {
106
+ return {
107
+ manifestPath,
108
+ errors: [
109
+ `${manifestPath}: ${error instanceof Error ? error.message : String(error)}`
110
+ ]
111
+ };
112
+ }
113
+ }
114
+ async function collectPluginDirectories(rootDir) {
115
+ const pluginDirs = [];
116
+ let entries;
117
+ try {
118
+ entries = await fs.readdir(contentRoot(rootDir, "plugins"), {
119
+ withFileTypes: true
120
+ });
121
+ } catch {
122
+ return pluginDirs;
123
+ }
124
+ for (const entry of entries) {
125
+ if (!entry.isDirectory()) {
126
+ continue;
127
+ }
128
+ const pluginDir = path.join(contentRoot(rootDir, "plugins"), entry.name);
129
+ if (await pathIsFile(path.join(pluginDir, "plugin.yaml"))) {
130
+ pluginDirs.push(pluginDir);
131
+ }
132
+ }
133
+ return pluginDirs.sort((left, right) => left.localeCompare(right));
134
+ }
135
+ async function collectSkillDirectories(root) {
136
+ const skillDirs = [];
137
+ let entries;
138
+ try {
139
+ entries = await fs.readdir(root, { withFileTypes: true });
140
+ } catch {
141
+ return skillDirs;
142
+ }
143
+ for (const entry of entries) {
144
+ if (entry.isDirectory()) {
145
+ skillDirs.push(path.join(root, entry.name));
146
+ }
147
+ }
148
+ return skillDirs.sort((left, right) => left.localeCompare(right));
149
+ }
150
+ function formatDisplayPath(rootDir, targetPath) {
151
+ const relativePath = path.relative(rootDir, targetPath);
152
+ const displayPath = relativePath.length > 0 && !relativePath.startsWith("..") && !path.isAbsolute(relativePath) ? relativePath : targetPath;
153
+ return displayPath.split(path.sep).join("/");
154
+ }
155
+ function formatStatus(errorCount, warningCount) {
156
+ if (errorCount > 0) {
157
+ return "error";
158
+ }
159
+ if (warningCount > 0) {
160
+ return "warn";
161
+ }
162
+ return "ok";
163
+ }
164
+ function statusIcon(status) {
165
+ switch (status) {
166
+ case "ok":
167
+ return color("\u2713", ANSI.green, ANSI.bold);
168
+ case "warn":
169
+ return color("\u26A0", ANSI.yellow, ANSI.bold);
170
+ case "error":
171
+ return color("\u2716", ANSI.red, ANSI.bold);
172
+ }
173
+ }
174
+ function formatHeading(status, label) {
175
+ const styledLabel = status === "ok" ? color(label, ANSI.bold) : status === "warn" ? color(label, ANSI.bold, ANSI.yellow) : color(label, ANSI.bold, ANSI.red);
176
+ return `${statusIcon(status)} ${styledLabel}`;
177
+ }
178
+ function reportSkillResult(result, io, indent, isLast) {
179
+ const status = formatStatus(result.errors.length, result.warnings.length);
180
+ const skillName = result.skill?.name ?? path.basename(path.dirname(result.skillFile));
181
+ const branch = isLast ? "\u2514\u2500" : "\u251C\u2500";
182
+ io.info(`${indent}${branch} ${formatHeading(status, `skill ${skillName}`)}`);
183
+ }
184
+ function reportPluginResult(result, io) {
185
+ const skillErrorCount = result.skillResults.reduce(
186
+ (count, skillResult) => count + skillResult.errors.length,
187
+ 0
188
+ );
189
+ const skillWarningCount = result.skillResults.reduce(
190
+ (count, skillResult) => count + skillResult.warnings.length,
191
+ 0
192
+ );
193
+ const status = formatStatus(
194
+ result.errors.length + skillErrorCount,
195
+ skillWarningCount
196
+ );
197
+ const pluginName = result.manifest?.name ?? path.basename(result.pluginDir);
198
+ io.info(formatHeading(status, `plugin ${pluginName}`));
199
+ for (const [index, skillResult] of result.skillResults.entries()) {
200
+ reportSkillResult(
201
+ skillResult,
202
+ io,
203
+ " ",
204
+ index === result.skillResults.length - 1
205
+ );
206
+ }
207
+ }
208
+ function reportAppSkills(skillResults, io) {
209
+ const errorCount = skillResults.reduce(
210
+ (count, skillResult) => count + skillResult.errors.length,
211
+ 0
212
+ );
213
+ const warningCount = skillResults.reduce(
214
+ (count, skillResult) => count + skillResult.warnings.length,
215
+ 0
216
+ );
217
+ const status = formatStatus(errorCount, warningCount);
218
+ io.info(formatHeading(status, "app skills"));
219
+ for (const [index, skillResult] of skillResults.entries()) {
220
+ reportSkillResult(skillResult, io, " ", index === skillResults.length - 1);
221
+ }
222
+ }
223
+ async function runCheck(rootDir = process.cwd(), io = DEFAULT_IO) {
224
+ const resolvedRoot = path.resolve(rootDir);
225
+ if (!await pathIsDirectory(resolvedRoot)) {
226
+ throw new Error(
227
+ `validation root does not exist or is not a directory: ${resolvedRoot}`
228
+ );
229
+ }
230
+ const pluginDirs = await collectPluginDirectories(resolvedRoot);
231
+ const appSkillsRoot = contentRoot(resolvedRoot, "skills");
232
+ const appSkillDirs = await collectSkillDirectories(appSkillsRoot);
233
+ const pluginSkillDirs = /* @__PURE__ */ new Map();
234
+ for (const pluginDir of pluginDirs) {
235
+ pluginSkillDirs.set(
236
+ pluginDir,
237
+ await collectSkillDirectories(path.join(pluginDir, "skills"))
238
+ );
239
+ }
240
+ const skillDirs = [
241
+ ...appSkillDirs,
242
+ ...pluginDirs.flatMap((pluginDir) => pluginSkillDirs.get(pluginDir) ?? [])
243
+ ].sort((left, right) => left.localeCompare(right));
244
+ const duplicateSkillNames = /* @__PURE__ */ new Map();
245
+ const duplicatePluginNames = /* @__PURE__ */ new Map();
246
+ const warnings = [];
247
+ const errors = [];
248
+ const pluginResults = [];
249
+ const skillResultsByDir = /* @__PURE__ */ new Map();
250
+ for (const pluginDir of pluginDirs) {
251
+ const result = await validatePluginDirectory(
252
+ pluginDir,
253
+ duplicatePluginNames
254
+ );
255
+ pluginResults.push({
256
+ pluginDir,
257
+ manifestPath: result.manifestPath,
258
+ ...result.manifest ? { manifest: result.manifest } : {},
259
+ errors: result.errors,
260
+ skillResults: []
261
+ });
262
+ errors.push(...result.errors);
263
+ }
264
+ for (const skillDir of skillDirs) {
265
+ const result = await validateSkillDirectory(skillDir, duplicateSkillNames);
266
+ skillResultsByDir.set(skillDir, result);
267
+ warnings.push(...result.warnings);
268
+ errors.push(...result.errors);
269
+ }
270
+ for (const pluginResult of pluginResults) {
271
+ pluginResult.skillResults = (pluginSkillDirs.get(pluginResult.pluginDir) ?? []).map((skillDir) => skillResultsByDir.get(skillDir)).filter((result) => Boolean(result));
272
+ }
273
+ const appSkillResults = appSkillDirs.map((skillDir) => skillResultsByDir.get(skillDir)).filter((result) => Boolean(result));
274
+ io.info(
275
+ `${color("Checking", ANSI.bold, ANSI.cyan)} ${color(
276
+ formatDisplayPath(resolvedRoot, resolvedRoot),
277
+ ANSI.dim
278
+ )}`
279
+ );
280
+ for (const pluginResult of pluginResults) {
281
+ reportPluginResult(pluginResult, io);
282
+ }
283
+ if (appSkillResults.length > 0) {
284
+ reportAppSkills(appSkillResults, io);
285
+ }
286
+ for (const warning of warnings) {
287
+ io.warn(`${statusIcon("warn")} warning: ${warning}`);
288
+ }
289
+ if (errors.length > 0) {
290
+ for (const error of errors) {
291
+ io.error(`${statusIcon("error")} error: ${error}`);
292
+ }
293
+ throw new Error(
294
+ `Validation failed (${errors.length} error${errors.length === 1 ? "" : "s"}, ${pluginDirs.length} plugin manifest${pluginDirs.length === 1 ? "" : "s"}, ${skillDirs.length} skill director${skillDirs.length === 1 ? "y" : "ies"} checked).`
295
+ );
296
+ }
297
+ io.info(
298
+ `${formatHeading("ok", `Validation passed (${pluginDirs.length} plugin manifest${pluginDirs.length === 1 ? "" : "s"}, ${skillDirs.length} skill director${skillDirs.length === 1 ? "y" : "ies"} checked).`)}`
299
+ );
300
+ }
301
+ export {
302
+ runCheck
303
+ };
package/dist/cli/init.js CHANGED
@@ -74,6 +74,13 @@ async function runInit(dir, log = console.log) {
74
74
 
75
75
  You are ${name}, a helpful assistant.
76
76
  `);
77
+ fs.writeFileSync(
78
+ path.join(dataDir, "ABOUT.md"),
79
+ `# About ${name}
80
+
81
+ Describe what ${name} helps users do.
82
+ `
83
+ );
77
84
  const skillsDir = path.join(target, "app", "skills");
78
85
  fs.mkdirSync(skillsDir, { recursive: true });
79
86
  fs.writeFileSync(path.join(skillsDir, ".gitkeep"), "");
package/dist/cli/run.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- declare const CLI_USAGE = "usage: junior init <dir>\n junior snapshot create";
1
+ declare const CLI_USAGE = "usage: junior init <dir>\n junior snapshot create\n junior check [dir]";
2
2
  interface CliHandlers {
3
3
  runInit: (dir: string) => Promise<void>;
4
4
  runSnapshotCreate: () => Promise<void>;
5
+ runCheck: (dir?: string) => Promise<void>;
5
6
  }
6
7
  interface CliIo {
7
8
  error: (line: string) => void;
package/dist/cli/run.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/cli/run.ts
2
- var CLI_USAGE = "usage: junior init <dir>\n junior snapshot create";
2
+ var CLI_USAGE = "usage: junior init <dir>\n junior snapshot create\n junior check [dir]";
3
3
  var DEFAULT_IO = {
4
4
  error: console.error
5
5
  };
@@ -21,6 +21,14 @@ async function runCli(argv, handlers, io = DEFAULT_IO) {
21
21
  await handlers.runSnapshotCreate();
22
22
  return 0;
23
23
  }
24
+ if (command === "check") {
25
+ if (rest.length > 0) {
26
+ io.error(CLI_USAGE);
27
+ return 1;
28
+ }
29
+ await handlers.runCheck(subcommand);
30
+ return 0;
31
+ }
24
32
  io.error(CLI_USAGE);
25
33
  return 1;
26
34
  }
@@ -1,9 +1,13 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
+ getPluginProviders,
4
+ getPluginRuntimeDependencies,
5
+ getPluginRuntimePostinstall,
3
6
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-OZFXD5IG.js";
5
- import "../chunk-PY4AI2GZ.js";
7
+ } from "../chunk-5UJSQX4R.js";
6
8
  import "../chunk-Z5E25LRN.js";
9
+ import "../chunk-PY4AI2GZ.js";
10
+ import "../chunk-VW26MOSO.js";
7
11
 
8
12
  // src/cli/snapshot-warmup.ts
9
13
  var DEFAULT_RUNTIME = "node22";
@@ -23,10 +27,63 @@ function progressMessage(phase) {
23
27
  }
24
28
  return "Sandbox snapshot build complete.";
25
29
  }
30
+ function formatList(values) {
31
+ return values.length > 0 ? values.join(", ") : "none";
32
+ }
33
+ function logSnapshotProfile(log) {
34
+ const providers = getPluginProviders();
35
+ const pluginNames = providers.map((plugin) => plugin.manifest.name).sort();
36
+ const snapshotPluginNames = providers.filter(
37
+ (plugin) => (plugin.manifest.runtimeDependencies?.length ?? 0) > 0 || (plugin.manifest.runtimePostinstall?.length ?? 0) > 0
38
+ ).map((plugin) => plugin.manifest.name).sort();
39
+ const systemDependencies = [];
40
+ const npmDependencies = [];
41
+ for (const dep of getPluginRuntimeDependencies()) {
42
+ if (dep.type === "npm") {
43
+ npmDependencies.push(`${dep.package}@${dep.version}`);
44
+ continue;
45
+ }
46
+ systemDependencies.push("package" in dep ? dep.package : dep.url);
47
+ }
48
+ const postinstallCommands = getPluginRuntimePostinstall().map(
49
+ ({ cmd, args }) => [cmd, ...args ?? []].filter((part) => part.trim().length > 0).join(" ")
50
+ );
51
+ log(`Loaded plugins (${pluginNames.length}): ${formatList(pluginNames)}`);
52
+ log(
53
+ "Sandbox snapshot inputs: " + [
54
+ `plugins=${snapshotPluginNames.length}`,
55
+ `system_dependencies=${systemDependencies.length}`,
56
+ `npm_dependencies=${npmDependencies.length}`,
57
+ `postinstall_commands=${postinstallCommands.length}`
58
+ ].join(" ")
59
+ );
60
+ if (snapshotPluginNames.length === 0 && systemDependencies.length === 0 && npmDependencies.length === 0 && postinstallCommands.length === 0) {
61
+ return;
62
+ }
63
+ log(
64
+ `Snapshot plugins (${snapshotPluginNames.length}): ${formatList(snapshotPluginNames)}`
65
+ );
66
+ if (systemDependencies.length > 0) {
67
+ log(
68
+ `System dependencies (${systemDependencies.length}): ${systemDependencies.join(", ")}`
69
+ );
70
+ }
71
+ if (npmDependencies.length > 0) {
72
+ log(
73
+ `NPM dependencies (${npmDependencies.length}): ${npmDependencies.join(", ")}`
74
+ );
75
+ }
76
+ if (postinstallCommands.length > 0) {
77
+ log(
78
+ `Runtime postinstall (${postinstallCommands.length}): ${postinstallCommands.join(", ")}`
79
+ );
80
+ }
81
+ }
26
82
  async function runSnapshotCreate(log = console.log) {
27
83
  const runtime = DEFAULT_RUNTIME;
28
84
  const timeoutMs = DEFAULT_TIMEOUT_MS;
29
85
  try {
86
+ logSnapshotProfile(log);
30
87
  const emitted = /* @__PURE__ */ new Set();
31
88
  const snapshot = await resolveRuntimeDependencySnapshot({
32
89
  runtime,
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  POST
3
- } from "../chunk-DGKNXMK4.js";
4
- import "../chunk-RFUE5VBK.js";
5
- import "../chunk-OZFXD5IG.js";
6
- import "../chunk-PY4AI2GZ.js";
3
+ } from "../chunk-VPCCZ3PK.js";
4
+ import "../chunk-QNOV65P4.js";
5
+ import "../chunk-KT5HARSN.js";
6
+ import "../chunk-5UJSQX4R.js";
7
7
  import "../chunk-Z5E25LRN.js";
8
+ import "../chunk-PY4AI2GZ.js";
9
+ import "../chunk-VW26MOSO.js";
8
10
  export {
9
11
  POST
10
12
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  POST as POST2
3
- } from "../chunk-TEQ3UIS7.js";
3
+ } from "../chunk-EZ6WIJL2.js";
4
4
  import {
5
5
  POST
6
- } from "../chunk-DGKNXMK4.js";
6
+ } from "../chunk-VPCCZ3PK.js";
7
7
  import {
8
8
  escapeXml,
9
9
  formatProviderLabel,
@@ -13,22 +13,24 @@ import {
13
13
  publishAppHomeView,
14
14
  resolveBaseUrl,
15
15
  truncateStatusText
16
- } from "../chunk-RFUE5VBK.js";
16
+ } from "../chunk-QNOV65P4.js";
17
17
  import {
18
18
  GET
19
19
  } from "../chunk-4RBEYCOG.js";
20
+ import "../chunk-KT5HARSN.js";
20
21
  import {
21
22
  botConfig,
22
23
  buildOAuthTokenRequest,
23
24
  getPluginOAuthConfig,
24
25
  getStateAdapter,
25
26
  parseOAuthTokenResponse
26
- } from "../chunk-OZFXD5IG.js";
27
+ } from "../chunk-5UJSQX4R.js";
28
+ import "../chunk-Z5E25LRN.js";
27
29
  import {
28
30
  logException,
29
31
  logInfo
30
32
  } from "../chunk-PY4AI2GZ.js";
31
- import "../chunk-Z5E25LRN.js";
33
+ import "../chunk-VW26MOSO.js";
32
34
 
33
35
  // src/handlers/oauth-callback.ts
34
36
  import { after } from "next/server";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  POST
3
- } from "../chunk-TEQ3UIS7.js";
3
+ } from "../chunk-EZ6WIJL2.js";
4
4
  import "../chunk-PY4AI2GZ.js";
5
5
  export {
6
6
  POST
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"