@ic-reactor/vite-plugin 0.15.1 → 4.0.0-beta.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/README.md +141 -173
- package/dist/index.cjs +534 -207
- package/dist/index.d.cts +88 -30
- package/dist/index.d.ts +88 -30
- package/dist/index.js +534 -211
- package/package.json +6 -8
- package/src/generate.ts +594 -0
- package/src/index.ts +456 -430
- package/llms.txt +0 -156
package/dist/index.cjs
CHANGED
|
@@ -33,9 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
icReactor: () => icReactor
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
var
|
|
37
|
-
var
|
|
38
|
-
var import_codegen = require("@ic-reactor/codegen");
|
|
36
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
37
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
39
38
|
|
|
40
39
|
// src/env.ts
|
|
41
40
|
var import_child_process = require("child_process");
|
|
@@ -268,149 +267,549 @@ function icEnvMiddleware(environment) {
|
|
|
268
267
|
};
|
|
269
268
|
}
|
|
270
269
|
|
|
270
|
+
// src/generate.ts
|
|
271
|
+
var import_node_child_process = require("child_process");
|
|
272
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
273
|
+
var import_node_module = require("module");
|
|
274
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
275
|
+
var CANDID_CORE_CLI_VERSION = "0.2.0-beta.1";
|
|
276
|
+
var REPORT_SCHEMA_VERSION = 1;
|
|
277
|
+
var GENERATE_TIMEOUT_MS = 6e4;
|
|
278
|
+
var MAX_STREAMED_LINES = 200;
|
|
279
|
+
var MAX_LINE_CHARS = 4096;
|
|
280
|
+
var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
281
|
+
function resolveCliBin(root) {
|
|
282
|
+
const requireFromApp = (0, import_node_module.createRequire)(import_node_path.default.join(root, "package.json"));
|
|
283
|
+
let manifestPath;
|
|
284
|
+
try {
|
|
285
|
+
manifestPath = requireFromApp.resolve("@candid-core/cli/package.json");
|
|
286
|
+
} catch {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`cannot find @candid-core/cli from ${root}. Install the generator that pairs with this plugin next to @candid-core/schema: npm install --save-dev --save-exact @candid-core/cli@${CANDID_CORE_CLI_VERSION}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const manifest = JSON.parse(import_node_fs.default.readFileSync(manifestPath, "utf-8"));
|
|
292
|
+
const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.["candid-core-cli"];
|
|
293
|
+
if (!bin) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`${manifestPath} declares no candid-core-cli bin, so it is not the generator this plugin runs`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return import_node_path.default.resolve(import_node_path.default.dirname(manifestPath), bin);
|
|
299
|
+
}
|
|
300
|
+
var keep = (text, chunk) => text.length >= MAX_OUTPUT_BYTES ? text : text + chunk;
|
|
301
|
+
function runProcess(bin, args, cwd, timeoutMs, signal, onStderrLine) {
|
|
302
|
+
return new Promise((resolve) => {
|
|
303
|
+
let stdout = "";
|
|
304
|
+
let stderr = "";
|
|
305
|
+
let timedOut = false;
|
|
306
|
+
let settled = false;
|
|
307
|
+
const stopped = {
|
|
308
|
+
code: null,
|
|
309
|
+
signal: null,
|
|
310
|
+
problem: "was stopped",
|
|
311
|
+
stopped: true
|
|
312
|
+
};
|
|
313
|
+
if (signal?.aborted) {
|
|
314
|
+
resolve({ stdout, stderr, ...stopped });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
let unfinished = "";
|
|
318
|
+
let cut = false;
|
|
319
|
+
let passed = 0;
|
|
320
|
+
const pass = (text) => {
|
|
321
|
+
if (!text.trim() || passed > MAX_STREAMED_LINES) return;
|
|
322
|
+
passed++;
|
|
323
|
+
onStderrLine?.(
|
|
324
|
+
passed > MAX_STREAMED_LINES ? `more than ${MAX_STREAMED_LINES} lines on stderr: the rest is not logged` : text.trimEnd()
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
const passUnfinished = () => {
|
|
328
|
+
pass(
|
|
329
|
+
cut ? `${unfinished} [line cut at ${MAX_LINE_CHARS} characters]` : unfinished
|
|
330
|
+
);
|
|
331
|
+
unfinished = "";
|
|
332
|
+
cut = false;
|
|
333
|
+
};
|
|
334
|
+
const settle = (result) => {
|
|
335
|
+
if (settled) return;
|
|
336
|
+
settled = true;
|
|
337
|
+
passUnfinished();
|
|
338
|
+
clearTimeout(timer);
|
|
339
|
+
signal?.removeEventListener("abort", stop);
|
|
340
|
+
resolve({ stdout, stderr, ...result });
|
|
341
|
+
};
|
|
342
|
+
const stop = () => {
|
|
343
|
+
unfinished = "";
|
|
344
|
+
cut = false;
|
|
345
|
+
child.kill("SIGKILL");
|
|
346
|
+
settle(stopped);
|
|
347
|
+
};
|
|
348
|
+
const child = (0, import_node_child_process.spawn)(process.execPath, [bin, ...args], {
|
|
349
|
+
cwd,
|
|
350
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
351
|
+
windowsHide: true
|
|
352
|
+
});
|
|
353
|
+
const timer = setTimeout(() => {
|
|
354
|
+
timedOut = true;
|
|
355
|
+
child.kill("SIGKILL");
|
|
356
|
+
}, timeoutMs);
|
|
357
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
358
|
+
child.stdout.setEncoding("utf-8");
|
|
359
|
+
child.stderr.setEncoding("utf-8");
|
|
360
|
+
child.stdout.on("data", (chunk) => stdout = keep(stdout, chunk));
|
|
361
|
+
child.stderr.on("data", (chunk) => {
|
|
362
|
+
stderr = keep(stderr, chunk);
|
|
363
|
+
if (settled || !onStderrLine || passed > MAX_STREAMED_LINES) return;
|
|
364
|
+
const pieces = chunk.split(/\r?\n/);
|
|
365
|
+
pieces.forEach((piece, index) => {
|
|
366
|
+
const room = MAX_LINE_CHARS - unfinished.length;
|
|
367
|
+
if (piece.length > room) cut = true;
|
|
368
|
+
unfinished += piece.slice(0, room);
|
|
369
|
+
if (index < pieces.length - 1) passUnfinished();
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
child.on(
|
|
373
|
+
"error",
|
|
374
|
+
(error) => settle({
|
|
375
|
+
code: null,
|
|
376
|
+
signal: null,
|
|
377
|
+
problem: `could not start: ${error.message}`,
|
|
378
|
+
unstartable: true
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
child.on(
|
|
382
|
+
"close",
|
|
383
|
+
(code, signal2) => settle({
|
|
384
|
+
code,
|
|
385
|
+
signal: signal2,
|
|
386
|
+
problem: timedOut ? `did not finish within ${timeoutMs / 1e3}s and was killed` : void 0
|
|
387
|
+
})
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function describeProcess(run, reason) {
|
|
392
|
+
const ending = run.problem ?? (run.signal ? `was killed by ${run.signal}` : `exited with code ${run.code}`);
|
|
393
|
+
const lines = [`candid-core-cli ${ending}`];
|
|
394
|
+
if (reason) lines.push(reason);
|
|
395
|
+
const stderr = run.stderr.trim();
|
|
396
|
+
if (stderr) lines.push(stderr);
|
|
397
|
+
return lines.join("\n");
|
|
398
|
+
}
|
|
399
|
+
function parseReport(stdout, count) {
|
|
400
|
+
let report;
|
|
401
|
+
try {
|
|
402
|
+
report = JSON.parse(stdout);
|
|
403
|
+
} catch {
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
const { schemaVersion, entries } = report ?? {};
|
|
407
|
+
if (schemaVersion !== REPORT_SCHEMA_VERSION) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`its report has schemaVersion ${String(schemaVersion)}, and this plugin reads ${REPORT_SCHEMA_VERSION}; install @candid-core/cli@${CANDID_CORE_CLI_VERSION}`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (!Array.isArray(entries) || entries.length !== count) {
|
|
413
|
+
throw new Error(`its report does not have one entry per .did (${count})`);
|
|
414
|
+
}
|
|
415
|
+
return entries.map((entry) => {
|
|
416
|
+
const { status, module: module2, omitted, diagnostics } = entry;
|
|
417
|
+
return {
|
|
418
|
+
status: String(status),
|
|
419
|
+
module: typeof module2 === "string" ? module2 : void 0,
|
|
420
|
+
omitted: Array.isArray(omitted) ? omitted : [],
|
|
421
|
+
diagnostics: Array.isArray(diagnostics) ? diagnostics : []
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function formatDiagnostic(diagnostic) {
|
|
426
|
+
const { code, message, notes } = diagnostic ?? {};
|
|
427
|
+
if (typeof message !== "string") return JSON.stringify(diagnostic);
|
|
428
|
+
const lead = typeof code === "string" ? `${code}: ${message}` : message;
|
|
429
|
+
const extra = Array.isArray(notes) ? notes.map((note) => ` ${note}`) : [];
|
|
430
|
+
return [lead, ...extra].join("\n");
|
|
431
|
+
}
|
|
432
|
+
async function runGroup(context, outDir, units) {
|
|
433
|
+
const run = await runProcess(
|
|
434
|
+
context.cli,
|
|
435
|
+
["gen", ...units.map((unit) => unit.didFile), "-o", outDir, "--json"],
|
|
436
|
+
context.root,
|
|
437
|
+
context.timeoutMs,
|
|
438
|
+
context.signal,
|
|
439
|
+
(line) => context.onStderr?.(
|
|
440
|
+
line,
|
|
441
|
+
units.map((unit) => unit.didFile)
|
|
442
|
+
)
|
|
443
|
+
);
|
|
444
|
+
let entries;
|
|
445
|
+
let reason;
|
|
446
|
+
if (!run.problem) {
|
|
447
|
+
try {
|
|
448
|
+
entries = parseReport(run.stdout, units.length);
|
|
449
|
+
if (!entries && run.code === 0) reason = "it printed no report";
|
|
450
|
+
} catch (error) {
|
|
451
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!entries) {
|
|
455
|
+
if (units.length > 1 && !run.unstartable && !run.stopped) {
|
|
456
|
+
const results = await Promise.all(
|
|
457
|
+
units.map((unit) => runGroup(context, outDir, [unit]))
|
|
458
|
+
);
|
|
459
|
+
return { outcomes: results.flatMap((result) => result.outcomes) };
|
|
460
|
+
}
|
|
461
|
+
const failure = describeProcess(run, reason);
|
|
462
|
+
return {
|
|
463
|
+
outcomes: units.map(({ names }) => ({
|
|
464
|
+
names,
|
|
465
|
+
status: "failed",
|
|
466
|
+
omitted: [],
|
|
467
|
+
failure
|
|
468
|
+
}))
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
outcomes: units.map((unit, index) => {
|
|
473
|
+
const entry = entries[index];
|
|
474
|
+
if (entry.status === "failed") {
|
|
475
|
+
return {
|
|
476
|
+
names: unit.names,
|
|
477
|
+
status: "failed",
|
|
478
|
+
omitted: [],
|
|
479
|
+
failure: entry.diagnostics.map(formatDiagnostic).join("\n") || "candid-core-cli reported the entry as failed"
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
names: unit.names,
|
|
484
|
+
module: entry.module,
|
|
485
|
+
status: entry.status === "unchanged" ? "unchanged" : "written",
|
|
486
|
+
omitted: entry.omitted
|
|
487
|
+
};
|
|
488
|
+
})
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function quoteNames(names) {
|
|
492
|
+
return names.map((name) => `"${name}"`).join(", ");
|
|
493
|
+
}
|
|
494
|
+
function foldsCase(dir) {
|
|
495
|
+
const swapCase = (name) => name.replace(
|
|
496
|
+
/[a-z]/gi,
|
|
497
|
+
(letter) => letter === letter.toLowerCase() ? letter.toUpperCase() : letter.toLowerCase()
|
|
498
|
+
);
|
|
499
|
+
const probe = (entry) => {
|
|
500
|
+
const swapped = import_node_path.default.join(
|
|
501
|
+
import_node_path.default.dirname(entry),
|
|
502
|
+
swapCase(import_node_path.default.basename(entry))
|
|
503
|
+
);
|
|
504
|
+
if (swapped === entry) return void 0;
|
|
505
|
+
try {
|
|
506
|
+
const found = import_node_fs.default.lstatSync(entry, { bigint: true });
|
|
507
|
+
if (found.ino === 0n) return void 0;
|
|
508
|
+
try {
|
|
509
|
+
const other = import_node_fs.default.lstatSync(swapped, { bigint: true });
|
|
510
|
+
return other.dev === found.dev && other.ino === found.ino;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
return error.code === "ENOENT" ? false : void 0;
|
|
513
|
+
}
|
|
514
|
+
} catch {
|
|
515
|
+
return void 0;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
for (let at = dir; ; at = import_node_path.default.dirname(at)) {
|
|
519
|
+
let names = [];
|
|
520
|
+
try {
|
|
521
|
+
names = import_node_fs.default.readdirSync(at);
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
const named = names.find((name) => swapCase(name) !== name);
|
|
525
|
+
const verdict = (named === void 0 ? void 0 : probe(import_node_path.default.join(at, named))) ?? probe(at);
|
|
526
|
+
if (verdict !== void 0) return verdict;
|
|
527
|
+
if (import_node_path.default.dirname(at) === at) return void 0;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function generate(request) {
|
|
531
|
+
const { root } = request;
|
|
532
|
+
const units = /* @__PURE__ */ new Map();
|
|
533
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
534
|
+
const refused = /* @__PURE__ */ new Map();
|
|
535
|
+
const folding = /* @__PURE__ */ new Map();
|
|
536
|
+
const targetOf = (unit) => {
|
|
537
|
+
let folds = folding.get(unit.outDir);
|
|
538
|
+
if (folds === void 0) {
|
|
539
|
+
folds = (request.foldsCase ?? foldsCase)(unit.outDir) ?? false;
|
|
540
|
+
folding.set(unit.outDir, folds);
|
|
541
|
+
}
|
|
542
|
+
return folds ? moduleOf(unit).toLowerCase() : moduleOf(unit);
|
|
543
|
+
};
|
|
544
|
+
for (const canister of request.canisters) {
|
|
545
|
+
const key = JSON.stringify([canister.didFile, canister.outDir]);
|
|
546
|
+
const same = units.get(key);
|
|
547
|
+
if (same) {
|
|
548
|
+
same.names.push(canister.name);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const unit = {
|
|
552
|
+
names: [canister.name],
|
|
553
|
+
didFile: canister.didFile,
|
|
554
|
+
outDir: canister.outDir
|
|
555
|
+
};
|
|
556
|
+
units.set(key, unit);
|
|
557
|
+
const target = targetOf(unit);
|
|
558
|
+
const owner = claimed.get(target);
|
|
559
|
+
if (owner) refused.set(unit, owner);
|
|
560
|
+
else claimed.set(target, unit);
|
|
561
|
+
}
|
|
562
|
+
const outcomes = [...refused].map(([unit, owner]) => ({
|
|
563
|
+
names: unit.names,
|
|
564
|
+
status: "failed",
|
|
565
|
+
omitted: [],
|
|
566
|
+
failure: `would write ${import_node_path.default.relative(root, moduleOf(unit))}, which ${quoteNames(owner.names)} already writes` + (moduleOf(unit) === moduleOf(owner) ? ". " : ` as ${import_node_path.default.relative(root, moduleOf(owner))}: the filesystem does not tell the two names apart. `) + `The generator names a module after its .did file: rename one file or give one canister its own outDir`
|
|
567
|
+
}));
|
|
568
|
+
const groups = /* @__PURE__ */ new Map();
|
|
569
|
+
for (const unit of units.values()) {
|
|
570
|
+
if (refused.has(unit)) continue;
|
|
571
|
+
groups.set(unit.outDir, [...groups.get(unit.outDir) ?? [], unit]);
|
|
572
|
+
}
|
|
573
|
+
const results = await Promise.all(
|
|
574
|
+
[...groups].map(([outDir, members]) => runGroup(request, outDir, members))
|
|
575
|
+
);
|
|
576
|
+
return {
|
|
577
|
+
outcomes: [...outcomes, ...results.flatMap((result) => result.outcomes)]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function moduleOf(unit) {
|
|
581
|
+
return import_node_path.default.join(
|
|
582
|
+
unit.outDir,
|
|
583
|
+
import_node_path.default.basename(unit.didFile, import_node_path.default.extname(unit.didFile)) + ".ts"
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
271
587
|
// src/index.ts
|
|
272
588
|
var PLUGIN_NAME = "ic-reactor-plugin";
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
} = options;
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
589
|
+
var DEFAULT_OUT_DIR = "src/canisters";
|
|
590
|
+
var GUIDE_LINE = "ic-reactor: agent guide at node_modules/@ic-reactor/core/llms.txt";
|
|
591
|
+
var consoleLog = {
|
|
592
|
+
info: (message) => console.log(message),
|
|
593
|
+
warn: (message) => console.warn(message),
|
|
594
|
+
error: (message) => console.error(message)
|
|
595
|
+
};
|
|
596
|
+
function icReactor(options = {}) {
|
|
597
|
+
const { canisters = {}, injectEnvironment = true, failOnError } = options;
|
|
598
|
+
const names = Object.keys(canisters);
|
|
599
|
+
const configuredCanisterIds = Object.fromEntries(
|
|
600
|
+
names.flatMap((name) => {
|
|
601
|
+
const { canisterId } = canisters[name];
|
|
602
|
+
return canisterId ? [[name, canisterId]] : [];
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
const generated = names.flatMap((name) => {
|
|
606
|
+
const { didFile, outDir = DEFAULT_OUT_DIR } = canisters[name];
|
|
607
|
+
return didFile === void 0 ? [] : [{ name, didFile, outDir }];
|
|
608
|
+
});
|
|
287
609
|
let projectRoot = process.cwd();
|
|
288
610
|
let command = "build";
|
|
611
|
+
let log = consoleLog;
|
|
612
|
+
let announced = false;
|
|
289
613
|
let localEnvironment;
|
|
290
614
|
const apiProxyOptions = /* @__PURE__ */ new Set();
|
|
291
615
|
let devServer = null;
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
);
|
|
616
|
+
const didPath = (canister) => import_node_path2.default.resolve(projectRoot, canister.didFile);
|
|
617
|
+
const relativeToRoot = (file) => import_node_path2.default.relative(projectRoot, file) || ".";
|
|
295
618
|
const generatedFrom = /* @__PURE__ */ new Map();
|
|
296
|
-
const
|
|
619
|
+
const unfixed = /* @__PURE__ */ new Map();
|
|
620
|
+
let tail = Promise.resolve();
|
|
621
|
+
const serially = (job) => {
|
|
622
|
+
const result = tail.then(job);
|
|
623
|
+
tail = result.catch(() => void 0);
|
|
624
|
+
return result;
|
|
625
|
+
};
|
|
626
|
+
const queued = /* @__PURE__ */ new Set();
|
|
627
|
+
let stopper = new AbortController();
|
|
628
|
+
const readDid = (canister) => {
|
|
297
629
|
try {
|
|
298
|
-
return
|
|
630
|
+
return import_node_fs2.default.readFileSync(didPath(canister), "utf-8");
|
|
299
631
|
} catch {
|
|
300
632
|
return void 0;
|
|
301
633
|
}
|
|
302
634
|
};
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
635
|
+
const generateNow = async (wanted, force, signal) => {
|
|
636
|
+
if (signal.aborted) return void 0;
|
|
637
|
+
try {
|
|
638
|
+
const sources = new Map(
|
|
639
|
+
wanted.map((canister) => [canister.name, readDid(canister)])
|
|
640
|
+
);
|
|
641
|
+
const stale = wanted.filter(
|
|
642
|
+
({ name }) => force || sources.get(name) === void 0 || generatedFrom.get(name) !== sources.get(name)
|
|
643
|
+
);
|
|
644
|
+
if (stale.length === 0) return [];
|
|
645
|
+
let cli;
|
|
646
|
+
try {
|
|
647
|
+
cli = resolveCliBin(projectRoot);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
return stale.map((canister) => ({ canister, message: describe(error) }));
|
|
650
|
+
}
|
|
651
|
+
const result = await generate({
|
|
652
|
+
cli,
|
|
653
|
+
root: projectRoot,
|
|
654
|
+
timeoutMs: GENERATE_TIMEOUT_MS,
|
|
655
|
+
signal,
|
|
656
|
+
// Shown as it arrives: a run that takes long, or is killed at the
|
|
657
|
+
// timeout, would otherwise say nothing until it is over.
|
|
658
|
+
onStderr: (line, didFiles) => {
|
|
659
|
+
if (signal.aborted) return;
|
|
660
|
+
log.warn(
|
|
661
|
+
`ic-reactor: candid-core-cli (${didFiles.map((file) => relativeToRoot(file)).join(", ")}): ${line}`
|
|
662
|
+
);
|
|
663
|
+
},
|
|
664
|
+
canisters: stale.map((canister) => ({
|
|
665
|
+
name: canister.name,
|
|
666
|
+
didFile: didPath(canister),
|
|
667
|
+
outDir: import_node_path2.default.resolve(projectRoot, canister.outDir)
|
|
668
|
+
}))
|
|
669
|
+
});
|
|
670
|
+
if (signal.aborted) return void 0;
|
|
671
|
+
const failures = [];
|
|
672
|
+
for (const outcome of result.outcomes) {
|
|
673
|
+
const members = stale.filter(({ name }) => outcome.names.includes(name));
|
|
674
|
+
const names2 = members.map(({ name }) => name).join(", ");
|
|
675
|
+
if (outcome.status === "failed") {
|
|
676
|
+
for (const canister of members) {
|
|
677
|
+
generatedFrom.delete(canister.name);
|
|
678
|
+
failures.push({ canister, message: outcome.failure ?? "" });
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
for (const { name } of members) {
|
|
683
|
+
const source = sources.get(name);
|
|
684
|
+
if (source !== void 0) generatedFrom.set(name, source);
|
|
685
|
+
}
|
|
686
|
+
if (outcome.status === "written" && outcome.module) {
|
|
687
|
+
log.info(
|
|
688
|
+
`ic-reactor: generated ${names2} into ${relativeToRoot(outcome.module)}`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
for (const { kind, name: what, reason, via } of outcome.omitted) {
|
|
692
|
+
log.warn(
|
|
693
|
+
`ic-reactor: ${names2}: omitted ${kind} ${what} (${reason}${via ? ` via ${via}` : ""})`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return failures;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
if (signal.aborted) return void 0;
|
|
700
|
+
return wanted.map((canister) => ({ canister, message: describe(error) }));
|
|
701
|
+
}
|
|
311
702
|
};
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
};
|
|
323
|
-
server?.ws.send({ type: "error", err });
|
|
324
|
-
return { message: err.message, stack: err.stack };
|
|
703
|
+
const describeFailures = (failures) => {
|
|
704
|
+
const byMessage = /* @__PURE__ */ new Map();
|
|
705
|
+
for (const { canister, message } of failures) {
|
|
706
|
+
const label = `${canister.name} (${relativeToRoot(didPath(canister))})`;
|
|
707
|
+
byMessage.set(message, [...byMessage.get(message) ?? [], label]);
|
|
708
|
+
}
|
|
709
|
+
return `ic-reactor: could not generate ${failures.length} of ${generated.length} canisters:
|
|
710
|
+
` + [...byMessage].map(
|
|
711
|
+
([message, labels]) => ` - ${labels.join(", ")}: ${message.replace(/\n/g, "\n ")}`
|
|
712
|
+
).join("\n");
|
|
325
713
|
};
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const running = inFlight.get(canisterConfig);
|
|
331
|
-
if (running) {
|
|
332
|
-
rerunQueued.add(canisterConfig);
|
|
333
|
-
return running;
|
|
714
|
+
const publish = (attempted, failures) => {
|
|
715
|
+
let wasFailing = false;
|
|
716
|
+
for (const { name } of attempted) {
|
|
717
|
+
if (unfixed.delete(name)) wasFailing = true;
|
|
334
718
|
}
|
|
335
|
-
const
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
);
|
|
341
|
-
|
|
719
|
+
for (const failure of failures) unfixed.set(failure.canister.name, failure);
|
|
720
|
+
if (failures.length > 0) {
|
|
721
|
+
log.error(describeFailures(failures));
|
|
722
|
+
showOverlay();
|
|
723
|
+
} else if (wasFailing && unfixed.size > 0) {
|
|
724
|
+
showOverlay();
|
|
725
|
+
} else if (wasFailing) {
|
|
726
|
+
devServer?.ws.send({ type: "full-reload" });
|
|
342
727
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
} else {
|
|
353
|
-
pendingFailures.set(
|
|
354
|
-
canisterConfig,
|
|
355
|
-
reportFailure(
|
|
356
|
-
server,
|
|
357
|
-
`Regeneration failed for ${name}: ${result.error ?? "unknown error"}`
|
|
358
|
-
)
|
|
359
|
-
);
|
|
728
|
+
};
|
|
729
|
+
const showOverlay = (client) => {
|
|
730
|
+
if (unfixed.size === 0) return;
|
|
731
|
+
const payload = {
|
|
732
|
+
type: "error",
|
|
733
|
+
err: {
|
|
734
|
+
message: describeFailures([...unfixed.values()]),
|
|
735
|
+
stack: "",
|
|
736
|
+
plugin: PLUGIN_NAME
|
|
360
737
|
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
`Regeneration failed for ${name}: ${describeError(error)}`,
|
|
367
|
-
error
|
|
368
|
-
)
|
|
369
|
-
);
|
|
370
|
-
}).finally(() => {
|
|
371
|
-
inFlight.delete(canisterConfig);
|
|
372
|
-
if (rerunQueued.delete(canisterConfig)) {
|
|
373
|
-
void regenerate(canisterConfig, server);
|
|
738
|
+
};
|
|
739
|
+
if (typeof client?.send === "function") {
|
|
740
|
+
try {
|
|
741
|
+
client.send(JSON.stringify(payload));
|
|
742
|
+
} catch {
|
|
374
743
|
}
|
|
375
|
-
});
|
|
376
|
-
inFlight.set(canisterConfig, run);
|
|
377
|
-
return run;
|
|
378
|
-
};
|
|
379
|
-
const regenerateForDid = (file, server) => {
|
|
380
|
-
if (!file.endsWith(".did")) {
|
|
381
744
|
return;
|
|
382
745
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
746
|
+
devServer?.ws.send(payload);
|
|
747
|
+
};
|
|
748
|
+
const onDidSaved = (file) => {
|
|
749
|
+
const saved = import_node_path2.default.normalize(file);
|
|
750
|
+
const affected = generated.filter((canister) => didPath(canister) === saved);
|
|
751
|
+
if (affected.length === 0 || queued.has(saved)) return;
|
|
752
|
+
queued.add(saved);
|
|
753
|
+
log.info(
|
|
754
|
+
`ic-reactor: ${relativeToRoot(saved)} changed, regenerating ${affected.map(({ name }) => name).join(", ")}`
|
|
386
755
|
);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
756
|
+
const { signal } = stopper;
|
|
757
|
+
void serially(async () => {
|
|
758
|
+
queued.delete(saved);
|
|
759
|
+
const failures = await generateNow(affected, true, signal);
|
|
760
|
+
if (failures) publish(affected, failures);
|
|
761
|
+
});
|
|
762
|
+
};
|
|
763
|
+
const onDidRemoved = (file) => {
|
|
764
|
+
const removed = import_node_path2.default.normalize(file);
|
|
765
|
+
const affected = generated.filter(
|
|
766
|
+
(canister) => didPath(canister) === removed
|
|
392
767
|
);
|
|
393
|
-
|
|
394
|
-
|
|
768
|
+
if (affected.length === 0) return;
|
|
769
|
+
const { signal } = stopper;
|
|
770
|
+
void serially(async () => {
|
|
771
|
+
try {
|
|
772
|
+
if (signal.aborted || import_node_fs2.default.existsSync(removed)) return;
|
|
773
|
+
for (const { name } of affected) generatedFrom.delete(name);
|
|
774
|
+
publish(
|
|
775
|
+
affected,
|
|
776
|
+
affected.map((canister) => ({
|
|
777
|
+
canister,
|
|
778
|
+
message: "the .did file was deleted; its module is not regenerated until the file comes back"
|
|
779
|
+
}))
|
|
780
|
+
);
|
|
781
|
+
} catch (error) {
|
|
782
|
+
log.error(`ic-reactor: ${describe(error)}`);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
};
|
|
786
|
+
const announceGuide = (userConfig) => {
|
|
787
|
+
if (announced) return;
|
|
788
|
+
announced = true;
|
|
789
|
+
if (userConfig.customLogger) {
|
|
790
|
+
userConfig.customLogger.info(GUIDE_LINE);
|
|
791
|
+
} else if (!["silent", "error", "warn"].includes(userConfig.logLevel ?? "")) {
|
|
792
|
+
console.log(GUIDE_LINE);
|
|
395
793
|
}
|
|
396
794
|
};
|
|
397
795
|
const plugin = {
|
|
398
796
|
name: PLUGIN_NAME,
|
|
399
797
|
enforce: "pre",
|
|
400
798
|
// Run before other plugins
|
|
401
|
-
async config(userConfig, { command: viteCommand }) {
|
|
799
|
+
async config(userConfig, { command: viteCommand, mode }) {
|
|
800
|
+
announceGuide(userConfig);
|
|
402
801
|
command = viteCommand;
|
|
403
|
-
if (viteCommand !== "serve" || !injectEnvironment) {
|
|
802
|
+
if (viteCommand !== "serve" || mode === "test" || !injectEnvironment) {
|
|
404
803
|
return {};
|
|
405
804
|
}
|
|
406
805
|
const ownsApiProxy = !userConfig.server?.proxy?.["/api"];
|
|
407
806
|
const environment = createLocalEnvironment({
|
|
408
|
-
canisterNames:
|
|
807
|
+
canisterNames: names,
|
|
409
808
|
configuredCanisterIds,
|
|
410
809
|
// `configResolved` has not run yet, so resolve the root the way Vite
|
|
411
810
|
// will. icp finds the project from the directory it starts in, and
|
|
412
811
|
// with `vite apps/web` or a `root` option that is not the process cwd.
|
|
413
|
-
projectRoot:
|
|
812
|
+
projectRoot: import_node_path2.default.resolve(userConfig.root ?? process.cwd()),
|
|
414
813
|
onDiagnostic: debugLog,
|
|
415
814
|
onUpdate: (previous, next) => {
|
|
416
815
|
for (const proxyOptions of apiProxyOptions) {
|
|
@@ -423,7 +822,7 @@ function icReactor(options) {
|
|
|
423
822
|
});
|
|
424
823
|
localEnvironment = environment;
|
|
425
824
|
const state = await environment.detect();
|
|
426
|
-
warnAboutIncompleteDetection(state,
|
|
825
|
+
warnAboutIncompleteDetection(state, names.length > 0, ownsApiProxy);
|
|
427
826
|
return {
|
|
428
827
|
server: {
|
|
429
828
|
// The cookie is not a static `server.headers` entry: the middleware
|
|
@@ -445,29 +844,19 @@ function icReactor(options) {
|
|
|
445
844
|
configResolved(config) {
|
|
446
845
|
projectRoot = config.root;
|
|
447
846
|
command = config.command;
|
|
847
|
+
log = config.logger;
|
|
448
848
|
},
|
|
449
849
|
configureServer(server) {
|
|
450
850
|
devServer = server;
|
|
451
851
|
if (localEnvironment) {
|
|
452
852
|
server.middlewares.use(icEnvMiddleware(localEnvironment));
|
|
453
853
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
message: failures.map((failure) => failure.message).join("\n"),
|
|
461
|
-
stack: failures.map((failure) => failure.stack).filter(Boolean).join("\n"),
|
|
462
|
-
plugin: PLUGIN_NAME
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
});
|
|
466
|
-
const didFiles = canisters.map((c) => resolveDidPath(c.didFile));
|
|
467
|
-
server.watcher.add(didFiles);
|
|
468
|
-
const onDidEvent = (file) => regenerateForDid(file, server);
|
|
469
|
-
server.watcher.on("change", onDidEvent);
|
|
470
|
-
server.watcher.on("add", onDidEvent);
|
|
854
|
+
if (generated.length === 0) return;
|
|
855
|
+
server.ws.on?.("connection", showOverlay);
|
|
856
|
+
server.watcher.add(generated.map(didPath));
|
|
857
|
+
server.watcher.on("change", onDidSaved);
|
|
858
|
+
server.watcher.on("add", onDidSaved);
|
|
859
|
+
server.watcher.on("unlink", onDidRemoved);
|
|
471
860
|
},
|
|
472
861
|
// `vite preview` resolves the config with the `serve` command too, and
|
|
473
862
|
// used to inherit the cookie from `server.headers`.
|
|
@@ -477,90 +866,28 @@ function icReactor(options) {
|
|
|
477
866
|
}
|
|
478
867
|
},
|
|
479
868
|
async buildStart() {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const sources = canisters.map(readDidSource);
|
|
486
|
-
const pending = canisters.filter(
|
|
487
|
-
(canister, index) => !this.meta.watchMode || sources[index] === void 0 || generatedFrom.get(canister) !== sources[index]
|
|
869
|
+
if (generated.length === 0) return;
|
|
870
|
+
for (const canister of generated) this.addWatchFile(didPath(canister));
|
|
871
|
+
const { signal } = stopper;
|
|
872
|
+
const failures = await serially(
|
|
873
|
+
() => generateNow(generated, false, signal)
|
|
488
874
|
);
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
console.log(
|
|
493
|
-
`[ic-reactor] Generating canister bindings for ${pending.length} canisters...`
|
|
494
|
-
);
|
|
495
|
-
const outcomes = await Promise.allSettled(
|
|
496
|
-
pending.map((canisterConfig) => {
|
|
497
|
-
const sharedError = sharedOutDirError(canisterConfig);
|
|
498
|
-
if (sharedError !== void 0) {
|
|
499
|
-
return Promise.reject(new SharedOutDirError(sharedError));
|
|
500
|
-
}
|
|
501
|
-
return (0, import_codegen.runCanisterPipeline)({
|
|
502
|
-
canisterConfig,
|
|
503
|
-
projectRoot,
|
|
504
|
-
globalConfig
|
|
505
|
-
});
|
|
506
|
-
})
|
|
507
|
-
);
|
|
508
|
-
outcomes.forEach((outcome, index) => {
|
|
509
|
-
const canister = pending[index];
|
|
510
|
-
const source = sources[canisters.indexOf(canister)];
|
|
511
|
-
if (outcome.status === "fulfilled") {
|
|
512
|
-
reportWarnings(canister.name, outcome.value.warnings);
|
|
513
|
-
}
|
|
514
|
-
if (outcome.status === "fulfilled" && outcome.value.success && source !== void 0) {
|
|
515
|
-
generatedFrom.set(canister, source);
|
|
516
|
-
} else {
|
|
517
|
-
generatedFrom.delete(canister);
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
const failures = outcomes.flatMap((outcome, index) => {
|
|
521
|
-
const canister = pending[index];
|
|
522
|
-
const name = canister?.name ?? `canister #${index}`;
|
|
523
|
-
if (outcome.status === "rejected") {
|
|
524
|
-
const detail = outcome.reason instanceof SharedOutDirError ? outcome.reason.message : `${name}: ${describeError(outcome.reason)}`;
|
|
525
|
-
return [{ canister, detail }];
|
|
526
|
-
}
|
|
527
|
-
if (!outcome.value.success) {
|
|
528
|
-
return [
|
|
529
|
-
{
|
|
530
|
-
canister,
|
|
531
|
-
detail: `${name}: ${outcome.value.error ?? "unknown error"}`
|
|
532
|
-
}
|
|
533
|
-
];
|
|
534
|
-
}
|
|
535
|
-
return [];
|
|
536
|
-
});
|
|
537
|
-
if (failures.length === 0) {
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
const message = `Failed to generate ${failures.length} of ${pending.length} canisters:
|
|
541
|
-
` + failures.map(({ detail }) => ` - ${detail}`).join("\n");
|
|
542
|
-
if (failOnError ?? command === "build") {
|
|
543
|
-
this.error(`[ic-reactor] ${message}`);
|
|
544
|
-
}
|
|
545
|
-
reportFailure(devServer, message);
|
|
546
|
-
for (const { canister, detail } of failures) {
|
|
547
|
-
pendingFailures.set(canister, {
|
|
548
|
-
message: `[ic-reactor] Failed to generate ${detail}`,
|
|
549
|
-
stack: ""
|
|
550
|
-
});
|
|
875
|
+
if (!failures) return;
|
|
876
|
+
if (failures.length > 0 && (failOnError ?? command === "build")) {
|
|
877
|
+
this.error(describeFailures(failures));
|
|
551
878
|
}
|
|
879
|
+
publish(generated, failures);
|
|
880
|
+
},
|
|
881
|
+
// The end of a build, and the close of a dev server, which Vite reports
|
|
882
|
+
// here once for each of its environments.
|
|
883
|
+
closeBundle() {
|
|
884
|
+
stopper.abort();
|
|
885
|
+
stopper = new AbortController();
|
|
552
886
|
}
|
|
553
887
|
};
|
|
554
888
|
return plugin;
|
|
555
889
|
}
|
|
556
|
-
|
|
557
|
-
};
|
|
558
|
-
function reportWarnings(name, warnings) {
|
|
559
|
-
for (const warning of warnings ?? []) {
|
|
560
|
-
console.warn(`[ic-reactor] ${name}: ${warning}`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
function describeError(error) {
|
|
890
|
+
function describe(error) {
|
|
564
891
|
return error instanceof Error ? error.message : String(error);
|
|
565
892
|
}
|
|
566
893
|
function apiProxy(userConfig, target, configure) {
|
|
@@ -578,7 +905,7 @@ function apiProxy(userConfig, target, configure) {
|
|
|
578
905
|
}
|
|
579
906
|
};
|
|
580
907
|
}
|
|
581
|
-
function
|
|
908
|
+
function quoteNames2(names) {
|
|
582
909
|
return names.map((name) => `"${name}"`).join(", ");
|
|
583
910
|
}
|
|
584
911
|
function warnAboutIncompleteDetection(state, hasCanisters, ownsApiProxy) {
|
|
@@ -594,7 +921,7 @@ function warnAboutIncompleteDetection(state, hasCanisters, ownsApiProxy) {
|
|
|
594
921
|
if (missing.length > 0) {
|
|
595
922
|
const it = missing.length === 1 ? "it" : "them";
|
|
596
923
|
console.warn(
|
|
597
|
-
`[ic-reactor] The local replica is running, but no canister ID could be resolved for ${
|
|
924
|
+
`[ic-reactor] The local replica is running, but no canister ID could be resolved for ${quoteNames2(missing)}. Until one is, the ic_env cookie carries no PUBLIC_CANISTER_ID for ${it} and the app will see an undefined canister id. Deploy ${it} (\`icp deploy\`) and reload the page: the plugin asks \`icp\` again on each page load until every configured canister has an ID. Re-run with DEBUG=ic-reactor to see the \`icp\` output.`
|
|
598
925
|
);
|
|
599
926
|
}
|
|
600
927
|
}
|
|
@@ -609,7 +936,7 @@ function reportDetectionProgress(previous, next, followsApiProxy) {
|
|
|
609
936
|
);
|
|
610
937
|
if (next.environment && resolved.length > 0) {
|
|
611
938
|
console.log(
|
|
612
|
-
`[ic-reactor] The ic_env cookie now carries the canister ID${resolved.length === 1 ? "" : "s"} for ${
|
|
939
|
+
`[ic-reactor] The ic_env cookie now carries the canister ID${resolved.length === 1 ? "" : "s"} for ${quoteNames2(resolved)}.`
|
|
613
940
|
);
|
|
614
941
|
}
|
|
615
942
|
if (next.complete && !previous.complete) {
|