@walkeros/cli 1.1.2 → 1.2.0
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/CHANGELOG.md +22 -0
- package/dist/examples/flow-complete.json +10 -1
- package/dist/examples/flow-complete.md +23 -24
- package/dist/index.d.ts +29 -1
- package/dist/index.js +325 -30
- package/dist/index.js.map +1 -1
- package/examples/flow-complete.json +10 -1
- package/examples/flow-complete.md +23 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @walkeros/cli
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cc68f50: Add validate command for events, flows, and mappings
|
|
8
|
+
- `walkeros validate event` - validates event structure using
|
|
9
|
+
PartialEventSchema
|
|
10
|
+
- `walkeros validate flow` - validates flow configurations using SetupSchema
|
|
11
|
+
- `walkeros validate mapping` - validates mapping event patterns
|
|
12
|
+
|
|
13
|
+
Includes programmatic API via `import { validate } from '@walkeros/cli'`
|
|
14
|
+
|
|
15
|
+
## 1.1.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 6fcfaf5: Fix chain property handling for all component types in bundler.
|
|
20
|
+
Sources now correctly output `next` property for pre-collector transformer
|
|
21
|
+
chains. Unified inline code generation for sources, destinations, and
|
|
22
|
+
transformers. Standardized transformer `next` as top-level property
|
|
23
|
+
(consistent with destination `before`).
|
|
24
|
+
|
|
3
25
|
## 1.1.2
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -250,7 +250,10 @@
|
|
|
250
250
|
"this",
|
|
251
251
|
{
|
|
252
252
|
"map": {
|
|
253
|
-
"item_id":
|
|
253
|
+
"item_id": [
|
|
254
|
+
{ "key": "data.sku" },
|
|
255
|
+
{ "key": "data.id" }
|
|
256
|
+
],
|
|
254
257
|
"item_name": "data.name",
|
|
255
258
|
"item_category": "data.category",
|
|
256
259
|
"price": "data.price"
|
|
@@ -322,6 +325,12 @@
|
|
|
322
325
|
"url": "$var.apiUrl",
|
|
323
326
|
"batch": 5
|
|
324
327
|
},
|
|
328
|
+
"data": {
|
|
329
|
+
"map": {
|
|
330
|
+
"sent_at": { "fn": "$code:() => Date.now()" },
|
|
331
|
+
"flow_version": "$var.flowVersion"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
325
334
|
"mapping": {
|
|
326
335
|
"order": {
|
|
327
336
|
"complete": {
|
|
@@ -71,7 +71,7 @@ npx walkeros serve packages/cli/examples/flow-complete.json --flow web
|
|
|
71
71
|
|
|
72
72
|
## Feature Inventory
|
|
73
73
|
|
|
74
|
-
### Features Used (
|
|
74
|
+
### Features Used (53)
|
|
75
75
|
|
|
76
76
|
#### Mapping - Value Extraction
|
|
77
77
|
|
|
@@ -81,6 +81,7 @@ npx walkeros serve packages/cli/examples/flow-complete.json --flow web
|
|
|
81
81
|
| Static value | Meta ViewContent | `"content_type": { "value": "product" }` |
|
|
82
82
|
| Key with fallback | GA4 add_to_cart | `{ "key": "data.currency", "value": "$variables.currency" }` |
|
|
83
83
|
| Nested key (deep) | dataLayer mapping | `"items.0.item_id"` |
|
|
84
|
+
| Fallback array | GA4 view_item | `[{ "key": "data.sku" }, { "key": "data.id" }]` |
|
|
84
85
|
|
|
85
86
|
#### Mapping - Structure
|
|
86
87
|
|
|
@@ -92,6 +93,7 @@ npx walkeros serve packages/cli/examples/flow-complete.json --flow web
|
|
|
92
93
|
| Set (single value) | Meta ViewContent | `"content_ids": { "set": ["data.id"] }` |
|
|
93
94
|
| Set (multiple values) | Meta settings | `"external_id": { "set": ["user.device", "user.session"] }` |
|
|
94
95
|
| Direct passthrough | Meta PageView | `"data": "data"` |
|
|
96
|
+
| Config-level data | API destination | `"data": { "map": { "sent_at": {...} } }` |
|
|
95
97
|
|
|
96
98
|
#### Mapping - Control
|
|
97
99
|
|
|
@@ -175,36 +177,33 @@ npx walkeros serve packages/cli/examples/flow-complete.json --flow web
|
|
|
175
177
|
|
|
176
178
|
---
|
|
177
179
|
|
|
178
|
-
### Features NOT Used (
|
|
180
|
+
### Features NOT Used (6)
|
|
179
181
|
|
|
180
|
-
####
|
|
182
|
+
#### Now Available via $code: Prefix ✅
|
|
181
183
|
|
|
182
|
-
These features
|
|
184
|
+
These features are now fully supported in JSON via `$code:` prefix (and ARE used
|
|
185
|
+
in this example):
|
|
183
186
|
|
|
184
|
-
| Feature |
|
|
185
|
-
| --------------------------- |
|
|
186
|
-
| `fn:` function |
|
|
187
|
-
| `condition:` |
|
|
188
|
-
| Conditional mapping (array) |
|
|
189
|
-
| Custom transformer code |
|
|
190
|
-
| Custom
|
|
191
|
-
| Custom destination code | Requires JavaScript |
|
|
192
|
-
| Event handler callbacks | Requires JavaScript |
|
|
187
|
+
| Feature | Status |
|
|
188
|
+
| --------------------------- | ----------------------------------- |
|
|
189
|
+
| `fn:` function | ✅ Used via `$code:` in GA4 value |
|
|
190
|
+
| `condition:` | ✅ Used via `$code:` in definitions |
|
|
191
|
+
| Conditional mapping (array) | ✅ Used in serverValidator |
|
|
192
|
+
| Custom transformer code | ✅ Used in enricher, filter |
|
|
193
|
+
| Custom destination code | ✅ Used in debug logger |
|
|
193
194
|
|
|
194
|
-
#### Omitted for Clarity (
|
|
195
|
+
#### Omitted for Clarity (6)
|
|
195
196
|
|
|
196
197
|
These features could be added but were omitted to keep the example focused:
|
|
197
198
|
|
|
198
|
-
| Feature | Why Omitted
|
|
199
|
-
| ------------------------- |
|
|
200
|
-
| Multiple named flows (3+) | Two flows sufficient for demo
|
|
201
|
-
| Queue config | Advanced batching scenario
|
|
202
|
-
| Retry config | Advanced error handling
|
|
203
|
-
| Custom fetch options | API destination advanced
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
| Custom headers in API | Would add complexity |
|
|
207
|
-
| Multiple validators | One per flow sufficient |
|
|
199
|
+
| Feature | Why Omitted |
|
|
200
|
+
| ------------------------- | ------------------------------ |
|
|
201
|
+
| Multiple named flows (3+) | Two flows sufficient for demo |
|
|
202
|
+
| Queue config | Advanced batching scenario |
|
|
203
|
+
| Retry config | Advanced error handling |
|
|
204
|
+
| Custom fetch options | API destination advanced |
|
|
205
|
+
| Custom headers in API | Would add complexity |
|
|
206
|
+
| `validate:` function | Could add via $code: if needed |
|
|
208
207
|
|
|
209
208
|
---
|
|
210
209
|
|
package/dist/index.d.ts
CHANGED
|
@@ -419,4 +419,32 @@ declare function runCommand(mode: string, options: RunCommandOptions): Promise<v
|
|
|
419
419
|
*/
|
|
420
420
|
declare function run(mode: RunMode, options: RunOptions): Promise<RunResult>;
|
|
421
421
|
|
|
422
|
-
|
|
422
|
+
type ValidationType = 'event' | 'flow' | 'mapping';
|
|
423
|
+
interface ValidationError {
|
|
424
|
+
path: string;
|
|
425
|
+
message: string;
|
|
426
|
+
value?: unknown;
|
|
427
|
+
code?: string;
|
|
428
|
+
}
|
|
429
|
+
interface ValidationWarning {
|
|
430
|
+
path: string;
|
|
431
|
+
message: string;
|
|
432
|
+
suggestion?: string;
|
|
433
|
+
}
|
|
434
|
+
interface ValidateResult {
|
|
435
|
+
valid: boolean;
|
|
436
|
+
type: ValidationType;
|
|
437
|
+
errors: ValidationError[];
|
|
438
|
+
warnings: ValidationWarning[];
|
|
439
|
+
details: Record<string, unknown>;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Programmatic API for validation.
|
|
444
|
+
* Can be called directly from code or MCP server.
|
|
445
|
+
*/
|
|
446
|
+
declare function validate(type: ValidationType, input: unknown, options?: {
|
|
447
|
+
flow?: string;
|
|
448
|
+
}): Promise<ValidateResult>;
|
|
449
|
+
|
|
450
|
+
export { type BuildOptions, type BundleStats, type CLIBuildOptions, type GlobalOptions, type MinifyOptions, type RunCommandOptions, type RunMode, type RunOptions, type RunResult, type SimulationResult, type ValidateResult, type ValidationError, type ValidationType, type ValidationWarning, bundle, bundleCommand, pushCommand, run, runCommand, simulate, simulateCommand, validate };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk3 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
8
|
import { readFileSync } from "fs";
|
|
@@ -822,34 +822,34 @@ function validateReference(type, name, ref) {
|
|
|
822
822
|
throw new Error(`${type} "${name}": Must specify either package or code.`);
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
|
-
function generateInlineCode(inline, config, env) {
|
|
825
|
+
function generateInlineCode(inline, config, env, chain, chainPropertyName, isDestination) {
|
|
826
826
|
const pushFn = inline.push.replace("$code:", "");
|
|
827
827
|
const initFn = inline.init ? inline.init.replace("$code:", "") : void 0;
|
|
828
828
|
const typeLine = inline.type ? `type: '${inline.type}',` : "";
|
|
829
|
-
|
|
830
|
-
|
|
829
|
+
const chainLine = chain && chainPropertyName ? `${chainPropertyName}: ${JSON.stringify(chain)},` : "";
|
|
830
|
+
if (isDestination) {
|
|
831
|
+
return `{
|
|
832
|
+
code: {
|
|
831
833
|
${typeLine}
|
|
832
|
-
config:
|
|
834
|
+
config: ${JSON.stringify(config || {})},
|
|
833
835
|
${initFn ? `init: ${initFn},` : ""}
|
|
834
836
|
push: ${pushFn}
|
|
835
|
-
}
|
|
837
|
+
},
|
|
836
838
|
config: ${JSON.stringify(config || {})},
|
|
837
|
-
env: ${JSON.stringify(env || {})}
|
|
839
|
+
env: ${JSON.stringify(env || {})}${chain ? `,
|
|
840
|
+
${chainLine.slice(0, -1)}` : ""}
|
|
838
841
|
}`;
|
|
839
|
-
}
|
|
840
|
-
function generateInlineDestinationCode(inline, config, env) {
|
|
841
|
-
const pushFn = inline.push.replace("$code:", "");
|
|
842
|
-
const initFn = inline.init ? inline.init.replace("$code:", "") : void 0;
|
|
843
|
-
const typeLine = inline.type ? `type: '${inline.type}',` : "";
|
|
842
|
+
}
|
|
844
843
|
return `{
|
|
845
|
-
code: {
|
|
844
|
+
code: async (context) => ({
|
|
846
845
|
${typeLine}
|
|
847
|
-
config:
|
|
846
|
+
config: context.config,
|
|
848
847
|
${initFn ? `init: ${initFn},` : ""}
|
|
849
848
|
push: ${pushFn}
|
|
850
|
-
},
|
|
849
|
+
}),
|
|
851
850
|
config: ${JSON.stringify(config || {})},
|
|
852
|
-
env: ${JSON.stringify(env || {})}
|
|
851
|
+
env: ${JSON.stringify(env || {})}${chain ? `,
|
|
852
|
+
${chainLine.slice(0, -1)}` : ""}
|
|
853
853
|
}`;
|
|
854
854
|
}
|
|
855
855
|
async function copyIncludes(includes, sourceDir, outputDir, logger2) {
|
|
@@ -1406,7 +1406,7 @@ function buildConfigObject(flowConfig, explicitCodeImports) {
|
|
|
1406
1406
|
([, source]) => source.code !== true && (source.package || isInlineCode(source.code))
|
|
1407
1407
|
).map(([key, source]) => {
|
|
1408
1408
|
if (isInlineCode(source.code)) {
|
|
1409
|
-
return ` ${key}: ${generateInlineCode(source.code, source.config || {}, source.env)}`;
|
|
1409
|
+
return ` ${key}: ${generateInlineCode(source.code, source.config || {}, source.env, source.next, "next")}`;
|
|
1410
1410
|
}
|
|
1411
1411
|
let codeVar;
|
|
1412
1412
|
if (source.code && typeof source.code === "string" && explicitCodeImports.has(source.package)) {
|
|
@@ -1417,16 +1417,18 @@ function buildConfigObject(flowConfig, explicitCodeImports) {
|
|
|
1417
1417
|
const configStr = source.config ? processConfigValue(source.config) : "{}";
|
|
1418
1418
|
const envStr = source.env ? `,
|
|
1419
1419
|
env: ${processConfigValue(source.env)}` : "";
|
|
1420
|
+
const nextStr = source.next ? `,
|
|
1421
|
+
next: ${JSON.stringify(source.next)}` : "";
|
|
1420
1422
|
return ` ${key}: {
|
|
1421
1423
|
code: ${codeVar},
|
|
1422
|
-
config: ${configStr}${envStr}
|
|
1424
|
+
config: ${configStr}${envStr}${nextStr}
|
|
1423
1425
|
}`;
|
|
1424
1426
|
});
|
|
1425
1427
|
const destinationsEntries = Object.entries(destinations).filter(
|
|
1426
1428
|
([, dest]) => dest.code !== true && (dest.package || isInlineCode(dest.code))
|
|
1427
1429
|
).map(([key, dest]) => {
|
|
1428
1430
|
if (isInlineCode(dest.code)) {
|
|
1429
|
-
return ` ${key}: ${
|
|
1431
|
+
return ` ${key}: ${generateInlineCode(dest.code, dest.config || {}, dest.env, dest.before, "before", true)}`;
|
|
1430
1432
|
}
|
|
1431
1433
|
let codeVar;
|
|
1432
1434
|
if (dest.code && typeof dest.code === "string" && explicitCodeImports.has(dest.package)) {
|
|
@@ -1437,20 +1439,18 @@ function buildConfigObject(flowConfig, explicitCodeImports) {
|
|
|
1437
1439
|
const configStr = dest.config ? processConfigValue(dest.config) : "{}";
|
|
1438
1440
|
const envStr = dest.env ? `,
|
|
1439
1441
|
env: ${processConfigValue(dest.env)}` : "";
|
|
1442
|
+
const beforeStr = dest.before ? `,
|
|
1443
|
+
before: ${JSON.stringify(dest.before)}` : "";
|
|
1440
1444
|
return ` ${key}: {
|
|
1441
1445
|
code: ${codeVar},
|
|
1442
|
-
config: ${configStr}${envStr}
|
|
1446
|
+
config: ${configStr}${envStr}${beforeStr}
|
|
1443
1447
|
}`;
|
|
1444
1448
|
});
|
|
1445
1449
|
const transformersEntries = Object.entries(transformers).filter(
|
|
1446
1450
|
([, transformer]) => transformer.code !== true && (transformer.package || isInlineCode(transformer.code))
|
|
1447
1451
|
).map(([key, transformer]) => {
|
|
1448
1452
|
if (isInlineCode(transformer.code)) {
|
|
1449
|
-
|
|
1450
|
-
...transformer.config || {},
|
|
1451
|
-
next: transformer.next
|
|
1452
|
-
} : transformer.config || {};
|
|
1453
|
-
return ` ${key}: ${generateInlineCode(transformer.code, configWithNext2, transformer.env)}`;
|
|
1453
|
+
return ` ${key}: ${generateInlineCode(transformer.code, transformer.config || {}, transformer.env, transformer.next, "next")}`;
|
|
1454
1454
|
}
|
|
1455
1455
|
let codeVar;
|
|
1456
1456
|
if (transformer.code && typeof transformer.code === "string" && explicitCodeImports.has(transformer.package)) {
|
|
@@ -1458,13 +1458,14 @@ function buildConfigObject(flowConfig, explicitCodeImports) {
|
|
|
1458
1458
|
} else {
|
|
1459
1459
|
codeVar = packageNameToVariable(transformer.package);
|
|
1460
1460
|
}
|
|
1461
|
-
const
|
|
1462
|
-
const configStr = configWithNext ? processConfigValue(configWithNext) : "{}";
|
|
1461
|
+
const configStr = transformer.config ? processConfigValue(transformer.config) : "{}";
|
|
1463
1462
|
const envStr = transformer.env ? `,
|
|
1464
1463
|
env: ${processConfigValue(transformer.env)}` : "";
|
|
1464
|
+
const nextStr = transformer.next ? `,
|
|
1465
|
+
next: ${JSON.stringify(transformer.next)}` : "";
|
|
1465
1466
|
return ` ${key}: {
|
|
1466
1467
|
code: ${codeVar},
|
|
1467
|
-
config: ${configStr}${envStr}
|
|
1468
|
+
config: ${configStr}${envStr}${nextStr}
|
|
1468
1469
|
}`;
|
|
1469
1470
|
});
|
|
1470
1471
|
const collectorStr = flowWithProps.collector ? `,
|
|
@@ -2904,6 +2905,288 @@ async function run(mode, options) {
|
|
|
2904
2905
|
}
|
|
2905
2906
|
}
|
|
2906
2907
|
|
|
2908
|
+
// src/commands/validate/index.ts
|
|
2909
|
+
import chalk2 from "chalk";
|
|
2910
|
+
|
|
2911
|
+
// src/commands/validate/validators/event.ts
|
|
2912
|
+
import { schemas as schemas3 } from "@walkeros/core/dev";
|
|
2913
|
+
var { PartialEventSchema } = schemas3;
|
|
2914
|
+
function validateEvent(input) {
|
|
2915
|
+
const errors = [];
|
|
2916
|
+
const warnings = [];
|
|
2917
|
+
const details = {};
|
|
2918
|
+
const event = typeof input === "object" && input !== null ? input : {};
|
|
2919
|
+
if (!("name" in event) || event.name === void 0) {
|
|
2920
|
+
errors.push({
|
|
2921
|
+
path: "name",
|
|
2922
|
+
message: "Event must have a name field",
|
|
2923
|
+
code: "MISSING_EVENT_NAME"
|
|
2924
|
+
});
|
|
2925
|
+
} else if (typeof event.name !== "string" || event.name.trim() === "") {
|
|
2926
|
+
errors.push({
|
|
2927
|
+
path: "name",
|
|
2928
|
+
message: "Event name cannot be empty",
|
|
2929
|
+
value: event.name,
|
|
2930
|
+
code: "EMPTY_EVENT_NAME"
|
|
2931
|
+
});
|
|
2932
|
+
} else {
|
|
2933
|
+
const name = event.name;
|
|
2934
|
+
if (!name.includes(" ")) {
|
|
2935
|
+
errors.push({
|
|
2936
|
+
path: "name",
|
|
2937
|
+
message: 'Event name must be "entity action" format with space (e.g., "page view")',
|
|
2938
|
+
value: name,
|
|
2939
|
+
code: "INVALID_EVENT_NAME"
|
|
2940
|
+
});
|
|
2941
|
+
details.entity = null;
|
|
2942
|
+
details.action = null;
|
|
2943
|
+
} else {
|
|
2944
|
+
const parts = name.trim().split(/\s+/);
|
|
2945
|
+
const action = parts.pop();
|
|
2946
|
+
const entity = parts.join(" ");
|
|
2947
|
+
details.entity = entity;
|
|
2948
|
+
details.action = action;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
const zodResult = PartialEventSchema.safeParse(input);
|
|
2952
|
+
if (!zodResult.success) {
|
|
2953
|
+
for (const issue of zodResult.error.issues) {
|
|
2954
|
+
const path14 = issue.path.join(".");
|
|
2955
|
+
if (path14 === "name") continue;
|
|
2956
|
+
errors.push({
|
|
2957
|
+
path: path14 || "root",
|
|
2958
|
+
message: issue.message,
|
|
2959
|
+
code: "SCHEMA_VALIDATION"
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
if (!event.consent) {
|
|
2964
|
+
warnings.push({
|
|
2965
|
+
path: "consent",
|
|
2966
|
+
message: "No consent object provided",
|
|
2967
|
+
suggestion: "Consider adding a consent object for GDPR/privacy compliance"
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
details.hasConsent = !!event.consent;
|
|
2971
|
+
details.hasData = !!event.data;
|
|
2972
|
+
details.hasContext = !!event.context;
|
|
2973
|
+
return {
|
|
2974
|
+
valid: errors.length === 0,
|
|
2975
|
+
type: "event",
|
|
2976
|
+
errors,
|
|
2977
|
+
warnings,
|
|
2978
|
+
details
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// src/commands/validate/validators/flow.ts
|
|
2983
|
+
import { schemas as schemas4 } from "@walkeros/core/dev";
|
|
2984
|
+
var { SetupSchema } = schemas4;
|
|
2985
|
+
function validateFlow(input, options = {}) {
|
|
2986
|
+
const errors = [];
|
|
2987
|
+
const warnings = [];
|
|
2988
|
+
const details = {};
|
|
2989
|
+
const config = typeof input === "object" && input !== null ? input : {};
|
|
2990
|
+
const zodResult = SetupSchema.safeParse(input);
|
|
2991
|
+
if (!zodResult.success) {
|
|
2992
|
+
for (const issue of zodResult.error.issues) {
|
|
2993
|
+
const path14 = issue.path.join(".");
|
|
2994
|
+
errors.push({
|
|
2995
|
+
path: path14 || "root",
|
|
2996
|
+
message: issue.message,
|
|
2997
|
+
code: "SCHEMA_VALIDATION"
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
const flows = config.flows;
|
|
3002
|
+
if (flows && typeof flows === "object" && Object.keys(flows).length === 0) {
|
|
3003
|
+
errors.push({
|
|
3004
|
+
path: "flows",
|
|
3005
|
+
message: "At least one flow is required",
|
|
3006
|
+
code: "EMPTY_FLOWS"
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
if (flows && typeof flows === "object") {
|
|
3010
|
+
const flowNames = Object.keys(flows);
|
|
3011
|
+
details.flowNames = flowNames;
|
|
3012
|
+
details.flowCount = flowNames.length;
|
|
3013
|
+
if (options.flow) {
|
|
3014
|
+
if (!flowNames.includes(options.flow)) {
|
|
3015
|
+
errors.push({
|
|
3016
|
+
path: "flows",
|
|
3017
|
+
message: `Flow "${options.flow}" not found. Available: ${flowNames.join(", ")}`,
|
|
3018
|
+
code: "FLOW_NOT_FOUND"
|
|
3019
|
+
});
|
|
3020
|
+
} else {
|
|
3021
|
+
details.validatedFlow = options.flow;
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
const packages = config.packages;
|
|
3026
|
+
if (packages && typeof packages === "object") {
|
|
3027
|
+
for (const [pkgName, pkgConfig] of Object.entries(packages)) {
|
|
3028
|
+
if (!pkgConfig.version && !pkgConfig.path) {
|
|
3029
|
+
warnings.push({
|
|
3030
|
+
path: `packages.${pkgName}`,
|
|
3031
|
+
message: `Package "${pkgName}" has no version specified`,
|
|
3032
|
+
suggestion: "Consider specifying a version for reproducible builds"
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
details.packageCount = Object.keys(packages).length;
|
|
3037
|
+
}
|
|
3038
|
+
return {
|
|
3039
|
+
valid: errors.length === 0,
|
|
3040
|
+
type: "flow",
|
|
3041
|
+
errors,
|
|
3042
|
+
warnings,
|
|
3043
|
+
details
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/commands/validate/validators/mapping.ts
|
|
3048
|
+
function validateMapping(input) {
|
|
3049
|
+
const errors = [];
|
|
3050
|
+
const warnings = [];
|
|
3051
|
+
const details = {};
|
|
3052
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
3053
|
+
errors.push({
|
|
3054
|
+
path: "root",
|
|
3055
|
+
message: "Mapping must be an object with event patterns as keys",
|
|
3056
|
+
code: "INVALID_MAPPING_TYPE"
|
|
3057
|
+
});
|
|
3058
|
+
return { valid: false, type: "mapping", errors, warnings, details };
|
|
3059
|
+
}
|
|
3060
|
+
const mapping = input;
|
|
3061
|
+
const patterns = Object.keys(mapping);
|
|
3062
|
+
details.eventPatterns = patterns;
|
|
3063
|
+
details.patternCount = patterns.length;
|
|
3064
|
+
patterns.forEach((pattern, index) => {
|
|
3065
|
+
const isWildcard = pattern.includes("*");
|
|
3066
|
+
const hasSpace = pattern.includes(" ");
|
|
3067
|
+
if (!isWildcard && !hasSpace) {
|
|
3068
|
+
errors.push({
|
|
3069
|
+
path: pattern,
|
|
3070
|
+
message: `Invalid event pattern "${pattern}". Must be "entity action" format or contain wildcard (*)`,
|
|
3071
|
+
code: "INVALID_EVENT_PATTERN"
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
if (pattern === "*" && index !== patterns.length - 1) {
|
|
3075
|
+
warnings.push({
|
|
3076
|
+
path: "*",
|
|
3077
|
+
message: "Catch-all pattern (*) should be last",
|
|
3078
|
+
suggestion: "Move the catch-all pattern (*) to last position for predictable matching"
|
|
3079
|
+
});
|
|
3080
|
+
}
|
|
3081
|
+
const rule = mapping[pattern];
|
|
3082
|
+
const isValidRule = Array.isArray(rule) ? rule.every((r) => typeof r === "object" && r !== null) : typeof rule === "object" && rule !== null;
|
|
3083
|
+
if (!isValidRule) {
|
|
3084
|
+
errors.push({
|
|
3085
|
+
path: pattern,
|
|
3086
|
+
message: "Mapping rule must be an object or array of objects",
|
|
3087
|
+
code: "INVALID_RULE_TYPE"
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
return {
|
|
3092
|
+
valid: errors.length === 0,
|
|
3093
|
+
type: "mapping",
|
|
3094
|
+
errors,
|
|
3095
|
+
warnings,
|
|
3096
|
+
details
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/commands/validate/index.ts
|
|
3101
|
+
async function validate(type, input, options = {}) {
|
|
3102
|
+
switch (type) {
|
|
3103
|
+
case "event":
|
|
3104
|
+
return validateEvent(input);
|
|
3105
|
+
case "flow":
|
|
3106
|
+
return validateFlow(input, { flow: options.flow });
|
|
3107
|
+
case "mapping":
|
|
3108
|
+
return validateMapping(input);
|
|
3109
|
+
default:
|
|
3110
|
+
throw new Error(`Unknown validation type: ${type}`);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
function formatResult(result, options) {
|
|
3114
|
+
if (options.json) {
|
|
3115
|
+
return JSON.stringify(result, null, 2);
|
|
3116
|
+
}
|
|
3117
|
+
const lines = [];
|
|
3118
|
+
lines.push("");
|
|
3119
|
+
lines.push(`Validating ${result.type}...`);
|
|
3120
|
+
lines.push("");
|
|
3121
|
+
if (options.verbose && Object.keys(result.details).length > 0) {
|
|
3122
|
+
lines.push("Details:");
|
|
3123
|
+
for (const [key, value] of Object.entries(result.details)) {
|
|
3124
|
+
lines.push(` ${key}: ${JSON.stringify(value)}`);
|
|
3125
|
+
}
|
|
3126
|
+
lines.push("");
|
|
3127
|
+
}
|
|
3128
|
+
lines.push("Validation Results:");
|
|
3129
|
+
for (const error of result.errors) {
|
|
3130
|
+
lines.push(chalk2.red(` \u2717 ${error.path}: ${error.message}`));
|
|
3131
|
+
}
|
|
3132
|
+
for (const warning of result.warnings) {
|
|
3133
|
+
lines.push(chalk2.yellow(` \u26A0 ${warning.path}: ${warning.message}`));
|
|
3134
|
+
if (warning.suggestion) {
|
|
3135
|
+
lines.push(chalk2.gray(` \u2192 ${warning.suggestion}`));
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
if (result.valid) {
|
|
3139
|
+
lines.push(chalk2.green(` \u2713 All checks passed`));
|
|
3140
|
+
}
|
|
3141
|
+
lines.push("");
|
|
3142
|
+
lines.push(
|
|
3143
|
+
`Summary: ${result.errors.length} error(s), ${result.warnings.length} warning(s)`
|
|
3144
|
+
);
|
|
3145
|
+
return lines.join("\n");
|
|
3146
|
+
}
|
|
3147
|
+
async function validateCommand(options) {
|
|
3148
|
+
const logger2 = createCommandLogger(options);
|
|
3149
|
+
try {
|
|
3150
|
+
const input = await loadJsonFromSource(options.input, {
|
|
3151
|
+
name: options.type,
|
|
3152
|
+
required: true
|
|
3153
|
+
});
|
|
3154
|
+
const result = await validate(options.type, input, {
|
|
3155
|
+
flow: options.flow
|
|
3156
|
+
});
|
|
3157
|
+
const output = formatResult(result, {
|
|
3158
|
+
json: options.json,
|
|
3159
|
+
verbose: options.verbose
|
|
3160
|
+
});
|
|
3161
|
+
if (options.json) {
|
|
3162
|
+
console.log(output);
|
|
3163
|
+
} else {
|
|
3164
|
+
logger2.log(output);
|
|
3165
|
+
}
|
|
3166
|
+
if (!result.valid) {
|
|
3167
|
+
process.exit(1);
|
|
3168
|
+
}
|
|
3169
|
+
if (options.strict && result.warnings.length > 0) {
|
|
3170
|
+
process.exit(2);
|
|
3171
|
+
}
|
|
3172
|
+
process.exit(0);
|
|
3173
|
+
} catch (error) {
|
|
3174
|
+
const errorMessage = getErrorMessage(error);
|
|
3175
|
+
if (options.json) {
|
|
3176
|
+
logger2.json({
|
|
3177
|
+
valid: false,
|
|
3178
|
+
type: options.type,
|
|
3179
|
+
errors: [{ path: "input", message: errorMessage, code: "INPUT_ERROR" }],
|
|
3180
|
+
warnings: [],
|
|
3181
|
+
details: {}
|
|
3182
|
+
});
|
|
3183
|
+
} else {
|
|
3184
|
+
logger2.error(`Error: ${errorMessage}`);
|
|
3185
|
+
}
|
|
3186
|
+
process.exit(3);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
2907
3190
|
// src/commands/cache.ts
|
|
2908
3191
|
import fs13 from "fs-extra";
|
|
2909
3192
|
function registerCacheCommand(program2) {
|
|
@@ -2954,7 +3237,7 @@ program.name("walkeros").description("walkerOS CLI - Bundle and deploy walkerOS
|
|
|
2954
3237
|
program.hook("preAction", (thisCommand, actionCommand) => {
|
|
2955
3238
|
const options = actionCommand.opts();
|
|
2956
3239
|
if (!options.silent && !options.json) {
|
|
2957
|
-
console.log(`${
|
|
3240
|
+
console.log(`${chalk3.hex("#01b5e2")("walkerOS")} v${VERSION}`);
|
|
2958
3241
|
}
|
|
2959
3242
|
});
|
|
2960
3243
|
program.command("bundle [file]").description("Bundle NPM packages with custom code").option("--flow <name>", "flow name for multi-flow configs").option("--all", "build all flows for multi-flow configs").option("--stats", "show bundle statistics").option("--json", "output as JSON (implies --stats)").option("--no-cache", "disable package caching").option("-v, --verbose", "verbose output").option("-s, --silent", "suppress output").option(
|
|
@@ -3001,6 +3284,17 @@ program.command("push [file]").description("Push an event through the flow with
|
|
|
3001
3284
|
silent: options.silent
|
|
3002
3285
|
});
|
|
3003
3286
|
});
|
|
3287
|
+
program.command("validate <type> [input]").description("Validate event, flow, or mapping configuration").option("--flow <name>", "flow name for multi-flow configs").option("--json", "output as JSON").option("-v, --verbose", "verbose output").option("-s, --silent", "suppress output").option("--strict", "fail on warnings").action(async (type, input, options) => {
|
|
3288
|
+
await validateCommand({
|
|
3289
|
+
type,
|
|
3290
|
+
input,
|
|
3291
|
+
flow: options.flow,
|
|
3292
|
+
json: options.json,
|
|
3293
|
+
verbose: options.verbose,
|
|
3294
|
+
silent: options.silent,
|
|
3295
|
+
strict: options.strict
|
|
3296
|
+
});
|
|
3297
|
+
});
|
|
3004
3298
|
var runCmd = program.command("run").description("Run walkerOS flows in collect or serve mode");
|
|
3005
3299
|
runCmd.command("collect [file]").description(
|
|
3006
3300
|
"Run collector mode (event collection endpoint). Defaults to server-collect.mjs if no file specified."
|
|
@@ -3038,6 +3332,7 @@ export {
|
|
|
3038
3332
|
run,
|
|
3039
3333
|
runCommand,
|
|
3040
3334
|
simulate,
|
|
3041
|
-
simulateCommand
|
|
3335
|
+
simulateCommand,
|
|
3336
|
+
validate
|
|
3042
3337
|
};
|
|
3043
3338
|
//# sourceMappingURL=index.js.map
|