@dataparade/cli 0.0.6 → 0.0.7

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 (55) hide show
  1. package/README.md +3 -2
  2. package/dist/package.json +1 -1
  3. package/dist/src/cli.js +65 -36
  4. package/dist/src/config/env.js +7 -2
  5. package/dist/src/config/load-cli-env.d.ts +2 -0
  6. package/dist/src/config/load-cli-env.js +11 -2
  7. package/dist/src/config/redact.js +3 -0
  8. package/dist/src/config/validate-scan-ai.js +13 -7
  9. package/dist/src/core/pipeline/ai-orchestrator-options.js +8 -4
  10. package/dist/src/core/pipeline/orchestrator.js +1 -1
  11. package/dist/src/core/schema/scan-config.schema.d.ts +1 -0
  12. package/dist/src/core/schema/scan-config.schema.js +1 -0
  13. package/dist/src/core/types/config.d.ts +5 -0
  14. package/dist/src/platform-api/anonymous-ai-session-client.d.ts +14 -0
  15. package/dist/src/platform-api/anonymous-ai-session-client.js +40 -0
  16. package/dist/src/platform-api/upload-client.d.ts +10 -3
  17. package/dist/src/platform-api/upload-client.js +35 -16
  18. package/dist/src/platform-api/upload.types.d.ts +10 -5
  19. package/dist/src/upload/build-preview-url.d.ts +1 -0
  20. package/dist/src/upload/build-preview-url.js +6 -0
  21. package/dist/src/upload/run-upload.js +20 -4
  22. package/dist/src/upload/types.d.ts +2 -2
  23. package/dist/tests/cli/cli.spec.js +17 -3
  24. package/dist/tests/unit/cli/scan-command-ai-flags.spec.js +19 -0
  25. package/dist/tests/unit/cli/scan-command-e2e-monorepo-app-plus-terraform.spec.js +12 -8
  26. package/dist/tests/unit/cli/scan-command-e2e-monorepo-front-back-sections.spec.js +10 -1
  27. package/dist/tests/unit/cli/scan-command-e2e-python-basic.spec.js +10 -1
  28. package/dist/tests/unit/cli/scan-command-e2e-terraform-basic.spec.js +12 -8
  29. package/dist/tests/unit/cli/scan-command-e2e-ts-sample.spec.js +10 -1
  30. package/dist/tests/unit/cli/scan-command-e2e-ts-sentry-route-env.spec.js +1 -1
  31. package/dist/tests/unit/cli/scan-command-exit-code.spec.js +21 -0
  32. package/dist/tests/unit/cli/scan-quota-flow.spec.js +1 -1
  33. package/dist/tests/unit/config/load-cli-env.spec.d.ts +1 -0
  34. package/dist/tests/unit/config/load-cli-env.spec.js +52 -0
  35. package/dist/tests/unit/config/redact.spec.js +1 -1
  36. package/dist/tests/unit/config/resolve-config.spec.js +1 -1
  37. package/dist/tests/unit/config/validate-scan-ai.spec.js +25 -0
  38. package/dist/tests/unit/core/ai-orchestrator-options-anon.spec.d.ts +1 -0
  39. package/dist/tests/unit/core/ai-orchestrator-options-anon.spec.js +29 -0
  40. package/dist/tests/unit/core/dependency-manifest-performance-budgets.spec.js +1 -1
  41. package/dist/tests/unit/core/graph-mapping.spec.js +1 -1
  42. package/dist/tests/unit/core/invariant-tests-dedupe-determinism.spec.js +1 -1
  43. package/dist/tests/unit/core/no-source-code-leakage.spec.js +1 -1
  44. package/dist/tests/unit/core/orchestrator.spec.js +12 -12
  45. package/dist/tests/unit/core/output-json.spec.js +2 -2
  46. package/dist/tests/unit/core/parsing-error-handling-nonfatal.spec.js +1 -1
  47. package/dist/tests/unit/core/scan-result-language-stats.spec.js +1 -1
  48. package/dist/tests/unit/core/structural-scan.spec.js +1 -1
  49. package/dist/tests/unit/core/terraform-json-overlay.spec.js +1 -1
  50. package/dist/tests/unit/platform-api/anonymous-ai-session-client.spec.d.ts +1 -0
  51. package/dist/tests/unit/platform-api/anonymous-ai-session-client.spec.js +35 -0
  52. package/dist/tests/unit/upload/build-preview-url.spec.js +16 -0
  53. package/dist/tests/unit/upload/run-upload.spec.d.ts +1 -0
  54. package/dist/tests/unit/upload/run-upload.spec.js +43 -0
  55. package/package.json +1 -1
@@ -34,14 +34,33 @@ jest.mock("../../../src/core/pipeline/graph-mapping", () => ({
34
34
  }));
35
35
  jest.mock("../../../src/output/json", () => ({
36
36
  writeDataflowJson: jest.fn(() => { }),
37
+ buildDataflowWrapper: jest.fn(() => ({
38
+ schemaVersion: "1.0",
39
+ graph: { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } },
40
+ metadata: {
41
+ componentsCount: 0,
42
+ dataFlowsCount: 0,
43
+ filesScanned: 0,
44
+ scanDurationMs: 0,
45
+ },
46
+ })),
37
47
  }));
38
48
  describe("cli scan command ai flags", () => {
39
49
  let consoleLogSpy;
50
+ let prevSkipAutoUpload;
40
51
  beforeEach(() => {
41
52
  consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => { });
53
+ prevSkipAutoUpload = process.env.DATAPARADE_SKIP_AUTO_UPLOAD;
54
+ process.env.DATAPARADE_SKIP_AUTO_UPLOAD = "true";
42
55
  });
43
56
  afterEach(() => {
44
57
  consoleLogSpy.mockRestore();
58
+ if (prevSkipAutoUpload === undefined) {
59
+ delete process.env.DATAPARADE_SKIP_AUTO_UPLOAD;
60
+ }
61
+ else {
62
+ process.env.DATAPARADE_SKIP_AUTO_UPLOAD = prevSkipAutoUpload;
63
+ }
45
64
  });
46
65
  it("normalizes --ai-inference-scope third-party-only to third_party_only", async () => {
47
66
  const { run } = require("../../../src/cli");
@@ -21,12 +21,19 @@ function nodeSourcePaths(node) {
21
21
  }
22
22
  describe("cli scan command - monorepo app plus terraform", () => {
23
23
  it("keeps app API hub, reduces TF to provider and module shells, and lays out app left of TF hub", async () => {
24
- const prevAiInference = process.env.DATAPARADE_AI_INFERENCE;
25
- process.env.DATAPARADE_AI_INFERENCE = "false";
26
24
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "monorepo-app-plus-terraform");
27
25
  const outputPath = path_1.default.join(os_1.default.tmpdir(), `dataparade-scan-e2e-monorepo-app-plus-terraform-${Date.now()}.json`);
26
+ await (0, cli_1.run)([
27
+ "node",
28
+ "cli",
29
+ "scan",
30
+ fixturesRoot,
31
+ "--output",
32
+ outputPath,
33
+ "--no-ai-inference",
34
+ "--skip-auto-upload",
35
+ ]);
28
36
  try {
29
- await (0, cli_1.run)(["node", "cli", "scan", fixturesRoot, "--output", outputPath]);
30
37
  const contents = fs_1.default.readFileSync(outputPath, "utf8");
31
38
  const parsed = JSON.parse(contents);
32
39
  const validation = (0, dataflow_wrapper_schema_1.validateDataflowJson)(parsed);
@@ -96,11 +103,8 @@ describe("cli scan command - monorepo app plus terraform", () => {
96
103
  fs_1.default.unlinkSync(outputPath);
97
104
  }
98
105
  finally {
99
- if (prevAiInference === undefined) {
100
- delete process.env.DATAPARADE_AI_INFERENCE;
101
- }
102
- else {
103
- process.env.DATAPARADE_AI_INFERENCE = prevAiInference;
106
+ if (fs_1.default.existsSync(outputPath)) {
107
+ fs_1.default.unlinkSync(outputPath);
104
108
  }
105
109
  }
106
110
  }, 20000);
@@ -12,7 +12,16 @@ describe("cli scan command - monorepo sections", () => {
12
12
  it("tags nodes with section_id and avoids cross-section edges", async () => {
13
13
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "monorepo-front-back-sections");
14
14
  const outputPath = path_1.default.join(os_1.default.tmpdir(), `dataparade-scan-e2e-monorepo-front-back-sections-${Date.now()}.json`);
15
- await (0, cli_1.run)(["node", "cli", "scan", fixturesRoot, "--output", outputPath]);
15
+ await (0, cli_1.run)([
16
+ "node",
17
+ "cli",
18
+ "scan",
19
+ fixturesRoot,
20
+ "--output",
21
+ outputPath,
22
+ "--no-ai-inference",
23
+ "--skip-auto-upload",
24
+ ]);
16
25
  const contents = fs_1.default.readFileSync(outputPath, "utf8");
17
26
  const parsed = JSON.parse(contents);
18
27
  const validation = (0, dataflow_wrapper_schema_1.validateDataflowJson)(parsed);
@@ -12,7 +12,16 @@ describe("cli scan command - DP-P0-CLI-705 e2e Python basic", () => {
12
12
  it("scans python-basic fixture and produces valid dataflow.json with route, database, third-party, and flows", async () => {
13
13
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "python-basic");
14
14
  const outputPath = path_1.default.join(os_1.default.tmpdir(), `dataparade-scan-e2e-python-basic-${Date.now()}.json`);
15
- await (0, cli_1.run)(["node", "cli", "scan", fixturesRoot, "--output", outputPath]);
15
+ await (0, cli_1.run)([
16
+ "node",
17
+ "cli",
18
+ "scan",
19
+ fixturesRoot,
20
+ "--output",
21
+ outputPath,
22
+ "--no-ai-inference",
23
+ "--skip-auto-upload",
24
+ ]);
16
25
  const contents = fs_1.default.readFileSync(outputPath, "utf8");
17
26
  const parsed = JSON.parse(contents);
18
27
  const validation = (0, dataflow_wrapper_schema_1.validateDataflowJson)(parsed);
@@ -10,12 +10,19 @@ const cli_1 = require("../../../src/cli");
10
10
  const dataflow_wrapper_schema_1 = require("../../../src/core/schema/dataflow-wrapper.schema");
11
11
  describe("cli scan command - terraform-basic fixture", () => {
12
12
  it("scans terraform-basic fixture and produces valid dataflow.json with nodes and edges", async () => {
13
- const prevAiInference = process.env.DATAPARADE_AI_INFERENCE;
14
- process.env.DATAPARADE_AI_INFERENCE = "false";
15
13
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "terraform-basic");
16
14
  const outputPath = path_1.default.join(os_1.default.tmpdir(), `dataparade-scan-e2e-terraform-basic-${Date.now()}.json`);
15
+ await (0, cli_1.run)([
16
+ "node",
17
+ "cli",
18
+ "scan",
19
+ fixturesRoot,
20
+ "--output",
21
+ outputPath,
22
+ "--no-ai-inference",
23
+ "--skip-auto-upload",
24
+ ]);
17
25
  try {
18
- await (0, cli_1.run)(["node", "cli", "scan", fixturesRoot, "--output", outputPath]);
19
26
  const contents = fs_1.default.readFileSync(outputPath, "utf8");
20
27
  const parsed = JSON.parse(contents);
21
28
  const validation = (0, dataflow_wrapper_schema_1.validateDataflowJson)(parsed);
@@ -51,11 +58,8 @@ describe("cli scan command - terraform-basic fixture", () => {
51
58
  fs_1.default.unlinkSync(outputPath);
52
59
  }
53
60
  finally {
54
- if (prevAiInference === undefined) {
55
- delete process.env.DATAPARADE_AI_INFERENCE;
56
- }
57
- else {
58
- process.env.DATAPARADE_AI_INFERENCE = prevAiInference;
61
+ if (fs_1.default.existsSync(outputPath)) {
62
+ fs_1.default.unlinkSync(outputPath);
59
63
  }
60
64
  }
61
65
  }, 15000);
@@ -12,7 +12,16 @@ describe("cli scan command - DP-P0-CLI-404 e2e TypeScript sample", () => {
12
12
  it("scans the e2e-ts-sample fixture and produces a valid dataflow.json with database and third-party components and flows", async () => {
13
13
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "e2e-ts-sample");
14
14
  const outputPath = path_1.default.join(os_1.default.tmpdir(), `dataparade-scan-e2e-ts-sample-${Date.now()}.json`);
15
- await (0, cli_1.run)(["node", "cli", "scan", fixturesRoot, "--output", outputPath]);
15
+ await (0, cli_1.run)([
16
+ "node",
17
+ "cli",
18
+ "scan",
19
+ fixturesRoot,
20
+ "--output",
21
+ outputPath,
22
+ "--no-ai-inference",
23
+ "--skip-auto-upload",
24
+ ]);
16
25
  const contents = fs_1.default.readFileSync(outputPath, "utf8");
17
26
  const parsed = JSON.parse(contents);
18
27
  const validation = (0, dataflow_wrapper_schema_1.validateDataflowJson)(parsed);
@@ -8,7 +8,7 @@ const orchestrator_1 = require("../../../src/core/pipeline/orchestrator");
8
8
  describe("cli scan - DP-P0-CLI regression: sentry/route/env", () => {
9
9
  it("detects Sentry third_party, Express route, and API_KEY env_variable", async () => {
10
10
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "e2e-ts-sentry-route-env");
11
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
11
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
12
12
  projectName: "SentryRouteEnvApp",
13
13
  });
14
14
  const { scanResult, findings } = await (0, orchestrator_1.scan)(fixturesRoot, config, undefined);
@@ -44,14 +44,35 @@ jest.mock("../../../src/core/pipeline/graph-mapping", () => ({
44
44
  }));
45
45
  jest.mock("../../../src/output/json", () => ({
46
46
  writeDataflowJson: jest.fn(async () => { }),
47
+ buildDataflowWrapper: jest.fn(() => ({
48
+ schemaVersion: "1.0",
49
+ graph: { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } },
50
+ metadata: {
51
+ componentsCount: 0,
52
+ dataFlowsCount: 0,
53
+ filesScanned: 0,
54
+ scanDurationMs: 0,
55
+ },
56
+ })),
47
57
  }));
48
58
  describe("cli scan command - exit codes", () => {
49
59
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
50
60
  function tmpOutputPath() {
51
61
  return path_1.default.join(os_1.default.tmpdir(), `dataparade-exit-${Date.now()}.json`);
52
62
  }
63
+ let prevSkipAutoUpload;
64
+ beforeEach(() => {
65
+ prevSkipAutoUpload = process.env.DATAPARADE_SKIP_AUTO_UPLOAD;
66
+ process.env.DATAPARADE_SKIP_AUTO_UPLOAD = "true";
67
+ });
53
68
  afterEach(() => {
54
69
  process.exitCode = undefined;
70
+ if (prevSkipAutoUpload === undefined) {
71
+ delete process.env.DATAPARADE_SKIP_AUTO_UPLOAD;
72
+ }
73
+ else {
74
+ process.env.DATAPARADE_SKIP_AUTO_UPLOAD = prevSkipAutoUpload;
75
+ }
55
76
  });
56
77
  it("sets exitCode=1 when scanResult.errors is non-empty", async () => {
57
78
  const { run } = require("../../../src/cli");
@@ -33,7 +33,7 @@ describe("scan quota flow", () => {
33
33
  it("skips quota API when workspace key is present but AI is disabled", async () => {
34
34
  process.env.DATAPARADE_WORKSPACE_API_KEY = "dp_live_test";
35
35
  process.env.DATAPARADE_SKIP_AUTO_UPLOAD = "true";
36
- await (0, cli_1.run)(["node", "cli", "scan", tempRoot]);
36
+ await (0, cli_1.run)(["node", "cli", "scan", tempRoot, "--no-ai-inference"]);
37
37
  expect(fetchMock).not.toHaveBeenCalled();
38
38
  });
39
39
  it("prints workspace quota message when preflight is quota-blocked", async () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = __importDefault(require("node:fs"));
7
+ const node_os_1 = __importDefault(require("node:os"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const load_cli_env_1 = require("../../../src/config/load-cli-env");
10
+ const CLI_PACKAGE_NAME = "@dataparade/cli";
11
+ function writeCliPackageJson(dir) {
12
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
13
+ node_fs_1.default.writeFileSync(node_path_1.default.join(dir, "package.json"), JSON.stringify({ name: CLI_PACKAGE_NAME, version: "0.0.0" }));
14
+ }
15
+ describe("findCliPackageRoot", () => {
16
+ let tempRoot;
17
+ beforeEach(() => {
18
+ tempRoot = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "dataparade-cli-root-"));
19
+ });
20
+ afterEach(() => {
21
+ node_fs_1.default.rmSync(tempRoot, { recursive: true, force: true });
22
+ });
23
+ it("prefers the package root over dist/ when tsc copies package.json", () => {
24
+ const packageRoot = node_path_1.default.join(tempRoot, "cli");
25
+ writeCliPackageJson(packageRoot);
26
+ writeCliPackageJson(node_path_1.default.join(packageRoot, "dist"));
27
+ const startDir = node_path_1.default.join(packageRoot, "dist", "src", "config");
28
+ node_fs_1.default.mkdirSync(startDir, { recursive: true });
29
+ expect((0, load_cli_env_1.findCliPackageRoot)(startDir)).toBe(packageRoot);
30
+ });
31
+ it("returns the npm package root when dist/ contains a copied package.json", () => {
32
+ const packageRoot = node_path_1.default.join(tempRoot, "node_modules", "@dataparade", "cli");
33
+ writeCliPackageJson(packageRoot);
34
+ writeCliPackageJson(node_path_1.default.join(packageRoot, "dist"));
35
+ const startDir = node_path_1.default.join(packageRoot, "dist", "src", "config");
36
+ node_fs_1.default.mkdirSync(startDir, { recursive: true });
37
+ expect((0, load_cli_env_1.findCliPackageRoot)(startDir)).toBe(packageRoot);
38
+ });
39
+ it("returns dist/ when it is the only matching package root", () => {
40
+ const distRoot = node_path_1.default.join(tempRoot, "dist-only");
41
+ writeCliPackageJson(distRoot);
42
+ const startDir = node_path_1.default.join(distRoot, "src", "config");
43
+ node_fs_1.default.mkdirSync(startDir, { recursive: true });
44
+ expect((0, load_cli_env_1.findCliPackageRoot)(startDir)).toBe(distRoot);
45
+ });
46
+ it("returns undefined when no @dataparade/cli package.json exists", () => {
47
+ const startDir = node_path_1.default.join(tempRoot, "other", "src");
48
+ node_fs_1.default.mkdirSync(startDir, { recursive: true });
49
+ node_fs_1.default.writeFileSync(node_path_1.default.join(tempRoot, "other", "package.json"), JSON.stringify({ name: "other-package" }));
50
+ expect((0, load_cli_env_1.findCliPackageRoot)(startDir)).toBeUndefined();
51
+ });
52
+ });
@@ -12,7 +12,7 @@ describe("redactScanConfigurationForDisplay", () => {
12
12
  expect(JSON.stringify(redacted)).not.toContain("sk-secret-key-12345");
13
13
  });
14
14
  it("leaves config unchanged when aiApiKey is absent", () => {
15
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
15
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
16
16
  const redacted = (0, redact_1.redactScanConfigurationForDisplay)(config);
17
17
  expect(redacted.aiApiKey).toBeUndefined();
18
18
  });
@@ -98,7 +98,7 @@ describe("resolveScanConfiguration", () => {
98
98
  }
99
99
  }
100
100
  });
101
- it("does not auto-enable AI when BYOK envs are set without SCAN_AI_INFERENCE", () => {
101
+ it("does not set enableAiInference from BYOK envs alone (default comes from createDefaultScanConfiguration)", () => {
102
102
  const prevProvider = process.env.SCAN_BYOK_PROVIDER;
103
103
  const prevModel = process.env.SCAN_BYOK_MODEL;
104
104
  const prevKey = process.env.SCAN_BYOK_API_KEY;
@@ -39,6 +39,31 @@ describe("validate-scan-ai", () => {
39
39
  expect((0, validate_scan_ai_1.validateAiInferenceCredentials)(config)).toEqual([]);
40
40
  expect((0, validate_scan_ai_1.resolveAiMode)(config)).toBe("platform");
41
41
  });
42
+ it("accepts anonymous session token with job id for platform mode", () => {
43
+ const config = baseConfig({
44
+ enableAiInference: true,
45
+ anonSessionToken: "dp_anon_abc",
46
+ cliQuotaJobId: "job-1",
47
+ });
48
+ expect((0, validate_scan_ai_1.validateAiInferenceCredentials)(config)).toEqual([]);
49
+ expect((0, validate_scan_ai_1.resolveAiMode)(config)).toBe("platform");
50
+ });
51
+ it("rejects workspace key and anonymous session together", () => {
52
+ const errors = (0, validate_scan_ai_1.validateAiInferenceCredentials)(baseConfig({
53
+ enableAiInference: true,
54
+ workspaceApiKey: "dp_live_abc",
55
+ anonSessionToken: "dp_anon_abc",
56
+ cliQuotaJobId: "job-1",
57
+ }));
58
+ expect(errors.some((e) => e.includes("not both"))).toBe(true);
59
+ });
60
+ it("requires job id for anonymous platform mode", () => {
61
+ const errors = (0, validate_scan_ai_1.validateAiInferenceCredentials)(baseConfig({
62
+ enableAiInference: true,
63
+ anonSessionToken: "dp_anon_abc",
64
+ }));
65
+ expect(errors.some((e) => e.includes("job id"))).toBe(true);
66
+ });
42
67
  it("accepts hosted worker infer proxy without BYOK or workspace key", () => {
43
68
  const config = baseConfig({
44
69
  enableAiInference: true,
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ai_orchestrator_options_1 = require("../../../src/core/pipeline/ai-orchestrator-options");
4
+ function baseConfig(overrides = {}) {
5
+ return {
6
+ enableAPIDetection: true,
7
+ enableDatabaseDetection: true,
8
+ enableDataFlowDetection: true,
9
+ minimumConfidence: 0.5,
10
+ enableAiInference: true,
11
+ ...overrides,
12
+ };
13
+ }
14
+ describe("buildAgentOrchestratorOptions platform anon", () => {
15
+ it("wires platformProxy with anon session token as bearer", () => {
16
+ const options = (0, ai_orchestrator_options_1.buildAgentOrchestratorOptions)(baseConfig({
17
+ anonSessionToken: "dp_anon_session_token",
18
+ cliQuotaJobId: "job-1",
19
+ platformApiBaseUrl: "http://localhost:3000",
20
+ }), { llmEnabled: true, skipStructuralHeuristics: false });
21
+ expect(options.platformProxy).toEqual({
22
+ apiBaseUrl: "http://localhost:3000",
23
+ workspaceApiKey: "dp_anon_session_token",
24
+ jobId: "job-1",
25
+ });
26
+ expect(options.apiKey).toBeUndefined();
27
+ expect(options.providerConcurrency).toBe(1);
28
+ });
29
+ });
@@ -47,7 +47,7 @@ describe("dependency-manifest performance budgets (non-fatal)", () => {
47
47
  // Create enough manifests to exceed default budgets (maxManifestFiles=500).
48
48
  await createManyTypeScriptManifests(root, 600);
49
49
  await createManyPythonManifests(root, 600);
50
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
50
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
51
51
  languages: ["typescript", "python"],
52
52
  enableDataFlowDetection: false,
53
53
  deepAnalysis: false,
@@ -11,7 +11,7 @@ const scan_result_builders_1 = require("../../helpers/scan-result-builders");
11
11
  describe("core/pipeline/graph-mapping - DP-P0-CLI-402", () => {
12
12
  it("builds a DiagramGraphJson from a real ScanResult for the typescript-basic fixture", async () => {
13
13
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
14
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
14
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
15
15
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
16
16
  const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
17
17
  expect(graph.nodes.length).toBe(scanResult.components.length);
@@ -19,7 +19,7 @@ function shuffleDeterministic(arr, mode) {
19
19
  describe("invariant-tests - determinism", () => {
20
20
  it("scan() twice produces identical graph outputs", async () => {
21
21
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
22
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
22
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
23
23
  deepAnalysis: false,
24
24
  });
25
25
  const { scanResult: scanResult1 } = await (0, orchestrator_1.scan)(fixturesRoot, config);
@@ -40,7 +40,7 @@ function graphNodesContainCodeKey(graph) {
40
40
  describe("output safety - no source code leakage (DP-P0-CLI-XXX)", () => {
41
41
  it("does not emit `code` under graph.nodes[].data", async () => {
42
42
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
43
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
43
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
44
44
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
45
45
  const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
46
46
  expect(graphNodesContainCodeKey(graph)).toBe(false);
@@ -10,7 +10,7 @@ const orchestrator_1 = require("../../../src/core/pipeline/orchestrator");
10
10
  describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
11
11
  it("runs the full structural pipeline for the typescript-basic fixture", async () => {
12
12
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
13
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
13
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
14
14
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
15
15
  expect(scanResult.components.length).toBeGreaterThan(0);
16
16
  expect(scanResult.dataFlows.length).toBeGreaterThan(0);
@@ -21,7 +21,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
21
21
  expect(Array.isArray(scanResult.warnings)).toBe(true);
22
22
  });
23
23
  it("enables Terraform and monorepo package section auto-inference by default", () => {
24
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
24
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
25
25
  expect(config.autoInferTerraformStackSectionPathDepth).toBe(true);
26
26
  expect(config.terraformStackSectionPathDepth).toBeUndefined();
27
27
  expect(config.autoInferMonorepoPackageSectionPathDepth).toBe(true);
@@ -29,7 +29,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
29
29
  });
30
30
  it("honors excludePaths by reducing the number of scanned files", async () => {
31
31
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
32
- const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)();
32
+ const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
33
33
  const { scanResult: baseline } = await (0, orchestrator_1.scan)(fixturesRoot, baselineConfig);
34
34
  const configWithExcludes = (0, orchestrator_1.createDefaultScanConfiguration)({
35
35
  excludePaths: ["**/db.ts"],
@@ -39,7 +39,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
39
39
  });
40
40
  it("supports ? wildcard in excludePaths patterns", async () => {
41
41
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
42
- const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)();
42
+ const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
43
43
  const { files: baselineFiles } = await (0, orchestrator_1.scan)(fixturesRoot, baselineConfig);
44
44
  const configWithQuestionGlob = (0, orchestrator_1.createDefaultScanConfiguration)({
45
45
  excludePaths: ["d?.ts"],
@@ -52,7 +52,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
52
52
  });
53
53
  it("filters files by languages when languages are specified", async () => {
54
54
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
55
- const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)();
55
+ const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
56
56
  const { scanResult: baseline } = await (0, orchestrator_1.scan)(fixturesRoot, baselineConfig);
57
57
  const configLanguagesOnlyTs = (0, orchestrator_1.createDefaultScanConfiguration)({
58
58
  languages: ["typescript"],
@@ -79,7 +79,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
79
79
  });
80
80
  it("respects enableAPIDetection and enableDatabaseDetection", async () => {
81
81
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
82
- const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)();
82
+ const baselineConfig = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
83
83
  const { findings: baselineFindings } = await (0, orchestrator_1.scan)(fixturesRoot, baselineConfig);
84
84
  const apiDisabledConfig = (0, orchestrator_1.createDefaultScanConfiguration)({
85
85
  enableAPIDetection: false,
@@ -95,7 +95,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
95
95
  });
96
96
  it("emits ScanProgress events in the expected phase order", async () => {
97
97
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
98
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
98
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
99
99
  const phases = [];
100
100
  await (0, orchestrator_1.scan)(fixturesRoot, config, (progress) => {
101
101
  phases.push(progress.phase);
@@ -112,7 +112,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
112
112
  });
113
113
  it("detects third-party services from Python dependency manifests", async () => {
114
114
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "python-dependency-manifests-basic");
115
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
115
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
116
116
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
117
117
  const thirdParties = scanResult.components.filter((c) => c.type === "third_party");
118
118
  const serviceNames = thirdParties
@@ -123,7 +123,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
123
123
  });
124
124
  it("detects third-party services from TypeScript dependency manifests", async () => {
125
125
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-dependency-manifests-basic");
126
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
126
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
127
127
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
128
128
  const thirdParties = scanResult.components.filter((c) => c.type === "third_party");
129
129
  const serviceNames = thirdParties
@@ -176,7 +176,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
176
176
  await promises_1.default.writeFile(path_1.default.join(root, "src", "app.ts"), "export const x = 1;\n", "utf8");
177
177
  await promises_1.default.writeFile(path_1.default.join(root, "src", "app.spec.ts"), "import { x } from './app';\n", "utf8");
178
178
  await promises_1.default.writeFile(path_1.default.join(root, "playwright.config.ts"), "export default {};\n", "utf8");
179
- const { files } = await (0, orchestrator_1.scan)(root, (0, orchestrator_1.createDefaultScanConfiguration)());
179
+ const { files } = await (0, orchestrator_1.scan)(root, (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false }));
180
180
  const paths = files.map((f) => f.path);
181
181
  expect(paths.some((p) => p.endsWith("app.spec.ts"))).toBe(false);
182
182
  expect(paths.some((p) => p.includes("playwright.config"))).toBe(false);
@@ -188,7 +188,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
188
188
  });
189
189
  it("produces a combined ScanResult for mixed TypeScript and Python repos", async () => {
190
190
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "mixed-language-basic");
191
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
191
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
192
192
  const { scanResult, files, findings } = await (0, orchestrator_1.scan)(fixturesRoot, config);
193
193
  const scannedLanguages = new Set(files.map((file) => file.language));
194
194
  expect(scannedLanguages.has("typescript")).toBe(true);
@@ -231,7 +231,7 @@ describe("core/pipeline/orchestrator - DP-P0-CLI-401", () => {
231
231
  "export default app;",
232
232
  "",
233
233
  ].join("\n"), "utf8");
234
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
234
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
235
235
  const { scanResult } = await (0, orchestrator_1.scan)(root, config);
236
236
  const postgresTargets = scanResult.components.filter((c) => c.type === "asset" &&
237
237
  c.subType === "database" &&
@@ -13,7 +13,7 @@ const json_1 = require("../../../src/output/json");
13
13
  describe("output/json - DP-P0-CLI-403", () => {
14
14
  it("builds and writes a validated dataflow.json wrapper for a real ScanResult", async () => {
15
15
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
16
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
16
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
17
17
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
18
18
  const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
19
19
  const wrapper = (0, json_1.buildDataflowWrapper)(scanResult, graph, {
@@ -43,7 +43,7 @@ describe("output/json - DP-P0-CLI-403", () => {
43
43
  });
44
44
  it("throws and does not write a file when validation fails", async () => {
45
45
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
46
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
46
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
47
47
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
48
48
  const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
49
49
  // Create an intentionally invalid graph by clearing the first node id so
@@ -22,7 +22,7 @@ describe("parsing error handling - non-fatal", () => {
22
22
  // Python file with a literal null byte to trigger parser warning.
23
23
  // Use a Buffer to ensure the null byte is present in the file.
24
24
  await promises_1.default.writeFile(path_1.default.join(root, "main.py"), Buffer.from("import os\n\x00\n", "utf8"));
25
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
25
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
26
26
  languages: ["typescript", "python"],
27
27
  enableDataFlowDetection: false,
28
28
  });
@@ -4,7 +4,7 @@ const orchestrator_1 = require("../../../src/core/pipeline/orchestrator");
4
4
  describe("ScanResult.languageStats for Python", () => {
5
5
  it("populates languageStats entry for Python files", async () => {
6
6
  const rootPath = `${__dirname}/../../fixtures/python-parser-basic`;
7
- const config = (0, orchestrator_1.createDefaultScanConfiguration)();
7
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false });
8
8
  const { scanResult } = await (0, orchestrator_1.scan)(rootPath, config);
9
9
  expect(scanResult.languageStats).toBeDefined();
10
10
  const pythonStats = scanResult.languageStats?.find((entry) => entry.language === "python");
@@ -74,7 +74,7 @@ describe("core/pipeline/structural-scan - DP-P0-CLI-303", () => {
74
74
  const fixturesRoot = path_1.default.join(__dirname, "..", "..", "fixtures", "typescript-basic");
75
75
  const outside = path_1.default.join(fixturesRoot, "..", "terraform-show-extra-bucket.json");
76
76
  const warnings = [];
77
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
77
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
78
78
  terraformJsonPath: outside,
79
79
  });
80
80
  await (0, structural_scan_1.runStructuralScanPhase)(fixturesRoot, config, (w) => warnings.push(w));
@@ -15,7 +15,7 @@ describe("terraform JSON overlay in scan pipeline", () => {
15
15
  const overlayInRoot = path_1.default.join(fixturesRoot, "show-overlay.json");
16
16
  fs_1.default.copyFileSync(jsonFixture, overlayInRoot);
17
17
  try {
18
- const config = (0, orchestrator_1.createDefaultScanConfiguration)({
18
+ const config = (0, orchestrator_1.createDefaultScanConfiguration)({ enableAiInference: false,
19
19
  terraformJsonPath: "show-overlay.json",
20
20
  });
21
21
  const { scanResult } = await (0, orchestrator_1.scan)(fixturesRoot, config);
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const anonymous_ai_session_client_1 = require("../../../src/platform-api/anonymous-ai-session-client");
4
+ describe("cliAnonymousAiSession", () => {
5
+ const originalFetch = global.fetch;
6
+ afterEach(() => {
7
+ global.fetch = originalFetch;
8
+ });
9
+ it("returns session payload on success", async () => {
10
+ global.fetch = jest.fn().mockResolvedValue({
11
+ ok: true,
12
+ json: async () => ({
13
+ sessionToken: "dp_anon_abc",
14
+ jobId: "job-1",
15
+ suggestedAiBudgetTokens: 100000,
16
+ expiresAt: "2026-01-01T00:00:00.000Z",
17
+ aiDelivery: "platform_proxy",
18
+ }),
19
+ });
20
+ const out = await (0, anonymous_ai_session_client_1.cliAnonymousAiSession)({ projectName: "Demo" });
21
+ expect(out.sessionToken).toBe("dp_anon_abc");
22
+ expect(out.jobId).toBe("job-1");
23
+ expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining("/api/scans/cli/ai/anonymous-session"), expect.objectContaining({ method: "POST" }));
24
+ });
25
+ it("throws CliAnonymousIpLimitError on 429", async () => {
26
+ global.fetch = jest.fn().mockResolvedValue({
27
+ ok: false,
28
+ status: 429,
29
+ json: async () => ({
30
+ message: "Too many anonymous CLI scans from this IP (max 3).",
31
+ }),
32
+ });
33
+ await expect((0, anonymous_ai_session_client_1.cliAnonymousAiSession)()).rejects.toBeInstanceOf(anonymous_ai_session_client_1.CliAnonymousIpLimitError);
34
+ });
35
+ });