diff --git a/crates/db/.sqlx/query-92cb5228802d105bcfe9e36083f314c7c38039711bb50a21fc1dbbae84d80426.json b/crates/db/.sqlx/query-089ab7af5f065fd097e7e233347badf8467d4ad702ea33772fba8e0622f861b7.json
similarity index 61%
rename from crates/db/.sqlx/query-92cb5228802d105bcfe9e36083f314c7c38039711bb50a21fc1dbbae84d80426.json
rename to crates/db/.sqlx/query-089ab7af5f065fd097e7e233347badf8467d4ad702ea33772fba8e0622f861b7.json
index e634afdc..91db241c 100644
--- a/crates/db/.sqlx/query-92cb5228802d105bcfe9e36083f314c7c38039711bb50a21fc1dbbae84d80426.json
+++ b/crates/db/.sqlx/query-089ab7af5f065fd097e7e233347badf8467d4ad702ea33772fba8e0622f861b7.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
@@ -19,18 +19,23 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
@@ -42,9 +47,10 @@
false,
true,
true,
+ true,
false,
false
]
},
- "hash": "92cb5228802d105bcfe9e36083f314c7c38039711bb50a21fc1dbbae84d80426"
+ "hash": "089ab7af5f065fd097e7e233347badf8467d4ad702ea33772fba8e0622f861b7"
}
diff --git a/crates/db/.sqlx/query-066723f137b290d5863918c976100b674ce2f08c75cf53705ddc2259b9e64093.json b/crates/db/.sqlx/query-18716516b8473f0832677113853a73500423c944b642cdcd10790028ba6ebaad.json
similarity index 55%
rename from crates/db/.sqlx/query-066723f137b290d5863918c976100b674ce2f08c75cf53705ddc2259b9e64093.json
rename to crates/db/.sqlx/query-18716516b8473f0832677113853a73500423c944b642cdcd10790028ba6ebaad.json
index fe6292d6..88d8088c 100644
--- a/crates/db/.sqlx/query-066723f137b290d5863918c976100b674ce2f08c75cf53705ddc2259b9e64093.json
+++ b/crates/db/.sqlx/query-18716516b8473f0832677113853a73500423c944b642cdcd10790028ba6ebaad.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN task_attempts ta ON ta.task_id = t.id\n ORDER BY ta.updated_at DESC\n )\n LIMIT $1\n ",
+ "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script, p.dev_script_working_dir,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN task_attempts ta ON ta.task_id = t.id\n ORDER BY ta.updated_at DESC\n )\n LIMIT $1\n ",
"describe": {
"columns": [
{
@@ -19,18 +19,23 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
@@ -42,9 +47,10 @@
false,
true,
true,
+ true,
false,
false
]
},
- "hash": "066723f137b290d5863918c976100b674ce2f08c75cf53705ddc2259b9e64093"
+ "hash": "18716516b8473f0832677113853a73500423c944b642cdcd10790028ba6ebaad"
}
diff --git a/crates/db/.sqlx/query-1163d3dcc69f930d52dbf8175689aa16b4419b7c1bead43eedef4c82e8a5c442.json b/crates/db/.sqlx/query-85fd1ae6ff6ab171c0ca452709697e659e75def14212c7cf69fd80bb85b0f7db.json
similarity index 68%
rename from crates/db/.sqlx/query-1163d3dcc69f930d52dbf8175689aa16b4419b7c1bead43eedef4c82e8a5c442.json
rename to crates/db/.sqlx/query-85fd1ae6ff6ab171c0ca452709697e659e75def14212c7cf69fd80bb85b0f7db.json
index 9cf756d5..21c70e18 100644
--- a/crates/db/.sqlx/query-1163d3dcc69f930d52dbf8175689aa16b4419b7c1bead43eedef4c82e8a5c442.json
+++ b/crates/db/.sqlx/query-85fd1ae6ff6ab171c0ca452709697e659e75def14212c7cf69fd80bb85b0f7db.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
+ "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
"describe": {
"columns": [
{
@@ -19,18 +19,23 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
@@ -41,10 +46,11 @@
true,
false,
true,
+ false,
true,
false,
false
]
},
- "hash": "1163d3dcc69f930d52dbf8175689aa16b4419b7c1bead43eedef4c82e8a5c442"
+ "hash": "85fd1ae6ff6ab171c0ca452709697e659e75def14212c7cf69fd80bb85b0f7db"
}
diff --git a/crates/db/.sqlx/query-1d1fccd2d177a09d3e1d091ca90349f2fc5a5a77e36bec46765f98cd4ee9c837.json b/crates/db/.sqlx/query-a25e23ffeeeec845811692df45efafe76b2ca89a19c59b41492d17fd52169356.json
similarity index 60%
rename from crates/db/.sqlx/query-1d1fccd2d177a09d3e1d091ca90349f2fc5a5a77e36bec46765f98cd4ee9c837.json
rename to crates/db/.sqlx/query-a25e23ffeeeec845811692df45efafe76b2ca89a19c59b41492d17fd52169356.json
index 56e59811..8ace76be 100644
--- a/crates/db/.sqlx/query-1d1fccd2d177a09d3e1d091ca90349f2fc5a5a77e36bec46765f98cd4ee9c837.json
+++ b/crates/db/.sqlx/query-a25e23ffeeeec845811692df45efafe76b2ca89a19c59b41492d17fd52169356.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
"describe": {
"columns": [
{
@@ -19,18 +19,23 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
@@ -42,9 +47,10 @@
false,
true,
true,
+ true,
false,
false
]
},
- "hash": "1d1fccd2d177a09d3e1d091ca90349f2fc5a5a77e36bec46765f98cd4ee9c837"
+ "hash": "a25e23ffeeeec845811692df45efafe76b2ca89a19c59b41492d17fd52169356"
}
diff --git a/crates/db/.sqlx/query-2c9c27d9781af4e7c1b5a6f15530c420255fcf6844fdc708422da887760c2693.json b/crates/db/.sqlx/query-ad85226d4ebe3d4eb3df8d2d6087146929ec6ea7345395fda9498bae9507c152.json
similarity index 56%
rename from crates/db/.sqlx/query-2c9c27d9781af4e7c1b5a6f15530c420255fcf6844fdc708422da887760c2693.json
rename to crates/db/.sqlx/query-ad85226d4ebe3d4eb3df8d2d6087146929ec6ea7345395fda9498bae9507c152.json
index 948d51d5..ecf19f09 100644
--- a/crates/db/.sqlx/query-2c9c27d9781af4e7c1b5a6f15530c420255fcf6844fdc708422da887760c2693.json
+++ b/crates/db/.sqlx/query-ad85226d4ebe3d4eb3df8d2d6087146929ec6ea7345395fda9498bae9507c152.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "UPDATE projects\n SET name = $2, dev_script = $3\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
+ "query": "UPDATE projects\n SET name = $2, dev_script = $3, dev_script_working_dir = $4\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
"describe": {
"columns": [
{
@@ -19,32 +19,38 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
"parameters": {
- "Right": 3
+ "Right": 4
},
"nullable": [
true,
false,
true,
true,
+ true,
false,
false
]
},
- "hash": "2c9c27d9781af4e7c1b5a6f15530c420255fcf6844fdc708422da887760c2693"
+ "hash": "ad85226d4ebe3d4eb3df8d2d6087146929ec6ea7345395fda9498bae9507c152"
}
diff --git a/crates/db/.sqlx/query-4e7ef1b4c55cd0651e3c1f0535dbc29ad857c3fc7ec80d0ce5cb9bf097ea6490.json b/crates/db/.sqlx/query-e3ad69990a8d5e62a61d824db16a04d8893df37bc0980441d01f6e4e416702fa.json
similarity index 61%
rename from crates/db/.sqlx/query-4e7ef1b4c55cd0651e3c1f0535dbc29ad857c3fc7ec80d0ce5cb9bf097ea6490.json
rename to crates/db/.sqlx/query-e3ad69990a8d5e62a61d824db16a04d8893df37bc0980441d01f6e4e416702fa.json
index b957c835..44b13a7d 100644
--- a/crates/db/.sqlx/query-4e7ef1b4c55cd0651e3c1f0535dbc29ad857c3fc7ec80d0ce5cb9bf097ea6490.json
+++ b/crates/db/.sqlx/query-e3ad69990a8d5e62a61d824db16a04d8893df37bc0980441d01f6e4e416702fa.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -19,18 +19,23 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_script_working_dir",
"ordinal": 3,
+ "type_info": "Text"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 4,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 4,
+ "ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 5,
+ "ordinal": 6,
"type_info": "Text"
}
],
@@ -42,9 +47,10 @@
false,
true,
true,
+ true,
false,
false
]
},
- "hash": "4e7ef1b4c55cd0651e3c1f0535dbc29ad857c3fc7ec80d0ce5cb9bf097ea6490"
+ "hash": "e3ad69990a8d5e62a61d824db16a04d8893df37bc0980441d01f6e4e416702fa"
}
diff --git a/crates/db/migrations/20251216000000_add_dev_script_working_dir_to_projects.sql b/crates/db/migrations/20251216000000_add_dev_script_working_dir_to_projects.sql
new file mode 100644
index 00000000..ec95902f
--- /dev/null
+++ b/crates/db/migrations/20251216000000_add_dev_script_working_dir_to_projects.sql
@@ -0,0 +1 @@
+ALTER TABLE projects ADD COLUMN dev_script_working_dir TEXT DEFAULT '';
diff --git a/crates/db/src/models/project.rs b/crates/db/src/models/project.rs
index 12fdb69b..d27e978c 100644
--- a/crates/db/src/models/project.rs
+++ b/crates/db/src/models/project.rs
@@ -22,6 +22,7 @@ pub struct Project {
pub id: Uuid,
pub name: String,
pub dev_script: Option,
+ pub dev_script_working_dir: Option,
pub remote_project_id: Option,
#[ts(type = "Date")]
pub created_at: DateTime,
@@ -39,6 +40,7 @@ pub struct CreateProject {
pub struct UpdateProject {
pub name: Option,
pub dev_script: Option,
+ pub dev_script_working_dir: Option,
}
#[derive(Debug, Serialize, TS)]
@@ -68,6 +70,7 @@ impl Project {
r#"SELECT id as "id!: Uuid",
name,
dev_script,
+ dev_script_working_dir,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -83,7 +86,7 @@ impl Project {
sqlx::query_as!(
Project,
r#"
- SELECT p.id as "id!: Uuid", p.name, p.dev_script,
+ SELECT p.id as "id!: Uuid", p.name, p.dev_script, p.dev_script_working_dir,
p.remote_project_id as "remote_project_id: Uuid",
p.created_at as "created_at!: DateTime", p.updated_at as "updated_at!: DateTime"
FROM projects p
@@ -107,6 +110,7 @@ impl Project {
r#"SELECT id as "id!: Uuid",
name,
dev_script,
+ dev_script_working_dir,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -127,6 +131,7 @@ impl Project {
r#"SELECT id as "id!: Uuid",
name,
dev_script,
+ dev_script_working_dir,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -155,6 +160,7 @@ impl Project {
RETURNING id as "id!: Uuid",
name,
dev_script,
+ dev_script_working_dir,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime""#,
@@ -175,22 +181,25 @@ impl Project {
.ok_or(sqlx::Error::RowNotFound)?;
let name = payload.name.clone().unwrap_or(existing.name);
- let dev_script = payload.dev_script.clone().or(existing.dev_script);
+ let dev_script = payload.dev_script.clone();
+ let dev_script_working_dir = payload.dev_script_working_dir.clone();
sqlx::query_as!(
Project,
r#"UPDATE projects
- SET name = $2, dev_script = $3
+ SET name = $2, dev_script = $3, dev_script_working_dir = $4
WHERE id = $1
RETURNING id as "id!: Uuid",
name,
dev_script,
+ dev_script_working_dir,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime""#,
id,
name,
dev_script,
+ dev_script_working_dir,
)
.fetch_one(pool)
.await
diff --git a/crates/server/src/routes/task_attempts.rs b/crates/server/src/routes/task_attempts.rs
index 2e5b37e5..8770ddef 100644
--- a/crates/server/src/routes/task_attempts.rs
+++ b/crates/server/src/routes/task_attempts.rs
@@ -1263,12 +1263,19 @@ pub async fn start_dev_server(
)));
}
};
+
+ let working_dir = project
+ .dev_script_working_dir
+ .as_ref()
+ .filter(|dir| !dir.is_empty())
+ .cloned();
+
let executor_action = ExecutorAction::new(
ExecutorActionType::ScriptRequest(ScriptRequest {
script: dev_script,
language: ScriptRequestLanguage::Bash,
context: ScriptContext::DevServer,
- working_dir: None,
+ working_dir,
}),
None,
);
diff --git a/crates/services/src/services/container.rs b/crates/services/src/services/container.rs
index cf7cfb65..4c8db2e7 100644
--- a/crates/services/src/services/container.rs
+++ b/crates/services/src/services/container.rs
@@ -343,7 +343,7 @@ pub trait ContainerService {
}
/// Backfill repo names that were migrated with a sentinel placeholder.
- /// Also backfills dev_script for single-repo projects to prepend cd prefix.
+ /// Also backfills dev_script_working_dir for single-repo projects.
async fn backfill_repo_names(&self) -> Result<(), ContainerError> {
let pool = &self.db().pool;
let repos = Repo::list_needing_name_fix(pool).await?;
@@ -364,22 +364,30 @@ pub trait ContainerService {
Repo::update_name(pool, repo.id, &name, &name).await?;
- // Also update dev_script for single-repo projects
+ // Also update dev_script_working_dir for single-repo projects
let project_repos = ProjectRepo::find_by_repo_id(pool, repo.id).await?;
for pr in project_repos {
let all_repos = ProjectRepo::find_by_project_id(pool, pr.project_id).await?;
if all_repos.len() == 1
&& let Some(project) = Project::find_by_id(pool, pr.project_id).await?
- && let Some(old_script) = &project.dev_script
- && !old_script.is_empty()
+ && project
+ .dev_script
+ .as_ref()
+ .map(|s| !s.is_empty())
+ .unwrap_or(false)
+ && project
+ .dev_script_working_dir
+ .as_ref()
+ .map(|s| s.is_empty())
+ .unwrap_or(true)
{
- let new_script = format!("cd ./{} && {}", name, old_script);
Project::update(
pool,
pr.project_id,
&UpdateProject {
- name: None,
- dev_script: Some(new_script),
+ name: Some(project.name.clone()),
+ dev_script: project.dev_script.clone(),
+ dev_script_working_dir: Some(name.clone()),
},
)
.await?;
diff --git a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
index a2cb9eef..f2dda4cd 100644
--- a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
+++ b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
@@ -88,7 +88,10 @@ export function NoServerContent({
}
updateProject.mutate(
- { projectId: project.id, data: { name: null, dev_script: script } },
+ {
+ projectId: project.id,
+ data: { name: null, dev_script: script, dev_script_working_dir: null },
+ },
{
onSuccess: () => {
setIsEditingExistingScript(false);
diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json
index 1c482a36..d39499b1 100644
--- a/frontend/src/i18n/locales/en/settings.json
+++ b/frontend/src/i18n/locales/en/settings.json
@@ -342,7 +342,12 @@
},
"dev": {
"label": "Dev Server Script",
- "helper": "Starts a development server from task attempts. Scripts execute from the workspace directory (one level above each repo's worktree), so cd into the repo first (e.g., cd my-repo && pnpm run dev)."
+ "helper": "Starts a development server from task attempts. Scripts execute from the workspace directory (one level above each repo's worktree) unless a working directory is specified below."
+ },
+ "devWorkingDir": {
+ "label": "Dev Server Working Directory",
+ "placeholder": "e.g., my-repo",
+ "helper": "The directory to run the dev server script from, relative to the workspace root. Leave empty to run from the workspace root."
},
"cleanup": {
"label": "Cleanup Script",
diff --git a/frontend/src/i18n/locales/es/settings.json b/frontend/src/i18n/locales/es/settings.json
index 772894c3..3e6216d4 100644
--- a/frontend/src/i18n/locales/es/settings.json
+++ b/frontend/src/i18n/locales/es/settings.json
@@ -342,7 +342,12 @@
},
"dev": {
"label": "Script del Servidor de Desarrollo",
- "helper": "Inicia un servidor de desarrollo desde los intentos de tarea. Los scripts se ejecutan desde el directorio del workspace (un nivel arriba del worktree de cada repositorio), así que primero entra al directorio del repositorio (ej., cd mi-repo && pnpm run dev)."
+ "helper": "Inicia un servidor de desarrollo desde los intentos de tarea. Los scripts se ejecutan desde el directorio del workspace (un nivel arriba del worktree de cada repositorio) a menos que se especifique un directorio de trabajo abajo."
+ },
+ "devWorkingDir": {
+ "label": "Directorio de Trabajo del Servidor de Desarrollo",
+ "placeholder": "ej., mi-repo",
+ "helper": "El directorio desde el cual ejecutar el script del servidor de desarrollo, relativo a la raíz del workspace. Déjalo vacío para ejecutar desde la raíz del workspace."
},
"cleanup": {
"label": "Script de Limpieza",
diff --git a/frontend/src/i18n/locales/ja/settings.json b/frontend/src/i18n/locales/ja/settings.json
index d2cdfac2..a38c5426 100644
--- a/frontend/src/i18n/locales/ja/settings.json
+++ b/frontend/src/i18n/locales/ja/settings.json
@@ -342,7 +342,12 @@
},
"dev": {
"label": "開発サーバースクリプト",
- "helper": "タスク試行から開発サーバーを起動します。スクリプトはワークスペースディレクトリ(各リポジトリのワークツリーの1つ上のレベル)から実行されるため、まずリポジトリディレクトリに移動してください(例:cd my-repo && pnpm run dev)。"
+ "helper": "タスク試行から開発サーバーを起動します。スクリプトは、下記で作業ディレクトリが指定されていない限り、ワークスペースディレクトリ(各リポジトリのワークツリーの1つ上のレベル)から実行されます。"
+ },
+ "devWorkingDir": {
+ "label": "開発サーバー作業ディレクトリ",
+ "placeholder": "例:my-repo",
+ "helper": "開発サーバースクリプトを実行するディレクトリ。ワークスペースルートからの相対パス。空欄にするとワークスペースルートから実行します。"
},
"cleanup": {
"label": "クリーンアップスクリプト",
diff --git a/frontend/src/i18n/locales/ko/settings.json b/frontend/src/i18n/locales/ko/settings.json
index ce561d67..864a0961 100644
--- a/frontend/src/i18n/locales/ko/settings.json
+++ b/frontend/src/i18n/locales/ko/settings.json
@@ -342,7 +342,12 @@
},
"dev": {
"label": "개발 서버 스크립트",
- "helper": "작업 시도에서 개발 서버를 시작합니다. 스크립트는 워크스페이스 디렉토리(각 저장소의 워크트리보다 한 단계 위)에서 실행되므로 먼저 저장소 디렉토리로 이동하세요 (예: cd my-repo && pnpm run dev)."
+ "helper": "작업 시도에서 개발 서버를 시작합니다. 스크립트는 아래에서 작업 디렉토리를 지정하지 않는 한 워크스페이스 디렉토리(각 저장소의 워크트리보다 한 단계 위)에서 실행됩니다."
+ },
+ "devWorkingDir": {
+ "label": "개발 서버 작업 디렉토리",
+ "placeholder": "예: my-repo",
+ "helper": "개발 서버 스크립트를 실행할 디렉토리로, 워크스페이스 루트 기준 상대 경로입니다. 비워두면 워크스페이스 루트에서 실행됩니다."
},
"cleanup": {
"label": "정리 스크립트",
diff --git a/frontend/src/i18n/locales/zh-Hans/settings.json b/frontend/src/i18n/locales/zh-Hans/settings.json
index 19f329ee..b7e50596 100644
--- a/frontend/src/i18n/locales/zh-Hans/settings.json
+++ b/frontend/src/i18n/locales/zh-Hans/settings.json
@@ -342,7 +342,12 @@
},
"dev": {
"label": "开发服务器脚本",
- "helper": "从任务尝试中启动开发服务器。脚本从工作区目录(每个仓库工作树的上一级)执行,因此请先 cd 进入仓库目录(例如:cd my-repo && pnpm run dev)。"
+ "helper": "从任务尝试中启动开发服务器。脚本从工作区目录(每个仓库工作树的上一级)执行,除非在下方指定了工作目录。"
+ },
+ "devWorkingDir": {
+ "label": "开发服务器工作目录",
+ "placeholder": "例如:my-repo",
+ "helper": "运行开发服务器脚本的目录,相对于工作区根目录。留空则从工作区根目录运行。"
},
"cleanup": {
"label": "清理脚本",
diff --git a/frontend/src/pages/settings/ProjectSettings.tsx b/frontend/src/pages/settings/ProjectSettings.tsx
index 96b33356..c32fb229 100644
--- a/frontend/src/pages/settings/ProjectSettings.tsx
+++ b/frontend/src/pages/settings/ProjectSettings.tsx
@@ -36,6 +36,7 @@ import type { Project, ProjectRepo, Repo, UpdateProject } from 'shared/types';
interface ProjectFormState {
name: string;
dev_script: string;
+ dev_script_working_dir: string;
}
interface RepoScriptsFormState {
@@ -49,6 +50,7 @@ function projectToFormState(project: Project): ProjectFormState {
return {
name: project.name,
dev_script: project.dev_script ?? '',
+ dev_script_working_dir: project.dev_script_working_dir ?? '',
};
}
@@ -390,6 +392,7 @@ export function ProjectSettings() {
const updateData: UpdateProject = {
name: draft.name.trim(),
dev_script: draft.dev_script.trim() || null,
+ dev_script_working_dir: draft.dev_script_working_dir.trim() || null,
};
updateProject.mutate({
@@ -583,6 +586,26 @@ export function ProjectSettings() {
+
+
+
+ updateDraft({ dev_script_working_dir: e.target.value })
+ }
+ placeholder={t(
+ 'settings.projects.scripts.devWorkingDir.placeholder'
+ )}
+ className="font-mono"
+ />
+
+ {t('settings.projects.scripts.devWorkingDir.helper')}
+
+
+
{/* Save Button */}
{hasUnsavedProjectChanges ? (
diff --git a/frontend/src/utils/scriptPlaceholders.ts b/frontend/src/utils/scriptPlaceholders.ts
index 83a2cef0..7c8b0462 100644
--- a/frontend/src/utils/scriptPlaceholders.ts
+++ b/frontend/src/utils/scriptPlaceholders.ts
@@ -15,7 +15,6 @@ class WindowsScriptPlaceholderStrategy implements ScriptPlaceholderStrategy {
npm install
REM Add any setup commands here...`,
dev: `@echo off
-cd my_website
npm run dev
REM Add dev server start command here...`,
cleanup: `@echo off
@@ -32,7 +31,6 @@ class UnixScriptPlaceholderStrategy implements ScriptPlaceholderStrategy {
npm install
# Add any setup commands here...`,
dev: `#!/bin/bash
-cd my_website
npm run dev
# Add dev server start command here...`,
cleanup: `#!/bin/bash
diff --git a/shared/types.ts b/shared/types.ts
index feae1a94..6fd8b17c 100644
--- a/shared/types.ts
+++ b/shared/types.ts
@@ -12,11 +12,11 @@ export type SharedTask = { id: string, organization_id: string, project_id: stri
export type UserData = { user_id: string, first_name: string | null, last_name: string | null, username: string | null, };
-export type Project = { id: string, name: string, dev_script: string | null, remote_project_id: string | null, created_at: Date, updated_at: Date, };
+export type Project = { id: string, name: string, dev_script: string | null, dev_script_working_dir: string | null, remote_project_id: string | null, created_at: Date, updated_at: Date, };
export type CreateProject = { name: string, repositories: Array, };
-export type UpdateProject = { name: string | null, dev_script: string | null, };
+export type UpdateProject = { name: string | null, dev_script: string | null, dev_script_working_dir: string | null, };
export type SearchResult = { path: string, is_file: boolean, match_type: SearchMatchType, };