ci: fix releases (#1755)

* ci: fix releases

* ci: e2e tests

* chore: remove QEMU setup

* chore: fix e2e imports

* fix: guest dashboard not displaying apps correctly

* chore: add logs in playwright report

* feat(install.sh): allow env-file arg during install

* chore(e2e): reload page after logouts
This commit is contained in:
Nicolas Meienberger 2024-11-09 15:16:08 +01:00 committed by GitHub
parent 58234abe40
commit f5180e6de6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 186 additions and 68 deletions

View File

@ -22,10 +22,6 @@ jobs:
VERSION=$(npm run version --silent) VERSION=$(npm run version --silent)
echo "tagname=v${VERSION}-alpha.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT echo "tagname=v${VERSION}-alpha.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.get_tag.outputs.tagname }}
build-images: build-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: create-tag needs: create-tag
@ -33,9 +29,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -22,10 +22,6 @@ jobs:
VERSION=$(npm run version --silent) VERSION=$(npm run version --silent)
echo "tagname=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT echo "tagname=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.get_tag.outputs.tagname }}
build-images: build-images:
needs: create-tag needs: create-tag
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -33,9 +29,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -5,7 +5,7 @@ on:
version: version:
required: true required: true
type: string type: string
description: 'Version to test (e.g. v1.6.0-beta.1)' description: "Version to test (e.g. v1.6.0-beta.1)"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -16,8 +16,18 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with:
version: "lab:latest"
driver: cloud
endpoint: "runtipi/runtipi-cloud-builder"
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@ -36,8 +46,6 @@ jobs:
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ghcr.io/runtipi/runtipi:e2e tags: ghcr.io/runtipi/runtipi:e2e
cache-from: type=registry,ref=ghcr.io/runtipi/runtipi:buildcache
cache-to: type=registry,ref=ghcr.io/runtipi/runtipi:buildcache,mode=max
- name: Create cli folder - name: Create cli folder
run: mkdir -p bin run: mkdir -p bin
@ -79,24 +87,28 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Create .env.local
run: |
echo "LOG_LEVEL=debug" > .env.local
- name: Run install script - name: Run install script
if: ${{ inputs.version }} if: ${{ inputs.version }}
run: | run: |
curl -s https://raw.githubusercontent.com/runtipi/runtipi/${{ inputs.version }}/scripts/install.sh > install.sh curl -s https://raw.githubusercontent.com/runtipi/runtipi/${{ inputs.version }}/scripts/install.sh > install.sh
chmod +x install.sh chmod +x install.sh
./install.sh --version ${{ inputs.version }} --asset runtipi-cli-linux-x86_64.tar.gz ./install.sh --version ${{ inputs.version }} --asset runtipi-cli-linux-x86_64.tar.gz --env-file ${{ github.workspace }}/.env.local
- name: Run install script - name: Run install script
if: ${{ !inputs.version }} if: ${{ !inputs.version }}
run: | run: |
./scripts/install.sh --version e2e ./scripts/install.sh --version e2e --env-file ${{ github.workspace }}/.env.local
cd .. cd ..
- uses: pnpm/action-setup@v4.0.0 - uses: pnpm/action-setup@v4.0.0
name: Install pnpm name: Install pnpm
id: pnpm-install id: pnpm-install
with: with:
version: 9.4.0 version: 9.12.2
run_install: false run_install: false
- name: Get pnpm store directory - name: Get pnpm store directory
@ -112,7 +124,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-pnpm-store- ${{ runner.os }}-pnpm-store-
- name: Create .env.e2e file with Droplet IP - name: Create .env.e2e file
run: | run: |
echo "SERVER_IP=$(hostname -I | awk '{print $1}')" > .env.e2e echo "SERVER_IP=$(hostname -I | awk '{print $1}')" > .env.e2e
echo "POSTGRES_PASSWORD=$(grep POSTGRES_PASSWORD runtipi/.env | cut -d '=' -f2)" >> .env.e2e echo "POSTGRES_PASSWORD=$(grep POSTGRES_PASSWORD runtipi/.env | cut -d '=' -f2)" >> .env.e2e
@ -125,13 +137,31 @@ jobs:
with: with:
node-version: 20 node-version: 20
- name: Get installed playwright version
id: playwright-version
run: echo "version=$(npx playwright --version)" >> $GITHUB_OUTPUT
- name: Cache Playwright binaries
id: cache-playwright-binaries
uses: actions/cache@v2
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
if: steps.cache-playwright-binaries.outputs.cache-hit != 'true'
run: npx playwright install --with-deps run: npx playwright install --with-deps
- name: Run Playwright tests - name: Run Playwright tests
id: run-e2e id: run-e2e
run: npm run test:e2e run: npm run test:e2e
- name: Dump app logs in playwright-report folder
if: always()
run: |
mkdir -p playwright-report
cp ./runtipi/logs/* playwright-report/
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:

View File

@ -17,10 +17,6 @@ jobs:
VERSION=$(npm run version --silent) VERSION=$(npm run version --silent)
echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT
- uses: rickstaa/action-create-tag@v1
with:
tag: ${{ steps.get_tag.outputs.tagname }}
build-images: build-images:
if: github.repository == 'runtipi/runtipi' if: github.repository == 'runtipi/runtipi'
needs: create-tag needs: create-tag
@ -29,9 +25,6 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -1,11 +1,11 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { appTable } from '@runtipi/db'; import { appTable } from '../packages/backend/src/core/database/schema';
import { loginUser } from './fixtures/fixtures'; import { loginUser } from './fixtures/fixtures';
import { clearDatabase, db } from './helpers/db'; import { clearDatabase, db } from './helpers/db';
import { setSettings } from './helpers/settings'; import { setSettings } from './helpers/settings';
test.beforeEach(async () => { test.beforeEach(async () => {
test.fixme(true, 'This test is flaky due to incorrect revalidation of the guest dashboard'); test.fixme(true, 'Not working yet');
await clearDatabase(); await clearDatabase();
await setSettings({}); await setSettings({});
}); });
@ -16,14 +16,15 @@ test('user can activate the guest dashboard and see it when logged out', async (
await page.getByRole('tab', { name: 'Settings' }).click(); await page.getByRole('tab', { name: 'Settings' }).click();
await page.getByLabel('guestDashboard').setChecked(true); await page.getByLabel('guestDashboard').setChecked(true);
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Update settings' }).click();
await page.getByTestId('logout-button').click(); await page.getByTestId('logout-button').click();
await page.reload();
await expect(page.getByText('No apps to display')).toBeVisible(); await expect(page.getByText('No apps to display')).toBeVisible();
}); });
test('logged out users can see the apps on the guest dashboard', async ({ browser }) => { test('logged out users can see the apps on the guest dashboard', async ({ browser }) => {
await setSettings({ guestDashboard: true });
await db.insert(appTable).values({ await db.insert(appTable).values({
config: {}, config: {},
isVisibleOnGuestDashboard: true, isVisibleOnGuestDashboard: true,
@ -46,12 +47,24 @@ test('logged out users can see the apps on the guest dashboard', async ({ browse
const context = await browser.newContext(); const context = await browser.newContext();
const page = await context.newPage(); const page = await context.newPage();
await page.goto('/');
await loginUser(page, context);
await page.goto('/settings');
await page.getByRole('tab', { name: 'Settings' }).click();
await page.getByLabel('guestDashboard').setChecked(true);
await page.getByRole('button', { name: 'Update settings' }).click();
await page.getByTestId('logout-button').click();
await page.reload();
await expect(page.getByText(/Hello World web server/)).toBeVisible(); await expect(page.getByText(/Hello World web server/)).toBeVisible();
const locator = page.locator('text=Actual Budget'); const locator = page.locator('text=Actual Budget');
await expect(locator).not.toBeVisible(); await expect(locator).not.toBeVisible();
const [newPage] = await Promise.all([context.waitForEvent('page'), await page.getByRole('link', { name: /Hello World/ }).click()]); await page.getByRole('link', { name: /Hello World/ }).click();
const [newPage] = await Promise.all([context.waitForEvent('page'), page.getByRole('menuitem', { name: 'duckduckgo.com' }).click()]);
await newPage.waitForLoadState(); await newPage.waitForLoadState();
expect(newPage.url()).toBe('https://duckduckgo.com/'); expect(newPage.url()).toBe('https://duckduckgo.com/');
@ -66,10 +79,10 @@ test('user can deactivate the guest dashboard and not see it when logged out', a
await page.getByRole('tab', { name: 'Settings' }).click(); await page.getByRole('tab', { name: 'Settings' }).click();
await page.getByLabel('guestDashboard').setChecked(false); await page.getByLabel('guestDashboard').setChecked(false);
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Update settings' }).click();
await page.getByTestId('logout-button').click(); await page.getByTestId('logout-button').click();
await page.goto('/'); await page.reload();
// We should be redirected to the login page // We should be redirected to the login page
await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible();

View File

@ -1,13 +1,13 @@
import { type BrowserContext, type Page, expect } from '@playwright/test'; import { type BrowserContext, type Page, expect } from '@playwright/test';
import { userTable } from '@runtipi/db';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import { userTable } from '../../packages/backend/src/core/database/schema';
import { testUser } from '../helpers/constants'; import { testUser } from '../helpers/constants';
import { db } from '../helpers/db'; import { db } from '../helpers/db';
export const createTestUser = async () => { export const createTestUser = async () => {
// Create user in database // Create user in database
const password = await argon2.hash(testUser.password); const password = await argon2.hash(testUser.password);
await db.insert(userTable).values({ password, username: testUser.email, operator: true }); await db.insert(userTable).values({ password, username: testUser.email, operator: true, hasSeenWelcome: true });
}; };
export const loginUser = async (page: Page, _: BrowserContext) => { export const loginUser = async (page: Page, _: BrowserContext) => {

View File

@ -1,6 +1,6 @@
import * as schema from '@runtipi/db';
import { drizzle } from 'drizzle-orm/node-postgres'; import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg'; import { Pool } from 'pg';
import * as schema from '../../packages/backend/src/core/database/schema';
const connectionString = `postgresql://tipi:${process.env.POSTGRES_PASSWORD}@${process.env.SERVER_IP}:5432/tipi?connect_timeout=300`; const connectionString = `postgresql://tipi:${process.env.POSTGRES_PASSWORD}@${process.env.SERVER_IP}:5432/tipi?connect_timeout=300`;

View File

@ -1,11 +1,21 @@
import { promises } from 'node:fs'; import { promises } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import type { settingsSchema } from '@runtipi/shared';
import { pathExists } from '@runtipi/shared/node';
import type { z } from 'zod'; import type { z } from 'zod';
import type { settingsSchema } from '../../packages/backend/src/app.dto';
import { userTable } from '../../packages/backend/src/core/database/schema';
import { BASE_PATH } from './constants'; import { BASE_PATH } from './constants';
import { db } from './db';
export const setSettings = async (settings: z.infer<typeof settingsSchema>) => { const pathExists = async (path: string) => {
try {
await promises.access(path);
return true;
} catch {
return false;
}
};
export const setSettings = async (settings: Partial<z.infer<typeof settingsSchema>>) => {
await promises.mkdir(path.join(BASE_PATH, 'state'), { recursive: true }); await promises.mkdir(path.join(BASE_PATH, 'state'), { recursive: true });
await promises.writeFile(path.join(BASE_PATH, 'state', 'settings.json'), JSON.stringify(settings)); await promises.writeFile(path.join(BASE_PATH, 'state', 'settings.json'), JSON.stringify(settings));
}; };
@ -22,15 +32,6 @@ export const unsetPasswordChangeRequest = async () => {
}; };
export const setWelcomeSeen = async (seen: boolean) => { export const setWelcomeSeen = async (seen: boolean) => {
const seenPath = path.join(BASE_PATH, 'state', 'seen-welcome'); await db.update(userTable).set({ hasSeenWelcome: seen });
if (seen && !(await pathExists(seenPath))) {
return promises.writeFile(seenPath, '');
}
if (!seen && (await pathExists(seenPath))) {
return promises.unlink(seenPath);
}
return Promise.resolve(); return Promise.resolve();
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "runtipi", "name": "runtipi",
"version": "1.0.0", "version": "3.7.0",
"description": "", "description": "",
"packageManager": "pnpm@9.12.2", "packageManager": "pnpm@9.12.2",
"scripts": { "scripts": {
@ -9,9 +9,12 @@
"start": "node ./index.js", "start": "node ./index.js",
"start:dev": "docker compose --project-name runtipi -f docker-compose.dev.yml up --build", "start:dev": "docker compose --project-name runtipi -f docker-compose.dev.yml up --build",
"start:prod": "docker compose --project-name runtipi -f docker-compose.prod.yml up --build ", "start:prod": "docker compose --project-name runtipi -f docker-compose.prod.yml up --build ",
"test:e2e": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test",
"test:e2e:ui": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test --ui",
"gen:api-client": "openapi-ts", "gen:api-client": "openapi-ts",
"lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched", "lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched",
"lint": "biome check" "lint": "biome check",
"version": "echo $npm_package_version"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -19,6 +22,15 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.2", "@biomejs/biome": "^1.9.2",
"@hey-api/openapi-ts": "^0.53.7", "@hey-api/openapi-ts": "^0.53.7",
"@playwright/test": "^1.48.2",
"@types/pg": "^8.11.10",
"dotenv-cli": "^7.4.2",
"turbo": "^2.2.3" "turbo": "^2.2.3"
},
"dependencies": {
"argon2": "^0.41.1",
"drizzle-orm": "^0.33.0",
"pg": "^8.12.0",
"zod": "^3.23.8"
} }
} }

View File

@ -2,6 +2,7 @@ import { TranslatableError } from '@/common/error/translatable-error';
import { notEmpty, pLimit } from '@/common/helpers/file-helpers'; import { notEmpty, pLimit } from '@/common/helpers/file-helpers';
import { type Architecture, ConfigurationService } from '@/core/config/configuration.service'; import { type Architecture, ConfigurationService } from '@/core/config/configuration.service';
import type { App } from '@/core/database/schema'; import type { App } from '@/core/database/schema';
import { LoggerService } from '@/core/logger/logger.service';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import MiniSearch from 'minisearch'; import MiniSearch from 'minisearch';
import { AppFilesManager } from './app-files-manager'; import { AppFilesManager } from './app-files-manager';
@ -30,6 +31,7 @@ export class AppCatalogService {
private readonly filesManager: AppFilesManager, private readonly filesManager: AppFilesManager,
private readonly configuration: ConfigurationService, private readonly configuration: ConfigurationService,
private readonly appsRepository: AppsRepository, private readonly appsRepository: AppsRepository,
private readonly logger: LoggerService,
) {} ) {}
private appsAvailable: AppList | null = null; private appsAvailable: AppList | null = null;
@ -39,7 +41,7 @@ export class AppCatalogService {
private async constructSingleApp(app: App) { private async constructSingleApp(app: App) {
try { try {
const info = await this.filesManager.getInstalledAppInfo(app.id); const info = await this.filesManager.getAppInfoFromAppStore(app.id);
const updateInfo = await this.filesManager.getAppUpdateInfo(app.id); const updateInfo = await this.filesManager.getAppUpdateInfo(app.id);
return info ? { app, info, updateInfo } : null; return info ? { app, info, updateInfo } : null;
} catch (e) { } catch (e) {
@ -211,7 +213,10 @@ export class AppCatalogService {
} }
public async getGuestDashboardApps() { public async getGuestDashboardApps() {
this.logger.debug('Getting guest dashboard apps');
const apps = await this.appsRepository.getGuestDashboardApps(); const apps = await this.appsRepository.getGuestDashboardApps();
this.logger.debug(`Got ${apps.length} guest dashboard apps`);
return this.constructAppList(apps); return this.constructAppList(apps);
} }
} }

View File

@ -4,7 +4,7 @@ import { useUIStore } from '@/stores/ui-store';
import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react'; import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Tooltip } from 'react-tooltip'; import { Tooltip } from 'react-tooltip';
import { NavBar } from '../navbar/navbar'; import { NavBar } from '../navbar/navbar';
@ -21,8 +21,13 @@ export const Header = (props: HeaderProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const logout = useMutation({ const logout = useMutation({
...logoutMutation(), ...logoutMutation(),
onSuccess: () => {
navigate('/', { replace: true });
},
}); });
return ( return (

View File

@ -15,6 +15,7 @@ import { IconLock, IconLockOff } from '@tabler/icons-react';
import { useSuspenseQuery } from '@tanstack/react-query'; import { useSuspenseQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import './guest-dashboard.css'; import './guest-dashboard.css';
import { EmptyPage } from '@/components/empty-page/empty-page';
const Tile = ({ data }: { data: GuestAppsDto['installed'][number] }) => { const Tile = ({ data }: { data: GuestAppsDto['installed'][number] }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -88,6 +89,7 @@ export const GuestDashboard = () => {
</div> </div>
<div className="page-body"> <div className="page-body">
<div className="container-xl"> <div className="container-xl">
{data.installed.length === 0 && <EmptyPage title="GUEST_DASHBOARD_NO_APPS" subtitle="GUEST_DASHBOARD_NO_APPS_SUBTITLE" />}
<div className="row row-cards"> <div className="row row-cards">
{data.installed.map((data) => { {data.installed.map((data) => {
return <Tile key={data.app.id} data={data} />; return <Tile key={data.app.id} data={data} />;

View File

@ -3,6 +3,8 @@ import { defineConfig, devices } from '@playwright/test';
/** /**
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
// biome-ignore lint/style/noDefaultExport: needed for Playwright to work
export default defineConfig({ export default defineConfig({
globalSetup: require.resolve('./e2e/helpers/global-setup'), globalSetup: require.resolve('./e2e/helpers/global-setup'),
testDir: './e2e', testDir: './e2e',
@ -11,21 +13,18 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
/* Retry on CI only */ /* Retry on CI only */
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 0 : 0,
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: 1, workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: `http://${process.env.SERVER_IP}${process.env.SERVER_PORT ? `:${process.env.SERVER_PORT}` : ''}`, baseURL: `http://${process.env.SERVER_IP}${process.env.SERVER_PORT ? `:${process.env.SERVER_PORT}` : ''}`,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: 'on',
video: 'retain-on-failure',
video: 'on',
}, },
// timeout: 5000, // timeout: 5000,

View File

@ -7,6 +7,19 @@ settings:
importers: importers:
.: .:
dependencies:
argon2:
specifier: ^0.41.1
version: 0.41.1
drizzle-orm:
specifier: ^0.33.0
version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.6)(pg@8.12.0)(react@18.3.1)(sqlite3@5.1.7)
pg:
specifier: ^8.12.0
version: 8.12.0
zod:
specifier: ^3.23.8
version: 3.23.8
devDependencies: devDependencies:
'@biomejs/biome': '@biomejs/biome':
specifier: ^1.9.2 specifier: ^1.9.2
@ -14,6 +27,15 @@ importers:
'@hey-api/openapi-ts': '@hey-api/openapi-ts':
specifier: ^0.53.7 specifier: ^0.53.7
version: 0.53.7(typescript@5.6.2) version: 0.53.7(typescript@5.6.2)
'@playwright/test':
specifier: ^1.48.2
version: 1.48.2
'@types/pg':
specifier: ^8.11.10
version: 8.11.10
dotenv-cli:
specifier: ^7.4.2
version: 7.4.2
turbo: turbo:
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.3 version: 2.2.3
@ -293,7 +315,7 @@ importers:
version: 3.1.7 version: 3.1.7
geist: geist:
specifier: ^1.3.1 specifier: ^1.3.1
version: 1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) version: 1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
i18next: i18next:
specifier: ^23.15.1 specifier: ^23.15.1
version: 23.15.1 version: 23.15.1
@ -1676,6 +1698,11 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
'@playwright/test@1.48.2':
resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==}
engines: {node: '>=18'}
hasBin: true
'@popperjs/core@2.11.8': '@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@ -3850,6 +3877,11 @@ packages:
fs.realpath@1.0.0: fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -5019,6 +5051,16 @@ packages:
pkg-types@1.2.0: pkg-types@1.2.0:
resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==}
playwright-core@1.48.2:
resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==}
engines: {node: '>=18'}
hasBin: true
playwright@1.48.2:
resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==}
engines: {node: '>=18'}
hasBin: true
pluralize@8.0.0: pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -7571,6 +7613,10 @@ snapshots:
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
'@playwright/test@1.48.2':
dependencies:
playwright: 1.48.2
'@popperjs/core@2.11.8': {} '@popperjs/core@2.11.8': {}
'@prisma/instrumentation@5.19.1': '@prisma/instrumentation@5.19.1':
@ -9842,6 +9888,9 @@ snapshots:
fs.realpath@1.0.0: fs.realpath@1.0.0:
optional: true optional: true
fsevents@2.3.2:
optional: true
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@ -9859,9 +9908,9 @@ snapshots:
wide-align: 1.1.5 wide-align: 1.1.5
optional: true optional: true
geist@1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): geist@1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
dependencies: dependencies:
next: 14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: 14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
gensync@1.0.0-beta.2: {} gensync@1.0.0-beta.2: {}
@ -10969,7 +11018,7 @@ snapshots:
'@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/swagger': 7.4.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/swagger': 7.4.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@next/env': 14.2.11 '@next/env': 14.2.11
'@swc/helpers': 0.5.5 '@swc/helpers': 0.5.5
@ -10991,6 +11040,7 @@ snapshots:
'@next/swc-win32-ia32-msvc': 14.2.11 '@next/swc-win32-ia32-msvc': 14.2.11
'@next/swc-win32-x64-msvc': 14.2.11 '@next/swc-win32-x64-msvc': 14.2.11
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@playwright/test': 1.48.2
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
@ -11283,6 +11333,14 @@ snapshots:
mlly: 1.7.1 mlly: 1.7.1
pathe: 1.1.2 pathe: 1.1.2
playwright-core@1.48.2: {}
playwright@1.48.2:
dependencies:
playwright-core: 1.48.2
optionalDependencies:
fsevents: 2.3.2
pluralize@8.0.0: {} pluralize@8.0.0: {}
postcss@8.4.31: postcss@8.4.31:

View File

@ -1,3 +1,3 @@
prefer-workspace-packages: true prefer-workspace-packages: true
packages: packages:
- "packages/**" - "./packages/**"

View File

@ -18,6 +18,7 @@ fi
UPDATE="false" UPDATE="false"
VERSION="latest" VERSION="latest"
ASSET="runtipi-cli-linux-x86_64.tar.gz" ASSET="runtipi-cli-linux-x86_64.tar.gz"
ENV_FILE=""
while [ -n "${1-}" ]; do while [ -n "${1-}" ]; do
case "$1" in case "$1" in
@ -36,6 +37,13 @@ while [ -n "${1-}" ]; do
echo "Option --asset requires a value" && exit 1 echo "Option --asset requires a value" && exit 1
fi fi
;; ;;
--env-file)
shift # Move to the next parameter
ENV_FILE="$1" # Assign the value to ENV_FILE
if [ -z "$ENV_FILE" ]; then
echo "Option --env-file requires a value" && exit 1
fi
;;
--) --)
shift # The double dash makes them parameters shift # The double dash makes them parameters
break break
@ -209,4 +217,10 @@ else
fi fi
chmod +x ./runtipi-cli chmod +x ./runtipi-cli
sudo ./runtipi-cli start
if [[ "${ENV_FILE}" != "" ]]; then
echo "Starting runtipi with env file ${ENV_FILE}"
sudo ./runtipi-cli start --env-file "${ENV_FILE}"
else
sudo ./runtipi-cli start
fi