@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.
@@ -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 ImageConfigStore {
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?: ImageConfigStore;
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 { RepoConfigStore } from "../core/runtime/RepoConfigStore.js";
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 RepoConfigStore();
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 project config.")
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) => {
@@ -0,0 +1,4 @@
1
+ export declare const PACKAGED_IMAGE_CONFIG: {
2
+ readonly api: "public.ecr.aws/x6n0f2k4/companyhelm-api:0.6.1";
3
+ readonly frontend: "public.ecr.aws/x6n0f2k4/companyhelm-web:0.1.1";
4
+ };
@@ -0,0 +1,4 @@
1
+ export const PACKAGED_IMAGE_CONFIG = {
2
+ api: "public.ecr.aws/x6n0f2k4/companyhelm-api:0.6.1",
3
+ frontend: "public.ecr.aws/x6n0f2k4/companyhelm-web:0.1.1"
4
+ };
@@ -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 { RepoConfigStore } from "./RepoConfigStore.js";
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 = new RepoConfigStore().load().images;
9
+ const configuredImages = this.configStore.load().images;
6
10
  return {
7
- api: configuredImages.api || process.env.COMPANYHELM_API_IMAGE || defaultManagedImageReference("api"),
8
- frontend: configuredImages.frontend || process.env.COMPANYHELM_WEB_IMAGE || defaultManagedImageReference("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 { ManagedImageService } from "./ManagedImages.js";
2
- export interface RepoConfig {
1
+ import { type ManagedImageService } from "./ManagedImages.js";
2
+ export interface ImageConfig {
3
3
  images: Partial<Record<ManagedImageService, string>>;
4
4
  }
5
- export declare class RepoConfigStore {
5
+ export declare class ImageConfigStore {
6
6
  private readonly root;
7
7
  constructor(root?: string);
8
8
  configPath(): string;
9
- load(): RepoConfig;
9
+ load(): ImageConfig;
10
10
  setImage(service: ManagedImageService, image: string): {
11
11
  configPath: string;
12
12
  image: string;
13
13
  };
14
- save(config: RepoConfig): void;
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.0",
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"
@@ -0,0 +1,4 @@
1
+ export const PACKAGED_IMAGE_CONFIG = {
2
+ api: "public.ecr.aws/x6n0f2k4/companyhelm-api:0.6.1",
3
+ frontend: "public.ecr.aws/x6n0f2k4/companyhelm-web:0.1.1"
4
+ } as const;
@@ -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
- }