diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index ad2e9cf6..b6c43e17 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -22,10 +22,6 @@ jobs: VERSION=$(npm run version --silent) 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: runs-on: ubuntu-latest needs: create-tag @@ -33,9 +29,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Log in to Docker Hub uses: docker/login-action@v3 with: diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 9e43a43b..f2b607bb 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -22,10 +22,6 @@ jobs: VERSION=$(npm run version --silent) 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: needs: create-tag runs-on: ubuntu-latest @@ -33,9 +29,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Log in to Docker Hub uses: docker/login-action@v3 with: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e3a28de3..3260d16a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -5,7 +5,7 @@ on: version: required: true 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: jobs: @@ -16,8 +16,18 @@ jobs: - name: Checkout 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 uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "runtipi/runtipi-cloud-builder" - name: Login to GitHub Container Registry uses: docker/login-action@v3 @@ -36,8 +46,6 @@ jobs: platforms: linux/amd64 push: true 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 run: mkdir -p bin @@ -79,24 +87,28 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Create .env.local + run: | + echo "LOG_LEVEL=debug" > .env.local + - name: Run install script if: ${{ inputs.version }} run: | curl -s https://raw.githubusercontent.com/runtipi/runtipi/${{ inputs.version }}/scripts/install.sh > 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 if: ${{ !inputs.version }} run: | - ./scripts/install.sh --version e2e + ./scripts/install.sh --version e2e --env-file ${{ github.workspace }}/.env.local cd .. - uses: pnpm/action-setup@v4.0.0 name: Install pnpm id: pnpm-install with: - version: 9.4.0 + version: 9.12.2 run_install: false - name: Get pnpm store directory @@ -112,7 +124,7 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Create .env.e2e file with Droplet IP + - name: Create .env.e2e file run: | echo "SERVER_IP=$(hostname -I | awk '{print $1}')" > .env.e2e echo "POSTGRES_PASSWORD=$(grep POSTGRES_PASSWORD runtipi/.env | cut -d '=' -f2)" >> .env.e2e @@ -125,13 +137,31 @@ jobs: with: 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 + if: steps.cache-playwright-binaries.outputs.cache-hit != 'true' run: npx playwright install --with-deps - name: Run Playwright tests id: run-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 if: always() with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a94d8ef..e4b11831 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,10 +17,6 @@ jobs: VERSION=$(npm run version --silent) echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT - - uses: rickstaa/action-create-tag@v1 - with: - tag: ${{ steps.get_tag.outputs.tagname }} - build-images: if: github.repository == 'runtipi/runtipi' needs: create-tag @@ -29,9 +25,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Log in to Docker Hub uses: docker/login-action@v3 with: diff --git a/e2e/0005-guest-dashboard.spec.ts b/e2e/0005-guest-dashboard.spec.ts index 4738469c..1b266f2b 100644 --- a/e2e/0005-guest-dashboard.spec.ts +++ b/e2e/0005-guest-dashboard.spec.ts @@ -1,11 +1,11 @@ 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 { clearDatabase, db } from './helpers/db'; import { setSettings } from './helpers/settings'; 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 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.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.reload(); + await expect(page.getByText('No apps to display')).toBeVisible(); }); test('logged out users can see the apps on the guest dashboard', async ({ browser }) => { - await setSettings({ guestDashboard: true }); await db.insert(appTable).values({ config: {}, 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 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(); const locator = page.locator('text=Actual Budget'); 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(); 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.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.goto('/'); + await page.reload(); // We should be redirected to the login page await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); diff --git a/e2e/fixtures/fixtures.ts b/e2e/fixtures/fixtures.ts index 34e648ad..791e3ed3 100644 --- a/e2e/fixtures/fixtures.ts +++ b/e2e/fixtures/fixtures.ts @@ -1,13 +1,13 @@ import { type BrowserContext, type Page, expect } from '@playwright/test'; -import { userTable } from '@runtipi/db'; import * as argon2 from 'argon2'; +import { userTable } from '../../packages/backend/src/core/database/schema'; import { testUser } from '../helpers/constants'; import { db } from '../helpers/db'; export const createTestUser = async () => { // Create user in database 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) => { diff --git a/e2e/helpers/db.ts b/e2e/helpers/db.ts index 3fdc2dd5..6be536b7 100644 --- a/e2e/helpers/db.ts +++ b/e2e/helpers/db.ts @@ -1,6 +1,6 @@ -import * as schema from '@runtipi/db'; import { drizzle } from 'drizzle-orm/node-postgres'; 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`; diff --git a/e2e/helpers/settings.ts b/e2e/helpers/settings.ts index c564b68f..95031733 100644 --- a/e2e/helpers/settings.ts +++ b/e2e/helpers/settings.ts @@ -1,11 +1,21 @@ import { promises } from 'node:fs'; import path from 'node:path'; -import type { settingsSchema } from '@runtipi/shared'; -import { pathExists } from '@runtipi/shared/node'; 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 { db } from './db'; -export const setSettings = async (settings: z.infer) => { +const pathExists = async (path: string) => { + try { + await promises.access(path); + return true; + } catch { + return false; + } +}; + +export const setSettings = async (settings: Partial>) => { await promises.mkdir(path.join(BASE_PATH, 'state'), { recursive: true }); 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) => { - const seenPath = path.join(BASE_PATH, 'state', 'seen-welcome'); - - if (seen && !(await pathExists(seenPath))) { - return promises.writeFile(seenPath, ''); - } - - if (!seen && (await pathExists(seenPath))) { - return promises.unlink(seenPath); - } - + await db.update(userTable).set({ hasSeenWelcome: seen }); return Promise.resolve(); }; diff --git a/package.json b/package.json index 1a3530da..6b817e10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "runtipi", - "version": "1.0.0", + "version": "3.7.0", "description": "", "packageManager": "pnpm@9.12.2", "scripts": { @@ -9,9 +9,12 @@ "start": "node ./index.js", "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 ", + "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", "lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched", - "lint": "biome check" + "lint": "biome check", + "version": "echo $npm_package_version" }, "keywords": [], "author": "", @@ -19,6 +22,15 @@ "devDependencies": { "@biomejs/biome": "^1.9.2", "@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" + }, + "dependencies": { + "argon2": "^0.41.1", + "drizzle-orm": "^0.33.0", + "pg": "^8.12.0", + "zod": "^3.23.8" } } diff --git a/packages/backend/src/modules/apps/app-catalog.service.ts b/packages/backend/src/modules/apps/app-catalog.service.ts index e5823a23..92e9ce21 100644 --- a/packages/backend/src/modules/apps/app-catalog.service.ts +++ b/packages/backend/src/modules/apps/app-catalog.service.ts @@ -2,6 +2,7 @@ import { TranslatableError } from '@/common/error/translatable-error'; import { notEmpty, pLimit } from '@/common/helpers/file-helpers'; import { type Architecture, ConfigurationService } from '@/core/config/configuration.service'; import type { App } from '@/core/database/schema'; +import { LoggerService } from '@/core/logger/logger.service'; import { Injectable } from '@nestjs/common'; import MiniSearch from 'minisearch'; import { AppFilesManager } from './app-files-manager'; @@ -30,6 +31,7 @@ export class AppCatalogService { private readonly filesManager: AppFilesManager, private readonly configuration: ConfigurationService, private readonly appsRepository: AppsRepository, + private readonly logger: LoggerService, ) {} private appsAvailable: AppList | null = null; @@ -39,7 +41,7 @@ export class AppCatalogService { private async constructSingleApp(app: App) { 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); return info ? { app, info, updateInfo } : null; } catch (e) { @@ -211,7 +213,10 @@ export class AppCatalogService { } public async getGuestDashboardApps() { + this.logger.debug('Getting guest dashboard apps'); const apps = await this.appsRepository.getGuestDashboardApps(); + this.logger.debug(`Got ${apps.length} guest dashboard apps`); + return this.constructAppList(apps); } } diff --git a/packages/frontend/src/components/header/header.tsx b/packages/frontend/src/components/header/header.tsx index 9a6a109b..07fab12b 100644 --- a/packages/frontend/src/components/header/header.tsx +++ b/packages/frontend/src/components/header/header.tsx @@ -4,7 +4,7 @@ import { useUIStore } from '@/stores/ui-store'; import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react'; import { useMutation } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; import { NavBar } from '../navbar/navbar'; @@ -21,8 +21,13 @@ export const Header = (props: HeaderProps) => { const { t } = useTranslation(); + const navigate = useNavigate(); + const logout = useMutation({ ...logoutMutation(), + onSuccess: () => { + navigate('/', { replace: true }); + }, }); return ( diff --git a/packages/frontend/src/modules/dashboard/pages/guest-dashboard.tsx b/packages/frontend/src/modules/dashboard/pages/guest-dashboard.tsx index ebae887b..82e6fa2e 100644 --- a/packages/frontend/src/modules/dashboard/pages/guest-dashboard.tsx +++ b/packages/frontend/src/modules/dashboard/pages/guest-dashboard.tsx @@ -15,6 +15,7 @@ import { IconLock, IconLockOff } from '@tabler/icons-react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import './guest-dashboard.css'; +import { EmptyPage } from '@/components/empty-page/empty-page'; const Tile = ({ data }: { data: GuestAppsDto['installed'][number] }) => { const { t } = useTranslation(); @@ -88,6 +89,7 @@ export const GuestDashboard = () => {
+ {data.installed.length === 0 && }
{data.installed.map((data) => { return ; diff --git a/playwright.config.ts b/playwright.config.ts index 69559b69..dcc18ae2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,6 +3,8 @@ import { defineConfig, devices } from '@playwright/test'; /** * See https://playwright.dev/docs/test-configuration. */ + +// biome-ignore lint/style/noDefaultExport: needed for Playwright to work export default defineConfig({ globalSetup: require.resolve('./e2e/helpers/global-setup'), testDir: './e2e', @@ -11,21 +13,18 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 0 : 0, /* Opt out of parallel tests on CI. */ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { /* Base URL to use in actions like `await page.goto('/')`. */ 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 */ - trace: 'on-first-retry', - - video: 'on', + trace: 'on', + video: 'retain-on-failure', }, // timeout: 5000, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15492874..d0e04c04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,19 @@ settings: 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: '@biomejs/biome': specifier: ^1.9.2 @@ -14,6 +27,15 @@ importers: '@hey-api/openapi-ts': specifier: ^0.53.7 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: specifier: ^2.2.3 version: 2.2.3 @@ -293,7 +315,7 @@ importers: version: 3.1.7 geist: 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: specifier: ^23.15.1 version: 23.15.1 @@ -1676,6 +1698,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 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': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -3850,6 +3877,11 @@ packages: fs.realpath@1.0.0: 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: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -5019,6 +5051,16 @@ packages: pkg-types@1.2.0: 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: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -7571,6 +7613,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.48.2': + dependencies: + playwright: 1.48.2 + '@popperjs/core@2.11.8': {} '@prisma/instrumentation@5.19.1': @@ -9842,6 +9888,9 @@ snapshots: fs.realpath@1.0.0: optional: true + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -9859,9 +9908,9 @@ snapshots: wide-align: 1.1.5 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: - 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: {} @@ -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/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: '@next/env': 14.2.11 '@swc/helpers': 0.5.5 @@ -10991,6 +11040,7 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.11 '@next/swc-win32-x64-msvc': 14.2.11 '@opentelemetry/api': 1.9.0 + '@playwright/test': 1.48.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -11283,6 +11333,14 @@ snapshots: mlly: 1.7.1 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: {} postcss@8.4.31: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ef2ec889..8246101e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,3 @@ prefer-workspace-packages: true packages: - - "packages/**" + - "./packages/**" diff --git a/scripts/install.sh b/scripts/install.sh index 6b08ca22..21bf61d6 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -18,6 +18,7 @@ fi UPDATE="false" VERSION="latest" ASSET="runtipi-cli-linux-x86_64.tar.gz" +ENV_FILE="" while [ -n "${1-}" ]; do case "$1" in @@ -36,6 +37,13 @@ while [ -n "${1-}" ]; do echo "Option --asset requires a value" && exit 1 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 break @@ -209,4 +217,10 @@ else fi 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