mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
d2d962f201
403 changed files with 598 additions and 5390 deletions
27
.github/workflows/release.yml
vendored
27
.github/workflows/release.yml
vendored
|
|
@ -13,25 +13,8 @@ env:
|
|||
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }}
|
||||
|
||||
jobs:
|
||||
verify-spa:
|
||||
name: Verify SPA assets
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
no-cache: true
|
||||
- run: bun install --frozen-lockfile
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
- run: cargo dev spa check
|
||||
|
||||
compile:
|
||||
name: Compile (${{ matrix.target }})
|
||||
needs: verify-spa
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
@ -60,6 +43,13 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
no-cache: true
|
||||
|
||||
- name: Install bun deps
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Install Linux build tools
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
|
|
@ -89,6 +79,9 @@ jobs:
|
|||
|
||||
- uses: taiki-e/install-action@773334c0e05d7e699e4d78234494308223f3a2cf # nextest
|
||||
|
||||
- name: Refresh embedded SPA
|
||||
run: cargo dev spa refresh
|
||||
|
||||
- name: Test (x86_64-musl)
|
||||
# nextest still shells through cargo test for this target, so
|
||||
# build.rs C code needs an explicit musl compiler/linker.
|
||||
|
|
|
|||
3
.github/workflows/typescript.yml
vendored
3
.github/workflows/typescript.yml
vendored
|
|
@ -72,6 +72,5 @@ jobs:
|
|||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
- run: bun install --frozen-lockfile
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
- run: cargo dev spa check
|
||||
- run: cargo build -p fabro-cli --release
|
||||
- run: cargo dev build -- -p fabro-cli --release
|
||||
- run: wc -c < target/release/fabro
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,6 +3,8 @@ target
|
|||
.entire
|
||||
node_modules
|
||||
apps/fabro-web/dist/
|
||||
lib/crates/fabro-spa/assets/*
|
||||
!lib/crates/fabro-spa/assets/.gitkeep
|
||||
tmp
|
||||
evals/swe-bench/repos/
|
||||
evals/swe-bench/results/
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@ macOS note: if `cargo nextest run` fails with `Too many open files (os error 24)
|
|||
- `cd apps/fabro-web && bun test` — run tests
|
||||
- `cd apps/fabro-web && bun run typecheck` — type check
|
||||
- `cd apps/fabro-web && bun run build` — production build (writes to `apps/fabro-web/dist/` only; does NOT update the bundled SPA that ships in the Rust binary)
|
||||
- `cargo dev spa refresh` — **run this before committing any TypeScript change in `apps/fabro-web/` or `lib/packages/fabro-api-client/`**. It runs the production build, verifies SPA asset budgets, and then copies `dist/` into `lib/crates/fabro-spa/assets/` (which is tracked in git). CI's TypeScript `Build` job runs `cargo dev spa check` — if the committed bundle drifts from source or exceeds budgets, the check fails. `bun run build` on its own is not enough.
|
||||
- `cargo dev build [-- <cargo args>]` — refreshes the embedded SPA assets from the production build, verifies SPA asset budgets, and then runs `cargo build` with forwarded args. The embedded assets are gitignored except for `.gitkeep`; use this when building a Rust binary that should include a populated SPA bundle. `bun run dev` for local development is unchanged because debug builds prefer `apps/fabro-web/dist/` on disk via the server fallback.
|
||||
|
||||
### Docker image
|
||||
- `cargo dev docker-build` — builds the local Docker image from the current tree using the release pipeline's cargo-zigbuild approach. Honors `--arch amd64|arm64`, `--tag <name>` (default `fabro-sh/fabro`), `--compile-only` (stages `tmp/docker-context/<arch>/fabro` without `docker build`), and `--dry-run` (prints the Docker commands without running them). Prefer this over writing a throwaway Dockerfile; the release pipeline, `Dockerfile`, and this command share the same binary layout.
|
||||
- Refresh the embedded SPA before rebuilding the image after any `apps/fabro-web` change: `cargo dev spa refresh` runs the bun build, verifies budgets, and copies `dist/` into `lib/crates/fabro-spa/assets/`. Skipping this step produces a Docker image whose Rust binary embeds a stale SPA bundle.
|
||||
|
||||
### Docker sandbox provider
|
||||
- Docker is the default runtime sandbox provider from `defaults.toml`. The Fabro process must have a working Docker client environment (`DOCKER_HOST`, socket access, Docker Desktop behavior, TLS settings, groups/permissions, and any remote daemon policy are operator responsibilities).
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { formatElapsedSecs, formatDurationSecs } from "../lib/format";
|
|||
import type {
|
||||
RunListItem,
|
||||
RunStatus as ApiRunStatus,
|
||||
StoreRunSummary,
|
||||
RunSummary,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
|
||||
export type CiStatus = "passing" | "failing" | "pending";
|
||||
|
|
@ -84,9 +84,9 @@ export function mapRunListItem(item: RunListItem): RunItem {
|
|||
};
|
||||
}
|
||||
|
||||
export type RunSummaryResponse = StoreRunSummary;
|
||||
export type { RunSummary };
|
||||
|
||||
export function mapRunSummaryToRunItem(summary: RunSummaryResponse): RunItem {
|
||||
export function mapRunSummaryToRunItem(summary: RunSummary): RunItem {
|
||||
const lifecycleStatus = runStatusKind(summary.status);
|
||||
return {
|
||||
id: summary.run_id,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type {
|
|||
PaginatedStageTurnList,
|
||||
RunBilling,
|
||||
ServerSettings,
|
||||
RunSummary,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
|
||||
import type { PaginatedWorkflowListResponse, WorkflowDetailResponse } from "./workflow-api";
|
||||
|
|
@ -20,7 +21,6 @@ import {
|
|||
type PaginatedEnvelope,
|
||||
} from "./api-client";
|
||||
import { queryKeys } from "./query-keys";
|
||||
import type { RunSummaryResponse } from "../data/runs";
|
||||
|
||||
const immutableOptions: SWRConfiguration = {
|
||||
revalidateIfStale: false,
|
||||
|
|
@ -63,7 +63,7 @@ export function useBoardsRuns() {
|
|||
}
|
||||
|
||||
export function useRun(id: string | undefined) {
|
||||
return useSWR<RunSummaryResponse | null>(
|
||||
return useSWR<RunSummary | null>(
|
||||
id ? queryKeys.runs.detail(id) : null,
|
||||
apiNullableFetcher,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
isRunStatus,
|
||||
mapRunSummaryToRunItem,
|
||||
runStatusDisplay,
|
||||
type RunSummaryResponse,
|
||||
type RunSummary,
|
||||
} from "../data/runs";
|
||||
import { useDemoMode } from "../lib/demo-mode";
|
||||
import {
|
||||
|
|
@ -78,7 +78,7 @@ export function lifecycleActionVisibility(status: string | null | undefined) {
|
|||
};
|
||||
}
|
||||
|
||||
function buildRunDetailRun(summary: RunSummaryResponse): RunDetailRun {
|
||||
function buildRunDetailRun(summary: RunSummary): RunDetailRun {
|
||||
const item = mapRunSummaryToRunItem(summary);
|
||||
const rawStatus = summary.status;
|
||||
const statusKind = rawStatus.kind;
|
||||
|
|
|
|||
|
|
@ -536,7 +536,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StoreRunSummary"
|
||||
$ref: "#/components/schemas/RunSummary"
|
||||
"400":
|
||||
description: Selector is invalid or ambiguous
|
||||
content:
|
||||
|
|
@ -617,7 +617,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StoreRunSummary"
|
||||
$ref: "#/components/schemas/RunSummary"
|
||||
"404":
|
||||
description: Run not found
|
||||
content:
|
||||
|
|
@ -2858,7 +2858,7 @@ components:
|
|||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/StoreRunSummary"
|
||||
$ref: "#/components/schemas/RunSummary"
|
||||
meta:
|
||||
$ref: "#/components/schemas/PaginationMeta"
|
||||
|
||||
|
|
@ -4374,7 +4374,7 @@ components:
|
|||
additionalProperties:
|
||||
$ref: "#/components/schemas/NodeState"
|
||||
|
||||
StoreRunSummary:
|
||||
RunSummary:
|
||||
description: Durable run summary derived from the backing store.
|
||||
type: object
|
||||
required:
|
||||
|
|
|
|||
|
|
@ -171,9 +171,10 @@ fn main() {
|
|||
"fabro_types::status::RunControlAction",
|
||||
&[],
|
||||
),
|
||||
("RunSummary", "fabro_types::RunSummary", &[]),
|
||||
(
|
||||
"RunStatusRecord",
|
||||
"fabro_types::status::RunStatusRecord",
|
||||
"RepositoryReference",
|
||||
"fabro_types::RepositoryReference",
|
||||
&[],
|
||||
),
|
||||
("WorkflowSettings", "fabro_types::WorkflowSettings", &[]),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub mod types {
|
|||
pub use fabro_types::status::{
|
||||
BlockedReason, FailureReason, RunControlAction, RunStatus, SuccessReason, TerminalStatus,
|
||||
};
|
||||
pub use fabro_types::{ServerSettings, WorkflowSettings};
|
||||
pub use fabro_types::{RepositoryReference, RunSummary, ServerSettings, WorkflowSettings};
|
||||
|
||||
pub use crate::generated::types::*;
|
||||
}
|
||||
|
|
|
|||
122
lib/crates/fabro-api/tests/run_summary_round_trip.rs
Normal file
122
lib/crates/fabro-api/tests/run_summary_round_trip.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use std::any::{TypeId, type_name};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::{TimeZone, Utc};
|
||||
use fabro_api::types::{
|
||||
RepositoryReference as ApiRepositoryReference, RunSummary as ApiRunSummary,
|
||||
};
|
||||
use fabro_types::status::{RunStatus, SuccessReason, TerminalStatus};
|
||||
use fabro_types::{RepositoryReference, RunId, RunSummary};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn run_summary_reuses_domain_types() {
|
||||
assert_same_type::<ApiRunSummary, RunSummary>();
|
||||
assert_same_type::<ApiRepositoryReference, RepositoryReference>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_summary_json_matches_openapi_shape() {
|
||||
let created_at = Utc.with_ymd_and_hms(2026, 4, 20, 12, 0, 0).unwrap();
|
||||
let run_id = RunId::with_timestamp(created_at, 7);
|
||||
let superseded_by = RunId::with_timestamp(created_at, 8);
|
||||
let summary = RunSummary::new(
|
||||
run_id,
|
||||
Some("workflow".to_string()),
|
||||
Some("workflow".to_string()),
|
||||
String::new(),
|
||||
HashMap::from([("team".to_string(), "core".to_string())]),
|
||||
Some("/tmp/fabro".to_string()),
|
||||
Some(created_at),
|
||||
RunStatus::Archived {
|
||||
prior: TerminalStatus::Succeeded {
|
||||
reason: SuccessReason::PartialSuccess,
|
||||
},
|
||||
},
|
||||
None,
|
||||
Some(42_000),
|
||||
Some(123),
|
||||
Some(superseded_by),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&summary).unwrap(),
|
||||
json!({
|
||||
"run_id": run_id.to_string(),
|
||||
"workflow_name": "workflow",
|
||||
"workflow_slug": "workflow",
|
||||
"goal": "",
|
||||
"title": "",
|
||||
"labels": {
|
||||
"team": "core"
|
||||
},
|
||||
"host_repo_path": "/tmp/fabro",
|
||||
"repository": {
|
||||
"name": "fabro"
|
||||
},
|
||||
"start_time": "2026-04-20T12:00:00Z",
|
||||
"created_at": "2026-04-20T12:00:00Z",
|
||||
"status": {
|
||||
"kind": "archived",
|
||||
"prior": {
|
||||
"kind": "succeeded",
|
||||
"reason": "partial_success"
|
||||
}
|
||||
},
|
||||
"pending_control": null,
|
||||
"duration_ms": 42000,
|
||||
"elapsed_secs": 42.0,
|
||||
"total_usd_micros": 123,
|
||||
"superseded_by": superseded_by.to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_summary_deserializes_when_optional_fields_are_absent() {
|
||||
let created_at = Utc.with_ymd_and_hms(2026, 4, 20, 12, 0, 0).unwrap();
|
||||
let run_id = RunId::with_timestamp(created_at, 7);
|
||||
let summary: RunSummary = serde_json::from_value(json!({
|
||||
"run_id": run_id.to_string(),
|
||||
"goal": "ship it",
|
||||
"title": "ship it",
|
||||
"labels": {},
|
||||
"status": {
|
||||
"kind": "running"
|
||||
},
|
||||
"repository": {
|
||||
"name": "fabro"
|
||||
},
|
||||
"created_at": "2026-04-20T12:00:00Z"
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(summary.run_id, run_id);
|
||||
assert_eq!(summary.workflow_name, None);
|
||||
assert_eq!(summary.workflow_slug, None);
|
||||
assert_eq!(summary.goal, "ship it");
|
||||
assert_eq!(summary.title, "ship it");
|
||||
assert_eq!(summary.labels, HashMap::new());
|
||||
assert_eq!(summary.host_repo_path, None);
|
||||
assert_eq!(summary.repository, RepositoryReference {
|
||||
name: "fabro".to_string(),
|
||||
});
|
||||
assert_eq!(summary.start_time, None);
|
||||
assert_eq!(summary.created_at, created_at);
|
||||
assert_eq!(summary.status, RunStatus::Running);
|
||||
assert_eq!(summary.pending_control, None);
|
||||
assert_eq!(summary.duration_ms, None);
|
||||
assert_eq!(summary.elapsed_secs, None);
|
||||
assert_eq!(summary.total_usd_micros, None);
|
||||
assert_eq!(summary.superseded_by, None);
|
||||
}
|
||||
|
||||
fn assert_same_type<T: 'static, U: 'static>() {
|
||||
assert_eq!(
|
||||
TypeId::of::<T>(),
|
||||
TypeId::of::<U>(),
|
||||
"{} should be the same type as {}",
|
||||
type_name::<T>(),
|
||||
type_name::<U>()
|
||||
);
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ impl ServerRunSummaryInfo {
|
|||
}
|
||||
|
||||
pub(crate) fn goal(&self) -> String {
|
||||
self.summary.goal.clone().unwrap_or_default()
|
||||
self.summary.goal.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
23
lib/crates/fabro-dev/src/commands/build.rs
Normal file
23
lib/crates/fabro-dev/src/commands/build.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
use super::{PlannedCommand, spa_refresh};
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub(crate) struct BuildArgs {
|
||||
/// Arguments forwarded to `cargo build`.
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
cargo_args: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn build(args: BuildArgs) -> Result<()> {
|
||||
let root = super::workspace_root();
|
||||
spa_refresh::spa_refresh_root(&root)?;
|
||||
|
||||
let mut command = PlannedCommand::new("cargo").arg("build");
|
||||
for arg in args.cargo_args {
|
||||
command = command.arg(arg);
|
||||
}
|
||||
|
||||
super::run_command(&root, &command)
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
|||
use anyhow::{Context, Result, bail};
|
||||
use clap::{Args, ValueEnum};
|
||||
|
||||
use super::{PlannedCommand, run_command, shell_arg, workspace_root};
|
||||
use super::{PlannedCommand, run_command, shell_arg, spa_refresh, workspace_root};
|
||||
|
||||
const ZIG_VERSION: &str = "0.13.0";
|
||||
|
||||
|
|
@ -98,6 +98,9 @@ impl DockerBuildPlan {
|
|||
reason = "dev docker-build command reports progress directly"
|
||||
)]
|
||||
fn run(&self) -> Result<()> {
|
||||
println!("Refreshing embedded SPA assets...");
|
||||
spa_refresh::spa_refresh_root(&self.workspace_root)?;
|
||||
|
||||
println!(
|
||||
"Building fabro-cli for {} inside rust:1-bookworm via cargo-zigbuild...",
|
||||
self.arch.target()
|
||||
|
|
@ -130,6 +133,7 @@ impl DockerBuildPlan {
|
|||
|
||||
fn dry_run_lines(&self) -> Vec<String> {
|
||||
let mut lines = vec![
|
||||
Self::spa_refresh_command().to_shell_line(),
|
||||
self.build_command().to_shell_line(),
|
||||
format!("mkdir -p {}", shell_arg(self.relative_context_dir())),
|
||||
self.extract_command().to_shell_line(),
|
||||
|
|
@ -142,6 +146,13 @@ impl DockerBuildPlan {
|
|||
lines
|
||||
}
|
||||
|
||||
fn spa_refresh_command() -> PlannedCommand {
|
||||
PlannedCommand::new("cargo")
|
||||
.arg("dev")
|
||||
.arg("spa")
|
||||
.arg("refresh")
|
||||
}
|
||||
|
||||
fn build_command(&self) -> PlannedCommand {
|
||||
let arch = self.arch.to_string();
|
||||
let target = self.arch.target();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod build;
|
||||
mod docker_build;
|
||||
mod docs;
|
||||
mod docs_cli_reference;
|
||||
|
|
@ -11,6 +12,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::process::{Command, Output};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
pub(crate) use build::{BuildArgs, build};
|
||||
pub(crate) use docker_build::{DockerBuildArgs, docker_build};
|
||||
pub(crate) use docs::{DocsArgs, docs};
|
||||
pub(crate) use release::{ReleaseArgs, release};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail};
|
|||
use chrono::{Local, NaiveDate};
|
||||
use clap::Args;
|
||||
|
||||
use super::{PlannedCommand, capture_command, run_command, workspace_root};
|
||||
use super::{PlannedCommand, capture_command, run_command, spa_refresh, workspace_root};
|
||||
|
||||
const RELEASE_EPOCH: &str = "2026-01-01";
|
||||
const RELEASE_TEST_SEGMENT_WRITE_KEY: &str = "fake-for-local-smoke";
|
||||
|
|
@ -66,7 +66,7 @@ pub(crate) fn release(args: ReleaseArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
plan.ensure_clean_worktree()?;
|
||||
plan.verify_spa_assets()?;
|
||||
spa_refresh::spa_refresh_root(&plan.root)?;
|
||||
plan.verify_release_tests()?;
|
||||
update_version(&cargo_toml, ¤t_version, &new_version)?;
|
||||
println!("Updated {}", cargo_toml.display());
|
||||
|
|
@ -177,10 +177,6 @@ impl ReleasePlan {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_spa_assets(&self) -> Result<()> {
|
||||
run_command(&self.root, &Self::spa_check_command())
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::print_stdout,
|
||||
reason = "dev release command reports release test progress directly"
|
||||
|
|
@ -200,8 +196,8 @@ impl ReleasePlan {
|
|||
reason = "dev release command reports dry-run commands directly"
|
||||
)]
|
||||
fn print_dry_run(&self, current_version: &str, new_version: &str, tag: &str) {
|
||||
println!("DRY RUN: would verify SPA assets:");
|
||||
println!("{}", Self::spa_check_command().to_shell_line());
|
||||
println!("DRY RUN: would refresh SPA assets:");
|
||||
println!("{}", Self::spa_refresh_command().to_shell_line());
|
||||
|
||||
if self.skip_tests {
|
||||
println!("--skip-tests set, would skip release-mode test smoke");
|
||||
|
|
@ -239,11 +235,11 @@ impl ReleasePlan {
|
|||
}
|
||||
}
|
||||
|
||||
fn spa_check_command() -> PlannedCommand {
|
||||
fn spa_refresh_command() -> PlannedCommand {
|
||||
PlannedCommand::new("cargo")
|
||||
.arg("dev")
|
||||
.arg("spa")
|
||||
.arg("check")
|
||||
.arg("refresh")
|
||||
}
|
||||
|
||||
fn release_tests_command() -> PlannedCommand {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ pub(super) fn check_spa_asset_budgets(
|
|||
|
||||
if report.asset_bytes > asset_budget_bytes {
|
||||
bail!(
|
||||
"fabro-spa assets exceed budget: {} > {}",
|
||||
"fabro-spa embedded assets exceed budget: {} > {}",
|
||||
report.asset_bytes,
|
||||
asset_budget_bytes
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ pub(crate) struct SpaRefreshArgs {
|
|||
/// Repository root containing apps/fabro-web and lib/crates/fabro-spa.
|
||||
#[arg(long, hide = true)]
|
||||
root: Option<PathBuf>,
|
||||
/// Skip bun run build and only mirror an existing dist directory.
|
||||
#[arg(long, hide = true)]
|
||||
pub(super) skip_build: bool,
|
||||
/// Override the raw asset budget.
|
||||
#[arg(long, hide = true, default_value_t = DEFAULT_ASSET_BUDGET_BYTES)]
|
||||
pub(super) asset_budget_bytes: u64,
|
||||
|
|
@ -29,11 +26,14 @@ pub(crate) struct SpaRefreshArgs {
|
|||
|
||||
pub(crate) fn spa_refresh(args: SpaRefreshArgs) -> Result<()> {
|
||||
let root = args.root.unwrap_or_else(workspace_root);
|
||||
spa_refresh_root(
|
||||
&root,
|
||||
args.skip_build,
|
||||
args.asset_budget_bytes,
|
||||
args.payload_budget_bytes,
|
||||
spa_refresh_root_with_budgets(&root, args.asset_budget_bytes, args.payload_budget_bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn spa_refresh_root(root: &Path) -> Result<()> {
|
||||
spa_refresh_root_with_budgets(
|
||||
root,
|
||||
DEFAULT_ASSET_BUDGET_BYTES,
|
||||
DEFAULT_PAYLOAD_BUDGET_BYTES,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -41,9 +41,8 @@ pub(crate) fn spa_refresh(args: SpaRefreshArgs) -> Result<()> {
|
|||
clippy::print_stdout,
|
||||
reason = "dev spa refresh command reports progress directly"
|
||||
)]
|
||||
pub(super) fn spa_refresh_root(
|
||||
fn spa_refresh_root_with_budgets(
|
||||
root: &Path,
|
||||
skip_build: bool,
|
||||
asset_budget_bytes: u64,
|
||||
payload_budget_bytes: u64,
|
||||
) -> Result<()> {
|
||||
|
|
@ -51,20 +50,36 @@ pub(super) fn spa_refresh_root(
|
|||
let dist_dir = web_dir.join("dist");
|
||||
let asset_dir = root.join("lib/crates/fabro-spa/assets");
|
||||
|
||||
if !skip_build {
|
||||
println!("Running bun run build in apps/fabro-web...");
|
||||
run_bun_build(&web_dir)?;
|
||||
}
|
||||
println!("Running bun run build in apps/fabro-web...");
|
||||
run_bun_build(&web_dir)?;
|
||||
|
||||
let staging = TempDir::new(root, "refresh")?;
|
||||
mirror_dist(&dist_dir, staging.path())?;
|
||||
check_spa_asset_budgets(staging.path(), asset_budget_bytes, payload_budget_bytes)?;
|
||||
mirror_dist(staging.path(), &asset_dir)?;
|
||||
refresh_from_dist(
|
||||
root,
|
||||
&dist_dir,
|
||||
&asset_dir,
|
||||
asset_budget_bytes,
|
||||
payload_budget_bytes,
|
||||
)?;
|
||||
println!("Refreshed lib/crates/fabro-spa/assets");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_from_dist(
|
||||
root: &Path,
|
||||
dist_dir: &Path,
|
||||
asset_dir: &Path,
|
||||
asset_budget_bytes: u64,
|
||||
payload_budget_bytes: u64,
|
||||
) -> Result<()> {
|
||||
let staging = TempDir::new(root, "refresh")?;
|
||||
mirror_dist(dist_dir, staging.path())?;
|
||||
check_spa_asset_budgets(staging.path(), asset_budget_bytes, payload_budget_bytes)?;
|
||||
mirror_dist(staging.path(), asset_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "dev spa refresh intentionally runs a synchronous Bun subprocess"
|
||||
|
|
@ -128,6 +143,9 @@ pub(super) fn mirror_dist(dist_dir: &Path, asset_dir: &Path) -> Result<()> {
|
|||
})?;
|
||||
}
|
||||
|
||||
std::fs::write(asset_dir.join(".gitkeep"), b"")
|
||||
.with_context(|| format!("writing {}", asset_dir.join(".gitkeep").display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -170,3 +188,89 @@ impl Drop for TempDir {
|
|||
let _ = std::fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "tests stage temporary SPA fixture files with sync std::fs operations"
|
||||
)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use super::{mirror_dist, refresh_from_dist};
|
||||
|
||||
fn write_file(root: &Path, path: &str, contents: impl AsRef<[u8]>) {
|
||||
let path = root.join(path);
|
||||
std::fs::create_dir_all(path.parent().expect("fixture path should have parent"))
|
||||
.expect("creating fixture parent directory");
|
||||
std::fs::write(path, contents).expect("writing fixture file");
|
||||
}
|
||||
|
||||
fn read_bytes(root: &Path, path: &str) -> Vec<u8> {
|
||||
std::fs::read(root.join(path)).expect("reading fixture file")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_dist_removes_stale_files_source_maps_and_keeps_directory_tracked() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
write_file(fixture.path(), "dist/index.html", b"index");
|
||||
write_file(fixture.path(), "dist/assets/app.js", b"app");
|
||||
write_file(fixture.path(), "dist/assets/app.js.map", b"map");
|
||||
write_file(fixture.path(), "assets/stale.txt", b"stale");
|
||||
|
||||
mirror_dist(&fixture.path().join("dist"), &fixture.path().join("assets"))
|
||||
.expect("mirroring dist");
|
||||
|
||||
assert!(fixture.path().join("assets/index.html").is_file());
|
||||
assert!(fixture.path().join("assets/assets/app.js").is_file());
|
||||
assert!(fixture.path().join("assets/.gitkeep").is_file());
|
||||
assert!(!fixture.path().join("assets/assets/app.js.map").exists());
|
||||
assert!(!fixture.path().join("assets/stale.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_dist_missing_source_errors_cleanly() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
|
||||
let error = mirror_dist(&fixture.path().join("dist"), &fixture.path().join("assets"))
|
||||
.expect_err("missing dist should fail");
|
||||
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("apps/fabro-web/dist is missing; run `bun run build`"),
|
||||
"missing dist should explain how to recover: {error:#}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_budget_failure_leaves_assets_untouched() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
write_file(fixture.path(), "apps/fabro-web/dist/index.html", b"hello");
|
||||
write_file(
|
||||
fixture.path(),
|
||||
"lib/crates/fabro-spa/assets/index.html",
|
||||
b"embedded",
|
||||
);
|
||||
|
||||
let error = refresh_from_dist(
|
||||
fixture.path(),
|
||||
&fixture.path().join("apps/fabro-web/dist"),
|
||||
&fixture.path().join("lib/crates/fabro-spa/assets"),
|
||||
4,
|
||||
100,
|
||||
)
|
||||
.expect_err("budget failure should fail");
|
||||
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("fabro-spa embedded assets exceed budget: 5 > 4"),
|
||||
"budget failure should report raw byte overage: {error:#}"
|
||||
);
|
||||
assert_eq!(
|
||||
read_bytes(fixture.path(), "lib/crates/fabro-spa/assets/index.html"),
|
||||
b"embedded"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ struct Cli {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
/// Refresh embedded SPA assets and run cargo build.
|
||||
Build(commands::BuildArgs),
|
||||
/// Build Fabro Docker images with the release pipeline layout.
|
||||
DockerBuild(commands::DockerBuildArgs),
|
||||
/// Manage generated reference documentation.
|
||||
|
|
@ -31,6 +33,7 @@ enum Command {
|
|||
impl Command {
|
||||
fn run(self) -> Result<()> {
|
||||
match self {
|
||||
Self::Build(args) => commands::build(args),
|
||||
Self::DockerBuild(args) => commands::docker_build(args),
|
||||
Self::Docs(args) => commands::docs(args),
|
||||
Self::Release(args) => commands::release(args),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ fn dry_run_prints_equivalent_build_commands() {
|
|||
.clone();
|
||||
let stdout = output_text(&output.stdout);
|
||||
|
||||
assert!(
|
||||
stdout.contains("cargo dev spa refresh"),
|
||||
"dry-run should print SPA refresh command:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("docker run --rm --platform linux/amd64"),
|
||||
"dry-run should print builder docker run:\n{stdout}"
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ fn help_lists_scaffolded_commands() {
|
|||
.clone();
|
||||
let stdout = output_text(&output.stdout);
|
||||
|
||||
for command in ["docker-build", "docs", "release", "spa"] {
|
||||
for command in ["build", "docker-build", "docs", "release", "spa"] {
|
||||
assert!(
|
||||
stdout.contains(command),
|
||||
"top-level help should list {command}:\n{stdout}"
|
||||
|
|
@ -114,6 +114,22 @@ fn group_only_docs_prints_subcommand_help_successfully() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_help_lists_forwarded_cargo_args() {
|
||||
let output = fabro_dev()
|
||||
.args(["build", "--help"])
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.clone();
|
||||
let stdout = output_text(&output.stdout);
|
||||
|
||||
assert!(
|
||||
stdout.contains("Arguments forwarded to `cargo build`"),
|
||||
"build help should explain forwarded cargo args:\n{stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cargo_dev_alias_points_at_fabro_dev() {
|
||||
let config = read_file(&workspace_root(), ".cargo/config.toml");
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ fn dry_run_computes_stable_version_from_date() {
|
|||
"dry-run should compute base version from date:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("cargo dev spa check"),
|
||||
"dry-run should print one SPA verification command:\n{stdout}"
|
||||
stdout.contains("cargo dev spa refresh"),
|
||||
"dry-run should print one SPA refresh command:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
!stdout.contains("git diff --exit-code -- lib/crates/fabro-spa/assets"),
|
||||
|
|
|
|||
|
|
@ -1,135 +1,19 @@
|
|||
use super::{fabro_dev, output_text, read_bytes, write_file};
|
||||
|
||||
#[test]
|
||||
fn refresh_mirrors_dist_and_removes_source_maps() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
write_file(fixture.path(), "apps/fabro-web/dist/index.html", b"index");
|
||||
write_file(fixture.path(), "apps/fabro-web/dist/assets/app.js", b"app");
|
||||
write_file(
|
||||
fixture.path(),
|
||||
"apps/fabro-web/dist/assets/app.js.map",
|
||||
b"map",
|
||||
);
|
||||
write_file(
|
||||
fixture.path(),
|
||||
"lib/crates/fabro-spa/assets/stale.txt",
|
||||
b"stale",
|
||||
);
|
||||
|
||||
fn refresh_rejects_removed_skip_build_flag() {
|
||||
let output = fabro_dev()
|
||||
.args([
|
||||
"spa",
|
||||
"refresh",
|
||||
"--root",
|
||||
fixture
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("fixture path should be utf-8"),
|
||||
"--skip-build",
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.clone();
|
||||
let stdout = output_text(&output.stdout);
|
||||
|
||||
assert!(
|
||||
stdout.contains("Refreshed lib/crates/fabro-spa/assets"),
|
||||
"spa refresh should report refreshed assets:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
fixture
|
||||
.path()
|
||||
.join("lib/crates/fabro-spa/assets/index.html")
|
||||
.is_file()
|
||||
);
|
||||
assert!(
|
||||
fixture
|
||||
.path()
|
||||
.join("lib/crates/fabro-spa/assets/assets/app.js")
|
||||
.is_file()
|
||||
);
|
||||
assert!(
|
||||
!fixture
|
||||
.path()
|
||||
.join("lib/crates/fabro-spa/assets/assets/app.js.map")
|
||||
.exists()
|
||||
);
|
||||
assert!(
|
||||
!fixture
|
||||
.path()
|
||||
.join("lib/crates/fabro-spa/assets/stale.txt")
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_missing_dist_errors_cleanly() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
|
||||
let output = fabro_dev()
|
||||
.args([
|
||||
"spa",
|
||||
"refresh",
|
||||
"--root",
|
||||
fixture
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("fixture path should be utf-8"),
|
||||
"--skip-build",
|
||||
])
|
||||
.args(["spa", "refresh", "--skip-build"])
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.code(2)
|
||||
.get_output()
|
||||
.clone();
|
||||
let stderr = output_text(&output.stderr);
|
||||
|
||||
assert!(
|
||||
stderr.contains("apps/fabro-web/dist is missing; run `bun run build`"),
|
||||
"missing dist should explain how to recover:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_budget_failure_leaves_assets_untouched() {
|
||||
let fixture = tempfile::tempdir().expect("creating fixture");
|
||||
write_file(fixture.path(), "apps/fabro-web/dist/index.html", b"hello");
|
||||
write_file(
|
||||
fixture.path(),
|
||||
"lib/crates/fabro-spa/assets/index.html",
|
||||
b"committed",
|
||||
);
|
||||
|
||||
let output = fabro_dev()
|
||||
.args([
|
||||
"spa",
|
||||
"refresh",
|
||||
"--root",
|
||||
fixture
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("fixture path should be utf-8"),
|
||||
"--skip-build",
|
||||
"--asset-budget-bytes",
|
||||
"4",
|
||||
"--payload-budget-bytes",
|
||||
"100",
|
||||
])
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.get_output()
|
||||
.clone();
|
||||
let stderr = output_text(&output.stderr);
|
||||
|
||||
assert!(
|
||||
stderr.contains("fabro-spa assets exceed budget: 5 > 4"),
|
||||
"budget failure should report raw byte overage:\n{stderr}"
|
||||
);
|
||||
assert_eq!(
|
||||
read_bytes(fixture.path(), "lib/crates/fabro-spa/assets/index.html"),
|
||||
b"committed"
|
||||
stderr.contains("unexpected argument '--skip-build'"),
|
||||
"spa refresh should reject removed --skip-build flag:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +26,7 @@ fn check_passes_when_dist_matches_assets_and_budgets_pass() {
|
|||
"lib/crates/fabro-spa/assets/index.html",
|
||||
b"hello",
|
||||
);
|
||||
write_file(fixture.path(), "lib/crates/fabro-spa/assets/.gitkeep", b"");
|
||||
|
||||
let output = fabro_dev()
|
||||
.args([
|
||||
|
|
@ -206,7 +91,7 @@ fn check_fails_when_assets_exceed_budget() {
|
|||
let stderr = output_text(&output.stderr);
|
||||
|
||||
assert!(
|
||||
stderr.contains("fabro-spa assets exceed budget: 5 > 4"),
|
||||
stderr.contains("fabro-spa embedded assets exceed budget: 5 > 4"),
|
||||
"budget failure should report raw byte overage:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
|
@ -218,7 +103,7 @@ fn check_fails_when_assets_do_not_match_dist() {
|
|||
write_file(
|
||||
fixture.path(),
|
||||
"lib/crates/fabro-spa/assets/index.html",
|
||||
b"committed",
|
||||
b"embedded",
|
||||
);
|
||||
|
||||
let output = fabro_dev()
|
||||
|
|
@ -245,7 +130,7 @@ fn check_fails_when_assets_do_not_match_dist() {
|
|||
);
|
||||
assert_eq!(
|
||||
read_bytes(fixture.path(), "lib/crates/fabro-spa/assets/index.html"),
|
||||
b"committed"
|
||||
b"embedded"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ pub(crate) async fn resolve_run(
|
|||
match resolve_run_by_selector(
|
||||
&runs,
|
||||
¶ms.selector,
|
||||
|run| run.run_id.clone(),
|
||||
|run| run.run_id.to_string(),
|
||||
|run| run.workflow_slug.clone(),
|
||||
|run| run.workflow_name.clone(),
|
||||
|run| run.created_at,
|
||||
|
|
@ -312,7 +312,10 @@ pub(crate) async fn get_run_status(
|
|||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
match runs::summaries().into_iter().find(|run| run.run_id == id) {
|
||||
match runs::summaries()
|
||||
.into_iter()
|
||||
.find(|run| run.run_id.to_string() == id)
|
||||
{
|
||||
Some(run) => (StatusCode::OK, Json(run)).into_response(),
|
||||
None => ApiError::not_found("Run not found.").into_response(),
|
||||
}
|
||||
|
|
@ -755,18 +758,18 @@ fn ts(s: &str) -> DateTime<Utc> {
|
|||
|
||||
mod runs {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use fabro_api::types::*;
|
||||
use fabro_types::WorkflowSettings;
|
||||
use fabro_types::settings::run::{
|
||||
DaytonaSettings, DaytonaSnapshotSettings, LocalSandboxSettings, RunGoal, RunModelSettings,
|
||||
RunNamespace, RunPrepareSettings, RunSandboxSettings,
|
||||
};
|
||||
use fabro_types::settings::{InterpString, ProjectNamespace, WorkflowNamespace};
|
||||
use fabro_types::{RunId, WorkflowSettings};
|
||||
|
||||
use super::ts;
|
||||
use crate::server::truncate_goal;
|
||||
|
||||
fn labels(entries: &[(&str, &str)]) -> HashMap<String, String> {
|
||||
entries
|
||||
|
|
@ -775,8 +778,26 @@ mod runs {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn demo_run_ids() -> &'static [RunId; 6] {
|
||||
static IDS: OnceLock<[RunId; 6]> = OnceLock::new();
|
||||
IDS.get_or_init(|| {
|
||||
[
|
||||
RunId::with_timestamp(ts("2026-03-06T14:30:00Z"), 1),
|
||||
RunId::with_timestamp(ts("2026-03-06T12:00:00Z"), 2),
|
||||
RunId::with_timestamp(ts("2026-03-04T15:00:00Z"), 3),
|
||||
RunId::with_timestamp(ts("2026-03-04T10:00:00Z"), 4),
|
||||
RunId::with_timestamp(ts("2026-03-03T16:45:00Z"), 5),
|
||||
RunId::with_timestamp(ts("2026-02-28T14:00:00Z"), 6),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
fn demo_run_id(index: usize) -> RunId {
|
||||
demo_run_ids()[index - 1]
|
||||
}
|
||||
|
||||
fn summary(
|
||||
run_id: &str,
|
||||
sequence: u128,
|
||||
repo_name: &str,
|
||||
workflow_slug: &str,
|
||||
workflow_name: &str,
|
||||
|
|
@ -788,28 +809,24 @@ mod runs {
|
|||
pending_control: Option<RunControlAction>,
|
||||
total_usd_micros: Option<i64>,
|
||||
entries: &[(&str, &str)],
|
||||
) -> StoreRunSummary {
|
||||
StoreRunSummary {
|
||||
created_at: ts(created_at),
|
||||
duration_ms: elapsed_secs.and_then(duration_ms_from_secs),
|
||||
elapsed_secs,
|
||||
goal: goal.into(),
|
||||
host_repo_path: Some(format!("/demo/{repo_name}")),
|
||||
labels: labels(entries),
|
||||
pending_control,
|
||||
repository: RepositoryReference {
|
||||
name: repo_name.into(),
|
||||
},
|
||||
run_id: run_id.into(),
|
||||
start_time: Some(ts(created_at)),
|
||||
status: parse_run_status(status, status_reason)
|
||||
) -> RunSummary {
|
||||
let created_at = ts(created_at);
|
||||
let run_id = RunId::with_timestamp(created_at, sequence);
|
||||
RunSummary::new(
|
||||
run_id,
|
||||
Some(workflow_name.into()),
|
||||
Some(workflow_slug.into()),
|
||||
goal.into(),
|
||||
labels(entries),
|
||||
Some(format!("/demo/{repo_name}")),
|
||||
Some(created_at),
|
||||
parse_run_status(status, status_reason)
|
||||
.unwrap_or_else(|| panic!("invalid demo run status: {status}")),
|
||||
superseded_by: None,
|
||||
title: truncate_goal(goal),
|
||||
pending_control,
|
||||
elapsed_secs.and_then(duration_ms_from_secs),
|
||||
total_usd_micros,
|
||||
workflow_name: Some(workflow_name.into()),
|
||||
workflow_slug: Some(workflow_slug.into()),
|
||||
}
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_run_status(status: &str, status_reason: Option<&str>) -> Option<RunStatus> {
|
||||
|
|
@ -860,22 +877,19 @@ mod runs {
|
|||
}
|
||||
}
|
||||
|
||||
fn duration_ms_from_secs(secs: f64) -> Option<i64> {
|
||||
fn duration_ms_from_secs(secs: f64) -> Option<u64> {
|
||||
let duration = Duration::try_from_secs_f64(secs).ok()?;
|
||||
duration.as_millis().try_into().ok()
|
||||
}
|
||||
|
||||
fn take_summary(
|
||||
summaries: &mut HashMap<String, StoreRunSummary>,
|
||||
run_id: &str,
|
||||
) -> StoreRunSummary {
|
||||
fn take_summary(summaries: &mut HashMap<RunId, RunSummary>, run_id: RunId) -> RunSummary {
|
||||
summaries
|
||||
.remove(run_id)
|
||||
.remove(&run_id)
|
||||
.unwrap_or_else(|| panic!("missing demo summary: {run_id}"))
|
||||
}
|
||||
|
||||
fn board_item(
|
||||
summary: StoreRunSummary,
|
||||
summary: RunSummary,
|
||||
column: BoardColumn,
|
||||
pull_request: Option<RunPullRequest>,
|
||||
sandbox: Option<RunSandbox>,
|
||||
|
|
@ -884,7 +898,7 @@ mod runs {
|
|||
RunListItem {
|
||||
column,
|
||||
created_at: summary.created_at,
|
||||
duration_ms: summary.duration_ms,
|
||||
duration_ms: summary.duration_ms.and_then(|ms| i64::try_from(ms).ok()),
|
||||
elapsed_secs: summary.elapsed_secs,
|
||||
goal: summary.goal,
|
||||
host_repo_path: summary.host_repo_path,
|
||||
|
|
@ -893,7 +907,7 @@ mod runs {
|
|||
pull_request,
|
||||
question,
|
||||
repository: summary.repository,
|
||||
run_id: summary.run_id,
|
||||
run_id: summary.run_id.to_string(),
|
||||
sandbox,
|
||||
start_time: summary.start_time,
|
||||
status: summary.status,
|
||||
|
|
@ -960,10 +974,10 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub(super) fn summaries() -> Vec<StoreRunSummary> {
|
||||
pub(super) fn summaries() -> Vec<RunSummary> {
|
||||
vec![
|
||||
summary(
|
||||
"run-1",
|
||||
1,
|
||||
"api-server",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -977,7 +991,7 @@ mod runs {
|
|||
&[("branch", "rate-limit"), ("team", "platform")],
|
||||
),
|
||||
summary(
|
||||
"run-2",
|
||||
2,
|
||||
"web-dashboard",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -991,7 +1005,7 @@ mod runs {
|
|||
&[("owner", "frontend")],
|
||||
),
|
||||
summary(
|
||||
"run-3",
|
||||
3,
|
||||
"shared-types",
|
||||
"expand",
|
||||
"Expand",
|
||||
|
|
@ -1005,7 +1019,7 @@ mod runs {
|
|||
&[("priority", "high")],
|
||||
),
|
||||
summary(
|
||||
"run-4",
|
||||
4,
|
||||
"shared-types",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -1019,7 +1033,7 @@ mod runs {
|
|||
&[("owner", "runtime")],
|
||||
),
|
||||
summary(
|
||||
"run-5",
|
||||
5,
|
||||
"web-dashboard",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -1033,7 +1047,7 @@ mod runs {
|
|||
&[("environment", "staging")],
|
||||
),
|
||||
summary(
|
||||
"run-6",
|
||||
6,
|
||||
"api-server",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -1052,26 +1066,26 @@ mod runs {
|
|||
pub(super) fn board_items() -> Vec<RunListItem> {
|
||||
let mut summaries = summaries()
|
||||
.into_iter()
|
||||
.map(|summary| (summary.run_id.clone(), summary))
|
||||
.map(|summary| (summary.run_id, summary))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
vec![
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-1"),
|
||||
take_summary(&mut summaries, demo_run_id(1)),
|
||||
BoardColumn::Running,
|
||||
None,
|
||||
Some(sandbox("sb-a1b2c3d4", 4, 8)),
|
||||
None,
|
||||
),
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-2"),
|
||||
take_summary(&mut summaries, demo_run_id(2)),
|
||||
BoardColumn::Running,
|
||||
None,
|
||||
Some(sandbox("sb-e5f6g7h8", 8, 16)),
|
||||
None,
|
||||
),
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-3"),
|
||||
take_summary(&mut summaries, demo_run_id(3)),
|
||||
BoardColumn::Initializing,
|
||||
Some(pull_request(0, 567, 234, 0, vec![])),
|
||||
Some(sandbox("sb-q7r8s9t0", 4, 8)),
|
||||
|
|
@ -1080,7 +1094,7 @@ mod runs {
|
|||
}),
|
||||
),
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-4"),
|
||||
take_summary(&mut summaries, demo_run_id(4)),
|
||||
BoardColumn::Blocked,
|
||||
Some(pull_request(0, 145, 23, 0, vec![])),
|
||||
Some(sandbox("sb-u1v2w3x4", 4, 8)),
|
||||
|
|
@ -1089,7 +1103,7 @@ mod runs {
|
|||
}),
|
||||
),
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-5"),
|
||||
take_summary(&mut summaries, demo_run_id(5)),
|
||||
BoardColumn::Failed,
|
||||
Some(pull_request(889, 234, 67, 4, vec![
|
||||
check("lint", CheckRunStatus::Success, Some(23.0)),
|
||||
|
|
@ -1102,7 +1116,7 @@ mod runs {
|
|||
None,
|
||||
),
|
||||
board_item(
|
||||
take_summary(&mut summaries, "run-6"),
|
||||
take_summary(&mut summaries, demo_run_id(6)),
|
||||
BoardColumn::Succeeded,
|
||||
Some(pull_request(1249, 189, 45, 7, vec![
|
||||
check("lint", CheckRunStatus::Success, Some(21.0)),
|
||||
|
|
@ -1410,7 +1424,7 @@ mod runs {
|
|||
#[test]
|
||||
fn summary_parses_known_status_reason_values() {
|
||||
let summary = summary(
|
||||
"run-test",
|
||||
99,
|
||||
"demo-repo",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -1432,7 +1446,7 @@ mod runs {
|
|||
#[test]
|
||||
fn summary_ignores_unknown_status_reason() {
|
||||
let summary = summary(
|
||||
"run-test",
|
||||
99,
|
||||
"demo-repo",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
@ -1455,7 +1469,7 @@ mod runs {
|
|||
fn summary_derives_title_like_server() {
|
||||
let goal = format!("## Plan: {}", "a".repeat(120));
|
||||
let summary = summary(
|
||||
"run-test",
|
||||
99,
|
||||
"demo-repo",
|
||||
"implement",
|
||||
"Implement",
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ use fabro_types::{
|
|||
RunBlobId, RunClientProvenance, RunControlAction, RunEvent, RunId, RunProvenance,
|
||||
RunServerProvenance, RunSubjectProvenance, ServerSettings,
|
||||
};
|
||||
use fabro_util::text::strip_goal_decoration;
|
||||
use fabro_util::version::FABRO_VERSION;
|
||||
use fabro_vault::{Error as VaultError, SecretType, Vault};
|
||||
use fabro_workflow::artifact_upload::ArtifactSink;
|
||||
|
|
@ -2868,56 +2867,6 @@ pub(crate) fn board_columns() -> serde_json::Value {
|
|||
])
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_goal(goal: &str) -> String {
|
||||
const MAX_LEN: usize = 100;
|
||||
|
||||
let stripped = strip_goal_decoration(goal);
|
||||
let char_count = stripped.chars().count();
|
||||
if char_count <= MAX_LEN {
|
||||
return stripped.to_string();
|
||||
}
|
||||
|
||||
let truncated: String = stripped.chars().take(MAX_LEN - 3).collect();
|
||||
format!("{truncated}...")
|
||||
}
|
||||
|
||||
fn repository_name(host_repo_path: Option<&str>) -> String {
|
||||
host_repo_path
|
||||
.and_then(|path| path.rsplit(['/', '\\']).find(|segment| !segment.is_empty()))
|
||||
.unwrap_or("unknown")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn elapsed_secs(duration_ms: Option<u64>) -> Option<f64> {
|
||||
duration_ms.map(|ms| ms as f64 / 1000.0)
|
||||
}
|
||||
|
||||
fn summary_to_api_run_summary(summary: fabro_types::RunSummary) -> serde_json::Value {
|
||||
let goal = summary.goal.unwrap_or_default();
|
||||
let title = truncate_goal(&goal);
|
||||
let repository = repository_name(summary.host_repo_path.as_deref());
|
||||
let created_at = summary.run_id.created_at().to_rfc3339();
|
||||
|
||||
serde_json::json!({
|
||||
"run_id": summary.run_id.to_string(),
|
||||
"workflow_name": summary.workflow_name,
|
||||
"workflow_slug": summary.workflow_slug,
|
||||
"goal": goal,
|
||||
"title": title,
|
||||
"labels": summary.labels,
|
||||
"host_repo_path": summary.host_repo_path,
|
||||
"repository": { "name": repository },
|
||||
"start_time": summary.start_time.map(|time| time.to_rfc3339()),
|
||||
"status": summary.status,
|
||||
"pending_control": summary.pending_control,
|
||||
"duration_ms": summary.duration_ms,
|
||||
"elapsed_secs": elapsed_secs(summary.duration_ms),
|
||||
"total_usd_micros": summary.total_usd_micros,
|
||||
"superseded_by": summary.superseded_by.map(|run_id| run_id.to_string()),
|
||||
"created_at": created_at,
|
||||
})
|
||||
}
|
||||
|
||||
async fn board_run_metadata(
|
||||
state: &AppState,
|
||||
run_id: RunId,
|
||||
|
|
@ -3010,7 +2959,8 @@ async fn list_board_runs(
|
|||
let mut data = Vec::with_capacity(page_summaries.len());
|
||||
for (summary, column) in page_summaries {
|
||||
let run_id = summary.run_id;
|
||||
let mut item = summary_to_api_run_summary(summary);
|
||||
let mut item =
|
||||
serde_json::to_value(&summary).expect("RunSummary serialization is infallible");
|
||||
item["column"] = serde_json::json!(column);
|
||||
if let Some(object) = item.as_object_mut() {
|
||||
object.extend(board_run_metadata(state.as_ref(), run_id).await);
|
||||
|
|
@ -3045,7 +2995,6 @@ async fn list_runs(
|
|||
.filter(|summary| {
|
||||
include_archived || !matches!(summary.status, RunStatus::Archived { .. })
|
||||
})
|
||||
.map(summary_to_api_run_summary)
|
||||
.collect::<Vec<_>>();
|
||||
let (data, has_more) = paginate_items(items, ¶ms.pagination());
|
||||
(
|
||||
|
|
@ -3099,11 +3048,7 @@ async fn resolve_run(
|
|||
|run| run.workflow_name.clone(),
|
||||
|run| run.run_id.created_at(),
|
||||
) {
|
||||
Ok(run) => (
|
||||
StatusCode::OK,
|
||||
Json(summary_to_api_run_summary(run.clone())),
|
||||
)
|
||||
.into_response(),
|
||||
Ok(run) => (StatusCode::OK, Json(run.clone())).into_response(),
|
||||
Err(err @ (ResolveRunError::InvalidSelector | ResolveRunError::AmbiguousPrefix { .. })) => {
|
||||
ApiError::bad_request(err.to_string()).into_response()
|
||||
}
|
||||
|
|
@ -5174,7 +5119,7 @@ async fn get_run_status(
|
|||
.await
|
||||
{
|
||||
Ok(runs) => match runs.into_iter().find(|run| run.run_id == id) {
|
||||
Some(run) => (StatusCode::OK, Json(summary_to_api_run_summary(run))).into_response(),
|
||||
Some(run) => (StatusCode::OK, Json(run)).into_response(),
|
||||
None => ApiError::not_found("Run not found.").into_response(),
|
||||
},
|
||||
Err(err) => {
|
||||
|
|
@ -13501,18 +13446,24 @@ provider = "local"
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn demo_get_run_returns_store_run_summary_shape() {
|
||||
async fn demo_get_run_returns_run_summary_shape() {
|
||||
let state = create_app_state();
|
||||
let app = build_router(state, AuthMode::Disabled);
|
||||
let run_id = RunId::with_timestamp(
|
||||
"2026-03-06T14:30:00Z"
|
||||
.parse()
|
||||
.expect("demo timestamp should parse"),
|
||||
1,
|
||||
);
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri(api("/runs/run-1"))
|
||||
.uri(api(&format!("/runs/{run_id}")))
|
||||
.header("X-Fabro-Demo", "1")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let response = app.oneshot(req).await.unwrap();
|
||||
let body = response_json!(response, StatusCode::OK).await;
|
||||
// Should have StoreRunSummary fields, not RunStatusResponse fields
|
||||
// Should have RunSummary fields, not RunStatusResponse fields
|
||||
assert!(body["run_id"].is_string(), "should have run_id field");
|
||||
assert!(body["goal"].is_string(), "should have goal field");
|
||||
assert!(
|
||||
|
|
|
|||
0
lib/crates/fabro-spa/assets/.gitkeep
generated
Normal file
0
lib/crates/fabro-spa/assets/.gitkeep
generated
Normal file
BIN
lib/crates/fabro-spa/assets/apple-touch-icon.png
generated
BIN
lib/crates/fabro-spa/assets/apple-touch-icon.png
generated
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 KiB |
2
lib/crates/fabro-spa/assets/assets/app.css
generated
2
lib/crates/fabro-spa/assets/assets/app.css
generated
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{S as a}from"./chunk-c8zhk10v.js";import"./chunk-xg9nsz1a.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{d as a}from"./chunk-5q0vf5kd.js";import"./chunk-z1p7fbkb.js";import"./chunk-amk943wr.js";import"./chunk-972wx742.js";import"./chunk-ept66kdn.js";import"./chunk-1ehq66yp.js";import"./chunk-xg9nsz1a.js";import"./chunk-z868q2s0.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{e}from"./chunk-7zy2rxws.js";import"./chunk-gf0502ds.js";var n=Object.freeze(JSON.parse('{"displayName":"Nextflow","name":"nextflow","patterns":[{"include":"#nextflow"}],"repository":{"enum-def":{"begin":"^\\\\s*(enum)\\\\s+(\\\\w+)\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"},"2":{"name":"storage.type.groovy"}},"end":"}","patterns":[{"include":"source.nextflow-groovy#groovy"},{"include":"#enum-values"}]},"enum-values":{"patterns":[{"begin":"(?<=;|^)\\\\s*\\\\b([0-9A-Z_]+)(?=\\\\s*(?:[(,}]|$))","beginCaptures":{"1":{"name":"constant.enum.name.groovy"}},"end":",|(?=})|^(?!\\\\s*\\\\w+\\\\s*(?:,|$))","patterns":[{"begin":"\\\\(","end":"\\\\)","name":"meta.enum.value.groovy","patterns":[{"match":",","name":"punctuation.definition.seperator.parameter.groovy"},{"include":"#groovy-code"}]}]}]},"function-body":{"patterns":[{"match":"\\\\s"},{"begin":"(?=[<\\\\w][^(]*\\\\s+[$<\\\\w]+\\\\s*\\\\()","end":"(?=[$\\\\w]+\\\\s*\\\\()","name":"meta.method.return-type.java","patterns":[{"include":"source.nextflow-groovy#types"}]},{"begin":"([$\\\\w]+)\\\\s*\\\\(","beginCaptures":{"1":{"name":"entity.name.function.nextflow"}},"end":"\\\\)","name":"meta.definition.method.signature.java","patterns":[{"begin":"(?=[^)])","end":"(?=\\\\))","name":"meta.method.parameters.groovy","patterns":[{"begin":"(?=[^),])","end":"(?=[),])","name":"meta.method.parameter.groovy","patterns":[{"match":",","name":"punctuation.definition.separator.groovy"},{"begin":"=","beginCaptures":{"0":{"name":"keyword.operator.assignment.groovy"}},"end":"(?=[),])","name":"meta.parameter.default.groovy","patterns":[{"include":"source.nextflow-groovy#groovy-code"}]},{"include":"source.nextflow-groovy#parameters"}]}]}]},{"begin":"(?=<)","end":"(?=\\\\s)","name":"meta.method.paramerised-type.groovy","patterns":[{"begin":"<","end":">","name":"storage.type.parameters.groovy","patterns":[{"include":"source.nextflow-groovy#types"},{"match":",","name":"punctuation.definition.seperator.groovy"}]}]},{"begin":"\\\\{","end":"(?=})","name":"meta.method.body.java","patterns":[{"include":"source.nextflow-groovy#groovy-code"}]}]},"function-def":{"applyEndPatternLast":1,"begin":"(?<=;|^|\\\\{)(?=\\\\s*(?:def|(?:(?:boolean|byte|char|short|int|float|long|double)|@?(?:[A-Za-z]\\\\w*\\\\.)*[A-Z]+\\\\w*)[]\\\\[]*(?:<.*>)?n)\\\\s+([^=]+\\\\s+)?\\\\w+\\\\s*\\\\()","end":"}|(?=[^{])","name":"meta.definition.method.groovy","patterns":[{"include":"#function-body"}]},"include-decl":{"patterns":[{"match":"^\\\\b(include)\\\\b","name":"keyword.nextflow"},{"match":"\\\\b(from)\\\\b","name":"keyword.nextflow"}]},"nextflow":{"patterns":[{"include":"#record-def"},{"include":"#enum-def"},{"include":"#function-def"},{"include":"#process-def"},{"include":"#workflow-def"},{"include":"#params-def"},{"include":"#output-def"},{"include":"#include-decl"},{"include":"source.nextflow-groovy"}]},"output-def":{"begin":"^\\\\s*(output)\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"}},"end":"}","name":"output.nextflow","patterns":[{"include":"source.nextflow-groovy#groovy"}]},"params-def":{"begin":"^\\\\s*(params)\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"}},"end":"}","name":"params.nextflow","patterns":[{"include":"source.nextflow-groovy#groovy"}]},"process-body":{"patterns":[{"match":"(?:input|output|when|script|shell|exec):","name":"constant.block.nextflow"},{"match":"\\\\b(val|env|file|path|stdin|stdout|tuple)([(\\\\s])","name":"entity.name.function.nextflow"},{"include":"source.nextflow-groovy#groovy"}]},"process-def":{"begin":"^\\\\s*(process)\\\\s+(\\\\w+)\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"},"2":{"name":"entity.name.function.nextflow"}},"end":"}","name":"process.nextflow","patterns":[{"include":"#process-body"}]},"record-def":{"begin":"^\\\\s*(record)\\\\s+(\\\\w+)\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"},"2":{"name":"storage.type.groovy"}},"end":"}","name":"record.nextflow","patterns":[{"include":"source.nextflow-groovy#groovy"}]},"workflow-body":{"patterns":[{"match":"(?:take|main|emit|publish):","name":"constant.block.nextflow"},{"include":"source.nextflow-groovy#groovy"}]},"workflow-def":{"begin":"^\\\\s*(workflow)(?:\\\\s+(\\\\w+))?\\\\s*\\\\{","beginCaptures":{"1":{"name":"keyword.nextflow"},"2":{"name":"entity.name.function.nextflow"}},"end":"}","name":"workflow.nextflow","patterns":[{"include":"#workflow-body"}]}},"scopeName":"source.nextflow","embeddedLangs":["nextflow-groovy"],"aliases":["nf"]}')),t=[...e,n];export{t as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{v as a}from"./chunk-ktx0nkhz.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{O as a}from"./chunk-ept66kdn.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"CODEOWNERS","name":"codeowners","patterns":[{"include":"#comment"},{"include":"#pattern"},{"include":"#owner"}],"repository":{"comment":{"patterns":[{"begin":"^\\\\s*#","captures":{"0":{"name":"punctuation.definition.comment.codeowners"}},"end":"$","name":"comment.line.codeowners"}]},"owner":{"match":"\\\\S*@\\\\S+","name":"storage.type.function.codeowners"},"pattern":{"match":"^\\\\s*(\\\\S+)","name":"variable.other.codeowners"}},"scopeName":"text.codeowners"}')),n=[e];export{n as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"Gettext PO","fileTypes":["po","pot","potx"],"name":"po","patterns":[{"begin":"^(?:(?=(msg(?:id(_plural)?|ctxt))\\\\s*\\"[^\\"])|\\\\s*$)","end":"\\\\z","patterns":[{"include":"#body"}]},{"include":"#comments"},{"match":"^msg(id|str)\\\\s+\\"\\"\\\\s*$\\\\n?","name":"comment.line.number-sign.po"},{"captures":{"1":{"name":"constant.language.po"},"2":{"name":"punctuation.separator.key-value.po"},"3":{"name":"string.other.po"}},"match":"^\\"(?:([^:\\\\s]+)(:)\\\\s+)?([^\\"]*)\\"\\\\s*$\\\\n?","name":"meta.header.po"}],"repository":{"body":{"patterns":[{"begin":"^(msgid(_plural)?)\\\\s+","beginCaptures":{"1":{"name":"keyword.control.msgid.po"}},"end":"^(?!\\")","name":"meta.scope.msgid.po","patterns":[{"begin":"(\\\\G|^)\\"","end":"\\"","name":"string.quoted.double.po","patterns":[{"match":"\\\\\\\\[\\"\\\\\\\\]","name":"constant.character.escape.po"}]}]},{"begin":"^(msgstr)(?:(\\\\[)(\\\\d+)(]))?\\\\s+","beginCaptures":{"1":{"name":"keyword.control.msgstr.po"},"2":{"name":"keyword.control.msgstr.po"},"3":{"name":"constant.numeric.po"},"4":{"name":"keyword.control.msgstr.po"}},"end":"^(?!\\")","name":"meta.scope.msgstr.po","patterns":[{"begin":"(\\\\G|^)\\"","end":"\\"","name":"string.quoted.double.po","patterns":[{"match":"\\\\\\\\[\\"\\\\\\\\]","name":"constant.character.escape.po"}]}]},{"begin":"^(msgctxt)(?:(\\\\[)(\\\\d+)(]))?\\\\s+","beginCaptures":{"1":{"name":"keyword.control.msgctxt.po"},"2":{"name":"keyword.control.msgctxt.po"},"3":{"name":"constant.numeric.po"},"4":{"name":"keyword.control.msgctxt.po"}},"end":"^(?!\\")","name":"meta.scope.msgctxt.po","patterns":[{"begin":"(\\\\G|^)\\"","end":"\\"","name":"string.quoted.double.po","patterns":[{"match":"\\\\\\\\[\\"\\\\\\\\]","name":"constant.character.escape.po"}]}]},{"captures":{"1":{"name":"punctuation.definition.comment.po"}},"match":"^(#~).*$\\\\n?","name":"comment.line.number-sign.obsolete.po"},{"include":"#comments"},{"match":"^(?!\\\\s*$)[^\\"#].*$\\\\n?","name":"invalid.illegal.po"}]},"comments":{"patterns":[{"begin":"^(?=#)","end":"(?!\\\\G)","patterns":[{"begin":"(#,)\\\\s+","beginCaptures":{"1":{"name":"punctuation.definition.comment.po"}},"end":"\\\\n","name":"comment.line.number-sign.flag.po","patterns":[{"captures":{"1":{"name":"entity.name.type.flag.po"}},"match":"(?:\\\\G|,\\\\s*)(fuzzy|(?:no-)?(?:c|objc|sh|lisp|elisp|librep|scheme|smalltalk|java|csharp|awk|object-pascal|ycp|tcl|perl|perl-brace|php|gcc-internal|qt|boost)-format)"}]},{"begin":"#\\\\.","beginCaptures":{"0":{"name":"punctuation.definition.comment.po"}},"end":"\\\\n","name":"comment.line.number-sign.extracted.po"},{"begin":"(#:)[\\\\t ]*","beginCaptures":{"1":{"name":"punctuation.definition.comment.po"}},"end":"\\\\n","name":"comment.line.number-sign.reference.po","patterns":[{"match":"(\\\\S+:)([;\\\\d]*)","name":"storage.type.class.po"}]},{"begin":"#\\\\|","beginCaptures":{"0":{"name":"punctuation.definition.comment.po"}},"end":"\\\\n","name":"comment.line.number-sign.previous.po"},{"begin":"#","beginCaptures":{"0":{"name":"punctuation.definition.comment.po"}},"end":"\\\\n","name":"comment.line.number-sign.po"}]}]}},"scopeName":"source.po","aliases":["pot","potx"]}')),n=[e];export{n as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"Tcl","fileTypes":["tcl"],"foldingStartMarker":"\\\\{\\\\s*$","foldingStopMarker":"^\\\\s*}","name":"tcl","patterns":[{"begin":"(?<=^|;)\\\\s*((#))","beginCaptures":{"1":{"name":"comment.line.number-sign.tcl"},"2":{"name":"punctuation.definition.comment.tcl"}},"contentName":"comment.line.number-sign.tcl","end":"\\\\n","patterns":[{"match":"(\\\\\\\\[\\\\n\\\\\\\\])"}]},{"captures":{"1":{"name":"keyword.control.tcl"}},"match":"(?<=^|[;\\\\[{])\\\\s*(if|while|for|catch|default|return|break|continue|switch|exit|foreach|try|throw)\\\\b"},{"captures":{"1":{"name":"keyword.control.tcl"}},"match":"(?<=^|})\\\\s*(then|elseif|else)\\\\b"},{"captures":{"1":{"name":"keyword.other.tcl"},"2":{"name":"entity.name.function.tcl"}},"match":"(?<=^|\\\\{)\\\\s*(proc)\\\\s+(\\\\S+)"},{"captures":{"1":{"name":"keyword.other.tcl"}},"match":"(?<=^|[;\\\\[{])\\\\s*(after|append|array|auto_execok|auto_import|auto_load|auto_mkindex|auto_mkindex_old|auto_qualify|auto_reset|bgerror|binary|cd|clock|close|concat|dde|encoding|eof|error|eval|exec|expr|fblocked|fconfigure|fcopy|file|fileevent|filename|flush|format|gets|glob|global|history|http|incr|info|interp|join|lappend|library|lindex|linsert|list|llength|load|lrange|lreplace|lsearch|lset|lsort|memory|msgcat|namespace|open|package|parray|pid|pkg::create|pkg_mkIndex|proc|puts|pwd|re_syntax|read|registry|rename|resource|scan|seek|set|socket|SafeBase|source|split|string|subst|Tcl|tcl_endOfWord|tcl_findLibrary|tcl_startOfNextWord|tcl_startOfPreviousWord|tcl_wordBreakAfter|tcl_wordBreakBefore|tcltest|tclvars|tell|time|trace|unknown|unset|update|uplevel|upvar|variable|vwait)\\\\b"},{"begin":"(?<=^|[;\\\\[{])\\\\s*(reg(?:exp|sub))\\\\b\\\\s*","beginCaptures":{"1":{"name":"keyword.other.tcl"}},"end":"[]\\\\n;]","patterns":[{"match":"\\\\\\\\(?:.|\\\\n)","name":"constant.character.escape.tcl"},{"match":"-\\\\w+\\\\s*"},{"applyEndPatternLast":1,"begin":"--\\\\s*","end":"","patterns":[{"include":"#regexp"}]},{"include":"#regexp"}]},{"include":"#escape"},{"include":"#variable"},{"include":"#operator"},{"include":"#numeric"},{"begin":"\\"","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.tcl"}},"end":"\\"","endCaptures":{"0":{"name":"punctuation.definition.string.end.tcl"}},"name":"string.quoted.double.tcl","patterns":[{"include":"#escape"},{"include":"#variable"},{"include":"#embedded"}]}],"repository":{"bare-string":{"begin":"(?:^|(?<=\\\\s))\\"","end":"\\"([^]\\\\s]*)","endCaptures":{"1":{"name":"invalid.illegal.tcl"}},"patterns":[{"include":"#escape"},{"include":"#variable"}]},"braces":{"begin":"(?:^|(?<=\\\\s))\\\\{","end":"}([^]\\\\s]*)","endCaptures":{"1":{"name":"invalid.illegal.tcl"}},"patterns":[{"match":"\\\\\\\\[\\\\n{}]","name":"constant.character.escape.tcl"},{"include":"#inner-braces"}]},"embedded":{"begin":"\\\\[","beginCaptures":{"0":{"name":"punctuation.section.embedded.begin.tcl"}},"end":"]","endCaptures":{"0":{"name":"punctuation.section.embedded.end.tcl"}},"name":"source.tcl.embedded","patterns":[{"include":"source.tcl"}]},"escape":{"match":"\\\\\\\\(\\\\d{1,3}|x\\\\h+|u\\\\h{1,4}|.|\\\\n)","name":"constant.character.escape.tcl"},"inner-braces":{"begin":"\\\\{","end":"}","patterns":[{"match":"\\\\\\\\[\\\\n{}]","name":"constant.character.escape.tcl"},{"include":"#inner-braces"}]},"numeric":{"match":"(?<![A-Za-z])([-+]?([0-9]*\\\\.)?[0-9]+f?)(?![.A-Za-z])","name":"constant.numeric.tcl"},"operator":{"match":"(?<=[ \\\\d])([-+~]|&{1,2}|\\\\|{1,2}|<{1,2}|>{1,2}|\\\\*{1,2}|[!%/]|<=|>=|={1,2}|!=|\\\\^)(?=[ \\\\d])","name":"keyword.operator.tcl"},"regexp":{"begin":"(?=\\\\S)(?![]\\\\n;])","end":"(?=[]\\\\n;])","patterns":[{"begin":"(?=[^\\\\t\\\\n ;])","end":"(?=[\\\\t\\\\n ;])","name":"string.regexp.tcl","patterns":[{"include":"#braces"},{"include":"#bare-string"},{"include":"#escape"},{"include":"#variable"}]},{"begin":"[\\\\t ]","end":"(?=[]\\\\n;])","patterns":[{"include":"#variable"},{"include":"#embedded"},{"include":"#escape"},{"include":"#braces"},{"include":"#string"}]}]},"string":{"applyEndPatternLast":1,"begin":"(?:^|(?<=\\\\s))(?=\\")","end":"","name":"string.quoted.double.tcl","patterns":[{"include":"#bare-string"}]},"variable":{"captures":{"1":{"name":"punctuation.definition.variable.tcl"}},"match":"(\\\\$)((?:[0-9A-Z_a-z]|::)+(\\\\([^)]+\\\\))?|\\\\{[^}]*})","name":"support.function.tcl"}},"scopeName":"source.tcl"}')),n=[e];export{n as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"Gleam","fileTypes":["gleam"],"name":"gleam","patterns":[{"include":"#comments"},{"include":"#keywords"},{"include":"#strings"},{"include":"#constant"},{"include":"#entity"},{"include":"#discards"}],"repository":{"binary_number":{"match":"\\\\b0[Bb][01_]*\\\\b","name":"constant.numeric.binary.gleam","patterns":[]},"comments":{"patterns":[{"match":"//.*","name":"comment.line.gleam"}]},"constant":{"patterns":[{"include":"#binary_number"},{"include":"#octal_number"},{"include":"#hexadecimal_number"},{"include":"#decimal_number"},{"match":"\\\\p{upper}\\\\p{alnum}*","name":"entity.name.type.gleam"}]},"decimal_number":{"match":"\\\\b([0-9][0-9_]*)(\\\\.([0-9_]*)?(e-?[0-9]+)?)?\\\\b","name":"constant.numeric.decimal.gleam","patterns":[]},"discards":{"match":"\\\\b_\\\\p{word}+{0,1}\\\\b","name":"comment.unused.gleam"},"entity":{"patterns":[{"begin":"\\\\b(\\\\p{lower}\\\\p{word}*)\\\\b\\\\s*\\\\(","captures":{"1":{"name":"entity.name.function.gleam"}},"end":"\\\\)","patterns":[{"include":"$self"}]},{"match":"\\\\b(\\\\p{lower}\\\\p{word}*):\\\\s","name":"variable.parameter.gleam"},{"match":"\\\\b(\\\\p{lower}\\\\p{word}*):","name":"entity.name.namespace.gleam"}]},"hexadecimal_number":{"match":"\\\\b0[Xx][_\\\\h]+\\\\b","name":"constant.numeric.hexadecimal.gleam","patterns":[]},"keywords":{"patterns":[{"match":"\\\\b(as|use|case|if|fn|import|let|assert|pub|type|opaque|const|todo|panic|else|echo)\\\\b","name":"keyword.control.gleam"},{"match":"(<-|->)","name":"keyword.operator.arrow.gleam"},{"match":"\\\\|>","name":"keyword.operator.pipe.gleam"},{"match":"\\\\.\\\\.","name":"keyword.operator.splat.gleam"},{"match":"([!=]=)","name":"keyword.operator.comparison.gleam"},{"match":"([<>]=?\\\\.)","name":"keyword.operator.comparison.float.gleam"},{"match":"(<=|>=|[<>])","name":"keyword.operator.comparison.int.gleam"},{"match":"(&&|\\\\|\\\\|)","name":"keyword.operator.logical.gleam"},{"match":"<>","name":"keyword.operator.string.gleam"},{"match":"\\\\|","name":"keyword.operator.other.gleam"},{"match":"([-*+/]\\\\.)","name":"keyword.operator.arithmetic.float.gleam"},{"match":"([-%*+/])","name":"keyword.operator.arithmetic.int.gleam"},{"match":"=","name":"keyword.operator.assignment.gleam"}]},"octal_number":{"match":"\\\\b0[Oo][0-7_]*\\\\b","name":"constant.numeric.octal.gleam","patterns":[]},"strings":{"begin":"\\"","end":"\\"","name":"string.quoted.double.gleam","patterns":[{"match":"\\\\\\\\.","name":"constant.character.escape.gleam"}]}},"scopeName":"source.gleam"}')),a=[e];export{a as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{Y as a}from"./chunk-1ehq66yp.js";import"./chunk-xg9nsz1a.js";import"./chunk-z868q2s0.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"QML Directory","name":"qmldir","patterns":[{"include":"#comment"},{"include":"#keywords"},{"include":"#version"},{"include":"#names"}],"repository":{"comment":{"patterns":[{"begin":"#","end":"$","name":"comment.line.number-sign.qmldir"}]},"file-name":{"patterns":[{"match":"\\\\b\\\\w+\\\\.(qmltypes|qml|js)\\\\b","name":"string.unquoted.qmldir"}]},"identifier":{"patterns":[{"match":"\\\\b\\\\w+\\\\b","name":"variable.parameter.qmldir"}]},"keywords":{"patterns":[{"match":"\\\\b(module|singleton|internal|plugin|classname|typeinfo|depends|designersupported)\\\\b","name":"keyword.other.qmldir"}]},"module-name":{"patterns":[{"match":"\\\\b[A-Z]\\\\w*\\\\b","name":"entity.name.type.qmldir"}]},"names":{"patterns":[{"include":"#file-name"},{"include":"#module-name"},{"include":"#identifier"}]},"version":{"patterns":[{"match":"\\\\b\\\\d+\\\\.\\\\d+\\\\b","name":"constant.numeric.qml"}]}},"scopeName":"source.qmldir"}')),n=[e];export{n as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{G as n}from"./chunk-z9jws129.js";import{N as t}from"./chunk-71s03bbh.js";import{Y as e}from"./chunk-1ehq66yp.js";import"./chunk-xg9nsz1a.js";import"./chunk-z868q2s0.js";import"./chunk-gf0502ds.js";var a=Object.freeze(JSON.parse('{"displayName":"Edge","injections":{"text.html.edge - (meta.embedded | meta.tag | comment.block.edge), L:(text.html.edge meta.tag - (comment.block.edge | meta.embedded.block.edge)), L:(source.ts.embedded.html - (comment.block.edge | meta.embedded.block.edge))":{"patterns":[{"include":"#comment"},{"include":"#escapedMustache"},{"include":"#safeMustache"},{"include":"#mustache"},{"include":"#nonSeekableTag"},{"include":"#tag"}]}},"name":"edge","patterns":[{"include":"text.html.basic"},{"include":"text.html.derivative"}],"repository":{"comment":{"begin":"\\\\{\\\\{--","beginCaptures":{"0":{"name":"punctuation.definition.comment.begin.edge"}},"end":"--}}","endCaptures":{"0":{"name":"punctuation.definition.comment.end.edge"}},"name":"comment.block"},"escapedMustache":{"begin":"@\\\\{\\\\{","beginCaptures":{"0":{"name":"punctuation.definition.comment.begin.edge"}},"end":"}}","endCaptures":{"0":{"name":"punctuation.definition.comment.end.edge"}},"name":"comment.block"},"mustache":{"begin":"\\\\{\\\\{","beginCaptures":{"0":{"name":"punctuation.mustache.begin"}},"end":"}}","endCaptures":{"0":{"name":"punctuation.mustache.end"}},"name":"meta.embedded.block.javascript","patterns":[{"include":"source.ts#expression"}]},"nonSeekableTag":{"captures":{"2":{"name":"support.function.edge"}},"match":"^(\\\\s*)((@{1,2})(!)?([.A-Z_a-z]+))(~)?$","name":"meta.embedded.block.javascript","patterns":[{"include":"source.ts#expression"}]},"safeMustache":{"begin":"\\\\{\\\\{\\\\{","beginCaptures":{"0":{"name":"punctuation.mustache.begin"}},"end":"}}}","endCaptures":{"0":{"name":"punctuation.mustache.end"}},"name":"meta.embedded.block.javascript","patterns":[{"include":"source.ts#expression"}]},"tag":{"begin":"^(\\\\s*)((@{1,2})(!)?([.A-Z_a-z]+)(\\\\s{0,2}))(\\\\()","beginCaptures":{"2":{"name":"support.function.edge"},"7":{"name":"punctuation.paren.open"}},"end":"\\\\)","endCaptures":{"0":{"name":"punctuation.paren.close"}},"name":"meta.embedded.block.javascript","patterns":[{"include":"source.ts#expression"}]}},"scopeName":"text.html.edge","embeddedLangs":["typescript","html","html-derivative"]}')),i=[...t,...e,...n,a];export{i as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var t=Object.freeze(JSON.parse('{"displayName":"TSV","fileTypes":["tsv","tab"],"name":"tsv","patterns":[{"captures":{"1":{"name":"rainbow1"},"2":{"name":"keyword.rainbow2"},"3":{"name":"entity.name.function.rainbow3"},"4":{"name":"comment.rainbow4"},"5":{"name":"string.rainbow5"},"6":{"name":"variable.parameter.rainbow6"},"7":{"name":"constant.numeric.rainbow7"},"8":{"name":"entity.name.type.rainbow8"},"9":{"name":"markup.bold.rainbow9"},"10":{"name":"invalid.rainbow10"}},"match":"([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)([^\\\\t]*\\\\t?)","name":"rainbowgroup"}],"scopeName":"text.tsv"}')),a=[t];export{a as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{k as a}from"./chunk-pce8yxd3.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"Rel","name":"rel","patterns":[{"include":"#strings"},{"include":"#comment"},{"include":"#single-line-comment-consuming-line-ending"},{"include":"#deprecated-temporary"},{"include":"#operators"},{"include":"#symbols"},{"include":"#keywords"},{"include":"#otherkeywords"},{"include":"#types"},{"include":"#constants"}],"repository":{"comment":{"patterns":[{"begin":"/\\\\*\\\\*(?!/)","beginCaptures":{"0":{"name":"punctuation.definition.comment.rel"}},"end":"\\\\*/","endCaptures":{"0":{"name":"punctuation.definition.comment.rel"}},"name":"comment.block.documentation.rel","patterns":[{"include":"#docblock"}]},{"begin":"(/\\\\*)(?:\\\\s*((@)internal)(?=\\\\s|(\\\\*/)))?","beginCaptures":{"1":{"name":"punctuation.definition.comment.rel"},"2":{"name":"storage.type.internaldeclaration.rel"},"3":{"name":"punctuation.decorator.internaldeclaration.rel"}},"end":"\\\\*/","endCaptures":{"0":{"name":"punctuation.definition.comment.rel"}},"name":"comment.block.rel"},{"begin":"doc\\"\\"\\"","end":"\\"\\"\\"","name":"comment.block.documentation.rel"},{"begin":"(^[\\\\t ]+)?((//)(?:\\\\s*((@)internal)(?=\\\\s|$))?)","beginCaptures":{"1":{"name":"punctuation.whitespace.comment.leading.rel"},"2":{"name":"comment.line.double-slash.rel"},"3":{"name":"punctuation.definition.comment.rel"},"4":{"name":"storage.type.internaldeclaration.rel"},"5":{"name":"punctuation.decorator.internaldeclaration.rel"}},"contentName":"comment.line.double-slash.rel","end":"(?=$)"}]},"constants":{"patterns":[{"match":"\\\\b((true|false))\\\\b","name":"constant.language.rel"}]},"deprecated-temporary":{"patterns":[{"match":"@inspect","name":"keyword.other.rel"}]},"keywords":{"patterns":[{"match":"\\\\b((def|entity|bound|include|ic|forall|exists|[∀∃]|return|module|^end))\\\\b|(((<)?\\\\|(>)?)|[∀∃])","name":"keyword.control.rel"}]},"operators":{"patterns":[{"match":"\\\\b((if|then|else|and|or|not|eq|neq|lt|lt_eq|gt|gt_eq))\\\\b|([-%*+/=^÷]|!=|[<≠]|<=|[>≤]|>=|[\\\\&≥])|\\\\s+(end)","name":"keyword.other.rel"}]},"otherkeywords":{"patterns":[{"match":"\\\\s*(@inline)\\\\s*|\\\\s*(@auto_number)\\\\s*|\\\\s*(function)\\\\s|\\\\b((implies|select|from|∈|where|for|in))\\\\b|(((<)?\\\\|(>)?)|∈)","name":"keyword.other.rel"}]},"single-line-comment-consuming-line-ending":{"begin":"(^[\\\\t ]+)?((//)(?:\\\\s*((@)internal)(?=\\\\s|$))?)","beginCaptures":{"1":{"name":"punctuation.whitespace.comment.leading.rel"},"2":{"name":"comment.line.double-slash.rel"},"3":{"name":"punctuation.definition.comment.rel"},"4":{"name":"storage.type.internaldeclaration.rel"},"5":{"name":"punctuation.decorator.internaldeclaration.rel"}},"contentName":"comment.line.double-slash.rel","end":"(?=^)"},"strings":{"begin":"\\"","end":"\\"","name":"string.quoted.double.rel","patterns":[{"match":"\\\\\\\\.","name":"constant.character.escape.rel"}]},"symbols":{"patterns":[{"match":"(:[$\\\\[_[:alpha:]](]|[$_[:alnum:]]*))","name":"variable.parameter.rel"}]},"types":{"patterns":[{"match":"\\\\b((Symbol|Char|Bool|Rational|FixedDecimal|Float16|Float32|Float64|Int8|Int16|Int32|Int64|Int128|UInt8|UInt16|UInt32|UInt64|UInt128|Date|DateTime|Day|Week|Month|Year|Nanosecond|Microsecond|Millisecond|Second|Minute|Hour|FilePos|HashValue|AutoNumberValue))\\\\b","name":"entity.name.type.rel"}]}},"scopeName":"source.rel"}')),n=[e];export{n as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{s as a}from"./chunk-crmnbecm.js";import"./chunk-0gpjqeyh.js";import"./chunk-nkw6kj41.js";import"./chunk-71s03bbh.js";import"./chunk-z868q2s0.js";import"./chunk-gf0502ds.js";export{a as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import"./chunk-gf0502ds.js";var e=Object.freeze(JSON.parse('{"displayName":"Berry","name":"berry","patterns":[{"include":"#controls"},{"include":"#strings"},{"include":"#comment-block"},{"include":"#comments"},{"include":"#keywords"},{"include":"#function"},{"include":"#member"},{"include":"#identifier"},{"include":"#number"},{"include":"#operator"}],"repository":{"comment-block":{"begin":"#-","end":"-#","name":"comment.berry","patterns":[{}]},"comments":{"begin":"#","end":"\\\\n","name":"comment.line.berry","patterns":[{}]},"controls":{"patterns":[{"match":"\\\\b(if|elif|else|for|while|do|end|break|continue|return|try|except|raise)\\\\b","name":"keyword.control.berry"}]},"function":{"patterns":[{"match":"\\\\b([A-Z_a-z][0-9A-Z_a-z]*(?=\\\\s*\\\\())","name":"entity.name.function.berry"}]},"identifier":{"patterns":[{"match":"\\\\b[A-Z_a-z]\\\\w+\\\\b","name":"identifier.berry"}]},"keywords":{"patterns":[{"match":"\\\\b(var|static|def|class|true|false|nil|self|super|import|as|_class)\\\\b","name":"keyword.berry"}]},"member":{"patterns":[{"captures":{"0":{"name":"entity.other.attribute-name.berry"}},"match":"\\\\.([A-Z_a-z][0-9A-Z_a-z]*)"}]},"number":{"patterns":[{"match":"0x\\\\h+|\\\\d+|(\\\\d+\\\\.?|\\\\.\\\\d)\\\\d*([Ee][-+]?\\\\d+)?","name":"constant.numeric.berry"}]},"operator":{"patterns":[{"match":"[-\\\\]!%\\\\&(-+./:<=>\\\\[^|~]","name":"keyword.operator.berry"}]},"strings":{"patterns":[{"begin":"f(?=[\\"\'])","patterns":[{"begin":"\\"","end":"\\"","name":"string.quoted.other.berry","patterns":[{"match":"(\\\\\\\\x\\\\h{2})|(\\\\\\\\[0-7]{3})|(\\\\\\\\\\\\\\\\)|(\\\\\\\\\\")|(\\\\\\\\\')|(\\\\\\\\a)|(\\\\\\\\b)|(\\\\\\\\f)|(\\\\\\\\n)|(\\\\\\\\r)|(\\\\\\\\t)|(\\\\\\\\v)","name":"constant.character.escape.berry"},{"match":"\\\\{\\\\{[^}]*}}","name":"string.quoted.other.berry"},{"begin":"\\\\{","end":"}","name":"keyword.other.unit.berry","patterns":[{"include":"#keywords"},{"include":"#numbers"},{"include":"#identifier"},{"include":"#operator"},{"include":"#member"},{"include":"#function"}]}]},{"begin":"\'","end":"\'","name":"string.quoted.other.berry","patterns":[{"match":"(\\\\\\\\x\\\\h{2})|(\\\\\\\\[0-7]{3})|(\\\\\\\\\\\\\\\\)|(\\\\\\\\\\")|(\\\\\\\\\')|(\\\\\\\\a)|(\\\\\\\\b)|(\\\\\\\\f)|(\\\\\\\\n)|(\\\\\\\\r)|(\\\\\\\\t)|(\\\\\\\\v)","name":"constant.character.escape.berry"},{"match":"\\\\{\\\\{[^}]*}}","name":"string.quoted.other.berry"},{"begin":"\\\\{","end":"}","name":"keyword.other.unit.berry","patterns":[{"include":"#keywords"},{"include":"#numbers"},{"include":"#identifier"},{"include":"#operator"},{"include":"#member"},{"include":"#function"}]}]}],"while":"\\\\G|^[\\\\t ]*(?=[\\"\'])"},{"begin":"([\\"\'])","end":"\\\\1","name":"string.quoted.double.berry","patterns":[{"match":"(\\\\\\\\x\\\\h{2})|(\\\\\\\\[0-7]{3})|(\\\\\\\\\\\\\\\\)|(\\\\\\\\\\")|(\\\\\\\\\')|(\\\\\\\\a)|(\\\\\\\\b)|(\\\\\\\\f)|(\\\\\\\\n)|(\\\\\\\\r)|(\\\\\\\\t)|(\\\\\\\\v)","name":"constant.character.escape.berry"}]}]}},"scopeName":"source.berry","aliases":["be"]}')),r=[e];export{r as default};
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue