Compare commits
4 Commits
9124cc90aa
...
21d65af470
| Author | SHA1 | Date | |
|---|---|---|---|
| 21d65af470 | |||
| 4699ffe6af | |||
| b2a7787e45 | |||
| 8cb1f4142b |
@@ -70,12 +70,13 @@ async fn main() -> Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
|
const BODY_LIMIT: usize = 1000 * 1000 * 1000 * 5;
|
||||||
let pool = PgPool::connect(&database_url).await.unwrap();
|
let pool = PgPool::connect(&database_url).await.unwrap();
|
||||||
sqlx::migrate!().run(&pool).await?;
|
sqlx::migrate!().run(&pool).await?;
|
||||||
let file_repository = FileRepository::new(pool.clone())?;
|
let file_repository = FileRepository::new(pool.clone())?;
|
||||||
let routes_file = routes_file(file_repository.clone())
|
let routes_file = routes_file(file_repository.clone())
|
||||||
.route_layer(middleware::from_fn(mw_require_auth))
|
.route_layer(middleware::from_fn(mw_require_auth))
|
||||||
.layer(DefaultBodyLimit::disable());
|
.layer(DefaultBodyLimit::max(BODY_LIMIT));
|
||||||
|
|
||||||
let user_repository = UserRepository::new(pool)?;
|
let user_repository = UserRepository::new(pool)?;
|
||||||
let routes_auth =
|
let routes_auth =
|
||||||
|
|||||||
@@ -120,6 +120,25 @@ impl FileRepository {
|
|||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn stream_part(
|
||||||
|
&self,
|
||||||
|
file_id: i64,
|
||||||
|
user_id: i64,
|
||||||
|
from: u64,
|
||||||
|
to: u64,
|
||||||
|
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>, LoftError> {
|
||||||
|
info!(
|
||||||
|
"Fetching metadata of file \"{}\" from file_records",
|
||||||
|
file_id
|
||||||
|
);
|
||||||
|
let record = self.get_file(file_id, user_id).await?;
|
||||||
|
info!("Streaming chunk of file \"{}\"", file_id);
|
||||||
|
let reader = self.op.reader(&record.storage_key).await.unwrap();
|
||||||
|
let stream = reader.into_bytes_stream(from..to).await.unwrap();
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_file(&self, file_id: i64, user_id: i64) -> Result<FileRecord, LoftError> {
|
pub async fn get_file(&self, file_id: i64, user_id: i64) -> Result<FileRecord, LoftError> {
|
||||||
info!(
|
info!(
|
||||||
"Fetching metadata of file \"{}\" from file_records",
|
"Fetching metadata of file \"{}\" from file_records",
|
||||||
@@ -412,12 +431,14 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let _downloaded = file_repository
|
let chunks: Vec<_> = file_repository
|
||||||
.download_file(file_record.id, user.id)
|
.download_file(file_record.id, user.id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.collect()
|
||||||
//TODO: add assert
|
.await;
|
||||||
|
let downloaded: Vec<u8> = chunks.into_iter().flat_map(|c| c.unwrap()).collect();
|
||||||
|
assert_eq!(downloaded, vec![0u8; 10]);
|
||||||
|
|
||||||
truncate_file_records(&file_repository.pool).await;
|
truncate_file_records(&file_repository.pool).await;
|
||||||
truncate_users(&user_repository.pool).await;
|
truncate_users(&user_repository.pool).await;
|
||||||
@@ -435,6 +456,45 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
async fn test_stream_part() {
|
||||||
|
let user_repository = user_repository().await.unwrap();
|
||||||
|
let user = user_repository
|
||||||
|
.create_user("username", "password_hash")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let file_repository = file_repository().await.unwrap();
|
||||||
|
truncate_file_records(&file_repository.pool).await;
|
||||||
|
|
||||||
|
let file_name = "a.png";
|
||||||
|
let file_storage_key = format!("{}-{}", file_name, uuid::Uuid::new_v4());
|
||||||
|
let file_size = file_repository
|
||||||
|
.upload_file(
|
||||||
|
stream::iter(vec![Ok(Bytes::from((0u8..10).collect::<Vec<u8>>()))]),
|
||||||
|
&file_storage_key,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let file_record = file_repository
|
||||||
|
.create_file_record(user.id, "a.jpg", file_size, &file_storage_key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let chunks: Vec<_> = file_repository
|
||||||
|
.stream_part(file_record.id, user.id, 2, 6)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
let partial: Vec<u8> = chunks.into_iter().flat_map(|c| c.unwrap()).collect();
|
||||||
|
assert_eq!(partial, vec![2u8, 3, 4, 5]);
|
||||||
|
|
||||||
|
truncate_file_records(&file_repository.pool).await;
|
||||||
|
truncate_users(&user_repository.pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
async fn test_delete() {
|
async fn test_delete() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use axum::{
|
|||||||
Json, Router,
|
Json, Router,
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{Multipart, Path, State},
|
extract::{Multipart, Path, State},
|
||||||
|
http::{HeaderMap, Response, StatusCode, header},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
@@ -19,6 +20,7 @@ pub fn routes_file(file_repository: FileRepository) -> Router {
|
|||||||
.route("/files", get(list_files).post(upload_file))
|
.route("/files", get(list_files).post(upload_file))
|
||||||
.route("/files/{id}", get(get_file).delete(delete_file))
|
.route("/files/{id}", get(get_file).delete(delete_file))
|
||||||
.route("/files/{id}/download", get(download_file))
|
.route("/files/{id}/download", get(download_file))
|
||||||
|
.route("/files/{id}/stream_part", get(stream_part))
|
||||||
.with_state(file_repository)
|
.with_state(file_repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +56,6 @@ async fn upload_file(
|
|||||||
Err(LoftError::UndefinedErrorType)
|
Err(LoftError::UndefinedErrorType)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
async fn get_file(
|
async fn get_file(
|
||||||
State(file_repository): State<FileRepository>,
|
State(file_repository): State<FileRepository>,
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
@@ -66,7 +67,6 @@ async fn get_file(
|
|||||||
Ok(Json(record))
|
Ok(Json(record))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
async fn download_file(
|
async fn download_file(
|
||||||
State(file_repository): State<FileRepository>,
|
State(file_repository): State<FileRepository>,
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
@@ -78,6 +78,49 @@ async fn download_file(
|
|||||||
Ok(Body::from_stream(stream))
|
Ok(Body::from_stream(stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn stream_part(
|
||||||
|
State(file_repository): State<FileRepository>,
|
||||||
|
ctx: Ctx,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(file_id): Path<u64>,
|
||||||
|
) -> Result<impl IntoResponse, LoftError> {
|
||||||
|
info!("stream_part");
|
||||||
|
let file_record = file_repository
|
||||||
|
.get_file(file_id as i64, ctx.user_id())
|
||||||
|
.await?;
|
||||||
|
let file_size = file_record.size as u64;
|
||||||
|
let (start, end): (u64, u64) = parse_range(&headers, file_size);
|
||||||
|
|
||||||
|
let stream = file_repository
|
||||||
|
.stream_part(file_id as i64, ctx.user_id(), start, end + 1)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let respones = Response::builder()
|
||||||
|
.status(StatusCode::PARTIAL_CONTENT)
|
||||||
|
.header(header::CONTENT_TYPE, file_record.file_type)
|
||||||
|
.header(header::CONTENT_LENGTH, end - start + 1)
|
||||||
|
.header(
|
||||||
|
header::CONTENT_RANGE,
|
||||||
|
format!("bytes {start}-{end}/{file_size}"),
|
||||||
|
)
|
||||||
|
.header(header::ACCEPT_RANGES, "bytes")
|
||||||
|
.body(Body::from_stream(stream))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(respones)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_range(headers: &HeaderMap, file_size: u64) -> (u64, u64) {
|
||||||
|
let range = headers.get(header::RANGE);
|
||||||
|
let str = range.unwrap().to_str().unwrap();
|
||||||
|
let strip = str.strip_prefix("bytes=").unwrap();
|
||||||
|
let tuple = strip.split_once("-").unwrap();
|
||||||
|
let start = tuple.0.parse::<u64>().unwrap();
|
||||||
|
let end = tuple.1.parse::<u64>().unwrap_or(file_size - 1);
|
||||||
|
|
||||||
|
(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
async fn delete_file(
|
async fn delete_file(
|
||||||
State(file_repository): State<FileRepository>,
|
State(file_repository): State<FileRepository>,
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
@@ -103,7 +146,7 @@ async fn list_files(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use axum::{Router, middleware};
|
use axum::{Router, http::header, middleware};
|
||||||
use axum_test::{
|
use axum_test::{
|
||||||
TestServer,
|
TestServer,
|
||||||
multipart::{MultipartForm, Part},
|
multipart::{MultipartForm, Part},
|
||||||
@@ -291,6 +334,71 @@ mod tests {
|
|||||||
truncate(&file_repository.pool).await;
|
truncate(&file_repository.pool).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
async fn test_stream_part_range() {
|
||||||
|
let user_repository = user_repository().await;
|
||||||
|
let token = create_test_session(&user_repository).await;
|
||||||
|
let file_repository = file_repository().await;
|
||||||
|
truncate(&file_repository.pool).await;
|
||||||
|
|
||||||
|
let server = test_server().await;
|
||||||
|
let post_res = server
|
||||||
|
.post("/api/files")
|
||||||
|
.add_header(axum::http::header::COOKIE, format!("auth-token={token}"))
|
||||||
|
.multipart(MultipartForm::new().add_part(
|
||||||
|
"file",
|
||||||
|
Part::bytes(b"0123456789".to_vec()).file_name("a.txt"),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
let id = post_res.json::<serde_json::Value>()["id"].as_i64().unwrap();
|
||||||
|
|
||||||
|
let res = server
|
||||||
|
.get(&format!("/api/files/{id}/stream_part"))
|
||||||
|
.add_header(axum::http::header::COOKIE, format!("auth-token={token}"))
|
||||||
|
.add_header(axum::http::header::RANGE, "bytes=2-5")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(axum::http::StatusCode::PARTIAL_CONTENT);
|
||||||
|
assert_eq!(res.as_bytes(), b"2345".as_ref());
|
||||||
|
assert_eq!(res.header(header::CONTENT_RANGE), "bytes 2-5/10");
|
||||||
|
assert_eq!(res.header(header::CONTENT_LENGTH), "4");
|
||||||
|
assert_eq!(res.header(header::ACCEPT_RANGES), "bytes");
|
||||||
|
truncate(&file_repository.pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
async fn test_stream_part_open_ended() {
|
||||||
|
let user_repository = user_repository().await;
|
||||||
|
let token = create_test_session(&user_repository).await;
|
||||||
|
let file_repository = file_repository().await;
|
||||||
|
truncate(&file_repository.pool).await;
|
||||||
|
|
||||||
|
let server = test_server().await;
|
||||||
|
let post_res = server
|
||||||
|
.post("/api/files")
|
||||||
|
.add_header(axum::http::header::COOKIE, format!("auth-token={token}"))
|
||||||
|
.multipart(MultipartForm::new().add_part(
|
||||||
|
"file",
|
||||||
|
Part::bytes(b"0123456789".to_vec()).file_name("a.txt"),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
let id = post_res.json::<serde_json::Value>()["id"].as_i64().unwrap();
|
||||||
|
|
||||||
|
let res = server
|
||||||
|
.get(&format!("/api/files/{id}/stream_part"))
|
||||||
|
.add_header(axum::http::header::COOKIE, format!("auth-token={token}"))
|
||||||
|
.add_header(axum::http::header::RANGE, "bytes=0-")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(axum::http::StatusCode::PARTIAL_CONTENT);
|
||||||
|
assert_eq!(res.as_bytes(), b"0123456789".as_ref());
|
||||||
|
assert_eq!(res.header(header::CONTENT_RANGE), "bytes 0-9/10");
|
||||||
|
assert_eq!(res.header(header::CONTENT_LENGTH), "10");
|
||||||
|
truncate(&file_repository.pool).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
async fn test_download_file_not_found() {
|
async fn test_download_file_not_found() {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
onclick={(e) => e.stopPropagation()}
|
onclick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{#if isVideo}
|
{#if isVideo}
|
||||||
<VideoPreview src={`/api/files/${file.id}/download`} />
|
<VideoPreview src={`/api/files/${file.id}/stream_part`} />
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-x-0 top-0 z-10 flex items-center justify-between bg-linear-to-b from-black/70 to-transparent px-4 py-3"
|
class="pointer-events-none absolute inset-x-0 top-0 z-10 flex items-center justify-between bg-linear-to-b from-black/70 to-transparent px-4 py-3"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"devDependencies": {
|
|
||||||
"prettier": "^3.8.3",
|
|
||||||
"prettier-plugin-svelte": "^4.1.0",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.8.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
253
pnpm-lock.yaml
generated
253
pnpm-lock.yaml
generated
@@ -1,253 +0,0 @@
|
|||||||
lockfileVersion: '9.0'
|
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|
||||||
importers:
|
|
||||||
|
|
||||||
.:
|
|
||||||
devDependencies:
|
|
||||||
prettier:
|
|
||||||
specifier: ^3.8.3
|
|
||||||
version: 3.8.3
|
|
||||||
prettier-plugin-svelte:
|
|
||||||
specifier: ^4.1.0
|
|
||||||
version: 4.1.0(prettier@3.8.3)(svelte@5.56.0)
|
|
||||||
prettier-plugin-tailwindcss:
|
|
||||||
specifier: ^0.8.0
|
|
||||||
version: 0.8.0(prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.56.0))(prettier@3.8.3)
|
|
||||||
|
|
||||||
packages:
|
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
|
||||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
|
||||||
|
|
||||||
'@jridgewell/remapping@2.3.5':
|
|
||||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2':
|
|
||||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
|
||||||
engines: {node: '>=6.0.0'}
|
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5':
|
|
||||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.31':
|
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
|
||||||
|
|
||||||
'@sveltejs/acorn-typescript@1.0.10':
|
|
||||||
resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==}
|
|
||||||
peerDependencies:
|
|
||||||
acorn: ^8.9.0
|
|
||||||
|
|
||||||
'@types/estree@1.0.9':
|
|
||||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7':
|
|
||||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
|
||||||
|
|
||||||
acorn@8.16.0:
|
|
||||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
|
||||||
engines: {node: '>=0.4.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
aria-query@5.3.1:
|
|
||||||
resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
|
|
||||||
axobject-query@4.1.0:
|
|
||||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
|
|
||||||
clsx@2.1.1:
|
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
devalue@5.8.1:
|
|
||||||
resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==}
|
|
||||||
|
|
||||||
esm-env@1.2.2:
|
|
||||||
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
|
|
||||||
|
|
||||||
esrap@2.2.9:
|
|
||||||
resolution: {integrity: sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==}
|
|
||||||
peerDependencies:
|
|
||||||
'@typescript-eslint/types': ^8.2.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@typescript-eslint/types':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
is-reference@3.0.3:
|
|
||||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
|
||||||
|
|
||||||
locate-character@3.0.0:
|
|
||||||
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
|
||||||
|
|
||||||
magic-string@0.30.21:
|
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
|
||||||
|
|
||||||
prettier-plugin-svelte@4.1.0:
|
|
||||||
resolution: {integrity: sha512-YZkhA2Q9oOerFFG9tq+2f98WYT7Z2JgrybJrAyrB78jpsH9i/DdgplXemehuFPgsldetFNCcR/yCcYlDjPy94Q==}
|
|
||||||
engines: {node: '>=20'}
|
|
||||||
peerDependencies:
|
|
||||||
prettier: ^3.0.0
|
|
||||||
svelte: ^5.0.0
|
|
||||||
|
|
||||||
prettier-plugin-tailwindcss@0.8.0:
|
|
||||||
resolution: {integrity: sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==}
|
|
||||||
engines: {node: '>=20.19'}
|
|
||||||
peerDependencies:
|
|
||||||
'@ianvs/prettier-plugin-sort-imports': '*'
|
|
||||||
'@prettier/plugin-hermes': '*'
|
|
||||||
'@prettier/plugin-oxc': '*'
|
|
||||||
'@prettier/plugin-pug': '*'
|
|
||||||
'@shopify/prettier-plugin-liquid': '*'
|
|
||||||
'@trivago/prettier-plugin-sort-imports': '*'
|
|
||||||
'@zackad/prettier-plugin-twig': '*'
|
|
||||||
prettier: ^3.0
|
|
||||||
prettier-plugin-astro: '*'
|
|
||||||
prettier-plugin-css-order: '*'
|
|
||||||
prettier-plugin-jsdoc: '*'
|
|
||||||
prettier-plugin-marko: '*'
|
|
||||||
prettier-plugin-multiline-arrays: '*'
|
|
||||||
prettier-plugin-organize-attributes: '*'
|
|
||||||
prettier-plugin-organize-imports: '*'
|
|
||||||
prettier-plugin-sort-imports: '*'
|
|
||||||
prettier-plugin-svelte: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@ianvs/prettier-plugin-sort-imports':
|
|
||||||
optional: true
|
|
||||||
'@prettier/plugin-hermes':
|
|
||||||
optional: true
|
|
||||||
'@prettier/plugin-oxc':
|
|
||||||
optional: true
|
|
||||||
'@prettier/plugin-pug':
|
|
||||||
optional: true
|
|
||||||
'@shopify/prettier-plugin-liquid':
|
|
||||||
optional: true
|
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
|
||||||
optional: true
|
|
||||||
'@zackad/prettier-plugin-twig':
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-astro:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-css-order:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-jsdoc:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-marko:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-multiline-arrays:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-organize-attributes:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-organize-imports:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-sort-imports:
|
|
||||||
optional: true
|
|
||||||
prettier-plugin-svelte:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
prettier@3.8.3:
|
|
||||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
svelte@5.56.0:
|
|
||||||
resolution: {integrity: sha512-kTXr26t1bchFp28ROrb957LtbujpBmBDibmqMGziVpUs7awBi96TGgX6SovrA8BNoEUDVRK2Fb9FkeYlGspoVg==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
zimmerframe@1.1.4:
|
|
||||||
resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
|
|
||||||
|
|
||||||
snapshots:
|
|
||||||
|
|
||||||
'@jridgewell/gen-mapping@0.3.13':
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
|
||||||
|
|
||||||
'@jridgewell/remapping@2.3.5':
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/gen-mapping': 0.3.13
|
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2': {}
|
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.31':
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
|
||||||
|
|
||||||
'@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)':
|
|
||||||
dependencies:
|
|
||||||
acorn: 8.16.0
|
|
||||||
|
|
||||||
'@types/estree@1.0.9': {}
|
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7': {}
|
|
||||||
|
|
||||||
acorn@8.16.0: {}
|
|
||||||
|
|
||||||
aria-query@5.3.1: {}
|
|
||||||
|
|
||||||
axobject-query@4.1.0: {}
|
|
||||||
|
|
||||||
clsx@2.1.1: {}
|
|
||||||
|
|
||||||
devalue@5.8.1: {}
|
|
||||||
|
|
||||||
esm-env@1.2.2: {}
|
|
||||||
|
|
||||||
esrap@2.2.9:
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
|
||||||
|
|
||||||
is-reference@3.0.3:
|
|
||||||
dependencies:
|
|
||||||
'@types/estree': 1.0.9
|
|
||||||
|
|
||||||
locate-character@3.0.0: {}
|
|
||||||
|
|
||||||
magic-string@0.30.21:
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
|
||||||
|
|
||||||
prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.56.0):
|
|
||||||
dependencies:
|
|
||||||
prettier: 3.8.3
|
|
||||||
svelte: 5.56.0
|
|
||||||
|
|
||||||
prettier-plugin-tailwindcss@0.8.0(prettier-plugin-svelte@4.1.0(prettier@3.8.3)(svelte@5.56.0))(prettier@3.8.3):
|
|
||||||
dependencies:
|
|
||||||
prettier: 3.8.3
|
|
||||||
optionalDependencies:
|
|
||||||
prettier-plugin-svelte: 4.1.0(prettier@3.8.3)(svelte@5.56.0)
|
|
||||||
|
|
||||||
prettier@3.8.3: {}
|
|
||||||
|
|
||||||
svelte@5.56.0:
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/remapping': 2.3.5
|
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
|
||||||
'@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0)
|
|
||||||
'@types/estree': 1.0.9
|
|
||||||
'@types/trusted-types': 2.0.7
|
|
||||||
acorn: 8.16.0
|
|
||||||
aria-query: 5.3.1
|
|
||||||
axobject-query: 4.1.0
|
|
||||||
clsx: 2.1.1
|
|
||||||
devalue: 5.8.1
|
|
||||||
esm-env: 1.2.2
|
|
||||||
esrap: 2.2.9
|
|
||||||
is-reference: 3.0.3
|
|
||||||
locate-character: 3.0.0
|
|
||||||
magic-string: 0.30.21
|
|
||||||
zimmerframe: 1.1.4
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@typescript-eslint/types'
|
|
||||||
|
|
||||||
zimmerframe@1.1.4: {}
|
|
||||||
Reference in New Issue
Block a user