@ethlete/cli 3.0.0-next.17 → 3.0.0-next.18
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 +6 -0
- package/package.json +1 -1
- package/src/lib/api/api-context.d.ts +15 -0
- package/src/lib/api/api-context.js +23 -0
- package/src/lib/api/api-context.js.map +1 -0
- package/src/lib/api/api-state.d.ts +31 -0
- package/src/lib/api/api-state.js +80 -0
- package/src/lib/api/api-state.js.map +1 -0
- package/src/lib/api/definition.d.ts +37 -1
- package/src/lib/api/definition.js +6 -0
- package/src/lib/api/definition.js.map +1 -1
- package/src/lib/api/doctor.d.ts +16 -0
- package/src/lib/api/doctor.js +71 -0
- package/src/lib/api/doctor.js.map +1 -0
- package/src/lib/api/fingerprint.d.ts +18 -0
- package/src/lib/api/fingerprint.js +63 -0
- package/src/lib/api/fingerprint.js.map +1 -0
- package/src/lib/api/git.d.ts +2 -0
- package/src/lib/api/git.js +7 -1
- package/src/lib/api/git.js.map +1 -1
- package/src/lib/api/help.js +13 -0
- package/src/lib/api/help.js.map +1 -1
- package/src/lib/api/index.d.ts +3 -0
- package/src/lib/api/index.js +3 -0
- package/src/lib/api/index.js.map +1 -1
- package/src/lib/api/load-definitions.js +3 -0
- package/src/lib/api/load-definitions.js.map +1 -1
- package/src/lib/api/lock.d.ts +46 -0
- package/src/lib/api/lock.js +144 -0
- package/src/lib/api/lock.js.map +1 -0
- package/src/lib/api/parse-argv.d.ts +12 -0
- package/src/lib/api/parse-argv.js +31 -0
- package/src/lib/api/parse-argv.js.map +1 -0
- package/src/lib/api/run-locked.d.ts +14 -0
- package/src/lib/api/run-locked.js +62 -0
- package/src/lib/api/run-locked.js.map +1 -0
- package/src/lib/api/run-ready.d.ts +60 -0
- package/src/lib/api/run-ready.js +212 -0
- package/src/lib/api/run-ready.js.map +1 -0
- package/src/lib/api/run-snapshot.d.ts +33 -0
- package/src/lib/api/run-snapshot.js +124 -0
- package/src/lib/api/run-snapshot.js.map +1 -0
- package/src/lib/api/run-status.d.ts +9 -0
- package/src/lib/api/run-status.js +55 -0
- package/src/lib/api/run-status.js.map +1 -0
- package/src/lib/api/run.d.ts +1 -1
- package/src/lib/api/run.js +122 -8
- package/src/lib/api/run.js.map +1 -1
- package/src/lib/api/snapshot.d.ts +63 -0
- package/src/lib/api/snapshot.js +132 -0
- package/src/lib/api/snapshot.js.map +1 -0
- package/src/lib/api/validate-definition.d.ts +7 -0
- package/src/lib/api/validate-definition.js +79 -0
- package/src/lib/api/validate-definition.js.map +1 -0
|
@@ -5,6 +5,7 @@ const fs_1 = require("fs");
|
|
|
5
5
|
const module_1 = require("module");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const definition_1 = require("./definition");
|
|
8
|
+
const validate_definition_1 = require("./validate-definition");
|
|
8
9
|
exports.API_DEFINITIONS_FILE_NAMES = ['ethlete.apis.js', 'ethlete.apis.cjs'];
|
|
9
10
|
/**
|
|
10
11
|
* Loads the repo's API definitions from `ethlete.apis.js`. The file is a module rather than JSON
|
|
@@ -25,6 +26,8 @@ const loadApiDefinitions = (root) => {
|
|
|
25
26
|
`which ${shadowing.length === 1 ? 'is a built-in command' : 'are built-in commands'}. Rename the entry.`);
|
|
26
27
|
}
|
|
27
28
|
}
|
|
29
|
+
for (const [name, api] of Object.entries(apis))
|
|
30
|
+
(0, validate_definition_1.validateApiDefinition)({ fileName, name, api });
|
|
28
31
|
return { found: true, apis, fileName };
|
|
29
32
|
};
|
|
30
33
|
exports.loadApiDefinitions = loadApiDefinitions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-definitions.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/load-definitions.ts"],"names":[],"mappings":";;;AAAA,2BAAgC;AAChC,mCAAuC;AACvC,+BAA4B;AAC5B,6CAAqE;
|
|
1
|
+
{"version":3,"file":"load-definitions.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/load-definitions.ts"],"names":[],"mappings":";;;AAAA,2BAAgC;AAChC,mCAAuC;AACvC,+BAA4B;AAC5B,6CAAqE;AACrE,+DAA8D;AAEjD,QAAA,0BAA0B,GAAG,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;AAKlF;;;GAGG;AACI,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAA4B,EAAE;;IAC3E,MAAM,QAAQ,GAAG,kCAA0B,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAEnG,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAY,IAAA,sBAAa,EAAC,IAAA,WAAI,EAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,IAAA,WAAI,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,MAAC,MAAuC,CAAC,OAAO,mCAAK,MAAyB,CAAC;IAC5F,MAAM,QAAQ,GAAa,CAAC,GAAG,kCAAqB,EAAE,MAAM,CAAC,CAAC;IAE9D,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,IAAI,mCAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAE/F,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,KAAK,IAAI,kBAAkB,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBAC7F,SAAS,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,uBAAuB,qBAAqB,CAC3G,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,IAAA,2CAAqB,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAE/F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACzC,CAAC,CAAC;AAvBW,QAAA,kBAAkB,sBAuB7B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Who holds an API's lock. `token` is handed to the holder's command as `ETHLETE_API_LOCK`. */
|
|
2
|
+
export type ApiLock = {
|
|
3
|
+
pid: number;
|
|
4
|
+
hostname: string;
|
|
5
|
+
command: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
startedAt: string;
|
|
8
|
+
token: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const API_LOCK_ENV = "ETHLETE_API_LOCK";
|
|
11
|
+
export type LockSystem = {
|
|
12
|
+
now: () => number;
|
|
13
|
+
sleep: (ms: number) => Promise<void>;
|
|
14
|
+
kill: (pid: number, signal: 0) => void;
|
|
15
|
+
hostname: () => string;
|
|
16
|
+
pid: number;
|
|
17
|
+
};
|
|
18
|
+
export declare const lockPath: (directory: string) => string;
|
|
19
|
+
/** The current lock, or `undefined` when there is none or it cannot be read. */
|
|
20
|
+
export declare const readApiLock: (directory: string, path?: string) => ApiLock | undefined;
|
|
21
|
+
/** One line naming the holder, for the wait and timeout messages. */
|
|
22
|
+
export declare const describeApiLock: (lock: ApiLock | undefined, directory: string) => string;
|
|
23
|
+
export type AcquireApiLockResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
lock: ApiLock;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
holder: ApiLock | undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Takes the lock in `directory`. A lock whose pid is dead on this host is taken over, and the state
|
|
32
|
+
* becomes dirty. Otherwise it polls every second for up to `wait` seconds, or without end when `wait`
|
|
33
|
+
* is `undefined`, and calls `onHeld` once with the holder.
|
|
34
|
+
*/
|
|
35
|
+
export declare const acquireApiLock: (options: {
|
|
36
|
+
directory: string;
|
|
37
|
+
command: string;
|
|
38
|
+
cwd: string;
|
|
39
|
+
wait?: number;
|
|
40
|
+
onHeld?: (holder: ApiLock | undefined) => void;
|
|
41
|
+
system?: LockSystem;
|
|
42
|
+
}) => Promise<AcquireApiLockResult>;
|
|
43
|
+
/** Removes the lock only while it is still the one `token` names. */
|
|
44
|
+
export declare const releaseApiLock: (directory: string, token: string) => boolean;
|
|
45
|
+
/** The current lock when `ETHLETE_API_LOCK` names it, so a command run under the lock does not wait for it. */
|
|
46
|
+
export declare const heldLockFromEnv: (directory: string, env?: NodeJS.ProcessEnv) => ApiLock | undefined;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.heldLockFromEnv = exports.releaseApiLock = exports.acquireApiLock = exports.describeApiLock = exports.readApiLock = exports.lockPath = exports.API_LOCK_ENV = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const promises_1 = require("timers/promises");
|
|
10
|
+
const api_state_1 = require("./api-state");
|
|
11
|
+
exports.API_LOCK_ENV = 'ETHLETE_API_LOCK';
|
|
12
|
+
const realSystem = () => ({
|
|
13
|
+
now: () => Date.now(),
|
|
14
|
+
sleep: (ms) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { return void (yield (0, promises_1.setTimeout)(ms)); }),
|
|
15
|
+
kill: (pid, signal) => void process.kill(pid, signal),
|
|
16
|
+
hostname: os_1.hostname,
|
|
17
|
+
pid: process.pid,
|
|
18
|
+
});
|
|
19
|
+
const POLL_MS = 1000;
|
|
20
|
+
const lockPath = (directory) => (0, path_1.join)(directory, 'lock.json');
|
|
21
|
+
exports.lockPath = lockPath;
|
|
22
|
+
/** The current lock, or `undefined` when there is none or it cannot be read. */
|
|
23
|
+
const readApiLock = (directory, path = (0, exports.lockPath)(directory)) => {
|
|
24
|
+
try {
|
|
25
|
+
const value = JSON.parse((0, fs_1.readFileSync)(path, 'utf8'));
|
|
26
|
+
return typeof value.token === 'string' && typeof value.pid === 'number' ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.readApiLock = readApiLock;
|
|
33
|
+
/** One line naming the holder, for the wait and timeout messages. */
|
|
34
|
+
const describeApiLock = (lock, directory) => lock
|
|
35
|
+
? `pid ${lock.pid} on ${lock.hostname} since ${lock.startedAt}: ${lock.command} (in ${lock.cwd})`
|
|
36
|
+
: `an unreadable ${(0, exports.lockPath)(directory)}`;
|
|
37
|
+
exports.describeApiLock = describeApiLock;
|
|
38
|
+
const isAlive = (system, pid) => {
|
|
39
|
+
try {
|
|
40
|
+
system.kill(pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return error.code !== 'ESRCH';
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const isStale = (system, lock) => lock.hostname === system.hostname() && !isAlive(system, lock.pid);
|
|
48
|
+
const tryCreate = (directory, lock) => {
|
|
49
|
+
try {
|
|
50
|
+
(0, fs_1.writeFileSync)((0, exports.lockPath)(directory), `${JSON.stringify(lock, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === 'EEXIST')
|
|
55
|
+
return false;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Renames the stale lock away, so of several waiters only the one whose rename succeeds removes it.
|
|
61
|
+
* A rename that took a live lock a faster waiter had just created is linked back.
|
|
62
|
+
*/
|
|
63
|
+
const claimStaleLock = (options) => {
|
|
64
|
+
var _a;
|
|
65
|
+
const { directory, stale, system } = options;
|
|
66
|
+
const claimed = `${(0, exports.lockPath)(directory)}.stale-${system.pid}-${(0, crypto_1.randomUUID)()}`;
|
|
67
|
+
try {
|
|
68
|
+
(0, fs_1.renameSync)((0, exports.lockPath)(directory), claimed);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code === 'ENOENT')
|
|
72
|
+
return;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (((_a = (0, exports.readApiLock)(directory, claimed)) === null || _a === void 0 ? void 0 : _a.token) === stale.token) {
|
|
77
|
+
(0, api_state_1.markApiStateDirty)(directory, new Date(system.now()));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
(0, fs_1.linkSync)(claimed, (0, exports.lockPath)(directory));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code !== 'EEXIST')
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
(0, fs_1.rmSync)(claimed, { force: true });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Takes the lock in `directory`. A lock whose pid is dead on this host is taken over, and the state
|
|
94
|
+
* becomes dirty. Otherwise it polls every second for up to `wait` seconds, or without end when `wait`
|
|
95
|
+
* is `undefined`, and calls `onHeld` once with the holder.
|
|
96
|
+
*/
|
|
97
|
+
const acquireApiLock = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
+
const { directory, command, cwd, wait, onHeld, system = realSystem() } = options;
|
|
99
|
+
const startedAt = system.now();
|
|
100
|
+
let announced = false;
|
|
101
|
+
(0, fs_1.mkdirSync)(directory, { recursive: true });
|
|
102
|
+
for (;;) {
|
|
103
|
+
const lock = {
|
|
104
|
+
pid: system.pid,
|
|
105
|
+
hostname: system.hostname(),
|
|
106
|
+
command,
|
|
107
|
+
cwd,
|
|
108
|
+
startedAt: new Date(system.now()).toISOString(),
|
|
109
|
+
token: (0, crypto_1.randomUUID)(),
|
|
110
|
+
};
|
|
111
|
+
if (tryCreate(directory, lock))
|
|
112
|
+
return { ok: true, lock };
|
|
113
|
+
const holder = (0, exports.readApiLock)(directory);
|
|
114
|
+
if (holder && isStale(system, holder)) {
|
|
115
|
+
claimStaleLock({ directory, stale: holder, system });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!announced) {
|
|
119
|
+
announced = true;
|
|
120
|
+
onHeld === null || onHeld === void 0 ? void 0 : onHeld(holder);
|
|
121
|
+
}
|
|
122
|
+
if (wait !== undefined && system.now() - startedAt >= wait * 1000)
|
|
123
|
+
return { ok: false, holder };
|
|
124
|
+
yield system.sleep(POLL_MS);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
exports.acquireApiLock = acquireApiLock;
|
|
128
|
+
/** Removes the lock only while it is still the one `token` names. */
|
|
129
|
+
const releaseApiLock = (directory, token) => {
|
|
130
|
+
var _a;
|
|
131
|
+
if (((_a = (0, exports.readApiLock)(directory)) === null || _a === void 0 ? void 0 : _a.token) !== token)
|
|
132
|
+
return false;
|
|
133
|
+
(0, fs_1.rmSync)((0, exports.lockPath)(directory), { force: true });
|
|
134
|
+
return true;
|
|
135
|
+
};
|
|
136
|
+
exports.releaseApiLock = releaseApiLock;
|
|
137
|
+
/** The current lock when `ETHLETE_API_LOCK` names it, so a command run under the lock does not wait for it. */
|
|
138
|
+
const heldLockFromEnv = (directory, env = process.env) => {
|
|
139
|
+
const token = env[exports.API_LOCK_ENV];
|
|
140
|
+
const lock = (0, exports.readApiLock)(directory);
|
|
141
|
+
return token && (lock === null || lock === void 0 ? void 0 : lock.token) === token ? lock : undefined;
|
|
142
|
+
};
|
|
143
|
+
exports.heldLockFromEnv = heldLockFromEnv;
|
|
144
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/lock.ts"],"names":[],"mappings":";;;;AAAA,mCAAoC;AACpC,2BAA0F;AAC1F,2BAA8B;AAC9B,+BAA4B;AAC5B,8CAAsD;AACtD,2CAAgD;AAYnC,QAAA,YAAY,GAAG,kBAAkB,CAAC;AAU/C,MAAM,UAAU,GAAG,GAAe,EAAE,CAAC,CAAC;IACpC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IACrB,KAAK,EAAE,CAAO,EAAE,EAAE,EAAE,0DAAC,OAAA,KAAK,CAAC,MAAM,IAAA,qBAAK,EAAC,EAAE,CAAC,CAAC,CAAA,GAAA;IAC3C,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;IACrD,QAAQ,EAAR,aAAQ;IACR,GAAG,EAAE,OAAO,CAAC,GAAG;CACjB,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,IAAI,CAAC;AAEd,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,IAAA,WAAI,EAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAA/D,QAAA,QAAQ,YAAuD;AAE5E,gFAAgF;AACzE,MAAM,WAAW,GAAG,CAAC,SAAiB,EAAE,IAAI,GAAG,IAAA,gBAAQ,EAAC,SAAS,CAAC,EAAuB,EAAE;IAChG,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAqB,CAAC;QAEzE,OAAO,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3G,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AARW,QAAA,WAAW,eAQtB;AAEF,qEAAqE;AAC9D,MAAM,eAAe,GAAG,CAAC,IAAyB,EAAE,SAAiB,EAAE,EAAE,CAC9E,IAAI;IACF,CAAC,CAAC,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC,QAAQ,UAAU,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,OAAO,QAAQ,IAAI,CAAC,GAAG,GAAG;IACjG,CAAC,CAAC,iBAAiB,IAAA,gBAAQ,EAAC,SAAS,CAAC,EAAE,CAAC;AAHhC,QAAA,eAAe,mBAGiB;AAE7C,MAAM,OAAO,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAEpB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC3D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,MAAkB,EAAE,IAAa,EAAE,EAAE,CACpD,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AAEpE,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAE,IAAa,EAAE,EAAE;IACrD,IAAI,CAAC;QACH,IAAA,kBAAa,EAAC,IAAA,gBAAQ,EAAC,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3G,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAErE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,cAAc,GAAG,CAAC,OAAkE,EAAE,EAAE;;IAC5F,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC7C,MAAM,OAAO,GAAG,GAAG,IAAA,gBAAQ,EAAC,SAAS,CAAC,UAAU,MAAM,CAAC,GAAG,IAAI,IAAA,mBAAU,GAAE,EAAE,CAAC;IAE7E,IAAI,CAAC;QACH,IAAA,eAAU,EAAC,IAAA,gBAAQ,EAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QAE/D,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,IAAI,CAAA,MAAA,IAAA,mBAAW,EAAC,SAAS,EAAE,OAAO,CAAC,0CAAE,KAAK,MAAK,KAAK,CAAC,KAAK,EAAE,CAAC;YAC3D,IAAA,6BAAiB,EAAC,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAErD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAA,aAAQ,EAAC,OAAO,EAAE,IAAA,gBAAQ,EAAC,SAAS,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;QACtE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAA,WAAM,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;AACH,CAAC,CAAC;AAIF;;;;GAIG;AACI,MAAM,cAAc,GAAG,CAAO,OAOpC,EAAiC,EAAE;IAClC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,EAAE,GAAG,OAAO,CAAC;IACjF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;IAC/B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAA,cAAS,EAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,SAAS,CAAC;QACR,MAAM,IAAI,GAAY;YACpB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;YAC3B,OAAO;YACP,GAAG;YACH,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;YAC/C,KAAK,EAAE,IAAA,mBAAU,GAAE;SACpB,CAAC;QAEF,IAAI,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAE1D,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,SAAS,CAAC,CAAC;QAEtC,IAAI,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YACtC,cAAc,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAErD,SAAS;QACX,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,MAAM,CAAC,CAAC;QACnB,CAAC;QAED,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,IAAI,GAAG,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAEhG,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC,CAAA,CAAC;AA3CW,QAAA,cAAc,kBA2CzB;AAEF,qEAAqE;AAC9D,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,KAAa,EAAE,EAAE;;IACjE,IAAI,CAAA,MAAA,IAAA,mBAAW,EAAC,SAAS,CAAC,0CAAE,KAAK,MAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAE1D,IAAA,WAAM,EAAC,IAAA,gBAAQ,EAAC,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AANW,QAAA,cAAc,kBAMzB;AAEF,+GAA+G;AACxG,MAAM,eAAe,GAAG,CAAC,SAAiB,EAAE,MAAyB,OAAO,CAAC,GAAG,EAAE,EAAE;IACzF,MAAM,KAAK,GAAG,GAAG,CAAC,oBAAY,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,IAAA,mBAAW,EAAC,SAAS,CAAC,CAAC;IAEpC,OAAO,KAAK,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,MAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC,CAAC;AALW,QAAA,eAAe,mBAK1B"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ParsedApiArgv = {
|
|
2
|
+
/** Every argument before the first `--`, flags included. */
|
|
3
|
+
head: string[];
|
|
4
|
+
/** The non-flag arguments of `head`, without the values of `--profile` and `--wait`. */
|
|
5
|
+
positionals: string[];
|
|
6
|
+
/** Everything after the first `--`, unchanged. `undefined` when there is no `--`. */
|
|
7
|
+
rest: string[] | undefined;
|
|
8
|
+
profile: string | undefined;
|
|
9
|
+
wait: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
/** Splits `et api` arguments at the first `--` and reads the value flags before it. */
|
|
12
|
+
export declare const parseApiArgv: (argv: string[]) => ParsedApiArgv;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseApiArgv = void 0;
|
|
4
|
+
const VALUE_FLAGS = ['--profile', '--wait'];
|
|
5
|
+
/** Splits `et api` arguments at the first `--` and reads the value flags before it. */
|
|
6
|
+
const parseApiArgv = (argv) => {
|
|
7
|
+
const separator = argv.indexOf('--');
|
|
8
|
+
const head = separator === -1 ? argv : argv.slice(0, separator);
|
|
9
|
+
const rest = separator === -1 ? undefined : argv.slice(separator + 1);
|
|
10
|
+
const positionals = [];
|
|
11
|
+
const values = {};
|
|
12
|
+
for (let index = 0; index < head.length; index++) {
|
|
13
|
+
const arg = head[index];
|
|
14
|
+
const flag = VALUE_FLAGS.find((candidate) => arg === candidate || arg.startsWith(`${candidate}=`));
|
|
15
|
+
if (flag !== undefined) {
|
|
16
|
+
if (arg === flag) {
|
|
17
|
+
values[flag] = head[index + 1];
|
|
18
|
+
index++;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
values[flag] = arg.slice(flag.length + 1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (!arg.startsWith('-')) {
|
|
25
|
+
positionals.push(arg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { head, positionals, rest, profile: values['--profile'], wait: values['--wait'] };
|
|
29
|
+
};
|
|
30
|
+
exports.parseApiArgv = parseApiArgv;
|
|
31
|
+
//# sourceMappingURL=parse-argv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-argv.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/parse-argv.ts"],"names":[],"mappings":";;;AAAA,MAAM,WAAW,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAU,CAAC;AAarD,uFAAuF;AAChF,MAAM,YAAY,GAAG,CAAC,IAAc,EAAiB,EAAE;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,MAAM,GAAuC,EAAE,CAAC;IAEtD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAW,CAAC;QAClC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC;QAEnG,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC/B,KAAK,EAAE,CAAC;YACV,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC3F,CAAC,CAAC;AAxBW,QAAA,YAAY,gBAwBvB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LockSystem } from './lock';
|
|
2
|
+
export declare const LOCK_TIMEOUT_EXIT_CODE = 75;
|
|
3
|
+
/**
|
|
4
|
+
* Runs `command` under the API lock in `directory` and returns its exit code, or 75 when the lock
|
|
5
|
+
* stayed taken for `wait` seconds. The state becomes dirty once the command ends.
|
|
6
|
+
*/
|
|
7
|
+
export declare const runLocked: (options: {
|
|
8
|
+
directory: string;
|
|
9
|
+
command: string[];
|
|
10
|
+
cwd: string;
|
|
11
|
+
wait?: number;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
system?: LockSystem;
|
|
14
|
+
}) => Promise<number>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runLocked = exports.LOCK_TIMEOUT_EXIT_CODE = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const api_state_1 = require("./api-state");
|
|
8
|
+
const lock_1 = require("./lock");
|
|
9
|
+
exports.LOCK_TIMEOUT_EXIT_CODE = 75;
|
|
10
|
+
const FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM'];
|
|
11
|
+
const runChild = (options) => new Promise((resolve) => {
|
|
12
|
+
const { command, cwd, env } = options;
|
|
13
|
+
const [binary, ...args] = command;
|
|
14
|
+
const child = (0, child_process_1.spawn)(binary, args, { cwd, env, stdio: 'inherit' });
|
|
15
|
+
const forward = (signal) => void child.kill(signal);
|
|
16
|
+
const done = (code) => {
|
|
17
|
+
for (const signal of FORWARDED_SIGNALS)
|
|
18
|
+
process.off(signal, forward);
|
|
19
|
+
resolve(code);
|
|
20
|
+
};
|
|
21
|
+
for (const signal of FORWARDED_SIGNALS)
|
|
22
|
+
process.on(signal, forward);
|
|
23
|
+
child.once('error', (error) => {
|
|
24
|
+
console.error(`Could not run ${binary}: ${error.message}`);
|
|
25
|
+
done(127);
|
|
26
|
+
});
|
|
27
|
+
child.once('exit', (code, signal) => { var _a; return done(signal ? 128 + ((_a = os_1.constants.signals[signal]) !== null && _a !== void 0 ? _a : 0) : (code !== null && code !== void 0 ? code : 1)); });
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Runs `command` under the API lock in `directory` and returns its exit code, or 75 when the lock
|
|
31
|
+
* stayed taken for `wait` seconds. The state becomes dirty once the command ends.
|
|
32
|
+
*/
|
|
33
|
+
const runLocked = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const { directory, command, cwd, wait, env = process.env, system } = options;
|
|
35
|
+
const acquired = yield (0, lock_1.acquireApiLock)({
|
|
36
|
+
directory,
|
|
37
|
+
command: command.join(' '),
|
|
38
|
+
cwd,
|
|
39
|
+
wait,
|
|
40
|
+
system,
|
|
41
|
+
onHeld: (holder) => console.error(`Waiting for the API lock, held by ${(0, lock_1.describeApiLock)(holder, directory)}.`),
|
|
42
|
+
});
|
|
43
|
+
if (!acquired.ok) {
|
|
44
|
+
console.error(`Gave up after ${wait} s. The API lock is held by ${(0, lock_1.describeApiLock)(acquired.holder, directory)}.`);
|
|
45
|
+
return exports.LOCK_TIMEOUT_EXIT_CODE;
|
|
46
|
+
}
|
|
47
|
+
const { token } = acquired.lock;
|
|
48
|
+
const release = () => {
|
|
49
|
+
process.off('exit', release);
|
|
50
|
+
(0, api_state_1.markApiStateDirty)(directory);
|
|
51
|
+
(0, lock_1.releaseApiLock)(directory, token);
|
|
52
|
+
};
|
|
53
|
+
process.once('exit', release);
|
|
54
|
+
try {
|
|
55
|
+
return yield runChild({ command, cwd, env: Object.assign(Object.assign({}, env), { [lock_1.API_LOCK_ENV]: token }) });
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
release();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
exports.runLocked = runLocked;
|
|
62
|
+
//# sourceMappingURL=run-locked.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-locked.js","sourceRoot":"","sources":["../../../../../../libs/cli/src/lib/api/run-locked.ts"],"names":[],"mappings":";;;;AAAA,iDAAsC;AACtC,2BAA+B;AAC/B,2CAAgD;AAChD,iCAAmG;AAEtF,QAAA,sBAAsB,GAAG,EAAE,CAAC;AAEzC,MAAM,iBAAiB,GAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAElE,MAAM,QAAQ,GAAG,CAAC,OAAmE,EAAE,EAAE,CACvF,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;IAC9B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,OAAgC,CAAC;IAC3D,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,CAAC,MAAsB,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE;QAC5B,KAAK,MAAM,MAAM,IAAI,iBAAiB;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,KAAK,MAAM,MAAM,IAAI,iBAAiB;QAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEpE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QAC5B,OAAO,CAAC,KAAK,CAAC,iBAAiB,MAAM,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAC,OAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,MAAA,cAAS,CAAC,OAAO,CAAC,MAAM,CAAC,mCAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,CAAC,CAAC,CAAC,CAAA,EAAA,CAAC,CAAC;AAC5G,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACI,MAAM,SAAS,GAAG,CAAO,OAO/B,EAAmB,EAAE;IACpB,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC7E,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAc,EAAC;QACpC,SAAS;QACT,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,GAAG;QACH,IAAI;QACJ,MAAM;QACN,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,qCAAqC,IAAA,sBAAe,EAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC;KAC9G,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,iBAAiB,IAAI,+BAA+B,IAAA,sBAAe,EAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;QAElH,OAAO,8BAAsB,CAAC;IAChC,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;IAChC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7B,IAAA,6BAAiB,EAAC,SAAS,CAAC,CAAC;QAC7B,IAAA,qBAAc,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE9B,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,kCAAO,GAAG,KAAE,CAAC,mBAAY,CAAC,EAAE,KAAK,GAAE,EAAE,CAAC,CAAC;IAClF,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAA,CAAC;AAtCW,QAAA,SAAS,aAsCpB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ApiDefinition } from './definition';
|
|
2
|
+
import { LockSystem } from './lock';
|
|
3
|
+
import { SeedStep } from './seeds';
|
|
4
|
+
type Parsed<T> = {
|
|
5
|
+
ok: true;
|
|
6
|
+
value: T;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
problem: string;
|
|
10
|
+
};
|
|
11
|
+
/** One run of the seed machinery: manifest steps copied and run together, or one `exec` entry of the API. */
|
|
12
|
+
export type ReadySeedUnit = {
|
|
13
|
+
kind: 'steps';
|
|
14
|
+
steps: SeedStep[];
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'exec';
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const HEALTH_TIMEOUT_MS = 60000;
|
|
20
|
+
export declare const HEALTH_RETRY_MS = 2000;
|
|
21
|
+
/** Each name is `fixtures`, a seed of `.ethlete/seeds/<api>/seeds.json`, or an `exec` entry, checked in that order. */
|
|
22
|
+
export declare const resolveProfileSeeds: (options: {
|
|
23
|
+
root: string;
|
|
24
|
+
name: string;
|
|
25
|
+
api: ApiDefinition;
|
|
26
|
+
profileName: string;
|
|
27
|
+
seeds: readonly string[];
|
|
28
|
+
}) => Parsed<ReadySeedUnit[]>;
|
|
29
|
+
/** GETs `url` until it answers `expect`, every `retryMs` for up to `timeoutMs`. */
|
|
30
|
+
export declare const waitForHealth: (options: {
|
|
31
|
+
url: string;
|
|
32
|
+
expect: number;
|
|
33
|
+
fetch?: typeof fetch;
|
|
34
|
+
sleep?: (ms: number) => Promise<void>;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
retryMs?: number;
|
|
38
|
+
}) => Promise<{
|
|
39
|
+
ok: true;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
last: string;
|
|
43
|
+
}>;
|
|
44
|
+
export type RunReadyOptions = {
|
|
45
|
+
root: string;
|
|
46
|
+
name: string;
|
|
47
|
+
api: ApiDefinition;
|
|
48
|
+
invocation: string;
|
|
49
|
+
profile: string | undefined;
|
|
50
|
+
wait: number | undefined;
|
|
51
|
+
up: () => Promise<number>;
|
|
52
|
+
runSeeds: (unit: ReadySeedUnit) => Promise<number>;
|
|
53
|
+
fetch?: typeof fetch;
|
|
54
|
+
sleep?: (ms: number) => Promise<void>;
|
|
55
|
+
now?: () => number;
|
|
56
|
+
lockSystem?: LockSystem;
|
|
57
|
+
};
|
|
58
|
+
/** Runs `ready <api> --profile <p>` and returns the exit code. */
|
|
59
|
+
export declare const runReadyCommand: (options: RunReadyOptions) => Promise<number>;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runReadyCommand = exports.waitForHealth = exports.resolveProfileSeeds = exports.HEALTH_RETRY_MS = exports.HEALTH_TIMEOUT_MS = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const promises_1 = require("timers/promises");
|
|
7
|
+
const api_context_1 = require("./api-context");
|
|
8
|
+
const api_state_1 = require("./api-state");
|
|
9
|
+
const compose_1 = require("./compose");
|
|
10
|
+
const doctor_1 = require("./doctor");
|
|
11
|
+
const fingerprint_1 = require("./fingerprint");
|
|
12
|
+
const lock_1 = require("./lock");
|
|
13
|
+
const resolve_checkout_1 = require("./resolve-checkout");
|
|
14
|
+
const run_locked_1 = require("./run-locked");
|
|
15
|
+
const run_snapshot_1 = require("./run-snapshot");
|
|
16
|
+
const seeds_1 = require("./seeds");
|
|
17
|
+
const suggest_1 = require("./suggest");
|
|
18
|
+
const snapshot_1 = require("./snapshot");
|
|
19
|
+
exports.HEALTH_TIMEOUT_MS = 60000;
|
|
20
|
+
exports.HEALTH_RETRY_MS = 2000;
|
|
21
|
+
const has = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
22
|
+
/** Each name is `fixtures`, a seed of `.ethlete/seeds/<api>/seeds.json`, or an `exec` entry, checked in that order. */
|
|
23
|
+
const resolveProfileSeeds = (options) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
const { root, name, api, profileName, seeds } = options;
|
|
26
|
+
const manifest = (0, seeds_1.readSeedManifest)(root, name);
|
|
27
|
+
const units = [];
|
|
28
|
+
for (const seed of seeds) {
|
|
29
|
+
const fromManifest = manifest.ok && (seed === 'fixtures' ? manifest.value.fixtures !== undefined : has(manifest.value.seeds, seed))
|
|
30
|
+
? (0, seeds_1.seedSteps)({
|
|
31
|
+
manifest: manifest.value,
|
|
32
|
+
names: seed === 'fixtures' ? [] : [seed],
|
|
33
|
+
withFixtures: seed === 'fixtures',
|
|
34
|
+
api: name,
|
|
35
|
+
})
|
|
36
|
+
: undefined;
|
|
37
|
+
if (fromManifest === null || fromManifest === void 0 ? void 0 : fromManifest.ok) {
|
|
38
|
+
const last = units[units.length - 1];
|
|
39
|
+
if ((last === null || last === void 0 ? void 0 : last.kind) === 'steps')
|
|
40
|
+
last.steps.push(...fromManifest.value);
|
|
41
|
+
else
|
|
42
|
+
units.push({ kind: 'steps', steps: [...fromManifest.value] });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (((_a = api.exec) === null || _a === void 0 ? void 0 : _a[seed]) !== undefined) {
|
|
46
|
+
units.push({ kind: 'exec', name: seed });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const candidates = [
|
|
50
|
+
...(manifest.ok && manifest.value.fixtures ? ['fixtures'] : []),
|
|
51
|
+
...(manifest.ok ? Object.keys(manifest.value.seeds) : []),
|
|
52
|
+
...Object.keys((_b = api.exec) !== null && _b !== void 0 ? _b : {}),
|
|
53
|
+
];
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
problem: `The ${profileName} profile of the ${name} API names "${seed}", which is no seed, fixtures list or exec entry.` +
|
|
57
|
+
`${(0, suggest_1.didYouMean)(seed, candidates)}\n\nKnown: ${candidates.length > 0 ? candidates.join(', ') : 'none'}` +
|
|
58
|
+
(manifest.ok ? '' : `\n\n${manifest.problem}`),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, value: units };
|
|
62
|
+
};
|
|
63
|
+
exports.resolveProfileSeeds = resolveProfileSeeds;
|
|
64
|
+
/** GETs `url` until it answers `expect`, every `retryMs` for up to `timeoutMs`. */
|
|
65
|
+
const waitForHealth = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
const { url, expect, fetch: get = fetch, sleep = (ms) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { return void (yield (0, promises_1.setTimeout)(ms)); }), now = Date.now, timeoutMs = exports.HEALTH_TIMEOUT_MS, retryMs = exports.HEALTH_RETRY_MS, } = options;
|
|
67
|
+
const startedAt = now();
|
|
68
|
+
for (;;) {
|
|
69
|
+
let last;
|
|
70
|
+
try {
|
|
71
|
+
const left = Math.max(1, timeoutMs - (now() - startedAt));
|
|
72
|
+
const response = yield get(url, { signal: AbortSignal.timeout(Math.min(retryMs * 5, left)) });
|
|
73
|
+
if (response.status === expect)
|
|
74
|
+
return { ok: true };
|
|
75
|
+
last = `status ${response.status}`;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
last = error.message;
|
|
79
|
+
}
|
|
80
|
+
if (now() - startedAt >= timeoutMs)
|
|
81
|
+
return { ok: false, last };
|
|
82
|
+
yield sleep(retryMs);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
exports.waitForHealth = waitForHealth;
|
|
86
|
+
const step = (text) => console.error(`\n==> ${text}`);
|
|
87
|
+
/** Runs `ready <api> --profile <p>` and returns the exit code. */
|
|
88
|
+
const runReadyCommand = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
+
var _a, _b, _c, _d, _e;
|
|
90
|
+
const { root, name, api, invocation, profile: profileName, wait, now = Date.now } = options;
|
|
91
|
+
const startedAt = now();
|
|
92
|
+
const profileNames = Object.keys((_a = api.profiles) !== null && _a !== void 0 ? _a : {});
|
|
93
|
+
const profile = profileName === undefined ? undefined : (_b = api.profiles) === null || _b === void 0 ? void 0 : _b[profileName];
|
|
94
|
+
if (profileName === undefined || profile === undefined) {
|
|
95
|
+
console.error((profileName === undefined
|
|
96
|
+
? `ready needs a profile: ${invocation} ready ${name} --profile <profile>.`
|
|
97
|
+
: `The ${name} API has no profile "${profileName}".${(0, suggest_1.didYouMean)(profileName, profileNames)}`) +
|
|
98
|
+
`\n\nProfiles: ${profileNames.length > 0 ? profileNames.join(', ') : 'none'}`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const units = (0, exports.resolveProfileSeeds)({ root, name, api, profileName, seeds: profile.seeds });
|
|
102
|
+
if (!units.ok) {
|
|
103
|
+
console.error(units.problem);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
const context = (0, api_context_1.stateDirectoryOf)({ root, name, api });
|
|
107
|
+
if (!context.ok) {
|
|
108
|
+
console.error((0, resolve_checkout_1.checkoutProblem)({ failure: context, name, invocation }));
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
const tool = (0, compose_1.resolveComposeTool)();
|
|
112
|
+
if (!tool) {
|
|
113
|
+
console.error(`No compose tool found. Tried: ${(0, compose_1.composeToolNames)().join(', ')}.`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
const { checkout, directory } = context;
|
|
117
|
+
const env = Object.assign(Object.assign(Object.assign({}, (0, compose_1.engineEnv)(tool.engine)), process.env), (0, api_context_1.definedEntries)((_d = (_c = api.env) === null || _c === void 0 ? void 0 : _c.call(api)) !== null && _d !== void 0 ? _d : {}));
|
|
118
|
+
let token;
|
|
119
|
+
if ((0, lock_1.heldLockFromEnv)(directory)) {
|
|
120
|
+
step('Using the API lock this command runs under');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
step('Taking the API lock');
|
|
124
|
+
const acquired = yield (0, lock_1.acquireApiLock)({
|
|
125
|
+
directory,
|
|
126
|
+
command: `${invocation} ready ${name} --profile ${profileName}`,
|
|
127
|
+
cwd: root,
|
|
128
|
+
wait,
|
|
129
|
+
system: options.lockSystem,
|
|
130
|
+
onHeld: (holder) => console.error(`Waiting for the API lock, held by ${(0, lock_1.describeApiLock)(holder, directory)}.`),
|
|
131
|
+
});
|
|
132
|
+
if (!acquired.ok) {
|
|
133
|
+
console.error(`Gave up after ${wait} s. The API lock is held by ${(0, lock_1.describeApiLock)(acquired.holder, directory)}.`);
|
|
134
|
+
return run_locked_1.LOCK_TIMEOUT_EXIT_CODE;
|
|
135
|
+
}
|
|
136
|
+
token = acquired.lock.token;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
step(`Starting ${name}`);
|
|
140
|
+
const upCode = yield options.up();
|
|
141
|
+
if (upCode !== 0)
|
|
142
|
+
return upCode;
|
|
143
|
+
step('Checking the API (doctor --fix)');
|
|
144
|
+
if (!(0, doctor_1.runDoctor)({ name, api, tool, cwd: checkout.composePath, env, invocation, fix: true }).ok)
|
|
145
|
+
return 1;
|
|
146
|
+
const fingerprint = (0, fingerprint_1.fingerprintApi)({
|
|
147
|
+
root,
|
|
148
|
+
name,
|
|
149
|
+
repoPath: checkout.repoPath,
|
|
150
|
+
migrationsDir: api.migrationsDir,
|
|
151
|
+
seeds: profile.seeds,
|
|
152
|
+
});
|
|
153
|
+
const state = (0, api_state_1.readApiState)(directory);
|
|
154
|
+
const [binary, ...prefix] = tool.compose;
|
|
155
|
+
const compose = { binary, prefix, cwd: checkout.composePath, env };
|
|
156
|
+
const file = (0, snapshot_1.snapshotFilePath)({ root, api: name, name: profileName, fingerprint });
|
|
157
|
+
if (state && !state.dirty && state.profile === profileName && state.fingerprint === fingerprint) {
|
|
158
|
+
step(`Already ready: ${profileName} at ${fingerprint}`);
|
|
159
|
+
}
|
|
160
|
+
else if (profile.snapshot && (0, fs_1.existsSync)(file)) {
|
|
161
|
+
step(`Restoring the ${profileName} snapshot ${fingerprint}`);
|
|
162
|
+
const restored = yield (0, snapshot_1.restoreSnapshot)({
|
|
163
|
+
file,
|
|
164
|
+
snapshot: profile.snapshot,
|
|
165
|
+
compose,
|
|
166
|
+
state: { directory, profile: profileName, fingerprint },
|
|
167
|
+
});
|
|
168
|
+
if (restored !== 0)
|
|
169
|
+
return restored;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
(0, api_state_1.markApiStateDirty)(directory);
|
|
173
|
+
for (const unit of units.value) {
|
|
174
|
+
step(`Seeding ${unit.kind === 'exec' ? unit.name : unit.steps.map(({ label }) => label).join(', ')}`);
|
|
175
|
+
const seeded = yield options.runSeeds(unit);
|
|
176
|
+
if (seeded !== 0)
|
|
177
|
+
return seeded;
|
|
178
|
+
}
|
|
179
|
+
if (profile.snapshot) {
|
|
180
|
+
step(`Saving the ${profileName} snapshot ${fingerprint}`);
|
|
181
|
+
(0, run_snapshot_1.warnUnlessSnapshotIgnored)(root, file);
|
|
182
|
+
const saved = yield (0, snapshot_1.saveSnapshot)({ file, snapshot: profile.snapshot, compose });
|
|
183
|
+
if (saved !== 0)
|
|
184
|
+
return saved;
|
|
185
|
+
}
|
|
186
|
+
(0, api_state_1.writeApiState)({ directory, state: { profile: profileName, fingerprint, dirty: false } });
|
|
187
|
+
}
|
|
188
|
+
if (profile.health) {
|
|
189
|
+
const expect = (_e = profile.health.expect) !== null && _e !== void 0 ? _e : 200;
|
|
190
|
+
step(`Waiting for ${profile.health.url} to answer ${expect}`);
|
|
191
|
+
const health = yield (0, exports.waitForHealth)({
|
|
192
|
+
url: profile.health.url,
|
|
193
|
+
expect,
|
|
194
|
+
fetch: options.fetch,
|
|
195
|
+
sleep: options.sleep,
|
|
196
|
+
now,
|
|
197
|
+
});
|
|
198
|
+
if (!health.ok) {
|
|
199
|
+
console.error(`${profile.health.url} did not answer ${expect} within ${exports.HEALTH_TIMEOUT_MS / 1000} s. Last: ${health.last}.`);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
console.log(`${name} is ready: profile ${profileName}, fingerprint ${fingerprint}, in ${((now() - startedAt) / 1000).toFixed(1)} s.`);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
if (token !== undefined)
|
|
208
|
+
(0, lock_1.releaseApiLock)(directory, token);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
exports.runReadyCommand = runReadyCommand;
|
|
212
|
+
//# sourceMappingURL=run-ready.js.map
|