ci: automerge minor and patch app upgrades (#720)
* fix: app version mismatchs between config and docker-compose * ci: create automerge workflow wip
This commit is contained in:
		
							parent
							
								
									c354f1f094
								
							
						
					
					
						commit
						e495cc1293
					
				
							
								
								
									
										28
									
								
								.github/workflows/auto-merge.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/auto-merge.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					name: automerge
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    types:
 | 
				
			||||||
 | 
					      - labeled
 | 
				
			||||||
 | 
					      - unlabeled
 | 
				
			||||||
 | 
					      - synchronize
 | 
				
			||||||
 | 
					      - opened
 | 
				
			||||||
 | 
					      - edited
 | 
				
			||||||
 | 
					      - ready_for_review
 | 
				
			||||||
 | 
					      - reopened
 | 
				
			||||||
 | 
					      - unlocked
 | 
				
			||||||
 | 
					  pull_request_review:
 | 
				
			||||||
 | 
					    types:
 | 
				
			||||||
 | 
					      - submitted
 | 
				
			||||||
 | 
					  check_suite:
 | 
				
			||||||
 | 
					    types:
 | 
				
			||||||
 | 
					      - completed
 | 
				
			||||||
 | 
					  status: {}
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  automerge:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - id: automerge
 | 
				
			||||||
 | 
					        name: automerge
 | 
				
			||||||
 | 
					        uses: "pascalgn/automerge-action@v0.15.6"
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
 | 
				
			||||||
							
								
								
									
										70
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
name: Tipi CI
 | 
					name: Tipi CI
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    types: [opened, synchronize, reopened, ready_for_review]
 | 
					    types: [opened, synchronize, reopened, ready_for_review]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +25,7 @@ jobs:
 | 
				
			||||||
      - name: Get pnpm store directory
 | 
					      - name: Get pnpm store directory
 | 
				
			||||||
        id: pnpm-cache
 | 
					        id: pnpm-cache
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
 | 
					          echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - uses: actions/cache@v3
 | 
					      - uses: actions/cache@v3
 | 
				
			||||||
        name: Setup pnpm cache
 | 
					        name: Setup pnpm cache
 | 
				
			||||||
| 
						 | 
					@ -44,3 +43,70 @@ jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run linter
 | 
					      - name: Run linter
 | 
				
			||||||
        run: pnpm run lint
 | 
					        run: pnpm run lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Check bumped version
 | 
				
			||||||
 | 
					        id: check-bumped-version
 | 
				
			||||||
 | 
					        uses: actions/github-script@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					          script: |
 | 
				
			||||||
 | 
					            const semver = require('semver')
 | 
				
			||||||
 | 
					            const { data } = await github.pulls.listFiles({
 | 
				
			||||||
 | 
					              owner: context.repo.owner,
 | 
				
			||||||
 | 
					              repo: context.repo.repo,
 | 
				
			||||||
 | 
					              pull_number: context.issue.number
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const modifiedFiles = data.map(file => file.filename);
 | 
				
			||||||
 | 
					            const filesInAppsFolder = modifiedFiles.filter(file => file.includes('apps/'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (filesInAppsFolder.length < modifiedFiles.length) {
 | 
				
			||||||
 | 
					              console.log('Not all files are in apps folder, skipping automerge')
 | 
				
			||||||
 | 
					              core.setOutput('major_bump', 'true')
 | 
				
			||||||
 | 
					              return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const configs = modifiedFiles.filter(file => file.includes('config.json'))
 | 
				
			||||||
 | 
					            let majorBump = 'false'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (const configFile of configs) {
 | 
				
			||||||
 | 
					              const baseContent = await github.repos.getContent({
 | 
				
			||||||
 | 
					                owner: context.repo.owner,
 | 
				
			||||||
 | 
					                repo: context.repo.repo,
 | 
				
			||||||
 | 
					                path: configFile,
 | 
				
			||||||
 | 
					                ref: context.payload.pull_request.base.ref
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					              const baseConfig = JSON.parse(Buffer.from(baseContent.data.content, 'base64').toString('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const currentContent = await github.repos.getContent({
 | 
				
			||||||
 | 
					                owner: context.repo.owner,
 | 
				
			||||||
 | 
					                repo: context.repo.repo,
 | 
				
			||||||
 | 
					                path: configFile,
 | 
				
			||||||
 | 
					                ref: context.payload.pull_request.head.ref
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					              const currentConfig = JSON.parse(Buffer.from(currentContent.data.content, 'base64').toString('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const baseVersion = semver.coerce(baseConfig.version)
 | 
				
			||||||
 | 
					              const currentVersion = semver.coerce(currentConfig.version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              if (currentVersion?.major > baseVersion?.major) {
 | 
				
			||||||
 | 
					                console.log('Major bump detected, skipping automerge')
 | 
				
			||||||
 | 
					                majorBump = 'true'
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					              } 
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            core.setOutput('major_bump', majorBump)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Label this PR as "automerge" if major_bump is false
 | 
				
			||||||
 | 
					        if: steps.check-bumped-version.outputs.major_bump == 'false'
 | 
				
			||||||
 | 
					        uses: actions/github-script@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					          script: |
 | 
				
			||||||
 | 
					            github.issues.addLabels({
 | 
				
			||||||
 | 
					              issue_number: context.issue.number,
 | 
				
			||||||
 | 
					              owner: context.repo.owner,
 | 
				
			||||||
 | 
					              repo: context.repo.repo,
 | 
				
			||||||
 | 
					              labels: ["automerge"]
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/renovate-app-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/renovate-app-version.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -21,7 +21,7 @@ jobs:
 | 
				
			||||||
      - name: Get list of updated files by the last commit in this branch separated by space
 | 
					      - name: Get list of updated files by the last commit in this branch separated by space
 | 
				
			||||||
        id: updated-files
 | 
					        id: updated-files
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::set-output name=files::$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')"
 | 
					          echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Run renovate-app-version.sh on updated files
 | 
					      - name: Run renovate-app-version.sh on updated files
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import fs from 'fs';
 | 
					import fs from "fs";
 | 
				
			||||||
import jsyaml from 'js-yaml';
 | 
					import semver from "semver";
 | 
				
			||||||
 | 
					import jsyaml from "js-yaml";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface AppConfig {
 | 
					interface AppConfig {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
| 
						 | 
					@ -18,11 +19,18 @@ interface AppConfig {
 | 
				
			||||||
  available: boolean;
 | 
					  available: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const networkExceptions = ['pihole', 'tailscale', 'homeassistant', 'plex', 'zerotier', 'gladys'];
 | 
					const networkExceptions = [
 | 
				
			||||||
 | 
					  "pihole",
 | 
				
			||||||
 | 
					  "tailscale",
 | 
				
			||||||
 | 
					  "homeassistant",
 | 
				
			||||||
 | 
					  "plex",
 | 
				
			||||||
 | 
					  "zerotier",
 | 
				
			||||||
 | 
					  "gladys",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
const getAppConfigs = (): AppConfig[] => {
 | 
					const getAppConfigs = (): AppConfig[] => {
 | 
				
			||||||
  const apps: AppConfig[] = [];
 | 
					  const apps: AppConfig[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const appsDir = fs.readdirSync('./apps');
 | 
					  const appsDir = fs.readdirSync("./apps");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  appsDir.forEach((app: string) => {
 | 
					  appsDir.forEach((app: string) => {
 | 
				
			||||||
    const path = `./apps/${app}/config.json`;
 | 
					    const path = `./apps/${app}/config.json`;
 | 
				
			||||||
| 
						 | 
					@ -36,7 +44,7 @@ const getAppConfigs = (): AppConfig[] => {
 | 
				
			||||||
          apps.push(config);
 | 
					          apps.push(config);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        console.error('Error parsing config file', app);
 | 
					        console.error("Error parsing config file", app);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -44,152 +52,198 @@ const getAppConfigs = (): AppConfig[] => {
 | 
				
			||||||
  return apps;
 | 
					  return apps;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('App configs', () => {
 | 
					describe("App configs", () => {
 | 
				
			||||||
  it('Get app config should return at least one app', () => {
 | 
					  it("Get app config should return at least one app", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(apps.length).toBeGreaterThan(0);
 | 
					    expect(apps.length).toBeGreaterThan(0);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have an id', () => {
 | 
					  describe("Each app should have an id", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(app.id).toBeDefined();
 | 
					        expect(app.id).toBeDefined();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a md description', () => {
 | 
					  describe("Each app should have a md description", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        const path = `./apps/${app.id}/metadata/description.md`;
 | 
					        const path = `./apps/${app.id}/metadata/description.md`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (fs.existsSync(path)) {
 | 
					        if (fs.existsSync(path)) {
 | 
				
			||||||
          const description = fs.readFileSync(path).toString();
 | 
					          const description = fs.readFileSync(path).toString();
 | 
				
			||||||
          expect(description).toBeDefined();
 | 
					          expect(description).toBeDefined();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
        console.error(`Missing description for app ${app.id}`);
 | 
					 | 
				
			||||||
          expect(true).toBe(false);
 | 
					          expect(true).toBe(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have categories defined as an array', () => {
 | 
					  describe("Each app should have categories defined as an array", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(app.categories).toBeDefined();
 | 
					        expect(app.categories).toBeDefined();
 | 
				
			||||||
        expect(app.categories).toBeInstanceOf(Array);
 | 
					        expect(app.categories).toBeInstanceOf(Array);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a name', () => {
 | 
					  describe("Each app should have a name", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(app.name).toBeDefined();
 | 
					        expect(app.name).toBeDefined();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a description', () => {
 | 
					  describe("Each app should have a description", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(app.description).toBeDefined();
 | 
					        expect(app.description).toBeDefined();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a port', () => {
 | 
					  describe("Each app should have a port", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(app.port).toBeDefined();
 | 
					        expect(app.port).toBeDefined();
 | 
				
			||||||
        expect(app.port).toBeGreaterThan(999);
 | 
					        expect(app.port).toBeGreaterThan(999);
 | 
				
			||||||
        expect(app.port).toBeLessThan(65535);
 | 
					        expect(app.port).toBeLessThan(65535);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a different port', () => {
 | 
					  test("Each app should have a different port", () => {
 | 
				
			||||||
    const appConfigs = getAppConfigs();
 | 
					    const appConfigs = getAppConfigs();
 | 
				
			||||||
    const ports = appConfigs.map((app) => app.port);
 | 
					    const ports = appConfigs.map((app) => app.port);
 | 
				
			||||||
    expect(new Set(ports).size).toBe(appConfigs.length);
 | 
					    expect(new Set(ports).size).toBe(appConfigs.length);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a unique id', () => {
 | 
					  test("Each app should have a unique id", () => {
 | 
				
			||||||
    const appConfigs = getAppConfigs();
 | 
					    const appConfigs = getAppConfigs();
 | 
				
			||||||
    const ids = appConfigs.map((app) => app.id);
 | 
					    const ids = appConfigs.map((app) => app.id);
 | 
				
			||||||
    expect(new Set(ids).size).toBe(appConfigs.length);
 | 
					    expect(new Set(ids).size).toBe(appConfigs.length);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a version', () => {
 | 
					  describe("Each app should have a version", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
 | 
					        expect(app.version).toBeDefined();
 | 
				
			||||||
        expect(app.tipi_version).toBeDefined();
 | 
					        expect(app.tipi_version).toBeDefined();
 | 
				
			||||||
        expect(app.tipi_version).toBeGreaterThan(0);
 | 
					        expect(app.tipi_version).toBeGreaterThan(0);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a docker-compose file beside it', () => {
 | 
					  describe("Each app should have a docker-compose file beside it", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
 | 
					        expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a metadata folder beside it', () => {
 | 
					  describe("Each app should have a metadata folder beside it", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
 | 
					        expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a file named logo.jpg in the metadata folder', () => {
 | 
					  describe("Each app should have a file named logo.jpg in the metadata folder", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
 | 
					        expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have a container name equals to its id', () => {
 | 
					  describe("Each app should have a container name equals to its id", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
      const dockerComposeFile = fs.readFileSync(`./apps/${app.id}/docker-compose.yml`).toString();
 | 
					      test(app.id, () => {
 | 
				
			||||||
 | 
					        const dockerComposeFile = fs
 | 
				
			||||||
 | 
					          .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
				
			||||||
 | 
					          .toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
					        const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!dockerCompose.services[app.id]) {
 | 
					 | 
				
			||||||
        console.error(app.id);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expect(dockerCompose.services[app.id]).toBeDefined();
 | 
					        expect(dockerCompose.services[app.id]).toBeDefined();
 | 
				
			||||||
        expect(dockerCompose.services[app.id].container_name).toBe(app.id);
 | 
					        expect(dockerCompose.services[app.id].container_name).toBe(app.id);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('Each app should have network tipi_main_network', () => {
 | 
					  describe("Each app should have the same version in config.json and docker-compose.yml", () => {
 | 
				
			||||||
 | 
					    const exceptions = ["revolt"];
 | 
				
			||||||
 | 
					    const apps = getAppConfigs().filter((app) => !exceptions.includes(app.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
 | 
					        const dockerComposeFile = fs
 | 
				
			||||||
 | 
					          .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
				
			||||||
 | 
					          .toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(dockerCompose.services[app.id]).toBeDefined();
 | 
				
			||||||
 | 
					        expect(dockerCompose.services[app.id].image).toBeDefined();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dockerImage = dockerCompose.services[app.id].image;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const version = dockerImage.split(":")[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(version).toContain(app.version);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("Each app should have network tipi_main_network", () => {
 | 
				
			||||||
    const apps = getAppConfigs();
 | 
					    const apps = getAppConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apps.forEach((app) => {
 | 
					    apps.forEach((app) => {
 | 
				
			||||||
 | 
					      test(app.id, () => {
 | 
				
			||||||
        if (!networkExceptions.includes(app.id)) {
 | 
					        if (!networkExceptions.includes(app.id)) {
 | 
				
			||||||
        const dockerComposeFile = fs.readFileSync(`./apps/${app.id}/docker-compose.yml`).toString();
 | 
					          const dockerComposeFile = fs
 | 
				
			||||||
 | 
					            .readFileSync(`./apps/${app.id}/docker-compose.yml`)
 | 
				
			||||||
 | 
					            .toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
					          const dockerCompose: any = jsyaml.load(dockerComposeFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expect(dockerCompose.services[app.id]).toBeDefined();
 | 
					          expect(dockerCompose.services[app.id]).toBeDefined();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!dockerCompose.services[app.id].networks) {
 | 
					 | 
				
			||||||
          console.error(app.id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          expect(dockerCompose.services[app.id].networks).toBeDefined();
 | 
					          expect(dockerCompose.services[app.id].networks).toBeDefined();
 | 
				
			||||||
        expect(dockerCompose.services[app.id].networks).toStrictEqual(['tipi_main_network']);
 | 
					          expect(dockerCompose.services[app.id].networks).toStrictEqual([
 | 
				
			||||||
 | 
					            "tipi_main_network",
 | 
				
			||||||
 | 
					          ]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
  "available": true,
 | 
					  "available": true,
 | 
				
			||||||
  "exposable": true,
 | 
					  "exposable": true,
 | 
				
			||||||
  "tipi_version": 11,
 | 
					  "tipi_version": 11,
 | 
				
			||||||
  "version": "0.107.32",
 | 
					  "version": "v0.107.32",
 | 
				
			||||||
  "port": 8104,
 | 
					  "port": 8104,
 | 
				
			||||||
  "id": "adguard",
 | 
					  "id": "adguard",
 | 
				
			||||||
  "categories": [
 | 
					  "categories": [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,10 @@
 | 
				
			||||||
  "exposable": true,
 | 
					  "exposable": true,
 | 
				
			||||||
  "id": "autobrr",
 | 
					  "id": "autobrr",
 | 
				
			||||||
  "tipi_version": 6,
 | 
					  "tipi_version": 6,
 | 
				
			||||||
  "version": "1.26.1",
 | 
					  "version": "v1.26.2",
 | 
				
			||||||
  "categories": ["media"],
 | 
					  "categories": [
 | 
				
			||||||
 | 
					    "media"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "description": "autobrr is the modern download automation tool for torrents. With inspiration and ideas from tools like trackarr, autodl-irssi and flexget we built one tool that can do it all, and then some.",
 | 
					  "description": "autobrr is the modern download automation tool for torrents. With inspiration and ideas from tools like trackarr, autodl-irssi and flexget we built one tool that can do it all, and then some.",
 | 
				
			||||||
  "short_desc": "Automation for downloads.",
 | 
					  "short_desc": "Automation for downloads.",
 | 
				
			||||||
  "author": "autobrr",
 | 
					  "author": "autobrr",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,13 +6,17 @@
 | 
				
			||||||
  "exposable": true,
 | 
					  "exposable": true,
 | 
				
			||||||
  "id": "chatgpt-ui",
 | 
					  "id": "chatgpt-ui",
 | 
				
			||||||
  "tipi_version": 9,
 | 
					  "tipi_version": 9,
 | 
				
			||||||
  "version": "2.5.5",
 | 
					  "version": "v2.5.5",
 | 
				
			||||||
  "categories": ["ai"],
 | 
					  "categories": [
 | 
				
			||||||
 | 
					    "ai"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "description": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
					  "description": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
				
			||||||
  "short_desc": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
					  "short_desc": "A ChatGPT web client that supports multiple users, multiple languages, and multiple database connections for persistent data storage",
 | 
				
			||||||
  "author": "https://github.com/WongSaang",
 | 
					  "author": "https://github.com/WongSaang",
 | 
				
			||||||
  "source": "https://github.com/WongSaang/chatgpt-ui",
 | 
					  "source": "https://github.com/WongSaang/chatgpt-ui",
 | 
				
			||||||
  "supported_architectures": ["amd64"],
 | 
					  "supported_architectures": [
 | 
				
			||||||
 | 
					    "amd64"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "form_fields": [
 | 
					  "form_fields": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "type": "random",
 | 
					      "type": "random",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ version: "3"
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  deemix:
 | 
					  deemix:
 | 
				
			||||||
    container_name: deemix
 | 
					    container_name: deemix
 | 
				
			||||||
    image: registry.gitlab.com/bockiii/deemix-docker
 | 
					    image: registry.gitlab.com/bockiii/deemix-docker:latest
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${APP_PORT}:6595
 | 
					      - ${APP_PORT}:6595
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,10 @@
 | 
				
			||||||
  "port": 8000,
 | 
					  "port": 8000,
 | 
				
			||||||
  "id": "hello-world",
 | 
					  "id": "hello-world",
 | 
				
			||||||
  "tipi_version": 2,
 | 
					  "tipi_version": 2,
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "latest",
 | 
				
			||||||
  "categories": ["utilities"],
 | 
					  "categories": [
 | 
				
			||||||
 | 
					    "utilities"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "description": "Hello World web server in under 2 MB",
 | 
					  "description": "Hello World web server in under 2 MB",
 | 
				
			||||||
  "short_desc": "Hello World web server in under 2 MB",
 | 
					  "short_desc": "Hello World web server in under 2 MB",
 | 
				
			||||||
  "author": "crccheck",
 | 
					  "author": "crccheck",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ version: "3.7"
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  hello-world:
 | 
					  hello-world:
 | 
				
			||||||
    container_name: hello-world
 | 
					    container_name: hello-world
 | 
				
			||||||
    image: crccheck/hello-world
 | 
					    image: crccheck/hello-world:latest
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${APP_PORT}:8000
 | 
					      - ${APP_PORT}:8000
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ version: "3.7"
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  minecraft-server:
 | 
					  minecraft-server:
 | 
				
			||||||
    container_name: minecraft-server
 | 
					    container_name: minecraft-server
 | 
				
			||||||
    image: itzg/minecraft-server
 | 
					    image: itzg/minecraft-server:latest
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${APP_PORT}:25565
 | 
					      - ${APP_PORT}:25565
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,13 +6,18 @@
 | 
				
			||||||
  "exposable": true,
 | 
					  "exposable": true,
 | 
				
			||||||
  "id": "mixpost-pro",
 | 
					  "id": "mixpost-pro",
 | 
				
			||||||
  "tipi_version": 6,
 | 
					  "tipi_version": 6,
 | 
				
			||||||
  "version": "0.7",
 | 
					  "version": "latest",
 | 
				
			||||||
  "categories": ["social", "utilities"],
 | 
					  "categories": [
 | 
				
			||||||
 | 
					    "social",
 | 
				
			||||||
 | 
					    "utilities"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "description": "Mixpost it's the coolest Self-hosted social media management software.",
 | 
					  "description": "Mixpost it's the coolest Self-hosted social media management software.",
 | 
				
			||||||
  "short_desc": "Self-hosted social media management. Schedule and organize your social content. ",
 | 
					  "short_desc": "Self-hosted social media management. Schedule and organize your social content. ",
 | 
				
			||||||
  "author": "Inovector",
 | 
					  "author": "Inovector",
 | 
				
			||||||
  "source": "https://github.com/inovector/mixpost",
 | 
					  "source": "https://github.com/inovector/mixpost",
 | 
				
			||||||
  "supported_architectures": ["amd64"],
 | 
					  "supported_architectures": [
 | 
				
			||||||
 | 
					    "amd64"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "form_fields": [
 | 
					  "form_fields": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "type": "random",
 | 
					      "type": "random",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,11 @@
 | 
				
			||||||
  "id": "openbooks",
 | 
					  "id": "openbooks",
 | 
				
			||||||
  "tipi_version": 3,
 | 
					  "tipi_version": 3,
 | 
				
			||||||
  "url_suffix": "/openbooks/",
 | 
					  "url_suffix": "/openbooks/",
 | 
				
			||||||
  "version": "v4.5.0",
 | 
					  "version": "4.5.0",
 | 
				
			||||||
  "categories": ["media", "books"],
 | 
					  "categories": [
 | 
				
			||||||
 | 
					    "media",
 | 
				
			||||||
 | 
					    "books"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "description": "Openbooks allows you to download ebooks from irc.irchighway.net quickly and easily.  ",
 | 
					  "description": "Openbooks allows you to download ebooks from irc.irchighway.net quickly and easily.  ",
 | 
				
			||||||
  "short_desc": "Search and Download eBooks",
 | 
					  "short_desc": "Search and Download eBooks",
 | 
				
			||||||
  "author": "https://github.com/evan-buss",
 | 
					  "author": "https://github.com/evan-buss",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@
 | 
				
			||||||
    "social"
 | 
					    "social"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "description": "",
 | 
					  "description": "",
 | 
				
			||||||
  "tipi_version": 3,
 | 
					  "tipi_version": 4,
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "2.4.9.2",
 | 
				
			||||||
  "short_desc": "Open source alternative frontend for TikTok made using PHP ",
 | 
					  "short_desc": "Open source alternative frontend for TikTok made using PHP ",
 | 
				
			||||||
  "author": "pablouser1",
 | 
					  "author": "pablouser1",
 | 
				
			||||||
  "source": "https://github.com/pablouser1/ProxiTok",
 | 
					  "source": "https://github.com/pablouser1/ProxiTok",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ services:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  proxitok:
 | 
					  proxitok:
 | 
				
			||||||
    container_name: proxitok
 | 
					    container_name: proxitok
 | 
				
			||||||
    image: ghcr.io/pablouser1/proxitok:master
 | 
					    image: ghcr.io/pablouser1/proxitok:v2.4.9.2
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${APP_PORT}:80
 | 
					      - ${APP_PORT}:80
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
version: '3.7'
 | 
					version: '3.7'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  vikunja-frontend:
 | 
					  vikunja:
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - vikunja-api
 | 
					      - vikunja-api
 | 
				
			||||||
    container_name: vikunja-frontend
 | 
					    container_name: vikunja
 | 
				
			||||||
    image: vikunja/frontend:0.20.5
 | 
					    image: vikunja/frontend:0.20.5
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ services:
 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - tipi_main_network
 | 
					      - tipi_main_network
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  vikunja:
 | 
					  vikunja-proxy:
 | 
				
			||||||
    container_name: vikunja
 | 
					    container_name: vikunja
 | 
				
			||||||
    image: nginx
 | 
					    image: nginx
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,12 +5,9 @@
 | 
				
			||||||
  "exposable": true,
 | 
					  "exposable": true,
 | 
				
			||||||
  "port": 8103,
 | 
					  "port": 8103,
 | 
				
			||||||
  "id": "your-spotify",
 | 
					  "id": "your-spotify",
 | 
				
			||||||
  "tipi_version": 3,
 | 
					  "tipi_version": 4,
 | 
				
			||||||
  "version": "latest",
 | 
					  "version": "1.6.0",
 | 
				
			||||||
  "categories": [
 | 
					  "categories": ["music", "utilities"],
 | 
				
			||||||
    "music",
 | 
					 | 
				
			||||||
    "utilities"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "description": "Self hosted Spotify tracking dashboard.",
 | 
					  "description": "Self hosted Spotify tracking dashboard.",
 | 
				
			||||||
  "short_desc": "Self hosted Spotify tracking dashboard.",
 | 
					  "short_desc": "Self hosted Spotify tracking dashboard.",
 | 
				
			||||||
  "author": "Yooooomi",
 | 
					  "author": "Yooooomi",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ version: "3"
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  your-spotify:
 | 
					  your-spotify:
 | 
				
			||||||
    container_name: your-spotify
 | 
					    container_name: your-spotify
 | 
				
			||||||
    image: yooooomi/your_spotify_client
 | 
					    image: yooooomi/your_spotify_client:1.6.0
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - your-spotify-server
 | 
					      - your-spotify-server
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@
 | 
				
			||||||
    "@types/jest": "^28.1.6",
 | 
					    "@types/jest": "^28.1.6",
 | 
				
			||||||
    "@types/js-yaml": "^4.0.5",
 | 
					    "@types/js-yaml": "^4.0.5",
 | 
				
			||||||
    "@types/node": "^18.6.2",
 | 
					    "@types/node": "^18.6.2",
 | 
				
			||||||
 | 
					    "@types/semver": "^7.5.0",
 | 
				
			||||||
    "commitizen": "^4.2.5",
 | 
					    "commitizen": "^4.2.5",
 | 
				
			||||||
    "eslint": "^8.22.0",
 | 
					    "eslint": "^8.22.0",
 | 
				
			||||||
    "eslint-plugin-json-schema-validator": "^4.0.1",
 | 
					    "eslint-plugin-json-schema-validator": "^4.0.1",
 | 
				
			||||||
| 
						 | 
					@ -33,6 +34,8 @@
 | 
				
			||||||
    "husky": "^8.0.1",
 | 
					    "husky": "^8.0.1",
 | 
				
			||||||
    "jest": "^28.1.3",
 | 
					    "jest": "^28.1.3",
 | 
				
			||||||
    "js-yaml": "^4.1.0",
 | 
					    "js-yaml": "^4.1.0",
 | 
				
			||||||
 | 
					    "prettier": "^2.8.8",
 | 
				
			||||||
 | 
					    "semver": "^7.5.2",
 | 
				
			||||||
    "ts-jest": "^28.0.7",
 | 
					    "ts-jest": "^28.0.7",
 | 
				
			||||||
    "typescript": "^4.7.4"
 | 
					    "typescript": "^4.7.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1237
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
							
						
						
									
										1237
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user