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

fix: Add env to enable glob dot

FoxxMD (Aug 22, 2025, 8:21 PM UTC) 5ce5e8d1 e4e98948

+49 -23
+2 -1
docsite/docs/usage/overview.mdx
··· 201 201 | ENV | Required | Default | Description | 202 202 | :------------------------ | :------- | -------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | 203 203 | `SERVER_NAME` | ☑️ | | The name of the Komodo [Server](https://komo.do/docs/resources#server) where a Stack is located | 204 - | `STACKS_FROM` | ☑️ | `dir` | What [**Stack Sources**](#stack-sources) to generate Stacks from? `dir` or `compose` | 204 + | `STACKS_FROM` | ☑️ | `dir` | What [**Stack Sources**](#stack-sources) to generate Stacks from? `dir` or `compose` | 205 205 | `HOST_DIR` | ☑️ | | The parent directory on the **host** that mounted into the Komodo Import container | 206 206 | `WRITE_ENV` | - | `true` | Write the contents of found .env files to Komodo **Environment**. Likely want this as `true`. | 207 207 | `COMPOSE_FILE_GLOB` | - | `**/{compose,docker-compose}*.y?(a)ml` | The [glob](https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer) pattern to use for finding files for **Files Paths** in Stack config | 208 208 | `ENV_FILE_GLOB` | - | `**/.env` | The [glob](https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer) pattern to use for finding files for **Additional Env Files** in Stack config | 209 + | `GLOB_DOT` | - | `false` | Allow glob patterns, outside of ENV pattern, to find files/folders that start with `.` | 209 210 | `KOMODO_ENV_NAME` | - | `.komodoenv` | If existing .env files are found, and `WRITE_ENV=false`, then this name will be used for the .env that Komodo writes using its own Environment section | 210 211 | `IMAGE_REGISTRY_PROVIDER` | - | | Name of Image Registry to use | 211 212 | `IMAGE_REGISTRY_ACCOUNT` | - | | Image Registry account to use |
+8
docsite/src/components/QuickstartCompose.tsx
··· 26 26 const doc = parseDocument(ComposeExample); 27 27 const proxyCollection = parseDocument(SocketProxyExample); 28 28 29 + const DOT_FOLDER_REGEX = new RegExp(/(?:^|[\/\\])\.\S/); 30 + 29 31 interface ComposeStateData { 30 32 autoUpdate?: boolean 31 33 pollUpdate?: boolean ··· 131 133 const hostS = new Scalar(`HOST_PARENT_PATH=${hostPath}`); 132 134 hostS.commentBefore = `# Same as mounted directory above` 133 135 seq.add(hostS); 136 + 137 + if(DOT_FOLDER_REGEX.test(hostPath)) { 138 + const dotS = new Scalar(`GLOB_DOT=true`); 139 + dotS.commentBefore = `# Forces glob to not ignore dot folders` 140 + seq.add(dotS); 141 + } 134 142 135 143 const stacksFromS = new Scalar(`STACKS_FROM=${composeState.stacksFrom}`); 136 144 stacksFromS.commentBefore = '# Determines what sources to generate Stacks from'
+2 -1
src/builders/stack/gitStack.ts
··· 25 25 projectName, 26 26 composeFiles = [], 27 27 writeEnv = false, 28 + allowGlobDot, 28 29 } = options 29 30 30 31 let gitStackConfig: _PartialStackConfig = {} ··· 52 53 } 53 54 54 55 if(gitData === undefined) { 55 - const parentGitPath = await findPathRecuriveParently(path, '.git'); 56 + const parentGitPath = await findPathRecuriveParently(path, '.git', {dot: allowGlobDot}); 56 57 if (parentGitPath !== undefined) { 57 58 const parentGitDir = dirname(parentGitPath) 58 59 try {
+6 -5
src/builders/stack/stackBuilder.ts
··· 48 48 ignoreFolderGlob, 49 49 envFileGlob = DEFAULT_ENV_GLOB, 50 50 composeGlob = DEFAULT_GLOB_COMPOSE, 51 - ignoreComposeGlob 51 + ignoreComposeGlob, 52 + allowGlobDot = false 52 53 } = this.stackConfigOptions; 53 54 54 55 let folderPaths: string[] = []; ··· 66 67 67 68 await this.parseComposeProjects(); 68 69 69 - const includeGlob = new Minimatch(composeGlob); 70 - const excludeGlob = ignoreComposeGlob === undefined ? undefined : new Minimatch(ignoreComposeGlob); 71 - const komodoGlob = new Minimatch(KOMODO_GLOB_IGNORE_COMPOSE); 70 + const includeGlob = new Minimatch(composeGlob, {dot: allowGlobDot}); 71 + const excludeGlob = ignoreComposeGlob === undefined ? undefined : new Minimatch(ignoreComposeGlob, {dot: allowGlobDot}); 72 + const komodoGlob = new Minimatch(KOMODO_GLOB_IGNORE_COMPOSE, {dot: allowGlobDot}); 72 73 73 74 for (const c of this.composeCandidateStacks) { 74 75 if (!c.workingDir.includes(this.dirData.host)) { ··· 100 101 this.logger.info(`Folder Ignore Glob: ${ignoreFolderGlob ?? 'N/A'}`); 101 102 102 103 await this.parseComposeProjects(); 103 - const dirs = await findFolders(this.dirData.scan, folderGlob, ignoreFolderGlob) 104 + const dirs = await findFolders(this.dirData.scan, folderGlob, {ignore: ignoreFolderGlob, dot: allowGlobDot}) 104 105 folderPaths = dirs.map(x => joinPath(this.dirData.scan, x)); 105 106 this.logger.verbose(`Got ${folderPaths.length} folders in ${this.dirData.scan}:\n${formatIntoColumns(dirs, 3)}`); 106 107 }
+1
src/common/infrastructure/config/common.ts
··· 11 11 ignoreFolderGlob?: string 12 12 composeGlob?: string 13 13 ignoreComposeGlob?: string 14 + allowGlobDot?: boolean 14 15 }
+9 -4
src/common/utils/io.ts
··· 1 1 import { accessSync, constants, promises } from "fs"; 2 2 import pathUtil, { join, dirname } from "path"; 3 - import { glob, GlobOptionsWithFileTypesUnset } from 'glob'; 3 + import { glob, GlobOptionsWithFileTypesUnset, GlobOptionsWithFileTypesTrue } from 'glob'; 4 4 import clone from 'clone'; 5 5 import { DEFAULT_GLOB_FOLDER, DirectoryConfig, DirectoryConfigValues } from "../infrastructure/atomic.js"; 6 6 import { parseBool } from "./utils.js"; 7 + import { testMaybeRegex } from "@foxxmd/regex-buddy-core"; 7 8 8 9 export async function writeFile(path: any, text: any) { 9 10 try { ··· 80 81 } 81 82 } 82 83 83 - export const findPathRecuriveParently = async (fromDir: string, path: string) => { 84 + export const findPathRecuriveParently = async (fromDir: string, path: string, options: GlobOptionsWithFileTypesUnset = {}) => { 84 85 85 86 let currDirectory = fromDir; 86 87 let parentDirectory = fromDir; ··· 93 94 94 95 const files = await glob(path, { 95 96 cwd: parentDirectory, 97 + ...options 96 98 }); 97 99 98 100 if(files.length > 0) { ··· 157 159 } 158 160 } 159 161 160 - export const findFolders = async (fromDir: string, filePattern: string = DEFAULT_GLOB_FOLDER, ignore?: string): Promise<string[]> => { 162 + export const findFolders = async (fromDir: string, filePattern: string = DEFAULT_GLOB_FOLDER, options: Partial<GlobOptionsWithFileTypesTrue> = {}): Promise<string[]> => { 161 163 try { 162 164 const res = await glob(filePattern, { 163 165 cwd: fromDir, 164 166 nodir: false, 165 167 withFileTypes: true, 166 - ignore 168 + ...options, 167 169 }); 168 170 return res.filter(x => x.isDirectory()).map(x => x.name); 169 171 } catch (e) { ··· 174 176 export const dirHasGitConfig = (paths: string[]): boolean => { 175 177 return paths.some(x => x === '.git'); 176 178 } 179 + 180 + const DOT_FOLDER_REGEX = new RegExp(/(?:^|[\/\\])\.\S/); 181 + export const pathHasDotFolder = (pathVal: string): boolean => DOT_FOLDER_REGEX.test(pathVal) 177 182 178 183 export const parseDirectoryConfig = async (dirConfig: DirectoryConfigValues = {}): Promise<[DirectoryConfigValues, DirectoryConfig]> => { 179 184 const {
+20 -11
src/index.ts
··· 17 17 import { exportToSync } from './exporters/exportToApiSync.js'; 18 18 import { getDefaultKomodoApi } from './common/utils/komodo.js'; 19 19 import { StackBuilder } from './builders/stack/stackBuilder.js'; 20 - import { parseDirectoryConfig } from './common/utils/io.js'; 20 + import { parseDirectoryConfig, pathHasDotFolder } from './common/utils/io.js'; 21 21 import { DirectoryConfig, DirectoryConfigValues } from './common/infrastructure/atomic.js'; 22 22 23 23 dayjs.extend(utc) ··· 56 56 57 57 getDefaultKomodoApi(logger); 58 58 59 - let dirData: [DirectoryConfigValues, DirectoryConfig]; 60 - try { 61 - dirData = await parseDirectoryConfig(); 62 - logger.info(`Mount Dir : ${dirData[0].mountVal} -> Resolved: ${dirData[1].mount}`); 63 - logger.info(`Host Dir : ${dirData[0].hostVal} -> Resolved: ${dirData[1].host}`); 64 - logger.info(`Scan Dir : ${dirData[0].scanVal} -> Resolved: ${dirData[1].scan}`); 65 - } catch (e) { 66 - throw new Error('Could not parse required directories', {cause: e}); 67 - } 68 - 69 59 const importOptions: CommonImportOptions = { 70 60 server: process.env.SERVER_NAME, 71 61 imageRegistryProvider: process.env.IMAGE_REGISTRY_PROVIDER, ··· 74 64 pollForUpdate: isUndefinedOrEmptyString(process.env.POLL_FOR_UPDATE) ? undefined : parseBool(process.env.POLL_FOR_UPDATE), 75 65 komodoEnvName: process.env.KOMODO_ENV_NAME, 76 66 composeFileGlob: process.env.COMPOSE_FILE_GLOB, 67 + allowGlobDot: isUndefinedOrEmptyString(process.env.GLOB_DOT) ? undefined : parseBool(process.env.GLOB_DOT), 77 68 envFileGlob: process.env.ENV_FILE_GLOB, 78 69 folderGlob: isUndefinedOrEmptyString(process.env.FOLDER_GLOB) ? undefined : process.env.FOLDER_GLOB.trim(), 79 70 ignoreFolderGlob: isUndefinedOrEmptyString(process.env.FOLDER_IGNORE_GLOB) ? undefined : process.env.FOLDER_IGNORE_GLOB.trim(), 80 71 }; 72 + 73 + let dirData: [DirectoryConfigValues, DirectoryConfig]; 74 + try { 75 + dirData = await parseDirectoryConfig(undefined); 76 + logger.info(`Mount Dir : ${dirData[0].mountVal} -> Resolved: ${dirData[1].mount}`); 77 + logger.info(`Host Dir : ${dirData[0].hostVal} -> Resolved: ${dirData[1].host}`); 78 + logger.info(`Scan Dir : ${dirData[0].scanVal} -> Resolved: ${dirData[1].scan}`); 79 + } catch (e) { 80 + throw new Error('Could not parse required directories', {cause: e}); 81 + } 82 + 83 + if(pathHasDotFolder(dirData[1].host) && importOptions.allowGlobDot !== true) { 84 + logger.warn(`It looks like your Host Dir has a dot folder in its path and the env GLOB_DOT is not true/set. Komodo Import may not be able to match your paths because of this. If results are not as expected try setting GLOB_DOT=true`); 85 + } 86 + if(pathHasDotFolder(dirData[1].scan) && dirData[1].host !== dirData[1].scan && importOptions.allowGlobDot !== true) { 87 + logger.warn(`It looks like your Scan Dir has a dot folder in its path and the env GLOB_DOT is not true/set. Komodo Import may not be able to match your paths because of this. If results are not as expected try setting GLOB_DOT=true`); 88 + } 89 + 81 90 82 91 if (importOptions.server === undefined || importOptions.server.trim() === '') { 83 92 logger.error('ENV SERVER_NAME must be set');
+1 -1
tests/util.test.ts
··· 99 99 await mkdir(path.join(process.cwd(), 'test_1')); 100 100 await mkdir(path.join(process.cwd(), 'ignoreme', 'subfolder'), {recursive: true}); 101 101 await writeFile(path.join(process.cwd(), 'ignoreme', 'subfolder', 'test.txt'), ''); 102 - const folders = await findFolders(process.cwd(), undefined, 'ignore*'); 102 + const folders = await findFolders(process.cwd(), undefined, {ignore: 'ignore*'}); 103 103 expect(folders).to.deep.eq(['test_1']); 104 104 }, { unsafeCleanup: true }); 105 105 });