Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,24 @@ Events are still sent to the analytics endpoint in debug mode, but you can see e

## Custom Tracking Endpoint (for Organizations)

Organizations can point telemetry to their own analytics endpoint:
Organizations can point telemetry to their own analytics endpoint using environment variables. These environment variables have the **highest priority** and will override any configuration from project-level config or global config files:

```bash
# Set custom endpoint
# Set custom endpoint (highest priority - overrides all other configs)
export CODEGEN_TELEMETRY_ENDPOINT=https://analytics.mycompany.com/telemetry
export CODEGEN_TELEMETRY_ID=custom-tracking-id
export CODEGEN_TELEMETRY_API_SECRET=your-api-secret
```

**Configuration Priority Order (highest to lowest):**
1. **Environment variables** (highest priority):
- `CODEGEN_TELEMETRY_DISABLED` / `DO_NOT_TRACK` - disable telemetry
- `CODEGEN_TELEMETRY_ENDPOINT` - custom analytics endpoint
- `CODEGEN_TELEMETRY_ID` - custom tracking ID
- `CODEGEN_TELEMETRY_API_SECRET` - custom API secret
2. **Project-level config** (from `codegen.config.js`)
3. **Global config file** (`~/.the-codegen-project/config.json`)

Expected endpoint format (GA4 Measurement Protocol compatible):

```
Expand Down
25 changes: 21 additions & 4 deletions src/telemetry/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import {ProjectTelemetryConfig} from '../codegen/types';
* This function never throws - returns disabled config on any error.
*
* Priority order (highest to lowest):
* 1. Project-level config (from codegen.config.js)
* 2. Global config file (~/.the-codegen-project/config.json)
* 3. Environment variable overrides (CODEGEN_TELEMETRY_DISABLED, DO_NOT_TRACK)
* 1. Environment variable overrides (highest priority):
* - CODEGEN_TELEMETRY_DISABLED / DO_NOT_TRACK: disable telemetry
* - CODEGEN_TELEMETRY_ENDPOINT: custom analytics endpoint
* - CODEGEN_TELEMETRY_ID: custom tracking ID
* - CODEGEN_TELEMETRY_API_SECRET: custom API secret
* 2. Project-level config (from codegen.config.js)
* 3. Global config file (~/.the-codegen-project/config.json)
*
* @param projectConfig - Optional project-level telemetry config from codegen.config.js
* @returns Promise resolving to telemetry configuration
Expand All @@ -30,13 +34,26 @@ export async function getTelemetryConfig(
...(projectConfig ?? {})
};

// Apply environment variable overrides
// Apply environment variable overrides (highest priority)
if (
process.env.CODEGEN_TELEMETRY_DISABLED === '1' ||
process.env.DO_NOT_TRACK
) {
telemetryConfig.enabled = false;
}

if (process.env.CODEGEN_TELEMETRY_ENDPOINT) {
telemetryConfig.endpoint = process.env.CODEGEN_TELEMETRY_ENDPOINT;
}

if (process.env.CODEGEN_TELEMETRY_ID) {
telemetryConfig.trackingId = process.env.CODEGEN_TELEMETRY_ID;
}

if (process.env.CODEGEN_TELEMETRY_API_SECRET) {
telemetryConfig.apiSecret = process.env.CODEGEN_TELEMETRY_API_SECRET;
}

return telemetryConfig;
} catch (error) {
// On any error, return disabled config
Expand Down
175 changes: 168 additions & 7 deletions test/telemetry/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand All @@ -51,7 +52,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand Down Expand Up @@ -92,7 +94,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand Down Expand Up @@ -120,7 +123,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand All @@ -138,6 +142,160 @@ describe('Telemetry Config', () => {
expect(config.endpoint).toBe('https://example.com'); // From global
expect(config.trackingId).toBe('G-TEST123'); // From global
});

it('should apply CODEGEN_TELEMETRY_ENDPOINT environment variable override', async () => {
process.env.CODEGEN_TELEMETRY_ENDPOINT = 'https://custom-endpoint.com';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const config = await getTelemetryConfig();

expect(config.endpoint).toBe('https://custom-endpoint.com');
expect(config.enabled).toBe(true); // Should not affect other properties
expect(config.trackingId).toBe('G-TEST123');
});

it('should apply CODEGEN_TELEMETRY_ID environment variable override', async () => {
process.env.CODEGEN_TELEMETRY_ID = 'G-CUSTOM123';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const config = await getTelemetryConfig();

expect(config.trackingId).toBe('G-CUSTOM123');
expect(config.enabled).toBe(true); // Should not affect other properties
expect(config.endpoint).toBe('https://example.com');
});

it('should apply CODEGEN_TELEMETRY_API_SECRET environment variable override', async () => {
process.env.CODEGEN_TELEMETRY_API_SECRET = 'custom-secret-123';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123',
apiSecret: 'original-secret'
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const config = await getTelemetryConfig();

expect(config.apiSecret).toBe('custom-secret-123');
expect(config.enabled).toBe(true); // Should not affect other properties
expect(config.endpoint).toBe('https://example.com');
});

it('should apply multiple environment variable overrides together', async () => {
process.env.CODEGEN_TELEMETRY_ENDPOINT = 'https://org-analytics.com';
process.env.CODEGEN_TELEMETRY_ID = 'G-ORG123';
process.env.CODEGEN_TELEMETRY_API_SECRET = 'org-secret';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123',
apiSecret: 'original-secret'
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const config = await getTelemetryConfig();

expect(config.endpoint).toBe('https://org-analytics.com');
expect(config.trackingId).toBe('G-ORG123');
expect(config.apiSecret).toBe('org-secret');
expect(config.enabled).toBe(true);
expect(config.anonymousId).toBe('test-uuid'); // Should keep from global
});

it('should prioritize environment variables over project config', async () => {
process.env.CODEGEN_TELEMETRY_ENDPOINT = 'https://env-endpoint.com';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://global-endpoint.com',
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const projectConfig = {
endpoint: 'https://project-endpoint.com'
};

const config = await getTelemetryConfig(projectConfig);

// Environment variable should override both project and global config
expect(config.endpoint).toBe('https://env-endpoint.com');
});

it('should disable telemetry via environment variable even if project config enables it', async () => {
process.env.CODEGEN_TELEMETRY_DISABLED = '1';

const mockConfig = {
version: '1.0.0',
telemetry: {
enabled: false,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};

mockGetGlobalConfig.mockResolvedValue(mockConfig);

const projectConfig = {
enabled: true // Try to enable in project config
};

const config = await getTelemetryConfig(projectConfig);

// Environment variable should have highest priority
expect(config.enabled).toBe(false);
});
});

describe('setTelemetryEnabled', () => {
Expand All @@ -148,7 +306,8 @@ describe('Telemetry Config', () => {
enabled: false,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand All @@ -174,7 +333,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand Down Expand Up @@ -216,7 +376,8 @@ describe('Telemetry Config', () => {
enabled: true,
anonymousId: 'test-uuid',
endpoint: 'https://example.com',
trackingId: 'G-TEST123'
trackingId: 'G-TEST123',
apiSecret: ''
},
hasShownTelemetryNotice: true
};
Expand Down