@fairfox/polly 0.72.0 → 0.73.1
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/dist/src/elysia/index.js +464 -4
- package/dist/src/elysia/index.js.map +6 -4
- package/dist/src/peer.d.ts +2 -0
- package/dist/src/peer.js +468 -4
- package/dist/src/peer.js.map +8 -5
- package/dist/src/polly-ui/ActionInput.d.ts +2 -1
- package/dist/src/polly-ui/ActionSelect.d.ts +2 -1
- package/dist/src/polly-ui/Button.d.ts +4 -0
- package/dist/src/polly-ui/Cluster.d.ts +2 -1
- package/dist/src/polly-ui/Code.d.ts +2 -1
- package/dist/src/polly-ui/Dropdown.d.ts +6 -0
- package/dist/src/polly-ui/Surface.d.ts +12 -1
- package/dist/src/polly-ui/Text.d.ts +23 -11
- package/dist/src/polly-ui/index.css +44 -18
- package/dist/src/polly-ui/index.js +118 -12
- package/dist/src/polly-ui/index.js.map +12 -11
- package/dist/src/polly-ui/internal/passthrough.d.ts +25 -0
- package/dist/src/polly-ui/styles.css +59 -18
- package/dist/src/polly-ui/theme.css +1 -0
- package/dist/src/shared/lib/peer-repo-server.d.ts +18 -0
- package/dist/src/shared/lib/sweep-sealed.d.ts +111 -0
- package/dist/tools/test/src/browser/run.js +42 -33
- package/dist/tools/test/src/browser/run.js.map +6 -5
- package/dist/tools/test/src/browser/runner-core.d.ts +32 -0
- package/dist/tools/test/src/e2e-mesh/index.js +193 -171
- package/dist/tools/test/src/e2e-mesh/index.js.map +4 -4
- package/dist/tools/test/src/visual/index.js +248 -229
- package/dist/tools/test/src/visual/index.js.map +5 -5
- package/dist/tools/verify/specs/tla/MeshSeed.cfg +27 -0
- package/dist/tools/verify/specs/tla/MeshSeed.tla +179 -0
- package/dist/tools/verify/specs/tla/README.md +11 -1
- package/dist/tools/verify/src/cli.js +79 -2
- package/dist/tools/verify/src/cli.js.map +7 -6
- package/dist/tools/visualize/src/cli.js +179 -3
- package/dist/tools/visualize/src/cli.js.map +6 -6
- package/package.json +3 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
SPECIFICATION Spec
|
|
2
|
+
|
|
3
|
+
\* Three peers. The concurrent-seed race needs only two seeders; the
|
|
4
|
+
\* third costs nothing and gives margin. The state space is tiny — each
|
|
5
|
+
\* peer's slot moves once, from Unseeded to seeded — so TLC exhausts it
|
|
6
|
+
\* instantly.
|
|
7
|
+
\*
|
|
8
|
+
\* SeedDeterministic = TRUE models the polly#113 fix. The verify CLI
|
|
9
|
+
\* rewrites this constant to FALSE when POLLY_113_DISABLE_FIX is set, to
|
|
10
|
+
\* falsify the guard: under FALSE, TLC must report a SeedConvergence
|
|
11
|
+
\* violation.
|
|
12
|
+
CONSTANTS
|
|
13
|
+
Peers = {peer_a, peer_b, peer_c}
|
|
14
|
+
SeedDeterministic = TRUE
|
|
15
|
+
|
|
16
|
+
\* MeshSeed is a terminating protocol: once every peer has seeded, no
|
|
17
|
+
\* action is enabled. That terminal state is the goal, not a deadlock
|
|
18
|
+
\* bug, so deadlock checking is disabled — EventuallySeeded is the
|
|
19
|
+
\* property that asserts the protocol actually reaches it.
|
|
20
|
+
CHECK_DEADLOCK FALSE
|
|
21
|
+
|
|
22
|
+
INVARIANTS
|
|
23
|
+
TypeOK
|
|
24
|
+
SeedConvergence
|
|
25
|
+
|
|
26
|
+
PROPERTIES
|
|
27
|
+
EventuallySeeded
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
-------------------------- MODULE MeshSeed --------------------------
|
|
2
|
+
(*
|
|
3
|
+
Formal specification of Polly's $meshState concurrent first-time seed.
|
|
4
|
+
|
|
5
|
+
This spec exists to close the polly#114 gap: the polly#113 concurrent-
|
|
6
|
+
seed race lived entirely outside the verify and visualise pipelines, so
|
|
7
|
+
"all green" told a mesh-using consumer nothing about it. MeshSeed makes
|
|
8
|
+
the seed path a first-class, model-checked property.
|
|
9
|
+
|
|
10
|
+
The race
|
|
11
|
+
--------
|
|
12
|
+
|
|
13
|
+
When a device first reads a $meshState document it materialises the
|
|
14
|
+
document against empty local storage — the loadOrSeed path in
|
|
15
|
+
mesh-state.ts. If two devices do this concurrently for the same logical
|
|
16
|
+
key, each writes its own copy of the initial document.
|
|
17
|
+
|
|
18
|
+
- Pre-fix, loadOrSeed used `Automerge.from(initialDoc)`, which stamps
|
|
19
|
+
the seed change with a fresh random actorId and a `Date.now()`
|
|
20
|
+
timestamp. Two devices therefore produce two *distinct* Automerge
|
|
21
|
+
documents — same logical content, different identity. They have no
|
|
22
|
+
common ancestor, so a later sync cannot reconcile them: the two
|
|
23
|
+
seeds coexist as a permanent fork.
|
|
24
|
+
|
|
25
|
+
- The polly#113 fix switched loadOrSeed to
|
|
26
|
+
`Automerge.init({ actor: deriveSeedActor(docId) })` plus
|
|
27
|
+
`Automerge.change(doc, { time: 0 }, …)`. The actor is derived from
|
|
28
|
+
the docId and the change time is fixed, so every device seeding the
|
|
29
|
+
same key produces byte-identical content — literally the same
|
|
30
|
+
document. There is nothing to reconcile.
|
|
31
|
+
|
|
32
|
+
Model
|
|
33
|
+
-----
|
|
34
|
+
|
|
35
|
+
`SeedDeterministic` is the spec's single knob. TRUE models the
|
|
36
|
+
polly#113 fix; FALSE restores the pre-fix behaviour — the same toggle
|
|
37
|
+
the `POLLY_113_DISABLE_FIX` environment variable exposes in
|
|
38
|
+
seedDocumentBytes. The verify CLI flips this constant when the
|
|
39
|
+
environment variable is set, so a regression in the seed path is
|
|
40
|
+
caught here as a TLC counterexample rather than only by a hand-written
|
|
41
|
+
unit test.
|
|
42
|
+
|
|
43
|
+
- `doc[p]` is peer p's copy of the document, or `Unseeded` before it
|
|
44
|
+
has materialised one.
|
|
45
|
+
|
|
46
|
+
- `SeedValue(p)` is the content peer p writes when it seeds. Under the
|
|
47
|
+
fix it is a single shared constant (all peers agree); pre-fix it is
|
|
48
|
+
tagged by the seeding peer (every peer differs). The pre-fix model
|
|
49
|
+
treats every peer's seed as distinct: a specification must withstand
|
|
50
|
+
what is *possible*, and pre-fix divergence is possible, so the worst
|
|
51
|
+
case is the honest one. A nondeterministic seed choice would only
|
|
52
|
+
multiply states without adding insight — the divergent branch is
|
|
53
|
+
what matters and TLC explores it either way.
|
|
54
|
+
|
|
55
|
+
- `Sync` delivers a seeded document to a peer that holds none. A peer
|
|
56
|
+
that has already seeded independently is never overwritten: its
|
|
57
|
+
document and the remote one have no common ancestor, so no merge
|
|
58
|
+
reconciles them. Modelling the fork this way is sound for the safety
|
|
59
|
+
property below — `SeedConvergence` is violated the moment two peers
|
|
60
|
+
hold distinct documents, and no later step can un-violate a safety
|
|
61
|
+
invariant TLC has already reported.
|
|
62
|
+
|
|
63
|
+
One document is enough
|
|
64
|
+
----------------------
|
|
65
|
+
|
|
66
|
+
The polly#113 race is per-docId. Distinct $meshState documents seed
|
|
67
|
+
through independent loadOrSeed calls, hold independent storage entries,
|
|
68
|
+
and never share state — there is no action by which one document's
|
|
69
|
+
seed influences another's. A model of one document is therefore a
|
|
70
|
+
sound representative of any number of them: the reachable seed/sync
|
|
71
|
+
interleavings of N independent documents are exactly the product of
|
|
72
|
+
N copies of this model, and a safety invariant that holds on one copy
|
|
73
|
+
holds on the product. Modelling one document keeps the state space
|
|
74
|
+
minimal without weakening the result.
|
|
75
|
+
|
|
76
|
+
Properties verified
|
|
77
|
+
-------------------
|
|
78
|
+
|
|
79
|
+
1. TypeOK — type safety across every transition.
|
|
80
|
+
|
|
81
|
+
2. SeedConvergence — every peer that holds the document holds the same
|
|
82
|
+
one. Under the fix this is invariant. Pre-fix, the trace
|
|
83
|
+
`Seed(p)` then `Seed(q)` reaches a state where p and q hold
|
|
84
|
+
distinct documents and the invariant fails — that failure is the
|
|
85
|
+
polly#113 race, surfaced by model checking.
|
|
86
|
+
|
|
87
|
+
3. EventuallySeeded (liveness) — every peer eventually materialises
|
|
88
|
+
the document. Holds under both settings of SeedDeterministic; it
|
|
89
|
+
is the property that earns the WF_vars(Next) fairness conjunct.
|
|
90
|
+
Weak fairness on the whole next-state relation suffices because the
|
|
91
|
+
model is monotone: every step moves one peer from Unseeded to
|
|
92
|
+
seeded and none ever back, so no step can be starved of progress.
|
|
93
|
+
*)
|
|
94
|
+
|
|
95
|
+
CONSTANTS
|
|
96
|
+
Peers, \* Set of mesh peer identifiers
|
|
97
|
+
SeedDeterministic \* TRUE = polly#113 fix; FALSE = pre-fix seed race
|
|
98
|
+
|
|
99
|
+
VARIABLES
|
|
100
|
+
doc \* [Peers -> SeedValues \cup {Unseeded}]
|
|
101
|
+
|
|
102
|
+
vars == <<doc>>
|
|
103
|
+
|
|
104
|
+
\* A peer that has not yet materialised the document.
|
|
105
|
+
Unseeded == "unseeded"
|
|
106
|
+
|
|
107
|
+
\* The content a peer writes when it first seeds the document. Under the
|
|
108
|
+
\* fix every peer produces the same bytes, so the value is one shared
|
|
109
|
+
\* constant; pre-fix each peer produces a distinct document, modelled as
|
|
110
|
+
\* a value tagged by the seeding peer.
|
|
111
|
+
SeedValue(p) == IF SeedDeterministic THEN "seed" ELSE p
|
|
112
|
+
|
|
113
|
+
\* The set of all possible seeded values, for the type invariant.
|
|
114
|
+
SeedValues == IF SeedDeterministic THEN {"seed"} ELSE Peers
|
|
115
|
+
|
|
116
|
+
-----------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
(* Initial state: no peer has materialised the document. *)
|
|
119
|
+
|
|
120
|
+
Init ==
|
|
121
|
+
doc = [p \in Peers |-> Unseeded]
|
|
122
|
+
|
|
123
|
+
-----------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
(* Actions *)
|
|
126
|
+
|
|
127
|
+
(* A peer materialises the document for the first time against empty
|
|
128
|
+
local storage — Polly's $meshState loadOrSeed path. *)
|
|
129
|
+
Seed(p) ==
|
|
130
|
+
/\ doc[p] = Unseeded
|
|
131
|
+
/\ doc' = [doc EXCEPT ![p] = SeedValue(p)]
|
|
132
|
+
|
|
133
|
+
(* Sync delivers a seeded peer's document to a peer that holds none.
|
|
134
|
+
A peer that has already seeded independently is never overwritten —
|
|
135
|
+
see the header note on why that is sound for SeedConvergence. *)
|
|
136
|
+
Sync(src, dst) ==
|
|
137
|
+
/\ src # dst
|
|
138
|
+
/\ doc[src] # Unseeded
|
|
139
|
+
/\ doc[dst] = Unseeded
|
|
140
|
+
/\ doc' = [doc EXCEPT ![dst] = doc[src]]
|
|
141
|
+
|
|
142
|
+
-----------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
(* Next-state relation *)
|
|
145
|
+
|
|
146
|
+
Next ==
|
|
147
|
+
\/ \E p \in Peers : Seed(p)
|
|
148
|
+
\/ \E src, dst \in Peers : Sync(src, dst)
|
|
149
|
+
|
|
150
|
+
Spec == Init /\ [][Next]_vars /\ WF_vars(Next)
|
|
151
|
+
|
|
152
|
+
-----------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
(* Invariants *)
|
|
155
|
+
|
|
156
|
+
(* Type safety: every peer's slot is Unseeded or a valid seeded value. *)
|
|
157
|
+
TypeOK ==
|
|
158
|
+
doc \in [Peers -> SeedValues \cup {Unseeded}]
|
|
159
|
+
|
|
160
|
+
(* Seed convergence: every peer that holds the document holds the same
|
|
161
|
+
one. Under the polly#113 fix (SeedDeterministic = TRUE) this is
|
|
162
|
+
invariant. Pre-fix (FALSE) the trace Seed(p) ; Seed(q) reaches a
|
|
163
|
+
state where p and q hold distinct documents and TLC reports the
|
|
164
|
+
violation — that is the polly#113 race. *)
|
|
165
|
+
SeedConvergence ==
|
|
166
|
+
\A p, q \in Peers :
|
|
167
|
+
(doc[p] # Unseeded /\ doc[q] # Unseeded) => doc[p] = doc[q]
|
|
168
|
+
|
|
169
|
+
-----------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
(* Liveness *)
|
|
172
|
+
|
|
173
|
+
(* Every peer eventually materialises the document. Holds under both
|
|
174
|
+
settings of SeedDeterministic — see the header note on why weak
|
|
175
|
+
fairness on Next suffices for this monotone model. *)
|
|
176
|
+
EventuallySeeded ==
|
|
177
|
+
<>(\A p \in Peers : doc[p] # Unseeded)
|
|
178
|
+
|
|
179
|
+
=============================================================================
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# TLA+ Formal Specifications for Polly
|
|
2
2
|
|
|
3
3
|
This directory contains formal specifications for Polly's distributed
|
|
4
|
-
protocols using TLA+ (Temporal Logic of Actions). There are
|
|
4
|
+
protocols using TLA+ (Temporal Logic of Actions). There are four specs:
|
|
5
5
|
|
|
6
6
|
- **MessageRouter.tla** — the original message-routing spec for the web
|
|
7
7
|
extension's cross-context message bus. Single-writer, routing-focused,
|
|
@@ -21,6 +21,16 @@ protocols using TLA+ (Temporal Logic of Actions). There are three specs:
|
|
|
21
21
|
peer is revoked, honest peers drop its future ops), and no-fabrication
|
|
22
22
|
(every op in any replica has a known producer).
|
|
23
23
|
|
|
24
|
+
- **MeshSeed.tla** — the `$meshState` concurrent first-time seed
|
|
25
|
+
(polly#114). Model-checks the polly#113 race: two devices materialising
|
|
26
|
+
the same document concurrently against empty storage. Its single
|
|
27
|
+
`SeedDeterministic` constant is TRUE for the shipped fix and FALSE for
|
|
28
|
+
the pre-fix seed; under FALSE, TLC reports a `SeedConvergence`
|
|
29
|
+
violation. The verify CLI runs this spec whenever a project declares
|
|
30
|
+
`mesh:` documents and flips the constant when `POLLY_113_DISABLE_FIX`
|
|
31
|
+
is set, so a regression in `seedDocumentBytes` cannot land green.
|
|
32
|
+
`scripts/e2e-verify-mesh-seed.ts` runs both directions through TLC.
|
|
33
|
+
|
|
24
34
|
Each spec has a companion `.cfg` file with the small bounded constants
|
|
25
35
|
TLC uses when model-checking the spec exhaustively.
|
|
26
36
|
|
|
@@ -5001,14 +5001,37 @@ class HandlerExtractor {
|
|
|
5001
5001
|
return null;
|
|
5002
5002
|
const key = keyArg.getLiteralValue();
|
|
5003
5003
|
const variableName = this.getVariableNameFromParent(node) || key;
|
|
5004
|
+
const access = this.extractMeshAccess(args[2]);
|
|
5004
5005
|
return {
|
|
5005
5006
|
kind,
|
|
5006
5007
|
key,
|
|
5007
5008
|
variableName,
|
|
5008
5009
|
filePath,
|
|
5009
|
-
line: node.getStartLineNumber()
|
|
5010
|
+
line: node.getStartLineNumber(),
|
|
5011
|
+
...access ? { access } : {}
|
|
5010
5012
|
};
|
|
5011
5013
|
}
|
|
5014
|
+
extractMeshAccess(optionsArg) {
|
|
5015
|
+
if (!optionsArg || !Node2.isObjectLiteralExpression(optionsArg))
|
|
5016
|
+
return;
|
|
5017
|
+
const accessProp = optionsArg.getProperty("access");
|
|
5018
|
+
if (!accessProp || !Node2.isPropertyAssignment(accessProp))
|
|
5019
|
+
return;
|
|
5020
|
+
const accessObj = accessProp.getInitializer();
|
|
5021
|
+
if (!accessObj || !Node2.isObjectLiteralExpression(accessObj))
|
|
5022
|
+
return;
|
|
5023
|
+
const predicateText = (name) => {
|
|
5024
|
+
const prop = accessObj.getProperty(name);
|
|
5025
|
+
if (!prop || !Node2.isPropertyAssignment(prop))
|
|
5026
|
+
return;
|
|
5027
|
+
return prop.getInitializer()?.getText().replace(/\s+/g, " ").trim();
|
|
5028
|
+
};
|
|
5029
|
+
const read = predicateText("read");
|
|
5030
|
+
const write = predicateText("write");
|
|
5031
|
+
if (read === undefined && write === undefined)
|
|
5032
|
+
return;
|
|
5033
|
+
return { read: read ?? "unset", write: write ?? "unset" };
|
|
5034
|
+
}
|
|
5012
5035
|
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates, resources) {
|
|
5013
5036
|
const filePath = sourceFile.getFilePath();
|
|
5014
5037
|
if (this.analyzedFiles.has(filePath)) {
|
|
@@ -7535,6 +7558,20 @@ async function analyzeCodebase(options) {
|
|
|
7535
7558
|
const extractor = new TypeExtractor(options.tsConfigPath);
|
|
7536
7559
|
return extractor.analyzeCodebase(options.stateFilePath);
|
|
7537
7560
|
}
|
|
7561
|
+
// tools/verify/src/runner/mesh-seed.ts
|
|
7562
|
+
function meshSeedCfg(baseCfg, opts) {
|
|
7563
|
+
if (!opts.disableFix)
|
|
7564
|
+
return baseCfg;
|
|
7565
|
+
const declaration = /^([ \t]*)SeedDeterministic = TRUE[ \t]*$/m;
|
|
7566
|
+
if (!declaration.test(baseCfg)) {
|
|
7567
|
+
throw new Error("MeshSeed.cfg no longer declares `SeedDeterministic = TRUE` on its own line — the polly#114 seed-race guard cannot be falsified. Update tools/verify/src/runner/mesh-seed.ts.");
|
|
7568
|
+
}
|
|
7569
|
+
return baseCfg.replace(declaration, "$1SeedDeterministic = FALSE");
|
|
7570
|
+
}
|
|
7571
|
+
function isSeedFixDisabled(env = process.env) {
|
|
7572
|
+
return env["POLLY_113_DISABLE_FIX"] === "1";
|
|
7573
|
+
}
|
|
7574
|
+
|
|
7538
7575
|
// tools/verify/src/cli.ts
|
|
7539
7576
|
var __dirname = "/Users/AJT/projects/polly/packages/polly/tools/verify/src";
|
|
7540
7577
|
var COLORS = {
|
|
@@ -7921,6 +7958,17 @@ async function runMonolithicVerification(config, analysis) {
|
|
|
7921
7958
|
timeout: timeoutSeconds > 0 ? timeoutSeconds * 1000 : undefined,
|
|
7922
7959
|
maxDepth
|
|
7923
7960
|
});
|
|
7961
|
+
const meshSeed = await runMeshSeedGuard(docker, specDir, config);
|
|
7962
|
+
if (meshSeed && !meshSeed.success) {
|
|
7963
|
+
console.log(color(`
|
|
7964
|
+
❌ Mesh seed-race guard failed (polly#113 / polly#114)
|
|
7965
|
+
`, COLORS.red));
|
|
7966
|
+
displayVerificationResults(meshSeed, specDir);
|
|
7967
|
+
}
|
|
7968
|
+
if (meshSeed) {
|
|
7969
|
+
console.log(color(`✓ Mesh seed-race guard passed (MeshSeed.tla)
|
|
7970
|
+
`, COLORS.green));
|
|
7971
|
+
}
|
|
7924
7972
|
displayVerificationResults(result, specDir);
|
|
7925
7973
|
}
|
|
7926
7974
|
async function runSubsystemVerification(config, analysis) {
|
|
@@ -8131,6 +8179,35 @@ function findAndCopyBaseSpec(specDir) {
|
|
|
8131
8179
|
}
|
|
8132
8180
|
}
|
|
8133
8181
|
}
|
|
8182
|
+
function findMeshSeedSpecDir() {
|
|
8183
|
+
const candidates = [
|
|
8184
|
+
path4.join(process.cwd(), "specs", "tla"),
|
|
8185
|
+
path4.join(__dirname, "..", "specs", "tla"),
|
|
8186
|
+
path4.join(__dirname, "..", "..", "specs", "tla"),
|
|
8187
|
+
path4.join(process.cwd(), "node_modules", "@fairfox", "polly-verify", "specs", "tla")
|
|
8188
|
+
];
|
|
8189
|
+
for (const dir of candidates) {
|
|
8190
|
+
if (fs4.existsSync(path4.join(dir, "MeshSeed.tla")))
|
|
8191
|
+
return dir;
|
|
8192
|
+
}
|
|
8193
|
+
return null;
|
|
8194
|
+
}
|
|
8195
|
+
async function runMeshSeedGuard(docker, specDir, config) {
|
|
8196
|
+
const mesh = config.mesh;
|
|
8197
|
+
if (!mesh || Object.keys(mesh).length === 0)
|
|
8198
|
+
return;
|
|
8199
|
+
const sourceDir = findMeshSeedSpecDir();
|
|
8200
|
+
if (!sourceDir) {
|
|
8201
|
+
console.log(color("⚠️ MeshSeed.tla not found — skipping seed-race guard", COLORS.yellow));
|
|
8202
|
+
return;
|
|
8203
|
+
}
|
|
8204
|
+
const fixDisabled = isSeedFixDisabled();
|
|
8205
|
+
fs4.copyFileSync(path4.join(sourceDir, "MeshSeed.tla"), path4.join(specDir, "MeshSeed.tla"));
|
|
8206
|
+
const baseCfg = fs4.readFileSync(path4.join(sourceDir, "MeshSeed.cfg"), "utf8");
|
|
8207
|
+
fs4.writeFileSync(path4.join(specDir, "MeshSeed.cfg"), meshSeedCfg(baseCfg, { disableFix: fixDisabled }));
|
|
8208
|
+
console.log(color(`⚙️ Running mesh seed-race guard (MeshSeed.tla, SeedDeterministic = ${fixDisabled ? "FALSE" : "TRUE"})...`, COLORS.blue));
|
|
8209
|
+
return docker.runTLC(path4.join(specDir, "MeshSeed.tla"), { workers: 1 });
|
|
8210
|
+
}
|
|
8134
8211
|
async function setupDocker() {
|
|
8135
8212
|
const { DockerRunner: DockerRunner2 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
|
|
8136
8213
|
console.log(color("\uD83D\uDC33 Checking Docker...", COLORS.blue));
|
|
@@ -8287,4 +8364,4 @@ main().catch((error) => {
|
|
|
8287
8364
|
process.exit(1);
|
|
8288
8365
|
});
|
|
8289
8366
|
|
|
8290
|
-
//# debugId=
|
|
8367
|
+
//# debugId=F1AE4F074F9AD2C964756E2164756E21
|