@certik/skynet 0.25.5 → 0.25.6
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 +4 -0
- package/bun.lockb +0 -0
- package/dist/api.js +47 -38
- package/dist/app.js +308 -283
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +4 -0
- package/dist/deploy.js +32 -20
- package/dist/dynamodb.d.ts +2 -1
- package/dist/dynamodb.js +28 -22
- package/dist/email.d.ts +9 -0
- package/dist/email.js +31 -0
- package/dist/indexer.js +154 -132
- package/dist/selector.js +10 -1
- package/examples/api.ts +0 -0
- package/examples/indexer.ts +0 -0
- package/examples/mode-indexer.ts +0 -0
- package/package.json +1 -1
- package/src/selector.ts +11 -1
- package/.vscode/settings.json +0 -5
package/dist/app.js
CHANGED
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
// src/selector.ts
|
|
2
|
-
function getSelectorDesc(selector) {
|
|
3
|
-
return Object.keys(selector).map((name) => {
|
|
4
|
-
return ` --${name.padEnd(14)}${selector[name].desc || selector[name].description || ""}`;
|
|
5
|
-
}).join(`
|
|
6
|
-
`);
|
|
7
|
-
}
|
|
8
|
-
function getSelectorFlags(selector) {
|
|
9
|
-
return Object.keys(selector).reduce((acc, name) => {
|
|
10
|
-
const flag = {
|
|
11
|
-
type: selector[name].type || "string",
|
|
12
|
-
...selector[name]
|
|
13
|
-
};
|
|
14
|
-
if (!selector[name].optional && selector[name].isRequired !== false) {
|
|
15
|
-
flag.isRequired = true;
|
|
16
|
-
}
|
|
17
|
-
return { ...acc, [name]: flag };
|
|
18
|
-
}, {});
|
|
19
|
-
}
|
|
20
|
-
function toSelectorString(selectorFlags, delim = ",") {
|
|
21
|
-
return Object.keys(selectorFlags).sort().map((flag) => {
|
|
22
|
-
return `${flag}=${selectorFlags[flag]}`;
|
|
23
|
-
}).join(delim);
|
|
24
|
-
}
|
|
25
|
-
function normalizeSelectorValue(v) {
|
|
26
|
-
return v.replace(/[^A-Za-z0-9]+/g, "-");
|
|
27
|
-
}
|
|
28
|
-
function getJobName(name, selectorFlags, mode) {
|
|
29
|
-
const selectorNamePart = Object.keys(selectorFlags).sort().map((name2) => selectorFlags[name2]).join("-");
|
|
30
|
-
let jobName = name;
|
|
31
|
-
if (mode) {
|
|
32
|
-
jobName += `-${mode}`;
|
|
33
|
-
}
|
|
34
|
-
if (selectorNamePart.length > 0) {
|
|
35
|
-
jobName += `-${normalizeSelectorValue(selectorNamePart)}`;
|
|
36
|
-
}
|
|
37
|
-
return jobName;
|
|
38
|
-
}
|
|
39
1
|
// src/env.ts
|
|
40
2
|
function ensureAndGet(envName, defaultValue) {
|
|
41
3
|
return process.env[envName] || defaultValue;
|
|
@@ -55,207 +17,56 @@ function isProduction() {
|
|
|
55
17
|
function isDev() {
|
|
56
18
|
return getEnvironment() === "dev";
|
|
57
19
|
}
|
|
58
|
-
// src/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (Array.isArray(o)) {
|
|
64
|
-
return `[${o.map(print).join(", ")}]`;
|
|
65
|
-
}
|
|
66
|
-
if (isObject(o)) {
|
|
67
|
-
return `{${Object.keys(o).map((k) => `${k}: ${o[k]}`).join(", ")}}`;
|
|
20
|
+
// src/util.ts
|
|
21
|
+
function range(startAt, endAt, step) {
|
|
22
|
+
const arr = [];
|
|
23
|
+
for (let i = startAt;i <= endAt; i += step) {
|
|
24
|
+
arr.push([i, Math.min(endAt, i + step - 1)]);
|
|
68
25
|
}
|
|
69
|
-
return
|
|
26
|
+
return arr;
|
|
70
27
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
for (let i = 0
|
|
74
|
-
|
|
28
|
+
function arrayGroup(array, groupSize) {
|
|
29
|
+
const groups = [];
|
|
30
|
+
for (let i = 0;i < array.length; i += groupSize) {
|
|
31
|
+
groups.push(array.slice(i, i + groupSize));
|
|
75
32
|
}
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
function timestamp() {
|
|
79
|
-
return new Date().toISOString();
|
|
33
|
+
return groups;
|
|
80
34
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
log: function(...args) {
|
|
88
|
-
if (true) {
|
|
89
|
-
console.log(`${timestamp()} ${getLine(args)}`);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
error: function(...args) {
|
|
93
|
-
if (true) {
|
|
94
|
-
console.error(`${timestamp()} ${getLine(args)}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
var logger = {
|
|
99
|
-
debug: function(...args) {
|
|
100
|
-
if (true) {
|
|
101
|
-
console.log(`[${timestamp()}]`, ...args);
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
log: function(...args) {
|
|
105
|
-
if (true) {
|
|
106
|
-
console.log(`[${timestamp()}]`, ...args);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
error: function(...args) {
|
|
110
|
-
if (true) {
|
|
111
|
-
console.error(`[${timestamp()}]`, ...args);
|
|
112
|
-
}
|
|
35
|
+
function fillRange(start, end) {
|
|
36
|
+
const result = [];
|
|
37
|
+
for (let i = start;i <= end; i++) {
|
|
38
|
+
result.push(i);
|
|
113
39
|
}
|
|
114
|
-
|
|
115
|
-
// src/api.ts
|
|
116
|
-
import osModule from "os";
|
|
117
|
-
import express from "express";
|
|
118
|
-
import meow from "meow";
|
|
119
|
-
async function logStartMiddleware(_, res, next) {
|
|
120
|
-
const start = new Date;
|
|
121
|
-
res.set("x-requested-at", start.toISOString());
|
|
122
|
-
next();
|
|
40
|
+
return result;
|
|
123
41
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
42
|
+
// src/date.ts
|
|
43
|
+
var MS_IN_A_DAY = 3600 * 24 * 1000;
|
|
44
|
+
function getDateOnly(date) {
|
|
45
|
+
return new Date(date).toISOString().split("T")[0];
|
|
127
46
|
}
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
next();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const start = new Date(requestedAt).getTime();
|
|
136
|
-
const end = new Date().getTime();
|
|
137
|
-
const logInfo = {
|
|
138
|
-
start,
|
|
139
|
-
end,
|
|
140
|
-
elapsed: `${end - start}ms`,
|
|
141
|
-
endpoint: req.path,
|
|
142
|
-
host: req.hostname,
|
|
143
|
-
status: res.statusCode
|
|
144
|
-
};
|
|
145
|
-
if (res.statusMessage) {
|
|
146
|
-
logInfo.errorMessage = res.statusMessage;
|
|
147
|
-
}
|
|
148
|
-
inline.log(JSON.stringify(logInfo));
|
|
149
|
-
next();
|
|
47
|
+
function findDateAfter(date, n) {
|
|
48
|
+
const d = new Date(date);
|
|
49
|
+
const after = new Date(d.getTime() + MS_IN_A_DAY * n);
|
|
50
|
+
return getDateOnly(after);
|
|
150
51
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
inline.log("request without api key");
|
|
157
|
-
res.status(400).send("require x-api-key header");
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (typeof key === "string") {
|
|
161
|
-
if (apiKey !== key) {
|
|
162
|
-
inline.log("request has an invalid api key");
|
|
163
|
-
res.status(400).send("invalid api key");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
inline.log(`requested by valid key ${key.slice(0, 6)}`);
|
|
167
|
-
} else {
|
|
168
|
-
const name = key[apiKey];
|
|
169
|
-
if (!name) {
|
|
170
|
-
inline.log("request has an invalid api key");
|
|
171
|
-
res.status(400).send("invalid api key");
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
inline.log(`requested by authorized user ${name}`);
|
|
175
|
-
}
|
|
176
|
-
next();
|
|
177
|
-
} catch (err) {
|
|
178
|
-
inline.log("check api key error", err);
|
|
179
|
-
res.status(500).send("internal error");
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return requireAPIKey;
|
|
183
|
-
};
|
|
184
|
-
async function startApiApp({
|
|
185
|
-
binaryName,
|
|
186
|
-
name,
|
|
187
|
-
selector = {},
|
|
188
|
-
routes,
|
|
189
|
-
serve,
|
|
190
|
-
beforeListen
|
|
191
|
-
}) {
|
|
192
|
-
const app = express();
|
|
193
|
-
app.use(express.json({ limit: "20mb" }));
|
|
194
|
-
const cli = meow(`
|
|
195
|
-
Usage
|
|
196
|
-
$ ${binaryName} <options>
|
|
197
|
-
|
|
198
|
-
Options
|
|
199
|
-
${getSelectorDesc(selector)}
|
|
200
|
-
--verbose Output debug messages
|
|
201
|
-
`, {
|
|
202
|
-
importMeta: import.meta,
|
|
203
|
-
description: false,
|
|
204
|
-
flags: {
|
|
205
|
-
...getSelectorFlags(selector),
|
|
206
|
-
verbose: {
|
|
207
|
-
type: "boolean",
|
|
208
|
-
default: false
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
const { verbose, ...selectorFlags } = cli.flags;
|
|
213
|
-
for (const route of routes) {
|
|
214
|
-
const method = route.method ? route.method.toLowerCase() : "get";
|
|
215
|
-
const middlewares = route.middlewares || [];
|
|
216
|
-
if (route.protected) {
|
|
217
|
-
if (!serve.apiKey) {
|
|
218
|
-
throw new Error("serve.apiKey is required for protected route");
|
|
219
|
-
}
|
|
220
|
-
middlewares.unshift(apiKeyMiddleware(serve.apiKey));
|
|
221
|
-
}
|
|
222
|
-
if (app[method]) {
|
|
223
|
-
if (verbose) {
|
|
224
|
-
inline.log(`registering ${method} ${route.path}`);
|
|
225
|
-
}
|
|
226
|
-
app[method](route.path, contextMiddleware, logStartMiddleware, ...middlewares, async (req, res, next) => {
|
|
227
|
-
try {
|
|
228
|
-
await route.handler({ req, res, ...selectorFlags });
|
|
229
|
-
} catch (routeErr) {
|
|
230
|
-
if (routeErr instanceof Error) {
|
|
231
|
-
inline.log("caught route err", routeErr, routeErr.stack);
|
|
232
|
-
res.status(500).send(`internal server error: ${routeErr.message}`);
|
|
233
|
-
} else {
|
|
234
|
-
inline.log("caught route err", routeErr);
|
|
235
|
-
res.status(500).send("internal server error");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
next();
|
|
239
|
-
}, logEndMiddleware);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (!routes.some((r) => r.path === "/" && r.method?.toUpperCase() === "GET")) {
|
|
243
|
-
app.get("/", (_, res) => {
|
|
244
|
-
res.send("ok");
|
|
245
|
-
});
|
|
52
|
+
function daysInRange(from, to) {
|
|
53
|
+
const fromTime = new Date(from).getTime();
|
|
54
|
+
const toTime = new Date(to).getTime();
|
|
55
|
+
if (fromTime > toTime) {
|
|
56
|
+
throw new Error(`range to date couldn't be earlier than range from date`);
|
|
246
57
|
}
|
|
247
|
-
|
|
248
|
-
|
|
58
|
+
const daysBetween = Math.floor((toTime - fromTime) / MS_IN_A_DAY);
|
|
59
|
+
const dates = [getDateOnly(new Date(fromTime))];
|
|
60
|
+
for (let i = 1;i <= daysBetween; i += 1) {
|
|
61
|
+
dates.push(getDateOnly(new Date(fromTime + i * MS_IN_A_DAY)));
|
|
249
62
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
});
|
|
63
|
+
return dates;
|
|
64
|
+
}
|
|
65
|
+
function dateRange(from, to, step) {
|
|
66
|
+
const days = daysInRange(from, to);
|
|
67
|
+
const windows = arrayGroup(days, step);
|
|
68
|
+
return windows.map((w) => [w[0], w[w.length - 1]]);
|
|
257
69
|
}
|
|
258
|
-
|
|
259
70
|
// src/object-hash.ts
|
|
260
71
|
import xh from "@node-rs/xxhash";
|
|
261
72
|
function getHash(obj) {
|
|
@@ -382,28 +193,63 @@ function memoize(func, options) {
|
|
|
382
193
|
}
|
|
383
194
|
return pMemoize(func, options);
|
|
384
195
|
}
|
|
385
|
-
// src/
|
|
386
|
-
function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
196
|
+
// src/log.ts
|
|
197
|
+
function isObject(a) {
|
|
198
|
+
return !!a && a.constructor === Object;
|
|
199
|
+
}
|
|
200
|
+
function print(o) {
|
|
201
|
+
if (Array.isArray(o)) {
|
|
202
|
+
return `[${o.map(print).join(", ")}]`;
|
|
390
203
|
}
|
|
391
|
-
|
|
204
|
+
if (isObject(o)) {
|
|
205
|
+
return `{${Object.keys(o).map((k) => `${k}: ${o[k]}`).join(", ")}}`;
|
|
206
|
+
}
|
|
207
|
+
return `${o}`;
|
|
392
208
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
for (let i = 0;i <
|
|
396
|
-
|
|
209
|
+
function getLine(params) {
|
|
210
|
+
let line = "";
|
|
211
|
+
for (let i = 0, l = params.length;i < l; i++) {
|
|
212
|
+
line += `${print(params[i])} `.replace(/\n/gm, "\t");
|
|
213
|
+
}
|
|
214
|
+
return line.trim();
|
|
215
|
+
}
|
|
216
|
+
function timestamp() {
|
|
217
|
+
return new Date().toISOString();
|
|
218
|
+
}
|
|
219
|
+
var inline = {
|
|
220
|
+
debug: function(...args) {
|
|
221
|
+
if (true) {
|
|
222
|
+
console.log(`${timestamp()} ${getLine(args)}`);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
log: function(...args) {
|
|
226
|
+
if (true) {
|
|
227
|
+
console.log(`${timestamp()} ${getLine(args)}`);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
error: function(...args) {
|
|
231
|
+
if (true) {
|
|
232
|
+
console.error(`${timestamp()} ${getLine(args)}`);
|
|
233
|
+
}
|
|
397
234
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
235
|
+
};
|
|
236
|
+
var logger = {
|
|
237
|
+
debug: function(...args) {
|
|
238
|
+
if (true) {
|
|
239
|
+
console.log(`[${timestamp()}]`, ...args);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
log: function(...args) {
|
|
243
|
+
if (true) {
|
|
244
|
+
console.log(`[${timestamp()}]`, ...args);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
error: function(...args) {
|
|
248
|
+
if (true) {
|
|
249
|
+
console.error(`[${timestamp()}]`, ...args);
|
|
250
|
+
}
|
|
404
251
|
}
|
|
405
|
-
|
|
406
|
-
}
|
|
252
|
+
};
|
|
407
253
|
// src/dynamodb.ts
|
|
408
254
|
import {
|
|
409
255
|
DynamoDBDocumentClient,
|
|
@@ -718,6 +564,58 @@ async function deleteRecordsByHashKey(tableName, indexName, hashKeyValue, verbos
|
|
|
718
564
|
}
|
|
719
565
|
return totalDeleted;
|
|
720
566
|
}
|
|
567
|
+
function destroyDynamoDB() {
|
|
568
|
+
if (_dynamoDB) {
|
|
569
|
+
_dynamoDB.destroy();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// src/selector.ts
|
|
573
|
+
function getSelectorDesc(selector) {
|
|
574
|
+
return Object.keys(selector).map((name) => {
|
|
575
|
+
return ` --${name.padEnd(14)}${selector[name].desc || selector[name].description || ""}`;
|
|
576
|
+
}).join(`
|
|
577
|
+
`);
|
|
578
|
+
}
|
|
579
|
+
function getSelectorFlags(selector) {
|
|
580
|
+
return Object.keys(selector).reduce((acc, name) => {
|
|
581
|
+
const flag = {
|
|
582
|
+
type: selector[name].type || "string",
|
|
583
|
+
...selector[name]
|
|
584
|
+
};
|
|
585
|
+
if (!selector[name].optional && selector[name].isRequired !== false) {
|
|
586
|
+
flag.isRequired = true;
|
|
587
|
+
}
|
|
588
|
+
return { ...acc, [name]: flag };
|
|
589
|
+
}, {});
|
|
590
|
+
}
|
|
591
|
+
function toSelectorString(selectorFlags, delim = ",") {
|
|
592
|
+
return Object.keys(selectorFlags).sort().map((flag) => {
|
|
593
|
+
return `${flag}=${selectorFlags[flag]}`;
|
|
594
|
+
}).join(delim);
|
|
595
|
+
}
|
|
596
|
+
function normalizeSelectorValue(v) {
|
|
597
|
+
return v.replace(/[^A-Za-z0-9]+/g, "-");
|
|
598
|
+
}
|
|
599
|
+
function includeSelectorValueInJobName(value) {
|
|
600
|
+
if (value === false)
|
|
601
|
+
return false;
|
|
602
|
+
if (value === undefined || value === null)
|
|
603
|
+
return false;
|
|
604
|
+
if (value === "")
|
|
605
|
+
return false;
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
function getJobName(name, selectorFlags, mode) {
|
|
609
|
+
const selectorNamePart = Object.keys(selectorFlags).sort().map((key) => selectorFlags[key]).filter(includeSelectorValueInJobName).map((value) => String(value)).join("-");
|
|
610
|
+
let jobName = name;
|
|
611
|
+
if (mode) {
|
|
612
|
+
jobName += `-${mode}`;
|
|
613
|
+
}
|
|
614
|
+
if (selectorNamePart.length > 0) {
|
|
615
|
+
jobName += `-${normalizeSelectorValue(selectorNamePart)}`;
|
|
616
|
+
}
|
|
617
|
+
return jobName;
|
|
618
|
+
}
|
|
721
619
|
// src/cli.ts
|
|
722
620
|
import path from "path";
|
|
723
621
|
import fs from "fs";
|
|
@@ -753,36 +651,11 @@ function detectBin() {
|
|
|
753
651
|
const wd = detectDirectory(process.argv[1], "package.json");
|
|
754
652
|
return process.argv[1].slice(wd.length + path.sep.length).replace(path.sep, "/");
|
|
755
653
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
function getDateOnly(date) {
|
|
759
|
-
return new Date(date).toISOString().split("T")[0];
|
|
760
|
-
}
|
|
761
|
-
function findDateAfter(date, n) {
|
|
762
|
-
const d = new Date(date);
|
|
763
|
-
const after = new Date(d.getTime() + MS_IN_A_DAY * n);
|
|
764
|
-
return getDateOnly(after);
|
|
765
|
-
}
|
|
766
|
-
function daysInRange(from, to) {
|
|
767
|
-
const fromTime = new Date(from).getTime();
|
|
768
|
-
const toTime = new Date(to).getTime();
|
|
769
|
-
if (fromTime > toTime) {
|
|
770
|
-
throw new Error(`range to date couldn't be earlier than range from date`);
|
|
771
|
-
}
|
|
772
|
-
const daysBetween = Math.floor((toTime - fromTime) / MS_IN_A_DAY);
|
|
773
|
-
const dates = [getDateOnly(new Date(fromTime))];
|
|
774
|
-
for (let i = 1;i <= daysBetween; i += 1) {
|
|
775
|
-
dates.push(getDateOnly(new Date(fromTime + i * MS_IN_A_DAY)));
|
|
776
|
-
}
|
|
777
|
-
return dates;
|
|
778
|
-
}
|
|
779
|
-
function dateRange(from, to, step) {
|
|
780
|
-
const days = daysInRange(from, to);
|
|
781
|
-
const windows = arrayGroup(days, step);
|
|
782
|
-
return windows.map((w) => [w[0], w[w.length - 1]]);
|
|
654
|
+
function getDeployBin(bin) {
|
|
655
|
+
return bin.endsWith(".ts") ? `bun ${bin}` : bin;
|
|
783
656
|
}
|
|
784
657
|
// src/indexer.ts
|
|
785
|
-
import
|
|
658
|
+
import meow from "meow";
|
|
786
659
|
var STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
|
|
787
660
|
async function getIndexerLatestId(name, selectorFlags) {
|
|
788
661
|
const record = await getRecordByKey(STATE_TABLE_NAME, {
|
|
@@ -884,6 +757,7 @@ function createModeIndexerApp({
|
|
|
884
757
|
inline.log(`[MODE INDEXER] mode=${mode}, env=${getEnvironment()}, ${toSelectorString(selectorFlags, ", ")}`);
|
|
885
758
|
if (mode === "reset") {
|
|
886
759
|
await runReset(selectorFlags);
|
|
760
|
+
process.exit(0);
|
|
887
761
|
} else if (mode === "rebuild") {
|
|
888
762
|
const rebuildFrom = from || await finalState.getMinId(selectorFlags);
|
|
889
763
|
const rebuildTo = to || await finalState.getMaxId(selectorFlags);
|
|
@@ -995,6 +869,7 @@ function createModeIndexerApp({
|
|
|
995
869
|
}
|
|
996
870
|
}
|
|
997
871
|
inline.log(`[MODE INDEXER] validated ${offsetRange(from, to)} ${finalState.type} successfully in ${Date.now() - startTime}ms`);
|
|
872
|
+
process.exit(0);
|
|
998
873
|
}
|
|
999
874
|
async function execBuild(selectorFlags, from, to, verbose, shouldSaveState = false) {
|
|
1000
875
|
let failedIds = [];
|
|
@@ -1070,7 +945,7 @@ function createModeIndexerApp({
|
|
|
1070
945
|
const startTime = Date.now();
|
|
1071
946
|
if (to < from) {
|
|
1072
947
|
inline.log(`[MODE INDEXER] skip delta, there're no more items need to be processed, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}`);
|
|
1073
|
-
|
|
948
|
+
process.exit(0);
|
|
1074
949
|
}
|
|
1075
950
|
inline.log(`[MODE INDEXER] starting delta, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`);
|
|
1076
951
|
try {
|
|
@@ -1125,7 +1000,7 @@ function createModeIndexerApp({
|
|
|
1125
1000
|
if (!binaryName) {
|
|
1126
1001
|
binaryName = getBinaryName();
|
|
1127
1002
|
}
|
|
1128
|
-
const cli =
|
|
1003
|
+
const cli = meow(`
|
|
1129
1004
|
Usage
|
|
1130
1005
|
|
|
1131
1006
|
$ ${binaryName} <options>
|
|
@@ -1169,6 +1044,8 @@ ${selector ? getSelectorDesc(selector) : ""}
|
|
|
1169
1044
|
} catch (err) {
|
|
1170
1045
|
inline.error(err);
|
|
1171
1046
|
process.exit(1);
|
|
1047
|
+
} finally {
|
|
1048
|
+
destroyDynamoDB();
|
|
1172
1049
|
}
|
|
1173
1050
|
}
|
|
1174
1051
|
return { run };
|
|
@@ -1183,7 +1060,7 @@ function createIndexerApp({
|
|
|
1183
1060
|
if (!binaryName) {
|
|
1184
1061
|
binaryName = getBinaryName();
|
|
1185
1062
|
}
|
|
1186
|
-
const cli =
|
|
1063
|
+
const cli = meow(`
|
|
1187
1064
|
Usage
|
|
1188
1065
|
$ ${binaryName} <options>
|
|
1189
1066
|
|
|
@@ -1228,6 +1105,7 @@ ${selector ? getSelectorDesc(selector) : ""}
|
|
|
1228
1105
|
throw new Error(`[INDEXER] Build failed due to critical errors`);
|
|
1229
1106
|
}
|
|
1230
1107
|
inline.log(`[INDEXER] build successfully in ${Date.now() - startTime}ms`);
|
|
1108
|
+
process.exit(0);
|
|
1231
1109
|
}
|
|
1232
1110
|
return runBuild(cli.flags).catch((err) => {
|
|
1233
1111
|
inline.error(err);
|
|
@@ -1240,7 +1118,7 @@ ${selector ? getSelectorDesc(selector) : ""}
|
|
|
1240
1118
|
import fs2 from "fs/promises";
|
|
1241
1119
|
import fso from "fs";
|
|
1242
1120
|
import { execa } from "execa";
|
|
1243
|
-
import
|
|
1121
|
+
import meow2 from "meow";
|
|
1244
1122
|
import chalk from "chalk";
|
|
1245
1123
|
import which from "which";
|
|
1246
1124
|
var INTERVAL_ALIASES = {
|
|
@@ -1547,7 +1425,7 @@ function createModeDeploy({
|
|
|
1547
1425
|
if (!binaryName) {
|
|
1548
1426
|
binaryName = getBinaryName();
|
|
1549
1427
|
}
|
|
1550
|
-
const cli =
|
|
1428
|
+
const cli = meow2(`
|
|
1551
1429
|
Usage
|
|
1552
1430
|
|
|
1553
1431
|
$ ${binaryName} <options>
|
|
@@ -1677,7 +1555,7 @@ function createDeploy({
|
|
|
1677
1555
|
if (!binaryName) {
|
|
1678
1556
|
binaryName = getBinaryName();
|
|
1679
1557
|
}
|
|
1680
|
-
const cli =
|
|
1558
|
+
const cli = meow2(`
|
|
1681
1559
|
Usage
|
|
1682
1560
|
|
|
1683
1561
|
$ ${binaryName} <options>
|
|
@@ -1725,6 +1603,150 @@ ${getSelectorDesc(selector)}
|
|
|
1725
1603
|
}
|
|
1726
1604
|
return { deploy };
|
|
1727
1605
|
}
|
|
1606
|
+
// src/api.ts
|
|
1607
|
+
import osModule from "os";
|
|
1608
|
+
import express from "express";
|
|
1609
|
+
import meow3 from "meow";
|
|
1610
|
+
async function logStartMiddleware(_, res, next) {
|
|
1611
|
+
const start = new Date;
|
|
1612
|
+
res.set("x-requested-at", start.toISOString());
|
|
1613
|
+
next();
|
|
1614
|
+
}
|
|
1615
|
+
async function contextMiddleware(_, res, next) {
|
|
1616
|
+
res.set("x-instance-id", osModule.hostname());
|
|
1617
|
+
next();
|
|
1618
|
+
}
|
|
1619
|
+
async function logEndMiddleware(req, res, next) {
|
|
1620
|
+
const requestedAt = res.get("x-requested-at");
|
|
1621
|
+
if (!requestedAt) {
|
|
1622
|
+
inline.log("missing x-requested-at header");
|
|
1623
|
+
next();
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const start = new Date(requestedAt).getTime();
|
|
1627
|
+
const end = new Date().getTime();
|
|
1628
|
+
const logInfo = {
|
|
1629
|
+
start,
|
|
1630
|
+
end,
|
|
1631
|
+
elapsed: `${end - start}ms`,
|
|
1632
|
+
endpoint: req.path,
|
|
1633
|
+
host: req.hostname,
|
|
1634
|
+
status: res.statusCode
|
|
1635
|
+
};
|
|
1636
|
+
if (res.statusMessage) {
|
|
1637
|
+
logInfo.errorMessage = res.statusMessage;
|
|
1638
|
+
}
|
|
1639
|
+
inline.log(JSON.stringify(logInfo));
|
|
1640
|
+
next();
|
|
1641
|
+
}
|
|
1642
|
+
var apiKeyMiddleware = (key) => {
|
|
1643
|
+
async function requireAPIKey(req, res, next) {
|
|
1644
|
+
try {
|
|
1645
|
+
const apiKey = req.get("x-api-key") || req.query["api-key"];
|
|
1646
|
+
if (!apiKey) {
|
|
1647
|
+
inline.log("request without api key");
|
|
1648
|
+
res.status(400).send("require x-api-key header");
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof key === "string") {
|
|
1652
|
+
if (apiKey !== key) {
|
|
1653
|
+
inline.log("request has an invalid api key");
|
|
1654
|
+
res.status(400).send("invalid api key");
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
inline.log(`requested by valid key ${key.slice(0, 6)}`);
|
|
1658
|
+
} else {
|
|
1659
|
+
const name = key[apiKey];
|
|
1660
|
+
if (!name) {
|
|
1661
|
+
inline.log("request has an invalid api key");
|
|
1662
|
+
res.status(400).send("invalid api key");
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
inline.log(`requested by authorized user ${name}`);
|
|
1666
|
+
}
|
|
1667
|
+
next();
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
inline.log("check api key error", err);
|
|
1670
|
+
res.status(500).send("internal error");
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return requireAPIKey;
|
|
1674
|
+
};
|
|
1675
|
+
async function startApiApp({
|
|
1676
|
+
binaryName,
|
|
1677
|
+
name,
|
|
1678
|
+
selector = {},
|
|
1679
|
+
routes,
|
|
1680
|
+
serve,
|
|
1681
|
+
beforeListen
|
|
1682
|
+
}) {
|
|
1683
|
+
const app = express();
|
|
1684
|
+
app.use(express.json({ limit: "20mb" }));
|
|
1685
|
+
const cli = meow3(`
|
|
1686
|
+
Usage
|
|
1687
|
+
$ ${binaryName} <options>
|
|
1688
|
+
|
|
1689
|
+
Options
|
|
1690
|
+
${getSelectorDesc(selector)}
|
|
1691
|
+
--verbose Output debug messages
|
|
1692
|
+
`, {
|
|
1693
|
+
importMeta: import.meta,
|
|
1694
|
+
description: false,
|
|
1695
|
+
flags: {
|
|
1696
|
+
...getSelectorFlags(selector),
|
|
1697
|
+
verbose: {
|
|
1698
|
+
type: "boolean",
|
|
1699
|
+
default: false
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
const { verbose, ...selectorFlags } = cli.flags;
|
|
1704
|
+
for (const route of routes) {
|
|
1705
|
+
const method = route.method ? route.method.toLowerCase() : "get";
|
|
1706
|
+
const middlewares = route.middlewares || [];
|
|
1707
|
+
if (route.protected) {
|
|
1708
|
+
if (!serve.apiKey) {
|
|
1709
|
+
throw new Error("serve.apiKey is required for protected route");
|
|
1710
|
+
}
|
|
1711
|
+
middlewares.unshift(apiKeyMiddleware(serve.apiKey));
|
|
1712
|
+
}
|
|
1713
|
+
if (app[method]) {
|
|
1714
|
+
if (verbose) {
|
|
1715
|
+
inline.log(`registering ${method} ${route.path}`);
|
|
1716
|
+
}
|
|
1717
|
+
app[method](route.path, contextMiddleware, logStartMiddleware, ...middlewares, async (req, res, next) => {
|
|
1718
|
+
try {
|
|
1719
|
+
await route.handler({ req, res, ...selectorFlags });
|
|
1720
|
+
} catch (routeErr) {
|
|
1721
|
+
if (routeErr instanceof Error) {
|
|
1722
|
+
inline.log("caught route err", routeErr, routeErr.stack);
|
|
1723
|
+
res.status(500).send(`internal server error: ${routeErr.message}`);
|
|
1724
|
+
} else {
|
|
1725
|
+
inline.log("caught route err", routeErr);
|
|
1726
|
+
res.status(500).send("internal server error");
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
next();
|
|
1730
|
+
}, logEndMiddleware);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (!routes.some((r) => r.path === "/" && r.method?.toUpperCase() === "GET")) {
|
|
1734
|
+
app.get("/", (_, res) => {
|
|
1735
|
+
res.send("ok");
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
if (beforeListen) {
|
|
1739
|
+
await beforeListen({ app });
|
|
1740
|
+
}
|
|
1741
|
+
app.listen(serve.port, () => {
|
|
1742
|
+
if (isProduction()) {
|
|
1743
|
+
inline.log(`${name} listening at https://api.wf.corp.certik.com${serve.prefix}`);
|
|
1744
|
+
} else {
|
|
1745
|
+
inline.log(`${name} listening at http://localhost:${serve.port}`);
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1728
1750
|
// src/app.ts
|
|
1729
1751
|
import { EOL } from "os";
|
|
1730
1752
|
function printAppHelp() {
|
|
@@ -1834,12 +1856,13 @@ function indexer({
|
|
|
1834
1856
|
},
|
|
1835
1857
|
onDeploy: () => {
|
|
1836
1858
|
const bin = detectBin();
|
|
1859
|
+
const runBin = getDeployBin(bin);
|
|
1837
1860
|
const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
|
|
1838
1861
|
const { deploy } = createDeploy({
|
|
1839
1862
|
binaryName: `${getBinaryName()} deploy`,
|
|
1840
1863
|
name,
|
|
1841
1864
|
workingDirectory: detectWorkingDirectory(),
|
|
1842
|
-
bin: needDoppler ? `doppler run -- ${
|
|
1865
|
+
bin: needDoppler ? `doppler run -- ${runBin} run` : `${runBin} run`,
|
|
1843
1866
|
selector,
|
|
1844
1867
|
region,
|
|
1845
1868
|
env,
|
|
@@ -1917,12 +1940,13 @@ function modeIndexer({
|
|
|
1917
1940
|
},
|
|
1918
1941
|
onDeploy: () => {
|
|
1919
1942
|
const bin = detectBin();
|
|
1943
|
+
const runBin = getDeployBin(bin);
|
|
1920
1944
|
const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
|
|
1921
1945
|
const { deploy } = createModeDeploy({
|
|
1922
1946
|
binaryName: `${getBinaryName()} deploy`,
|
|
1923
1947
|
name,
|
|
1924
1948
|
workingDirectory: detectWorkingDirectory(),
|
|
1925
|
-
bin: needDoppler ? `doppler run -- ${
|
|
1949
|
+
bin: needDoppler ? `doppler run -- ${runBin} run` : `${runBin} run`,
|
|
1926
1950
|
selector,
|
|
1927
1951
|
region,
|
|
1928
1952
|
env,
|
|
@@ -2012,12 +2036,13 @@ function api({
|
|
|
2012
2036
|
},
|
|
2013
2037
|
onDeploy: () => {
|
|
2014
2038
|
const bin = detectBin();
|
|
2039
|
+
const runBin = getDeployBin(bin);
|
|
2015
2040
|
const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
|
|
2016
2041
|
const { deploy } = createDeploy({
|
|
2017
2042
|
binaryName: `${getBinaryName()} deploy`,
|
|
2018
2043
|
name,
|
|
2019
2044
|
workingDirectory: detectWorkingDirectory(),
|
|
2020
|
-
bin: needDoppler ? `doppler run -- ${
|
|
2045
|
+
bin: needDoppler ? `doppler run -- ${runBin} run` : `${runBin} run`,
|
|
2021
2046
|
selector,
|
|
2022
2047
|
region,
|
|
2023
2048
|
env,
|