Skip to content
Open
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
39 changes: 39 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[default]
# Ignore patterns that match commit hashes and Git-related identifiers
extend-ignore-re = [
# Git commit hashes in parentheses: (7553ede), (88d0ded), etc.
"\\([a-f0-9]{7,40}\\)",
# Git commit hashes in square brackets: [7553ede], [88d0ded], etc.
"\\[[a-f0-9]{7,40}\\]",
# Full GitHub commit URLs
"github\\.com/[^/]+/[^/]+/commit/[a-f0-9]{7,40}",
# Commit hash at end of markdown links: ...commit/abcd123)
"commit/[a-f0-9]{7,40}\\)",
]

[files]
extend-exclude = [
# Exclude lock files
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
# Exclude build artifacts
"dist/",
"build/",
"*.min.js",
# Exclude node_modules
"node_modules/",
# Exclude Xcode project files that contain hex identifiers - these files have
# automatically generated hex IDs that look like typos but must not be changed
"**/project.pbxproj",
"**/*.xcscheme",
"**/*.storyboard",
# Exclude all CHANGELOG files - they contain commit hashes and historical text
# that should not be modified by typo fixes
"CHANGELOG.md",
"*/CHANGELOG.md",
]

[default.extend-words]
# `thn` is a real MIME type in ios/.../WebViewAssetHandler.swift, not a typo of "then".
thn = "thn"

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

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

thn is added to the custom dictionary but doesn’t appear to exist anywhere in the repository (and looks like a typo of "then"). Consider removing it until there is a concrete false-positive to suppress, to avoid masking real typos.

Suggested change
thn = "thn"

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Kept thn — it is a real MIME type in WebViewAssetHandler.swift ("thn": "application/octet-stream"), not a typo of "then". Added a comment in .typos.toml explaining why it stays in the custom dictionary.

Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ public void handleDestroy() {

@Deprecated
@Override
public void sendJavascript(String statememt) {
nativeToJsMessageQueue.addJavaScript(statememt);
public void sendJavascript(String statement) {
nativeToJsMessageQueue.addJavaScript(statement);
}

public void eval(final String js, final ValueCallback<String> callback) {
Expand Down
4 changes: 2 additions & 2 deletions cli/src/android/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ export async function installGradlePlugins(

const settingsPath = config.android.platformDirAbs;
const dependencyPath = config.android.appDirAbs;
const relativeCapcitorAndroidPath = convertToUnixPath(relative(settingsPath, capacitorAndroidPath));
const relativeCapacitorAndroidPath = convertToUnixPath(relative(settingsPath, capacitorAndroidPath));
const settingsLines = `// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('${relativeCapcitorAndroidPath}')
project(':capacitor-android').projectDir = new File('${relativeCapacitorAndroidPath}')
${capacitorPlugins
.map((p) => {
if (!p.android) {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ export interface CapacitorConfig {
* Configure the local scheme on Android.
*
* Custom schemes on Android are unable to change the URL path as of Webview 117. Changing this value from anything other than `http` or `https` can result in your
* application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarentees that this
* application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarantees that this
* will continue to work long term as allowing non-standard schemes to modify query parameters and url fragments is only allowed for compatibility reasons.
* https://ionic.io/blog/capacitor-android-customscheme-issue-with-chrome-117
*
Expand Down
2 changes: 1 addition & 1 deletion cli/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface FrameworkConfig {
/**
* Specific UI libraries (Ionic) and higher-level frameworks (NextJs/Gatsby)
* should be prioritorized over a more generic framework like React/Angular/Vue.
* Lower the priorty number the more important it is (1 has more priority over 2).
* Lower the priority number the more important it is (1 has more priority over 2).
* This helps to make sure a specific framework like "NextJs" is chosen before
* the more generic "React".
*/
Expand Down
2 changes: 1 addition & 1 deletion cli/src/ios/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function doctorIOS(config: Config): Promise<void> {
// check plugins versions
// check native project deps are up-to-date === npm install
// check if npm install was updated
// check online datebase of common errors
// check online database of common errors
// check if www folder is empty (index.html does not exist)
try {
await check([() => checkWebDir(config), checkXcode, ...(await getCommonChecks(config))]);
Expand Down
2 changes: 1 addition & 1 deletion cli/src/sysconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface SystemConfig {
readonly telemetry?: boolean;

/**
* Wheter the user choose to signup or not.
* Whether the user chooses to sign up or not.
*
* If undefined, the prompt has not been shown.
*/
Expand Down
8 changes: 4 additions & 4 deletions cli/src/tasks/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,12 @@ async function getAndroidVariablesAndClasspaths(config: Config) {
}
deleteFolderRecursive(tempAndroidTemplateFolder);

const firstIndxOfCATBGV = buildGradleFile.indexOf(`classpath 'com.android.tools.build:gradle:`) + 42;
const firstIndxOfCGGGS = buildGradleFile.indexOf(`com.google.gms:google-services:`) + 31;
const firstIndexOfCATBGV = buildGradleFile.indexOf(`classpath 'com.android.tools.build:gradle:`) + 42;
const firstIndexOfCGGGS = buildGradleFile.indexOf(`com.google.gms:google-services:`) + 31;
const comAndroidToolsBuildGradleVersion =
'' + buildGradleFile.substring(firstIndxOfCATBGV, buildGradleFile.indexOf("'", firstIndxOfCATBGV));
'' + buildGradleFile.substring(firstIndexOfCATBGV, buildGradleFile.indexOf("'", firstIndexOfCATBGV));
const comGoogleGmsGoogleServices =
'' + buildGradleFile.substring(firstIndxOfCGGGS, buildGradleFile.indexOf("'", firstIndxOfCGGGS));
'' + buildGradleFile.substring(firstIndexOfCGGGS, buildGradleFile.indexOf("'", firstIndexOfCGGGS));

const variablesGradleAsJSON = JSON.parse(
variablesGradleFile
Expand Down
2 changes: 1 addition & 1 deletion cli/src/util/emoji.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Emoji falback, right now just uses fallback on windows,
// Emoji fallback, right now just uses fallback on windows,
// but could expand to be more sophisticated to allow emoji
// on Hyper term on windows, for example.
export const emoji = (x: string, fallback: string): string => {
Expand Down
109 changes: 97 additions & 12 deletions cli/src/util/spm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pathExists, existsSync, readFileSync, writeFileSync, remove, move, mkdtemp } from 'fs-extra';
import { ensureSymlink, pathExists, existsSync, readFileSync, writeFileSync, remove, move, mkdtemp } from 'fs-extra';
import { tmpdir } from 'os';
import { join, relative, resolve } from 'path';
import type { PlistObject } from 'plist';
Expand All @@ -11,7 +11,8 @@ import { fatal } from '../errors';
import { getMajoriOSVersion } from '../ios/common';
import { logger } from '../log';
import type { Plugin } from '../plugin';
import { getPluginType, PluginType } from '../plugin';
import { getPlatformElement, getPluginPlatform, getPluginType, PluginType } from '../plugin';
import { convertToUnixPath } from '../util/fs';
import { runCommand } from '../util/subprocess';

export interface SwiftPlugin {
Expand Down Expand Up @@ -52,14 +53,15 @@ export async function generatePackageFile(config: Config, plugins: Plugin[]): Pr

export async function checkPluginsForPackageSwift(config: Config, plugins: Plugin[]): Promise<Plugin[]> {
const iOSCapacitorPlugins = plugins.filter((p) => getPluginType(p, 'ios') === PluginType.Core);

const packageSwiftPluginList = await pluginsWithPackageSwift(iOSCapacitorPlugins);

if (plugins.length == packageSwiftPluginList.length) {
logger.debug(`Found ${plugins.length} iOS plugins, ${packageSwiftPluginList.length} have a Package.swift file`);
logger.info('All plugins have a Package.swift file and will be included in Package.swift');
if (iOSCapacitorPlugins.length == packageSwiftPluginList.length) {
logger.debug(
`Found ${iOSCapacitorPlugins.length} Capacitor iOS plugins, ${packageSwiftPluginList.length} have a Package.swift file`,
);
logger.info('All Capacitor plugins have a Package.swift file and will be included in Package.swift');
} else {
logger.warn('Some installed packages are not compatable with SPM');
logger.warn('Some installed Capacitor plugins are not compatible with SPM');
}

return packageSwiftPluginList;
Expand Down Expand Up @@ -99,6 +101,7 @@ export async function generatePackageText(config: Config, plugins: Plugin[]): Pr
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
const iosVersion = getMajoriOSVersion(config);
const packageTraits = config.app.extConfig.experimental?.ios?.spm?.packageTraits ?? {};
const packageOptions = config.app.extConfig.experimental?.ios?.spm?.packageOptions ?? {};
const swiftToolsVersion = config.app.extConfig.experimental?.ios?.spm?.swiftToolsVersion ?? '5.9';

let packageSwiftText = `// swift-tools-version: ${swiftToolsVersion}
Expand All @@ -118,15 +121,34 @@ let package = Package(

for (const plugin of plugins) {
if (getPluginType(plugin, config.ios.name) === PluginType.Cordova) {
packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`;
const platformTag = getPluginPlatform(plugin, config.ios.name);
if (platformTag.$?.package) {
const relPath = convertToUnixPath(relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath));
packageSwiftText += `,\n .package(name: "${plugin.id}", path: "${relPath}")`;
} else {
const sourceFiles = getPlatformElement(plugin, config.ios.name, 'source-file');
const headerFiles = getPlatformElement(plugin, config.ios.name, 'header-file');
if (sourceFiles.length === 0 && headerFiles.length === 0) {
continue;
}
packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`;
}
} else {
const relPath = relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
const options = packageOptions[plugin.id];
const symlink = options?.symlink;
const symlinkFolder = join('symlinks', plugin.name);
const relPath = symlink
? symlinkFolder
: convertToUnixPath(relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath));
if (symlink) {
await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
}
const traits = packageTraits[plugin.id];
const traitsSuffix = traits?.length
? `, traits: [${traits
.map((t) => {
// Any trait is written with quotes, with the exception of .defaults
return /^\.?defaults?$/i.test(t) ? '.defaults' : `"${t}"`;
return /^\\.?defaults?$/i.test(t) ? '.defaults' : `"${t}"`;
})
.join(', ')}]`
: '';
Expand All @@ -144,7 +166,28 @@ let package = Package(
.product(name: "Cordova", package: "capacitor-swift-pm")`;

for (const plugin of plugins) {
packageSwiftText += `,\n .product(name: "${plugin.ios?.name}", package: "${plugin.ios?.name}")`;
const aliases = Object.entries(packageOptions[plugin.id]?.moduleAliases ?? {});
const aliasText = aliases?.length
? `, moduleAliases: [${aliases
.map(([target, replacement]) => {
return `"${target}": "${replacement}"`;
})
.join(', ')}]`
: '';
let pluginText = `,\n .product(name: "${plugin.ios?.name}", package: "${plugin.ios?.name}"${aliasText})`;
if (getPluginType(plugin, config.ios.name) === PluginType.Cordova) {
const platformTag = getPluginPlatform(plugin, config.ios.name);
if (platformTag.$?.package) {
pluginText = `,\n .product(name: "${plugin.id}", package: "${plugin.id}")`;
} else {
const sourceFiles = getPlatformElement(plugin, config.ios.name, 'source-file');
const headerFiles = getPlatformElement(plugin, config.ios.name, 'header-file');
if (sourceFiles.length === 0 && headerFiles.length === 0) {
pluginText = '';
}
}
}
packageSwiftText += pluginText;
}

packageSwiftText += `
Expand Down Expand Up @@ -199,12 +242,54 @@ export async function addInfoPlistDebugIfNeeded(config: Config): Promise<void> {
}
}

export function hasSceneManifest(config: Config): boolean {
const infoPlist = resolve(config.ios.nativeTargetDirAbs, 'Info.plist');
if (!existsSync(infoPlist)) {
return false;
}
const entries = parse(readFileSync(infoPlist, 'utf-8')) as PlistObject;
return entries['UIApplicationSceneManifest'] !== undefined;
}

export async function addSceneManifestIfNeeded(config: Config): Promise<void> {
type Mutable<T> = { -readonly [P in keyof T]: T[P] };

const infoPlist = resolve(config.ios.nativeTargetDirAbs, 'Info.plist');

if (!existsSync(infoPlist)) {
logger.warn(infoPlist + ' not found.');
return;
}

const entries = parse(readFileSync(infoPlist, 'utf-8')) as Mutable<PlistObject>;

if (entries['UIApplicationSceneManifest'] !== undefined) {
logger.warn('Found UIApplicationSceneManifest in ' + infoPlist + ', skipping.');
return;
}

entries['UIApplicationSceneManifest'] = {
UIApplicationSupportsMultipleScenes: false,
UISceneConfigurations: {
UIWindowSceneSessionRoleApplication: [
{
UISceneConfigurationName: 'Default Configuration',
UISceneDelegateClassName: '$(PRODUCT_MODULE_NAME).SceneDelegate',
UISceneStoryboardFile: 'Main',
},
],
},
};

writeFileSync(infoPlist, build(entries));
}

export async function checkSwiftToolsVersion(config: Config, version: string | undefined): Promise<string | null> {
if (!version) {
return null;
}

const swiftToolsVersionRegex = /^[0-9]+\.[0-9]+(\.[0-9]+)?$/;
const swiftToolsVersionRegex = /^[0-9]+\\.[0-9]+(\\.[0-9]+)?$/;

if (!swiftToolsVersionRegex.test(version)) {
return (
Expand Down
2 changes: 1 addition & 1 deletion core/cordova.js
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@
}

capacitorExec.nativeFetchMessages = function () {
// Stop listing for window detatch once native side confirms poke.
// Stop listening for window detach once native side confirms poke.
if (failSafeTimerId) {
clearTimeout(failSafeTimerId);
failSafeTimerId = 0;
Expand Down
2 changes: 1 addition & 1 deletion ios/Capacitor/Capacitor/CAPBridgeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ extension CAPBridgeViewController {
aWebView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior
aWebView.allowsLinkPreview = configuration.allowLinkPreviews
aWebView.scrollView.isScrollEnabled = configuration.scrollingEnabled
if let overrideUserAgent = configuration.overridenUserAgentString {
if let overrideUserAgent = configuration.overriddenUserAgentString {
aWebView.customUserAgent = overrideUserAgent
}
if let backgroundColor = configuration.backgroundColor {
Expand Down
3 changes: 2 additions & 1 deletion ios/Capacitor/Capacitor/CAPInstanceConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
NS_SWIFT_NAME(InstanceConfiguration)
@interface CAPInstanceConfiguration: NSObject
@property (nonatomic, readonly, nullable) NSString *appendedUserAgentString;
@property (nonatomic, readonly, nullable) NSString *overridenUserAgentString;
@property (nonatomic, readonly, nullable) NSString *overriddenUserAgentString;
Comment thread
riderx marked this conversation as resolved.
@property (nonatomic, readonly, nullable, getter=overriddenUserAgentString) NSString *overridenUserAgentString DEPRECATED_MSG_ATTRIBUTE("Use overriddenUserAgentString instead");
@property (nonatomic, readonly, nullable) UIColor *backgroundColor;
@property (nonatomic, readonly, nonnull) NSArray<NSString*> *allowedNavigationHostnames;
@property (nonatomic, readonly, nonnull) NSURL *localURL;
Expand Down
4 changes: 2 additions & 2 deletions ios/Capacitor/Capacitor/CAPInstanceConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ - (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor isDebug:(
[descriptor normalize];
// now copy the simple properties
_appendedUserAgentString = descriptor.appendedUserAgentString;
_overridenUserAgentString = descriptor.overridenUserAgentString;
_overriddenUserAgentString = descriptor.overriddenUserAgentString;
_backgroundColor = descriptor.backgroundColor;
_allowedNavigationHostnames = descriptor.allowedNavigationHostnames;
switch (descriptor.loggingBehavior) {
Expand Down Expand Up @@ -59,7 +59,7 @@ - (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor isDebug:(
- (instancetype)initWithConfiguration:(CAPInstanceConfiguration*)configuration andLocation:(NSURL*)location {
if (self = [super init]) {
_appendedUserAgentString = [[configuration appendedUserAgentString] copy];
_overridenUserAgentString = [[configuration overridenUserAgentString] copy];
_overriddenUserAgentString = [[configuration overriddenUserAgentString] copy];
_backgroundColor = configuration.backgroundColor;
_allowedNavigationHostnames = [[configuration allowedNavigationHostnames] copy];
_localURL = [[configuration localURL] copy];
Expand Down
12 changes: 8 additions & 4 deletions ios/Capacitor/Capacitor/CAPInstanceDescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ extern NSString * _Nonnull const CAPInstanceDescriptorDefaultHostname NS_SWIFT_U
NS_SWIFT_NAME(InstanceDescriptor)
@interface CAPInstanceDescriptor : NSObject
/**
@brief A value to append to the @c User-Agent string. Ignored if @c overridenUserAgentString is set.
@brief A value to append to the @c User-Agent string. Ignored if @c overriddenUserAgentString is set.
@discussion Set by @c appendUserAgent in the configuration file.
*/
@property (nonatomic, copy, nullable) NSString *appendedUserAgentString;
/**
@brief A value that will completely replace the @c User-Agent string. Overrides @c appendedUserAgentString.
@discussion Set by @c overrideUserAgent in the configuration file.
*/
@property (nonatomic, copy, nullable) NSString *overridenUserAgentString;
@property (nonatomic, copy, nullable) NSString *overriddenUserAgentString;
/**
Comment thread
riderx marked this conversation as resolved.
@brief Deprecated, misspelled alias for @c overriddenUserAgentString.
*/
@property (nonatomic, copy, nullable, getter=overriddenUserAgentString, setter=setOverriddenUserAgentString:) NSString *overridenUserAgentString __attribute__((deprecated("Use overriddenUserAgentString instead")));
/**
@brief The background color to set on the web view where content is not visible.
@discussion Set by @c backgroundColor in the configuration file.
Expand Down Expand Up @@ -120,7 +124,7 @@ NS_SWIFT_NAME(InstanceDescriptor)
*/
@property (nonatomic, copy, nonnull) NSURL *appLocation;
/**
@brief The path (relative to @c appLocation) which Capacitor will use for the inital URL at launch.
@brief The path (relative to @c appLocation) which Capacitor will use for the initial URL at launch.
@discussion Defaults to nil, in which case Capacitor will attempt to load @c index.html.
*/
@property (nonatomic, copy, nullable) NSString *appStartPath;
Expand All @@ -135,7 +139,7 @@ NS_SWIFT_NAME(InstanceDescriptor)
*/
@property (nonatomic, copy, nullable) NSString *preferredContentMode;
/**
@brief The parser used to load the cofiguration for Cordova plugins.
@brief The parser used to load the configuration for Cordova plugins.
*/
@property (nonatomic, copy, nonnull) CDVConfigParser *cordovaConfiguration;
/**
Expand Down
2 changes: 1 addition & 1 deletion ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ internal extension InstanceDescriptor {
appendedUserAgentString = agentString
}
if let agentString = (config[keyPath: "ios.overrideUserAgent"] as? String) ?? (config[keyPath: "overrideUserAgent"] as? String) {
overridenUserAgentString = agentString
overriddenUserAgentString = agentString
}
if let colorString = (config[keyPath: "ios.backgroundColor"] as? String) ?? (config[keyPath: "backgroundColor"] as? String),
let color = UIColor.capacitor.color(fromHex: colorString) {
Expand Down
2 changes: 1 addition & 1 deletion ios/Capacitor/Capacitor/CAPNotifications.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
Notificaton types for NotificationCenter and NSNotificationCenter
Notification types for NotificationCenter and NSNotificationCenter

We want to include `capacitor` in the name(s) to uniquely identify these even though it can make the names long
and the deprecated notifications are only here for backwards compatibility.
Expand Down
Loading