소스 검색

chore: Merge branch 'main' into minor

Evan You 1 년 전
부모
커밋
741d8a0710

+ 1 - 1
.github/workflows/canary-minor.yml

@@ -28,6 +28,6 @@ jobs:
 
       - run: pnpm install
 
-      - run: pnpm release --canary --tag minor
+      - run: pnpm release --canary --publish --tag minor
         env:
           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

+ 1 - 1
.github/workflows/canary.yml

@@ -26,6 +26,6 @@ jobs:
 
       - run: pnpm install
 
-      - run: pnpm release --canary
+      - run: pnpm release --canary --publish
         env:
           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

+ 3 - 132
.github/workflows/ci.yml

@@ -8,136 +8,7 @@ on:
       - main
       - minor
 
-permissions:
-  contents: read # to fetch code (actions/checkout)
-
 jobs:
-  unit-test:
-    runs-on: ubuntu-latest
-    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
-    env:
-      PUPPETEER_SKIP_DOWNLOAD: 'true'
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4.0.0
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version-file: '.node-version'
-          cache: 'pnpm'
-
-      - run: pnpm install
-
-      - name: Run unit tests
-        run: pnpm run test-unit
-
-  unit-test-windows:
-    runs-on: windows-latest
-    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
-    env:
-      PUPPETEER_SKIP_DOWNLOAD: 'true'
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4.0.0
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version-file: '.node-version'
-          cache: 'pnpm'
-
-      - run: pnpm install
-
-      - name: Run compiler unit tests
-        run: pnpm run test-unit compiler
-
-      - name: Run ssr unit tests
-        run: pnpm run test-unit server-renderer
-
-  e2e-test:
-    runs-on: ubuntu-latest
-    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Setup cache for Chromium binary
-        uses: actions/cache@v4
-        with:
-          path: ~/.cache/puppeteer
-          key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4.0.0
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version-file: '.node-version'
-          cache: 'pnpm'
-
-      - run: pnpm install
-      - run: node node_modules/puppeteer/install.mjs
-
-      - name: Run e2e tests
-        run: pnpm run test-e2e
-
-      - name: verify treeshaking
-        run: node scripts/verify-treeshaking.js
-
-  lint-and-test-dts:
-    runs-on: ubuntu-latest
-    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
-    env:
-      PUPPETEER_SKIP_DOWNLOAD: 'true'
-    steps:
-      - uses: actions/checkout@v4
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4.0.0
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version-file: '.node-version'
-          cache: 'pnpm'
-
-      - run: pnpm install
-
-      - name: Run eslint
-        run: pnpm run lint
-
-      - name: Run prettier
-        run: pnpm run format-check
-
-      - name: Run type declaration tests
-        run: pnpm run test-dts
-
-  # benchmarks:
-  #   runs-on: ubuntu-latest
-  #   if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
-  #   env:
-  #     PUPPETEER_SKIP_DOWNLOAD: 'true'
-  #   steps:
-  #     - uses: actions/checkout@v4
-
-  #     - name: Install pnpm
-  #       uses: pnpm/action-setup@v3.0.0
-
-  #     - name: Install Node.js
-  #       uses: actions/setup-node@v4
-  #       with:
-  #         node-version-file: '.node-version'
-  #         cache: 'pnpm'
-
-  #     - run: pnpm install
-
-  #     - name: Run benchmarks
-  #       uses: CodSpeedHQ/action@v2
-  #       with:
-  #         run: pnpm vitest bench --run
-  #         token: ${{ secrets.CODSPEED_TOKEN }}
+  test:
+    if: ${{ ! startsWith(github.event.head_commit.message, 'release:') && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) }}
+    uses: ./.github/workflows/test.yml

+ 0 - 28
.github/workflows/release-gh.yml

@@ -1,28 +0,0 @@
-on:
-  push:
-    tags:
-      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
-
-name: Create GH Release for Tag
-
-permissions: {}
-jobs:
-  build:
-    permissions:
-      contents: write # to create release (yyx990803/release-tag)
-
-    name: Create Release
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@master
-      - name: Create Release for Tag
-        id: release_tag
-        uses: yyx990803/release-tag@master
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          tag_name: ${{ github.ref }}
-          body: |
-            For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
-            For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.

+ 12 - 51
.github/workflows/release.yml

@@ -1,37 +1,18 @@
 name: Release
 
 on:
-  workflow_dispatch:
-    inputs:
-      branch:
-        description: 'Branch to publish'
-        required: true
-        default: 'main'
-        type: choice
-        options:
-          - main
-          - minor
-      bump:
-        description: 'Bump version'
-        required: true
-        default: 'patch'
-        type: choice
-        options:
-          - patch
-          - minor
-          - prepatch
-          - preminor
-          - custom
-      custom_version:
-        description: 'Custom version'
-        required: false
-        default: ''
-        type: string
+  push:
+    tags:
+      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
 
 jobs:
+  test:
+    uses: ./.github/workflows/test.yml
+
   release:
     # prevents this action from running on forks
     if: github.repository == 'vuejs/core'
+    needs: [test]
     runs-on: ubuntu-latest
     permissions:
       contents: write
@@ -41,8 +22,6 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v4
-        with:
-          ref: ${{ inputs.branch }}
 
       - name: Install pnpm
         uses: pnpm/action-setup@v4
@@ -57,38 +36,20 @@ jobs:
       - name: Install deps
         run: pnpm install
 
-      - name: Configure git user as vue bot
+      - name: Build and publish
+        id: publish
         run: |
-          git config user.name "vue-bot"
-          git config user.email "<bot@vuejs.org>"
-
-      - name: Import GPG key
-        uses: crazy-max/ghaction-import-gpg@v6
-        with:
-          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
-          passphrase: ${{ secrets.GPG_PASSPHRASE }}
-          git_user_signingkey: true
-          git_commit_gpgsign: true
-
-      - name: Run release script
-        id: release
-        run: |
-          pnpm release ${{ inputs.bump != 'custom' && inputs.bump || inputs.custom_version }} --skipPrompts
-          RELEASE_TAG=$(git describe --tags --abbrev=0)
-          echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
+          pnpm release --publishOnly
         env:
           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
 
-      - name: Push tags
-        run: git push -u origin ${{ inputs.branch }} --follow-tags
-
-      - name: Create Release for Tag
+      - name: Create GitHub release
         id: release_tag
         uses: yyx990803/release-tag@master
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
-          tag_name: ${{ steps.release.outputs.tag }}
+          tag_name: ${{ github.ref }}
           body: |
             For stable releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/main/CHANGELOG.md) for details.
             For pre-releases, please refer to [CHANGELOG.md](https://github.com/vuejs/core/blob/minor/CHANGELOG.md) of the `minor` branch.

+ 8 - 4
.github/workflows/size-data.yml

@@ -43,12 +43,16 @@ jobs:
           name: size-data
           path: temp/size
 
-      - name: Save PR number
+      - name: Save PR number & base branch
         if: ${{github.event_name == 'pull_request'}}
-        run: echo ${{ github.event.number }} > ./pr.txt
+        run: |
+          echo ${{ github.event.number }} > ./number.txt
+          echo ${{ github.base_ref }} > ./base.txt
 
       - uses: actions/upload-artifact@v4
         if: ${{github.event_name == 'pull_request'}}
         with:
-          name: pr-number
-          path: pr.txt
+          name: pr-info
+          path: |
+            number.txt
+            base.txt

+ 11 - 5
.github/workflows/size-report.yml

@@ -35,18 +35,24 @@ jobs:
       - name: Install dependencies
         run: pnpm install
 
-      - name: Download PR number
+      - name: Download PR info
         uses: dawidd6/action-download-artifact@v6
         with:
-          name: pr-number
+          name: pr-info
           run_id: ${{ github.event.workflow_run.id }}
-          path: /tmp/pr-number
+          path: /tmp/pr-info
 
       - name: Read PR Number
         id: pr-number
         uses: juliangruber/read-file-action@v1
         with:
-          path: /tmp/pr-number/pr.txt
+          path: /tmp/pr-info/number.txt
+
+      - name: Read PR base branch
+        id: pr-base
+        uses: juliangruber/read-file-action@v1
+        with:
+          path: /tmp/pr-info/base.txt
 
       - name: Download Size Data
         uses: dawidd6/action-download-artifact@v6
@@ -58,7 +64,7 @@ jobs:
       - name: Download Previous Size Data
         uses: dawidd6/action-download-artifact@v6
         with:
-          branch: ${{ github.base_ref }}
+          branch: ${{ steps.pr-base.outputs.content }}
           workflow: size-data.yml
           event: push
           name: size-data

+ 108 - 0
.github/workflows/test.yml

@@ -0,0 +1,108 @@
+name: 'test'
+
+on: workflow_call
+
+permissions:
+  contents: read # to fetch code (actions/checkout)
+
+jobs:
+  unit-test:
+    runs-on: ubuntu-latest
+    env:
+      PUPPETEER_SKIP_DOWNLOAD: 'true'
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4.0.0
+
+      - name: Install Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: '.node-version'
+          cache: 'pnpm'
+
+      - run: pnpm install
+
+      - name: Run unit tests
+        run: pnpm run test-unit
+
+  unit-test-windows:
+    runs-on: windows-latest
+    env:
+      PUPPETEER_SKIP_DOWNLOAD: 'true'
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4.0.0
+
+      - name: Install Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: '.node-version'
+          cache: 'pnpm'
+
+      - run: pnpm install
+
+      - name: Run compiler unit tests
+        run: pnpm run test-unit compiler
+
+      - name: Run ssr unit tests
+        run: pnpm run test-unit server-renderer
+
+  e2e-test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Setup cache for Chromium binary
+        uses: actions/cache@v4
+        with:
+          path: ~/.cache/puppeteer
+          key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4.0.0
+
+      - name: Install Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: '.node-version'
+          cache: 'pnpm'
+
+      - run: pnpm install
+      - run: node node_modules/puppeteer/install.mjs
+
+      - name: Run e2e tests
+        run: pnpm run test-e2e
+
+      - name: verify treeshaking
+        run: node scripts/verify-treeshaking.js
+
+  lint-and-test-dts:
+    runs-on: ubuntu-latest
+    env:
+      PUPPETEER_SKIP_DOWNLOAD: 'true'
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4.0.0
+
+      - name: Install Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: '.node-version'
+          cache: 'pnpm'
+
+      - run: pnpm install
+
+      - name: Run eslint
+        run: pnpm run lint
+
+      - name: Run prettier
+        run: pnpm run format-check
+
+      - name: Run type declaration tests
+        run: pnpm run test-dts

+ 21 - 0
CHANGELOG.md

@@ -1,3 +1,24 @@
+## [3.4.37](https://github.com/vuejs/core/compare/v3.4.36...v3.4.37) (2024-08-08)
+
+
+### Bug Fixes
+
+* **compiler-core:** use `isProp.arg.loc` instead of `isProp.loc` ([#11547](https://github.com/vuejs/core/issues/11547)) ([236fb7a](https://github.com/vuejs/core/commit/236fb7abebe567b73826a3ddc2120f3273377ba0))
+* **custom-element:** fix custom-element double render on immediate prop change ([978ff3c](https://github.com/vuejs/core/commit/978ff3c1dbff9c93ec284c1804d3c77331ea33f8)), closes [#9885](https://github.com/vuejs/core/issues/9885) [#11335](https://github.com/vuejs/core/issues/11335)
+* **defineModel:** detect changes respect custom getter and setter ([#11543](https://github.com/vuejs/core/issues/11543)) ([e042888](https://github.com/vuejs/core/commit/e0428884b57ac834274045bd33841263aeae259e)), closes [#11541](https://github.com/vuejs/core/issues/11541) [#11526](https://github.com/vuejs/core/issues/11526) [#11527](https://github.com/vuejs/core/issues/11527)
+* **keep-alive:** avoid cache suspense comment root ([#11479](https://github.com/vuejs/core/issues/11479)) ([a917c05](https://github.com/vuejs/core/commit/a917c0539cdc55c0188ca91f70b6ff79fee13ed9))
+* **keep-alive:** fix render error in cached is undefined ([#11496](https://github.com/vuejs/core/issues/11496)) ([81351dc](https://github.com/vuejs/core/commit/81351dc7fbdabcfa0f545f7d924c31a3c367e496)), closes [#11427](https://github.com/vuejs/core/issues/11427) [#11431](https://github.com/vuejs/core/issues/11431)
+* Revert "fix(types/ref): allow getter and setter types to be unrelated ([#11442](https://github.com/vuejs/core/issues/11442))" ([b1abac0](https://github.com/vuejs/core/commit/b1abac06cdb198bd72f8e614b1f68b92e1c78339))
+* Revert "fix(types/ref): correct type inference for nested refs ([#11536](https://github.com/vuejs/core/issues/11536))" ([3a56315](https://github.com/vuejs/core/commit/3a56315f94bc0e11cfbb288b65482ea8fc3a39b4))
+* **runtime-core:** fix warning for missing event handler ([#11489](https://github.com/vuejs/core/issues/11489)) ([e359ff0](https://github.com/vuejs/core/commit/e359ff0046286aee03fe31656c023677be457e07)), closes [#4803](https://github.com/vuejs/core/issues/4803) [#8268](https://github.com/vuejs/core/issues/8268)
+* **runtime-core:** prioritize using the provides from currentApp in nested createApp ([#11502](https://github.com/vuejs/core/issues/11502)) ([7e75de0](https://github.com/vuejs/core/commit/7e75de002f08076a02c9361a58fa1d0af1772964)), closes [#11488](https://github.com/vuejs/core/issues/11488)
+* **runtime-dom:** apply css vars before mount ([#11538](https://github.com/vuejs/core/issues/11538)) ([fdc2a31](https://github.com/vuejs/core/commit/fdc2a31dbd4196d6432be16767a1bfdab1240d49)), closes [#11533](https://github.com/vuejs/core/issues/11533)
+* **ssr:** ensure content is valid when rendering normal slot ([#11491](https://github.com/vuejs/core/issues/11491)) ([6c90324](https://github.com/vuejs/core/commit/6c903248703e2413c6197b9ad4d535f31c8eac39)), closes [#11326](https://github.com/vuejs/core/issues/11326)
+* **types/ref:** correct type inference for nested refs ([#11536](https://github.com/vuejs/core/issues/11536)) ([536f623](https://github.com/vuejs/core/commit/536f62332c455ba82ef2979ba634b831f91928ba)), closes [#11532](https://github.com/vuejs/core/issues/11532) [#11537](https://github.com/vuejs/core/issues/11537)
+* **types:** allow `DirectiveArguments` third parameter to accept undefined ([#11540](https://github.com/vuejs/core/issues/11540)) ([1058ce8](https://github.com/vuejs/core/commit/1058ce8e747ce606e5e86fca5a2acce3c12a0846))
+
+
+
 ## [3.4.36](https://github.com/vuejs/core/compare/v3.4.35...v3.4.36) (2024-08-06)
 
 ### Bug Fixes

+ 1 - 1
packages/compiler-core/src/transforms/transformElement.ts

@@ -250,7 +250,7 @@ export function resolveComponentType(
         exp = isProp.exp
         if (!exp) {
           // #10469 handle :is shorthand
-          exp = createSimpleExpression(`is`, false, isProp.loc)
+          exp = createSimpleExpression(`is`, false, isProp.arg!.loc)
           if (!__BROWSER__) {
             exp = isProp.exp = processExpression(exp, context)
           }

+ 10 - 0
packages/compiler-sfc/__tests__/parse.spec.ts

@@ -425,5 +425,15 @@ h1 { color: red }
         `At least one <template> or <script> is required in a single file component`,
       )
     })
+
+    test('should throw error if template functional is given', () => {
+      assertWarning(
+        parse(`<template functional></template>`).errors,
+        `<template functional> is no longer supported in Vue 3, since ` +
+          `functional components no longer have significant performance ` +
+          `difference from stateful ones. Just use a normal <template> ` +
+          `instead.`,
+      )
+    })
   })
 })

+ 92 - 0
packages/runtime-core/__tests__/helpers/useModel.spec.ts

@@ -657,4 +657,96 @@ describe('useModel', () => {
     expect(setValue).toBeCalledTimes(2)
     expect(msg.value).toBe(defaultVal)
   })
+
+  // #11526
+  test('custom getter', () => {
+    let changeChildMsg!: (val: boolean) => void
+    const getter = (value: boolean) => !value
+
+    const Comp = defineComponent({
+      props: ['msg'],
+      emits: ['update:msg'],
+      setup(props) {
+        const childMsg = useModel(props, 'msg', {
+          get: getter,
+          set: value => !value,
+        })
+        changeChildMsg = (val: boolean) => (childMsg.value = val)
+        return () => {
+          return childMsg.value
+        }
+      },
+    })
+
+    const defaultVal = false
+    const msg = ref(defaultVal)
+    const Parent = defineComponent({
+      setup() {
+        return () =>
+          h(Comp, {
+            msg: msg.value,
+            'onUpdate:msg': val => {
+              msg.value = val
+            },
+          })
+      },
+    })
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+
+    changeChildMsg(!getter(msg.value))
+    expect(msg.value).toBe(true)
+
+    changeChildMsg(!getter(msg.value))
+    expect(msg.value).toBe(false)
+  })
+
+  // #11541
+  test('custom setter', () => {
+    let changeChildMsg!: (val: boolean) => void
+
+    const Comp = defineComponent({
+      props: ['msg'],
+      emits: ['update:msg'],
+      setup(props) {
+        const childMsg = useModel(props, 'msg', {
+          set: value => {
+            if (value === msg.value) {
+              return null
+            } else {
+              return value
+            }
+          },
+        })
+        changeChildMsg = (val: boolean) => (childMsg.value = val)
+        return () => {
+          return childMsg.value
+        }
+      },
+    })
+
+    const defaultVal = false
+    const msg = ref(defaultVal)
+    const Parent = defineComponent({
+      setup() {
+        return () =>
+          h(Comp, {
+            msg: msg.value,
+            'onUpdate:msg': val => {
+              msg.value = val
+            },
+          })
+      },
+    })
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+
+    changeChildMsg(true)
+    expect(msg.value).toBe(true)
+
+    changeChildMsg(true)
+    expect(msg.value).toBe(null)
+  })
 })

+ 3 - 2
packages/runtime-core/src/helpers/useModel.ts

@@ -51,8 +51,9 @@ export function useModel(
       },
 
       set(value) {
+        const emittedValue = options.set ? options.set(value) : value
         if (
-          !hasChanged(value, localValue) &&
+          !hasChanged(emittedValue, localValue) &&
           !(prevSetValue !== EMPTY_OBJ && hasChanged(value, prevSetValue))
         ) {
           return
@@ -74,7 +75,7 @@ export function useModel(
           localValue = value
           trigger()
         }
-        const emittedValue = options.set ? options.set(value) : value
+
         i.emit(`update:${name}`, emittedValue)
         // #10279: if the local value is converted via a setter but the value
         // emitted to parent was the same, the parent will not trigger any

+ 28 - 0
packages/sfc-playground/src/VersionSelect.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { onMounted, ref } from 'vue'
+import Copy from './icons/Copy.vue'
 
 const expanded = ref(false)
 const versions = ref<string[]>()
@@ -53,6 +54,12 @@ function setVersion(v: string) {
   expanded.value = false
 }
 
+function copyVersion(v: string) {
+  window.navigator.clipboard.writeText(v).then(() => {
+    alert('Vue version has been copied to clipboard.')
+  })
+}
+
 onMounted(() => {
   window.addEventListener('click', () => {
     expanded.value = false
@@ -76,11 +83,19 @@ onMounted(() => {
       <li v-if="!versions"><a>loading versions...</a></li>
       <li
         v-for="(ver, index) of versions"
+        class="versions-item"
         :class="{
           active: ver === version || (version === 'latest' && index === 0),
         }"
       >
         <a @click="setVersion(ver)">v{{ ver }}</a>
+        <button
+          title="Copy Version"
+          class="version-copy"
+          @click="copyVersion(`v${ver}`)"
+        >
+          <Copy />
+        </button>
       </li>
       <div @click="expanded = false">
         <slot />
@@ -120,4 +135,17 @@ onMounted(() => {
 .versions .active a {
   color: var(--green);
 }
+
+.versions .versions-item {
+  display: flex;
+  justify-content: space-between;
+}
+
+.versions .versions-item .version-copy {
+  display: none;
+}
+
+.versions .versions-item:hover .version-copy {
+  display: block;
+}
 </style>

+ 14 - 0
packages/sfc-playground/src/icons/Copy.vue

@@ -0,0 +1,14 @@
+<template>
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    width="1.3em"
+    height="1.3em"
+    viewBox="0 0 24 24"
+  >
+    <path fill="currentColor" d="M8 7h11v14H8z" opacity=".3" />
+    <path
+      fill="currentColor"
+      d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m0 16H8V7h11z"
+    />
+  </svg>
+</template>

+ 97 - 69
scripts/release.js

@@ -51,6 +51,13 @@ const { values: args, positionals } = parseArgs({
     skipPrompts: {
       type: 'boolean',
     },
+    publish: {
+      type: 'boolean',
+      default: false,
+    },
+    publishOnly: {
+      type: 'boolean',
+    },
   },
 })
 
@@ -247,41 +254,7 @@ async function main() {
     }
   }
 
-  if (!skipTests) {
-    step('Checking CI status for HEAD...')
-    let isCIPassed = await getCIResult()
-    skipTests ||= isCIPassed
-
-    if (isCIPassed) {
-      if (!skipPrompts) {
-        /** @type {{ yes: boolean }} */
-        const { yes: promptSkipTests } = await prompt({
-          type: 'confirm',
-          name: 'yes',
-          message: `CI for this commit passed. Skip local tests?`,
-        })
-        skipTests = promptSkipTests
-      } else {
-        skipTests = true
-      }
-    } else if (skipPrompts) {
-      throw new Error(
-        'CI for the latest commit has not passed yet. ' +
-          'Only run the release workflow after the CI has passed.',
-      )
-    }
-  }
-
-  if (!skipTests) {
-    step('\nRunning tests...')
-    if (!isDryRun) {
-      await run('pnpm', ['run', 'test', '--run'])
-    } else {
-      console.log(`Skipped (dry run)`)
-    }
-  } else {
-    step('Tests skipped.')
-  }
+  await runTestsIfNeeded()
 
   // update all package versions and inter-dependencies
   step('\nUpdating cross dependencies...')
@@ -291,16 +264,6 @@ async function main() {
   )
   versionUpdated = true
 
-  // build all packages with types
-  step('\nBuilding all packages...')
-  if (!skipBuild && !isDryRun) {
-    await run('pnpm', ['run', 'build', '--withTypes'])
-    step('\nTesting built types...')
-    await run('pnpm', ['test-dts-only'])
-  } else {
-    console.log(`(skipped)`)
-  }
-
   // generate changelog
   step('\nGenerating changelog...')
   await run(`pnpm`, ['run', 'changelog'])
@@ -337,29 +300,15 @@ async function main() {
   }
 
   // publish packages
-  step('\nPublishing packages...')
-
-  const additionalPublishFlags = []
-  if (isDryRun) {
-    additionalPublishFlags.push('--dry-run')
-  }
-  if (isDryRun || skipGit) {
-    additionalPublishFlags.push('--no-git-checks')
-  }
-  // bypass the pnpm --publish-branch restriction which isn't too useful to us
-  // otherwise it leads to a prompt and blocks the release script
-  const branch = await getBranch()
-  if (branch !== 'main') {
-    additionalPublishFlags.push('--publish-branch', branch)
-  }
-  // add provenance metadata when releasing from CI
-  // canary release commits are not pushed therefore we don't need to add provenance
-  if (process.env.CI && !isCanary) {
-    additionalPublishFlags.push('--provenance')
-  }
-
-  for (const pkg of packages) {
-    await publishPackage(pkg, targetVersion, additionalPublishFlags)
+  if (args.publish) {
+    await buildPackages()
+    await publishPackages(targetVersion)
+  } else {
+    console.log(
+      pico.yellow(
+        '\nPublish step skipped (will be done in GitHub actions on successful push)',
+      ),
+    )
   }
 
   // push to GitHub
@@ -386,6 +335,44 @@ async function main() {
   console.log()
 }
 
+async function runTestsIfNeeded() {
+  if (!skipTests) {
+    step('Checking CI status for HEAD...')
+    let isCIPassed = await getCIResult()
+    skipTests ||= isCIPassed
+
+    if (isCIPassed) {
+      if (!skipPrompts) {
+        /** @type {{ yes: boolean }} */
+        const { yes: promptSkipTests } = await prompt({
+          type: 'confirm',
+          name: 'yes',
+          message: `CI for this commit passed. Skip local tests?`,
+        })
+        skipTests = promptSkipTests
+      } else {
+        skipTests = true
+      }
+    } else if (skipPrompts) {
+      throw new Error(
+        'CI for the latest commit has not passed yet. ' +
+          'Only run the release workflow after the CI has passed.',
+      )
+    }
+  }
+
+  if (!skipTests) {
+    step('\nRunning tests...')
+    if (!isDryRun) {
+      await run('pnpm', ['run', 'test', '--run'])
+    } else {
+      console.log(`Skipped (dry run)`)
+    }
+  } else {
+    step('Tests skipped.')
+  }
+}
+
 async function getCIResult() {
   try {
     const sha = await getSha()
@@ -492,6 +479,40 @@ function updateDeps(pkg, depType, version, getNewPackageName) {
   })
 }
 
+async function buildPackages() {
+  step('\nBuilding all packages...')
+  if (!skipBuild) {
+    await run('pnpm', ['run', 'build', '--withTypes'])
+  } else {
+    console.log(`(skipped)`)
+  }
+}
+
+/**
+ * @param {string} version
+ */
+async function publishPackages(version) {
+  // publish packages
+  step('\nPublishing packages...')
+
+  const additionalPublishFlags = []
+  if (isDryRun) {
+    additionalPublishFlags.push('--dry-run')
+  }
+  if (isDryRun || skipGit || process.env.CI) {
+    additionalPublishFlags.push('--no-git-checks')
+  }
+  // add provenance metadata when releasing from CI
+  // canary release commits are not pushed therefore we don't need to add provenance
+  if (process.env.CI && !isCanary) {
+    additionalPublishFlags.push('--provenance')
+  }
+
+  for (const pkg of packages) {
+    await publishPackage(pkg, version, additionalPublishFlags)
+  }
+}
+
 /**
  * @param {string} pkgName
  * @param {string} version
@@ -541,7 +562,14 @@ async function publishPackage(pkgName, version, additionalFlags) {
   }
 }
 
-main().catch(err => {
+async function publishOnly() {
+  await buildPackages()
+  await publishPackages(currentVersion)
+}
+
+const fnToRun = args.publishOnly ? publishOnly : main
+
+fnToRun().catch(err => {
   if (versionUpdated) {
     // revert to current version on failed releases
     updateVersions(currentVersion)