[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.

feat: Improve parsing and logging

* Move "all" files-on-server logic to own function to reduce glob logging
* Add option to log json for each stack and toml json data behind DEBUG_MODE
* try-catch files-on-server stack building and toml stringify, log error and json data if possible
* Log all folders that will be processed
* Fix using sorted array when no compose file found #1

FoxxMD (Aug 11, 2025, 2:56 PM UTC) 897501df 24b6c868

+94 -43
+2 -1
package.json
··· 8 8 "test": "mocha --reporter spec", 9 9 "build:backend": "tsc -p src", 10 10 "dev": "nodemon -w src -x tsx src/index.ts", 11 - "start": "NODE_ENV=production tsx src/index.ts" 11 + "start": "NODE_ENV=production tsx src/index.ts", 12 + "demo": "HOST_PARENT_PATH=/my/cool/path SERVER_NAME=my-cool-server FILES_ON_SERVER_DIR=tests/exampleDir tsx src/index.ts" 12 13 }, 13 14 "engines": { 14 15 "node": ">=18.19.1",
+76 -36
src/builders/stack/filesOnServer.ts
··· 5 5 import { TomlStack } from "../../common/infrastructure/tomlObjects.js"; 6 6 import { findFilesRecurive, sortComposePaths } from "../../common/utils/io.js"; 7 7 import { stripIndents } from "common-tags"; 8 + import { isDebugMode } from "../../common/utils/utils.js"; 8 9 9 10 const DEFAULT_COMPOSE_GLOB = '**/{compose,docker-compose}*.y?(a)ml'; 10 - //const DEFAULT_COMPOSE_GLOB = 'compose.yaml'; 11 11 12 12 export const buildFileStack = async (path: string, options: FilesOnServerConfig & { logger: Logger }): Promise<TomlStack> => { 13 13 ··· 28 28 const logger = childLogger(options.logger, pathInfo.name); 29 29 logger.info(`Found Stack '${pathInfo.name}' at dir ${path}`); 30 30 31 - const stack: TomlStack = { 32 - name: pathInfo.name, 33 - config: { 34 - server, 35 - run_directory: join(hostParentPath, pathInfo.name), 36 - files_on_host: true, 37 - registry_account: imageRegistryAccount, 38 - registry_provider: imageRegistryProvider, 39 - auto_update: autoUpdate, 40 - poll_for_updates: pollForUpdate 41 - } 42 - }; 31 + let stack: TomlStack; 32 + let logJson = isDebugMode(); 43 33 44 - const composeFiles = await findFilesRecurive(composeFileGlob, path); 45 - let sorted = [...composeFiles].reverse(); 46 - if (composeFiles.length === 0) { 47 - logger.warn(`Did not find any files patterns matching compose pattern ${composeFileGlob}`); 48 - } else { 49 - sorted = sortComposePaths(composeFiles); 50 - logger.info(stripIndents`Found ${composeFiles.length} files matching compose pattern ${composeFileGlob}: 34 + try { 35 + stack = { 36 + name: pathInfo.name, 37 + config: { 38 + server, 39 + run_directory: join(hostParentPath, pathInfo.name), 40 + files_on_host: true, 41 + registry_account: imageRegistryAccount, 42 + registry_provider: imageRegistryProvider, 43 + auto_update: autoUpdate, 44 + poll_for_updates: pollForUpdate 45 + } 46 + }; 47 + 48 + const composeFiles = await findFilesRecurive(composeFileGlob, path); 49 + let sorted = [...composeFiles].reverse(); 50 + if (composeFiles.length === 0) { 51 + logger.warn(`Did not find any files patterns matching compose glob`); 52 + } else { 53 + sorted = sortComposePaths(composeFiles); 54 + logger.info(stripIndents`Found ${composeFiles.length} files matching compose glob: 51 55 ${sorted.join('\n')}`); 52 - } 53 - // only take first file if using default 54 - if (DEFAULT_COMPOSE_GLOB === composeFileGlob) { 55 - stack.config.file_paths = [sorted[0]]; 56 - } else { 57 - // otherwise assume user wants all matched files 58 - stack.config.file_paths = sorted; 59 - } 60 56 61 - const envFiles = await findFilesRecurive(envFileGlob, path); 62 - if (envFiles.length > 0) { 63 - stack.config.env_file_path = komodoEnvName 64 - logger.info(stripIndents`Found ${envFiles.length} env files matching pattern ${envFileGlob}: 57 + // only take first file if using default 58 + if (DEFAULT_COMPOSE_GLOB === composeFileGlob) { 59 + stack.config.file_paths = [sorted[0]]; 60 + } else { 61 + // otherwise assume user wants all matched files 62 + stack.config.file_paths = sorted; 63 + } 64 + logger.info(`Using file(s): ${stack.config.file_paths.join('\n')}`); 65 + } 66 + 67 + 68 + const envFiles = await findFilesRecurive(envFileGlob, path); 69 + if (envFiles.length > 0) { 70 + stack.config.env_file_path = komodoEnvName 71 + logger.info(stripIndents`Found ${envFiles.length} env files matching pattern ${envFileGlob}: 65 72 ${envFiles.join('\n')}`); 66 - logger.info(`Using ${komodoEnvName} for Komodo-written env file`); 67 - stack.config.additional_env_files = envFiles; 73 + logger.info(`Using ${komodoEnvName} for Komodo-written env file`); 74 + stack.config.additional_env_files = envFiles; 75 + } 76 + 77 + logger.info('Stack config complete'); 78 + 79 + return stack; 80 + } catch (e) { 81 + logJson = true; 82 + throw new Error(`Error occurred while processing Stack for folder ${pathInfo.name}`, {cause: e}); 83 + } finally { 84 + if(logJson) { 85 + logger.debug(`Stack Config: ${JSON.stringify(stack)}}`); 86 + } 68 87 } 88 + } 69 89 70 - logger.info('Stack config complete'); 90 + export const buildFileStacks = async (dirs: string[], options: FilesOnServerConfig & { logger: Logger }): Promise<TomlStack[]> => { 91 + const logger = childLogger(options.logger, 'Files On Server'); 71 92 72 - return stack; 93 + const { 94 + composeFileGlob = DEFAULT_COMPOSE_GLOB, 95 + envFileGlob = '**/.env', 96 + } = options; 97 + 98 + logger.info(`Processing Stacks for ${dirs.length} folders:\n${dirs.join('\n')}`); 99 + logger.info(`Compose File Glob: ${composeFileGlob}`); 100 + logger.info(`Env Glob: ${envFileGlob}`); 101 + 102 + const stacks: TomlStack[] = []; 103 + for (const dir of dirs) { 104 + try { 105 + stacks.push(await buildFileStack(dir, {...options, logger})); 106 + } catch (e) { 107 + logger.error(new Error(`Unable to build Stack for folder ${dir}`, { cause: e })); 108 + } 109 + } 110 + 111 + logger.info(`Built Stack configs for ${stacks.length} folders`); 112 + return stacks; 73 113 }
+16 -6
src/index.ts
··· 12 12 import { fileOrDirectoryIsWriteable, pathExistsAndIsReadable, readDirectories, writeFile } from './common/utils/io.js'; 13 13 import { _PartialStackConfig } from 'komodo_client/dist/types.js'; 14 14 import { TomlStack } from './common/infrastructure/tomlObjects.js'; 15 - import { buildFileStack } from './builders/stack/filesOnServer.js'; 15 + import { buildFileStack, buildFileStacks } from './builders/stack/filesOnServer.js'; 16 16 import { CommonImportOptions } from './common/infrastructure/config/common.js'; 17 17 import { FilesOnServerConfig } from './common/infrastructure/config/filesOnServer.js'; 18 18 ··· 98 98 99 99 const dirs = await readDirectories(FILES_ON_SERVER_DIR); 100 100 101 - const stacks: TomlStack[] = []; 102 - for (const folder of dirs) { 103 - stacks.push(await buildFileStack(path.join(FILES_ON_SERVER_DIR, folder), { ...filesOnServerConfig, logger })) 104 - } 101 + let stacks: TomlStack[] = []; 102 + const folderPaths = dirs.map(x => path.join(FILES_ON_SERVER_DIR, x)); 103 + stacks = await buildFileStacks(folderPaths, { ...filesOnServerConfig, logger }); 105 104 106 105 if (stacks.length === 0) { 107 106 logger.info('No Stacks found! Nothing to do.'); ··· 114 113 115 114 const time = dayjs().format('YYYY-MM-DD--HH-mm-ss'); 116 115 117 - const toml = stringify(data); 116 + let toml: string; 117 + let logTomlData = isDebugMode(); 118 + try { 119 + toml = stringify(data); 120 + } catch (e) { 121 + logger.error(new Error('Could not produce TOML', {cause: e})); 122 + logTomlData = true; 123 + } finally { 124 + if(logTomlData) { 125 + logger.info(`TOML Data: ${JSON.stringify(data)}`); 126 + } 127 + } 118 128 119 129 logger.info(`TOML:\n${toml}`); 120 130