apify-test-tools 0.10.2-beta.18 → 0.10.2-beta.19

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 (45) hide show
  1. package/.github/cli/apify-test-tools.mjs +89 -89
  2. package/.github/scripts/run-with-apify-tokens.mjs +62 -1
  3. package/CHANGELOG.md +1 -0
  4. package/bin/utils/config/load-config.ts +17 -2
  5. package/bin/utils/config/parser.ts +17 -8
  6. package/bin/utils/config/structures/grouped.ts +2 -2
  7. package/bin/utils/config/structures/legacy.ts +1 -1
  8. package/dist/bin/utils/config/load-config.d.ts +5 -0
  9. package/dist/bin/utils/config/load-config.d.ts.map +1 -1
  10. package/dist/bin/utils/config/load-config.js +12 -1
  11. package/dist/bin/utils/config/load-config.js.map +1 -1
  12. package/dist/bin/utils/config/parser.d.ts +6 -2
  13. package/dist/bin/utils/config/parser.d.ts.map +1 -1
  14. package/dist/bin/utils/config/parser.js +17 -8
  15. package/dist/bin/utils/config/parser.js.map +1 -1
  16. package/dist/bin/utils/config/structures/grouped.js +2 -2
  17. package/dist/bin/utils/config/structures/grouped.js.map +1 -1
  18. package/dist/bin/utils/config/structures/legacy.js +1 -1
  19. package/dist/bin/utils/config/structures/legacy.js.map +1 -1
  20. package/dist/test/bundle/token-wrapper.test.d.ts +2 -0
  21. package/dist/test/bundle/token-wrapper.test.d.ts.map +1 -0
  22. package/dist/test/bundle/token-wrapper.test.js +80 -0
  23. package/dist/test/bundle/token-wrapper.test.js.map +1 -0
  24. package/dist/test/unit/bin/utils/config/load-config.test.js +19 -1
  25. package/dist/test/unit/bin/utils/config/load-config.test.js.map +1 -1
  26. package/dist/test/unit/bin/utils/config/parser.test.js +21 -4
  27. package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
  28. package/dist/test/unit/bin/utils/config/structures/grouped.test.js +6 -0
  29. package/dist/test/unit/bin/utils/config/structures/grouped.test.js.map +1 -1
  30. package/dist/test/unit/bin/utils/config/structures/legacy.test.js +1 -0
  31. package/dist/test/unit/bin/utils/config/structures/legacy.test.js.map +1 -1
  32. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +3 -3
  33. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/dist/workflow-scripts-src/run-with-apify-tokens.js +5 -29
  36. package/dist/workflow-scripts-src/run-with-apify-tokens.js.map +1 -1
  37. package/docs/workflows.md +3 -0
  38. package/package.json +1 -1
  39. package/test/bundle/token-wrapper.test.ts +110 -0
  40. package/test/unit/bin/utils/config/load-config.test.ts +26 -1
  41. package/test/unit/bin/utils/config/parser.test.ts +23 -5
  42. package/test/unit/bin/utils/config/structures/grouped.test.ts +12 -0
  43. package/test/unit/bin/utils/config/structures/legacy.test.ts +1 -0
  44. package/test/unit/github-scripts/run-with-apify-tokens.test.ts +3 -3
  45. package/workflow-scripts-src/run-with-apify-tokens.ts +5 -34
@@ -16,11 +16,7 @@
16
16
  // The command runs without a shell, so branch names and other interpolated arguments are passed
17
17
  // through as literal argv entries.
18
18
  import { spawnSync } from 'node:child_process';
19
- import fs from 'node:fs';
20
- import path from 'node:path';
21
- // Resolved against the working directory, matching how apify-test-tools' own readConfigFile
22
- // locates it.
23
- const CONFIG_FILE_NAME = 'apify-test-tools.config.json';
19
+ import { CONFIG_FILE_NAME, readBaseConfigFile } from '../bin/utils/config/load-config.js';
24
20
  function fail(message) {
25
21
  console.error(message);
26
22
  process.exit(1);
@@ -66,34 +62,14 @@ const unpackConfigSecrets = () => {
66
62
  return value;
67
63
  };
68
64
  const secrets = { ...workflowSecretsMap, ...unpackConfigSecrets() };
69
- const configPath = path.resolve(process.cwd(), CONFIG_FILE_NAME);
70
- let config;
65
+ let actorConfigs;
71
66
  try {
72
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
67
+ actorConfigs = await readBaseConfigFile();
73
68
  }
74
69
  catch (error) {
75
- fail(`Cannot read "${configPath}": ${error instanceof Error ? error.message : String(error)}`);
76
- }
77
- if (!isRecord(config) || !Array.isArray(config.actors)) {
78
- fail(`"${configPath}" must have an "actors" array at the top level.`);
79
- }
80
- const tokenNames = [];
81
- const entriesWithoutToken = [];
82
- const actors = config.actors;
83
- for (const [index, actor] of actors.entries()) {
84
- const tokenEnvVar = isRecord(actor) ? actor.tokenEnvVar : undefined;
85
- if (typeof tokenEnvVar !== 'string' || tokenEnvVar === '') {
86
- entriesWithoutToken.push((isRecord(actor) ? actor.actorFullName : undefined) ?? `entry at index ${index}`);
87
- continue;
88
- }
89
- if (!tokenNames.includes(tokenEnvVar))
90
- tokenNames.push(tokenEnvVar);
91
- }
92
- tokenNames.sort();
93
- if (entriesWithoutToken.length > 0) {
94
- console.error(`Warning: no "tokenEnvVar" in ${CONFIG_FILE_NAME} for: ${entriesWithoutToken.join(', ')}. ` +
95
- `Building those Actors will fail.`);
70
+ fail(error instanceof Error ? error.message : String(error));
96
71
  }
72
+ const tokenNames = [...new Set(actorConfigs.map(({ tokenEnvVar }) => tokenEnvVar))].sort();
97
73
  // Declared but absent is a warning, not an error. A repo can carry an Actor whose token isn't
98
74
  // configured and still build fine as long as that Actor never changes, and apify-test-tools raises
99
75
  // a precise error naming the Actor at the point it actually needs the token.
@@ -1 +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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"}
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/workflows.md CHANGED
@@ -396,6 +396,9 @@ token is not passed on. A token the config declares but the repo hasn't set is a
396
396
  carry an Actor whose token isn't configured and still build fine as long as that Actor never changes. The build fails
397
397
  with an error naming the Actor once it actually needs the token.
398
398
 
399
+ The wrapper validates the config with the CLI's parser. Each Actor must declare a nonempty `tokenEnvVar` or inherit
400
+ one from its group. An empty name or a missing required `tokenEnvVar` fails during config validation.
401
+
399
402
  The `unitTest` job runs static checks and needs `NPM_TOKEN` only. No job runs `npm ci` or `pnpm install` with Apify
400
403
  or Slack credentials in scope, so a postinstall script in the dependency tree cannot read them.
401
404
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.10.2-beta.18",
3
+ "version": "0.10.2-beta.19",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -0,0 +1,110 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
9
+
10
+ const wrapperPath = fileURLToPath(new URL('../../.github/scripts/run-with-apify-tokens.mjs', import.meta.url));
11
+ const configFileName = 'apify-test-tools.config.json';
12
+
13
+ let repoDir: string;
14
+
15
+ describe('token wrapper bundle', () => {
16
+ beforeAll(async () => {
17
+ repoDir = await mkdtemp(path.join(os.tmpdir(), 'apify-token-wrapper-'));
18
+ });
19
+
20
+ afterAll(async () => {
21
+ if (repoDir) await rm(repoDir, { recursive: true, force: true });
22
+ });
23
+
24
+ const actorSecrets = {
25
+ GROUP_TOKEN: 'group-value',
26
+ OVERRIDE_TOKEN: 'override-value',
27
+ OTHER: 'hidden',
28
+ };
29
+
30
+ const runWrapper = (workflowSecrets: Record<string, string> = actorSecrets) =>
31
+ spawnSync(process.execPath, [wrapperPath, process.execPath, '-e', 'console.log(JSON.stringify(process.env))'], {
32
+ cwd: repoDir,
33
+ env: {
34
+ PATH: process.env.PATH ?? '',
35
+ WORKFLOW_SECRETS: JSON.stringify(workflowSecrets),
36
+ },
37
+ encoding: 'utf8',
38
+ });
39
+
40
+ it.each(['inherited secrets', 'CONFIG_SECRETS'])(
41
+ 'passes grouped tokens and actor overrides from %s without unrelated secrets',
42
+ async (source) => {
43
+ await writeFile(
44
+ path.join(repoDir, configFileName),
45
+ JSON.stringify({
46
+ mode: 'grouped',
47
+ groups: {
48
+ example: {
49
+ tokenEnvVar: 'GROUP_TOKEN',
50
+ actors: [
51
+ { folder: 'actors/one', actorFullName: 'example/one' },
52
+ { folder: 'actors/two', actorFullName: 'example/two', tokenEnvVar: 'OVERRIDE_TOKEN' },
53
+ ],
54
+ },
55
+ },
56
+ }),
57
+ );
58
+
59
+ const { status, stdout, stderr } = runWrapper(
60
+ source === 'CONFIG_SECRETS' ? { CONFIG_SECRETS: JSON.stringify(actorSecrets) } : actorSecrets,
61
+ );
62
+ expect(status, stderr).toBe(0);
63
+ const childEnv: Record<string, string> = JSON.parse(stdout);
64
+ expect(childEnv).toMatchObject({ GROUP_TOKEN: 'group-value', OVERRIDE_TOKEN: 'override-value' });
65
+ expect(childEnv).not.toHaveProperty('OTHER');
66
+ expect(childEnv).not.toHaveProperty('WORKFLOW_SECRETS');
67
+ expect(childEnv).not.toHaveProperty('CONFIG_SECRETS');
68
+ expect(stderr).toContain('Passing 2 Actor token(s)');
69
+ },
70
+ );
71
+
72
+ it('does not start the child when config validation fails', async () => {
73
+ await writeFile(path.join(repoDir, configFileName), JSON.stringify({ mode: 'grouped', groups: {} }));
74
+
75
+ const { status, stdout, stderr } = runWrapper();
76
+ expect(status).toBe(1);
77
+ expect(stdout).toBe('');
78
+ expect(stderr).toContain('At least one configuration group is required');
79
+ expect(stderr).not.toContain('Cannot read');
80
+ });
81
+
82
+ it.each(['', undefined])(
83
+ 'rejects an empty or missing token name (%s) before starting the child',
84
+ async (tokenEnvVar) => {
85
+ await writeFile(
86
+ path.join(repoDir, configFileName),
87
+ JSON.stringify({ actors: [{ folder: 'actors/one', actorFullName: 'example/one', tokenEnvVar }] }),
88
+ );
89
+
90
+ const { status, stdout, stderr } = runWrapper();
91
+ expect(status).toBe(1);
92
+ expect(stdout).toBe('');
93
+ expect(stderr).toContain('actors[0].tokenEnvVar');
94
+ expect(stderr).not.toContain('Warning:');
95
+ expect(stderr).not.toContain('Cannot read');
96
+ },
97
+ );
98
+
99
+ it('reports a missing config file without a duplicate error prefix', async () => {
100
+ await rm(path.join(repoDir, configFileName), { force: true });
101
+
102
+ const { status, stdout, stderr } = runWrapper();
103
+ expect(status).toBe(1);
104
+ expect(stdout).toBe('');
105
+ expect(stderr).toBe(
106
+ `Config file "${configFileName}" not found in the current directory. ` +
107
+ 'Please create one with the required actor entries.\n',
108
+ );
109
+ });
110
+ });
@@ -4,7 +4,12 @@ import path from 'node:path';
4
4
 
5
5
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
6
 
7
- import { CONFIG_FILE_NAME, loadActorConfig, readConfigFile } from '../../../../../bin/utils/config/load-config.js';
7
+ import {
8
+ CONFIG_FILE_NAME,
9
+ loadActorConfig,
10
+ readBaseConfigFile,
11
+ readConfigFile,
12
+ } from '../../../../../bin/utils/config/load-config.js';
8
13
  import type { ResolvedActorConfig } from '../../../../../bin/utils/config/structures/base.js';
9
14
  import { ExistingDir, ExistingFile, RelativeDir, RelativeFile } from '../../../../../bin/utils/path/repo-relative.js';
10
15
 
@@ -265,3 +270,23 @@ describe('readConfigFile', () => {
265
270
  await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/at actors/);
266
271
  });
267
272
  });
273
+
274
+ describe('readBaseConfigFile', () => {
275
+ it('returns non-file settings without checking Actor paths or reading actor.json', async () => {
276
+ await writeFiles({
277
+ [CONFIG_FILE_NAME]: validConfig([
278
+ {
279
+ folder: 'missing-actor-dir',
280
+ actorFullName: 'myteam/shopify',
281
+ tokenEnvVar: 'APIFY_TOKEN',
282
+ overrideActorContext: ['../outside'],
283
+ },
284
+ ]),
285
+ });
286
+
287
+ await expect(readBaseConfigFile()).resolves.toEqual([
288
+ { actorFullName: 'myteam/shopify', tokenEnvVar: 'APIFY_TOKEN' },
289
+ ]);
290
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow();
291
+ });
292
+ });
@@ -4,13 +4,18 @@ import path from 'node:path';
4
4
 
5
5
  import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6
6
 
7
- import { _privates, parseConfigFile } from '../../../../../bin/utils/config/parser.js';
8
- import { CONFIG_FILE_STRATEGY } from '../../../../../bin/utils/config/structures/base.js';
7
+ import { _privates, parseConfigFile, parseResolvedConfigFile } from '../../../../../bin/utils/config/parser.js';
8
+ import { CONFIG_FILE_STRATEGY, type ResolvedActorConfig } from '../../../../../bin/utils/config/structures/base.js';
9
9
  import { GROUPED_PARSER } from '../../../../../bin/utils/config/structures/grouped.js';
10
10
  import { LEGACY_PARSER } from '../../../../../bin/utils/config/structures/legacy.js';
11
11
  import { ExistingDir, RelativeDir } from '../../../../../bin/utils/path/repo-relative.js';
12
12
 
13
- const { selectStrategy, verifyConfiguration } = _privates;
13
+ const { selectStrategy, initializePaths, verifyConfiguration: verifyInitializedConfiguration } = _privates;
14
+ const verifyConfiguration = (body: ResolvedActorConfig[]) => {
15
+ const initialized = initializePaths(body);
16
+ verifyInitializedConfiguration(initialized);
17
+ return initialized;
18
+ };
14
19
 
15
20
  let repoDir: string;
16
21
  const originalCwd = process.cwd();
@@ -95,13 +100,13 @@ describe('verifyConfiguration', () => {
95
100
  ).toThrow(/Duplicate folder/);
96
101
  });
97
102
 
98
- it('quotes a duplicate folder the way it was written, not the way it normalized', () => {
103
+ it('reports the normalized path for a duplicate folder', () => {
99
104
  expect(() =>
100
105
  verifyConfiguration([
101
106
  actor({ folder: '.', actorFullName: 'myteam/actor-a' }),
102
107
  actor({ folder: './', actorFullName: 'myteam/actor-b' }),
103
108
  ]),
104
- ).toThrow('Duplicate folder "./"');
109
+ ).toThrow('Duplicate folder "."');
105
110
  });
106
111
 
107
112
  it('rejects two entries pointing at the same actor', () => {
@@ -150,6 +155,19 @@ describe('verifyConfiguration', () => {
150
155
  });
151
156
 
152
157
  describe('parseConfigFile', () => {
158
+ it('shares strategy resolution with parsing that does not initialize paths', () => {
159
+ const body = { actors: [actor({ folder: 'missing-actor-dir', overrideActorContext: ['../outside'] })] };
160
+ expect(parseResolvedConfigFile(body)).toEqual([
161
+ {
162
+ actorFullName: 'myteam/shopify',
163
+ folder: 'missing-actor-dir',
164
+ tokenEnvVar: 'APIFY_TOKEN',
165
+ overrideActorContext: ['../outside'],
166
+ },
167
+ ]);
168
+ expect(() => parseConfigFile(body)).toThrow();
169
+ });
170
+
153
171
  it('validates and normalizes paths after resolving the config', () => {
154
172
  const [parsed] = parseConfigFile({ actors: [actor({ folder: './bin/', overrideActorContext: ['test/'] })] });
155
173
  expect(parsed.folder).toBeInstanceOf(ExistingDir);
@@ -68,6 +68,18 @@ describe('GROUPED_PARSER', () => {
68
68
  expect(() => GROUPED_PARSER.parse({})).toThrow();
69
69
  });
70
70
 
71
+ it('rejects an empty group token name', () => {
72
+ expect(() => GROUPED_PARSER.parse({ groups: { myteam: group({ tokenEnvVar: '' }) } })).toThrow(
73
+ /at groups\.myteam\.tokenEnvVar/,
74
+ );
75
+ });
76
+
77
+ it('rejects an empty actor token override instead of falling back to the group token', () => {
78
+ expect(() =>
79
+ GROUPED_PARSER.parse({ groups: { myteam: group({ actors: [actor({ tokenEnvVar: '' })] }) } }),
80
+ ).toThrow(/at groups\.myteam\.actors\[0\]\.tokenEnvVar/);
81
+ });
82
+
71
83
  it('reports problems from every group at once instead of only the first', () => {
72
84
  const message = (() => {
73
85
  try {
@@ -32,6 +32,7 @@ describe('LEGACY_PARSER', () => {
32
32
  ['folder is missing', { folder: undefined }, /at actors\[0\]\.folder/],
33
33
  ['folder is not a string', { folder: 123 }, /at actors\[0\]\.folder/],
34
34
  ['tokenEnvVar is missing', { tokenEnvVar: undefined }, /at actors\[0\]\.tokenEnvVar/],
35
+ ['tokenEnvVar is empty', { tokenEnvVar: '' }, /at actors\[0\]\.tokenEnvVar/],
35
36
  ['overrideActorContext is not an array', { overrideActorContext: 'packages' }, /expected array/],
36
37
  ['overrideActorContext holds non-strings', { overrideActorContext: [123] }, /overrideActorContext\[0\]/],
37
38
  ])('rejects an entry where %s', (_name, fields, expected) => {
@@ -12,9 +12,9 @@ const SCRIPT = fileURLToPath(new URL('../../../.github/scripts/run-with-apify-to
12
12
  // Three Actors, each with its own token, so a test can leave any of them out.
13
13
  const CONFIG = {
14
14
  actors: [
15
- { actorFullName: 'team/one', tokenEnvVar: 'TOKEN_ONE' },
16
- { actorFullName: 'team/two', tokenEnvVar: 'TOKEN_TWO' },
17
- { actorFullName: 'team/three', tokenEnvVar: 'TOKEN_THREE' },
15
+ { folder: 'actors/one', actorFullName: 'team/one', tokenEnvVar: 'TOKEN_ONE' },
16
+ { folder: 'actors/two', actorFullName: 'team/two', tokenEnvVar: 'TOKEN_TWO' },
17
+ { folder: 'actors/three', actorFullName: 'team/three', tokenEnvVar: 'TOKEN_THREE' },
18
18
  ],
19
19
  };
20
20
 
@@ -17,12 +17,8 @@
17
17
  // through as literal argv entries.
18
18
 
19
19
  import { spawnSync } from 'node:child_process';
20
- import fs from 'node:fs';
21
- import path from 'node:path';
22
20
 
23
- // Resolved against the working directory, matching how apify-test-tools' own readConfigFile
24
- // locates it.
25
- const CONFIG_FILE_NAME = 'apify-test-tools.config.json';
21
+ import { CONFIG_FILE_NAME, readBaseConfigFile } from '../bin/utils/config/load-config.js';
26
22
 
27
23
  function fail(message: string): never {
28
24
  console.error(message);
@@ -71,39 +67,14 @@ const unpackConfigSecrets = (): Record<string, unknown> => {
71
67
 
72
68
  const secrets = { ...workflowSecretsMap, ...unpackConfigSecrets() };
73
69
 
74
- const configPath = path.resolve(process.cwd(), CONFIG_FILE_NAME);
75
- let config: unknown;
70
+ let actorConfigs: Awaited<ReturnType<typeof readBaseConfigFile>>;
76
71
  try {
77
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
72
+ actorConfigs = await readBaseConfigFile();
78
73
  } catch (error) {
79
- fail(`Cannot read "${configPath}": ${error instanceof Error ? error.message : String(error)}`);
74
+ fail(error instanceof Error ? error.message : String(error));
80
75
  }
81
76
 
82
- if (!isRecord(config) || !Array.isArray(config.actors)) {
83
- fail(`"${configPath}" must have an "actors" array at the top level.`);
84
- }
85
-
86
- const tokenNames: string[] = [];
87
- const entriesWithoutToken: unknown[] = [];
88
-
89
- const actors: unknown[] = config.actors;
90
- for (const [index, actor] of actors.entries()) {
91
- const tokenEnvVar = isRecord(actor) ? actor.tokenEnvVar : undefined;
92
- if (typeof tokenEnvVar !== 'string' || tokenEnvVar === '') {
93
- entriesWithoutToken.push((isRecord(actor) ? actor.actorFullName : undefined) ?? `entry at index ${index}`);
94
- continue;
95
- }
96
- if (!tokenNames.includes(tokenEnvVar)) tokenNames.push(tokenEnvVar);
97
- }
98
-
99
- tokenNames.sort();
100
-
101
- if (entriesWithoutToken.length > 0) {
102
- console.error(
103
- `Warning: no "tokenEnvVar" in ${CONFIG_FILE_NAME} for: ${entriesWithoutToken.join(', ')}. ` +
104
- `Building those Actors will fail.`,
105
- );
106
- }
77
+ const tokenNames = [...new Set(actorConfigs.map(({ tokenEnvVar }) => tokenEnvVar))].sort();
107
78
 
108
79
  // Declared but absent is a warning, not an error. A repo can carry an Actor whose token isn't
109
80
  // configured and still build fine as long as that Actor never changes, and apify-test-tools raises