submodule-version 2.3.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -20
- package/dist/runner/handleErrors/handleErrors.d.ts +1 -1
- package/dist/runner/handleErrors/handleErrors.js +3 -10
- package/dist/runner/handleErrors/handleErrors.js.map +1 -1
- package/dist/runner/index.js +0 -0
- package/dist/runner/runner.d.ts +4 -0
- package/dist/runner/runner.js +91 -37
- package/dist/runner/runner.js.map +1 -1
- package/dist/src/PubGrub/PubGrub.d.ts +28 -0
- package/dist/src/PubGrub/PubGrub.js +366 -0
- package/dist/src/PubGrub/PubGrub.js.map +1 -0
- package/dist/src/PubGrub/index.d.ts +2 -0
- package/dist/src/PubGrub/index.js +7 -0
- package/dist/src/PubGrub/index.js.map +1 -0
- package/dist/src/PubGrub/types.d.ts +56 -0
- package/dist/src/PubGrub/types.js +3 -0
- package/dist/src/PubGrub/types.js.map +1 -0
- package/dist/src/entryStore.d.ts +16 -0
- package/dist/src/entryStore.js +152 -0
- package/dist/src/entryStore.js.map +1 -0
- package/dist/src/errors.d.ts +28 -0
- package/dist/src/errors.js +30 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/git/api/GithubError.d.ts +4 -0
- package/dist/src/git/api/GithubError.js +11 -0
- package/dist/src/git/api/GithubError.js.map +1 -0
- package/dist/src/git/api/api.d.ts +8 -0
- package/dist/src/git/api/api.js +133 -0
- package/dist/src/git/api/api.js.map +1 -0
- package/dist/src/git/api/index.d.ts +3 -0
- package/dist/src/git/api/index.js +8 -0
- package/dist/src/git/api/index.js.map +1 -0
- package/dist/src/git/git.d.ts +30 -23
- package/dist/src/git/git.js +8 -258
- package/dist/src/git/git.js.map +1 -1
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +3 -1
- package/dist/src/git/index.js.map +1 -1
- package/dist/src/git/local/index.d.ts +1 -0
- package/dist/src/git/local/index.js +6 -0
- package/dist/src/git/local/index.js.map +1 -0
- package/dist/src/git/local/local.d.ts +26 -0
- package/dist/src/git/local/local.js +288 -0
- package/dist/src/git/local/local.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +16 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/printError.d.ts +1 -0
- package/dist/src/printError.js +158 -0
- package/dist/src/printError.js.map +1 -0
- package/dist/src/sv.d.ts +18 -8
- package/dist/src/sv.js +235 -131
- package/dist/src/sv.js.map +1 -1
- package/dist/src/versionUtil/index.d.ts +2 -0
- package/dist/src/versionUtil/index.js +6 -0
- package/dist/src/versionUtil/index.js.map +1 -0
- package/dist/src/versionUtil/versionUtil.d.ts +16 -0
- package/dist/src/versionUtil/versionUtil.js +70 -0
- package/dist/src/versionUtil/versionUtil.js.map +1 -0
- package/package.json +4 -2
- package/runner/handleErrors/handleErrors.ts +3 -10
- package/runner/runner.ts +128 -46
- package/src/PubGrub/PubGrub.ts +578 -0
- package/src/PubGrub/__tests__/PubGrub.test.ts +351 -0
- package/src/PubGrub/index.ts +12 -0
- package/src/PubGrub/types.ts +72 -0
- package/src/__tests__/entryStore.test.ts +248 -0
- package/src/__tests__/printError.test.ts +148 -0
- package/src/__tests__/sv.test.ts +441 -0
- package/src/entryStore.ts +212 -0
- package/src/errors.ts +39 -0
- package/src/git/api/GithubError.ts +5 -0
- package/src/git/api/api.ts +180 -0
- package/src/git/api/index.ts +3 -0
- package/src/git/git.ts +8 -396
- package/src/git/index.ts +1 -0
- package/src/git/local/index.ts +1 -0
- package/src/git/local/local.ts +430 -0
- package/src/index.ts +17 -0
- package/src/printError.ts +189 -0
- package/src/sv.ts +321 -134
- package/src/versionUtil/__tests__/versionUtil.test.ts +62 -0
- package/src/versionUtil/index.ts +2 -0
- package/src/versionUtil/versionUtil.ts +103 -0
- package/tsconfig.json +1 -1
- package/dist/runner/handleErrors/handleBuildGraphError.d.ts +0 -1
- package/dist/runner/handleErrors/handleBuildGraphError.js +0 -42
- package/dist/runner/handleErrors/handleBuildGraphError.js.map +0 -1
- package/dist/runner/handleErrors/handleGitError.d.ts +0 -1
- package/dist/runner/handleErrors/handleGitError.js +0 -27
- package/dist/runner/handleErrors/handleGitError.js.map +0 -1
- package/dist/runner/handleErrors/handleInstallError.d.ts +0 -1
- package/dist/runner/handleErrors/handleInstallError.js +0 -30
- package/dist/runner/handleErrors/handleInstallError.js.map +0 -1
- package/dist/src/__tests__/versionUtil.test.d.ts +0 -1
- package/dist/src/__tests__/versionUtil.test.js +0 -80
- package/dist/src/__tests__/versionUtil.test.js.map +0 -1
- package/dist/src/buildGraph/GraphError.d.ts +0 -11
- package/dist/src/buildGraph/GraphError.js +0 -11
- package/dist/src/buildGraph/GraphError.js.map +0 -1
- package/dist/src/buildGraph/buildGraph.d.ts +0 -8
- package/dist/src/buildGraph/buildGraph.js +0 -100
- package/dist/src/buildGraph/buildGraph.js.map +0 -1
- package/dist/src/buildGraph/index.d.ts +0 -2
- package/dist/src/buildGraph/index.js +0 -8
- package/dist/src/buildGraph/index.js.map +0 -1
- package/dist/src/git/__tests__/parseGitUrl.test.d.ts +0 -1
- package/dist/src/git/__tests__/parseGitUrl.test.js +0 -30
- package/dist/src/git/__tests__/parseGitUrl.test.js.map +0 -1
- package/dist/src/versionUtil.d.ts +0 -13
- package/dist/src/versionUtil.js +0 -79
- package/dist/src/versionUtil.js.map +0 -1
- package/runner/handleErrors/handleBuildGraphError.ts +0 -49
- package/runner/handleErrors/handleGitError.ts +0 -28
- package/runner/handleErrors/handleInstallError.ts +0 -26
- package/src/__tests__/versionUtil.test.ts +0 -96
- package/src/buildGraph/GraphError.ts +0 -12
- package/src/buildGraph/buildGraph.ts +0 -150
- package/src/buildGraph/index.ts +0 -2
- package/src/versionUtil.ts +0 -97
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { printError } from '../printError';
|
|
2
|
+
import { SVError } from '../errors';
|
|
3
|
+
import { GitError, GithubError } from '../git';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
|
|
6
|
+
jest.mock('fs', () => ({
|
|
7
|
+
readFileSync: jest.fn(() => {
|
|
8
|
+
throw new Error('ENOENT');
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const readFileSyncMock = readFileSync as jest.Mock;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
readFileSyncMock.mockImplementation(() => {
|
|
16
|
+
throw new Error('ENOENT');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('printError', () => {
|
|
21
|
+
it('reports a requested version that does not exist', () => {
|
|
22
|
+
const error = new SVError('VERSION_CONFLICT', {
|
|
23
|
+
name: 'Medusa',
|
|
24
|
+
parents: { '<root>': '^0.5.4' },
|
|
25
|
+
constraints: [{ range: '^0.5.4', positive: true, requiredBy: '<root>' }],
|
|
26
|
+
versions: ['0.5.2', '0.5.1'],
|
|
27
|
+
chain: ['<root>', 'Medusa'],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(printError(error)).toBe(
|
|
31
|
+
'Package Medusa does not contain a version matching ^0.5.4'
|
|
32
|
+
+ ' required by the project root. Use one of [0.5.2, 0.5.1]',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
it('formats a version conflict with parents, chain and versions', () => {
|
|
36
|
+
const error = new SVError('VERSION_CONFLICT', {
|
|
37
|
+
name: 'C',
|
|
38
|
+
parents: { '<root>': '^1.3.2', 'B@1.2.0': 'not ^1.4.2' },
|
|
39
|
+
chain: ['A', 'B', 'C'],
|
|
40
|
+
versions: ['1.4.2', '1.3.2'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(printError(error)).toBe([
|
|
44
|
+
'Version conflict for C:',
|
|
45
|
+
' - ^1.3.2 required by the project root',
|
|
46
|
+
' - not ^1.4.2 required by B@1.2.0',
|
|
47
|
+
' chain: A -> B -> C',
|
|
48
|
+
' available versions: 1.4.2, 1.3.2',
|
|
49
|
+
].join('\n'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('shows the project name from package.json instead of <root>', () => {
|
|
53
|
+
readFileSyncMock.mockReturnValue(JSON.stringify({ name: 'ewa' }));
|
|
54
|
+
|
|
55
|
+
const error = new SVError('VERSION_CONFLICT', {
|
|
56
|
+
name: 'Medusa',
|
|
57
|
+
parents: { '<root>': '0.2.8' },
|
|
58
|
+
chain: ['<root>', 'Medusa'],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(printError(error)).toBe([
|
|
62
|
+
'Version conflict for Medusa:',
|
|
63
|
+
' - 0.2.8 required by ewa',
|
|
64
|
+
' chain: ewa -> Medusa',
|
|
65
|
+
].join('\n'));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('formats a circular dependency chain', () => {
|
|
69
|
+
const error = new SVError('CIRCULAR_DEPENDENCY', {
|
|
70
|
+
name: 'A',
|
|
71
|
+
chain: ['A', 'B', 'A'],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(printError(error)).toBe('Circular dependency: A -> B -> A');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('formats an unknown version with module and parent', () => {
|
|
78
|
+
const error = new SVError('UNKNOWN_VERSION', {
|
|
79
|
+
name: 'B',
|
|
80
|
+
parent: 'A@1.0.0',
|
|
81
|
+
version: '^2.x',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(printError(error)).toBe('Unknown version ^2.x for B in A@1.0.0');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('formats conflicting urls of a missing addon', () => {
|
|
88
|
+
const error = new SVError('ADDON_NOT_FOUND', {
|
|
89
|
+
name: 'B',
|
|
90
|
+
urls: ['git@git:one/B.git', 'git@git:two/B.git'],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(printError(error)).toBe(
|
|
94
|
+
'Module B is not found'
|
|
95
|
+
+ ' (conflicting urls: git@git:one/B.git, git@git:two/B.git)',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('formats a missing requested version with available versions', () => {
|
|
100
|
+
const error = new SVError('REQUESTED_VERSION_NOT_EXISTS', {
|
|
101
|
+
name: 'A',
|
|
102
|
+
requestedVersion: '2.0.0',
|
|
103
|
+
versions: ['1.2.0', '1.0.0'],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(printError(error)).toBe(
|
|
107
|
+
'Package A does not contain version 2.0.0. Use one of [1.2.0, 1.0.0]',
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('formats git layer errors', () => {
|
|
112
|
+
expect(printError(new GitError('GIT_SYNC_CONFLICT'))).toBe(
|
|
113
|
+
'Sync with remote failed - resolve conflicts manually',
|
|
114
|
+
);
|
|
115
|
+
expect(printError(new GitError('GIT_REPO_URL_REQUIRED'))).toBe(
|
|
116
|
+
'Module is not published yet - pass --repo-url for the first publish',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('includes the real git stderr of a failed submodule add', () => {
|
|
121
|
+
const error = new GitError('GIT_SUBMODULE_ADD_FAILED', {
|
|
122
|
+
url: 'git@git:repo/B.git',
|
|
123
|
+
reason: 'fatal: repository not found',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(printError(error)).toBe(
|
|
127
|
+
'Failed to add submodule git@git:repo/B.git'
|
|
128
|
+
+ ': fatal: repository not found',
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('formats GitHub API errors with a status hint', () => {
|
|
133
|
+
const error = new GithubError('GITHUB_API_FAILED', {
|
|
134
|
+
status: 404,
|
|
135
|
+
url: 'org/repo',
|
|
136
|
+
errors: ['NOT_FOUND'],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(printError(error)).toBe(
|
|
140
|
+
'GitHub API failed (404) for org/repo: NOT_FOUND'
|
|
141
|
+
+ ': repo not found or private (set GITHUB_TOKEN)',
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('falls back to the message of unhandled errors', () => {
|
|
146
|
+
expect(printError(new Error('boom'))).toBe('Unhandled error: boom');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { SV } from '../sv';
|
|
2
|
+
import { PubGrub } from '../PubGrub';
|
|
3
|
+
import { git } from '../git';
|
|
4
|
+
import { pkgJSONManager } from '../pkgJSONManager';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
jest.mock('../PubGrub', () => ({
|
|
8
|
+
PubGrub: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock('fs/promises', () => ({
|
|
12
|
+
readFile: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('../git', () => {
|
|
16
|
+
const actual = jest.requireActual('../git');
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
GitError: class GitError extends Error {},
|
|
20
|
+
git: {
|
|
21
|
+
parseUrl: actual.git.parseUrl,
|
|
22
|
+
local: {
|
|
23
|
+
isGitRepo: jest.fn(async () => true),
|
|
24
|
+
addSubmodule: jest.fn(async () => undefined),
|
|
25
|
+
checkout: jest.fn(async () => undefined),
|
|
26
|
+
rm: jest.fn(async () => undefined),
|
|
27
|
+
currentVersion: jest.fn(async () => null),
|
|
28
|
+
},
|
|
29
|
+
api: {
|
|
30
|
+
setToken: jest.fn(),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
jest.mock('../pkgJSONManager', () => ({
|
|
37
|
+
pkgJSONManager: {
|
|
38
|
+
read: jest.fn(async () => null),
|
|
39
|
+
write: jest.fn(async () => undefined),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const PubGrubMock = PubGrub as unknown as jest.Mock;
|
|
44
|
+
const resolveMock = jest.fn();
|
|
45
|
+
const readMock = pkgJSONManager.read as jest.Mock;
|
|
46
|
+
const writeMock = pkgJSONManager.write as jest.Mock;
|
|
47
|
+
const readFileMock = readFile as jest.Mock;
|
|
48
|
+
const localMock = git.local as unknown as Record<string, jest.Mock>;
|
|
49
|
+
const url = (name: string) => `git@git:repo/${name}.git`;
|
|
50
|
+
|
|
51
|
+
const entry = (
|
|
52
|
+
name: string,
|
|
53
|
+
versions: Record<string, Record<string, string>>,
|
|
54
|
+
version: string,
|
|
55
|
+
) => ({
|
|
56
|
+
name,
|
|
57
|
+
url: url(name),
|
|
58
|
+
versions,
|
|
59
|
+
version,
|
|
60
|
+
dependencies: versions[version] || {},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
66
|
+
PubGrubMock.mockImplementation(() => ({ resolve: resolveMock }));
|
|
67
|
+
resolveMock.mockResolvedValue({});
|
|
68
|
+
readFileMock.mockRejectedValue(new Error('ENOENT'));
|
|
69
|
+
localMock.isGitRepo.mockResolvedValue(true);
|
|
70
|
+
localMock.currentVersion.mockResolvedValue(null);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
jest.restoreAllMocks();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('SV.init', () => {
|
|
78
|
+
it('fails before resolving when package.json is missing', async () => {
|
|
79
|
+
readMock.mockResolvedValue(null);
|
|
80
|
+
const sv = new SV('/project');
|
|
81
|
+
|
|
82
|
+
await expect(sv.init()).rejects.toMatchObject({ error: 'NOT_A_NPM' });
|
|
83
|
+
expect(resolveMock).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('fails before resolving outside a git repository', async () => {
|
|
87
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
88
|
+
localMock.isGitRepo.mockResolvedValue(false);
|
|
89
|
+
const sv = new SV('/project');
|
|
90
|
+
|
|
91
|
+
await expect(sv.init()).rejects.toMatchObject({
|
|
92
|
+
error: 'NOT_A_GIT_REPO',
|
|
93
|
+
});
|
|
94
|
+
expect(resolveMock).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('passes rootDeps to PubGrub and stores its result', async () => {
|
|
98
|
+
const rootDeps = { 'git@git:repo/A.git': '^1.0.0' };
|
|
99
|
+
|
|
100
|
+
const result = {
|
|
101
|
+
A: entry('A', { '1.2.0': {} }, '1.2.0'),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
readMock.mockResolvedValue({ sv: rootDeps });
|
|
105
|
+
resolveMock.mockResolvedValue(result);
|
|
106
|
+
|
|
107
|
+
const sv = new SV('/project');
|
|
108
|
+
|
|
109
|
+
await sv.init();
|
|
110
|
+
|
|
111
|
+
expect(resolveMock).toHaveBeenCalledWith(rootDeps);
|
|
112
|
+
expect(sv.getResolution()).toBe(result);
|
|
113
|
+
expect(sv.listVersions()).toEqual({ A: ['1.2.0'] });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('guards result before init', () => {
|
|
117
|
+
expect(() => new SV('/project').getResolution()).toThrow(
|
|
118
|
+
expect.objectContaining({ error: 'NOT_INITIALIZED' }),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('adds the modules dir to package.json workspaces', async () => {
|
|
123
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
124
|
+
resolveMock.mockResolvedValue({});
|
|
125
|
+
|
|
126
|
+
const sv = new SV('/project');
|
|
127
|
+
|
|
128
|
+
await sv.init();
|
|
129
|
+
|
|
130
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
131
|
+
sv: {},
|
|
132
|
+
workspaces: ['modules/*'],
|
|
133
|
+
'sv-dir': 'modules',
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('keeps workspaces and respects a custom modules dir', async () => {
|
|
138
|
+
readMock.mockResolvedValue({ sv: {}, workspaces: ['packages/*'] });
|
|
139
|
+
resolveMock.mockResolvedValue({});
|
|
140
|
+
|
|
141
|
+
const sv = new SV('/project', 'libs');
|
|
142
|
+
|
|
143
|
+
await sv.init();
|
|
144
|
+
|
|
145
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
146
|
+
sv: {},
|
|
147
|
+
workspaces: ['packages/*', 'libs/*'],
|
|
148
|
+
'sv-dir': 'libs',
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('skips the write when the workspace entry exists', async () => {
|
|
153
|
+
readMock.mockResolvedValue({
|
|
154
|
+
sv: {},
|
|
155
|
+
workspaces: ['modules/*'],
|
|
156
|
+
'sv-dir': 'modules',
|
|
157
|
+
});
|
|
158
|
+
resolveMock.mockResolvedValue({});
|
|
159
|
+
|
|
160
|
+
const sv = new SV('/project');
|
|
161
|
+
|
|
162
|
+
await sv.init();
|
|
163
|
+
|
|
164
|
+
expect(writeMock).not.toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('uses the recorded sv-dir when no dir is passed', async () => {
|
|
168
|
+
readMock.mockResolvedValue({
|
|
169
|
+
sv: { [url('A')]: '*' },
|
|
170
|
+
workspaces: ['libs/*'],
|
|
171
|
+
'sv-dir': 'libs',
|
|
172
|
+
});
|
|
173
|
+
resolveMock.mockResolvedValue({
|
|
174
|
+
A: entry('A', { '1.0.0': {} }, '1.0.0'),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const sv = new SV('/project');
|
|
178
|
+
|
|
179
|
+
await sv.init();
|
|
180
|
+
|
|
181
|
+
expect(writeMock).not.toHaveBeenCalled();
|
|
182
|
+
expect(localMock.isGitRepo).toHaveBeenCalledWith('libs/A');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('derives the modules dir from existing .gitmodules', async () => {
|
|
186
|
+
readFileMock.mockResolvedValue([
|
|
187
|
+
'[submodule "addons/A"]',
|
|
188
|
+
'\tpath = addons/A',
|
|
189
|
+
'[submodule "addons/B"]',
|
|
190
|
+
'\tpath = addons/B',
|
|
191
|
+
].join('\n'));
|
|
192
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
193
|
+
resolveMock.mockResolvedValue({});
|
|
194
|
+
|
|
195
|
+
const sv = new SV('/project');
|
|
196
|
+
|
|
197
|
+
await sv.init();
|
|
198
|
+
|
|
199
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
200
|
+
sv: {},
|
|
201
|
+
workspaces: ['addons/*'],
|
|
202
|
+
'sv-dir': 'addons',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('SV.put', () => {
|
|
208
|
+
it('installs a root module and writes the range', async () => {
|
|
209
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
210
|
+
localMock.isGitRepo.mockResolvedValue(false);
|
|
211
|
+
resolveMock.mockResolvedValue({
|
|
212
|
+
B: entry('B', { '1.2.0': {} }, '1.2.0'),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const sv = new SV('/project');
|
|
216
|
+
|
|
217
|
+
await sv.put(url('B'), '^1.2.0');
|
|
218
|
+
|
|
219
|
+
expect(resolveMock).toHaveBeenCalledWith({ [url('B')]: '^1.2.0' });
|
|
220
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
221
|
+
sv: { [url('B')]: '^1.2.0' },
|
|
222
|
+
'sv-dir': 'modules',
|
|
223
|
+
});
|
|
224
|
+
expect(localMock.addSubmodule).toHaveBeenCalledWith(url('B'));
|
|
225
|
+
expect(localMock.checkout).toHaveBeenCalledWith('B', '1.2.0');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('falls back to * when version is omitted', async () => {
|
|
229
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
230
|
+
resolveMock.mockResolvedValue({});
|
|
231
|
+
|
|
232
|
+
const sv = new SV('/project');
|
|
233
|
+
|
|
234
|
+
await sv.put(url('B'));
|
|
235
|
+
|
|
236
|
+
expect(resolveMock).toHaveBeenCalledWith({ [url('B')]: '*' });
|
|
237
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
238
|
+
sv: { [url('B')]: '*' },
|
|
239
|
+
'sv-dir': 'modules',
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('treats a non-version second argument as parent name', async () => {
|
|
244
|
+
readMock.mockResolvedValue({
|
|
245
|
+
sv: { [url('A')]: '*' },
|
|
246
|
+
workspaces: ['modules/*'],
|
|
247
|
+
'sv-dir': 'modules',
|
|
248
|
+
});
|
|
249
|
+
resolveMock.mockResolvedValue({
|
|
250
|
+
A: entry('A', { '1.0.0': {} }, '1.0.0'),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const sv = new SV('/project');
|
|
254
|
+
|
|
255
|
+
await sv.init();
|
|
256
|
+
await sv.put(url('B'), 'A');
|
|
257
|
+
|
|
258
|
+
/* rootDeps не меняются, диапазон пишется в package.json родителя */
|
|
259
|
+
expect(resolveMock).toHaveBeenLastCalledWith({ [url('A')]: '*' });
|
|
260
|
+
expect(writeMock).toHaveBeenCalledWith(
|
|
261
|
+
{
|
|
262
|
+
sv: { [url('A')]: '*', [url('B')]: '*' },
|
|
263
|
+
workspaces: ['modules/*'],
|
|
264
|
+
'sv-dir': 'modules',
|
|
265
|
+
},
|
|
266
|
+
'A',
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('accepts an installed module name instead of url', async () => {
|
|
271
|
+
readMock.mockResolvedValue({
|
|
272
|
+
sv: { [url('B')]: '*' },
|
|
273
|
+
workspaces: ['modules/*'],
|
|
274
|
+
'sv-dir': 'modules',
|
|
275
|
+
});
|
|
276
|
+
resolveMock.mockResolvedValue({
|
|
277
|
+
B: entry('B', { '1.2.0': {} }, '1.2.0'),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const sv = new SV('/project');
|
|
281
|
+
|
|
282
|
+
await sv.init();
|
|
283
|
+
await sv.put('B', '^1.1.0');
|
|
284
|
+
|
|
285
|
+
expect(resolveMock).toHaveBeenLastCalledWith({ [url('B')]: '^1.1.0' });
|
|
286
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
287
|
+
sv: { [url('B')]: '^1.1.0' },
|
|
288
|
+
workspaces: ['modules/*'],
|
|
289
|
+
'sv-dir': 'modules',
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('rejects a name that is not an installed module', async () => {
|
|
294
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
295
|
+
resolveMock.mockResolvedValue({});
|
|
296
|
+
|
|
297
|
+
const sv = new SV('/project');
|
|
298
|
+
|
|
299
|
+
await sv.init();
|
|
300
|
+
|
|
301
|
+
await expect(sv.put('Ghost')).rejects.toMatchObject({
|
|
302
|
+
error: 'NOT_A_GIT_URL',
|
|
303
|
+
details: { url: 'Ghost' },
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('rejects put into a parent that is not installed', async () => {
|
|
308
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
309
|
+
resolveMock.mockResolvedValue({});
|
|
310
|
+
|
|
311
|
+
const sv = new SV('/project');
|
|
312
|
+
|
|
313
|
+
await sv.init();
|
|
314
|
+
writeMock.mockClear(); /* init пишет workspaces — интересует только put */
|
|
315
|
+
|
|
316
|
+
await expect(sv.put(url('B'), 'Nope')).rejects.toMatchObject({
|
|
317
|
+
error: 'ADDON_NOT_FOUND',
|
|
318
|
+
details: { name: 'Nope' },
|
|
319
|
+
});
|
|
320
|
+
expect(writeMock).not.toHaveBeenCalled();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('applies nothing when the check resolve fails', async () => {
|
|
324
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
325
|
+
resolveMock.mockRejectedValue(new Error('version conflict'));
|
|
326
|
+
|
|
327
|
+
const sv = new SV('/project');
|
|
328
|
+
|
|
329
|
+
await expect(sv.put(url('B'), '^1.0.0')).rejects.toThrow(
|
|
330
|
+
'version conflict',
|
|
331
|
+
);
|
|
332
|
+
expect(writeMock).not.toHaveBeenCalled();
|
|
333
|
+
expect(localMock.addSubmodule).not.toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('SV.delete', () => {
|
|
338
|
+
it('removes a root dependency and uninstalls its module', async () => {
|
|
339
|
+
readMock.mockResolvedValue({
|
|
340
|
+
sv: { [url('A')]: '*' },
|
|
341
|
+
workspaces: ['modules/*'],
|
|
342
|
+
'sv-dir': 'modules',
|
|
343
|
+
});
|
|
344
|
+
resolveMock.mockResolvedValueOnce({
|
|
345
|
+
A: entry('A', { '1.0.0': {} }, '1.0.0'),
|
|
346
|
+
});
|
|
347
|
+
resolveMock.mockResolvedValue({});
|
|
348
|
+
|
|
349
|
+
const sv = new SV('/project');
|
|
350
|
+
|
|
351
|
+
await sv.init();
|
|
352
|
+
await sv.delete('A');
|
|
353
|
+
|
|
354
|
+
expect(resolveMock).toHaveBeenLastCalledWith({});
|
|
355
|
+
expect(writeMock).toHaveBeenCalledWith({
|
|
356
|
+
sv: {},
|
|
357
|
+
workspaces: ['modules/*'],
|
|
358
|
+
'sv-dir': 'modules',
|
|
359
|
+
});
|
|
360
|
+
expect(localMock.rm).toHaveBeenCalledWith('A');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('rejects deleting a module missing from rootDeps', async () => {
|
|
364
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
365
|
+
|
|
366
|
+
const sv = new SV('/project');
|
|
367
|
+
|
|
368
|
+
await expect(sv.delete('Ghost')).rejects.toMatchObject({
|
|
369
|
+
error: 'ADDON_NOT_FOUND',
|
|
370
|
+
details: { name: 'Ghost' },
|
|
371
|
+
});
|
|
372
|
+
expect(resolveMock).not.toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('removes a dependency from the parent package.json', async () => {
|
|
376
|
+
readMock.mockImplementation(async (module?: string) => (
|
|
377
|
+
module === 'A'
|
|
378
|
+
? { sv: { [url('B')]: '^1.0.0' } }
|
|
379
|
+
: { sv: { [url('A')]: '*' } }
|
|
380
|
+
));
|
|
381
|
+
resolveMock.mockResolvedValue({
|
|
382
|
+
A: entry('A', { '1.0.0': { B: '^1.0.0' } }, '1.0.0'),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const sv = new SV('/project');
|
|
386
|
+
|
|
387
|
+
await sv.init();
|
|
388
|
+
await sv.delete('B', 'A');
|
|
389
|
+
|
|
390
|
+
expect(writeMock).toHaveBeenCalledWith({ sv: {} }, 'A');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('rejects deleting a module the parent does not depend on', async () => {
|
|
394
|
+
readMock.mockResolvedValue({ sv: { [url('A')]: '*' } });
|
|
395
|
+
resolveMock.mockResolvedValue({
|
|
396
|
+
A: entry('A', { '1.0.0': {} }, '1.0.0'),
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const sv = new SV('/project');
|
|
400
|
+
|
|
401
|
+
await sv.init();
|
|
402
|
+
|
|
403
|
+
await expect(sv.delete('B', 'A')).rejects.toMatchObject({
|
|
404
|
+
error: 'ADDON_NOT_FOUND',
|
|
405
|
+
details: { name: 'B' },
|
|
406
|
+
});
|
|
407
|
+
/* resolve не вызывался повторно — только внутри init */
|
|
408
|
+
expect(resolveMock).toHaveBeenCalledTimes(1);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('SV.listVersions', () => {
|
|
413
|
+
it('returns versions of one package by name', async () => {
|
|
414
|
+
readMock.mockResolvedValue({ sv: { [url('A')]: '*' } });
|
|
415
|
+
resolveMock.mockResolvedValue({
|
|
416
|
+
A: entry('A', { '1.0.0': {}, '1.2.0': {} }, '1.2.0'),
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const sv = new SV('/project');
|
|
420
|
+
|
|
421
|
+
await sv.init();
|
|
422
|
+
|
|
423
|
+
expect(sv.listVersions('A')).toEqual(['1.2.0', '1.0.0']);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('rejects an unknown package name', async () => {
|
|
427
|
+
readMock.mockResolvedValue({ sv: {} });
|
|
428
|
+
resolveMock.mockResolvedValue({});
|
|
429
|
+
|
|
430
|
+
const sv = new SV('/project');
|
|
431
|
+
|
|
432
|
+
await sv.init();
|
|
433
|
+
|
|
434
|
+
expect(() => sv.listVersions('Nope')).toThrow(
|
|
435
|
+
expect.objectContaining({
|
|
436
|
+
error: 'ADDON_NOT_FOUND',
|
|
437
|
+
details: { name: 'Nope' },
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
});
|