@meantjs/cli 0.0.0-bootstrap
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 +733 -0
- package/LICENSE +202 -0
- package/README.md +514 -0
- package/bin/cli.js +762 -0
- package/lib/index.d.ts +521 -0
- package/lib/index.js +131 -0
- package/package.json +49 -0
- package/wasm/candid_core_wasm.js +291 -0
- package/wasm/candid_core_wasm_bg.wasm +0 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The library surface of `@meantjs/cli`: data in, data out.
|
|
3
|
+
*
|
|
4
|
+
* Every type here describes what the wasm compiler actually serializes. In
|
|
5
|
+
* particular a {@link Diagnostic} carries only `code` and `message`
|
|
6
|
+
* unconditionally — every other field is genuinely optional, because the same
|
|
7
|
+
* item algebra carries both compile diagnostics (which populate `phase` and
|
|
8
|
+
* `severity`) and validation violations (which populate `path` and never
|
|
9
|
+
* those two).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Candid input. Either self-contained text, or a bundle naming its entry.
|
|
14
|
+
*
|
|
15
|
+
* `{ source }` and `{ entry, files }` are mutually exclusive, and no other key
|
|
16
|
+
* is accepted — a request carrying one is refused with `invalid_request`.
|
|
17
|
+
*/
|
|
18
|
+
export type Sources =
|
|
19
|
+
string | { source: string } | { entry: string; files: Record<string, string> };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A logical source location. Every field is optional: an *approximate* span
|
|
23
|
+
* names the source without offsets, and a span may name offsets without a
|
|
24
|
+
* source.
|
|
25
|
+
*/
|
|
26
|
+
export interface SourceSpan {
|
|
27
|
+
source_name?: string;
|
|
28
|
+
start_byte?: number;
|
|
29
|
+
end_byte?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A secondary location attached to a diagnostic. */
|
|
33
|
+
export interface RelatedLocation {
|
|
34
|
+
message: string;
|
|
35
|
+
span?: SourceSpan;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Which bound refused, the ceiling it enforces, and what was observed. */
|
|
39
|
+
export interface ResourceLimitInfo {
|
|
40
|
+
resource: string;
|
|
41
|
+
limit: number;
|
|
42
|
+
observed: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One failure item.
|
|
47
|
+
*
|
|
48
|
+
* `phase` is deliberately `string` rather than a union: the compiler's own
|
|
49
|
+
* phases are joined by values this package writes itself, so any closed set
|
|
50
|
+
* would be wrong today and wronger later. Compare `code`, which is the stable
|
|
51
|
+
* identifier meant for programmatic use.
|
|
52
|
+
*/
|
|
53
|
+
export interface Diagnostic {
|
|
54
|
+
code: string;
|
|
55
|
+
message: string;
|
|
56
|
+
phase?: string;
|
|
57
|
+
severity?: string;
|
|
58
|
+
path?: string;
|
|
59
|
+
span?: SourceSpan;
|
|
60
|
+
related?: RelatedLocation[];
|
|
61
|
+
notes?: string[];
|
|
62
|
+
resource_limit?: ResourceLimitInfo;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The failure document, identical to what the native `meant` CLI
|
|
67
|
+
* prints. Nothing is thrown for a data error.
|
|
68
|
+
*/
|
|
69
|
+
export interface Failure {
|
|
70
|
+
ok: false;
|
|
71
|
+
diagnostics: Diagnostic[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One `[container, id, name]` field-label triple. */
|
|
75
|
+
export type FieldNameTriple = [container: number, id: number, name: string];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A one-document `ContractEnvelope`: the canonical contract plus its
|
|
79
|
+
* extensions map. `contract` is typed `unknown` on purpose — it is handed
|
|
80
|
+
* whole to `schemaFromContract`, which itself accepts `unknown`, and pinning
|
|
81
|
+
* its shape here would duplicate a model that versions independently.
|
|
82
|
+
*/
|
|
83
|
+
export interface ContractEnvelope {
|
|
84
|
+
contract: unknown;
|
|
85
|
+
extensions: {
|
|
86
|
+
"org.candid-core.field-names/v1"?: FieldNameTriple[];
|
|
87
|
+
[extension: string]: unknown;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* One declaration or method a module leaves out.
|
|
93
|
+
*/
|
|
94
|
+
export interface Omission {
|
|
95
|
+
/** A named declaration, or a method of the actor's service. */
|
|
96
|
+
kind: "declaration" | "method";
|
|
97
|
+
/** The declaration or method name, as the Candid source spells it. */
|
|
98
|
+
name: string;
|
|
99
|
+
/**
|
|
100
|
+
* Why, from a closed set: `reserved_field_name` (a field or arm named
|
|
101
|
+
* like the `_N_` id rendering), `ambiguous_variant_arm` (an arm whose
|
|
102
|
+
* payload is a declared `opt` of an uninhabited type),
|
|
103
|
+
* `reserved_export_name` (a declaration named `actor` or `Actor`),
|
|
104
|
+
* `invalid_declaration_name` (a name that is not identifier-shaped;
|
|
105
|
+
* Contract documents only), or `references_omitted` (it references the
|
|
106
|
+
* omitted declaration named by `via`).
|
|
107
|
+
*/
|
|
108
|
+
reason:
|
|
109
|
+
| "reserved_field_name"
|
|
110
|
+
| "ambiguous_variant_arm"
|
|
111
|
+
| "reserved_export_name"
|
|
112
|
+
| "invalid_declaration_name"
|
|
113
|
+
| "references_omitted";
|
|
114
|
+
/** For `references_omitted` only: the omitted declaration referenced. */
|
|
115
|
+
via?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A successful module generation. A module that had to leave something out
|
|
120
|
+
* is still a success: everything it emits is exactly what it would be
|
|
121
|
+
* without the omitted declarations, and `omitted` lists what is missing.
|
|
122
|
+
*/
|
|
123
|
+
export interface ModuleSuccess {
|
|
124
|
+
ok: true;
|
|
125
|
+
/**
|
|
126
|
+
* The generated TypeScript. With omissions, its header lists them, one
|
|
127
|
+
* `// Omitted:` line each; with none, the text is unchanged.
|
|
128
|
+
*/
|
|
129
|
+
module: string;
|
|
130
|
+
/**
|
|
131
|
+
* What the module leaves out: a declaration no module can
|
|
132
|
+
* represent, together with every declaration and actor method that
|
|
133
|
+
* references it — through nested `func` and `service` types too, up to the
|
|
134
|
+
* containing declaration. Only the actor drops individual methods, from
|
|
135
|
+
* both `actor` and `Actor`. Declarations first, then methods, each sorted
|
|
136
|
+
* by name; empty, never absent, when nothing is omitted.
|
|
137
|
+
* `schemaFromContract` reports the same list for the same Contract.
|
|
138
|
+
*/
|
|
139
|
+
omitted: Omission[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Initialize the embedded wasm module once; repeated calls return the same
|
|
144
|
+
* promise. Node needs no argument. A browser may also pass nothing, or supply
|
|
145
|
+
* its own `BufferSource`, `URL` or `Response`.
|
|
146
|
+
*
|
|
147
|
+
* The parameter is typed `unknown` deliberately: `BufferSource`, `URL` and
|
|
148
|
+
* `Response` are DOM types, and naming them here would break every consumer
|
|
149
|
+
* compiling without the DOM lib — which is most Node consumers of a CLI.
|
|
150
|
+
*/
|
|
151
|
+
export function init(input?: unknown): Promise<void>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compile Candid sources into a one-document `ContractEnvelope` carrying the
|
|
155
|
+
* `org.candid-core.field-names/v1` extension — exactly the document
|
|
156
|
+
* `meant compile <path> --envelope` emits, ready for
|
|
157
|
+
* `schemaFromContract`.
|
|
158
|
+
*
|
|
159
|
+
* Discriminate on the envelope, not on `ok`: success carries no `ok` key.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* const result = await didToContract("service : { ping : () -> (); }");
|
|
163
|
+
* if ("contract" in result) {
|
|
164
|
+
* const built = schemaFromContract(result);
|
|
165
|
+
* } else {
|
|
166
|
+
* for (const issue of result.diagnostics) console.error(issue.code, issue.message);
|
|
167
|
+
* }
|
|
168
|
+
*/
|
|
169
|
+
export function didToContract(sources: Sources): Promise<ContractEnvelope | Failure>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate the `@meantjs/schema` TypeScript module for Candid sources,
|
|
173
|
+
* byte-identical to what the Rust-native generator emits, with the list of
|
|
174
|
+
* declarations and methods it had to leave out.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const result = await didToModule("service : { ping : () -> (); }");
|
|
178
|
+
* if (result.ok) {
|
|
179
|
+
* for (const entry of result.omitted) console.warn("omitted", entry.name, entry.reason);
|
|
180
|
+
* await writeFile("./service.ts", result.module);
|
|
181
|
+
* }
|
|
182
|
+
*/
|
|
183
|
+
export function didToModule(sources: Sources): Promise<ModuleSuccess | Failure>;
|
|
184
|
+
|
|
185
|
+
/** Where a SourceInfo entry came from: a declaration, or the actor. */
|
|
186
|
+
export type SourceOrigin =
|
|
187
|
+
{ kind: "declaration"; source: string; name: string } | { kind: "actor"; source: string };
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* One argument value of a doc attribute. A number keeps the text written
|
|
191
|
+
* (`"8"`, `"-1"`, `"0.25"`), never a binary float.
|
|
192
|
+
*/
|
|
193
|
+
export type DocValue =
|
|
194
|
+
| { kind: "text"; value: string }
|
|
195
|
+
| { kind: "number"; value: string }
|
|
196
|
+
| { kind: "bool"; value: boolean }
|
|
197
|
+
| { kind: "ident"; value: string }
|
|
198
|
+
| { kind: "call"; name: string; args: DocValue[] }
|
|
199
|
+
| { kind: "list"; items: DocValue[] }
|
|
200
|
+
| { kind: "record"; fields: DocArgument[] };
|
|
201
|
+
|
|
202
|
+
/** `key = value`. Keys are unique within one argument list or record. */
|
|
203
|
+
export interface DocArgument {
|
|
204
|
+
key: string;
|
|
205
|
+
value: DocValue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** One `@name(…)` doc line, or one that continues over several lines. */
|
|
209
|
+
export interface DocAttribute {
|
|
210
|
+
/** Lowercase segments joined by `.`: `amount`, `ui.hidden`. */
|
|
211
|
+
name: string;
|
|
212
|
+
/** Index of the attribute's first line in the item's docs, from 0. */
|
|
213
|
+
doc_line: number;
|
|
214
|
+
/** In the order written; empty for `@name` and `@name()`. */
|
|
215
|
+
args: DocArgument[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** A documented item: its prose lines and its attributes. */
|
|
219
|
+
export interface DocItem {
|
|
220
|
+
kind: "actor" | "declaration" | "field" | "method";
|
|
221
|
+
origin: SourceOrigin;
|
|
222
|
+
/**
|
|
223
|
+
* `actor`, `type:<Name>`, or the occurrence path SourceInfo records for a
|
|
224
|
+
* method or field (`actor.methods[2]`, `type:Tokens.fields[0]`,
|
|
225
|
+
* `actor.methods[1].function.args[0].type.fields[0]`).
|
|
226
|
+
*/
|
|
227
|
+
path: string;
|
|
228
|
+
/** The declaration or method name, or a field's named label. */
|
|
229
|
+
name?: string;
|
|
230
|
+
/** The doc lines that are not attributes, normalized, in order. */
|
|
231
|
+
prose: string[];
|
|
232
|
+
attributes: DocAttribute[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Doc attributes v1 of one compilation: every item with a doc line. */
|
|
236
|
+
export interface DocAttributes {
|
|
237
|
+
doc_attributes_version: 1;
|
|
238
|
+
/** The actor, then declarations, methods and fields, in SourceInfo order. */
|
|
239
|
+
items: DocItem[];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** A successful `didToDocAttributes`. */
|
|
243
|
+
export interface DocAttributesSuccess {
|
|
244
|
+
ok: true;
|
|
245
|
+
/** The canonical Contract, as in `didToContract`'s envelope. */
|
|
246
|
+
contract: unknown;
|
|
247
|
+
/**
|
|
248
|
+
* The SourceInfo sidecar as `meant compile <path>` prints it, typed
|
|
249
|
+
* `unknown` for the same reason as `contract`: it versions independently.
|
|
250
|
+
*/
|
|
251
|
+
source_info: unknown;
|
|
252
|
+
doc_attributes: DocAttributes;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Compile Candid sources and read the `@name(key = value, …)` attributes in
|
|
257
|
+
* their doc comments. The compiler gives no attribute a meaning; the reading
|
|
258
|
+
* tool decides which names it knows. A malformed attribute fails the call
|
|
259
|
+
* with `doc_attribute_malformed` or `doc_attribute_duplicate_key`, whose
|
|
260
|
+
* `path` names the item. Comments never change `contract_id` or
|
|
261
|
+
* `interface_id`.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* const result = await didToDocAttributes(serviceDid);
|
|
265
|
+
* if (result.ok) {
|
|
266
|
+
* for (const item of result.doc_attributes.items)
|
|
267
|
+
* for (const attribute of item.attributes) console.log(item.path, attribute.name);
|
|
268
|
+
* }
|
|
269
|
+
*/
|
|
270
|
+
export function didToDocAttributes(sources: Sources): Promise<DocAttributesSuccess | Failure>;
|
|
271
|
+
|
|
272
|
+
/** The identities of one compiled interface. */
|
|
273
|
+
export interface Identities {
|
|
274
|
+
/** The Contract identity: the whole canonical graph, declarations included. */
|
|
275
|
+
contract_id: string;
|
|
276
|
+
/**
|
|
277
|
+
* The interface identity: only what the service reaches. Equal identities
|
|
278
|
+
* mean an unchanged interface.
|
|
279
|
+
*/
|
|
280
|
+
interface_id: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** A successful projection. */
|
|
284
|
+
export interface ProjectionSuccess {
|
|
285
|
+
ok: true;
|
|
286
|
+
/**
|
|
287
|
+
* The projected `.did`: the methods named and every declaration they
|
|
288
|
+
* reach, as one self-contained file. Declaration names, doc comments and
|
|
289
|
+
* argument names are kept; a service class's init arguments are not.
|
|
290
|
+
* Methods are printed in name order (code point), record fields and
|
|
291
|
+
* variant arms in label-id order, declarations in source order (the
|
|
292
|
+
* entry's first, then each imported source's).
|
|
293
|
+
*/
|
|
294
|
+
did: string;
|
|
295
|
+
/** The methods the projection holds, in name order (code point). */
|
|
296
|
+
methods: string[];
|
|
297
|
+
/** The identities of the sources projected. */
|
|
298
|
+
input: Identities;
|
|
299
|
+
/** The identities of the projection. */
|
|
300
|
+
projection: Identities;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Project Candid sources onto the methods named: a self-contained `.did`
|
|
305
|
+
* holding exactly those methods and every declaration they reach. The same
|
|
306
|
+
* sources and the same set of names give the same bytes, in any order.
|
|
307
|
+
*
|
|
308
|
+
* A failure is `unknown_method` (one per distinct name the service lacks,
|
|
309
|
+
* in the order first named; the failure's first diagnostic lists the
|
|
310
|
+
* service's methods in its `notes`, once), `empty_method_list`,
|
|
311
|
+
* `no_service`, `resource_limit_exceeded`, or the compiler's own
|
|
312
|
+
* diagnostics. `resource_limit_exceeded` names its bound in
|
|
313
|
+
* `resource_limit.resource`: `projection_bytes` when the projection would
|
|
314
|
+
* pass the 1 MiB the compiler accepts in one source (`observed` is where
|
|
315
|
+
* printing stopped), or `projection_diagnostic_bytes` when the
|
|
316
|
+
* unknown-method diagnostics would pass 4 MiB, each counted as the failure
|
|
317
|
+
* writes it (the list of methods aside), the names left being counted in
|
|
318
|
+
* that diagnostic instead of reported.
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* const result = await projectDid(ledgerDid, ["icrc1_balance_of", "icrc1_transfer"]);
|
|
322
|
+
* if (result.ok) await writeFile("./ledger.did", result.did);
|
|
323
|
+
*/
|
|
324
|
+
export function projectDid(
|
|
325
|
+
sources: Sources,
|
|
326
|
+
methods: readonly string[],
|
|
327
|
+
): Promise<ProjectionSuccess | Failure>;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* One finding of a compatibility check.
|
|
331
|
+
*
|
|
332
|
+
* - `method_missing` (error): the live service has no method of that name.
|
|
333
|
+
* - `mode_changed` (error): the method's mode differs (`query`,
|
|
334
|
+
* `composite_query`, `update` or `oneway`).
|
|
335
|
+
* - `method_incompatible` (error): the live method's type is not a subtype
|
|
336
|
+
* of the written one; `path` says where.
|
|
337
|
+
* - `special_opt_rule` (warning): the types are compatible only because
|
|
338
|
+
* Candid reads a mismatch under an `opt` as `null`, so a value at `path`
|
|
339
|
+
* decodes as `null`. One is reported at every such path along which no
|
|
340
|
+
* pair of types repeats: a shared type under each path that reaches it,
|
|
341
|
+
* and a recursive type up to where a path comes back round to a pair it
|
|
342
|
+
* already passed through.
|
|
343
|
+
* - `resource_limit_exceeded`: as an error, the check of this method
|
|
344
|
+
* stopped at one of its bounds (`resource_limit.resource` is
|
|
345
|
+
* `check_depth` or `check_steps`; a step is one unit of work done, such
|
|
346
|
+
* as a pair of types visited or a field examined) and fails closed. As a
|
|
347
|
+
* warning (`check_warnings`), the method has more `special_opt_rule`
|
|
348
|
+
* warnings than the 1,000 reported; its verdict is complete and stands.
|
|
349
|
+
* As an error naming `check_total_steps` or `check_total_warnings`, the
|
|
350
|
+
* check as a whole reached its bound of 10,000,000 steps or 10,000
|
|
351
|
+
* `special_opt_rule` warnings: methods are checked in name order, and
|
|
352
|
+
* the method at which the bound was reached and every method after it
|
|
353
|
+
* get this one diagnostic and nothing else. As an error naming
|
|
354
|
+
* `check_output_bytes`, the reported diagnostics reached their bound of
|
|
355
|
+
* 4 MiB, each counted as the response writes it: this one ends the list,
|
|
356
|
+
* names no `method`, and counts in `unreported_methods` the methods it
|
|
357
|
+
* stands for, the last written ones in name order, from the one whose
|
|
358
|
+
* diagnostics did not fit; they fail closed. Below these bounds a
|
|
359
|
+
* method's findings do not depend on the other methods.
|
|
360
|
+
*/
|
|
361
|
+
export interface CompatibilityDiagnostic {
|
|
362
|
+
code:
|
|
363
|
+
| "method_missing"
|
|
364
|
+
| "mode_changed"
|
|
365
|
+
| "method_incompatible"
|
|
366
|
+
| "special_opt_rule"
|
|
367
|
+
| "resource_limit_exceeded";
|
|
368
|
+
severity: "error" | "warning";
|
|
369
|
+
/**
|
|
370
|
+
* The written method the finding is about. Absent only on the
|
|
371
|
+
* `check_output_bytes` diagnostic that ends a check, which counts methods
|
|
372
|
+
* in `unreported_methods` instead.
|
|
373
|
+
*/
|
|
374
|
+
method?: string;
|
|
375
|
+
/**
|
|
376
|
+
* Where in the method's type: `$args[i]` or `$results[i]`, then `.name`
|
|
377
|
+
* (or `["name"]`, or `[id]` for a numeric label) per record field or
|
|
378
|
+
* variant arm, `[*]` per vec element, `?` per opt content, and
|
|
379
|
+
* `::args[i]`, `::results[i]` or `::name` into a func or service type.
|
|
380
|
+
* Absent for `method_missing`, `mode_changed` and
|
|
381
|
+
* `resource_limit_exceeded`.
|
|
382
|
+
*/
|
|
383
|
+
path?: string;
|
|
384
|
+
message: string;
|
|
385
|
+
resource_limit?: ResourceLimitInfo;
|
|
386
|
+
/**
|
|
387
|
+
* On the `check_output_bytes` diagnostic only: how many written methods
|
|
388
|
+
* it stands for, the last ones in name order. They are not reported, and
|
|
389
|
+
* fail closed.
|
|
390
|
+
*/
|
|
391
|
+
unreported_methods?: number;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** A completed compatibility check. */
|
|
395
|
+
export interface CompatibilityReport {
|
|
396
|
+
ok: true;
|
|
397
|
+
/** True when no diagnostic is an error. Warnings do not clear it. */
|
|
398
|
+
compatible: boolean;
|
|
399
|
+
written: Identities;
|
|
400
|
+
/**
|
|
401
|
+
* The live interface's identities. Compare `interface_id` with the one
|
|
402
|
+
* recorded when the written file was made to tell an unchanged interface
|
|
403
|
+
* from one that changed compatibly.
|
|
404
|
+
*/
|
|
405
|
+
live: Identities;
|
|
406
|
+
/** By written method name, each method's errors before its warnings. */
|
|
407
|
+
diagnostics: CompatibilityDiagnostic[];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* A check that could not run. `input` names the side that failed when one
|
|
412
|
+
* side does not compile or declares no service; a malformed request has none.
|
|
413
|
+
*/
|
|
414
|
+
export interface CheckFailure extends Failure {
|
|
415
|
+
input?: "written" | "live";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Check that a live interface is still compatible with a written one: the
|
|
420
|
+
* live service must be a Candid subtype of the written one. Every written
|
|
421
|
+
* method must exist in the live service with the same mode, its arguments
|
|
422
|
+
* contravariant and its results covariant; methods only the live service
|
|
423
|
+
* has are ignored. A hand-written subset `.did` is as valid a written side
|
|
424
|
+
* as a projection.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* const report = await checkCompatible(writtenDid, liveDid);
|
|
428
|
+
* if (report.ok && !report.compatible) {
|
|
429
|
+
* for (const item of report.diagnostics) console.error(item.method, item.code, item.path);
|
|
430
|
+
* }
|
|
431
|
+
*/
|
|
432
|
+
export function checkCompatible(
|
|
433
|
+
written: Sources,
|
|
434
|
+
live: Sources,
|
|
435
|
+
): Promise<CompatibilityReport | CheckFailure>;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* What happened to one entry of `meant gen`.
|
|
439
|
+
*
|
|
440
|
+
* - `written`: the entry generated and at least one of its two files was
|
|
441
|
+
* created or changed on disk.
|
|
442
|
+
* - `unchanged`: the entry generated and both files already held exactly
|
|
443
|
+
* those bytes; nothing was written.
|
|
444
|
+
* - `drifted`: `--check` only — at least one file is missing or differs from
|
|
445
|
+
* what the entry now generates; nothing was written.
|
|
446
|
+
* - `failed`: the entry did not generate; `diagnostics` says why. A compile
|
|
447
|
+
* failure writes nothing for the entry.
|
|
448
|
+
*/
|
|
449
|
+
export type CliEntryStatus = "written" | "unchanged" | "drifted" | "failed";
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The identities of one compiled `gen` entry, equal to its envelope's
|
|
453
|
+
* `contract.identities` under the key names {@link Identities} uses.
|
|
454
|
+
*/
|
|
455
|
+
export interface CliIdentities {
|
|
456
|
+
/** The Contract identity: the whole canonical graph, declarations included. */
|
|
457
|
+
contract_id: string;
|
|
458
|
+
/**
|
|
459
|
+
* The interface identity: only what the service reaches. Absent when the
|
|
460
|
+
* `.did` has no actor, as in the envelope.
|
|
461
|
+
*/
|
|
462
|
+
interface_id?: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** One entry's part of the `--json` document. */
|
|
466
|
+
export interface CliEntryReport {
|
|
467
|
+
/** The entry exactly as it was given on the command line. */
|
|
468
|
+
entry: string;
|
|
469
|
+
status: CliEntryStatus;
|
|
470
|
+
/** The generated module's path: `-o` joined with `<stem>.ts`. */
|
|
471
|
+
module: string;
|
|
472
|
+
/** The contract envelope's path: `-o` joined with `<stem>.envelope.json`. */
|
|
473
|
+
envelope: string;
|
|
474
|
+
/**
|
|
475
|
+
* The entry's identities, the same the human report prints. Present on
|
|
476
|
+
* every entry that compiled (`written`, `unchanged`, `drifted`), with or
|
|
477
|
+
* without `--check`; absent on a `failed` entry.
|
|
478
|
+
*/
|
|
479
|
+
identities?: CliIdentities;
|
|
480
|
+
/**
|
|
481
|
+
* What the module leaves out, exactly as `didToModule` returns it. Empty
|
|
482
|
+
* for a `failed` entry and when nothing is omitted. Omissions never make an
|
|
483
|
+
* entry fail.
|
|
484
|
+
*/
|
|
485
|
+
omitted: Omission[];
|
|
486
|
+
/**
|
|
487
|
+
* The compiler's diagnostics for a `failed` entry, in the same shape the
|
|
488
|
+
* library returns; empty otherwise. Failures that occur before compilation
|
|
489
|
+
* use the same shape with codes this CLI originates:
|
|
490
|
+
* `did_file_read_error`, `did_source_not_found`, `resource_limit_exceeded`,
|
|
491
|
+
* `nondeterministic_output` and `internal_error` (which carries no `phase`).
|
|
492
|
+
*/
|
|
493
|
+
diagnostics: Diagnostic[];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* The one document `meant gen … --json` prints on stdout, and
|
|
498
|
+
* nothing else: stderr stays empty. The process exit code is 0 exactly when
|
|
499
|
+
* `ok` is true, and 1 otherwise. A usage error exits 64 and prints no
|
|
500
|
+
* document.
|
|
501
|
+
*/
|
|
502
|
+
export interface CliReport {
|
|
503
|
+
/**
|
|
504
|
+
* The document's version. It changes only when an existing field's meaning
|
|
505
|
+
* or shape changes; adding a field does not change it. Refuse a version
|
|
506
|
+
* you do not know.
|
|
507
|
+
*/
|
|
508
|
+
schemaVersion: 1;
|
|
509
|
+
/** True when no entry is `failed` or `drifted`. Omissions do not clear it. */
|
|
510
|
+
ok: boolean;
|
|
511
|
+
/** Whether `--check` was given. */
|
|
512
|
+
check: boolean;
|
|
513
|
+
/** One report per entry, in command-line order. */
|
|
514
|
+
entries: CliEntryReport[];
|
|
515
|
+
/**
|
|
516
|
+
* Every path `--check` found missing or different from what its entry now
|
|
517
|
+
* generates, in entry order, module before envelope. Always empty without
|
|
518
|
+
* `--check`, and for a `failed` entry.
|
|
519
|
+
*/
|
|
520
|
+
drift: string[];
|
|
521
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// The library surface of @meantjs/cli: data in, data out.
|
|
2
|
+
// Every function hands one JSON-serializable request to the wasm compiler and
|
|
3
|
+
// return its parsed JSON response verbatim — no eval and no network, nothing
|
|
4
|
+
// thrown for data errors, and no filesystem access from the wasm side (this
|
|
5
|
+
// module reads the embedded artifact itself on Node):
|
|
6
|
+
// a failure is the same `{ ok: false, diagnostics }` document the native
|
|
7
|
+
// `meant` CLI prints, passed through byte-for-byte.
|
|
8
|
+
|
|
9
|
+
import initWasm, {
|
|
10
|
+
checkCompatible as wasmCheckCompatible,
|
|
11
|
+
didToContract as wasmDidToContract,
|
|
12
|
+
didToDocAttributes as wasmDidToDocAttributes,
|
|
13
|
+
didToModule as wasmDidToModule,
|
|
14
|
+
projectDid as wasmProjectDid,
|
|
15
|
+
} from "../wasm/candid_core_wasm.js";
|
|
16
|
+
|
|
17
|
+
let initialized;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the embedded wasm module once. Node needs no argument (the
|
|
21
|
+
* artifact is read from this package); a browser may also pass nothing (the
|
|
22
|
+
* artifact is fetched relative to this module) or supply its own
|
|
23
|
+
* `BufferSource`/`URL`/`Response`.
|
|
24
|
+
*/
|
|
25
|
+
export function init(input) {
|
|
26
|
+
if (initialized === undefined) {
|
|
27
|
+
initialized = (async () => {
|
|
28
|
+
if (input === undefined && typeof process !== "undefined" && process.versions?.node) {
|
|
29
|
+
const { readFile } = await import("node:fs/promises");
|
|
30
|
+
const bytes = await readFile(
|
|
31
|
+
new URL("../wasm/candid_core_wasm_bg.wasm", import.meta.url),
|
|
32
|
+
);
|
|
33
|
+
await initWasm({ module_or_path: bytes });
|
|
34
|
+
} else {
|
|
35
|
+
await initWasm(input === undefined ? undefined : { module_or_path: input });
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
}
|
|
39
|
+
return initialized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sourcesOf(sources) {
|
|
43
|
+
return typeof sources === "string" ? { source: sources } : sources;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function requestOf(sources) {
|
|
47
|
+
return JSON.stringify(sourcesOf(sources));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compile Candid sources into a one-document ContractEnvelope carrying the
|
|
52
|
+
* `org.candid-core.field-names/v1` extension — exactly the document
|
|
53
|
+
* `meant compile <path> --envelope` emits, ready for
|
|
54
|
+
* `schemaFromContract`. `sources` is Candid text, or
|
|
55
|
+
* `{ entry, files: { name: text } }` for a multi-file bundle.
|
|
56
|
+
*
|
|
57
|
+
* Returns the parsed envelope (`"contract" in result`), or
|
|
58
|
+
* `{ ok: false, diagnostics }` with the compiler's diagnostics verbatim.
|
|
59
|
+
*/
|
|
60
|
+
export async function didToContract(sources) {
|
|
61
|
+
await init();
|
|
62
|
+
return JSON.parse(wasmDidToContract(requestOf(sources)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate the `@meantjs/schema` TypeScript module for Candid sources.
|
|
67
|
+
* Returns `{ ok: true, module, omitted }` with the generated text —
|
|
68
|
+
* byte-identical to what the Rust-native generator emits — and the
|
|
69
|
+
* declarations and methods it left out, or `{ ok: false, diagnostics }`.
|
|
70
|
+
*/
|
|
71
|
+
export async function didToModule(sources) {
|
|
72
|
+
await init();
|
|
73
|
+
return JSON.parse(wasmDidToModule(requestOf(sources)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Compile Candid sources and read the `@name(key = value, …)` attributes in
|
|
78
|
+
* their doc comments (`docs/doc-attributes.md` in the Meant
|
|
79
|
+
* repository). Returns `{ ok: true, contract, source_info, doc_attributes }`
|
|
80
|
+
* — the Contract and SourceInfo `meant compile <path>` prints, plus
|
|
81
|
+
* every documented item's prose and attributes keyed by its SourceInfo path
|
|
82
|
+
* — or `{ ok: false, diagnostics }`: `doc_attribute_malformed`,
|
|
83
|
+
* `doc_attribute_duplicate_key`, `resource_limit_exceeded`, or the
|
|
84
|
+
* compiler's diagnostics.
|
|
85
|
+
*/
|
|
86
|
+
export async function didToDocAttributes(sources) {
|
|
87
|
+
await init();
|
|
88
|
+
return JSON.parse(wasmDidToDocAttributes(requestOf(sources)));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Project Candid sources onto the methods named: a self-contained `.did`
|
|
93
|
+
* holding exactly those methods of the service and every declaration they
|
|
94
|
+
* reach, deterministic to the byte for the same sources and the same set of
|
|
95
|
+
* names, whatever their order.
|
|
96
|
+
*
|
|
97
|
+
* Returns `{ ok: true, did, methods, input, projection }` — the text, the
|
|
98
|
+
* methods it holds in name order, and the identities of the input and of
|
|
99
|
+
* the projection — or `{ ok: false, diagnostics }`: `unknown_method` (one
|
|
100
|
+
* per distinct name the service lacks; the failure's first diagnostic lists
|
|
101
|
+
* the service's methods in its `notes`, once), `empty_method_list`,
|
|
102
|
+
* `no_service`, `resource_limit_exceeded` (`projection_bytes`: the
|
|
103
|
+
* projection would pass the compiler's 1 MiB bound on one source;
|
|
104
|
+
* `projection_diagnostic_bytes`: the unknown-method diagnostics would pass
|
|
105
|
+
* 4 MiB as the failure writes them, and the names left are counted, not
|
|
106
|
+
* reported), or the compiler's diagnostics.
|
|
107
|
+
*/
|
|
108
|
+
export async function projectDid(sources, methods) {
|
|
109
|
+
await init();
|
|
110
|
+
return JSON.parse(wasmProjectDid(JSON.stringify({ ...sourcesOf(sources), methods })));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check that a live interface is still compatible with a written one: every
|
|
115
|
+
* method of the written service exists in the live one with the same mode,
|
|
116
|
+
* and the live method's type is a Candid subtype of the written one.
|
|
117
|
+
* Methods only the live service has are ignored.
|
|
118
|
+
*
|
|
119
|
+
* Returns `{ ok: true, compatible, written, live, diagnostics }`, where each
|
|
120
|
+
* diagnostic names its method and, inside a type, the path where the check
|
|
121
|
+
* failed; or `{ ok: false, input, diagnostics }` when a side does not
|
|
122
|
+
* compile (`input` names it). The diagnostics are bounded at 4 MiB as the
|
|
123
|
+
* response writes them: past that bound one last diagnostic, which names no
|
|
124
|
+
* method, counts the methods left in `unreported_methods`.
|
|
125
|
+
*/
|
|
126
|
+
export async function checkCompatible(written, live) {
|
|
127
|
+
await init();
|
|
128
|
+
return JSON.parse(
|
|
129
|
+
wasmCheckCompatible(JSON.stringify({ written: sourcesOf(written), live: sourcesOf(live) })),
|
|
130
|
+
);
|
|
131
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meantjs/cli",
|
|
3
|
+
"version": "0.0.0-bootstrap",
|
|
4
|
+
"description": "Candid .did to canonical Contract envelopes and @meantjs/schema modules, compiled to WebAssembly: a JavaScript-only on-ramp with no Rust toolchain, for Node CLIs and browsers alike.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/B3Pay/Meant.git",
|
|
9
|
+
"directory": "crates/candid-core-wasm/npm"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/B3Pay/Meant/tree/main/crates/candid-core-wasm/npm#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"bin": {
|
|
15
|
+
"meant": "./bin/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./lib/index.d.ts",
|
|
20
|
+
"default": "./lib/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"lib",
|
|
27
|
+
"wasm/candid_core_wasm.js",
|
|
28
|
+
"wasm/candid_core_wasm_bg.wasm",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "cd .. && wasm-pack build --target web --out-dir npm/wasm --no-pack --release",
|
|
35
|
+
"test": "node --test test/*.test.js",
|
|
36
|
+
"test:browser": "node --test test/browser/*.test.js"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"playwright-core": "1.62.1"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@meantjs/schema": "0.6.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@meantjs/schema": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|