22 * Target Resolution
33 *
44 * Shared utilities for resolving organization and project context from
5- * various sources: CLI flags, config defaults, and DSN detection.
5+ * various sources: CLI flags, environment variables, config defaults,
6+ * and DSN detection.
67 *
78 * Resolution priority (highest to lowest):
89 * 1. Explicit CLI flags
9- * 2. Config defaults
10- * 3. DSN auto-detection (source code, .env files, environment variables)
11- * 4. Directory name inference (matches project slugs with word boundaries)
10+ * 2. SENTRY_ORG / SENTRY_PROJECT environment variables
11+ * 3. Config defaults
12+ * 4. DSN auto-detection (source code, .env files, environment variables)
13+ * 5. Directory name inference (matches project slugs with word boundaries)
1214 */
1315
1416import { basename } from "node:path" ;
@@ -461,6 +463,49 @@ async function inferFromDirectoryName(cwd: string): Promise<ResolvedTargets> {
461463 } ;
462464}
463465
466+ /**
467+ * Read org/project from SENTRY_ORG and SENTRY_PROJECT environment variables.
468+ *
469+ * SENTRY_PROJECT supports the `<org>/<project>` combo notation (presence of
470+ * `/` distinguishes it from a plain project slug). When the combo form is
471+ * used, SENTRY_ORG is ignored.
472+ *
473+ * @returns Resolved org+project, org-only, or null if no env vars are set
474+ */
475+ function resolveFromEnvVars ( ) : {
476+ org : string ;
477+ project ?: string ;
478+ detectedFrom : string ;
479+ } | null {
480+ const envProject = process . env . SENTRY_PROJECT ?. trim ( ) ;
481+
482+ // SENTRY_PROJECT=org/project combo takes priority
483+ if ( envProject ?. includes ( "/" ) ) {
484+ const slashIdx = envProject . indexOf ( "/" ) ;
485+ const org = envProject . slice ( 0 , slashIdx ) ;
486+ const project = envProject . slice ( slashIdx + 1 ) ;
487+ if ( org && project ) {
488+ return { org, project, detectedFrom : "SENTRY_PROJECT env var" } ;
489+ }
490+ }
491+
492+ const envOrg = process . env . SENTRY_ORG ?. trim ( ) ;
493+
494+ if ( envOrg && envProject ) {
495+ return {
496+ org : envOrg ,
497+ project : envProject ,
498+ detectedFrom : "SENTRY_ORG / SENTRY_PROJECT env vars" ,
499+ } ;
500+ }
501+
502+ if ( envOrg ) {
503+ return { org : envOrg , detectedFrom : "SENTRY_ORG env var" } ;
504+ }
505+
506+ return null ;
507+ }
508+
464509/**
465510 * Resolve all targets for monorepo-aware commands.
466511 *
@@ -469,9 +514,10 @@ async function inferFromDirectoryName(cwd: string): Promise<ResolvedTargets> {
469514 *
470515 * Resolution priority:
471516 * 1. Explicit org and project - returns single target
472- * 2. Config defaults - returns single target
473- * 3. DSN auto-detection - may return multiple targets
474- * 4. Directory name inference - matches project slugs with word boundaries
517+ * 2. SENTRY_ORG / SENTRY_PROJECT env vars - returns single target
518+ * 3. Config defaults - returns single target
519+ * 4. DSN auto-detection - may return multiple targets
520+ * 5. Directory name inference - matches project slugs with word boundaries
475521 *
476522 * @param options - Resolution options with org, project, and cwd
477523 * @returns All resolved targets and optional footer message
@@ -504,7 +550,23 @@ export async function resolveAllTargets(
504550 ) ;
505551 }
506552
507- // 2. Config defaults
553+ // 2. SENTRY_ORG / SENTRY_PROJECT environment variables
554+ const envVars = resolveFromEnvVars ( ) ;
555+ if ( envVars ?. project ) {
556+ return {
557+ targets : [
558+ {
559+ org : envVars . org ,
560+ project : envVars . project ,
561+ orgDisplay : envVars . org ,
562+ projectDisplay : envVars . project ,
563+ detectedFrom : envVars . detectedFrom ,
564+ } ,
565+ ] ,
566+ } ;
567+ }
568+
569+ // 3. Config defaults
508570 const defaultOrg = await getDefaultOrganization ( ) ;
509571 const defaultProject = await getDefaultProject ( ) ;
510572 if ( defaultOrg && defaultProject ) {
@@ -520,11 +582,11 @@ export async function resolveAllTargets(
520582 } ;
521583 }
522584
523- // 3 . DSN auto-detection (may find multiple in monorepos)
585+ // 4 . DSN auto-detection (may find multiple in monorepos)
524586 const detection = await detectAllDsns ( cwd ) ;
525587
526588 if ( detection . all . length === 0 ) {
527- // 4 . Fallback: infer from directory name
589+ // 5 . Fallback: infer from directory name
528590 return inferFromDirectoryName ( cwd ) ;
529591 }
530592
@@ -576,9 +638,10 @@ export async function resolveAllTargets(
576638 *
577639 * Resolution priority:
578640 * 1. Explicit org and project - both must be provided together
579- * 2. Config defaults
580- * 3. DSN auto-detection
581- * 4. Directory name inference - matches project slugs with word boundaries
641+ * 2. SENTRY_ORG / SENTRY_PROJECT env vars
642+ * 3. Config defaults
643+ * 4. DSN auto-detection
644+ * 5. Directory name inference - matches project slugs with word boundaries
582645 *
583646 * @param options - Resolution options with org, project, and cwd
584647 * @returns Resolved target, or null if resolution failed
@@ -607,7 +670,19 @@ export async function resolveOrgAndProject(
607670 ) ;
608671 }
609672
610- // 2. Config defaults
673+ // 2. SENTRY_ORG / SENTRY_PROJECT environment variables
674+ const envVars = resolveFromEnvVars ( ) ;
675+ if ( envVars ?. project ) {
676+ return {
677+ org : envVars . org ,
678+ project : envVars . project ,
679+ orgDisplay : envVars . org ,
680+ projectDisplay : envVars . project ,
681+ detectedFrom : envVars . detectedFrom ,
682+ } ;
683+ }
684+
685+ // 3. Config defaults
611686 const defaultOrg = await getDefaultOrganization ( ) ;
612687 const defaultProject = await getDefaultProject ( ) ;
613688 if ( defaultOrg && defaultProject ) {
@@ -619,7 +694,7 @@ export async function resolveOrgAndProject(
619694 } ;
620695 }
621696
622- // 3 . DSN auto-detection
697+ // 4 . DSN auto-detection
623698 try {
624699 const dsnResult = await resolveFromDsn ( cwd ) ;
625700 if ( dsnResult ) {
@@ -629,32 +704,31 @@ export async function resolveOrgAndProject(
629704 // Fall through to directory inference
630705 }
631706
632- // 4 . Fallback: infer from directory name
707+ // 5 . Fallback: infer from directory name
633708 const inferred = await inferFromDirectoryName ( cwd ) ;
634- if ( inferred . targets . length > 0 ) {
635- const [ first ] = inferred . targets ;
636- if ( first ) {
637- // If multiple matches, note it in detectedFrom
638- return {
639- ...first ,
640- detectedFrom :
641- inferred . targets . length > 1
642- ? `${ first . detectedFrom } (1 of ${ inferred . targets . length } matches)`
643- : first . detectedFrom ,
644- } ;
645- }
709+ const [ first ] = inferred . targets ;
710+ if ( ! first ) {
711+ return null ;
646712 }
647713
648- return null ;
714+ // If multiple matches, note it in detectedFrom
715+ return {
716+ ...first ,
717+ detectedFrom :
718+ inferred . targets . length > 1
719+ ? `${ first . detectedFrom } (1 of ${ inferred . targets . length } matches)`
720+ : first . detectedFrom ,
721+ } ;
649722}
650723
651724/**
652725 * Resolve organization only from multiple sources.
653726 *
654727 * Resolution priority:
655728 * 1. Positional argument
656- * 2. Config defaults
657- * 3. DSN auto-detection
729+ * 2. SENTRY_ORG / SENTRY_PROJECT env vars
730+ * 3. Config defaults
731+ * 4. DSN auto-detection
658732 *
659733 * @param options - Resolution options with flag and cwd
660734 * @returns Resolved org, or null if resolution failed
@@ -669,13 +743,19 @@ export async function resolveOrg(
669743 return { org } ;
670744 }
671745
672- // 2. Config defaults
746+ // 2. SENTRY_ORG / SENTRY_PROJECT environment variables
747+ const envVars = resolveFromEnvVars ( ) ;
748+ if ( envVars ) {
749+ return { org : envVars . org , detectedFrom : envVars . detectedFrom } ;
750+ }
751+
752+ // 3. Config defaults
673753 const defaultOrg = await getDefaultOrganization ( ) ;
674754 if ( defaultOrg ) {
675755 return { org : defaultOrg } ;
676756 }
677757
678- // 3 . DSN auto-detection
758+ // 4 . DSN auto-detection
679759 try {
680760 return await resolveOrgFromDsn ( cwd ) ;
681761 } catch {
@@ -756,6 +836,12 @@ export async function resolveOrgsForListing(
756836 return { orgs : [ orgFlag ] } ;
757837 }
758838
839+ // 2. SENTRY_ORG / SENTRY_PROJECT environment variables
840+ const envVars = resolveFromEnvVars ( ) ;
841+ if ( envVars ) {
842+ return { orgs : [ envVars . org ] } ;
843+ }
844+
759845 const defaultOrg = await getDefaultOrganization ( ) ;
760846 if ( defaultOrg ) {
761847 return { orgs : [ defaultOrg ] } ;
0 commit comments