Skip to content
Draft
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
6 changes: 4 additions & 2 deletions cli/src/api/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ export function displayBundles(
if (silent)
return

if (!data.length)
throw new Error('No bundle found')
if (!data.length) {
log.info('No bundles found')
return
}

const t = new Table()
t.theme = Table.roundTheme
Expand Down
5 changes: 4 additions & 1 deletion cli/src/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,11 @@ export async function capturePosthogException(payload: CapturePosthogExceptionPa
const distinctId = `cli:${pack.version}:${payload.functionName}`
const frames = parseExceptionFrames(serializedError.stack, payload.functionName)
const topFrame = frames[0]
// Deliberately exclude the CLI version from the fingerprint so the same bug
// stays a single error-tracking issue across releases instead of minting a
// brand-new issue on every version bump.
const fingerprint = [
distinctId,
payload.functionName,
payload.kind,
serializedError.name || 'Error',
topFrame?.function || payload.functionName,
Expand Down
25 changes: 23 additions & 2 deletions cli/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2187,6 +2187,27 @@ async function calculatePlatformChecksums(dependencyFolderPath: string): Promise
return { ios_checksum, android_checksum }
}

// Collect every `node_modules` directory from `startDir` up to the filesystem
// root. This mirrors the parent-directory walk `getAllPackagesDependencies`
// uses to resolve versions, so that the existence check validates the same
// hoisted locations the enumeration reads from. Without this, dependencies
// hoisted to a monorepo/workspace root read as missing.
function getHoistedNodeModulesPaths(startDir: string): string[] {
const paths: string[] = []
let currentDir = startDir
const root = path.parse(currentDir).root
while (true) {
paths.push(join(currentDir, 'node_modules'))
if (currentDir === root)
break
const parentDir = dirname(currentDir)
if (parentDir === currentDir)
break
currentDir = parentDir
}
return paths
}

export async function getLocalDependencies(packageJsonPath: string | undefined, nodeModulesString: string | undefined) {
const nodeModules = nodeModulesString
? nodeModulesString
Expand Down Expand Up @@ -2220,7 +2241,7 @@ export async function getLocalDependencies(packageJsonPath: string | undefined,
}

const nodeModulesPaths = nodeModules.length === 0
? [join(cwd(), 'node_modules')]
? getHoistedNodeModulesPaths(cwd())
: nodeModules

const anyValidPath = nodeModulesPaths.some(path => existsSync(path))
Expand Down Expand Up @@ -2303,7 +2324,7 @@ export async function getLocalDependencies(packageJsonPath: string | undefined,
ios_checksum,
android_checksum,
}
})).catch(() => [])
}))

if (anyInvalid || dependenciesObject.some(a => a.native === undefined)) {
log.error('Missing dependencies or invalid dependencies')
Expand Down
46 changes: 46 additions & 0 deletions cli/test/test-native-dependencies.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import assert from 'node:assert/strict'
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { chdir, cwd } from 'node:process'
import { getLocalDependencies } from '../src/utils.ts'

const fixtureDir = join(tmpdir(), `capgo-native-dependencies-${process.pid}`)
Expand Down Expand Up @@ -70,3 +71,48 @@ try {
finally {
rmSync(fixtureDir, { recursive: true, force: true })
}

// Monorepo hoisting: dependencies installed at the workspace root node_modules
// must be discovered when no explicit --node-modules path is given, by walking
// up parent directories from the app package.
const monorepoDir = join(tmpdir(), `capgo-native-dependencies-hoist-${process.pid}`)
const appDir = join(monorepoDir, 'packages', 'app')
const rootNodeModules = join(monorepoDir, 'node_modules')
const originalCwd = cwd()

try {
mkdirSync(appDir, { recursive: true })
mkdirSync(rootNodeModules, { recursive: true })

writeJson(join(appDir, 'package.json'), {
name: 'app',
dependencies: {
'@capgo/capacitor-updater': '^8.0.0',
},
})

// Hoisted to the workspace root, not the app-local node_modules.
const hoistedPackageDir = join(rootNodeModules, '@capgo', 'capacitor-updater')
mkdirSync(join(hoistedPackageDir, 'ios'), { recursive: true })
writeJson(join(hoistedPackageDir, 'package.json'), {
name: '@capgo/capacitor-updater',
version: '8.3.0',
capacitor: { ios: { src: 'ios' } },
})
writeFileSync(join(hoistedPackageDir, 'ios', 'UpdaterPlugin.swift'), 'final class UpdaterPlugin {}\n')

// Run from the app directory so the default node_modules resolution has to
// walk up to the workspace root.
chdir(appDir)
const dependencies = await getLocalDependencies(join(appDir, 'package.json'), undefined)
const updater = dependencies.find(dep => dep.name === '@capgo/capacitor-updater')

assert.equal(updater?.version, '8.3.0')
assert.equal(updater?.native, true)

console.log('hoisted monorepo dependencies are resolved via the parent-directory walk')
}
finally {
chdir(originalCwd)
rmSync(monorepoDir, { recursive: true, force: true })
}
5 changes: 4 additions & 1 deletion cli/test/test-posthog-exception.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ try {
assert.equal(body.properties.error_kind, 'unhandled_error')
assert.equal(body.properties.status, 1)
assert.match(body.properties.distinct_id, /^cli:[^:]+:bundle upload$/)
assert.match(body.properties.$exception_fingerprint, /cli:[^:]+:bundle upload:unhandled_error:Error:runUpload:/)
// Fingerprint must NOT include the CLI version, so the same bug stays one
// error-tracking issue across releases.
assert.equal(body.properties.$exception_fingerprint, 'bundle upload:unhandled_error:Error:runUpload:<cwd>/src/index.ts:1')
assert.doesNotMatch(body.properties.$exception_fingerprint, /cli:/)
assert.equal(body.properties.$exception_list[0].type, 'Error')
assert.equal(body.properties.$exception_list[0].value, 'boom')
assert.equal(body.properties.$exception_list[0].mechanism.handled, true)
Expand Down
Loading