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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,9 @@
},
"dependencies": {
"@nuxt/kit": "^3.20.2",
"citty": "^0.1.6",
"citty": "^0.2.0",
"consola": "^3.4.2",
"destr": "^2.0.5",
"dotenv": "^17.2.3",
"git-url-parse": "^16.1.0",
"is-docker": "^4.0.0",
"ofetch": "^1.5.1",
"package-manager-detector": "^1.6.0",
"pathe": "^2.0.3",
"rc9": "^2.1.2",
"std-env": "^3.10.0"
},
Expand All @@ -62,7 +56,6 @@
"@nuxt/module-builder": "^1.0.2",
"@nuxt/schema": "^3.20.2",
"@nuxt/test-utils": "^3.23.0",
"@types/git-url-parse": "^16.0.2",
"@vitest/coverage-v8": "^4.0.17",
"changelogen": "^0.6.2",
"eslint": "^9.39.2",
Expand All @@ -80,6 +73,6 @@
"@nuxt/telemetry": "workspace:*"
},
"engines": {
"node": ">=20.0.0"
"node": ">=18.12.0"
}
Comment thread
danielroe marked this conversation as resolved.
}
99 changes: 9 additions & 90 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 27 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
import { existsSync, readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { resolve } from 'node:path'

import { resolve } from 'pathe'
import { destr } from 'destr'
import * as rc from 'rc9'
import { colors as c } from 'consola/utils'
import { consola } from 'consola'
import { loadNuxtConfig } from '@nuxt/kit'
import { isTest } from 'std-env'
import { parse as parseDotenv } from 'dotenv'
import { createMain, defineCommand } from 'citty'

import { version } from '../package.json'
import { consentVersion } from './meta'
import { ensureUserconsent } from './consent'

function isTruthy(val: unknown): boolean {
return val === true || val === 'true' || val === '1' || val === 1
}
Comment on lines +16 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

isTruthy doesn't handle case-insensitive variants like 'TRUE' or 'True'.

Environment variables are commonly set with varying cases. A user writing NUXT_TELEMETRY_DISABLED=TRUE would bypass this check. Consider normalizing:

Suggested fix
 function isTruthy(val: unknown): boolean {
-  return val === true || val === 'true' || val === '1' || val === 1
+  return val === true || val === 1 || (typeof val === 'string' && (val.toLowerCase() === 'true' || val === '1'))
 }
πŸ€– Prompt for AI Agents
In `@src/cli.ts` around lines 16 - 18, The isTruthy function currently only
matches exact case-sensitive values and misses variants like "TRUE" or "True";
update isTruthy to normalize string inputs (e.g., convert to lowercase) before
comparison and handle numeric values consistently: for non-boolean inputs coerce
to string, .toLowerCase() it, and compare against 'true' and '1', while still
accepting boolean true and numeric 1; modify the isTruthy function so it first
checks typeof val and uses a lowercase string comparison for case-insensitive
matches.


function parseDotenv(src: string): Record<string, string> {
const result: Record<string, string> = {}
for (const line of src.split('\n')) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith('#')) continue
const eqIndex = trimmed.indexOf('=')
if (eqIndex === -1) continue
const key = trimmed.slice(0, eqIndex).trim()
let value = trimmed.slice(eqIndex + 1).trim()
// Remove surrounding quotes
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''))) {
value = value.slice(1, -1)
}
result[key] = value
}
return result
}
Comment on lines +20 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

parseDotenv doesn't handle export prefix or inline comments.

Two common .env patterns supported by the dotenv library this replaces are missing:

  1. export prefix: export NUXT_TELEMETRY_DISABLED=true would produce the key export NUXT_TELEMETRY_DISABLED, never matching the expected key.
  2. Inline comments: NUXT_TELEMETRY_DISABLED=true # enable this would store the value as true # enable this, which would fail the isTruthy check.

Since this replaces a well-tested library, it's worth covering these edge cases to avoid subtle regressions.

Suggested fix
 function parseDotenv(src: string): Record<string, string> {
   const result: Record<string, string> = {}
   for (const line of src.split('\n')) {
-    const trimmed = line.trim()
+    let trimmed = line.trim()
     if (!trimmed || trimmed.startsWith('#')) continue
+    // Strip optional `export` prefix
+    if (trimmed.startsWith('export ')) {
+      trimmed = trimmed.slice(7).trim()
+    }
     const eqIndex = trimmed.indexOf('=')
     if (eqIndex === -1) continue
     const key = trimmed.slice(0, eqIndex).trim()
     let value = trimmed.slice(eqIndex + 1).trim()
     // Remove surrounding quotes
     if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''))) {
       value = value.slice(1, -1)
     }
+    else {
+      // Strip inline comments for unquoted values
+      const hashIndex = value.indexOf(' #')
+      if (hashIndex !== -1) {
+        value = value.slice(0, hashIndex).trim()
+      }
+    }
     result[key] = value
   }
   return result
 }
πŸ€– Prompt for AI Agents
In `@src/cli.ts` around lines 20 - 36, The parseDotenv function fails to handle
lines prefixed with "export" and inline comments; update parseDotenv so for each
non-empty, non-# line it first removes a leading "export " token from the start
of the trimmed line before splitting on '=', then when extracting the value
strip an inline comment only if the value is not enclosed in matching single or
double quotes (i.e., remove any unquoted " #..." suffix after trimming), then
remove surrounding quotes and unescape any escaped quotes; ensure the key is the
cleaned left-hand side and the value is the cleaned right-hand side so isTruthy
checks work as expected.


const RC_FILENAME = '.nuxtrc'

const sharedArgs = {
global: {
type: 'boolean',
alias: 'g',
default: false,
description: 'Apply globally',
},
dir: {
Expand Down Expand Up @@ -103,14 +124,14 @@ async function _checkDisabled(dir: string): Promise<string | false | undefined>
return 'because you are running in a test environment'
}

if (destr(process.env.NUXT_TELEMETRY_DISABLED)) {
if (isTruthy(process.env.NUXT_TELEMETRY_DISABLED)) {
return 'by the `NUXT_TELEMETRY_DISABLED` environment variable'
}

const dotenvFile = resolve(dir, '.env')
if (existsSync(dotenvFile)) {
const _env = parseDotenv(readFileSync(dotenvFile))
if (destr(_env.NUXT_TELEMETRY_DISABLED)) {
const _env = parseDotenv(readFileSync(dotenvFile, 'utf8'))
if (isTruthy(_env.NUXT_TELEMETRY_DISABLED)) {
return 'by the `NUXT_TELEMETRY_DISABLED` environment variable set in ' + dotenvFile
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/consent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { colors as c } from 'consola/utils'
import { consola } from 'consola'
import { isMinimal } from 'std-env'
import isDocker from 'is-docker'
import { updateUserNuxtRc } from './utils/nuxtrc'
import type { TelemetryOptions } from './types'
import { consentVersion } from './meta'
import { isDocker } from './utils/is-docker'

export async function ensureUserconsent(options: TelemetryOptions): Promise<boolean> {
if (options.consent && options.consent >= consentVersion) {
Expand Down
Loading