@companyhelm/cli 0.4.0 → 0.4.1
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/commands/set-image-version.d.ts +2 -2
- package/dist/commands/set-image-version.js +3 -3
- package/dist/config/image_config.d.ts +4 -0
- package/dist/config/image_config.js +4 -0
- package/dist/core/runtime/ImageCatalog.d.ts +3 -0
- package/dist/core/runtime/ImageCatalog.js +8 -4
- package/dist/core/runtime/{RepoConfigStore.d.ts → ImageConfigStore.d.ts} +5 -6
- package/dist/core/runtime/ImageConfigStore.js +51 -0
- package/package.json +4 -3
- package/src/config/image_config.ts +4 -0
- package/dist/core/runtime/RepoConfigStore.js +0 -63
|
@@ -13,7 +13,7 @@ export interface InteractiveImageSelector {
|
|
|
13
13
|
listAvailableTags(service: ManagedImageService, limit: number): Promise<AvailableImageTag[]>;
|
|
14
14
|
buildImageReference(service: ManagedImageService, tag: string): string;
|
|
15
15
|
}
|
|
16
|
-
export interface
|
|
16
|
+
export interface ImageVersionStore {
|
|
17
17
|
load(): {
|
|
18
18
|
images: Partial<Record<ManagedImageService, string>>;
|
|
19
19
|
};
|
|
@@ -26,6 +26,6 @@ export declare function runSetImageVersion(options: SetImageVersionOptions, depe
|
|
|
26
26
|
input?: Readable;
|
|
27
27
|
output?: Writable;
|
|
28
28
|
registry?: InteractiveImageSelector;
|
|
29
|
-
configStore?:
|
|
29
|
+
configStore?: ImageVersionStore;
|
|
30
30
|
}): Promise<void>;
|
|
31
31
|
export declare function registerSetImageVersionCommand(program: Command): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as clack from "@clack/prompts";
|
|
2
2
|
import { MANAGED_IMAGE_SERVICES, requireManagedImageService } from "../core/runtime/ManagedImages.js";
|
|
3
3
|
import { PublicImageTagRegistry } from "../core/runtime/PublicImageTagRegistry.js";
|
|
4
|
-
import {
|
|
4
|
+
import { ImageConfigStore } from "../core/runtime/ImageConfigStore.js";
|
|
5
5
|
import { requireInteractiveTerminal, unwrapPromptResult } from "./interactive.js";
|
|
6
6
|
function parsePositiveInteger(value) {
|
|
7
7
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -59,7 +59,7 @@ export async function runSetImageVersion(options, dependencies = {}) {
|
|
|
59
59
|
const input = dependencies.input ?? process.stdin;
|
|
60
60
|
const output = dependencies.output ?? process.stdout;
|
|
61
61
|
const registry = dependencies.registry ?? new PublicImageTagRegistry();
|
|
62
|
-
const configStore = dependencies.configStore ?? new
|
|
62
|
+
const configStore = dependencies.configStore ?? new ImageConfigStore();
|
|
63
63
|
clack.intro("CompanyHelm image selection", { output });
|
|
64
64
|
const selectedService = options.service
|
|
65
65
|
? requireManagedImageService(options.service)
|
|
@@ -78,7 +78,7 @@ export async function runSetImageVersion(options, dependencies = {}) {
|
|
|
78
78
|
export function registerSetImageVersionCommand(program) {
|
|
79
79
|
program
|
|
80
80
|
.command("set-image-version")
|
|
81
|
-
.description("Interactively choose an API or frontend image tag and store it in the
|
|
81
|
+
.description("Interactively choose an API or frontend image tag and store it in the packaged image config.")
|
|
82
82
|
.option("-s, --service <service>", "Prefill the service to update (api or frontend)")
|
|
83
83
|
.option("-l, --limit <count>", "How many image tags to show", parsePositiveInteger, 20)
|
|
84
84
|
.action(async (options) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { ImageConfigStore } from "./ImageConfigStore.js";
|
|
1
2
|
export interface RuntimeImages {
|
|
2
3
|
api: string;
|
|
3
4
|
frontend: string;
|
|
4
5
|
postgres: string;
|
|
5
6
|
}
|
|
6
7
|
export declare class ImageCatalog {
|
|
8
|
+
private readonly configStore;
|
|
9
|
+
constructor(configStore?: ImageConfigStore);
|
|
7
10
|
resolve(): RuntimeImages;
|
|
8
11
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { defaultManagedImageReference } from "./ManagedImages.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ImageConfigStore } from "./ImageConfigStore.js";
|
|
3
3
|
export class ImageCatalog {
|
|
4
|
+
configStore;
|
|
5
|
+
constructor(configStore = new ImageConfigStore()) {
|
|
6
|
+
this.configStore = configStore;
|
|
7
|
+
}
|
|
4
8
|
resolve() {
|
|
5
|
-
const configuredImages =
|
|
9
|
+
const configuredImages = this.configStore.load().images;
|
|
6
10
|
return {
|
|
7
|
-
api:
|
|
8
|
-
frontend:
|
|
11
|
+
api: process.env.COMPANYHELM_API_IMAGE || configuredImages.api || defaultManagedImageReference("api"),
|
|
12
|
+
frontend: process.env.COMPANYHELM_WEB_IMAGE || configuredImages.frontend || defaultManagedImageReference("frontend"),
|
|
9
13
|
postgres: process.env.COMPANYHELM_POSTGRES_IMAGE || "postgres:16-alpine"
|
|
10
14
|
};
|
|
11
15
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export interface
|
|
1
|
+
import { type ManagedImageService } from "./ManagedImages.js";
|
|
2
|
+
export interface ImageConfig {
|
|
3
3
|
images: Partial<Record<ManagedImageService, string>>;
|
|
4
4
|
}
|
|
5
|
-
export declare class
|
|
5
|
+
export declare class ImageConfigStore {
|
|
6
6
|
private readonly root;
|
|
7
7
|
constructor(root?: string);
|
|
8
8
|
configPath(): string;
|
|
9
|
-
load():
|
|
9
|
+
load(): ImageConfig;
|
|
10
10
|
setImage(service: ManagedImageService, image: string): {
|
|
11
11
|
configPath: string;
|
|
12
12
|
image: string;
|
|
13
13
|
};
|
|
14
|
-
save(config:
|
|
15
|
-
private parse;
|
|
14
|
+
save(config: ImageConfig): void;
|
|
16
15
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { MANAGED_IMAGE_SERVICES, defaultManagedImageReference } from "./ManagedImages.js";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
function defaultPackageRoot() {
|
|
8
|
+
return path.resolve(__dirname, "../../..");
|
|
9
|
+
}
|
|
10
|
+
export class ImageConfigStore {
|
|
11
|
+
root;
|
|
12
|
+
constructor(root = defaultPackageRoot()) {
|
|
13
|
+
this.root = root;
|
|
14
|
+
}
|
|
15
|
+
configPath() {
|
|
16
|
+
return path.join(this.root, "src", "config", "image_config.ts");
|
|
17
|
+
}
|
|
18
|
+
load() {
|
|
19
|
+
const configPath = this.configPath();
|
|
20
|
+
if (!fs.existsSync(configPath)) {
|
|
21
|
+
return { images: {} };
|
|
22
|
+
}
|
|
23
|
+
const images = {};
|
|
24
|
+
for (const rawLine of fs.readFileSync(configPath, "utf8").split(/\r?\n/)) {
|
|
25
|
+
const match = rawLine.match(/^\s+(api|frontend):\s+"([^"]+)",?$/);
|
|
26
|
+
if (match) {
|
|
27
|
+
images[match[1]] = match[2];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { images };
|
|
31
|
+
}
|
|
32
|
+
setImage(service, image) {
|
|
33
|
+
const nextConfig = this.load();
|
|
34
|
+
nextConfig.images[service] = image;
|
|
35
|
+
this.save(nextConfig);
|
|
36
|
+
return { configPath: this.configPath(), image };
|
|
37
|
+
}
|
|
38
|
+
save(config) {
|
|
39
|
+
const lines = [
|
|
40
|
+
"export const PACKAGED_IMAGE_CONFIG = {",
|
|
41
|
+
...MANAGED_IMAGE_SERVICES.map((service) => {
|
|
42
|
+
const image = config.images[service] ?? defaultManagedImageReference(service);
|
|
43
|
+
return ` ${service}: "${image}"`;
|
|
44
|
+
}).map((line, index, all) => `${line}${index < all.length - 1 ? "," : ""}`),
|
|
45
|
+
"} as const;",
|
|
46
|
+
""
|
|
47
|
+
];
|
|
48
|
+
fs.mkdirSync(path.dirname(this.configPath()), { recursive: true });
|
|
49
|
+
fs.writeFileSync(this.configPath(), lines.join("\n"), "utf8");
|
|
50
|
+
}
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@companyhelm/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Bootstrap and manage a local CompanyHelm deployment.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
-
"src/templates"
|
|
20
|
+
"src/templates",
|
|
21
|
+
"src/config/image_config.ts"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
|
-
"build": "tsc -p tsconfig.json && node scripts/copy-templates.cjs",
|
|
24
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && node scripts/copy-templates.cjs",
|
|
24
25
|
"set-image-version": "npm run build && node dist/cli.js set-image-version",
|
|
25
26
|
"start": "npm run build && node dist/cli.js",
|
|
26
27
|
"test": "npm run build && vitest run"
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
function defaultRepoConfigRoot() {
|
|
4
|
-
return process.cwd();
|
|
5
|
-
}
|
|
6
|
-
export class RepoConfigStore {
|
|
7
|
-
root;
|
|
8
|
-
constructor(root = defaultRepoConfigRoot()) {
|
|
9
|
-
this.root = root;
|
|
10
|
-
}
|
|
11
|
-
configPath() {
|
|
12
|
-
return path.join(this.root, "config.yaml");
|
|
13
|
-
}
|
|
14
|
-
load() {
|
|
15
|
-
const configPath = this.configPath();
|
|
16
|
-
if (!fs.existsSync(configPath)) {
|
|
17
|
-
return { images: {} };
|
|
18
|
-
}
|
|
19
|
-
return this.parse(fs.readFileSync(configPath, "utf8"));
|
|
20
|
-
}
|
|
21
|
-
setImage(service, image) {
|
|
22
|
-
const nextConfig = this.load();
|
|
23
|
-
nextConfig.images[service] = image;
|
|
24
|
-
this.save(nextConfig);
|
|
25
|
-
return { configPath: this.configPath(), image };
|
|
26
|
-
}
|
|
27
|
-
save(config) {
|
|
28
|
-
const lines = ["images:"];
|
|
29
|
-
if (config.images.api) {
|
|
30
|
-
lines.push(` api: ${config.images.api}`);
|
|
31
|
-
}
|
|
32
|
-
if (config.images.frontend) {
|
|
33
|
-
lines.push(` frontend: ${config.images.frontend}`);
|
|
34
|
-
}
|
|
35
|
-
fs.mkdirSync(path.dirname(this.configPath()), { recursive: true });
|
|
36
|
-
fs.writeFileSync(this.configPath(), `${lines.join("\n")}\n`, "utf8");
|
|
37
|
-
}
|
|
38
|
-
parse(content) {
|
|
39
|
-
const images = {};
|
|
40
|
-
let inImagesSection = false;
|
|
41
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
42
|
-
const line = rawLine.trimEnd();
|
|
43
|
-
if (line.trim().length === 0 || line.trimStart().startsWith("#")) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (line === "images:") {
|
|
47
|
-
inImagesSection = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (inImagesSection && /^[^\s]/.test(line)) {
|
|
51
|
-
inImagesSection = false;
|
|
52
|
-
}
|
|
53
|
-
if (!inImagesSection) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const match = line.match(/^ (api|frontend):\s*(.+)$/);
|
|
57
|
-
if (match) {
|
|
58
|
-
images[match[1]] = match[2];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { images };
|
|
62
|
-
}
|
|
63
|
-
}
|