@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.
- package/lib/cli/src/commands/app/list.d.ts +3 -0
- package/lib/cli/src/commands/app/list.js +72 -18
- package/lib/cli/src/commands/app/status.d.ts +1 -0
- package/lib/cli/src/commands/app/status.js +27 -2
- package/lib/cli/src/commands/cluster/destroy.d.ts +1 -1
- package/lib/cli/src/commands/cluster/destroy.js +2 -2
- package/lib/cli/src/commands/deploy.d.ts +3 -0
- package/lib/cli/src/commands/deploy.js +19 -0
- package/lib/cli/src/commands/dev/creds.d.ts +0 -1
- package/lib/cli/src/commands/dev/creds.js +6 -27
- package/lib/cli/src/commands/dev/tunnel.js +8 -8
- package/lib/cli/src/commands/env/capacity.js +4 -4
- package/lib/cli/src/commands/env/create.d.ts +4 -1
- package/lib/cli/src/commands/env/create.js +78 -52
- package/lib/cli/src/commands/env/credentials.js +12 -12
- package/lib/cli/src/commands/env/destroy.d.ts +2 -1
- package/lib/cli/src/commands/env/destroy.js +45 -28
- package/lib/cli/src/commands/env/diag-ca.js +5 -5
- package/lib/cli/src/commands/env/export-config.d.ts +0 -17
- package/lib/cli/src/commands/env/export-config.js +50 -47
- package/lib/cli/src/commands/env/force-ready.d.ts +1 -1
- package/lib/cli/src/commands/env/force-ready.js +8 -8
- package/lib/cli/src/commands/env/inspect.js +5 -5
- package/lib/cli/src/commands/env/refresh-kubeconfig.js +4 -4
- package/lib/cli/src/commands/env/repair-ssh-ca.js +4 -4
- package/lib/cli/src/commands/env/repair-storage.d.ts +9 -0
- package/lib/cli/src/commands/env/repair-storage.js +82 -0
- package/lib/cli/src/commands/env/restart.d.ts +1 -1
- package/lib/cli/src/commands/env/restart.js +9 -9
- package/lib/cli/src/commands/env/scale-master.js +4 -4
- package/lib/cli/src/commands/env/scale-node.js +4 -4
- package/lib/cli/src/commands/env/set-master-protection.d.ts +16 -0
- package/lib/cli/src/commands/env/set-master-protection.js +120 -0
- package/lib/cli/src/commands/env/status.d.ts +1 -1
- package/lib/cli/src/commands/env/status.js +10 -10
- package/lib/cli/src/commands/env/stop.d.ts +1 -1
- package/lib/cli/src/commands/env/stop.js +8 -8
- package/lib/cli/src/commands/env/storage-expand.js +4 -4
- package/lib/cli/src/commands/env/storage.d.ts +1 -1
- package/lib/cli/src/commands/env/storage.js +5 -5
- package/lib/cli/src/commands/env/sync.js +5 -5
- package/lib/cli/src/commands/env/uncordon.js +4 -4
- package/lib/cli/src/commands/env/update-firewall.d.ts +13 -1
- package/lib/cli/src/commands/env/update-firewall.js +232 -126
- package/lib/cli/src/commands/integration/connect.d.ts +1 -0
- package/lib/cli/src/commands/integration/connect.js +19 -1
- package/lib/cli/src/commands/integration/reset.d.ts +13 -0
- package/lib/cli/src/commands/integration/reset.js +95 -0
- package/lib/cli/src/commands/integration/setup.d.ts +18 -0
- package/lib/cli/src/commands/integration/setup.js +320 -0
- package/lib/cli/src/commands/integration/status.d.ts +9 -0
- package/lib/cli/src/commands/integration/status.js +117 -0
- package/lib/cli/src/commands/node/list.d.ts +1 -0
- package/lib/cli/src/commands/node/list.js +19 -2
- package/lib/cli/src/commands/server-types/list.d.ts +3 -0
- package/lib/cli/src/commands/server-types/list.js +84 -0
- package/lib/cli/src/commands/ssh.js +5 -5
- package/lib/cli/src/commands/version.d.ts +18 -0
- package/lib/cli/src/commands/version.js +100 -0
- package/lib/cli/src/config/bootstrap.config.d.ts +10 -1
- package/lib/cli/src/config/bootstrap.config.js +24 -4
- package/lib/cli/src/config/preferences-schema.js +5 -5
- package/lib/cli/src/config/release-override.d.ts +43 -0
- package/lib/cli/src/config/release-override.js +203 -0
- package/lib/cli/src/config/release.config.d.ts +31 -0
- package/lib/cli/src/config/release.config.js +38 -0
- package/lib/cli/src/lib/prompts.d.ts +1 -6
- package/lib/cli/src/lib/prompts.js +33 -13
- package/lib/cli/src/lib/services/cli-app.service.d.ts +33 -0
- package/lib/cli/src/lib/services/cli-app.service.js +9 -0
- package/lib/cli/src/lib/services/reconciliation.service.js +1 -1
- package/lib/cli/src/lib/templates/firewall-rules.d.ts +2 -2
- package/lib/cli/src/lib/templates/firewall-rules.js +3 -3
- package/lib/cli/src/modules/cli-infrastructure.module.js +3 -3
- package/lib/cli/src/services/cli-cluster-creator.service.js +31 -6
- package/lib/cli/src/services/cli-clusters.service.d.ts +3 -3
- package/lib/cli/src/services/cli-clusters.service.js +57 -34
- package/lib/cli/src/services/cli-control-cluster.service.d.ts +129 -0
- package/lib/cli/src/services/cli-control-cluster.service.js +545 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.d.ts +1 -0
- package/lib/cli/src/services/cli-endpoint-resolver.service.js +25 -11
- package/lib/cli/src/services/cli-k3s-script.service.d.ts +8 -1
- package/lib/cli/src/services/cli-k3s-script.service.js +14 -6
- package/lib/src/config/release.config.d.ts +28 -0
- package/lib/src/config/release.config.js +35 -0
- package/lib/src/modules/applications/entities/application.entity.d.ts +13 -20
- package/lib/src/modules/applications/entities/application.entity.js +12 -0
- package/lib/src/modules/applications/enums/application-exposure.enum.d.ts +2 -1
- package/lib/src/modules/applications/enums/application-exposure.enum.js +1 -0
- package/lib/src/modules/applications/interfaces/source-config.interface.d.ts +1 -0
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.d.ts +8 -2
- package/lib/src/modules/infrastructure/clusters/entities/cluster.entity.js +16 -1
- package/lib/src/modules/infrastructure/clusters/services/cluster-node-scaling.service.js +2 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.d.ts +3 -2
- package/lib/src/modules/infrastructure/firewalls/templates/firewall-rules.template.js +11 -4
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.d.ts +26 -0
- package/lib/src/modules/infrastructure/shared/services/kubernetes.service.js +105 -8
- package/lib/src/modules/management/entities/provider-capabilities.entity.d.ts +2 -0
- package/lib/src/modules/providers/implementations/contabo/contabo-capabilities.service.js +2 -0
- package/lib/src/modules/providers/implementations/hetzner/hetzner-capabilities.service.js +3 -6
- package/lib/src/modules/providers/implementations/scaleway/scaleway-capabilities.service.js +2 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-firewall.service.js +3 -1
- package/lib/src/modules/providers/implementations/scaleway/scaleway-provider.service.js +3 -1
- package/lib/src/modules/providers/interfaces/provider-capabilities.interface.d.ts +0 -2
- package/lib/src/modules/providers/services/hetzner-firewall.service.d.ts +1 -1
- package/lib/src/modules/providers/services/hetzner-firewall.service.js +2 -1
- package/oclif.manifest.json +1201 -854
- 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/
|
|
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/
|
|
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
|
-
|
|
16
|
-
|
|
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:
|
|
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:
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
305
|
+
return null;
|
|
286
306
|
}
|
|
287
307
|
}
|
|
288
308
|
/**
|