aranea-sdk-cli 0.1.0 → 0.1.1
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 +4 -2
- package/dist/commands/schema.d.ts +2 -1
- package/dist/commands/schema.js +271 -202
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# aranea-sdk-cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/aranea-sdk-cli)
|
|
2
4
|
|
|
3
5
|
AraneaSDK CLI - ESP32 IoTデバイス開発支援ツール
|
|
4
6
|
|
|
@@ -9,7 +11,7 @@ AraneaSDK CLIは、AraneaSDK対応ESP32デバイスの開発・テスト・登
|
|
|
9
11
|
## インストール
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
npm install -g
|
|
14
|
+
npm install -g aranea-sdk-cli
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
## クイックスタート
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* schema
|
|
2
|
+
* schema command
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* aranea-sdk schema get --type "aranea_ar-is04a"
|
|
6
6
|
* aranea-sdk schema list
|
|
7
|
+
* aranea-sdk schema validate --type "aranea_ar-is04a" --file state.json
|
|
7
8
|
*/
|
|
8
9
|
import { Command } from 'commander';
|
|
9
10
|
export declare const schemaCommand: Command;
|
package/dist/commands/schema.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* schema
|
|
3
|
+
* schema command
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* aranea-sdk schema get --type "aranea_ar-is04a"
|
|
7
7
|
* aranea-sdk schema list
|
|
8
|
+
* aranea-sdk schema validate --type "aranea_ar-is04a" --file state.json
|
|
8
9
|
*/
|
|
9
10
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
11
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -14,268 +15,336 @@ exports.schemaCommand = void 0;
|
|
|
14
15
|
const commander_1 = require("commander");
|
|
15
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
16
17
|
const ora_1 = __importDefault(require("ora"));
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
commandFields: {},
|
|
31
|
-
},
|
|
32
|
-
'aranea_ar-is04a': {
|
|
33
|
-
type: 'aranea_ar-is04a',
|
|
34
|
-
displayName: 'Network Scanner',
|
|
35
|
-
description: 'ネットワークスキャナー (ESP32)',
|
|
36
|
-
productType: '004',
|
|
37
|
-
stateFields: {
|
|
38
|
-
scanMode: { type: 'string', description: 'スキャンモード (arp/ping/passive)' },
|
|
39
|
-
discoveredHosts: { type: 'number', description: '発見ホスト数' },
|
|
40
|
-
lastScanAt: { type: 'string', description: '最終スキャン時刻 (ISO8601)' },
|
|
41
|
-
RSSI: { type: 'number', description: 'WiFi信号強度 (dBm)' },
|
|
42
|
-
},
|
|
43
|
-
commandFields: {
|
|
44
|
-
startScan: { type: 'boolean', description: 'スキャン開始' },
|
|
45
|
-
setScanMode: { type: 'string', description: 'スキャンモード設定' },
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
'aranea_ar-is05a': {
|
|
49
|
-
type: 'aranea_ar-is05a',
|
|
50
|
-
displayName: 'Environment Sensor',
|
|
51
|
-
description: '環境センサー (ESP32)',
|
|
52
|
-
productType: '005',
|
|
53
|
-
stateFields: {
|
|
54
|
-
temperature: { type: 'number', description: '温度 (℃)' },
|
|
55
|
-
humidity: { type: 'number', description: '湿度 (%)' },
|
|
56
|
-
pressure: { type: 'number', description: '気圧 (hPa)' },
|
|
57
|
-
co2: { type: 'number', description: 'CO2濃度 (ppm)' },
|
|
58
|
-
RSSI: { type: 'number', description: 'WiFi信号強度 (dBm)' },
|
|
59
|
-
},
|
|
60
|
-
commandFields: {
|
|
61
|
-
calibrate: { type: 'boolean', description: 'センサーキャリブレーション' },
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
'aranea_ar-is06a': {
|
|
65
|
-
type: 'aranea_ar-is06a',
|
|
66
|
-
displayName: 'Power Monitor',
|
|
67
|
-
description: '電力モニター (ESP32)',
|
|
68
|
-
productType: '006',
|
|
69
|
-
stateFields: {
|
|
70
|
-
voltage: { type: 'number', description: '電圧 (V)' },
|
|
71
|
-
current: { type: 'number', description: '電流 (A)' },
|
|
72
|
-
power: { type: 'number', description: '電力 (W)' },
|
|
73
|
-
energy: { type: 'number', description: '積算電力量 (Wh)' },
|
|
74
|
-
RSSI: { type: 'number', description: 'WiFi信号強度 (dBm)' },
|
|
75
|
-
},
|
|
76
|
-
commandFields: {
|
|
77
|
-
resetEnergy: { type: 'boolean', description: '積算電力量リセット' },
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
'aranea_ar-is10': {
|
|
81
|
-
type: 'aranea_ar-is10',
|
|
82
|
-
displayName: 'Router Inspector',
|
|
83
|
-
description: 'OpenWrt/AsusWRT ルーターインスペクター',
|
|
84
|
-
productType: '010',
|
|
85
|
-
stateFields: {
|
|
86
|
-
routerType: { type: 'string', description: 'ルータータイプ (openwrt/asuswrt)' },
|
|
87
|
-
connectedClients: { type: 'number', description: '接続クライアント数' },
|
|
88
|
-
wanStatus: { type: 'string', description: 'WAN状態' },
|
|
89
|
-
uptime: { type: 'number', description: '稼働時間 (秒)' },
|
|
90
|
-
},
|
|
91
|
-
commandFields: {
|
|
92
|
-
refresh: { type: 'boolean', description: '状態更新' },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
};
|
|
18
|
+
const axios_1 = __importDefault(require("axios"));
|
|
19
|
+
// API Configuration
|
|
20
|
+
const SCHEMA_API_BASE = 'https://asia-northeast1-mobesorder.cloudfunctions.net/araneaSchemaAPI';
|
|
21
|
+
// API Functions
|
|
22
|
+
async function fetchSchemaList() {
|
|
23
|
+
const response = await axios_1.default.get(`${SCHEMA_API_BASE}?action=list`);
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
async function fetchSchema(type) {
|
|
27
|
+
const response = await axios_1.default.get(`${SCHEMA_API_BASE}?action=get&type=${encodeURIComponent(type)}`);
|
|
28
|
+
return response.data;
|
|
29
|
+
}
|
|
96
30
|
exports.schemaCommand = new commander_1.Command('schema')
|
|
97
|
-
.description('Type
|
|
31
|
+
.description('Type schema retrieval and validation');
|
|
98
32
|
// schema list
|
|
99
33
|
exports.schemaCommand
|
|
100
34
|
.command('list')
|
|
101
|
-
.description('
|
|
102
|
-
.action(() => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
35
|
+
.description('List available Type schemas')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
const spinner = (0, ora_1.default)('Fetching schema list from API...').start();
|
|
38
|
+
try {
|
|
39
|
+
const result = await fetchSchemaList();
|
|
40
|
+
spinner.stop();
|
|
41
|
+
console.log(chalk_1.default.bold('\n=== AraneaDevice Type Schema List ===\n'));
|
|
42
|
+
if (!result.ok || result.count === 0) {
|
|
43
|
+
console.log(chalk_1.default.yellow('No schemas found.'));
|
|
44
|
+
console.log(chalk_1.default.gray('Run schema registration script to initialize schemas.'));
|
|
45
|
+
console.log('');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk_1.default.cyan('Registered Types:'));
|
|
112
49
|
console.log('');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
50
|
+
for (const schema of result.schemas) {
|
|
51
|
+
console.log(` ${chalk_1.default.green(schema.type)}`);
|
|
52
|
+
console.log(` ${schema.displayName} - ${schema.description}`);
|
|
53
|
+
console.log(` ProductType: ${schema.productType}`);
|
|
54
|
+
console.log(` Capabilities: ${schema.capabilities.join(', ')}`);
|
|
55
|
+
console.log('');
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk_1.default.gray(`Total: ${result.count} types`));
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
spinner.fail('Failed to fetch schema list');
|
|
62
|
+
if (error.response?.status === 404 || error.code === 'ENOTFOUND') {
|
|
63
|
+
console.error(chalk_1.default.red('\nSchema API is not available.'));
|
|
64
|
+
console.error(chalk_1.default.yellow('Please deploy the araneaSchemaAPI function first.'));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.error(chalk_1.default.red(`\nError: ${error.message}`));
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
116
71
|
});
|
|
117
72
|
// schema get
|
|
118
73
|
exports.schemaCommand
|
|
119
74
|
.command('get')
|
|
120
|
-
.description('
|
|
121
|
-
.requiredOption('-t, --type <type>', '
|
|
122
|
-
.option('--json', 'JSON
|
|
123
|
-
.action((options) => {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
75
|
+
.description('Get detailed schema for a specific Type')
|
|
76
|
+
.requiredOption('-t, --type <type>', 'Type name to retrieve')
|
|
77
|
+
.option('--json', 'Output as JSON')
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
const spinner = (0, ora_1.default)(`Fetching schema for ${options.type}...`).start();
|
|
80
|
+
try {
|
|
81
|
+
const result = await fetchSchema(options.type);
|
|
82
|
+
spinner.stop();
|
|
83
|
+
if (!result.ok) {
|
|
84
|
+
console.error(chalk_1.default.red(`\nSchema "${options.type}" not found`));
|
|
85
|
+
console.log('');
|
|
86
|
+
if (result.availableTypes && result.availableTypes.length > 0) {
|
|
87
|
+
console.log(chalk_1.default.yellow('Available Types:'));
|
|
88
|
+
result.availableTypes.forEach((t) => {
|
|
89
|
+
console.log(` - ${t}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
console.log('');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const schema = result.schema;
|
|
96
|
+
if (options.json) {
|
|
97
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk_1.default.bold(`\n=== ${schema.type} Schema ===\n`));
|
|
101
|
+
console.log(`DisplayName: ${chalk_1.default.cyan(schema.displayName)}`);
|
|
102
|
+
console.log(`Description: ${schema.description}`);
|
|
103
|
+
console.log(`ProductType: ${schema.productType}`);
|
|
104
|
+
console.log(`Capabilities: ${schema.capabilities.join(', ')}`);
|
|
105
|
+
console.log(`Version: ${schema.version}`);
|
|
128
106
|
console.log('');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log(
|
|
132
|
-
|
|
107
|
+
// Features
|
|
108
|
+
if (schema.features && schema.features.length > 0) {
|
|
109
|
+
console.log(chalk_1.default.bold('Features:'));
|
|
110
|
+
schema.features.forEach((f) => {
|
|
111
|
+
console.log(` - ${f}`);
|
|
112
|
+
});
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
// State Schema
|
|
116
|
+
console.log(chalk_1.default.bold('State Schema (deviceState fields):'));
|
|
117
|
+
const stateProps = schema.stateSchema?.properties || {};
|
|
118
|
+
const stateRequired = schema.stateSchema?.required || [];
|
|
119
|
+
const stateEntries = Object.entries(stateProps);
|
|
120
|
+
if (stateEntries.length === 0) {
|
|
121
|
+
console.log(chalk_1.default.gray(' (none)'));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
stateEntries.forEach(([name, def]) => {
|
|
125
|
+
const required = stateRequired.includes(name) ? chalk_1.default.red('*') : '';
|
|
126
|
+
const type = def.type || 'any';
|
|
127
|
+
const desc = def.description || '';
|
|
128
|
+
console.log(` ${chalk_1.default.green(name)}${required}: ${chalk_1.default.yellow(type)}`);
|
|
129
|
+
if (desc) {
|
|
130
|
+
console.log(` ${desc}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
133
134
|
console.log('');
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
console.log(chalk_1.default.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
135
|
+
// Command Schema
|
|
136
|
+
if (schema.commandSchema) {
|
|
137
|
+
console.log(chalk_1.default.bold('Command Schema (available commands):'));
|
|
138
|
+
const cmdProps = schema.commandSchema?.properties || {};
|
|
139
|
+
const cmdEntries = Object.entries(cmdProps);
|
|
140
|
+
if (cmdEntries.length === 0) {
|
|
141
|
+
console.log(chalk_1.default.gray(' (none)'));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
cmdEntries.forEach(([name, def]) => {
|
|
145
|
+
const desc = def.description || '';
|
|
146
|
+
console.log(` ${chalk_1.default.green(name)}`);
|
|
147
|
+
if (desc) {
|
|
148
|
+
console.log(` ${desc}`);
|
|
149
|
+
}
|
|
150
|
+
// Show properties if any
|
|
151
|
+
if (def.properties) {
|
|
152
|
+
Object.entries(def.properties).forEach(([propName, propDef]) => {
|
|
153
|
+
const propType = propDef.type || 'any';
|
|
154
|
+
const propDesc = propDef.description || '';
|
|
155
|
+
console.log(` ${chalk_1.default.cyan(propName)}: ${chalk_1.default.yellow(propType)} ${propDesc}`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
console.log('');
|
|
161
|
+
}
|
|
162
|
+
// Sample State Report
|
|
163
|
+
console.log(chalk_1.default.bold('Sample State Report Request:'));
|
|
164
|
+
const sampleState = {};
|
|
165
|
+
// Generate sample values from state schema
|
|
166
|
+
Object.entries(stateProps).forEach(([name, def]) => {
|
|
167
|
+
const type = def.type;
|
|
168
|
+
if (type === 'boolean') {
|
|
169
|
+
sampleState[name] = true;
|
|
170
|
+
}
|
|
171
|
+
else if (type === 'number' || type === 'integer') {
|
|
172
|
+
if (name.toLowerCase().includes('rssi')) {
|
|
173
|
+
sampleState[name] = -65;
|
|
174
|
+
}
|
|
175
|
+
else if (def.minimum !== undefined) {
|
|
176
|
+
sampleState[name] = def.minimum;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
sampleState[name] = 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (type === 'string') {
|
|
183
|
+
sampleState[name] = def.format === 'date-time' ? new Date().toISOString() : `sample_${name}`;
|
|
184
|
+
}
|
|
185
|
+
else if (type === 'object') {
|
|
186
|
+
sampleState[name] = {};
|
|
187
|
+
}
|
|
188
|
+
else if (type === 'array') {
|
|
189
|
+
sampleState[name] = [];
|
|
190
|
+
}
|
|
168
191
|
});
|
|
192
|
+
const sampleRequest = {
|
|
193
|
+
auth: {
|
|
194
|
+
tid: 'T9999999999999999999',
|
|
195
|
+
lacisId: `3${schema.productType.padStart(3, '0')}AABBCCDDEEFF0001`,
|
|
196
|
+
cic: '******',
|
|
197
|
+
},
|
|
198
|
+
report: {
|
|
199
|
+
type: schema.type,
|
|
200
|
+
state: sampleState,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
console.log(chalk_1.default.gray(JSON.stringify(sampleRequest, null, 2)));
|
|
204
|
+
console.log('');
|
|
169
205
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
206
|
+
catch (error) {
|
|
207
|
+
spinner.fail('Failed to fetch schema');
|
|
208
|
+
if (error.response?.status === 404) {
|
|
209
|
+
console.error(chalk_1.default.red(`\nSchema "${options.type}" not found`));
|
|
210
|
+
// Try to show available types
|
|
211
|
+
try {
|
|
212
|
+
const listResult = await fetchSchemaList();
|
|
213
|
+
if (listResult.ok && listResult.count > 0) {
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(chalk_1.default.yellow('Available Types:'));
|
|
216
|
+
listResult.schemas.forEach((s) => {
|
|
217
|
+
console.log(` - ${s.type}`);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Ignore
|
|
223
|
+
}
|
|
187
224
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
},
|
|
195
|
-
report: {
|
|
196
|
-
type: schema.type,
|
|
197
|
-
state: sampleState,
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
console.log(chalk_1.default.gray(JSON.stringify(sampleRequest, null, 2)));
|
|
201
|
-
console.log('');
|
|
225
|
+
else {
|
|
226
|
+
console.error(chalk_1.default.red(`\nError: ${error.message}`));
|
|
227
|
+
}
|
|
228
|
+
console.log('');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
202
231
|
});
|
|
203
232
|
// schema validate
|
|
204
233
|
exports.schemaCommand
|
|
205
234
|
.command('validate')
|
|
206
|
-
.description('
|
|
207
|
-
.requiredOption('-t, --type <type>', 'Type
|
|
208
|
-
.requiredOption('-f, --file <path>', 'JSON
|
|
209
|
-
.action((options) => {
|
|
210
|
-
const spinner = (0, ora_1.default)('
|
|
235
|
+
.description('Validate a state report JSON against schema')
|
|
236
|
+
.requiredOption('-t, --type <type>', 'Type name')
|
|
237
|
+
.requiredOption('-f, --file <path>', 'JSON file path')
|
|
238
|
+
.action(async (options) => {
|
|
239
|
+
const spinner = (0, ora_1.default)('Validating...').start();
|
|
211
240
|
try {
|
|
212
241
|
const fs = require('fs');
|
|
213
242
|
const path = require('path');
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
if (!
|
|
217
|
-
spinner.fail(`
|
|
243
|
+
// Fetch schema from API
|
|
244
|
+
const schemaResult = await fetchSchema(options.type);
|
|
245
|
+
if (!schemaResult.ok) {
|
|
246
|
+
spinner.fail(`Schema "${options.type}" not found`);
|
|
218
247
|
process.exit(1);
|
|
219
248
|
}
|
|
249
|
+
const schema = schemaResult.schema;
|
|
250
|
+
// Read input file
|
|
220
251
|
const filePath = path.resolve(options.file);
|
|
221
252
|
if (!fs.existsSync(filePath)) {
|
|
222
|
-
spinner.fail(
|
|
253
|
+
spinner.fail(`File not found: ${filePath}`);
|
|
223
254
|
process.exit(1);
|
|
224
255
|
}
|
|
225
256
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
226
257
|
const data = JSON.parse(fileContent);
|
|
227
258
|
spinner.stop();
|
|
228
|
-
console.log(chalk_1.default.bold(`\n===
|
|
259
|
+
console.log(chalk_1.default.bold(`\n=== Schema Validation: ${options.type} ===\n`));
|
|
229
260
|
const issues = [];
|
|
230
261
|
const warnings = [];
|
|
231
|
-
// state
|
|
262
|
+
// Extract state from input
|
|
232
263
|
const state = data.report?.state || data.state || data;
|
|
233
|
-
|
|
264
|
+
// Get state schema properties
|
|
265
|
+
const stateProps = schema.stateSchema?.properties || {};
|
|
266
|
+
const stateRequired = schema.stateSchema?.required || [];
|
|
267
|
+
// Check required fields
|
|
268
|
+
stateRequired.forEach((fieldName) => {
|
|
234
269
|
if (!(fieldName in state)) {
|
|
235
|
-
|
|
270
|
+
issues.push(`Required field "${fieldName}" is missing`);
|
|
236
271
|
}
|
|
237
|
-
|
|
272
|
+
});
|
|
273
|
+
// Validate field types
|
|
274
|
+
Object.entries(stateProps).forEach(([fieldName, fieldDef]) => {
|
|
275
|
+
if (fieldName in state) {
|
|
238
276
|
const value = state[fieldName];
|
|
239
|
-
const actualType = typeof value;
|
|
277
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
240
278
|
const expectedType = fieldDef.type;
|
|
241
|
-
if (expectedType === 'number'
|
|
242
|
-
|
|
279
|
+
if (expectedType === 'number' || expectedType === 'integer') {
|
|
280
|
+
if (actualType !== 'number') {
|
|
281
|
+
issues.push(`"${fieldName}": expected number but got ${actualType}`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// Check range
|
|
285
|
+
if (fieldDef.minimum !== undefined && value < fieldDef.minimum) {
|
|
286
|
+
issues.push(`"${fieldName}": value ${value} is below minimum ${fieldDef.minimum}`);
|
|
287
|
+
}
|
|
288
|
+
if (fieldDef.maximum !== undefined && value > fieldDef.maximum) {
|
|
289
|
+
issues.push(`"${fieldName}": value ${value} is above maximum ${fieldDef.maximum}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (expectedType === 'string') {
|
|
294
|
+
if (actualType !== 'string') {
|
|
295
|
+
issues.push(`"${fieldName}": expected string but got ${actualType}`);
|
|
296
|
+
}
|
|
243
297
|
}
|
|
244
|
-
else if (expectedType === '
|
|
245
|
-
|
|
298
|
+
else if (expectedType === 'boolean') {
|
|
299
|
+
if (actualType !== 'boolean') {
|
|
300
|
+
issues.push(`"${fieldName}": expected boolean but got ${actualType}`);
|
|
301
|
+
}
|
|
246
302
|
}
|
|
247
|
-
else if (expectedType === '
|
|
248
|
-
|
|
303
|
+
else if (expectedType === 'object') {
|
|
304
|
+
if (actualType !== 'object' || Array.isArray(value)) {
|
|
305
|
+
issues.push(`"${fieldName}": expected object but got ${actualType}`);
|
|
306
|
+
}
|
|
249
307
|
}
|
|
308
|
+
else if (expectedType === 'array') {
|
|
309
|
+
if (!Array.isArray(value)) {
|
|
310
|
+
issues.push(`"${fieldName}": expected array but got ${actualType}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (!stateRequired.includes(fieldName)) {
|
|
315
|
+
warnings.push(`Optional field "${fieldName}" is not present`);
|
|
250
316
|
}
|
|
251
317
|
});
|
|
252
|
-
//
|
|
318
|
+
// Check for unknown fields
|
|
253
319
|
Object.keys(state).forEach((key) => {
|
|
254
|
-
if (!(key in
|
|
255
|
-
warnings.push(
|
|
320
|
+
if (!(key in stateProps)) {
|
|
321
|
+
warnings.push(`Unknown field "${key}" (will be ignored)`);
|
|
256
322
|
}
|
|
257
323
|
});
|
|
258
|
-
//
|
|
324
|
+
// Show results
|
|
259
325
|
if (issues.length === 0) {
|
|
260
|
-
console.log(chalk_1.default.green('
|
|
326
|
+
console.log(chalk_1.default.green('Validation passed'));
|
|
261
327
|
}
|
|
262
328
|
else {
|
|
263
|
-
console.log(chalk_1.default.red('
|
|
329
|
+
console.log(chalk_1.default.red('Validation errors:'));
|
|
264
330
|
issues.forEach((issue) => {
|
|
265
331
|
console.log(chalk_1.default.red(` - ${issue}`));
|
|
266
332
|
});
|
|
267
333
|
}
|
|
268
334
|
if (warnings.length > 0) {
|
|
269
335
|
console.log('');
|
|
270
|
-
console.log(chalk_1.default.yellow('
|
|
336
|
+
console.log(chalk_1.default.yellow('Warnings:'));
|
|
271
337
|
warnings.forEach((warning) => {
|
|
272
338
|
console.log(chalk_1.default.yellow(` - ${warning}`));
|
|
273
339
|
});
|
|
274
340
|
}
|
|
275
341
|
console.log('');
|
|
342
|
+
if (issues.length > 0) {
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
276
345
|
}
|
|
277
346
|
catch (error) {
|
|
278
|
-
spinner.fail(
|
|
347
|
+
spinner.fail(`Validation error: ${error.message}`);
|
|
279
348
|
process.exit(1);
|
|
280
349
|
}
|
|
281
350
|
});
|