@a-company/paradigm 1.5.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/README.md +142 -0
- package/dist/accept-orchestration-CWZNCGZX.js +188 -0
- package/dist/agents-suggest-35LIQKDH.js +83 -0
- package/dist/aggregate-W7Q6VIM2.js +88 -0
- package/dist/auto-IU7VN55K.js +470 -0
- package/dist/beacon-B47XSTL7.js +251 -0
- package/dist/chunk-2M6OSOIG.js +1302 -0
- package/dist/chunk-4NCFWYGG.js +110 -0
- package/dist/chunk-5C4SGQKH.js +705 -0
- package/dist/chunk-5GOA7WYD.js +1095 -0
- package/dist/chunk-5JGJACDU.js +37 -0
- package/dist/chunk-6QC3YGB6.js +114 -0
- package/dist/chunk-753RICFF.js +325 -0
- package/dist/chunk-AD2LSCHB.js +1595 -0
- package/dist/chunk-CHSHON3O.js +669 -0
- package/dist/chunk-ELLR7WP6.js +3175 -0
- package/dist/chunk-ILOWBJRC.js +12 -0
- package/dist/chunk-IRKUEJVW.js +405 -0
- package/dist/chunk-MC7XC7XQ.js +533 -0
- package/dist/chunk-MO4EEYFW.js +38 -0
- package/dist/chunk-MQWH7PFI.js +13366 -0
- package/dist/chunk-N6PJAPDE.js +364 -0
- package/dist/chunk-PBHIFAL4.js +259 -0
- package/dist/chunk-PMXRGPRQ.js +305 -0
- package/dist/chunk-PW2EXJQT.js +689 -0
- package/dist/chunk-TAP5N3HH.js +245 -0
- package/dist/chunk-THFVK5AE.js +148 -0
- package/dist/chunk-UM54F7G5.js +1533 -0
- package/dist/chunk-UUZ2DMG5.js +185 -0
- package/dist/chunk-WS5KM7OL.js +780 -0
- package/dist/chunk-YDNKXH4Z.js +2316 -0
- package/dist/chunk-YO6DVTL7.js +99 -0
- package/dist/claude-SUYNN72C.js +362 -0
- package/dist/claude-cli-OF43XAO3.js +276 -0
- package/dist/claude-code-PW6SKD2M.js +126 -0
- package/dist/claude-code-teams-JLZ5IXB6.js +199 -0
- package/dist/constellation-K3CIQCHI.js +225 -0
- package/dist/cost-AEK6R7HK.js +174 -0
- package/dist/cost-KYXIQ62X.js +93 -0
- package/dist/cursor-cli-IHJMPRCW.js +269 -0
- package/dist/cursorrules-KI5QWHIX.js +84 -0
- package/dist/diff-AJJ5H6HV.js +125 -0
- package/dist/dist-7MPIRMTZ-IOQOREMZ.js +10866 -0
- package/dist/dist-NHJQVVUW.js +68 -0
- package/dist/dist-ZEMSQV74.js +20 -0
- package/dist/doctor-6Y6L6HEB.js +11 -0
- package/dist/echo-VYZW3OTT.js +248 -0
- package/dist/export-R4FJ5NOH.js +38 -0
- package/dist/history-EVO3L6SC.js +277 -0
- package/dist/hooks-MBWE4ILT.js +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +568 -0
- package/dist/lint-HXKTWRNO.js +316 -0
- package/dist/manual-Y3QOXWYA.js +204 -0
- package/dist/mcp.js +14745 -0
- package/dist/orchestrate-4ZH5GUQH.js +323 -0
- package/dist/probe-OYCP4JYG.js +151 -0
- package/dist/promote-Z52ZJTJU.js +181 -0
- package/dist/providers-4PGPZEWP.js +104 -0
- package/dist/remember-6VZ74B7E.js +77 -0
- package/dist/ripple-SBQOSTZD.js +215 -0
- package/dist/sentinel-LCFD56OJ.js +43 -0
- package/dist/server-F5ITNK6T.js +9846 -0
- package/dist/server-T6WIFYRQ.js +16076 -0
- package/dist/setup-DF4F3ICN.js +25 -0
- package/dist/setup-JHBPZAG7.js +296 -0
- package/dist/shift-HKIAP4ZN.js +226 -0
- package/dist/snapshot-GTVPRYZG.js +62 -0
- package/dist/spawn-BJRQA2NR.js +196 -0
- package/dist/summary-H6J6N6PJ.js +140 -0
- package/dist/switch-6EANJ7O6.js +232 -0
- package/dist/sync-BEOCW7TZ.js +11 -0
- package/dist/team-NWP2KJAB.js +32 -0
- package/dist/test-MA5TWJQV.js +934 -0
- package/dist/thread-JCJVRUQR.js +258 -0
- package/dist/triage-ETVXXFMV.js +1880 -0
- package/dist/tutorial-L5Q3ZDHK.js +666 -0
- package/dist/university-R2WDQLSI.js +40 -0
- package/dist/upgrade-5B3YGGC6.js +550 -0
- package/dist/validate-F3YHBCRZ.js +39 -0
- package/dist/validate-QEEY6KFS.js +64 -0
- package/dist/watch-4LT4O6K7.js +123 -0
- package/dist/watch-6IIWPWDN.js +111 -0
- package/dist/wisdom-LRM4FFCH.js +319 -0
- package/package.json +68 -0
- package/templates/paradigm/config.yaml +175 -0
- package/templates/paradigm/docs/commands.md +727 -0
- package/templates/paradigm/docs/decisions/000-template.md +47 -0
- package/templates/paradigm/docs/decisions/README.md +26 -0
- package/templates/paradigm/docs/error-patterns.md +215 -0
- package/templates/paradigm/docs/patterns.md +358 -0
- package/templates/paradigm/docs/queries.md +200 -0
- package/templates/paradigm/docs/troubleshooting.md +477 -0
- package/templates/paradigm/echoes.yaml +25 -0
- package/templates/paradigm/prompts/add-feature.md +152 -0
- package/templates/paradigm/prompts/add-gate.md +117 -0
- package/templates/paradigm/prompts/debug-auth.md +174 -0
- package/templates/paradigm/prompts/implement-ftux.md +722 -0
- package/templates/paradigm/prompts/implement-sandbox.md +651 -0
- package/templates/paradigm/prompts/read-docs.md +84 -0
- package/templates/paradigm/prompts/refactor.md +106 -0
- package/templates/paradigm/prompts/run-e2e-tests.md +340 -0
- package/templates/paradigm/prompts/trace-flow.md +202 -0
- package/templates/paradigm/prompts/validate-portals.md +279 -0
- package/templates/paradigm/specs/context-tracking.md +200 -0
- package/templates/paradigm/specs/context.md +461 -0
- package/templates/paradigm/specs/disciplines.md +413 -0
- package/templates/paradigm/specs/history.md +339 -0
- package/templates/paradigm/specs/logger.md +303 -0
- package/templates/paradigm/specs/navigator.md +236 -0
- package/templates/paradigm/specs/purpose.md +265 -0
- package/templates/paradigm/specs/scan.md +177 -0
- package/templates/paradigm/specs/symbols.md +451 -0
- package/templates/paradigm/specs/wisdom.md +294 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
parseGateConfig
|
|
4
|
+
} from "./chunk-IRKUEJVW.js";
|
|
5
|
+
import "./chunk-MO4EEYFW.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/portal/test.ts
|
|
8
|
+
import * as path3 from "path";
|
|
9
|
+
import * as fs3 from "fs";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
|
|
12
|
+
// ../portal/sdk/dist/chunk-BRTWJEZU.js
|
|
13
|
+
var globalGateClient = null;
|
|
14
|
+
function setGateClient(client) {
|
|
15
|
+
globalGateClient = client;
|
|
16
|
+
}
|
|
17
|
+
function getGateClient() {
|
|
18
|
+
return globalGateClient;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ../portal/manager/dist/index.js
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
import * as path from "path";
|
|
24
|
+
import * as fs2 from "fs";
|
|
25
|
+
import * as path2 from "path";
|
|
26
|
+
async function validateGateway(gateId, testCases, client) {
|
|
27
|
+
const gateClient = client || getGateClient();
|
|
28
|
+
if (!gateClient) {
|
|
29
|
+
return {
|
|
30
|
+
passed: false,
|
|
31
|
+
results: [],
|
|
32
|
+
errors: ["No gate client configured"]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const results = [];
|
|
36
|
+
const errors = [];
|
|
37
|
+
for (const testCase of testCases) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await gateClient.check(gateId, testCase.entity);
|
|
40
|
+
const passed = result.passed === testCase.expected;
|
|
41
|
+
if (testCase.expectedPrizes && result.passed) {
|
|
42
|
+
const triggeredPrizeIds = result.triggeredPrizes.map((p) => p.id);
|
|
43
|
+
const missingPrizes = testCase.expectedPrizes.filter(
|
|
44
|
+
(id) => !triggeredPrizeIds.includes(id)
|
|
45
|
+
);
|
|
46
|
+
if (missingPrizes.length > 0) {
|
|
47
|
+
errors.push(
|
|
48
|
+
`Test "${testCase.name}": Expected prizes not triggered: ${missingPrizes.join(", ")}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
results.push({
|
|
53
|
+
testCase,
|
|
54
|
+
result,
|
|
55
|
+
passed
|
|
56
|
+
});
|
|
57
|
+
if (!passed) {
|
|
58
|
+
errors.push(
|
|
59
|
+
`Test "${testCase.name}": Expected ${testCase.expected ? "pass" : "fail"}, got ${result.passed ? "pass" : "fail"}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
errors.push(`Test "${testCase.name}": ${error.message}`);
|
|
64
|
+
results.push({
|
|
65
|
+
testCase,
|
|
66
|
+
result: {
|
|
67
|
+
gate: { id: gateId, locks: [], prizes: [] },
|
|
68
|
+
passed: false,
|
|
69
|
+
lockResults: [],
|
|
70
|
+
triggeredPrizes: [],
|
|
71
|
+
timestamp: Date.now()
|
|
72
|
+
},
|
|
73
|
+
passed: false
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
passed: errors.length === 0,
|
|
79
|
+
results,
|
|
80
|
+
errors
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function generateTests(gateConfigPath, options = {}) {
|
|
84
|
+
const {
|
|
85
|
+
outputDir = "tests/gates",
|
|
86
|
+
framework = "jest",
|
|
87
|
+
includeFlows = true
|
|
88
|
+
} = options;
|
|
89
|
+
const gateConfig = await parseGateConfig(gateConfigPath);
|
|
90
|
+
const generatedFiles = [];
|
|
91
|
+
const absOutputDir = path.resolve(outputDir);
|
|
92
|
+
if (!fs.existsSync(absOutputDir)) {
|
|
93
|
+
fs.mkdirSync(absOutputDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
for (const gate of gateConfig.gates) {
|
|
96
|
+
const testFile = generateGateTest(gate, framework);
|
|
97
|
+
const fileName = `${gate.id.replace(/[^a-z0-9]/gi, "-")}.test.ts`;
|
|
98
|
+
const filePath = path.join(absOutputDir, fileName);
|
|
99
|
+
fs.writeFileSync(filePath, testFile, "utf8");
|
|
100
|
+
generatedFiles.push(filePath);
|
|
101
|
+
}
|
|
102
|
+
if (includeFlows && gateConfig.flows.length > 0) {
|
|
103
|
+
const flowsDir = path.join(absOutputDir, "..", "flows");
|
|
104
|
+
if (!fs.existsSync(flowsDir)) {
|
|
105
|
+
fs.mkdirSync(flowsDir, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
for (const flow of gateConfig.flows) {
|
|
108
|
+
const testFile = generateFlowTest(flow, gateConfig.gates, framework);
|
|
109
|
+
const fileName = `${flow.id.replace(/[^a-z0-9]/gi, "-")}.test.ts`;
|
|
110
|
+
const filePath = path.join(flowsDir, fileName);
|
|
111
|
+
fs.writeFileSync(filePath, testFile, "utf8");
|
|
112
|
+
generatedFiles.push(filePath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return generatedFiles;
|
|
116
|
+
}
|
|
117
|
+
function generateGateTest(gate, framework) {
|
|
118
|
+
const testFn = framework === "mocha" ? "it" : framework === "vitest" ? "it" : "test";
|
|
119
|
+
const gateId = gate.id;
|
|
120
|
+
const gateName = gate.description || gateId;
|
|
121
|
+
const lockTests = gate.locks.map((lock) => {
|
|
122
|
+
const lockTests2 = lock.keys.map((key, idx) => {
|
|
123
|
+
return ` ${testFn}('should check key: ${key.description || `Key ${idx + 1}`}', async () => {
|
|
124
|
+
const entity = {
|
|
125
|
+
// TODO: Add entity properties needed for this key
|
|
126
|
+
// Key expression: ${key.expression}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = await checkGateway('${gateId}', entity);
|
|
130
|
+
// TODO: Assert expected result
|
|
131
|
+
expect(result.passed).toBeDefined();
|
|
132
|
+
});`;
|
|
133
|
+
});
|
|
134
|
+
return ` describe('Lock: ${lock.description || lock.id}', () => {
|
|
135
|
+
${lockTests2.join("\n\n")}
|
|
136
|
+
});`;
|
|
137
|
+
});
|
|
138
|
+
return `/**
|
|
139
|
+
* Generated test file for gate: ${gateName}
|
|
140
|
+
* Gate ID: ${gateId}
|
|
141
|
+
*
|
|
142
|
+
* TODO: Fill in entity mock data and expected results
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
import { checkGateway, validateGateway } from '@a-company/portal-manager';
|
|
146
|
+
import { createGate, setGateClient } from '@a-company/portal-sdk';
|
|
147
|
+
import { parseGateConfig } from '@a-company/portal-core';
|
|
148
|
+
|
|
149
|
+
${framework === "vitest" ? "import { describe, it, expect, beforeAll } from 'vitest';" : framework === "mocha" ? "import { describe, it } from 'mocha';" : "import { describe, test as it, expect, beforeAll } from '@jest/globals';"}
|
|
150
|
+
|
|
151
|
+
describe('Gate: ${gateName}', () => {
|
|
152
|
+
beforeAll(async () => {
|
|
153
|
+
// Load gate configuration
|
|
154
|
+
const config = await parseGateConfig('./portal.yaml');
|
|
155
|
+
const client = createGate(config);
|
|
156
|
+
setGateClient(client);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
${lockTests.join("\n\n")}
|
|
160
|
+
|
|
161
|
+
${testFn}('should trigger prizes when gate passes', async () => {
|
|
162
|
+
const entity = {
|
|
163
|
+
// TODO: Add entity that passes all locks
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = await checkGateway('${gateId}', entity);
|
|
167
|
+
|
|
168
|
+
if (result.passed) {
|
|
169
|
+
expect(result.triggeredPrizes.length).toBeGreaterThan(0);
|
|
170
|
+
// TODO: Assert specific prizes
|
|
171
|
+
${gate.prizes.map((p) => `// expect(result.triggeredPrizes).toContainEqual(expect.objectContaining({ id: '${p.id}' }));`).join("\n ")}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
function generateFlowTest(flow, _gates, framework) {
|
|
178
|
+
const testFn = framework === "mocha" ? "it" : framework === "vitest" ? "it" : "test";
|
|
179
|
+
const flowName = flow.description || flow.id;
|
|
180
|
+
const gateTests = flow.gates.map((gateId, idx) => {
|
|
181
|
+
return ` ${testFn}('should pass gate ${idx + 1}: ${gateId}', async () => {
|
|
182
|
+
const entity = {
|
|
183
|
+
// TODO: Add entity properties
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = await checkGateway('${gateId}', entity);
|
|
187
|
+
expect(result.passed).toBe(true);
|
|
188
|
+
});`;
|
|
189
|
+
});
|
|
190
|
+
return `/**
|
|
191
|
+
* Generated test file for flow: ${flowName}
|
|
192
|
+
* Flow ID: ${flow.id}
|
|
193
|
+
*
|
|
194
|
+
* TODO: Fill in entity mock data for each gate in the flow
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
import { checkGateway } from '@a-company/portal-manager';
|
|
198
|
+
import { createGate, setGateClient } from '@a-company/portal-sdk';
|
|
199
|
+
import { parseGateConfig } from '@a-company/portal-core';
|
|
200
|
+
|
|
201
|
+
${framework === "vitest" ? "import { describe, it, expect, beforeAll } from 'vitest';" : framework === "mocha" ? "import { describe, it } from 'mocha';" : "import { describe, test as it, expect, beforeAll } from '@jest/globals';"}
|
|
202
|
+
|
|
203
|
+
describe('Flow: ${flowName}', () => {
|
|
204
|
+
beforeAll(async () => {
|
|
205
|
+
const config = await parseGateConfig('./portal.yaml');
|
|
206
|
+
const client = createGate(config);
|
|
207
|
+
setGateClient(client);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
${testFn}('should complete entire flow', async () => {
|
|
211
|
+
const entity = {
|
|
212
|
+
// TODO: Add entity that passes all gates in flow
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
${gateTests.join("\n\n")}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
async function scanComponents(options) {
|
|
221
|
+
const { rootDir, patterns = ["**/*.tsx", "**/*.jsx"] } = options;
|
|
222
|
+
const components = [];
|
|
223
|
+
const scannedFiles = /* @__PURE__ */ new Set();
|
|
224
|
+
function scanDirectory(dir) {
|
|
225
|
+
try {
|
|
226
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const fullPath = path2.join(dir, entry.name);
|
|
229
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === "build") {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (entry.isDirectory()) {
|
|
233
|
+
scanDirectory(fullPath);
|
|
234
|
+
} else if (entry.isFile()) {
|
|
235
|
+
const matchesPattern = patterns.some((pattern) => {
|
|
236
|
+
if (pattern.includes("**")) {
|
|
237
|
+
const ext = pattern.split(".").pop();
|
|
238
|
+
return fullPath.endsWith(`.${ext}`);
|
|
239
|
+
}
|
|
240
|
+
return fullPath.endsWith(pattern);
|
|
241
|
+
});
|
|
242
|
+
if (matchesPattern && !scannedFiles.has(fullPath)) {
|
|
243
|
+
scannedFiles.add(fullPath);
|
|
244
|
+
const info = analyzeComponent(fullPath);
|
|
245
|
+
if (info) {
|
|
246
|
+
components.push(info);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
scanDirectory(rootDir);
|
|
255
|
+
return components;
|
|
256
|
+
}
|
|
257
|
+
function analyzeComponent(filePath) {
|
|
258
|
+
try {
|
|
259
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
260
|
+
const componentName = path2.basename(filePath, path2.extname(filePath));
|
|
261
|
+
const gatePatterns = [
|
|
262
|
+
/@Gate\(['"]([^'"]+)['"]\)/g,
|
|
263
|
+
/@Gateway\(['"]([^'"]+)['"]\)/g,
|
|
264
|
+
/GateGuard\(['"]([^'"]+)['"]\)/g,
|
|
265
|
+
/checkGateway\(['"]([^'"]+)['"]/g,
|
|
266
|
+
/gate:\s*['"]([^'"]+)['"]/g
|
|
267
|
+
];
|
|
268
|
+
const requiredGates = /* @__PURE__ */ new Set();
|
|
269
|
+
const missingChecks = [];
|
|
270
|
+
for (const pattern of gatePatterns) {
|
|
271
|
+
let match;
|
|
272
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
273
|
+
const gateId = match[1];
|
|
274
|
+
if (gateId && gateId.startsWith("^")) {
|
|
275
|
+
requiredGates.add(gateId);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const hasGateCheck = content.includes("checkGateway") || content.includes("GateGuard") || content.includes("Gateway") || content.includes("useGate");
|
|
280
|
+
if (requiredGates.size > 0 && !hasGateCheck) {
|
|
281
|
+
missingChecks.push(...Array.from(requiredGates));
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
filePath,
|
|
285
|
+
componentName,
|
|
286
|
+
requiredGates: Array.from(requiredGates),
|
|
287
|
+
missingChecks
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function generateComponentReport(components) {
|
|
294
|
+
const lines = [];
|
|
295
|
+
lines.push("# Component Access Report\n");
|
|
296
|
+
lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
297
|
+
`);
|
|
298
|
+
lines.push(`Total Components: ${components.length}
|
|
299
|
+
`);
|
|
300
|
+
const withMissingChecks = components.filter((c) => c.missingChecks.length > 0);
|
|
301
|
+
if (withMissingChecks.length > 0) {
|
|
302
|
+
lines.push(`
|
|
303
|
+
## Components Missing Gate Checks (${withMissingChecks.length})
|
|
304
|
+
`);
|
|
305
|
+
for (const component of withMissingChecks) {
|
|
306
|
+
lines.push(`### ${component.componentName}`);
|
|
307
|
+
lines.push(`File: ${component.filePath}`);
|
|
308
|
+
lines.push(`Missing checks for gates: ${component.missingChecks.join(", ")}
|
|
309
|
+
`);
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
lines.push("\n\u2705 All components have proper gate checks!\n");
|
|
313
|
+
}
|
|
314
|
+
return lines.join("\n");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ../portal/sdk/dist/index.js
|
|
318
|
+
function tokenize(expr) {
|
|
319
|
+
const tokens = [];
|
|
320
|
+
let i = 0;
|
|
321
|
+
while (i < expr.length) {
|
|
322
|
+
const char = expr[i];
|
|
323
|
+
if (/\s/.test(char)) {
|
|
324
|
+
i++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (char === '"' || char === "'") {
|
|
328
|
+
const quote = char;
|
|
329
|
+
let str = "";
|
|
330
|
+
i++;
|
|
331
|
+
while (i < expr.length && expr[i] !== quote) {
|
|
332
|
+
if (expr[i] === "\\" && i + 1 < expr.length) {
|
|
333
|
+
i++;
|
|
334
|
+
str += expr[i];
|
|
335
|
+
} else {
|
|
336
|
+
str += expr[i];
|
|
337
|
+
}
|
|
338
|
+
i++;
|
|
339
|
+
}
|
|
340
|
+
i++;
|
|
341
|
+
tokens.push({ type: "STRING", value: str });
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (/\d/.test(char)) {
|
|
345
|
+
let num = "";
|
|
346
|
+
while (i < expr.length && /[\d.]/.test(expr[i])) {
|
|
347
|
+
num += expr[i];
|
|
348
|
+
i++;
|
|
349
|
+
}
|
|
350
|
+
tokens.push({ type: "NUMBER", value: num });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (/[a-zA-Z_]/.test(char)) {
|
|
354
|
+
let ident = "";
|
|
355
|
+
while (i < expr.length && /[a-zA-Z0-9_]/.test(expr[i])) {
|
|
356
|
+
ident += expr[i];
|
|
357
|
+
i++;
|
|
358
|
+
}
|
|
359
|
+
if (ident === "true" || ident === "false") {
|
|
360
|
+
tokens.push({ type: "BOOLEAN", value: ident });
|
|
361
|
+
} else if (ident === "null" || ident === "undefined") {
|
|
362
|
+
tokens.push({ type: "NULL", value: ident });
|
|
363
|
+
} else if (["and", "or", "not", "includes", "in"].includes(ident)) {
|
|
364
|
+
tokens.push({ type: "OPERATOR", value: ident });
|
|
365
|
+
} else {
|
|
366
|
+
tokens.push({ type: "IDENTIFIER", value: ident });
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (expr.slice(i, i + 3) === "===" || expr.slice(i, i + 3) === "!==") {
|
|
371
|
+
tokens.push({ type: "OPERATOR", value: expr.slice(i, i + 3) });
|
|
372
|
+
i += 3;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (expr.slice(i, i + 2) === "==" || expr.slice(i, i + 2) === "!=" || expr.slice(i, i + 2) === "<=" || expr.slice(i, i + 2) === ">=" || expr.slice(i, i + 2) === "&&" || expr.slice(i, i + 2) === "||") {
|
|
376
|
+
tokens.push({ type: "OPERATOR", value: expr.slice(i, i + 2) });
|
|
377
|
+
i += 2;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if ("<>=!".includes(char)) {
|
|
381
|
+
tokens.push({ type: "OPERATOR", value: char });
|
|
382
|
+
i++;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === "(") {
|
|
386
|
+
tokens.push({ type: "LPAREN", value: char });
|
|
387
|
+
i++;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (char === ")") {
|
|
391
|
+
tokens.push({ type: "RPAREN", value: char });
|
|
392
|
+
i++;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (char === ".") {
|
|
396
|
+
tokens.push({ type: "DOT", value: char });
|
|
397
|
+
i++;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (char === ",") {
|
|
401
|
+
tokens.push({ type: "COMMA", value: char });
|
|
402
|
+
i++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
i++;
|
|
406
|
+
}
|
|
407
|
+
tokens.push({ type: "EOF", value: "" });
|
|
408
|
+
return tokens;
|
|
409
|
+
}
|
|
410
|
+
var ExpressionEvaluator = class {
|
|
411
|
+
tokens;
|
|
412
|
+
pos;
|
|
413
|
+
context;
|
|
414
|
+
constructor(tokens, context) {
|
|
415
|
+
this.tokens = tokens;
|
|
416
|
+
this.pos = 0;
|
|
417
|
+
this.context = context;
|
|
418
|
+
}
|
|
419
|
+
current() {
|
|
420
|
+
return this.tokens[this.pos] || { type: "EOF", value: "" };
|
|
421
|
+
}
|
|
422
|
+
advance() {
|
|
423
|
+
const token = this.current();
|
|
424
|
+
this.pos++;
|
|
425
|
+
return token;
|
|
426
|
+
}
|
|
427
|
+
evaluate() {
|
|
428
|
+
return this.parseOr();
|
|
429
|
+
}
|
|
430
|
+
parseOr() {
|
|
431
|
+
let left = this.parseAnd();
|
|
432
|
+
while (this.current().value === "||" || this.current().value === "or") {
|
|
433
|
+
this.advance();
|
|
434
|
+
const right = this.parseAnd();
|
|
435
|
+
left = Boolean(left) || Boolean(right);
|
|
436
|
+
}
|
|
437
|
+
return left;
|
|
438
|
+
}
|
|
439
|
+
parseAnd() {
|
|
440
|
+
let left = this.parseNot();
|
|
441
|
+
while (this.current().value === "&&" || this.current().value === "and") {
|
|
442
|
+
this.advance();
|
|
443
|
+
const right = this.parseNot();
|
|
444
|
+
left = Boolean(left) && Boolean(right);
|
|
445
|
+
}
|
|
446
|
+
return left;
|
|
447
|
+
}
|
|
448
|
+
parseNot() {
|
|
449
|
+
if (this.current().value === "!" || this.current().value === "not") {
|
|
450
|
+
this.advance();
|
|
451
|
+
return !Boolean(this.parseNot());
|
|
452
|
+
}
|
|
453
|
+
return this.parseComparison();
|
|
454
|
+
}
|
|
455
|
+
parseComparison() {
|
|
456
|
+
let left = this.parsePrimary();
|
|
457
|
+
const op = this.current();
|
|
458
|
+
if (op.type === "OPERATOR") {
|
|
459
|
+
switch (op.value) {
|
|
460
|
+
case "==":
|
|
461
|
+
case "===":
|
|
462
|
+
this.advance();
|
|
463
|
+
return left === this.parsePrimary();
|
|
464
|
+
case "!=":
|
|
465
|
+
case "!==":
|
|
466
|
+
this.advance();
|
|
467
|
+
return left !== this.parsePrimary();
|
|
468
|
+
case "<":
|
|
469
|
+
this.advance();
|
|
470
|
+
return left < this.parsePrimary();
|
|
471
|
+
case ">":
|
|
472
|
+
this.advance();
|
|
473
|
+
return left > this.parsePrimary();
|
|
474
|
+
case "<=":
|
|
475
|
+
this.advance();
|
|
476
|
+
return left <= this.parsePrimary();
|
|
477
|
+
case ">=":
|
|
478
|
+
this.advance();
|
|
479
|
+
return left >= this.parsePrimary();
|
|
480
|
+
case "includes":
|
|
481
|
+
this.advance();
|
|
482
|
+
const includesValue = this.parsePrimary();
|
|
483
|
+
if (Array.isArray(left)) {
|
|
484
|
+
return left.includes(includesValue);
|
|
485
|
+
}
|
|
486
|
+
if (typeof left === "string") {
|
|
487
|
+
return left.includes(String(includesValue));
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
case "in":
|
|
491
|
+
this.advance();
|
|
492
|
+
const inArray = this.parsePrimary();
|
|
493
|
+
if (Array.isArray(inArray)) {
|
|
494
|
+
return inArray.includes(left);
|
|
495
|
+
}
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return left;
|
|
500
|
+
}
|
|
501
|
+
parsePrimary() {
|
|
502
|
+
const token = this.current();
|
|
503
|
+
if (token.type === "LPAREN") {
|
|
504
|
+
this.advance();
|
|
505
|
+
const value = this.parseOr();
|
|
506
|
+
if (this.current().type === "RPAREN") {
|
|
507
|
+
this.advance();
|
|
508
|
+
}
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
if (token.type === "STRING") {
|
|
512
|
+
this.advance();
|
|
513
|
+
return token.value;
|
|
514
|
+
}
|
|
515
|
+
if (token.type === "NUMBER") {
|
|
516
|
+
this.advance();
|
|
517
|
+
return parseFloat(token.value);
|
|
518
|
+
}
|
|
519
|
+
if (token.type === "BOOLEAN") {
|
|
520
|
+
this.advance();
|
|
521
|
+
return token.value === "true";
|
|
522
|
+
}
|
|
523
|
+
if (token.type === "NULL") {
|
|
524
|
+
this.advance();
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
if (token.type === "IDENTIFIER") {
|
|
528
|
+
return this.parseIdentifier();
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
parseIdentifier() {
|
|
533
|
+
let value = this.context;
|
|
534
|
+
while (this.current().type === "IDENTIFIER" || this.current().type === "DOT") {
|
|
535
|
+
if (this.current().type === "DOT") {
|
|
536
|
+
this.advance();
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const key = this.advance().value;
|
|
540
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
541
|
+
value = value[key];
|
|
542
|
+
} else {
|
|
543
|
+
value = void 0;
|
|
544
|
+
}
|
|
545
|
+
if (this.current().type === "LPAREN") {
|
|
546
|
+
this.advance();
|
|
547
|
+
const args = [];
|
|
548
|
+
while (this.current().type !== "RPAREN" && this.current().type !== "EOF") {
|
|
549
|
+
args.push(this.parseOr());
|
|
550
|
+
if (this.current().type === "COMMA") {
|
|
551
|
+
this.advance();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (this.current().type === "RPAREN") {
|
|
555
|
+
this.advance();
|
|
556
|
+
}
|
|
557
|
+
return this.callMethod(key, value, args);
|
|
558
|
+
}
|
|
559
|
+
if (this.current().type !== "DOT") {
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
callMethod(method, target, args) {
|
|
566
|
+
switch (method) {
|
|
567
|
+
case "includes":
|
|
568
|
+
if (Array.isArray(target)) {
|
|
569
|
+
return target.includes(args[0]);
|
|
570
|
+
}
|
|
571
|
+
if (typeof target === "string") {
|
|
572
|
+
return target.includes(String(args[0]));
|
|
573
|
+
}
|
|
574
|
+
return false;
|
|
575
|
+
case "startsWith":
|
|
576
|
+
if (typeof target === "string") {
|
|
577
|
+
return target.startsWith(String(args[0]));
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
case "endsWith":
|
|
581
|
+
if (typeof target === "string") {
|
|
582
|
+
return target.endsWith(String(args[0]));
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
case "length":
|
|
586
|
+
if (Array.isArray(target) || typeof target === "string") {
|
|
587
|
+
return target.length;
|
|
588
|
+
}
|
|
589
|
+
return 0;
|
|
590
|
+
default:
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
function evaluateExpression(expression, context) {
|
|
596
|
+
try {
|
|
597
|
+
const tokens = tokenize(expression);
|
|
598
|
+
const evaluator = new ExpressionEvaluator(tokens, context);
|
|
599
|
+
const result = evaluator.evaluate();
|
|
600
|
+
return {
|
|
601
|
+
passed: Boolean(result)
|
|
602
|
+
};
|
|
603
|
+
} catch (error) {
|
|
604
|
+
return {
|
|
605
|
+
passed: false,
|
|
606
|
+
error: error.message
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
var GateClient = class {
|
|
611
|
+
config;
|
|
612
|
+
prizeHandlers = /* @__PURE__ */ new Map();
|
|
613
|
+
firedPrizes = [];
|
|
614
|
+
ws = null;
|
|
615
|
+
options;
|
|
616
|
+
entityIdResolver;
|
|
617
|
+
constructor(config, options = {}) {
|
|
618
|
+
this.config = config;
|
|
619
|
+
this.options = options;
|
|
620
|
+
this.entityIdResolver = options.entityIdResolver || this.defaultEntityIdResolver;
|
|
621
|
+
if (options.devMode) {
|
|
622
|
+
this.connectWatcher();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Default entity ID resolver - looks for id, _id, userId, entityId
|
|
627
|
+
*/
|
|
628
|
+
defaultEntityIdResolver(entity) {
|
|
629
|
+
return entity.id || entity._id || entity.userId || entity.entityId || "anonymous";
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Connect to the Gate watcher server
|
|
633
|
+
*/
|
|
634
|
+
connectWatcher() {
|
|
635
|
+
const url = this.options.watcherUrl || `ws://localhost:${this.config.settings.dev.watcherPort}`;
|
|
636
|
+
try {
|
|
637
|
+
if (typeof WebSocket !== "undefined") {
|
|
638
|
+
this.ws = new WebSocket(url);
|
|
639
|
+
this.ws.onopen = () => {
|
|
640
|
+
console.log("[Gate SDK] Connected to watcher");
|
|
641
|
+
};
|
|
642
|
+
this.ws.onerror = () => {
|
|
643
|
+
console.warn("[Gate SDK] Failed to connect to watcher");
|
|
644
|
+
};
|
|
645
|
+
this.ws.onclose = () => {
|
|
646
|
+
setTimeout(() => this.connectWatcher(), 5e3);
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
} catch {
|
|
650
|
+
console.warn("[Gate SDK] WebSocket not available for watcher connection");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Send event to watcher
|
|
655
|
+
*/
|
|
656
|
+
sendWatcherEvent(event) {
|
|
657
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
658
|
+
this.ws.send(JSON.stringify(event));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Register a handler for a prize
|
|
663
|
+
*/
|
|
664
|
+
onPrize(prizeId, handler) {
|
|
665
|
+
if (!this.prizeHandlers.has(prizeId)) {
|
|
666
|
+
this.prizeHandlers.set(prizeId, []);
|
|
667
|
+
}
|
|
668
|
+
this.prizeHandlers.get(prizeId).push(handler);
|
|
669
|
+
return () => {
|
|
670
|
+
const handlers = this.prizeHandlers.get(prizeId);
|
|
671
|
+
if (handlers) {
|
|
672
|
+
const index = handlers.indexOf(handler);
|
|
673
|
+
if (index >= 0) {
|
|
674
|
+
handlers.splice(index, 1);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Check if an entity can pass through a gate
|
|
681
|
+
*/
|
|
682
|
+
async check(gateId, entity) {
|
|
683
|
+
const gate = this.config.gates.find((g) => g.id === gateId);
|
|
684
|
+
if (!gate) {
|
|
685
|
+
throw new Error(`Gate not found: ${gateId}`);
|
|
686
|
+
}
|
|
687
|
+
const entityId = this.entityIdResolver(entity);
|
|
688
|
+
const timestamp = Date.now();
|
|
689
|
+
this.sendWatcherEvent({
|
|
690
|
+
type: "gate:check",
|
|
691
|
+
timestamp,
|
|
692
|
+
entityId,
|
|
693
|
+
data: { gate, entitySnapshot: entity }
|
|
694
|
+
});
|
|
695
|
+
const lockResults = [];
|
|
696
|
+
let allLocksPassed = true;
|
|
697
|
+
for (const lock of gate.locks) {
|
|
698
|
+
const keyResults = [];
|
|
699
|
+
let lockPassed;
|
|
700
|
+
if (lock.mode === "any") {
|
|
701
|
+
lockPassed = false;
|
|
702
|
+
for (const key of lock.keys) {
|
|
703
|
+
const { passed, error } = evaluateExpression(key.expression, entity);
|
|
704
|
+
keyResults.push({ key, passed, error });
|
|
705
|
+
if (passed) {
|
|
706
|
+
lockPassed = true;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
lockPassed = true;
|
|
711
|
+
for (const key of lock.keys) {
|
|
712
|
+
const { passed, error } = evaluateExpression(key.expression, entity);
|
|
713
|
+
keyResults.push({ key, passed, error });
|
|
714
|
+
if (!passed) {
|
|
715
|
+
lockPassed = false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
lockResults.push({
|
|
720
|
+
lock,
|
|
721
|
+
passed: lockPassed,
|
|
722
|
+
keyResults
|
|
723
|
+
});
|
|
724
|
+
if (!lockPassed) {
|
|
725
|
+
allLocksPassed = false;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const triggeredPrizes = [];
|
|
729
|
+
if (allLocksPassed) {
|
|
730
|
+
for (const prize of gate.prizes) {
|
|
731
|
+
if (prize.oneTime) {
|
|
732
|
+
const alreadyFired = this.firedPrizes.some(
|
|
733
|
+
(fp) => fp.entityId === entityId && fp.prizeId === prize.id
|
|
734
|
+
);
|
|
735
|
+
if (alreadyFired) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
triggeredPrizes.push(prize);
|
|
740
|
+
if (prize.oneTime) {
|
|
741
|
+
this.firedPrizes.push({
|
|
742
|
+
entityId,
|
|
743
|
+
prizeId: prize.id,
|
|
744
|
+
timestamp
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const handlers = this.prizeHandlers.get(prize.id) || [];
|
|
748
|
+
for (const handler of handlers) {
|
|
749
|
+
try {
|
|
750
|
+
await handler(entity, {
|
|
751
|
+
gate,
|
|
752
|
+
prize,
|
|
753
|
+
timestamp
|
|
754
|
+
});
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error(`[Gate SDK] Prize handler error for ${prize.id}:`, error);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
this.sendWatcherEvent({
|
|
760
|
+
type: "prize:fire",
|
|
761
|
+
timestamp: Date.now(),
|
|
762
|
+
entityId,
|
|
763
|
+
data: { prizeId: prize.id, metadata: prize.metadata }
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const result = {
|
|
768
|
+
gate,
|
|
769
|
+
passed: allLocksPassed,
|
|
770
|
+
lockResults,
|
|
771
|
+
triggeredPrizes,
|
|
772
|
+
timestamp,
|
|
773
|
+
entitySnapshot: entity
|
|
774
|
+
};
|
|
775
|
+
this.sendWatcherEvent({
|
|
776
|
+
type: allLocksPassed ? "gate:pass" : "gate:fail",
|
|
777
|
+
timestamp: Date.now(),
|
|
778
|
+
entityId,
|
|
779
|
+
data: result
|
|
780
|
+
});
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Get a gate by ID
|
|
785
|
+
*/
|
|
786
|
+
getGate(gateId) {
|
|
787
|
+
return this.config.gates.find((g) => g.id === gateId);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Get all gates
|
|
791
|
+
*/
|
|
792
|
+
getGates() {
|
|
793
|
+
return this.config.gates;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Get all flows
|
|
797
|
+
*/
|
|
798
|
+
getFlows() {
|
|
799
|
+
return this.config.flows;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Reset fired prizes for an entity (useful for testing)
|
|
803
|
+
*/
|
|
804
|
+
resetPrizes(entityId) {
|
|
805
|
+
if (entityId) {
|
|
806
|
+
this.firedPrizes = this.firedPrizes.filter((fp) => fp.entityId !== entityId);
|
|
807
|
+
} else {
|
|
808
|
+
this.firedPrizes = [];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Disconnect from watcher
|
|
813
|
+
*/
|
|
814
|
+
disconnect() {
|
|
815
|
+
if (this.ws) {
|
|
816
|
+
this.ws.close();
|
|
817
|
+
this.ws = null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
async function createGate(configPath, options = {}) {
|
|
822
|
+
const config = await parseGateConfig(configPath);
|
|
823
|
+
const devMode = options.devMode ?? process.env.NODE_ENV !== "production";
|
|
824
|
+
return new GateClient(config, {
|
|
825
|
+
...options,
|
|
826
|
+
devMode
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
var isProduction = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : typeof import.meta !== "undefined" && import.meta.env?.PROD;
|
|
830
|
+
var enableValidation = typeof process !== "undefined" ? process.env.PORTAL_VALIDATION === "true" : typeof import.meta !== "undefined" && import.meta.env?.VITE_ENABLE_PORTAL_VALIDATION === "true";
|
|
831
|
+
var isTestMode = typeof process !== "undefined" ? process.env.PORTAL_TEST_MODE === "true" : typeof import.meta !== "undefined" && import.meta.env?.VITE_TEST_MODE === "true";
|
|
832
|
+
|
|
833
|
+
// src/commands/portal/test.ts
|
|
834
|
+
async function gateTestCommand(targetPath, options) {
|
|
835
|
+
const rootDir = targetPath ? path3.resolve(targetPath) : process.cwd();
|
|
836
|
+
const gateConfigPath = path3.join(rootDir, "portal.yaml");
|
|
837
|
+
if (!fs3.existsSync(gateConfigPath)) {
|
|
838
|
+
console.error(chalk.red(`\u274C portal.yaml not found at ${gateConfigPath}`));
|
|
839
|
+
console.error(chalk.gray(" Run `paradigm init` or create portal.yaml manually"));
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
if (options.generate) {
|
|
843
|
+
console.log(chalk.blue("\n\u{1F527} Generating test files...\n"));
|
|
844
|
+
try {
|
|
845
|
+
const generatedFiles = await generateTests(gateConfigPath, {
|
|
846
|
+
outputDir: options.output || "tests/gates",
|
|
847
|
+
framework: options.framework || "jest",
|
|
848
|
+
includeFlows: true
|
|
849
|
+
});
|
|
850
|
+
console.log(chalk.green(`\u2705 Generated ${generatedFiles.length} test files:`));
|
|
851
|
+
for (const file of generatedFiles) {
|
|
852
|
+
console.log(chalk.gray(` ${path3.relative(rootDir, file)}`));
|
|
853
|
+
}
|
|
854
|
+
console.log("");
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.error(chalk.red(`\u274C Error generating tests: ${error.message}`));
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (options.component) {
|
|
862
|
+
console.log(chalk.blue("\n\u{1F50D} Scanning components for gate checks...\n"));
|
|
863
|
+
try {
|
|
864
|
+
const components = await scanComponents({
|
|
865
|
+
rootDir,
|
|
866
|
+
gateConfigPath
|
|
867
|
+
});
|
|
868
|
+
const report = generateComponentReport(components);
|
|
869
|
+
console.log(report);
|
|
870
|
+
const withMissing = components.filter((c) => c.missingChecks.length > 0);
|
|
871
|
+
if (withMissing.length > 0) {
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
} catch (error) {
|
|
875
|
+
console.error(chalk.red(`\u274C Error scanning components: ${error.message}`));
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (options.gate) {
|
|
881
|
+
console.log(chalk.blue(`
|
|
882
|
+
\u{1F9EA} Testing gate: ${options.gate}
|
|
883
|
+
`));
|
|
884
|
+
try {
|
|
885
|
+
const config = await parseGateConfig(gateConfigPath);
|
|
886
|
+
const client = await createGate(gateConfigPath);
|
|
887
|
+
setGateClient(client);
|
|
888
|
+
const gate = config.gates.find((g) => g.id === options.gate);
|
|
889
|
+
if (!gate) {
|
|
890
|
+
console.error(chalk.red(`\u274C Gate "${options.gate}" not found`));
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
const testCases = [
|
|
894
|
+
{
|
|
895
|
+
name: "Empty entity (should fail)",
|
|
896
|
+
entity: {},
|
|
897
|
+
expected: false
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: "Entity with required properties",
|
|
901
|
+
entity: {
|
|
902
|
+
// TODO: Add properties based on gate locks
|
|
903
|
+
user: { id: "test-user" }
|
|
904
|
+
},
|
|
905
|
+
expected: true
|
|
906
|
+
}
|
|
907
|
+
];
|
|
908
|
+
const result = await validateGateway(options.gate, testCases, client);
|
|
909
|
+
if (result.passed) {
|
|
910
|
+
console.log(chalk.green("\u2705 All tests passed\n"));
|
|
911
|
+
} else {
|
|
912
|
+
console.log(chalk.red("\u274C Some tests failed\n"));
|
|
913
|
+
for (const error of result.errors) {
|
|
914
|
+
console.log(chalk.red(` ${error}`));
|
|
915
|
+
}
|
|
916
|
+
console.log("");
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
} catch (error) {
|
|
920
|
+
console.error(chalk.red(`\u274C Error testing gate: ${error.message}`));
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
console.log(chalk.blue("\n\u{1F9EA} Portal Testing\n"));
|
|
926
|
+
console.log("Usage:");
|
|
927
|
+
console.log(" paradigm portal test --generate Generate test files");
|
|
928
|
+
console.log(" paradigm portal test --portal ^auth-required Test specific portal");
|
|
929
|
+
console.log(" paradigm portal test --component Validate component access");
|
|
930
|
+
console.log("");
|
|
931
|
+
}
|
|
932
|
+
export {
|
|
933
|
+
gateTestCommand
|
|
934
|
+
};
|