mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 10:09:50 +00:00
8a378fc5b4
For exact (single-version) specs, only update a pin whose current version matches the version being upgraded from. This prevents the repo-wide Package.resolved sweep from rewriting a same-named transitive pin in an unrelated package to a version that package's constraints don't allow. Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
1126 lines
32 KiB
TypeScript
1126 lines
32 KiB
TypeScript
import { fs, logger, scm } from '~test/util.ts';
|
|
import { GitTagsDatasource } from '../../datasource/git-tags/index.ts';
|
|
import { GithubTagsDatasource } from '../../datasource/github-tags/index.ts';
|
|
import { GitlabTagsDatasource } from '../../datasource/gitlab-tags/index.ts';
|
|
import * as datasource from '../../datasource/index.ts';
|
|
import { updateArtifacts } from './artifacts.ts';
|
|
|
|
vi.mock('../../../util/fs/index.ts');
|
|
vi.mock('../../datasource/index.ts', async () => {
|
|
const actual = await vi.importActual<typeof datasource>(
|
|
'../../datasource/index.ts',
|
|
);
|
|
return {
|
|
...actual,
|
|
getDigest: vi.fn(),
|
|
};
|
|
});
|
|
|
|
const v2Fixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'by-commit',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/example/by-commit',
|
|
state: {
|
|
revision: 'decaf',
|
|
},
|
|
},
|
|
{
|
|
identity: 'alamofire',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/Alamofire/Alamofire',
|
|
state: {
|
|
revision: 'f455c2975872ccd2d9c81594c658af65716e9b9a',
|
|
version: '5.9.1',
|
|
},
|
|
},
|
|
{
|
|
identity: 'swift-argument-parser',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/apple/swift-argument-parser.git',
|
|
state: {
|
|
revision: 'c8ed701b513cf5177118a175d85c4c06d8f9f97c',
|
|
version: '1.4.0',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
|
|
const v3Fixture = JSON.stringify(
|
|
{
|
|
originHash: 'abc123def456',
|
|
pins: [
|
|
{
|
|
identity: 'alamofire',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/Alamofire/Alamofire',
|
|
state: {
|
|
revision: 'f455c2975872ccd2d9c81594c658af65716e9b9a',
|
|
version: '5.9.1',
|
|
},
|
|
},
|
|
],
|
|
version: 3,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
|
|
describe('modules/manager/swift/artifacts', () => {
|
|
it('returns null when no Package.resolved files exist', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.swift', 'Sources/main.swift']);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null when updatedDeps is empty', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for lockFileMaintenance', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: { isLockFileMaintenance: true },
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for unparseable JSON', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue('not valid json{{{');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for unsupported v1 format', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(
|
|
JSON.stringify({
|
|
object: { pins: [] },
|
|
version: 1,
|
|
}),
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('updates a single pin version and revision', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newrevisionsha123');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
expect(contents).toContain('"revision": "newrevisionsha123"');
|
|
// Other pin unchanged
|
|
expect(contents).toContain('"version": "1.4.0"');
|
|
expect(contents).toContain(
|
|
'"revision": "c8ed701b513cf5177118a175d85c4c06d8f9f97c"',
|
|
);
|
|
});
|
|
|
|
it('parses lockfile with mixed source-control and registry pins', async () => {
|
|
// Regression test: SwiftPM Package Registry (SE-0292)
|
|
const v2MixedFixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'alamofire',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/Alamofire/Alamofire',
|
|
state: {
|
|
revision: 'f455c2975872ccd2d9c81594c658af65716e9b9a',
|
|
version: '5.9.1',
|
|
},
|
|
},
|
|
{
|
|
identity: 'example.registry-package',
|
|
kind: 'registry',
|
|
location: '',
|
|
state: {
|
|
version: '1.2.3',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2MixedFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newrevisionsha123');
|
|
|
|
await expect(
|
|
updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
}),
|
|
).resolves.toEqual([
|
|
{
|
|
file: {
|
|
type: 'addition',
|
|
path: 'Package.resolved',
|
|
contents: JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'alamofire',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/Alamofire/Alamofire',
|
|
state: {
|
|
revision: 'newrevisionsha123',
|
|
version: '5.10.0',
|
|
},
|
|
},
|
|
{
|
|
identity: 'example.registry-package',
|
|
kind: 'registry',
|
|
location: '',
|
|
state: { version: '1.2.3' },
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('does not write `from:` range to Package.resolved', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newrevisionsha123');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: '5.10.0',
|
|
newValue: 'from: "5.10.0"',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
expect(contents).not.toContain('from:');
|
|
});
|
|
|
|
it('updates multiple pins in one call', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest)
|
|
.mockResolvedValueOnce('alamofiresha')
|
|
.mockResolvedValueOnce('argparsersha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
{
|
|
depName: 'apple/swift-argument-parser',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v1.5.0',
|
|
newValue: '1.5.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
expect(contents).toContain('"revision": "alamofiresha"');
|
|
expect(contents).toContain('"version": "1.5.0"');
|
|
expect(contents).toContain('"revision": "argparsersha"');
|
|
});
|
|
|
|
it('skips dep with no matching pin', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'nonexistent/package',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v1.0.0',
|
|
newValue: '1.0.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('handles getDigest failure — updates version, keeps old revision', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue(null);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
// Revision unchanged since getDigest returned null
|
|
expect(contents).toContain(
|
|
'"revision": "f455c2975872ccd2d9c81594c658af65716e9b9a"',
|
|
);
|
|
});
|
|
|
|
it('updates multiple Package.resolved files', async () => {
|
|
scm.getFileList.mockResolvedValue([
|
|
'Package.resolved',
|
|
'App.xcworkspace/xcshareddata/swiftpm/Package.resolved',
|
|
]);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result![0].file!.path).toBe('Package.resolved');
|
|
expect(result![1].file!.path).toBe(
|
|
'App.xcworkspace/xcshareddata/swiftpm/Package.resolved',
|
|
);
|
|
});
|
|
|
|
it('matches URL with .git suffix normalization', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
// swift-argument-parser has .git in the fixture location
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'apple/swift-argument-parser',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v1.5.0',
|
|
newValue: '1.5.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "1.5.0"');
|
|
});
|
|
|
|
it('matches URL with trailing slash normalization', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
const fixtureWithSlash = v2Fixture.replace(
|
|
'"https://github.com/Alamofire/Alamofire"',
|
|
'"https://github.com/Alamofire/Alamofire/"',
|
|
);
|
|
fs.readLocalFile.mockResolvedValue(fixtureWithSlash);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
it('matches URL case-insensitively', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'alamofire/alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
it('handles git-tags datasource (full URL as depName)', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'https://github.com/Alamofire/Alamofire.git',
|
|
datasource: GitTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
});
|
|
|
|
it('matches pin when Package.resolved uses SSH URL', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
const sshFixture = v2Fixture.replace(
|
|
'"https://github.com/Alamofire/Alamofire"',
|
|
'"git@github.com:Alamofire/Alamofire.git"',
|
|
);
|
|
fs.readLocalFile.mockResolvedValue(sshFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
});
|
|
|
|
it('matches pin with git-tags datasource using SSH URL as depName', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'git@github.com:Alamofire/Alamofire.git',
|
|
datasource: GitTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
});
|
|
|
|
it('matches pin when Package.resolved uses ssh:// URL', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
const sshFixture = v2Fixture.replace(
|
|
'"https://github.com/Alamofire/Alamofire"',
|
|
'"ssh://git@github.com/Alamofire/Alamofire.git"',
|
|
);
|
|
fs.readLocalFile.mockResolvedValue(sshFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
});
|
|
|
|
it('falls back to basic normalization for host-only URLs', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
const hostOnlyFixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'my-lib',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://custom-registry.example.com',
|
|
state: {
|
|
revision: 'oldsha',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
fs.readLocalFile.mockResolvedValue(hostOnlyFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
// No match expected since host-only URL won't match any dep
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'org/my-lib',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v2.0.0',
|
|
newValue: '2.0.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('handles gitlab-tags with custom registryUrls', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
const gitlabFixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'my-lib',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://gitlab.example.com/org/my-lib',
|
|
state: {
|
|
revision: 'oldsha',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
fs.readLocalFile.mockResolvedValue(gitlabFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'org/my-lib',
|
|
datasource: GitlabTagsDatasource.id,
|
|
registryUrls: ['https://gitlab.example.com'],
|
|
newVersion: 'v2.0.0',
|
|
newValue: '2.0.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "2.0.0"');
|
|
expect(contents).toContain('"revision": "newsha"');
|
|
});
|
|
|
|
it('uses dep.newDigest when already present', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
newDigest: 'precomputedsha456',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"revision": "precomputedsha456"');
|
|
// getDigest should not have been called
|
|
expect(datasource.getDigest).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('preserves v3 originHash', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v3Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"originHash": "abc123def456"');
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
});
|
|
|
|
it('returns null when pin is already up-to-date', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.9.1', // same as fixture
|
|
newValue: '5.9.1',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('preserves formatting in targeted replacement', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newsha');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
// Verify overall structure is preserved
|
|
expect(contents).toContain('"identity": "alamofire"');
|
|
expect(contents).toContain('"version": 2');
|
|
});
|
|
|
|
it('returns null when Package.resolved cannot be read', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(null);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('skips dep with no newValue', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
// no newValue
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null when dep has no datasource or packageName', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
// no datasource — resolveCommitSha returns null
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
// Revision unchanged — no datasource to resolve SHA
|
|
expect(contents).toContain(
|
|
'"revision": "f455c2975872ccd2d9c81594c658af65716e9b9a"',
|
|
);
|
|
});
|
|
|
|
it('returns null when Package.resolved has no pins array', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(
|
|
JSON.stringify({ version: 2, somethingElse: true }),
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('handles getDigest throwing an error', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockRejectedValue(
|
|
new Error('network error'),
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.10.0"');
|
|
// Revision unchanged — getDigest threw
|
|
expect(contents).toContain(
|
|
'"revision": "f455c2975872ccd2d9c81594c658af65716e9b9a"',
|
|
);
|
|
});
|
|
|
|
it('if newValue is present, but newVersion is absent, no update is performed', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newrevisionsha123');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: undefined,
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
expect(logger.logger.debug).toHaveBeenCalledWith(
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
newVersion: undefined,
|
|
newValue: '5.10.0',
|
|
},
|
|
'swift: found a newValue but not a newVersion',
|
|
);
|
|
});
|
|
|
|
it('newValue is used to look up digest', async () => {
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(v2Fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue('newrevisionsha123');
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'Alamofire/Alamofire',
|
|
datasource: GithubTagsDatasource.id,
|
|
newVersion: 'v5.10.0',
|
|
newValue: '5.10.0',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
// getDigest should have been called with the v-prefixed newVersion tag
|
|
expect(datasource.getDigest).toHaveBeenCalledWith(
|
|
expect.objectContaining({ datasource: GithubTagsDatasource.id }),
|
|
'v5.10.0',
|
|
);
|
|
});
|
|
|
|
it('skips a pin in an unrelated package whose version does not match the from-version (exact spec)', async () => {
|
|
// Package that depends on Yams directly and pins it at the from-version.
|
|
const directFixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'yams',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/jpsim/Yams.git',
|
|
state: {
|
|
revision: '0e75dd8ccd5e3f27e4def2a7f0dd0ef5e3af5e2e',
|
|
version: '6.2.1',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
// Unrelated package: Yams is only a transitive pin (via XcodeGen, which
|
|
// requires `from: "5.0.0"`, i.e. < 6.0.0), so 6.x must not be written here.
|
|
const transitiveFixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'yams',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/jpsim/Yams.git',
|
|
state: {
|
|
revision: '01835dc202670b5bb90d07f3eae41867e9ed29f6',
|
|
version: '5.0.1',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
|
|
scm.getFileList.mockResolvedValue([
|
|
'Package.resolved',
|
|
'tools/Package.resolved',
|
|
]);
|
|
fs.readLocalFile
|
|
.mockResolvedValueOnce(directFixture)
|
|
.mockResolvedValueOnce(transitiveFixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue(
|
|
'a27b21e0c81c5bf42049b897a62aaf387e80f279',
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'jpsim/Yams',
|
|
datasource: GithubTagsDatasource.id,
|
|
currentValue: '6.2.1',
|
|
currentVersion: '6.2.1',
|
|
newVersion: '6.2.2',
|
|
newValue: '6.2.2',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
// Only the direct dependent (pinned at the from-version 6.2.1) is updated;
|
|
// the unrelated package's transitive pin (5.0.1) is left untouched.
|
|
expect(result).toHaveLength(1);
|
|
expect(result![0].file!.path).toBe('Package.resolved');
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "6.2.2"');
|
|
expect(contents).toContain(
|
|
'"revision": "a27b21e0c81c5bf42049b897a62aaf387e80f279"',
|
|
);
|
|
});
|
|
|
|
it('updates a pin whose version matches the from-version (exact spec)', async () => {
|
|
const fixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'yams',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/jpsim/Yams.git',
|
|
state: {
|
|
revision: '0e75dd8ccd5e3f27e4def2a7f0dd0ef5e3af5e2e',
|
|
version: '6.2.1',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue(
|
|
'a27b21e0c81c5bf42049b897a62aaf387e80f279',
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'jpsim/Yams',
|
|
datasource: GithubTagsDatasource.id,
|
|
currentValue: '6.2.1',
|
|
newVersion: '6.2.2',
|
|
newValue: '6.2.2',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "6.2.2"');
|
|
});
|
|
|
|
it('updates a pin for a range spec regardless of its current version (guard applies only to exact specs)', async () => {
|
|
const fixture = JSON.stringify(
|
|
{
|
|
pins: [
|
|
{
|
|
identity: 'yams',
|
|
kind: 'remoteSourceControl',
|
|
location: 'https://github.com/jpsim/Yams.git',
|
|
state: {
|
|
revision: '01835dc202670b5bb90d07f3eae41867e9ed29f6',
|
|
version: '5.0.1',
|
|
},
|
|
},
|
|
],
|
|
version: 2,
|
|
},
|
|
null,
|
|
2,
|
|
);
|
|
scm.getFileList.mockResolvedValue(['Package.resolved']);
|
|
fs.readLocalFile.mockResolvedValue(fixture);
|
|
vi.mocked(datasource.getDigest).mockResolvedValue(
|
|
'11b8e57b8cc07be48e5d1e35c9e2f7abdfd3c81b',
|
|
);
|
|
|
|
const result = await updateArtifacts({
|
|
packageFileName: 'Package.swift',
|
|
updatedDeps: [
|
|
{
|
|
depName: 'jpsim/Yams',
|
|
datasource: GithubTagsDatasource.id,
|
|
currentValue: 'from: "5.0.0"',
|
|
newVersion: '5.1.0',
|
|
newValue: 'from: "5.0.0"',
|
|
},
|
|
],
|
|
newPackageFileContent: '',
|
|
config: {},
|
|
});
|
|
|
|
expect(result).toHaveLength(1);
|
|
const { contents } = result![0].file as { contents: string };
|
|
expect(contents).toContain('"version": "5.1.0"');
|
|
});
|
|
});
|