mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
feat: Cocoapods support (#4667)
This commit is contained in:
parent
da47a0f842
commit
8e60b28ca4
15 changed files with 1521 additions and 0 deletions
|
@ -161,6 +161,12 @@ RUN rm -rf /usr/bin/python && ln /usr/bin/python3.8 /usr/bin/python
|
||||||
|
|
||||||
RUN curl --silent https://bootstrap.pypa.io/get-pip.py | python
|
RUN curl --silent https://bootstrap.pypa.io/get-pip.py | python
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
RUN apt-get update && apt-get install -y ruby ruby2.5-dev && rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN ruby --version
|
||||||
|
ENV COCOAPODS_VERSION 1.9.0
|
||||||
|
RUN gem install --no-rdoc --no-ri cocoapods -v ${COCOAPODS_VERSION}
|
||||||
|
|
||||||
# Set up ubuntu user and home directory with access to users in the root group (0)
|
# Set up ubuntu user and home directory with access to users in the root group (0)
|
||||||
|
|
||||||
ENV HOME=/home/ubuntu
|
ENV HOME=/home/ubuntu
|
||||||
|
|
133
lib/datasource/pod/index.spec.ts
Normal file
133
lib/datasource/pod/index.spec.ts
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import { api as _api } from '../../platform/github/gh-got-wrapper';
|
||||||
|
import { getPkgReleases } from '.';
|
||||||
|
import { mocked } from '../../../test/util';
|
||||||
|
import { GotResponse } from '../../platform';
|
||||||
|
import { GetReleasesConfig } from '../common';
|
||||||
|
|
||||||
|
const api = mocked(_api);
|
||||||
|
|
||||||
|
jest.mock('../../platform/github/gh-got-wrapper');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
lookupName: 'foo',
|
||||||
|
registryUrls: ['https://github.com/CocoaPods/Specs'],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('datasource/cocoapods', () => {
|
||||||
|
describe('getPkgReleases', () => {
|
||||||
|
beforeEach(() => global.renovateCache.rmAll());
|
||||||
|
it('returns null for invalid inputs', async () => {
|
||||||
|
api.get.mockResolvedValueOnce(null);
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({ registryUrls: [] } as GetReleasesConfig)
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({
|
||||||
|
lookupName: null,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({
|
||||||
|
lookupName: 'foobar',
|
||||||
|
registryUrls: [],
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
it('returns null for empty result', async () => {
|
||||||
|
api.get.mockResolvedValueOnce(null);
|
||||||
|
expect(await getPkgReleases(config)).toBeNull();
|
||||||
|
});
|
||||||
|
it('returns null for missing fields', async () => {
|
||||||
|
api.get.mockResolvedValueOnce({} as GotResponse);
|
||||||
|
expect(await getPkgReleases(config)).toBeNull();
|
||||||
|
|
||||||
|
api.get.mockResolvedValueOnce({ body: '' } as GotResponse);
|
||||||
|
expect(await getPkgReleases(config)).toBeNull();
|
||||||
|
});
|
||||||
|
it('returns null for 404', async () => {
|
||||||
|
api.get.mockImplementation(() =>
|
||||||
|
Promise.reject({
|
||||||
|
statusCode: 404,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({
|
||||||
|
...config,
|
||||||
|
registryUrls: [
|
||||||
|
...config.registryUrls,
|
||||||
|
'invalid',
|
||||||
|
'https://github.com/foo/bar',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
it('returns null for 401', async () => {
|
||||||
|
api.get.mockImplementationOnce(() =>
|
||||||
|
Promise.reject({
|
||||||
|
statusCode: 401,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(await getPkgReleases(config)).toBeNull();
|
||||||
|
});
|
||||||
|
it('throws for 429', async () => {
|
||||||
|
api.get.mockImplementationOnce(() =>
|
||||||
|
Promise.reject({
|
||||||
|
statusCode: 429,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await expect(getPkgReleases(config)).rejects.toThrowError(
|
||||||
|
'registry-failure'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('throws for 5xx', async () => {
|
||||||
|
api.get.mockImplementationOnce(() =>
|
||||||
|
Promise.reject({
|
||||||
|
statusCode: 502,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await expect(getPkgReleases(config)).rejects.toThrowError(
|
||||||
|
'registry-failure'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('returns null for unknown error', async () => {
|
||||||
|
api.get.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
});
|
||||||
|
expect(await getPkgReleases(config)).toBeNull();
|
||||||
|
});
|
||||||
|
it('processes real data from CDN', async () => {
|
||||||
|
api.get.mockResolvedValueOnce({
|
||||||
|
body: 'foo/1.2.3',
|
||||||
|
} as GotResponse);
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({
|
||||||
|
...config,
|
||||||
|
registryUrls: ['https://cdn.cocoapods.org'],
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
releases: [
|
||||||
|
{
|
||||||
|
version: '1.2.3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('processes real data from Github', async () => {
|
||||||
|
api.get.mockResolvedValueOnce({
|
||||||
|
body: [{ name: '1.2.3' }],
|
||||||
|
} as GotResponse);
|
||||||
|
expect(
|
||||||
|
await getPkgReleases({
|
||||||
|
...config,
|
||||||
|
registryUrls: ['https://github.com/Artsy/Specs'],
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
releases: [
|
||||||
|
{
|
||||||
|
version: '1.2.3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
170
lib/datasource/pod/index.ts
Normal file
170
lib/datasource/pod/index.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { api } from '../../platform/github/gh-got-wrapper';
|
||||||
|
import { GetReleasesConfig, ReleaseResult } from '../common';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
|
||||||
|
export const id = 'pod';
|
||||||
|
|
||||||
|
const cacheNamespace = `datasource-${id}`;
|
||||||
|
const cacheMinutes = 30;
|
||||||
|
|
||||||
|
function shardParts(lookupName: string): string[] {
|
||||||
|
return crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(lookupName)
|
||||||
|
.digest('hex')
|
||||||
|
.slice(0, 3)
|
||||||
|
.split('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function releasesGithubUrl(
|
||||||
|
lookupName: string,
|
||||||
|
opts: { account: string; repo: string; useShard: boolean }
|
||||||
|
): string {
|
||||||
|
const { useShard, account, repo } = opts;
|
||||||
|
const prefix = 'https://api.github.com/repos';
|
||||||
|
const shard = shardParts(lookupName).join('/');
|
||||||
|
const suffix = useShard ? `${shard}/${lookupName}` : lookupName;
|
||||||
|
return `${prefix}/${account}/${repo}/contents/Specs/${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeRequest<T = unknown>(
|
||||||
|
url: string,
|
||||||
|
lookupName: string,
|
||||||
|
json = true
|
||||||
|
): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const resp = await api.get(url, { json });
|
||||||
|
if (resp && resp.body) {
|
||||||
|
return resp.body;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorData = { lookupName, err };
|
||||||
|
|
||||||
|
if (
|
||||||
|
err.statusCode === 429 ||
|
||||||
|
(err.statusCode >= 500 && err.statusCode < 600)
|
||||||
|
) {
|
||||||
|
logger.warn({ lookupName, err }, `CocoaPods registry failure`);
|
||||||
|
throw new Error('registry-failure');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.statusCode === 401) {
|
||||||
|
logger.debug(errorData, 'Authorization error');
|
||||||
|
} else if (err.statusCode === 404) {
|
||||||
|
logger.debug(errorData, 'Package lookup error');
|
||||||
|
} else {
|
||||||
|
logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const githubRegex = /^https:\/\/github\.com\/(?<account>[^/]+)\/(?<repo>[^/]+?)(\.git|\/.*)?$/;
|
||||||
|
|
||||||
|
async function getReleasesFromGithub(
|
||||||
|
lookupName: string,
|
||||||
|
registryUrl: string,
|
||||||
|
useShard = false
|
||||||
|
): Promise<ReleaseResult | null> {
|
||||||
|
const match = githubRegex.exec(registryUrl);
|
||||||
|
const { account, repo } = (match && match.groups) || {};
|
||||||
|
const opts = { account, repo, useShard };
|
||||||
|
const url = releasesGithubUrl(lookupName, opts);
|
||||||
|
const resp = await makeRequest<{ name: string }[]>(url, lookupName);
|
||||||
|
if (resp) {
|
||||||
|
const releases = resp.map(({ name }) => ({ version: name }));
|
||||||
|
return { releases };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useShard) {
|
||||||
|
return getReleasesFromGithub(lookupName, registryUrl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function releasesCDNUrl(lookupName: string, registryUrl: string): string {
|
||||||
|
const shard = shardParts(lookupName).join('_');
|
||||||
|
return `${registryUrl}/all_pods_versions_${shard}.txt`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getReleasesFromCDN(
|
||||||
|
lookupName: string,
|
||||||
|
registryUrl: string
|
||||||
|
): Promise<ReleaseResult | null> {
|
||||||
|
const url = releasesCDNUrl(lookupName, registryUrl);
|
||||||
|
const resp = await makeRequest<string>(url, lookupName, false);
|
||||||
|
if (resp) {
|
||||||
|
const lines = resp.split('\n');
|
||||||
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
||||||
|
const line = lines[idx];
|
||||||
|
const [name, ...versions] = line.split('/');
|
||||||
|
if (name === lookupName.replace(/\/.*$/, '')) {
|
||||||
|
const releases = versions.map(version => ({ version }));
|
||||||
|
return { releases };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCDN = 'https://cdn.cocoapods.org';
|
||||||
|
|
||||||
|
function isDefaultRepo(url: string): boolean {
|
||||||
|
const match = githubRegex.exec(url);
|
||||||
|
if (match) {
|
||||||
|
const { account, repo } = match.groups || {};
|
||||||
|
return (
|
||||||
|
account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs'
|
||||||
|
); // https://github.com/CocoaPods/Specs.git
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPkgReleases(
|
||||||
|
config: GetReleasesConfig
|
||||||
|
): Promise<ReleaseResult | null> {
|
||||||
|
const { lookupName } = config;
|
||||||
|
let { registryUrls } = config;
|
||||||
|
registryUrls =
|
||||||
|
registryUrls && registryUrls.length ? registryUrls : [defaultCDN];
|
||||||
|
|
||||||
|
if (!lookupName) {
|
||||||
|
logger.debug(config, `CocoaPods: invalid lookup name`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const podName = lookupName.replace(/\/.*$/, '');
|
||||||
|
|
||||||
|
const cachedResult = await renovateCache.get<ReleaseResult>(
|
||||||
|
cacheNamespace,
|
||||||
|
podName
|
||||||
|
);
|
||||||
|
/* istanbul ignore next line */
|
||||||
|
if (cachedResult) {
|
||||||
|
logger.debug(`CocoaPods: Return cached result for ${podName}`);
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: ReleaseResult | null = null;
|
||||||
|
for (let idx = 0; !result && idx < registryUrls.length; idx += 1) {
|
||||||
|
let registryUrl = registryUrls[idx].replace(/\/+$/, '');
|
||||||
|
|
||||||
|
// In order to not abuse github API limits, query CDN instead
|
||||||
|
if (isDefaultRepo(registryUrl)) registryUrl = defaultCDN;
|
||||||
|
|
||||||
|
if (githubRegex.exec(registryUrl)) {
|
||||||
|
result = await getReleasesFromGithub(podName, registryUrl);
|
||||||
|
} else {
|
||||||
|
result = await getReleasesFromCDN(podName, registryUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
await renovateCache.set(cacheNamespace, podName, result, cacheMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
72
lib/manager/cocoapods/__fixtures__/Podfile.complex
Normal file
72
lib/manager/cocoapods/__fixtures__/Podfile.complex
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
platform :ios, '9.0'
|
||||||
|
# use_frameworks!
|
||||||
|
inhibit_all_warnings!
|
||||||
|
|
||||||
|
source "https://github.com/CocoaPods/Specs.git"
|
||||||
|
|
||||||
|
target 'Sample' do
|
||||||
|
|
||||||
|
pod 'IQKeyboardManager', '~> 6.5.0'
|
||||||
|
pod 'CYLTabBarController', '~> 1.28.3'
|
||||||
|
|
||||||
|
pod 'PureLayout', '~> 3.1.4'
|
||||||
|
pod 'AFNetworking/Serialization', '~> 3.2.1'
|
||||||
|
pod 'AFNetworking/Security', '~> 3.2.1'
|
||||||
|
pod 'AFNetworking/Reachability', '~> 3.2.1'
|
||||||
|
pod 'AFNetworking/NSURLSession', '~> 3.2.1'
|
||||||
|
# pod 'SVProgressHUD', '~> 2.2.5'
|
||||||
|
pod 'MBProgressHUD', '~> 1.1.0'
|
||||||
|
pod 'MJRefresh', '~> 3.1.16'
|
||||||
|
pod 'MJExtension', '~> 3.1.0'
|
||||||
|
pod 'TYPagerController', '~> 2.1.2'
|
||||||
|
pod 'YYImage', '~> 1.0.4'
|
||||||
|
pod 'SDWebImage', '~> 5.0'
|
||||||
|
pod 'SDCycleScrollView','~> 1.80'
|
||||||
|
pod 'NullSafe', '~> 2.0'
|
||||||
|
# pod 'ZLPhotoBrowser'
|
||||||
|
pod 'TZImagePickerController', '~> 3.2.1'
|
||||||
|
pod 'TOCropViewController', '~> 2.5.1'
|
||||||
|
# pod 'RSKImageCropper', '~> 2.2.3'
|
||||||
|
# pod 'LBPhotoBrowser', '~> 2.2.2'
|
||||||
|
# pod 'YBImageBrowser', '~> 3.0.3'
|
||||||
|
pod 'FMDB', '~> 2.7.5'
|
||||||
|
pod 'FDStackView', '~> 1.0.1'
|
||||||
|
pod 'LYEmptyView'
|
||||||
|
|
||||||
|
pod 'MMKV', '~> 1.0.22'
|
||||||
|
|
||||||
|
pod 'fishhook'
|
||||||
|
|
||||||
|
pod 'CocoaLumberjack', '~> 3.5.3'
|
||||||
|
|
||||||
|
pod 'GZIP', '~> 1.2'
|
||||||
|
|
||||||
|
pod 'LBXScan/LBXNative','~> 2.3'
|
||||||
|
pod 'LBXScan/LBXZXing','~> 2.3'
|
||||||
|
# pod 'LBXScan/LBXZBar','~> 2.3'
|
||||||
|
pod 'LBXScan/UI','~> 2.3'
|
||||||
|
|
||||||
|
pod 'MLeaksFinder'
|
||||||
|
pod 'FBMemoryProfiler'
|
||||||
|
|
||||||
|
target 'SampleTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
# Pods for testing
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'SampleUITests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
# Pods for testing
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f <= 8.0
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
lib/manager/cocoapods/__fixtures__/Podfile.simple
Normal file
11
lib/manager/cocoapods/__fixtures__/Podfile.simple
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
source 'https://github.com/Artsy/Specs.git'
|
||||||
|
|
||||||
|
pod 'a'
|
||||||
|
pod 'a/sub'
|
||||||
|
pod 'b', '1.2.3'
|
||||||
|
pod 'c', "1.2.3"
|
||||||
|
pod 'd', :path => '~/Documents/Alamofire'
|
||||||
|
pod 'e', :git => 'e.git'
|
||||||
|
pod 'f', :git => 'f.git', :branch => 'dev'
|
||||||
|
pod 'g', :git => 'g.git', :tag => '3.2.1'
|
||||||
|
pod 'h', :git => 'https://github.com/foo/bar.git', :tag => '0.0.1'
|
167
lib/manager/cocoapods/__snapshots__/artifacts.spec.ts.snap
Normal file
167
lib/manager/cocoapods/__snapshots__/artifacts.spec.ts.snap
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() catches write error 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"artifactError": Object {
|
||||||
|
"lockFile": "Podfile.lock",
|
||||||
|
"stderr": "not found",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() catches write error 2`] = `Array []`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() dynamically selects Docker image tag 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"cmd": "docker pull renovate/cocoapods:1.2.4",
|
||||||
|
"options": Object {
|
||||||
|
"encoding": "utf-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"cmd": "docker run --rm --user=ubuntu -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods:1.2.4 bash -l -c \\"pod install\\"",
|
||||||
|
"options": Object {
|
||||||
|
"cwd": "/tmp/github/some/repo",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"env": Object {
|
||||||
|
"HOME": "/home/user",
|
||||||
|
"HTTPS_PROXY": "https://example.com",
|
||||||
|
"HTTP_PROXY": "http://example.com",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US",
|
||||||
|
"NO_PROXY": "localhost",
|
||||||
|
"PATH": "/tmp/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() falls back to the \`latest\` Docker image tag 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"cmd": "docker pull renovate/cocoapods:latest",
|
||||||
|
"options": Object {
|
||||||
|
"encoding": "utf-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"cmd": "docker run --rm --user=ubuntu -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods:latest bash -l -c \\"pod install\\"",
|
||||||
|
"options": Object {
|
||||||
|
"cwd": "/tmp/github/some/repo",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"env": Object {
|
||||||
|
"HOME": "/home/user",
|
||||||
|
"HTTPS_PROXY": "https://example.com",
|
||||||
|
"HTTP_PROXY": "http://example.com",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US",
|
||||||
|
"NO_PROXY": "localhost",
|
||||||
|
"PATH": "/tmp/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns null for invalid local directory 1`] = `Array []`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns null if no Podfile.lock found 1`] = `Array []`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns null if no updatedDeps were provided 1`] = `Array []`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns null if unchanged 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"cmd": "pod install",
|
||||||
|
"options": Object {
|
||||||
|
"cwd": "/tmp/github/some/repo",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"env": Object {
|
||||||
|
"HOME": "/home/user",
|
||||||
|
"HTTPS_PROXY": "https://example.com",
|
||||||
|
"HTTP_PROXY": "http://example.com",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US",
|
||||||
|
"NO_PROXY": "localhost",
|
||||||
|
"PATH": "/tmp/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns null if updatedDeps is empty 1`] = `Array []`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns pod exec error 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"artifactError": Object {
|
||||||
|
"lockFile": "Podfile.lock",
|
||||||
|
"stderr": "exec exception",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns pod exec error 2`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"cmd": "pod install",
|
||||||
|
"options": Object {
|
||||||
|
"cwd": "/tmp/github/some/repo",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"env": Object {
|
||||||
|
"HOME": "/home/user",
|
||||||
|
"HTTPS_PROXY": "https://example.com",
|
||||||
|
"HTTP_PROXY": "http://example.com",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US",
|
||||||
|
"NO_PROXY": "localhost",
|
||||||
|
"PATH": "/tmp/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns updated Podfile 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"file": Object {
|
||||||
|
"contents": "New Podfile",
|
||||||
|
"name": "Podfile.lock",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`.updateArtifacts() returns updated Podfile 2`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"cmd": "docker pull renovate/cocoapods",
|
||||||
|
"options": Object {
|
||||||
|
"encoding": "utf-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"cmd": "docker run --rm -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/cocoapods bash -l -c \\"pod install\\"",
|
||||||
|
"options": Object {
|
||||||
|
"cwd": "/tmp/github/some/repo",
|
||||||
|
"encoding": "utf-8",
|
||||||
|
"env": Object {
|
||||||
|
"HOME": "/home/user",
|
||||||
|
"HTTPS_PROXY": "https://example.com",
|
||||||
|
"HTTP_PROXY": "http://example.com",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US",
|
||||||
|
"NO_PROXY": "localhost",
|
||||||
|
"PATH": "/tmp/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
394
lib/manager/cocoapods/__snapshots__/extract.spec.ts.snap
Normal file
394
lib/manager/cocoapods/__snapshots__/extract.spec.ts.snap
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`lib/manager/cocoapods/extract extractPackageFile() extracts all dependencies 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"depName": "a",
|
||||||
|
"groupName": "a",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "a/sub",
|
||||||
|
"groupName": "a",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "1.2.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "b",
|
||||||
|
"groupName": "b",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 4,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/Artsy/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "1.2.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "c",
|
||||||
|
"groupName": "c",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 5,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/Artsy/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "d",
|
||||||
|
"groupName": "d",
|
||||||
|
"skipReason": "path-dependency",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "e",
|
||||||
|
"groupName": "e",
|
||||||
|
"skipReason": "git-dependency",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "f",
|
||||||
|
"groupName": "f",
|
||||||
|
"skipReason": "git-dependency",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "0.0.1",
|
||||||
|
"datasource": "github-tags",
|
||||||
|
"depName": "h",
|
||||||
|
"lookupName": "foo/bar",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib/manager/cocoapods/extract extractPackageFile() extracts all dependencies 2`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 6.5.0",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "IQKeyboardManager",
|
||||||
|
"groupName": "IQKeyboardManager",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 8,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.28.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "CYLTabBarController",
|
||||||
|
"groupName": "CYLTabBarController",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 9,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.1.4",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "PureLayout",
|
||||||
|
"groupName": "PureLayout",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 11,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.2.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "AFNetworking/Serialization",
|
||||||
|
"groupName": "AFNetworking",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 12,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.2.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "AFNetworking/Security",
|
||||||
|
"groupName": "AFNetworking",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 13,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.2.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "AFNetworking/Reachability",
|
||||||
|
"groupName": "AFNetworking",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 14,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.2.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "AFNetworking/NSURLSession",
|
||||||
|
"groupName": "AFNetworking",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 15,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.1.0",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "MBProgressHUD",
|
||||||
|
"groupName": "MBProgressHUD",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 17,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.1.16",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "MJRefresh",
|
||||||
|
"groupName": "MJRefresh",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 18,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.1.0",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "MJExtension",
|
||||||
|
"groupName": "MJExtension",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 19,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.1.2",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "TYPagerController",
|
||||||
|
"groupName": "TYPagerController",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 20,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.0.4",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "YYImage",
|
||||||
|
"groupName": "YYImage",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 21,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 5.0",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "SDWebImage",
|
||||||
|
"groupName": "SDWebImage",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 22,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.80",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "SDCycleScrollView",
|
||||||
|
"groupName": "SDCycleScrollView",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 23,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.0",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "NullSafe",
|
||||||
|
"groupName": "NullSafe",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 24,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.2.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "TZImagePickerController",
|
||||||
|
"groupName": "TZImagePickerController",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 26,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.5.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "TOCropViewController",
|
||||||
|
"groupName": "TOCropViewController",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 27,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.7.5",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "FMDB",
|
||||||
|
"groupName": "FMDB",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 31,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.0.1",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "FDStackView",
|
||||||
|
"groupName": "FDStackView",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 32,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "LYEmptyView",
|
||||||
|
"groupName": "LYEmptyView",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.0.22",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "MMKV",
|
||||||
|
"groupName": "MMKV",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 35,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "fishhook",
|
||||||
|
"groupName": "fishhook",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 3.5.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "CocoaLumberjack",
|
||||||
|
"groupName": "CocoaLumberjack",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 39,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 1.2",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "GZIP",
|
||||||
|
"groupName": "GZIP",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 41,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "LBXScan/LBXNative",
|
||||||
|
"groupName": "LBXScan",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 43,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "LBXScan/LBXZXing",
|
||||||
|
"groupName": "LBXScan",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 44,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"currentValue": "~> 2.3",
|
||||||
|
"datasource": "pod",
|
||||||
|
"depName": "LBXScan/UI",
|
||||||
|
"groupName": "LBXScan",
|
||||||
|
"managerData": Object {
|
||||||
|
"lineNumber": 46,
|
||||||
|
},
|
||||||
|
"registryUrls": Array [
|
||||||
|
"https://github.com/CocoaPods/Specs.git",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "MLeaksFinder",
|
||||||
|
"groupName": "MLeaksFinder",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"depName": "FBMemoryProfiler",
|
||||||
|
"groupName": "FBMemoryProfiler",
|
||||||
|
"skipReason": "unknown-version",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
217
lib/manager/cocoapods/artifacts.spec.ts
Normal file
217
lib/manager/cocoapods/artifacts.spec.ts
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
import { join } from 'upath';
|
||||||
|
import _fs from 'fs-extra';
|
||||||
|
import { exec as _exec } from 'child_process';
|
||||||
|
import Git from 'simple-git/promise';
|
||||||
|
import { platform as _platform } from '../../platform';
|
||||||
|
import { updateArtifacts } from '.';
|
||||||
|
import * as _datasource from '../../datasource/docker';
|
||||||
|
import { mocked } from '../../../test/util';
|
||||||
|
import { envMock, mockExecAll } from '../../../test/execUtil';
|
||||||
|
import * as _env from '../../util/exec/env';
|
||||||
|
import { setExecConfig } from '../../util/exec';
|
||||||
|
import { BinarySource } from '../../util/exec/common';
|
||||||
|
|
||||||
|
jest.mock('fs-extra');
|
||||||
|
jest.mock('child_process');
|
||||||
|
jest.mock('../../util/exec/env');
|
||||||
|
jest.mock('../../platform');
|
||||||
|
jest.mock('../../datasource/docker');
|
||||||
|
|
||||||
|
const fs: jest.Mocked<typeof _fs> = _fs as any;
|
||||||
|
const exec: jest.Mock<typeof _exec> = _exec as any;
|
||||||
|
const env = mocked(_env);
|
||||||
|
const platform = mocked(_platform);
|
||||||
|
const datasource = mocked(_datasource);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
localDir: join('/tmp/github/some/repo'),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('.updateArtifacts()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
env.getChildProcessEnv.mockReturnValue(envMock.basic);
|
||||||
|
setExecConfig(config);
|
||||||
|
|
||||||
|
datasource.getPkgReleases.mockResolvedValue({
|
||||||
|
releases: [
|
||||||
|
{ version: '1.2.0' },
|
||||||
|
{ version: '1.2.1' },
|
||||||
|
{ version: '1.2.2' },
|
||||||
|
{ version: '1.2.3' },
|
||||||
|
{ version: '1.2.4' },
|
||||||
|
{ version: '1.2.5' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns null if no Podfile.lock found', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns null if no updatedDeps were provided', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: [],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns null for invalid local directory', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
const noLocalDirConfig = {
|
||||||
|
localDir: undefined,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config: noLocalDirConfig,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns null if updatedDeps is empty', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: [],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns null if unchanged', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
platform.getFile.mockResolvedValueOnce('Current Podfile');
|
||||||
|
platform.getRepoStatus.mockResolvedValueOnce({
|
||||||
|
modified: [],
|
||||||
|
} as Git.StatusResult);
|
||||||
|
fs.readFile.mockResolvedValueOnce('Current Podfile' as any);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toBeNull();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns updated Podfile', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
setExecConfig({ ...config, binarySource: BinarySource.Docker });
|
||||||
|
platform.getFile.mockResolvedValueOnce('Old Podfile');
|
||||||
|
platform.getRepoStatus.mockResolvedValueOnce({
|
||||||
|
modified: ['Podfile.lock'],
|
||||||
|
} as Git.StatusResult);
|
||||||
|
fs.readFile.mockResolvedValueOnce('New Podfile' as any);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('catches write error', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
platform.getFile.mockResolvedValueOnce('Current Podfile');
|
||||||
|
fs.outputFile.mockImplementationOnce(() => {
|
||||||
|
throw new Error('not found');
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('returns pod exec error', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec, new Error('exec exception'));
|
||||||
|
platform.getFile.mockResolvedValueOnce('Old Podfile.lock');
|
||||||
|
fs.outputFile.mockResolvedValueOnce(null as never);
|
||||||
|
fs.readFile.mockResolvedValueOnce('Old Podfile.lock' as any);
|
||||||
|
expect(
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('dynamically selects Docker image tag', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
|
||||||
|
setExecConfig({
|
||||||
|
...config,
|
||||||
|
binarySource: 'docker',
|
||||||
|
dockerUser: 'ubuntu',
|
||||||
|
});
|
||||||
|
|
||||||
|
platform.getFile.mockResolvedValueOnce('COCOAPODS: 1.2.4');
|
||||||
|
|
||||||
|
fs.readFile.mockResolvedValueOnce('New Podfile' as any);
|
||||||
|
|
||||||
|
platform.getRepoStatus.mockResolvedValueOnce({
|
||||||
|
modified: ['Podfile.lock'],
|
||||||
|
} as Git.StatusResult);
|
||||||
|
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('falls back to the `latest` Docker image tag', async () => {
|
||||||
|
const execSnapshots = mockExecAll(exec);
|
||||||
|
|
||||||
|
setExecConfig({
|
||||||
|
...config,
|
||||||
|
binarySource: 'docker',
|
||||||
|
dockerUser: 'ubuntu',
|
||||||
|
});
|
||||||
|
|
||||||
|
platform.getFile.mockResolvedValueOnce('COCOAPODS: 1.2.4');
|
||||||
|
datasource.getPkgReleases.mockResolvedValueOnce({
|
||||||
|
releases: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.readFile.mockResolvedValueOnce('New Podfile' as any);
|
||||||
|
|
||||||
|
platform.getRepoStatus.mockResolvedValueOnce({
|
||||||
|
modified: ['Podfile.lock'],
|
||||||
|
} as Git.StatusResult);
|
||||||
|
|
||||||
|
await updateArtifacts({
|
||||||
|
packageFileName: 'Podfile',
|
||||||
|
updatedDeps: ['foo'],
|
||||||
|
newPackageFileContent: '',
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
expect(execSnapshots).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
89
lib/manager/cocoapods/artifacts.ts
Normal file
89
lib/manager/cocoapods/artifacts.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { platform } from '../../platform';
|
||||||
|
import { exec, ExecOptions } from '../../util/exec';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { UpdateArtifact, UpdateArtifactsResult } from '../common';
|
||||||
|
import {
|
||||||
|
getSiblingFileName,
|
||||||
|
readLocalFile,
|
||||||
|
writeLocalFile,
|
||||||
|
} from '../../util/fs';
|
||||||
|
|
||||||
|
export async function updateArtifacts({
|
||||||
|
packageFileName,
|
||||||
|
updatedDeps,
|
||||||
|
newPackageFileContent,
|
||||||
|
config,
|
||||||
|
}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
|
||||||
|
logger.debug(`cocoapods.getArtifacts(${packageFileName})`);
|
||||||
|
|
||||||
|
if (updatedDeps.length < 1) {
|
||||||
|
logger.debug('CocoaPods: empty update - returning null');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lockFileName = getSiblingFileName(packageFileName, 'Podfile.lock');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeLocalFile(packageFileName, newPackageFileContent);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn({ err }, 'Podfile could not be written');
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
artifactError: {
|
||||||
|
lockFile: lockFileName,
|
||||||
|
stderr: err.message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingLockFileContent = await platform.getFile(lockFileName);
|
||||||
|
if (!existingLockFileContent) {
|
||||||
|
logger.debug(`Lockfile not found: ${lockFileName}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = new RegExp(/^COCOAPODS: (?<cocoapodsVersion>.*)$/m).exec(
|
||||||
|
existingLockFileContent
|
||||||
|
);
|
||||||
|
const tagConstraint =
|
||||||
|
match && match.groups ? match.groups.cocoapodsVersion : null;
|
||||||
|
|
||||||
|
const cmd = 'pod install';
|
||||||
|
const execOptions: ExecOptions = {
|
||||||
|
cwdFile: packageFileName,
|
||||||
|
docker: {
|
||||||
|
image: 'renovate/cocoapods',
|
||||||
|
tagScheme: 'ruby',
|
||||||
|
tagConstraint,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await exec(cmd, execOptions);
|
||||||
|
} catch (err) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
artifactError: {
|
||||||
|
lockFile: lockFileName,
|
||||||
|
stderr: err.stderr || err.stdout || err.message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await platform.getRepoStatus();
|
||||||
|
if (!status.modified.includes(lockFileName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
logger.debug('Returning updated Gemfile.lock');
|
||||||
|
const lockFileContent = await readLocalFile(lockFileName);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
file: {
|
||||||
|
name: lockFileName,
|
||||||
|
contents: lockFileContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
25
lib/manager/cocoapods/extract.spec.ts
Normal file
25
lib/manager/cocoapods/extract.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
|
import { extractPackageFile } from '.';
|
||||||
|
|
||||||
|
const simplePodfile = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, './__fixtures__/Podfile.simple'),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
const complexPodfile = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, './__fixtures__/Podfile.complex'),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('lib/manager/cocoapods/extract', () => {
|
||||||
|
describe('extractPackageFile()', () => {
|
||||||
|
it('extracts all dependencies', () => {
|
||||||
|
const simpleResult = extractPackageFile(simplePodfile).deps;
|
||||||
|
expect(simpleResult).toMatchSnapshot();
|
||||||
|
|
||||||
|
const complexResult = extractPackageFile(complexPodfile).deps;
|
||||||
|
expect(complexResult).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
133
lib/manager/cocoapods/extract.ts
Normal file
133
lib/manager/cocoapods/extract.ts
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { PackageDependency, PackageFile } from '../common';
|
||||||
|
import * as datasourcePod from '../../datasource/pod';
|
||||||
|
|
||||||
|
const regexMappings = [
|
||||||
|
/^\s*pod\s+(['"])(?<spec>[^'"/]+)(\/(?<subspec>[^'"]+))?\1/,
|
||||||
|
/^\s*pod\s+(['"])[^'"]+\1\s*,\s*(['"])(?<currentValue>[^'"]+)\2\s*$/,
|
||||||
|
/,\s*:git\s*=>\s*(['"])(?<git>[^'"]+)\1/,
|
||||||
|
/,\s*:tag\s*=>\s*(['"])(?<tag>[^'"]+)\1/,
|
||||||
|
/,\s*:path\s*=>\s*(['"])(?<path>[^'"]+)\1/,
|
||||||
|
/^\s*source\s*(['"])(?<source>[^'"]+)\1/,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ParsedLine {
|
||||||
|
depName?: string;
|
||||||
|
groupName?: string;
|
||||||
|
spec?: string;
|
||||||
|
subspec?: string;
|
||||||
|
currentValue?: string;
|
||||||
|
git?: string;
|
||||||
|
tag?: string;
|
||||||
|
path?: string;
|
||||||
|
source?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseLine(line: string): ParsedLine {
|
||||||
|
const result: ParsedLine = {};
|
||||||
|
for (const regex of Object.values(regexMappings)) {
|
||||||
|
const match = regex.exec(line.replace(/#.*$/, ''));
|
||||||
|
if (match && match.groups) {
|
||||||
|
Object.assign(result, match.groups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.spec) {
|
||||||
|
const depName = result.subspec
|
||||||
|
? `${result.spec}/${result.subspec}`
|
||||||
|
: result.spec;
|
||||||
|
const groupName = result.spec;
|
||||||
|
if (depName) result.depName = depName;
|
||||||
|
if (groupName) result.groupName = groupName;
|
||||||
|
delete result.spec;
|
||||||
|
delete result.subspec;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gitDep(parsedLine: ParsedLine): PackageDependency | null {
|
||||||
|
const { depName, git, tag } = parsedLine;
|
||||||
|
if (git && git.startsWith('https://github.com/')) {
|
||||||
|
const githubMatch = /https:\/\/github\.com\/(?<account>[^/]+)\/(?<repo>[^/]+)/.exec(
|
||||||
|
git
|
||||||
|
);
|
||||||
|
const { account, repo } = (githubMatch && githubMatch.groups) || {};
|
||||||
|
if (account && repo) {
|
||||||
|
return {
|
||||||
|
datasource: 'github-tags',
|
||||||
|
depName,
|
||||||
|
lookupName: `${account}/${repo.replace(/\.git$/, '')}`,
|
||||||
|
currentValue: tag,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // TODO: gitlab or gitTags datasources?
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractPackageFile(content: string): PackageFile | null {
|
||||||
|
logger.trace('cocoapods.extractPackageFile()');
|
||||||
|
const deps: PackageDependency[] = [];
|
||||||
|
const lines: string[] = content.split('\n');
|
||||||
|
|
||||||
|
const registryUrls: string[] = [];
|
||||||
|
|
||||||
|
for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
|
||||||
|
const line = lines[lineNumber];
|
||||||
|
const parsedLine = parseLine(line);
|
||||||
|
const {
|
||||||
|
depName,
|
||||||
|
groupName,
|
||||||
|
currentValue,
|
||||||
|
git,
|
||||||
|
tag,
|
||||||
|
path,
|
||||||
|
source,
|
||||||
|
}: ParsedLine = parsedLine;
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
registryUrls.push(source.replace(/\/*$/, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depName) {
|
||||||
|
const managerData = { lineNumber };
|
||||||
|
let dep: PackageDependency = {
|
||||||
|
depName,
|
||||||
|
groupName,
|
||||||
|
skipReason: 'unknown-version',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentValue) {
|
||||||
|
dep = {
|
||||||
|
depName,
|
||||||
|
groupName,
|
||||||
|
datasource: datasourcePod.id,
|
||||||
|
currentValue,
|
||||||
|
managerData,
|
||||||
|
registryUrls,
|
||||||
|
};
|
||||||
|
} else if (git) {
|
||||||
|
if (tag) {
|
||||||
|
dep = { ...gitDep(parsedLine), managerData };
|
||||||
|
} else {
|
||||||
|
dep = {
|
||||||
|
depName,
|
||||||
|
groupName,
|
||||||
|
skipReason: 'git-dependency',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (path) {
|
||||||
|
dep = {
|
||||||
|
depName,
|
||||||
|
groupName,
|
||||||
|
skipReason: 'path-dependency',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
deps.push(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deps.length ? { deps } : null;
|
||||||
|
}
|
11
lib/manager/cocoapods/index.ts
Normal file
11
lib/manager/cocoapods/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import * as rubyVersioning from '../../versioning/ruby';
|
||||||
|
|
||||||
|
export { extractPackageFile } from './extract';
|
||||||
|
export { updateDependency } from './update';
|
||||||
|
export { updateArtifacts } from './artifacts';
|
||||||
|
|
||||||
|
export const defaultConfig = {
|
||||||
|
enabled: false,
|
||||||
|
fileMatch: ['(^|/)Podfile$'],
|
||||||
|
versioning: rubyVersioning.id,
|
||||||
|
};
|
9
lib/manager/cocoapods/readme.md
Normal file
9
lib/manager/cocoapods/readme.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
The `cocoapods` manager extracts dependencies with`datasource` type `pod`. It is currently in beta so disabled by default. To opt-in to the beta, add the following to your configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cocoapods": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
45
lib/manager/cocoapods/update.spec.ts
Normal file
45
lib/manager/cocoapods/update.spec.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
|
import { updateDependency } from '.';
|
||||||
|
|
||||||
|
const fileContent = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, './__fixtures__/Podfile.simple'),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('lib/manager/cocoapods/update', () => {
|
||||||
|
describe('updateDependency', () => {
|
||||||
|
it('replaces existing value', () => {
|
||||||
|
const upgrade = {
|
||||||
|
depName: 'b',
|
||||||
|
managerData: { lineNumber: 4 },
|
||||||
|
currentValue: '1.2.3',
|
||||||
|
newValue: '2.0.0',
|
||||||
|
};
|
||||||
|
const res = updateDependency({ fileContent, upgrade });
|
||||||
|
expect(res).not.toEqual(fileContent);
|
||||||
|
expect(res.includes(upgrade.newValue)).toBe(true);
|
||||||
|
});
|
||||||
|
it('returns same content', () => {
|
||||||
|
const upgrade = {
|
||||||
|
depName: 'b',
|
||||||
|
managerData: { lineNumber: 4 },
|
||||||
|
currentValue: '1.2.3',
|
||||||
|
newValue: '1.2.3',
|
||||||
|
};
|
||||||
|
const res = updateDependency({ fileContent, upgrade });
|
||||||
|
expect(res).toEqual(fileContent);
|
||||||
|
expect(res).toBe(fileContent);
|
||||||
|
});
|
||||||
|
it('returns null', () => {
|
||||||
|
const upgrade = {
|
||||||
|
depName: 'b',
|
||||||
|
managerData: { lineNumber: 0 },
|
||||||
|
currentValue: '1.2.3',
|
||||||
|
newValue: '2.0.0',
|
||||||
|
};
|
||||||
|
const res = updateDependency({ fileContent, upgrade });
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
39
lib/manager/cocoapods/update.ts
Normal file
39
lib/manager/cocoapods/update.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { UpdateDependencyConfig } from '../common';
|
||||||
|
import { parseLine } from './extract';
|
||||||
|
|
||||||
|
function lineContainsDep(line: string, dep: string): boolean {
|
||||||
|
const { depName } = parseLine(line);
|
||||||
|
return dep === depName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDependency({
|
||||||
|
fileContent,
|
||||||
|
upgrade,
|
||||||
|
}: UpdateDependencyConfig): string | null {
|
||||||
|
const { currentValue, managerData, depName, newValue } = upgrade;
|
||||||
|
|
||||||
|
// istanbul ignore if
|
||||||
|
if (!currentValue || !managerData || !depName) {
|
||||||
|
logger.warn('Cocoapods: invalid upgrade object');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`cocoapods.updateDependency: ${newValue}`);
|
||||||
|
|
||||||
|
const lines = fileContent.split('\n');
|
||||||
|
const lineToChange = lines[managerData.lineNumber];
|
||||||
|
|
||||||
|
if (!lineContainsDep(lineToChange, depName)) return null;
|
||||||
|
|
||||||
|
const regex = new RegExp(`(['"])${currentValue.replace('.', '\\.')}\\1`);
|
||||||
|
const newLine = lineToChange.replace(regex, `$1${newValue}$1`);
|
||||||
|
|
||||||
|
if (newLine === lineToChange) {
|
||||||
|
logger.debug('No changes necessary');
|
||||||
|
return fileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines[managerData.lineNumber] = newLine;
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
Loading…
Reference in a new issue