[READ-ONLY] Mirror of https://github.com/FoxxMD/komodo-import. Import existing compose stacks into Komodo foxxmd.github.io/komodo-import
compose docker import komodo toml
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: Consolidate komodo stack environment parsing and tests

FoxxMD (Aug 14, 2025, 3:20 PM UTC) a2f70c31 c5b8f5c5

+197 -88
+9 -16
src/builders/stack/filesOnServer.ts
··· 5 5 import { TomlStack } from "../../common/infrastructure/tomlObjects.js"; 6 6 import { readText } from "../../common/utils/io.js"; 7 7 import { isDebugMode, removeUndefinedKeys } from "../../common/utils/utils.js"; 8 - import { DEFAULT_COMPOSE_GLOB, DEFAULT_ENV_GLOB, selectComposeFiles, selectEnvFiles } from "./stackUtils.js"; 8 + import { DEFAULT_COMPOSE_GLOB, DEFAULT_ENV_GLOB, parseEnvConfig, selectComposeFiles, selectEnvFiles } from "./stackUtils.js"; 9 9 10 10 export type BuildFileStackOptions = FilesOnServerConfig & { logger: Logger }; 11 11 ··· 50 50 51 51 stack.config.file_paths = await selectComposeFiles(composeFileGlob, path, logger); 52 52 53 - const envFiles = await selectEnvFiles(envFileGlob, path, logger); 54 - if (envFiles !== undefined) { 55 - if (writeEnv) { 56 - logger.verbose('Writing env file(s) contents to Komodo Environmnent'); 57 - const envContents: string[] = []; 58 - for (const f of envFiles) { 59 - envContents.push(await readText(envFiles)) 60 - } 61 - stack.config.environment = envContents.join('\n'); 62 - } 63 - else { 64 - stack.config.env_file_path = komodoEnvName 65 - logger.info(`Using ${komodoEnvName} for Komodo-written env file`); 66 - stack.config.additional_env_files = envFiles; 67 - } 53 + stack.config = { 54 + ...stack.config, 55 + ...await(parseEnvConfig(path, { 56 + envFileGlob, 57 + writeEnv, 58 + komodoEnvName, 59 + logger 60 + })) 68 61 } 69 62 70 63 logger.info('Stack config complete');
+10 -15
src/builders/stack/gitStack.ts
··· 6 6 import { dirHasGitConfig, findFilesRecurive, readDirectories, readText, sortComposePaths } from "../../common/utils/io.js"; 7 7 import { stripIndents } from "common-tags"; 8 8 import { isDebugMode, removeUndefinedKeys } from "../../common/utils/utils.js"; 9 - import { DEFAULT_COMPOSE_GLOB, DEFAULT_ENV_GLOB, selectComposeFiles, selectEnvFiles } from "./stackUtils.js"; 9 + import { DEFAULT_COMPOSE_GLOB, DEFAULT_ENV_GLOB, parseEnvConfig, selectComposeFiles, selectEnvFiles } from "./stackUtils.js"; 10 10 import { detectGitRepo, GitRepoData, komodoRepoFromRemote, matchGitDataWithKomodo, matchRemote, RemoteInfo } from "../../common/utils/git.js"; 11 11 import { SimpleError } from "../../common/errors.js"; 12 12 import { readFile } from "fs/promises"; ··· 108 108 stack.config.file_paths = composePaths.map(x => inMonorepo ? join(monoRepoPath, x) : x); 109 109 } 110 110 111 - const envFiles = await selectEnvFiles(envFileGlob, path, logger); 112 - if (envFiles !== undefined) { 113 - if (writeEnv) { 114 - logger.verbose('Writing env file(s) contents to Komodo Environmnent'); 115 - const envContents: string[] = []; 116 - for (const f of envFiles) { 117 - envContents.push(await readText(envFiles)) 118 - } 119 - stack.config.environment = envContents.join('\n'); 120 - } else { 121 - stack.config.env_file_path = komodoEnvName 122 - logger.info(`Using ${komodoEnvName} for Komodo-written env file`); 123 - stack.config.additional_env_files = envFiles.map(x => inMonorepo ? join(monoRepoPath, x) : x); 124 - } 111 + stack.config = { 112 + ...stack.config, 113 + ...await(parseEnvConfig(path, { 114 + envFileGlob, 115 + writeEnv, 116 + pathPrefix: monoRepoPath, 117 + komodoEnvName, 118 + logger 119 + })) 125 120 } 126 121 127 122 logger.info('Git Stack config complete');
+46 -4
src/builders/stack/stackUtils.ts
··· 1 - import { Logger } from "@foxxmd/logging"; 2 - import { findFilesRecurive, sortComposePaths } from "../../common/utils/io.js"; 1 + import { Logger, loggerTest } from "@foxxmd/logging"; 2 + import { findFilesRecurive, readText, sortComposePaths } from "../../common/utils/io.js"; 3 3 import { stripIndents } from "common-tags"; 4 + import { _PartialStackConfig } from "komodo_client/dist/types.js"; 5 + import { CommonImportOptions } from "../../common/infrastructure/config/common.js"; 6 + import { CommonStackConfig } from "../../common/infrastructure/config/stackConfig.js"; 7 + import { join } from 'path'; 4 8 5 9 export const DEFAULT_COMPOSE_GLOB = '**/{compose,docker-compose}*.y?(a)ml'; 6 10 7 - export const DEFAULT_ENV_GLOB = '**/.env'; 11 + export const DEFAULT_ENV_GLOB = '**/*.env'; 12 + 13 + export const DEFAULT_KOMODO_ENV_NAME = '.komodoEnv'; 8 14 9 15 export const selectComposeFiles = async (glob: string, path: string, logger: Logger): Promise<string[] | undefined> => { 10 16 ··· 39 45 40 46 export const selectEnvFiles = async (glob: string, path: string, logger: Logger): Promise<string[] | undefined> => { 41 47 42 - const envFiles = await findFilesRecurive(glob, path); 48 + const envFiles = await findFilesRecurive(glob, path, { dot: true }); 43 49 if (envFiles.length > 0) { 44 50 logger.info(stripIndents`Found ${envFiles.length} matching env files: 45 51 ${envFiles.join('\n')}`); 46 52 return envFiles; 47 53 } 48 54 return undefined; 55 + } 56 + 57 + export type StackEnvConfig = Pick<_PartialStackConfig, 'additional_env_files' | 'env_file_path' | 'environment'>; 58 + export type ParseEnvOptions = Pick<CommonStackConfig, 'komodoEnvName' | 'writeEnv' | 'envFileGlob'> & { logger?: Logger, pathPrefix?: string }; 59 + 60 + export const parseEnvConfig = async (path: string, options: ParseEnvOptions = {}): Promise<StackEnvConfig> => { 61 + 62 + const { 63 + logger = loggerTest, 64 + komodoEnvName = DEFAULT_KOMODO_ENV_NAME, 65 + writeEnv = false, 66 + envFileGlob = DEFAULT_ENV_GLOB, 67 + pathPrefix: pathPrefix 68 + } = options; 69 + 70 + const config: StackEnvConfig = {}; 71 + 72 + const envFiles = await selectEnvFiles(envFileGlob, path, logger); 73 + if (envFiles !== undefined) { 74 + if (writeEnv) { 75 + logger.verbose('Writing env file(s) contents to Komodo Environmnent'); 76 + const envContents: string[] = []; 77 + for (const f of envFiles) { 78 + envContents.push(await readText(join(path, f))) 79 + } 80 + config.environment = envContents.join('\n'); 81 + } 82 + else { 83 + config.env_file_path = komodoEnvName 84 + logger.info(`Using ${komodoEnvName} for Komodo-written env file`); 85 + config.additional_env_files = pathPrefix === undefined ? envFiles : envFiles.map(x => join(pathPrefix, x)); 86 + } 87 + } 88 + 89 + return config; 90 + 49 91 }
+7 -6
src/common/infrastructure/config/stackConfig.ts
··· 1 1 import { CommonImportOptions } from "./common.js"; 2 2 3 - export interface FilesOnServerConfig extends CommonImportOptions { 4 - hostParentPath: string 5 - writeEnv?: boolean 3 + export interface CommonStackConfig extends CommonImportOptions { 4 + writeEnv?: boolean 5 + hostParentPath?: string, 6 + } 7 + 8 + export interface FilesOnServerConfig extends CommonStackConfig { 6 9 } 7 10 8 - export interface GitStackCommonConfig extends CommonImportOptions { 11 + export interface GitStackCommonConfig extends CommonStackConfig { 9 12 inMonorepo?: boolean | string 10 - hostParentPath?: string, 11 - writeEnv?: boolean 12 13 } 13 14 14 15 export interface GitStackStandaloneConfig extends GitStackCommonConfig {
+4 -3
src/common/utils/io.ts
··· 1 1 import { accessSync, constants, promises } from "fs"; 2 2 import pathUtil from "path"; 3 - import { glob } from 'glob'; 3 + import { glob, GlobOptionsWithFileTypesUnset } from 'glob'; 4 4 import clone from 'clone'; 5 5 import { DEFAULT_GLOB_FOLDER } from "../infrastructure/atomic.js"; 6 6 ··· 67 67 return data.toString(); 68 68 } 69 69 70 - export const findFilesRecurive = async (filePattern: string, fromDir: string): Promise<string[]> => { 70 + export const findFilesRecurive = async (filePattern: string, fromDir: string, options: GlobOptionsWithFileTypesUnset = {}): Promise<string[]> => { 71 71 try { 72 72 return await glob(filePattern, { 73 73 cwd: fromDir, 74 - //nodir: true 74 + nodir: true, 75 + ...options 75 76 }); 76 77 } catch (e) { 77 78 throw new Error(`Error occurred while trying to find files for pattern ${filePattern}`, { cause: e });
+1 -44
tests/filesOnServer.test.ts
··· 6 6 import { loggerTest } from "@foxxmd/logging"; 7 7 import { buildFileStack, BuildFileStackOptions } from '../src/builders/stack/filesOnServer.js'; 8 8 import { FilesOnServerConfig } from '../src/common/infrastructure/config/stackConfig.js'; 9 + import { stripIndents } from 'common-tags'; 9 10 10 11 const DEFAULT_FOS_PATH = '/my/cool' 11 12 const DEFAULT_SERVER = 'test-server'; ··· 114 115 115 116 expect(data.config.file_paths).to.not.be.undefined; 116 117 expect(data.config.file_paths.length).eq(2); 117 - }, { unsafeCleanup: true }); 118 - }); 119 - 120 - }); 121 - 122 - describe('ENV Files', function () { 123 - 124 - it(`does not include additional_env_files when no env files found`, async function () { 125 - await withLocalTmpDir(async () => { 126 - const dir = path.join(process.cwd(), 'test_1'); 127 - 128 - await mkdir(dir); 129 - const data = await buildFileStack(dir, defaultFOSConfig); 130 - 131 - expect(data.config.additional_env_files).to.be.undefined; 132 - }, { unsafeCleanup: true }); 133 - }); 134 - 135 - it(`includes additional_env_files when env files found`, async function () { 136 - await withLocalTmpDir(async () => { 137 - const dir = path.join(process.cwd(), 'test_1'); 138 - 139 - await mkdir(dir); 140 - await writeFile(path.join(dir, '.env'), ''); 141 - await mkdir(path.join(dir, 'nested')); 142 - await writeFile(path.join(dir, 'nested', '.env'), ''); 143 - const data = await buildFileStack(dir, defaultFOSConfig); 144 - 145 - expect(data.config.additional_env_files).to.not.be.undefined; 146 - expect(data.config.additional_env_files.length).eq(2); 147 - expect(data.config.additional_env_files[0]).eq('.env'); 148 - expect(data.config.additional_env_files[1]).eq('nested/.env'); 149 - }, { unsafeCleanup: true }); 150 - }); 151 - 152 - it(`uses komodo env name when env files found`, async function () { 153 - await withLocalTmpDir(async () => { 154 - const dir = path.join(process.cwd(), 'test_1'); 155 - 156 - await mkdir(dir); 157 - await writeFile(path.join(dir, '.env'), ''); 158 - const data = await buildFileStack(dir, defaultFOSConfig); 159 - 160 - expect(data.config.env_file_path).eq(defaultFOSConfig.komodoEnvName) 161 118 }, { unsafeCleanup: true }); 162 119 }); 163 120
+120
tests/stackCommon.test.ts
··· 1 + import { describe, it } from 'mocha'; 2 + import chai, { expect } from 'chai'; 3 + import { detectGitRepo, parseGitStatus } from '../src/common/utils/git.js'; 4 + import { stripIndents } from 'common-tags'; 5 + import withLocalTmpDir from 'with-local-tmp-dir'; 6 + import { mkdir, writeFile, chmod, constants } from 'node:fs/promises'; 7 + import path from "path"; 8 + import { buildGitStack, BuildGitStackOptions } from '../src/builders/stack/gitStack.js'; 9 + import { loggerTest } from "@foxxmd/logging"; 10 + import chaiAsPromised from 'chai-as-promised'; 11 + import git from 'isomorphic-git'; 12 + import fs from 'fs'; 13 + import { execa, Options } from 'execa'; 14 + import { DEFAULT_KOMODO_ENV_NAME, parseEnvConfig } from '../src/builders/stack/stackUtils.js'; 15 + 16 + chai.use(chaiAsPromised); 17 + 18 + const DEFAULT_SERVER = 'test-server'; 19 + 20 + const defaultGitStandaloneConfig: BuildGitStackOptions = { 21 + server: DEFAULT_SERVER, 22 + logger: loggerTest 23 + } 24 + 25 + describe('#StackCommon', function () { 26 + 27 + describe('#EnvironmentParsing', function () { 28 + 29 + it(`does not include additional_env_files when no env files found`, async function () { 30 + await withLocalTmpDir(async () => { 31 + const dir = path.join(process.cwd(), 'test_1'); 32 + 33 + await mkdir(dir); 34 + const data = await parseEnvConfig(dir, { logger: loggerTest }); 35 + 36 + expect(data.additional_env_files).to.be.undefined; 37 + }, { unsafeCleanup: true }); 38 + }); 39 + 40 + it(`includes additional_env_files when env files found`, async function () { 41 + await withLocalTmpDir(async () => { 42 + const dir = path.join(process.cwd(), 'test_1'); 43 + 44 + await mkdir(dir); 45 + await writeFile(path.join(dir, '.env'), ''); 46 + await mkdir(path.join(dir, 'nested')); 47 + await writeFile(path.join(dir, 'nested', 'additional.env'), ''); 48 + const data = await parseEnvConfig(dir, { logger: loggerTest }); 49 + 50 + expect(data.additional_env_files).to.not.be.undefined; 51 + expect(data.additional_env_files.length).eq(2); 52 + expect(data.additional_env_files[0]).eq('.env'); 53 + expect(data.additional_env_files[1]).eq('nested/additional.env'); 54 + }, { unsafeCleanup: true }); 55 + }); 56 + 57 + it(`uses pathPrefix for additional_env_files`, async function () { 58 + await withLocalTmpDir(async () => { 59 + const dir = path.join(process.cwd(), 'test_1'); 60 + 61 + await mkdir(dir); 62 + await writeFile(path.join(dir, '.env'), ''); 63 + const data = await parseEnvConfig(dir, { logger: loggerTest, pathPrefix: '/mytest' }); 64 + 65 + expect(data.additional_env_files).to.not.be.undefined; 66 + expect(data.additional_env_files.length).eq(1); 67 + expect(data.additional_env_files[0]).eq('/mytest/.env'); 68 + }, { unsafeCleanup: true }); 69 + }); 70 + 71 + it(`uses komodo env name when files found includes .env`, async function () { 72 + await withLocalTmpDir(async () => { 73 + const dir = path.join(process.cwd(), 'test_1'); 74 + 75 + await mkdir(dir); 76 + await writeFile(path.join(dir, '.env'), ''); 77 + const data = await parseEnvConfig(dir, { logger: loggerTest }); 78 + 79 + expect(data.env_file_path).eq(DEFAULT_KOMODO_ENV_NAME) 80 + }, { unsafeCleanup: true }); 81 + }); 82 + 83 + describe('When writeEnv is true', function () { 84 + 85 + it(`writes env contents`, async function () { 86 + await withLocalTmpDir(async () => { 87 + const dir = path.join(process.cwd(), 'test_1'); 88 + 89 + await mkdir(dir); 90 + await writeFile(path.join(dir, '.env'), stripIndents`MY_1ENV=foo 91 + MY_2ENV=bar`); 92 + const data = await parseEnvConfig(dir, { logger: loggerTest, writeEnv: true }); 93 + 94 + expect(data.env_file_path).to.be.undefined; 95 + expect(data.environment).to.eq(`MY_1ENV=foo\nMY_2ENV=bar`) 96 + 97 + }, { unsafeCleanup: true }); 98 + }); 99 + 100 + it(`writes multiple env contents`, async function () { 101 + await withLocalTmpDir(async () => { 102 + const dir = path.join(process.cwd(), 'test_1'); 103 + 104 + await mkdir(dir); 105 + await writeFile(path.join(dir, '.env'), stripIndents`MY_1ENV=foo 106 + MY_2ENV=bar`); 107 + await writeFile(path.join(dir, 'additional.env'), stripIndents`MY_3ENV=foo 108 + MY_4ENV=bar`); 109 + const data = await parseEnvConfig(dir, { logger: loggerTest, writeEnv: true }); 110 + 111 + expect(data.env_file_path).to.be.undefined; 112 + expect(data.environment).to.eq(`MY_3ENV=foo\nMY_4ENV=bar\nMY_1ENV=foo\nMY_2ENV=bar`) 113 + 114 + }, { unsafeCleanup: true }); 115 + }); 116 + 117 + }); 118 + 119 + }); 120 + });