@ductape/cli 0.3.10 → 0.3.11
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/dist/commands/graph.d.ts +4 -0
- package/dist/commands/graph.js +65 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/commands/graph.d.ts
CHANGED
package/dist/commands/graph.js
CHANGED
|
@@ -3,16 +3,80 @@ import { setGraphContext } from '../lib/context-store.js';
|
|
|
3
3
|
import { GRAPH_PROXY_METHODS } from '../lib/db-methods.js';
|
|
4
4
|
import { getGraphProxy, requireSession } from '../lib/proxy/context.js';
|
|
5
5
|
import { printJson } from '../lib/output.js';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const GRAPH_ACTION_FIELDS = new Set(['graphTag', 'name', 'description', 'operation', 'query', 'parameters']);
|
|
8
|
+
function actionSlug(name) {
|
|
9
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
10
|
+
}
|
|
11
|
+
export function validateGraphActionFile(file, body) {
|
|
12
|
+
if (!file)
|
|
13
|
+
throw new Error('Graph action validation requires -f ductape/graphs/<graph-tag>/actions/<action-tag>.action.json');
|
|
14
|
+
for (const field of ['graphTag', 'name', 'operation', 'query', 'parameters']) {
|
|
15
|
+
if (!(field in body))
|
|
16
|
+
throw new Error(`Graph action is missing required field "${field}"`);
|
|
17
|
+
}
|
|
18
|
+
for (const field of Object.keys(body)) {
|
|
19
|
+
if (!GRAPH_ACTION_FIELDS.has(field))
|
|
20
|
+
throw new Error(`Unknown graph action field "${field}"`);
|
|
21
|
+
}
|
|
22
|
+
if (typeof body.graphTag !== 'string' || !body.graphTag)
|
|
23
|
+
throw new Error('graphTag must be a non-empty string');
|
|
24
|
+
if (typeof body.name !== 'string' || !body.name)
|
|
25
|
+
throw new Error('name must be a non-empty string');
|
|
26
|
+
if (typeof body.operation !== 'string' || !body.operation)
|
|
27
|
+
throw new Error('operation must be a non-empty string');
|
|
28
|
+
if (!body.query || typeof body.query !== 'object' || Array.isArray(body.query))
|
|
29
|
+
throw new Error('query must be an object');
|
|
30
|
+
if (!Array.isArray(body.parameters))
|
|
31
|
+
throw new Error('parameters must be an array');
|
|
32
|
+
for (const [index, parameter] of body.parameters.entries()) {
|
|
33
|
+
if (!parameter || typeof parameter !== 'object' || Array.isArray(parameter))
|
|
34
|
+
throw new Error(`parameters[${index}] must be an object`);
|
|
35
|
+
const value = parameter;
|
|
36
|
+
for (const field of ['name', 'path', 'defaultValue', 'type']) {
|
|
37
|
+
if (!(field in value))
|
|
38
|
+
throw new Error(`parameters[${index}] is missing required field "${field}"`);
|
|
39
|
+
}
|
|
40
|
+
if (!['string', 'number', 'boolean', 'array', 'object'].includes(String(value.type))) {
|
|
41
|
+
throw new Error(`parameters[${index}].type is invalid`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const actionTag = actionSlug(body.name);
|
|
45
|
+
const normalized = file.replaceAll('\\', '/');
|
|
46
|
+
const expectedSuffix = `ductape/graphs/${body.graphTag}/actions/${actionTag}.action.json`;
|
|
47
|
+
if (!normalized.endsWith(expectedSuffix))
|
|
48
|
+
throw new Error(`Graph action must be stored at ${expectedSuffix}`);
|
|
49
|
+
if (path.basename(file) !== `${actionTag}.action.json`)
|
|
50
|
+
throw new Error(`Graph action filename must be ${actionTag}.action.json`);
|
|
51
|
+
return { graphTag: body.graphTag, actionTag };
|
|
52
|
+
}
|
|
6
53
|
export async function runGraph(method, opts) {
|
|
7
54
|
const session = requireSession();
|
|
8
55
|
const proxy = getGraphProxy(session);
|
|
9
56
|
const body = readJsonBody(opts.file);
|
|
10
57
|
const m = method.includes('.') ? method : method.toLowerCase();
|
|
58
|
+
if (m === 'validateaction') {
|
|
59
|
+
const validated = validateGraphActionFile(opts.file, body);
|
|
60
|
+
printJson({ valid: true, file: opts.file, ...validated }, Boolean(opts.json));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
11
63
|
if (!GRAPH_PROXY_METHODS.includes(m) && !m.includes('.')) {
|
|
12
64
|
console.warn(`Method "${m}" not in known list; sending anyway. Known: ${GRAPH_PROXY_METHODS.join(', ')}`);
|
|
13
65
|
}
|
|
14
66
|
const params = [];
|
|
15
|
-
if (m === '
|
|
67
|
+
if (m === 'createaction') {
|
|
68
|
+
validateGraphActionFile(opts.file, body);
|
|
69
|
+
params.push(body, session.project.product_tag);
|
|
70
|
+
}
|
|
71
|
+
else if (m === 'updateaction') {
|
|
72
|
+
const actionTag = String(body.actionTag ?? '');
|
|
73
|
+
const graphTag = String(body.graphTag ?? '');
|
|
74
|
+
if (!actionTag || !graphTag || !body.updates || typeof body.updates !== 'object') {
|
|
75
|
+
throw new Error('updateAction requires { "actionTag", "graphTag", "updates": { ... } }');
|
|
76
|
+
}
|
|
77
|
+
params.push(actionTag, body.updates, graphTag, session.project.product_tag);
|
|
78
|
+
}
|
|
79
|
+
else if (m === 'connect') {
|
|
16
80
|
const cfg = {
|
|
17
81
|
product: session.project.product_tag,
|
|
18
82
|
env: session.project.env_slug,
|
package/dist/index.js
CHANGED
|
@@ -735,7 +735,7 @@ db
|
|
|
735
735
|
}));
|
|
736
736
|
const graph = program.command('graph').description('Graph runtime (graph-proxy)');
|
|
737
737
|
graph
|
|
738
|
-
.argument('<verb>', 'connect | query | …')
|
|
738
|
+
.argument('<verb>', 'connect | query | createAction | validateAction | updateAction | …')
|
|
739
739
|
.option('-f, --file <path>')
|
|
740
740
|
.option('--json', 'JSON output')
|
|
741
741
|
.action(wrap((verb, opts) => runGraph(verb, { file: opts.file, json: opts.json })));
|
package/package.json
CHANGED