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
62 changes: 61 additions & 1 deletion shared/createHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ function createHybridApp(config) {
}
utils.runProcessThrowError('cordova plugin add ' + config.cordovaPluginRepoUri + ' --force', config.projectDir);

// Override sdk dependencies in the generated iOS Podfile if an iOS override was provided.
// `cordova plugin add` above already ran `pod install` once against the plugin's hardcoded
// pod sources; the `cordova prepare` call further down always reparses and rewrites the
// Podfile (the plugin sets a deployment-target preference) and reruns `pod install`, which
// is what actually picks up this edit.
if (config.sdkdependencies && config.platform.split(',').includes('ios')) {
overrideHybridIosPodfile(path.join(config.projectDir, 'platforms', 'ios', 'Podfile'), config.sdkdependencies);
}

// Web directory - the home for the template
var webDir = path.join(config.projectDir, 'www');

Expand Down Expand Up @@ -479,6 +488,56 @@ function overrideSdkDependencies(packageJsonPath, sdkDependenciesString) {
}
}

//
// Override sdk dependencies in the generated hybrid app's iOS Podfile.
// The Cordova plugin's plugin.xml hardcodes each Mobile SDK pod's :git repo and :branch => 'dev';
// package.json has no influence over that Podfile, so overrideSdkDependencies() above is a no-op
// for hybrid+iOS. This rewrites the :git/:branch of any pod line sourced from a repo named in
// sdkDependenciesString (e.g. "SalesforceMobileSDK-iOS") to point at the given fork + branch.
//
function overrideHybridIosPodfile(podfilePath, sdkDependenciesString) {
try {
if (!fs.existsSync(podfilePath)) {
console.error(`Podfile not found at ${podfilePath}, skipping sdk dependencies override`);
return;
}

let sdkDependencies = JSON.parse(sdkDependenciesString);
let originalContent = fs.readFileSync(podfilePath, 'utf8');

let updatedContent = originalContent.split('\n').map(function(line) {
if (!/^\s*pod\s+'/.test(line)) {
return line;
}

for (var repoName in sdkDependencies) {
var gitRE = new RegExp(":git\\s*=>\\s*'[^']*\\/" + repoName + "(?:\\.git)?'");
if (!gitRE.test(line)) {
continue;
}
if (!/:branch\s*=>\s*'[^']*'/.test(line)) {
console.warn(`Skipping sdk dependencies override for ${repoName}: pod line is not pinned with :branch (e.g. it uses :tag or :commit instead), so it can't be safely rewritten: ${line.trim()}`);
continue;
}

var parts = sdkDependencies[repoName].split('#');
var newGitUrl = parts[0];
var newBranch = parts.length > 1 ? parts[1] : 'dev';

line = line.replace(gitRE, ":git => '" + newGitUrl + "'");
line = line.replace(/:branch\s*=>\s*'[^']*'/, ":branch => '" + newBranch + "'");
}

return line;
}).join('\n');

fs.writeFileSync(podfilePath, updatedContent, 'utf8');

} catch (err) {
console.error(`Failed to override sdk dependencies in Podfile: ${err}`);
}
}


//
// Actually create app
Expand Down Expand Up @@ -654,5 +713,6 @@ function validateCustomProperties(templateJsonPath, customProperties) {
module.exports = {
createApp,
validateCustomProperties,
parseCallbackUrl
parseCallbackUrl,
overrideHybridIosPodfile
};
123 changes: 123 additions & 0 deletions unittests/createHelper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,127 @@ describe('createHelper', () => {
expect(result.host).not.toBe('*');
});
});

describe('overrideHybridIosPodfile', () => {
const podfilePath = '/path/to/Podfile';

// Actual Podfile generated by the Cordova plugin (via `cordova plugin add`) for a
// hybrid_local iOS app against dev, captured from a real `test_force.js` run.
const originalPodfile =
"# DO NOT MODIFY -- auto-generated by Apache Cordova\n" +
"source 'https://cdn.cocoapods.org'\n" +
"source 'https://www.github.com/forcedotcom/SalesforceMobileSDK-iOS-Specs'\n" +
"platform :ios, '18.0'\n" +
"use_frameworks!\n" +
"target 'App' do\n" +
"\tproject 'App.xcodeproj'\n" +
"\tpod 'SalesforceHybridSDK', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS-Hybrid'\n" +
"\tpod 'MobileSync', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"\tpod 'SmartStore', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"\tpod 'SalesforceSDKCore', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"\tpod 'SalesforceAnalytics', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"\tpod 'SalesforceSDKCommon', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"\tpod 'Cordova', :tag => 'rel/8.1.0', :git => 'https://github.com/apache/cordova-ios'\n" +
"end\n";

beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
jest.spyOn(console, 'warn').mockImplementation(() => {});
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should rewrite the :git and :branch of every pod sourced from the overridden repo', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
jest.spyOn(fs, 'readFileSync').mockReturnValue(originalPodfile);
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

const sdkDependencies = JSON.stringify({
'SalesforceMobileSDK-iOS': 'https://github.com/someFork/SalesforceMobileSDK-iOS#someBranch'
});

createHelper.overrideHybridIosPodfile(podfilePath, sdkDependencies);

expect(writeFileSyncSpy).toHaveBeenCalledTimes(1);
const [writtenPath, writtenContent] = writeFileSyncSpy.mock.calls[0];
expect(writtenPath).toBe(podfilePath);

// The five pods sourced from SalesforceMobileSDK-iOS are all repointed
const overriddenLines = writtenContent.split('\n').filter(line =>
/^\s*pod\s+'(MobileSync|SmartStore|SalesforceSDKCore|SalesforceAnalytics|SalesforceSDKCommon)'/.test(line)
);
expect(overriddenLines).toHaveLength(5);
overriddenLines.forEach(line => {
expect(line).toContain(":git => 'https://github.com/someFork/SalesforceMobileSDK-iOS'");
expect(line).toContain(":branch => 'someBranch'");
});

// SalesforceHybridSDK (different upstream repo) and Cordova are left untouched
expect(writtenContent).toContain("pod 'SalesforceHybridSDK', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS-Hybrid'");
expect(writtenContent).toContain("pod 'Cordova', :tag => 'rel/8.1.0', :git => 'https://github.com/apache/cordova-ios'");
});

it('should default to the dev branch when the override omits a branch', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
jest.spyOn(fs, 'readFileSync').mockReturnValue(originalPodfile);
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

const sdkDependencies = JSON.stringify({
'SalesforceMobileSDK-iOS': 'https://github.com/someFork/SalesforceMobileSDK-iOS'
});

createHelper.overrideHybridIosPodfile(podfilePath, sdkDependencies);

const writtenContent = writeFileSyncSpy.mock.calls[0][1];
expect(writtenContent).toContain("pod 'SalesforceSDKCore', :branch => 'dev', :git => 'https://github.com/someFork/SalesforceMobileSDK-iOS'");
});

it('should not touch pods sourced from a repo not named in the override', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
jest.spyOn(fs, 'readFileSync').mockReturnValue(originalPodfile);
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

const sdkDependencies = JSON.stringify({
'SalesforceMobileSDK-iOS-Hybrid': 'https://github.com/someFork/SalesforceMobileSDK-iOS-Hybrid#someBranch'
});

createHelper.overrideHybridIosPodfile(podfilePath, sdkDependencies);

const writtenContent = writeFileSyncSpy.mock.calls[0][1];
expect(writtenContent).toContain("pod 'SalesforceHybridSDK', :branch => 'someBranch', :git => 'https://github.com/someFork/SalesforceMobileSDK-iOS-Hybrid'");
expect(writtenContent).toContain("pod 'MobileSync', :branch => 'dev', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'");
});

it('should warn and leave a matching pod untouched when it is not pinned with :branch', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
const podfileWithTaggedPod =
"target 'App' do\n" +
"\tpod 'MobileSync', :tag => 'v13.2.0', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'\n" +
"end\n";
jest.spyOn(fs, 'readFileSync').mockReturnValue(podfileWithTaggedPod);
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

const sdkDependencies = JSON.stringify({
'SalesforceMobileSDK-iOS': 'https://github.com/someFork/SalesforceMobileSDK-iOS#someBranch'
});

createHelper.overrideHybridIosPodfile(podfilePath, sdkDependencies);

const writtenContent = writeFileSyncSpy.mock.calls[0][1];
expect(writtenContent).toContain("pod 'MobileSync', :tag => 'v13.2.0', :git => 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS'");
expect(console.warn).toHaveBeenCalled();
});

it('should skip writing and log an error when the Podfile does not exist', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});

createHelper.overrideHybridIosPodfile(podfilePath, JSON.stringify({ 'SalesforceMobileSDK-iOS': 'https://github.com/someFork/SalesforceMobileSDK-iOS#someBranch' }));

expect(writeFileSyncSpy).not.toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
});
});
});
Loading