@dokebi/cli 0.4.0 → 0.4.2
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/AI.md +7 -7
- package/README.md +1 -1
- package/catalog.json +24 -20
- package/dist/bin.js +9 -0
- package/dist/guide.js +2 -2
- package/dist/install-module.d.ts +9 -1
- package/dist/install-module.js +97 -5
- package/dist/log-writer-control.js +89 -25
- package/dist/npm-client.d.ts +19 -0
- package/dist/npm-client.js +68 -1
- package/dist/output.d.ts +1 -1
- package/dist/output.js +1 -1
- package/dist/plugin-lifecycle.js +15 -8
- package/dist/project-guide.js +2 -1
- package/dist/project.js +10 -3
- package/dist/recipe.js +10 -2
- package/dist/server.d.ts +7 -0
- package/dist/server.js +189 -51
- package/dist/transaction.d.ts +20 -0
- package/dist/transaction.js +187 -39
- package/dist/verify.d.ts +10 -1
- package/dist/verify.js +55 -8
- package/package.json +1 -1
package/dist/verify.js
CHANGED
|
@@ -61,10 +61,31 @@ export async function npmScript(root, script, timeoutMs) {
|
|
|
61
61
|
export const VERIFY_TOKEN_ENV = 'DOKEBI_VERIFY_TOKEN';
|
|
62
62
|
/** The response header @dokebi/web answers with when that variable is set. */
|
|
63
63
|
export const VERIFY_HEADER = 'x-dokebi-verify';
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* GET with a Host header (fetch cannot set one). Settles once: with the answer when the body
|
|
66
|
+
* ended, or with an error when the request fails, the response is aborted or its connection
|
|
67
|
+
* closes before the end (ISSUE-0006 A09), or nothing complete came within `timeoutMs` in all
|
|
68
|
+
* (the socket timeout alone never fires for a server that keeps trickling bytes).
|
|
69
|
+
*/
|
|
65
70
|
function get(url, host, timeoutMs = 5000) {
|
|
66
71
|
return new Promise((resolve, reject) => {
|
|
67
72
|
const target = new URL(url);
|
|
73
|
+
let settled = false;
|
|
74
|
+
const finish = (error, answer) => {
|
|
75
|
+
if (settled)
|
|
76
|
+
return;
|
|
77
|
+
settled = true;
|
|
78
|
+
clearTimeout(deadline);
|
|
79
|
+
if (error) {
|
|
80
|
+
// Frees the socket of a response that will never complete.
|
|
81
|
+
if (!req.destroyed)
|
|
82
|
+
req.destroy();
|
|
83
|
+
reject(error);
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
resolve(answer);
|
|
87
|
+
};
|
|
88
|
+
const failed = (error) => finish(error);
|
|
68
89
|
const req = request({
|
|
69
90
|
host: target.hostname.replace(/^\[|\]$/g, ''),
|
|
70
91
|
port: target.port,
|
|
@@ -73,14 +94,23 @@ function get(url, host, timeoutMs = 5000) {
|
|
|
73
94
|
timeout: timeoutMs,
|
|
74
95
|
}, (response) => {
|
|
75
96
|
response.resume();
|
|
76
|
-
response.once('end', () =>
|
|
97
|
+
response.once('end', () => finish(undefined, {
|
|
77
98
|
status: response.statusCode ?? 0,
|
|
78
99
|
type: String(response.headers['content-type'] ?? ''),
|
|
79
100
|
instance: response.headers[VERIFY_HEADER],
|
|
80
101
|
}));
|
|
102
|
+
response.once('aborted', () => finish(new Error('The response was aborted')));
|
|
103
|
+
// Error listeners stay after settling: a late error (the destroy below) is then ignored
|
|
104
|
+
// by `finish` instead of being an unhandled 'error' event.
|
|
105
|
+
response.on('error', failed);
|
|
106
|
+
response.once('close', () => {
|
|
107
|
+
if (!response.complete)
|
|
108
|
+
finish(new Error('The connection closed before the response ended'));
|
|
109
|
+
});
|
|
81
110
|
});
|
|
111
|
+
const deadline = setTimeout(() => finish(new Error(`No complete answer within ${timeoutMs} ms`)), timeoutMs);
|
|
82
112
|
req.once('timeout', () => req.destroy(new Error(`No answer within ${timeoutMs} ms`)));
|
|
83
|
-
req.
|
|
113
|
+
req.on('error', failed);
|
|
84
114
|
req.end();
|
|
85
115
|
});
|
|
86
116
|
}
|
|
@@ -123,18 +153,30 @@ async function settings(root) {
|
|
|
123
153
|
* The HTTP check of verify: GET the readiness path, then `/` of each app domain, at `base`. An
|
|
124
154
|
* answer counts only when it carries `token` in the x-dokebi-verify header (the instance verify
|
|
125
155
|
* started); anything else is retried like a failed request and then reported FOREIGN_SERVER.
|
|
156
|
+
* With neither a readiness path (`healthRoutes: false`) nor an app domain, one identity probe
|
|
157
|
+
* runs instead (ISSUE-0006 A08: no request at all used to pass): `GET /`, which passes with any
|
|
158
|
+
* status below 500 that carries the token, a 404 included (the app may have no route at `/`).
|
|
159
|
+
* Each request must complete within `timeoutMs` (ISSUE-0006 A09).
|
|
126
160
|
*/
|
|
127
|
-
export async function httpChecks(base, paths, token, delayMs = 250) {
|
|
161
|
+
export async function httpChecks(base, paths, token, delayMs = 250, timeoutMs = 5000) {
|
|
128
162
|
const requests = [];
|
|
129
|
-
const probe = async (path, host, attempts) => {
|
|
163
|
+
const probe = async (path, host, attempts, identity = false) => {
|
|
130
164
|
const url = `${base}${path}`;
|
|
165
|
+
const kind = identity ? { identity: true } : {};
|
|
131
166
|
for (let attempt = 1;; attempt++) {
|
|
132
167
|
try {
|
|
133
|
-
const answer = await get(url, host);
|
|
168
|
+
const answer = await get(url, host, timeoutMs);
|
|
134
169
|
const instance = answer.instance === token ? 'ours' : 'other';
|
|
135
|
-
const ok = answer.status >= 200 && answer.status < 400 && instance === 'ours';
|
|
170
|
+
const ok = answer.status >= 200 && answer.status < (identity ? 500 : 400) && instance === 'ours';
|
|
136
171
|
if (ok || attempt >= attempts) {
|
|
137
|
-
requests.push({
|
|
172
|
+
requests.push({
|
|
173
|
+
url,
|
|
174
|
+
...(host ? { host } : {}),
|
|
175
|
+
status: answer.status,
|
|
176
|
+
ok,
|
|
177
|
+
instance,
|
|
178
|
+
...kind,
|
|
179
|
+
});
|
|
138
180
|
return;
|
|
139
181
|
}
|
|
140
182
|
if (instance === 'other')
|
|
@@ -151,6 +193,7 @@ export async function httpChecks(base, paths, token, delayMs = 250) {
|
|
|
151
193
|
status: 0,
|
|
152
194
|
ok: false,
|
|
153
195
|
error: error instanceof Error ? error.message : String(error),
|
|
196
|
+
...kind,
|
|
154
197
|
});
|
|
155
198
|
return;
|
|
156
199
|
}
|
|
@@ -163,6 +206,10 @@ export async function httpChecks(base, paths, token, delayMs = 250) {
|
|
|
163
206
|
await probe(paths.ready, undefined, 20);
|
|
164
207
|
for (const domain of paths.domains)
|
|
165
208
|
await probe('/', domain, 4);
|
|
209
|
+
if (!paths.ready && !paths.domains.length) {
|
|
210
|
+
debug('verify: no readiness route and no app domain; probing GET / for this run', { base });
|
|
211
|
+
await probe('/', undefined, 20, true);
|
|
212
|
+
}
|
|
166
213
|
const failed = requests.filter((item) => !item.ok);
|
|
167
214
|
const foreign = failed.filter((item) => item.instance === 'other');
|
|
168
215
|
return {
|