From a87e8a42cc40f5f253df5f2a79e9708b3ae41fb4 Mon Sep 17 00:00:00 2001 From: Josh GM Walker <56300765+Josh-Walker-GM@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:21:51 +0100 Subject: [PATCH] chore(cli): Add OpenTelemetry (#8653) * Initial OTel kernel for CLI * Add helper functions for telemetry * Add telemetry for info command * Capture and record top level errors * WIP compute of telemetry details in the background * wip changes to the background compute * Instrument CLI commands I'm not in love with this code. Seems difficult to ensure key consisitency but I'm working around the existing code and yargs. * Rework OTel exporting. Switched to use a custom exporter which writes spans to a file. Then have a background job that fires when OTel shuts down to read those saved spans and send them to the collector. This means we have one background job that runs to both compute the telemetry resources and send the data to the collector. * Re-enable existing telemetry and remove error messaging * Revert change to locking.js * Refactor the new telemetry setup Move some files around and rename some. Adds in the missing telemetry fields to the resource. Rewrites the spans to disk with the added resource information for verbose visibility. * Remove stray console logs and remove events from spans. * Produce top level error output and record an error ref. code We will likely have to iterate on how the output looks. * fix experiments and webBundler resource values * Tidy telemetry/index.js * Record top level errors with legacy telemetry also * Use error exit code if it exists I wouldn't have written this myself but I seen it used in the code so I may as well keep using it. * Started to remove the handler level try/catch. Stopped because of some many edge or special cases where the CLI immediately exists with non-zero exit code. * Remove unused imports * Add back try/catch for the lint command * Update test-fixture * Revert and just hardcode the command name * Revert change to vite.config.ts * Add missing otel api dep * Make use of fs-extra json funcs * Replace require with import * Simplify the exitCode setting * Fix telemetry yargs option * Introduce background job helper function The helper ensure's we maintain the spawn options we need to support different platforms. It also ensures the output is written to a log file within the '.redwood' folder. Reworks the new telemetry background process to use this helper. Introduces some relatively verbose output to the send process now that we do not need to be quiet. * lint * refactor telemetry enabling --------- Co-authored-by: Dominic Saadi --- packages/cli-helpers/package.json | 1 + packages/cli-helpers/src/index.ts | 2 + packages/cli-helpers/src/telemetry/index.ts | 46 +++++++ packages/cli/package.json | 7 ++ packages/cli/src/commands/buildHandler.js | 33 ++--- packages/cli/src/commands/check.js | 5 + packages/cli/src/commands/consoleHandler.js | 5 + .../cli/src/commands/dataMigrate/install.js | 5 + packages/cli/src/commands/dataMigrate/up.js | 6 + packages/cli/src/commands/deploy/baremetal.js | 16 +++ packages/cli/src/commands/deploy/edgio.js | 9 ++ .../cli/src/commands/deploy/flightcontrol.js | 8 ++ packages/cli/src/commands/deploy/netlify.js | 9 +- packages/cli/src/commands/deploy/render.js | 8 ++ .../cli/src/commands/deploy/serverless.js | 10 ++ packages/cli/src/commands/deploy/vercel.js | 12 +- .../src/commands/destroy/graphiql/graphiql.js | 5 + packages/cli/src/commands/destroy/helpers.js | 5 + .../cli/src/commands/destroy/page/page.js | 5 + .../src/commands/destroy/scaffold/scaffold.js | 5 + packages/cli/src/commands/destroy/sdl/sdl.js | 5 + packages/cli/src/commands/devHandler.js | 10 ++ packages/cli/src/commands/execHandler.js | 14 ++- .../src/commands/experimental/setupInngest.js | 6 + .../experimental/setupOpentelemetry.js | 7 ++ .../src/commands/experimental/setupSentry.js | 6 + .../commands/experimental/setupServerFile.js | 7 ++ .../cli/src/commands/experimental/studio.js | 6 + packages/cli/src/commands/generate.js | 5 + .../generate/dataMigration/dataMigration.js | 8 ++ .../src/commands/generate/dbAuth/dbAuth.js | 12 ++ .../commands/generate/directive/directive.js | 8 ++ .../commands/generate/function/function.js | 7 ++ packages/cli/src/commands/generate/helpers.js | 11 ++ .../cli/src/commands/generate/model/model.js | 8 ++ .../cli/src/commands/generate/page/page.js | 11 ++ .../commands/generate/scaffold/scaffold.js | 11 ++ .../src/commands/generate/script/script.js | 7 ++ packages/cli/src/commands/generate/sdl/sdl.js | 11 ++ .../src/commands/generate/secret/secret.js | 8 ++ packages/cli/src/commands/info.js | 29 +++-- packages/cli/src/commands/lint.js | 16 ++- packages/cli/src/commands/prerenderHandler.js | 7 ++ packages/cli/src/commands/prismaHandler.js | 36 +++--- packages/cli/src/commands/record/init.js | 4 + packages/cli/src/commands/serve.js | 25 ++++ packages/cli/src/commands/setup/auth/auth.js | 54 ++++++++- .../cli/src/commands/setup/cache/cache.js | 7 ++ .../custom-web-index/custom-web-index.js | 6 + .../setup/deploy/providers/baremetal.js | 5 + .../setup/deploy/providers/coherence.js | 6 + .../commands/setup/deploy/providers/edgio.js | 4 + .../setup/deploy/providers/flightcontrol.js | 6 + .../setup/deploy/providers/netlify.js | 5 + .../commands/setup/deploy/providers/render.js | 6 + .../setup/deploy/providers/serverless.js | 5 + .../commands/setup/deploy/providers/vercel.js | 4 + .../src/commands/setup/generator/generator.js | 7 ++ .../src/commands/setup/graphiql/graphiql.js | 7 ++ packages/cli/src/commands/setup/i18n/i18n.js | 6 + .../src/commands/setup/tsconfig/tsconfig.js | 6 + .../commands/setup/ui/libraries/chakra-ui.js | 8 ++ .../commands/setup/ui/libraries/mantine.js | 9 ++ .../setup/ui/libraries/tailwindcss.js | 6 + packages/cli/src/commands/setup/vite/vite.js | 8 ++ .../cli/src/commands/setup/webpack/webpack.js | 6 + packages/cli/src/commands/testHandler.js | 64 +++++----- packages/cli/src/commands/ts-to-js.js | 4 + .../cli/src/commands/type-checkHandler.js | 67 ++++++----- packages/cli/src/commands/upgrade.js | 18 +-- packages/cli/src/index.js | 109 ++++++++++++++++- packages/cli/src/lib/background.js | 51 ++++++++ packages/cli/src/telemetry/exporter.js | 71 +++++++++++ packages/cli/src/telemetry/index.js | 64 ++++++++++ packages/cli/src/telemetry/resource.js | 113 ++++++++++++++++++ packages/cli/src/telemetry/send.js | 107 +++++++++++++++++ yarn.lock | 8 ++ 77 files changed, 1195 insertions(+), 139 deletions(-) create mode 100644 packages/cli-helpers/src/telemetry/index.ts create mode 100644 packages/cli/src/lib/background.js create mode 100644 packages/cli/src/telemetry/exporter.js create mode 100644 packages/cli/src/telemetry/index.js create mode 100644 packages/cli/src/telemetry/resource.js create mode 100644 packages/cli/src/telemetry/send.js diff --git a/packages/cli-helpers/package.json b/packages/cli-helpers/package.json index 1d7064e75f55..7bc5cf427b9e 100644 --- a/packages/cli-helpers/package.json +++ b/packages/cli-helpers/package.json @@ -24,6 +24,7 @@ "dependencies": { "@babel/core": "7.22.5", "@babel/runtime-corejs3": "7.22.5", + "@opentelemetry/api": "1.4.1", "@redwoodjs/project-config": "5.0.0", "@redwoodjs/telemetry": "5.0.0", "chalk": "4.1.2", diff --git a/packages/cli-helpers/src/index.ts b/packages/cli-helpers/src/index.ts index edda963714ba..0a7450357603 100644 --- a/packages/cli-helpers/src/index.ts +++ b/packages/cli-helpers/src/index.ts @@ -8,3 +8,5 @@ export * from './lib/project' export * from './auth/setupHelpers' export * from './lib/installHelpers' + +export * from './telemetry/index' diff --git a/packages/cli-helpers/src/telemetry/index.ts b/packages/cli-helpers/src/telemetry/index.ts new file mode 100644 index 000000000000..bcb206afa06f --- /dev/null +++ b/packages/cli-helpers/src/telemetry/index.ts @@ -0,0 +1,46 @@ +import opentelemetry, { + SpanStatusCode, + AttributeValue, + Span, +} from '@opentelemetry/api' + +type TelemetryAttributes = { + [key: string]: AttributeValue +} + +/** + * Safely records attributes to the opentelemetry span + * + * @param attributes An object of key-value pairs to be individually recorded as attributes + * @param span An optional span to record the attributes to. If not provided, the current active span will be used + */ +export function recordTelemetryAttributes( + attributes: TelemetryAttributes, + span?: Span +) { + const spanToRecord = span ?? opentelemetry.trace.getActiveSpan() + if (spanToRecord === undefined) { + return + } + for (const [key, value] of Object.entries(attributes)) { + spanToRecord.setAttribute(key, value) + } +} + +/** + * Safely records an error to the opentelemetry span + * + * @param error An error to record to the span + * @param span An optional span to record the error to. If not provided, the current active span will be used + */ +export function recordTelemetryError(error: any, span?: Span) { + const spanToRecord = span ?? opentelemetry.trace.getActiveSpan() + if (spanToRecord === undefined) { + return + } + spanToRecord.setStatus({ + code: SpanStatusCode.ERROR, + message: error.toString().split('\n')[0], + }) + spanToRecord.recordException(error) +} diff --git a/packages/cli/package.json b/packages/cli/package.json index fd533a6a5af7..8f78f5c2fb9c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,6 +30,11 @@ "dependencies": { "@babel/runtime-corejs3": "7.22.5", "@iarna/toml": "2.2.5", + "@opentelemetry/api": "1.4.1", + "@opentelemetry/exporter-trace-otlp-http": "0.40.0", + "@opentelemetry/resources": "1.14.0", + "@opentelemetry/sdk-trace-node": "1.14.0", + "@opentelemetry/semantic-conventions": "1.14.0", "@prisma/internals": "4.15.0", "@redwoodjs/api-server": "5.0.0", "@redwoodjs/cli-helpers": "5.0.0", @@ -43,6 +48,7 @@ "boxen": "5.1.2", "camelcase": "6.3.0", "chalk": "4.1.2", + "ci-info": "3.8.0", "concurrently": "8.2.0", "configstore": "3.1.5", "core-js": "3.31.0", @@ -70,6 +76,7 @@ "secure-random-password": "0.2.3", "semver": "7.5.2", "string-env-interpolation": "1.0.1", + "systeminformation": "5.18.3", "terminal-link": "2.1.1", "title-case": "3.0.3", "uuid": "9.0.0", diff --git a/packages/cli/src/commands/buildHandler.js b/packages/cli/src/commands/buildHandler.js index e889e62f25b0..0742fc0ee59e 100644 --- a/packages/cli/src/commands/buildHandler.js +++ b/packages/cli/src/commands/buildHandler.js @@ -6,13 +6,13 @@ import { Listr } from 'listr2' import { rimraf } from 'rimraf' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { buildApi } from '@redwoodjs/internal/dist/build/api' import { loadAndValidateSdls } from '@redwoodjs/internal/dist/validateSchema' import { detectPrerenderRoutes } from '@redwoodjs/prerender/detection' -import { timedTelemetry, errorTelemetry } from '@redwoodjs/telemetry' +import { timedTelemetry } from '@redwoodjs/telemetry' import { getPaths, getConfig } from '../lib' -import c from '../lib/colors' import { generatePrismaCommand } from '../lib/generatePrismaClient' export const handler = async ({ @@ -23,6 +23,15 @@ export const handler = async ({ prisma = true, prerender, }) => { + recordTelemetryAttributes({ + command: 'build', + side: JSON.stringify(side), + verbose, + performance, + stats, + prisma, + prerender, + }) const rwjsPaths = getPaths() if (performance) { @@ -152,18 +161,12 @@ export const handler = async ({ renderer: verbose && 'verbose', }) - try { - await timedTelemetry(process.argv, { type: 'build' }, async () => { - await jobs.run() + await timedTelemetry(process.argv, { type: 'build' }, async () => { + await jobs.run() - if (side.includes('web') && prerender) { - // This step is outside Listr so that it prints clearer, complete messages - await triggerPrerender() - } - }) - } catch (e) { - console.log(c.error(e.message)) - errorTelemetry(process.argv, e.message) - process.exit(1) - } + if (side.includes('web') && prerender) { + // This step is outside Listr so that it prints clearer, complete messages + await triggerPrerender() + } + }) } diff --git a/packages/cli/src/commands/check.js b/packages/cli/src/commands/check.js index 476af99dfaac..3664f5d0fcc6 100644 --- a/packages/cli/src/commands/check.js +++ b/packages/cli/src/commands/check.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../lib' import c from '../lib/colors' @@ -7,6 +9,9 @@ export const description = 'Get structural diagnostics for a Redwood project (experimental)' export const handler = () => { + recordTelemetryAttributes({ + command, + }) // Deep dive // // It seems like we have to use `require` here instead of `await import` diff --git a/packages/cli/src/commands/consoleHandler.js b/packages/cli/src/commands/consoleHandler.js index 3aa33008eaa9..0fd79e79b4a9 100644 --- a/packages/cli/src/commands/consoleHandler.js +++ b/packages/cli/src/commands/consoleHandler.js @@ -2,6 +2,7 @@ import fs from 'fs' import path from 'path' import repl from 'repl' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { registerApiSideBabelHook } from '@redwoodjs/internal/dist/build/babel/api' import { getPaths } from '../lib' @@ -35,6 +36,10 @@ const loadConsoleHistory = async (r) => { } export const handler = () => { + recordTelemetryAttributes({ + command: 'console', + }) + // Transpile on the fly registerApiSideBabelHook({ plugins: [ diff --git a/packages/cli/src/commands/dataMigrate/install.js b/packages/cli/src/commands/dataMigrate/install.js index b5b85c9a1e48..85cde8f8fd0b 100644 --- a/packages/cli/src/commands/dataMigrate/install.js +++ b/packages/cli/src/commands/dataMigrate/install.js @@ -1,5 +1,7 @@ import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'install' export const description = 'Add the RW_DataMigration model to your schema' @@ -13,6 +15,9 @@ export function builder(yargs) { } export async function handler(options) { + recordTelemetryAttributes({ + command: ['data-migrate', command].join(' '), + }) const { handler } = await import('./installHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/dataMigrate/up.js b/packages/cli/src/commands/dataMigrate/up.js index 7e7cad41681a..2f5df43149e8 100644 --- a/packages/cli/src/commands/dataMigrate/up.js +++ b/packages/cli/src/commands/dataMigrate/up.js @@ -1,5 +1,7 @@ import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../../lib' export const command = 'up' @@ -32,6 +34,10 @@ export function builder(yargs) { } export async function handler(options) { + recordTelemetryAttributes({ + command: ['data-migrate', command].join(' '), + dbFromDist: options.importDbClientFromDist, + }) const { handler } = await import('./upHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/deploy/baremetal.js b/packages/cli/src/commands/deploy/baremetal.js index 1563d2e43692..5133b5e6ff1e 100644 --- a/packages/cli/src/commands/deploy/baremetal.js +++ b/packages/cli/src/commands/deploy/baremetal.js @@ -8,6 +8,8 @@ import { env as envInterpolation } from 'string-env-interpolation' import terminalLink from 'terminal-link' import { titleCase } from 'title-case' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../../lib' import c from '../../lib/colors' @@ -684,6 +686,20 @@ export const commands = (yargs, ssh) => { } export const handler = async (yargs) => { + recordTelemetryAttributes({ + command: ['deploy', 'baremetal'].join(' '), + firstRun: yargs.firstRun, + update: yargs.update, + install: yargs.install, + migrate: yargs.migrate, + build: yargs.build, + restart: yargs.restart, + cleanup: yargs.cleanup, + maintenance: yargs.maintenance, + rollback: yargs.rollback, + verbose: yargs.verbose, + }) + const { NodeSSH } = require('node-ssh') const ssh = new NodeSSH() diff --git a/packages/cli/src/commands/deploy/edgio.js b/packages/cli/src/commands/deploy/edgio.js index 5a54f2696005..01687db3c5ea 100644 --- a/packages/cli/src/commands/deploy/edgio.js +++ b/packages/cli/src/commands/deploy/edgio.js @@ -5,6 +5,7 @@ import fs from 'fs-extra' import { omit } from 'lodash' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getPaths } from '@redwoodjs/project-config' import c from '../../lib/colors' @@ -45,6 +46,14 @@ const execaOptions = { } export const handler = async (args) => { + recordTelemetryAttributes({ + command: ['deploy', 'edgio'].join(' '), + skipInit: args.skipInit, + build: args.build, + prisma: args.prisma, + dataMigrate: args.dataMigrate, + }) + const { builder: edgioBuilder } = require('@edgio/cli/commands/deploy') const cwd = path.join(getPaths().base) diff --git a/packages/cli/src/commands/deploy/flightcontrol.js b/packages/cli/src/commands/deploy/flightcontrol.js index f16f26321657..cdb903708c3c 100644 --- a/packages/cli/src/commands/deploy/flightcontrol.js +++ b/packages/cli/src/commands/deploy/flightcontrol.js @@ -3,6 +3,7 @@ import path from 'path' import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' import { getPaths } from '../../lib' @@ -44,6 +45,13 @@ export const builder = (yargs) => { } export const handler = async ({ side, serve, prisma, dm: dataMigrate }) => { + recordTelemetryAttributes({ + command: ['deploy', 'flightcontrol'].join(' '), + side, + prisma, + dataMigrate, + serve, + }) const rwjsPaths = getPaths() const execaConfig = { diff --git a/packages/cli/src/commands/deploy/netlify.js b/packages/cli/src/commands/deploy/netlify.js index 0eaeed87ba3c..b03f25eb29c7 100644 --- a/packages/cli/src/commands/deploy/netlify.js +++ b/packages/cli/src/commands/deploy/netlify.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deployBuilder, deployHandler } from './helpers/helpers' export const command = 'netlify [...commands]' @@ -5,4 +7,9 @@ export const description = 'Build command for Netlify deploy' export const builder = (yargs) => deployBuilder(yargs) -export const handler = deployHandler +export const handler = (yargs) => { + recordTelemetryAttributes({ + command: ['deploy', 'netlify'].join(' '), + }) + deployHandler(yargs) +} diff --git a/packages/cli/src/commands/deploy/render.js b/packages/cli/src/commands/deploy/render.js index 269248d64075..ae673e2d93ba 100644 --- a/packages/cli/src/commands/deploy/render.js +++ b/packages/cli/src/commands/deploy/render.js @@ -3,6 +3,7 @@ import path from 'path' import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' import { getPaths } from '../../lib' @@ -44,6 +45,13 @@ if (process.argv.slice(2).includes('api')) { } export const handler = async ({ side, prisma, dm: dataMigrate }) => { + recordTelemetryAttributes({ + command: ['deploy', 'render'].join(' '), + side, + prisma, + dataMigrate, + }) + const rwjsPaths = getPaths() const execaConfig = { diff --git a/packages/cli/src/commands/deploy/serverless.js b/packages/cli/src/commands/deploy/serverless.js index 34eb2ec6fd46..82026552940d 100644 --- a/packages/cli/src/commands/deploy/serverless.js +++ b/packages/cli/src/commands/deploy/serverless.js @@ -9,6 +9,8 @@ import { Listr } from 'listr2' import prompts from 'prompts' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../../lib' import c from '../../lib/colors' @@ -127,6 +129,14 @@ const loadDotEnvForStage = (dotEnvPath) => { } export const handler = async (yargs) => { + recordTelemetryAttributes({ + command: ['deploy', 'serverless'].join(' '), + sides: JSON.stringify(yargs.sides), + verbose: yargs.verbose, + packOnly: yargs.packOnly, + firstRun: yargs.firstRun, + }) + const rwjsPaths = getPaths() const dotEnvPath = path.join(rwjsPaths.base, `.env.${yargs.stage}`) diff --git a/packages/cli/src/commands/deploy/vercel.js b/packages/cli/src/commands/deploy/vercel.js index 2616c9779637..04c40d3b892b 100644 --- a/packages/cli/src/commands/deploy/vercel.js +++ b/packages/cli/src/commands/deploy/vercel.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deployBuilder, deployHandler } from './helpers/helpers' export const command = 'vercel [...commands]' @@ -5,4 +7,12 @@ export const description = 'Build command for Vercel deploy' export const builder = (yargs) => deployBuilder(yargs) -export const handler = deployHandler +export const handler = (yargs) => { + recordTelemetryAttributes({ + command: ['deploy', 'vercel'].join(' '), + build: yargs.build, + prisma: yargs.prisma, + dataMigrate: yargs.dataMigrate, + }) + deployHandler(yargs) +} diff --git a/packages/cli/src/commands/destroy/graphiql/graphiql.js b/packages/cli/src/commands/destroy/graphiql/graphiql.js index 7e6b7d90c6f6..03f25bcaf3f3 100644 --- a/packages/cli/src/commands/destroy/graphiql/graphiql.js +++ b/packages/cli/src/commands/destroy/graphiql/graphiql.js @@ -1,5 +1,7 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { existsAnyExtensionSync, deleteFile, @@ -33,6 +35,9 @@ export const command = 'graphiql' export const description = 'Destroy graphiql header' export const handler = () => { + recordTelemetryAttributes({ + command: ['destory', 'graphiql'].join(' '), + }) const path = getOutputPath() const tasks = new Listr( [ diff --git a/packages/cli/src/commands/destroy/helpers.js b/packages/cli/src/commands/destroy/helpers.js index 7d7040d4cbc0..5a5618bce079 100644 --- a/packages/cli/src/commands/destroy/helpers.js +++ b/packages/cli/src/commands/destroy/helpers.js @@ -1,5 +1,7 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask } from '../../lib' import c from '../../lib/colors' @@ -32,6 +34,9 @@ export const createYargsForComponentDestroy = ({ }) }, handler: async (options) => { + recordTelemetryAttributes({ + command: ['destory', componentName].join(' '), + }) try { options = await preTasksFn({ ...options, isDestroyer: true }) await tasks({ componentName, filesFn, name: options.name }).run() diff --git a/packages/cli/src/commands/destroy/page/page.js b/packages/cli/src/commands/destroy/page/page.js index 404ca245e9ce..529678f074b8 100644 --- a/packages/cli/src/commands/destroy/page/page.js +++ b/packages/cli/src/commands/destroy/page/page.js @@ -1,6 +1,8 @@ import camelcase from 'camelcase' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask, removeRoutesFromRouterTask } from '../../../lib' import c from '../../../lib/colors' import { pathName } from '../../generate/helpers' @@ -48,6 +50,9 @@ export const tasks = ({ name, path }) => ) export const handler = async ({ name, path }) => { + recordTelemetryAttributes({ + command: ['destory', 'page'].join(' '), + }) const t = tasks({ name, path }) try { await t.run() diff --git a/packages/cli/src/commands/destroy/scaffold/scaffold.js b/packages/cli/src/commands/destroy/scaffold/scaffold.js index d04708412cc4..990438852839 100644 --- a/packages/cli/src/commands/destroy/scaffold/scaffold.js +++ b/packages/cli/src/commands/destroy/scaffold/scaffold.js @@ -1,6 +1,8 @@ import { Listr } from 'listr2' import pascalcase from 'pascalcase' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask, getPaths, @@ -111,6 +113,9 @@ export const tasks = ({ model, path, tests, nestScaffoldByModel }) => ) export const handler = async ({ model: modelArg }) => { + recordTelemetryAttributes({ + command: ['destory', 'scaffold'].join(' '), + }) const { model, path } = splitPathAndModel(modelArg) try { const { name } = await verifyModelName({ name: model, isDestroyer: true }) diff --git a/packages/cli/src/commands/destroy/sdl/sdl.js b/packages/cli/src/commands/destroy/sdl/sdl.js index cc4215ae48ad..a06ebe638767 100644 --- a/packages/cli/src/commands/destroy/sdl/sdl.js +++ b/packages/cli/src/commands/destroy/sdl/sdl.js @@ -1,5 +1,7 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask } from '../../../lib' import c from '../../../lib/colors' import { verifyModelName } from '../../../lib/schemaHelpers' @@ -31,6 +33,9 @@ export const tasks = ({ model }) => ) export const handler = async ({ model }) => { + recordTelemetryAttributes({ + command: ['destory', 'sdl'].join(' '), + }) try { const { name } = await verifyModelName({ name: model, isDestroyer: true }) await tasks({ model: name }).run() diff --git a/packages/cli/src/commands/devHandler.js b/packages/cli/src/commands/devHandler.js index 11c422061835..3e356e73688d 100644 --- a/packages/cli/src/commands/devHandler.js +++ b/packages/cli/src/commands/devHandler.js @@ -3,6 +3,7 @@ import { argv } from 'process' import concurrently from 'concurrently' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { shutdownPort } from '@redwoodjs/internal/dist/dev' import { getConfig, getConfigPath } from '@redwoodjs/project-config' import { errorTelemetry } from '@redwoodjs/telemetry' @@ -21,6 +22,15 @@ export const handler = async ({ watchNodeModules = process.env.RWJS_WATCH_NODE_MODULES === '1', apiDebugPort, }) => { + recordTelemetryAttributes({ + command: 'dev', + side: JSON.stringify(side), + // forward, // TODO: Should we record this? + generate, + watchNodeModules, + apiDebugPort, + }) + const redwoodProjectPaths = getPaths() const redwoodProjectConfig = getConfig() diff --git a/packages/cli/src/commands/execHandler.js b/packages/cli/src/commands/execHandler.js index 9c17decf202c..d068188bad69 100644 --- a/packages/cli/src/commands/execHandler.js +++ b/packages/cli/src/commands/execHandler.js @@ -2,6 +2,7 @@ import path from 'path' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { registerApiSideBabelHook } from '@redwoodjs/internal/dist/build/babel/api' import { getWebSideDefaultBabelConfig } from '@redwoodjs/internal/dist/build/babel/web' import { findScripts } from '@redwoodjs/internal/dist/files' @@ -21,6 +22,12 @@ const printAvailableScriptsToConsole = () => { } export const handler = async (args) => { + recordTelemetryAttributes({ + command: 'exec', + prisma: args.prisma, + list: args.list, + }) + const { name, prisma, list, ...scriptArgs } = args if (list || !name) { printAvailableScriptsToConsole() @@ -127,10 +134,5 @@ export const handler = async (args) => { renderer: 'verbose', }) - try { - await tasks.run() - } catch (e) { - console.error(c.error(`The script exited with errors.`)) - process.exit(e?.exitCode || 1) - } + await tasks.run() } diff --git a/packages/cli/src/commands/experimental/setupInngest.js b/packages/cli/src/commands/experimental/setupInngest.js index 9c9704a3b9de..4ff9eca7111e 100644 --- a/packages/cli/src/commands/experimental/setupInngest.js +++ b/packages/cli/src/commands/experimental/setupInngest.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getEpilogue } from './util' export const command = 'setup-inngest' @@ -19,6 +21,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + force: options.force, + }) const { handler } = await import('./setupInngestHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/experimental/setupOpentelemetry.js b/packages/cli/src/commands/experimental/setupOpentelemetry.js index 22ebd5665300..46dbd53398f9 100644 --- a/packages/cli/src/commands/experimental/setupOpentelemetry.js +++ b/packages/cli/src/commands/experimental/setupOpentelemetry.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getEpilogue } from './util' export const command = 'setup-opentelemetry' @@ -24,6 +26,11 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + force: options.force, + verbose: options.verbose, + }) const { handler } = await import('./setupOpentelemetryHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/experimental/setupSentry.js b/packages/cli/src/commands/experimental/setupSentry.js index 1a2bb4398452..e41172772c64 100644 --- a/packages/cli/src/commands/experimental/setupSentry.js +++ b/packages/cli/src/commands/experimental/setupSentry.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getEpilogue } from './util' export const command = 'setup-sentry' @@ -18,6 +20,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['experimental', 'setup-sentry'].join(' '), + force: options.force, + }) const { handler } = await import('./setupSentryHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/experimental/setupServerFile.js b/packages/cli/src/commands/experimental/setupServerFile.js index 6b1db22f2128..b96152e5bf8e 100644 --- a/packages/cli/src/commands/experimental/setupServerFile.js +++ b/packages/cli/src/commands/experimental/setupServerFile.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getEpilogue } from './util' export const EXPERIMENTAL_TOPIC_ID = 4851 @@ -24,6 +26,11 @@ export function builder(yargs) { } export async function handler(options) { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + force: options.force, + verbose: options.verbose, + }) const { handler } = await import('./setupServerFileHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/experimental/studio.js b/packages/cli/src/commands/experimental/studio.js index dc0bd5433ddf..43699a4f84b8 100644 --- a/packages/cli/src/commands/experimental/studio.js +++ b/packages/cli/src/commands/experimental/studio.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getEpilogue } from './util' export const command = 'studio' @@ -15,6 +17,10 @@ export function builder(yargs) { } export async function handler(options) { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + open: options.open, + }) const { handler } = await import('./studioHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js index c20d7ff9e43b..52373479206f 100644 --- a/packages/cli/src/commands/generate.js +++ b/packages/cli/src/commands/generate.js @@ -1,6 +1,8 @@ import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'generate ' export const aliases = ['g'] export const description = 'Generate boilerplate code and type definitions' @@ -8,6 +10,9 @@ export const description = 'Generate boilerplate code and type definitions' export const builder = (yargs) => yargs .command('types', 'Generate supplementary code', {}, () => { + recordTelemetryAttributes({ + command: ['generate', 'types'].join(' '), + }) execa.sync('yarn rw-gen', { shell: true, stdio: 'inherit' }) }) .commandDir('./generate', { diff --git a/packages/cli/src/commands/generate/dataMigration/dataMigration.js b/packages/cli/src/commands/generate/dataMigration/dataMigration.js index 7a22055bff45..8a568f47bb83 100644 --- a/packages/cli/src/commands/generate/dataMigration/dataMigration.js +++ b/packages/cli/src/commands/generate/dataMigration/dataMigration.js @@ -5,6 +5,8 @@ import { Listr } from 'listr2' import { paramCase } from 'param-case' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths, writeFilesTask } from '../../../lib' import c from '../../../lib/colors' import { prepareForRollback } from '../../../lib/rollback' @@ -63,6 +65,12 @@ export const builder = (yargs) => { } export const handler = async (args) => { + recordTelemetryAttributes({ + command: ['generate', 'data-migration'].join(' '), + force: args.force, + rollback: args.rollback, + }) + validateName(args.name) const tasks = new Listr( diff --git a/packages/cli/src/commands/generate/dbAuth/dbAuth.js b/packages/cli/src/commands/generate/dbAuth/dbAuth.js index 86a5c48f37af..03cc4228b0b5 100644 --- a/packages/cli/src/commands/generate/dbAuth/dbAuth.js +++ b/packages/cli/src/commands/generate/dbAuth/dbAuth.js @@ -7,6 +7,8 @@ import { Listr } from 'listr2' import terminalLink from 'terminal-link' import { titleCase } from 'title-case' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { addRoutesToRouterTask, addScaffoldImport, @@ -372,6 +374,16 @@ const tasks = ({ } export const handler = async (yargs) => { + recordTelemetryAttributes({ + command: ['generate', 'dbAuth'].join(' '), + skipForgot: yargs.skipForgot, + skipLogin: yargs.skipLogin, + skipReset: yargs.skipReset, + skipSignup: yargs.skipSignup, + webauthn: yargs.webauthn, + force: yargs.force, + rollback: yargs.rollback, + }) const t = tasks({ ...yargs }) try { diff --git a/packages/cli/src/commands/generate/directive/directive.js b/packages/cli/src/commands/generate/directive/directive.js index f05631817e6e..3237dfe260f0 100644 --- a/packages/cli/src/commands/generate/directive/directive.js +++ b/packages/cli/src/commands/generate/directive/directive.js @@ -5,6 +5,7 @@ import execa from 'execa' import { Listr } from 'listr2' import prompts from 'prompts' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' import { getPaths, writeFilesTask, transformTSToJS } from '../../../lib' @@ -102,6 +103,13 @@ export const { command, description, builder } = }) export const handler = async (args) => { + recordTelemetryAttributes({ + command: ['generate', 'directive'].join(' '), + type: args.type, + force: args.force, + rollback: args.rollback, + }) + const POST_RUN_INSTRUCTIONS = `Next steps...\n\n ${c.warning( 'After modifying your directive, you can add it to your SDLs e.g.:' )} diff --git a/packages/cli/src/commands/generate/function/function.js b/packages/cli/src/commands/generate/function/function.js index 9fe09488dfcb..cd92a8b7fef4 100644 --- a/packages/cli/src/commands/generate/function/function.js +++ b/packages/cli/src/commands/generate/function/function.js @@ -4,6 +4,7 @@ import camelcase from 'camelcase' import { Listr } from 'listr2' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, transformTSToJS, writeFilesTask } from '../../../lib' @@ -122,6 +123,12 @@ export const builder = (yargs) => { // This could be built using createYargsForComponentGeneration; // however, we need to add a message after generating the function files export const handler = async ({ name, force, ...rest }) => { + recordTelemetryAttributes({ + command: ['generate', 'function'].join(' '), + force, + rollback: rest.rollback, + }) + validateName(name) const tasks = new Listr( diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index 7687404f43a5..fdb7785c2a02 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -6,6 +6,7 @@ import { paramCase } from 'param-case' import pascalcase from 'pascalcase' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig, ensurePosixPath } from '@redwoodjs/project-config' import { errorTelemetry } from '@redwoodjs/telemetry' @@ -206,6 +207,16 @@ export const createYargsForComponentGeneration = ({ }) }, handler: async (options) => { + recordTelemetryAttributes({ + command: ['generate', componentName].join(' '), + tests: options.tests, + stories: options.stories, + verbose: options.verbose, + rollback: options.rollback, + force: options.force, + // TODO: This does not cover the specific options that each generator might pass in + }) + if (options.tests === undefined) { options.tests = getConfig().generate.tests } diff --git a/packages/cli/src/commands/generate/model/model.js b/packages/cli/src/commands/generate/model/model.js index df18bab1cac3..dd4b1c667e47 100644 --- a/packages/cli/src/commands/generate/model/model.js +++ b/packages/cli/src/commands/generate/model/model.js @@ -3,6 +3,8 @@ import path from 'path' import { Listr } from 'listr2' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths, writeFilesTask, generateTemplate } from '../../../lib' import c from '../../../lib/colors' import { prepareForRollback } from '../../../lib/rollback' @@ -45,6 +47,12 @@ export const builder = (yargs) => { } export const handler = async ({ force, ...args }) => { + recordTelemetryAttributes({ + command: ['generate', 'model'].join(' '), + force, + rollback: args.rollback, + }) + validateName(args.name) const tasks = new Listr( diff --git a/packages/cli/src/commands/generate/page/page.js b/packages/cli/src/commands/generate/page/page.js index 81e582f44ec2..89a5fa3983e8 100644 --- a/packages/cli/src/commands/generate/page/page.js +++ b/packages/cli/src/commands/generate/page/page.js @@ -4,6 +4,7 @@ import camelcase from 'camelcase' import { Listr } from 'listr2' import pascalcase from 'pascalcase' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { generate as generateTypes } from '@redwoodjs/internal/dist/generate/generate' import { getConfig } from '@redwoodjs/project-config' import { errorTelemetry } from '@redwoodjs/telemetry' @@ -182,6 +183,16 @@ export const handler = async ({ if (stories === undefined) { stories = getConfig().generate.stories } + + recordTelemetryAttributes({ + command: ['generate', 'page'].join(' '), + force, + tests, + stories, + typescript, + rollback, + }) + if (process.platform === 'win32') { // running `yarn rw g page home /` on Windows using GitBash // POSIX-to-Windows path conversion will kick in. diff --git a/packages/cli/src/commands/generate/scaffold/scaffold.js b/packages/cli/src/commands/generate/scaffold/scaffold.js index f5ff28a376b2..9ac9a068f0ac 100644 --- a/packages/cli/src/commands/generate/scaffold/scaffold.js +++ b/packages/cli/src/commands/generate/scaffold/scaffold.js @@ -9,6 +9,7 @@ import { paramCase } from 'param-case' import pascalcase from 'pascalcase' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { generate as generateTypes } from '@redwoodjs/internal/dist/generate/generate' import { getConfig } from '@redwoodjs/project-config' @@ -844,6 +845,16 @@ export const handler = async ({ if (tests === undefined) { tests = getConfig().generate.tests } + recordTelemetryAttributes({ + command: ['generate', 'scaffold'].join(' '), + force, + tests, + typescript, + tailwind, + docs, + rollback, + }) + const { model, path } = splitPathAndModel(modelArg) tailwind = shouldUseTailwindCSS(tailwind) diff --git a/packages/cli/src/commands/generate/script/script.js b/packages/cli/src/commands/generate/script/script.js index 1b337c68e8ce..e577cda789c7 100644 --- a/packages/cli/src/commands/generate/script/script.js +++ b/packages/cli/src/commands/generate/script/script.js @@ -4,6 +4,7 @@ import path from 'path' import { Listr } from 'listr2' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, writeFilesTask } from '../../../lib' @@ -61,6 +62,12 @@ export const builder = (yargs) => { } export const handler = async ({ force, ...args }) => { + recordTelemetryAttributes({ + command: ['generate', 'script'].join(' '), + force, + rollback: args.rollback, + }) + const POST_RUN_INSTRUCTIONS = `Next steps...\n\n ${c.warning( 'After modifying your script, you can invoke it like:' )} diff --git a/packages/cli/src/commands/generate/sdl/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js index b8e632a13721..73117ec2e4aa 100644 --- a/packages/cli/src/commands/generate/sdl/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -6,6 +6,7 @@ import chalk from 'chalk' import { Listr } from 'listr2' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { generate as generateTypes } from '@redwoodjs/internal/dist/generate/generate' import { getConfig } from '@redwoodjs/project-config' import { errorTelemetry } from '@redwoodjs/telemetry' @@ -284,6 +285,16 @@ export const handler = async ({ tests = getConfig().generate.tests } + recordTelemetryAttributes({ + command: ['generate', 'sdl'].join(' '), + crud, + force, + tests, + typescript, + docs, + rollback, + }) + try { const { name } = await verifyModelName({ name: model }) diff --git a/packages/cli/src/commands/generate/secret/secret.js b/packages/cli/src/commands/generate/secret/secret.js index 164a02207716..b0db221964a0 100644 --- a/packages/cli/src/commands/generate/secret/secret.js +++ b/packages/cli/src/commands/generate/secret/secret.js @@ -1,6 +1,8 @@ import password from 'secure-random-password' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + const DEFAULT_LENGTH = 64 export const generateSecret = (length = DEFAULT_LENGTH) => { @@ -36,6 +38,12 @@ export const builder = (yargs) => ) export const handler = ({ length, raw }) => { + recordTelemetryAttributes({ + command: ['generate', 'secret'].join(' '), + length, + raw, + }) + if (raw) { console.log(generateSecret(length)) return diff --git a/packages/cli/src/commands/info.js b/packages/cli/src/commands/info.js index b53212457b33..1a95022dc924 100644 --- a/packages/cli/src/commands/info.js +++ b/packages/cli/src/commands/info.js @@ -3,6 +3,8 @@ import envinfo from 'envinfo' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'info' export const description = 'Print your system environment information' export const builder = (yargs) => { @@ -14,19 +16,16 @@ export const builder = (yargs) => { ) } export const handler = async () => { - try { - const output = await envinfo.run({ - System: ['OS', 'Shell'], - Binaries: ['Node', 'Yarn'], - Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'], - // yarn workspaces not supported :-/ - npmPackages: '@redwoodjs/*', - Databases: ['SQLite'], - }) - console.log(output) - } catch (e) { - console.log('Error: Cannot access environment info') - console.log(e) - process.exit(1) - } + recordTelemetryAttributes({ + command, + }) + const output = await envinfo.run({ + System: ['OS', 'Shell'], + Binaries: ['Node', 'Yarn'], + Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'], + // yarn workspaces not supported :-/ + npmPackages: '@redwoodjs/*', + Databases: ['SQLite'], + }) + console.log(output) } diff --git a/packages/cli/src/commands/lint.js b/packages/cli/src/commands/lint.js index ca717873c9b4..886af9517946 100644 --- a/packages/cli/src/commands/lint.js +++ b/packages/cli/src/commands/lint.js @@ -3,8 +3,9 @@ import fs from 'fs' import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../lib' -import c from '../lib/colors' export const command = 'lint [path..]' export const description = 'Lint your files' @@ -29,6 +30,11 @@ export const builder = (yargs) => { } export const handler = async ({ path, fix }) => { + recordTelemetryAttributes({ + command, + fix, + }) + try { const pathString = path?.join(' ') const result = await execa( @@ -47,9 +53,9 @@ export const handler = async ({ path, fix }) => { stdio: 'inherit', } ) - process.exit(result.exitCode) - } catch (e) { - console.log(c.error(e.message)) - process.exit(e?.exitCode || 1) + + process.exitCode = result.exitCode + } catch (error) { + process.exitCode = error.exitCode ?? 1 } } diff --git a/packages/cli/src/commands/prerenderHandler.js b/packages/cli/src/commands/prerenderHandler.js index fedbf5c50717..6765fa9989d1 100644 --- a/packages/cli/src/commands/prerenderHandler.js +++ b/packages/cli/src/commands/prerenderHandler.js @@ -3,6 +3,7 @@ import path from 'path' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { runPrerender, writePrerenderedHtmlFile } from '@redwoodjs/prerender' import { detectPrerenderRoutes } from '@redwoodjs/prerender/detection' import { getPaths } from '@redwoodjs/project-config' @@ -274,6 +275,12 @@ const diagnosticCheck = () => { } export const handler = async ({ path: routerPath, dryRun, verbose }) => { + recordTelemetryAttributes({ + command: 'prerender', + dryRun, + verbose, + }) + const listrTasks = await getTasks(dryRun, routerPath) const tasks = new Listr(listrTasks, { diff --git a/packages/cli/src/commands/prismaHandler.js b/packages/cli/src/commands/prismaHandler.js index cf388c8436a7..9b442b090b3e 100644 --- a/packages/cli/src/commands/prismaHandler.js +++ b/packages/cli/src/commands/prismaHandler.js @@ -4,13 +4,18 @@ import path from 'path' import boxen from 'boxen' import execa from 'execa' -import { errorTelemetry } from '@redwoodjs/telemetry' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import c from '../lib/colors' import { getPaths } from '../lib/index' // eslint-disable-next-line no-unused-vars export const handler = async ({ _, $0, commands = [], ...options }) => { + recordTelemetryAttributes({ + command: 'prisma', + // TODO: Consider needing more here? + }) + const rwjsPaths = getPaths() // Prisma only supports '--help', but Redwood CLI supports `prisma help` @@ -60,24 +65,19 @@ export const handler = async ({ _, $0, commands = [], ...options }) => { console.log(c.underline('$ yarn prisma ' + args.join(' '))) console.log() - try { - execa.sync( - `"${path.join(rwjsPaths.base, 'node_modules/.bin/prisma')}"`, - args, - { - shell: true, - cwd: rwjsPaths.base, - stdio: 'inherit', - cleanup: true, - } - ) - - if (hasHelpOption || commands.length === 0) { - printWrapInfo() + execa.sync( + `"${path.join(rwjsPaths.base, 'node_modules/.bin/prisma')}"`, + args, + { + shell: true, + cwd: rwjsPaths.base, + stdio: 'inherit', + cleanup: true, } - } catch (e) { - errorTelemetry(process.argv, `Error generating prisma client: ${e.message}`) - process.exit(e?.exitCode || 1) + ) + + if (hasHelpOption || commands.length === 0) { + printWrapInfo() } } diff --git a/packages/cli/src/commands/record/init.js b/packages/cli/src/commands/record/init.js index a7122fd30123..f5d28fc08095 100644 --- a/packages/cli/src/commands/record/init.js +++ b/packages/cli/src/commands/record/init.js @@ -1,5 +1,9 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { parseDatamodel } from '@redwoodjs/record' export const handler = async () => { + recordTelemetryAttributes({ + command: 'record', + }) await parseDatamodel() } diff --git a/packages/cli/src/commands/serve.js b/packages/cli/src/commands/serve.js index 24348b413dc1..b33ddb64913f 100644 --- a/packages/cli/src/commands/serve.js +++ b/packages/cli/src/commands/serve.js @@ -5,6 +5,8 @@ import chalk from 'chalk' import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths, getConfig } from '../lib' import c from '../lib/colors' @@ -39,6 +41,13 @@ export async function builder(yargs) { socket: { type: 'string' }, }), handler: async (argv) => { + recordTelemetryAttributes({ + command, + port: argv.port, + host: argv.host, + socket: argv.socket, + }) + // Run the experimental server file, if it exists, with web side also if (hasExperimentalServerFile()) { console.log( @@ -90,6 +99,14 @@ export async function builder(yargs) { }, }), handler: async (argv) => { + recordTelemetryAttributes({ + command, + port: argv.port, + host: argv.host, + socket: argv.socket, + apiRootPath: argv.apiRootPath, + }) + // Run the experimental server file, if it exists, api side only if (hasExperimentalServerFile()) { console.log( @@ -135,6 +152,14 @@ export async function builder(yargs) { }, }), handler: async (argv) => { + recordTelemetryAttributes({ + command, + port: argv.port, + host: argv.host, + socket: argv.socket, + apiHost: argv.apiHost, + }) + const { webServerHandler } = await import('./serveHandler.js') await webServerHandler(argv) }, diff --git a/packages/cli/src/commands/setup/auth/auth.js b/packages/cli/src/commands/setup/auth/auth.js index 3a649eba54a7..197aae1e635f 100644 --- a/packages/cli/src/commands/setup/auth/auth.js +++ b/packages/cli/src/commands/setup/auth/auth.js @@ -4,7 +4,10 @@ import execa from 'execa' import fs from 'fs-extra' import terminalLink from 'terminal-link' -import { standardAuthBuilder } from '@redwoodjs/cli-helpers' +import { + recordTelemetryAttributes, + standardAuthBuilder, +} from '@redwoodjs/cli-helpers' import { getPaths } from '../../../lib/' @@ -33,6 +36,11 @@ export async function builder(yargs) { 'Set up auth for Auth0', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'auth0'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-auth0-setup') console.log() handler(args) @@ -43,6 +51,11 @@ export async function builder(yargs) { 'Set up auth for Azure Active Directory', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'azure-active-directory'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler( '@redwoodjs/auth-azure-active-directory-setup' ) @@ -55,6 +68,11 @@ export async function builder(yargs) { 'Set up auth for Clerk', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'clerk'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-clerk-setup') console.log() handler(args) @@ -65,6 +83,11 @@ export async function builder(yargs) { 'Set up a custom auth provider', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'custom'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-custom-setup') console.log() handler(args) @@ -82,6 +105,12 @@ export async function builder(yargs) { }) }, async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'dbAuth'].join(' '), + force: args.force, + verbose: args.verbose, + webauthn: args.webauthn, + }) const handler = await getAuthHandler('@redwoodjs/auth-dbauth-setup') console.log() handler(args) @@ -92,6 +121,11 @@ export async function builder(yargs) { 'Set up auth for Firebase', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'firebase'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-firebase-setup') console.log() handler(args) @@ -102,6 +136,11 @@ export async function builder(yargs) { 'Set up auth for Netlify', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'netlify'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-netlify-setup') console.log() handler(args) @@ -112,6 +151,11 @@ export async function builder(yargs) { 'Set up auth for Supabase', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'supabase'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler('@redwoodjs/auth-supabase-setup') console.log() handler(args) @@ -122,6 +166,11 @@ export async function builder(yargs) { 'Set up auth for SuperTokens', (yargs) => standardAuthBuilder(yargs), async (args) => { + recordTelemetryAttributes({ + command: ['setup', 'auth', 'supertokens'].join(' '), + force: args.force, + verbose: args.verbose, + }) const handler = await getAuthHandler( '@redwoodjs/auth-supertokens-setup' ) @@ -141,6 +190,9 @@ function redirectCommand(provider) { false, () => {}, () => { + recordTelemetryAttributes({ + command: ['setup', 'auth', provider].join(' '), + }) console.log(getRedirectMessage(provider)) }, ] diff --git a/packages/cli/src/commands/setup/cache/cache.js b/packages/cli/src/commands/setup/cache/cache.js index 2368482e3e93..8a3ab9e4ee72 100644 --- a/packages/cli/src/commands/setup/cache/cache.js +++ b/packages/cli/src/commands/setup/cache/cache.js @@ -1,5 +1,7 @@ import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'cache ' export const description = 'Sets up an init file for service caching' @@ -27,6 +29,11 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'cache'].join(' '), + client: options.client, + force: options.force, + }) const { handler } = await import('./cacheHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/custom-web-index/custom-web-index.js b/packages/cli/src/commands/setup/custom-web-index/custom-web-index.js index f33186a85ed7..3cfb541f9bda 100644 --- a/packages/cli/src/commands/setup/custom-web-index/custom-web-index.js +++ b/packages/cli/src/commands/setup/custom-web-index/custom-web-index.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'custom-web-index' export const description = @@ -13,6 +15,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'custom-web-index'].join(' '), + force: options.force, + }) const { handler } = await import('./custom-web-index-handler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/deploy/providers/baremetal.js b/packages/cli/src/commands/setup/deploy/providers/baremetal.js index 239f00476550..8751e1b296b2 100644 --- a/packages/cli/src/commands/setup/deploy/providers/baremetal.js +++ b/packages/cli/src/commands/setup/deploy/providers/baremetal.js @@ -3,6 +3,7 @@ import path from 'path' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { addPackagesTask, getPaths, printSetupNotes } from '../../../../lib' @@ -37,6 +38,10 @@ const notes = [ ] export const handler = async ({ force }) => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'baremetal'].join(' '), + force, + }) const tasks = new Listr( [ addPackagesTask({ diff --git a/packages/cli/src/commands/setup/deploy/providers/coherence.js b/packages/cli/src/commands/setup/deploy/providers/coherence.js index 52f9f55daa45..636a3a7aabc6 100644 --- a/packages/cli/src/commands/setup/deploy/providers/coherence.js +++ b/packages/cli/src/commands/setup/deploy/providers/coherence.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'coherence' export const description = 'Setup Coherence deploy' @@ -11,6 +13,10 @@ export function builder(yargs) { } export async function handler(options) { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'coherence'].join(' '), + force: options.force, + }) const { handler } = await import('./coherenceHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/deploy/providers/edgio.js b/packages/cli/src/commands/setup/deploy/providers/edgio.js index 4c8e48305446..f3b3981367ff 100644 --- a/packages/cli/src/commands/setup/deploy/providers/edgio.js +++ b/packages/cli/src/commands/setup/deploy/providers/edgio.js @@ -2,6 +2,7 @@ import fs from 'fs' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { addPackagesTask, getPaths, printSetupNotes } from '../../../../lib' @@ -36,6 +37,9 @@ const prismaBinaryTargetAdditions = () => { } export const handler = async () => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'edgio'].join(' '), + }) const tasks = new Listr( [ addPackagesTask({ diff --git a/packages/cli/src/commands/setup/deploy/providers/flightcontrol.js b/packages/cli/src/commands/setup/deploy/providers/flightcontrol.js index 8fd842bdf73d..e317a8eb8286 100644 --- a/packages/cli/src/commands/setup/deploy/providers/flightcontrol.js +++ b/packages/cli/src/commands/setup/deploy/providers/flightcontrol.js @@ -6,6 +6,7 @@ import path from 'path' import { getSchema, getConfig } from '@prisma/internals' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, writeFilesTask, printSetupNotes } from '../../../../lib' @@ -304,6 +305,11 @@ const notes = [ ] export const handler = async ({ force, database }) => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'flightcontrol'].join(' '), + force, + database, + }) const tasks = new Listr( [ { diff --git a/packages/cli/src/commands/setup/deploy/providers/netlify.js b/packages/cli/src/commands/setup/deploy/providers/netlify.js index 13adb3fd5741..926f092cdd2b 100644 --- a/packages/cli/src/commands/setup/deploy/providers/netlify.js +++ b/packages/cli/src/commands/setup/deploy/providers/netlify.js @@ -3,6 +3,7 @@ import path from 'path' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, printSetupNotes } from '../../../../lib' @@ -26,6 +27,10 @@ const notes = [ ] export const handler = async ({ force }) => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'netlify'].join(' '), + force, + }) const tasks = new Listr( [ updateApiURLTask('/.netlify/functions'), diff --git a/packages/cli/src/commands/setup/deploy/providers/render.js b/packages/cli/src/commands/setup/deploy/providers/render.js index e568c2ef3cfd..f3ecbeccf82f 100644 --- a/packages/cli/src/commands/setup/deploy/providers/render.js +++ b/packages/cli/src/commands/setup/deploy/providers/render.js @@ -5,6 +5,7 @@ import path from 'path' import { getSchema, getConfig } from '@prisma/internals' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, writeFilesTask, printSetupNotes } from '../../../../lib' @@ -89,6 +90,11 @@ const additionalFiles = [ ] export const handler = async ({ force, database }) => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'render'].join(' '), + force, + database, + }) const tasks = new Listr( [ { diff --git a/packages/cli/src/commands/setup/deploy/providers/serverless.js b/packages/cli/src/commands/setup/deploy/providers/serverless.js index 361af7dbd91d..477d9760643e 100644 --- a/packages/cli/src/commands/setup/deploy/providers/serverless.js +++ b/packages/cli/src/commands/setup/deploy/providers/serverless.js @@ -4,6 +4,7 @@ import path from 'path' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { addPackagesTask, getPaths, printSetupNotes } from '../../../../lib' @@ -95,6 +96,10 @@ const updateRedwoodTomlTask = () => { } export const handler = async ({ force }) => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'serverless'].join(' '), + force, + }) const [serverless, serverlessLift, ...rest] = projectDevPackages const tasks = new Listr( diff --git a/packages/cli/src/commands/setup/deploy/providers/vercel.js b/packages/cli/src/commands/setup/deploy/providers/vercel.js index 21706ef27a65..22c11b1ce8b7 100644 --- a/packages/cli/src/commands/setup/deploy/providers/vercel.js +++ b/packages/cli/src/commands/setup/deploy/providers/vercel.js @@ -1,6 +1,7 @@ // import terminalLink from 'terminal-link' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { printSetupNotes } from '../../../../lib' @@ -16,6 +17,9 @@ const notes = [ ] export const handler = async () => { + recordTelemetryAttributes({ + command: ['setup', 'deploy', 'vercel'].join(' '), + }) const tasks = new Listr([updateApiURLTask('/api'), printSetupNotes(notes)], { rendererOptions: { collapseSubtasks: false }, }) diff --git a/packages/cli/src/commands/setup/generator/generator.js b/packages/cli/src/commands/setup/generator/generator.js index ba9a940ccc4f..28e29602db04 100644 --- a/packages/cli/src/commands/setup/generator/generator.js +++ b/packages/cli/src/commands/setup/generator/generator.js @@ -3,6 +3,8 @@ import path from 'path' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'generator ' export const description = 'Copies generator templates locally for customization' @@ -49,6 +51,11 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'generator'].join(' '), + name: options.name, + force: options.force, + }) const { handler } = await import('./generatorHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/graphiql/graphiql.js b/packages/cli/src/commands/setup/graphiql/graphiql.js index 4a1c5f06d0b6..7ec8003a1c9a 100644 --- a/packages/cli/src/commands/setup/graphiql/graphiql.js +++ b/packages/cli/src/commands/setup/graphiql/graphiql.js @@ -1,5 +1,7 @@ import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { supportedProviders } from './supportedProviders' export const command = 'graphiql ' @@ -43,6 +45,11 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'graphiql'].join(' '), + provider: options.provider, + view: options.view, + }) const { handler } = await import('./graphiqlHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/i18n/i18n.js b/packages/cli/src/commands/setup/i18n/i18n.js index b148e9c90d61..5b54c4a5c2c1 100644 --- a/packages/cli/src/commands/setup/i18n/i18n.js +++ b/packages/cli/src/commands/setup/i18n/i18n.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'i18n' export const description = 'Set up i18n' export const builder = (yargs) => { @@ -10,6 +12,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'i18n'].join(' '), + force: options.force, + }) const { handler } = await import('./i18nHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/tsconfig/tsconfig.js b/packages/cli/src/commands/setup/tsconfig/tsconfig.js index 266750808731..770be7d0c5b1 100644 --- a/packages/cli/src/commands/setup/tsconfig/tsconfig.js +++ b/packages/cli/src/commands/setup/tsconfig/tsconfig.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'tsconfig' export const description = 'Set up tsconfig for web and api sides' @@ -12,6 +14,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'tsconfig'].join(' '), + force: options.force, + }) const { handler } = await import('./tsconfigHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/ui/libraries/chakra-ui.js b/packages/cli/src/commands/setup/ui/libraries/chakra-ui.js index 7c768e9d2314..a5708593632a 100644 --- a/packages/cli/src/commands/setup/ui/libraries/chakra-ui.js +++ b/packages/cli/src/commands/setup/ui/libraries/chakra-ui.js @@ -3,6 +3,8 @@ import path from 'path' import execa from 'execa' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths, writeFile } from '../../../../lib' import c from '../../../../lib/colors' import extendStorybookConfiguration from '../../../../lib/configureStorybook.js' @@ -34,6 +36,12 @@ export default theme ` export async function handler({ force, install }) { + recordTelemetryAttributes({ + command: ['setup', 'ui', 'chakra-ui'].join(' '), + force, + install, + }) + const rwPaths = getPaths() const packages = [ diff --git a/packages/cli/src/commands/setup/ui/libraries/mantine.js b/packages/cli/src/commands/setup/ui/libraries/mantine.js index a6c8915fa0f6..e19bba37ec23 100644 --- a/packages/cli/src/commands/setup/ui/libraries/mantine.js +++ b/packages/cli/src/commands/setup/ui/libraries/mantine.js @@ -3,6 +3,8 @@ import path from 'path' import execa from 'execa' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths, writeFile } from '../../../../lib' import c from '../../../../lib/colors' import extendStorybookConfiguration from '../../../../lib/configureStorybook.js' @@ -54,6 +56,13 @@ export function builder(yargs) { } export async function handler({ force, install, packages }) { + recordTelemetryAttributes({ + command: ['setup', 'ui', 'mantine'].join(' '), + force, + install, + packages, + }) + const rwPaths = getPaths() const configFilePath = path.join(rwPaths.web.config, 'mantine.config.js') diff --git a/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js b/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js index bae076e3d4b0..3c997c8dbab8 100644 --- a/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js +++ b/packages/cli/src/commands/setup/ui/libraries/tailwindcss.js @@ -5,6 +5,7 @@ import execa from 'execa' import { outputFileSync } from 'fs-extra' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths, usingVSCode } from '../../../../lib' @@ -57,6 +58,11 @@ const tailwindImportsAndNotes = [ ] export const handler = async ({ force, install }) => { + recordTelemetryAttributes({ + command: ['setup', 'ui', 'tailwindcss'].join(' '), + force, + install, + }) const rwPaths = getPaths() const projectPackages = ['prettier-plugin-tailwindcss'] diff --git a/packages/cli/src/commands/setup/vite/vite.js b/packages/cli/src/commands/setup/vite/vite.js index 9cd54430549d..ce60693d23ba 100644 --- a/packages/cli/src/commands/setup/vite/vite.js +++ b/packages/cli/src/commands/setup/vite/vite.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'vite' export const description = @@ -25,6 +27,12 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'vite'].join(' '), + force: options.force, + verbose: options.verbose, + addPackage: options.addPackage, + }) const { handler } = await import('./viteHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/setup/webpack/webpack.js b/packages/cli/src/commands/setup/webpack/webpack.js index 6cd3fa64c5f4..e2021bcfd6a0 100644 --- a/packages/cli/src/commands/setup/webpack/webpack.js +++ b/packages/cli/src/commands/setup/webpack/webpack.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'webpack' export const description = 'Set up webpack in your project so you can add custom config' @@ -11,6 +13,10 @@ export const builder = (yargs) => { } export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['setup', 'webpack'].join(' '), + force: options.force, + }) const { handler } = await import('./webpackHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/testHandler.js b/packages/cli/src/commands/testHandler.js index d604ecfb5635..ce7ae80d5f45 100644 --- a/packages/cli/src/commands/testHandler.js +++ b/packages/cli/src/commands/testHandler.js @@ -3,6 +3,7 @@ import path from 'path' import execa from 'execa' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { ensurePosixPath } from '@redwoodjs/project-config' import { errorTelemetry, timedTelemetry } from '@redwoodjs/telemetry' @@ -61,6 +62,13 @@ export const handler = async ({ dbPush = true, ...others }) => { + recordTelemetryAttributes({ + command: 'test', + // TODO: filter (likely needs sanitised), + watch, + collectCoverage, + dbPush, + }) const rwjsPaths = getPaths() const forwardJestFlags = Object.keys(others).flatMap((flagName) => { if ( @@ -125,40 +133,32 @@ export const handler = async ({ //checking if Jest config files exists in each of the sides isJestConfigFile(sides) - try { - const cacheDirDb = `file:${ensurePosixPath( - rwjsPaths.generated.base - )}/test.db` - const DATABASE_URL = process.env.TEST_DATABASE_URL || cacheDirDb - - if (sides.includes('api') && !dbPush) { - // @NOTE - // DB push code now lives in packages/testing/config/jest/api/jest-preset.js - process.env.SKIP_DB_PUSH = '1' - } + const cacheDirDb = `file:${ensurePosixPath(rwjsPaths.generated.base)}/test.db` + const DATABASE_URL = process.env.TEST_DATABASE_URL || cacheDirDb - // **NOTE** There is no official way to run Jest programmatically, - // so we're running it via execa, since `jest.run()` is a bit unstable. - // https://github.com/facebook/jest/issues/5048 - const runCommand = async () => { - await execa('yarn jest', jestArgs, { - cwd: rwjsPaths.base, - shell: true, - stdio: 'inherit', - env: { DATABASE_URL }, - }) - } + if (sides.includes('api') && !dbPush) { + // @NOTE + // DB push code now lives in packages/testing/config/jest/api/jest-preset.js + process.env.SKIP_DB_PUSH = '1' + } - if (watch) { + // **NOTE** There is no official way to run Jest programmatically, + // so we're running it via execa, since `jest.run()` is a bit unstable. + // https://github.com/facebook/jest/issues/5048 + const runCommand = async () => { + await execa('yarn jest', jestArgs, { + cwd: rwjsPaths.base, + shell: true, + stdio: 'inherit', + env: { DATABASE_URL }, + }) + } + + if (watch) { + await runCommand() + } else { + await timedTelemetry(process.argv, { type: 'test' }, async () => { await runCommand() - } else { - await timedTelemetry(process.argv, { type: 'test' }, async () => { - await runCommand() - }) - } - } catch (e) { - // Errors already shown from execa inherited stderr - errorTelemetry(process.argv, e.message) - process.exit(e?.exitCode || 1) + }) } } diff --git a/packages/cli/src/commands/ts-to-js.js b/packages/cli/src/commands/ts-to-js.js index 3a9d6cf4449c..8bac567e9405 100644 --- a/packages/cli/src/commands/ts-to-js.js +++ b/packages/cli/src/commands/ts-to-js.js @@ -1,3 +1,4 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { convertTsProjectToJs, convertTsScriptsToJs, @@ -9,6 +10,9 @@ export const description = '[DEPRECATED]\n' + 'Convert a TypeScript project to JavaScript' export const handler = () => { + recordTelemetryAttributes({ + command, + }) convertTsProjectToJs(getPaths().base) convertTsScriptsToJs(getPaths().base) } diff --git a/packages/cli/src/commands/type-checkHandler.js b/packages/cli/src/commands/type-checkHandler.js index 3ce634673a85..32d9ba285755 100644 --- a/packages/cli/src/commands/type-checkHandler.js +++ b/packages/cli/src/commands/type-checkHandler.js @@ -4,14 +4,21 @@ import concurrently from 'concurrently' import execa from 'execa' import { Listr } from 'listr2' -import { errorTelemetry } from '@redwoodjs/telemetry' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getCmdMajorVersion } from '../commands/upgrade' import { getPaths } from '../lib' -import c from '../lib/colors' import { generatePrismaClient } from '../lib/generatePrismaClient' export const handler = async ({ sides, verbose, prisma, generate }) => { + recordTelemetryAttributes({ + command: 'type-check', + sides: JSON.stringify(sides), + verbose, + prisma, + generate, + }) + /** * Check types for the project directory : [web, api] */ @@ -50,37 +57,33 @@ export const handler = async ({ sides, verbose, prisma, generate }) => { return conclusiveExitCode } - try { - if (generate && prisma) { - await generatePrismaClient({ - verbose: verbose, - schema: getPaths().api.dbSchema, - }) - } - if (generate) { - await new Listr( - [ - { - title: 'Generating types', - task: () => - execa('yarn rw-gen', { - shell: true, - stdio: verbose ? 'inherit' : 'ignore', - }), - }, - ], + if (generate && prisma) { + await generatePrismaClient({ + verbose: verbose, + schema: getPaths().api.dbSchema, + }) + } + if (generate) { + await new Listr( + [ { - renderer: verbose && 'verbose', - rendererOptions: { collapseSubtasks: false }, - } - ).run() - } + title: 'Generating types', + task: () => + execa('yarn rw-gen', { + shell: true, + stdio: verbose ? 'inherit' : 'ignore', + }), + }, + ], + { + renderer: verbose && 'verbose', + rendererOptions: { collapseSubtasks: false }, + } + ).run() + } - const exitCode = await typeCheck() - exitCode > 0 && process.exit(exitCode) - } catch (e) { - errorTelemetry(process.argv, e.message) - console.log(c.error(e.message)) - process.exit(e?.exitCode || 1) + const exitCode = await typeCheck() + if (exitCode > 0) { + process.exitCode = exitCode } } diff --git a/packages/cli/src/commands/upgrade.js b/packages/cli/src/commands/upgrade.js index 200636922330..a74ee4e2cbe2 100644 --- a/packages/cli/src/commands/upgrade.js +++ b/packages/cli/src/commands/upgrade.js @@ -6,8 +6,8 @@ import latestVersion from 'latest-version' import { Listr } from 'listr2' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' -import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths } from '../lib' import c from '../lib/colors' @@ -90,6 +90,14 @@ export const validateTag = (tag) => { } export const handler = async ({ dryRun, tag, verbose, dedupe }) => { + recordTelemetryAttributes({ + command, + dryRun, + tag, + verbose, + dedupe, + }) + // structuring as nested tasks to avoid bug with task.title causing duplicates const tasks = new Listr( [ @@ -169,13 +177,7 @@ export const handler = async ({ dryRun, tag, verbose, dedupe }) => { } ) - try { - await tasks.run() - } catch (e) { - errorTelemetry(process.argv, e.message) - console.error(c.error(e.message)) - process.exit(e?.exitCode || 1) - } + await tasks.run() } async function yarnInstall({ verbose }) { const yarnVersion = await getCmdMajorVersion('yarn') diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js index 0a772acdc6e0..f9cd076fb785 100644 --- a/packages/cli/src/index.js +++ b/packages/cli/src/index.js @@ -3,11 +3,19 @@ import fs from 'fs' import path from 'path' +import { trace, SpanStatusCode } from '@opentelemetry/api' +import boxen from 'boxen' import { config } from 'dotenv-defaults' +import terminalLink from 'terminal-link' +import { v4 as uuidv4 } from 'uuid' import { hideBin, Parser } from 'yargs/helpers' import yargs from 'yargs/yargs' -import { telemetryMiddleware } from '@redwoodjs/telemetry' +import { + recordTelemetryError, + recordTelemetryAttributes, +} from '@redwoodjs/cli-helpers' +import { telemetryMiddleware, errorTelemetry } from '@redwoodjs/telemetry' import * as buildCommand from './commands/build' import * as checkCommand from './commands/check' @@ -33,6 +41,7 @@ import * as upgradeCommand from './commands/upgrade' import { getPaths, findUp } from './lib' import * as updateCheck from './lib/updateCheck' import { loadPlugins } from './plugin' +import { startTelemetry, shutdownTelemetry } from './telemetry/index' // # Setting the CWD // @@ -56,7 +65,17 @@ import { loadPlugins } from './plugin' // yarn rw info // ``` -let { cwd } = Parser(hideBin(process.argv)) +// Telemetry is enabled by default, but can be disabled in two ways +// - by passing a `--telemetry false` option +// - by setting a `REDWOOD_DISABLE_TELEMETRY` env var +let { cwd, telemetry } = Parser(hideBin(process.argv), { + boolean: ['telemetry'], + default: { + telemetry: + process.env.REDWOOD_DISABLE_TELEMETRY === undefined || + process.env.REDWOOD_DISABLE_TELEMETRY === '', + }, +}) cwd ??= process.env.RWJS_CWD try { @@ -97,6 +116,73 @@ config({ multiline: true, }) +async function main() { + // Start telemetry if it hasn't been disabled + if (telemetry) { + try { + await startTelemetry() + } catch (error) { + console.error('Telemetry startup error') + console.error(error) + } + } + + // Execute CLI within a span, this will be the root span + const tracer = trace.getTracer('redwoodjs') + await tracer.startActiveSpan('cli', async (span) => { + try { + // Run the command via yargs + await runYargs() + + // Span housekeeping + span?.setStatus({ code: SpanStatusCode.OK }) + } catch (error) { + const errorReferenceCode = uuidv4() + const errorMessage = [ + error.stack ?? error.toString(), + '', + '-'.repeat(process.stdout.columns - 8), + '', + 'Need help?', + ` - Not sure about something or need advice? Reach out on our ${terminalLink( + 'Forum', + 'https://community.redwoodjs.com/' + )}`, + ` - Think you've found a bug? Open an issue on our ${terminalLink( + 'GitHub', + 'https://github.com/redwoodjs/redwood' + )}`, + ` - Here's your unique error reference: '${errorReferenceCode}'`, + ].join('\n') + console.error( + boxen(errorMessage, { + padding: 1, + borderColor: 'red', + title: `Unexpected Error`, + titleAlignment: 'left', + }) + ) + + recordTelemetryError(error) + recordTelemetryAttributes({ errorReferenceCode }) + process.exitCode = error.exitCode ?? 1 + + // Legacy telemetry + errorTelemetry(process.argv, error.message) + } + + span?.end() + }) + + // Shutdown telemetry, ensures data is sent before the process exits + try { + await shutdownTelemetry() + } catch (error) { + console.error('Telemetry shutdown error') + console.error(error) + } +} + async function runYargs() { // # Build the CLI yargs instance const yarg = yargs(hideBin(process.argv)) @@ -106,22 +192,30 @@ async function runYargs() { [ // We've already handled `cwd` above, but it may still be in `argv`. // We don't need it anymore so let's get rid of it. + // Likewise for `telemetry`. (argv) => { delete argv.cwd + delete argv.telemetry }, - telemetryMiddleware, + telemetry && telemetryMiddleware, updateCheck.isEnabled() && updateCheck.updateCheckMiddleware, ].filter(Boolean) ) .option('cwd', { describe: 'Working directory to use (where `redwood.toml` is located)', }) + .option('telemetry', { + describe: 'Whether to send anonymous usage telemetry to RedwoodJS', + boolean: true, + // hidden: true, + }) .example( 'yarn rw g page home /', "\"Create a page component named 'Home' at path '/'\"" ) .demandCommand() .strict() + .exitProcess(false) // Commands (Built in or pre-plugin support) .command(buildCommand) @@ -150,7 +244,12 @@ async function runYargs() { await loadPlugins(yarg) // Run - yarg.parse() + await yarg.parse(process.argv.slice(2), {}, (_err, _argv, output) => { + // Show the output that yargs was going to if there was no callback provided + if (output) { + console.log(output) + } + }) } -runYargs() +main() diff --git a/packages/cli/src/lib/background.js b/packages/cli/src/lib/background.js new file mode 100644 index 000000000000..44d38c27eb7a --- /dev/null +++ b/packages/cli/src/lib/background.js @@ -0,0 +1,51 @@ +import { spawn } from 'child_process' +import os from 'os' +import path from 'path' + +import fs from 'fs-extra' + +import { getPaths } from '@redwoodjs/project-config' + +/** + * Spawn a background process with the stdout/stderr redirected to log files within the `.redwood` directory. + * Stdin will not be available to the process as it will be set to the 'ignore' value. + * + * @param {string} name A name for this background process, will be used to name the log files + * @param {string} cmd Command to pass to the `spawn` function + * @param {string[]} args Arguements to pass to the `spawn` function + */ +export function spawnBackgroundProcess(name, cmd, args) { + const logDirectory = path.join(getPaths().generated.base, 'logs') + fs.ensureDirSync(logDirectory) + + const safeName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + + const stdout = fs.openSync( + path.join(logDirectory, `${safeName}.out.log`), + 'w' + ) + const stderr = fs.openSync( + path.join(logDirectory, `${safeName}.err.log`), + 'w' + ) + + // We must account for some platform specific behaviour + const spawnOptions = + os.type() === 'Windows_NT' + ? { + // The following options run the process in the background without a console window, even though they don't look like they would. + // See https://github.com/nodejs/node/issues/21825#issuecomment-503766781 for information + detached: false, + windowsHide: false, + shell: true, + stdio: ['ignore', stdout, stderr], + } + : { + detached: true, + stdio: ['ignore', stdout, stderr], + } + + // Spawn and detach the process + const child = spawn(cmd, args, spawnOptions) + child.unref() +} diff --git a/packages/cli/src/telemetry/exporter.js b/packages/cli/src/telemetry/exporter.js new file mode 100644 index 000000000000..03e6054ce4f7 --- /dev/null +++ b/packages/cli/src/telemetry/exporter.js @@ -0,0 +1,71 @@ +import path from 'path' + +import fs from 'fs-extra' + +import { getPaths } from '@redwoodjs/project-config' + +/** + * Custom exporter which writes spans to a file inside of .redwood/spans + */ +export class CustomFileExporter { + /** + * @type string + * @private + */ + #storageFileName + + /** + * @type fs.WriteStream + * @private + */ + #storageFileStream + + constructor() { + this.#storageFileName = `${Date.now()}.json` + + // Ensure the path exists + const storageFilePath = path.join( + getPaths().generated.base, + 'telemetry', + this.#storageFileName + ) + fs.ensureDirSync(path.dirname(storageFilePath)) + + // Open the file for writing, open a JSON array + this.#storageFileStream = fs.createWriteStream(storageFilePath, { + flags: 'w', + autoClose: false, + }) + this.#storageFileStream.write('[') + } + + /** + * Called to export sampled {@link ReadableSpan}s. + * @param spans the list of sampled Spans to be exported. + */ + export(spans, resultCallback) { + for (let i = 0; i < spans.length; i++) { + const span = spans[i] + delete span['_spanProcessor'] // This is a circular reference and will cause issues with JSON.stringify + this.#storageFileStream.write(JSON.stringify(span, undefined, 2)) + if (i < spans.length - 1) { + this.#storageFileStream.write(',') + } + } + resultCallback({ code: 0 }) + } + + /** Stops the exporter. */ + shutdown() { + // Close the JSON array and then close the file + if (this.#storageFileStream.writable) { + this.#storageFileStream.write(']') + this.#storageFileStream.close() + } + } + + /** Immediately export all spans */ + forceFlush() { + // Do nothing + } +} diff --git a/packages/cli/src/telemetry/index.js b/packages/cli/src/telemetry/index.js new file mode 100644 index 000000000000..c485fa307bae --- /dev/null +++ b/packages/cli/src/telemetry/index.js @@ -0,0 +1,64 @@ +import path from 'path' + +import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api' +import opentelemetry from '@opentelemetry/api' +import { + NodeTracerProvider, + BatchSpanProcessor, +} from '@opentelemetry/sdk-trace-node' + +import { spawnBackgroundProcess } from '../lib/background' + +import { CustomFileExporter } from './exporter' + +/** + * @type NodeTracerProvider + */ +let traceProvider + +/** + * @type BatchSpanProcessor + */ +let traceProcessor + +/** + * @type CustomFileExporter + */ +let traceExporter + +export async function startTelemetry() { + diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR) + + // Tracing + traceProvider = new NodeTracerProvider() + traceExporter = new CustomFileExporter() + traceProcessor = new BatchSpanProcessor(traceExporter) + traceProvider.addSpanProcessor(traceProcessor) + traceProvider.register() + + process.on('SIGTERM', async () => { + await shutdownTelemetry() + }) + process.on('SIGINT', async () => { + // TODO: Should we record a SIGINT as a telemetry event? + await shutdownTelemetry() + }) +} + +export async function shutdownTelemetry() { + try { + opentelemetry.trace.getActiveSpan()?.end() + await traceProvider?.shutdown() + await traceProcessor?.shutdown() + traceExporter?.shutdown() + + // Send the telemetry in a background process, so we don't block the CLI + spawnBackgroundProcess('telemetry', 'yarn', [ + 'node', + path.join(__dirname, 'send.js'), + ]) + } catch (error) { + console.error('Telemetry error') + console.error(error) + } +} diff --git a/packages/cli/src/telemetry/resource.js b/packages/cli/src/telemetry/resource.js new file mode 100644 index 000000000000..42060d7eecb7 --- /dev/null +++ b/packages/cli/src/telemetry/resource.js @@ -0,0 +1,113 @@ +import path from 'path' + +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' +import ci from 'ci-info' +import envinfo from 'envinfo' +import fs from 'fs-extra' +import system from 'systeminformation' +import { v4 as uuidv4, validate as validateUUID } from 'uuid' + +import { getPaths, getConfig, getRawConfig } from '@redwoodjs/project-config' +import { DefaultHost } from '@redwoodjs/structure/dist/hosts' +import { RWProject } from '@redwoodjs/structure/dist/model/RWProject' + +import { name as packageName, version as packageVersion } from '../../package' + +export async function getResources() { + // Read the UUID from the file within .redwood or generate a new one if it doesn't exist + // or if it is too old + let UID = uuidv4() + try { + const telemetryFile = path.join(getPaths().generated.base, 'telemetry.txt') + if (!fs.existsSync(telemetryFile)) { + fs.ensureFileSync(telemetryFile) + } + if (fs.statSync(telemetryFile).mtimeMs < Date.now() - 86400000) { + // 86400000 is 24 hours in milliseconds, we rotate the UID every 24 hours + fs.writeFileSync(telemetryFile, UID) + } else { + const storedUID = fs.readFileSync(telemetryFile, { encoding: 'utf8' }) + if (storedUID && validateUUID(storedUID)) { + UID = storedUID + } else { + fs.writeFileSync(telemetryFile, UID) + } + } + } catch (_error) { + // We can ignore any errors here, we'll just use the generated UID in this case + } + + const info = JSON.parse( + await envinfo.run( + { + System: ['OS', 'Shell'], + Binaries: ['Node', 'Yarn', 'npm'], + npmPackages: '@redwoodjs/*', + IDEs: ['VSCode'], + }, + { json: true } + ) + ) + + // get shell name instead of path + const shell = info.System?.Shell // Windows doesn't always provide shell info, I guess + if (shell?.path?.match('/')) { + info.System.Shell.name = info.System.Shell.path.split('/').pop() + } else if (shell?.path.match('\\')) { + info.System.Shell.name = info.System.Shell.path.split('\\').pop() + } + const cpu = await system.cpu() + const mem = await system.mem() + + // Must only call getConfig() once the project is setup - so not within telemetry for CRWA + // Default to 'webpack' for new projects + const webBundler = getConfig().web.bundler + + // Returns a list of all enabled experiments + // This detects all top level [experimental.X] and returns all X's, ignoring all Y's for any [experimental.X.Y] + const experiments = Object.keys(getRawConfig()['experimental'] || {}) + + // NOTE: Added this way to avoid the need to disturb the existing toml structure + if (webBundler !== 'webpack') { + experiments.push(webBundler) + } + + // Project complexity metric + const project = new RWProject({ + host: new DefaultHost(), + projectRoot: getPaths().base, + }) + + const routes = project.getRouter().routes + const prerenderedRoutes = routes.filter((route) => route.hasPrerender) + const complexity = [ + routes.length, + prerenderedRoutes.length, + project.services.length, + project.cells.length, + project.pages.length, + ].join('.') + const sides = project.sides.join(',') + + return { + [SemanticResourceAttributes.SERVICE_NAME]: packageName, + [SemanticResourceAttributes.SERVICE_VERSION]: packageVersion, + [SemanticResourceAttributes.OS_TYPE]: info.System?.OS?.split(' ')[0], + [SemanticResourceAttributes.OS_VERSION]: info.System?.OS?.split(' ')[1], + 'shell.name': info.System?.Shell?.name, + 'node.version': info.Binaries?.Node?.version, + 'yarn.version': info.Binaries?.Yarn?.version, + 'npm.version': info.Binaries?.npm?.version, + 'vscode.version': info.IDEs?.VSCode?.version, + 'cpu.count': cpu.physicalCores, + 'memory.gb': Math.round(mem.total / 1073741824), + 'env.node_env': process.env.NODE_ENV || null, + 'ci.redwood': !!process.env.REDWOOD_CI, + 'ci.isci': ci.isCI, + complexity, + sides, + experiments: JSON.stringify(experiments), + webBundler, + uid: UID, + } +} diff --git a/packages/cli/src/telemetry/send.js b/packages/cli/src/telemetry/send.js new file mode 100644 index 000000000000..2bca22f97fad --- /dev/null +++ b/packages/cli/src/telemetry/send.js @@ -0,0 +1,107 @@ +import path from 'path' + +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' +import { Resource } from '@opentelemetry/resources' +import fs from 'fs-extra' + +import { getPaths } from '@redwoodjs/project-config' + +import { getResources } from './resource' + +async function main() { + // Get all telemetry files + const telemetryDir = path.join(getPaths().generated.base, 'telemetry') + fs.ensureDirSync(telemetryDir) + const telemetryFiles = fs.readdirSync( + path.join(getPaths().generated.base, 'telemetry') + ) + + // Compute all the resource information + console.time('Computed resource information') + const customResourceData = await getResources() + console.timeEnd('Computed resource information') + const resource = Resource.default().merge(new Resource(customResourceData)) + + const url = + process.env.REDWOOD_REDIRECT_TELEMETRY || + 'https://quark.quantumparticle.io/v1/traces' + const traceExporter = new OTLPTraceExporter({ + url, + }) + console.log(`Sending telemetry data to '${url}'`) + + // Go through all telemetry files and send the new spans to the telemetry collector + for (const [index, file] of telemetryFiles.entries()) { + // '_' denotes a file that has already been sent + if (file.startsWith('_')) { + continue + } + console.log(`Sending data from telemetry file '${file}'`) + + // Read the saved spans + const spans = fs.readJSONSync(path.join(telemetryDir, file)) + + /** + * We have to fix some of the span properties because we serialized the span + * to JSON and then deserialized it. This means that some of the properties that + * were functions are now just objects and that some of the properties were + * renamed. + */ + for (const span of spans) { + span.resource = resource + span.attributes ??= span._attributes ?? {} + span.spanContext = () => span._spanContext + + // This is only for visibility - we current do not record any events on the backend anyway. + // We do this for the time being because we don't want unsanitized error messages to be sent + span.events = [] + } + + traceExporter.export(spans, ({ code, error }) => { + if (code !== 0) { + console.error('Encountered:') + console.error(error) + console.error('while exporting the following spans:') + console.error(spans) + } + }) + + /** + * We have to rewrite the file because we recomputed the resource information + * and we also denote that the spans have been sent by adding a '_' prefix to + * the file name. + */ + fs.writeJSONSync(path.join(telemetryDir, `_${file}`), spans, { spaces: 2 }) + fs.unlinkSync(path.join(telemetryDir, file)) + telemetryFiles[index] = `_${file}` + } + + // Shutdown to ensure all spans are sent + traceExporter.shutdown() + + // Verbose means we keep the last 8 telemetry files as a log otherwise we delete all of them + if (process.env.REDWOOD_VERBOSE_TELEMETRY) { + console.log( + "Keeping only the last 8 telemetry files for inspection because 'REDWOOD_VERBOSE_TELEMETRY' is set." + ) + const sortedTelemetryFiles = telemetryFiles.sort((a, b) => { + return ( + parseInt(b.split('.')[0].replace('_', '')) - + parseInt(a.split('.')[0].replace('_', '')) + ) + }) + for (let i = 8; i < sortedTelemetryFiles.length; i++) { + console.log(`Removing telemetry file '${sortedTelemetryFiles[i]}'`) + fs.unlinkSync(path.join(telemetryDir, sortedTelemetryFiles[i])) + } + } else { + console.log( + "Removing telemetry files, set 'REDWOOD_VERBOSE_TELEMETRY' to keep the last 8 telemetry files for inspection." + ) + telemetryFiles.forEach((file) => { + console.log(`Removing telemetry file '${file}'`) + fs.unlinkSync(path.join(telemetryDir, file)) + }) + } +} +main() diff --git a/yarn.lock b/yarn.lock index 179d19f8f5de..74a258edf3f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7368,6 +7368,7 @@ __metadata: "@babel/cli": 7.22.5 "@babel/core": 7.22.5 "@babel/runtime-corejs3": 7.22.5 + "@opentelemetry/api": 1.4.1 "@redwoodjs/project-config": 5.0.0 "@redwoodjs/telemetry": 5.0.0 "@types/lodash": 4.14.195 @@ -7418,6 +7419,11 @@ __metadata: "@babel/core": 7.22.5 "@babel/runtime-corejs3": 7.22.5 "@iarna/toml": 2.2.5 + "@opentelemetry/api": 1.4.1 + "@opentelemetry/exporter-trace-otlp-http": 0.40.0 + "@opentelemetry/resources": 1.14.0 + "@opentelemetry/sdk-trace-node": 1.14.0 + "@opentelemetry/semantic-conventions": 1.14.0 "@prisma/internals": 4.15.0 "@redwoodjs/api-server": 5.0.0 "@redwoodjs/cli-helpers": 5.0.0 @@ -7432,6 +7438,7 @@ __metadata: boxen: 5.1.2 camelcase: 6.3.0 chalk: 4.1.2 + ci-info: 3.8.0 concurrently: 8.2.0 configstore: 3.1.5 core-js: 3.31.0 @@ -7460,6 +7467,7 @@ __metadata: secure-random-password: 0.2.3 semver: 7.5.2 string-env-interpolation: 1.0.1 + systeminformation: 5.18.3 terminal-link: 2.1.1 title-case: 3.0.3 typescript: 5.1.3