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 }}"
|
72
.github/workflows/ci.yml
vendored
72
.github/workflows/ci.yml
vendored
|
@ -1,9 +1,8 @@
|
|||
name: Tipi CI
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -26,7 +25,7 @@ jobs:
|
|||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
|
||||
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
|
@ -44,3 +43,70 @@ jobs:
|
|||
|
||||
- name: Run linter
|
||||
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
|
||||
id: updated-files
|
||||
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
|
||||
run: |
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import fs from 'fs';
|
||||
import jsyaml from 'js-yaml';
|
||||
import fs from "fs";
|
||||
import semver from "semver";
|
||||
import jsyaml from "js-yaml";
|
||||
|
||||
interface AppConfig {
|
||||
id: string;
|
||||
|
@ -18,11 +19,18 @@ interface AppConfig {
|
|||
available: boolean;
|
||||
}
|
||||
|
||||
const networkExceptions = ['pihole', 'tailscale', 'homeassistant', 'plex', 'zerotier', 'gladys'];
|
||||
const networkExceptions = [
|
||||
"pihole",
|
||||
"tailscale",
|
||||
"homeassistant",
|
||||
"plex",
|
||||
"zerotier",
|
||||
"gladys",
|
||||
];
|
||||
const getAppConfigs = (): AppConfig[] => {
|
||||
const apps: AppConfig[] = [];
|
||||
|
||||
const appsDir = fs.readdirSync('./apps');
|
||||
const appsDir = fs.readdirSync("./apps");
|
||||
|
||||
appsDir.forEach((app: string) => {
|
||||
const path = `./apps/${app}/config.json`;
|
||||
|
@ -36,7 +44,7 @@ const getAppConfigs = (): AppConfig[] => {
|
|||
apps.push(config);
|
||||
}
|
||||
} 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;
|
||||
};
|
||||
|
||||
describe('App configs', () => {
|
||||
it('Get app config should return at least one app', () => {
|
||||
describe("App configs", () => {
|
||||
it("Get app config should return at least one app", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
expect(apps.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('Each app should have an id', () => {
|
||||
describe("Each app should have an id", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.id).toBeDefined();
|
||||
test(app.id, () => {
|
||||
expect(app.id).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Each app should have a md description', () => {
|
||||
describe("Each app should have a md description", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
const path = `./apps/${app.id}/metadata/description.md`;
|
||||
test(app.id, () => {
|
||||
const path = `./apps/${app.id}/metadata/description.md`;
|
||||
|
||||
if (fs.existsSync(path)) {
|
||||
const description = fs.readFileSync(path).toString();
|
||||
expect(description).toBeDefined();
|
||||
} else {
|
||||
console.error(`Missing description for app ${app.id}`);
|
||||
expect(true).toBe(false);
|
||||
}
|
||||
if (fs.existsSync(path)) {
|
||||
const description = fs.readFileSync(path).toString();
|
||||
expect(description).toBeDefined();
|
||||
} else {
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.categories).toBeDefined();
|
||||
expect(app.categories).toBeInstanceOf(Array);
|
||||
test(app.id, () => {
|
||||
expect(app.categories).toBeDefined();
|
||||
expect(app.categories).toBeInstanceOf(Array);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Each app should have a name', () => {
|
||||
describe("Each app should have a name", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.name).toBeDefined();
|
||||
test(app.id, () => {
|
||||
expect(app.name).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Each app should have a description', () => {
|
||||
describe("Each app should have a description", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.description).toBeDefined();
|
||||
test(app.id, () => {
|
||||
expect(app.description).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Each app should have a port', () => {
|
||||
describe("Each app should have a port", () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.port).toBeDefined();
|
||||
expect(app.port).toBeGreaterThan(999);
|
||||
expect(app.port).toBeLessThan(65535);
|
||||
test(app.id, () => {
|
||||
expect(app.port).toBeDefined();
|
||||
expect(app.port).toBeGreaterThan(999);
|
||||
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 ports = appConfigs.map((app) => app.port);
|
||||
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 ids = appConfigs.map((app) => app.id);
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(app.tipi_version).toBeDefined();
|
||||
expect(app.tipi_version).toBeGreaterThan(0);
|
||||
test(app.id, () => {
|
||||
expect(app.version).toBeDefined();
|
||||
expect(app.tipi_version).toBeDefined();
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
|
||||
test(app.id, () => {
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(fs.existsSync(`./apps/${app.id}/metadata`)).toBe(true);
|
||||
test(app.id, () => {
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
expect(fs.existsSync(`./apps/${app.id}/metadata/logo.jpg`)).toBe(true);
|
||||
test(app.id, () => {
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
const dockerComposeFile = fs.readFileSync(`./apps/${app.id}/docker-compose.yml`).toString();
|
||||
|
||||
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].container_name).toBe(app.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('Each app should have network tipi_main_network', () => {
|
||||
const apps = getAppConfigs();
|
||||
|
||||
apps.forEach((app) => {
|
||||
if (!networkExceptions.includes(app.id)) {
|
||||
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);
|
||||
|
||||
expect(dockerCompose.services[app.id]).toBeDefined();
|
||||
expect(dockerCompose.services[app.id].container_name).toBe(app.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (!dockerCompose.services[app.id].networks) {
|
||||
console.error(app.id);
|
||||
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();
|
||||
|
||||
apps.forEach((app) => {
|
||||
test(app.id, () => {
|
||||
if (!networkExceptions.includes(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].networks).toBeDefined();
|
||||
expect(dockerCompose.services[app.id].networks).toStrictEqual([
|
||||
"tipi_main_network",
|
||||
]);
|
||||
}
|
||||
|
||||
expect(dockerCompose.services[app.id].networks).toBeDefined();
|
||||
expect(dockerCompose.services[app.id].networks).toStrictEqual(['tipi_main_network']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"available": true,
|
||||
"exposable": true,
|
||||
"tipi_version": 11,
|
||||
"version": "0.107.32",
|
||||
"version": "v0.107.32",
|
||||
"port": 8104,
|
||||
"id": "adguard",
|
||||
"categories": [
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
"exposable": true,
|
||||
"id": "autobrr",
|
||||
"tipi_version": 6,
|
||||
"version": "1.26.1",
|
||||
"categories": ["media"],
|
||||
"version": "v1.26.2",
|
||||
"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.",
|
||||
"short_desc": "Automation for downloads.",
|
||||
"author": "autobrr",
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
"exposable": true,
|
||||
"id": "chatgpt-ui",
|
||||
"tipi_version": 9,
|
||||
"version": "2.5.5",
|
||||
"categories": ["ai"],
|
||||
"version": "v2.5.5",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"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",
|
||||
"author": "https://github.com/WongSaang",
|
||||
"source": "https://github.com/WongSaang/chatgpt-ui",
|
||||
"supported_architectures": ["amd64"],
|
||||
"supported_architectures": [
|
||||
"amd64"
|
||||
],
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "random",
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "3"
|
|||
services:
|
||||
deemix:
|
||||
container_name: deemix
|
||||
image: registry.gitlab.com/bockiii/deemix-docker
|
||||
image: registry.gitlab.com/bockiii/deemix-docker:latest
|
||||
ports:
|
||||
- ${APP_PORT}:6595
|
||||
volumes:
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
"port": 8000,
|
||||
"id": "hello-world",
|
||||
"tipi_version": 2,
|
||||
"version": "1.0.0",
|
||||
"categories": ["utilities"],
|
||||
"version": "latest",
|
||||
"categories": [
|
||||
"utilities"
|
||||
],
|
||||
"description": "Hello World web server in under 2 MB",
|
||||
"short_desc": "Hello World web server in under 2 MB",
|
||||
"author": "crccheck",
|
||||
|
|
|
@ -2,7 +2,7 @@ version: "3.7"
|
|||
services:
|
||||
hello-world:
|
||||
container_name: hello-world
|
||||
image: crccheck/hello-world
|
||||
image: crccheck/hello-world:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- ${APP_PORT}:8000
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "3.7"
|
|||
services:
|
||||
minecraft-server:
|
||||
container_name: minecraft-server
|
||||
image: itzg/minecraft-server
|
||||
image: itzg/minecraft-server:latest
|
||||
ports:
|
||||
- ${APP_PORT}:25565
|
||||
environment:
|
||||
|
|
|
@ -6,13 +6,18 @@
|
|||
"exposable": true,
|
||||
"id": "mixpost-pro",
|
||||
"tipi_version": 6,
|
||||
"version": "0.7",
|
||||
"categories": ["social", "utilities"],
|
||||
"version": "latest",
|
||||
"categories": [
|
||||
"social",
|
||||
"utilities"
|
||||
],
|
||||
"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. ",
|
||||
"author": "Inovector",
|
||||
"source": "https://github.com/inovector/mixpost",
|
||||
"supported_architectures": ["amd64"],
|
||||
"supported_architectures": [
|
||||
"amd64"
|
||||
],
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "random",
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
"id": "openbooks",
|
||||
"tipi_version": 3,
|
||||
"url_suffix": "/openbooks/",
|
||||
"version": "v4.5.0",
|
||||
"categories": ["media", "books"],
|
||||
"version": "4.5.0",
|
||||
"categories": [
|
||||
"media",
|
||||
"books"
|
||||
],
|
||||
"description": "Openbooks allows you to download ebooks from irc.irchighway.net quickly and easily. ",
|
||||
"short_desc": "Search and Download eBooks",
|
||||
"author": "https://github.com/evan-buss",
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"social"
|
||||
],
|
||||
"description": "",
|
||||
"tipi_version": 3,
|
||||
"version": "1.0.0",
|
||||
"tipi_version": 4,
|
||||
"version": "2.4.9.2",
|
||||
"short_desc": "Open source alternative frontend for TikTok made using PHP ",
|
||||
"author": "pablouser1",
|
||||
"source": "https://github.com/pablouser1/ProxiTok",
|
||||
|
|
|
@ -3,7 +3,7 @@ services:
|
|||
|
||||
proxitok:
|
||||
container_name: proxitok
|
||||
image: ghcr.io/pablouser1/proxitok:master
|
||||
image: ghcr.io/pablouser1/proxitok:v2.4.9.2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- ${APP_PORT}:80
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
vikunja-frontend:
|
||||
vikunja:
|
||||
depends_on:
|
||||
- vikunja-api
|
||||
container_name: vikunja-frontend
|
||||
container_name: vikunja
|
||||
image: vikunja/frontend:0.20.5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
vikunja:
|
||||
vikunja-proxy:
|
||||
container_name: vikunja
|
||||
image: nginx
|
||||
ports:
|
||||
|
|
|
@ -5,12 +5,9 @@
|
|||
"exposable": true,
|
||||
"port": 8103,
|
||||
"id": "your-spotify",
|
||||
"tipi_version": 3,
|
||||
"version": "latest",
|
||||
"categories": [
|
||||
"music",
|
||||
"utilities"
|
||||
],
|
||||
"tipi_version": 4,
|
||||
"version": "1.6.0",
|
||||
"categories": ["music", "utilities"],
|
||||
"description": "Self hosted Spotify tracking dashboard.",
|
||||
"short_desc": "Self hosted Spotify tracking dashboard.",
|
||||
"author": "Yooooomi",
|
||||
|
|
|
@ -3,7 +3,7 @@ version: "3"
|
|||
services:
|
||||
your-spotify:
|
||||
container_name: your-spotify
|
||||
image: yooooomi/your_spotify_client
|
||||
image: yooooomi/your_spotify_client:1.6.0
|
||||
depends_on:
|
||||
- your-spotify-server
|
||||
restart: unless-stopped
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"@types/jest": "^28.1.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "^18.6.2",
|
||||
"@types/semver": "^7.5.0",
|
||||
"commitizen": "^4.2.5",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-json-schema-validator": "^4.0.1",
|
||||
|
@ -33,6 +34,8 @@
|
|||
"husky": "^8.0.1",
|
||||
"jest": "^28.1.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"semver": "^7.5.2",
|
||||
"ts-jest": "^28.0.7",
|
||||
"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