apify-test-tools 0.10.2-beta.0 → 0.10.2-beta.2
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/.gitattributes +2 -0
- package/.github/actions/checkout-restore-dependencies/action.yaml +13 -29
- package/.github/cli/apify-test-tools.mjs +362 -0
- package/.github/scripts/run-with-apify-tokens.mjs +2 -2
- package/.github/workflows/_check_code.yaml +25 -0
- package/.github/workflows/_move_major_tag.yaml +3 -81
- package/.github/workflows/_tests.yaml +34 -0
- package/.github/workflows/_update_release_metadata.yaml +0 -12
- package/.github/workflows/manual_move_major_tag.yaml +3 -3
- package/.github/workflows/manual_release_stable.yaml +3 -13
- package/.github/workflows/on_master.yaml +9 -5
- package/.github/workflows/on_pull_request.yaml +3 -48
- package/.github/workflows/public_platform-tests.yaml +1 -1
- package/.github/workflows/public_pr-build-test.yaml +5 -28
- package/.github/workflows/public_push-build-latest.yaml +2 -2
- package/.husky/pre-commit +5 -0
- package/.prettierignore +3 -0
- package/CHANGELOG.md +5 -0
- package/CONTRIBUTING.md +8 -18
- package/README.md +40 -42
- package/bin/build-from-local.ts +47 -13
- package/bin/build.ts +35 -19
- package/bin/diff-changes.ts +24 -3
- package/bin/main.ts +35 -7
- package/bin/slack.ts +28 -6
- package/bin/test-report.ts +6 -9
- package/bin/types.ts +10 -0
- package/dist/bin/build-from-local.d.ts +10 -2
- package/dist/bin/build-from-local.d.ts.map +1 -1
- package/dist/bin/build-from-local.js +29 -11
- package/dist/bin/build-from-local.js.map +1 -1
- package/dist/bin/build.d.ts +15 -5
- package/dist/bin/build.d.ts.map +1 -1
- package/dist/bin/build.js +24 -15
- package/dist/bin/build.js.map +1 -1
- package/dist/bin/diff-changes.d.ts +2 -1
- package/dist/bin/diff-changes.d.ts.map +1 -1
- package/dist/bin/diff-changes.js +16 -5
- package/dist/bin/diff-changes.js.map +1 -1
- package/dist/bin/main.js +32 -6
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts +8 -1
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +26 -5
- package/dist/bin/slack.js.map +1 -1
- package/dist/bin/test-report.d.ts.map +1 -1
- package/dist/bin/test-report.js +6 -8
- package/dist/bin/test-report.js.map +1 -1
- package/dist/bin/types.d.ts +9 -0
- package/dist/bin/types.d.ts.map +1 -1
- package/dist/test/bundle/cli-bundle.test.d.ts +2 -0
- package/dist/test/bundle/cli-bundle.test.d.ts.map +1 -0
- package/dist/test/bundle/cli-bundle.test.js +162 -0
- package/dist/test/bundle/cli-bundle.test.js.map +1 -0
- package/dist/test/e2e/cli-bundle.e2e.test.d.ts +2 -0
- package/dist/test/e2e/cli-bundle.e2e.test.d.ts.map +1 -0
- package/dist/test/e2e/cli-bundle.e2e.test.js +172 -0
- package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -0
- package/dist/test/unit/bin/build-from-local.test.js +53 -1
- package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
- package/dist/test/unit/bin/build.test.js +113 -3
- package/dist/test/unit/bin/build.test.js.map +1 -1
- package/dist/test/unit/bin/diff-changes.test.js +91 -0
- package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
- package/dist/test/unit/bin/slack.test.d.ts +2 -0
- package/dist/test/unit/bin/slack.test.d.ts.map +1 -0
- package/dist/test/unit/bin/slack.test.js +54 -0
- package/dist/test/unit/bin/slack.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -3
- package/test/bundle/cli-bundle.test.ts +192 -0
- package/test/e2e/cli-bundle.e2e.test.ts +237 -0
- package/test/unit/bin/build-from-local.test.ts +55 -0
- package/test/unit/bin/build.test.ts +135 -3
- package/test/unit/bin/diff-changes.test.ts +106 -0
- package/test/unit/bin/slack.test.ts +65 -0
- package/.github/scripts/check-package-version-bump.mjs +0 -74
- package/.github/workflows-package-version +0 -1
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
1
5
|
import type * as ApifyClientTypes from 'apify-client';
|
|
2
|
-
import { ActorSourceType } from 'apify-client';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { ActorSourceType, ApifyClient } from 'apify-client';
|
|
7
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
assertRepoUrlMatchesDefaultVersion,
|
|
11
|
+
deleteOldBuilds,
|
|
12
|
+
resolveDefaultVersion,
|
|
13
|
+
runBuilds,
|
|
14
|
+
} from '../../../bin/build.js';
|
|
15
|
+
import type { ActorConfig } from '../../../bin/types.js';
|
|
16
|
+
import { ExistingDir, ExistingFile, RelativeDir } from '../../../bin/utils/path/repo-relative.js';
|
|
4
17
|
|
|
5
|
-
|
|
18
|
+
// Only the client is faked, so the tests exercise the real builder code against a stubbed API
|
|
19
|
+
vi.mock('apify-client', async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof ApifyClientTypes>();
|
|
21
|
+
return { ...actual, ApifyClient: vi.fn() };
|
|
22
|
+
});
|
|
6
23
|
|
|
7
24
|
const ACTOR = 'owner/actor';
|
|
8
25
|
const REPO_URL = 'git@github.com:owner/repo';
|
|
@@ -66,3 +83,118 @@ describe('assertRepoUrlMatchesDefaultVersion', () => {
|
|
|
66
83
|
expect(() => assertRepoUrlMatchesDefaultVersion(ACTOR, undefined, REPO_URL)).toThrow('no longer exists');
|
|
67
84
|
});
|
|
68
85
|
});
|
|
86
|
+
|
|
87
|
+
describe('dry run', () => {
|
|
88
|
+
const TOKEN_ENV_VAR = 'APIFY_TOKEN_OWNER';
|
|
89
|
+
const originalCwd = process.cwd();
|
|
90
|
+
let fixtureRoot: string;
|
|
91
|
+
let actorConfig: ActorConfig;
|
|
92
|
+
|
|
93
|
+
// ExistingDir checks the folder exists, so the config is built inside a throwaway repo root
|
|
94
|
+
beforeAll(() => {
|
|
95
|
+
fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'apify-build-dry-run-'));
|
|
96
|
+
process.chdir(fixtureRoot);
|
|
97
|
+
fs.mkdirSync('actors/a/.actor', { recursive: true });
|
|
98
|
+
fs.writeFileSync('actors/a/.actor/actor.json', '{}');
|
|
99
|
+
actorConfig = {
|
|
100
|
+
actorFullName: ACTOR,
|
|
101
|
+
folder: new ExistingDir('actors/a'),
|
|
102
|
+
tokenEnvVar: TOKEN_ENV_VAR,
|
|
103
|
+
actorJson: {
|
|
104
|
+
file: new ExistingFile('actors/a/.actor/actor.json'),
|
|
105
|
+
dockerContextDir: new RelativeDir('actors/a'),
|
|
106
|
+
},
|
|
107
|
+
dockerContextDir: new RelativeDir('actors/a'),
|
|
108
|
+
contextPaths: [new RelativeDir('actors/a')],
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterAll(() => {
|
|
113
|
+
process.chdir(originalCwd);
|
|
114
|
+
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
|
115
|
+
});
|
|
116
|
+
const deleteBuild = vi.fn();
|
|
117
|
+
const startBuild = vi.fn();
|
|
118
|
+
const createVersion = vi.fn();
|
|
119
|
+
const buildClient = vi.fn(() => ({ delete: deleteBuild }));
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
vi.stubEnv(TOKEN_ENV_VAR, 'token');
|
|
123
|
+
const fakeClient = {
|
|
124
|
+
actor: () => ({
|
|
125
|
+
get: async () => makeActorInfo(),
|
|
126
|
+
// Old enough to be deleted, and not the default build 0.1.5
|
|
127
|
+
builds: () => ({
|
|
128
|
+
list: async () => ({ items: [{ id: 'old-build', buildNumber: '0.0.1', startedAt: new Date(0) }] }),
|
|
129
|
+
}),
|
|
130
|
+
versions: () => ({ create: createVersion }),
|
|
131
|
+
version: () => ({ update: createVersion }),
|
|
132
|
+
build: startBuild,
|
|
133
|
+
}),
|
|
134
|
+
build: buildClient,
|
|
135
|
+
};
|
|
136
|
+
vi.mocked(ApifyClient).mockImplementation(function () {
|
|
137
|
+
return fakeClient;
|
|
138
|
+
} as never);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
vi.unstubAllEnvs();
|
|
143
|
+
vi.clearAllMocks();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const runDryBuilds = async () =>
|
|
147
|
+
runBuilds({
|
|
148
|
+
actorConfigs: [actorConfig],
|
|
149
|
+
repoUrl: REPO_URL,
|
|
150
|
+
shouldVerifyRepoUrl: true,
|
|
151
|
+
branch: 'feature',
|
|
152
|
+
dryRun: true,
|
|
153
|
+
useDockerCache: false,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should return a build plan without creating versions or builds', async () => {
|
|
157
|
+
expect(await runDryBuilds()).toEqual({
|
|
158
|
+
dryRun: true,
|
|
159
|
+
builds: [
|
|
160
|
+
{
|
|
161
|
+
actorFullName: ACTOR,
|
|
162
|
+
versionNumber: '0.99',
|
|
163
|
+
gitRepoUrl: `${REPO_URL}#feature:actors/a`,
|
|
164
|
+
useDockerCache: false,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
expect(createVersion).not.toHaveBeenCalled();
|
|
169
|
+
expect(startBuild).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should still require the Apify token', async () => {
|
|
173
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
174
|
+
await expect(runDryBuilds()).rejects.toThrow(`Env var ${TOKEN_ENV_VAR} is not set`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should still check the repository URL', async () => {
|
|
178
|
+
await expect(
|
|
179
|
+
runBuilds({
|
|
180
|
+
actorConfigs: [actorConfig],
|
|
181
|
+
repoUrl: 'git@github.com:fork/repo',
|
|
182
|
+
shouldVerifyRepoUrl: true,
|
|
183
|
+
branch: 'feature',
|
|
184
|
+
dryRun: true,
|
|
185
|
+
useDockerCache: false,
|
|
186
|
+
}),
|
|
187
|
+
).rejects.toThrow('Repository mismatch');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should not delete old builds', async () => {
|
|
191
|
+
await deleteOldBuilds([actorConfig], { dryRun: true });
|
|
192
|
+
expect(deleteBuild).not.toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should delete old builds outside of a dry run', async () => {
|
|
196
|
+
await deleteOldBuilds([actorConfig], { dryRun: false });
|
|
197
|
+
expect(buildClient).toHaveBeenCalledWith('old-build');
|
|
198
|
+
expect(deleteBuild).toHaveBeenCalledTimes(1);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -63,6 +63,7 @@ const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
|
|
|
63
63
|
|
|
64
64
|
describe('getChangedActors', () => {
|
|
65
65
|
beforeEach(() => {
|
|
66
|
+
vi.restoreAllMocks();
|
|
66
67
|
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockResolvedValue(false);
|
|
67
68
|
});
|
|
68
69
|
|
|
@@ -393,6 +394,72 @@ describe('getChangedActors', () => {
|
|
|
393
394
|
expect(result).toEqual([miniActor]);
|
|
394
395
|
});
|
|
395
396
|
|
|
397
|
+
it('test file listed in .dockerignore triggers a build when it matches the test files glob', async () => {
|
|
398
|
+
vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue((filePath) => filePath.startsWith('test/'));
|
|
399
|
+
const result = await getChangedActors({
|
|
400
|
+
filepathsChanged: ['test/platform/daily/foo.test.ts'],
|
|
401
|
+
actorConfigs: [miniActor],
|
|
402
|
+
commits,
|
|
403
|
+
testFilesGlob: 'test/platform/**',
|
|
404
|
+
});
|
|
405
|
+
expect(result).toEqual([miniActor]);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('test file listed in .dockerignore stays ignored without a test files glob', async () => {
|
|
409
|
+
vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue((filePath) => filePath.startsWith('test/'));
|
|
410
|
+
const result = await getChangedActors({
|
|
411
|
+
filepathsChanged: ['test/platform/daily/foo.test.ts'],
|
|
412
|
+
actorConfigs: [miniActor],
|
|
413
|
+
commits,
|
|
414
|
+
});
|
|
415
|
+
expect(result).toEqual([]);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('test files glob matches case-insensitively and does not bypass .dockerignore for other files', async () => {
|
|
419
|
+
vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue(() => true);
|
|
420
|
+
const result = await getChangedActors({
|
|
421
|
+
filepathsChanged: ['Test/Platform/Foo.test.ts'],
|
|
422
|
+
actorConfigs: [miniActor],
|
|
423
|
+
commits,
|
|
424
|
+
testFilesGlob: 'test/platform/**',
|
|
425
|
+
});
|
|
426
|
+
expect(result).toEqual([miniActor]);
|
|
427
|
+
expect(
|
|
428
|
+
await getChangedActors({
|
|
429
|
+
filepathsChanged: ['actors/foo_bar/node_modules/foo.js'],
|
|
430
|
+
actorConfigs: [miniActor],
|
|
431
|
+
commits,
|
|
432
|
+
testFilesGlob: 'test/platform/**',
|
|
433
|
+
}),
|
|
434
|
+
).toEqual([]);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('test file outside an actor context still does not trigger it', async () => {
|
|
438
|
+
vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue(() => true);
|
|
439
|
+
const result = await getChangedActors({
|
|
440
|
+
filepathsChanged: ['test/platform/daily/foo.test.ts'],
|
|
441
|
+
actorConfigs: [standaloneActor],
|
|
442
|
+
commits,
|
|
443
|
+
testFilesGlob: 'test/platform/**',
|
|
444
|
+
});
|
|
445
|
+
expect(result).toEqual([]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('test file listed in .dockerignore triggers an actor whose overrideActorContext includes it', async () => {
|
|
449
|
+
vi.spyOn(Dockerignore, 'loadDockerIgnore').mockReturnValue(() => true);
|
|
450
|
+
const actorWithTests = {
|
|
451
|
+
...standaloneActor,
|
|
452
|
+
contextPaths: [...standaloneActor.contextPaths, new RelativeDir('test')],
|
|
453
|
+
};
|
|
454
|
+
const result = await getChangedActors({
|
|
455
|
+
filepathsChanged: ['test/platform/standalone.test.ts'],
|
|
456
|
+
actorConfigs: [actorWithTests],
|
|
457
|
+
commits,
|
|
458
|
+
testFilesGlob: 'test/platform/**',
|
|
459
|
+
});
|
|
460
|
+
expect(result).toEqual([actorWithTests]);
|
|
461
|
+
});
|
|
462
|
+
|
|
396
463
|
it('JSON file in context but outside actor folder is functional (not checked for cosmetic)', async () => {
|
|
397
464
|
const result = await getChangedActors({
|
|
398
465
|
filepathsChanged: ['lib/config.json'],
|
|
@@ -473,6 +540,7 @@ describe('getChangedActors logging', () => {
|
|
|
473
540
|
beforeEach(() => {
|
|
474
541
|
vi.spyOn(DiffJsonSchema, 'isCosmeticOnlyJsonSchemaChange').mockResolvedValue(false);
|
|
475
542
|
vi.spyOn(logger, 'info').mockImplementation(() => undefined);
|
|
543
|
+
vi.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
476
544
|
});
|
|
477
545
|
|
|
478
546
|
it('logs a single "specific" group for a single actor with one functional file', async () => {
|
|
@@ -525,6 +593,44 @@ describe('getChangedActors logging', () => {
|
|
|
525
593
|
]);
|
|
526
594
|
});
|
|
527
595
|
|
|
596
|
+
const outsideContextWarning = (testFile: string) =>
|
|
597
|
+
`[DIFF]: Test file ${testFile} is outside every Actor's context, so changing only it builds and tests nothing. ` +
|
|
598
|
+
'Add its folder to "overrideActorContext" of the Actors it tests.';
|
|
599
|
+
|
|
600
|
+
it('warns about a changed test file outside every Actor context', async () => {
|
|
601
|
+
await getChangedActors({
|
|
602
|
+
filepathsChanged: ['test/platform/standalone.test.ts'],
|
|
603
|
+
actorConfigs: [standaloneActor],
|
|
604
|
+
commits,
|
|
605
|
+
testFilesGlob: 'test/platform/**',
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
expect(logger.warn).toHaveBeenCalledWith(outsideContextWarning('test/platform/standalone.test.ts'));
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('still warns when the same push also rebuilds an Actor', async () => {
|
|
612
|
+
const result = await getChangedActors({
|
|
613
|
+
filepathsChanged: ['test/platform/standalone.test.ts', 'standalone-actors/standalone/src/main.ts'],
|
|
614
|
+
actorConfigs: [standaloneActor],
|
|
615
|
+
commits,
|
|
616
|
+
testFilesGlob: 'test/platform/**',
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
expect(result).toEqual([standaloneActor]);
|
|
620
|
+
expect(logger.warn).toHaveBeenCalledWith(outsideContextWarning('test/platform/standalone.test.ts'));
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('does not warn about test files inside an Actor context, even cosmetic ones', async () => {
|
|
624
|
+
await getChangedActors({
|
|
625
|
+
filepathsChanged: ['test/platform/foo.test.ts', 'test/platform/README.md', 'test/platform/CHANGELOG.md'],
|
|
626
|
+
actorConfigs: [miniActor],
|
|
627
|
+
commits,
|
|
628
|
+
testFilesGlob: 'test/platform/**',
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
632
|
+
});
|
|
633
|
+
|
|
528
634
|
it('logs no group lines when zero actors changed', async () => {
|
|
529
635
|
await getChangedActors({
|
|
530
636
|
filepathsChanged: ['.gitignore', '.prettierrc'],
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { WebClient } from '@slack/web-api';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { notifyToSlack } from '../../../bin/slack.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('@slack/web-api', () => ({ WebClient: vi.fn() }));
|
|
7
|
+
|
|
8
|
+
const TOKEN_ENV_VAR = 'SLACK_TOKEN_RELEASES_BOT';
|
|
9
|
+
const postMessage = vi.fn();
|
|
10
|
+
const authTest = vi.fn();
|
|
11
|
+
|
|
12
|
+
const notify = async (overrides: Partial<Parameters<typeof notifyToSlack>[0]> = {}) =>
|
|
13
|
+
notifyToSlack({
|
|
14
|
+
repository: 'owner/repo',
|
|
15
|
+
changedFiles: ['src/main.ts'],
|
|
16
|
+
changelog: '- Fixed a bug',
|
|
17
|
+
commits: [{ sha: '1', author: 'Test<test@example.com>', date: '', message: 'fix: bug' }],
|
|
18
|
+
author: 'Test<test@example.com>',
|
|
19
|
+
dryRun: true,
|
|
20
|
+
releaseSlackChannel: '#releases',
|
|
21
|
+
reportSlackChannel: '#notif',
|
|
22
|
+
...overrides,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('notifyToSlack', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.stubEnv(TOKEN_ENV_VAR, 'token');
|
|
28
|
+
vi.mocked(WebClient).mockImplementation(function () {
|
|
29
|
+
return { chat: { postMessage }, auth: { test: authTest } };
|
|
30
|
+
} as never);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.unstubAllEnvs();
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should verify the token but post nothing in a dry run', async () => {
|
|
39
|
+
await notify();
|
|
40
|
+
expect(authTest).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(postMessage).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should still require the token in a dry run', async () => {
|
|
45
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
46
|
+
await expect(notify()).rejects.toThrow(`${TOKEN_ENV_VAR} not defined`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should fail a dry run when Slack rejects the token', async () => {
|
|
50
|
+
authTest.mockRejectedValueOnce(new Error('An API error occurred: invalid_auth'));
|
|
51
|
+
await expect(notify()).rejects.toThrow(`${TOKEN_ENV_VAR} was rejected by Slack`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should not need a token when no channel is passed', async () => {
|
|
55
|
+
vi.stubEnv(TOKEN_ENV_VAR, '');
|
|
56
|
+
await notify({ dryRun: false, releaseSlackChannel: undefined, reportSlackChannel: undefined });
|
|
57
|
+
expect(WebClient).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should post to both channels outside of a dry run', async () => {
|
|
61
|
+
await notify({ dryRun: false });
|
|
62
|
+
expect(authTest).not.toHaveBeenCalled();
|
|
63
|
+
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Reads changed file paths on stdin (one per line) and fails if a PR changes both a consumer-facing
|
|
3
|
-
// workflow and the package code it calls, without repinning in workflows-package-version.
|
|
4
|
-
//
|
|
5
|
-
// The case this catches: a workflow starts using a CLI feature from the same PR. On merge the tag
|
|
6
|
-
// moves, the workflow goes live, and it calls something that is not on npm yet — breaking every
|
|
7
|
-
// consumer's CI at once. Pinning the unreleased version instead holds the tag until it publishes.
|
|
8
|
-
//
|
|
9
|
-
// It deliberately only looks at one PR. A workflow could also start depending on package code that
|
|
10
|
-
// landed in an *earlier*, still-unreleased PR, which this will not see — that needs someone to ship
|
|
11
|
-
// a breaking CLI change and then sit on it unreleased, and catching it means flagging every workflow
|
|
12
|
-
// edit made while any package change is unreleased, which is far more noise than the case is worth.
|
|
13
|
-
|
|
14
|
-
const PUBLIC_WORKFLOW = /^\.github\/workflows\/public_.*\.ya?ml$/;
|
|
15
|
-
// The composite action is consumer-facing too, and it is what installs the package.
|
|
16
|
-
const PUBLIC_ACTION = /^\.github\/actions\//;
|
|
17
|
-
// What `npx apify-test-tools ...` runs, and the library the platform tests import.
|
|
18
|
-
const PACKAGE_CODE = /^(bin\/|lib\/|index\.ts$)/;
|
|
19
|
-
const PIN_FILE = '.github/workflows-package-version';
|
|
20
|
-
const OVERRIDE_LABEL = 'no-version-bump-needed';
|
|
21
|
-
|
|
22
|
-
const changed = await new Promise((resolve) => {
|
|
23
|
-
let buffer = '';
|
|
24
|
-
process.stdin.setEncoding('utf8');
|
|
25
|
-
process.stdin.on('data', (chunk) => (buffer += chunk));
|
|
26
|
-
process.stdin.on('end', () =>
|
|
27
|
-
resolve(
|
|
28
|
-
buffer
|
|
29
|
-
.split('\n')
|
|
30
|
-
.map((l) => l.trim())
|
|
31
|
-
.filter(Boolean),
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const publicSurface = changed.filter((f) => PUBLIC_WORKFLOW.test(f) || PUBLIC_ACTION.test(f));
|
|
37
|
-
const packageCode = changed.filter((f) => PACKAGE_CODE.test(f));
|
|
38
|
-
const versionRepinned = changed.includes(PIN_FILE);
|
|
39
|
-
|
|
40
|
-
if (publicSurface.length === 0 || packageCode.length === 0) {
|
|
41
|
-
console.log('No consumer-facing workflow and package change in the same PR; nothing to check.');
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (versionRepinned) {
|
|
46
|
-
console.log(`Both sides changed and ${PIN_FILE} was repinned. The tag will hold until that version ships.`);
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
(process.env.PR_LABELS ?? '')
|
|
52
|
-
.split(',')
|
|
53
|
-
.map((l) => l.trim())
|
|
54
|
-
.includes(OVERRIDE_LABEL)
|
|
55
|
-
) {
|
|
56
|
-
console.log(`Both sides changed without a version bump, allowed by the "${OVERRIDE_LABEL}" label.`);
|
|
57
|
-
process.exit(0);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.error(`This PR changes consumer-facing workflows and the package code they call:
|
|
61
|
-
|
|
62
|
-
workflows: ${publicSurface.join(', ')}
|
|
63
|
-
package: ${packageCode.join(', ')}
|
|
64
|
-
|
|
65
|
-
On merge the tag moves and those workflows go live immediately, against whatever is on npm today.
|
|
66
|
-
If they rely on anything from the package change in this PR, that is not published yet and every
|
|
67
|
-
consumer's CI breaks.
|
|
68
|
-
|
|
69
|
-
If they do rely on it, set ${PIN_FILE} to the version that will contain it. The tag is then held
|
|
70
|
-
until you cut that release, and moves on its own once it is published.
|
|
71
|
-
|
|
72
|
-
If they do not rely on it — the two changes just happen to be in one PR — add the
|
|
73
|
-
"${OVERRIDE_LABEL}" label.`);
|
|
74
|
-
process.exit(1);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.10.1
|