chore: normalize line endings
This commit is contained in:
parent
82817cb50c
commit
4d127a038b
Binary file not shown.
|
|
@ -9,7 +9,6 @@ import { useFullscreen } from "@/hooks/use-fullscreen";
|
|||
import { CanvasRenderer } from "@/services/renderer/canvas-renderer";
|
||||
import type { RootNode } from "@/services/renderer/nodes/root-node";
|
||||
import { buildScene } from "@/services/renderer/scene-builder";
|
||||
import { getLastFrameTime } from "@/lib/time";
|
||||
import { PreviewInteractionOverlay } from "./preview-interaction-overlay";
|
||||
import { BookmarkNoteOverlay } from "./bookmark-note-overlay";
|
||||
import { GuideOverlay } from "./guide-overlay";
|
||||
|
|
@ -134,12 +133,7 @@ function PreviewCanvas({
|
|||
|
||||
const render = useCallback(() => {
|
||||
if (canvasRef.current && renderTree && !renderingRef.current) {
|
||||
const time = editor.playback.getCurrentTime();
|
||||
const lastFrameTime = getLastFrameTime({
|
||||
duration: renderTree.duration,
|
||||
fps: renderer.fps,
|
||||
});
|
||||
const renderTime = Math.min(time, lastFrameTime);
|
||||
const renderTime = editor.playback.getCurrentTime();
|
||||
const frame = Math.floor(renderTime * renderer.fps);
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ export class MediaManager {
|
|||
|
||||
try {
|
||||
await storageService.saveMediaAsset({ projectId, mediaAsset: newAsset });
|
||||
this.editor.project.ratchetFpsForImportedMedia({
|
||||
importedAssets: [newAsset],
|
||||
});
|
||||
return newAsset;
|
||||
} catch (error) {
|
||||
console.error("Failed to save media asset:", error);
|
||||
|
|
|
|||
|
|
@ -13,19 +13,19 @@ export class PlaybackManager {
|
|||
|
||||
constructor(private editor: EditorCore) {
|
||||
this.editor.timeline.subscribe(() => {
|
||||
const duration = this.editor.timeline.getTotalDuration();
|
||||
if (this.currentTime > duration && duration > 0) {
|
||||
this.currentTime = duration;
|
||||
const maxTime = this.editor.timeline.getLastSeekableTime();
|
||||
if (this.currentTime > maxTime && maxTime > 0) {
|
||||
this.currentTime = maxTime;
|
||||
this.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
play(): void {
|
||||
const duration = this.editor.timeline.getTotalDuration();
|
||||
const maxTime = this.editor.timeline.getLastSeekableTime();
|
||||
|
||||
if (duration > 0) {
|
||||
if (this.currentTime >= duration) {
|
||||
if (maxTime > 0) {
|
||||
if (this.currentTime >= maxTime) {
|
||||
this.seek({ time: 0 });
|
||||
}
|
||||
}
|
||||
|
|
@ -50,8 +50,8 @@ export class PlaybackManager {
|
|||
}
|
||||
|
||||
seek({ time }: { time: number }): void {
|
||||
const duration = this.editor.timeline.getTotalDuration();
|
||||
this.currentTime = Math.max(0, Math.min(duration, time));
|
||||
const maxTime = this.editor.timeline.getLastSeekableTime();
|
||||
this.currentTime = Math.max(0, Math.min(maxTime, time));
|
||||
this.notify();
|
||||
|
||||
window.dispatchEvent(
|
||||
|
|
@ -154,16 +154,16 @@ export class PlaybackManager {
|
|||
this.lastUpdate = now;
|
||||
|
||||
const newTime = this.currentTime + delta;
|
||||
const duration = this.editor.timeline.getTotalDuration();
|
||||
const maxTime = this.editor.timeline.getLastSeekableTime();
|
||||
|
||||
if (duration > 0 && newTime >= duration) {
|
||||
if (maxTime > 0 && newTime >= maxTime) {
|
||||
this.pause();
|
||||
this.currentTime = duration;
|
||||
this.currentTime = maxTime;
|
||||
this.notify();
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("playback-seek", {
|
||||
detail: { time: duration },
|
||||
detail: { time: maxTime },
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import {
|
|||
import { loadFonts } from "@/lib/fonts/google-fonts";
|
||||
import { DEFAULTS } from "@/lib/timeline/defaults";
|
||||
import { getElementFontFamilies } from "@/lib/timeline/element-utils";
|
||||
import { getRaisedProjectFpsForImportedMedia } from "@/lib/project/fps";
|
||||
import type { MediaAsset } from "@/lib/media/types";
|
||||
|
||||
export interface MigrationState {
|
||||
isMigrating: boolean;
|
||||
|
|
@ -492,6 +494,23 @@ export class ProjectManager {
|
|||
command.execute();
|
||||
}
|
||||
|
||||
ratchetFpsForImportedMedia({
|
||||
importedAssets,
|
||||
}: {
|
||||
importedAssets: Array<Pick<MediaAsset, "type" | "fps">>;
|
||||
}): number | null {
|
||||
if (!this.active) return null;
|
||||
|
||||
const nextFps = getRaisedProjectFpsForImportedMedia({
|
||||
currentFps: this.active.settings.fps,
|
||||
importedAssets,
|
||||
});
|
||||
if (nextFps === null) return null;
|
||||
|
||||
new UpdateProjectSettingsCommand({ fps: nextFps }).execute();
|
||||
return nextFps;
|
||||
}
|
||||
|
||||
async updateThumbnail({ thumbnail }: { thumbnail: string }): Promise<void> {
|
||||
if (!this.active) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { CanvasRenderer } from "@/services/renderer/canvas-renderer";
|
|||
import { SceneExporter } from "@/services/renderer/scene-exporter";
|
||||
import { buildScene } from "@/services/renderer/scene-builder";
|
||||
import { createTimelineAudioBuffer } from "@/lib/media/audio";
|
||||
import { formatTimeCode, getLastFrameTime } from "@/lib/time";
|
||||
import { formatTimeCode } from "@/lib/time";
|
||||
import { downloadBlob } from "@/utils/browser";
|
||||
|
||||
type SnapshotResult =
|
||||
|
|
@ -81,9 +81,7 @@ export class RendererManager {
|
|||
}
|
||||
|
||||
const { canvasSize, fps } = activeProject.settings;
|
||||
const currentTime = this.editor.playback.getCurrentTime();
|
||||
const lastFrameTime = getLastFrameTime({ duration, fps });
|
||||
const renderTime = Math.min(currentTime, lastFrameTime);
|
||||
const renderTime = this.editor.playback.getCurrentTime();
|
||||
|
||||
const renderer = new CanvasRenderer({
|
||||
width: canvasSize.width,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
AnimationValue,
|
||||
} from "@/lib/animation/types";
|
||||
import { calculateTotalDuration } from "@/lib/timeline";
|
||||
import { getLastFrameTime } from "@/lib/time";
|
||||
import {
|
||||
AddTrackCommand,
|
||||
RemoveTrackCommand,
|
||||
|
|
@ -224,6 +225,13 @@ export class TimelineManager {
|
|||
return calculateTotalDuration({ tracks: this.getTracks() });
|
||||
}
|
||||
|
||||
getLastSeekableTime(): number {
|
||||
const duration = this.getTotalDuration();
|
||||
const fps = this.editor.project.getActive()?.settings.fps;
|
||||
if (!fps || duration <= 0) return duration;
|
||||
return getLastFrameTime({ duration, fps });
|
||||
}
|
||||
|
||||
getTrackById({ trackId }: { trackId: string }): TimelineTrack | null {
|
||||
return this.getTracks().find((track) => track.id === trackId) ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import type { MediaAsset } from "@/lib/media/types";
|
|||
import { generateUUID } from "@/utils/id";
|
||||
import { storageService } from "@/services/storage/service";
|
||||
import { hasMediaId } from "@/lib/timeline/element-utils";
|
||||
import { getHighestImportedVideoFps } from "@/lib/project/fps";
|
||||
import { UpdateProjectSettingsCommand } from "@/lib/commands/project";
|
||||
|
||||
export class AddMediaAssetCommand extends Command {
|
||||
private assetId: string;
|
||||
private savedAssets: MediaAsset[] | null = null;
|
||||
private createdAsset: MediaAsset | null = null;
|
||||
private previousProjectFps: number | null = null;
|
||||
private appliedProjectFps: number | null = null;
|
||||
|
||||
constructor(
|
||||
private projectId: string,
|
||||
|
|
@ -31,6 +35,10 @@ export class AddMediaAssetCommand extends Command {
|
|||
editor.media.setAssets({
|
||||
assets: [...this.savedAssets, this.createdAsset],
|
||||
});
|
||||
this.previousProjectFps = editor.project.getActiveOrNull()?.settings.fps ?? null;
|
||||
this.appliedProjectFps = editor.project.ratchetFpsForImportedMedia({
|
||||
importedAssets: [this.createdAsset],
|
||||
});
|
||||
|
||||
storageService
|
||||
.saveMediaAsset({
|
||||
|
|
@ -64,6 +72,8 @@ export class AddMediaAssetCommand extends Command {
|
|||
editor.timeline.deleteElements({ elements: orphanedElements });
|
||||
}
|
||||
|
||||
this.restoreProjectFpsAfterFailedSave({ editor });
|
||||
|
||||
if (storageService.isQuotaExceededError({ error })) {
|
||||
toast.error("Not enough browser storage", {
|
||||
description: error instanceof Error ? error.message : undefined,
|
||||
|
|
@ -90,4 +100,28 @@ export class AddMediaAssetCommand extends Command {
|
|||
getAssetId(): string {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
private restoreProjectFpsAfterFailedSave({
|
||||
editor,
|
||||
}: {
|
||||
editor: EditorCore;
|
||||
}): void {
|
||||
if (this.previousProjectFps === null || this.appliedProjectFps === null) return;
|
||||
|
||||
const activeProject = editor.project.getActiveOrNull();
|
||||
if (!activeProject) return;
|
||||
if (activeProject.settings.fps !== this.appliedProjectFps) return;
|
||||
|
||||
const highestRemainingVideoFps = getHighestImportedVideoFps({
|
||||
mediaAssets: editor.media.getAssets(),
|
||||
});
|
||||
if (
|
||||
highestRemainingVideoFps !== null &&
|
||||
highestRemainingVideoFps >= this.appliedProjectFps
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
new UpdateProjectSettingsCommand({ fps: this.previousProjectFps }).execute();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
getHighestImportedVideoFps,
|
||||
getRaisedProjectFpsForImportedMedia,
|
||||
} from "@/lib/project/fps";
|
||||
|
||||
describe("getHighestImportedVideoFps", () => {
|
||||
test("returns the highest valid video fps", () => {
|
||||
expect(
|
||||
getHighestImportedVideoFps({
|
||||
mediaAssets: [
|
||||
{ type: "audio" },
|
||||
{ type: "video", fps: 30 },
|
||||
{ type: "image", fps: 120 },
|
||||
{ type: "video", fps: 60 },
|
||||
],
|
||||
}),
|
||||
).toBe(60);
|
||||
});
|
||||
|
||||
test("ignores missing and invalid fps values", () => {
|
||||
expect(
|
||||
getHighestImportedVideoFps({
|
||||
mediaAssets: [
|
||||
{ type: "video" },
|
||||
{ type: "video", fps: 0 },
|
||||
{ type: "video", fps: -10 },
|
||||
{ type: "audio", fps: 120 },
|
||||
],
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRaisedProjectFpsForImportedMedia", () => {
|
||||
test("raises the project fps to match a higher-fps import", () => {
|
||||
expect(
|
||||
getRaisedProjectFpsForImportedMedia({
|
||||
currentFps: 30,
|
||||
importedAssets: [{ type: "video", fps: 60 }],
|
||||
}),
|
||||
).toBe(60);
|
||||
});
|
||||
|
||||
test("does not lower the project fps for lower-fps imports", () => {
|
||||
expect(
|
||||
getRaisedProjectFpsForImportedMedia({
|
||||
currentFps: 60,
|
||||
importedAssets: [{ type: "video", fps: 10 }],
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("ignores non-video imports", () => {
|
||||
expect(
|
||||
getRaisedProjectFpsForImportedMedia({
|
||||
currentFps: 30,
|
||||
importedAssets: [
|
||||
{ type: "image", fps: 60 },
|
||||
{ type: "audio", fps: 120 },
|
||||
],
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import type { MediaAsset } from "@/lib/media/types";
|
||||
|
||||
type MediaAssetFpsInput = Pick<MediaAsset, "type" | "fps">;
|
||||
|
||||
export function getHighestImportedVideoFps({
|
||||
mediaAssets,
|
||||
}: {
|
||||
mediaAssets: MediaAssetFpsInput[];
|
||||
}): number | null {
|
||||
let highestFps: number | null = null;
|
||||
|
||||
for (const asset of mediaAssets) {
|
||||
const fps = asset.fps ?? Number.NaN;
|
||||
if (asset.type !== "video") continue;
|
||||
if (!Number.isFinite(fps) || fps <= 0) continue;
|
||||
|
||||
highestFps = highestFps === null ? fps : Math.max(highestFps, fps);
|
||||
}
|
||||
|
||||
return highestFps;
|
||||
}
|
||||
|
||||
export function getRaisedProjectFpsForImportedMedia({
|
||||
currentFps,
|
||||
importedAssets,
|
||||
}: {
|
||||
currentFps: number;
|
||||
importedAssets: MediaAssetFpsInput[];
|
||||
}): number | null {
|
||||
const highestImportedVideoFps = getHighestImportedVideoFps({
|
||||
mediaAssets: importedAssets,
|
||||
});
|
||||
|
||||
if (highestImportedVideoFps === null || highestImportedVideoFps <= currentFps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return highestImportedVideoFps;
|
||||
}
|
||||
|
|
@ -207,7 +207,8 @@ export function getSnappedSeekTime({
|
|||
fps: number;
|
||||
}): number {
|
||||
const snappedTime = snapTimeToFrame({ time: rawTime, fps });
|
||||
return Math.max(0, Math.min(duration, snappedTime));
|
||||
const lastFrame = getLastFrameTime({ duration, fps });
|
||||
return Math.max(0, Math.min(lastFrame, snappedTime));
|
||||
}
|
||||
|
||||
export function getLastFrameTime({
|
||||
|
|
|
|||
29
bun.lock
29
bun.lock
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "opencut",
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
"next": "^16.1.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.7.5",
|
||||
"turbo": "^2.8.20",
|
||||
"typescript": "5.8.3",
|
||||
},
|
||||
},
|
||||
|
|
@ -808,6 +809,18 @@
|
|||
|
||||
"@tsconfig/node18": ["@tsconfig/node18@1.0.3", "", {}, "sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ=="],
|
||||
|
||||
"@turbo/darwin-64": ["@turbo/darwin-64@2.8.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ=="],
|
||||
|
||||
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.8.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw=="],
|
||||
|
||||
"@turbo/linux-64": ["@turbo/linux-64@2.8.20", "", { "os": "linux", "cpu": "x64" }, "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA=="],
|
||||
|
||||
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.8.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA=="],
|
||||
|
||||
"@turbo/windows-64": ["@turbo/windows-64@2.8.20", "", { "os": "win32", "cpu": "x64" }, "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw=="],
|
||||
|
||||
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||
|
||||
"@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="],
|
||||
|
|
@ -1720,19 +1733,7 @@
|
|||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"turbo": ["turbo@2.7.5", "", { "optionalDependencies": { "turbo-darwin-64": "2.7.5", "turbo-darwin-arm64": "2.7.5", "turbo-linux-64": "2.7.5", "turbo-linux-arm64": "2.7.5", "turbo-windows-64": "2.7.5", "turbo-windows-arm64": "2.7.5" }, "bin": { "turbo": "bin/turbo" } }, "sha512-7Imdmg37joOloTnj+DPrab9hIaQcDdJ5RwSzcauo/wMOSAgO+A/I/8b3hsGGs6PWQz70m/jkPgdqWsfNKtwwDQ=="],
|
||||
|
||||
"turbo-darwin-64": ["turbo-darwin-64@2.7.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-nN3wfLLj4OES/7awYyyM7fkU8U8sAFxsXau2bYJwAWi6T09jd87DgHD8N31zXaJ7LcpyppHWPRI2Ov9MuZEwnQ=="],
|
||||
|
||||
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.7.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wCoDHMiTf3FgLAbZHDDx/unNNonSGhsF5AbbYODbxnpYyoKDpEYacUEPjZD895vDhNvYCH0Nnk24YsP4n/cD6g=="],
|
||||
|
||||
"turbo-linux-64": ["turbo-linux-64@2.7.5", "", { "os": "linux", "cpu": "x64" }, "sha512-KKPvhOmJMmzWj/yjeO4LywkQ85vOJyhru7AZk/+c4B6OUh/odQ++SiIJBSbTG2lm1CuV5gV5vXZnf/2AMlu3Zg=="],
|
||||
|
||||
"turbo-linux-arm64": ["turbo-linux-arm64@2.7.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-8PIva4L6BQhiPikUTds9lSFSHXVDAsEvV6QUlgwPsXrtXVQMVi6Sv9p+IxtlWQFvGkdYJUgX9GnK2rC030Xcmw=="],
|
||||
|
||||
"turbo-windows-64": ["turbo-windows-64@2.7.5", "", { "os": "win32", "cpu": "x64" }, "sha512-rupskv/mkIUgQXzX/wUiK00mKMorQcK8yzhGFha/D5lm05FEnLx8dsip6rWzMcVpvh+4GUMA56PgtnOgpel2AA=="],
|
||||
|
||||
"turbo-windows-arm64": ["turbo-windows-arm64@2.7.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-G377Gxn6P42RnCzfMyDvsqQV7j69kVHKlhz9J4RhtJOB5+DyY4yYh/w0oTIxZQ4JRMmhjwLu3w9zncMoQ6nNDw=="],
|
||||
"turbo": ["turbo@2.8.20", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.20", "@turbo/darwin-arm64": "2.8.20", "@turbo/linux-64": "2.8.20", "@turbo/linux-arm64": "2.8.20", "@turbo/windows-64": "2.8.20", "@turbo/windows-arm64": "2.8.20" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ=="],
|
||||
|
||||
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
"next": "^16.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.7.5",
|
||||
"turbo": "^2.8.20",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const toolsEnvSchema = z.object({
|
||||
// Node
|
||||
NODE_ENV: z.enum(["development", "production", "test"]),
|
||||
ANALYZE: z.string().optional(),
|
||||
NEXT_RUNTIME: z.enum(["nodejs", "edge"]).optional(),
|
||||
|
||||
// Public
|
||||
NEXT_PUBLIC_SITE_URL: z.url().default("http://localhost:3000"),
|
||||
|
||||
// Server
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.startsWith("postgres://")
|
||||
.or(z.string().startsWith("postgresql://")),
|
||||
|
||||
BETTER_AUTH_SECRET: z.string(),
|
||||
UPSTASH_REDIS_REST_URL: z.url(),
|
||||
UPSTASH_REDIS_REST_TOKEN: z.string(),
|
||||
CLOUDFLARE_ACCOUNT_ID: z.string(),
|
||||
R2_ACCESS_KEY_ID: z.string(),
|
||||
R2_SECRET_ACCESS_KEY: z.string(),
|
||||
R2_BUCKET_NAME: z.string(),
|
||||
});
|
||||
|
||||
export type ToolsEnv = z.infer<typeof toolsEnvSchema>;
|
||||
|
||||
export const toolsEnv = toolsEnvSchema.parse(process.env);
|
||||
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="node" />
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const webEnvSchema = z.object({
|
||||
// Node
|
||||
NODE_ENV: z.enum(["development", "production", "test"]),
|
||||
ANALYZE: z.string().optional(),
|
||||
NEXT_RUNTIME: z.enum(["nodejs", "edge"]).optional(),
|
||||
|
||||
// Public
|
||||
NEXT_PUBLIC_SITE_URL: z.url().default("http://localhost:3000"),
|
||||
NEXT_PUBLIC_MARBLE_API_URL: z.url(),
|
||||
|
||||
// Server
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.startsWith("postgres://")
|
||||
.or(z.string().startsWith("postgresql://")),
|
||||
|
||||
BETTER_AUTH_SECRET: z.string(),
|
||||
UPSTASH_REDIS_REST_URL: z.url(),
|
||||
UPSTASH_REDIS_REST_TOKEN: z.string(),
|
||||
MARBLE_WORKSPACE_KEY: z.string(),
|
||||
FREESOUND_CLIENT_ID: z.string(),
|
||||
FREESOUND_API_KEY: z.string(),
|
||||
CLOUDFLARE_ACCOUNT_ID: z.string(),
|
||||
R2_ACCESS_KEY_ID: z.string(),
|
||||
R2_SECRET_ACCESS_KEY: z.string(),
|
||||
R2_BUCKET_NAME: z.string(),
|
||||
MODAL_TRANSCRIPTION_URL: z.url(),
|
||||
});
|
||||
|
||||
export type WebEnv = z.infer<typeof webEnvSchema>;
|
||||
|
||||
export const webEnv = webEnvSchema.parse(process.env);
|
||||
Loading…
Reference in New Issue