Add release channels (#385)

## New release flow

1. Run "Bump Version" workflow with the desired version bump and the
prerelease flag set to `true`. This will push a new version bump to the
target branch and create a new git tag.
    - See below for more info on how the version bumping works.
2. A new "Build Helper" workflow run will kick off automatically for the
new tag. Once it is complete, test the new build locally by downloading
with the [download
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/download-staged-artifact.sh).
3. Release the new build using the [publish
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/publish-from-staging.sh).
This will trigger electron-updater to distribute the package to beta
users.
4. Run "Bump Version" again with a release bump (either `major`,
`minor`, or `patch`) and the prerelease flag set to `false`.
6. Release the new build to all channels using the [publish
script](https://github.com/wavetermdev/thenextwave/blob/main/scripts/artifacts/publish-from-staging.sh).
This will trigger electron-updater to distribute the package to all
users.

## Change Summary

Creates a new "Bump Version" workflow to manage versioning and tag
creation.

Build Helper is now automated.

### Version bumps

Updates the `version.cjs` script so that an argument can be passed to
trigger a version bump. Under the hood, this utilizes NPM's `semver`
package.

If arguments are present, the version will be bumped.
If only a single argument is given, the following are valid inputs:
    - `none`: No-op.
    - `patch`: Bumps the patch version.
    - `minor`: Bumps the minor version.
    - `major`: Bumps the major version.
    - '1', 'true': Bumps the prerelease version.
If two arguments are given, the first argument must be either `none`,
`patch`, `minor`, or `major`. The second argument must be `1` or `true`
to bump the prerelease version.

### electron-builder

We are now using the release channels support in electron-builder. This
will automatically detect the channel being built based on the package
version to determine which channel update files need to be generated.
See
[here](https://www.electron.build/tutorials/release-using-channels.html)
for more information.

### Github Actions

#### Bump Version

This adds a new "Bump Version" workflow for managing versioning and
queuing new builds. When run, this workflow will bump the version,
create a new tag, and push the changes to the target branch. There is a
new dropdown when queuing the "Bump Version" workflow to select what
kind of version bump to perform. A bump must always be performed when
running a new build to ensure consistency.

I had to create a GitHub App to grant write permissions to our main
branch for the version bump commits. I've made a separate workflow file
to manage the version bump commits, which should help prevent tampering.
Thanks to using the GitHub API directly, I am able to make these commits
signed!

#### Build Helper

Build Helper is now triggered when new tags are created, rather than
being triggered automatically. This ensures we're always creating
artifacts from known checkpoints.

### Settings

Adds a new `autoupdate:channel` configuration to the settings file. If
unset, the default from the artifact will be used (should correspond to
the channel of the artifact when downloaded).

## Future Work

I want to add a release workflow that will automatically copy over the
corresponding version artifacts to the release bucket when a new GitHub
Release is created.

I also want to separate versions into separate subdirectories in the
release bucket so we can clean them up more-easily.

---------

Co-authored-by: wave-builder <builds@commandline.dev>
Co-authored-by: wave-builder[bot] <181805596+wave-builder[bot]@users.noreply.github.com>
This commit is contained in:
Evan Simkowitz 2024-09-17 13:10:35 -07:00 committed by GitHub
parent 0159152c00
commit 5b7535d08f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 282 additions and 153 deletions

View File

@ -2,13 +2,19 @@
# For more information on the macOS signing and notarization, see https://www.electron.build/code-signing and https://www.electron.build/configuration/mac
# For more information on the Windows Code Signing, see https://docs.digicert.com/en/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html and https://docs.digicert.com/en/digicert-keylocker/signing-tools/sign-authenticode-with-electron-builder-using-ksp-integration.html
name: "Build Helper"
on: workflow_dispatch
name: Build Helper
run-name: Version ${{ github.ref_name }}
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+*"
env:
GO_VERSION: "1.22.5"
NODE_VERSION: "22.5.1"
jobs:
runbuild:
outputs:
version: ${{ steps.set-version.outputs.WAVETERM_VERSION }}
strategy:
matrix:
include:
@ -53,7 +59,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{env.NODE_VERSION}}
- name: Install yarn
- name: Install Yarn
run: |
corepack enable
yarn install
@ -62,9 +68,10 @@ jobs:
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Set Version
- name: "Set Version"
id: set-version
run: echo "WAVETERM_VERSION=$(node "./version.cjs")" >> "$GITHUB_OUTPUT"
run: echo "WAVETERM_VERSION=$(task version)" >> "$GITHUB_OUTPUT"
shell: bash
# Windows Code Signing Setup

View File

@ -1,19 +1,27 @@
# Workflow to manage bumping the package version and pushing it to the target branch.
# Workflow to manage bumping the package version and pushing it to the target branch with a new tag.
# This workflow uses a GitHub App to bypass branch protection and uses the GitHub API directly to ensure commits and tags are signed.
# For more information, see this doc: https://github.com/Nautilus-Cyberneering/pygithub/blob/main/docs/how_to_sign_automatic_commits_in_github_actions.md
name: Bump version
name: Bump Version
run-name: "branch: ${{ github.ref_name }}; version: ${{ inputs.bump }}; prerelease: ${{ inputs.is-prerelease }}"
on:
workflow_dispatch:
inputs:
bump:
description: SemVer Bump
required: true
default: beta
type: choice
default: none
options:
- beta
- none
- patch
- minor
- major
is-prerelease:
description: Is Prerelease
required: true
type: boolean
default: true
env:
NODE_VERSION: "22.5.1"
jobs:
@ -44,16 +52,32 @@ jobs:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: "Set Version: ${{ inputs.bump }}"
id: set-version
run: echo "WAVETERM_VERSION=$(task version:${{ inputs.bump }})" >> "$GITHUB_OUTPUT"
- name: "Bump Version: ${{ inputs.bump }}"
id: bump-version
run: echo "WAVETERM_VERSION=$( task version -- ${{ inputs.bump }} ${{inputs.is-prerelease}} )" >> "$GITHUB_OUTPUT"
shell: bash
- name: "Push version bump: ${{ steps.set-version.outputs.WAVETERM_VERSION }}"
- name: "Push version bump: ${{ steps.bump-version.outputs.WAVETERM_VERSION }}"
run: |
git config user.name wave-builder
git config user.email builds@commandline.dev
git add package.json
git commit -m "Bump version to ${{ steps.set-version.outputs.WAVETERM_VERSION }}"
git tag -a ${{ steps.set-version.outputs.WAVETERM_VERSION }} -m "Bump version to ${{ steps.set-version.outputs.WAVETERM_VERSION }}"
git push
# Create a new commit for the package version bump in package.json
export VERSION=${{ steps.bump-version.outputs.WAVETERM_VERSION }}
export MESSAGE="chore: bump package version to $VERSION"
export FILE=package.json
export BRANCH=${{github.ref_name}}
export SHA=$( git rev-parse $BRANCH:$FILE )
export CONTENT=$( base64 -i $FILE )
gh api --method PUT /repos/:owner/:repo/contents/$FILE \
--field branch="$BRANCH" \
--field message="$MESSAGE" \
--field content="$CONTENT" \
--field sha="$SHA"
# Fetch the new commit and create a tag referencing it
git fetch
export TAG_SHA=$( git rev-parse origin/$BRANCH )
gh api --method POST /repos/:owner/:repo/git/refs \
--field ref="refs/tags/$VERSION" \
--field sha="$TAG_SHA"
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -200,6 +200,19 @@ tasks:
- frontend/app/store/services.ts
- frontend/app/store/wshserver.ts
version:
desc: Get the current package version, or bump version if args are present.
summary: |
If no arguments are present, the current version will be returned.
If only a single argument is given, the following are valid inputs:
- `none`: No-op.
- `patch`: Bumps the patch version.
- `minor`: Bumps the minor version.
- `major`: Bumps the major version.
- '1', 'true': Bumps the prerelease version.
If two arguments are given, the first argument must be either `none`, `patch`, `minor`, or `major`. The second argument must be `1` or `true` to bump the prerelease version.
cmd: node version.cjs {{.CLI_ARGS}}
yarn:
desc: Runs `yarn`
internal: true

View File

@ -12,6 +12,7 @@ const config = {
productName: pkg.productName,
executableName: pkg.name,
artifactName: "${productName}-${platform}-${arch}-${version}.${ext}",
generateUpdatesFilesForAllChannels: true,
npmRebuild: false,
nodeGypRebuild: false,
electronCompile: false,

View File

@ -29,6 +29,12 @@ export class Updater {
autoUpdater.autoInstallOnAppQuit = settings["autoupdate:installonquit"];
// Only update the release channel if it's specified, otherwise use the one configured in the artifact.
const channel = settings["autoupdate:channel"];
if (channel) {
autoUpdater.channel = channel;
}
autoUpdater.removeAllListeners();
autoUpdater.on("error", (err) => {

View File

@ -410,6 +410,7 @@ declare global {
"autoupdate:enabled"?: boolean;
"autoupdate:intervalms"?: number;
"autoupdate:installonquit"?: boolean;
"autoupdate:channel"?: string;
"widget:*"?: boolean;
"widget:showhelp"?: boolean;
"window:*"?: boolean;

View File

@ -1,131 +1,133 @@
{
"name": "thenextwave",
"author": {
"name": "Command Line Inc",
"email": "info@commandline.dev"
},
"productName": "TheNextWave",
"description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows",
"license": "Apache-2.0",
"version": "0.1.12",
"homepage": "https://waveterm.dev",
"build": {
"appId": "dev.commandline.thenextwave"
},
"private": true,
"main": "./dist/main/index.js",
"type": "module",
"scripts": {
"dev": "electron-vite dev",
"start": "electron-vite preview",
"build:dev": "electron-vite build --mode development",
"build:prod": "electron-vite build --mode production",
"storybook": "storybook dev -p 6006 --no-open",
"build-storybook": "storybook build",
"coverage": "vitest run --coverage",
"test": "vitest",
"postinstall": "electron-builder install-app-deps"
},
"devDependencies": {
"@chromatic-com/storybook": "^2.0.2",
"@eslint/js": "^9.10.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@storybook/addon-essentials": "^8.3.0",
"@storybook/addon-interactions": "^8.3.0",
"@storybook/addon-links": "^8.3.0",
"@storybook/blocks": "^8.3.0",
"@storybook/react": "^8.3.0",
"@storybook/react-vite": "^8.3.0",
"@storybook/test": "^8.3.0",
"@types/css-tree": "^2",
"@types/debug": "^4",
"@types/electron": "^1.6.10",
"@types/node": "^22.5.4",
"@types/papaparse": "^5",
"@types/pngjs": "^6.0.5",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/shell-quote": "^1",
"@types/sprintf-js": "^1",
"@types/throttle-debounce": "^5",
"@types/tinycolor2": "^1",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"@vitest/coverage-istanbul": "^2.1.1",
"electron": "^32.1.0",
"electron-builder": "^25.0.5",
"electron-vite": "^2.3.0",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"less": "^4.2.0",
"prettier": "^3.3.3",
"prettier-plugin-jsdoc": "^1.3.0",
"prettier-plugin-organize-imports": "^4.0.0",
"rollup-plugin-flow": "^1.1.1",
"storybook": "^8.3.0",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0",
"vite": "^5.4.6",
"vite-plugin-image-optimizer": "^1.1.8",
"vite-plugin-static-copy": "^1.0.6",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.1.1"
},
"dependencies": {
"@monaco-editor/loader": "^1.4.0",
"@monaco-editor/react": "^4.6.0",
"@observablehq/plot": "^0.6.16",
"@react-hook/resize-observer": "^2.0.2",
"@table-nav/core": "^0.0.7",
"@table-nav/react": "^0.0.7",
"@tanstack/react-table": "^8.20.5",
"@types/color": "^3.0.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"base64-js": "^1.5.1",
"clsx": "^2.1.1",
"color": "^4.2.3",
"css-tree": "^3.0.0",
"dayjs": "^1.11.13",
"debug": "^4.3.7",
"electron-updater": "6.3.4",
"fast-average-color": "^9.4.0",
"htl": "^0.3.1",
"html-to-image": "^1.11.11",
"immer": "^10.1.1",
"jotai": "^2.9.3",
"monaco-editor": "^0.51.0",
"overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "^5.4.1",
"pngjs": "^7.0.0",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-frame-component": "^5.2.7",
"react-gauge-chart": "^0.5.1",
"react-markdown": "^9.0.1",
"rehype-highlight": "^7.0.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"remark-flexible-toc": "^1.1.1",
"remark-gfm": "^4.0.0",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"sprintf-js": "^1.1.3",
"throttle-debounce": "^5.0.2",
"tinycolor2": "^1.6.0",
"use-device-pixel-ratio": "^1.1.2",
"winston": "^3.14.2"
},
"packageManager": "yarn@4.4.1"
"name": "thenextwave",
"author": {
"name": "Command Line Inc",
"email": "info@commandline.dev"
},
"productName": "TheNextWave",
"description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows",
"license": "Apache-2.0",
"version": "0.1.13-beta.20",
"homepage": "https://waveterm.dev",
"build": {
"appId": "dev.commandline.thenextwave"
},
"private": true,
"main": "./dist/main/index.js",
"type": "module",
"scripts": {
"dev": "electron-vite dev",
"start": "electron-vite preview",
"build:dev": "electron-vite build --mode development",
"build:prod": "electron-vite build --mode production",
"storybook": "storybook dev -p 6006 --no-open",
"build-storybook": "storybook build",
"coverage": "vitest run --coverage",
"test": "vitest",
"postinstall": "electron-builder install-app-deps"
},
"devDependencies": {
"@chromatic-com/storybook": "^2.0.2",
"@eslint/js": "^9.10.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@storybook/addon-essentials": "^8.3.0",
"@storybook/addon-interactions": "^8.3.0",
"@storybook/addon-links": "^8.3.0",
"@storybook/blocks": "^8.3.0",
"@storybook/react": "^8.3.0",
"@storybook/react-vite": "^8.3.0",
"@storybook/test": "^8.3.0",
"@types/css-tree": "^2",
"@types/debug": "^4",
"@types/electron": "^1.6.10",
"@types/node": "^22.5.4",
"@types/papaparse": "^5",
"@types/pngjs": "^6.0.5",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/semver": "^7",
"@types/shell-quote": "^1",
"@types/sprintf-js": "^1",
"@types/throttle-debounce": "^5",
"@types/tinycolor2": "^1",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"@vitest/coverage-istanbul": "^2.1.1",
"electron": "^32.1.0",
"electron-builder": "^25.0.5",
"electron-vite": "^2.3.0",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
"less": "^4.2.0",
"prettier": "^3.3.3",
"prettier-plugin-jsdoc": "^1.3.0",
"prettier-plugin-organize-imports": "^4.0.0",
"rollup-plugin-flow": "^1.1.1",
"semver": "^7.6.3",
"storybook": "^8.3.0",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0",
"vite": "^5.4.6",
"vite-plugin-image-optimizer": "^1.1.8",
"vite-plugin-static-copy": "^1.0.6",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.1.1"
},
"dependencies": {
"@monaco-editor/loader": "^1.4.0",
"@monaco-editor/react": "^4.6.0",
"@observablehq/plot": "^0.6.16",
"@react-hook/resize-observer": "^2.0.2",
"@table-nav/core": "^0.0.7",
"@table-nav/react": "^0.0.7",
"@tanstack/react-table": "^8.20.5",
"@types/color": "^3.0.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"base64-js": "^1.5.1",
"clsx": "^2.1.1",
"color": "^4.2.3",
"css-tree": "^3.0.0",
"dayjs": "^1.11.13",
"debug": "^4.3.7",
"electron-updater": "6.3.4",
"fast-average-color": "^9.4.0",
"htl": "^0.3.1",
"html-to-image": "^1.11.11",
"immer": "^10.1.1",
"jotai": "^2.9.3",
"monaco-editor": "^0.51.0",
"overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "^5.4.1",
"pngjs": "^7.0.0",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-frame-component": "^5.2.7",
"react-gauge-chart": "^0.5.1",
"react-markdown": "^9.0.1",
"rehype-highlight": "^7.0.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"remark-flexible-toc": "^1.1.1",
"remark-gfm": "^4.0.0",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"sprintf-js": "^1.1.3",
"throttle-debounce": "^5.0.2",
"tinycolor2": "^1.6.0",
"use-device-pixel-ratio": "^1.1.2",
"winston": "^3.14.2"
},
"packageManager": "yarn@4.4.1"
}

View File

@ -32,6 +32,7 @@ const (
ConfigKey_AutoUpdateEnabled = "autoupdate:enabled"
ConfigKey_AutoUpdateIntervalMs = "autoupdate:intervalms"
ConfigKey_AutoUpdateInstallOnQuit = "autoupdate:installonquit"
ConfigKey_AutoUpdateChannel = "autoupdate:channel"
ConfigKey_WidgetClear = "widget:*"
ConfigKey_WidgetShowHelp = "widget:showhelp"

View File

@ -64,6 +64,7 @@ type SettingsType struct {
AutoUpdateEnabled bool `json:"autoupdate:enabled,omitempty"`
AutoUpdateIntervalMs float64 `json:"autoupdate:intervalms,omitempty"`
AutoUpdateInstallOnQuit bool `json:"autoupdate:installonquit,omitempty"`
AutoUpdateChannel string `json:"autoupdate:channel,omitempty"`
WidgetClear bool `json:"widget:*,omitempty"`
WidgetShowHelp bool `json:"widget:showhelp,omitempty"`

View File

@ -1,9 +1,18 @@
# Building for release
## Bump Version workflow
All releases start by first bumping the package version and creating a new Git tag. We have a workflow set up to automate this.
To run it, trigger a new run of the [Bump Version workflow](https://github.com/wavetermdev/thenextwave/actions/workflows/bump-version.yml). When triggering the run, you will be
prompted to select a version bump type, either `none`, `patch`, `minor`, or `major`, and whether the version is prerelease or not. This determines how much the version number is incremented.
See [`version.cjs`](../../version.cjs) for more details on how this works.
Once the tag has been created, a new [Build Helper](#build-helper-workflow) run will be automatically queued to generate the artifacts.
## Build Helper workflow
Our release builds are managed by the "Build Helper" GitHub Action, which is defined
in [`build-helper.yml`](../../.github/workflows/build-helper.yml).
Our release builds are managed by the [Build Helper workflow](https://github.com/wavetermdev/thenextwave/actions/workflows/build-helper.yml).
Under the hood, this will call the `package` task in
[`Taskfile.yml`](../../Taskfile.yml), which will build the Electron codebase using
@ -34,6 +43,11 @@ With each release, `latest-mac.yml`, `latest-linux.yml`, and `latest-linux-arm64
newest release. These also include file sizes and checksums to aid in validating the packages. The app
will check these files in our S3 bucket every hour to see if a new version is available.
### Update channels
We utilize update channels to roll out beta and stable releases. These are determined based on the package versioning [described above](#bump-version-workflow). Users can
select their update channel using the `autoupdate:channel` setting in Wave. See [here](https://www.electron.build/tutorials/release-using-channels.html) for more information.
### Homebrew
Homebrew is automatically bumped when new artifacts are published.

View File

@ -1,3 +1,5 @@
#!/bin/bash
# Takes a release from our staging bucket and publishes it to the public download bucket.
# Usage: publish-from-staging.sh <version> <aws-profile>
# Example: publish-from-staging.sh 0.1.0 storage

View File

@ -1,9 +1,57 @@
/**
* Script to get the current package version and bump the version, if specified.
*
* If no arguments are present, the current version will returned.
* If only a single argument is given, the following are valid inputs:
* - `none`: No-op.
* - `patch`: Bumps the patch version.
* - `minor`: Bumps the minor version.
* - `major`: Bumps the major version.
* - '1', 'true': Bumps the prerelease version.
* If two arguments are given, the first argument must be either `none`, `patch`, `minor`, or `major`. The second argument must be `1` or `true` to bump the prerelease version.
*/
const path = require("path");
const packageJson = require(path.resolve(__dirname, "package.json"));
const packageJsonPath = path.resolve(__dirname, "package.json");
const packageJson = require(packageJsonPath);
const VERSION = `${packageJson.version}`;
module.exports = VERSION;
if (typeof require !== "undefined" && require.main === module) {
console.log(VERSION);
if (process.argv.length > 2) {
const fs = require("fs");
const semver = require("semver");
const action = process.argv[2];
const isPrerelease =
process.argv.length > 3
? process.argv[3] === "true" || process.argv[3] === "1"
: action === "true" || action === "1";
let newVersion = packageJson.version;
switch (action) {
case "major":
case "minor":
case "patch":
newVersion = semver.inc(
VERSION,
`${isPrerelease ? "pre" : ""}${action}`,
null,
isPrerelease ? "beta" : null
);
break;
case "none":
case "true":
case "1":
if (isPrerelease) newVersion = semver.inc(VERSION, "prerelease", null, "beta");
break;
default:
throw new Error(`Unknown action ${action}`);
}
packageJson.version = newVersion;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4) + "\n");
console.log(newVersion);
} else {
console.log(VERSION);
}
}

View File

@ -2496,6 +2496,13 @@ __metadata:
languageName: node
linkType: hard
"@types/semver@npm:^7":
version: 7.5.8
resolution: "@types/semver@npm:7.5.8"
checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa
languageName: node
linkType: hard
"@types/send@npm:*":
version: 0.17.4
resolution: "@types/send@npm:0.17.4"
@ -10208,6 +10215,7 @@ __metadata:
"@types/pngjs": "npm:^6.0.5"
"@types/react": "npm:^18.3.5"
"@types/react-dom": "npm:^18.3.0"
"@types/semver": "npm:^7"
"@types/shell-quote": "npm:^1"
"@types/sprintf-js": "npm:^1"
"@types/throttle-debounce": "npm:^5"
@ -10261,6 +10269,7 @@ __metadata:
remark-gfm: "npm:^4.0.0"
rollup-plugin-flow: "npm:^1.1.1"
rxjs: "npm:^7.8.1"
semver: "npm:^7.6.3"
shell-quote: "npm:^1.8.1"
sprintf-js: "npm:^1.1.3"
storybook: "npm:^8.3.0"