feat: Refactor service management scripts to use a unified dev script
- Added package.json to manage development scripts. - Updated restart-services.ps1 to call the new dev script for starting services. - Refactored start-admin.ps1, start-backend.ps1, start-frontend.ps1, and start-mcp.ps1 to utilize the dev script for starting respective services. - Enhanced stop-services.ps1 to improve process termination logic by matching command patterns.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, Condition, EntityTrait, IntoActiveModel, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -18,12 +18,21 @@ struct MarkdownFrontmatter {
|
||||
title: Option<String>,
|
||||
slug: Option<String>,
|
||||
description: Option<String>,
|
||||
category: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
alias = "category",
|
||||
alias = "categories",
|
||||
deserialize_with = "deserialize_optional_string_list"
|
||||
)]
|
||||
categories: Option<Vec<String>>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_string_list")]
|
||||
tags: Option<Vec<String>>,
|
||||
post_type: Option<String>,
|
||||
image: Option<String>,
|
||||
images: Option<Vec<String>>,
|
||||
pinned: Option<bool>,
|
||||
published: Option<bool>,
|
||||
draft: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@@ -36,6 +45,7 @@ pub struct MarkdownPost {
|
||||
pub tags: Vec<String>,
|
||||
pub post_type: String,
|
||||
pub image: Option<String>,
|
||||
pub images: Vec<String>,
|
||||
pub pinned: bool,
|
||||
pub published: bool,
|
||||
pub file_path: String,
|
||||
@@ -51,6 +61,7 @@ pub struct MarkdownPostDraft {
|
||||
pub tags: Vec<String>,
|
||||
pub post_type: String,
|
||||
pub image: Option<String>,
|
||||
pub images: Vec<String>,
|
||||
pub pinned: bool,
|
||||
pub published: bool,
|
||||
}
|
||||
@@ -104,13 +115,71 @@ fn trim_to_option(input: Option<String>) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_string_list(values: Option<Vec<String>>) -> Vec<String> {
|
||||
values
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|item| item.trim().to_string())
|
||||
.filter(|item| !item.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn split_inline_list(value: &str) -> Vec<String> {
|
||||
value
|
||||
.split([',', ','])
|
||||
.map(|item| item.trim().to_string())
|
||||
.filter(|item| !item.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn deserialize_optional_string_list<'de, D>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<Option<Vec<String>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw = Option::<serde_yaml::Value>::deserialize(deserializer)?;
|
||||
|
||||
match raw {
|
||||
None | Some(serde_yaml::Value::Null) => Ok(None),
|
||||
Some(serde_yaml::Value::String(value)) => {
|
||||
let items = split_inline_list(&value);
|
||||
if items.is_empty() && !value.trim().is_empty() {
|
||||
Ok(Some(vec![value.trim().to_string()]))
|
||||
} else if items.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(items))
|
||||
}
|
||||
}
|
||||
Some(serde_yaml::Value::Sequence(items)) => Ok(Some(
|
||||
items
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
serde_yaml::Value::String(value) => {
|
||||
let trimmed = value.trim().to_string();
|
||||
(!trimmed.is_empty()).then_some(trimmed)
|
||||
}
|
||||
serde_yaml::Value::Number(value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)),
|
||||
Some(other) => Err(serde::de::Error::custom(format!(
|
||||
"unsupported frontmatter list value: {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn slugify(value: &str) -> String {
|
||||
let mut slug = String::new();
|
||||
let mut last_was_dash = false;
|
||||
|
||||
for ch in value.trim().chars() {
|
||||
if ch.is_ascii_alphanumeric() {
|
||||
slug.push(ch.to_ascii_lowercase());
|
||||
if ch.is_alphanumeric() {
|
||||
for lower in ch.to_lowercase() {
|
||||
slug.push(lower);
|
||||
}
|
||||
last_was_dash = false;
|
||||
} else if (ch.is_whitespace() || ch == '-' || ch == '_') && !last_was_dash {
|
||||
slug.push('-');
|
||||
@@ -208,7 +277,9 @@ fn parse_markdown_source(file_stem: &str, raw: &str, file_path: &str) -> Result<
|
||||
.unwrap_or_else(|| slug.clone());
|
||||
let description =
|
||||
trim_to_option(frontmatter.description.clone()).or_else(|| excerpt_from_content(&content));
|
||||
let category = trim_to_option(frontmatter.category.clone());
|
||||
let category = normalize_string_list(frontmatter.categories.clone())
|
||||
.into_iter()
|
||||
.next();
|
||||
let tags = frontmatter
|
||||
.tags
|
||||
.unwrap_or_default()
|
||||
@@ -227,8 +298,11 @@ fn parse_markdown_source(file_stem: &str, raw: &str, file_path: &str) -> Result<
|
||||
post_type: trim_to_option(frontmatter.post_type.clone())
|
||||
.unwrap_or_else(|| "article".to_string()),
|
||||
image: trim_to_option(frontmatter.image.clone()),
|
||||
images: normalize_string_list(frontmatter.images.clone()),
|
||||
pinned: frontmatter.pinned.unwrap_or(false),
|
||||
published: frontmatter.published.unwrap_or(true),
|
||||
published: frontmatter
|
||||
.published
|
||||
.unwrap_or(!frontmatter.draft.unwrap_or(false)),
|
||||
file_path: file_path.to_string(),
|
||||
})
|
||||
}
|
||||
@@ -266,6 +340,13 @@ fn build_markdown_document(post: &MarkdownPost) -> String {
|
||||
lines.push(format!("image: {}", image));
|
||||
}
|
||||
|
||||
if !post.images.is_empty() {
|
||||
lines.push("images:".to_string());
|
||||
for image in &post.images {
|
||||
lines.push(format!(" - {}", image));
|
||||
}
|
||||
}
|
||||
|
||||
if !post.tags.is_empty() {
|
||||
lines.push("tags:".to_string());
|
||||
for tag in &post.tags {
|
||||
@@ -307,6 +388,7 @@ fn ensure_markdown_posts_bootstrapped() -> Result<()> {
|
||||
tags: fixture.tags.unwrap_or_default(),
|
||||
post_type: "article".to_string(),
|
||||
image: None,
|
||||
images: Vec::new(),
|
||||
pinned: fixture.pinned.unwrap_or(false),
|
||||
published: fixture.published.unwrap_or(true),
|
||||
file_path: markdown_post_path(&fixture.slug)
|
||||
@@ -470,7 +552,11 @@ async fn canonicalize_tags(ctx: &AppContext, raw_tags: &[String]) -> Result<Vec<
|
||||
}
|
||||
|
||||
fn write_markdown_post_to_disk(post: &MarkdownPost) -> Result<()> {
|
||||
fs::write(markdown_post_path(&post.slug), build_markdown_document(post)).map_err(io_error)
|
||||
fs::write(
|
||||
markdown_post_path(&post.slug),
|
||||
build_markdown_document(post),
|
||||
)
|
||||
.map_err(io_error)
|
||||
}
|
||||
|
||||
pub fn rewrite_category_references(
|
||||
@@ -701,6 +787,17 @@ pub async fn sync_markdown_posts(ctx: &AppContext) -> Result<Vec<MarkdownPost>>
|
||||
});
|
||||
model.post_type = Set(Some(post.post_type.clone()));
|
||||
model.image = Set(post.image.clone());
|
||||
model.images = Set(if post.images.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Value::Array(
|
||||
post.images
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Value::String)
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
});
|
||||
model.pinned = Set(Some(post.pinned));
|
||||
|
||||
if has_existing {
|
||||
@@ -796,6 +893,7 @@ pub async fn create_markdown_post(
|
||||
}
|
||||
},
|
||||
image: trim_to_option(draft.image),
|
||||
images: normalize_string_list(Some(draft.images)),
|
||||
pinned: draft.pinned,
|
||||
published: draft.published,
|
||||
file_path: markdown_post_path(&slug).to_string_lossy().to_string(),
|
||||
|
||||
Reference in New Issue
Block a user