@flui-cloud/cli 0.0.1 → 0.2.0

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 (108) hide show
  1. package/lib/cli/src/commands/app/list.d.ts +3 -0
  2. package/lib/cli/src/commands/app/list.js +72 -18
  3. package/lib/cli/src/commands/app/status.d.ts +1 -0
  4. package/lib/cli/src/commands/app/status.js +27 -2
  5. package/lib/cli/src/commands/cluster/destroy.d.ts +1 -1
  6. package/lib/cli/src/commands/cluster/destroy.js +2 -2
  7. package/lib/cli/src/commands/deploy.d.ts +3 -0
  8. package/lib/cli/src/commands/deploy.js +19 -0
  9. package/lib/cli/src/commands/dev/creds.d.ts +0 -1
  10. package/lib/cli/src/commands/dev/creds.js +6 -27
  11. package/lib/cli/src/commands/dev/tunnel.js +8 -8
  12. package/lib/cli/src/commands/env/capacity.js +4 -4
  13. package/lib/cli/src/commands/env/create.d.ts +4 -1
  14. package/lib/cli/src/commands/env/create.js +78 -52
  15. package/lib/cli/src/commands/env/credentials.js +12 -12
  16. package/lib/cli/src/commands/env/destroy.d.ts +2 -1
  17. package/lib/cli/src/commands/env/destroy.js +45 -28
  18. package/lib/cli/src/commands/env/diag-ca.js +5 -5
  19. package/lib/cli/src/commands/env/export-config.d.ts +0 -17
  20. package/lib/cli/src/commands/env/export-config.js +50 -47
  21. package/lib/cli/src/commands/env/force-ready.d.ts +1 -1
  22. package/lib/cli/src/commands/env/force-ready.js +8 -8
  23. package/lib/cli/src/commands/env/inspect.js +5 -5
  24. package/lib/cli/src/commands/env/refresh-kubeconfig.js +4 -4
  25. package/lib/cli/src/commands/env/repair-ssh-ca.js +4 -4
  26. package/lib/cli/src/commands/env/repair-storage.d.ts +9 -0
  27. package/lib/cli/src/commands/env/repair-storage.js +82 -0
  28. package/lib/cli/src/commands/env/restart.d.ts +1 -1
  29. package/lib/cli/src/commands/env/restart.js +9 -9
  30. package/lib/cli/src/commands/env/scale-master.js +4 -4
  31. package/lib/cli/src/commands/env/scale-node.js +4 -4
  32. package/lib/cli/src/commands/env/set-master-protection.d.ts +16 -0
  33. package/lib/cli/src/commands/env/set-master-protection.js +120 -0
  34. package/lib/cli/src/commands/env/status.d.ts +1 -1
  35. package/lib/cli/src/commands/env/status.js +10 -10
  36. package/lib/cli/src/commands/env/stop.d.ts +1 -1
  37. package/lib/cli/src/commands/env/stop.js +8 -8
  38. package/lib/cli/src/commands/env/storage-expand.js +4 -4
  39. package/lib/cli/src/commands/env/storage.d.ts +1 -1
  40. package/lib/cli/src/commands/env/storage.js +5 -5
  41. package/lib/cli/src/commands/env/sync.js +5 -5
  42. package/lib/cli/src/commands/env/uncordon.js +4 -4
  43. package/lib/cli/src/commands/env/update-firewall.d.ts +13 -1
  44. package/lib/cli/src/commands/env/update-firewall.js +232 -126
  45. package/lib/cli/src/commands/integration/connect.d.ts +1 -0
  46. package/lib/cli/src/commands/integration/connect.js +19 -1
  47. package/lib/cli/src/commands/integration/reset.d.ts +13 -0
  48. package/lib/cli/src/commands/integration/reset.js +95 -0
  49. package/lib/cli/src/commands/integration/setup.d.ts +18 -0
  50. package/lib/cli/src/commands/integration/setup.js +320 -0
  51. package/lib/cli/src/commands/integration/status.d.ts +9 -0
  52. package/lib/cli/src/commands/integration/status.js +117 -0
  53. package/lib/cli/src/commands/node/list.d.ts +1 -0
  54. package/lib/cli/src/commands/node/list.js +19 -2
  55. package/lib/cli/src/commands/server-types/list.d.ts +3 -0
  56. package/lib/cli/src/commands/server-types/list.js +84 -0
  57. package/lib/cli/src/commands/ssh.js +5 -5
  58. package/lib/cli/src/commands/version.d.ts +18 -0
  59. package/lib/cli/src/commands/version.js +100 -0
  60. package/lib/cli/src/config/bootstrap.config.d.ts +10 -1
  61. package/lib/cli/src/config/bootstrap.config.js +24 -4
  62. package/lib/cli/src/config/preferences-schema.js +5 -5
  63. package/lib/cli/src/config/release-override.d.ts +43 -0
  64. package/lib/cli/src/config/release-override.js +203 -0
  65. package/lib/cli/src/config/release.config.d.ts +31 -0
  66. package/lib/cli/src/config/release.config.js +38 -0
  67. package/lib/cli/src/lib/prompts.d.ts +1 -6
  68. package/lib/cli/src/lib/prompts.js +33 -13
  69. package/lib/cli/src/lib/services/cli-app.service.d.ts +33 -0
  70. package/lib/cli/src/lib/services/cli-app.service.js +9 -0
  71. package/lib/cli/src/lib/services/reconciliation.service.js +1 -1
  72. package/lib/cli/src/lib/templates/firewall-rules.d.ts +2 -2
  73. package/lib/cli/src/lib/templates/firewall-rules.js +3 -3
  74. package/lib/cli/src/modules/cli-infrastructure.module.js +3 -3
  75. package/lib/cli/src/services/cli-cluster-creator.service.js +31 -6
  76. package/lib/cli/src/services/cli-clusters.service.d.ts +3 -3
  77. package/lib/cli/src/services/cli-clusters.service.js +57 -34
  78. package/lib/cli/src/services/cli-control-cluster.service.d.ts +129 -0
  79. package/lib/cli/src/services/cli-control-cluster.service.js +545 -0
  80. package/lib/cli/src/services/cli-endpoint-resolver.service.d.ts +1 -0
  81. package/lib/cli/src/services/cli-endpoint-resolver.service.js +25 -11
  82. package/lib/cli/src/services/cli-k3s-script.service.d.ts +8 -1
  83. package/lib/cli/src/services/cli-k3s-script.service.js +14 -6
  84. package/lib/src/config/release.config.d.ts +28 -0
  85. package/lib/src/config/release.config.js +35 -0
  86. package/lib/src/modules/applications/entities/application.entity.d.ts +13 -20
  87. package/lib/src/modules/applications/entities/application.entity.js +12 -0
  88. package/lib/src/modules/applications/enums/application-exposure.enum.d.ts +2 -1
  89. package/lib/src/modules/applications/enums/application-exposure.enum.js +1 -0
  90. package/lib/src/modules/applications/interfaces/source-config.interface.d.ts +1 -0
  91. package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.d.ts +8 -2
  92. package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.js +16 -1
  93. package/lib/src/modules/infrastructure/clusters/services/cluster-node-scaling.service.js +2 -2
  94. package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.d.ts +3 -2
  95. package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.js +11 -4
  96. package/lib/src/modules/infrastructure/shared/services/kubernetes.service.d.ts +26 -0
  97. package/lib/src/modules/infrastructure/shared/services/kubernetes.service.js +105 -8
  98. package/lib/src/modules/management/entities/provider-capabilities.entity.d.ts +2 -0
  99. package/lib/src/modules/providers/implementations/contabo/contabo-capabilities.service.js +2 -0
  100. package/lib/src/modules/providers/implementations/hetzner/hetzner-capabilities.service.js +3 -6
  101. package/lib/src/modules/providers/implementations/scaleway/scaleway-capabilities.service.js +2 -1
  102. package/lib/src/modules/providers/implementations/scaleway/scaleway-firewall.service.js +3 -1
  103. package/lib/src/modules/providers/implementations/scaleway/scaleway-provider.service.js +3 -1
  104. package/lib/src/modules/providers/interfaces/provider-capabilities.interface.d.ts +0 -2
  105. package/lib/src/modules/providers/services/hetzner-firewall.service.d.ts +1 -1
  106. package/lib/src/modules/providers/services/hetzner-firewall.service.js +2 -1
  107. package/oclif.manifest.json +1201 -854
  108. package/package.json +2 -2
@@ -0,0 +1,18 @@
1
+ import { Command } from '@oclif/core';
2
+ /**
3
+ * `flui version` — surfaces the CLI version AND the platform release it pins.
4
+ *
5
+ * The CLI's npm version is decoupled from the platform release (carried in
6
+ * RELEASE), so this command makes the mapping explicit: which bootstrap ref and
7
+ * which component image tags a `flui env create` would install. Handy when
8
+ * debugging an install ("which versions did this CLI actually use?").
9
+ */
10
+ export default class Version extends Command {
11
+ static readonly description = "Show the CLI version and the platform release it pins (component image tags + bootstrap ref). Useful for debugging an install.";
12
+ static readonly examples: string[];
13
+ static readonly flags: {
14
+ latest: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ run(): Promise<void>;
18
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const bootstrap_config_1 = require("../config/bootstrap.config");
9
+ const release_override_1 = require("../config/release-override");
10
+ function describePlatform(release) {
11
+ if (release.source === 'latest') {
12
+ return chalk_1.default.yellow('latest (mobile tags)');
13
+ }
14
+ if (release.source === 'override') {
15
+ return `${chalk_1.default.yellow(release.version ?? 'override')}${chalk_1.default.dim(' (release override)')}`;
16
+ }
17
+ return `${chalk_1.default.cyan(release.version ?? '')}${chalk_1.default.dim(' (pinned release)')}`;
18
+ }
19
+ /**
20
+ * `flui version` — surfaces the CLI version AND the platform release it pins.
21
+ *
22
+ * The CLI's npm version is decoupled from the platform release (carried in
23
+ * RELEASE), so this command makes the mapping explicit: which bootstrap ref and
24
+ * which component image tags a `flui env create` would install. Handy when
25
+ * debugging an install ("which versions did this CLI actually use?").
26
+ */
27
+ class Version extends core_1.Command {
28
+ async run() {
29
+ const { flags } = await this.parse(Version);
30
+ const useLatest = flags.latest;
31
+ const release = (0, release_override_1.getEffectiveRelease)(useLatest);
32
+ const tags = release.images;
33
+ const bootstrapRef = release.bootstrapRef;
34
+ const scriptsBaseUrl = (0, bootstrap_config_1.getScriptsBaseUrl)(useLatest);
35
+ const urlOverride = process.env.BOOTSTRAP_SCRIPTS_URL ?? null;
36
+ const images = {
37
+ 'flui-api': `ghcr.io/flui-cloud/core:${tags.fluiApi}`,
38
+ 'flui-web': `ghcr.io/flui-cloud/dashboard:${tags.fluiWeb}`,
39
+ 'flui-authz': `ghcr.io/flui-cloud/flui-authz:${tags.fluiAuthz}`,
40
+ };
41
+ if (flags.json) {
42
+ this.log(JSON.stringify({
43
+ cli: this.config.version,
44
+ mode: release.source,
45
+ platform: release.version,
46
+ bootstrapRef,
47
+ scriptsBaseUrl,
48
+ bootstrapUrlOverride: urlOverride,
49
+ releaseFile: release.filePath,
50
+ images: tags,
51
+ }, null, 2));
52
+ return;
53
+ }
54
+ const label = (s) => chalk_1.default.dim(s.padEnd(15));
55
+ const cliName = chalk_1.default.dim(`(${this.config.name})`);
56
+ const platformLabel = describePlatform(release);
57
+ this.log('');
58
+ this.log(chalk_1.default.bold('Flui CLI'));
59
+ this.log('');
60
+ this.log(` ${label('CLI')}${chalk_1.default.cyan(this.config.version)} ${cliName}`);
61
+ this.log(` ${label('Platform')}${platformLabel}`);
62
+ this.log(` ${label('Bootstrap ref')}${chalk_1.default.cyan(bootstrapRef)}`);
63
+ this.log('');
64
+ this.log(chalk_1.default.dim(' Component images:'));
65
+ for (const [name, ref] of Object.entries(images)) {
66
+ this.log(` ${chalk_1.default.dim(name.padEnd(12))}${ref}`);
67
+ }
68
+ this.log('');
69
+ this.log(` ${label('Scripts URL')}${chalk_1.default.dim(scriptsBaseUrl)}`);
70
+ if (urlOverride) {
71
+ this.log(` ${label('')}${chalk_1.default.yellow('↑ overridden via BOOTSTRAP_SCRIPTS_URL')}`);
72
+ }
73
+ if (release.source === 'override' && release.filePath) {
74
+ const rel = (0, release_override_1.displayReleaseFilePath)(release.filePath);
75
+ this.log(` ${label('Release file')}${chalk_1.default.yellow(rel)}`);
76
+ }
77
+ if (!useLatest && release.source !== 'override') {
78
+ this.log('');
79
+ this.log(chalk_1.default.dim(' Tip: `flui version --latest` shows what `env create --latest` would use.'));
80
+ }
81
+ this.log('');
82
+ }
83
+ }
84
+ Version.description = 'Show the CLI version and the platform release it pins (component image tags + bootstrap ref). Useful for debugging an install.';
85
+ Version.examples = [
86
+ '<%= config.bin %> <%= command.id %>',
87
+ '<%= config.bin %> <%= command.id %> --latest',
88
+ '<%= config.bin %> <%= command.id %> --json',
89
+ ];
90
+ Version.flags = {
91
+ latest: core_1.Flags.boolean({
92
+ description: 'Show what `env create --latest` would resolve to (mobile tags: bootstrap master, :latest images) instead of the pinned release',
93
+ default: false,
94
+ }),
95
+ json: core_1.Flags.boolean({
96
+ description: 'Output as JSON',
97
+ default: false,
98
+ }),
99
+ };
100
+ exports.default = Version;
@@ -2,8 +2,17 @@
2
2
  * Bootstrap Scripts Configuration
3
3
  *
4
4
  * Configuration for downloading initialization scripts from GitHub.
5
- * Scripts are hosted in the flui-cloud/flui-bootstrap repository.
5
+ * Scripts are hosted in the flui-cloud/bootstrap-scripts repository.
6
6
  */
7
+ /**
8
+ * Base URL for the bootstrap scripts directory.
9
+ *
10
+ * Precedence:
11
+ * 1. `BOOTSTRAP_SCRIPTS_URL` env — full override (dev/CI escape hatch), wins over all.
12
+ * 2. Otherwise derived from the release pin: `<repo>/<ref>/scripts`, where the
13
+ * ref is the pinned release tag, or `master` when `useLatest`.
14
+ */
15
+ export declare function getScriptsBaseUrl(useLatest?: boolean): string;
7
16
  export interface BootstrapConfig {
8
17
  /**
9
18
  * Base URL for downloading scripts
@@ -3,17 +3,37 @@
3
3
  * Bootstrap Scripts Configuration
4
4
  *
5
5
  * Configuration for downloading initialization scripts from GitHub.
6
- * Scripts are hosted in the flui-cloud/flui-bootstrap repository.
6
+ * Scripts are hosted in the flui-cloud/bootstrap-scripts repository.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.BOOTSTRAP_CONFIG = void 0;
10
+ exports.getScriptsBaseUrl = getScriptsBaseUrl;
10
11
  exports.getScriptUrl = getScriptUrl;
12
+ const release_config_1 = require("../../../src/config/release.config");
13
+ const release_override_1 = require("./release-override");
14
+ const BOOTSTRAP_REPO_RAW_BASE = 'https://raw.githubusercontent.com/flui-cloud/bootstrap-scripts';
15
+ /**
16
+ * Base URL for the bootstrap scripts directory.
17
+ *
18
+ * Precedence:
19
+ * 1. `BOOTSTRAP_SCRIPTS_URL` env — full override (dev/CI escape hatch), wins over all.
20
+ * 2. Otherwise derived from the release pin: `<repo>/<ref>/scripts`, where the
21
+ * ref is the pinned release tag, or `master` when `useLatest`.
22
+ */
23
+ function getScriptsBaseUrl(useLatest = false) {
24
+ if (process.env.BOOTSTRAP_SCRIPTS_URL) {
25
+ return process.env.BOOTSTRAP_SCRIPTS_URL;
26
+ }
27
+ return `${BOOTSTRAP_REPO_RAW_BASE}/${(0, release_override_1.resolveEffectiveBootstrapRef)(useLatest)}/scripts`;
28
+ }
11
29
  /**
12
30
  * Default bootstrap configuration
13
31
  */
14
32
  exports.BOOTSTRAP_CONFIG = {
15
- scriptsBaseUrl: process.env.BOOTSTRAP_SCRIPTS_URL ||
16
- 'https://raw.githubusercontent.com/flui-cloud/bootstrap-scripts/master/scripts',
33
+ // Static pinned default; per-install (override-aware) resolution goes through
34
+ // getScriptsBaseUrl() at runtime — keep this off the override path so importing
35
+ // the module never reads flui.release.json.
36
+ scriptsBaseUrl: `${BOOTSTRAP_REPO_RAW_BASE}/${(0, release_config_1.resolveBootstrapRef)(false)}/scripts`,
17
37
  scripts: {
18
38
  fluiInit: 'flui-init.sh',
19
39
  k3sMaster: 'k3s-master-init.sh',
@@ -22,7 +42,7 @@ exports.BOOTSTRAP_CONFIG = {
22
42
  repository: {
23
43
  org: 'flui-cloud',
24
44
  name: 'bootstrap-scripts',
25
- branch: 'master',
45
+ branch: (0, release_config_1.resolveBootstrapRef)(false),
26
46
  },
27
47
  };
28
48
  /**
@@ -16,15 +16,15 @@ exports.isPreferenceKey = isPreferenceKey;
16
16
  exports.PREFERENCES = {
17
17
  email: {
18
18
  key: 'email',
19
- description: 'Contact email used for ACME/Let\'s Encrypt and operational notifications',
19
+ description: "Contact email used for ACME/Let's Encrypt and operational notifications",
20
20
  envVar: 'FLUI_EMAIL',
21
21
  projectOverridable: true,
22
22
  required: true,
23
- validate: (v) => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? null : 'Not a valid email'),
23
+ validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? null : 'Not a valid email',
24
24
  },
25
25
  apiPath: {
26
26
  key: 'apiPath',
27
- description: 'Path to the flui.api repo, used to locate the .env file written by env export-config',
27
+ description: 'Path to the flui-core repo, used to locate the .env file written by env export-config',
28
28
  envVar: 'FLUI_API_PATH',
29
29
  projectOverridable: true,
30
30
  defaultValue: '.',
@@ -32,10 +32,10 @@ exports.PREFERENCES = {
32
32
  },
33
33
  dashboardPath: {
34
34
  key: 'dashboardPath',
35
- description: 'Path to the flui.dashboard repo, used when syncing its config.json',
35
+ description: 'Path to the flui-dashboard repo, used when syncing its config.json',
36
36
  envVar: 'FLUI_DASHBOARD_PATH',
37
37
  projectOverridable: true,
38
- defaultValue: '../flui.dashboard',
38
+ defaultValue: '../flui-dashboard',
39
39
  required: false,
40
40
  },
41
41
  certificateMode: {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-install override of the pinned release manifest.
3
+ *
4
+ * Default install pins every component to the built-in RELEASE (see
5
+ * src/config/release.config.ts). A local `flui.release.json` in the cwd (or the
6
+ * path in FLUI_RELEASE_FILE) overrides any subset of it — used to install a
7
+ * dev/staging build from a branch, tag or commit. Omitted fields stay pinned.
8
+ *
9
+ * Each value is `branch:<x>` / `tag:<x>` / `commit:<x>` (or a bare literal).
10
+ * Scripts and images resolve a ref differently: scripts take any git ref
11
+ * verbatim (raw.githubusercontent serves branch/tag/sha), while an image tag
12
+ * must match what CI publishes — a branch name sanitized to a tag, or a
13
+ * short commit sha.
14
+ */
15
+ import { type ComponentImageTags } from '../../../src/config/release.config';
16
+ declare const IMAGE_KEYS: readonly ["fluiApi", "fluiWeb", "fluiAuthz"];
17
+ type ImageKey = (typeof IMAGE_KEYS)[number];
18
+ export interface ReleaseOverrideFile {
19
+ version?: string;
20
+ bootstrapRef?: string;
21
+ images?: Partial<Record<ImageKey, string>>;
22
+ }
23
+ export interface OverrideEntry {
24
+ label: string;
25
+ spec: string;
26
+ resolved: string;
27
+ }
28
+ export interface EffectiveRelease {
29
+ version: string | null;
30
+ bootstrapRef: string;
31
+ images: ComponentImageTags;
32
+ source: 'pinned' | 'latest' | 'override';
33
+ overrides: OverrideEntry[];
34
+ filePath: string | null;
35
+ }
36
+ export declare function getEffectiveRelease(useLatest: boolean): EffectiveRelease;
37
+ export declare function resolveEffectiveBootstrapRef(useLatest: boolean): string;
38
+ export declare function resolveEffectiveImageTags(useLatest: boolean): ComponentImageTags;
39
+ /** Display path: relative when inside cwd, absolute when it would escape it. */
40
+ export declare function displayReleaseFilePath(filePath: string): string;
41
+ /** Loud banner so a stale override is never installed unnoticed. */
42
+ export declare function formatReleaseOverrideBanner(eff: EffectiveRelease): string | null;
43
+ export {};
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ /**
3
+ * Per-install override of the pinned release manifest.
4
+ *
5
+ * Default install pins every component to the built-in RELEASE (see
6
+ * src/config/release.config.ts). A local `flui.release.json` in the cwd (or the
7
+ * path in FLUI_RELEASE_FILE) overrides any subset of it — used to install a
8
+ * dev/staging build from a branch, tag or commit. Omitted fields stay pinned.
9
+ *
10
+ * Each value is `branch:<x>` / `tag:<x>` / `commit:<x>` (or a bare literal).
11
+ * Scripts and images resolve a ref differently: scripts take any git ref
12
+ * verbatim (raw.githubusercontent serves branch/tag/sha), while an image tag
13
+ * must match what CI publishes — a branch name sanitized to a tag, or a
14
+ * short commit sha.
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.getEffectiveRelease = getEffectiveRelease;
54
+ exports.resolveEffectiveBootstrapRef = resolveEffectiveBootstrapRef;
55
+ exports.resolveEffectiveImageTags = resolveEffectiveImageTags;
56
+ exports.displayReleaseFilePath = displayReleaseFilePath;
57
+ exports.formatReleaseOverrideBanner = formatReleaseOverrideBanner;
58
+ const fs = __importStar(require("node:fs"));
59
+ const path = __importStar(require("node:path"));
60
+ const chalk_1 = __importDefault(require("chalk"));
61
+ const release_config_1 = require("../../../src/config/release.config");
62
+ const OVERRIDE_FILE = 'flui.release.json';
63
+ const IMAGE_KEYS = ['fluiApi', 'fluiWeb', 'fluiAuthz'];
64
+ const COMPONENT_LABELS = {
65
+ fluiApi: 'flui-api (core)',
66
+ fluiWeb: 'flui-web (dashboard)',
67
+ fluiAuthz: 'flui-authz',
68
+ };
69
+ function parseRef(raw) {
70
+ const idx = raw.indexOf(':');
71
+ if (idx === -1)
72
+ return { kind: 'literal', value: raw };
73
+ const prefix = raw.slice(0, idx);
74
+ const value = raw.slice(idx + 1);
75
+ switch (prefix) {
76
+ case 'branch':
77
+ return { kind: 'branch', value };
78
+ case 'tag':
79
+ return { kind: 'tag', value };
80
+ case 'commit':
81
+ case 'sha':
82
+ return { kind: 'commit', value };
83
+ default:
84
+ throw new Error(`Invalid ref "${raw}" in ${OVERRIDE_FILE}: unknown prefix "${prefix}:" — use branch:/tag:/commit:, or a bare tag.`);
85
+ }
86
+ }
87
+ /** Mirror docker/metadata-action `type=ref,event=branch`: invalid tag chars → "-". */
88
+ function branchToImageTag(branch) {
89
+ return branch.replace(/[^a-zA-Z0-9._-]+/g, '-');
90
+ }
91
+ function resolveScriptsRef(spec) {
92
+ return parseRef(spec).value;
93
+ }
94
+ function resolveImageTag(spec) {
95
+ const ref = parseRef(spec);
96
+ switch (ref.kind) {
97
+ case 'branch':
98
+ return branchToImageTag(ref.value);
99
+ case 'commit':
100
+ return ref.value.slice(0, 7); // CI publishes type=sha,format=short
101
+ default:
102
+ return ref.value;
103
+ }
104
+ }
105
+ function loadOverrideFile() {
106
+ const explicit = process.env.FLUI_RELEASE_FILE;
107
+ const filePath = explicit
108
+ ? path.resolve(explicit)
109
+ : path.join(process.cwd(), OVERRIDE_FILE);
110
+ if (!fs.existsSync(filePath))
111
+ return null;
112
+ let data;
113
+ try {
114
+ data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
115
+ }
116
+ catch (err) {
117
+ throw new Error(`Failed to parse ${filePath}: ${err.message}`);
118
+ }
119
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
120
+ throw new Error(`${filePath} must contain a JSON object.`);
121
+ }
122
+ return { data, filePath };
123
+ }
124
+ let cache;
125
+ let cacheKey;
126
+ function applyOverride(base, data) {
127
+ const images = { ...base.images };
128
+ const overrides = [];
129
+ let bootstrapRef = base.bootstrapRef;
130
+ if (data.bootstrapRef !== undefined) {
131
+ bootstrapRef = resolveScriptsRef(data.bootstrapRef);
132
+ overrides.push({
133
+ label: 'bootstrap scripts',
134
+ spec: data.bootstrapRef,
135
+ resolved: bootstrapRef,
136
+ });
137
+ }
138
+ for (const k of IMAGE_KEYS) {
139
+ const spec = data.images?.[k];
140
+ if (spec === undefined)
141
+ continue;
142
+ const resolved = resolveImageTag(spec);
143
+ images[k] = resolved;
144
+ overrides.push({ label: COMPONENT_LABELS[k], spec, resolved });
145
+ }
146
+ return {
147
+ version: data.version ?? base.version,
148
+ bootstrapRef,
149
+ images,
150
+ overrides,
151
+ };
152
+ }
153
+ function getEffectiveRelease(useLatest) {
154
+ const key = `${useLatest}|${process.env.FLUI_RELEASE_FILE ?? ''}|${process.cwd()}`;
155
+ if (cache && cacheKey === key)
156
+ return cache;
157
+ const base = {
158
+ version: useLatest ? null : release_config_1.RELEASE.version,
159
+ bootstrapRef: (0, release_config_1.resolveBootstrapRef)(useLatest),
160
+ images: { ...(0, release_config_1.resolveImageTags)(useLatest) },
161
+ overrides: [],
162
+ };
163
+ const loaded = loadOverrideFile();
164
+ const resolved = loaded ? applyOverride(base, loaded.data) : base;
165
+ const isOverride = resolved.overrides.length > 0;
166
+ const pinnedSource = useLatest ? 'latest' : 'pinned';
167
+ const eff = {
168
+ version: resolved.version,
169
+ bootstrapRef: resolved.bootstrapRef,
170
+ images: resolved.images,
171
+ source: isOverride ? 'override' : pinnedSource,
172
+ overrides: resolved.overrides,
173
+ filePath: isOverride && loaded ? loaded.filePath : null,
174
+ };
175
+ cache = eff;
176
+ cacheKey = key;
177
+ return eff;
178
+ }
179
+ function resolveEffectiveBootstrapRef(useLatest) {
180
+ return getEffectiveRelease(useLatest).bootstrapRef;
181
+ }
182
+ function resolveEffectiveImageTags(useLatest) {
183
+ return getEffectiveRelease(useLatest).images;
184
+ }
185
+ /** Display path: relative when inside cwd, absolute when it would escape it. */
186
+ function displayReleaseFilePath(filePath) {
187
+ const rel = path.relative(process.cwd(), filePath);
188
+ return !rel || rel.startsWith('..') ? filePath : rel;
189
+ }
190
+ /** Loud banner so a stale override is never installed unnoticed. */
191
+ function formatReleaseOverrideBanner(eff) {
192
+ if (eff.source !== 'override' || !eff.filePath)
193
+ return null;
194
+ const rel = displayReleaseFilePath(eff.filePath);
195
+ const lines = eff.overrides.map((o) => {
196
+ const spec = chalk_1.default.dim(`(${o.spec})`);
197
+ return ` • ${o.label}: ${chalk_1.default.cyan(o.resolved)} ${spec}`;
198
+ });
199
+ return (chalk_1.default.yellow.bold('\n⚠ Release override active') +
200
+ chalk_1.default.dim(` — ${rel}\n`) +
201
+ chalk_1.default.dim(' Installing non-default component versions:\n') +
202
+ `${lines.join('\n')}\n`);
203
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Release manifest — single source of truth for the versions Flui pins at
3
+ * install time. Bumped by hand on each CLI release.
4
+ *
5
+ * As long as the installed CLI stays at this version, every component a fresh
6
+ * cluster receives is pinned: the bootstrap scripts/manifests (a git ref on
7
+ * flui-cloud/bootstrap-scripts) and the Docker image tags injected into the
8
+ * cluster. `flui env create --latest` opts back into the mobile dev behaviour
9
+ * (bootstrap ref `master`, `:latest` image tags).
10
+ */
11
+ export interface ComponentImageTags {
12
+ /** ghcr.io/flui-cloud/core */
13
+ fluiApi: string;
14
+ /** ghcr.io/flui-cloud/dashboard */
15
+ fluiWeb: string;
16
+ /** ghcr.io/flui-cloud/flui-authz */
17
+ fluiAuthz: string;
18
+ }
19
+ export interface ReleaseManifest {
20
+ /** Platform release version, recorded on the cluster at install. */
21
+ version: string;
22
+ /** Git ref (tag) on flui-cloud/bootstrap-scripts holding scripts + manifests. */
23
+ bootstrapRef: string;
24
+ /** Pinned Docker image tags, per Flui component. */
25
+ images: ComponentImageTags;
26
+ }
27
+ export declare const RELEASE: ReleaseManifest;
28
+ /** Bootstrap-scripts git ref to install from. `--latest` → `master`. */
29
+ export declare function resolveBootstrapRef(useLatest: boolean): string;
30
+ /** Docker image tags to deploy. `--latest` → all `latest`. */
31
+ export declare function resolveImageTags(useLatest: boolean): ComponentImageTags;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /**
3
+ * Release manifest — single source of truth for the versions Flui pins at
4
+ * install time. Bumped by hand on each CLI release.
5
+ *
6
+ * As long as the installed CLI stays at this version, every component a fresh
7
+ * cluster receives is pinned: the bootstrap scripts/manifests (a git ref on
8
+ * flui-cloud/bootstrap-scripts) and the Docker image tags injected into the
9
+ * cluster. `flui env create --latest` opts back into the mobile dev behaviour
10
+ * (bootstrap ref `master`, `:latest` image tags).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.RELEASE = void 0;
14
+ exports.resolveBootstrapRef = resolveBootstrapRef;
15
+ exports.resolveImageTags = resolveImageTags;
16
+ exports.RELEASE = {
17
+ version: '0.5.0',
18
+ bootstrapRef: 'v0.5.0',
19
+ images: {
20
+ fluiApi: '0.5.0',
21
+ fluiWeb: '0.5.0',
22
+ fluiAuthz: '0.5.0',
23
+ },
24
+ };
25
+ const LATEST_BOOTSTRAP_REF = 'master';
26
+ const LATEST_IMAGE_TAGS = {
27
+ fluiApi: 'latest',
28
+ fluiWeb: 'latest',
29
+ fluiAuthz: 'latest',
30
+ };
31
+ /** Bootstrap-scripts git ref to install from. `--latest` → `master`. */
32
+ function resolveBootstrapRef(useLatest) {
33
+ return useLatest ? LATEST_BOOTSTRAP_REF : exports.RELEASE.bootstrapRef;
34
+ }
35
+ /** Docker image tags to deploy. `--latest` → all `latest`. */
36
+ function resolveImageTags(useLatest) {
37
+ return useLatest ? { ...LATEST_IMAGE_TAGS } : { ...exports.RELEASE.images };
38
+ }
@@ -15,12 +15,7 @@ export declare function selectWithArrows(title: string, items: ArrowSelectItem[]
15
15
  * Supports both typing and paste (Cmd+V), including bracketed paste mode.
16
16
  */
17
17
  export declare function promptMaskedInput(message: string): Promise<string>;
18
- /**
19
- * Interactive provider setup wizard.
20
- * Shows arrow-key provider selection, prompts for token, saves to ConfigStorage.
21
- * Returns true if setup completed successfully, false if cancelled.
22
- */
23
- export declare function runProviderSetupWizard(): Promise<boolean>;
18
+ export declare function runProviderSetupWizard(preselectedProviderId?: string): Promise<string | null>;
24
19
  /**
25
20
  * Prompt user for yes/no confirmation
26
21
  */
@@ -226,7 +226,16 @@ async function promptMaskedInput(message) {
226
226
  * Shows arrow-key provider selection, prompts for token, saves to ConfigStorage.
227
227
  * Returns true if setup completed successfully, false if cancelled.
228
228
  */
229
- async function runProviderSetupWizard() {
229
+ async function pickProviderToConfigure(preselectedProviderId) {
230
+ // When the target provider is already known (explicit --provider, or a
231
+ // single-provider profile with missing creds), configure it directly.
232
+ const preselected = preselectedProviderId
233
+ ? SUPPORTED_PROVIDERS.find((p) => p.id === preselectedProviderId && p.available)
234
+ : undefined;
235
+ if (preselected) {
236
+ console.log(chalk_1.default.yellow(`\n⚠ No ${preselected.label} API token configured.`));
237
+ return preselected;
238
+ }
230
239
  console.log(chalk_1.default.yellow('\n⚠ No cloud provider API token configured.'));
231
240
  const items = SUPPORTED_PROVIDERS.map((p) => ({
232
241
  label: p.label,
@@ -236,15 +245,11 @@ async function runProviderSetupWizard() {
236
245
  const index = await selectWithArrows('Select a provider to configure:', items);
237
246
  if (index === -1) {
238
247
  console.log(chalk_1.default.dim('\n Cancelled. Run: flui config set hetzner YOUR_TOKEN\n'));
239
- return false;
240
- }
241
- const selectedProvider = SUPPORTED_PROVIDERS[index];
242
- const schema = (0, provider_credential_schemas_1.getCredentialSchema)(selectedProvider.id);
243
- if (!schema) {
244
- console.log(chalk_1.default.red(`\n Missing credential schema for ${selectedProvider.label}\n`));
245
- return false;
248
+ return null;
246
249
  }
247
- console.log(chalk_1.default.dim(`\n Enter your ${selectedProvider.label} credentials below.\n They will be stored encrypted on disk.\n`));
250
+ return SUPPORTED_PROVIDERS[index];
251
+ }
252
+ async function collectProviderCredentials(schema) {
248
253
  const collected = {};
249
254
  for (const field of schema.fields) {
250
255
  const label = ` ${field.label}`;
@@ -255,17 +260,32 @@ async function runProviderSetupWizard() {
255
260
  })).trim();
256
261
  if (!value.trim()) {
257
262
  console.log(chalk_1.default.red(`\n ${field.label} is required. Setup cancelled.\n`));
258
- return false;
263
+ return null;
259
264
  }
260
265
  collected[field.key] = value.trim();
261
266
  }
267
+ return collected;
268
+ }
269
+ async function runProviderSetupWizard(preselectedProviderId) {
270
+ const selectedProvider = await pickProviderToConfigure(preselectedProviderId);
271
+ if (!selectedProvider)
272
+ return null;
273
+ const schema = (0, provider_credential_schemas_1.getCredentialSchema)(selectedProvider.id);
274
+ if (!schema) {
275
+ console.log(chalk_1.default.red(`\n Missing credential schema for ${selectedProvider.label}\n`));
276
+ return null;
277
+ }
278
+ console.log(chalk_1.default.dim(`\n Enter your ${selectedProvider.label} credentials below.\n They will be stored encrypted on disk.\n`));
279
+ const collected = await collectProviderCredentials(schema);
280
+ if (!collected)
281
+ return null;
262
282
  if (selectedProvider.id === 'scaleway') {
263
283
  process.stdout.write(chalk_1.default.dim('\n Validating credentials with Scaleway IAM...'));
264
284
  const result = await (0, scaleway_validator_1.validateScalewayCredentials)(collected.accessKey, collected.secretKey);
265
285
  process.stdout.write('\r\u001B[2K');
266
286
  if (!result.success) {
267
287
  console.log(chalk_1.default.red(` ✖ ${result.message}\n`));
268
- return false;
288
+ return null;
269
289
  }
270
290
  console.log(chalk_1.default.green(` ✔ ${result.message}`));
271
291
  }
@@ -278,11 +298,11 @@ async function runProviderSetupWizard() {
278
298
  storage.saveToken(selectedProvider.id, collected[schema.fields[0].key]);
279
299
  }
280
300
  console.log(chalk_1.default.green(`\n ✔ ${selectedProvider.label} credentials saved (AES-256-GCM encrypted)\n`));
281
- return true;
301
+ return selectedProvider.id;
282
302
  }
283
303
  catch (err) {
284
304
  console.log(chalk_1.default.red(`\n Failed to save credentials: ${err instanceof Error ? err.message : String(err)}\n`));
285
- return false;
305
+ return null;
286
306
  }
287
307
  }
288
308
  /**