apify-test-tools 0.10.2-beta.2 → 0.10.2-beta.20

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.
Files changed (275) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/actionlint.yaml +11 -0
  3. package/.github/actions/checkout-restore-dependencies/action.yaml +58 -6
  4. package/.github/cli/apify-test-tools.mjs +135 -142
  5. package/.github/scripts/mask-config-secrets.sh +17 -0
  6. package/.github/scripts/run-with-apify-tokens.mjs +63 -103
  7. package/.github/workflows/_check_code.yaml +5 -21
  8. package/.github/workflows/_move_major_tag.yaml +2 -2
  9. package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +89 -8
  10. package/.github/workflows/public_platform-tests.yaml +68 -30
  11. package/.github/workflows/public_pr-build-test.yaml +77 -38
  12. package/.github/workflows/public_push-build-latest.yaml +49 -11
  13. package/.github/workflows/public_review.yaml +5 -4
  14. package/.github/workflows/public_stale-issues.yaml +51 -0
  15. package/.husky/pre-commit +4 -4
  16. package/.prettierignore +2 -1
  17. package/CHANGELOG.md +22 -0
  18. package/CONTRIBUTING.md +117 -17
  19. package/README.md +221 -595
  20. package/bin/actor-ids.ts +41 -0
  21. package/bin/affected-actors.ts +211 -0
  22. package/bin/build-from-local.ts +19 -1
  23. package/bin/build.ts +105 -39
  24. package/bin/diff-changes.ts +69 -36
  25. package/bin/diff-json-schema.ts +5 -6
  26. package/bin/git.ts +73 -175
  27. package/bin/main.ts +142 -189
  28. package/bin/notify.ts +198 -0
  29. package/bin/run-tests.ts +167 -0
  30. package/bin/slack.ts +16 -76
  31. package/bin/stdin-input.ts +156 -0
  32. package/bin/test-files-glob.ts +126 -0
  33. package/bin/test-report.ts +141 -140
  34. package/bin/types.ts +4 -7
  35. package/bin/utils/config/load-config.ts +20 -2
  36. package/bin/utils/config/parser.ts +40 -8
  37. package/bin/utils/config/structures/grouped.ts +2 -2
  38. package/bin/utils/config/structures/legacy.ts +1 -1
  39. package/dist/bin/actor-ids.d.ts +15 -0
  40. package/dist/bin/actor-ids.d.ts.map +1 -0
  41. package/dist/bin/actor-ids.js +30 -0
  42. package/dist/bin/actor-ids.js.map +1 -0
  43. package/dist/bin/affected-actors.d.ts +55 -0
  44. package/dist/bin/affected-actors.d.ts.map +1 -0
  45. package/dist/bin/affected-actors.js +142 -0
  46. package/dist/bin/affected-actors.js.map +1 -0
  47. package/dist/bin/build-from-local.d.ts +2 -1
  48. package/dist/bin/build-from-local.d.ts.map +1 -1
  49. package/dist/bin/build-from-local.js +6 -2
  50. package/dist/bin/build-from-local.js.map +1 -1
  51. package/dist/bin/build.d.ts +18 -5
  52. package/dist/bin/build.d.ts.map +1 -1
  53. package/dist/bin/build.js +74 -24
  54. package/dist/bin/build.js.map +1 -1
  55. package/dist/bin/diff-changes.d.ts +21 -5
  56. package/dist/bin/diff-changes.d.ts.map +1 -1
  57. package/dist/bin/diff-changes.js +41 -28
  58. package/dist/bin/diff-changes.js.map +1 -1
  59. package/dist/bin/diff-json-schema.d.ts +2 -2
  60. package/dist/bin/diff-json-schema.d.ts.map +1 -1
  61. package/dist/bin/diff-json-schema.js +1 -4
  62. package/dist/bin/diff-json-schema.js.map +1 -1
  63. package/dist/bin/git.d.ts +26 -34
  64. package/dist/bin/git.d.ts.map +1 -1
  65. package/dist/bin/git.js +60 -143
  66. package/dist/bin/git.js.map +1 -1
  67. package/dist/bin/main.js +126 -140
  68. package/dist/bin/main.js.map +1 -1
  69. package/dist/bin/notify.d.ts +35 -0
  70. package/dist/bin/notify.d.ts.map +1 -0
  71. package/dist/bin/notify.js +131 -0
  72. package/dist/bin/notify.js.map +1 -0
  73. package/dist/bin/run-tests.d.ts +21 -0
  74. package/dist/bin/run-tests.d.ts.map +1 -0
  75. package/dist/bin/run-tests.js +138 -0
  76. package/dist/bin/run-tests.js.map +1 -0
  77. package/dist/bin/slack.d.ts +2 -14
  78. package/dist/bin/slack.d.ts.map +1 -1
  79. package/dist/bin/slack.js +17 -46
  80. package/dist/bin/slack.js.map +1 -1
  81. package/dist/bin/stdin-input.d.ts +79 -0
  82. package/dist/bin/stdin-input.d.ts.map +1 -0
  83. package/dist/bin/stdin-input.js +124 -0
  84. package/dist/bin/stdin-input.js.map +1 -0
  85. package/dist/bin/test-files-glob.d.ts +18 -0
  86. package/dist/bin/test-files-glob.d.ts.map +1 -0
  87. package/dist/bin/test-files-glob.js +108 -0
  88. package/dist/bin/test-files-glob.js.map +1 -0
  89. package/dist/bin/test-report.d.ts +69 -8
  90. package/dist/bin/test-report.d.ts.map +1 -1
  91. package/dist/bin/test-report.js +76 -67
  92. package/dist/bin/test-report.js.map +1 -1
  93. package/dist/bin/types.d.ts +4 -7
  94. package/dist/bin/types.d.ts.map +1 -1
  95. package/dist/bin/utils/config/load-config.d.ts +8 -0
  96. package/dist/bin/utils/config/load-config.d.ts.map +1 -1
  97. package/dist/bin/utils/config/load-config.js +14 -1
  98. package/dist/bin/utils/config/load-config.js.map +1 -1
  99. package/dist/bin/utils/config/parser.d.ts +15 -2
  100. package/dist/bin/utils/config/parser.d.ts.map +1 -1
  101. package/dist/bin/utils/config/parser.js +29 -8
  102. package/dist/bin/utils/config/parser.js.map +1 -1
  103. package/dist/bin/utils/config/structures/grouped.js +2 -2
  104. package/dist/bin/utils/config/structures/grouped.js.map +1 -1
  105. package/dist/bin/utils/config/structures/legacy.js +1 -1
  106. package/dist/bin/utils/config/structures/legacy.js.map +1 -1
  107. package/dist/lib/extend-expect.d.ts.map +1 -1
  108. package/dist/lib/extend-expect.js +37 -218
  109. package/dist/lib/extend-expect.js.map +1 -1
  110. package/dist/lib/lib.d.ts +1 -5
  111. package/dist/lib/lib.d.ts.map +1 -1
  112. package/dist/lib/lib.js +19 -31
  113. package/dist/lib/lib.js.map +1 -1
  114. package/dist/lib/run-checks.d.ts +14 -0
  115. package/dist/lib/run-checks.d.ts.map +1 -0
  116. package/dist/lib/run-checks.js +82 -0
  117. package/dist/lib/run-checks.js.map +1 -0
  118. package/dist/lib/run-test-result.d.ts +3 -6
  119. package/dist/lib/run-test-result.d.ts.map +1 -1
  120. package/dist/lib/run-test-result.js +41 -54
  121. package/dist/lib/run-test-result.js.map +1 -1
  122. package/dist/test/bundle/cli-bundle.test.js +76 -56
  123. package/dist/test/bundle/cli-bundle.test.js.map +1 -1
  124. package/dist/test/bundle/token-wrapper.test.d.ts +2 -0
  125. package/dist/test/bundle/token-wrapper.test.d.ts.map +1 -0
  126. package/dist/test/bundle/token-wrapper.test.js +80 -0
  127. package/dist/test/bundle/token-wrapper.test.js.map +1 -0
  128. package/dist/test/e2e/cli-bundle.e2e.test.js +33 -68
  129. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
  130. package/dist/test/e2e/git-build-release.e2e.test.d.ts +2 -0
  131. package/dist/test/e2e/git-build-release.e2e.test.d.ts.map +1 -0
  132. package/dist/test/e2e/git-build-release.e2e.test.js +87 -0
  133. package/dist/test/e2e/git-build-release.e2e.test.js.map +1 -0
  134. package/dist/test/e2e/helpers.d.ts +37 -0
  135. package/dist/test/e2e/helpers.d.ts.map +1 -0
  136. package/dist/test/e2e/helpers.js +57 -0
  137. package/dist/test/e2e/helpers.js.map +1 -0
  138. package/dist/test/unit/bin/actor-filtering.test.js +7 -0
  139. package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
  140. package/dist/test/unit/bin/actor-ids.test.d.ts +2 -0
  141. package/dist/test/unit/bin/actor-ids.test.d.ts.map +1 -0
  142. package/dist/test/unit/bin/actor-ids.test.js +40 -0
  143. package/dist/test/unit/bin/actor-ids.test.js.map +1 -0
  144. package/dist/test/unit/bin/affected-actors-cli.test.d.ts +2 -0
  145. package/dist/test/unit/bin/affected-actors-cli.test.d.ts.map +1 -0
  146. package/dist/test/unit/bin/affected-actors-cli.test.js +224 -0
  147. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -0
  148. package/dist/test/unit/bin/affected-actors.test.d.ts +2 -0
  149. package/dist/test/unit/bin/affected-actors.test.d.ts.map +1 -0
  150. package/dist/test/unit/bin/affected-actors.test.js +153 -0
  151. package/dist/test/unit/bin/affected-actors.test.js.map +1 -0
  152. package/dist/test/unit/bin/build-cli.test.d.ts +2 -0
  153. package/dist/test/unit/bin/build-cli.test.d.ts.map +1 -0
  154. package/dist/test/unit/bin/build-cli.test.js +49 -0
  155. package/dist/test/unit/bin/build-cli.test.js.map +1 -0
  156. package/dist/test/unit/bin/build-from-local.test.js +12 -0
  157. package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
  158. package/dist/test/unit/bin/build.test.js +116 -6
  159. package/dist/test/unit/bin/build.test.js.map +1 -1
  160. package/dist/test/unit/bin/compiled-cli.d.ts +6 -0
  161. package/dist/test/unit/bin/compiled-cli.d.ts.map +1 -0
  162. package/dist/test/unit/bin/compiled-cli.js +23 -0
  163. package/dist/test/unit/bin/compiled-cli.js.map +1 -0
  164. package/dist/test/unit/bin/diff-changes.test.js +168 -114
  165. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  166. package/dist/test/unit/bin/diff.test.js +25 -28
  167. package/dist/test/unit/bin/diff.test.js.map +1 -1
  168. package/dist/test/unit/bin/git.test.js +67 -288
  169. package/dist/test/unit/bin/git.test.js.map +1 -1
  170. package/dist/test/unit/bin/notify.test.d.ts +2 -0
  171. package/dist/test/unit/bin/notify.test.d.ts.map +1 -0
  172. package/dist/test/unit/bin/notify.test.js +222 -0
  173. package/dist/test/unit/bin/notify.test.js.map +1 -0
  174. package/dist/test/unit/bin/stdin-input.test.d.ts +2 -0
  175. package/dist/test/unit/bin/stdin-input.test.d.ts.map +1 -0
  176. package/dist/test/unit/bin/stdin-input.test.js +92 -0
  177. package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
  178. package/dist/test/unit/bin/test-cli.test.d.ts +2 -0
  179. package/dist/test/unit/bin/test-cli.test.d.ts.map +1 -0
  180. package/dist/test/unit/bin/test-cli.test.js +146 -0
  181. package/dist/test/unit/bin/test-cli.test.js.map +1 -0
  182. package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
  183. package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
  184. package/dist/test/unit/bin/test-files-glob.test.js +102 -0
  185. package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
  186. package/dist/test/unit/bin/test-report.test.d.ts +2 -0
  187. package/dist/test/unit/bin/test-report.test.d.ts.map +1 -0
  188. package/dist/test/unit/bin/test-report.test.js +100 -0
  189. package/dist/test/unit/bin/test-report.test.js.map +1 -0
  190. package/dist/test/unit/bin/utils/config/load-config.test.js +19 -1
  191. package/dist/test/unit/bin/utils/config/load-config.test.js.map +1 -1
  192. package/dist/test/unit/bin/utils/config/parser.test.js +36 -4
  193. package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
  194. package/dist/test/unit/bin/utils/config/structures/grouped.test.js +6 -0
  195. package/dist/test/unit/bin/utils/config/structures/grouped.test.js.map +1 -1
  196. package/dist/test/unit/bin/utils/config/structures/legacy.test.js +1 -0
  197. package/dist/test/unit/bin/utils/config/structures/legacy.test.js.map +1 -1
  198. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts +2 -0
  199. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts.map +1 -0
  200. package/dist/test/unit/github-scripts/mask-config-secrets.test.js +50 -0
  201. package/dist/test/unit/github-scripts/mask-config-secrets.test.js.map +1 -0
  202. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts +2 -0
  203. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts.map +1 -0
  204. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +102 -0
  205. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -0
  206. package/dist/test/unit/run-checks.test.d.ts +2 -0
  207. package/dist/test/unit/run-checks.test.d.ts.map +1 -0
  208. package/dist/test/unit/run-checks.test.js +62 -0
  209. package/dist/test/unit/run-checks.test.js.map +1 -0
  210. package/dist/test/unit/run-test-result.test.d.ts +2 -0
  211. package/dist/test/unit/run-test-result.test.d.ts.map +1 -0
  212. package/dist/test/unit/run-test-result.test.js +34 -0
  213. package/dist/test/unit/run-test-result.test.js.map +1 -0
  214. package/dist/test/unit/should-built-and-test.test.js +41 -38
  215. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  216. package/dist/tsconfig.tsbuildinfo +1 -1
  217. package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts +3 -0
  218. package/dist/workflow-scripts-src/run-with-apify-tokens.d.ts.map +1 -0
  219. package/dist/workflow-scripts-src/run-with-apify-tokens.js +100 -0
  220. package/dist/workflow-scripts-src/run-with-apify-tokens.js.map +1 -0
  221. package/docs/cli.md +469 -0
  222. package/docs/lib.md +276 -0
  223. package/docs/workflows.md +455 -0
  224. package/eslint.config.mjs +10 -1
  225. package/lib/extend-expect.ts +48 -258
  226. package/lib/lib.ts +25 -34
  227. package/lib/run-checks.ts +101 -0
  228. package/lib/run-test-result.ts +45 -55
  229. package/package.json +4 -2
  230. package/platform-tests-investigate-and-fix/skills/assess-impact/SKILL.md +42 -0
  231. package/platform-tests-investigate-and-fix/skills/measure-flakiness/SKILL.md +4 -2
  232. package/test/bundle/cli-bundle.test.ts +97 -67
  233. package/test/bundle/token-wrapper.test.ts +110 -0
  234. package/test/e2e/cli-bundle.e2e.test.ts +60 -93
  235. package/test/e2e/git-build-release.e2e.test.ts +113 -0
  236. package/test/e2e/helpers.ts +82 -0
  237. package/test/unit/bin/actor-filtering.test.ts +8 -0
  238. package/test/unit/bin/actor-ids.test.ts +47 -0
  239. package/test/unit/bin/affected-actors-cli.test.ts +271 -0
  240. package/test/unit/bin/affected-actors.test.ts +221 -0
  241. package/test/unit/bin/build-cli.test.ts +61 -0
  242. package/test/unit/bin/build-from-local.test.ts +16 -0
  243. package/test/unit/bin/build.test.ts +149 -5
  244. package/test/unit/bin/compiled-cli.ts +23 -0
  245. package/test/unit/bin/diff-changes.test.ts +181 -114
  246. package/test/unit/bin/diff.test.ts +25 -28
  247. package/test/unit/bin/git.test.ts +76 -361
  248. package/test/unit/bin/notify.test.ts +299 -0
  249. package/test/unit/bin/stdin-input.test.ts +120 -0
  250. package/test/unit/bin/test-cli.test.ts +175 -0
  251. package/test/unit/bin/test-files-glob.test.ts +118 -0
  252. package/test/unit/bin/test-report.test.ts +118 -0
  253. package/test/unit/bin/utils/config/load-config.test.ts +26 -1
  254. package/test/unit/bin/utils/config/parser.test.ts +46 -5
  255. package/test/unit/bin/utils/config/structures/grouped.test.ts +12 -0
  256. package/test/unit/bin/utils/config/structures/legacy.test.ts +1 -0
  257. package/test/unit/github-scripts/mask-config-secrets.test.ts +61 -0
  258. package/test/unit/github-scripts/run-with-apify-tokens.test.ts +115 -0
  259. package/test/unit/run-checks.test.ts +76 -0
  260. package/test/unit/run-test-result.test.ts +44 -0
  261. package/test/unit/should-built-and-test.test.ts +46 -39
  262. package/tsconfig.json +1 -1
  263. package/vitest.config.ts +9 -0
  264. package/workflow-scripts-src/run-with-apify-tokens.ts +112 -0
  265. package/.github/scripts/check-major-tag-refs.mjs +0 -72
  266. package/dist/test/unit/bin/logging.test.d.ts +0 -2
  267. package/dist/test/unit/bin/logging.test.d.ts.map +0 -1
  268. package/dist/test/unit/bin/logging.test.js +0 -43
  269. package/dist/test/unit/bin/logging.test.js.map +0 -1
  270. package/dist/test/unit/bin/slack.test.d.ts +0 -2
  271. package/dist/test/unit/bin/slack.test.d.ts.map +0 -1
  272. package/dist/test/unit/bin/slack.test.js +0 -54
  273. package/dist/test/unit/bin/slack.test.js.map +0 -1
  274. package/test/unit/bin/logging.test.ts +0 -52
  275. package/test/unit/bin/slack.test.ts +0 -65
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=run-with-apify-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-with-apify-tokens.d.ts","sourceRoot":"","sources":["../../workflow-scripts-src/run-with-apify-tokens.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ // Runs apify-test-tools and passes it only secrets needed for the Actors, no other secrets are leaked.
3
+ //
4
+ // jq ... | node .github/scripts/run-with-apify-tokens.mjs apify-test-tools build --version-number 0.99
5
+ //
6
+ // The step passes the reusable workflow's whole secrets map as WORKFLOW_SECRETS. The token names
7
+ // come from `tokenEnvVar` in the repo's apify-test-tools.config.json, which is the same file
8
+ // apify-test-tools itself reads, so the two can't drift. Everything else in the secrets map (npm,
9
+ // Slack, GitHub, anything else the repo holds) is left out, and WORKFLOW_SECRETS itself is dropped,
10
+ // so the blob never reaches the CLI or anything under node_modules.
11
+ //
12
+ // Each token is looked up in the CONFIG_SECRETS secret first, a JSON object of secret name -> value,
13
+ // and then among the top-level secrets, which is where `secrets: inherit` puts them. The secrets the
14
+ // workflows read by name (NPM_TOKEN, TESTER_APIFY_TOKEN, ...) never come from CONFIG_SECRETS.
15
+ //
16
+ // The command runs without a shell, so branch names and other interpolated arguments are passed
17
+ // through as literal argv entries.
18
+ import { spawnSync } from 'node:child_process';
19
+ import { CONFIG_FILE_NAME, readBaseConfigFile } from '../bin/utils/config/load-config.js';
20
+ function fail(message) {
21
+ console.error(message);
22
+ process.exit(1);
23
+ }
24
+ function isRecord(value) {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+ const [command, ...args] = process.argv.slice(2);
28
+ if (!command)
29
+ fail('Usage: run-with-apify-tokens.mjs <command> [args...]');
30
+ const { WORKFLOW_SECRETS, ...baseEnv } = process.env;
31
+ if (!WORKFLOW_SECRETS) {
32
+ fail('WORKFLOW_SECRETS is not set. Add `WORKFLOW_SECRETS: ${{ toJSON(secrets) }}` to the step env.');
33
+ }
34
+ let workflowSecrets;
35
+ try {
36
+ workflowSecrets = JSON.parse(WORKFLOW_SECRETS);
37
+ }
38
+ catch (error) {
39
+ fail(`WORKFLOW_SECRETS is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
40
+ }
41
+ const workflowSecretsMap = workflowSecrets;
42
+ if (!isRecord(workflowSecretsMap)) {
43
+ fail('WORKFLOW_SECRETS must be a JSON object of secret names and values.');
44
+ }
45
+ // Never includes the parse error: Node quotes the input in it, and the input is a secret.
46
+ const unpackConfigSecrets = () => {
47
+ const packed = workflowSecretsMap.CONFIG_SECRETS;
48
+ if (!packed)
49
+ return {};
50
+ if (typeof packed !== 'string')
51
+ fail('The CONFIG_SECRETS secret is not valid JSON.');
52
+ let value;
53
+ try {
54
+ value = JSON.parse(packed);
55
+ }
56
+ catch {
57
+ fail('The CONFIG_SECRETS secret is not valid JSON.');
58
+ }
59
+ if (!isRecord(value)) {
60
+ fail('The CONFIG_SECRETS secret must be a JSON object.');
61
+ }
62
+ return value;
63
+ };
64
+ const secrets = { ...workflowSecretsMap, ...unpackConfigSecrets() };
65
+ let actorConfigs;
66
+ try {
67
+ actorConfigs = await readBaseConfigFile();
68
+ }
69
+ catch (error) {
70
+ fail(error instanceof Error ? error.message : String(error));
71
+ }
72
+ const tokenNames = [...new Set(actorConfigs.map(({ tokenEnvVar }) => tokenEnvVar))].sort();
73
+ // Declared but absent is a warning, not an error. A repo can carry an Actor whose token isn't
74
+ // configured and still build fine as long as that Actor never changes, and apify-test-tools raises
75
+ // a precise error naming the Actor at the point it actually needs the token.
76
+ const missing = tokenNames.filter((name) => !(name in secrets));
77
+ if (missing.length > 0) {
78
+ console.error(`Warning: ${CONFIG_FILE_NAME} declares ${missing.join(', ')}, but no such secret was passed ` +
79
+ `to this workflow, neither inside CONFIG_SECRETS nor inherited. Check the repository secrets ` +
80
+ `and the workflow's \`secrets:\` if a build fails on a missing token.`);
81
+ }
82
+ const present = tokenNames.filter((name) => name in secrets);
83
+ // Names only. The values are secrets and must never be printed, even though the runner masks
84
+ // registered secrets in logs.
85
+ console.error(`Passing ${present.length} Actor token(s) to \`${command}\`: ${present.join(', ') || '(none)'}`);
86
+ const env = { ...baseEnv };
87
+ for (const name of present) {
88
+ const value = secrets[name];
89
+ if (typeof value !== 'string')
90
+ fail(`Secret "${name}" must be a string.`);
91
+ env[name] = value;
92
+ }
93
+ const result = spawnSync(command, args, { stdio: 'inherit', env });
94
+ if (result.error) {
95
+ fail(`Failed to run \`${command}\`: ${result.error.message}`);
96
+ }
97
+ // Preserve the child's exit code so the step fails exactly when the command does. A child killed
98
+ // by a signal reports a null status, which would otherwise be read as success.
99
+ process.exit(result.status ?? 1);
100
+ //# sourceMappingURL=run-with-apify-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-with-apify-tokens.js","sourceRoot":"","sources":["../../workflow-scripts-src/run-with-apify-tokens.ts"],"names":[],"mappings":"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"}
package/docs/cli.md ADDED
@@ -0,0 +1,469 @@
1
+ # CLI API
2
+
3
+ The package includes the `apify-test-tools` binary. The [reusable workflows](workflows.md) call it to find changed
4
+ Actors, build them, and report results, and you can run it locally the same way:
5
+
6
+ ```bash
7
+ npx apify-test-tools <command> [flags]
8
+ ```
9
+
10
+ Run every command from the root of the Actor repository; it reads `apify-test-tools.config.json` from there.
11
+ Results are written to stdout as JSON, diagnostics to stderr, so commands can be piped into each other and into `jq`.
12
+
13
+ | Command | Does | Needs |
14
+ | --------------------------------------------------------------------- | ------------------------------------------------------- | -------------------- |
15
+ | [`get-affected-actors`](#finding-affected-actors-get-affected-actors) | Lists the Actors changed in a commit range | git only |
16
+ | [`build`](#building-actors-build) | Builds the Actors given on stdin from the pushed branch | Actor tokens |
17
+ | [`build-from-local`](#build-from-local-source-build-from-local) | Builds Actors from local files | Actor tokens |
18
+ | [`test`](#running-tests-test) | Runs the platform tests on the builds given on stdin | `TESTER_APIFY_TOKEN` |
19
+ | [`notify`](#notifying-notify) | Posts a test report or a release to Slack | a Slack token |
20
+ | [`delete-old-builds`](#deleting-old-builds-delete-old-builds) | Deletes old builds of the Actors | Actor tokens |
21
+ | [`get-actor-configs`](#listing-actors-get-actor-configs) | Prints the resolved config | nothing |
22
+
23
+ "Actor tokens" are the environment variables named by each Actor's `tokenEnvVar` in the config. A command only needs
24
+ the tokens of the Actors it touches.
25
+
26
+ ## Global flags
27
+
28
+ | Flag | Default | Meaning |
29
+ | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
30
+ | `--log-level` | `info` | Minimum level written to stderr: `debug`, `info`, `warn`, `error`, or `silent`. Results on stdout are unaffected |
31
+
32
+ ## Selecting Actors (`--actors`, `--ignore`)
33
+
34
+ Commands that read the config take `--actors` and `--ignore`, each a list of `actorFullName`s (space-separated,
35
+ or the flag repeated). `--actors` keeps only the listed Actors, `--ignore` drops them. An unknown
36
+ name is an error rather than silently selecting nothing.
37
+
38
+ ## Dry runs (`--dry-run`)
39
+
40
+ Every command that changes something (`build`, `build-from-local`, `notify`, `delete-old-builds`) takes
41
+ `--dry-run`. A dry run:
42
+
43
+ - **Changes nothing.** No versions or builds are created, nothing is uploaded or deleted, and no Slack message is
44
+ posted.
45
+ - **Does every read and check the real run does**, so it fails wherever the real run would: change detection, the
46
+ Apify token and Actor lookup, the [repository check](#repository-check), collecting and zipping local sources, and
47
+ the Slack token (verified with Slack's read-only `auth.test`).
48
+ - **Needs the same tokens as the real run.** Read-only tokens are enough, since nothing is written. A dry run can't
49
+ tell whether a token is also allowed to write, because the Apify API has no dry-run mode of its own.
50
+ - **Prints a plan, not results.** Logs start with `[DRY RUN] Would …`. `build` and `build-from-local` write
51
+ `{ "dryRun": true, "builds": [...] }` to stdout instead of the build array, so the plan can't be passed to tests as
52
+ `ACTOR_BUILDS` by mistake.
53
+
54
+ ## Change detection
55
+
56
+ When a PR is opened or code is pushed, the tool determines which Actors need to be built and tested based on the
57
+ changed files. For each changed file, for each Actor:
58
+
59
+ 1. **Sibling exclusion** — files inside another Actor's `folder` are excluded first. This prevents an Actor with
60
+ broad context from being triggered by changes that belong to a sibling Actor.
61
+ 2. **CHANGELOG classification** — a `CHANGELOG.md` file is always `cosmetic` (only triggers a release build, not
62
+ tests), for every Actor, regardless of context or folder. (See
63
+ [issue #106](https://github.com/apify/apify-test-tools/issues/106).)
64
+ 3. **Context matching** — the file must fall within one of the Actor's context paths (`dockerContextDir` from
65
+ `actor.json` by default, or `overrideActorContext` from config if set). Files outside every context path are
66
+ skipped.
67
+ 4. **Hardcoded ignore list, context-aware** — the file path is first "hoisted" relative to the context path it
68
+ matched (e.g. a standalone Actor's own `.eslintrc` is checked as just `.eslintrc`, not the full
69
+ repo-root-relative path), then checked against repo-level dev file patterns (`.vscode/`, `.gitignore`, `.husky/`,
70
+ `.eslintrc`, `eslint.config.mjs`, `.prettierrc`, `.editorconfig`). Directories like `code/` or `shared/`
71
+ get no special treatment: to treat them as top-level, list them in `overrideActorContext`.
72
+ 5. **`.dockerignore` filtering** — if a `.dockerignore` exists at the root of the Actor's `dockerContextDir`,
73
+ matching files are ignored. Patterns are resolved relative to `dockerContextDir`, matching Docker's own behavior.
74
+ Platform test files are the exception, see the next step.
75
+ **Platform test files** — a file that matches `platformTestsGlob` from the config file (a glob from the repo root,
76
+ default `test/platform/**`, helpers included) is a `test` change, also when `.dockerignore` lists it. This is
77
+ checked before the `.dockerignore` filtering. A test change puts the Actor in `testChangedActors`, so its tests
78
+ run, but it is neither functional nor cosmetic, so a release doesn't build it. Context matching still applies, so
79
+ tests outside an Actor's `dockerContextDir` (e.g. a root `test/` with standalone Actors) need to be listed in its
80
+ `overrideActorContext`; `get-affected-actors` logs a warning for changed test files outside every Actor's context.
81
+ 6. **README classification** — a `README.md` file is `cosmetic` (only triggers a release build, not tests) if it
82
+ lives inside the Actor's own `folder`; otherwise it's ignored entirely, since it isn't documentation for this
83
+ Actor.
84
+ 7. **Cosmetic JSON classification** — `.json` files inside the Actor's own `.actor/` directory with only cosmetic
85
+ schema changes (whitespace, key ordering) only trigger a release build.
86
+ 8. **Functional** — everything else triggers both build and tests.
87
+
88
+ ## Finding affected Actors (`get-affected-actors`)
89
+
90
+ `get-affected-actors` lists the Actors whose context changed in a commit range, using the rules in
91
+ [Change detection](#change-detection). It only reads git and the config file, so it needs no tokens. The range always
92
+ ends at the checked-out `HEAD`, so check out the branch you want to inspect first.
93
+
94
+ ```bash
95
+ # A PR: the branch's own changes, narrowed to the Actors that changed since its last validated commit
96
+ npx apify-test-tools get-affected-actors --base origin/master --since <sha>
97
+
98
+ # A release: everything after the last released commit
99
+ npx apify-test-tools get-affected-actors --since <sha>
100
+ ```
101
+
102
+ | Flag | Default | Meaning |
103
+ | ---------- | ------- | -------------------------------------------------------------------------------------- |
104
+ | `--base` | | Only what `HEAD` changes compared to this branch counts, like `git diff <base>...HEAD` |
105
+ | `--since` | | Only changes after this commit count (see below) |
106
+ | `--actors` | all | Only report these Actors |
107
+ | `--ignore` | none | Never report these Actors |
108
+
109
+ Pass `--base`, `--since`, or both:
110
+
111
+ - **`--base` only** inspects what the branch changes, the same files GitHub's "Files changed" tab shows: the diff
112
+ from where the branch forked from `--base`, or the last commit of `--base` it merged in. What it merged in from
113
+ `--base` is already validated there, so it doesn't count.
114
+ - **`--base` and `--since`** also inspects what changed after the branch's last validated commit, and reports only
115
+ the Actors that changed in both. An Actor in only one of them is already validated, by this branch or on `--base`.
116
+ When both changed an Actor, the changes can interact, so it is reported, but as functional only when both changes
117
+ are functional. A validated functional change followed by README edits is cosmetic, and so is a functional
118
+ change merged in from `--base` that meets only cosmetic changes of the branch. `--since` doesn't have to be on
119
+ the branch, so a rebase only re-checks the Actors that `--base` changed too. If `--since` is not in the local
120
+ history (a force-push), or it is `HEAD` (a rerun), it is ignored and the whole branch is checked.
121
+ - **`--since` only** inspects everything after it up to `HEAD`. It must be an ancestor of `HEAD` and fails
122
+ otherwise, see [Releasing Actors](#releasing-actors). If `HEAD` is `--since`, nothing is reported.
123
+
124
+ `commits`, `changedFiles` and `changelog` describe the whole range, `--base...HEAD` or `--since..HEAD`: `--since`
125
+ together with `--base` only narrows the Actor lists.
126
+
127
+ The output is a JSON document:
128
+
129
+ ```json
130
+ {
131
+ "version": 1,
132
+ "head": "<sha of HEAD>",
133
+ "commits": [{ "sha": "…", "author": "…", "date": "…", "message": "…" }],
134
+ "changedFiles": ["src/main.ts", "CHANGELOG.md"],
135
+ "changelog": "Lines added to CHANGELOG.md, or null",
136
+ "functionallyChangedActors": [{ "actorFullName": "john.doe/my-actor" }],
137
+ "cosmeticallyChangedActors": [{ "actorFullName": "john.doe/other-actor" }],
138
+ "testChangedActors": [{ "actorFullName": "john.doe/my-actor" }]
139
+ }
140
+ ```
141
+
142
+ `functionallyChangedActors` have at least one functional change, so they need a new build and tests.
143
+ `cosmeticallyChangedActors` have cosmetic changes and no functional one (see [Change detection](#change-detection)), so
144
+ they need a new build only when released. An Actor is in at most one of these two lists. `testChangedActors` have a
145
+ changed platform test (`platformTestsGlob` in the config) in their context, whatever else changed, so their tests need
146
+ to run. A test change alone needs no release. The PR workflow builds `functionallyChangedActors` and
147
+ `testChangedActors`. The release workflow builds `functionallyChangedActors` and `cosmeticallyChangedActors`.
148
+
149
+ With `--base` and `--since`, an Actor is in `testChangedActors` when its tests changed on the branch and again since
150
+ `--since`, the same rule as for functional changes.
151
+
152
+ ## Building Actors (`build`)
153
+
154
+ `build` builds exactly the Actors it is given on stdin, from the checked-out branch as it is on the remote, and
155
+ prints the builds as the same JSON array `build-from-local` prints. An Actor listed twice is built once, so the lists
156
+ of `get-affected-actors` can be joined without removing duplicates first. A detached HEAD is an error, since there is
157
+ no branch to build from. It doesn't look at git history: which Actors to build is up to you, usually picked from the
158
+ output of [`get-affected-actors`](#finding-affected-actors-get-affected-actors).
159
+
160
+ ```bash
161
+ # A PR: the Actors whose code or platform tests the branch changed, built into the test version
162
+ npx apify-test-tools get-affected-actors --base origin/master \
163
+ | jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' \
164
+ | npx apify-test-tools build --version-number 0.99
165
+
166
+ # A release: every Actor changed since the last release, into the version users run
167
+ npx apify-test-tools get-affected-actors --since <sha> \
168
+ | jq '{ actors: (.functionallyChangedActors + .cosmeticallyChangedActors) }' \
169
+ | npx apify-test-tools build --version-number default
170
+
171
+ # One Actor, whatever changed
172
+ echo '{ "actors": [{ "actorFullName": "john.doe/my-actor" }] }' | npx apify-test-tools build --version-number 0.99
173
+ ```
174
+
175
+ The input is `{ "actors": [{ "actorFullName": "<owner/name>" }] }`; other fields are ignored. An empty `actors` list
176
+ builds nothing and prints `[]`. Empty or invalid input is an error, so a failed command earlier in the pipe can't turn
177
+ into a successful run that built nothing. In GitHub Actions, give such a step `shell: bash` so the pipe also fails on
178
+ its own (the default shell has no `pipefail`).
179
+
180
+ | Flag | Default | Meaning |
181
+ | ---------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
182
+ | `--version-number` | required | Version to build, like `0.99`, or `default` for each Actor's default version (the one its default build tag points to) |
183
+ | `--repo-url` | the `origin` remote | Repository to build from, see [Repository check](#repository-check) |
184
+ | `--require-env-vars-in-sync` | `false` | Fail unless the built version's env var names match the default version's, see [Env var check](#env-var-check) |
185
+ | `--dry-run` | `false` | See [Dry runs](#dry-runs---dry-run) |
186
+
187
+ The output is the build array the vitest library reads from `ACTOR_BUILDS`:
188
+
189
+ ```json
190
+ [{ "buildId": "...", "actorRawId": "...", "buildNumber": "...", "actorFullName": "john.doe/my-actor" }]
191
+ ```
192
+
193
+ `build` never sets or changes build tags. A build gets the tag its version is configured with on the platform, so
194
+ building the default version moves its tag (usually `latest`) to the new build, and building a version without a
195
+ tag leaves every tag where it is. Builds always use the Docker cache.
196
+
197
+ ### Repository check
198
+
199
+ `build` points each Actor's version at the `origin` remote. Because a build overwrites the version's Git URL,
200
+ building from a fork or a mirror would repoint the Actor. So, before building, the remote is compared with the Git
201
+ URL of the Actor's default version (the one its default build tag points to), and the command fails if they differ
202
+ or if the default version isn't built from a Git repository. The URL form doesn't matter
203
+ (`https://github.com/org/repo` equals `git@github.com:org/repo.git`).
204
+
205
+ To build from a different repository on purpose, e.g. when moving an Actor, pass `--repo-url`. That skips the check.
206
+
207
+ ### Env var check
208
+
209
+ Tests should run the Actor with the same env vars users run it with. Pass `--require-env-vars-in-sync` to `build` or
210
+ `build-from-local` to compare the env vars of the version they build (e.g. `0.99`, or `0.98` for `build-from-local`)
211
+ with those of the default version, and fail if a name is missing or extra. Only names are compared, since the API
212
+ doesn't return secret values. A version that doesn't exist yet counts as having no env vars, so if the default
213
+ version has any, create the test version on the platform with the same env vars first. A dry run checks the env vars
214
+ too.
215
+
216
+ The CLI leaves it off by default, so a one-off build, e.g. of a debug branch, isn't held up by it.
217
+ [`public_pr-build-test.yaml`](workflows.md#public_pr-build-testyaml) turns it on by default; set its
218
+ `require-env-vars-in-sync` input to `false` to turn it off.
219
+
220
+ ## Build from local source (`build-from-local`)
221
+
222
+ Builds Actors directly from your local files, with no push needed: the sources are zipped into the Actor's
223
+ `actor-<id>-source` key-value store and built as `TARBALL`, like `apify push`. It prints the same build array as
224
+ `build`.
225
+
226
+ ```bash
227
+ APIFY_TOKEN_JOHN_DOE=<token> npx apify-test-tools build-from-local --actors john.doe/my-actor
228
+ ```
229
+
230
+ | Flag | Default | Meaning |
231
+ | ---------------------------- | ------- | -------------------------------------------------------------------------------------------------------- |
232
+ | `--actors` | all | Only build these Actors. Point vitest at their tests only, for quicker feedback |
233
+ | `--ignore` | none | Never build these Actors |
234
+ | `--require-env-vars-in-sync` | `false` | Fail unless the env var names of `0.98` match the default version's, see [Env var check](#env-var-check) |
235
+ | `--dry-run` | `false` | Check the token, the Actor and the zipped sources without uploading or building |
236
+
237
+ ## Releasing Actors
238
+
239
+ A release is three commands: find what changed since the last released commit, build those Actors into their
240
+ default version (the one users run), and announce it.
241
+
242
+ ```bash
243
+ npx apify-test-tools get-affected-actors --since <sha> > affected.json
244
+
245
+ jq '{ actors: (.functionallyChangedActors + .cosmeticallyChangedActors) }' affected.json \
246
+ | npx apify-test-tools build --version-number default --dry-run
247
+
248
+ npx apify-test-tools notify --slack-channel "#notif-my-repo" --dry-run < affected.json
249
+ ```
250
+
251
+ [`notify`](#notifying-notify) says what it posts for a release.
252
+
253
+ ### The last released commit (`--since`)
254
+
255
+ The last commit that is **already released**. Everything after it, up to `HEAD`, is released. Git alone can't tell
256
+ which commits a push brought, and guessing wrong silently leaves Actors on old code, so it has to be passed.
257
+
258
+ The changed files come from diffing `--since` and `HEAD` directly, so this works for every merge strategy as long as
259
+ `--since` is the branch tip from **before** the push:
260
+
261
+ | Merge strategy | What lands on the branch | What to pass |
262
+ | ------------------------------ | --------------------------------- | ---------------------------------------------------------------------- |
263
+ | Squash and merge | 1 new commit | the push's "before" commit (`HEAD~1` also works) |
264
+ | Merge commit | the PR's commits + 1 merge commit | the push's "before" commit (`HEAD^1`, the first parent, also works) |
265
+ | Rebase and merge / direct push | N new commits | the push's "before" commit (`HEAD~1` would miss the first N-1 commits) |
266
+
267
+ On GitHub, the push's "before" commit is `${{ github.event.before }}`, which the reusable workflow passes for you.
268
+ On other CIs use the equivalent, e.g. `$CI_COMMIT_BEFORE_SHA` on GitLab. The checkout needs the full history
269
+ (`fetch-depth: 0` in `actions/checkout`) so the commit is available locally.
270
+
271
+ `get-affected-actors --since` without `--base` fails instead of guessing when:
272
+
273
+ - the commit is all zeros (GitHub sends this for a push that created the branch)
274
+ - the commit isn't in the local history (shallow checkout) or isn't an ancestor of `HEAD` (the branch was
275
+ force-pushed)
276
+
277
+ In those cases, rerun it with a `--since` you pick yourself: an ancestor of `HEAD` from before the changes you want
278
+ to release (for a shallow checkout, fetch the full history instead). `--actors` only narrows which of the changed
279
+ Actors are reported; it never forces a build, so on its own it doesn't get past these errors. If `HEAD` is
280
+ `--since`, nothing is reported, so nothing is built or announced.
281
+
282
+ A failed release isn't retried by the next push: that push's base commit is the failed one's head, so its changes
283
+ are skipped. Re-run the failed workflow run, or release the Actors manually.
284
+
285
+ ## Running tests (`test`)
286
+
287
+ `test` runs the repository's own vitest (the version in its `node_modules`, so the tests keep the lib version
288
+ the repository locked) on the Actors it is given on stdin, and prints a report of the run for
289
+ [`notify`](#notifying-notify). Only the tests of those Actors run.
290
+
291
+ ```bash
292
+ # A PR: test what was just built
293
+ ... | npx apify-test-tools build --version-number 0.99 | npx apify-test-tools test > test-report.json
294
+
295
+ # Scheduled tests: every Actor in the config, on its default build
296
+ npx apify-test-tools get-actor-configs \
297
+ | npx apify-test-tools test --test-files-glob 'test/platform/{hourly,hourly-without-pr}/**' > test-report.json
298
+
299
+ # Only some of them
300
+ npx apify-test-tools get-actor-configs --actors john.doe/my-actor | npx apify-test-tools test > test-report.json
301
+ ```
302
+
303
+ The input is a JSON array of Actors, each given by its full name, its raw ID or both:
304
+ `[{ "actorFullName": "<owner/name>" }]` or `[{ "actorRawId": "<id>" }]`. An Actor's tests run against the build given
305
+ with it (`buildId` and `buildNumber`, as `build` and `build-from-local` print them), or against its default build
306
+ without one. Other fields are ignored, so the output of [`get-actor-configs`](#listing-actors-get-actor-configs) works
307
+ as it is. `test` looks up the ID an Actor is listed without with `TESTER_APIFY_TOKEN`, so its tests run whichever ID
308
+ they name it by. An Actor the token can't see keeps only the ID it was listed with, and a warning names it, so an
309
+ Actor no test uses doesn't stop the run. A dry-run plan, empty or invalid input is an error, as with `build`. So is a
310
+ missing `TESTER_APIFY_TOKEN`: without it the lib skips every platform test, and the run would pass having tested
311
+ nothing. `test` passes the Actors to vitest as `ACTOR_BUILDS`, in place of any
312
+ `ACTOR_BUILDS` in its own environment.
313
+
314
+ It runs vitest with `--maxConcurrency=20 --fileParallelism --maxWorkers=100`, since platform tests mostly wait on
315
+ Actor runs rather than the CPU, and with the default reporter plus the JSON one the report is read from. These
316
+ replace any `reporters` in the repository's vitest config; add more with `--reporter`, e.g. `github-actions` to
317
+ annotate the failing tests in a GitHub Actions run. It takes no other vitest options; for those, e.g. `-t` to run one
318
+ test by name, run vitest directly with the builds in `ACTOR_BUILDS` (see
319
+ [Running tests locally](lib.md#running-tests-locally)).
320
+
321
+ | Flag | Default | Meaning |
322
+ | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
323
+ | `--test-files-glob` | `test/platform/**` | Glob from the repo root of the test files to run, see below |
324
+ | `--vitest-output-file` | | Also keep vitest's own JSON report there, e.g. to upload it |
325
+ | `--reporter` | | [Vitest reporter](https://vitest.dev/guide/reporters) to add to `default` and `json`, e.g. `github-actions`. Repeat it for more |
326
+
327
+ `test` takes no test paths as arguments, only `--test-files-glob`. The glob is read the same way as
328
+ `platformTestsGlob` in the config file. Vitest filters are parts of a file path, not globs, so `test` finds the files
329
+ the glob matches and passes vitest as few paths as it can: each folder whose files all match, with a trailing `/`, and
330
+ the other matched files one by one. `test/platform/{daily,hourly}/**` becomes two folders however many tests they
331
+ hold, so the command line stays short. Vitest still applies its own `include` and `exclude`, so helper files in the
332
+ glob run nothing. A glob that matches no file is an error, because vitest without filters would run every test of the
333
+ repo. Quote the glob, so the shell doesn't expand it.
334
+
335
+ Vitest's own output goes to stderr, followed by a summary that lists every test with the link to its run. Stdout
336
+ gets only the report:
337
+
338
+ ```json
339
+ {
340
+ "kind": "test-report",
341
+ "version": 1,
342
+ "success": false,
343
+ "summary": { "total": 3, "passed": 1, "failed": 1, "skipped": 1 },
344
+ "tests": [
345
+ {
346
+ "name": "john.doe/my-actor: finishes",
347
+ "file": "test/platform/my-actor.test.ts",
348
+ "status": "failed",
349
+ "durationMs": 61234,
350
+ "failureMessages": ["AssertionError: expected 'FAILED' to be 'SUCCEEDED'"],
351
+ "run": { "actorId": "john.doe/my-actor", "runId": "…", "runLink": "https://console.apify.com/view/runs/…" }
352
+ }
353
+ ],
354
+ "errors": ["test/platform/other.test.ts: Cannot find package 'foo'"]
355
+ }
356
+ ```
357
+
358
+ `failureMessages` keeps the first line of each failure; diffs and stack traces are in the vitest output. `run` is
359
+ `null` for a test that started no run. `errors` lists what failed outside of any test: a test file that failed to
360
+ load, or vitest exiting without a report. `test` exits with 1 whenever `success` is false, and prints the report
361
+ anyway, so `notify` still hears about the failure.
362
+
363
+ ## Notifying (`notify`)
364
+
365
+ `notify` tells one target about what it reads on stdin: a test run, as [`test`](#running-tests-test) reports it,
366
+ or a release, as [`get-affected-actors`](#finding-affected-actors-get-affected-actors) reports it. It tells the two
367
+ apart by the report's `kind` and the release's `commits`, so either can be piped in as it is. Slack is the only target for now. To post to more
368
+ places, call it once for each.
369
+
370
+ ```bash
371
+ # A test run: posts only when the tests failed, the failure counts and the first failure, linked to its run,
372
+ # with the other failures in the message's thread
373
+ npx apify-test-tools notify --slack-channel '#notif-my-repo' < test-report.json
374
+ npx apify-test-tools notify --slack-channel '#my-team' < test-report.json
375
+
376
+ # Or straight from test. With `set -o pipefail` (`shell: bash` in GitHub Actions), failed tests still fail the pipe
377
+ npx apify-test-tools get-actor-configs | npx apify-test-tools test | npx apify-test-tools notify --slack-channel '#notif-my-repo'
378
+
379
+ # A release, see Releasing Actors
380
+ npx apify-test-tools get-affected-actors --since <sha> | npx apify-test-tools notify --slack-channel '#notif-my-repo'
381
+ ```
382
+
383
+ | Flag | Input | Default | Meaning |
384
+ | ----------------- | ------- | ------------------- | ----------------------------------------------------------------- |
385
+ | `--slack-channel` | both | | Slack channel to post to |
386
+ | `--on` | tests | `failure` | `failure` posts only when the tests failed, `always` on every run |
387
+ | `--title` | tests | `-` | What the message calls the run, e.g. the CI workflow name |
388
+ | `--job-url` | tests | | Link to the CI job, included in the message |
389
+ | `--repo-url` | release | the `origin` remote | The repository the release is named after and its commits link to |
390
+
391
+ A flag the input has no use for, e.g. `--repo-url` with a test report, is an error rather than ignored. Both kinds
392
+ of notification post with `SLACK_BOT_TOKEN`.
393
+
394
+ Without a target, `notify` posts nothing and needs no token. With one, it checks the token on every run, also when
395
+ there is nothing to post, so a broken token shows up on a green run rather than on the day tests fail. A release
396
+ with no new commits is the exception: there is nothing to announce, so it needs no token either.
397
+
398
+ For a release, `notify` posts the changelog additions, then the commits, oldest first. Each commit is its title (cut
399
+ at 200 characters) linked to its page on GitHub, and its author. A merge commit or a squash merge is one commit; a
400
+ rebase merge lists every commit it brought. A repository hosted elsewhere gets the titles without links. The release
401
+ is named after the `origin` remote (or `--repo-url`) and the author of the newest commit. Nothing is posted when there
402
+ are no new commits:
403
+
404
+ ```text
405
+ *my-repo* – New release (by Jane Doe):
406
+
407
+ - Fixed pagination on the search page
408
+
409
+ *Commits*:
410
+ 1. <https://github.com/my-org/my-repo/commit/1a2b3c…|fix: pagination on the search page> – Jane Doe
411
+ ```
412
+
413
+ ## Deleting old builds (`delete-old-builds`)
414
+
415
+ Deletes builds older than 30 days of every selected Actor. It never deletes the default build, or a build tagged
416
+ `latest`, `version…`, `v-…` or `v0`–`v9…`. A build tagged `devel` is kept for only 7 days.
417
+
418
+ | Flag | Default | Meaning |
419
+ | ----------- | ------- | ----------------------------------------------------------- |
420
+ | `--actors` | all | Only these Actors |
421
+ | `--ignore` | none | Never these Actors |
422
+ | `--dry-run` | `false` | List the builds that would be deleted without deleting them |
423
+
424
+ ## Listing Actors (`get-actor-configs`)
425
+
426
+ Prints the [config](../README.md#config-file) as the tool resolves it: one entry per Actor, with the group's values
427
+ applied and `actor.json` read. Takes `--actors` and `--ignore`. Useful to check a config change.
428
+
429
+ ## Running locally
430
+
431
+ Running the tests locally is useful when you only want to update the testing code in `test/`, because you can
432
+ iterate on it without pushing new code to the remote. If you only change tests, skip the builds entirely and run
433
+ them against the default builds, see [Running tests locally](lib.md#running-tests-locally).
434
+
435
+ To test changed Actor source, you need builds of it. The quickest way is
436
+ [`build-from-local`](#build-from-local-source-build-from-local), which needs no push:
437
+
438
+ ```bash
439
+ APIFY_TOKEN_JOHN_DOE=apify_api_xxx \
440
+ npx apify-test-tools build-from-local --actors john.doe/my-actor > builds.json
441
+ ```
442
+
443
+ To build from git, like CI does:
444
+
445
+ 1. Switch to a dummy branch that you will push and can later delete
446
+ 2. `npm i apify-test-tools@latest -D`
447
+ 3. Push your code (changes you want to test)
448
+ 4. Build the Actors on Apify (with your new code)
449
+ 5. Run tests against those builds. You can change tests and run on the same builds.
450
+
451
+ ```bash
452
+ npx apify-test-tools get-affected-actors --base origin/master \
453
+ | jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' \
454
+ | APIFY_TOKEN_JOHN_DOE=apify_api_xxx npx apify-test-tools build --version-number 0.99 > builds.json
455
+ ```
456
+
457
+ Run it with your dummy branch checked out: it builds the Actors that branch changed functionally, from the branch as
458
+ it is on the remote, into the test version `0.99`. Add `--dry-run` to `build` to print the plan (the version and Git
459
+ URL each Actor would be built from) first, and update `--base` to the branch you compare against. Tests only run for
460
+ Actors in `ACTOR_BUILDS`, so the `jq` filter also builds the Actors whose platform tests changed.
461
+
462
+ If you want to test against existing builds instead, skip the build and write the build array by hand, only for the
463
+ Actors you need to test.
464
+
465
+ Then run the tests against the builds with [`test`](#running-tests-test), as often as you like:
466
+
467
+ ```bash
468
+ TESTER_APIFY_TOKEN=apify_api_yyy npx apify-test-tools test < builds.json
469
+ ```