@automaton-labs/aib 0.0.2 → 0.0.4

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.
Files changed (46) hide show
  1. package/dist/bin/cli.js +2 -2
  2. package/dist/commands/config-command.js +1 -3
  3. package/dist/commands/config-output.js +8 -0
  4. package/dist/commands/init-workspace.js +1 -3
  5. package/dist/commands/inspect-tree.js +3 -3
  6. package/dist/commands/reveal-command.js +5 -0
  7. package/dist/commands/sync-command.js +2 -2
  8. package/dist/config/local-config.js +3 -3
  9. package/dist/config/resolve-command-alias.js +1 -1
  10. package/dist/config/tsconfig-discovery.js +1 -0
  11. package/dist/config/workspace-root.js +3 -3
  12. package/dist/config/workspace-state.js +1 -1
  13. package/dist/dsl/aib-dsl.js +1 -1
  14. package/dist/help/bootstrap.md +21 -11
  15. package/dist/help/docs/first-setup.md +277 -0
  16. package/dist/help/docs/inspect.tree.md +8 -6
  17. package/dist/help/docs/patterns.md +12 -3
  18. package/dist/help/dsl/bootstrap.md +21 -11
  19. package/dist/help/dsl/docs/first-setup.md +277 -0
  20. package/dist/help/dsl/docs/inspect.tree.md +8 -6
  21. package/dist/help/dsl/docs/patterns.md +12 -3
  22. package/dist/help/dsl/full.md +21 -11
  23. package/dist/help/dsl/topics/first-setup.md +275 -0
  24. package/dist/help/dsl/topics/inspect.tree.md +8 -6
  25. package/dist/help/dsl/topics/patterns.md +12 -3
  26. package/dist/help/full.md +21 -11
  27. package/dist/help/help-meta.json +11 -7
  28. package/dist/help/index.md +6 -0
  29. package/dist/help/json/bootstrap.md +21 -11
  30. package/dist/help/json/docs/first-setup.md +277 -0
  31. package/dist/help/json/docs/inspect.tree.md +8 -6
  32. package/dist/help/json/docs/patterns.md +12 -3
  33. package/dist/help/json/full.md +21 -11
  34. package/dist/help/json/topics/first-setup.md +275 -0
  35. package/dist/help/json/topics/inspect.tree.md +8 -6
  36. package/dist/help/json/topics/patterns.md +12 -3
  37. package/dist/help/topics/first-setup.md +275 -0
  38. package/dist/help/topics/inspect.tree.md +8 -6
  39. package/dist/help/topics/patterns.md +12 -3
  40. package/dist/runtime/run-command.js +1 -1
  41. package/extension/vscode-refactor-bridge-extension.vsix +0 -0
  42. package/package.json +1 -1
  43. package/runtimes/launcher/win-x64/aib.exe +0 -0
  44. package/scripts/install-windows-launcher.cjs +1 -0
  45. package/scripts/postinstall.cjs +5 -4
  46. package/scripts/provision-runtime.cjs +160 -24
@@ -14,6 +14,7 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.j
14
14
  const platformKey = `${process.platform}-${process.arch}`;
15
15
 
16
16
  const options = parseArgs(process.argv.slice(2));
17
+ const reporter = createReporter(!options.quiet);
17
18
 
18
19
  main().catch((error) => {
19
20
  if (!options.quiet) {
@@ -23,9 +24,11 @@ main().catch((error) => {
23
24
  });
24
25
 
25
26
  async function main() {
27
+ cleanupOldTempDownloads();
28
+
26
29
  if (runtimeComplete()) {
27
30
  if (!options.quiet) {
28
- process.stdout.write(`runtime ready: ${path.relative(process.cwd(), runtimesDir) || runtimesDir}\n`);
31
+ reporter.info(`aib runtime ready: ${path.relative(process.cwd(), runtimesDir) || runtimesDir}`);
29
32
  }
30
33
  return;
31
34
  }
@@ -49,31 +52,38 @@ async function main() {
49
52
 
50
53
  if (!source && !url && !manifestSource && !manifestUrl) {
51
54
  if (!options.quiet) {
52
- process.stdout.write("runtime not provisioned: set AIB_RUNTIME_ARCHIVE, AIB_RUNTIME_URL, AIB_RUNTIME_MANIFEST, or AIB_RUNTIME_MANIFEST_URL\n");
55
+ reporter.info("aib runtime not provisioned: set AIB_RUNTIME_ARCHIVE, AIB_RUNTIME_URL, AIB_RUNTIME_MANIFEST, or AIB_RUNTIME_MANIFEST_URL");
53
56
  }
54
57
  return;
55
58
  }
56
59
 
60
+ reporter.info(`aib runtime: provisioning ${platformKey}`);
57
61
  const resolution = source || url
58
- ? { archivePath: source ? path.resolve(source) : await downloadRuntimeArchive(url), expectedSha256: options.sha256 ?? process.env.AIB_RUNTIME_SHA256 ?? null }
62
+ ? { ...(source ? { archivePath: path.resolve(source), tempDir: null } : await downloadRuntimeArchive(url, "runtime archive")), expectedSha256: options.sha256 ?? process.env.AIB_RUNTIME_SHA256 ?? null }
59
63
  : await resolveFromManifest(manifestSource, manifestUrl);
60
64
  const archivePath = resolution.archivePath;
61
- if (!fs.existsSync(archivePath)) {
62
- throw new Error(`Runtime archive not found: ${archivePath}`);
63
- }
64
- if (resolution.expectedSha256) {
65
- assertSha256(archivePath, resolution.expectedSha256);
66
- }
65
+ try {
66
+ if (!fs.existsSync(archivePath)) {
67
+ throw new Error(`Runtime archive not found: ${archivePath}`);
68
+ }
69
+ if (resolution.expectedSha256) {
70
+ reporter.info("aib runtime: verifying checksum");
71
+ assertSha256(archivePath, resolution.expectedSha256);
72
+ }
67
73
 
68
- fs.mkdirSync(runtimesDir, { recursive: true });
69
- extractRuntimeArchive(archivePath);
74
+ fs.mkdirSync(runtimesDir, { recursive: true });
75
+ reporter.info("aib runtime: extracting archive");
76
+ extractRuntimeArchive(archivePath);
70
77
 
71
- if (!runtimeComplete()) {
72
- throw new Error(`Runtime archive did not provision a complete runtime under ${runtimesDir}`);
78
+ if (!runtimeComplete()) {
79
+ throw new Error(`Runtime archive did not provision a complete runtime under ${runtimesDir}`);
80
+ }
81
+ } finally {
82
+ cleanupOwnedTempDownload(resolution.tempDir);
73
83
  }
74
84
 
75
85
  if (!options.quiet) {
76
- process.stdout.write(`runtime provisioned: ${path.relative(process.cwd(), runtimesDir) || runtimesDir}\n`);
86
+ reporter.info("aib runtime: ready");
77
87
  }
78
88
  }
79
89
 
@@ -124,6 +134,7 @@ function parseArgs(argv) {
124
134
  }
125
135
 
126
136
  async function resolveFromManifest(manifestSource, manifestUrl) {
137
+ reporter.info("aib runtime: resolving manifest");
127
138
  const manifest = manifestSource
128
139
  ? JSON.parse(fs.readFileSync(path.resolve(manifestSource), "utf8"))
129
140
  : await downloadJson(manifestUrl);
@@ -133,21 +144,72 @@ async function resolveFromManifest(manifestSource, manifestUrl) {
133
144
  }
134
145
  const archivePath = artifact.file && manifestSource
135
146
  ? path.resolve(path.dirname(path.resolve(manifestSource)), String(artifact.file))
136
- : artifact.url
137
- ? await downloadRuntimeArchive(String(artifact.url))
138
- : null;
139
- if (!archivePath) {
147
+ : null;
148
+ const downloaded = !archivePath && artifact.url
149
+ ? await downloadRuntimeArchive(String(artifact.url))
150
+ : null;
151
+ const resolvedArchivePath = archivePath ?? downloaded?.archivePath ?? null;
152
+ const tempDir = downloaded?.tempDir ?? null;
153
+ if (!resolvedArchivePath) {
140
154
  throw new Error(`Runtime manifest artifact for ${platformKey} has no url or local file.`);
141
155
  }
142
156
  return {
143
- archivePath,
157
+ archivePath: resolvedArchivePath,
158
+ tempDir,
144
159
  expectedSha256: typeof artifact.sha256 === "string" ? artifact.sha256 : null
145
160
  };
146
161
  }
147
162
 
148
163
  async function downloadJson(url) {
149
- const filePath = await downloadRuntimeArchive(url);
150
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
164
+ const download = await downloadRuntimeArchive(url, "runtime manifest");
165
+ try {
166
+ return JSON.parse(fs.readFileSync(download.archivePath, "utf8"));
167
+ } finally {
168
+ cleanupOwnedTempDownload(download.tempDir);
169
+ }
170
+ }
171
+
172
+ function cleanupOldTempDownloads() {
173
+ const cutoffMs = Date.now() - 24 * 60 * 60 * 1000;
174
+ let entries = [];
175
+ try {
176
+ entries = fs.readdirSync(os.tmpdir(), { withFileTypes: true });
177
+ } catch {
178
+ return;
179
+ }
180
+ for (const entry of entries) {
181
+ if (!entry.isDirectory() || !entry.name.startsWith("aib-runtime-")) {
182
+ continue;
183
+ }
184
+ const tempDir = path.join(os.tmpdir(), entry.name);
185
+ try {
186
+ const stat = fs.statSync(tempDir);
187
+ if (stat.mtimeMs < cutoffMs) {
188
+ fs.rmSync(tempDir, { recursive: true, force: true });
189
+ }
190
+ } catch {
191
+ // Best-effort cleanup only.
192
+ }
193
+ }
194
+ }
195
+
196
+ function cleanupOwnedTempDownload(tempDir) {
197
+ if (!tempDir) {
198
+ return;
199
+ }
200
+ try {
201
+ const resolvedTemp = path.resolve(tempDir);
202
+ const resolvedOsTemp = path.resolve(os.tmpdir());
203
+ const relative = path.relative(resolvedOsTemp, resolvedTemp);
204
+ if (path.basename(resolvedTemp).startsWith("aib-runtime-")
205
+ && relative
206
+ && !relative.startsWith("..")
207
+ && !path.isAbsolute(relative)) {
208
+ fs.rmSync(resolvedTemp, { recursive: true, force: true });
209
+ }
210
+ } catch {
211
+ // Best-effort cleanup only.
212
+ }
151
213
  }
152
214
 
153
215
  function defaultManifestUrl() {
@@ -262,7 +324,7 @@ function assertSha256(filePath, expectedSha256) {
262
324
  }
263
325
  }
264
326
 
265
- function downloadRuntimeArchive(url) {
327
+ function downloadRuntimeArchive(url, label = "runtime archive") {
266
328
  return new Promise((resolve, reject) => {
267
329
  if (!url) {
268
330
  reject(new Error("Runtime URL is empty."));
@@ -276,24 +338,98 @@ function downloadRuntimeArchive(url) {
276
338
  if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
277
339
  file.close();
278
340
  fs.rmSync(targetPath, { force: true });
279
- downloadRuntimeArchive(new URL(response.headers.location, url).toString()).then(resolve, reject);
341
+ cleanupOwnedTempDownload(tempDir);
342
+ downloadRuntimeArchive(new URL(response.headers.location, url).toString(), label).then(resolve, reject);
280
343
  return;
281
344
  }
282
345
  if (response.statusCode !== 200) {
283
346
  file.close();
284
347
  fs.rmSync(targetPath, { force: true });
348
+ cleanupOwnedTempDownload(tempDir);
285
349
  reject(new Error(`Runtime download failed with HTTP ${response.statusCode}: ${url}`));
286
350
  return;
287
351
  }
352
+ const totalBytes = Number.parseInt(String(response.headers["content-length"] ?? ""), 10);
353
+ const progress = reporter.startProgress(`aib runtime: downloading ${label}`, Number.isFinite(totalBytes) ? totalBytes : null);
354
+ response.on("data", (chunk) => progress.add(chunk.length));
288
355
  response.pipe(file);
289
356
  file.on("finish", () => {
290
- file.close(() => resolve(targetPath));
357
+ file.close(() => {
358
+ progress.done();
359
+ resolve({ archivePath: targetPath, tempDir });
360
+ });
291
361
  });
292
362
  });
293
363
  request.on("error", (error) => {
294
364
  file.close();
295
365
  fs.rmSync(targetPath, { force: true });
366
+ cleanupOwnedTempDownload(tempDir);
296
367
  reject(error);
297
368
  });
298
369
  });
299
370
  }
371
+
372
+ function createReporter(enabled) {
373
+ const stream = process.stderr;
374
+ return {
375
+ info(message) {
376
+ if (!enabled) {
377
+ return;
378
+ }
379
+ stream.write(`${message}\n`);
380
+ },
381
+ startProgress(label, totalBytes) {
382
+ if (!enabled) {
383
+ return { add() {}, done() {} };
384
+ }
385
+ let downloadedBytes = 0;
386
+ let ended = false;
387
+ let nextPercent = 10;
388
+ const totalKnown = Number.isFinite(totalBytes) && totalBytes > 0;
389
+ const showPercentProgress = totalKnown && totalBytes >= 1024 * 1024;
390
+ stream.write(totalKnown
391
+ ? `${label} ${formatBytes(totalBytes)}\n`
392
+ : `${label}\n`);
393
+ const writeProgress = () => {
394
+ if (!showPercentProgress) {
395
+ return;
396
+ }
397
+ const percent = Math.floor(Math.min(100, (downloadedBytes / totalBytes) * 100));
398
+ while (percent >= nextPercent && nextPercent <= 100) {
399
+ const thresholdBytes = Math.min(totalBytes, Math.round((totalBytes * nextPercent) / 100));
400
+ stream.write(`aib runtime: ${nextPercent}% ${formatBytes(thresholdBytes)} / ${formatBytes(totalBytes)}\n`);
401
+ nextPercent += 10;
402
+ }
403
+ };
404
+ return {
405
+ add(byteCount) {
406
+ downloadedBytes += byteCount;
407
+ writeProgress();
408
+ },
409
+ done() {
410
+ if (ended) {
411
+ return;
412
+ }
413
+ ended = true;
414
+ downloadedBytes = Math.max(downloadedBytes, totalBytes ?? downloadedBytes);
415
+ writeProgress();
416
+ }
417
+ };
418
+ }
419
+ };
420
+ }
421
+
422
+ function formatBytes(bytes) {
423
+ if (!Number.isFinite(bytes) || bytes < 0) {
424
+ return "0 B";
425
+ }
426
+ const units = ["B", "KB", "MB", "GB"];
427
+ let value = bytes;
428
+ let unitIndex = 0;
429
+ while (value >= 1024 && unitIndex < units.length - 1) {
430
+ value /= 1024;
431
+ unitIndex += 1;
432
+ }
433
+ const digits = value >= 100 || unitIndex === 0 ? 0 : value >= 10 ? 1 : 2;
434
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
435
+ }