apify-test-tools 0.9.1-beta.9 → 0.10.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/.github/actions/checkout-restore-dependencies/action.yaml +2 -28
- package/.github/workflows/public_platform-tests.yaml +0 -12
- package/.github/workflows/public_pr-build-test.yaml +2 -24
- package/.github/workflows/public_push-build-latest.yaml +3 -14
- package/.github/workflows/public_review.yaml +0 -7
- package/.github/workflows-package-version +1 -1
- package/CHANGELOG.md +4 -3
- package/CONTRIBUTING.md +2 -4
- package/README.md +147 -36
- package/bin/build-from-local.ts +15 -11
- package/bin/build.ts +127 -67
- package/bin/git.ts +140 -0
- package/bin/main.ts +128 -91
- package/bin/slack.ts +5 -2
- package/bin/types.ts +0 -73
- package/bin/utils/config/parser.ts +6 -5
- package/bin/utils/config/structures/base.ts +5 -1
- package/bin/utils/config/structures/grouped.ts +40 -0
- package/bin/utils/config/structures/legacy.ts +3 -1
- package/bin/utils.ts +7 -15
- package/dist/bin/build-from-local.d.ts +2 -2
- package/dist/bin/build-from-local.d.ts.map +1 -1
- package/dist/bin/build-from-local.js +11 -6
- package/dist/bin/build-from-local.js.map +1 -1
- package/dist/bin/build.d.ts +26 -4
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +90 -47
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/git.d.ts +40 -0
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +118 -0
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/main.d.ts +1 -20
- package/dist/bin/main.d.ts.map +1 -1
- package/dist/bin/main.js +65 -62
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +4 -2
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/types.d.ts +0 -64
- package/dist/bin/types.d.ts.map +1 -1
- package/dist/bin/utils/config/parser.d.ts.map +1 -1
- package/dist/bin/utils/config/parser.js +6 -5
- package/dist/bin/utils/config/parser.js.map +1 -1
- package/dist/bin/utils/config/structures/base.d.ts +3 -2
- package/dist/bin/utils/config/structures/base.d.ts.map +1 -1
- package/dist/bin/utils/config/structures/base.js +1 -0
- package/dist/bin/utils/config/structures/base.js.map +1 -1
- package/dist/bin/utils/config/structures/grouped.d.ts +6 -0
- package/dist/bin/utils/config/structures/grouped.d.ts.map +1 -0
- package/dist/bin/utils/config/structures/grouped.js +29 -0
- package/dist/bin/utils/config/structures/grouped.js.map +1 -0
- package/dist/bin/utils/config/structures/legacy.d.ts +1 -1
- package/dist/bin/utils/config/structures/legacy.d.ts.map +1 -1
- package/dist/bin/utils/config/structures/legacy.js +1 -1
- package/dist/bin/utils/config/structures/legacy.js.map +1 -1
- package/dist/bin/utils.d.ts +5 -2
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +6 -12
- package/dist/bin/utils.js.map +1 -1
- package/dist/test/unit/bin/build.test.d.ts +2 -0
- package/dist/test/unit/bin/build.test.d.ts.map +1 -0
- package/dist/test/unit/bin/build.test.js +46 -0
- package/dist/test/unit/bin/build.test.js.map +1 -0
- package/dist/test/unit/bin/git.test.js +104 -1
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/load-config.test.js +1 -0
- package/dist/test/unit/bin/utils/config/load-config.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/parser.test.js +23 -5
- package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
- package/dist/test/unit/bin/utils/config/structures/grouped.test.d.ts +2 -0
- package/dist/test/unit/bin/utils/config/structures/grouped.test.d.ts.map +1 -0
- package/dist/test/unit/bin/utils/config/structures/grouped.test.js +75 -0
- package/dist/test/unit/bin/utils/config/structures/grouped.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +65 -63
- package/test/unit/bin/build.test.ts +68 -0
- package/test/unit/bin/git.test.ts +122 -0
- package/test/unit/bin/utils/config/load-config.test.ts +1 -0
- package/test/unit/bin/utils/config/parser.test.ts +26 -5
- package/test/unit/bin/utils/config/structures/grouped.test.ts +90 -0
- package/bin/github.ts +0 -69
- package/dist/bin/github.d.ts +0 -11
- package/dist/bin/github.d.ts.map +0 -1
- package/dist/bin/github.js +0 -59
- package/dist/bin/github.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,65 +1,67 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
2
|
+
"name": "apify-test-tools",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "TBD",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/apify/apify-test-tools.git"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"apify-test-tools": "dist/bin/main.js"
|
|
15
|
+
},
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"main": "./dist/lib/lib.js",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@apify/consts": "^2.43.0",
|
|
20
|
+
"@slack/web-api": "^7.9.2",
|
|
21
|
+
"adm-zip": "^0.6.1",
|
|
22
|
+
"apify-client": "^2.22.2",
|
|
23
|
+
"ignore": "^7.0.5",
|
|
24
|
+
"yargs": "^18.0.0",
|
|
25
|
+
"zod": "^4.6.5"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@apify/eslint-config": "^1.0.0",
|
|
29
|
+
"@apify/tsconfig": "^0.1.1",
|
|
30
|
+
"@types/adm-zip": "^0.5.8",
|
|
31
|
+
"@types/node": "^24.0.0",
|
|
32
|
+
"@types/yargs": "^17.0.33",
|
|
33
|
+
"eslint": "^9.29.0",
|
|
34
|
+
"eslint-config-prettier": "^10.1.5",
|
|
35
|
+
"globals": "^17.0.0",
|
|
36
|
+
"husky": "^9.0.11",
|
|
37
|
+
"knip": "^5.65.0",
|
|
38
|
+
"lint-staged": "^15.2.2",
|
|
39
|
+
"prettier": "^3.5.3",
|
|
40
|
+
"tsx": "^4.20.3",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"typescript-eslint": "^8.34.1",
|
|
43
|
+
"vitest": "^3.2.4"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"vitest": ">=3.2.4"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"start": "npm run dist/bin/main.js",
|
|
50
|
+
"start:dev": "tsx bin/main.ts --workspace local-clone",
|
|
51
|
+
"build": "tsc && chmod +x ./dist/bin/main.js",
|
|
52
|
+
"lint": "eslint",
|
|
53
|
+
"lint:fix": "eslint --fix",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check .",
|
|
56
|
+
"type-check": "tsc --noEmit",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"check-unused": "npx knip",
|
|
59
|
+
"prepare": "husky .husky || true"
|
|
60
|
+
},
|
|
61
|
+
"lint-staged": {
|
|
62
|
+
"*.js": "eslint",
|
|
63
|
+
"*.ts": "eslint"
|
|
64
|
+
},
|
|
65
|
+
"author": "oklinov",
|
|
66
|
+
"license": "ISC"
|
|
65
67
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type * as ApifyClientTypes from 'apify-client';
|
|
2
|
+
import { ActorSourceType } from 'apify-client';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { assertRepoUrlMatchesDefaultVersion, resolveDefaultVersion } from '../../../bin/build.js';
|
|
6
|
+
|
|
7
|
+
const ACTOR = 'owner/actor';
|
|
8
|
+
const REPO_URL = 'git@github.com:owner/repo';
|
|
9
|
+
|
|
10
|
+
const makeActorInfo = (overrides: Partial<ApifyClientTypes.Actor> = {}): ApifyClientTypes.Actor =>
|
|
11
|
+
({
|
|
12
|
+
defaultRunOptions: { build: 'latest' },
|
|
13
|
+
taggedBuilds: { latest: { buildNumber: '0.1.5' } },
|
|
14
|
+
versions: [
|
|
15
|
+
{ versionNumber: '0.1', sourceType: ActorSourceType.GitRepo, gitRepoUrl: `${REPO_URL}#master:actors/a` },
|
|
16
|
+
],
|
|
17
|
+
...overrides,
|
|
18
|
+
}) as unknown as ApifyClientTypes.Actor;
|
|
19
|
+
|
|
20
|
+
describe('resolveDefaultVersion', () => {
|
|
21
|
+
it('should resolve the version the default build tag points to', () => {
|
|
22
|
+
const { defaultBuildTag, defaultBuildNumber, defaultVersionNumber, defaultVersion } = resolveDefaultVersion(
|
|
23
|
+
ACTOR,
|
|
24
|
+
makeActorInfo(),
|
|
25
|
+
);
|
|
26
|
+
expect(defaultBuildTag).toBe('latest');
|
|
27
|
+
expect(defaultBuildNumber).toBe('0.1.5');
|
|
28
|
+
expect(defaultVersionNumber).toBe('0.1');
|
|
29
|
+
expect(defaultVersion?.versionNumber).toBe('0.1');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should explain the setup requirement when there is no default build', () => {
|
|
33
|
+
expect(() => resolveDefaultVersion(ACTOR, makeActorInfo({ taggedBuilds: {} }))).toThrow(
|
|
34
|
+
'every Actor needs at least one build under its default build tag',
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('assertRepoUrlMatchesDefaultVersion', () => {
|
|
40
|
+
const { defaultVersion } = resolveDefaultVersion(ACTOR, makeActorInfo());
|
|
41
|
+
|
|
42
|
+
it('should pass when the repositories match in a different URL form', () => {
|
|
43
|
+
expect(() =>
|
|
44
|
+
assertRepoUrlMatchesDefaultVersion(ACTOR, defaultVersion, 'https://github.com/owner/repo.git'),
|
|
45
|
+
).not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should throw when the remote points to a different repository', () => {
|
|
49
|
+
expect(() => assertRepoUrlMatchesDefaultVersion(ACTOR, defaultVersion, 'git@github.com:fork/repo')).toThrow(
|
|
50
|
+
'Repository mismatch',
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should throw when the default version is not built from a Git repository', () => {
|
|
55
|
+
const sourceFilesVersion: ApifyClientTypes.ActorVersion = {
|
|
56
|
+
versionNumber: '0.1',
|
|
57
|
+
sourceType: ActorSourceType.SourceFiles,
|
|
58
|
+
sourceFiles: [],
|
|
59
|
+
};
|
|
60
|
+
expect(() => assertRepoUrlMatchesDefaultVersion(ACTOR, sourceFilesVersion, REPO_URL)).toThrow(
|
|
61
|
+
'has source type "SOURCE_FILES"',
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should throw when the default version no longer exists', () => {
|
|
66
|
+
expect(() => assertRepoUrlMatchesDefaultVersion(ACTOR, undefined, REPO_URL)).toThrow('no longer exists');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -5,8 +5,13 @@ import {
|
|
|
5
5
|
getBranchOnlyChangedFiles,
|
|
6
6
|
getChangedFiles,
|
|
7
7
|
getCommits,
|
|
8
|
+
getCurrentBranch,
|
|
9
|
+
getReleaseChanges,
|
|
10
|
+
getRepoName,
|
|
8
11
|
hasMergeFromTarget,
|
|
12
|
+
normalizeRepoUrl,
|
|
9
13
|
parseBaseCommit,
|
|
14
|
+
resolveReleaseBaseCommit,
|
|
10
15
|
} from '../../../bin/git.js';
|
|
11
16
|
import * as Utils from '../../../bin/utils.js';
|
|
12
17
|
|
|
@@ -250,3 +255,120 @@ describe('parseBaseCommit', () => {
|
|
|
250
255
|
expect(() => parseBaseCommit(badJson)).toThrow('Invalid base commit SHA');
|
|
251
256
|
});
|
|
252
257
|
});
|
|
258
|
+
|
|
259
|
+
describe('getCurrentBranch', () => {
|
|
260
|
+
it('should return the checked-out branch', () => {
|
|
261
|
+
vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockReturnValue('master');
|
|
262
|
+
expect(getCurrentBranch()).toBe('master');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should throw on a detached HEAD', () => {
|
|
266
|
+
vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockReturnValue('HEAD');
|
|
267
|
+
expect(() => getCurrentBranch()).toThrow('HEAD is detached');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('normalizeRepoUrl', () => {
|
|
272
|
+
it.each([
|
|
273
|
+
'git@github.com:Apify-Store/google-maps.git',
|
|
274
|
+
'git@github.com:apify-store/google-maps',
|
|
275
|
+
'https://github.com/apify-store/google-maps',
|
|
276
|
+
'https://github.com/apify-store/google-maps.git/',
|
|
277
|
+
'ssh://git@github.com/apify-store/google-maps',
|
|
278
|
+
'git@github.com:apify-store/google-maps#master:actors/foo',
|
|
279
|
+
'https://github.com/apify-store/google-maps#master',
|
|
280
|
+
])('should normalize %s', (url) => {
|
|
281
|
+
expect(normalizeRepoUrl(url)).toBe('github.com/apify-store/google-maps');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should tell different repositories apart', () => {
|
|
285
|
+
expect(normalizeRepoUrl('git@github.com:my-fork/google-maps.git')).not.toBe(
|
|
286
|
+
normalizeRepoUrl('git@github.com:apify-store/google-maps.git'),
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('getRepoName', () => {
|
|
292
|
+
it('should return the repository name', () => {
|
|
293
|
+
expect(getRepoName('git@github.com:apify-store/google-maps.git')).toBe('google-maps');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('resolveReleaseBaseCommit', () => {
|
|
298
|
+
const baseSha = 'b'.repeat(40);
|
|
299
|
+
|
|
300
|
+
it('should return the base commit when it is an ancestor of HEAD', () => {
|
|
301
|
+
vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockImplementation((cmd: string) => {
|
|
302
|
+
if (cmd.startsWith('git rev-parse --verify')) return baseSha;
|
|
303
|
+
if (cmd.startsWith('git merge-base')) return baseSha;
|
|
304
|
+
return '';
|
|
305
|
+
});
|
|
306
|
+
expect(resolveReleaseBaseCommit(baseSha)).toBe(baseSha);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should throw on the all-zeros SHA of a newly created branch', () => {
|
|
310
|
+
const spy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
|
|
311
|
+
expect(() => resolveReleaseBaseCommit('0'.repeat(40))).toThrow('the branch was just created');
|
|
312
|
+
expect(spy).not.toHaveBeenCalled();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should throw when the base commit is missing from the local history', () => {
|
|
316
|
+
vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockReturnValue('');
|
|
317
|
+
expect(() => resolveReleaseBaseCommit(baseSha)).toThrow('is not in the local git history');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should throw when the base commit is not an ancestor of HEAD (force push)', () => {
|
|
321
|
+
vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockImplementation((cmd: string) => {
|
|
322
|
+
if (cmd.startsWith('git rev-parse --verify')) return baseSha;
|
|
323
|
+
if (cmd.startsWith('git merge-base')) return 'c'.repeat(40);
|
|
324
|
+
return '';
|
|
325
|
+
});
|
|
326
|
+
expect(() => resolveReleaseBaseCommit(baseSha)).toThrow('is not an ancestor of HEAD');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should throw on an invalid SHA', () => {
|
|
330
|
+
expect(() => resolveReleaseBaseCommit('not-a-sha')).toThrow('Invalid base commit SHA');
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('getReleaseChanges', () => {
|
|
335
|
+
const baseSha = 'b'.repeat(40);
|
|
336
|
+
const headSha = 'd'.repeat(40);
|
|
337
|
+
const mergedBranchCommit = `${'1'.repeat(40)}»¦«Dev<dev@example.com>»¦«Date1»¦«feat: branch change`;
|
|
338
|
+
const mergeCommit = `${headSha}»¦«Dev<dev@example.com>»¦«Date2»¦«Merge pull request #1`;
|
|
339
|
+
|
|
340
|
+
let gitCommandSpy: MockInstance;
|
|
341
|
+
|
|
342
|
+
beforeEach(() => {
|
|
343
|
+
gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace').mockImplementation((cmd: string) => {
|
|
344
|
+
if (cmd === 'git rev-parse HEAD') return headSha;
|
|
345
|
+
if (cmd.startsWith('git log')) return `${mergeCommit}\n${mergedBranchCommit}`;
|
|
346
|
+
if (cmd.startsWith('git diff --name-only')) return 'actors/foo/src/main.ts\nCHANGELOG.md';
|
|
347
|
+
if (cmd === 'git')
|
|
348
|
+
return 'diff --git a/CHANGELOG.md b/CHANGELOG.md\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1 +1,2 @@\n+- Added foo\n # Changelog';
|
|
349
|
+
return '';
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should diff the range directly and return the commits oldest first', () => {
|
|
354
|
+
const changes = getReleaseChanges(baseSha);
|
|
355
|
+
|
|
356
|
+
expect(changes).toStrictEqual({
|
|
357
|
+
commits: [
|
|
358
|
+
{ sha: '1'.repeat(40), author: 'Dev<dev@example.com>', date: 'Date1', message: 'feat: branch change' },
|
|
359
|
+
{ sha: headSha, author: 'Dev<dev@example.com>', date: 'Date2', message: 'Merge pull request #1' },
|
|
360
|
+
],
|
|
361
|
+
changedFiles: ['actors/foo/src/main.ts', 'CHANGELOG.md'],
|
|
362
|
+
changelog: '- Added foo',
|
|
363
|
+
});
|
|
364
|
+
// Not from the parent of the oldest commit, which may predate the base commit for merge commits
|
|
365
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${baseSha} HEAD`);
|
|
366
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(
|
|
367
|
+
`git log --pretty=format:'%H»¦«%aN<%aE>»¦«%aD»¦«%s' ${baseSha}..HEAD`,
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should return null when HEAD is the base commit', () => {
|
|
372
|
+
expect(getReleaseChanges(headSha)).toBeNull();
|
|
373
|
+
});
|
|
374
|
+
});
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import { _privates, parseConfigFile } from '../../../../../bin/utils/config/parser.js';
|
|
4
4
|
import { CONFIG_FILE_STRATEGY } from '../../../../../bin/utils/config/structures/base.js';
|
|
5
|
+
import { GROUPED_PARSER } from '../../../../../bin/utils/config/structures/grouped.js';
|
|
5
6
|
import { LEGACY_PARSER } from '../../../../../bin/utils/config/structures/legacy.js';
|
|
6
7
|
|
|
7
8
|
const { selectStrategy, verifyConfiguration } = _privates;
|
|
@@ -10,27 +11,31 @@ const actor = (fields: Record<string, unknown> = {}) => ({
|
|
|
10
11
|
folder: 'actors/shopify',
|
|
11
12
|
actorFullName: 'myteam/shopify',
|
|
12
13
|
tokenEnvVar: 'APIFY_TOKEN',
|
|
14
|
+
overrideActorContext: undefined,
|
|
13
15
|
...fields,
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
describe('selectStrategy', () => {
|
|
17
19
|
it('falls back to the legacy strategy when no mode is declared', () => {
|
|
18
|
-
expect(selectStrategy(
|
|
20
|
+
expect(selectStrategy({})).toBe(LEGACY_PARSER);
|
|
19
21
|
});
|
|
20
22
|
|
|
21
|
-
it(
|
|
22
|
-
|
|
23
|
+
it.each([
|
|
24
|
+
[CONFIG_FILE_STRATEGY.LEGACY, LEGACY_PARSER],
|
|
25
|
+
[CONFIG_FILE_STRATEGY.GROUPED, GROUPED_PARSER],
|
|
26
|
+
])('honours an explicit "%s" mode', (mode, expected) => {
|
|
27
|
+
expect(selectStrategy({ mode })).toBe(expected);
|
|
23
28
|
});
|
|
24
29
|
|
|
25
30
|
it('throws on a mode no strategy is registered for', () => {
|
|
26
|
-
expect(() => selectStrategy('some-nonexistent-mode')).toThrow(
|
|
31
|
+
expect(() => selectStrategy('some-nonexistent-mode')).toThrow();
|
|
27
32
|
});
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
describe('verifyConfiguration', () => {
|
|
31
36
|
it('resolves the repo root to "" and gets rid of trailing slashes', () => {
|
|
32
37
|
const result = verifyConfiguration([
|
|
33
|
-
{ folder: '.', actorFullName: 'myteam/root', tokenEnvVar: 'APIFY_TOKEN' },
|
|
38
|
+
{ folder: '.', actorFullName: 'myteam/root', tokenEnvVar: 'APIFY_TOKEN', overrideActorContext: undefined },
|
|
34
39
|
{
|
|
35
40
|
folder: 'actors/shopify/',
|
|
36
41
|
actorFullName: 'myteam/shopify',
|
|
@@ -124,6 +129,22 @@ describe('parseConfigFile', () => {
|
|
|
124
129
|
]);
|
|
125
130
|
});
|
|
126
131
|
|
|
132
|
+
it('parses a grouped config file, which needs mode to be stripped', () => {
|
|
133
|
+
expect(
|
|
134
|
+
parseConfigFile({
|
|
135
|
+
mode: CONFIG_FILE_STRATEGY.GROUPED,
|
|
136
|
+
groups: {
|
|
137
|
+
myteam: {
|
|
138
|
+
actors: [{ folder: 'actors/shopify/', actorFullName: 'myteam/shopify' }],
|
|
139
|
+
tokenEnvVar: 'APIFY_TOKEN',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
).toEqual([
|
|
144
|
+
actor({ folder: 'actors/shopify', actorFullName: 'myteam/shopify', overrideActorContext: undefined }),
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
|
|
127
148
|
it('surfaces cross-entry violations from verifyConfiguration', () => {
|
|
128
149
|
expect(() =>
|
|
129
150
|
parseConfigFile({
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { GROUPED_PARSER } from '../../../../../../bin/utils/config/structures/grouped.js';
|
|
4
|
+
|
|
5
|
+
const actor = (fields: Record<string, unknown> = {}) => ({
|
|
6
|
+
folder: 'actors/shopify',
|
|
7
|
+
actorFullName: 'myteam/shopify',
|
|
8
|
+
...fields,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const group = (fields: Record<string, unknown> = {}) => ({
|
|
12
|
+
actors: [actor()],
|
|
13
|
+
tokenEnvVar: 'APIFY_TOKEN',
|
|
14
|
+
...fields,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('GROUPED_PARSER', () => {
|
|
18
|
+
it('flattens every group into one list, handing each actor its group’s settings', () => {
|
|
19
|
+
expect(
|
|
20
|
+
GROUPED_PARSER.parse({
|
|
21
|
+
groups: {
|
|
22
|
+
myteam: {
|
|
23
|
+
actors: [actor(), actor({ folder: 'actors/email', actorFullName: 'myteam/email' })],
|
|
24
|
+
tokenEnvVar: 'APIFY_TOKEN_MYTEAM',
|
|
25
|
+
overrideActorContext: ['packages'],
|
|
26
|
+
},
|
|
27
|
+
other: { actors: [actor({ folder: 'actors/x', actorFullName: 'other/x' })], tokenEnvVar: 'TOKEN' },
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
).toEqual([
|
|
31
|
+
{
|
|
32
|
+
folder: 'actors/shopify',
|
|
33
|
+
actorFullName: 'myteam/shopify',
|
|
34
|
+
tokenEnvVar: 'APIFY_TOKEN_MYTEAM',
|
|
35
|
+
overrideActorContext: ['packages'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
folder: 'actors/email',
|
|
39
|
+
actorFullName: 'myteam/email',
|
|
40
|
+
tokenEnvVar: 'APIFY_TOKEN_MYTEAM',
|
|
41
|
+
overrideActorContext: ['packages'],
|
|
42
|
+
},
|
|
43
|
+
{ folder: 'actors/x', actorFullName: 'other/x', tokenEnvVar: 'TOKEN', overrideActorContext: undefined },
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('ignores the group key entirely', () => {
|
|
48
|
+
expect(GROUPED_PARSER.parse({ groups: { 'not-a-folder-at-all': group() } })).toEqual([
|
|
49
|
+
{ folder: 'actors/shopify', actorFullName: 'myteam/shopify', tokenEnvVar: 'APIFY_TOKEN' },
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('allows actors to override some fields', () => {
|
|
54
|
+
expect(
|
|
55
|
+
GROUPED_PARSER.parse({ groups: { myteam: group({ actors: [actor({ tokenEnvVar: 'FROM_ACTOR' })] }) } }),
|
|
56
|
+
).toEqual([{ folder: 'actors/shopify', actorFullName: 'myteam/shopify', tokenEnvVar: 'FROM_ACTOR' }]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns the entries as written — normalizing is not this layer's job", () => {
|
|
60
|
+
const raw = actor({ folder: 'actors/shopify/' });
|
|
61
|
+
|
|
62
|
+
expect(
|
|
63
|
+
GROUPED_PARSER.parse({ groups: { myteam: group({ actors: [raw], overrideActorContext: ['packages/'] }) } }),
|
|
64
|
+
).toEqual([{ ...raw, tokenEnvVar: 'APIFY_TOKEN', overrideActorContext: ['packages/'] }]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('rejects a config with no groups (and thus no actors)', () => {
|
|
68
|
+
expect(() => GROUPED_PARSER.parse({})).toThrow();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('reports problems from every group at once instead of only the first', () => {
|
|
72
|
+
const message = (() => {
|
|
73
|
+
try {
|
|
74
|
+
GROUPED_PARSER.parse({
|
|
75
|
+
groups: {
|
|
76
|
+
myteam: { actors: [actor({ folder: 123 })], tokenEnvVar: 'APIFY_TOKEN' },
|
|
77
|
+
other: { actors: [actor({ actorFullName: 'nope' })] },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
throw new Error('Function should have thrown');
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return (err as Error).message;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
expect(message).toContain('myteam.actors[0].folder');
|
|
87
|
+
expect(message).toContain('other.actors[0].actorFullName');
|
|
88
|
+
expect(message).toContain('other.tokenEnvVar');
|
|
89
|
+
});
|
|
90
|
+
});
|
package/bin/github.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
import type { Commit, GitHubEventPush } from './types.js';
|
|
4
|
-
import { spawnCommandInGhWorkspace } from './utils.js';
|
|
5
|
-
|
|
6
|
-
export const getPushData = async (path: string) => {
|
|
7
|
-
const event = await loadGitHubEvent(path);
|
|
8
|
-
const branch = event.ref.replace('refs/heads/', '');
|
|
9
|
-
const {
|
|
10
|
-
commits: ghCommits,
|
|
11
|
-
repository: { ssh_url: repoUrl, name },
|
|
12
|
-
head_commit: { author },
|
|
13
|
-
} = event;
|
|
14
|
-
const commits: Commit[] = ghCommits.map(({ author: commitAuthor, message, id, timestamp }) => ({
|
|
15
|
-
author: commitAuthor.username,
|
|
16
|
-
sha: id,
|
|
17
|
-
message,
|
|
18
|
-
date: timestamp,
|
|
19
|
-
}));
|
|
20
|
-
const changedFiles = getChangedFiles(event);
|
|
21
|
-
const changelog = getChangelogChanges(changedFiles, event);
|
|
22
|
-
return {
|
|
23
|
-
branch,
|
|
24
|
-
commits,
|
|
25
|
-
changedFiles,
|
|
26
|
-
repoUrl,
|
|
27
|
-
changelog,
|
|
28
|
-
repository: name,
|
|
29
|
-
author: author.name,
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const loadGitHubEvent = async (path: string): Promise<GitHubEventPush> => {
|
|
34
|
-
const pushEvent = JSON.parse((await fs.readFile(path)).toString()) as GitHubEventPush;
|
|
35
|
-
return pushEvent;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const getChangedFiles = ({ after, before }: GitHubEventPush): string[] => {
|
|
39
|
-
const changedFiles = spawnCommandInGhWorkspace(`git diff --name-only ${after}..${before}`);
|
|
40
|
-
return changedFiles.split('\n');
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const getChangelogChanges = (changedFiles: string[], event: GitHubEventPush): string | null => {
|
|
44
|
-
const changelogPath = 'CHANGELOG.md';
|
|
45
|
-
if (!changedFiles.includes(changelogPath)) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
const { after, before } = event;
|
|
49
|
-
const diff = spawnCommandInGhWorkspace('git', ['diff', before, after, '--', changelogPath]);
|
|
50
|
-
|
|
51
|
-
const added: string[] = [];
|
|
52
|
-
let startedChangelog = false;
|
|
53
|
-
for (const line of diff.split('\n')) {
|
|
54
|
-
// We should already get only files we care about from getLastCommitDiffForFile but better to double check
|
|
55
|
-
if (line.startsWith('+++') && line.toLowerCase().includes(changelogPath.toLowerCase())) {
|
|
56
|
-
startedChangelog = true;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (startedChangelog) {
|
|
60
|
-
if (line.startsWith('diff')) {
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
if (line.startsWith('+')) {
|
|
64
|
-
added.push(line.slice(1).trim());
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return added.join('\n').trim();
|
|
69
|
-
};
|
package/dist/bin/github.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Commit } from './types.js';
|
|
2
|
-
export declare const getPushData: (path: string) => Promise<{
|
|
3
|
-
branch: string;
|
|
4
|
-
commits: Commit[];
|
|
5
|
-
changedFiles: string[];
|
|
6
|
-
repoUrl: string;
|
|
7
|
-
changelog: string | null;
|
|
8
|
-
repository: string;
|
|
9
|
-
author: string;
|
|
10
|
-
}>;
|
|
11
|
-
//# sourceMappingURL=github.d.ts.map
|
package/dist/bin/github.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../bin/github.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,YAAY,CAAC;AAG1D,eAAO,MAAM,WAAW,GAAU,MAAM,MAAM;;;;;;;;EAyB7C,CAAC"}
|
package/dist/bin/github.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import { spawnCommandInGhWorkspace } from './utils.js';
|
|
3
|
-
export const getPushData = async (path) => {
|
|
4
|
-
const event = await loadGitHubEvent(path);
|
|
5
|
-
const branch = event.ref.replace('refs/heads/', '');
|
|
6
|
-
const { commits: ghCommits, repository: { ssh_url: repoUrl, name }, head_commit: { author }, } = event;
|
|
7
|
-
const commits = ghCommits.map(({ author: commitAuthor, message, id, timestamp }) => ({
|
|
8
|
-
author: commitAuthor.username,
|
|
9
|
-
sha: id,
|
|
10
|
-
message,
|
|
11
|
-
date: timestamp,
|
|
12
|
-
}));
|
|
13
|
-
const changedFiles = getChangedFiles(event);
|
|
14
|
-
const changelog = getChangelogChanges(changedFiles, event);
|
|
15
|
-
return {
|
|
16
|
-
branch,
|
|
17
|
-
commits,
|
|
18
|
-
changedFiles,
|
|
19
|
-
repoUrl,
|
|
20
|
-
changelog,
|
|
21
|
-
repository: name,
|
|
22
|
-
author: author.name,
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
const loadGitHubEvent = async (path) => {
|
|
26
|
-
const pushEvent = JSON.parse((await fs.readFile(path)).toString());
|
|
27
|
-
return pushEvent;
|
|
28
|
-
};
|
|
29
|
-
const getChangedFiles = ({ after, before }) => {
|
|
30
|
-
const changedFiles = spawnCommandInGhWorkspace(`git diff --name-only ${after}..${before}`);
|
|
31
|
-
return changedFiles.split('\n');
|
|
32
|
-
};
|
|
33
|
-
const getChangelogChanges = (changedFiles, event) => {
|
|
34
|
-
const changelogPath = 'CHANGELOG.md';
|
|
35
|
-
if (!changedFiles.includes(changelogPath)) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
const { after, before } = event;
|
|
39
|
-
const diff = spawnCommandInGhWorkspace('git', ['diff', before, after, '--', changelogPath]);
|
|
40
|
-
const added = [];
|
|
41
|
-
let startedChangelog = false;
|
|
42
|
-
for (const line of diff.split('\n')) {
|
|
43
|
-
// We should already get only files we care about from getLastCommitDiffForFile but better to double check
|
|
44
|
-
if (line.startsWith('+++') && line.toLowerCase().includes(changelogPath.toLowerCase())) {
|
|
45
|
-
startedChangelog = true;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (startedChangelog) {
|
|
49
|
-
if (line.startsWith('diff')) {
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
if (line.startsWith('+')) {
|
|
53
|
-
added.push(line.slice(1).trim());
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return added.join('\n').trim();
|
|
58
|
-
};
|
|
59
|
-
//# sourceMappingURL=github.js.map
|
package/dist/bin/github.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../bin/github.ts"],"names":[],"mappings":"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"}
|