@fire-spark/cli 0.13.2 → 0.13.3
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/package.json +3 -1
- package/src/commands/doctor/client/admin.flow.ts +553 -0
- package/src/commands/doctor/client/checkout.flow.ts +400 -0
- package/src/commands/doctor/client/content.flow.ts +450 -0
- package/src/commands/doctor/client/copy.ts +22 -2
- package/src/commands/doctor/client/flow.ts +182 -0
- package/src/commands/doctor/client/log.flow.ts +410 -0
- package/src/commands/doctor/client/men.flow.ts +412 -0
- package/src/commands/doctor/client/ops.flow.ts +312 -0
- package/src/commands/doctor/client/ord.flow.ts +445 -0
- package/src/commands/doctor/client/order.ts +1 -1
- package/src/commands/doctor/client/pay.flow.ts +772 -0
- package/src/commands/doctor/client/repo.scan.ts +142 -0
- package/src/commands/doctor/client/runner.ts +21 -1
- package/src/commands/doctor/client/sdk.flow.ts +630 -0
- package/src/commands/doctor/client/store.flow.ts +577 -0
- package/src/commands/doctor/live/http.ts +10 -18
- package/src/commands/doctor/live/live.session.ts +13 -6
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import type { Copy } from "../../../core/i18n";
|
|
2
|
+
import { fromLive, isOutcome, openLive, prepare } from "./flow";
|
|
3
|
+
import { readPage, say, textOf, trpcCall, withSafeFix } from "./flow";
|
|
4
|
+
import { readSources } from "./repo.scan";
|
|
5
|
+
import type { CheckBinding, Outcome, RunCtx } from "./runner";
|
|
6
|
+
|
|
7
|
+
const line = (es: string, en: string, pt: string): Copy => {
|
|
8
|
+
return { "es-EC": es, "en-US": en, "pt-BR": pt };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const COPY = {
|
|
12
|
+
channel: line(
|
|
13
|
+
"{id} no aplica al canal {template}.",
|
|
14
|
+
"{id} does not apply to the {template} channel.",
|
|
15
|
+
"{id} não se aplica ao canal {template}."
|
|
16
|
+
),
|
|
17
|
+
need: line(
|
|
18
|
+
"{id} se omite: falta {field}.",
|
|
19
|
+
"{id} is skipped: {field} is missing.",
|
|
20
|
+
"{id} é omitido: falta {field}."
|
|
21
|
+
),
|
|
22
|
+
named: line(
|
|
23
|
+
"{id} solo corre cuando lo nombras. No cambié el equipo.",
|
|
24
|
+
"{id} runs only when you name it. The device was not changed.",
|
|
25
|
+
"{id} só roda quando você o nomeia. O equipamento não foi alterado."
|
|
26
|
+
),
|
|
27
|
+
print: line(
|
|
28
|
+
"El repo del canal no llama a printReceipt.",
|
|
29
|
+
"The channel repo does not call printReceipt.",
|
|
30
|
+
"O repo do canal não chama printReceipt."
|
|
31
|
+
),
|
|
32
|
+
page: line(
|
|
33
|
+
"La página pública del canal no respondió.",
|
|
34
|
+
"The channel public page did not respond.",
|
|
35
|
+
"A página pública do canal não respondeu."
|
|
36
|
+
),
|
|
37
|
+
socket: line(
|
|
38
|
+
"RT-10 encontró un listener de realtime fuera de checkout u orden: {file}.",
|
|
39
|
+
"RT-10 found a realtime listener outside checkout or order: {file}.",
|
|
40
|
+
"RT-10 encontrou um listener de realtime fora do checkout ou do pedido: {file}."
|
|
41
|
+
),
|
|
42
|
+
reboot: line(
|
|
43
|
+
"RT-07 kioskDevices.reboot respondió {status}.",
|
|
44
|
+
"RT-07 kioskDevices.reboot responded {status}.",
|
|
45
|
+
"RT-07 kioskDevices.reboot respondeu {status}."
|
|
46
|
+
)
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const applies = (ctx: RunCtx, id: string, allowed: ReadonlySet<string>) => {
|
|
50
|
+
const ready = prepare(ctx, ["channel_id"]);
|
|
51
|
+
if (isOutcome(ready)) return ready;
|
|
52
|
+
const template = textOf(ready.template);
|
|
53
|
+
if (allowed.has(template)) return null;
|
|
54
|
+
return say(ctx.locale, "skip", COPY.channel, { id, template });
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const LIVE = [
|
|
58
|
+
"api_url",
|
|
59
|
+
"channel_id",
|
|
60
|
+
"FIRESPARK_DOCTOR_CUSTOMER_EMAIL",
|
|
61
|
+
"FIRESPARK_DOCTOR_CUSTOMER_PASSWORD",
|
|
62
|
+
"FIRESPARK_DOCTOR_FIREBASE_API_KEY"
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
const need = (ctx: RunCtx, id: string, field: string) => {
|
|
66
|
+
return say(ctx.locale, "skip", COPY.need, { id, field });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const deviceGap = (env: NodeJS.ProcessEnv) => {
|
|
70
|
+
if (!textOf(env.FIRESPARK_DOCTOR_DEVICE_UID)) {
|
|
71
|
+
return "FIRESPARK_DOCTOR_DEVICE_UID";
|
|
72
|
+
}
|
|
73
|
+
if (!textOf(env.FIRESPARK_DOCTOR_DEVICE_SECRET)) {
|
|
74
|
+
return "FIRESPARK_DOCTOR_DEVICE_SECRET";
|
|
75
|
+
}
|
|
76
|
+
return "";
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const heartbeat = async (ctx: RunCtx, id: string, path: string) => {
|
|
80
|
+
const ready = prepare(ctx, LIVE);
|
|
81
|
+
if (isOutcome(ready)) return ready;
|
|
82
|
+
const field = deviceGap(ctx.env);
|
|
83
|
+
if (field) return need(ctx, id, field);
|
|
84
|
+
const live = openLive(ctx, ready);
|
|
85
|
+
const device_uid = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_UID);
|
|
86
|
+
const device_secret = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_SECRET);
|
|
87
|
+
const version = { app_version_code: 1, app_version_name: "doctor" };
|
|
88
|
+
const body = { device_uid, device_secret, ...version };
|
|
89
|
+
const codes = [200];
|
|
90
|
+
const args = ["device", "POST", path, codes, body] as const;
|
|
91
|
+
const step = await live.probe(...args);
|
|
92
|
+
if (!step.ok) return fromLive(ctx.locale, step.result);
|
|
93
|
+
return { status: "pass" as const };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const scoped = (rel: string) => {
|
|
97
|
+
const path = rel.toLowerCase();
|
|
98
|
+
if (path.includes("checkout")) return true;
|
|
99
|
+
if (path.includes("order")) return true;
|
|
100
|
+
return false;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const listener = (body: string) => {
|
|
104
|
+
if (body.includes("@fire-spark/realtime")) return true;
|
|
105
|
+
if (body.includes("new WebSocket")) return true;
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const runRt10 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
110
|
+
const allowed = new Set(["web", "app", "chat", "call-center"]);
|
|
111
|
+
const skipped = applies(ctx, "RT-10", allowed);
|
|
112
|
+
if (skipped) return skipped;
|
|
113
|
+
const files = await readSources(ctx.cwd);
|
|
114
|
+
const found = files.find(file => !scoped(file.rel) && listener(file.body));
|
|
115
|
+
if (found) return say(ctx.locale, "fail", COPY.socket, { file: found.rel });
|
|
116
|
+
return { status: "pass" };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const runRt06 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
120
|
+
const skipped = applies(ctx, "RT-06", new Set(["kiosk", "dashboard"]));
|
|
121
|
+
if (skipped) return skipped;
|
|
122
|
+
if (!ctx.named.has("RT-06"))
|
|
123
|
+
return say(ctx.locale, "skip", COPY.named, { id: "RT-06" });
|
|
124
|
+
const ready = prepare(ctx, LIVE);
|
|
125
|
+
if (isOutcome(ready)) return ready;
|
|
126
|
+
const field = deviceGap(ctx.env);
|
|
127
|
+
if (field) return need(ctx, "RT-06", field);
|
|
128
|
+
const live = openLive(ctx, ready);
|
|
129
|
+
const device_uid = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_UID);
|
|
130
|
+
const device_secret = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_SECRET);
|
|
131
|
+
const body = { device_uid, device_secret };
|
|
132
|
+
const codes = [200];
|
|
133
|
+
const args = ["unlink", "DELETE", "/kiosk/devices/me", codes, body] as const;
|
|
134
|
+
const step = await live.probe(...args);
|
|
135
|
+
if (!step.ok) return fromLive(ctx.locale, step.result);
|
|
136
|
+
return { status: "pass" };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const runRt07 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
140
|
+
const skipped = applies(ctx, "RT-07", new Set(["kiosk", "dashboard"]));
|
|
141
|
+
if (skipped) return skipped;
|
|
142
|
+
const device = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_ID);
|
|
143
|
+
if (!device) return need(ctx, "RT-07", "FIRESPARK_DOCTOR_DEVICE_ID");
|
|
144
|
+
const cookie = textOf(ctx.env.FIRESPARK_DOCTOR_DASHBOARD_COOKIE);
|
|
145
|
+
if (!cookie) return need(ctx, "RT-07", "FIRESPARK_DOCTOR_DASHBOARD_COOKIE");
|
|
146
|
+
const ready = prepare(ctx, ["dashboard_url", "merchant_id", "env"]);
|
|
147
|
+
if (isOutcome(ready)) return ready;
|
|
148
|
+
const url = `${textOf(ready.dashboard_url)}/api/trpc/kioskDevices.reboot`;
|
|
149
|
+
const merchant_id = textOf(ready.merchant_id);
|
|
150
|
+
const env = textOf(ready.env);
|
|
151
|
+
const input = { merchant_id, env, id: device };
|
|
152
|
+
const headers = { "content-type": "application/json", cookie };
|
|
153
|
+
const signal = AbortSignal.timeout(15000);
|
|
154
|
+
const payload = JSON.stringify({ json: input });
|
|
155
|
+
const init = { method: "POST", headers, body: payload, signal };
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch(url, init);
|
|
158
|
+
if (response.status === 200 || response.status === 412) {
|
|
159
|
+
return { status: "pass" };
|
|
160
|
+
}
|
|
161
|
+
return say(ctx.locale, "fail", COPY.reboot, { status: response.status });
|
|
162
|
+
} catch {
|
|
163
|
+
return say(ctx.locale, "fail", COPY.reboot, { status: 0 });
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const runRt08 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
168
|
+
const allowed = new Set(["kiosk", "pos", "dashboard"]);
|
|
169
|
+
const skipped = applies(ctx, "RT-08", allowed);
|
|
170
|
+
if (skipped) return skipped;
|
|
171
|
+
const ready = prepare(ctx, ["channel_id"]);
|
|
172
|
+
if (isOutcome(ready)) return ready;
|
|
173
|
+
const pos = textOf(ready.template) === "pos";
|
|
174
|
+
const path = pos ? "/pos/devices/me" : "/kiosk/devices/me";
|
|
175
|
+
return heartbeat(ctx, "RT-08", path);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const runRt09 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
179
|
+
const allowed = new Set(["kiosk", "pos", "dashboard"]);
|
|
180
|
+
const skipped = applies(ctx, "RT-09", allowed);
|
|
181
|
+
if (skipped) return skipped;
|
|
182
|
+
const ready = prepare(ctx, ["channel_id"]);
|
|
183
|
+
if (isOutcome(ready)) return ready;
|
|
184
|
+
const template = textOf(ready.template);
|
|
185
|
+
if (template === "dashboard") {
|
|
186
|
+
return say(ctx.locale, "skip", COPY.need, {
|
|
187
|
+
id: "RT-09",
|
|
188
|
+
field: "kiosk channel_id"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const cookie = textOf(ctx.env.FIRESPARK_DOCTOR_DASHBOARD_COOKIE);
|
|
192
|
+
if (!cookie) {
|
|
193
|
+
return say(ctx.locale, "skip", COPY.need, {
|
|
194
|
+
id: "RT-09",
|
|
195
|
+
field: "FIRESPARK_DOCTOR_DASHBOARD_COOKIE"
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const dash = textOf(ready.dashboard_url);
|
|
199
|
+
if (!dash) {
|
|
200
|
+
return say(ctx.locale, "skip", COPY.need, {
|
|
201
|
+
id: "RT-09",
|
|
202
|
+
field: "dashboard_url"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const input = {
|
|
206
|
+
merchant_id: textOf(ready.merchant_id),
|
|
207
|
+
env: textOf(ready.env),
|
|
208
|
+
channel_id: textOf(ready.channel_id)
|
|
209
|
+
};
|
|
210
|
+
const proc = "kioskDevices.selectByRange";
|
|
211
|
+
const step = await trpcCall(dash, cookie, proc, input);
|
|
212
|
+
if (step.status < 200 || step.status >= 300) {
|
|
213
|
+
return say(ctx.locale, "fail", COPY.reboot, { status: step.status });
|
|
214
|
+
}
|
|
215
|
+
if (!step.text.includes("store_id")) {
|
|
216
|
+
return say(ctx.locale, "fail", COPY.need, { id: "RT-09", field: "stores" });
|
|
217
|
+
}
|
|
218
|
+
if (step.text.includes("PENDING") && !step.text.includes("LINKED")) {
|
|
219
|
+
return say(ctx.locale, "fail", COPY.need, { id: "RT-09", field: "link" });
|
|
220
|
+
}
|
|
221
|
+
if (!step.text.includes("is_online")) {
|
|
222
|
+
return say(ctx.locale, "fail", COPY.need, { id: "RT-09", field: "online" });
|
|
223
|
+
}
|
|
224
|
+
return { status: "pass" };
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const runPrt01 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
228
|
+
const skipped = applies(ctx, "PRT-01", new Set(["pos"]));
|
|
229
|
+
if (skipped) return skipped;
|
|
230
|
+
const files = await readSources(ctx.cwd);
|
|
231
|
+
const found = files.some(file => file.body.includes("printReceipt"));
|
|
232
|
+
if (!found) return say(ctx.locale, "fail", COPY.print);
|
|
233
|
+
return { status: "pass" };
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const currentSession = async (ctx: RunCtx, id: string) => {
|
|
237
|
+
const ready = prepare(ctx, LIVE);
|
|
238
|
+
if (isOutcome(ready)) return ready;
|
|
239
|
+
const field = deviceGap(ctx.env);
|
|
240
|
+
if (field) return need(ctx, id, field);
|
|
241
|
+
const live = openLive(ctx, ready);
|
|
242
|
+
const device_uid = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_UID);
|
|
243
|
+
const device_secret = textOf(ctx.env.FIRESPARK_DOCTOR_DEVICE_SECRET);
|
|
244
|
+
const body = { device_uid, device_secret };
|
|
245
|
+
const codes = [200];
|
|
246
|
+
const p1 = ["session", "POST", "/pos/sessions/current"] as const;
|
|
247
|
+
const args = [...p1, codes, body] as const;
|
|
248
|
+
const step = await live.probe(...args);
|
|
249
|
+
if (!step.ok) return fromLive(ctx.locale, step.result);
|
|
250
|
+
return { status: "pass" as const };
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const runCaj01 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
254
|
+
const skipped = applies(ctx, "CAJ-01", new Set(["pos", "dashboard"]));
|
|
255
|
+
if (skipped) return skipped;
|
|
256
|
+
return currentSession(ctx, "CAJ-01");
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const runCaj02 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
260
|
+
const skipped = applies(ctx, "CAJ-02", new Set(["pos"]));
|
|
261
|
+
if (skipped) return skipped;
|
|
262
|
+
return currentSession(ctx, "CAJ-02");
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const runCaj03 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
266
|
+
const skipped = applies(ctx, "CAJ-03", new Set(["pos"]));
|
|
267
|
+
if (skipped) return skipped;
|
|
268
|
+
return currentSession(ctx, "CAJ-03");
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const pageOf = async (ctx: RunCtx, id: string) => {
|
|
272
|
+
const ready = prepare(ctx, ["url"]);
|
|
273
|
+
if (isOutcome(ready)) return ready;
|
|
274
|
+
let page: { status: number; body: string };
|
|
275
|
+
try {
|
|
276
|
+
page = await readPage(textOf(ready.url));
|
|
277
|
+
} catch {
|
|
278
|
+
return say(ctx.locale, "fail", COPY.page);
|
|
279
|
+
}
|
|
280
|
+
if (page.status >= 400) return say(ctx.locale, "fail", COPY.page);
|
|
281
|
+
if (!page.body.toLowerCase().includes("whatsapp") && id === "CHT-01") {
|
|
282
|
+
return need(ctx, id, "url");
|
|
283
|
+
}
|
|
284
|
+
return { status: "pass" as const };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const runCht01 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
288
|
+
const skipped = applies(ctx, "CHT-01", new Set(["chat", "dashboard"]));
|
|
289
|
+
if (skipped) return skipped;
|
|
290
|
+
return pageOf(ctx, "CHT-01");
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const runCht03 = async (ctx: RunCtx): Promise<Outcome> => {
|
|
294
|
+
const allowed = new Set(["chat", "call-center", "dashboard"]);
|
|
295
|
+
const skipped = applies(ctx, "CHT-03", allowed);
|
|
296
|
+
if (skipped) return skipped;
|
|
297
|
+
return pageOf(ctx, "CHT-03");
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export const OPS_BINDINGS: Record<string, CheckBinding> = {
|
|
301
|
+
"RT-06": withSafeFix(runRt06),
|
|
302
|
+
"RT-07": withSafeFix(runRt07),
|
|
303
|
+
"RT-08": withSafeFix(runRt08),
|
|
304
|
+
"RT-09": withSafeFix(runRt09),
|
|
305
|
+
"RT-10": withSafeFix(runRt10),
|
|
306
|
+
"PRT-01": withSafeFix(runPrt01),
|
|
307
|
+
"CAJ-01": withSafeFix(runCaj01),
|
|
308
|
+
"CAJ-02": withSafeFix(runCaj02),
|
|
309
|
+
"CAJ-03": withSafeFix(runCaj03),
|
|
310
|
+
"CHT-01": withSafeFix(runCht01),
|
|
311
|
+
"CHT-03": withSafeFix(runCht03)
|
|
312
|
+
};
|