├── test └── fixtures │ ├── subdir │ └── mod.ts │ ├── deno.json │ ├── mod.ts │ ├── mod_test.ts │ └── deno.lock ├── .github ├── workflows │ ├── ci.yml │ ├── update.yml │ ├── example.yml │ ├── release.yml │ ├── test.yml │ └── integration.yml └── FUNDING.yml ├── src ├── strings.ts ├── strings_test.ts ├── summary.ts ├── paths.ts ├── inputs.ts ├── summary_test.ts ├── params_test.ts ├── params.ts ├── main.ts ├── report.ts ├── paths_test.ts └── report_test.ts ├── tools └── bump.ts ├── LICENSE ├── deno.json ├── action.yml ├── README.md └── deno.lock /test/fixtures/subdir/mod.ts: -------------------------------------------------------------------------------- 1 | export function fn(): string { 2 | return "Hello, subdir!"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "@luca/flag": "jsr:@luca/flag@^1.0.0", 4 | "std/": "https://deno.land/std@0.222.0/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/mod.ts: -------------------------------------------------------------------------------- 1 | import { pick } from "jsr:@std/collections@0.224.0"; 2 | 3 | export function fn() { 4 | return pick({ a: 1, b: 2 }, ["a"]); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/mod_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert@0.224.0"; 2 | import { fn } from "./mod.ts"; 3 | 4 | Deno.test("fn", () => { 5 | assertEquals(fn(), { a: 1 }); 6 | }); 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | uses: ./.github/workflows/test.yml 18 | secrets: inherit 19 | 20 | integration: 21 | uses: ./.github/workflows/integration.yml 22 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: update 2 | 3 | on: 4 | schedule: 5 | - cron: '0 23 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | update: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: hasundue/molt-action@v1 18 | with: 19 | token: ${{ secrets.PAT_UPDATE }} 20 | -------------------------------------------------------------------------------- /src/strings.ts: -------------------------------------------------------------------------------- 1 | import { match, placeholder as _ } from "@core/match"; 2 | 3 | export function parseGitUser(user: string) { 4 | const pattern = _`${_("name")} <${_("email")}>`; 5 | const matched = match(pattern, user); 6 | 7 | const name = matched?.name.trim(); 8 | const email = matched?.email.trim(); 9 | 10 | if (!matched || !name || !email) { 11 | throw new Error( 12 | `${user} is not a valid format. Expected "Display Name ".`, 13 | ); 14 | } 15 | return { name, email }; 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/example.yml: -------------------------------------------------------------------------------- 1 | name: example 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | create: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run molt-action 23 | uses: ./ 24 | with: 25 | branch: molt-action/example 26 | draft: true 27 | labels: example 28 | root: test/fixtures 29 | -------------------------------------------------------------------------------- /test/fixtures/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@luca/flag@^1.0.0": "jsr:@luca/flag@1.0.0" 6 | }, 7 | "jsr": { 8 | "@luca/flag@1.0.0": { 9 | "integrity": "1c76cf54839a86d0929a619c61bd65bb73d7d8a4e31788e48c720dbc46c5d546" 10 | } 11 | } 12 | }, 13 | "remote": { 14 | "https://deno.land/std@0.222.0/bytes/copy.ts": "f29c03168853720dfe82eaa57793d0b9e3543ebfe5306684182f0f1e3bfd422a" 15 | }, 16 | "workspace": { 17 | "dependencies": [ 18 | "jsr:@luca/flag@^1.0.0" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/strings_test.ts: -------------------------------------------------------------------------------- 1 | import { assertObjectMatch, assertThrows } from "@std/assert"; 2 | import { parseGitUser } from "./strings.ts"; 3 | 4 | Deno.test("parseGitUser - valid format", () => { 5 | assertObjectMatch( 6 | parseGitUser( 7 | "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>", 8 | ), 9 | { 10 | name: "github-actions[bot]", 11 | email: "41898282+github-actions[bot]@users.noreply.github.com", 12 | }, 13 | ); 14 | }); 15 | 16 | Deno.test("parseGitUser - invalid formats", () => { 17 | [ 18 | "invalid", 19 | "invalid <>", 20 | "", 21 | ].forEach((committer) => assertThrows(() => parseGitUser(committer))); 22 | }); 23 | -------------------------------------------------------------------------------- /src/summary.ts: -------------------------------------------------------------------------------- 1 | import type * as Molt from "@molt/core/types"; 2 | 3 | interface Update { 4 | dep: Pick; 5 | summary: Molt.Update["summary"]; 6 | } 7 | 8 | export function createSummary( 9 | updates: Update[], 10 | options: { prefix: string }, 11 | ): string { 12 | if (updates.length === 0) { 13 | return "All dependencies are up-to-date"; 14 | } 15 | if (updates.length === 1) { 16 | return updates[0].summary(options.prefix); 17 | } 18 | const deps = new Intl.ListFormat("en", { 19 | style: "long", 20 | type: "conjunction", 21 | }).format(updates.map((it) => it.dep.name)); 22 | const full = options.prefix + `update ${deps}`; 23 | return (full.length <= 50) ? full : options.prefix + "update dependencies"; 24 | } 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: hasundue 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /tools/bump.ts: -------------------------------------------------------------------------------- 1 | import { format, increment, parse, type ReleaseType } from "@std/semver"; 2 | 3 | const type = Deno.args.at(0) as ReleaseType; 4 | if (!type) throw new Error("Missing release type argument"); 5 | 6 | const { default: config } = await import("../deno.json", { 7 | with: { type: "json" }, 8 | }); 9 | 10 | const bumped = format(increment(parse(config.version), type)); 11 | 12 | config.version = bumped; 13 | await Deno.writeTextFile("deno.json", JSON.stringify(config, null, 2) + "\n"); 14 | 15 | const yaml = await Deno.readTextFile("./action.yml"); 16 | 17 | const updated = yaml 18 | .replace(/default: \d+\.\d+\.\d+/, `default: ${bumped}`) 19 | .replace( 20 | /default: jsr:@molt\/action@\d+\.\d+\.\d+/, 21 | `default: jsr:@molt\/action@${bumped}`, 22 | ); 23 | 24 | await Deno.writeTextFile("./action.yml", updated); 25 | 26 | await new Deno.Command("git", { 27 | args: [ 28 | "commit", 29 | "-m", 30 | `chore: release ${bumped}`, 31 | "action.yml", 32 | "deno.json", 33 | ], 34 | stdout: "inherit", 35 | stderr: "inherit", 36 | }).output(); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 hasundue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/paths.ts: -------------------------------------------------------------------------------- 1 | import { expandGlob } from "@std/fs"; 2 | import { SEPARATOR } from "@std/path"; 3 | 4 | import type { ActionParams } from "./params.ts"; 5 | 6 | export async function collectFromParams( 7 | params: ActionParams, 8 | ): Promise { 9 | const files = new Set(); 10 | const excludeFiles = new Set(); 11 | const excludeDirs = new Set(); 12 | 13 | for (const excludePattern of params.exclude) { 14 | for await ( 15 | const entry of expandGlob(excludePattern, { root: params.root }) 16 | ) { 17 | if (entry.isFile) { 18 | excludeFiles.add(entry.path); 19 | } else if (entry.isDirectory) { 20 | excludeDirs.add(entry.path + SEPARATOR); 21 | } 22 | } 23 | } 24 | 25 | for (const source of params.source) { 26 | for await (const entry of expandGlob(source, { root: params.root })) { 27 | if (entry.isFile) files.add(entry.path); 28 | } 29 | } 30 | 31 | const excludeDirsArray = [...excludeDirs]; 32 | const filtered = [...files.difference(excludeFiles)] 33 | .filter((path) => !excludeDirsArray.some((dir) => path.startsWith(dir))); 34 | 35 | return filtered; 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | defaults: 7 | run: 8 | shell: bash 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: read 16 | id-token: write 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Publish package 22 | run: npx jsr publish 23 | 24 | tag: 25 | runs-on: ubuntu-latest 26 | 27 | permissions: 28 | contents: write 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Configure Git 34 | run: | 35 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 36 | git config --global user.name "github-actions[bot]" 37 | 38 | - name: Tag release 39 | run: | 40 | tag=v$(jq -r '.version' deno.json) 41 | git tag $tag 42 | git push origin $tag 43 | 44 | - name: Move the major release tag 45 | run: | 46 | tag=v$(jq -r '.version' deno.json | cut -d '.' -f 1) 47 | git tag -d $tag 2>/dev/null || echo "Tag $tag not found" 48 | git push origin :refs/tags/$tag 49 | git tag $tag 50 | git push origin $tag 51 | 52 | - name: Move the minor release tag 53 | run: | 54 | tag=v$(jq -r '.version' deno.json | cut -d '.' -f 1,2) 55 | git tag -d $tag 2>/dev/null || echo "Tag $tag not found" 56 | git push origin :refs/tags/$tag 57 | git tag $tag 58 | git push origin $tag 59 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@molt/action", 3 | "version": "1.0.3", 4 | "exports": "./src/main.ts", 5 | "publish": { 6 | "include": [ 7 | "README.md", 8 | "LICENSE", 9 | "src/*.ts" 10 | ], 11 | "exclude": [ 12 | "src/*_test.ts" 13 | ] 14 | }, 15 | "imports": { 16 | "@actions/core": "npm:@actions/core@^1.10.1", 17 | "@actions/github": "npm:@actions/github@^6.0.0", 18 | "@core/match": "jsr:@core/match@^0.3.1", 19 | "@core/unknownutil": "jsr:@core/unknownutil@^4.0.0", 20 | "@molt/core": "jsr:@molt/core@^0.19.8", 21 | "@molt/integration": "jsr:@molt/integration@^0.19.0", 22 | "@molt/lib": "jsr:@molt/lib@^0.19.0", 23 | "@std/assert": "jsr:@std/assert@^1.0.0", 24 | "@std/collections": "jsr:@std/collections@^1.0.0", 25 | "@std/fs": "jsr:@std/fs@^1.0.0", 26 | "@std/jsonc": "jsr:@std/jsonc@^1.0.0", 27 | "@std/path": "jsr:@std/path@^1.0.0", 28 | "@std/semver": "jsr:@std/semver@^1.0.0", 29 | "dedent": "npm:dedent@^1.5.3" 30 | }, 31 | "tasks": { 32 | "act": "act -j integration", 33 | "bump": "deno run --allow-read=. --allow-write=./deno.json,./action.yml --allow-run=git ./tools/bump.ts", 34 | "cache": "deno cache ./src/*.ts", 35 | "check": "deno check **/*.ts", 36 | "pre-commit": "deno fmt && deno lint && deno task check && deno task test", 37 | "test": "deno test --allow-env --allow-read --allow-write=. --allow-net --allow-run=deno --env --no-check --unstable-kv", 38 | "update": "deno run --unstable-kv --allow-env --allow-read --allow-write --allow-net --allow-run=git,deno jsr:@molt/cli", 39 | "update:commit": "deno task -q update --commit --pre-commit=check,test --prefix 'chore:'" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/inputs.ts: -------------------------------------------------------------------------------- 1 | import actions from "@actions/core"; 2 | 3 | export interface ActionInputs { 4 | /** @default true */ 5 | commit: boolean; 6 | 7 | /** @default "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> */ 8 | committer: string; 9 | 10 | /** @default "" */ 11 | config: boolean | string; 12 | 13 | /** @default "" */ 14 | lock: boolean | string; 15 | 16 | /** @default "chore:" */ 17 | prefix: string; 18 | 19 | /** @default "" */ 20 | root: string; 21 | 22 | /** @default [`./**\/*.ts`] */ 23 | source: string[]; 24 | 25 | /** @default [] */ 26 | exclude: string[]; 27 | 28 | /** @default false */ 29 | write: boolean; 30 | } 31 | 32 | export const defaults: ActionInputs = { 33 | commit: true, 34 | committer: 35 | "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>", 36 | config: "", 37 | lock: "", 38 | prefix: "chore:", 39 | root: "", 40 | source: [], 41 | exclude: [], 42 | write: false, 43 | }; 44 | 45 | function getMaybeBooleanInput(name: string): boolean | string { 46 | try { 47 | return actions.getBooleanInput(name); 48 | } catch { 49 | return actions.getInput(name); 50 | } 51 | } 52 | 53 | export function getInputs(): ActionInputs { 54 | return { 55 | commit: actions.getBooleanInput("commit"), 56 | committer: actions.getInput("committer"), 57 | config: getMaybeBooleanInput("config"), 58 | lock: getMaybeBooleanInput("lock"), 59 | prefix: actions.getInput("commit-prefix"), 60 | root: actions.getInput("root"), 61 | source: actions.getMultilineInput("source"), 62 | exclude: actions.getMultilineInput("exclude"), 63 | write: actions.getBooleanInput("write"), 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/summary_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "@std/assert"; 2 | import { createSummary } from "./summary.ts"; 3 | 4 | Deno.test("summary - empty sequence", () => { 5 | assertEquals( 6 | createSummary([], { prefix: "" }), 7 | "All dependencies are up-to-date", 8 | ); 9 | }); 10 | 11 | Deno.test("summary - single commit", () => { 12 | assertEquals( 13 | createSummary( 14 | [{ 15 | dep: { name: "@molt/core" }, 16 | summary: () => "update @molt/core to 1.0.0", 17 | }], 18 | { prefix: "" }, 19 | ), 20 | "update @molt/core to 1.0.0", 21 | ); 22 | }); 23 | 24 | Deno.test("summary - two commits", () => { 25 | assertEquals( 26 | createSummary( 27 | [ 28 | { 29 | dep: { name: "@molt/core" }, 30 | summary: () => "update @molt/core", 31 | }, 32 | { 33 | dep: { name: "@molt/cli" }, 34 | summary: () => "update @molt/cli", 35 | }, 36 | ], 37 | { prefix: "" }, 38 | ), 39 | "update @molt/core and @molt/cli", 40 | ); 41 | }); 42 | 43 | Deno.test("summary - three commits", () => { 44 | assertEquals( 45 | createSummary( 46 | [ 47 | { 48 | dep: { name: "@molt/core" }, 49 | summary: () => "update @molt/core", 50 | }, 51 | { 52 | dep: { name: "@molt/cli" }, 53 | summary: () => "update @molt/cli", 54 | }, 55 | { 56 | dep: { name: "@molt/lib" }, 57 | summary: () => "update @molt/lib", 58 | }, 59 | ], 60 | { prefix: "" }, 61 | ), 62 | "update @molt/core, @molt/cli, and @molt/lib", 63 | ); 64 | }); 65 | 66 | Deno.test("summary - many commits", () => { 67 | assertEquals( 68 | createSummary( 69 | [ 70 | { 71 | dep: { name: "@molt/core" }, 72 | summary: () => "update @molt/core", 73 | }, 74 | { 75 | dep: { name: "@molt/cli" }, 76 | summary: () => "update @molt/cli", 77 | }, 78 | { 79 | dep: { name: "@molt/lib" }, 80 | summary: () => "update @molt/lib", 81 | }, 82 | { 83 | dep: { name: "@molt/integration" }, 84 | summary: () => "update @molt/integration", 85 | }, 86 | ], 87 | { prefix: "" }, 88 | ), 89 | "update dependencies", 90 | ); 91 | }); 92 | -------------------------------------------------------------------------------- /src/params_test.ts: -------------------------------------------------------------------------------- 1 | import { assertObjectMatch } from "@std/assert"; 2 | import { defaults } from "./inputs.ts"; 3 | import { fromInputs } from "./params.ts"; 4 | 5 | Deno.test("fromInputs - defaults", async () => { 6 | assertObjectMatch( 7 | await fromInputs({ 8 | ...defaults, 9 | root: "test/fixtures", 10 | }), 11 | { 12 | config: "deno.json", 13 | prefix: "chore: ", 14 | lock: "deno.lock", 15 | root: "test/fixtures", 16 | source: [], 17 | }, 18 | ); 19 | }); 20 | 21 | Deno.test("fromInputs - no config", async () => { 22 | assertObjectMatch( 23 | await fromInputs({ 24 | ...defaults, 25 | config: false, 26 | root: "test/fixtures", 27 | }), 28 | { 29 | config: undefined, 30 | prefix: "chore: ", 31 | lock: "deno.lock", 32 | root: "test/fixtures", 33 | source: ["./**/*.ts"], 34 | }, 35 | ); 36 | }); 37 | 38 | Deno.test("fromInputs - explicit sources", async () => { 39 | assertObjectMatch( 40 | await fromInputs({ 41 | ...defaults, 42 | config: false, 43 | root: "test/fixtures", 44 | source: ["mod.ts", "deps.ts"], 45 | }), 46 | { 47 | config: undefined, 48 | lock: "deno.lock", 49 | prefix: "chore: ", 50 | root: "test/fixtures", 51 | source: ["mod.ts", "deps.ts"], 52 | }, 53 | ); 54 | }); 55 | 56 | Deno.test("fromInputs - no lock", async () => { 57 | assertObjectMatch( 58 | await fromInputs({ 59 | ...defaults, 60 | lock: false, 61 | root: "test/fixtures", 62 | }), 63 | { 64 | config: "deno.json", 65 | prefix: "chore: ", 66 | lock: undefined, 67 | root: "test/fixtures", 68 | source: [], 69 | }, 70 | ); 71 | }); 72 | 73 | Deno.test("fromInputs - modules", async () => { 74 | assertObjectMatch( 75 | await fromInputs({ 76 | ...defaults, 77 | root: "src", 78 | }), 79 | { 80 | config: undefined, 81 | lock: undefined, 82 | prefix: "chore: ", 83 | root: "src", 84 | source: ["./**/*.ts"], 85 | }, 86 | ); 87 | }); 88 | 89 | Deno.test("fromInputs - root", async () => { 90 | assertObjectMatch( 91 | await fromInputs(defaults), 92 | { 93 | config: "deno.json", 94 | lock: "deno.lock", 95 | root: ".", 96 | source: [], 97 | }, 98 | ); 99 | }); 100 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | fmt: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Deno 23 | uses: denoland/setup-deno@v2 24 | with: 25 | deno-version: v1.x 26 | 27 | - name: Check format 28 | run: deno fmt --check 29 | 30 | lint: 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Setup Deno 38 | uses: denoland/setup-deno@v2 39 | with: 40 | deno-version: v1.x 41 | 42 | - name: Check lint 43 | run: deno lint 44 | 45 | check: 46 | runs-on: ubuntu-latest 47 | 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v4 51 | 52 | - name: Setup Deno 53 | uses: denoland/setup-deno@v2 54 | with: 55 | deno-version: v1.x 56 | 57 | - name: Restore cache 58 | uses: actions/cache@v4 59 | with: 60 | path: | 61 | ~/.cache/deno 62 | ~/.deno 63 | ~/.local/share/deno-wasmbuild 64 | key: deno-${{ hashFiles('deno.lock') }} 65 | restore-keys: deno 66 | 67 | - name: Run type check 68 | run: deno task check 69 | 70 | test: 71 | runs-on: ubuntu-latest 72 | 73 | steps: 74 | - name: Checkout 75 | uses: actions/checkout@v4 76 | 77 | - name: Setup Deno 78 | uses: denoland/setup-deno@v2 79 | with: 80 | deno-version: v1.x 81 | 82 | - name: Restore cache 83 | uses: actions/cache@v4 84 | with: 85 | path: | 86 | ~/.cache/deno 87 | ~/.deno 88 | ~/.local/share/deno-wasmbuild 89 | key: deno-${{ hashFiles('deno.lock') }} 90 | restore-keys: deno 91 | 92 | - name: Run tests 93 | run: deno task test --coverage=./coverage_profile 94 | 95 | - name: Create coverage report 96 | run: deno coverage ./coverage_profile --lcov --output=./coverage.lcov 97 | 98 | - name: Upload to Codecov 99 | uses: codecov/codecov-action@v4 100 | with: 101 | directory: ./ 102 | file: ./coverage.lcov 103 | token: ${{ secrets.CODECOV_TOKEN }} 104 | -------------------------------------------------------------------------------- /src/params.ts: -------------------------------------------------------------------------------- 1 | import { exists, walk } from "@std/fs"; 2 | import { parse } from "@std/jsonc"; 3 | import { dirname, join } from "@std/path"; 4 | import type { ActionInputs } from "./inputs.ts"; 5 | 6 | export type ActionParams = Omit & { 7 | config?: string; 8 | lock?: string; 9 | }; 10 | 11 | export async function fromInputs(inputs: ActionInputs): Promise { 12 | const { config, lock, root } = inputs.root.length 13 | ? { 14 | config: inputs.config === false 15 | ? undefined 16 | : inputs.config === true || inputs.config === "" 17 | ? await findConfig(inputs.root) 18 | : inputs.config, 19 | lock: inputs.lock === false 20 | ? undefined 21 | : inputs.lock === true || inputs.lock === "" 22 | ? await findLock(inputs.root) 23 | : inputs.lock, 24 | root: inputs.root, 25 | } 26 | : await findRootConfigLock(); 27 | const source = inputs.source.length 28 | ? inputs.source 29 | : (config ? [] : ["./**/*.ts"]); 30 | const prefix = inputs.prefix.length ? `${inputs.prefix} ` : ""; 31 | return { ...inputs, config, lock, root, source, prefix }; 32 | } 33 | 34 | async function findConfig(root: string): Promise { 35 | const json = join(root, "deno.json"); 36 | if (await exists(json) && await hasImports(json)) { 37 | return "deno.json"; 38 | } 39 | const jsonc = join(root, "deno.jsonc"); 40 | if (await exists(jsonc) && await hasImports(jsonc)) { 41 | return "deno.jsonc"; 42 | } 43 | } 44 | 45 | async function findLock(root: string): Promise { 46 | return await exists(join(root, "deno.lock")) ? "deno.lock" : undefined; 47 | } 48 | 49 | async function findRootConfigLock(): Promise< 50 | { config?: string; lock?: string; root: string } 51 | > { 52 | let root, config, lock; 53 | for await (const entry of walk(".", { match: [/\/?deno\.jsonc?$/] })) { 54 | if (await hasImports(entry.path) === false) { 55 | continue; 56 | } 57 | const dir = dirname(entry.path); 58 | if (root && dir.length > root.length) { 59 | continue; 60 | } 61 | root = dir; 62 | config = entry.name; 63 | if (await exists(entry.path.replace(/\.jsonc?$/, ".lock"))) { 64 | lock = "deno.lock"; 65 | } 66 | } 67 | return { config, lock, root: root ?? "." }; 68 | } 69 | 70 | async function hasImports(config: string): Promise { 71 | if (!config) return false; 72 | const jsonc = parse(await Deno.readTextFile(config)); 73 | return jsonc !== null && typeof jsonc === "object" && "imports" in jsonc; 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: integration 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | test-json: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run molt-action 23 | id: action 24 | uses: ./ 25 | with: 26 | cache-key-suffix: ${{ hashFiles('deno.lock') }} 27 | pull-request: false 28 | root: 'test/fixtures' 29 | script: 'src/main.ts' 30 | 31 | - name: Check updated dependencies 32 | uses: nick-fields/assert-action@v2 33 | with: 34 | actual: ${{ steps.action.outputs.dependencies }} 35 | expected: '["@luca/flag","deno.land/std"]' 36 | 37 | - name: Check updated files 38 | uses: nick-fields/assert-action@v2 39 | with: 40 | actual: ${{ steps.action.outputs.files }} 41 | expected: '["deno.json","deno.lock"]' 42 | 43 | - name: Check summary 44 | uses: nick-fields/assert-action@v2 45 | with: 46 | actual: ${{ steps.action.outputs.summary }} 47 | expected: 'chore: update @luca/flag and deno.land/std' 48 | 49 | - name: Check report 50 | uses: nick-fields/assert-action@v2 51 | with: 52 | actual: ${{ steps.action.outputs.report }} 53 | expected: '#### :package: @luca/flag [1.0.0](https://jsr.io/@luca/flag/1.0.0)' 54 | comparison: startsWith 55 | 56 | test-ts: 57 | runs-on: ubuntu-latest 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | 63 | - name: Run molt-action 64 | id: action 65 | uses: ./ 66 | with: 67 | cache-key-suffix: ${{ hashFiles('deno.lock') }} 68 | config: false 69 | pull-request: false 70 | root: 'test/fixtures' 71 | script: 'src/main.ts' 72 | 73 | - name: Check updated dependencies 74 | uses: nick-fields/assert-action@v2 75 | with: 76 | actual: ${{ steps.action.outputs.dependencies }} 77 | expected: '["@std/assert","@std/collections"]' 78 | 79 | - name: Check updated files 80 | uses: nick-fields/assert-action@v2 81 | with: 82 | actual: ${{ steps.action.outputs.files }} 83 | expected: '["deno.lock","mod.ts","mod_test.ts"]' 84 | 85 | - name: Check summary 86 | uses: nick-fields/assert-action@v2 87 | with: 88 | actual: ${{ steps.action.outputs.summary }} 89 | expected: 'chore: update @std/assert and @std/collections' 90 | 91 | - name: Check report 92 | uses: nick-fields/assert-action@v2 93 | with: 94 | actual: ${{ steps.action.outputs.report }} 95 | expected: '#### :package: @std/assert [0.224.0](https://jsr.io/@std/assert/0.224.0) → ' 96 | comparison: startsWith 97 | 98 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import actions from "@actions/core"; 2 | import * as github from "@actions/github"; 3 | import { collect } from "@molt/core"; 4 | import { distinct } from "@std/collections"; 5 | import { join, relative } from "@std/path"; 6 | import { getInputs } from "./inputs.ts"; 7 | import { fromInputs } from "./params.ts"; 8 | import { collectFromParams } from "./paths.ts"; 9 | import { createReport } from "./report.ts"; 10 | import { parseGitUser } from "./strings.ts"; 11 | import { createSummary } from "./summary.ts"; 12 | 13 | async function run( 14 | command: string, 15 | options: Deno.CommandOptions = {}, 16 | ) { 17 | actions.info(`$ ${command} ${options.args?.join(" ")}`); 18 | await new Deno.Command(command, { 19 | stderr: "inherit", 20 | stdout: "inherit", 21 | ...options, 22 | }).output(); 23 | } 24 | 25 | async function main() { 26 | actions.debug(JSON.stringify(github.context)); 27 | 28 | const inputs = getInputs(); 29 | actions.debug("inputs: " + JSON.stringify(inputs)); 30 | 31 | const params = await fromInputs(inputs); 32 | actions.debug("params: " + JSON.stringify(params)); 33 | 34 | const config = params.config ? join(params.root, params.config) : undefined; 35 | 36 | const paths = await collectFromParams(params); 37 | actions.debug("paths: " + JSON.stringify(paths)); 38 | 39 | const all = await collect({ 40 | config, 41 | lock: params.lock ? join(params.root, params.lock) : undefined, 42 | source: paths, 43 | }); 44 | 45 | // TODO: Implement filter 46 | const deps = all; 47 | 48 | const updates = (await Promise.all(deps.map((dep) => dep.check()))) 49 | .filter((it) => it !== undefined) 50 | .sort((a, b) => a.dep.name.localeCompare(b.dep.name)); 51 | 52 | if (updates.length === 0) { 53 | for (const output of ["dependencies", "file", "summary", "report"]) { 54 | actions.setOutput(output, ""); 55 | } 56 | actions.info("All dependencies are up-to-date."); 57 | return; 58 | } 59 | 60 | const files = distinct( 61 | updates.flatMap((it) => it.dep.refs.map((it) => relative(params.root, it))), 62 | ); 63 | if (params.lock) files.push(params.lock); 64 | 65 | actions.setOutput("dependencies", deps.map((it) => it.name).sort()); 66 | actions.setOutput("files", files.sort()); 67 | actions.setOutput("report", await createReport(updates)); 68 | actions.setOutput("summary", createSummary(updates, params)); 69 | 70 | if (params.commit) { 71 | const { name, email } = parseGitUser(params.committer); 72 | await run("git", { 73 | args: ["config", "--global", "user.name", name], 74 | }); 75 | await run("git", { 76 | args: ["config", "--global", "user.email", email], 77 | }); 78 | } 79 | 80 | for (const update of updates) { 81 | if (params.write) { 82 | await update.write(); 83 | } 84 | if (params.commit) { 85 | const message = update.summary(params.prefix); 86 | await update.commit(message); 87 | } 88 | } 89 | } 90 | 91 | if (import.meta.main) { 92 | try { 93 | await main(); 94 | } catch (error) { 95 | actions.setFailed(error instanceof Error ? error.message : String(error)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/report.ts: -------------------------------------------------------------------------------- 1 | import type * as Molt from "@molt/core/types"; 2 | import { 3 | compareCommits, 4 | resolveCreatedDate, 5 | resolvePackageRoot, 6 | resolveRepository, 7 | tryParse, 8 | } from "@molt/integration"; 9 | import { curateChangeLog } from "@molt/lib/changelog"; 10 | import { distinct, mapNotNullish, minWith } from "@std/collections"; 11 | import * as SemVer from "@std/semver"; 12 | 13 | type Dependency = Pick; 14 | 15 | type Update = { 16 | dep: Dependency; 17 | } & Pick; 18 | 19 | /** 20 | * Generate a detailed report of changes in Markdown format. 21 | */ 22 | export async function createReport(updates: Update[]): Promise { 23 | return (await Promise.all( 24 | updates.map(async (update) => { 25 | let content = ""; 26 | content += _header(update); 27 | const changelog = await _changelog(update); 28 | if (changelog) { 29 | content += "\n\n" + changelog; 30 | } 31 | return content; 32 | }), 33 | )).join("\n\n"); 34 | } 35 | 36 | export function _header(update: Update): string { 37 | let header = `#### :package: ${update.dep.name} `; 38 | const bump = update.lock ?? update.constraint!; 39 | const froms = distinct(bump.from.split(", ")); 40 | header += froms.map((it) => _version(update.dep, it)).join(", ") + " → "; 41 | return header += _version(update.dep, bump.to); 42 | } 43 | 44 | export function _version( 45 | dep: Dependency, 46 | version: string, 47 | ): string { 48 | const { kind, name } = dep; 49 | if (!version) { 50 | return ""; 51 | } 52 | if (!SemVer.tryParse(version)) { 53 | return version; 54 | } 55 | switch (kind) { 56 | case "jsr": 57 | return `[${version}](https://jsr.io/${name}/${version})`; 58 | case "npm": 59 | return `[${version}](https://www.npmjs.com/package/${name}/v/${version})`; 60 | case "https": 61 | return `[${version}](${dep.specifier}@${version})`; 62 | default: 63 | return version; 64 | } 65 | } 66 | 67 | export async function _changelog(update: Update): Promise { 68 | const bump = update.lock ?? update.constraint!; 69 | // Can't provide a changelog for a non-semver update 70 | if (!SemVer.tryParse(bump.to)) { 71 | return ""; 72 | } 73 | const froms = bump.from.split(", "); 74 | const to = bump.to; 75 | 76 | const pkg = tryParse(update.dep.specifier); 77 | 78 | // Can't provide a changelog for a non-package dependency 79 | if (!pkg) return ""; 80 | 81 | const repo = await resolveRepository(pkg); 82 | if (!repo) return ""; 83 | 84 | /** A map of dependency names to the created date of the oldest update */ 85 | const dates = new Map(); 86 | await Promise.all(froms.map(async (it) => { 87 | dates.set(it, await resolveCreatedDate(pkg, it)); 88 | })); 89 | /** The oldest update from which to fetch commit logs */ 90 | const oldest = minWith( 91 | froms, 92 | (a, b) => Math.sign(dates.get(a)! - dates.get(b)!), 93 | ); 94 | if (!oldest) { 95 | // The dependency was newly added in this update 96 | return ""; 97 | } 98 | const messages = await compareCommits(repo, oldest, to); 99 | if (!messages.length) { 100 | // Couldn't find tags for the given versions 101 | return ""; 102 | } 103 | const root = await resolvePackageRoot(repo, pkg, to); 104 | if (!root) { 105 | // The package seems to be generated dynamically on publish 106 | return ""; 107 | } 108 | const changelog = curateChangeLog(messages, { 109 | types: ["feat", "fix", "deprecation"], 110 | scope: root !== "." ? root : undefined, 111 | }); 112 | 113 | return mapNotNullish( 114 | Object.entries(changelog), 115 | ([kind, records]) => 116 | records.length 117 | ? `${records.map((it) => `- ${kind}: ${it.text}`).join("\n")}` 118 | : null, 119 | ).join("\n"); 120 | } 121 | -------------------------------------------------------------------------------- /src/paths_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "@std/assert"; 2 | import { join } from "@std/path"; 3 | import { collectFromParams } from "./paths.ts"; 4 | import type { ActionParams } from "./params.ts"; 5 | 6 | const fixturesDir = join(Deno.cwd(), "test", "fixtures"); 7 | 8 | Deno.test("collectFromParams - source specific file", async () => { 9 | const params: ActionParams = { 10 | root: fixturesDir, 11 | source: ["deno.json"], 12 | exclude: [], 13 | config: undefined, 14 | lock: undefined, 15 | commit: false, 16 | committer: "", 17 | prefix: "", 18 | write: false, 19 | }; 20 | 21 | const paths = await collectFromParams(params); 22 | assertEquals(paths.sort(), [ 23 | join(fixturesDir, "deno.json"), 24 | ]); 25 | }); 26 | 27 | Deno.test("collectFromParams - source with glob pattern", async () => { 28 | const params: ActionParams = { 29 | root: fixturesDir, 30 | source: ["**/*.ts"], 31 | exclude: [], 32 | config: undefined, 33 | lock: undefined, 34 | commit: false, 35 | committer: "", 36 | prefix: "", 37 | write: false, 38 | }; 39 | 40 | const paths = await collectFromParams(params); 41 | assertEquals(paths.sort(), [ 42 | join(fixturesDir, "mod.ts"), 43 | join(fixturesDir, "mod_test.ts"), 44 | join(fixturesDir, "subdir/mod.ts"), 45 | ]); 46 | }); 47 | 48 | Deno.test("collectFromParams - multiple sources", async () => { 49 | const params: ActionParams = { 50 | root: fixturesDir, 51 | source: ["**/*.ts", "**/*.json"], 52 | exclude: [], 53 | config: undefined, 54 | lock: undefined, 55 | commit: false, 56 | committer: "", 57 | prefix: "", 58 | write: false, 59 | }; 60 | 61 | const paths = await collectFromParams(params); 62 | assertEquals(paths.sort(), [ 63 | join(fixturesDir, "deno.json"), 64 | join(fixturesDir, "mod.ts"), 65 | join(fixturesDir, "mod_test.ts"), 66 | join(fixturesDir, "subdir/mod.ts"), 67 | ]); 68 | }); 69 | 70 | Deno.test("collectFromParams - source no matches", async () => { 71 | const params: ActionParams = { 72 | root: fixturesDir, 73 | source: ["**/*.js"], // No .js files in fixtures 74 | exclude: [], 75 | config: undefined, 76 | lock: undefined, 77 | commit: false, 78 | committer: "", 79 | prefix: "", 80 | write: false, 81 | }; 82 | 83 | const paths = await collectFromParams(params); 84 | assertEquals(paths, []); 85 | }); 86 | 87 | Deno.test("collectFromParams - exclude specific file", async () => { 88 | const params: ActionParams = { 89 | root: fixturesDir, 90 | source: ["**/*.ts"], 91 | exclude: ["mod_test.ts"], 92 | config: undefined, 93 | lock: undefined, 94 | commit: false, 95 | committer: "", 96 | prefix: "", 97 | write: false, 98 | }; 99 | 100 | const paths = await collectFromParams(params); 101 | assertEquals(paths, [ 102 | join(fixturesDir, "mod.ts"), 103 | join(fixturesDir, "subdir/mod.ts"), 104 | ]); 105 | }); 106 | 107 | Deno.test("collectFromParams - exclude with glob pattern", async () => { 108 | const params: ActionParams = { 109 | root: fixturesDir, 110 | source: ["**/*.ts"], 111 | exclude: ["**/*_test.ts"], 112 | config: undefined, 113 | lock: undefined, 114 | commit: false, 115 | committer: "", 116 | prefix: "", 117 | write: false, 118 | }; 119 | 120 | const paths = await collectFromParams(params); 121 | assertEquals(paths, [ 122 | join(fixturesDir, "mod.ts"), 123 | join(fixturesDir, "subdir/mod.ts"), 124 | ]); 125 | }); 126 | 127 | Deno.test("collectFromParams - exclude with directory", async () => { 128 | const params: ActionParams = { 129 | root: fixturesDir, 130 | source: ["**/*.ts"], 131 | exclude: ["subdir"], 132 | config: undefined, 133 | lock: undefined, 134 | commit: false, 135 | committer: "", 136 | prefix: "", 137 | write: false, 138 | }; 139 | 140 | const paths = await collectFromParams(params); 141 | assertEquals(paths, [ 142 | join(fixturesDir, "mod.ts"), 143 | join(fixturesDir, "mod_test.ts"), 144 | ]); 145 | }); 146 | 147 | Deno.test("collectFromParams - multiple exclude patterns", async () => { 148 | const params: ActionParams = { 149 | root: fixturesDir, 150 | source: ["**/*.ts", "**/*.json"], 151 | exclude: ["*_test.ts", "deno.json"], 152 | config: undefined, 153 | lock: undefined, 154 | commit: false, 155 | committer: "", 156 | prefix: "", 157 | write: false, 158 | }; 159 | 160 | const paths = await collectFromParams(params); 161 | assertEquals(paths, [ 162 | join(fixturesDir, "mod.ts"), 163 | join(fixturesDir, "subdir/mod.ts"), 164 | ]); 165 | }); 166 | -------------------------------------------------------------------------------- /src/report_test.ts: -------------------------------------------------------------------------------- 1 | import { collect } from "@molt/core"; 2 | import { assertEquals } from "@std/assert"; 3 | import dedent from "dedent"; 4 | import { _changelog, _header, _version } from "./report.ts"; 5 | import { createReport } from "./report.ts"; 6 | 7 | Deno.test("_version - jsr", () => { 8 | assertEquals( 9 | _version( 10 | { 11 | specifier: "jsr:@molt/core", 12 | kind: "jsr", 13 | name: "@molt/core", 14 | }, 15 | "1.0.0", 16 | ), 17 | "[1.0.0](https://jsr.io/@molt/core/1.0.0)", 18 | ); 19 | }); 20 | 21 | Deno.test("_version - npm", () => { 22 | assertEquals( 23 | _version( 24 | { 25 | specifier: "npm:@actions/core", 26 | kind: "npm", 27 | name: "@actions/core", 28 | }, 29 | "1.0.0", 30 | ), 31 | "[1.0.0](https://www.npmjs.com/package/@actions/core/v/1.0.0)", 32 | ); 33 | }); 34 | 35 | Deno.test("_version - https", () => { 36 | assertEquals( 37 | _version( 38 | { 39 | specifier: "https://deno.land/std", 40 | kind: "https", 41 | name: "deno.land/std", 42 | }, 43 | "1.0.0", 44 | ), 45 | "[1.0.0](https://deno.land/std@1.0.0)", 46 | ); 47 | }); 48 | 49 | Deno.test("_version - constraint", () => { 50 | assertEquals( 51 | _version( 52 | { 53 | specifier: "jsr:@molt/core", 54 | kind: "jsr", 55 | name: "@molt/core", 56 | }, 57 | "^1.0.0", 58 | ), 59 | "^1.0.0", 60 | ); 61 | }); 62 | 63 | Deno.test("_header - with a single from", () => { 64 | assertEquals( 65 | _header( 66 | { 67 | dep: { 68 | specifier: "jsr:@molt/core", 69 | kind: "jsr", 70 | name: "@molt/core", 71 | }, 72 | lock: { 73 | from: "0.18.0", 74 | to: "1.0.0", 75 | }, 76 | }, 77 | ), 78 | "#### :package: @molt/core [0.18.0](https://jsr.io/@molt/core/0.18.0) → [1.0.0](https://jsr.io/@molt/core/1.0.0)", 79 | ); 80 | }); 81 | 82 | Deno.test("_header - with multiple froms", () => { 83 | assertEquals( 84 | _header( 85 | { 86 | dep: { 87 | specifier: "jsr:@molt/core", 88 | kind: "jsr", 89 | name: "@molt/core", 90 | }, 91 | lock: { 92 | from: "0.18.0, 0.19.0", 93 | to: "1.0.0", 94 | }, 95 | }, 96 | ), 97 | "#### :package: @molt/core [0.18.0](https://jsr.io/@molt/core/0.18.0), [0.19.0](https://jsr.io/@molt/core/0.19.0) → [1.0.0](https://jsr.io/@molt/core/1.0.0)", 98 | ); 99 | }); 100 | 101 | Deno.test("_header - constraints", () => { 102 | assertEquals( 103 | _header( 104 | { 105 | dep: { 106 | specifier: "jsr:@molt/core", 107 | kind: "jsr", 108 | name: "@molt/core", 109 | }, 110 | constraint: { 111 | from: "^0.18.0", 112 | to: "^1.0.0", 113 | }, 114 | }, 115 | ), 116 | "#### :package: @molt/core ^0.18.0 → ^1.0.0", 117 | ); 118 | }); 119 | 120 | Deno.test("_changelog - non-package", async () => { 121 | assertEquals( 122 | await _changelog( 123 | { 124 | dep: { 125 | specifier: "https://deno.land/std", 126 | kind: "https", 127 | name: "deno.land/std", 128 | }, 129 | constraint: { 130 | from: "0.222.0", 131 | to: "0.224.0", 132 | }, 133 | }, 134 | ), 135 | "", 136 | ); 137 | }); 138 | 139 | Deno.test("_changelog - jsr:@molt/core", async () => { 140 | assertEquals( 141 | await _changelog( 142 | { 143 | dep: { 144 | specifier: "jsr:@molt/core", 145 | kind: "jsr", 146 | name: "@molt/core", 147 | }, 148 | lock: { 149 | from: "0.18.0", 150 | to: "0.18.4", 151 | }, 152 | }, 153 | ), 154 | dedent` 155 | - fix: use \`jsr:@deno/graph\` for \`x/deno_graph\` 156 | - fix: do not throw on unresolved deps (#166) 157 | - fix: resolve mapped jsr imports with subpath 158 | - fix: handle identical dependencies correctly 159 | `, 160 | ); 161 | }); 162 | 163 | Deno.test("_changelog - jsr:@core/unknownutil", async () => { 164 | assertEquals( 165 | await _changelog( 166 | { 167 | dep: { 168 | specifier: "jsr:@core/unknownutil", 169 | kind: "jsr", 170 | name: "@core/unknownutil", 171 | }, 172 | lock: { 173 | from: "3.18.0", 174 | to: "3.18.1", 175 | }, 176 | }, 177 | ), 178 | "", // the repository uses gitmoji, which is not supported yet 179 | ); 180 | }); 181 | 182 | Deno.test("createReport - empty result", async () => { 183 | assertEquals( 184 | await createReport([]), 185 | "", 186 | ); 187 | }); 188 | 189 | Deno.test("createReport", async () => { 190 | const deps = await collect({ 191 | config: new URL("../test/fixtures/deno.json", import.meta.url), 192 | lock: new URL("../test/fixtures/deno.lock", import.meta.url), 193 | }); 194 | const updates = (await Promise.all(deps.map((dep) => dep.check()))) 195 | .filter((it) => it !== undefined); 196 | const actual = await createReport(updates); 197 | assertEquals( 198 | actual, 199 | dedent` 200 | #### :package: @luca/flag [1.0.0](https://jsr.io/@luca/flag/1.0.0) → [1.0.1](https://jsr.io/@luca/flag/1.0.1) 201 | 202 | #### :package: deno.land/std [0.222.0](https://deno.land/std@0.222.0) → [0.224.0](https://deno.land/std@0.224.0) 203 | `, 204 | ); 205 | }); 206 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: molt-action 2 | 3 | description: > 4 | Create pull requests to update dependencies in Deno projects with jsr.io/@molt 5 | 6 | branding: 7 | icon: 'refresh-cw' 8 | color: 'gray-dark' 9 | 10 | inputs: 11 | author: 12 | description: > 13 | Author of the pull request in the format of `Display Name `. 14 | default: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> 15 | 16 | base: 17 | description: > 18 | Base branch to create the pull request against. Defaults to the branch 19 | checked out in the workflow. 20 | 21 | branch: 22 | description: Head branch for the pull request. 23 | default: molt-action 24 | 25 | cache-key-suffix: 26 | description: Suffix for the cache key (development purposes only). 27 | default: 1.0.3 28 | 29 | commit: 30 | description: Whether to commit changes locally. 31 | default: true 32 | 33 | commit-prefix: 34 | description: Prefix for commit messages. 35 | default: 'chore:' 36 | 37 | committer: 38 | description: > 39 | Name of the committer in the format of `Display Name ` 40 | default: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 41 | 42 | config: 43 | description: > 44 | Relative path to the configuration file including imports from the root 45 | directory. Ignored if `root` is not given. Set to `false` to disable auto 46 | discovery. Defaults to `deno.json` or `deno.jsonc` if available. 47 | 48 | draft: 49 | description: Whether to create a draft pull request. 50 | default: false 51 | 52 | labels: 53 | description: > 54 | Comma or newline-separated list of labels to add to the pull request. 55 | default: 'dependencies' 56 | 57 | lock: 58 | description: > 59 | Relative path to the lock file to update from the root directory. 60 | Ignored if `root` is not given. Set to `false` to disable auto discovery. 61 | Defaults to `deno.lock` if available. 62 | 63 | pull-request: 64 | description: Whether to create a pull request. 65 | default: true 66 | 67 | root: 68 | description: > 69 | Root directory of the relevant source files. Defaults to the shallowest 70 | directory containing `deno.json` or `deno.jsonc` if available, otherwise 71 | the repository root. 72 | 73 | script: 74 | description: > 75 | Specifier for the main script to run (development purposes only). 76 | default: jsr:@molt/action@1.0.3 77 | 78 | source: 79 | description: > 80 | Source files to update dependencies in, specified as glob patterns. 81 | If a Deno configuration file with imports is specified or found, this 82 | defaults to nothing. Otherwise, it defaults to `./**/*.ts`. 83 | 84 | exclude: 85 | description: > 86 | Files to exclude from dependency updates, specified as glob patterns. 87 | Files matching these patterns will be skipped even if they match the source patterns. 88 | If a directory path is specified, all files under that directory will be excluded. 89 | 90 | token: 91 | description: > 92 | GitHub token with permissions `contents: write` and `pull-requests: write` 93 | or a repo scoped personal access token (PAT). 94 | default: ${{ github.token }} 95 | 96 | write: 97 | description: > 98 | Whether to write changes to disk. Forced to `true` if `commit` or `pull-request` is `true`. 99 | default: true 100 | 101 | outputs: 102 | dependencies: 103 | description: A JSON list of updated dependencies, or an empty string. 104 | value: ${{ steps.main.outputs.dependencies }} 105 | 106 | files: 107 | description: A JSON list of updated files, or an empty string. 108 | value: ${{ steps.main.outputs.files }} 109 | 110 | report: 111 | description: A detailed report of the changes made in Markdown format. 112 | value: ${{ steps.main.outputs.report }} 113 | 114 | summary: 115 | description: A summary of the changes made. 116 | value: ${{ steps.main.outputs.summary }} 117 | 118 | permissions: 119 | contents: write 120 | pull-requests: write 121 | 122 | runs: 123 | using: composite 124 | 125 | steps: 126 | - name: Setup Deno 127 | uses: denoland/setup-deno@v2 128 | with: 129 | deno-version: v1.43 130 | 131 | - name: Restore cache 132 | uses: actions/cache@v4 133 | with: 134 | path: | 135 | ~/.cache/deno 136 | ~/.deno 137 | ~/.local/share/deno-wasmbuild 138 | key: molt-action-${{ inputs.cache-key-suffix }} 139 | 140 | - name: Run main script 141 | id: main 142 | run: > 143 | deno run --allow-env --allow-read --allow-write --allow-net 144 | --allow-run=deno,git --no-prompt --unstable-kv ${{ inputs.script }} 145 | shell: bash 146 | env: 147 | INPUT_COMMIT: ${{ inputs.commit }} 148 | INPUT_COMMITTER: ${{ inputs.committer }} 149 | INPUT_COMMIT-PREFIX: ${{ inputs.commit-prefix }} 150 | INPUT_CONFIG: ${{ inputs.config }} 151 | INPUT_LOCK: ${{ inputs.lock }} 152 | INPUT_ROOT: ${{ inputs.root }} 153 | INPUT_SOURCE: ${{ inputs.source }} 154 | INPUT_EXCLUDE: ${{ inputs.exclude }} 155 | INPUT_WRITE: ${{ inputs.pr || inputs.write }} 156 | 157 | - name: Create a pull request 158 | uses: peter-evans/create-pull-request@v6 159 | if: inputs.pull-request == 'true' && steps.main.outputs.files != '' 160 | with: 161 | author: ${{ inputs.author }} 162 | base: ${{ inputs.base }} 163 | body: ${{ steps.main.outputs.report }} 164 | branch: ${{ inputs.branch }} 165 | delete-branch: true 166 | draft: ${{ inputs.draft }} 167 | labels: ${{ inputs.labels }} 168 | title: ${{ steps.main.outputs.summary }} 169 | token: ${{ inputs.token }} 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🦕 molt-action 2 | 3 | A GitHub Action to create a pull request to update dependencies in a Deno 4 | project with [molt](https://github.com/hasundue/molt). See 5 | [Pull requests](https://github.com/hasundue/molt-action/pulls) for an example. 6 | 7 | ## Usage 8 | 9 | ```yaml 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: hasundue/molt-action@v1 16 | with: 17 | # optional inputs 18 | ``` 19 | 20 | ### Inputs 21 | 22 | All inputs are **optional**. If not set, sensible defaults will be used. Many of 23 | them are inherited from 24 | [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) 25 | and passed through to it. 26 | 27 | | Name | Description | Default | 28 | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | 29 | | `author` | Author of the pull request in the format of `Display Name `. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` | 30 | | `base` | Base branch to create the pull request against. | The branch checked out in the workflow | 31 | | `branch` | Head branch to create the pull request from. | `molt-action` | 32 | | `commit` | Whether to commit changes locally. | `true` | 33 | | `commit-prefix` | Prefix for commit messages. | `chore:` | 34 | | `committer` | Name of the committer in the format of `Display Name ` | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` | 35 | | `config` | Relative path to the configuration file including imports from the root directory. Ignored if `root` is not given. Set to `false` to disable auto discovery. | `deno.json` or `deno.jsonc` if available | 36 | | `draft` | Whether to create a draft pull request. | `false` | 37 | | `labels` | Comma or newline-separated list of labels to add to the pull request. | `dependencies` | 38 | | `lock` | Relative path to the lock file to update from the root directory. Ignored if `root` is not given. Set to `false` to disable auto discovery. | `deno.lock` if available | 39 | | `pull-request` | Whether to create a pull request. | `true` | 40 | | `root` | Root directory of the relevant source files. | The shallowest directory containing `deno.json` or `deno.jsonc` if available, otherwise the repository root | 41 | | `source` | Source files to update dependencies in, specified as glob patterns. | If a Deno configuration file with imports is specified or found, this defaults to nothing. Otherwise, it defaults to `**/*.ts`. | 42 | | `exclude` | Files to exclude from dependency updates, specified as glob patterns. If a directory is specified, all files under it are excluded. | `[]` | 43 | | `token` | GitHub token with permissions `contents: write` and `pull-requests: write` or a repo scoped personal access token (PAT). | `${{ secrets.GITHUB_TOKEN }}` | 44 | | `write` | Whether to write changes to disk. Forced to `true` if `commit` or `pull-request` is `true`. | `true` | 45 | 46 | ### Outputs 47 | 48 | | Name | Description | 49 | | -------------- | --------------------------------------------------------- | 50 | | `dependencies` | A JSON list of updated dependencies, or an empty string. | 51 | | `files` | A list of updated files. | 52 | | `report` | A detailed report of the changes made in Markdown format. | 53 | | `summary` | A summary of the changes made. | 54 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@core/match@^0.3.1": "jsr:@core/match@0.3.1", 6 | "jsr:@core/unknownutil@3.18.0": "jsr:@core/unknownutil@3.18.0", 7 | "jsr:@core/unknownutil@^3.18.1": "jsr:@core/unknownutil@3.18.1", 8 | "jsr:@core/unknownutil@^4.0.0": "jsr:@core/unknownutil@4.0.1", 9 | "jsr:@deno/graph@^0.81.0": "jsr:@deno/graph@0.81.0", 10 | "jsr:@molt/core@^0.19.0": "jsr:@molt/core@0.19.8", 11 | "jsr:@molt/core@^0.19.1": "jsr:@molt/core@0.19.3", 12 | "jsr:@molt/core@^0.19.8": "jsr:@molt/core@0.19.8", 13 | "jsr:@molt/integration@^0.19.0": "jsr:@molt/integration@0.19.5", 14 | "jsr:@molt/lib@^0.19.0": "jsr:@molt/lib@0.19.0", 15 | "jsr:@std/assert@0.224.0": "jsr:@std/assert@0.224.0", 16 | "jsr:@std/assert@^1.0.0": "jsr:@std/assert@1.0.2", 17 | "jsr:@std/bytes@^1.0.2-rc.3": "jsr:@std/bytes@1.0.2", 18 | "jsr:@std/collections@0.224.0": "jsr:@std/collections@0.224.0", 19 | "jsr:@std/collections@^1.0.0": "jsr:@std/collections@1.0.5", 20 | "jsr:@std/collections@^1.0.1": "jsr:@std/collections@1.0.5", 21 | "jsr:@std/fmt@^0.224.0": "jsr:@std/fmt@0.224.0", 22 | "jsr:@std/fs@^1.0.0": "jsr:@std/fs@1.0.1", 23 | "jsr:@std/internal@^0.224.0": "jsr:@std/internal@0.224.0", 24 | "jsr:@std/internal@^1.0.1": "jsr:@std/internal@1.0.1", 25 | "jsr:@std/json@^1.0.0": "jsr:@std/json@1.0.0", 26 | "jsr:@std/jsonc@^1.0.0": "jsr:@std/jsonc@1.0.0", 27 | "jsr:@std/path@^1.0.0": "jsr:@std/path@1.0.2", 28 | "jsr:@std/path@^1.0.2": "jsr:@std/path@1.0.2", 29 | "jsr:@std/semver@^0.224.3": "jsr:@std/semver@0.224.3", 30 | "jsr:@std/semver@^1.0.0": "jsr:@std/semver@1.0.1", 31 | "jsr:@std/streams@^1.0.0": "jsr:@std/streams@1.0.0", 32 | "npm:@actions/core@^1.10.1": "npm:@actions/core@1.10.1", 33 | "npm:@actions/github@^6.0.0": "npm:@actions/github@6.0.0_@octokit+core@5.2.0", 34 | "npm:@conventional-commits/parser@^0.4.1": "npm:@conventional-commits/parser@0.4.1", 35 | "npm:@octokit/rest@^21.0.0": "npm:@octokit/rest@21.0.1", 36 | "npm:dedent@^1.5.3": "npm:dedent@1.5.3", 37 | "npm:ts-toolbelt@9.6.0": "npm:ts-toolbelt@9.6.0" 38 | }, 39 | "jsr": { 40 | "@core/match@0.3.1": { 41 | "integrity": "020bb0199f56877daf270a457bfeee19daa8c721819d36dc987c2a9df7cca4fd", 42 | "dependencies": [ 43 | "jsr:@core/unknownutil@3.18.0", 44 | "npm:ts-toolbelt@9.6.0" 45 | ] 46 | }, 47 | "@core/unknownutil@3.18.0": { 48 | "integrity": "bff7ab4a2f554bbade301127519523b0d3baa4273ecbed51287133ac00a48738" 49 | }, 50 | "@core/unknownutil@3.18.1": { 51 | "integrity": "6aaa108b623ff971d062dd345da7ca03b97f61ae3d29c8677bff14fec11f0d76" 52 | }, 53 | "@core/unknownutil@4.0.1": { 54 | "integrity": "5e08141ea8284d064bc4ee8c70aab42825355fb1399eaeb6140936f5f763f42d" 55 | }, 56 | "@deno/graph@0.81.0": { 57 | "integrity": "2e2c3c9b5569bc759c132727a7d372e6d5c4dc792f8ba15c1a99c1226d6d0a56" 58 | }, 59 | "@deno/graph@0.81.1": { 60 | "integrity": "0a513d58e06c1f593b782ef893583c61b7ebdbfe471ee96eb53b1efe4702ae30" 61 | }, 62 | "@molt/core@0.19.0": { 63 | "integrity": "dfa8a86a8c4e7756fefda2bd444d86b8fb11bc1dbfa782ad0f80af07587f8886", 64 | "dependencies": [ 65 | "jsr:@core/unknownutil@^3.18.1", 66 | "jsr:@deno/graph@^0.80.0", 67 | "jsr:@molt/lib@^0.19.0", 68 | "jsr:@std/assert@^1.0.0", 69 | "jsr:@std/collections@^1.0.1", 70 | "jsr:@std/fs@^1.0.0", 71 | "jsr:@std/jsonc@^1.0.0", 72 | "jsr:@std/path@^1.0.0", 73 | "jsr:@std/semver@^0.224.3" 74 | ] 75 | }, 76 | "@molt/core@0.19.3": { 77 | "integrity": "5523b7882566c8433d2d2da1ff093c759975c40582f46d485413ccdd8d3c0325", 78 | "dependencies": [ 79 | "jsr:@core/unknownutil@^3.18.1", 80 | "jsr:@deno/graph@^0.81.0", 81 | "jsr:@molt/lib@^0.19.0", 82 | "jsr:@std/assert@^1.0.0", 83 | "jsr:@std/collections@^1.0.1", 84 | "jsr:@std/fs@^1.0.0", 85 | "jsr:@std/jsonc@^1.0.0", 86 | "jsr:@std/path@^1.0.0", 87 | "jsr:@std/semver@^0.224.3" 88 | ] 89 | }, 90 | "@molt/core@0.19.8": { 91 | "integrity": "7af509a9c7ff9b58e12c5235dc7fd09986a599baefb862e91a95b8f5d4f0585e", 92 | "dependencies": [ 93 | "jsr:@core/unknownutil@^4.0.0", 94 | "jsr:@deno/graph@^0.81.0", 95 | "jsr:@molt/lib@^0.19.0", 96 | "jsr:@std/assert@^1.0.0", 97 | "jsr:@std/collections@^1.0.1", 98 | "jsr:@std/fs@^1.0.0", 99 | "jsr:@std/jsonc@^1.0.0", 100 | "jsr:@std/path@^1.0.0", 101 | "jsr:@std/semver@^1.0.0" 102 | ] 103 | }, 104 | "@molt/integration@0.19.5": { 105 | "integrity": "8fe4fb5176ae7fd464cdb21cc89bfeae6e85a5b6883f8a63b380207b1fd13c05", 106 | "dependencies": [ 107 | "jsr:@core/match@^0.3.1", 108 | "jsr:@core/unknownutil@^3.18.1", 109 | "jsr:@molt/core@^0.19.0", 110 | "jsr:@std/jsonc@^1.0.0", 111 | "jsr:@std/path@^1.0.0", 112 | "jsr:@std/semver@^0.224.3", 113 | "npm:@octokit/rest@^21.0.0" 114 | ] 115 | }, 116 | "@molt/lib@0.19.0": { 117 | "integrity": "5e9e6b70ba643abd257a8340957a2e2251d4fa06cf5009b9346a29f4e74f0988", 118 | "dependencies": [ 119 | "jsr:@std/assert@^1.0.0", 120 | "jsr:@std/collections@^1.0.1", 121 | "jsr:@std/path@^1.0.0", 122 | "jsr:@std/semver@^0.224.3", 123 | "npm:@conventional-commits/parser@^0.4.1" 124 | ] 125 | }, 126 | "@std/assert@0.224.0": { 127 | "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f", 128 | "dependencies": [ 129 | "jsr:@std/fmt@^0.224.0", 130 | "jsr:@std/internal@^0.224.0" 131 | ] 132 | }, 133 | "@std/assert@1.0.1": { 134 | "integrity": "13590ef8e5854f59e4ad252fd987e83239a1bf1f16cb9c69c1d123ebb807a75b", 135 | "dependencies": [ 136 | "jsr:@std/internal@^1.0.1" 137 | ] 138 | }, 139 | "@std/assert@1.0.2": { 140 | "integrity": "ccacec332958126deaceb5c63ff8b4eaf9f5ed0eac9feccf124110435e59e49c", 141 | "dependencies": [ 142 | "jsr:@std/internal@^1.0.1" 143 | ] 144 | }, 145 | "@std/bytes@1.0.2": { 146 | "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" 147 | }, 148 | "@std/collections@0.224.0": { 149 | "integrity": "d00201d1833c6e05371c7e9f0b178522597b1dda0b0a9ecf0c579414e1dd2770" 150 | }, 151 | "@std/collections@1.0.5": { 152 | "integrity": "ab9eac23b57a0c0b89ba45134e61561f69f3d001f37235a248ed40be260c0c10" 153 | }, 154 | "@std/fmt@0.224.0": { 155 | "integrity": "e20e9a2312a8b5393272c26191c0a68eda8d2c4b08b046bad1673148f1d69851" 156 | }, 157 | "@std/fs@1.0.0": { 158 | "integrity": "d72e4a125af7168d717a2ed1dca77a728b422b0d138fd20579e3fa41a77da943", 159 | "dependencies": [ 160 | "jsr:@std/path@^1.0.2" 161 | ] 162 | }, 163 | "@std/fs@1.0.1": { 164 | "integrity": "d6914ca2c21abe591f733b31dbe6331e446815e513e2451b3b9e472daddfefcb", 165 | "dependencies": [ 166 | "jsr:@std/path@^1.0.2" 167 | ] 168 | }, 169 | "@std/internal@0.224.0": { 170 | "integrity": "afc50644f9cdf4495eeb80523a8f6d27226b4b36c45c7c195dfccad4b8509291", 171 | "dependencies": [ 172 | "jsr:@std/fmt@^0.224.0" 173 | ] 174 | }, 175 | "@std/internal@1.0.1": { 176 | "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" 177 | }, 178 | "@std/json@1.0.0": { 179 | "integrity": "985c1e544918d42e4e84072fc739ac4a19c3a5093292c99742ffcdd03fb6a268", 180 | "dependencies": [ 181 | "jsr:@std/streams@^1.0.0" 182 | ] 183 | }, 184 | "@std/jsonc@1.0.0": { 185 | "integrity": "835da212e586f3ef94ab25e8f0e8a7711a86fddbee95ad40c34d6b3d74da1a1b", 186 | "dependencies": [ 187 | "jsr:@std/json@^1.0.0" 188 | ] 189 | }, 190 | "@std/path@1.0.2": { 191 | "integrity": "a452174603f8c620bd278a380c596437a9eef50c891c64b85812f735245d9ec7" 192 | }, 193 | "@std/semver@0.224.3": { 194 | "integrity": "7bb34b5ad46de2c0c73de0ca3e30081ef64b4361f66abd57c84ff1011c6a1233" 195 | }, 196 | "@std/semver@1.0.1": { 197 | "integrity": "f0c9b41b70e27e8cdfe9252b486c55a727d66ead72625e0fa1aae75f45ca15e1" 198 | }, 199 | "@std/streams@1.0.0": { 200 | "integrity": "350242b8fad9874ed45f3c42df3d132bd0a958f8a8bae9bbfa1ff039716aa6fb", 201 | "dependencies": [ 202 | "jsr:@std/bytes@^1.0.2-rc.3" 203 | ] 204 | } 205 | }, 206 | "npm": { 207 | "@actions/core@1.10.1": { 208 | "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", 209 | "dependencies": { 210 | "@actions/http-client": "@actions/http-client@2.2.1", 211 | "uuid": "uuid@8.3.2" 212 | } 213 | }, 214 | "@actions/github@6.0.0_@octokit+core@5.2.0": { 215 | "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", 216 | "dependencies": { 217 | "@actions/http-client": "@actions/http-client@2.2.1", 218 | "@octokit/core": "@octokit/core@5.2.0", 219 | "@octokit/plugin-paginate-rest": "@octokit/plugin-paginate-rest@9.2.1_@octokit+core@5.2.0", 220 | "@octokit/plugin-rest-endpoint-methods": "@octokit/plugin-rest-endpoint-methods@10.4.1_@octokit+core@5.2.0" 221 | } 222 | }, 223 | "@actions/http-client@2.2.1": { 224 | "integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==", 225 | "dependencies": { 226 | "tunnel": "tunnel@0.0.6", 227 | "undici": "undici@5.28.4" 228 | } 229 | }, 230 | "@conventional-commits/parser@0.4.1": { 231 | "integrity": "sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==", 232 | "dependencies": { 233 | "unist-util-visit": "unist-util-visit@2.0.3", 234 | "unist-util-visit-parents": "unist-util-visit-parents@3.1.1" 235 | } 236 | }, 237 | "@fastify/busboy@2.1.1": { 238 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 239 | "dependencies": {} 240 | }, 241 | "@octokit/auth-token@4.0.0": { 242 | "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", 243 | "dependencies": {} 244 | }, 245 | "@octokit/auth-token@5.1.1": { 246 | "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", 247 | "dependencies": {} 248 | }, 249 | "@octokit/core@5.2.0": { 250 | "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", 251 | "dependencies": { 252 | "@octokit/auth-token": "@octokit/auth-token@4.0.0", 253 | "@octokit/graphql": "@octokit/graphql@7.1.0", 254 | "@octokit/request": "@octokit/request@8.4.0", 255 | "@octokit/request-error": "@octokit/request-error@5.1.0", 256 | "@octokit/types": "@octokit/types@13.5.0", 257 | "before-after-hook": "before-after-hook@2.2.3", 258 | "universal-user-agent": "universal-user-agent@6.0.1" 259 | } 260 | }, 261 | "@octokit/core@6.1.2": { 262 | "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", 263 | "dependencies": { 264 | "@octokit/auth-token": "@octokit/auth-token@5.1.1", 265 | "@octokit/graphql": "@octokit/graphql@8.1.1", 266 | "@octokit/request": "@octokit/request@9.1.3", 267 | "@octokit/request-error": "@octokit/request-error@6.1.4", 268 | "@octokit/types": "@octokit/types@13.5.0", 269 | "before-after-hook": "before-after-hook@3.0.2", 270 | "universal-user-agent": "universal-user-agent@7.0.2" 271 | } 272 | }, 273 | "@octokit/endpoint@10.1.1": { 274 | "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", 275 | "dependencies": { 276 | "@octokit/types": "@octokit/types@13.5.0", 277 | "universal-user-agent": "universal-user-agent@7.0.2" 278 | } 279 | }, 280 | "@octokit/endpoint@9.0.5": { 281 | "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", 282 | "dependencies": { 283 | "@octokit/types": "@octokit/types@13.5.0", 284 | "universal-user-agent": "universal-user-agent@6.0.1" 285 | } 286 | }, 287 | "@octokit/graphql@7.1.0": { 288 | "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", 289 | "dependencies": { 290 | "@octokit/request": "@octokit/request@8.4.0", 291 | "@octokit/types": "@octokit/types@13.5.0", 292 | "universal-user-agent": "universal-user-agent@6.0.1" 293 | } 294 | }, 295 | "@octokit/graphql@8.1.1": { 296 | "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", 297 | "dependencies": { 298 | "@octokit/request": "@octokit/request@9.1.3", 299 | "@octokit/types": "@octokit/types@13.5.0", 300 | "universal-user-agent": "universal-user-agent@7.0.2" 301 | } 302 | }, 303 | "@octokit/openapi-types@20.0.0": { 304 | "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", 305 | "dependencies": {} 306 | }, 307 | "@octokit/openapi-types@22.2.0": { 308 | "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", 309 | "dependencies": {} 310 | }, 311 | "@octokit/plugin-paginate-rest@11.3.3": { 312 | "integrity": "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==", 313 | "dependencies": { 314 | "@octokit/types": "@octokit/types@13.5.0" 315 | } 316 | }, 317 | "@octokit/plugin-paginate-rest@9.2.1_@octokit+core@5.2.0": { 318 | "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", 319 | "dependencies": { 320 | "@octokit/core": "@octokit/core@5.2.0", 321 | "@octokit/types": "@octokit/types@12.6.0" 322 | } 323 | }, 324 | "@octokit/plugin-request-log@5.3.1": { 325 | "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", 326 | "dependencies": {} 327 | }, 328 | "@octokit/plugin-rest-endpoint-methods@10.4.1_@octokit+core@5.2.0": { 329 | "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", 330 | "dependencies": { 331 | "@octokit/core": "@octokit/core@5.2.0", 332 | "@octokit/types": "@octokit/types@12.6.0" 333 | } 334 | }, 335 | "@octokit/plugin-rest-endpoint-methods@13.2.4": { 336 | "integrity": "sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==", 337 | "dependencies": { 338 | "@octokit/types": "@octokit/types@13.5.0" 339 | } 340 | }, 341 | "@octokit/request-error@5.1.0": { 342 | "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", 343 | "dependencies": { 344 | "@octokit/types": "@octokit/types@13.5.0", 345 | "deprecation": "deprecation@2.3.1", 346 | "once": "once@1.4.0" 347 | } 348 | }, 349 | "@octokit/request-error@6.1.4": { 350 | "integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==", 351 | "dependencies": { 352 | "@octokit/types": "@octokit/types@13.5.0" 353 | } 354 | }, 355 | "@octokit/request@8.4.0": { 356 | "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", 357 | "dependencies": { 358 | "@octokit/endpoint": "@octokit/endpoint@9.0.5", 359 | "@octokit/request-error": "@octokit/request-error@5.1.0", 360 | "@octokit/types": "@octokit/types@13.5.0", 361 | "universal-user-agent": "universal-user-agent@6.0.1" 362 | } 363 | }, 364 | "@octokit/request@9.1.3": { 365 | "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", 366 | "dependencies": { 367 | "@octokit/endpoint": "@octokit/endpoint@10.1.1", 368 | "@octokit/request-error": "@octokit/request-error@6.1.4", 369 | "@octokit/types": "@octokit/types@13.5.0", 370 | "universal-user-agent": "universal-user-agent@7.0.2" 371 | } 372 | }, 373 | "@octokit/rest@21.0.1": { 374 | "integrity": "sha512-RWA6YU4CqK0h0J6tfYlUFnH3+YgBADlxaHXaKSG+BVr2y4PTfbU2tlKuaQoQZ83qaTbi4CUxLNAmbAqR93A6mQ==", 375 | "dependencies": { 376 | "@octokit/core": "@octokit/core@6.1.2", 377 | "@octokit/plugin-paginate-rest": "@octokit/plugin-paginate-rest@11.3.3", 378 | "@octokit/plugin-request-log": "@octokit/plugin-request-log@5.3.1", 379 | "@octokit/plugin-rest-endpoint-methods": "@octokit/plugin-rest-endpoint-methods@13.2.4" 380 | } 381 | }, 382 | "@octokit/types@12.6.0": { 383 | "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", 384 | "dependencies": { 385 | "@octokit/openapi-types": "@octokit/openapi-types@20.0.0" 386 | } 387 | }, 388 | "@octokit/types@13.5.0": { 389 | "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", 390 | "dependencies": { 391 | "@octokit/openapi-types": "@octokit/openapi-types@22.2.0" 392 | } 393 | }, 394 | "@types/unist@2.0.10": { 395 | "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", 396 | "dependencies": {} 397 | }, 398 | "before-after-hook@2.2.3": { 399 | "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", 400 | "dependencies": {} 401 | }, 402 | "before-after-hook@3.0.2": { 403 | "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", 404 | "dependencies": {} 405 | }, 406 | "dedent@1.5.3": { 407 | "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", 408 | "dependencies": {} 409 | }, 410 | "deprecation@2.3.1": { 411 | "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", 412 | "dependencies": {} 413 | }, 414 | "once@1.4.0": { 415 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 416 | "dependencies": { 417 | "wrappy": "wrappy@1.0.2" 418 | } 419 | }, 420 | "ts-toolbelt@9.6.0": { 421 | "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", 422 | "dependencies": {} 423 | }, 424 | "tunnel@0.0.6": { 425 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 426 | "dependencies": {} 427 | }, 428 | "undici@5.28.4": { 429 | "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", 430 | "dependencies": { 431 | "@fastify/busboy": "@fastify/busboy@2.1.1" 432 | } 433 | }, 434 | "unist-util-is@4.1.0": { 435 | "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", 436 | "dependencies": {} 437 | }, 438 | "unist-util-visit-parents@3.1.1": { 439 | "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", 440 | "dependencies": { 441 | "@types/unist": "@types/unist@2.0.10", 442 | "unist-util-is": "unist-util-is@4.1.0" 443 | } 444 | }, 445 | "unist-util-visit@2.0.3": { 446 | "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", 447 | "dependencies": { 448 | "@types/unist": "@types/unist@2.0.10", 449 | "unist-util-is": "unist-util-is@4.1.0", 450 | "unist-util-visit-parents": "unist-util-visit-parents@3.1.1" 451 | } 452 | }, 453 | "universal-user-agent@6.0.1": { 454 | "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", 455 | "dependencies": {} 456 | }, 457 | "universal-user-agent@7.0.2": { 458 | "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", 459 | "dependencies": {} 460 | }, 461 | "uuid@8.3.2": { 462 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 463 | "dependencies": {} 464 | }, 465 | "wrappy@1.0.2": { 466 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 467 | "dependencies": {} 468 | } 469 | } 470 | }, 471 | "remote": {}, 472 | "workspace": { 473 | "dependencies": [ 474 | "jsr:@core/match@^0.3.1", 475 | "jsr:@core/unknownutil@^4.0.0", 476 | "jsr:@molt/core@^0.19.8", 477 | "jsr:@molt/integration@^0.19.0", 478 | "jsr:@molt/lib@^0.19.0", 479 | "jsr:@std/assert@^1.0.0", 480 | "jsr:@std/collections@^1.0.0", 481 | "jsr:@std/fs@^1.0.0", 482 | "jsr:@std/jsonc@^1.0.0", 483 | "jsr:@std/path@^1.0.0", 484 | "jsr:@std/semver@^1.0.0", 485 | "npm:@actions/core@^1.10.1", 486 | "npm:@actions/github@^6.0.0", 487 | "npm:dedent@^1.5.3" 488 | ] 489 | } 490 | } 491 | --------------------------------------------------------------------------------