Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(crwrsca): implement basic telemetry #11132

Merged
merged 4 commits into from
Aug 1, 2024
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
1 change: 1 addition & 0 deletions packages/create-redwood-rsc-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"chalk": "5.3.0",
"ci-info": "4.0.0",
"enquirer": "2.4.1",
"execa": "9.3.0",
"node-fetch": "3.3.2",
Expand Down
9 changes: 9 additions & 0 deletions packages/create-redwood-rsc-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

import type { TelemetryInfo } from './telemetry.js'

import { initConfig } from './config.js'
import { downloadTemplate } from './download.js'
import { handleError } from './error.js'
Expand All @@ -9,11 +11,16 @@ import { setInstallationDir } from './installationDir.js'
import { relaunchOnLatest, shouldRelaunch } from './latest.js'
import { printDone, printWelcome } from './messages.js'
import { checkNodeVersion, checkYarnInstallation } from './prerequisites.js'
import { sendTelemetry } from './telemetry.js'
import { upgradeToLatestCanary } from './upgradeToLatestCanary.js'
import { unzip } from './zip.js'

const startTime = Date.now()
const telemetryInfo: TelemetryInfo = {}

try {
const config = initConfig()
telemetryInfo.template = config.template

if (shouldRelaunch(config)) {
await relaunchOnLatest(config)
Expand All @@ -34,3 +41,5 @@ try {
} catch (e) {
handleError(e)
}

await sendTelemetry(telemetryInfo, Date.now() - startTime)
2 changes: 1 addition & 1 deletion packages/create-redwood-rsc-app/src/installationDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async function promptForInstallationDir() {
console.log('The `~username` syntax is not supported here')
console.log(
'Please use the full path or specify the target directory on the ' +
'command line.',
'command line.',
)

return await promptForInstallationDir()
Expand Down
4 changes: 2 additions & 2 deletions packages/create-redwood-rsc-app/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function printWelcome() {
)
console.log(
'If you need a more customized setup, please use the official installer ' +
'by running `yarn create redwood-app`',
'by running `yarn create redwood-app`',
)
console.log()
}
Expand All @@ -26,7 +26,7 @@ export function printDone(config: Config) {
console.log()
console.log(
'You can now run the following commands to build and serve the included ' +
'example application',
'example application',
)
console.log()
console.log(chalk.hex('cef792')('> cd ' + config.installationDir))
Expand Down
2 changes: 1 addition & 1 deletion packages/create-redwood-rsc-app/src/prerequisites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function checkYarnInstallation(config: Config) {
console.log('')
console.log(
'You have more than one active yarn installation. One is installed ' +
"by corepack,\nbut it's not the first one in $PATH.",
"by corepack,\nbut it's not the first one in $PATH.",
)
console.log("Perhaps you've manually installed it using Homebrew or npm.")
console.log(
Expand Down
100 changes: 100 additions & 0 deletions packages/create-redwood-rsc-app/src/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import ci from 'ci-info'
import fetch from 'node-fetch'

const TELEMETRY_URL =
process.env.REDWOOD_REDIRECT_TELEMETRY ??
'https://telemetry.redwoodjs.com/api/v1/telemetry'

export interface TelemetryInfo {
template?: string
}

// Note: The fields and their names are constrained by the telemetry API
interface TelemetryPayload {
ci: boolean
command: string
complexity: string
duration: number
error?: string
experiments?: string[]
redwoodCi: boolean
system: string
type: 'command'
}

function buildPayload(
telemetryInfo: TelemetryInfo,
duration: number,
): TelemetryPayload {
const command = ['create', 'redwood-rsc-app']
if (process.argv.includes('--no-check-latest')) {
command.push('--no-check-latest')
}

// We don't have a field for the template, so we're using/abusing the experiments field
const experiments: string[] = []
if (telemetryInfo.template) {
experiments.push(`template:${telemetryInfo.template}`)
}

// Detect CI environments
const isCi = ci.isCI
const isRedwoodCi = !!process.env.REDWOOD_CI

// Note: The complexity field is required by the API so we are using a placeholder value
const complexity = '-1.-1.-1.-1.-1'

// Note: The system field is required by the API so we are using a placeholder value
const system = '-1.-1'

return {
ci: isCi,
command: command.join(' '),
complexity,
duration,
experiments,
redwoodCi: isRedwoodCi,
system,
type: 'command',
}
}

export async function sendTelemetry(
telemetryInfo: TelemetryInfo,
duration: number,
) {
if (process.env.REDWOOD_DISABLE_TELEMETRY) {
return
}

try {
const payload = buildPayload(telemetryInfo, duration)

if (process.env.REDWOOD_VERBOSE_TELEMETRY) {
console.info('Redwood Telemetry Payload', payload)
}

const response = await fetch(TELEMETRY_URL, {
method: 'post',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
})

if (process.env.REDWOOD_VERBOSE_TELEMETRY) {
console.info('Redwood Telemetry Response:', response)
}

// Normally we would report on any non-error response here (like a 500)
// but since the process is spawned and stdout/stderr is ignored, it can
// never be seen by the user, so ignore.
if (process.env.REDWOOD_VERBOSE_TELEMETRY && response.status !== 200) {
console.error('Error from telemetry insert:', await response.text())
}
} catch (e) {
// service interruption: network down or telemetry API not responding
// don't let telemetry errors bubble up to user, just do nothing.
if (process.env.REDWOOD_VERBOSE_TELEMETRY) {
console.error('Uncaught error in telemetry:', e)
}
}
}
8 changes: 8 additions & 0 deletions packages/create-redwood-rsc-app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,13 @@ __metadata:
languageName: node
linkType: hard

"ci-info@npm:4.0.0":
version: 4.0.0
resolution: "ci-info@npm:4.0.0"
checksum: 10c0/ecc003e5b60580bd081d83dd61d398ddb8607537f916313e40af4667f9c92a1243bd8e8a591a5aa78e418afec245dbe8e90a0e26e39ca0825129a99b978dd3f9
languageName: node
linkType: hard

"clean-stack@npm:^2.0.0":
version: 2.2.0
resolution: "clean-stack@npm:2.2.0"
Expand Down Expand Up @@ -1532,6 +1539,7 @@ __metadata:
"@types/which": "npm:3.0.4"
"@types/yauzl-promise": "npm:4.0.1"
chalk: "npm:5.3.0"
ci-info: "npm:4.0.0"
concurrently: "npm:^8.2.2"
enquirer: "npm:2.4.1"
esbuild: "npm:0.23.0"
Expand Down
Loading