@dataparade/cli 0.0.2 → 0.0.4

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 (74) hide show
  1. package/README.md +25 -0
  2. package/dist/package.json +85 -0
  3. package/dist/src/ai-enrichment/agent-orchestrator.d.ts +2 -0
  4. package/dist/src/ai-enrichment/agent-orchestrator.js +8 -5
  5. package/dist/src/ai-enrichment/providers/hosted-worker-infer-provider.d.ts +10 -0
  6. package/dist/src/ai-enrichment/providers/hosted-worker-infer-provider.js +47 -0
  7. package/dist/src/cli.js +85 -1
  8. package/dist/src/config/env.js +2 -0
  9. package/dist/src/config/resolve.js +3 -0
  10. package/dist/src/config/scan-env.d.ts +1 -0
  11. package/dist/src/config/scan-env.js +5 -0
  12. package/dist/src/config/types.d.ts +1 -0
  13. package/dist/src/config/upload-env.d.ts +1 -0
  14. package/dist/src/config/upload-env.js +10 -0
  15. package/dist/src/config/validate-scan-ai.d.ts +1 -1
  16. package/dist/src/config/validate-scan-ai.js +5 -0
  17. package/dist/src/core/pipeline/ai-orchestrator-options.js +5 -0
  18. package/dist/src/core/pipeline/graph-mapping.js +5 -0
  19. package/dist/src/core/pipeline/infer-data-flow-protocol.d.ts +7 -0
  20. package/dist/src/core/pipeline/infer-data-flow-protocol.js +39 -0
  21. package/dist/src/core/schema/scan-config.schema.d.ts +2 -0
  22. package/dist/src/core/schema/scan-config.schema.js +2 -1
  23. package/dist/src/core/types/config.d.ts +3 -1
  24. package/dist/src/observability/scan-sentry.d.ts +1 -1
  25. package/dist/src/platform-api/dataparade-app-base-url.d.ts +7 -0
  26. package/dist/src/platform-api/dataparade-app-base-url.js +14 -0
  27. package/dist/src/platform-api/upload-client.d.ts +3 -0
  28. package/dist/src/platform-api/upload-client.js +38 -0
  29. package/dist/src/platform-api/upload.types.d.ts +10 -0
  30. package/dist/src/platform-api/upload.types.js +2 -0
  31. package/dist/src/upload/build-preview-url.d.ts +1 -0
  32. package/dist/src/upload/build-preview-url.js +10 -0
  33. package/dist/src/upload/run-upload.d.ts +2 -0
  34. package/dist/src/upload/run-upload.js +18 -0
  35. package/dist/src/upload/types.d.ts +11 -0
  36. package/dist/src/upload/types.js +2 -0
  37. package/dist/tests/unit/analyzers/typescript/property-detection.spec.js +22 -0
  38. package/dist/tests/unit/cli/scan-quota-flow.spec.js +2 -0
  39. package/dist/tests/unit/config/upload-env.spec.d.ts +1 -0
  40. package/dist/tests/unit/config/upload-env.spec.js +12 -0
  41. package/dist/tests/unit/config/validate-scan-ai.spec.js +8 -0
  42. package/dist/tests/unit/core/graph-mapping.spec.js +52 -0
  43. package/dist/tests/unit/core/infer-data-flow-protocol.spec.d.ts +1 -0
  44. package/dist/tests/unit/core/infer-data-flow-protocol.spec.js +37 -0
  45. package/dist/tests/unit/platform-api/dataparade-app-base-url.spec.d.ts +1 -0
  46. package/dist/tests/unit/platform-api/dataparade-app-base-url.spec.js +13 -0
  47. package/dist/tests/unit/platform-api/upload-client.spec.d.ts +1 -0
  48. package/dist/tests/unit/platform-api/upload-client.spec.js +40 -0
  49. package/dist/tests/unit/publish-manifest.spec.d.ts +1 -0
  50. package/dist/tests/unit/publish-manifest.spec.js +18 -0
  51. package/dist/tests/unit/upload/build-preview-url.spec.d.ts +1 -0
  52. package/dist/tests/unit/upload/build-preview-url.spec.js +19 -0
  53. package/package.json +4 -3
  54. package/patterns/README.md +322 -0
  55. package/patterns/actor.patterns.yaml +51 -0
  56. package/patterns/aws-terraform-catalog.snapshot.json +1760 -0
  57. package/patterns/aws-terraform-service-hints.generated.json +1446 -0
  58. package/patterns/azure-terraform-catalog.snapshot.json +1195 -0
  59. package/patterns/azure-terraform-service-hints.generated.json +864 -0
  60. package/patterns/classifier/actors.classifier.yaml +17 -0
  61. package/patterns/classifier/components.classifier.yaml +110 -0
  62. package/patterns/classifier/third-party.classifier.yaml +169 -0
  63. package/patterns/kubernetes-terraform-catalog.snapshot.json +94 -0
  64. package/patterns/kubernetes-terraform-service-hints.generated.json +258 -0
  65. package/patterns/non-pii-signals.rules.yaml +84 -0
  66. package/patterns/pii-signals.rules.yaml +115 -0
  67. package/patterns/property.patterns.yaml +431 -0
  68. package/patterns/provider-topology.rules.yaml +765 -0
  69. package/patterns/python.md +211 -0
  70. package/patterns/python.patterns.yaml +160 -0
  71. package/patterns/terraform.md +37 -0
  72. package/patterns/terraform.patterns.yaml +495 -0
  73. package/patterns/third-party.patterns.yaml +167 -0
  74. package/patterns/typescript.patterns.yaml +139 -0
@@ -0,0 +1,10 @@
1
+ export type CliUploadPreviewInput = {
2
+ apiKey: string;
3
+ dataflow: unknown;
4
+ projectName?: string;
5
+ scanJobId?: string;
6
+ };
7
+ export type CliUploadPreviewResponse = {
8
+ draftId: string;
9
+ projectName?: string | null;
10
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare function buildImportPreviewUrl(draftId: string): string;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildImportPreviewUrl = buildImportPreviewUrl;
4
+ const dataparade_app_base_url_1 = require("../platform-api/dataparade-app-base-url");
5
+ function buildImportPreviewUrl(draftId) {
6
+ const base = (0, dataparade_app_base_url_1.getDataparadeAppBaseUrl)();
7
+ const url = new URL("/dashboard", `${base}/`);
8
+ url.searchParams.set("importDraft", draftId);
9
+ return url.toString();
10
+ }
@@ -0,0 +1,2 @@
1
+ import type { RunDataflowUploadInput, RunDataflowUploadResult } from "./types";
2
+ export declare function runDataflowUpload(input: RunDataflowUploadInput): Promise<RunDataflowUploadResult>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDataflowUpload = runDataflowUpload;
4
+ const upload_client_1 = require("../platform-api/upload-client");
5
+ const build_preview_url_1 = require("./build-preview-url");
6
+ async function runDataflowUpload(input) {
7
+ const prefix = input.logPrefix ?? "[upload]";
8
+ const result = await (0, upload_client_1.cliUploadPreview)({
9
+ apiKey: input.apiKey,
10
+ dataflow: input.dataflow,
11
+ projectName: input.projectName,
12
+ scanJobId: input.scanJobId,
13
+ });
14
+ const previewUrl = (0, build_preview_url_1.buildImportPreviewUrl)(result.draftId);
15
+ // eslint-disable-next-line no-console
16
+ console.log(`${prefix} Preview ready: ${previewUrl}`);
17
+ return { draftId: result.draftId, previewUrl };
18
+ }
@@ -0,0 +1,11 @@
1
+ export type RunDataflowUploadInput = {
2
+ apiKey: string;
3
+ dataflow: unknown;
4
+ projectName?: string;
5
+ scanJobId?: string;
6
+ logPrefix?: string;
7
+ };
8
+ export type RunDataflowUploadResult = {
9
+ draftId: string;
10
+ previewUrl: string;
11
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -122,6 +122,18 @@ describe("property-detection", () => {
122
122
  expect(out.integration_method).toBe("api");
123
123
  expect(out.authentication_method).toBe("api_key");
124
124
  });
125
+ it("sets api_type graphql when url path is GraphQL", () => {
126
+ const finding = makeFinding({
127
+ pattern: "external_api_call",
128
+ name: "Graph API",
129
+ properties: {
130
+ serviceName: "internal",
131
+ url: "https://api.example.com/graphql",
132
+ },
133
+ });
134
+ const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
135
+ expect(out.api_type).toBe("graphql");
136
+ });
125
137
  });
126
138
  describe("express_route", () => {
127
139
  it("sets request_validation and connection_encryption", () => {
@@ -133,6 +145,16 @@ describe("property-detection", () => {
133
145
  const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
134
146
  expect(out.request_validation).toBe(true);
135
147
  expect(out.connection_encryption).toBe(true);
148
+ expect(out.api_type).toBe("rest");
149
+ });
150
+ it("sets api_type graphql for GraphQL route path", () => {
151
+ const finding = makeFinding({
152
+ pattern: "express_route",
153
+ name: "POST /graphql",
154
+ properties: { httpMethods: ["POST"], path: "/graphql" },
155
+ });
156
+ const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
157
+ expect(out.api_type).toBe("graphql");
136
158
  });
137
159
  });
138
160
  });
@@ -27,10 +27,12 @@ describe("scan quota flow", () => {
27
27
  afterEach(() => {
28
28
  fs_1.default.rmSync(tempRoot, { recursive: true, force: true });
29
29
  delete process.env.DATAPARADE_WORKSPACE_API_KEY;
30
+ delete process.env.DATAPARADE_SKIP_AUTO_UPLOAD;
30
31
  process.exitCode = 0;
31
32
  });
32
33
  it("skips quota API when workspace key is present but AI is disabled", async () => {
33
34
  process.env.DATAPARADE_WORKSPACE_API_KEY = "dp_live_test";
35
+ process.env.DATAPARADE_SKIP_AUTO_UPLOAD = "true";
34
36
  await (0, cli_1.run)(["node", "cli", "scan", tempRoot]);
35
37
  expect(fetchMock).not.toHaveBeenCalled();
36
38
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const upload_env_1 = require("../../../src/config/upload-env");
4
+ describe("resolveSkipAutoUpload", () => {
5
+ it("is false when unset", () => {
6
+ expect((0, upload_env_1.resolveSkipAutoUpload)({})).toBe(false);
7
+ });
8
+ it("is true for truthy env values", () => {
9
+ expect((0, upload_env_1.resolveSkipAutoUpload)({ DATAPARADE_SKIP_AUTO_UPLOAD: "true" })).toBe(true);
10
+ expect((0, upload_env_1.resolveSkipAutoUpload)({ DATAPARADE_SKIP_AUTO_UPLOAD: "1" })).toBe(true);
11
+ });
12
+ });
@@ -39,6 +39,14 @@ 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 hosted worker infer proxy without BYOK or workspace key", () => {
43
+ const config = baseConfig({
44
+ enableAiInference: true,
45
+ hostedInferProxyUrl: "http://127.0.0.1:4567/infer",
46
+ });
47
+ expect((0, validate_scan_ai_1.validateAiInferenceCredentials)(config)).toEqual([]);
48
+ expect((0, validate_scan_ai_1.resolveAiMode)(config)).toBe("hosted_worker");
49
+ });
42
50
  it("allows workspace key with AI disabled", () => {
43
51
  const errors = (0, validate_scan_ai_1.validateAiInferenceCredentials)(baseConfig({
44
52
  enableAiInference: false,
@@ -106,6 +106,7 @@ describe("core/pipeline/graph-mapping - DP-P0-CLI-402", () => {
106
106
  return;
107
107
  const properties = edge.data.properties;
108
108
  expect(properties.engineering.transferType).toBe("api_call");
109
+ expect(properties.engineering.protocol).toBe("rest");
109
110
  expect(properties.engineering.name).toContain("Test Application");
110
111
  expect(properties.engineering.actions).toEqual(["read"]);
111
112
  expect(properties.privacy.dataCategories).toEqual([
@@ -122,6 +123,57 @@ describe("core/pipeline/graph-mapping - DP-P0-CLI-402", () => {
122
123
  expect(properties.targetScopeConfidence).toBe("high");
123
124
  expect(properties.targetScopeReason).toBe("same-section-id");
124
125
  });
126
+ it("sets engineering.protocol to graphql when flow endpoint is a GraphQL URL", () => {
127
+ const scanResult = {
128
+ components: [
129
+ {
130
+ id: "component-1",
131
+ name: "API Service",
132
+ type: "asset",
133
+ subType: "api",
134
+ confidence: 0.9,
135
+ detectedFrom: [{ pattern: "express_route" }],
136
+ sourceLocations: [
137
+ { filePath: "src/api.ts", startLine: 1, endLine: 10 },
138
+ ],
139
+ properties: {},
140
+ },
141
+ {
142
+ id: "component-2",
143
+ name: "External API",
144
+ type: "third_party",
145
+ subType: "saas_service",
146
+ confidence: 0.9,
147
+ detectedFrom: [{ pattern: "external_api_call" }],
148
+ sourceLocations: [
149
+ { filePath: "src/api.ts", startLine: 20, endLine: 30 },
150
+ ],
151
+ properties: {},
152
+ },
153
+ ],
154
+ dataFlows: [
155
+ {
156
+ id: "flow-gql",
157
+ sourceComponentId: "component-1",
158
+ targetComponentId: "component-2",
159
+ type: "api_call",
160
+ confidence: 0.9,
161
+ endpoint: "https://api.example.com/graphql",
162
+ method: "POST",
163
+ },
164
+ ],
165
+ filesScanned: 1,
166
+ filesSkipped: 0,
167
+ totalLines: 50,
168
+ scanDurationMs: 5,
169
+ warnings: [],
170
+ errors: [],
171
+ };
172
+ const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
173
+ const properties = graph.edges[0]?.data
174
+ ?.properties;
175
+ expect(properties?.engineering?.protocol).toBe("graphql");
176
+ });
125
177
  it("places managed provider service nodes after provider in layout", () => {
126
178
  const scanResult = {
127
179
  components: [
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const infer_data_flow_protocol_1 = require("../../../src/core/pipeline/infer-data-flow-protocol");
4
+ function baseFlow(overrides = {}) {
5
+ return {
6
+ id: "flow-1",
7
+ sourceComponentId: "a",
8
+ targetComponentId: "b",
9
+ type: "api_call",
10
+ confidence: 0.9,
11
+ ...overrides,
12
+ };
13
+ }
14
+ describe("inferDataFlowProtocol", () => {
15
+ it("returns graphql for /graphql endpoint", () => {
16
+ expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ endpoint: "https://api.example.com/graphql" }))).toBe("graphql");
17
+ });
18
+ it("returns graphql when source code mentions graphql", () => {
19
+ expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({
20
+ sourceLocation: {
21
+ filePath: "src/client.ts",
22
+ startLine: 1,
23
+ endLine: 2,
24
+ code: "client.query({ query: GET_USERS }) // graphql",
25
+ },
26
+ }))).toBe("graphql");
27
+ });
28
+ it("returns rest for api_call with HTTP endpoint", () => {
29
+ expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ endpoint: "/api/users", method: "GET" }))).toBe("rest");
30
+ });
31
+ it("returns undefined for database_query", () => {
32
+ expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ type: "database_query", endpoint: "postgres://..." }))).toBeUndefined();
33
+ });
34
+ it("returns undefined for api_call without endpoint or method", () => {
35
+ expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow())).toBeUndefined();
36
+ });
37
+ });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const dataparade_app_base_url_1 = require("../../../src/platform-api/dataparade-app-base-url");
4
+ describe("getDataparadeAppBaseUrl", () => {
5
+ it("defaults to production frontend URL", () => {
6
+ expect((0, dataparade_app_base_url_1.getDataparadeAppBaseUrl)({})).toBe(dataparade_app_base_url_1.DEFAULT_DATAPARADE_APP_URL);
7
+ });
8
+ it("strips trailing slash from DATAPARADE_APP_URL", () => {
9
+ expect((0, dataparade_app_base_url_1.getDataparadeAppBaseUrl)({
10
+ DATAPARADE_APP_URL: "http://localhost:3001/",
11
+ })).toBe("http://localhost:3001");
12
+ });
13
+ });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const upload_client_1 = require("../../../src/platform-api/upload-client");
4
+ describe("cliUploadPreview", () => {
5
+ const originalFetch = global.fetch;
6
+ afterEach(() => {
7
+ global.fetch = originalFetch;
8
+ jest.restoreAllMocks();
9
+ });
10
+ it("posts dataflow to upload endpoint", async () => {
11
+ global.fetch = jest.fn().mockResolvedValue({
12
+ ok: true,
13
+ json: async () => ({ draftId: "draft-1", projectName: "proj" }),
14
+ });
15
+ const result = await (0, upload_client_1.cliUploadPreview)({
16
+ apiKey: "dp_live_testkey123456",
17
+ dataflow: { schemaVersion: "1.0", graph: { nodes: [], edges: [] } },
18
+ projectName: "proj",
19
+ scanJobId: "job-1",
20
+ });
21
+ expect(result.draftId).toBe("draft-1");
22
+ expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining("/api/scans/cli/upload"), expect.objectContaining({
23
+ method: "POST",
24
+ headers: expect.objectContaining({
25
+ Authorization: "Bearer dp_live_testkey123456",
26
+ }),
27
+ }));
28
+ });
29
+ it("throws with API error message", async () => {
30
+ global.fetch = jest.fn().mockResolvedValue({
31
+ ok: false,
32
+ status: 400,
33
+ json: async () => ({ message: "Invalid dataflow" }),
34
+ });
35
+ await expect((0, upload_client_1.cliUploadPreview)({
36
+ apiKey: "dp_live_testkey123456",
37
+ dataflow: {},
38
+ })).rejects.toThrow("Invalid dataflow");
39
+ });
40
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
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 fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ describe("npm publish manifest", () => {
9
+ const pkgPath = path_1.default.resolve(__dirname, "../../package.json");
10
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, "utf8"));
11
+ it("lists typescript as a runtime dependency (required by TS analyzer)", () => {
12
+ expect(pkg.dependencies?.typescript).toBeDefined();
13
+ expect(pkg.devDependencies?.typescript).toBeUndefined();
14
+ });
15
+ it("includes patterns/ in published files (runtime YAML configs)", () => {
16
+ expect(pkg.files).toContain("patterns");
17
+ });
18
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const build_preview_url_1 = require("../../../src/upload/build-preview-url");
4
+ describe("buildImportPreviewUrl", () => {
5
+ const original = process.env.DATAPARADE_APP_URL;
6
+ afterEach(() => {
7
+ if (original === undefined) {
8
+ delete process.env.DATAPARADE_APP_URL;
9
+ }
10
+ else {
11
+ process.env.DATAPARADE_APP_URL = original;
12
+ }
13
+ });
14
+ it("builds dashboard deep link with importDraft query param", () => {
15
+ process.env.DATAPARADE_APP_URL = "http://localhost:3001";
16
+ const url = (0, build_preview_url_1.buildImportPreviewUrl)("draft-abc");
17
+ expect(url).toBe("http://localhost:3001/dashboard?importDraft=draft-abc");
18
+ });
19
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataparade/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "dataPARADE CLI — scan codebases for privacy and security data flows",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -58,9 +58,10 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@sentry/node": "^10.38.0",
61
+ "commander": "^12.1.0",
61
62
  "dotenv": "^16.4.7",
62
63
  "langsmith": "^0.3.14",
63
- "commander": "^12.1.0",
64
+ "typescript": "^5.7.3",
64
65
  "yaml": "^2.8.2",
65
66
  "zod": "^4.3.5"
66
67
  },
@@ -72,11 +73,11 @@
72
73
  "globals": "^15.14.0",
73
74
  "jest": "^29.7.0",
74
75
  "ts-jest": "^29.2.5",
75
- "typescript": "^5.7.3",
76
76
  "typescript-eslint": "^8.20.0"
77
77
  },
78
78
  "files": [
79
79
  "dist",
80
+ "patterns",
80
81
  "LICENSE",
81
82
  "README.md",
82
83
  "package.json"