@walkeros/cli 1.1.3 → 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 CHANGED
@@ -1,5 +1,17 @@
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
+
3
15
  ## 1.1.3
4
16
 
5
17
  ### Patch Changes
@@ -250,7 +250,10 @@
250
250
  "this",
251
251
  {
252
252
  "map": {
253
- "item_id": "data.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 (51)
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 (15)
180
+ ### Features NOT Used (6)
179
181
 
180
- #### Requires JavaScript (7)
182
+ #### Now Available via $code: Prefix ✅
181
183
 
182
- These features cannot be used in pure JSON configurations:
184
+ These features are now fully supported in JSON via `$code:` prefix (and ARE used
185
+ in this example):
183
186
 
184
- | Feature | Reason |
185
- | --------------------------- | ----------------------------- |
186
- | `fn:` function | Requires JavaScript callback |
187
- | `condition:` | Requires JavaScript predicate |
188
- | Conditional mapping (array) | Requires condition functions |
189
- | Custom transformer code | Requires JavaScript |
190
- | Custom source code | Requires JavaScript |
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 (8)
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
- | Dynamic routing | Requires condition logic |
205
- | Transform before send | Covered by policy |
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
- export { type BuildOptions, type BundleStats, type CLIBuildOptions, type GlobalOptions, type MinifyOptions, type RunCommandOptions, type RunMode, type RunOptions, type RunResult, type SimulationResult, bundle, bundleCommand, pushCommand, run, runCommand, simulate, simulateCommand };
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 chalk2 from "chalk";
5
+ import chalk3 from "chalk";
6
6
 
7
7
  // src/version.ts
8
8
  import { readFileSync } from "fs";
@@ -2905,6 +2905,288 @@ async function run(mode, options) {
2905
2905
  }
2906
2906
  }
2907
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
+
2908
3190
  // src/commands/cache.ts
2909
3191
  import fs13 from "fs-extra";
2910
3192
  function registerCacheCommand(program2) {
@@ -2955,7 +3237,7 @@ program.name("walkeros").description("walkerOS CLI - Bundle and deploy walkerOS
2955
3237
  program.hook("preAction", (thisCommand, actionCommand) => {
2956
3238
  const options = actionCommand.opts();
2957
3239
  if (!options.silent && !options.json) {
2958
- console.log(`${chalk2.hex("#01b5e2")("walkerOS")} v${VERSION}`);
3240
+ console.log(`${chalk3.hex("#01b5e2")("walkerOS")} v${VERSION}`);
2959
3241
  }
2960
3242
  });
2961
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(
@@ -3002,6 +3284,17 @@ program.command("push [file]").description("Push an event through the flow with
3002
3284
  silent: options.silent
3003
3285
  });
3004
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
+ });
3005
3298
  var runCmd = program.command("run").description("Run walkerOS flows in collect or serve mode");
3006
3299
  runCmd.command("collect [file]").description(
3007
3300
  "Run collector mode (event collection endpoint). Defaults to server-collect.mjs if no file specified."
@@ -3039,6 +3332,7 @@ export {
3039
3332
  run,
3040
3333
  runCommand,
3041
3334
  simulate,
3042
- simulateCommand
3335
+ simulateCommand,
3336
+ validate
3043
3337
  };
3044
3338
  //# sourceMappingURL=index.js.map