@camstack/system 1.0.3 → 1.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.
package/dist/index.js CHANGED
@@ -3473,6 +3473,66 @@ var AddonManifest = class {
3473
3473
  this.cache = null;
3474
3474
  }
3475
3475
  /**
3476
+ * Reconcile the manifest with what is actually present on disk.
3477
+ *
3478
+ * For every `@scope/<pkg>` directory under `addonsDir` that has a
3479
+ * readable `package.json` but NO manifest entry yet, add one with
3480
+ * source `'seed'`. Existing entries are left untouched (their source,
3481
+ * version and timestamps are authoritative — a later install/update
3482
+ * overwrites them through `upsert`).
3483
+ *
3484
+ * Why this exists: addons baked into the Docker/Electron image are
3485
+ * copied straight into `addonsDir` by the container entrypoint (the
3486
+ * launcher's `ensureRequiredPackages` then sees `package.json` already
3487
+ * present and skips, never calling `upsert`). Without this reconcile
3488
+ * those seeded addons stay absent from the manifest, so `applyUpdate`
3489
+ * rejects them with "not currently tracked in manifest" — which is
3490
+ * exactly what broke "Update all" on a fresh image. Idempotent and
3491
+ * safe to run on every boot.
3492
+ *
3493
+ * Returns the number of entries added.
3494
+ */
3495
+ reconcileFromDisk(addonsDir) {
3496
+ if (!node_fs.existsSync(addonsDir)) return 0;
3497
+ const m = this.read();
3498
+ let added = 0;
3499
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3500
+ for (const scope of node_fs.readdirSync(addonsDir, { withFileTypes: true })) {
3501
+ if (!scope.isDirectory() || !scope.name.startsWith("@")) continue;
3502
+ const scopeDir = node_path.join(addonsDir, scope.name);
3503
+ for (const pkg of node_fs.readdirSync(scopeDir, { withFileTypes: true })) {
3504
+ if (!pkg.isDirectory()) continue;
3505
+ const pkgJsonPath = node_path.join(scopeDir, pkg.name, "package.json");
3506
+ if (!node_fs.existsSync(pkgJsonPath)) continue;
3507
+ let pkgJson;
3508
+ try {
3509
+ const parsed = JSON.parse(node_fs.readFileSync(pkgJsonPath, "utf-8"));
3510
+ if (typeof parsed !== "object" || parsed == null) continue;
3511
+ pkgJson = parsed;
3512
+ } catch {
3513
+ continue;
3514
+ }
3515
+ if (!pkgJson.name || !pkgJson.version) continue;
3516
+ const addons = pkgJson.camstack?.addons;
3517
+ if (!Array.isArray(addons) || addons.length === 0) continue;
3518
+ if (m.addons[pkgJson.name] != null) continue;
3519
+ m.addons[pkgJson.name] = {
3520
+ name: pkgJson.name,
3521
+ version: pkgJson.version,
3522
+ source: "seed",
3523
+ installedAt: now,
3524
+ updatedAt: now
3525
+ };
3526
+ added++;
3527
+ }
3528
+ }
3529
+ if (added > 0) {
3530
+ m.updatedAt = now;
3531
+ this.write(m);
3532
+ }
3533
+ return added;
3534
+ }
3535
+ /**
3476
3536
  * Migration helper: if the manifest is empty but the addons directory
3477
3537
  * has packages with `.install-source` markers (the legacy per-addon
3478
3538
  * tracking), seed the manifest from those markers. Runs once at boot.
@@ -3846,6 +3906,21 @@ var AddonInstaller = class AddonInstaller {
3846
3906
  }
3847
3907
  }
3848
3908
  /**
3909
+ * Reconcile the install manifest with the addons actually present on
3910
+ * disk. Seeded addons (baked into the image and copied verbatim by the
3911
+ * container entrypoint) never pass through an `install*` codepath, so
3912
+ * they are missing from `manifest.json` and `applyUpdate` would reject
3913
+ * them with "not currently tracked in manifest". Call this once after
3914
+ * `ensureRequiredPackages` so every on-disk addon is updatable.
3915
+ *
3916
+ * Returns the number of manifest entries added.
3917
+ */
3918
+ reconcileManifest() {
3919
+ const added = this.manifest.reconcileFromDisk(this.addonsDir);
3920
+ if (added > 0) this.logger.info(`Manifest reconciled — ${added} seeded addon(s) registered`);
3921
+ return added;
3922
+ }
3923
+ /**
3849
3924
  * Install a single addon — on-demand installer used by AddonPackageService
3850
3925
  * (admin UI "Install addon" / "Reinstall" buttons).
3851
3926
  *
package/dist/index.mjs CHANGED
@@ -3467,6 +3467,66 @@ var AddonManifest = class {
3467
3467
  this.cache = null;
3468
3468
  }
3469
3469
  /**
3470
+ * Reconcile the manifest with what is actually present on disk.
3471
+ *
3472
+ * For every `@scope/<pkg>` directory under `addonsDir` that has a
3473
+ * readable `package.json` but NO manifest entry yet, add one with
3474
+ * source `'seed'`. Existing entries are left untouched (their source,
3475
+ * version and timestamps are authoritative — a later install/update
3476
+ * overwrites them through `upsert`).
3477
+ *
3478
+ * Why this exists: addons baked into the Docker/Electron image are
3479
+ * copied straight into `addonsDir` by the container entrypoint (the
3480
+ * launcher's `ensureRequiredPackages` then sees `package.json` already
3481
+ * present and skips, never calling `upsert`). Without this reconcile
3482
+ * those seeded addons stay absent from the manifest, so `applyUpdate`
3483
+ * rejects them with "not currently tracked in manifest" — which is
3484
+ * exactly what broke "Update all" on a fresh image. Idempotent and
3485
+ * safe to run on every boot.
3486
+ *
3487
+ * Returns the number of entries added.
3488
+ */
3489
+ reconcileFromDisk(addonsDir) {
3490
+ if (!fs$18.existsSync(addonsDir)) return 0;
3491
+ const m = this.read();
3492
+ let added = 0;
3493
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3494
+ for (const scope of fs$18.readdirSync(addonsDir, { withFileTypes: true })) {
3495
+ if (!scope.isDirectory() || !scope.name.startsWith("@")) continue;
3496
+ const scopeDir = path$40.join(addonsDir, scope.name);
3497
+ for (const pkg of fs$18.readdirSync(scopeDir, { withFileTypes: true })) {
3498
+ if (!pkg.isDirectory()) continue;
3499
+ const pkgJsonPath = path$40.join(scopeDir, pkg.name, "package.json");
3500
+ if (!fs$18.existsSync(pkgJsonPath)) continue;
3501
+ let pkgJson;
3502
+ try {
3503
+ const parsed = JSON.parse(fs$18.readFileSync(pkgJsonPath, "utf-8"));
3504
+ if (typeof parsed !== "object" || parsed == null) continue;
3505
+ pkgJson = parsed;
3506
+ } catch {
3507
+ continue;
3508
+ }
3509
+ if (!pkgJson.name || !pkgJson.version) continue;
3510
+ const addons = pkgJson.camstack?.addons;
3511
+ if (!Array.isArray(addons) || addons.length === 0) continue;
3512
+ if (m.addons[pkgJson.name] != null) continue;
3513
+ m.addons[pkgJson.name] = {
3514
+ name: pkgJson.name,
3515
+ version: pkgJson.version,
3516
+ source: "seed",
3517
+ installedAt: now,
3518
+ updatedAt: now
3519
+ };
3520
+ added++;
3521
+ }
3522
+ }
3523
+ if (added > 0) {
3524
+ m.updatedAt = now;
3525
+ this.write(m);
3526
+ }
3527
+ return added;
3528
+ }
3529
+ /**
3470
3530
  * Migration helper: if the manifest is empty but the addons directory
3471
3531
  * has packages with `.install-source` markers (the legacy per-addon
3472
3532
  * tracking), seed the manifest from those markers. Runs once at boot.
@@ -3840,6 +3900,21 @@ var AddonInstaller = class AddonInstaller {
3840
3900
  }
3841
3901
  }
3842
3902
  /**
3903
+ * Reconcile the install manifest with the addons actually present on
3904
+ * disk. Seeded addons (baked into the image and copied verbatim by the
3905
+ * container entrypoint) never pass through an `install*` codepath, so
3906
+ * they are missing from `manifest.json` and `applyUpdate` would reject
3907
+ * them with "not currently tracked in manifest". Call this once after
3908
+ * `ensureRequiredPackages` so every on-disk addon is updatable.
3909
+ *
3910
+ * Returns the number of manifest entries added.
3911
+ */
3912
+ reconcileManifest() {
3913
+ const added = this.manifest.reconcileFromDisk(this.addonsDir);
3914
+ if (added > 0) this.logger.info(`Manifest reconciled — ${added} seeded addon(s) registered`);
3915
+ return added;
3916
+ }
3917
+ /**
3843
3918
  * Install a single addon — on-demand installer used by AddonPackageService
3844
3919
  * (admin UI "Install addon" / "Reinstall" buttons).
3845
3920
  *
@@ -86,6 +86,17 @@ export declare class AddonInstaller {
86
86
  * @param packages — optional custom package list (default: REQUIRED_PACKAGES)
87
87
  */
88
88
  ensureRequiredPackages(packages?: readonly string[]): Promise<void>;
89
+ /**
90
+ * Reconcile the install manifest with the addons actually present on
91
+ * disk. Seeded addons (baked into the image and copied verbatim by the
92
+ * container entrypoint) never pass through an `install*` codepath, so
93
+ * they are missing from `manifest.json` and `applyUpdate` would reject
94
+ * them with "not currently tracked in manifest". Call this once after
95
+ * `ensureRequiredPackages` so every on-disk addon is updatable.
96
+ *
97
+ * Returns the number of manifest entries added.
98
+ */
99
+ reconcileManifest(): number;
89
100
  /**
90
101
  * Install a single addon — on-demand installer used by AddonPackageService
91
102
  * (admin UI "Install addon" / "Reinstall" buttons).
@@ -66,6 +66,27 @@ export declare class AddonManifest {
66
66
  * Useful when an external process may have written the manifest.
67
67
  */
68
68
  invalidate(): void;
69
+ /**
70
+ * Reconcile the manifest with what is actually present on disk.
71
+ *
72
+ * For every `@scope/<pkg>` directory under `addonsDir` that has a
73
+ * readable `package.json` but NO manifest entry yet, add one with
74
+ * source `'seed'`. Existing entries are left untouched (their source,
75
+ * version and timestamps are authoritative — a later install/update
76
+ * overwrites them through `upsert`).
77
+ *
78
+ * Why this exists: addons baked into the Docker/Electron image are
79
+ * copied straight into `addonsDir` by the container entrypoint (the
80
+ * launcher's `ensureRequiredPackages` then sees `package.json` already
81
+ * present and skips, never calling `upsert`). Without this reconcile
82
+ * those seeded addons stay absent from the manifest, so `applyUpdate`
83
+ * rejects them with "not currently tracked in manifest" — which is
84
+ * exactly what broke "Update all" on a fresh image. Idempotent and
85
+ * safe to run on every boot.
86
+ *
87
+ * Returns the number of entries added.
88
+ */
89
+ reconcileFromDisk(addonsDir: string): number;
69
90
  /**
70
91
  * Migration helper: if the manifest is empty but the addons directory
71
92
  * has packages with `.install-source` markers (the legacy per-addon
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",