working on pack installation.
This commit is contained in:
@@ -48,10 +48,15 @@ pub struct CreatePackRequest {
|
|||||||
#[schema(example = json!(["messaging", "collaboration"]))]
|
#[schema(example = json!(["messaging", "collaboration"]))]
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
||||||
/// Runtime dependencies (refs of required packs)
|
/// Runtime dependencies (e.g., shell, python, nodejs)
|
||||||
|
#[serde(default)]
|
||||||
|
#[schema(example = json!(["shell", "python"]))]
|
||||||
|
pub runtime_deps: Vec<String>,
|
||||||
|
|
||||||
|
/// Pack dependencies (refs of required packs)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[schema(example = json!(["core"]))]
|
#[schema(example = json!(["core"]))]
|
||||||
pub runtime_deps: Vec<String>,
|
pub dependencies: Vec<String>,
|
||||||
|
|
||||||
/// Whether this is a standard/built-in pack
|
/// Whether this is a standard/built-in pack
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -152,10 +157,14 @@ pub struct UpdatePackRequest {
|
|||||||
#[schema(example = json!(["messaging", "collaboration", "webhooks"]))]
|
#[schema(example = json!(["messaging", "collaboration", "webhooks"]))]
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Runtime dependencies
|
/// Runtime dependencies (e.g., shell, python, nodejs)
|
||||||
#[schema(example = json!(["core", "http"]))]
|
#[schema(example = json!(["shell", "python"]))]
|
||||||
pub runtime_deps: Option<Vec<String>>,
|
pub runtime_deps: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Pack dependencies (refs of required packs)
|
||||||
|
#[schema(example = json!(["core", "http"]))]
|
||||||
|
pub dependencies: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Whether this is a standard pack
|
/// Whether this is a standard pack
|
||||||
#[schema(example = false)]
|
#[schema(example = false)]
|
||||||
pub is_standard: Option<bool>,
|
pub is_standard: Option<bool>,
|
||||||
@@ -200,10 +209,14 @@ pub struct PackResponse {
|
|||||||
#[schema(example = json!(["messaging", "collaboration"]))]
|
#[schema(example = json!(["messaging", "collaboration"]))]
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
||||||
/// Runtime dependencies
|
/// Runtime dependencies (e.g., shell, python, nodejs)
|
||||||
#[schema(example = json!(["core"]))]
|
#[schema(example = json!(["shell", "python"]))]
|
||||||
pub runtime_deps: Vec<String>,
|
pub runtime_deps: Vec<String>,
|
||||||
|
|
||||||
|
/// Pack dependencies (refs of required packs)
|
||||||
|
#[schema(example = json!(["core"]))]
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
|
|
||||||
/// Is standard pack
|
/// Is standard pack
|
||||||
#[schema(example = false)]
|
#[schema(example = false)]
|
||||||
pub is_standard: bool,
|
pub is_standard: bool,
|
||||||
@@ -271,6 +284,7 @@ impl From<attune_common::models::Pack> for PackResponse {
|
|||||||
meta: pack.meta,
|
meta: pack.meta,
|
||||||
tags: pack.tags,
|
tags: pack.tags,
|
||||||
runtime_deps: pack.runtime_deps,
|
runtime_deps: pack.runtime_deps,
|
||||||
|
dependencies: pack.dependencies,
|
||||||
is_standard: pack.is_standard,
|
is_standard: pack.is_standard,
|
||||||
created: pack.created,
|
created: pack.created,
|
||||||
updated: pack.updated,
|
updated: pack.updated,
|
||||||
@@ -803,6 +817,7 @@ mod tests {
|
|||||||
assert_eq!(req.version, "1.0.0");
|
assert_eq!(req.version, "1.0.0");
|
||||||
assert!(req.tags.is_empty());
|
assert!(req.tags.is_empty());
|
||||||
assert!(req.runtime_deps.is_empty());
|
assert!(req.runtime_deps.is_empty());
|
||||||
|
assert!(req.dependencies.is_empty());
|
||||||
assert!(!req.is_standard);
|
assert!(!req.is_standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,6 +833,7 @@ mod tests {
|
|||||||
meta: default_empty_object(),
|
meta: default_empty_object(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ pub async fn create_pack(
|
|||||||
meta: request.meta,
|
meta: request.meta,
|
||||||
tags: request.tags,
|
tags: request.tags,
|
||||||
runtime_deps: request.runtime_deps,
|
runtime_deps: request.runtime_deps,
|
||||||
|
dependencies: request.dependencies,
|
||||||
is_standard: request.is_standard,
|
is_standard: request.is_standard,
|
||||||
installers: serde_json::json!({}),
|
installers: serde_json::json!({}),
|
||||||
};
|
};
|
||||||
@@ -222,6 +223,7 @@ pub async fn update_pack(
|
|||||||
meta: request.meta,
|
meta: request.meta,
|
||||||
tags: request.tags,
|
tags: request.tags,
|
||||||
runtime_deps: request.runtime_deps,
|
runtime_deps: request.runtime_deps,
|
||||||
|
dependencies: request.dependencies,
|
||||||
is_standard: request.is_standard,
|
is_standard: request.is_standard,
|
||||||
installers: None,
|
installers: None,
|
||||||
};
|
};
|
||||||
@@ -522,6 +524,15 @@ async fn register_pack_internal(
|
|||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
runtime_deps: pack_yaml
|
runtime_deps: pack_yaml
|
||||||
|
.get("runtime_deps")
|
||||||
|
.and_then(|v| v.as_sequence())
|
||||||
|
.map(|seq| {
|
||||||
|
seq.iter()
|
||||||
|
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
dependencies: pack_yaml
|
||||||
.get("dependencies")
|
.get("dependencies")
|
||||||
.and_then(|v| v.as_sequence())
|
.and_then(|v| v.as_sequence())
|
||||||
.map(|seq| {
|
.map(|seq| {
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ pub async fn create_test_pack(pool: &PgPool, ref_name: &str) -> Result<Pack> {
|
|||||||
}),
|
}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ async fn setup_test_pack_and_action(pool: &PgPool) -> Result<(Pack, Action)> {
|
|||||||
meta: json!({"author": "test"}),
|
meta: json!({"author": "test"}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ async fn create_test_pack(state: &AppState, name: &str) -> i64 {
|
|||||||
meta: serde_json::json!({}),
|
meta: serde_json::json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ async fn create_test_pack(state: &AppState, name: &str) -> i64 {
|
|||||||
meta: serde_json::json!({}),
|
meta: serde_json::json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ pub async fn handle_index_update(
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let runtime_deps: Vec<String> = pack_yaml
|
let runtime_deps: Vec<String> = pack_yaml
|
||||||
.get("dependencies")
|
.get("runtime_deps")
|
||||||
.and_then(|v| v.as_sequence())
|
.and_then(|v| v.as_sequence())
|
||||||
.map(|seq| {
|
.map(|seq| {
|
||||||
seq.iter()
|
seq.iter()
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ pub mod pack {
|
|||||||
pub meta: JsonDict,
|
pub meta: JsonDict,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub runtime_deps: Vec<String>,
|
pub runtime_deps: Vec<String>,
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
pub is_standard: bool,
|
pub is_standard: bool,
|
||||||
pub installers: JsonDict,
|
pub installers: JsonDict,
|
||||||
// Installation metadata (nullable for non-installed packs)
|
// Installation metadata (nullable for non-installed packs)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub struct CreatePackInput {
|
|||||||
pub meta: JsonDict,
|
pub meta: JsonDict,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub runtime_deps: Vec<String>,
|
pub runtime_deps: Vec<String>,
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
pub is_standard: bool,
|
pub is_standard: bool,
|
||||||
pub installers: JsonDict,
|
pub installers: JsonDict,
|
||||||
}
|
}
|
||||||
@@ -46,30 +47,24 @@ pub struct UpdatePackInput {
|
|||||||
pub meta: Option<JsonDict>,
|
pub meta: Option<JsonDict>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub runtime_deps: Option<Vec<String>>,
|
pub runtime_deps: Option<Vec<String>>,
|
||||||
|
pub dependencies: Option<Vec<String>>,
|
||||||
pub is_standard: Option<bool>,
|
pub is_standard: Option<bool>,
|
||||||
pub installers: Option<JsonDict>,
|
pub installers: Option<JsonDict>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PACK_COLUMNS: &str = "id, ref, label, description, version, conf_schema, config, meta, tags, runtime_deps, dependencies, is_standard, installers, source_type, source_url, source_ref, checksum, checksum_verified, installed_at, installed_by, installation_method, storage_path, created, updated";
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl FindById for PackRepository {
|
impl FindById for PackRepository {
|
||||||
async fn find_by_id<'e, E>(executor: E, id: i64) -> Result<Option<Self::Entity>>
|
async fn find_by_id<'e, E>(executor: E, id: i64) -> Result<Option<Self::Entity>>
|
||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let pack = sqlx::query_as::<_, Pack>(
|
let query = format!("SELECT {} FROM pack WHERE id = $1", PACK_COLUMNS);
|
||||||
r#"
|
let pack = sqlx::query_as::<_, Pack>(&query)
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
.bind(id)
|
||||||
tags, runtime_deps, is_standard, installers,
|
.fetch_optional(executor)
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
.await?;
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
|
||||||
created, updated
|
|
||||||
FROM pack
|
|
||||||
WHERE id = $1
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(id)
|
|
||||||
.fetch_optional(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(pack)
|
Ok(pack)
|
||||||
}
|
}
|
||||||
@@ -81,20 +76,11 @@ impl FindByRef for PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let pack = sqlx::query_as::<_, Pack>(
|
let query = format!("SELECT {} FROM pack WHERE ref = $1", PACK_COLUMNS);
|
||||||
r#"
|
let pack = sqlx::query_as::<_, Pack>(&query)
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
.bind(ref_str)
|
||||||
tags, runtime_deps, is_standard, installers,
|
.fetch_optional(executor)
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
.await?;
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
|
||||||
created, updated
|
|
||||||
FROM pack
|
|
||||||
WHERE ref = $1
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(ref_str)
|
|
||||||
.fetch_optional(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(pack)
|
Ok(pack)
|
||||||
}
|
}
|
||||||
@@ -106,19 +92,10 @@ impl List for PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!("SELECT {} FROM pack ORDER BY ref ASC", PACK_COLUMNS);
|
||||||
r#"
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
.fetch_all(executor)
|
||||||
tags, runtime_deps, is_standard, installers,
|
.await?;
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
|
||||||
created, updated
|
|
||||||
FROM pack
|
|
||||||
ORDER BY ref ASC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -143,41 +120,41 @@ impl Create for PackRepository {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to insert - database will enforce uniqueness constraint
|
let query = format!(
|
||||||
let pack = sqlx::query_as::<_, Pack>(
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO pack (ref, label, description, version, conf_schema, config, meta,
|
INSERT INTO pack (ref, label, description, version, conf_schema, config, meta,
|
||||||
tags, runtime_deps, is_standard, installers)
|
tags, runtime_deps, dependencies, is_standard, installers)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING id, ref, label, description, version, conf_schema, config, meta,
|
RETURNING {}
|
||||||
tags, runtime_deps, is_standard, installers,
|
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
|
||||||
created, updated
|
|
||||||
"#,
|
"#,
|
||||||
)
|
PACK_COLUMNS
|
||||||
.bind(&input.r#ref)
|
);
|
||||||
.bind(&input.label)
|
|
||||||
.bind(&input.description)
|
// Try to insert - database will enforce uniqueness constraint
|
||||||
.bind(&input.version)
|
let pack = sqlx::query_as::<_, Pack>(&query)
|
||||||
.bind(&input.conf_schema)
|
.bind(&input.r#ref)
|
||||||
.bind(&input.config)
|
.bind(&input.label)
|
||||||
.bind(&input.meta)
|
.bind(&input.description)
|
||||||
.bind(&input.tags)
|
.bind(&input.version)
|
||||||
.bind(&input.runtime_deps)
|
.bind(&input.conf_schema)
|
||||||
.bind(input.is_standard)
|
.bind(&input.config)
|
||||||
.bind(&input.installers)
|
.bind(&input.meta)
|
||||||
.fetch_one(executor)
|
.bind(&input.tags)
|
||||||
.await
|
.bind(&input.runtime_deps)
|
||||||
.map_err(|e| {
|
.bind(&input.dependencies)
|
||||||
// Convert unique constraint violation to AlreadyExists error
|
.bind(input.is_standard)
|
||||||
if let sqlx::Error::Database(db_err) = &e {
|
.bind(&input.installers)
|
||||||
if db_err.is_unique_violation() {
|
.fetch_one(executor)
|
||||||
return Error::already_exists("Pack", "ref", &input.r#ref);
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
// Convert unique constraint violation to AlreadyExists error
|
||||||
|
if let sqlx::Error::Database(db_err) = &e {
|
||||||
|
if db_err.is_unique_violation() {
|
||||||
|
return Error::already_exists("Pack", "ref", &input.r#ref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
e.into()
|
||||||
e.into()
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(pack)
|
Ok(pack)
|
||||||
}
|
}
|
||||||
@@ -267,6 +244,15 @@ impl Update for PackRepository {
|
|||||||
has_updates = true;
|
has_updates = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(dependencies) = &input.dependencies {
|
||||||
|
if has_updates {
|
||||||
|
query.push(", ");
|
||||||
|
}
|
||||||
|
query.push("dependencies = ");
|
||||||
|
query.push_bind(dependencies);
|
||||||
|
has_updates = true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(is_standard) = input.is_standard {
|
if let Some(is_standard) = input.is_standard {
|
||||||
if has_updates {
|
if has_updates {
|
||||||
query.push(", ");
|
query.push(", ");
|
||||||
@@ -295,7 +281,8 @@ impl Update for PackRepository {
|
|||||||
// Add updated timestamp
|
// Add updated timestamp
|
||||||
query.push(", updated = NOW() WHERE id = ");
|
query.push(", updated = NOW() WHERE id = ");
|
||||||
query.push_bind(id);
|
query.push_bind(id);
|
||||||
query.push(" RETURNING id, ref, label, description, version, conf_schema, config, meta, tags, runtime_deps, is_standard, installers, source_type, source_url, source_ref, checksum, checksum_verified, installed_at, installed_by, installation_method, storage_path, created, updated");
|
query.push(" RETURNING ");
|
||||||
|
query.push(PACK_COLUMNS);
|
||||||
|
|
||||||
let pack = query
|
let pack = query
|
||||||
.build_query_as::<Pack>()
|
.build_query_as::<Pack>()
|
||||||
@@ -331,22 +318,15 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
"SELECT {} FROM pack ORDER BY ref ASC LIMIT $1 OFFSET $2",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.bind(pagination.limit())
|
||||||
created, updated
|
.bind(pagination.offset())
|
||||||
FROM pack
|
.fetch_all(executor)
|
||||||
ORDER BY ref ASC
|
.await?;
|
||||||
LIMIT $1 OFFSET $2
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(pagination.limit())
|
|
||||||
.bind(pagination.offset())
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -368,21 +348,14 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
"SELECT {} FROM pack WHERE $1 = ANY(tags) ORDER BY ref ASC",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.bind(tag)
|
||||||
created, updated
|
.fetch_all(executor)
|
||||||
FROM pack
|
.await?;
|
||||||
WHERE $1 = ANY(tags)
|
|
||||||
ORDER BY ref ASC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(tag)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -392,20 +365,13 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
"SELECT {} FROM pack WHERE is_standard = true ORDER BY ref ASC",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.fetch_all(executor)
|
||||||
created, updated
|
.await?;
|
||||||
FROM pack
|
|
||||||
WHERE is_standard = true
|
|
||||||
ORDER BY ref ASC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -416,21 +382,14 @@ impl PackRepository {
|
|||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let search_pattern = format!("%{}%", query.to_lowercase());
|
let search_pattern = format!("%{}%", query.to_lowercase());
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let sql = format!(
|
||||||
r#"
|
"SELECT {} FROM pack WHERE LOWER(ref) LIKE $1 OR LOWER(label) LIKE $1 OR LOWER(description) LIKE $1 ORDER BY ref ASC",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&sql)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.bind(&search_pattern)
|
||||||
created, updated
|
.fetch_all(executor)
|
||||||
FROM pack
|
.await?;
|
||||||
WHERE LOWER(ref) LIKE $1 OR LOWER(label) LIKE $1 OR LOWER(description) LIKE $1
|
|
||||||
ORDER BY ref ASC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(&search_pattern)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -464,7 +423,7 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let pack = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE pack
|
UPDATE pack
|
||||||
SET source_type = $2,
|
SET source_type = $2,
|
||||||
@@ -478,28 +437,26 @@ impl PackRepository {
|
|||||||
storage_path = $9,
|
storage_path = $9,
|
||||||
updated = NOW()
|
updated = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, ref, label, description, version, conf_schema, config, meta,
|
RETURNING {}
|
||||||
tags, runtime_deps, is_standard, installers,
|
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
|
||||||
created, updated
|
|
||||||
"#,
|
"#,
|
||||||
)
|
PACK_COLUMNS
|
||||||
.bind(id)
|
);
|
||||||
.bind(source_type)
|
let pack = sqlx::query_as::<_, Pack>(&query)
|
||||||
.bind(source_url)
|
.bind(id)
|
||||||
.bind(source_ref)
|
.bind(source_type)
|
||||||
.bind(checksum)
|
.bind(source_url)
|
||||||
.bind(checksum_verified)
|
.bind(source_ref)
|
||||||
.bind(installed_by)
|
.bind(checksum)
|
||||||
.bind(installation_method)
|
.bind(checksum_verified)
|
||||||
.bind(storage_path)
|
.bind(installed_by)
|
||||||
.fetch_one(executor)
|
.bind(installation_method)
|
||||||
.await
|
.bind(storage_path)
|
||||||
.map_err(|e| match e {
|
.fetch_one(executor)
|
||||||
sqlx::Error::RowNotFound => Error::not_found("pack", "id", id.to_string()),
|
.await
|
||||||
_ => e.into(),
|
.map_err(|e| match e {
|
||||||
})?;
|
sqlx::Error::RowNotFound => Error::not_found("pack", "id", id.to_string()),
|
||||||
|
_ => e.into(),
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(pack)
|
Ok(pack)
|
||||||
}
|
}
|
||||||
@@ -524,20 +481,13 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
"SELECT {} FROM pack WHERE installed_at IS NOT NULL ORDER BY installed_at DESC",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.fetch_all(executor)
|
||||||
created, updated
|
.await?;
|
||||||
FROM pack
|
|
||||||
WHERE installed_at IS NOT NULL
|
|
||||||
ORDER BY installed_at DESC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -547,21 +497,14 @@ impl PackRepository {
|
|||||||
where
|
where
|
||||||
E: Executor<'e, Database = Postgres> + 'e,
|
E: Executor<'e, Database = Postgres> + 'e,
|
||||||
{
|
{
|
||||||
let packs = sqlx::query_as::<_, Pack>(
|
let query = format!(
|
||||||
r#"
|
"SELECT {} FROM pack WHERE source_type = $1 ORDER BY installed_at DESC",
|
||||||
SELECT id, ref, label, description, version, conf_schema, config, meta,
|
PACK_COLUMNS
|
||||||
tags, runtime_deps, is_standard, installers,
|
);
|
||||||
source_type, source_url, source_ref, checksum, checksum_verified,
|
let packs = sqlx::query_as::<_, Pack>(&query)
|
||||||
installed_at, installed_by, installation_method, storage_path,
|
.bind(source_type)
|
||||||
created, updated
|
.fetch_all(executor)
|
||||||
FROM pack
|
.await?;
|
||||||
WHERE source_type = $1
|
|
||||||
ORDER BY installed_at DESC
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(source_type)
|
|
||||||
.fetch_all(executor)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(packs)
|
Ok(packs)
|
||||||
}
|
}
|
||||||
@@ -583,6 +526,7 @@ mod tests {
|
|||||||
meta: serde_json::json!({}),
|
meta: serde_json::json!({}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: serde_json::json!({}),
|
installers: serde_json::json!({}),
|
||||||
};
|
};
|
||||||
@@ -597,5 +541,6 @@ mod tests {
|
|||||||
assert!(input.label.is_none());
|
assert!(input.label.is_none());
|
||||||
assert!(input.description.is_none());
|
assert!(input.description.is_none());
|
||||||
assert!(input.version.is_none());
|
assert!(input.version.is_none());
|
||||||
|
assert!(input.dependencies.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ pub struct PackFixture {
|
|||||||
pub meta: serde_json::Value,
|
pub meta: serde_json::Value,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub runtime_deps: Vec<String>,
|
pub runtime_deps: Vec<String>,
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
pub is_standard: bool,
|
pub is_standard: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +301,7 @@ impl PackFixture {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,6 +321,7 @@ impl PackFixture {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,6 +362,7 @@ impl PackFixture {
|
|||||||
meta: self.meta,
|
meta: self.meta,
|
||||||
tags: self.tags,
|
tags: self.tags,
|
||||||
runtime_deps: self.runtime_deps,
|
runtime_deps: self.runtime_deps,
|
||||||
|
dependencies: self.dependencies,
|
||||||
is_standard: self.is_standard,
|
is_standard: self.is_standard,
|
||||||
installers: serde_json::json!({}),
|
installers: serde_json::json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -358,6 +358,7 @@ async fn test_pack_columns() {
|
|||||||
"conf_schema",
|
"conf_schema",
|
||||||
"config",
|
"config",
|
||||||
"created",
|
"created",
|
||||||
|
"dependencies",
|
||||||
"description",
|
"description",
|
||||||
"id",
|
"id",
|
||||||
"is_standard",
|
"is_standard",
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ async fn test_pack_transaction_commit() {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
@@ -428,6 +429,7 @@ async fn test_pack_transaction_rollback() {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
@@ -457,6 +459,7 @@ async fn test_pack_invalid_ref_format() {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
@@ -491,8 +494,9 @@ async fn test_pack_valid_ref_formats() {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = PackRepository::create(&pool, input).await;
|
let result = PackRepository::create(&pool, input).await;
|
||||||
|
|||||||
@@ -79,8 +79,9 @@ impl PermissionSetFixture {
|
|||||||
config: json!({}),
|
config: json!({}),
|
||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
PackRepository::create(&self.pool, input)
|
PackRepository::create(&self.pool, input)
|
||||||
.await
|
.await
|
||||||
@@ -95,7 +96,7 @@ impl PermissionSetFixture {
|
|||||||
login,
|
login,
|
||||||
display_name: Some("Test User".to_string()),
|
display_name: Some("Test User".to_string()),
|
||||||
attributes: json!({}),
|
attributes: json!({}),
|
||||||
password_hash: None,
|
password_hash: None,
|
||||||
};
|
};
|
||||||
IdentityRepository::create(&self.pool, input)
|
IdentityRepository::create(&self.pool, input)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ async fn test_find_by_pack() {
|
|||||||
}),
|
}),
|
||||||
tags: vec!["test".to_string()],
|
tags: vec!["test".to_string()],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ async fn create_test_pack(pool: &PgPool, suffix: &str) -> i64 {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ async fn create_test_pack(pool: &PgPool, suffix: &str) -> i64 {
|
|||||||
meta: json!({}),
|
meta: json!({}),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
runtime_deps: vec![],
|
runtime_deps: vec![],
|
||||||
|
dependencies: vec![],
|
||||||
is_standard: false,
|
is_standard: false,
|
||||||
installers: json!({}),
|
installers: json!({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ CREATE TABLE pack (
|
|||||||
meta JSONB NOT NULL DEFAULT '{}'::jsonb,
|
meta JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||||
runtime_deps TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
runtime_deps TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||||
|
dependencies TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||||
is_standard BOOLEAN NOT NULL DEFAULT FALSE,
|
is_standard BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
installers JSONB DEFAULT '[]'::jsonb,
|
installers JSONB DEFAULT '[]'::jsonb,
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ CREATE INDEX idx_pack_config_gin ON pack USING GIN (config);
|
|||||||
CREATE INDEX idx_pack_meta_gin ON pack USING GIN (meta);
|
CREATE INDEX idx_pack_meta_gin ON pack USING GIN (meta);
|
||||||
CREATE INDEX idx_pack_tags_gin ON pack USING GIN (tags);
|
CREATE INDEX idx_pack_tags_gin ON pack USING GIN (tags);
|
||||||
CREATE INDEX idx_pack_runtime_deps_gin ON pack USING GIN (runtime_deps);
|
CREATE INDEX idx_pack_runtime_deps_gin ON pack USING GIN (runtime_deps);
|
||||||
|
CREATE INDEX idx_pack_dependencies_gin ON pack USING GIN (dependencies);
|
||||||
CREATE INDEX idx_pack_installed_at ON pack(installed_at DESC) WHERE installed_at IS NOT NULL;
|
CREATE INDEX idx_pack_installed_at ON pack(installed_at DESC) WHERE installed_at IS NOT NULL;
|
||||||
CREATE INDEX idx_pack_installed_by ON pack(installed_by) WHERE installed_by IS NOT NULL;
|
CREATE INDEX idx_pack_installed_by ON pack(installed_by) WHERE installed_by IS NOT NULL;
|
||||||
CREATE INDEX idx_pack_source_type ON pack(source_type) WHERE source_type IS NOT NULL;
|
CREATE INDEX idx_pack_source_type ON pack(source_type) WHERE source_type IS NOT NULL;
|
||||||
@@ -70,7 +72,8 @@ COMMENT ON COLUMN pack.version IS 'Semantic version of the pack';
|
|||||||
COMMENT ON COLUMN pack.conf_schema IS 'JSON schema for pack configuration';
|
COMMENT ON COLUMN pack.conf_schema IS 'JSON schema for pack configuration';
|
||||||
COMMENT ON COLUMN pack.config IS 'Pack configuration values';
|
COMMENT ON COLUMN pack.config IS 'Pack configuration values';
|
||||||
COMMENT ON COLUMN pack.meta IS 'Pack metadata';
|
COMMENT ON COLUMN pack.meta IS 'Pack metadata';
|
||||||
COMMENT ON COLUMN pack.runtime_deps IS 'Array of required runtime references';
|
COMMENT ON COLUMN pack.runtime_deps IS 'Array of required runtime references (e.g., shell, python, nodejs)';
|
||||||
|
COMMENT ON COLUMN pack.dependencies IS 'Array of required pack references (e.g., core, utils)';
|
||||||
COMMENT ON COLUMN pack.is_standard IS 'Whether this is a core/built-in pack';
|
COMMENT ON COLUMN pack.is_standard IS 'Whether this is a core/built-in pack';
|
||||||
COMMENT ON COLUMN pack.source_type IS 'Installation source type (e.g., "git", "local", "registry")';
|
COMMENT ON COLUMN pack.source_type IS 'Installation source type (e.g., "git", "local", "registry")';
|
||||||
COMMENT ON COLUMN pack.source_url IS 'URL or path where pack was installed from';
|
COMMENT ON COLUMN pack.source_url IS 'URL or path where pack was installed from';
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
const [description, setDescription] = useState(pack?.description || "");
|
const [description, setDescription] = useState(pack?.description || "");
|
||||||
const [version, setVersion] = useState(pack?.version || "1.0.0");
|
const [version, setVersion] = useState(pack?.version || "1.0.0");
|
||||||
const [tags, setTags] = useState(pack?.tags?.join(", ") || "");
|
const [tags, setTags] = useState(pack?.tags?.join(", ") || "");
|
||||||
const [runtimeDeps, setRuntimeDeps] = useState(
|
const [deps, setDeps] = useState(pack?.dependencies?.join(", ") || "");
|
||||||
pack?.runtime_deps?.join(", ") || "",
|
|
||||||
);
|
|
||||||
const [isStandard, setIsStandard] = useState(pack?.is_standard ?? false);
|
const [isStandard, setIsStandard] = useState(pack?.is_standard ?? false);
|
||||||
|
|
||||||
const [configValues, setConfigValues] =
|
const [configValues, setConfigValues] =
|
||||||
@@ -134,7 +132,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((t) => t.trim())
|
.map((t) => t.trim())
|
||||||
.filter((t) => t);
|
.filter((t) => t);
|
||||||
const runtimeDepsList = runtimeDeps
|
const depsList = deps
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((d) => d.trim())
|
.map((d) => d.trim())
|
||||||
.filter((d) => d);
|
.filter((d) => d);
|
||||||
@@ -149,7 +147,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
config: configValues,
|
config: configValues,
|
||||||
meta: parsedMeta,
|
meta: parsedMeta,
|
||||||
tags: tagsList,
|
tags: tagsList,
|
||||||
runtime_deps: runtimeDepsList,
|
dependencies: depsList,
|
||||||
is_standard: isStandard,
|
is_standard: isStandard,
|
||||||
};
|
};
|
||||||
await updatePack.mutateAsync({ ref: pack!.ref, data: updateData });
|
await updatePack.mutateAsync({ ref: pack!.ref, data: updateData });
|
||||||
@@ -166,7 +164,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
config: configValues,
|
config: configValues,
|
||||||
meta: parsedMeta,
|
meta: parsedMeta,
|
||||||
tags: tagsList,
|
tags: tagsList,
|
||||||
runtime_deps: runtimeDepsList,
|
dependencies: depsList,
|
||||||
is_standard: isStandard,
|
is_standard: isStandard,
|
||||||
};
|
};
|
||||||
const newPackResponse = await createPack.mutateAsync(createData);
|
const newPackResponse = await createPack.mutateAsync(createData);
|
||||||
@@ -443,19 +441,19 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Runtime Dependencies */}
|
{/* Pack Dependencies */}
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="runtimeDeps"
|
htmlFor="deps"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
>
|
>
|
||||||
Runtime Dependencies
|
Pack Dependencies
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="runtimeDeps"
|
id="deps"
|
||||||
value={runtimeDeps}
|
value={deps}
|
||||||
onChange={(e) => setRuntimeDeps(e.target.value)}
|
onChange={(e) => setDeps(e.target.value)}
|
||||||
disabled={isStandard}
|
disabled={isStandard}
|
||||||
className={`w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
className={`w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${
|
||||||
isStandard ? "bg-gray-100 cursor-not-allowed" : ""
|
isStandard ? "bg-gray-100 cursor-not-allowed" : ""
|
||||||
@@ -463,7 +461,8 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
|
|||||||
placeholder="e.g., core, utils"
|
placeholder="e.g., core, utils"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-gray-500">
|
<p className="mt-1 text-xs text-gray-500">
|
||||||
Comma-separated list of required pack refs
|
Comma-separated list of required pack refs (other packs this pack
|
||||||
|
depends on)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user