@sentry/junior-github 0.162.0 → 0.163.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/dist/index.js CHANGED
@@ -730,12 +730,28 @@ import {
730
730
  pluginToolOutputSchema
731
731
  } from "@sentry/junior-plugin-api";
732
732
  import { z } from "zod";
733
- var RESERVED_SANDBOX_DIRECTORIES = /* @__PURE__ */ new Set([".junior", "data", "skills"]);
733
+
734
+ // src/sandbox-paths.ts
735
+ var RESERVED_SANDBOX_DIRECTORIES = /* @__PURE__ */ new Set([
736
+ ".junior",
737
+ "data",
738
+ "skills"
739
+ ]);
740
+ function isReservedSandboxDirectory(path) {
741
+ return RESERVED_SANDBOX_DIRECTORIES.has(path.toLowerCase());
742
+ }
743
+
744
+ // src/tools/clone-repository.ts
734
745
  var inputSchema = z.object({
735
746
  repo: z.string().describe('Repository in "owner/name" format.'),
736
- directory: z.string().regex(/^[A-Za-z0-9._-]+$/).refine((value) => value !== "." && value !== "..", {
737
- message: "Directory must be a single directory name."
738
- }).describe("Optional destination directory under the sandbox root.").optional()
747
+ directory: z.string().regex(/^(?:[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)$/).refine(
748
+ (value) => !value.split("/").some((part) => part === "." || part === ".."),
749
+ {
750
+ message: "Directory must be a relative path without . or .. segments."
751
+ }
752
+ ).describe(
753
+ "Optional destination directory under the sandbox root. Defaults to repos/{name}."
754
+ ).optional()
739
755
  }).strict();
740
756
  var cloneSchema = z.object({
741
757
  path: z.string(),
@@ -753,7 +769,7 @@ function parseRepo(value) {
753
769
  return { owner: parts[0], name: parts[1] };
754
770
  }
755
771
  function defaultDirectory(repoName) {
756
- return RESERVED_SANDBOX_DIRECTORIES.has(repoName) ? `${repoName}-repo` : repoName;
772
+ return `repos/${repoName}`;
757
773
  }
758
774
  function commandSignal(signal, timeoutMs) {
759
775
  const timeout = AbortSignal.timeout(timeoutMs);
@@ -802,7 +818,27 @@ function createGitHubCloneRepositoryTool(ctx) {
802
818
  async execute(input, options) {
803
819
  const repo = parseRepo(input.repo);
804
820
  const directory = input.directory ?? defaultDirectory(repo.name);
821
+ const rootSegment = directory.split("/")[0] ?? directory;
822
+ if (isReservedSandboxDirectory(rootSegment)) {
823
+ throw new PluginToolInputError(
824
+ `Directory conflicts with a reserved sandbox path: ${directory}`
825
+ );
826
+ }
805
827
  const path = `${ctx.sandbox.root}/${directory}`;
828
+ const parentDirectory = directory.includes("/") ? directory.slice(0, directory.lastIndexOf("/")) : void 0;
829
+ if (parentDirectory) {
830
+ const mkdir = await ctx.sandbox.run({
831
+ cmd: "mkdir",
832
+ args: ["-p", "--", `${ctx.sandbox.root}/${parentDirectory}`],
833
+ cwd: ctx.sandbox.root,
834
+ signal: commandSignal(options.signal, 3e4)
835
+ });
836
+ if (mkdir.exitCode !== 0) {
837
+ throw new PluginToolInputError(
838
+ `Failed to create clone parent directory: ${parentDirectory}`
839
+ );
840
+ }
841
+ }
806
842
  const exists = await ctx.sandbox.run({
807
843
  cmd: "bash",
808
844
  args: ["-c", `test -e "$1"`, "bash", path],
@@ -4911,6 +4947,63 @@ function linkifyGitHubReferences(text2) {
4911
4947
  }).join("\n");
4912
4948
  }
4913
4949
 
4950
+ // src/workspace-prepare.ts
4951
+ async function prepareWorkspace(ctx) {
4952
+ const repos = ctx.repos.map((entry) => {
4953
+ const [owner, name, ...rest] = entry.repo.split("/");
4954
+ if (!owner || !name || rest.length > 0) {
4955
+ throw new Error(`Invalid GitHub repository: ${entry.repo}`);
4956
+ }
4957
+ const segments = entry.path.split("/");
4958
+ if (segments.length === 0 || segments.some(
4959
+ (part) => !part || part === "." || part === ".." || !/^[A-Za-z0-9._-]+$/.test(part)
4960
+ ) || isReservedSandboxDirectory(segments[0])) {
4961
+ throw new Error(`Invalid workspace checkout path: ${entry.path}`);
4962
+ }
4963
+ return { owner, name, path: entry.path, repo: entry.repo };
4964
+ });
4965
+ const paths = /* @__PURE__ */ new Set();
4966
+ for (const entry of repos) {
4967
+ const key = entry.path.toLowerCase();
4968
+ if (paths.has(key)) {
4969
+ throw new Error(`Workspace checkout path collision: ${entry.path}`);
4970
+ }
4971
+ paths.add(key);
4972
+ }
4973
+ for (const { owner, name, path, repo } of repos) {
4974
+ const parent = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : void 0;
4975
+ if (parent) {
4976
+ const mkdir = await ctx.sandbox.run({
4977
+ cmd: "mkdir",
4978
+ args: ["-p", "--", parent],
4979
+ cwd: ctx.sandbox.root
4980
+ });
4981
+ if (mkdir.exitCode !== 0) {
4982
+ throw new Error(
4983
+ `GitHub workspace checkout parent failed for ${repo}: ${mkdir.stderr.trim() || `exit ${mkdir.exitCode}`}`
4984
+ );
4985
+ }
4986
+ }
4987
+ const result = await ctx.sandbox.run({
4988
+ cmd: "git",
4989
+ args: [
4990
+ "clone",
4991
+ "--quiet",
4992
+ "--depth=1",
4993
+ "--",
4994
+ `https://github.com/${owner}/${name}.git`,
4995
+ path
4996
+ ],
4997
+ cwd: ctx.sandbox.root
4998
+ });
4999
+ if (result.exitCode !== 0) {
5000
+ throw new Error(
5001
+ `GitHub workspace clone failed for ${repo}: ${result.stderr.trim() || `exit ${result.exitCode}`}`
5002
+ );
5003
+ }
5004
+ }
5005
+ }
5006
+
4914
5007
  // src/plugin.ts
4915
5008
  function githubSmartHttpAccess(upstreamUrl) {
4916
5009
  const pathname = upstreamUrl.pathname.toLowerCase();
@@ -5453,6 +5546,7 @@ function githubPlugin(options = {}) {
5453
5546
  tools(ctx) {
5454
5547
  return createGitHubTools(ctx);
5455
5548
  },
5549
+ workspacePrepare: prepareWorkspace,
5456
5550
  async sandboxPrepare(ctx) {
5457
5551
  const hooksPath = `${ctx.sandbox.juniorRoot}/git-hooks`;
5458
5552
  await ctx.sandbox.writeFile({
@@ -0,0 +1,4 @@
1
+ /** Sandbox root directories reserved for Junior runtime material. */
2
+ export declare const RESERVED_SANDBOX_DIRECTORIES: Set<string>;
3
+ /** True when a checkout path collides with a reserved sandbox root (case-insensitive). */
4
+ export declare function isReservedSandboxDirectory(path: string): boolean;
@@ -0,0 +1,3 @@
1
+ import type { WorkspacePrepareHookContext } from "@sentry/junior-plugin-api";
2
+ /** Clone GitHub repositories into their host-selected Workspace paths. */
3
+ export declare function prepareWorkspace(ctx: WorkspacePrepareHookContext): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.162.0",
3
+ "version": "0.163.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "drizzle-orm": "^0.45.2",
33
33
  "zod": "^4.4.3",
34
- "@sentry/junior-plugin-api": "0.162.0"
34
+ "@sentry/junior-plugin-api": "0.163.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.9.1",