Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:41:49.391Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

extension_bump.rs

478 lines · 16.8 KB · rust
1use gh_workflow::*;
2use indoc::{formatdoc, indoc};
3
4use crate::tasks::workflows::{
5    extension_tests::{self},
6    runners,
7    steps::{
8        self, BASH_SHELL, CommonJobConditions, CommonPermissionSets,
9        DEFAULT_REPOSITORY_OWNER_GUARD, GitHubScriptStep, GitRef, NamedJob, RefSha,
10        RepositoryTarget, TokenPermissions, cache_rust_dependencies_namespace, checkout_repo,
11        create_ref, dependant_job, generate_token, named,
12    },
13    vars::{
14        JobOutput, StepOutput, WorkflowInput, WorkflowSecret,
15        one_workflow_per_non_main_branch_and_token,
16    },
17};
18
19const VERSION_CHECK: &str =
20    r#"sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]'"#;
21
22// This is used by various extensions repos in the zed-extensions org to bump extension versions.
23pub(crate) fn extension_bump() -> Workflow {
24    let bump_type = WorkflowInput::string("bump-type", Some("patch".to_owned()));
25    // TODO: Ideally, this would have a default of `false`, but this is currently not
26    // supported in gh-workflows
27    let force_bump = WorkflowInput::bool("force-bump", None);
28    let working_directory = WorkflowInput::string("working-directory", Some(".".to_owned()));
29
30    let (app_id, app_secret) = extension_workflow_secrets();
31    let (check_version_changed, version_changed, current_version) = check_version_changed();
32
33    let version_changed = version_changed.as_job_output(&check_version_changed);
34    let current_version = current_version.as_job_output(&check_version_changed);
35
36    let dependencies = [&check_version_changed];
37    let bump_version = bump_extension_version(
38        &dependencies,
39        &current_version,
40        &bump_type,
41        &version_changed,
42        &force_bump,
43        &app_id,
44        &app_secret,
45    );
46    let (create_label, tag) = create_version_label(
47        &dependencies,
48        &version_changed,
49        &current_version,
50        &app_id,
51        &app_secret,
52    );
53    let tag = tag.as_job_output(&create_label);
54    let trigger_release = trigger_release(
55        &[&check_version_changed, &create_label],
56        tag,
57        &app_id,
58        &app_secret,
59    );
60
61    named::workflow()
62        .with_minimal_permissions()
63        .add_event(
64            Event::default().workflow_call(
65                WorkflowCall::default()
66                    .add_input(bump_type.name, bump_type.call_input())
67                    .add_input(force_bump.name, force_bump.call_input())
68                    .add_input(working_directory.name, working_directory.call_input())
69                    .secrets([
70                        (app_id.name.to_owned(), app_id.secret_configuration()),
71                        (
72                            app_secret.name.to_owned(),
73                            app_secret.secret_configuration(),
74                        ),
75                    ]),
76            ),
77        )
78        .concurrency(one_workflow_per_non_main_branch_and_token("extension-bump"))
79        .add_env(("CARGO_TERM_COLOR", "always"))
80        .add_env(("RUST_BACKTRACE", 1))
81        .add_env(("CARGO_INCREMENTAL", 0))
82        .add_env((
83            "ZED_EXTENSION_CLI_SHA",
84            extension_tests::ZED_EXTENSION_CLI_SHA,
85        ))
86        .add_job(check_version_changed.name, check_version_changed.job)
87        .add_job(bump_version.name, bump_version.job)
88        .add_job(create_label.name, create_label.job)
89        .add_job(trigger_release.name, trigger_release.job)
90}
91
92fn extension_job_defaults() -> Defaults {
93    Defaults::default().run(
94        RunDefaults::default()
95            .shell(BASH_SHELL)
96            .working_directory("${{ inputs.working-directory }}"),
97    )
98}
99
100fn check_version_changed() -> (NamedJob, StepOutput, StepOutput) {
101    let (compare_versions, version_changed, current_version) = compare_versions();
102
103    let job = Job::default()
104        .defaults(extension_job_defaults())
105        .with_repository_owner_guard()
106        .outputs([
107            (version_changed.name.to_owned(), version_changed.to_string()),
108            (
109                current_version.name.to_string(),
110                current_version.to_string(),
111            ),
112        ])
113        .runs_on(runners::LINUX_SMALL)
114        .timeout_minutes(1u32)
115        .add_step(steps::checkout_repo().with_full_history())
116        .add_step(compare_versions);
117
118    (named::job(job), version_changed, current_version)
119}
120
121fn create_version_label(
122    dependencies: &[&NamedJob],
123    version_changed_output: &JobOutput,
124    current_version: &JobOutput,
125    app_id: &WorkflowSecret,
126    app_secret: &WorkflowSecret,
127) -> (NamedJob, StepOutput) {
128    let (generate_token, generated_token) =
129        generate_token(&app_id.to_string(), &app_secret.to_string())
130            .for_repository(RepositoryTarget::current())
131            .with_permissions([(TokenPermissions::Contents, Level::Write)])
132            .into();
133    let (determine_tag_step, tag) = determine_tag(current_version);
134    let job = steps::dependant_job(dependencies)
135        .defaults(extension_job_defaults())
136        .cond(Expression::new(format!(
137            "{DEFAULT_REPOSITORY_OWNER_GUARD} && github.event_name == 'push' && \
138            github.ref == 'refs/heads/main' && {version_changed} == 'true'",
139            version_changed = version_changed_output.expr(),
140        )))
141        .outputs([(tag.name.to_owned(), tag.to_string())])
142        .runs_on(runners::LINUX_SMALL)
143        .timeout_minutes(1u32)
144        .add_step(generate_token)
145        .add_step(steps::checkout_repo())
146        .add_step(determine_tag_step)
147        .add_step(create_version_tag(&tag, generated_token));
148
149    (named::job(job), tag)
150}
151
152fn create_version_tag(tag: &StepOutput, generated_token: StepOutput) -> Step<Use> {
153    create_ref(
154        GitRef::Tag(tag.to_string()),
155        RefSha::Context,
156        &generated_token,
157    )
158    .into()
159}
160
161fn determine_tag(current_version: &JobOutput) -> (Step<Run>, StepOutput) {
162    let step = named::bash(formatdoc! {r#"
163        EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"
164
165        if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then
166            TAG="v${{CURRENT_VERSION}}"
167        else
168            TAG="${{EXTENSION_ID}}-v${{CURRENT_VERSION}}"
169        fi
170
171        echo "tag=${{TAG}}" >> "$GITHUB_OUTPUT"
172    "#})
173    .id("determine-tag")
174    .add_env(("CURRENT_VERSION", current_version.to_string()))
175    .add_env(("WORKING_DIR", "${{ inputs.working-directory }}"));
176
177    let tag = StepOutput::new(&step, "tag");
178    (step, tag)
179}
180
181/// Compares the current and previous commit and checks whether versions changed inbetween.
182pub(crate) fn compare_versions() -> (Step<Run>, StepOutput, StepOutput) {
183    let check_needs_bump = named::bash(formatdoc! {
184    r#"
185        CURRENT_VERSION="$({VERSION_CHECK})"
186
187        if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
188            PR_FORK_POINT="$(git merge-base origin/main HEAD)"
189            git checkout "$PR_FORK_POINT"
190        else
191            git checkout "$(git log -1 --format=%H)"~1
192        fi
193
194        PARENT_COMMIT_VERSION="$({VERSION_CHECK})"
195
196        [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
197            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
198            echo "version_changed=true" >> "$GITHUB_OUTPUT"
199
200        echo "current_version=${{CURRENT_VERSION}}" >> "$GITHUB_OUTPUT"
201        "#
202    })
203    .id("compare-versions-check");
204
205    let version_changed = StepOutput::new(&check_needs_bump, "version_changed");
206    let current_version = StepOutput::new(&check_needs_bump, "current_version");
207
208    (check_needs_bump, version_changed, current_version)
209}
210
211fn bump_extension_version(
212    dependencies: &[&NamedJob],
213    current_version: &JobOutput,
214    bump_type: &WorkflowInput,
215    version_changed_output: &JobOutput,
216    force_bump_output: &WorkflowInput,
217    app_id: &WorkflowSecret,
218    app_secret: &WorkflowSecret,
219) -> NamedJob {
220    let (generate_token, generated_token) =
221        generate_token(&app_id.to_string(), &app_secret.to_string())
222            .for_repository(RepositoryTarget::current())
223            .with_permissions([
224                (TokenPermissions::Contents, Level::Write),
225                (TokenPermissions::PullRequests, Level::Write),
226            ])
227            .into();
228    let (bump_version, _new_version, title, body, branch_name) =
229        bump_version(current_version, bump_type);
230
231    let job = steps::dependant_job(dependencies)
232        .defaults(extension_job_defaults())
233        .cond(Expression::new(format!(
234            "{DEFAULT_REPOSITORY_OWNER_GUARD} &&\n({force_bump} == true || {version_changed} == 'false')",
235            force_bump = force_bump_output.expr(),
236            version_changed = version_changed_output.expr(),
237        )))
238        .runs_on(runners::LINUX_SMALL)
239        .timeout_minutes(5u32)
240        .add_step(generate_token)
241        .add_step(steps::checkout_repo())
242        .add_step(cache_rust_dependencies_namespace())
243        .add_step(install_bump_2_version())
244        .add_step(bump_version)
245        .add_step(create_pull_request(
246            title,
247            body,
248            generated_token,
249            branch_name,
250        ));
251
252    named::job(job)
253}
254
255fn install_bump_2_version() -> Step<Run> {
256    named::run(
257        runners::Platform::Linux,
258        "pip install bump2version --break-system-packages",
259    )
260}
261
262fn bump_version(
263    current_version: &JobOutput,
264    bump_type: &WorkflowInput,
265) -> (Step<Run>, StepOutput, StepOutput, StepOutput, StepOutput) {
266    let step = named::bash(formatdoc! {r#"
267        BUMP_FILES=("extension.toml")
268        if [[ -f "Cargo.toml" ]]; then
269            BUMP_FILES+=("Cargo.toml")
270        fi
271
272        bump2version \
273            --search "version = \"{{current_version}}"\" \
274            --replace "version = \"{{new_version}}"\" \
275            --current-version "$OLD_VERSION" \
276            --no-configured-files "$BUMP_TYPE" "${{BUMP_FILES[@]}}"
277
278        if [[ -f "Cargo.toml" ]]; then
279            cargo +stable update --workspace
280        fi
281
282        NEW_VERSION="$({VERSION_CHECK})"
283        EXTENSION_ID="$(sed -n 's/^id = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"
284        EXTENSION_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < extension.toml | head -1 | tr -d '[:space:]')"
285
286        if [[ "$WORKING_DIR" == "." || -z "$WORKING_DIR" ]]; then
287            {{
288                echo "title=Bump version to ${{NEW_VERSION}}";
289                echo "body=This PR bumps the version of this extension to v${{NEW_VERSION}}";
290                echo "branch_name=zed-zippy-autobump";
291            }} >> "$GITHUB_OUTPUT"
292        else
293            {{
294                echo "title=${{EXTENSION_ID}}: Bump to v${{NEW_VERSION}}";
295                echo "body<<EOF";
296                echo "This PR bumps the version of the ${{EXTENSION_NAME}} extension to v${{NEW_VERSION}}.";
297                echo "";
298                echo "Release Notes:";
299                echo "";
300                echo "- N/A";
301                echo "EOF";
302                echo "branch_name=zed-zippy-${{EXTENSION_ID}}-autobump";
303            }} >> "$GITHUB_OUTPUT"
304        fi
305
306        echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
307        "#
308    })
309    .id("bump-version")
310    .add_env(("OLD_VERSION", current_version.to_string()))
311    .add_env(("BUMP_TYPE", bump_type.to_string()))
312    .add_env(("WORKING_DIR", "${{ inputs.working-directory }}"));
313
314    let new_version = StepOutput::new(&step, "new_version");
315    let title = StepOutput::new(&step, "title");
316    let body = StepOutput::new(&step, "body");
317    let branch_name = StepOutput::new(&step, "branch_name");
318    (step, new_version, title, body, branch_name)
319}
320
321fn create_pull_request(
322    title: StepOutput,
323    body: StepOutput,
324    generated_token: StepOutput,
325    branch_name: StepOutput,
326) -> Step<Use> {
327    steps::CreatePrStep::new(title.to_string(), branch_name, &generated_token)
328        .with_body(body)
329        .into()
330}
331
332fn trigger_release(
333    dependencies: &[&NamedJob],
334    tag: JobOutput,
335    app_id: &WorkflowSecret,
336    app_secret: &WorkflowSecret,
337) -> NamedJob {
338    let extension_registry = RepositoryTarget::new("zed-industries", &["extensions"]);
339    let (generate_token, generated_token) =
340        generate_token(&app_id.to_string(), &app_secret.to_string())
341            .for_repository(extension_registry)
342            .with_permissions([
343                (TokenPermissions::Contents, Level::Write),
344                (TokenPermissions::Issues, Level::Write),
345                (TokenPermissions::Members, Level::Read),
346                (TokenPermissions::PullRequests, Level::Write),
347            ])
348            .into();
349    let (get_extension_id, extension_id) = get_extension_id();
350    let (release_action, pull_request_number) = release_action(extension_id, tag, &generated_token);
351
352    let job = dependant_job(dependencies)
353        .defaults(extension_job_defaults())
354        .with_repository_owner_guard()
355        .runs_on(runners::LINUX_SMALL)
356        .add_step(generate_token)
357        .add_step(checkout_repo())
358        .add_step(get_extension_id)
359        .add_step(release_action)
360        .add_step(enable_automerge_if_staff(
361            pull_request_number,
362            generated_token,
363        ));
364
365    named::job(job)
366}
367
368fn get_extension_id() -> (Step<Run>, StepOutput) {
369    let step = named::bash(indoc! {
370    r#"
371        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
372
373        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
374    "#})
375    .id("get-extension-id");
376
377    let extension_id = StepOutput::new(&step, "extension_id");
378
379    (step, extension_id)
380}
381
382fn release_action(
383    extension_id: StepOutput,
384    tag: JobOutput,
385    generated_token: &StepOutput,
386) -> (Step<Use>, StepOutput) {
387    let step = named::uses(
388        "huacnlee",
389        "zed-extension-action",
390        "82920ff0876879f65ffbcfa3403589114a8919c6",
391    )
392    .id("extension-update")
393    .add_with(("extension-name", extension_id.to_string()))
394    .add_with(("push-to", "zed-industries/extensions"))
395    .add_with(("tag", tag.to_string()))
396    .add_env(("COMMITTER_TOKEN", generated_token.to_string()));
397
398    let pull_request_number = StepOutput::new(&step, "pull-request-number");
399
400    (step, pull_request_number)
401}
402
403fn enable_automerge_if_staff(
404    pull_request_number: StepOutput,
405    generated_token: StepOutput,
406) -> GitHubScriptStep {
407    steps::github_script(indoc! {r#"
408        const prNumber = process.env.PR_NUMBER;
409        if (!prNumber) {
410            console.log('No pull request number set, skipping automerge.');
411            return;
412        }
413
414        const author = process.env.GITHUB_ACTOR;
415        let isStaff = false;
416        try {
417            const response = await github.rest.teams.getMembershipForUserInOrg({
418                org: 'zed-industries',
419                team_slug: 'staff',
420                username: author
421            });
422            isStaff = response.data.state === 'active';
423        } catch (error) {
424            if (error.status !== 404) {
425                throw error;
426            }
427        }
428
429        if (!isStaff) {
430            console.log(`Actor ${author} is not a staff member, skipping automerge.`);
431            return;
432        }
433
434        // Assign staff member responsible for the bump
435        const pullNumber = parseInt(prNumber);
436
437        await github.rest.issues.addAssignees({
438            owner: 'zed-industries',
439            repo: 'extensions',
440            issue_number: pullNumber,
441            assignees: [author]
442        });
443        console.log(`Assigned ${author} to PR #${prNumber} in zed-industries/extensions`);
444
445        // Get the GraphQL node ID
446        const { data: pr } = await github.rest.pulls.get({
447            owner: 'zed-industries',
448            repo: 'extensions',
449            pull_number: pullNumber
450        });
451
452        await github.graphql(`
453            mutation($pullRequestId: ID!) {
454                enablePullRequestAutoMerge(input: { pullRequestId: $pullRequestId, mergeMethod: SQUASH }) {
455                    pullRequest {
456                        autoMergeRequest {
457                            enabledAt
458                        }
459                    }
460                }
461            }
462        `, { pullRequestId: pr.node_id });
463
464        console.log(`Automerge enabled for PR #${prNumber} in zed-industries/extensions`);
465    "#})
466    .custom_name("enable_automerge_if_staff")
467    .token(generated_token)
468    .env("PR_NUMBER", pull_request_number.to_string())
469}
470
471fn extension_workflow_secrets() -> (WorkflowSecret, WorkflowSecret) {
472    let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
473    let app_secret =
474        WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
475
476    (app_id, app_secret)
477}
478
Served at tenant.openagents/omega Member data and write actions are omitted.