@dataparade/cli 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,6 +112,7 @@ The CLI emits **all** Engineering, Privacy, and Security property keys (from the
112
112
  - Database: `connection_encryption`, `backup_frequency`, `audit_logging_enabled`, etc. when patterns match.
113
113
  - Auth: `mfa_required`, `authentication_method`, `sso_integration` from auth middleware patterns.
114
114
  - Routes: `request_validation`, `api_type`, `https_enforced` for route patterns.
115
+ - Data flow edges (graph mapping): `engineering.protocol` is set to `rest` or `graphql` for `api_call` flows when the endpoint, method, or source code indicates HTTP REST vs GraphQL (see `infer-data-flow-protocol.ts`). Pattern rules in `property.patterns.yaml` also set `api_type` to `graphql` for `/graphql` URLs and paths.
115
116
 
116
117
  **Terraform / IaC (`.tf` / `.tfvars`):**
117
118
 
@@ -10,6 +10,7 @@ const minimal_terraform_layout_1 = require("./diagram-layout/minimal-terraform-l
10
10
  const section_helpers_1 = require("./diagram-layout/section-helpers");
11
11
  const terraform_lane_layout_1 = require("./diagram-layout/terraform-lane-layout");
12
12
  const terraform_minimal_services_1 = require("./terraform-minimal-services");
13
+ const infer_data_flow_protocol_1 = require("./infer-data-flow-protocol");
13
14
  function stripCodeFromSourceLocation(loc) {
14
15
  if (!loc)
15
16
  return undefined;
@@ -157,6 +158,10 @@ function mapDataFlowToEdge(flow, componentsById) {
157
158
  if (flow.actions && flow.actions.length > 0) {
158
159
  engineering.actions = flow.actions;
159
160
  }
161
+ const protocol = (0, infer_data_flow_protocol_1.inferDataFlowProtocol)(flow);
162
+ if (protocol) {
163
+ engineering.protocol = protocol;
164
+ }
160
165
  const privacy = {
161
166
  dataCategories: flow.dataCategories ?? [],
162
167
  dataSubjectCategories: flow.dataSubjectCategories,
@@ -0,0 +1,7 @@
1
+ import type { DetectedDataFlow } from "../types/data-flow";
2
+ export type InferredDataFlowProtocol = "rest" | "graphql";
3
+ /**
4
+ * Infer engineering.protocol for a scanned data flow (rest | graphql).
5
+ * Returns undefined when the flow type is not HTTP/API-shaped or there is no signal.
6
+ */
7
+ export declare function inferDataFlowProtocol(flow: DetectedDataFlow): InferredDataFlowProtocol | undefined;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inferDataFlowProtocol = inferDataFlowProtocol;
4
+ const GRAPHQL_SIGNAL = /\bgraphql\b|\/graphql\b/i;
5
+ function textSuggestsGraphql(text) {
6
+ return GRAPHQL_SIGNAL.test(text);
7
+ }
8
+ function flowCodeSuggestsGraphql(flow) {
9
+ const locations = [
10
+ flow.sourceLocation,
11
+ ...(flow.sourceLocations ?? []),
12
+ ].filter((loc) => loc != null);
13
+ for (const loc of locations) {
14
+ if (typeof loc.code === "string" && textSuggestsGraphql(loc.code)) {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ /**
21
+ * Infer engineering.protocol for a scanned data flow (rest | graphql).
22
+ * Returns undefined when the flow type is not HTTP/API-shaped or there is no signal.
23
+ */
24
+ function inferDataFlowProtocol(flow) {
25
+ const endpoint = flow.endpoint?.trim() ?? "";
26
+ if (endpoint && textSuggestsGraphql(endpoint)) {
27
+ return "graphql";
28
+ }
29
+ if (flowCodeSuggestsGraphql(flow)) {
30
+ return "graphql";
31
+ }
32
+ if (flow.type !== "api_call") {
33
+ return undefined;
34
+ }
35
+ if (endpoint || flow.method?.trim()) {
36
+ return "rest";
37
+ }
38
+ return undefined;
39
+ }
@@ -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
  });
@@ -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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataparade/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "dataPARADE CLI — scan codebases for privacy and security data flows",
5
5
  "publishConfig": {
6
6
  "access": "public",