Implement the bDS2 sync scripting API.
This commit is contained in:
@@ -10,7 +10,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
|||||||
- Post and translation authoring with draft/published lifecycle, metadata, tags, categories, links, media, and batch gallery-image import.
|
- Post and translation authoring with draft/published lifecycle, metadata, tags, categories, links, media, and batch gallery-image import.
|
||||||
- Media import, thumbnails, metadata translations, filters, validation, and post assignment.
|
- Media import, thumbnails, metadata translations, filters, validation, and post assignment.
|
||||||
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
||||||
- Template and Lua script management with explicit syntax-check feedback, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms.
|
- Template and Lua script management with explicit syntax-check feedback, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms, including airplane-gated Git sync.
|
||||||
- SQLite and filesystem persistence with frontmatter, sidecars, rebuild, metadata diff/repair, and FTS5 search.
|
- SQLite and filesystem persistence with frontmatter, sidecars, rebuild, metadata diff/repair, and FTS5 search.
|
||||||
- Optional on-device multilingual semantic search and tag suggestions backed by a persistent USearch index, with duplicate-post review and dismissal in the desktop workspace.
|
- Optional on-device multilingual semantic search and tag suggestions backed by a persistent USearch index, with duplicate-post review and dismissal in the desktop workspace.
|
||||||
- Read-only in-app browsers in the Help menu for the bundled global `DOCUMENTATION.md`, the generated Lua API reference with public types and runnable examples, the [CLI/server/TUI documentation](CLI.md), and the [MCP server documentation](MCP.md), with safe GFM rendering and confirmed external links.
|
- Read-only in-app browsers in the Help menu for the bundled global `DOCUMENTATION.md`, the generated Lua API reference with public types and runnable examples, the [CLI/server/TUI documentation](CLI.md), and the [MCP server documentation](MCP.md), with safe GFM rendering and confirmed external links.
|
||||||
|
|||||||
@@ -335,47 +335,20 @@ fn active_project(db: &Database) -> Result<(Project, PathBuf)> {
|
|||||||
fn rebuild(db: &Database, incremental: bool) -> Result<CommandOutput> {
|
fn rebuild(db: &Database, incremental: bool) -> Result<CommandOutput> {
|
||||||
let (project, data_dir) = active_project(db)?;
|
let (project, data_dir) = active_project(db)?;
|
||||||
if incremental {
|
if incremental {
|
||||||
let (applied, imported, failed) = cli_sync::run_cli_mutation(db.conn(), || {
|
let report = cli_sync::run_cli_mutation(db.conn(), || {
|
||||||
let report =
|
let report = engine::rebuild::rebuild_incremental(db.conn(), &data_dir, &project.id)?;
|
||||||
engine::metadata_diff::compute_metadata_diff(db.conn(), &data_dir, &project.id)?;
|
if report.differences_applied > 0 || report.orphans_imported > 0 {
|
||||||
if !report.errors.is_empty() {
|
|
||||||
return Err(engine::EngineError::Validation(report.errors.join("; ")));
|
|
||||||
}
|
|
||||||
for item in &report.diffs {
|
|
||||||
engine::metadata_diff::repair_metadata_diff_item(
|
|
||||||
db.conn(),
|
|
||||||
&data_dir,
|
|
||||||
&project.id,
|
|
||||||
engine::metadata_diff::RepairDirection::FileToDatabase,
|
|
||||||
item,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
let filesystem_orphans = report
|
|
||||||
.orphans
|
|
||||||
.iter()
|
|
||||||
.filter(|orphan| orphan.reason == "file_without_db_entry")
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let mut imported = 0;
|
|
||||||
let mut failed = 0;
|
|
||||||
for orphan in filesystem_orphans {
|
|
||||||
match engine::metadata_diff::import_orphan_file(
|
|
||||||
db.conn(),
|
|
||||||
&data_dir,
|
|
||||||
&project.id,
|
|
||||||
orphan,
|
|
||||||
) {
|
|
||||||
Ok(()) => imported += 1,
|
|
||||||
Err(_) => failed += 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !report.diffs.is_empty() || imported > 0 {
|
|
||||||
emit_bulk(&project.id);
|
emit_bulk(&project.id);
|
||||||
}
|
}
|
||||||
Ok((report.diffs.len(), imported, failed))
|
Ok(report)
|
||||||
})?;
|
})?;
|
||||||
return Ok(output(
|
return Ok(output(
|
||||||
"Applied incremental filesystem changes",
|
"Applied incremental filesystem changes",
|
||||||
json!({"differences_applied": applied, "orphans_imported": imported, "orphans_failed": failed}),
|
json!({
|
||||||
|
"differences_applied": report.differences_applied,
|
||||||
|
"orphans_imported": report.orphans_imported,
|
||||||
|
"orphans_failed": report.orphans_failed,
|
||||||
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ mod tests {
|
|||||||
use crate::db::Database;
|
use crate::db::Database;
|
||||||
use crate::db::schema::{
|
use crate::db::schema::{
|
||||||
ai_catalog_meta, ai_endpoint_models, ai_model_modalities, ai_models, ai_providers,
|
ai_catalog_meta, ai_endpoint_models, ai_model_modalities, ai_models, ai_providers,
|
||||||
chat_conversations,
|
chat_conversations, chat_messages, db_notifications, dismissed_duplicate_pairs,
|
||||||
chat_messages, db_notifications, dismissed_duplicate_pairs, embedding_keys,
|
embedding_keys, generated_file_hashes, import_definitions, mcp_proposals, media,
|
||||||
generated_file_hashes, import_definitions, mcp_proposals, media, media_translations,
|
media_translations, post_links, post_media, post_translations, posts, projects, scripts,
|
||||||
post_links, post_media, post_translations, posts, projects, scripts, settings, tags,
|
settings, tags, templates,
|
||||||
templates,
|
|
||||||
};
|
};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
|
|||||||
@@ -29,6 +29,51 @@ pub struct FullRebuildReport {
|
|||||||
pub errors: Vec<String>,
|
pub errors: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct IncrementalRebuildReport {
|
||||||
|
pub differences_applied: usize,
|
||||||
|
pub orphans_imported: usize,
|
||||||
|
pub orphans_failed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply portable filesystem changes to one project's cache database.
|
||||||
|
pub fn rebuild_incremental(
|
||||||
|
conn: &Connection,
|
||||||
|
data_dir: &Path,
|
||||||
|
project_id: &str,
|
||||||
|
) -> EngineResult<IncrementalRebuildReport> {
|
||||||
|
let report = super::metadata_diff::compute_metadata_diff(conn, data_dir, project_id)?;
|
||||||
|
if !report.errors.is_empty() {
|
||||||
|
return Err(crate::engine::EngineError::Validation(
|
||||||
|
report.errors.join("; "),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for item in &report.diffs {
|
||||||
|
super::metadata_diff::repair_metadata_diff_item(
|
||||||
|
conn,
|
||||||
|
data_dir,
|
||||||
|
project_id,
|
||||||
|
super::metadata_diff::RepairDirection::FileToDatabase,
|
||||||
|
item,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let mut result = IncrementalRebuildReport {
|
||||||
|
differences_applied: report.diffs.len(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for orphan in report
|
||||||
|
.orphans
|
||||||
|
.iter()
|
||||||
|
.filter(|orphan| orphan.reason == "file_without_db_entry")
|
||||||
|
{
|
||||||
|
match super::metadata_diff::import_orphan_file(conn, data_dir, project_id, orphan) {
|
||||||
|
Ok(()) => result.orphans_imported += 1,
|
||||||
|
Err(_) => result.orphans_failed += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Progress callback: (percent 0.0..1.0, phase description).
|
/// Progress callback: (percent 0.0..1.0, phase description).
|
||||||
pub type ProgressFn = Arc<dyn Fn(f32, &str) + Send + Sync>;
|
pub type ProgressFn = Arc<dyn Fn(f32, &str) + Send + Sync>;
|
||||||
|
|
||||||
|
|||||||
@@ -1239,6 +1239,64 @@ impl CoreHost {
|
|||||||
Ok(Value::Bool(true))
|
Ok(Value::Bool(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync(&self, method: &str, args: &[Value]) -> HostResult<Value> {
|
||||||
|
if method == "check_availability" {
|
||||||
|
return Ok(std::process::Command::new("git")
|
||||||
|
.arg("--version")
|
||||||
|
.output()
|
||||||
|
.is_ok()
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if self.offline_mode && matches!(method, "fetch" | "pull" | "push") {
|
||||||
|
return Err(format!("Git {method} is unavailable in airplane mode").into());
|
||||||
|
}
|
||||||
|
let git = engine::git::GitEngine::new(&self.data_dir);
|
||||||
|
let cancelled = || {
|
||||||
|
self.task_manager
|
||||||
|
.as_ref()
|
||||||
|
.zip(self.task_id)
|
||||||
|
.is_some_and(|(manager, id)| manager.is_cancelled(id))
|
||||||
|
};
|
||||||
|
match method {
|
||||||
|
"get_repo_state" | "get_remote_state" => {
|
||||||
|
Ok(public_git_repository(git.repository().map_err(text)?))
|
||||||
|
}
|
||||||
|
"get_status" => Ok(json!({
|
||||||
|
"files": git.status().map_err(text)?.into_iter().map(public_git_status).collect::<Vec<_>>(),
|
||||||
|
})),
|
||||||
|
"get_history" => {
|
||||||
|
let repository = git.repository().map_err(text)?;
|
||||||
|
let commits = match repository.current_branch {
|
||||||
|
Some(branch) => git.history(&branch).map_err(text)?,
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
Ok(json!({
|
||||||
|
"commits": commits.into_iter().map(public_git_commit).collect::<Vec<_>>(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"fetch" => {
|
||||||
|
let result = git.fetch(cancelled, |_| {}).map_err(text)?;
|
||||||
|
Ok(json!({"updated": true, "output": result.output}))
|
||||||
|
}
|
||||||
|
"pull" => {
|
||||||
|
let result = git.pull(cancelled, |_| {}).map_err(text)?;
|
||||||
|
let db = self.database()?;
|
||||||
|
engine::rebuild::rebuild_incremental(db.conn(), &self.data_dir, &self.project_id)?;
|
||||||
|
Ok(json!({"updated": true, "output": result.output}))
|
||||||
|
}
|
||||||
|
"push" => {
|
||||||
|
let result = git.push(cancelled, |_| {}).map_err(text)?;
|
||||||
|
Ok(json!({"updated": true, "output": result.output}))
|
||||||
|
}
|
||||||
|
"commit_all" => {
|
||||||
|
let message = string_arg(args, 0)?;
|
||||||
|
let result = git.commit_all(message).map_err(text)?;
|
||||||
|
Ok(json!({"message": message, "output": result.output}))
|
||||||
|
}
|
||||||
|
_ => Err(format!("unknown sync capability: {method}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn publish(&self, method: &str, args: &[Value]) -> HostResult<Value> {
|
fn publish(&self, method: &str, args: &[Value]) -> HostResult<Value> {
|
||||||
if method != "upload_site" {
|
if method != "upload_site" {
|
||||||
return Err(format!("unknown publish capability: {method}").into());
|
return Err(format!("unknown publish capability: {method}").into());
|
||||||
@@ -1492,6 +1550,7 @@ impl HostApi for CoreHost {
|
|||||||
"templates" => self.templates(method, &arguments),
|
"templates" => self.templates(method, &arguments),
|
||||||
"tags" => self.tags(method, &arguments),
|
"tags" => self.tags(method, &arguments),
|
||||||
"tasks" => self.tasks(method, &arguments),
|
"tasks" => self.tasks(method, &arguments),
|
||||||
|
"sync" => self.sync(method, &arguments),
|
||||||
"publish" => self.publish(method, &arguments),
|
"publish" => self.publish(method, &arguments),
|
||||||
"chat" => self.chat(method, &arguments),
|
"chat" => self.chat(method, &arguments),
|
||||||
"embeddings" => self.embeddings(method, &arguments),
|
"embeddings" => self.embeddings(method, &arguments),
|
||||||
@@ -1587,6 +1646,54 @@ fn public_tasks(values: Vec<TaskSnapshot>) -> HostResult<Value> {
|
|||||||
.map(Value::Array)
|
.map(Value::Array)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn public_git_repository(value: engine::git::GitRepository) -> Value {
|
||||||
|
json!({
|
||||||
|
"is_initialized": value.is_initialized,
|
||||||
|
"remote_url": value.remote_url,
|
||||||
|
"provider": value.provider.map(|provider| json!({"kind": match provider {
|
||||||
|
engine::git::GitProvider::GitHub => "github",
|
||||||
|
engine::git::GitProvider::GitLab => "gitlab",
|
||||||
|
engine::git::GitProvider::GiteaForgejo => "gitea_forgejo",
|
||||||
|
}})),
|
||||||
|
"current_branch": value.current_branch,
|
||||||
|
"has_lfs": value.has_lfs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_git_status(value: engine::git::GitFileStatus) -> Value {
|
||||||
|
let mut result = Map::new();
|
||||||
|
result.insert("path".into(), value.path.into());
|
||||||
|
if let Some(old_path) = value.old_path {
|
||||||
|
result.insert("old_path".into(), old_path.into());
|
||||||
|
}
|
||||||
|
result.insert(
|
||||||
|
"status".into(),
|
||||||
|
match value.kind {
|
||||||
|
engine::git::FileStatusKind::Added => "added",
|
||||||
|
engine::git::FileStatusKind::Modified => "modified",
|
||||||
|
engine::git::FileStatusKind::Deleted => "deleted",
|
||||||
|
engine::git::FileStatusKind::Renamed => "renamed",
|
||||||
|
engine::git::FileStatusKind::Untracked => "untracked",
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
Value::Object(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_git_commit(value: engine::git::GitCommit) -> Value {
|
||||||
|
json!({
|
||||||
|
"hash": value.hash,
|
||||||
|
"subject": value.subject,
|
||||||
|
"author": value.author,
|
||||||
|
"date": value.date,
|
||||||
|
"sync_status": {"kind": match value.sync_status {
|
||||||
|
engine::git::SyncStatus::LocalOnly => "local_only",
|
||||||
|
engine::git::SyncStatus::RemoteOnly => "remote_only",
|
||||||
|
engine::git::SyncStatus::Both => "both",
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn public_metadata(data_dir: &Path) -> HostResult<Value> {
|
fn public_metadata(data_dir: &Path) -> HostResult<Value> {
|
||||||
let metadata = engine::meta::read_project_json(data_dir)?;
|
let metadata = engine::meta::read_project_json(data_dir)?;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -1862,6 +1969,255 @@ fn one_shot_json(value: engine::ai::OneShotResponse) -> HostResult<Value> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::scripting::{ExecutionControl, ExecutionKind, execute_with_host};
|
use crate::scripting::{ExecutionControl, ExecutionKind, execute_with_host};
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
struct SyncFixture {
|
||||||
|
_temp: tempfile::TempDir,
|
||||||
|
db_path: PathBuf,
|
||||||
|
data_dir: PathBuf,
|
||||||
|
remote_dir: PathBuf,
|
||||||
|
project_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncFixture {
|
||||||
|
fn new() -> Self {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let db_path = temp.path().join("ruds.db");
|
||||||
|
let data_dir = temp.path().join("project");
|
||||||
|
let remote_dir = temp.path().join("remote.git");
|
||||||
|
let upstream_dir = temp.path().join("upstream");
|
||||||
|
let db = Database::open(&db_path).unwrap();
|
||||||
|
db.migrate().unwrap();
|
||||||
|
crate::db::fts::ensure_fts_tables(db.conn()).unwrap();
|
||||||
|
let project = engine::project::create_project(
|
||||||
|
db.conn(),
|
||||||
|
"Active",
|
||||||
|
Some(data_dir.to_str().unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
engine::project::set_active_project(db.conn(), &project.id).unwrap();
|
||||||
|
drop(db);
|
||||||
|
|
||||||
|
git(&data_dir, &["init", "-b", "master"]);
|
||||||
|
configure_git(&data_dir);
|
||||||
|
git(&data_dir, &["add", "-A"]);
|
||||||
|
git(&data_dir, &["commit", "-m", "Initial project"]);
|
||||||
|
git(
|
||||||
|
temp.path(),
|
||||||
|
&[
|
||||||
|
"init",
|
||||||
|
"--bare",
|
||||||
|
"-b",
|
||||||
|
"master",
|
||||||
|
remote_dir.to_str().unwrap(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
git(
|
||||||
|
&data_dir,
|
||||||
|
&["remote", "add", "origin", remote_dir.to_str().unwrap()],
|
||||||
|
);
|
||||||
|
git(&data_dir, &["push", "-u", "origin", "master"]);
|
||||||
|
git(
|
||||||
|
temp.path(),
|
||||||
|
&[
|
||||||
|
"clone",
|
||||||
|
remote_dir.to_str().unwrap(),
|
||||||
|
upstream_dir.to_str().unwrap(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
configure_git(&upstream_dir);
|
||||||
|
let mut metadata = engine::meta::read_project_json(&upstream_dir).unwrap();
|
||||||
|
metadata.name = "Pulled Name".into();
|
||||||
|
engine::meta::write_project_json(&upstream_dir, &metadata).unwrap();
|
||||||
|
git(&upstream_dir, &["add", "-A"]);
|
||||||
|
git(&upstream_dir, &["commit", "-m", "Update project name"]);
|
||||||
|
git(&upstream_dir, &["push", "origin", "master"]);
|
||||||
|
fs::write(data_dir.join("pending.txt"), "pending").unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
_temp: temp,
|
||||||
|
db_path,
|
||||||
|
data_dir,
|
||||||
|
remote_dir,
|
||||||
|
project_id: project.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host(&self, offline: bool) -> CoreHost {
|
||||||
|
CoreHost::new(&self.db_path, &self.project_id, &self.data_dir)
|
||||||
|
.with_offline_mode(offline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_git(dir: &Path) {
|
||||||
|
git(dir, &["config", "user.name", "Lua Test"]);
|
||||||
|
git(dir, &["config", "user.email", "lua@example.invalid"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn git(dir: &Path, args: &[&str]) -> String {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(args)
|
||||||
|
.current_dir(dir)
|
||||||
|
.env("GIT_TERMINAL_PROMPT", "0")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"git {} failed: {}",
|
||||||
|
args.join(" "),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
String::from_utf8(output.stdout).unwrap().trim().to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_namespace_runs_every_bds2_method_and_reconciles_pull() {
|
||||||
|
let fixture = SyncFixture::new();
|
||||||
|
let result = execute_with_host(
|
||||||
|
r#"
|
||||||
|
function main()
|
||||||
|
local status = bds.sync.get_status()
|
||||||
|
local history = bds.sync.get_history()
|
||||||
|
return {
|
||||||
|
available = bds.sync.check_availability(),
|
||||||
|
repo = bds.sync.get_repo_state(),
|
||||||
|
remote = bds.sync.get_remote_state(),
|
||||||
|
status = status,
|
||||||
|
history = history,
|
||||||
|
fetched = bds.sync.fetch(),
|
||||||
|
pulled = bds.sync.pull(),
|
||||||
|
committed = bds.sync.commit_all("Lua commit"),
|
||||||
|
pushed = bds.sync.push(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
"#,
|
||||||
|
"main",
|
||||||
|
&Value::Null,
|
||||||
|
ExecutionKind::Utility,
|
||||||
|
&ExecutionControl::default(),
|
||||||
|
Arc::new(fixture.host(false)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.value["available"], true);
|
||||||
|
assert_eq!(result.value["repo"]["is_initialized"], true);
|
||||||
|
assert_eq!(result.value["remote"], result.value["repo"]);
|
||||||
|
assert!(
|
||||||
|
result.value["status"]["files"]
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.any(|file| file == &json!({"path":"pending.txt","status":"untracked"}))
|
||||||
|
);
|
||||||
|
let commit = &result.value["history"]["commits"][0];
|
||||||
|
assert!(commit["date"].as_str().is_some_and(|date| date.len() == 10));
|
||||||
|
assert!(commit["sync_status"]["kind"].is_string());
|
||||||
|
assert_eq!(result.value["fetched"]["updated"], true);
|
||||||
|
assert_eq!(result.value["pulled"]["updated"], true);
|
||||||
|
assert_eq!(result.value["committed"]["message"], "Lua commit");
|
||||||
|
assert_eq!(result.value["pushed"]["updated"], true);
|
||||||
|
|
||||||
|
let db = Database::open(&fixture.db_path).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
project::get_project_by_id(db.conn(), &fixture.project_id)
|
||||||
|
.unwrap()
|
||||||
|
.name,
|
||||||
|
"Pulled Name"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
git(&fixture.remote_dir, &["log", "-1", "--format=%s"]),
|
||||||
|
"Lua commit"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_namespace_uses_nil_for_non_repository_failures() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let data_dir = temp.path().join("not-a-repository");
|
||||||
|
fs::create_dir(&data_dir).unwrap();
|
||||||
|
let result = execute_with_host(
|
||||||
|
r#"
|
||||||
|
function main()
|
||||||
|
return {
|
||||||
|
repo = bds.sync.get_repo_state(),
|
||||||
|
remote = bds.sync.get_remote_state(),
|
||||||
|
status = bds.sync.get_status(),
|
||||||
|
history = bds.sync.get_history(),
|
||||||
|
fetched = bds.sync.fetch(),
|
||||||
|
pulled = bds.sync.pull(),
|
||||||
|
pushed = bds.sync.push(),
|
||||||
|
committed = bds.sync.commit_all("no repository"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
"#,
|
||||||
|
"main",
|
||||||
|
&Value::Null,
|
||||||
|
ExecutionKind::Utility,
|
||||||
|
&ExecutionControl::default(),
|
||||||
|
Arc::new(CoreHost::new(
|
||||||
|
temp.path().join("missing.db"),
|
||||||
|
"p1",
|
||||||
|
&data_dir,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.value["repo"]["is_initialized"], false);
|
||||||
|
assert_eq!(result.value["remote"], result.value["repo"]);
|
||||||
|
assert_eq!(result.value["history"], json!({"commits": []}));
|
||||||
|
for key in ["status", "fetched", "pulled", "pushed", "committed"] {
|
||||||
|
assert!(result.value[key].is_null(), "{key} was not nil");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_namespace_airplane_gate_prevents_network_commands_and_rejects_blank_commit() {
|
||||||
|
let fixture = SyncFixture::new();
|
||||||
|
git(&fixture.data_dir, &["add", "pending.txt"]);
|
||||||
|
git(&fixture.data_dir, &["commit", "-m", "Local only"]);
|
||||||
|
let local_head = git(&fixture.data_dir, &["rev-parse", "HEAD"]);
|
||||||
|
let tracking_head = git(
|
||||||
|
&fixture.data_dir,
|
||||||
|
&["rev-parse", "refs/remotes/origin/master"],
|
||||||
|
);
|
||||||
|
let remote_head = git(&fixture.remote_dir, &["rev-parse", "refs/heads/master"]);
|
||||||
|
|
||||||
|
let result = execute_with_host(
|
||||||
|
r#"
|
||||||
|
function main()
|
||||||
|
return {
|
||||||
|
fetched = bds.sync.fetch(),
|
||||||
|
pulled = bds.sync.pull(),
|
||||||
|
pushed = bds.sync.push(),
|
||||||
|
committed = bds.sync.commit_all(" "),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
"#,
|
||||||
|
"main",
|
||||||
|
&Value::Null,
|
||||||
|
ExecutionKind::Utility,
|
||||||
|
&ExecutionControl::default(),
|
||||||
|
Arc::new(fixture.host(true)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for key in ["fetched", "pulled", "pushed", "committed"] {
|
||||||
|
assert!(result.value[key].is_null(), "{key} was not nil");
|
||||||
|
}
|
||||||
|
assert_eq!(git(&fixture.data_dir, &["rev-parse", "HEAD"]), local_head);
|
||||||
|
assert_eq!(
|
||||||
|
git(
|
||||||
|
&fixture.data_dir,
|
||||||
|
&["rev-parse", "refs/remotes/origin/master"],
|
||||||
|
),
|
||||||
|
tracking_head
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
git(&fixture.remote_dir, &["rev-parse", "refs/heads/master"]),
|
||||||
|
remote_head
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_host_round_trips_engines_and_enforces_scope_and_failure_values() {
|
fn project_host_round_trips_engines_and_enforces_scope_and_failure_values() {
|
||||||
|
|||||||
@@ -376,6 +376,23 @@ fn field_description(field: &str) -> &'static str {
|
|||||||
"slug" => "URL-safe record identifier.",
|
"slug" => "URL-safe record identifier.",
|
||||||
"description" => "Human-readable description.",
|
"description" => "Human-readable description.",
|
||||||
"status" => "Current lifecycle state.",
|
"status" => "Current lifecycle state.",
|
||||||
|
"kind" => "Public enum value.",
|
||||||
|
"path" => "Current repository-relative path.",
|
||||||
|
"old_path" => "Previous repository-relative path for a rename.",
|
||||||
|
"files" => "Changed repository paths.",
|
||||||
|
"commits" => "Commit history entries.",
|
||||||
|
"hash" => "Git object identifier.",
|
||||||
|
"subject" => "Commit subject.",
|
||||||
|
"author" => "Commit author.",
|
||||||
|
"date" => "ISO-8601 commit date.",
|
||||||
|
"sync_status" => "Local/remote presence classification.",
|
||||||
|
"current_branch" => "Checked-out branch name.",
|
||||||
|
"has_lfs" => "Whether Git LFS tracking is configured.",
|
||||||
|
"is_initialized" => "Whether the project folder is a Git repository.",
|
||||||
|
"provider" => "Hosting provider inferred from the remote URL.",
|
||||||
|
"remote_url" => "Configured origin URL.",
|
||||||
|
"output" => "Git command output.",
|
||||||
|
"updated" => "Whether the Git network command completed successfully.",
|
||||||
"progress" => "Completion value reported by the task.",
|
"progress" => "Completion value reported by the task.",
|
||||||
"message" => "Latest user-facing task message.",
|
"message" => "Latest user-facing task message.",
|
||||||
"created_at" => "Creation timestamp.",
|
"created_at" => "Creation timestamp.",
|
||||||
@@ -403,7 +420,6 @@ fn field_description(field: &str) -> &'static str {
|
|||||||
"file_path" => "Stored media file path.",
|
"file_path" => "Stored media file path.",
|
||||||
"alt" => "Alternative text for the media.",
|
"alt" => "Alternative text for the media.",
|
||||||
"caption" => "Media caption.",
|
"caption" => "Media caption.",
|
||||||
"kind" => "Script or template kind.",
|
|
||||||
"entrypoint" => "Lua function invoked by the runtime.",
|
"entrypoint" => "Lua function invoked by the runtime.",
|
||||||
"color" => "Optional display color.",
|
"color" => "Optional display color.",
|
||||||
"post_template_slug" => "Template selected for tagged posts.",
|
"post_template_slug" => "Template selected for tagged posts.",
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ mod tests {
|
|||||||
"projects",
|
"projects",
|
||||||
"publish",
|
"publish",
|
||||||
"scripts",
|
"scripts",
|
||||||
|
"sync",
|
||||||
"tags",
|
"tags",
|
||||||
"tasks",
|
"tasks",
|
||||||
"templates",
|
"templates",
|
||||||
@@ -473,7 +474,7 @@ mod tests {
|
|||||||
r#"
|
r#"
|
||||||
function main()
|
function main()
|
||||||
return {
|
return {
|
||||||
sync = bds.sync,
|
sync = type(bds.sync),
|
||||||
embeddings = type(bds.embeddings),
|
embeddings = type(bds.embeddings),
|
||||||
report_progress = type(bds.report_progress),
|
report_progress = type(bds.report_progress),
|
||||||
post_search = type(bds.posts.search),
|
post_search = type(bds.posts.search),
|
||||||
@@ -494,6 +495,7 @@ mod tests {
|
|||||||
"post_search": "function",
|
"post_search": "function",
|
||||||
"app_toast": "function",
|
"app_toast": "function",
|
||||||
"embeddings": "table",
|
"embeddings": "table",
|
||||||
|
"sync": "table",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -515,6 +517,7 @@ mod tests {
|
|||||||
bds.tags.get_all(),
|
bds.tags.get_all(),
|
||||||
bds.tasks.status_snapshot(),
|
bds.tasks.status_snapshot(),
|
||||||
bds.publish.upload_site({}),
|
bds.publish.upload_site({}),
|
||||||
|
bds.sync.check_availability(),
|
||||||
bds.chat.detect_post_language("title", "body"),
|
bds.chat.detect_post_language("title", "body"),
|
||||||
bds.embeddings.get_progress(),
|
bds.embeddings.get_progress(),
|
||||||
}
|
}
|
||||||
@@ -528,8 +531,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(execution.value.as_array().unwrap().len(), 12);
|
assert_eq!(execution.value.as_array().unwrap().len(), 13);
|
||||||
assert_eq!(host.0.lock().unwrap().len(), 12);
|
assert_eq!(host.0.lock().unwrap().len(), 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -352,7 +352,9 @@ fn user_guide_path() -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn root_document_path(name: &str) -> PathBuf {
|
fn root_document_path(name: &str) -> PathBuf {
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").join(name)
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../..")
|
||||||
|
.join(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_document(source: &str) -> ParsedDocument {
|
pub fn parse_document(source: &str) -> ParsedDocument {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Macro scripts expose `render(input, context)` and transform scripts expose `main
|
|||||||
- [`bds.projects`](#bdsprojects)
|
- [`bds.projects`](#bdsprojects)
|
||||||
- [`bds.publish`](#bdspublish)
|
- [`bds.publish`](#bdspublish)
|
||||||
- [`bds.scripts`](#bdsscripts)
|
- [`bds.scripts`](#bdsscripts)
|
||||||
|
- [`bds.sync`](#bdssync)
|
||||||
- [`bds.tags`](#bdstags)
|
- [`bds.tags`](#bdstags)
|
||||||
- [`bds.tasks`](#bdstasks)
|
- [`bds.tasks`](#bdstasks)
|
||||||
- [`bds.templates`](#bdstemplates)
|
- [`bds.templates`](#bdstemplates)
|
||||||
@@ -3987,6 +3988,280 @@ local result = bds.scripts.rebuild_from_files()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `bds.sync`
|
||||||
|
|
||||||
|
### `bds.sync.check_availability`
|
||||||
|
|
||||||
|
Return whether Git is available on the current machine.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.check_availability() -> boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`boolean`. `false` means the operation was rejected or failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.check_availability()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.get_repo_state`
|
||||||
|
|
||||||
|
Return repository state for the active project using the GitRepositoryState shape.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.get_repo_state() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.get_repo_state()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.get_status`
|
||||||
|
|
||||||
|
Return Git status for the active project using the GitStatusResult shape.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.get_status() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.get_status()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.get_history`
|
||||||
|
|
||||||
|
Return commit history for the active project using the GitHistoryResult shape.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.get_history() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.get_history()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.get_remote_state`
|
||||||
|
|
||||||
|
Return the GitRepositoryState for the active project, matching the bDS2 remote-state alias.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.get_remote_state() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.get_remote_state()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.fetch`
|
||||||
|
|
||||||
|
Fetch and prune remote Git refs for the active project, returning GitNetworkResult; unavailable in airplane mode.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.fetch() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.fetch()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.pull`
|
||||||
|
|
||||||
|
Fast-forward pull the active project, reconcile its cache database, and return GitNetworkResult; unavailable in airplane mode.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.pull() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.pull()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.push`
|
||||||
|
|
||||||
|
Push the active project to its configured remote and return GitNetworkResult; unavailable in airplane mode.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.push() -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.push()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bds.sync.commit_all`
|
||||||
|
|
||||||
|
Stage and commit every pending change in the active project, returning GitCommitResult.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```text
|
||||||
|
bds.sync.commit_all(message: string) -> table | nil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Required | Example |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `message` | `string` | Yes | `"Working"` |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`table | nil`. `nil` means no value was available or the host operation failed.
|
||||||
|
|
||||||
|
**Example call**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result = bds.sync.commit_all("Working")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{ key = "value" }
|
||||||
|
```
|
||||||
|
|
||||||
## `bds.tags`
|
## `bds.tags`
|
||||||
|
|
||||||
### `bds.tags.create`
|
### `bds.tags.create`
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ These are the public, JSON-compatible records returned by the Lua host API. They
|
|||||||
- [`TagData`](#tagdata)
|
- [`TagData`](#tagdata)
|
||||||
- [`TaskData`](#taskdata)
|
- [`TaskData`](#taskdata)
|
||||||
- [`TaskStatus`](#taskstatus)
|
- [`TaskStatus`](#taskstatus)
|
||||||
|
- [`GitProviderData`](#gitproviderdata)
|
||||||
|
- [`GitRepositoryState`](#gitrepositorystate)
|
||||||
|
- [`GitFileStatus`](#gitfilestatus)
|
||||||
|
- [`GitStatusResult`](#gitstatusresult)
|
||||||
|
- [`GitSyncStatus`](#gitsyncstatus)
|
||||||
|
- [`GitCommitData`](#gitcommitdata)
|
||||||
|
- [`GitHistoryResult`](#githistoryresult)
|
||||||
|
- [`GitNetworkResult`](#gitnetworkresult)
|
||||||
|
- [`GitCommitResult`](#gitcommitresult)
|
||||||
- [`ValidationResult`](#validationresult)
|
- [`ValidationResult`](#validationresult)
|
||||||
|
|
||||||
## `ProjectData`
|
## `ProjectData`
|
||||||
@@ -185,7 +194,7 @@ Lua script record.
|
|||||||
| `enabled` | `boolean` | Yes | Whether the record is enabled. |
|
| `enabled` | `boolean` | Yes | Whether the record is enabled. |
|
||||||
| `entrypoint` | `string` | Yes | Lua function invoked by the runtime. |
|
| `entrypoint` | `string` | Yes | Lua function invoked by the runtime. |
|
||||||
| `id` | `string` | Yes | Stable record identifier. |
|
| `id` | `string` | Yes | Stable record identifier. |
|
||||||
| `kind` | `string` | Yes | Script or template kind. |
|
| `kind` | `string` | Yes | Public enum value. |
|
||||||
| `project_id` | `string` | Yes | Identifier of the owning project. |
|
| `project_id` | `string` | Yes | Identifier of the owning project. |
|
||||||
| `slug` | `string` | Yes | URL-safe record identifier. |
|
| `slug` | `string` | Yes | URL-safe record identifier. |
|
||||||
| `status` | `string` | Yes | Current lifecycle state. |
|
| `status` | `string` | Yes | Current lifecycle state. |
|
||||||
@@ -217,7 +226,7 @@ Template record for site rendering.
|
|||||||
| `created_at` | `ISO-8601 string` | Yes | Creation timestamp. |
|
| `created_at` | `ISO-8601 string` | Yes | Creation timestamp. |
|
||||||
| `enabled` | `boolean` | Yes | Whether the record is enabled. |
|
| `enabled` | `boolean` | Yes | Whether the record is enabled. |
|
||||||
| `id` | `string` | Yes | Stable record identifier. |
|
| `id` | `string` | Yes | Stable record identifier. |
|
||||||
| `kind` | `string` | Yes | Script or template kind. |
|
| `kind` | `string` | Yes | Public enum value. |
|
||||||
| `project_id` | `string` | Yes | Identifier of the owning project. |
|
| `project_id` | `string` | Yes | Identifier of the owning project. |
|
||||||
| `slug` | `string` | Yes | URL-safe record identifier. |
|
| `slug` | `string` | Yes | URL-safe record identifier. |
|
||||||
| `status` | `string` | Yes | Current lifecycle state. |
|
| `status` | `string` | Yes | Current lifecycle state. |
|
||||||
@@ -306,6 +315,194 @@ Aggregate task status snapshot.
|
|||||||
| `running_count` | `integer` | Yes | Number of currently running tasks. |
|
| `running_count` | `integer` | Yes | Number of currently running tasks. |
|
||||||
| `tasks` | `TaskData[]` | Yes | Tasks included in this status snapshot. |
|
| `tasks` | `TaskData[]` | Yes | Tasks included in this status snapshot. |
|
||||||
|
|
||||||
|
## `GitProviderData`
|
||||||
|
|
||||||
|
Git hosting provider classification used by bDS2 repository state.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
kind = "utility",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `kind` | `string` | Yes | Public enum value. |
|
||||||
|
|
||||||
|
## `GitRepositoryState`
|
||||||
|
|
||||||
|
Repository state returned by bds.sync.get_repo_state and get_remote_state.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
current_branch = "example",
|
||||||
|
has_lfs = true,
|
||||||
|
is_initialized = true,
|
||||||
|
provider = {
|
||||||
|
kind = "utility",
|
||||||
|
},
|
||||||
|
remote_url = "example",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `current_branch` | `string \| nil` | No | Checked-out branch name. |
|
||||||
|
| `has_lfs` | `boolean` | Yes | Whether Git LFS tracking is configured. |
|
||||||
|
| `is_initialized` | `boolean` | Yes | Whether the project folder is a Git repository. |
|
||||||
|
| `provider` | `GitProviderData \| nil` | No | Hosting provider inferred from the remote URL. |
|
||||||
|
| `remote_url` | `string \| nil` | No | Configured origin URL. |
|
||||||
|
|
||||||
|
## `GitFileStatus`
|
||||||
|
|
||||||
|
One changed path in the active repository.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
old_path = "example",
|
||||||
|
path = "example",
|
||||||
|
status = "draft",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `old_path` | `string \| nil` | No | Previous repository-relative path for a rename. |
|
||||||
|
| `path` | `string` | Yes | Current repository-relative path. |
|
||||||
|
| `status` | `string` | Yes | Current lifecycle state. |
|
||||||
|
|
||||||
|
## `GitStatusResult`
|
||||||
|
|
||||||
|
Repository working-tree status returned by bds.sync.get_status.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
files = {
|
||||||
|
{
|
||||||
|
old_path = "example",
|
||||||
|
path = "example",
|
||||||
|
status = "draft",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `files` | `GitFileStatus[]` | Yes | Changed repository paths. |
|
||||||
|
|
||||||
|
## `GitSyncStatus`
|
||||||
|
|
||||||
|
Whether a commit exists locally, remotely, or in both histories.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
kind = "utility",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `kind` | `string` | Yes | Public enum value. |
|
||||||
|
|
||||||
|
## `GitCommitData`
|
||||||
|
|
||||||
|
Commit entry returned in bds.sync history.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
author = "example",
|
||||||
|
date = "2026-07-19T08:00:00Z",
|
||||||
|
hash = "example",
|
||||||
|
subject = "example",
|
||||||
|
sync_status = {
|
||||||
|
kind = "utility",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `author` | `string \| nil` | No | Commit author. |
|
||||||
|
| `date` | `ISO-8601 string \| nil` | No | ISO-8601 commit date. |
|
||||||
|
| `hash` | `string` | Yes | Git object identifier. |
|
||||||
|
| `subject` | `string \| nil` | No | Commit subject. |
|
||||||
|
| `sync_status` | `GitSyncStatus` | Yes | Local/remote presence classification. |
|
||||||
|
|
||||||
|
## `GitHistoryResult`
|
||||||
|
|
||||||
|
Commit history returned by bds.sync.get_history.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
commits = {
|
||||||
|
{
|
||||||
|
author = "example",
|
||||||
|
date = "2026-07-19T08:00:00Z",
|
||||||
|
hash = "example",
|
||||||
|
subject = "example",
|
||||||
|
sync_status = {
|
||||||
|
kind = "utility",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `commits` | `GitCommitData[]` | Yes | Commit history entries. |
|
||||||
|
|
||||||
|
## `GitNetworkResult`
|
||||||
|
|
||||||
|
Result of a successful bds.sync network operation.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
output = "example",
|
||||||
|
updated = true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `output` | `string` | Yes | Git command output. |
|
||||||
|
| `updated` | `boolean` | Yes | Whether the Git network command completed successfully. |
|
||||||
|
|
||||||
|
## `GitCommitResult`
|
||||||
|
|
||||||
|
Result of successfully committing all pending repository changes.
|
||||||
|
|
||||||
|
**Lua shape**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
message = "Working",
|
||||||
|
output = "example",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Required | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `message` | `string` | Yes | Latest user-facing task message. |
|
||||||
|
| `output` | `string` | Yes | Git command output. |
|
||||||
|
|
||||||
## `ValidationResult`
|
## `ValidationResult`
|
||||||
|
|
||||||
Template validation result.
|
Template validation result.
|
||||||
|
|||||||
@@ -1428,6 +1428,75 @@
|
|||||||
"params": [],
|
"params": [],
|
||||||
"returns": "boolean"
|
"returns": "boolean"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Return whether Git is available on the current machine.",
|
||||||
|
"name": "check_availability",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return repository state for the active project using the GitRepositoryState shape.",
|
||||||
|
"name": "get_repo_state",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return Git status for the active project using the GitStatusResult shape.",
|
||||||
|
"name": "get_status",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return commit history for the active project using the GitHistoryResult shape.",
|
||||||
|
"name": "get_history",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return the GitRepositoryState for the active project, matching the bDS2 remote-state alias.",
|
||||||
|
"name": "get_remote_state",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fetch and prune remote Git refs for the active project, returning GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"name": "fetch",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fast-forward pull the active project, reconcile its cache database, and return GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"name": "pull",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Push the active project to its configured remote and return GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"name": "push",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Stage and commit every pending change in the active project, returning GitCommitResult.",
|
||||||
|
"name": "commit_all",
|
||||||
|
"namespace": "sync",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Upload the rendered site using the provided publishing credentials.",
|
"description": "Upload the rendered site using the provided publishing credentials.",
|
||||||
"name": "upload_site",
|
"name": "upload_site",
|
||||||
@@ -1782,6 +1851,81 @@
|
|||||||
},
|
},
|
||||||
"name": "TaskStatus"
|
"name": "TaskStatus"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Git hosting provider classification used by bDS2 repository state.",
|
||||||
|
"fields": {
|
||||||
|
"kind": "string"
|
||||||
|
},
|
||||||
|
"name": "GitProviderData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Repository state returned by bds.sync.get_repo_state and get_remote_state.",
|
||||||
|
"fields": {
|
||||||
|
"current_branch": "string | nil",
|
||||||
|
"has_lfs": "boolean",
|
||||||
|
"is_initialized": "boolean",
|
||||||
|
"provider": "GitProviderData | nil",
|
||||||
|
"remote_url": "string | nil"
|
||||||
|
},
|
||||||
|
"name": "GitRepositoryState"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "One changed path in the active repository.",
|
||||||
|
"fields": {
|
||||||
|
"old_path": "string | nil",
|
||||||
|
"path": "string",
|
||||||
|
"status": "string"
|
||||||
|
},
|
||||||
|
"name": "GitFileStatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Repository working-tree status returned by bds.sync.get_status.",
|
||||||
|
"fields": {
|
||||||
|
"files": "GitFileStatus[]"
|
||||||
|
},
|
||||||
|
"name": "GitStatusResult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Whether a commit exists locally, remotely, or in both histories.",
|
||||||
|
"fields": {
|
||||||
|
"kind": "string"
|
||||||
|
},
|
||||||
|
"name": "GitSyncStatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Commit entry returned in bds.sync history.",
|
||||||
|
"fields": {
|
||||||
|
"author": "string | nil",
|
||||||
|
"date": "ISO-8601 string | nil",
|
||||||
|
"hash": "string",
|
||||||
|
"subject": "string | nil",
|
||||||
|
"sync_status": "GitSyncStatus"
|
||||||
|
},
|
||||||
|
"name": "GitCommitData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Commit history returned by bds.sync.get_history.",
|
||||||
|
"fields": {
|
||||||
|
"commits": "GitCommitData[]"
|
||||||
|
},
|
||||||
|
"name": "GitHistoryResult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Result of a successful bds.sync network operation.",
|
||||||
|
"fields": {
|
||||||
|
"output": "string",
|
||||||
|
"updated": "boolean"
|
||||||
|
},
|
||||||
|
"name": "GitNetworkResult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Result of successfully committing all pending repository changes.",
|
||||||
|
"fields": {
|
||||||
|
"message": "string",
|
||||||
|
"output": "string"
|
||||||
|
},
|
||||||
|
"name": "GitCommitResult"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Template validation result.",
|
"description": "Template validation result.",
|
||||||
"fields": {
|
"fields": {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1546,6 +1546,84 @@
|
|||||||
"parameters": [],
|
"parameters": [],
|
||||||
"returns": "boolean"
|
"returns": "boolean"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Return whether Git is available on the current machine.",
|
||||||
|
"detail": "bds.sync.check_availability() -> boolean",
|
||||||
|
"insert_text": "bds.sync.check_availability()",
|
||||||
|
"label": "bds.sync.check_availability",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return repository state for the active project using the GitRepositoryState shape.",
|
||||||
|
"detail": "bds.sync.get_repo_state() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.get_repo_state()",
|
||||||
|
"label": "bds.sync.get_repo_state",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return Git status for the active project using the GitStatusResult shape.",
|
||||||
|
"detail": "bds.sync.get_status() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.get_status()",
|
||||||
|
"label": "bds.sync.get_status",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return commit history for the active project using the GitHistoryResult shape.",
|
||||||
|
"detail": "bds.sync.get_history() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.get_history()",
|
||||||
|
"label": "bds.sync.get_history",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Return the GitRepositoryState for the active project, matching the bDS2 remote-state alias.",
|
||||||
|
"detail": "bds.sync.get_remote_state() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.get_remote_state()",
|
||||||
|
"label": "bds.sync.get_remote_state",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fetch and prune remote Git refs for the active project, returning GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"detail": "bds.sync.fetch() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.fetch()",
|
||||||
|
"label": "bds.sync.fetch",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fast-forward pull the active project, reconcile its cache database, and return GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"detail": "bds.sync.pull() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.pull()",
|
||||||
|
"label": "bds.sync.pull",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Push the active project to its configured remote and return GitNetworkResult; unavailable in airplane mode.",
|
||||||
|
"detail": "bds.sync.push() -> table | nil",
|
||||||
|
"insert_text": "bds.sync.push()",
|
||||||
|
"label": "bds.sync.push",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Stage and commit every pending change in the active project, returning GitCommitResult.",
|
||||||
|
"detail": "bds.sync.commit_all(message: string) -> table | nil",
|
||||||
|
"insert_text": "bds.sync.commit_all(\"Working\")",
|
||||||
|
"label": "bds.sync.commit_all",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": "table | nil"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Upload the rendered site using the provided publishing credentials.",
|
"description": "Upload the rendered site using the provided publishing credentials.",
|
||||||
"detail": "bds.publish.upload_site(credentials: table) -> TaskData | nil",
|
"detail": "bds.publish.upload_site(credentials: table) -> TaskData | nil",
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ surface ScriptRuntimeSurface {
|
|||||||
-- sync_from_posts.
|
-- sync_from_posts.
|
||||||
-- bds.tasks: get, status_snapshot, cancel, get_all, get_running,
|
-- bds.tasks: get, status_snapshot, cancel, get_all, get_running,
|
||||||
-- clear_completed.
|
-- clear_completed.
|
||||||
|
-- bds.sync: check_availability, get_repo_state, get_status,
|
||||||
|
-- get_history, get_remote_state, fetch, pull, push, commit_all.
|
||||||
-- bds.publish: upload_site.
|
-- bds.publish: upload_site.
|
||||||
-- bds.chat: detect_post_language, analyze_post, translate_post,
|
-- bds.chat: detect_post_language, analyze_post, translate_post,
|
||||||
-- analyze_media_image, detect_media_language,
|
-- analyze_media_image, detect_media_language,
|
||||||
@@ -144,7 +146,6 @@ surface ScriptRuntimeSurface {
|
|||||||
-- host may retain bds.app.progress and bds.app.toast as additive APIs.
|
-- host may retain bds.app.progress and bds.app.toast as additive APIs.
|
||||||
-- bds.embeddings: compute_similarities, dismiss_pair, find_duplicates,
|
-- bds.embeddings: compute_similarities, dismiss_pair, find_duplicates,
|
||||||
-- find_similar, get_progress, index_unindexed_posts, suggest_tags.
|
-- find_similar, get_progress, index_unindexed_posts, suggest_tags.
|
||||||
-- bds.sync remains a future extension contract.
|
|
||||||
|
|
||||||
@guarantee HostApiFailureValues
|
@guarantee HostApiFailureValues
|
||||||
-- Host failures do not expose implementation exceptions across the Lua
|
-- Host failures do not expose implementation exceptions across the Lua
|
||||||
|
|||||||
Reference in New Issue
Block a user