Add callable workflow to bump the version of an extension (#43566)
This adds an intial workflow file that can be pulled in to create a bump commit for an extension version in an extension repository. Release Notes: - N/A
This commit is contained in:
@@ -7,6 +7,7 @@ mod after_release;
|
||||
mod cherry_pick;
|
||||
mod compare_perf;
|
||||
mod danger;
|
||||
mod extension_bump;
|
||||
mod extension_tests;
|
||||
mod nix_build;
|
||||
mod release_nightly;
|
||||
@@ -44,6 +45,7 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
|
||||
("run_agent_evals.yml", run_agent_evals::run_agent_evals()),
|
||||
("after_release.yml", after_release::after_release()),
|
||||
("extension_tests.yml", extension_tests::extension_tests()),
|
||||
("extension_bump.yml", extension_bump::extension_bump()),
|
||||
];
|
||||
fs::create_dir_all(dir)
|
||||
.with_context(|| format!("Failed to create directory: {}", dir.display()))?;
|
||||
|
||||
@@ -3,14 +3,14 @@ use gh_workflow::*;
|
||||
use crate::tasks::workflows::{
|
||||
runners,
|
||||
steps::{self, NamedJob, named},
|
||||
vars::{self, Input, StepOutput},
|
||||
vars::{self, StepOutput, WorkflowInput},
|
||||
};
|
||||
|
||||
pub fn cherry_pick() -> Workflow {
|
||||
let branch = Input::string("branch", None);
|
||||
let commit = Input::string("commit", None);
|
||||
let channel = Input::string("channel", None);
|
||||
let pr_number = Input::string("pr_number", None);
|
||||
let branch = WorkflowInput::string("branch", None);
|
||||
let commit = WorkflowInput::string("commit", None);
|
||||
let channel = WorkflowInput::string("channel", None);
|
||||
let pr_number = WorkflowInput::string("pr_number", None);
|
||||
let cherry_pick = run_cherry_pick(&branch, &commit, &channel);
|
||||
named::workflow()
|
||||
.run_name(format!("cherry_pick to {channel} #{pr_number}"))
|
||||
@@ -24,7 +24,11 @@ pub fn cherry_pick() -> Workflow {
|
||||
.add_job(cherry_pick.name, cherry_pick.job)
|
||||
}
|
||||
|
||||
fn run_cherry_pick(branch: &Input, commit: &Input, channel: &Input) -> NamedJob {
|
||||
fn run_cherry_pick(
|
||||
branch: &WorkflowInput,
|
||||
commit: &WorkflowInput,
|
||||
channel: &WorkflowInput,
|
||||
) -> NamedJob {
|
||||
fn authenticate_as_zippy() -> (Step<Use>, StepOutput) {
|
||||
let step = named::uses(
|
||||
"actions",
|
||||
@@ -39,9 +43,9 @@ fn run_cherry_pick(branch: &Input, commit: &Input, channel: &Input) -> NamedJob
|
||||
}
|
||||
|
||||
fn cherry_pick(
|
||||
branch: &Input,
|
||||
commit: &Input,
|
||||
channel: &Input,
|
||||
branch: &WorkflowInput,
|
||||
commit: &WorkflowInput,
|
||||
channel: &WorkflowInput,
|
||||
token: &StepOutput,
|
||||
) -> Step<Run> {
|
||||
named::bash(&format!("./script/cherry-pick {branch} {commit} {channel}"))
|
||||
|
||||
@@ -5,13 +5,13 @@ use crate::tasks::workflows::steps::FluentBuilder;
|
||||
use crate::tasks::workflows::{
|
||||
runners,
|
||||
steps::{self, NamedJob, named},
|
||||
vars::Input,
|
||||
vars::WorkflowInput,
|
||||
};
|
||||
|
||||
pub fn compare_perf() -> Workflow {
|
||||
let head = Input::string("head", None);
|
||||
let base = Input::string("base", None);
|
||||
let crate_name = Input::string("crate_name", Some("".to_owned()));
|
||||
let head = WorkflowInput::string("head", None);
|
||||
let base = WorkflowInput::string("base", None);
|
||||
let crate_name = WorkflowInput::string("crate_name", Some("".to_owned()));
|
||||
let run_perf = run_perf(&base, &head, &crate_name);
|
||||
named::workflow()
|
||||
.on(Event::default().workflow_dispatch(
|
||||
@@ -23,8 +23,12 @@ pub fn compare_perf() -> Workflow {
|
||||
.add_job(run_perf.name, run_perf.job)
|
||||
}
|
||||
|
||||
pub fn run_perf(base: &Input, head: &Input, crate_name: &Input) -> NamedJob {
|
||||
fn cargo_perf_test(ref_name: &Input, crate_name: &Input) -> Step<Run> {
|
||||
pub fn run_perf(
|
||||
base: &WorkflowInput,
|
||||
head: &WorkflowInput,
|
||||
crate_name: &WorkflowInput,
|
||||
) -> NamedJob {
|
||||
fn cargo_perf_test(ref_name: &WorkflowInput, crate_name: &WorkflowInput) -> Step<Run> {
|
||||
named::bash(&format!(
|
||||
"
|
||||
if [ -n \"{crate_name}\" ]; then
|
||||
@@ -39,7 +43,7 @@ pub fn run_perf(base: &Input, head: &Input, crate_name: &Input) -> NamedJob {
|
||||
named::uses("taiki-e", "install-action", "hyperfine")
|
||||
}
|
||||
|
||||
fn compare_runs(head: &Input, base: &Input) -> Step<Run> {
|
||||
fn compare_runs(head: &WorkflowInput, base: &WorkflowInput) -> Step<Run> {
|
||||
named::bash(&format!(
|
||||
"cargo perf-compare --save=results.md {base} {head}"
|
||||
))
|
||||
|
||||
217
tooling/xtask/src/tasks/workflows/extension_bump.rs
Normal file
217
tooling/xtask/src/tasks/workflows/extension_bump.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use gh_workflow::*;
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::tasks::workflows::{
|
||||
extension_tests::{self},
|
||||
runners,
|
||||
steps::{self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, NamedJob, named},
|
||||
vars::{
|
||||
JobOutput, StepOutput, WorkflowInput, WorkflowSecret, one_workflow_per_non_main_branch,
|
||||
},
|
||||
};
|
||||
|
||||
const BUMPVERSION_CONFIG: &str = indoc! {r#"
|
||||
[bumpversion]
|
||||
current_version = "$OLD_VERSION"
|
||||
|
||||
[bumpversion:file:Cargo.toml]
|
||||
|
||||
[bumpversion:file:extension.toml]
|
||||
"#
|
||||
};
|
||||
|
||||
const VERSION_CHECK: &str = r#"sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml"#;
|
||||
|
||||
// This is used by various extensions repos in the zed-extensions org to bump extension versions.
|
||||
pub(crate) fn extension_bump() -> Workflow {
|
||||
let bump_type = WorkflowInput::string("bump-type", Some("patch".to_owned()));
|
||||
|
||||
let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
|
||||
let app_secret =
|
||||
WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
|
||||
|
||||
let test_extension = extension_tests::check_extension();
|
||||
let (check_bump_needed, needs_bump) = check_bump_needed();
|
||||
let bump_version = bump_extension_version(
|
||||
&[&test_extension, &check_bump_needed],
|
||||
&bump_type,
|
||||
needs_bump.as_job_output(&check_bump_needed),
|
||||
&app_id,
|
||||
&app_secret,
|
||||
);
|
||||
|
||||
named::workflow()
|
||||
.add_event(
|
||||
Event::default().workflow_call(
|
||||
WorkflowCall::default()
|
||||
.add_input(bump_type.name, bump_type.call_input())
|
||||
.secrets([
|
||||
(app_id.name.to_owned(), app_id.secret_configuration()),
|
||||
(
|
||||
app_secret.name.to_owned(),
|
||||
app_secret.secret_configuration(),
|
||||
),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.concurrency(one_workflow_per_non_main_branch())
|
||||
.add_env(("CARGO_TERM_COLOR", "always"))
|
||||
.add_env(("RUST_BACKTRACE", 1))
|
||||
.add_env(("CARGO_INCREMENTAL", 0))
|
||||
.add_env((
|
||||
"ZED_EXTENSION_CLI_SHA",
|
||||
extension_tests::ZED_EXTENSION_CLI_SHA,
|
||||
))
|
||||
.add_job(test_extension.name, test_extension.job)
|
||||
.add_job(check_bump_needed.name, check_bump_needed.job)
|
||||
.add_job(bump_version.name, bump_version.job)
|
||||
}
|
||||
|
||||
fn check_bump_needed() -> (NamedJob, StepOutput) {
|
||||
let (compare_versions, version_changed) = compare_versions();
|
||||
|
||||
let job = Job::default()
|
||||
.with_repository_owner_guard()
|
||||
.outputs([(version_changed.name.to_owned(), version_changed.to_string())])
|
||||
.runs_on(runners::LINUX_SMALL)
|
||||
.timeout_minutes(1u32)
|
||||
.add_step(steps::checkout_repo().add_with(("fetch-depth", 10)))
|
||||
.add_step(compare_versions);
|
||||
|
||||
(named::job(job), version_changed)
|
||||
}
|
||||
|
||||
/// Compares the current and previous commit and checks whether versions changed inbetween.
|
||||
fn compare_versions() -> (Step<Run>, StepOutput) {
|
||||
let check_needs_bump = named::bash(format!(
|
||||
indoc! {
|
||||
r#"
|
||||
CURRENT_VERSION="$({})"
|
||||
|
||||
git checkout "$(git log -1 --format=%H)"~1
|
||||
|
||||
PREV_COMMIT_VERSION="$({})"
|
||||
|
||||
[[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
|
||||
echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
|
||||
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
"#
|
||||
},
|
||||
VERSION_CHECK, VERSION_CHECK
|
||||
))
|
||||
.id("compare-versions-check");
|
||||
|
||||
let needs_bump = StepOutput::new(&check_needs_bump, "needs_bump");
|
||||
|
||||
(check_needs_bump, needs_bump)
|
||||
}
|
||||
|
||||
fn bump_extension_version(
|
||||
dependencies: &[&NamedJob],
|
||||
bump_type: &WorkflowInput,
|
||||
needs_bump: JobOutput,
|
||||
app_id: &WorkflowSecret,
|
||||
app_secret: &WorkflowSecret,
|
||||
) -> NamedJob {
|
||||
let (generate_token, generated_token) = generate_token(app_id, app_secret);
|
||||
let (bump_version, old_version, new_version) = bump_version(bump_type);
|
||||
|
||||
let job = steps::dependant_job(dependencies)
|
||||
.cond(Expression::new(format!(
|
||||
"{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'true'",
|
||||
needs_bump.expr(),
|
||||
)))
|
||||
.runs_on(runners::LINUX_LARGE)
|
||||
.timeout_minutes(1u32)
|
||||
.add_step(generate_token)
|
||||
.add_step(steps::checkout_repo())
|
||||
.add_step(install_bump_2_version())
|
||||
.add_step(bump_version)
|
||||
.add_step(create_pull_request(
|
||||
old_version,
|
||||
new_version,
|
||||
generated_token,
|
||||
));
|
||||
|
||||
named::job(job)
|
||||
}
|
||||
|
||||
fn generate_token(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> (Step<Use>, StepOutput) {
|
||||
let step = named::uses("actions", "create-github-app-token", "v2")
|
||||
.id("generate-token")
|
||||
.add_with(
|
||||
Input::default()
|
||||
.add("app-id", app_id.to_string())
|
||||
.add("private-key", app_secret.to_string()),
|
||||
);
|
||||
|
||||
let generated_token = StepOutput::new(&step, "token");
|
||||
|
||||
(step, generated_token)
|
||||
}
|
||||
|
||||
fn install_bump_2_version() -> Step<Run> {
|
||||
named::run(runners::Platform::Linux, "pip install bump2version")
|
||||
}
|
||||
|
||||
fn bump_version(bump_type: &WorkflowInput) -> (Step<Run>, StepOutput, StepOutput) {
|
||||
let step = named::bash(format!(
|
||||
indoc! {r#"
|
||||
OLD_VERSION="$({})"
|
||||
|
||||
cat <<EOF > .bumpversion.cfg
|
||||
{}
|
||||
EOF
|
||||
|
||||
bump2version --verbose {}
|
||||
NEW_VERSION="$({})"
|
||||
cargo update --workspace
|
||||
|
||||
rm .bumpversion.cfg
|
||||
|
||||
echo "old_version=${{OLD_VERSION}}" >> "$GITHUB_OUTPUT"
|
||||
echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
|
||||
"#
|
||||
},
|
||||
VERSION_CHECK, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK
|
||||
))
|
||||
.id("bump-version");
|
||||
|
||||
let old_version = StepOutput::new(&step, "old_version");
|
||||
let new_version = StepOutput::new(&step, "new_version");
|
||||
(step, old_version, new_version)
|
||||
}
|
||||
|
||||
fn create_pull_request(
|
||||
old_version: StepOutput,
|
||||
new_version: StepOutput,
|
||||
generated_token: StepOutput,
|
||||
) -> Step<Use> {
|
||||
let formatted_version = format!("v{}", new_version);
|
||||
|
||||
named::uses("peter-evans", "create-pull-request", "v7").with(
|
||||
Input::default()
|
||||
.add("title", format!("Bump version to {}", new_version))
|
||||
.add(
|
||||
"body",
|
||||
format!(
|
||||
"This PR bumps the version of this extension to {}",
|
||||
formatted_version
|
||||
),
|
||||
)
|
||||
.add(
|
||||
"commit-message",
|
||||
format!("Bump version to {}", formatted_version),
|
||||
)
|
||||
.add("branch", format!("bump-from-{}", old_version))
|
||||
.add(
|
||||
"committer",
|
||||
"zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>",
|
||||
)
|
||||
.add("base", "main")
|
||||
.add("delete-branch", true)
|
||||
.add("token", generated_token.to_string())
|
||||
.add("sign-commits", true),
|
||||
)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use crate::tasks::workflows::{
|
||||
};
|
||||
|
||||
const RUN_TESTS_INPUT: &str = "run_tests";
|
||||
const ZED_EXTENSION_CLI_SHA: &str = "7cfce605704d41ca247e3f84804bf323f6c6caaf";
|
||||
pub(crate) const ZED_EXTENSION_CLI_SHA: &str = "7cfce605704d41ca247e3f84804bf323f6c6caaf";
|
||||
|
||||
// This is used by various extensions repos in the zed-extensions org to run automated tests.
|
||||
pub(crate) fn extension_tests() -> Workflow {
|
||||
@@ -77,7 +77,7 @@ fn check_rust() -> NamedJob {
|
||||
named::job(job)
|
||||
}
|
||||
|
||||
fn check_extension() -> NamedJob {
|
||||
pub(crate) fn check_extension() -> NamedJob {
|
||||
let (cache_download, cache_hit) = cache_zed_extension_cli();
|
||||
let job = Job::default()
|
||||
.with_repository_owner_guard()
|
||||
|
||||
@@ -3,12 +3,12 @@ use gh_workflow::{Event, Expression, Job, Run, Schedule, Step, Use, Workflow, Wo
|
||||
use crate::tasks::workflows::{
|
||||
runners::{self, Platform},
|
||||
steps::{self, FluentBuilder as _, NamedJob, named, setup_cargo_config},
|
||||
vars::{self, Input},
|
||||
vars::{self, WorkflowInput},
|
||||
};
|
||||
|
||||
pub(crate) fn run_agent_evals() -> Workflow {
|
||||
let agent_evals = agent_evals();
|
||||
let model_name = Input::string("model_name", None);
|
||||
let model_name = WorkflowInput::string("model_name", None);
|
||||
|
||||
named::workflow()
|
||||
.on(Event::default().workflow_dispatch(
|
||||
@@ -29,8 +29,8 @@ pub(crate) fn run_agent_evals() -> Workflow {
|
||||
}
|
||||
|
||||
pub(crate) fn run_unit_evals() -> Workflow {
|
||||
let model_name = Input::string("model_name", None);
|
||||
let commit_sha = Input::string("commit_sha", None);
|
||||
let model_name = WorkflowInput::string("model_name", None);
|
||||
let commit_sha = WorkflowInput::string("commit_sha", None);
|
||||
|
||||
let unit_evals = named::job(unit_evals(Some(&commit_sha)));
|
||||
|
||||
@@ -117,7 +117,7 @@ fn cron_unit_evals() -> NamedJob {
|
||||
named::job(unit_evals(None).add_step(send_failure_to_slack()))
|
||||
}
|
||||
|
||||
fn unit_evals(commit: Option<&Input>) -> Job {
|
||||
fn unit_evals(commit: Option<&WorkflowInput>) -> Job {
|
||||
let script_step = add_api_keys(steps::script("./script/run-unit-evals"));
|
||||
|
||||
Job::default()
|
||||
|
||||
@@ -142,9 +142,13 @@ pub struct NamedJob {
|
||||
// }
|
||||
// }
|
||||
|
||||
pub(crate) const DEFAULT_REPOSITORY_OWNER_GUARD: &str =
|
||||
"(github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')";
|
||||
|
||||
pub fn repository_owner_guard_expression(trigger_always: bool) -> Expression {
|
||||
Expression::new(format!(
|
||||
"(github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions'){}",
|
||||
"{}{}",
|
||||
DEFAULT_REPOSITORY_OWNER_GUARD,
|
||||
trigger_always.then_some(" && always()").unwrap_or_default()
|
||||
))
|
||||
}
|
||||
@@ -248,8 +252,10 @@ pub mod named {
|
||||
/// Returns a bash-script step with the same name as the enclosing function.
|
||||
/// (You shouldn't inline this function into the workflow definition, you must
|
||||
/// wrap it in a new function.)
|
||||
pub fn bash(script: &str) -> Step<Run> {
|
||||
Step::new(function_name(1)).run(script).shell(BASH_SHELL)
|
||||
pub fn bash(script: impl AsRef<str>) -> Step<Run> {
|
||||
Step::new(function_name(1))
|
||||
.run(script.as_ref())
|
||||
.shell(BASH_SHELL)
|
||||
}
|
||||
|
||||
/// Returns a pwsh-script step with the same name as the enclosing function.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use gh_workflow::{Concurrency, Env, Expression, Step, WorkflowDispatchInput};
|
||||
use gh_workflow::{
|
||||
Concurrency, Env, Expression, Step, WorkflowCallInput, WorkflowCallSecret,
|
||||
WorkflowDispatchInput,
|
||||
};
|
||||
|
||||
use crate::tasks::workflows::{runners::Platform, steps::NamedJob};
|
||||
|
||||
@@ -132,7 +135,7 @@ impl PathCondition {
|
||||
}
|
||||
|
||||
pub(crate) struct StepOutput {
|
||||
name: &'static str,
|
||||
pub name: &'static str,
|
||||
step_id: String,
|
||||
}
|
||||
|
||||
@@ -151,6 +154,13 @@ impl StepOutput {
|
||||
pub fn expr(&self) -> String {
|
||||
format!("steps.{}.outputs.{}", self.step_id, self.name)
|
||||
}
|
||||
|
||||
pub fn as_job_output(self, job: &NamedJob) -> JobOutput {
|
||||
JobOutput {
|
||||
job_name: job.name.clone(),
|
||||
name: self.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for StepOutput {
|
||||
@@ -164,17 +174,43 @@ impl serde::Serialize for StepOutput {
|
||||
|
||||
impl std::fmt::Display for StepOutput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "${{{{ steps.{}.outputs.{} }}}}", self.step_id, self.name)
|
||||
write!(f, "${{{{ {} }}}}", self.expr())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
pub(crate) struct JobOutput {
|
||||
job_name: String,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl JobOutput {
|
||||
pub fn expr(&self) -> String {
|
||||
format!("needs.{}.outputs.{}", self.job_name, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for JobOutput {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for JobOutput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "${{{{ {} }}}}", self.expr())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkflowInput {
|
||||
pub input_type: &'static str,
|
||||
pub name: &'static str,
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
impl WorkflowInput {
|
||||
pub fn string(name: &'static str, default: Option<String>) -> Self {
|
||||
Self {
|
||||
input_type: "string",
|
||||
@@ -191,15 +227,62 @@ impl Input {
|
||||
default: self.default.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_input(&self) -> WorkflowCallInput {
|
||||
WorkflowCallInput {
|
||||
description: self.name.to_owned(),
|
||||
required: self.default.is_none(),
|
||||
input_type: self.input_type.to_owned(),
|
||||
default: self.default.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Input {
|
||||
impl std::fmt::Display for WorkflowInput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "${{{{ inputs.{} }}}}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Input {
|
||||
impl serde::Serialize for WorkflowInput {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WorkflowSecret {
|
||||
pub name: &'static str,
|
||||
description: String,
|
||||
required: bool,
|
||||
}
|
||||
|
||||
impl WorkflowSecret {
|
||||
pub fn new(name: &'static str, description: impl ToString) -> Self {
|
||||
Self {
|
||||
name,
|
||||
description: description.to_string(),
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secret_configuration(&self) -> WorkflowCallSecret {
|
||||
WorkflowCallSecret {
|
||||
description: self.description.clone(),
|
||||
required: self.required,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WorkflowSecret {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "${{{{ secrets.{} }}}}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for WorkflowSecret {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
|
||||
Reference in New Issue
Block a user