@@ -61,7 +61,7 @@ type DxTracker struct {
6161// NewDxTracker initializes a tracker with automatic GitHub CLI integration for authentication.
6262// APITokenVariableName is the name of the GitHub repository variable containing the DX API token.
6363// Each DX project has its own token for tracking purposes.
64- func NewDxTracker (APITokenVariableName string ) (Tracker , error ) {
64+ func NewDxTracker (APITokenVariableName , product string ) (Tracker , error ) {
6565 t := & DxTracker {}
6666
6767 lvlStr := os .Getenv (EnvVarLogLevel )
@@ -86,21 +86,30 @@ func NewDxTracker(APITokenVariableName string) (Tracker, error) {
8686 t .logger .Debug ().Msg ("Tracking in test mode" )
8787 }
8888
89+ if APITokenVariableName == "" {
90+ return nil , errors .New ("API token variable name is required. It is the name of the GitHub repository variable containing the DX API token" )
91+ }
92+
93+ if product == "" {
94+ return nil , errors .New ("product is required. Each DX project has its own token for tracking purposes" )
95+ }
96+ product = strings .ToLower (product )
97+
8998 c , isConfigAvailable , configErr := openConfig ()
9099 if configErr != nil {
91100 return nil , errors .Wrap (configErr , "failed to open local config" )
92101 }
93102
94103 // if local config is available read it and set mode to online
95- if isConfigAvailable && isConfigValid (c ) {
104+ if isConfigAvailable && isConfigValid (c , product ) {
96105 t .logger .Debug ().Msg ("Valid local config found" )
97106 t .mode = ModeOnline
98107 } else {
99108 // if local config is not available check if GH CLI is available
100109 // and if so, try to configure tracker with it
101110 if t .checkIfGhCLIAvailable () {
102111 var configErr error
103- c , configErr = t .buildConfigWithGhCLI (APITokenVariableName )
112+ c , configErr = t .buildConfigWithGhCLI (APITokenVariableName , product )
104113 if configErr != nil {
105114 t .mode = ModeOffline
106115 t .logger .Warn ().Msgf ("Failed to build config with GH CLI: %s" , configErr .Error ())
@@ -121,7 +130,8 @@ func NewDxTracker(APITokenVariableName string) (Tracker, error) {
121130 }
122131
123132 if t .mode == ModeOnline {
124- t .apiToken = c .DxAPIToken
133+ // at this point config should be available and valid, since we've checked it above
134+ t .apiToken = c .APITokens [product ]
125135 t .githubUsername = c .GithubUsername
126136
127137 go func () {
@@ -137,19 +147,21 @@ func NewDxTracker(APITokenVariableName string) (Tracker, error) {
137147 return t , nil
138148}
139149
140- func (t * DxTracker ) buildConfigWithGhCLI (APITokenVariableName string ) (* config , error ) {
150+ func (t * DxTracker ) buildConfigWithGhCLI (APITokenVariableName , product string ) (* config , error ) {
141151 var userNameErr error
142152 c := & config {}
143153 c .GithubUsername , userNameErr = t .readGHUsername ()
144154 if userNameErr != nil {
145155 return nil , errors .Wrap (userNameErr , "failed to read github username" )
146156 }
147157
148- var apiTokenErr error
149- c .DxAPIToken , apiTokenErr = t .readDXAPIToken (APITokenVariableName )
158+ apiToken , apiTokenErr := t .readDXAPIToken (APITokenVariableName )
150159 if apiTokenErr != nil {
151160 return nil , errors .Wrap (apiTokenErr , "failed to read DX API token" )
152161 }
162+ c .APITokens = map [Product ]string {
163+ product : apiToken ,
164+ }
153165
154166 saveErr := saveConfig (c )
155167 if saveErr != nil {
@@ -322,12 +334,20 @@ func (t *DxTracker) readDXAPIToken(APITokenVariableName string) (string, error)
322334}
323335
324336// config stores authentication credentials for the DX API.
325- type config struct {
337+ // deprecated: legacyConfig is used to read old config files and migrate them to the new format. Use config instead.
338+ type legacyConfig struct {
326339 DxAPIToken string `json:"dx_api_token"`
327340 GithubUsername string `json:"github_username"`
328341}
329342
343+ type Product = string
344+ type config struct {
345+ GithubUsername string `json:"github_username"`
346+ APITokens map [Product ]string `json:"tokens"`
347+ }
348+
330349// openConfig attempts to load existing configuration from the user's home directory.
350+ // If a legacy config is found, it is migrated to the new format.
331351func openConfig () (* config , bool , error ) {
332352 configPath , pathErr := configPath ()
333353 if pathErr != nil {
@@ -343,6 +363,29 @@ func openConfig() (*config, bool, error) {
343363 return nil , false , errors .Wrap (readErr , "failed to read config file" )
344364 }
345365
366+ var legacyConfig legacyConfig
367+ legacyUnmarshalErr := json .Unmarshal (configContent , & legacyConfig )
368+ if legacyUnmarshalErr != nil {
369+ return nil , false , errors .Wrap (legacyUnmarshalErr , "failed to unmarshal legacy config file" )
370+ }
371+
372+ if legacyConfig .DxAPIToken != "" && legacyConfig .GithubUsername != "" {
373+ newConfig := config {
374+ GithubUsername : legacyConfig .GithubUsername ,
375+ APITokens : map [Product ]string {
376+ // it is the only product that uses tracking at the moment
377+ "local_CRE" : legacyConfig .DxAPIToken ,
378+ },
379+ }
380+
381+ saveErr := saveConfig (& newConfig )
382+ if saveErr != nil {
383+ return nil , false , errors .Wrap (saveErr , "failed to save new config" )
384+ }
385+
386+ return & newConfig , true , nil
387+ }
388+
346389 var localConfig config
347390 unmarshalErr := json .Unmarshal (configContent , & localConfig )
348391 if unmarshalErr != nil {
@@ -353,8 +396,8 @@ func openConfig() (*config, bool, error) {
353396}
354397
355398// isConfigValid ensures both API token and GitHub username are present.
356- func isConfigValid (c * config ) bool {
357- return c .DxAPIToken != "" && c .GithubUsername != ""
399+ func isConfigValid (c * config , product string ) bool {
400+ return c .APITokens [ product ] != "" && c .GithubUsername != ""
358401}
359402
360403// saveConfig persists configuration to the user's home directory with proper permissions.
0 commit comments