@meshxdata/fops 0.1.45 → 0.1.46

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 (33) hide show
  1. package/CHANGELOG.md +16 -18
  2. package/package.json +1 -1
  3. package/src/commands/lifecycle.js +81 -5
  4. package/src/commands/setup.js +45 -4
  5. package/src/plugins/bundled/fops-plugin-azure/index.js +29 -0
  6. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +1185 -0
  7. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-flux.js +1180 -0
  8. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-ingress.js +393 -0
  9. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-naming.js +104 -0
  10. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-network.js +296 -0
  11. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-postgres.js +768 -0
  12. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-reconcilers.js +538 -0
  13. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-secrets.js +849 -0
  14. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-stacks.js +643 -0
  15. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-state.js +145 -0
  16. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +496 -0
  17. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-terraform.js +1032 -0
  18. package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +155 -4245
  19. package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault.js +186 -0
  20. package/src/plugins/bundled/fops-plugin-azure/lib/azure-results.js +5 -0
  21. package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +758 -0
  22. package/src/plugins/bundled/fops-plugin-azure/lib/commands/registry-cmds.js +250 -0
  23. package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +2 -1
  24. package/src/plugins/bundled/fops-plugin-foundation/lib/apply.js +3 -2
  25. package/src/plugins/bundled/fops-plugin-foundation/lib/helpers.js +21 -0
  26. package/src/plugins/bundled/fops-plugin-foundation/lib/tools-read.js +3 -5
  27. package/src/ui/tui/App.js +13 -13
  28. package/src/web/dist/assets/index-NXC8Hvnp.css +1 -0
  29. package/src/web/dist/assets/index-QH1N4ejK.js +112 -0
  30. package/src/web/dist/index.html +2 -2
  31. package/src/web/server.js +4 -4
  32. package/src/web/dist/assets/index-BphVaAUd.css +0 -1
  33. package/src/web/dist/assets/index-CSckLzuG.js +0 -129
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
- ## [0.1.45] - 2026-03-12
2
-
1
+ ## [0.1.46] - 2026-03-23
2
+
3
+ - default locust to CLI mode, add --web for UI (ca35bff)
4
+ - add locust command for load testing AKS clusters (1278722)
5
+ - update spot node pool default autoscaling to 1-20 (617c182)
6
+ - module for aks (3dd1a61)
7
+ - add hive to PG_SERVICE_DBS for fops pg-setup (afccb16)
8
+ - feat(azure): enhance aks doctor with ExternalSecrets and PGSSLMODE checks (8b14861)
9
+ - add foundation-postgres ExternalName service to reconciler (ea88e11)
10
+ - new flux templates (0e2e372)
11
+ - feat(azure): add storage-engine secrets to Key Vault (a4f488e)
12
+ - feat(azure-aks): add AUTH0_DOMAIN to template rendering variables (216c37e)
13
+ - feat(azure): add storage account creation per cluster (aa1b138)
14
+ - bump watcher (ab24473)
15
+ - fix: concurrent compute calls (#66) (03e2edf)
16
+ - bump backend version (5058ff5)
3
17
  - bump fops to 0.1.44 (8c0ef5d)
4
18
  - Mlflow and azure plugin fix (176881f)
5
19
  - fix lifecycle (a2cb9e7)
@@ -166,22 +180,6 @@
166
180
  - tui fixes (0599779)
167
181
  - cleanup (27731f0)
168
182
  - train (90bf559)
169
- - training (f809bf6)
170
- - training (ba2b836)
171
- - training (6fc5267)
172
- - training (4af8ac9)
173
- - fix build script (bd82836)
174
- - infra test (5b79815)
175
- - infra test (3a0ac05)
176
- - infra test (e5c67b5)
177
- - tests (ae7b621)
178
- - tests (c09ae6a)
179
- - update tui (4784153)
180
- - training (0a5a330)
181
- - tui (df4dd4a)
182
- - pkg builds (4dc9993)
183
- - also source env for creds (9a17d8f)
184
- - fcl support (e8a5743)
185
183
 
186
184
  # Changelog
187
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshxdata/fops",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "CLI to install and manage data mesh platforms",
5
5
  "keywords": [
6
6
  "fops",
@@ -32,6 +32,7 @@ export function registerLifecycleCommands(program, registry) {
32
32
  .option("--url <url>", "Set the public URL (e.g. https://192.168.1.10) — writes FOUNDATION_PUBLIC_URL to .env and enables traefik if port is 443")
33
33
  .option("--pull", "Pull latest images before starting (default: skip pull)")
34
34
  .option("--frontend-dev", "Run frontend in development mode (hot reload) instead of production build")
35
+ .option("--local", "Run frontend dev server locally (outside container) with npm run dev — requires --frontend-dev")
35
36
  .option("--minio-storage", "Use MinIO as the storage backend instead of foundation-storage-engine")
36
37
  .option("--non-interactive", "Skip interactive prompts (auto-grant admin, no browser open)")
37
38
  .action(async (component, branch, opts) => {
@@ -443,6 +444,14 @@ async function runUp(program, registry, opts) {
443
444
  console.error(chalk.dim(` Available: ${Object.keys(COMPONENT_SUBMODULES).join(", ")}\n`));
444
445
  process.exit(1);
445
446
  }
447
+
448
+ // --local requires --frontend-dev
449
+ if (opts.local && !opts.frontendDev) {
450
+ console.error(chalk.red(`\n --local requires --frontend-dev flag`));
451
+ console.error(chalk.dim(` Usage: fops up --frontend-dev --local\n`));
452
+ process.exit(1);
453
+ }
454
+
446
455
  if (comp && opts.branch) {
447
456
  const componentDir = path.join(root, comp.dir);
448
457
  if (!fs.existsSync(path.join(componentDir, ".git"))) {
@@ -580,7 +589,10 @@ async function runUp(program, registry, opts) {
580
589
  }
581
590
 
582
591
  // Frontend: either production (frontend-prod) or dev with hot reload (frontend-dev)
583
- activeProfiles.add(opts.frontendDev ? "frontend-dev" : "frontend-prod");
592
+ // --local: skip frontend profile entirely, we'll run npm dev locally
593
+ if (!opts.local) {
594
+ activeProfiles.add(opts.frontendDev ? "frontend-dev" : "frontend-prod");
595
+ }
584
596
 
585
597
  const profileArgs = [
586
598
  ...(opts.minioStorage ? ["-f", "docker-compose.yaml", "-f", "docker-compose.minio-storage.yaml"] : []),
@@ -1363,11 +1375,18 @@ async function runUp(program, registry, opts) {
1363
1375
  clearSpinner();
1364
1376
 
1365
1377
  let result;
1378
+ const skipFrontendContainer = opts.local && opts.frontendDev;
1366
1379
  if (componentOnlyUp) {
1367
- const serviceList = opts.frontendDev && opts.component === "frontend"
1368
- ? ["foundation-frontend-dev"]
1369
- : comp.restart;
1370
- result = await runUpInner(` Starting ${opts.component}...`, null, serviceList);
1380
+ if (skipFrontendContainer && opts.component === "frontend") {
1381
+ // --local: skip container, we'll run npm dev locally after
1382
+ result = { exitCode: 0 };
1383
+ console.log(chalk.dim(" Skipping frontend container (--local mode)..."));
1384
+ } else {
1385
+ const serviceList = opts.frontendDev && opts.component === "frontend"
1386
+ ? ["foundation-frontend-dev"]
1387
+ : comp.restart;
1388
+ result = await runUpInner(` Starting ${opts.component}...`, null, serviceList);
1389
+ }
1371
1390
  } else if (needsSequentialUp) {
1372
1391
  const k3sOnlyArgs = profileArgs.filter((a, i, arr) => !(a === "--profile" && arr[i + 1] === "traefik") && !(arr[i - 1] === "--profile" && a === "traefik"));
1373
1392
  result = await runUpInner(" Starting services (k3s)...", k3sOnlyArgs);
@@ -1479,6 +1498,63 @@ async function runUp(program, registry, opts) {
1479
1498
  }
1480
1499
  }
1481
1500
 
1501
+ // --local: Run frontend dev server locally with compose env vars
1502
+ if (skipFrontendContainer) {
1503
+ console.log(chalk.cyan("\n Starting local frontend dev server..."));
1504
+ const frontendDir = path.join(root, "foundation-frontend");
1505
+ if (!fs.existsSync(path.join(frontendDir, "package.json"))) {
1506
+ console.error(chalk.red(" foundation-frontend not found. Run: git submodule update --init foundation-frontend"));
1507
+ process.exitCode = 1;
1508
+ return;
1509
+ }
1510
+
1511
+ // Extract environment variables from compose config for frontend-dev service
1512
+ let frontendEnv = {};
1513
+ try {
1514
+ const { stdout: configJson } = await execa("docker", [
1515
+ "compose", "--profile", "frontend-dev", "config", "--format", "json",
1516
+ ], { cwd: root, reject: false, timeout: 15000 });
1517
+ if (configJson?.trim()) {
1518
+ const config = JSON.parse(configJson);
1519
+ const frontendSvc = config.services?.["foundation-frontend-dev"];
1520
+ if (frontendSvc?.environment) {
1521
+ for (const [key, val] of Object.entries(frontendSvc.environment)) {
1522
+ frontendEnv[key] = val;
1523
+ }
1524
+ }
1525
+ }
1526
+ } catch (err) {
1527
+ console.error(chalk.yellow(" Could not extract compose env vars:"), err.message);
1528
+ }
1529
+
1530
+ // Transform docker-internal URLs to localhost for local dev
1531
+ // Backend is exposed on host port 9001 (container 9000 → host 9001)
1532
+ frontendEnv.INTERNAL_API_URL = "http://localhost:9001";
1533
+ frontendEnv.PORT = "3002";
1534
+ // Remove WATCHPACK_POLLING - not needed outside container
1535
+ delete frontendEnv.WATCHPACK_POLLING;
1536
+
1537
+ // Merge with process.env (compose vars override, then specific overrides)
1538
+ const npmEnv = { ...process.env, ...frontendEnv };
1539
+
1540
+ console.log(chalk.dim(" Environment:"));
1541
+ console.log(chalk.dim(` NEXT_PUBLIC_API_URL=${frontendEnv.NEXT_PUBLIC_API_URL || "not set"}`));
1542
+ console.log(chalk.dim(` INTERNAL_API_URL=${frontendEnv.INTERNAL_API_URL}`));
1543
+ console.log(chalk.dim(` AUTH0_ENABLED=${frontendEnv.AUTH0_ENABLED || "not set"}`));
1544
+
1545
+ // Run npm run dev in foreground (replaces this process)
1546
+ console.log(chalk.cyan("\n Running: npm run dev"));
1547
+ console.log(chalk.dim(" Press Ctrl+C to stop the dev server\n"));
1548
+ const npmProc = execa("npm", ["run", "dev"], {
1549
+ cwd: frontendDir,
1550
+ env: npmEnv,
1551
+ stdio: "inherit",
1552
+ });
1553
+ npmProc.catch(() => {}); // Don't throw on Ctrl+C
1554
+ await npmProc;
1555
+ return;
1556
+ }
1557
+
1482
1558
  // Detect non-interactive environment (no TTY on stdin, e.g. remote VM, CI, cloud-init)
1483
1559
  // --non-interactive flag forces non-interactive mode (used by fops azure provisioning)
1484
1560
  const isInteractive = !opts.nonInteractive && process.stdin.isTTY === true;
@@ -246,12 +246,53 @@ export function registerSetupCommands(program, registry) {
246
246
  const root = rootDir();
247
247
  if (root) {
248
248
  console.log(DIM(" Updating compose project..."));
249
+ let stashedMain = false;
249
250
  try {
250
251
  const { stdout: branch } = await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: root, timeout: 5000 });
251
- await retry(
252
- () => execa("git", ["pull", "--ff-only", "origin", branch.trim()], { cwd: root, stdio: "inherit", timeout: 120_000 }),
253
- { attempts: 3, delay: 2000, onRetry: (n, max) => console.log(DIM(` Retrying pull (${n}/${max - 1})...`)) }
254
- );
252
+ const branchName = branch.trim();
253
+
254
+ const tryPull = async () => {
255
+ return execa("git", ["pull", "--ff-only", "origin", branchName], { cwd: root, stdio: "pipe", timeout: 120_000 });
256
+ };
257
+
258
+ try {
259
+ await retry(tryPull, { attempts: 3, delay: 2000, onRetry: (n, max) => console.log(DIM(` Retrying pull (${n}/${max - 1})...`)) });
260
+ } catch (pullErr) {
261
+ const errOut = (pullErr.stderr || pullErr.stdout || pullErr.message || "").toString();
262
+ const wouldOverwrite = /would be overwritten by merge|Your local changes to the following files would be overwritten/i.test(errOut);
263
+ const needsStash = /Please commit your changes or stash them/i.test(errOut);
264
+
265
+ if (wouldOverwrite || needsStash) {
266
+ if (!process.stdin.isTTY) {
267
+ console.log(WARN(" ⚠ Local changes would be overwritten by pull."));
268
+ console.log(DIM(" Run in a TTY to be prompted to stash, or: git stash && fops update"));
269
+ throw pullErr;
270
+ }
271
+ const { getInquirer } = await import("../lazy.js");
272
+ const { stash } = await (await getInquirer()).prompt([
273
+ {
274
+ type: "confirm",
275
+ name: "stash",
276
+ message: "Local changes would be overwritten. Stash them and continue?",
277
+ default: true,
278
+ },
279
+ ]);
280
+ if (!stash) {
281
+ console.log(DIM(" Skipping update. Stash or commit changes then run fops update again."));
282
+ throw pullErr;
283
+ }
284
+ await execa("git", ["stash", "push", "-m", "fops update: stashed before pull"], { cwd: root, timeout: 10_000 });
285
+ stashedMain = true;
286
+ await tryPull();
287
+ } else {
288
+ throw pullErr;
289
+ }
290
+ }
291
+
292
+ if (stashedMain) {
293
+ console.log(WARN(" Stashed local changes in compose project"));
294
+ console.log(DIM(" Restore with: git stash pop"));
295
+ }
255
296
  console.log(OK(" ✓ Compose project updated"));
256
297
  } catch (err) {
257
298
  console.log(WARN(` ⚠ git pull failed: ${err.message}`));
@@ -18,6 +18,7 @@ import { registerVmCommands } from "./lib/commands/vm-cmds.js";
18
18
  import { registerFleetCommands } from "./lib/commands/fleet-cmds.js";
19
19
  import { registerTestCommands } from "./lib/commands/test-cmds.js";
20
20
  import { registerInfraCommands } from "./lib/commands/infra-cmds.js";
21
+ import { registerRegistryCommands } from "./lib/commands/registry-cmds.js";
21
22
 
22
23
  export { resolveFoundationCreds, resolveAuth0Config, authenticateVm, vmFetch };
23
24
 
@@ -33,6 +34,7 @@ export async function register(api) {
33
34
  registerFleetCommands(azure);
34
35
  registerTestCommands(azure);
35
36
  registerInfraCommands(azure);
37
+ registerRegistryCommands(azure);
36
38
  });
37
39
 
38
40
  // ── Cost agent tools + agent ───────────────────────────────────────────
@@ -186,6 +188,22 @@ export async function register(api) {
186
188
  },
187
189
  });
188
190
 
191
+ api.registerDoctorCheck({
192
+ name: "cloudflared",
193
+ fn: async (ok, warn) => {
194
+ try {
195
+ const { execa } = await import("execa");
196
+ const { stdout } = await execa("cloudflared", ["--version"], { timeout: 10000 });
197
+ ok(stdout.trim().split("\n")[0]);
198
+ } catch {
199
+ const hint = process.platform === "darwin"
200
+ ? "optional — run: brew install cloudflared"
201
+ : "optional — see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/";
202
+ warn("cloudflared not installed", hint);
203
+ }
204
+ },
205
+ });
206
+
189
207
  // ── Hook: before:up — auto-sync Key Vault secrets ─────────────────────
190
208
 
191
209
  api.registerHook("before:up", async () => {
@@ -381,6 +399,17 @@ export async function register(api) {
381
399
  "- `fops azure aks flux reconcile [name]` — Force reconciliation",
382
400
  "Flux watches a Git repo and auto-applies manifests to the cluster.",
383
401
  "Push to repo → Flux detects → applies. No kubectl needed.",
402
+ "",
403
+ "### Registry (OCI/Docker via Cloudflare Access)",
404
+ "Push images to AKS-hosted Zot registry through Cloudflare Access tunnel.",
405
+ "",
406
+ "- `fops azure registry login [--domain <url>]` — Authenticate via cloudflared (opens browser)",
407
+ "- `fops azure registry push <image> [--tag <tag>]` — Tag and push image to registry",
408
+ "- `fops azure registry pull <image>` — Pull image from registry",
409
+ "- `fops azure registry logout` — Remove stored credentials",
410
+ "",
411
+ "Workflow: login once per session → push branch images → they're available in AKS.",
412
+ "Credentials stored in ~/.fops/registry/ and ~/.docker/config.json.",
384
413
  ].join("\n"),
385
414
  score: 0.9,
386
415
  },