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
24 changes: 10 additions & 14 deletions packages/cli/src/platforms/ios/entitlements.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { readTextFile, writeTextFile } from '../../fs/readWrite'
import { pathExists, readTextFile, writeTextFile } from '../../fs/readWrite'
import { toRelativePath } from '../../fs/path'
import { VoltraCliError } from '../../reporting/summary'

import { buildPlistXml, parsePlistFile } from './plist'
import { resolveMainAppEntitlementsPath } from './mainAppEntitlements'

import type { IOSProjectDiscovery } from '../../discovery/ios'
import type { NormalizedVoltraIOSConfig } from '../../config/types'
Expand Down Expand Up @@ -33,22 +34,17 @@ export class IOSEntitlementsMutationError extends VoltraCliError {

export async function ensureEntitlements(options: EnsureEntitlementsOptions): Promise<EnsureEntitlementsResult> {
const { projectRoot, ios, discovery } = options
const entitlementsPath = resolveMainAppEntitlementsPath(discovery)

if (!discovery.entitlementsPath) {
if (!needsEntitlementsMutation(ios)) {
return {}
}

throw new IOSEntitlementsMutationError(
`Could not determine the main app entitlements file. Set ios.project.entitlementsPath to update entitlements for target '${discovery.mainTargetName}'.`
)
}

const entitlements = await parsePlistFile(
discovery.entitlementsPath,
'main app entitlements',
createEntitlementsError
)
const entitlements: Record<string, unknown> = (await pathExists(entitlementsPath))
? await parsePlistFile(entitlementsPath, 'main app entitlements', createEntitlementsError)
: {}
const previousVoltraValues = await readPreviousVoltraEntitlementValues(discovery.infoPlistPath)

ensureStringArrayValue(
Expand All @@ -70,12 +66,12 @@ export async function ensureEntitlements(options: EnsureEntitlementsOptions): Pr
}

const nextContent = buildPlistXml(entitlements, createEntitlementsError)
const change = await writeEntitlementsIfChanged(projectRoot, discovery.entitlementsPath, nextContent)
const change = await writeEntitlementsIfChanged(projectRoot, entitlementsPath, nextContent)

return { change }
}

function needsEntitlementsMutation(ios: NormalizedVoltraIOSConfig): boolean {
export function needsEntitlementsMutation(ios: NormalizedVoltraIOSConfig): boolean {
return ios.enablePushNotifications || ios.groupIdentifier !== undefined || ios.keychainGroup !== undefined
}

Expand Down Expand Up @@ -120,7 +116,7 @@ async function writeEntitlementsIfChanged(
entitlementsPath: string,
nextContent: string
): Promise<ReportedChange | undefined> {
const previousContent = await readTextFile(entitlementsPath)
const previousContent = (await pathExists(entitlementsPath)) ? await readTextFile(entitlementsPath) : undefined

if (previousContent === nextContent) {
return undefined
Expand All @@ -129,7 +125,7 @@ async function writeEntitlementsIfChanged(
await writeTextFile(entitlementsPath, nextContent)

return {
kind: 'updated',
kind: previousContent === undefined ? 'created' : 'updated',
path: toRelativePath(projectRoot, entitlementsPath),
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/platforms/ios/mainAppEntitlements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import path from 'node:path'

import type { IOSProjectDiscovery } from '../../discovery/ios'

export function resolveMainAppEntitlementsPath(discovery: IOSProjectDiscovery): string {
if (discovery.entitlementsPath) {
return discovery.entitlementsPath
}

return path.join(path.dirname(discovery.infoPlistPath), `${discovery.mainTargetName}.entitlements`)
}
24 changes: 20 additions & 4 deletions packages/cli/src/platforms/ios/xcodeTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import {
} from '@bacons/xcode'

import { normalizeRelativePath, toRelativePath } from '../../fs/path'
import { pathExists } from '../../fs/readWrite'
import { VoltraCliError } from '../../reporting/summary'
import { resolveIOSWidgetTargetName } from './targetName'
import { ensureMainGroupChild, openIOSXcodeProject, saveIOSXcodeProject } from './xcode'
import { resolveMainAppEntitlementsPath } from './mainAppEntitlements'
import { needsEntitlementsMutation } from './entitlements'

import type { IOSProjectDiscovery } from '../../discovery/ios'
import type { NormalizedVoltraIOSConfig } from '../../config/types'
Expand Down Expand Up @@ -65,7 +68,7 @@ export async function ensureIOSWidgetTarget(
const staleTargetNames = getStaleWidgetTargetNames(previousWidgetFiles, targetName)
const bundleIdentifier = resolveBundleIdentifier(context, discovery, targetName)
const codeSigning = getMainAppCodeSigningSettings(context)
const mainAppEntitlementsPath = getMainAppEntitlementsBuildSetting(projectRoot, discovery)
const mainAppEntitlementsPath = await getMainAppEntitlementsBuildSetting(discovery, ios)

removeStaleWidgetTargets(context, staleTargetNames)
ensureMainAppEntitlementsBuildSetting(context, mainAppEntitlementsPath)
Expand Down Expand Up @@ -739,12 +742,25 @@ function getMainAppCodeSigningSettings(context: IOSXcodeProjectContext): MainApp
}
}

function getMainAppEntitlementsBuildSetting(projectRoot: string, discovery: IOSProjectDiscovery): string | undefined {
if (!discovery.entitlementsPath) {
async function getMainAppEntitlementsBuildSetting(
discovery: IOSProjectDiscovery,
ios: NormalizedVoltraIOSConfig
): Promise<string | undefined> {
if (discovery.entitlementsPath) {
return normalizeRelativePath(path.relative(discovery.iosRoot, discovery.entitlementsPath))
}

if (!needsEntitlementsMutation(ios)) {
return undefined
}

const entitlementsPath = resolveMainAppEntitlementsPath(discovery)

if (!(await pathExists(entitlementsPath))) {
return undefined
}

return normalizeRelativePath(path.relative(discovery.iosRoot, discovery.entitlementsPath))
return normalizeRelativePath(path.relative(discovery.iosRoot, entitlementsPath))
}

function ensureMainAppEntitlementsBuildSetting(
Expand Down
66 changes: 66 additions & 0 deletions packages/cli/test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ function loadCliModule() {
return require(path.join(packageRoot, 'build/cjs/index.js'))
}

function loadIosMainAppEntitlementsModule() {
return require(path.join(packageRoot, 'build/cjs/platforms/ios/mainAppEntitlements.js'))
}

function writeFakePackage(projectRoot, packageName) {
const packagePath = path.join(projectRoot, 'node_modules', ...packageName.split('/'), 'package.json')
fs.mkdirSync(path.dirname(packagePath), { recursive: true })
Expand Down Expand Up @@ -130,6 +134,68 @@ test('ios preflight reports missing client package when renderer is installed',
assert.doesNotMatch(result.issues[0].message, /@use-voltra\/ios and/)
})

test('resolves the standard main app entitlements path when discovery is missing one', () => {
const { resolveMainAppEntitlementsPath } = loadIosMainAppEntitlementsModule()
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'voltra-cli-test-'))

const discovery = {
iosRoot: path.join(tempDir, 'ios'),
xcodeprojPath: path.join(tempDir, 'ios', 'TestApp.xcodeproj'),
pbxprojPath: path.join(tempDir, 'ios', 'TestApp.xcodeproj', 'project.pbxproj'),
podfilePath: path.join(tempDir, 'ios', 'Podfile'),
mainTargetName: 'TestApp',
mainTargetCandidates: ['TestApp'],
infoPlistPath: path.join(tempDir, 'ios', 'TestApp', 'Info.plist'),
}

assert.equal(resolveMainAppEntitlementsPath(discovery), path.join(tempDir, 'ios', 'TestApp', 'TestApp.entitlements'))
})

test('ensureEntitlements creates the main app entitlements file when it is missing', async () => {
const { ensureEntitlements } = loadCliModule()
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'voltra-cli-test-'))
const iosRoot = path.join(tempDir, 'ios')
const infoPlistPath = path.join(iosRoot, 'TestApp', 'Info.plist')
const entitlementsPath = path.join(iosRoot, 'TestApp', 'TestApp.entitlements')

fs.mkdirSync(path.dirname(infoPlistPath), { recursive: true })
fs.writeFileSync(
infoPlistPath,
`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict></dict>
</plist>
`
)

const result = await ensureEntitlements({
projectRoot: tempDir,
ios: {
enablePushNotifications: true,
groupIdentifier: 'group.com.example.app',
project: {},
},
discovery: {
iosRoot,
xcodeprojPath: path.join(iosRoot, 'TestApp.xcodeproj'),
pbxprojPath: path.join(iosRoot, 'TestApp.xcodeproj', 'project.pbxproj'),
podfilePath: path.join(iosRoot, 'Podfile'),
mainTargetName: 'TestApp',
mainTargetCandidates: ['TestApp'],
infoPlistPath,
},
})

assert.ok(result.change)
assert.equal(result.change.kind, 'created')
assert.equal(fs.existsSync(entitlementsPath), true)
const entitlements = fs.readFileSync(entitlementsPath, 'utf8')
assert.match(entitlements, /com\.apple\.security\.application-groups/)
assert.match(entitlements, /group\.com\.example\.app/)
assert.match(entitlements, /aps-environment/)
})

test('android preflight reports missing client package when renderer is installed', async () => {
const { createAndroidPreflightRunner } = loadCliModule()
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'voltra-cli-test-'))
Expand Down
Loading