diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.yml b/.github/ISSUE_TEMPLATE/Bug_Report.yml index ca0f71121..4702e9352 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_Report.yml @@ -1,40 +1,73 @@ name: Bug report description: Report a reproducible winTerm problem without sensitive terminal data. title: "[Bug]: " -labels: [bug] +labels: + - bug body: - type: markdown attributes: - value: Do not include passwords, tokens, private terminal output, proprietary source code, or confidential Workspace files. + value: Do not include passwords, tokens, private terminal output, commands, proprietary source code, memory dumps, or confidential Workspace files. - type: input id: version - attributes: { label: winTerm version, placeholder: 0.6.0-beta.1 } - validations: { required: true } + attributes: + label: winTerm version + placeholder: 1.0.0 or exact commit + validations: + required: true - type: dropdown id: install - attributes: { label: Installation type, options: [MSIX, Portable, Development build, Other] } - validations: { required: true } + attributes: + label: Installation type + options: + - Public MSIX + - Draft MSIX + - Development build + - Other + validations: + required: true - type: dropdown id: architecture - attributes: { label: Architecture, options: [x64, ARM64, Unknown] } - validations: { required: true } + attributes: + label: Architecture + options: + - x64 + - ARM64 + - Unknown + validations: + required: true - type: textarea id: environment - attributes: { label: Windows version, description: Include edition and build only. } - - type: input - id: shell - attributes: { label: Shell and feature area, placeholder: PowerShell 7 - Workspace Restore } + attributes: + label: Windows and Shell versions + description: Include edition and build only; redact user and machine names. + validations: + required: true - type: textarea id: reproduce - attributes: { label: Steps to reproduce } - validations: { required: true } + attributes: + label: Steps to reproduce + description: Replace private commands, paths, and data with safe examples. + validations: + required: true - type: textarea id: expected - attributes: { label: Expected and actual behavior } - validations: { required: true } + attributes: + label: Expected and actual behavior + validations: + required: true - type: dropdown id: frequency - attributes: { label: Frequency, options: [Always, Often, Once, Unknown] } + attributes: + label: Frequency + options: + - Always + - Often + - Once + - Unknown + validations: + required: true - type: textarea id: diagnostics - attributes: { label: Diagnostic bundle or screenshots, description: Attach only a reviewed, redacted bundle. } + attributes: + label: Reviewed diagnostic bundle or screenshots + description: Attach only a user-generated, reviewed, redacted bundle. diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.yml b/.github/ISSUE_TEMPLATE/Feature_Request.yml index 1ec2fd32b..737b61fe4 100644 --- a/.github/ISSUE_TEMPLATE/Feature_Request.yml +++ b/.github/ISSUE_TEMPLATE/Feature_Request.yml @@ -1,20 +1,33 @@ -name: "Feature Request/Idea 🚀" -description: Suggest a new feature or improvement (this does not mean you have to implement it) -labels: [Issue-Feature] -type: Feature +name: Feature request +description: Propose a post-1.0 improvement without expanding the active Stable release. +title: "[Feature]: " +labels: + - enhancement body: -- type: textarea - attributes: - label: Description of the new feature - description: A clear and concise description of what the problem is that the new feature would solve. - placeholder: | - ... and guess what? I have four Terminals. And I have a hover car, and a hover house. And my computer's a runner, and it shows. - validations: - required: true - -- type: textarea - attributes: - label: Proposed technical implementation details - description: This field is optional. If you have any ideas, let us know! - validations: - required: false + - type: markdown + attributes: + value: winTerm 1.0 is feature-frozen. Accepted ideas are tracked for a later release and are not added to the Stable branch. + - type: textarea + id: problem + attributes: + label: Problem + description: Describe the user problem without private commands, output, or Workspace data. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed behavior + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: checkboxes + id: scope + attributes: + label: Scope confirmation + options: + - label: I understand this request is not part of winTerm 1.0 Stable release work. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0b4ddd0a2..9a9291018 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,12 +1,9 @@ blank_issues_enabled: false contact_links: - - name: Microsoft Security Response Center 🔐 - url: https://msrc.microsoft.com/create-report - about: Please report security vulnerabilities here. - - name: Windows Terminal Documentation issue 📄 - url: https://github.com/MicrosoftDocs/terminal/issues/new - about: Report issues with the documentation for the Windows Terminal (in docs.microsoft.com/windows/terminal) - - name: Console Documentation issue 📄 - url: https://github.com/MicrosoftDocs/console-docs/issues/new - about: Report issues with the documentation for the Console (in docs.microsoft.com/windows/console) + - name: Report a winTerm security vulnerability privately + url: https://github.com/HelloThisWorld/winTerm/security + about: Use the Security tab and Private Vulnerability Reporting. Do not open a public Issue. + - name: winTerm support policy + url: https://github.com/HelloThisWorld/winTerm/blob/main/SUPPORT.md + about: Review supported releases, platforms, Shells, and reporting guidance. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d8735c42b..e81f9596f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,14 +1,23 @@ -## Summary of the Pull Request +## Summary -## References and Relevant Issues +## Related issues -## Detailed Description of the Pull Request / Additional comments +Closes # -## Validation Steps Performed +## Detailed changes -## PR Checklist -- [ ] Closes #xxx -- [ ] Tests added/passed -- [ ] Documentation updated - - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx -- [ ] Schema updated (if necessary) +## Validation performed + +List exact commands, environments, results, and any tests that were not available. + +## Checklist + +- [ ] The change is focused and does not include unrelated formatting. +- [ ] Tests were added or updated where appropriate. +- [ ] All tests claimed above actually ran and passed. +- [ ] User-facing behavior and limitations are documented in this repository. +- [ ] Version or schema changes include compatibility and migration notes. +- [ ] Package identity, `winterm.exe`, and Microsoft Terminal coexistence remain isolated. +- [ ] No command text, terminal output, clipboard content, credentials, or private paths are logged. +- [ ] New source and script files contain the appropriate MIT license header. +- [ ] I did not include generated build output, secrets, certificates, or local absolute paths. diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..8abb698ab --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,35 @@ +changelog: + exclude: + labels: + - duplicate + - internal + - skip-changelog + authors: + - dependabot + categories: + - title: Security + labels: + - security + - title: Accessibility + labels: + - accessibility + - title: Performance + labels: + - performance + - title: Fixes + labels: + - bug + - fix + - title: Documentation + labels: + - documentation + - title: Dependencies + labels: + - dependencies + - title: Features + labels: + - feature + - enhancement + - title: Other changes + labels: + - "*" diff --git a/.github/workflows/addToProject.yml b/.github/workflows/addToProject.yml deleted file mode 100644 index c8a5c6832..000000000 --- a/.github/workflows/addToProject.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Add triaged bugs & tasks to project board -# https://github.com/actions/add-to-project - -on: - issues: - types: - - labeled - - unlabeled - -permissions: {} -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v1.0.2 - with: - project-url: https://github.com/orgs/microsoft/projects/159 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} - labeled: Issue-Feature, Needs-Triage, Needs-Author-Feedback, Issue-Scenario - label-operator: NOT diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 203a07e50..1b33f7997 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,55 +1,452 @@ -name: winTerm draft release +name: winTerm stable release on: push: tags: - - 'v0.6.0-beta.*' + - 'v1.0.0' workflow_dispatch: inputs: - version: - description: Beta version without the v prefix + mode: + description: Prepare a new draft or publish an already verified signed draft required: true - default: 0.6.0-beta.1 + default: prepare + type: choice + options: + - prepare + - publish + publish_arm64: + description: Include ARM64 only after native build, install, and launch validation + required: true + default: false + type: boolean + arm64_validation_commit: + description: Exact v1.0.0 commit validated on native ARM64 hardware + required: false + type: string + validated_commit_sha: + description: Exact v1.0.0 commit that passed clean install, upgrade, uninstall, alias, and coexistence validation + required: false + type: string permissions: - contents: write + contents: read + +concurrency: + group: winterm-v1.0.0-stable-release + cancel-in-progress: false + +env: + WINTERM_VERSION: 1.0.0 + WINTERM_TAG: v1.0.0 + WINTERM_UPSTREAM_REVISION: 1cea42d433253d95c4487a3037db48197b5e72f4 jobs: - release: - name: Build draft release artifacts + validate: + name: Validate exact release source runs-on: windows-2022 - timeout-minutes: 240 + timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - name: Check out the tagged source without persisted credentials + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Resolve release version - id: version + persist-credentials: false + + - name: Verify tag, commit, and clean checkout shell: pwsh run: | - $version = if ('${{ github.event_name }}' -eq 'push') { '${{ github.ref_name }}'.Substring(1) } else { '${{ inputs.version }}' } - if ($version -notmatch '^0\.6\.0-beta\.[0-9]+$') { throw "Unsupported release version '$version'." } - "value=$version" >> $env:GITHUB_OUTPUT - - name: Validate release source + if ('${{ github.ref }}' -ne 'refs/tags/v1.0.0') { + throw 'Stable release runs must check out refs/tags/v1.0.0.' + } + $tagCommit = (git rev-list -n 1 v1.0.0).Trim() + if ($tagCommit -ne '${{ github.sha }}') { + throw 'v1.0.0 does not point to the checked-out release commit.' + } + if (-not [string]::IsNullOrWhiteSpace((git status --porcelain))) { + throw 'The release checkout is not clean.' + } + .\scripts\winterm\verify-version.ps1 -RequireTag + + - name: Run static, security, privacy, and release-workflow gates shell: pwsh run: .\scripts\winterm\test.ps1 -Suite Smoke -Configuration Release -Platform x64 - - name: Build x64 MSIX + + test-x64: + name: Build and test x64 Release + if: github.event_name == 'push' || inputs.mode == 'prepare' + needs: validate + runs-on: windows-2022 + timeout-minutes: 240 + steps: + - name: Check out the tagged source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Build x64 Release and compiled tests shell: pwsh - run: .\scripts\winterm\package.ps1 -Platform x64 - - name: Build ARM64 MSIX + run: .\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -IncludeTests + + - name: Run relevant compiled tests + shell: pwsh + run: .\scripts\winterm\test.ps1 -Suite Relevant -Configuration Release -Platform x64 + + prepare: + name: Build, sign, attest, and verify Draft Release + if: github.event_name == 'push' || inputs.mode == 'prepare' + needs: + - validate + - test-x64 + runs-on: windows-2022 + timeout-minutes: 300 + environment: winterm-stable-release + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Check out the tagged source without persisted credentials + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Record the UTC build timestamp shell: pwsh - run: .\scripts\winterm\package.ps1 -Platform ARM64 - - name: Generate release metadata + run: | + "WINTERM_BUILD_TIMESTAMP=$([DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ'))" >> $env:GITHUB_ENV + + - name: Embed traceable build metadata shell: pwsh - run: .\scripts\winterm\generate-release-artifacts.ps1 -Version '${{ steps.version.outputs.value }}' -ArtifactDirectory src\cascadia\CascadiaPackage\AppPackages -CommitSha '${{ github.sha }}' -RequiredArchitecture x64,ARM64 - - name: Upload verified release artifacts - uses: actions/upload-artifact@v6 + run: | + .\scripts\winterm\generate-build-metadata.ps1 ` + -CommitSha '${{ github.sha }}' ` + -BuildTimestamp $env:WINTERM_BUILD_TIMESTAMP ` + -WorkflowRunId '${{ github.run_id }}' + + - name: Select signing mode and protect the package publisher + id: signing + shell: pwsh + env: + SIGNING_PFX_BASE64: ${{ secrets.WINTERM_SIGNING_PFX_BASE64 }} + SIGNING_PFX_PASSWORD: ${{ secrets.WINTERM_SIGNING_PFX_PASSWORD }} + PRODUCTION_PUBLISHER: ${{ vars.WINTERM_PACKAGE_PUBLISHER }} + TIMESTAMP_URL: ${{ vars.WINTERM_TIMESTAMP_URL }} + run: | + $values = @($env:SIGNING_PFX_BASE64, $env:SIGNING_PFX_PASSWORD, $env:PRODUCTION_PUBLISHER, $env:TIMESTAMP_URL) + $present = @($values | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count + if ($present -ne 0 -and $present -ne $values.Count) { + throw 'Signing configuration is incomplete. Configure all protected signing values or none.' + } + + if ($present -eq $values.Count) { + .\scripts\winterm\set-package-publisher.ps1 -Publisher $env:PRODUCTION_PUBLISHER + $certificatePath = Join-Path $env:RUNNER_TEMP 'winterm-release-signing.pfx' + [IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:SIGNING_PFX_BASE64)) + "WINTERM_CERTIFICATE_PATH=$certificatePath" >> $env:GITHUB_ENV + "WINTERM_PUBLISHER=$($env:PRODUCTION_PUBLISHER)" >> $env:GITHUB_ENV + "WINTERM_TIMESTAMP_URL=$($env:TIMESTAMP_URL)" >> $env:GITHUB_ENV + 'WINTERM_SIGNING_STATUS=valid-production-signature' >> $env:GITHUB_ENV + 'signed=true' >> $env:GITHUB_OUTPUT + } else { + 'WINTERM_PUBLISHER=CN=winTerm Development' >> $env:GITHUB_ENV + 'WINTERM_SIGNING_STATUS=unsigned-draft' >> $env:GITHUB_ENV + 'signed=false' >> $env:GITHUB_OUTPUT + } + + - name: Build and inspect x64 MSIX + shell: pwsh + run: | + .\scripts\winterm\package.ps1 -Platform x64 -ExpectedPublisher $env:WINTERM_PUBLISHER + $packages = @(Get-ChildItem -LiteralPath 'src\cascadia\CascadiaPackage\AppPackages' -Recurse -File -Filter '*.msix' | + Where-Object { $_.FullName -match '(?i)x64' }) + if ($packages.Count -ne 1) { + throw "Expected one x64 MSIX, found $($packages.Count)." + } + "WINTERM_X64_PACKAGE=$($packages[0].FullName)" >> $env:GITHUB_ENV + + - name: Sign and timestamp x64 MSIX + if: steps.signing.outputs.signed == 'true' + shell: pwsh + env: + SIGNING_PFX_PASSWORD: ${{ secrets.WINTERM_SIGNING_PFX_PASSWORD }} + run: | + .\scripts\winterm\sign-release-artifacts.ps1 ` + -Path $env:WINTERM_X64_PACKAGE ` + -CertificatePath $env:WINTERM_CERTIFICATE_PATH ` + -CertificatePassword $env:SIGNING_PFX_PASSWORD ` + -ExpectedPublisher $env:WINTERM_PUBLISHER ` + -TimestampUrl $env:WINTERM_TIMESTAMP_URL + + - name: Require native ARM64 validation evidence + if: inputs.publish_arm64 == true + shell: pwsh + env: + ARM64_VALIDATION_COMMIT: ${{ inputs.arm64_validation_commit }} + run: | + if ($env:ARM64_VALIDATION_COMMIT -ne '${{ github.sha }}') { + throw 'ARM64 publication requires native build, install, and launch evidence for the exact release commit.' + } + + - name: Build and inspect ARM64 MSIX + if: inputs.publish_arm64 == true + shell: pwsh + run: | + .\scripts\winterm\package.ps1 -Platform ARM64 -ExpectedPublisher $env:WINTERM_PUBLISHER + $packages = @(Get-ChildItem -LiteralPath 'src\cascadia\CascadiaPackage\AppPackages' -Recurse -File -Filter '*.msix' | + Where-Object { $_.FullName -match '(?i)arm64' }) + if ($packages.Count -ne 1) { + throw "Expected one ARM64 MSIX, found $($packages.Count)." + } + "WINTERM_ARM64_PACKAGE=$($packages[0].FullName)" >> $env:GITHUB_ENV + + - name: Sign and timestamp ARM64 MSIX + if: inputs.publish_arm64 == true && steps.signing.outputs.signed == 'true' + shell: pwsh + env: + SIGNING_PFX_PASSWORD: ${{ secrets.WINTERM_SIGNING_PFX_PASSWORD }} + run: | + .\scripts\winterm\sign-release-artifacts.ps1 ` + -Path $env:WINTERM_ARM64_PACKAGE ` + -CertificatePath $env:WINTERM_CERTIFICATE_PATH ` + -CertificatePassword $env:SIGNING_PFX_PASSWORD ` + -ExpectedPublisher $env:WINTERM_PUBLISHER ` + -TimestampUrl $env:WINTERM_TIMESTAMP_URL + + - name: Create and sign the multi-architecture bundle + if: inputs.publish_arm64 == true + shell: pwsh + env: + SIGNING_PFX_PASSWORD: ${{ secrets.WINTERM_SIGNING_PFX_PASSWORD }} + run: | + $bundle = Join-Path $env:RUNNER_TEMP 'winTerm-1.0.0.msixbundle' + .\scripts\winterm\create-msixbundle.ps1 ` + -X64PackagePath $env:WINTERM_X64_PACKAGE ` + -Arm64PackagePath $env:WINTERM_ARM64_PACKAGE ` + -OutputPath $bundle + if ('${{ steps.signing.outputs.signed }}' -eq 'true') { + .\scripts\winterm\sign-release-artifacts.ps1 ` + -Path $bundle ` + -CertificatePath $env:WINTERM_CERTIFICATE_PATH ` + -CertificatePassword $env:SIGNING_PFX_PASSWORD ` + -ExpectedPublisher $env:WINTERM_PUBLISHER ` + -TimestampUrl $env:WINTERM_TIMESTAMP_URL + } + "WINTERM_BUNDLE=$bundle" >> $env:GITHUB_ENV + + - name: Generate allowlisted release assets, SBOMs, symbols, and checksums + shell: pwsh + run: | + $arguments = @{ + Version = '1.0.0' + X64PackagePath = $env:WINTERM_X64_PACKAGE + SymbolsDirectory = 'bin\x64\Release' + OutputDirectory = 'artifacts\release' + CommitSha = '${{ github.sha }}' + UpstreamRevision = $env:WINTERM_UPSTREAM_REVISION + BuildTimestamp = $env:WINTERM_BUILD_TIMESTAMP + WorkflowRunId = '${{ github.run_id }}' + SigningStatus = $env:WINTERM_SIGNING_STATUS + } + if ('${{ inputs.publish_arm64 }}' -eq 'true') { + $arguments.Arm64PackagePath = $env:WINTERM_ARM64_PACKAGE + $arguments.BundlePath = $env:WINTERM_BUNDLE + } + .\scripts\winterm\generate-release-artifacts.ps1 @arguments + + - name: Verify generated release assets + shell: pwsh + run: | + $architectures = @('x64') + if ('${{ inputs.publish_arm64 }}' -eq 'true') { + $architectures += 'arm64' + } + $arguments = @{ Directory = 'artifacts\release'; Architecture = $architectures } + if ('${{ steps.signing.outputs.signed }}' -eq 'true') { + $arguments.RequireSigned = $true + $arguments.ExpectedPublisher = $env:WINTERM_PUBLISHER + } + .\scripts\winterm\verify-release-assets.ps1 @arguments + + - name: Generate GitHub artifact attestations + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: artifacts/release/* + create-storage-record: false + + - name: Create the immutable-candidate Draft Release + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release view v1.0.0 --repo '${{ github.repository }}' *> $null + if ($LASTEXITCODE -eq 0) { + throw 'A v1.0.0 Release already exists. This workflow never replaces or clobbers Release assets.' + } + + $notesPath = Join-Path $env:RUNNER_TEMP 'winTerm-1.0.0-release-notes.md' + Copy-Item -LiteralPath 'docs\releases\1.0.0.md' -Destination $notesPath + if ($env:WINTERM_SIGNING_STATUS -eq 'unsigned-draft') { + Add-Content -LiteralPath $notesPath -Value "`n> **Release blocker:** this Draft contains an unsigned development package. It must not be published or presented as a stable installer." + } else { + Add-Content -LiteralPath $notesPath -Value "`nSignature gate: production-signed and timestamped for publisher ``$env:WINTERM_PUBLISHER``." + } + + $assets = @( + 'artifacts\release\winTerm-1.0.0-x64.msix', + 'artifacts\release\SHA256SUMS.txt', + 'artifacts\release\THIRD_PARTY_NOTICES.md', + 'artifacts\release\SBOM.spdx.json', + 'artifacts\release\SBOM.cyclonedx.json', + 'artifacts\release\release-metadata.json', + 'artifacts\release\winTerm-1.0.0-symbols.zip', + 'artifacts\release\winTerm-1.0.0-release-notes.md' + ) + if ('${{ inputs.publish_arm64 }}' -eq 'true') { + $assets += 'artifacts\release\winTerm-1.0.0-arm64.msix' + $assets += 'artifacts\release\winTerm-1.0.0.msixbundle' + } + gh release create v1.0.0 @assets ` + --repo '${{ github.repository }}' ` + --verify-tag ` + --title 'winTerm 1.0.0' ` + --notes-file $notesPath ` + --draft + if ($LASTEXITCODE -ne 0) { + throw 'GitHub Draft Release creation failed.' + } + + - name: Re-download and verify every Draft Release asset + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $verificationDirectory = Join-Path $env:RUNNER_TEMP 'winterm-draft-download' + New-Item -ItemType Directory -Path $verificationDirectory | Out-Null + gh release download v1.0.0 --repo '${{ github.repository }}' --dir $verificationDirectory + if ($LASTEXITCODE -ne 0) { + throw 'Draft Release asset download failed.' + } + $architectures = @('x64') + if ('${{ inputs.publish_arm64 }}' -eq 'true') { + $architectures += 'arm64' + } + $arguments = @{ Directory = $verificationDirectory; Architecture = $architectures } + if ('${{ steps.signing.outputs.signed }}' -eq 'true') { + $arguments.RequireSigned = $true + $arguments.ExpectedPublisher = $env:WINTERM_PUBLISHER + } + .\scripts\winterm\verify-release-assets.ps1 @arguments + gh attestation verify (Join-Path $verificationDirectory 'winTerm-1.0.0-x64.msix') --repo '${{ github.repository }}' + if ($LASTEXITCODE -ne 0) { + throw 'Artifact attestation verification failed.' + } + gh release view v1.0.0 --repo '${{ github.repository }}' --json tagName,name,isDraft,isPrerelease,assets,url + + - name: Remove the ephemeral signing file + if: always() + shell: pwsh + run: | + if (-not [string]::IsNullOrWhiteSpace($env:WINTERM_CERTIFICATE_PATH) -and + (Test-Path -LiteralPath $env:WINTERM_CERTIFICATE_PATH)) { + Remove-Item -LiteralPath $env:WINTERM_CERTIFICATE_PATH -Force + } + + publish: + name: Publish an already verified signed Draft + if: github.event_name == 'workflow_dispatch' && inputs.mode == 'publish' + needs: validate + runs-on: windows-2022 + timeout-minutes: 45 + environment: winterm-stable-release + permissions: + contents: write + id-token: write + attestations: read + steps: + - name: Check out the tagged source without persisted credentials + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - name: winTerm-${{ steps.version.outputs.value }}-release - path: src/cascadia/CascadiaPackage/AppPackages/winTerm-${{ steps.version.outputs.value }}-release/* - if-no-files-found: error - - name: Create draft GitHub Release + persist-credentials: false + + - name: Require exact clean-machine validation evidence + shell: pwsh + env: + VALIDATED_COMMIT_SHA: ${{ inputs.validated_commit_sha }} + PRODUCTION_PUBLISHER: ${{ vars.WINTERM_PACKAGE_PUBLISHER }} + run: | + if ($env:VALIDATED_COMMIT_SHA -ne '${{ github.sha }}') { + throw 'Publication requires clean install, upgrade, uninstall, winterm.exe, wt.exe, and Windows Terminal coexistence evidence for the exact release commit.' + } + if ([string]::IsNullOrWhiteSpace($env:PRODUCTION_PUBLISHER) -or + $env:PRODUCTION_PUBLISHER -eq 'CN=winTerm Development' -or + $env:PRODUCTION_PUBLISHER -match '(?i)Microsoft') { + throw 'A protected non-development winTerm package publisher is required.' + } + "WINTERM_PUBLISHER=$($env:PRODUCTION_PUBLISHER)" >> $env:GITHUB_ENV + + - name: Download and verify the Draft Release shell: pwsh env: GH_TOKEN: ${{ github.token }} - run: gh release create "v${{ steps.version.outputs.value }}" --draft --title "winTerm ${{ steps.version.outputs.value }}" --notes-file "src/cascadia/CascadiaPackage/AppPackages/winTerm-${{ steps.version.outputs.value }}-release/release-notes.md" "src/cascadia/CascadiaPackage/AppPackages/winTerm-${{ steps.version.outputs.value }}-release/*" + run: | + $release = gh release view v1.0.0 --repo '${{ github.repository }}' --json tagName,name,isDraft,isPrerelease,assets,url | ConvertFrom-Json + if ($release.tagName -ne 'v1.0.0' -or $release.name -ne 'winTerm 1.0.0' -or -not $release.isDraft -or $release.isPrerelease) { + throw 'The expected stable Draft Release was not found.' + } + + $verificationDirectory = Join-Path $env:RUNNER_TEMP 'winterm-publish-verification' + New-Item -ItemType Directory -Path $verificationDirectory | Out-Null + gh release download v1.0.0 --repo '${{ github.repository }}' --dir $verificationDirectory + if ($LASTEXITCODE -ne 0) { + throw 'Draft Release asset download failed.' + } + $architectures = @('x64') + if (Test-Path -LiteralPath (Join-Path $verificationDirectory 'winTerm-1.0.0-arm64.msix')) { + if ('${{ inputs.arm64_validation_commit }}' -ne '${{ github.sha }}') { + throw 'The Draft contains ARM64, but exact native ARM64 validation evidence was not supplied.' + } + $architectures += 'arm64' + } + .\scripts\winterm\verify-release-assets.ps1 ` + -Directory $verificationDirectory ` + -Architecture $architectures ` + -RequireSigned ` + -ExpectedPublisher $env:WINTERM_PUBLISHER + gh attestation verify (Join-Path $verificationDirectory 'winTerm-1.0.0-x64.msix') --repo '${{ github.repository }}' + if ($LASTEXITCODE -ne 0) { + throw 'Artifact attestation verification failed.' + } + + - name: Publish as the Latest stable Release + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release edit v1.0.0 ` + --repo '${{ github.repository }}' ` + --draft=false ` + --prerelease=false ` + --latest + if ($LASTEXITCODE -ne 0) { + throw 'GitHub Release publication failed.' + } + $release = gh release view v1.0.0 --repo '${{ github.repository }}' --json tagName,name,isDraft,isPrerelease,assets,url | ConvertFrom-Json + if ($release.isDraft -or $release.isPrerelease -or $release.tagName -ne 'v1.0.0') { + throw 'Published Release state verification failed.' + } + "Published Release URL: $($release.url)" >> $env:GITHUB_STEP_SUMMARY + + - name: Re-download and hash-verify the public assets + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $publicDirectory = Join-Path $env:RUNNER_TEMP 'winterm-public-download' + New-Item -ItemType Directory -Path $publicDirectory | Out-Null + gh release download v1.0.0 --repo '${{ github.repository }}' --dir $publicDirectory + if ($LASTEXITCODE -ne 0) { + throw 'Public Release asset download failed.' + } + .\scripts\winterm\verify-checksums.ps1 -Directory $publicDirectory + 'Public assets were re-downloaded and their SHA-256 hashes were verified. The release phase remains open until the clean Windows 11 public-download install and launch smoke test is recorded.' >> $env:GITHUB_STEP_SUMMARY diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 4adf3837b..1f2cbc74c 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -1,27 +1,66 @@ -name: Publish to WinGet - -on: - release: - types: [published] - -env: - REGEX: 'Microsoft\.WindowsTerminal(?:Preview)?_([\d.]+)_8wekyb3d8bbwe\.msixbundle$' - # winget-create will read the following environment variable to access the GitHub token needed for submitting a PR - # See https://aka.ms/winget-create-token - WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }} - -jobs: - publish: - runs-on: windows-latest # Action can only run on Windows - steps: - - name: Publish Windows Terminal ${{ github.event.release.prerelease && 'Preview' || 'Stable' }} - run: | - $assets = '${{ toJSON(github.event.release.assets) }}' | ConvertFrom-Json - $wingetRelevantAsset = $assets | Where-Object { $_.name -like '*.msixbundle' } | Select-Object -First 1 - $regex = [Regex]::New($env:REGEX) - $version = $regex.Match($wingetRelevantAsset.name).Groups[1].Value - - $wingetPackage = "Microsoft.WindowsTerminal${{ github.event.release.prerelease && '.Preview' || '' }}" - - & curl.exe -JLO https://aka.ms/wingetcreate/latest - & .\wingetcreate.exe update $wingetPackage -s -v $version -u $wingetRelevantAsset.browser_download_url +name: Prepare winTerm WinGet manifests + +on: + release: + types: + - published + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + name: Generate and validate public-release manifests + if: github.event_name == 'workflow_dispatch' || github.event.release.tag_name == 'v1.0.0' + runs-on: windows-2022 + timeout-minutes: 20 + steps: + - name: Check out source without persisted credentials + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Download the public x64 installer and derive its actual hash + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $release = gh release view v1.0.0 --repo '${{ github.repository }}' --json tagName,isDraft,isPrerelease,url,assets | ConvertFrom-Json + if ($release.tagName -ne 'v1.0.0' -or $release.isDraft -or $release.isPrerelease) { + throw 'WinGet manifests may be generated only from the public stable v1.0.0 Release.' + } + $asset = @($release.assets | Where-Object { $_.name -eq 'winTerm-1.0.0-x64.msix' }) + if ($asset.Count -ne 1) { + throw 'The public Release does not contain exactly one x64 installer.' + } + + $downloadDirectory = Join-Path $env:RUNNER_TEMP 'winterm-winget-source' + New-Item -ItemType Directory -Path $downloadDirectory | Out-Null + gh release download v1.0.0 --repo '${{ github.repository }}' --pattern 'winTerm-1.0.0-x64.msix' --dir $downloadDirectory + if ($LASTEXITCODE -ne 0) { + throw 'Public installer download failed.' + } + $packagePath = Join-Path $downloadDirectory 'winTerm-1.0.0-x64.msix' + $sha = (Get-FileHash -LiteralPath $packagePath -Algorithm SHA256).Hash + $url = 'https://github.com/${{ github.repository }}/releases/download/v1.0.0/winTerm-1.0.0-x64.msix' + .\scripts\winterm\generate-winget-manifests.ps1 ` + -InstallerUrl $url ` + -InstallerSha256 $sha ` + -OutputDirectory 'packaging\winget\1.0.0' + + - name: Validate WinGet manifests + shell: pwsh + run: | + winget validate 'packaging\winget\1.0.0' + if ($LASTEXITCODE -ne 0) { + throw 'winget validate failed.' + } + + - name: Upload the validated manifest set for manual submission + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: Kaname.winTerm-1.0.0-winget + path: packaging/winget/1.0.0 + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/winterm-full-build.yml b/.github/workflows/winterm-full-build.yml index 8bc2576b0..3b086e11c 100644 --- a/.github/workflows/winterm-full-build.yml +++ b/.github/workflows/winterm-full-build.yml @@ -19,7 +19,9 @@ jobs: steps: - name: Check out source - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Validate appearance assets and licenses shell: pwsh @@ -76,14 +78,16 @@ jobs: steps: - name: Check out source - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Build and verify unsigned development package shell: pwsh run: .\scripts\winterm\package.ps1 -Platform x64 - name: Upload MSIX artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: winTerm-x64-Release-unsigned path: src/cascadia/CascadiaPackage/AppPackages/**/*.msix diff --git a/.github/workflows/winterm-nightly.yml b/.github/workflows/winterm-nightly.yml index 41d4057ad..c09e13b60 100644 --- a/.github/workflows/winterm-nightly.yml +++ b/.github/workflows/winterm-nightly.yml @@ -14,15 +14,17 @@ jobs: runs-on: windows-2022 timeout-minutes: 180 steps: - - uses: actions/checkout@v6 - - name: Run Public Beta source validation + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run winTerm source validation shell: pwsh run: .\scripts\winterm\test.ps1 -Suite Smoke -Configuration Release -Platform x64 - name: Cross-build ARM64 package shell: pwsh run: .\scripts\winterm\package.ps1 -Platform ARM64 - name: Upload ARM64 candidate - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: winTerm-ARM64-cross-build-unsigned path: src/cascadia/CascadiaPackage/AppPackages/**/*.msix diff --git a/.github/workflows/winterm-validation.yml b/.github/workflows/winterm-validation.yml index e40baa1b0..96a27014f 100644 --- a/.github/workflows/winterm-validation.yml +++ b/.github/workflows/winterm-validation.yml @@ -16,7 +16,9 @@ jobs: steps: - name: Check out source - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Validate appearance assets and licenses shell: powershell diff --git a/.gitignore b/.gitignore index 9e8281ee5..ce79b1a90 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,12 @@ # Build results [Dd]ebug/ [Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ +[Rr]elease/ +[Rr]eleases/ +!docs/releases/ +docs/releases/* +!docs/releases/1.0.0.md +x64/ x86/ ARM64/ bld/ @@ -285,4 +288,7 @@ profiles.json *.swp # MSBuildCache -/MSBuildCacheLogs/ \ No newline at end of file +/MSBuildCacheLogs/ + +# Generated by scripts/winterm/generate-build-metadata.ps1 for release builds. +/src/winterm/Branding/ReleaseMetadata.generated.h diff --git a/AGENTS.md b/AGENTS.md index 16770abe9..f47974d77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,22 @@ - Build wrappers must preserve compiler output and must not hide compiler errors. - Do not commit local absolute paths or generated build output. +## Create PR Protocol + +When the user says `create pr`, follow this workflow: + +- Run `git remote -v`, `git status --short`, and `gh auth status` before changing Git state. If GitHub CLI is not authenticated, use `gh auth login` with browser authorization. +- Work on a feature branch, never directly on the default branch. +- Inspect the complete diff, stage only files belonging to the requested work, and run the relevant tests before committing. +- Never force-push. Push the branch with upstream tracking on its first push. +- Check for an existing pull request with the same head branch before creating one. +- Follow `.github/PULL_REQUEST_TEMPLATE.md`. For a substantial body, write a temporary Markdown file and pass it with `--body-file`. +- Create a Draft pull request unless the user explicitly requests a ready-for-review pull request. +- Monitor the pull request checks without rapid polling. Prefer a background `gh pr checks --watch` or `gh run watch --exit-status` process with a 30-second interval. +- On failure, inspect failed-job logs, fix only failures caused by the requested changes, rerun relevant local tests, commit, and push to the same branch so the same pull request updates. +- Remember that `workflow_dispatch` cannot dispatch a workflow that exists only outside the default branch. Use the pull-request trigger for new workflows or merge them before dispatching. +- Before release operations, check for an existing tag or Release and never overwrite an existing release asset. + ## Code Rules - Use English for code, function names, variable names, comments, logs, and error messages. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f695d5e7..4dd141a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,32 @@ # Changelog -## 0.6.0-beta.1 - Unreleased +## 1.0.0 - Release candidate ### Added -- Public Beta release criteria, compatibility matrix, accessibility audit plan, security review, release checklist, and release-notes draft. -- Privacy policy, security reporting policy, and issue templates for bug, compatibility, and crash reports. -- Release-artifact automation for checksums, SBOM metadata, and draft-release preparation. +- Stable compatibility and schema policies for Workspace 2, Docking 1, Shell Protocol 1, Theme 1, and Update Manifest 1. +- Version-consistency, privacy, checksum, package, signing, SBOM, Attestation, and downloaded-asset verification. +- A protected two-stage GitHub Draft and Stable publication workflow. +- Stable release notes, feature status, security, accessibility, performance, support, and release checklists. ### Changed -- Package version is `0.6.0.0`; application release channel is `beta`. +- Application, package, executable metadata, Workspace metadata, and PowerShell module versions are 1.0.0. +- The release channel is `stable`. +- Runtime Docking, cross-process Pane transfer, Update Check, Git Bash integration, and ARM64 remain Disabled until their gates pass. -### Fixed +### Security -- The release process now records unsupported runtime paths rather than presenting source-only validation as full product verification. +- Release assets are allowlisted and must come from the exact `v1.0.0` commit. +- Published installers require a production signature, trusted timestamp, package Publisher match, checksum verification, and clean-machine validation evidence. +- winTerm performs no update request without explicit consent. +- The Release workflow never uses `--clobber` and never silently replaces assets. -### Security +### Known issues -- The Public Beta release flow excludes secrets and requires checksums, identity verification, and redacted diagnostics. +- See [winTerm 1.0.0 release notes](docs/releases/1.0.0.md). +- Stable publication remains blocked until signing, clean installation, upgrade, uninstall, accessibility, runtime, and performance gates pass. -### Known Issues +## 0.6.0-beta.1 - Unpublished baseline -- See [the beta release notes](docs/releases/0.6.0-beta.1.md). +- Added public-beta release infrastructure, compatibility evidence, privacy boundaries, security reporting, Workspace and Docking source validation, and unsigned development packaging. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c72a5749c..e4f27e721 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,9 +1,9 @@ -# Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +# Code of Conduct + +winTerm contributors are expected to provide a welcoming, harassment-free, and professional environment. + +We adopt the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) as the behavioral standard for project spaces and public interactions representing the project. + +Report conduct concerns privately to the repository owner through GitHub. Do not place personal, confidential, or security-sensitive details in a public Issue. Maintainers may remove content or restrict participation when behavior is abusive, threatening, discriminatory, harassing, or persistently disruptive. + +This policy is administered by the independent winTerm project and does not imply Microsoft affiliation or endorsement. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76b68a08d..83479acd8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,179 +1,34 @@ -# Terminal Contributor's Guide - -Below is our guidance for how to report issues, propose new features, and submit contributions via Pull Requests (PRs). - -## Open Development Workflow - -The Windows Terminal team is VERY active in this GitHub Repo. In fact, we live in it all day long and carry out all our development in the open! - -When the team finds issues we file them in the repo. When we propose new ideas or think-up new features, we file new feature requests. When we work on fixes or features, we create branches and work on those improvements. And when PRs are reviewed, we review in public - including all the good, the bad, and the ugly parts. - -The point of doing all this work in public is to ensure that we are holding ourselves to a high degree of transparency, and so that the community sees that we apply the same processes and hold ourselves to the same quality-bar as we do to community-submitted issues and PRs. We also want to make sure that we expose our team culture and "tribal knowledge" that is inherent in any closely-knit team, which often contains considerable value to those new to the project who are trying to figure out "why the heck does this thing look/work like this???" - -### Repo Bot - -The team triages new issues several times a week. During triage, the team uses labels to categorize, manage, and drive the project workflow. - -We employ [a bot engine](./doc/bot.md) to help us automate common processes within our workflow. - -We drive the bot by tagging issues with specific labels which cause the bot engine to close issues, merge branches, etc. This bot engine helps us keep the repo clean by automating the process of notifying appropriate parties if/when information/follow-up is needed, and closing stale issues/PRs after reminders have remained unanswered for several days. - -Therefore, if you do file issues, or create PRs, please keep an eye on your GitHub notifications. If you do not respond to requests for information, your issues/PRs may be closed automatically. - ---- -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC). See [SECURITY.md](./SECURITY.md) for more information. - -## Before you start, file an issue - -Please follow this simple rule to help us eliminate any unnecessary wasted effort & frustration, and ensure an efficient and effective use of everyone's time - yours, ours, and other community members': - -> 👉 If you have a question, think you've discovered an issue, would like to propose a new feature, etc., then find/file an issue **BEFORE** starting work to fix/implement it. - -### Search existing issues first - -Before filing a new issue, search existing open and closed issues first: This project is moving fast! It is likely someone else has found the problem you're seeing, and someone may be working on or have already contributed a fix! - -If no existing item describes your issue/feature, great - please file a new issue: - -### File a new Issue - -* Don't know whether you're reporting an issue or requesting a feature? File an issue -* Have a question that you don't see answered in docs, videos, etc.? File an issue -* Want to know if we're planning on building a particular feature? File an issue -* Got a great idea for a new feature? File an issue/request/idea -* Don't understand how to do something? File an issue -* Found an existing issue that describes yours? Great - upvote and add additional commentary / info / repro-steps / etc. - -When you hit "New Issue", select the type of issue closest to what you want to report/ask/request: -![New issue types](/doc/images/new-issue-template.png) - -### Complete the template - -**Complete the information requested in the issue template, providing as much information as possible**. The more information you provide, the more likely your issue/ask will be understood and implemented. Helpful information includes: - -* What device you're running (inc. CPU type, memory, disk, etc.) -* What build of Windows your device is running - - 👉 Tip: Run the following in PowerShell Core - - ```powershell - C:\> $PSVersionTable.OS - Microsoft Windows 10.0.18909 - ``` - - ... or in Windows PowerShell - - ```powershell - C:\> $PSVersionTable.BuildVersion - - Major Minor Build Revision - ----- ----- ----- -------- - 10 0 18912 1001 - ``` - - ... or Cmd: - - ```cmd - C:\> ver - - Microsoft Windows [Version 10.0.18900.1001] - ``` - -* What tools and apps you're using (e.g. VS 2022, VSCode, etc.) -* Don't assume we're experts in setting up YOUR environment and don't assume we are experts in ``. Teach us to help you! -* **We LOVE detailed repro steps!** What steps do we need to take to reproduce the issue? Assume we love to read repro steps. As much detail as you can stand is probably _barely_ enough detail for us! -* If you're reporting a particular character/glyph not rendering correctly, the specific Unicode codepoint would be MOST welcome (e.g. U+1F4AF, U+4382) -* Prefer error message text where possible or screenshots of errors if text cannot be captured -* We MUCH prefer text command-line script than screenshots of command-line script. -* **If you intend to implement the fix/feature yourself then say so!** If you do not indicate otherwise we will assume that the issue is our to solve, or may label the issue as `Help-Wanted`. - -### DO NOT post "+1" comments - -> ⚠ DO NOT post "+1", "me too", or similar comments - they just add noise to an issue. - -If you don't have any additional info/context to add but would like to indicate that you're affected by the issue, upvote the original issue by clicking its [+😊] button and hitting 👍 (+1) icon. This way we can actually measure how impactful an issue is. - ---- - -## Contributing fixes / features - -If you're able & willing to help fix issues and/or implement features, we'd love your contribution! - -The best place to start is the list of ["walkthroughs"](https://aka.ms/terminal-walkthroughs). This is a collection of issues where we've written a "walkthrough", little guides to help get started with a particular issue. These are usually good first issues, and are a great way to get familiar with the codebase. Additionally, the list of ["good first issue"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22++label%3A%22good+first+issue%22+)s is another set of issues that might be easier for first-time contributors. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find any issue you're interested in and hop in! - -Generally, we categorize issues in the following way, which is largely derived from our old internal work tracking system: -* ["Bugs"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Bug%22+) are parts of the Terminal & Console that are not quite working the right way. There's code to already support some scenario, but it's not quite working right. Fixing these is generally a matter of debugging the broken functionality and fixing the wrong code. -* ["Tasks"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Task%22+) are usually new pieces of functionality that aren't yet implemented for the Terminal/Console. These are usually smaller features, which we believe - - could be a single, atomic PR - - Don't require much design consideration, or we've already written the spec for the larger feature they belong to. -* ["Features"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Feature%22+) are larger pieces of new functionality. These are usually things we believe would require larger discussion of how they should be implemented, or they'll require some complicated new settings. They might just be features that are composed of many individual tasks. Often times, with features, we like to have a spec written before development work is started, to make sure we're all on the same page (see below). - -Bugs and tasks are obviously the easiest to get started with, but don't feel afraid of features either! We've had some community members contribute some amazing "feature"-level work to the Terminal (albeit, with lots of discussion 😄). - - -Often, we like to assign issues that generally belong to somebody's area of expertise to the team member that owns that area. This doesn't mean the community can't jump in -- they should reach out and have a chat with the assignee to see if it'd okay to take. If an issue's been assigned more than a month ago, there's a good chance it's fair game to try yourself. - -### To Spec or not to Spec - -Some issues/features may be quick and simple to describe and understand. For such scenarios, once a team member has agreed with your approach, skip ahead to the section headed "Fork, Branch, and Create your PR", below. - -Small issues that do not require a spec will be labelled `Issue-Bug` or `Issue-Task`. - -However, some issues/features will require careful thought & formal design before implementation. For these scenarios, we'll request that a spec is written and the associated issue will be labeled `Issue-Feature`. More often than not, we'll add such features to the ["Specification Tracker" project](https://github.com/microsoft/terminal/projects/1). - -Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run. - -Specs will be managed in a very similar manner as code contributions so please follow the "[Fork, Branch and Create your PR](CONTRIBUTING.md#fork-clone-branch-and-create-your-pr)" section below. - -### Writing / Contributing-to a Spec - -To write/contribute to a spec: fork, branch and commit via PRs, as you would with any code changes. - -Specs are written in markdown, stored under the [`\doc\specs`](./doc/specs) folder and named `[issue id] - [spec description].md`. - -👉 **It is important to follow the spec templates and complete the requested information**. The available spec templates will help ensure that specs contain the minimum information & decisions necessary to permit development to begin. In particular, specs require you to confirm that you've already discussed the issue/idea with the team in an issue and that you provide the issue ID for reference. - -Team members will be happy to help review specs and guide them to completion. - -### Help Wanted - -Once the team has approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted). - ---- - -## Development - -### Fork, Clone, Branch and Create your PR - -Once you've discussed your proposed feature/fix/etc. with a team member, and you've agreed an approach or a spec has been written and approved, it's time to start development: - -1. Fork the repo if you haven't already -1. Clone your fork locally -1. Create & push a feature branch -1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/) -1. Work on your changes -1. Build and see if it works. Consult [How to build OpenConsole](./doc/building.md) if you have problems. - -### Testing - -Testing is a key component in the development workflow. Both Windows Terminal and Windows Console use TAEF (the Test Authoring and Execution Framework) as the main framework for testing. - -If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [TAEF](./doc/TAEF.md) for more information about how to validate your work locally. - -### Code Review - -When you'd like the team to take a look, (even if the work is not yet fully-complete), mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. - -> ⚠ Remember: **changes you make may affect both Windows Terminal and Windows Console and may end up being re-incorporated into Windows itself!** Because of this, we will treat community PR's with the same level of scrutiny and rigor as commits submitted to the official Windows source by team members and partners. - -### Merge - -Once your code has been reviewed and approved by the requisite number of team members, it will be merged into the main branch. Once merged, your PR will be automatically closed. - ---- - -## Thank you - -Thank you in advance for your contribution! Now, [what's next on the list](https://github.com/microsoft/terminal/labels/Help%20Wanted)? 😜 +# Contributing to winTerm + +winTerm is an independent project based on the Microsoft Terminal open-source codebase. It is not a Microsoft product and is not endorsed by Microsoft. + +## Before starting + +Search existing winTerm Issues and open an Issue before a substantial change. Security vulnerabilities must follow `SECURITY.md`, never a public Issue. + +The v1.0 release branch is feature-frozen. It accepts bug, security, accessibility, performance, compatibility, installation, upgrade, release-engineering, documentation, and diagnostics fixes only. Record new feature ideas in `docs/roadmap-post-1.0.md`. + +## Build and test + +Follow `AGENTS.md`, `docs/build.md`, and upstream Microsoft Terminal toolchain guidance. Keep protected areas unchanged unless an acceptance requirement cannot be met otherwise. Run the smallest relevant checks and record exactly what was and was not tested. + +```powershell +.\scripts\winterm\test.ps1 -Suite Smoke +.\scripts\winterm\build.ps1 -Configuration Debug -Platform x64 -IncludeTests +.\scripts\winterm\test.ps1 -Suite Relevant -Configuration Debug -Platform x64 +``` + +Do not claim a build, package, architecture, signature, installer, or runtime test passed unless it actually ran. + +## Code and commits + +- Use English for code, identifiers, comments, logs, tests, and error messages. +- Follow existing Microsoft Terminal C++, C++/WinRT, XAML, and PowerShell style. +- Add the appropriate MIT header to new source and scripts. +- Avoid large formatting-only changes, generated output, absolute paths, secrets, certificates, and unrelated changes. +- Preserve Microsoft Terminal copyright, licenses, and third-party notices. +- Keep commits focused and do not rewrite public history or force-push release work. + +## Pull requests + +Describe scope, tests, security and privacy impact, accessibility impact, package impact, schema impact, and known limitations. Release pull requests must link the current release checklist and record signing, architecture, install, upgrade, uninstall, and coexistence status. diff --git a/PRIVACY.md b/PRIVACY.md index 6a8684b12..2c56fef26 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,17 +1,23 @@ # winTerm privacy policy -winTerm does not collect general usage analytics. It does not collect or upload command text, terminal output, clipboard content, Workspace contents, directory paths, shell usage frequency, theme selection, or feature-usage events. +winTerm does not collect general usage analytics. It does not collect or upload command text, terminal output, clipboard content, Workspace contents, working-directory paths, shell usage frequency, Theme selection, or feature-usage events. -## Optional features +## Network and optional features -- **Update checks** are off by default. A future enabled check may request only the current application version, architecture, selected release channel, and public release metadata. -- **Crash-report upload** is off by default. Crash metadata remains local unless the user explicitly chooses to share a redacted report. -- **Diagnostic bundles** are created only when the user requests one. They exclude terminal output, commands, clipboard data, environment variables, SSH configuration, full settings, and raw Workspace files by default. +- **Update checks are off.** winTerm 1.0 contains no enabled update request path because explicit user consent is not yet implemented. A future opt-in request may contain only application version, architecture, release channel, and public release metadata. +- **Crash-report upload is off by default** and remains opt-in. Local crash metadata is not uploaded unless the user explicitly chooses a reviewed, redacted report. +- **Diagnostic bundles are created only when the user requests one.** They exclude terminal output, commands, clipboard data, environment variables, SSH configuration, full settings, raw Workspace files, full paths, tokens, email addresses, and connection strings by default. + +winTerm does not upload Workspaces or silently contact a telemetry service on first launch. ## Local data -winTerm may store settings, named Workspaces, recovery snapshots, imported themes, app-private font metadata, local diagnostics, and logs in its own application-data boundary. It does not modify Windows Terminal settings or data. +winTerm may store settings, named Workspaces, recovery snapshots, imported Themes, app-private font metadata, local diagnostics, and logs inside its own application-data boundary. Logs must not include commands, terminal output, or clipboard content. winTerm does not modify Windows Terminal settings or data. + +## User control + +Diagnostic bundles are generated and shared by the user. Review a bundle before attaching it to a report. Crash upload, if introduced after 1.0, must remain disabled until the user opts in and must be independently reversible. ## Removing local data -MSIX uninstall follows Windows platform behavior for application binaries. See [uninstall guidance](docs/user/uninstall.md) before deleting any remaining winTerm-specific local data. Never delete Windows Terminal data, PowerShell profiles, WSL distributions, shell history, or unrelated user folders to remove winTerm. +MSIX uninstall removes application binaries according to Windows package behavior. See [uninstall guidance](docs/user/uninstall.md) before deleting remaining winTerm-specific local data. Never delete Windows Terminal data, PowerShell profiles, WSL distributions, shell history, global fonts, or unrelated user folders to remove winTerm. diff --git a/README.md b/README.md index 4fc05bbbe..41daed6b6 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,84 @@ # winTerm [![Validation](https://github.com/HelloThisWorld/winTerm/actions/workflows/winterm-validation.yml/badge.svg)](https://github.com/HelloThisWorld/winTerm/actions/workflows/winterm-validation.yml) -[![License](https://img.shields.io/github/license/HelloThisWorld/winTerm)](LICENSE) +[![Windows build](https://github.com/HelloThisWorld/winTerm/actions/workflows/winterm-full-build.yml/badge.svg)](https://github.com/HelloThisWorld/winTerm/actions/workflows/winterm-full-build.yml) +[![Stable release](https://img.shields.io/badge/stable%20release-blocked-critical)](docs/release-checklist-v1.0.md) -winTerm is an independent, open-source Windows terminal application based on Microsoft Windows Terminal. It is not affiliated with or endorsed by Microsoft. +winTerm is an independent open-source terminal application based on Microsoft Windows Terminal. -## Public Beta status +winTerm is not affiliated with or endorsed by Microsoft. It does not use Microsoft, Windows, or Windows Terminal logos. -The current target is **winTerm 0.6.0-beta.1**. It prepares a public beta with repeatable validation, release metadata, checksums, SBOM generation, privacy boundaries, security reporting, user documentation, and structured feedback templates. +## Latest stable release -This is not a public release yet. The [release candidate gate](docs/v0.6-rc-gate.md) records the remaining evidence before a draft release can be approved. In particular, x64 CI builds and unsigned development MSIX packaging are verified; clean install, upgrade, uninstall, accessibility, PowerShell 7, WSL, live Docking, and ARM64 native validation still require provisioned Windows testing. +There is no approved public winTerm Stable release yet. The 1.0.0 candidate is blocked on production signing, clean Windows 11 installation, upgrade, uninstall, accessibility, packaged runtime, and performance evidence. A download link is intentionally withheld so an unsigned Draft or Actions artifact is not presented as a user installer. -## What is available +When the real public Release exists and its assets pass re-download verification, this section must link to the repository’s `/releases/latest` page. -- Independent MSIX identity `Kaname.winTerm`, `winterm.exe` alias, and separate application-data boundary. `wt.exe` and Windows Terminal settings are untouched. -- PowerShell 7, Windows PowerShell 5.1, Command Prompt, and dynamically discovered WSL profile foundations. -- Theme and app-private font asset registries, ANSI/Unicode foundations, paste-risk analysis, and conservative local-shell helpers. -- Workspace schema v2, recovery snapshots, named Workspaces, layout validation, empty slots, layout history, and Docking transaction safety models. -- Privacy-safe release and diagnostics foundations: no general telemetry, no default command/output/clipboard collection, and opt-in-only future update or crash upload paths. +## Supported Windows versions -See the [compatibility matrix](docs/compatibility-matrix.md) for evidence by shell and architecture, and the [feature freeze](docs/v0.6-feature-freeze.md) for Stable/Beta/Experimental/Disabled classification. +The intended winTerm 1.0 support target is Windows 11 x64. Windows 10 is unsupported for the Stable commitment. ARM64 remains Disabled until a native ARM64 package is built, installed, and launched successfully and is actually attached to a public Release. -## Important limitations +## Installation -- The Visual Docking runtime adapter is **disabled by default**. The model, preview, rollback, and keyboard descriptions are present, but live transfer needs Windows runtime verification. Cross-process pane transfer is unsupported. -- Windows 11 x64 is the current CI packaging target. ARM64 is not supported until a native package is built and launched successfully. -- The development package is unsigned. Never treat it as a signed public release; verify official release SHA-256 checksums before installation. -- winTerm does not provide general Bash compatibility, remote session persistence, shell restarts to imitate session transfer, cloud synchronization, AI command generation, or a plugin marketplace. +Do not install a development or Draft package as a Stable release. An approved installer must: -## Install and get started +- be downloaded from the public `v1.0.0` Release in this repository; +- be named `winTerm-1.0.0-x64.msix`; +- match `SHA256SUMS.txt`; +- have a trusted production signature and timestamp whose subject matches the package Publisher; +- install without Visual Studio, Git, Developer Mode, registry edits, global font installation, or profile modification. -When an approved release is available, download only the official GitHub Release MSIX and verify `SHA256SUMS.txt` first. The development package uses `Kaname.winTerm` and can coexist with Windows Terminal. +See [installation guidance](docs/user/installation.md). -- [Installation](docs/user/installation.md) -- [Getting started](docs/user/getting-started.md) -- [Shells and Linux compatibility](docs/user/shells.md) -- [Themes and fonts](docs/user/themes-and-fonts.md) -- [Workspaces](docs/user/workspaces.md) -- [Visual Docking](docs/user/visual-docking.md) -- [Keyboard shortcuts](docs/user/keyboard-shortcuts.md) -- [Accessibility](docs/user/accessibility.md) -- [Updates](docs/user/updates.md) -- [Diagnostics](docs/user/diagnostics.md) -- [Privacy](PRIVACY.md) -- [Uninstall](docs/user/uninstall.md) -- [Troubleshooting](docs/user/troubleshooting.md) +## Screenshots -## Build, test, and package +Release screenshots must be captured from the signed winTerm package after branding and accessibility review. No Microsoft or Windows Terminal screenshot is reused as winTerm promotional artwork. Screenshots are therefore withheld while the Stable gate is blocked. -Use a provisioned Windows development environment with Visual Studio 2022, Windows SDK 10.0.22621.0, PowerShell 7, Git, and the repository NuGet client. +## Core features -```powershell -.\scripts\winterm\build.ps1 -Configuration Debug -Platform x64 -.\scripts\winterm\test.ps1 -Suite Relevant -Configuration Debug -Platform x64 -.\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -.\scripts\winterm\package.ps1 -Platform x64 -``` +- independent `Kaname.winTerm` package identity and `winterm.exe` alias; +- PowerShell 7, Windows PowerShell 5.1, CMD, and dynamic WSL profile foundations; +- conservative Linux-style Safe Compatibility for local PowerShell and CMD; +- built-in and open-source Themes with pinned hashes and license records; +- app-private Cascadia programming fonts; +- ANSI color, CJK, Emoji, multiple windows, tabs, panes, and Command Palette foundations inherited from Microsoft Terminal; +- Workspace Schema version 2, Named Workspaces, restore planning, backups, and crash-recovery foundations; +- transactional edge and corner Docking models, keyboard commands, and Layout Undo/Redo; +- multiline and suspicious-paste protection; +- user-generated redacted diagnostics. -The source-only smoke suite can run with Windows PowerShell 5.1: +Runtime evidence is tracked in [feature status](docs/feature-status.md) and the [compatibility matrix](docs/compatibility-matrix.md). Runtime Docking, cross-process Pane transfer, Update Check, Git Bash integration, and ARM64 are Disabled. -```powershell -powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\winterm\test.ps1 -Suite Smoke -``` +## Privacy -Release automation builds x64 and ARM64 candidates, generates SHA-256 checksums and SPDX/CycloneDX metadata, and creates a **draft** GitHub Release. It never automatically publishes a release or stores signing secrets in the repository. +winTerm does not collect command text, terminal output, clipboard content, Workspace contents, working directories, or usage analytics. Crash upload is off and opt-in. Update checks are disabled until explicit consent exists. See [PRIVACY.md](PRIVACY.md). -## Security and feedback +## Release status -Use the [bug](.github/ISSUE_TEMPLATE/bug_report.yml), [compatibility](.github/ISSUE_TEMPLATE/compatibility_report.yml), or [crash](.github/ISSUE_TEMPLATE/crash_report.yml) template. Do not include passwords, tokens, private terminal output, proprietary source code, or unredacted Workspace files. +The exact gate status is recorded in: -Report security vulnerabilities privately under the [security policy](SECURITY.md). See the [privacy policy](PRIVACY.md) before creating or sharing diagnostics. +- [winTerm 1.0 release checklist](docs/release-checklist-v1.0.md); +- [current progress](docs/v1.0-progress.md); +- [security review](docs/security-review-v1.0.md); +- [accessibility audit](docs/accessibility-audit-v1.0.md); +- [performance validation](docs/performance-v1.0.md); +- [release notes](docs/releases/1.0.0.md). -## Release engineering +GitHub Actions may prepare an explicitly blocked Draft Release. It cannot publish an unsigned or untested installer, replace an existing Stable asset, or upload ARM64 without exact native-validation evidence. -- [Feature freeze and classification](docs/v0.6-feature-freeze.md) -- [Performance baseline](docs/performance-baseline.md) -- [Accessibility audit](docs/accessibility-audit.md) -- [Security review](docs/security-review-v0.6.md) -- [Release checklist](docs/release-checklist.md) -- [Release notes draft](docs/releases/0.6.0-beta.1.md) -- [WinGet plan](packaging/winget/README.md) +## Build + +Use the Microsoft Terminal upstream toolchain described in [build guidance](docs/build.md). A release build requires PowerShell 7, the supported Visual Studio toolset, Windows SDK 10.0.22621.0, and the repository’s pinned dependencies. + +```powershell +.\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -IncludeTests +.\scripts\winterm\test.ps1 -Suite Relevant -Configuration Release -Platform x64 +``` -The source baseline is Microsoft Terminal `release-1.25` at commit `1cea42d433253d95c4487a3037db48197b5e72f4`. The `upstream` remote points to `https://github.com/microsoft/terminal.git`. +Local builds and unsigned development MSIX packages are not public releases. -## License and attribution +## License and upstream -winTerm is distributed under the repository [MIT License](LICENSE). It is based on the [Microsoft Terminal open-source project](https://github.com/microsoft/terminal), which is also MIT licensed. Microsoft copyright notices, [third-party notices](NOTICE.md), and package notices are retained. +winTerm retains the Microsoft Terminal MIT license, copyright notices, and third-party notices. The pinned upstream baseline is `release-1.25@1cea42d433253d95c4487a3037db48197b5e72f4`. -Microsoft, Windows, and Windows Terminal are trademarks of Microsoft Corporation. Their names are used only for accurate attribution and technical compatibility documentation. +See [LICENSE](LICENSE), [NOTICE.md](NOTICE.md), [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md), and [upstream synchronization](docs/upstream-sync.md). diff --git a/SECURITY.md b/SECURITY.md index e88aa095b..15183d256 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,18 +2,20 @@ ## Supported versions -Security fixes are assessed for the current `main` branch and supported winTerm beta releases. An unsigned development package is not a public security release. +The latest public winTerm Stable release receives security support. There is currently no supported public Stable release while the 1.0.0 candidate is blocked. An unsigned development or Draft package is not a security release. ## Reporting a vulnerability -Do not open a public issue for a suspected vulnerability and do not include real credentials, tokens, private terminal output, Workspace files, or exploit payloads in public discussions. +Do not open a public Issue for a suspected vulnerability. Do not publish credentials, tokens, private terminal output, commands, Workspace files, memory dumps, certificate material, or exploit payloads. -This repository has GitHub Private Vulnerability Reporting enabled. Use the repository Security tab to submit a private report. If that feature is unavailable, contact a repository maintainer through GitHub and request a private reporting channel before sharing technical details. +Use GitHub Private Vulnerability Reporting from this repository’s Security tab. If it is unavailable, contact the repository owner through GitHub and request a private channel before sharing technical details. ## Scope -Examples include arbitrary command execution, unsafe import processing, secret or clipboard exposure, package identity confusion, unsafe update behavior, diagnostic-redaction failure, docking session loss, and vulnerabilities in winTerm-maintained release automation. +Relevant reports include arbitrary command execution, unsafe imports, secret or clipboard exposure, package identity confusion, unsafe update behavior, diagnostic-redaction failure, Workspace corruption, Docking ownership or live-session loss, and vulnerabilities in winTerm-maintained release automation. -## Response process +## Response -Maintainers will acknowledge a valid report through the private channel, assess impact and affected versions, coordinate a fix, and publish a disclosure only after users have a reasonable opportunity to update. We do not promise a specific response time or a bug bounty. +Maintainers assess impact and affected versions through the private channel, coordinate a fix, and publish disclosure only after users have a reasonable opportunity to update. No response-time or bounty promise is made. + +Microsoft Terminal vulnerabilities that are not introduced by winTerm should also be checked against the upstream Microsoft Terminal security process; do not imply that winTerm can coordinate a Microsoft response. diff --git a/SUPPORT.md b/SUPPORT.md index 275d58a9a..7e11b0ac0 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,17 +1,27 @@ -# Support - -## How to file issues and get help - -This project uses [GitHub issues][gh-issue] to [track bugs][gh-bug] and [feature requests][gh-feature]. Please search the existing issues before filing new issues to avoid duplicates. For new topics, file your bug or feature request as a new issue. - -For help and questions about using this project, please look at the [docs site for Windows Terminal][docs] and our [Contributor's Guide][contributor] if you want to work on Windows Terminal. - -## Microsoft Support Policy - -Support for Windows Terminal is limited to the resources listed above. - -[gh-issue]: https://github.com/microsoft/terminal/issues/new/choose -[gh-bug]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title= -[gh-feature]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Feature&template=Feature_Request.md&title= -[docs]: https://docs.microsoft.com/windows/terminal -[contributor]: ./CONTRIBUTING.md +# winTerm support policy + +## Supported releases + +The latest public Stable winTerm release is supported. A prerelease is supported for testing and feedback only. At present, winTerm 1.0.0 remains a release candidate and is not a supported public Stable release until every publication gate passes. + +## Platforms and architectures + +- Windows 11 x64 is the intended fully supported platform after clean installation, upgrade, uninstall, and runtime validation pass. +- Windows 11 ARM64 is unsupported unless an ARM64 asset is actually published with native build, install, and launch evidence. +- Windows 10 is unsupported for the winTerm 1.0 Stable commitment. + +## Shells and feature levels + +PowerShell 7, Windows PowerShell 5.1, Command Prompt, and WSL have different evidence levels recorded in `docs/compatibility-matrix.md`. Experimental and Disabled features are listed in `docs/feature-status.md`; they do not receive the same compatibility promise as Stable features. + +## Getting help + +Use the [winTerm GitHub Issue chooser](https://github.com/HelloThisWorld/winTerm/issues/new/choose) for reproducible bugs, crashes, compatibility reports, and feature requests. Remove credentials, private terminal output, commands, proprietary source, and unredacted Workspace files before posting. + +## Security + +Do not report a vulnerability in a public Issue. Follow `SECURITY.md` and use GitHub Private Vulnerability Reporting when available. + +## Release cadence and data compatibility + +No fixed release cadence is promised. The latest Stable release receives priority for correctness and security fixes. Data compatibility follows `docs/compatibility-policy.md` and `docs/schema-support-policy.md`; winTerm 1.x reads winTerm 1.0 settings and Workspace Schema version 2 unless an explicit deprecation and migration policy is published. diff --git a/custom.props b/custom.props index f1a846c48..65fb6920c 100644 --- a/custom.props +++ b/custom.props @@ -4,8 +4,8 @@ true 2026 - 1 - 25 + 1 + 0 winTerm Windows Terminal 1033 diff --git a/docs/accessibility-audit-v1.0.md b/docs/accessibility-audit-v1.0.md new file mode 100644 index 000000000..5751fde09 --- /dev/null +++ b/docs/accessibility-audit-v1.0.md @@ -0,0 +1,20 @@ +# winTerm 1.0 accessibility audit + +Static checks cover accessible Docking descriptions, keyboard command descriptors, non-color labels, and privacy-safe diagnostics categories. They do not replace assistive-technology testing. + +| Required flow or environment | Result | Evidence | +| --- | --- | --- | +| Launch, new tab, close tab, split, and pane focus | Not available | Requires a packaged Windows 11 test. | +| Settings, Theme, Font, save and open Workspace | Not available | Requires keyboard-only packaged validation. | +| Keyboard Docking, Dock Tab, Dock Pane | Skipped with reason | Runtime Docking is disabled. Model keyboard commands pass static checks. | +| Layout Undo and Redo | Skipped with reason | Runtime Docking is disabled. Model history passes static checks. | +| Diagnostics and update controls | Not available | Diagnostics requires packaged validation; update check is disabled pending consent UI. | +| Narrator | Not available | No recorded manual session. | +| High Contrast | Not available | No recorded manual session. | +| 100%, 150%, 200%, and 300% text scaling | Not available | No recorded manual session. | +| Keyboard-only usage | Not available | No recorded end-to-end session. | +| Pseudo-localization | Not available | No complete `en-XA` run is recorded. | + +No core control may have invisible focus, communicate state only through color, expose an unlabelled Docking zone or import dialog, read internal GUIDs to a screen reader, or create a keyboard trap. + +Accessibility gate: **Not available**. Public Stable publication is blocked until the core keyboard and Narrator paths pass. diff --git a/docs/branding.md b/docs/branding.md index f403bf035..02ba71ebe 100644 --- a/docs/branding.md +++ b/docs/branding.md @@ -2,7 +2,7 @@ ## Public identity -| Surface | v0.1 value | +| Surface | winTerm 1.0 value | | --- | --- | | Product and display name | `winTerm` | | Package name | `Kaname.winTerm` | @@ -10,45 +10,30 @@ | Execution alias | `winterm.exe` | | Forbidden alias | `wt.exe` | | Development publisher | `CN=winTerm Development` | -| Package version | `0.6.0.0` | -| Release status | `0.6.0-beta.1`; public beta preparation | +| Stable publisher | Injected only from the protected signing environment and required to match the production certificate subject | +| Package version | `1.0.0.0` | +| Application version | `1.0.0` | +| Release channel | `stable` | | Package description | `Independent open-source terminal based on Microsoft Windows Terminal` | -The WinTerm build brand selects these values without changing Microsoft Terminal Stable, Preview, Canary, or Dev package identities. Unique WinTerm COM handoff and shell-extension identifiers are used so package registration does not replace the upstream application. +The WinTerm build brand does not change Microsoft Terminal Stable, Preview, Canary, or Dev package identities. Unique winTerm package, application-data, COM handoff, and shell-extension identities prevent package registration from replacing the upstream application. -The terminal host may retain the internal filename `WindowsTerminal.exe`, and upstream project names, namespaces, API contracts, compatibility identifiers, and source paths remain unchanged. Its WinTerm build metadata uses product name `winTerm` and description `winTerm Terminal Host`. The package maps the upstream launcher output to `winterm.exe`; it does not ship or register `wt.exe` for the WinTerm brand. +The terminal host may retain the internal filename `WindowsTerminal.exe`, and upstream project names, namespaces, API contracts, compatibility identifiers, and source paths remain unchanged. The package maps the launcher output to `winterm.exe`; it neither ships nor registers `wt.exe`. -## URI protocol +## Publisher and signing -No `winterm:` URI protocol is registered in v0.1. The upstream package does not provide a reusable URI activation handler that can be renamed safely as a packaging-only change. Registering a protocol without an activation contract would expose a broken public interface. A protocol can be designed in a later version only with an explicit parser, security review, tests, and compatibility policy. +`CN=winTerm Development` is a source-controlled development placeholder, not a production publisher. The protected Release workflow may replace it only in the clean tag checkout immediately before packaging. The certificate subject must exactly match the injected Publisher. A private key, PFX, password, token, or signing-service credential must never be committed or uploaded as a release asset. -## Signing identity +An unsigned package can exist only in an explicitly blocked Draft Release. It cannot be described or published as a Stable installer. -`CN=winTerm Development` is a local-development placeholder, not a production publisher. The subject of any signing certificate must match the manifest Publisher value. Before a public release, replace both values with the publisher's real identity, validate upgrades and side-by-side behavior, and keep the certificate, private key, password, and secret-provider configuration outside Git. +## Application data and coexistence -## Application data +Packaged winTerm has its own package-local data. Unpackaged winTerm uses `%LOCALAPPDATA%\winTerm`. It does not import, overwrite, or remove Microsoft Windows Terminal settings. Uninstall and Reset operations may target only winTerm-owned data. -The unique package name gives packaged winTerm its own package-local application data. Unpackaged WinTerm builds use `%LOCALAPPDATA%\winTerm`. The WinTerm branch bypasses the upstream Stable settings migration path, so first launch does not import or overwrite Microsoft Windows Terminal settings. +## Public contracts -Shared fragment discovery paths are retained as a compatibility input for third-party profile fragments. Those paths are not used as winTerm's settings or state root. See [Architecture](architecture.md) for the complete state table. - -## User-visible changes - -Conditional WinTerm values cover the package and Start menu display name, description, application fallback title, settings tab title, command-line help heading, tray tooltip, About feedback destination, executable file metadata, package resources, and selected English resource strings. Branding verification intentionally checks these owned surfaces rather than rejecting every technical occurrence of `Windows Terminal`. - -## Retained upstream strings - -Microsoft Terminal names remain where they describe: - -- Copyright and license attribution -- Third-party notices and historical comments -- Internal namespaces, project files, types, and executable host filename -- API, protocol, policy, fragment, and compatibility identifiers -- Alternative upstream build-brand branches and their test fixtures -- Documentation that explains the upstream relationship - -This avoids a high-risk repository-wide rename and keeps upstream integrations reviewable. +The `winterm.exe` alias is frozen for winTerm 1.x. No `winterm:` URI protocol is registered because no reviewed activation contract exists. Shared profile-fragment identifiers retained from upstream are compatibility inputs, not winTerm settings roots or a claim of Microsoft endorsement. ## Artwork -`assets/winterm/icons/winterm.svg` is the canonical original placeholder. It uses a dark rounded rectangle, an accent rail, and original `>_` geometry. It contains no Microsoft, Windows, Windows Terminal, or Apple artwork. Generated PNG and ICO files are committed so Pillow is not a build dependency. Artwork source, sizes, regeneration, review, and licensing are documented in `assets/winterm/icons/README.md`. +`assets/winterm/icons/winterm.svg` is original placeholder geometry. It contains no Microsoft, Windows, Windows Terminal, or Apple logo. Generated PNG and ICO files are committed so image generation is not a build dependency. diff --git a/docs/build.md b/docs/build.md index ce095c053..79c239afd 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,7 +2,7 @@ ## Supported development target -The v0.1 release target is Windows 11 x64. The wrappers accept `ARM64` so a later validation effort does not require a command-line redesign, but ARM64 is not a v0.1 validated target. +The winTerm 1.0 release target is Windows 11 x64. The wrappers accept `ARM64`, but ARM64 remains Disabled until a native build, install, and launch validation passes. ## Required tools @@ -73,10 +73,10 @@ The wrapper builds Release with MSIX generation and signing disabled, locates th ## CI runner -The lightweight workflow uses a GitHub-hosted Windows runner. The full workflow is manual and requires a self-hosted runner with labels `self-hosted`, `windows`, `x64`, and `winterm-build`, the complete toolchain above, and a GitHub Actions runner new enough for `actions/checkout@v6` (2.329.0 or later). The self-hosted workflow does not receive signing secrets. +The validation and full-build workflows use GitHub-hosted Windows runners. The Stable workflow uses the protected `winterm-stable-release` environment and an exact `v1.0.0` checkout. Only the protected prepare job may access production signing configuration; pull-request workflows never receive those secrets. ## Known build issues in the implementation environment -On 2026-07-13, the available implementation machine did not contain PowerShell 7, Visual Studio or `vswhere.exe`, MSBuild, or Windows SDK 10.0.22621.0. The initial unmodified build could not pass upstream module import, and the winTerm Debug and Release wrappers stop with a clear PowerShell 7 prerequisite error. Consequently, no compiler, unit-test binary, application launch, or package result is claimed from that machine. +On 2026-07-18, the available implementation machine did not contain PowerShell 7, Visual Studio or `vswhere.exe`, MSBuild, MakeAppx, SignTool, Winget, or Windows SDK 10.0.22621.0. Consequently, no v1.0 compiler, unit-test binary, application launch, package, signature, or installer result is claimed from that machine. Install the declared prerequisites, reopen a PowerShell 7 prompt, and rerun Smoke, both x64 builds, Relevant tests, and packaging. Do not treat a source-only Smoke pass as a successful application build. diff --git a/docs/compatibility-matrix.md b/docs/compatibility-matrix.md index 0048c1633..dadbc9fd5 100644 --- a/docs/compatibility-matrix.md +++ b/docs/compatibility-matrix.md @@ -1,21 +1,23 @@ -# winTerm Public Beta compatibility matrix +# winTerm 1.0 compatibility matrix This matrix records evidence, not aspirations. `Not tested` is never equivalent to `Supported`. | Shell or platform | Launch | CWD | Marks | Safe commands | Completion | Workspace | Docking | Status | Evidence | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| PowerShell 7 | Not tested | Not tested | Source validated | Source validated | Not tested | Not tested | Disabled | Not tested | Requires a provisioned Windows machine with `pwsh.exe`. | -| Windows PowerShell 5.1 | Source validated | Source validated | Source validated | Source validated | Not tested | Not tested | Disabled | Partial | Smoke and shell-asset validation only. | -| Command Prompt | Source validated | Source validated | N/A | Source validated | N/A | Not tested | Disabled | Partial | Smoke and CMD helper validation only. | -| WSL Ubuntu | Not tested | Not tested | Not tested | Not tested | Not tested | Not tested | Disabled | Not tested | Dynamic discovery is source-validated. | -| WSL Debian | Not tested | Not tested | Not tested | Not tested | Not tested | Not tested | Disabled | Not tested | No installed distribution was available. | -| Git Bash | Not tested | Not tested | Not tested | Not supported | Not tested | Not tested | Disabled | Not tested | No compatibility promise is made. | -| SSH | Not tested | Not tested | Not tested | Disabled | Not tested | Not tested | Disabled | Not tested | Safe command translation is intentionally excluded. | -| Windows 11 x64 | CI build and package | Not tested | Not tested | Source validated | Not tested | Source validated | Disabled | Partial | GitHub Actions validates build and MSIX output. | -| Windows 11 ARM64 | Not tested | Not tested | Not tested | Not tested | Not tested | Not tested | Disabled | Not supported | No native ARM64 artifact has been verified. | +| PowerShell 7 | Not tested | Not tested | Source validated | Source validated | Not tested | Not tested | Disabled | Experimental | Requires a signed package and provisioned Windows 11 machine with `pwsh.exe`. | +| Windows PowerShell 5.1 | Source validated | Source validated | Source validated | Source validated | Not tested | Not tested | Disabled | Experimental | Profile, module, and shell-asset validation only. | +| Command Prompt | Source validated | Source validated | N/A | Source validated | N/A | Not tested | Disabled | Experimental | CMD helper, macro isolation, and precedence validation only. | +| WSL Ubuntu | Not tested | Not tested | Not tested | Disabled | Not tested | Not tested | Disabled | Experimental | Dynamic discovery is source-validated; Safe Compatibility is intentionally off. | +| WSL Debian | Not tested | Not tested | Not tested | Disabled | Not tested | Not tested | Disabled | Experimental | No installed distribution was available. | +| Git Bash | Not tested | Not tested | Not tested | Disabled | Not tested | Not tested | Disabled | Disabled | No 1.0 compatibility promise. | +| SSH | Not tested | Not tested | Not tested | Disabled | Not tested | Not tested | Disabled | Boundary only | Safe command translation is intentionally excluded. | +| Windows 11 x64 | v0.6 CI build only | Not tested | Not tested | Source validated | Not tested | Source validated | Disabled | Candidate | v1.0 signed package, install, and launch evidence is unavailable. | +| Windows 11 ARM64 | Not tested | Not tested | Not tested | Not tested | Not tested | Not tested | Disabled | Disabled | No native ARM64 artifact has been verified. | +| Windows 10 | Not tested | Not tested | Not tested | Not tested | Not tested | Not tested | Disabled | Unsupported | Outside the 1.0 Stable support commitment. | ## Terminology -- **Source validated**: static, fixture, or script checks passed; it is not a runtime claim. -- **Partial**: a defined subset is verified and the gaps are stated above. -- **Disabled**: intentionally unavailable in the Public Beta default configuration. +- **Source validated** means static, fixture, or script checks passed; it is not a runtime claim. +- **Experimental** means a capability exists but lacks complete Stable evidence. +- **Disabled** means intentionally unavailable by default. +- **Candidate** means release engineering exists but installation and runtime gates are open. diff --git a/docs/compatibility-policy.md b/docs/compatibility-policy.md new file mode 100644 index 000000000..a9060b71e --- /dev/null +++ b/docs/compatibility-policy.md @@ -0,0 +1,16 @@ +# winTerm 1.x compatibility policy + +winTerm 1.x is intended to read settings created by winTerm 1.0. Unknown safe settings are preserved where the existing settings model can do so without weakening validation. Missing settings use documented defaults, and malformed settings must fall back without blocking startup. + +This policy commits to: + +- reading winTerm 1.0 settings throughout the supported 1.x line; +- reading Workspace Schema version 2 unless a future release explicitly deprecates it with migration guidance; +- retaining backward compatibility for Theme Schema version 1 imports; +- preserving unsupported newer Workspace files rather than overwriting them; +- creating a backup before a migration replaces persisted data; +- deterministic and idempotent migrations; +- preserving the `winterm.exe` alias and the `Kaname.winTerm` package boundary throughout 1.x; +- leaving Windows Terminal settings, package identity, and `wt.exe` untouched. + +This is not a promise to read every future schema forever. A future incompatible schema must use a new schema version, publish a migration policy, and never silently downgrade or overwrite newer data. diff --git a/docs/feature-status.md b/docs/feature-status.md new file mode 100644 index 000000000..4cff57b21 --- /dev/null +++ b/docs/feature-status.md @@ -0,0 +1,35 @@ +# winTerm 1.0 feature status + +This table records verified evidence, not intended capability. The only allowed classifications are **Stable**, **Experimental**, and **Disabled**. A feature is not Stable merely because its source or fixtures pass validation. + +| Feature | Status | Default | Limitations | +| --- | --- | --- | --- | +| PowerShell 7 | Experimental | On when discovered | Source discovery is validated; packaged runtime launch is not yet recorded. | +| Windows PowerShell 5.1 | Experimental | On | Profile and shell assets are source-validated; packaged runtime launch is not yet recorded. | +| CMD | Experimental | On | Shell assets and conservative macros are validated; packaged runtime launch is not yet recorded. | +| WSL | Experimental | On when discovered | Dynamic discovery is validated; no distribution runtime matrix is complete. | +| Git Bash | Disabled | Off | No stable integration promise. | +| Linux Safe Compatibility | Experimental | On for local PowerShell and CMD; off elsewhere | Conservative mappings only; never a general Linux command translator. | +| Themes | Experimental | On | Asset hashes and licenses pass; packaged visual inspection remains open. | +| Theme Import | Experimental | On | Data-only import boundaries pass static validation; live dialogs remain unverified. | +| Fonts | Experimental | On | App-private bundled Cascadia fonts pass hash validation; runtime fallback remains unverified. | +| ANSI True Color | Experimental | On | Inherited upstream implementation; winTerm packaged runtime validation is open. | +| Color Emoji | Experimental | On where DirectWrite supports it | Complex sequences and cell width can vary. | +| Workspace Restore | Experimental | On | Schema, migration, fallback, and fixtures pass; repeated packaged restore is unverified. | +| Named Workspaces | Experimental | On | Data model and persistence foundations exist; packaged workflow validation is open. | +| Crash Recovery | Experimental | On | Snapshot and recovery foundations pass; crash-loop and packaged recovery tests are open. | +| Tab Dragging | Disabled | Off | Runtime Docking adapter is disabled. | +| Pane Dragging | Disabled | Off | Runtime Docking adapter is disabled. | +| Edge Docking | Disabled | Off | Model and transaction validation pass; live UI validation is open. | +| Corner Docking | Disabled | Off | Model and transaction validation pass; live UI validation is open. | +| Empty Slots | Experimental | On in the layout model | Model-only until runtime Docking is enabled. | +| Cross-window Tab Transfer | Experimental | Off | Same-process model only; no shell restart fallback. | +| Cross-window Pane Transfer | Disabled | Off | Cross-process transfer is rejected. | +| Layout Undo/Redo | Experimental | On in the layout model | Model validation exists; runtime UI remains disabled. | +| Update Check | Disabled | Off | winTerm makes no update request without a future explicit consent path. | +| Diagnostic Bundle | Experimental | User initiated | Redaction boundaries pass static checks; end-to-end bundle inspection remains open. | +| ARM64 | Disabled | Off | No native ARM64 install and launch evidence exists. | + +## Stable-release consequence + +The current candidate is not eligible for public Stable publication while core shell, installation, accessibility, and runtime Workspace evidence remain Experimental or unavailable. The Release workflow can prepare an explicitly blocked Draft without promoting it. diff --git a/docs/performance-v1.0.md b/docs/performance-v1.0.md new file mode 100644 index 000000000..72eec335c --- /dev/null +++ b/docs/performance-v1.0.md @@ -0,0 +1,18 @@ +# winTerm 1.0 performance and resource validation + +The source tree includes non-runtime Workspace and Docking model benchmarks. No packaged Windows 11 performance or soak run is available for this candidate. + +| Scenario group | Result | Evidence | +| --- | --- | --- | +| Workspace and Docking model scripts | Passed | Non-runtime scripts execute in CI; they do not measure packaged UI or shell processes. | +| Cold and warm startup; PowerShell and CMD startup | Not available | Requires a signed packaged build on named hardware. | +| Single and large Workspace restore | Not available | Requires packaged runtime instrumentation. | +| Theme Gallery and Font Picker | Not available | Requires packaged UI measurement. | +| ANSI, Emoji, scrollback, Tab, Pane, and Docking load | Not available | Requires runtime and renderer resource measurement. | +| Shutdown | Not available | Requires process and handle tracking. | +| 500 Tab, Pane, and Docking operations | Not available | Runtime Docking is disabled and no soak environment is provisioned. | +| 100 Workspace restores, Settings opens, Theme imports, and diagnostic bundles | Not available | No provisioned soak environment. | + +The release must check shell processes, ConPTY objects, file handles, drag tokens, session leases, Workspace timers, XAML objects, renderer resources, and temporary files for persistent unbounded growth. A median startup regression above 15 percent or Workspace/Docking model regression above 20 percent requires investigation against a recorded baseline. + +Performance gate: **Not available**. No claim of leak-free long-duration behavior is made. diff --git a/docs/powershell-integration.md b/docs/powershell-integration.md index 547827f69..cce03b5e2 100644 --- a/docs/powershell-integration.md +++ b/docs/powershell-integration.md @@ -1,6 +1,6 @@ # PowerShell integration -The packaged module is `ShellAssets\powershell\winTerm.Shell\winTerm.Shell.psd1`, version `0.3.0`. It supports PowerShell 7 and Windows PowerShell 5.1 with the same syntax. +The packaged module is `ShellAssets\powershell\winTerm.Shell\winTerm.Shell.psd1`, version `1.0.0`. It supports PowerShell 7 and Windows PowerShell 5.1 with the same syntax. An explicit winTerm profile launcher must set these process-local variables before importing the module: diff --git a/docs/release-checklist-v1.0.md b/docs/release-checklist-v1.0.md new file mode 100644 index 000000000..164dd2760 --- /dev/null +++ b/docs/release-checklist-v1.0.md @@ -0,0 +1,48 @@ +# winTerm 1.0 release checklist + +Recorded on 2026-07-18. `Passed` means evidence exists. `Not available` is never treated as passed. + +| Gate | Status | Evidence or reason | +| --- | --- | --- | +| Feature freeze complete | Passed | `docs/feature-status.md` and `docs/roadmap-post-1.0.md`. | +| P0 count is zero | Passed | Connected GitHub repository returned no open Issues; no known P0 was found in source review. | +| P1 count is zero or explicitly approved | Failed | Production signing, clean install, runtime core flows, accessibility, and performance evidence are unresolved. | +| Version is 1.0.0 everywhere | Passed | `scripts/winterm/verify-version.ps1` after implementation validation. | +| Full CI passes | Not available | v1.0 branch has not been pushed or run in GitHub Actions. | +| Security review passes | Not available | Static review exists; runtime evidence is incomplete. | +| Privacy review passes | Passed | Static privacy and diagnostic checks exist; end-to-end bundle inspection remains separately open. | +| Accessibility review passes | Not available | Narrator, keyboard-only, contrast, scaling, and pseudo-localization are untested. | +| Performance review passes | Not available | Packaged benchmarks and soak tests are untested. | +| x64 package builds | Not available | v0.6 CI built an unsigned package; the v1.0 package has not been built. | +| x64 signature verifies | Not available | No production signing certificate or CI signing configuration is available. | +| ARM64 status verified | Passed | Status is Disabled and no ARM64 artifact will be published without native evidence. | +| Clean install passes | Not available | No clean Windows 11 machine is provisioned. | +| Upgrade passes | Not available | 0.6-to-1.0 upgrade is untested. | +| Uninstall passes | Not available | Clean uninstall is untested. | +| Delete all winTerm local data passes | Not available | The required reviewed UI and packaged deletion boundary are not verified. Manual guessed-path deletion is not accepted as evidence. | +| Windows Terminal coexistence passes | Not available | Manifest isolation is source-validated; installed coexistence is untested. | +| winterm.exe works | Not available | Alias registration is source-validated; installed launch is untested. | +| wt.exe remains untouched | Partial | Manifest and payload validation prohibit `wt.exe`; installed coexistence is untested. | +| Schemas are compatible | Passed | Versions remain Workspace 2, Docking 1, Shell 1, Theme 1; policies are documented. | +| Workspace migration passes | Partial | Fixtures and source checks pass; upgrade runtime is untested. | +| SBOM generated | Not available | Generator exists; no final package input exists. | +| Checksums generated | Not available | Generator and verifier exist; no final package input exists. | +| Third-party notices current | Passed | Existing notice check passes. | +| Release notes complete | Passed | Draft notes exist and do not claim unavailable assets or tests. | +| Git tag created | Not available | Prohibited until the final release commit and all publication gates are selected. | +| Tag pushed | Not available | No tag exists. | +| Draft Release created | Not available | No tag or final package exists. | +| Assets uploaded | Not available | No Draft Release exists. | +| Assets downloaded again | Not available | No Draft Release exists. | +| Downloaded hashes verified | Not available | No Draft Release exists. | +| Draft published | Not available | Publication is blocked. | +| Release marked Latest | Not available | No public Release exists. | +| Release URL verified | Not available | No Release URL exists. | +| Public download smoke test passes | Not available | No public Release or clean Windows 11 environment exists. | +| README download link updated | Skipped with reason | A `/releases/latest` installer link would be misleading before a public stable Release exists. | +| Stable update manifest updated | Skipped with reason | It must contain the actual public URL, publication time, and downloaded hash. | +| Final report completed | Not available | Release phase remains blocked. | + +## Publication decision + +**Release blocked before publication.** The workflow may create an explicitly blocked Draft only after a tag is deliberately selected. It cannot publish an unsigned or untested installer. diff --git a/docs/release-process.md b/docs/release-process.md index 603277a16..c05d277c2 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -1,83 +1,77 @@ -# winTerm release process +# winTerm Stable release process -## 1. Establish the release input +## 1. Select exact source -- Start from a clean, reviewed branch. -- Record the exact Microsoft Terminal upstream commit. -- Confirm `git diff` contains no generated build output, certificate, key, credential, unmanifested font or theme, or downloaded logo. -- Confirm the release contains no post-v0.5 or out-of-scope feature work. +Start from reviewed `main`, create a release branch, and record: -Run source validation first: +- repository and branch; +- commit SHA and v0.6 baseline; +- Microsoft Terminal upstream revision; +- clean working tree and untracked files; +- CI, package, signing, architecture, and blocker status. + +Run: ```powershell -.\scripts\winterm\test.ps1 -Suite Smoke +.\scripts\winterm\verify-version.ps1 +.\scripts\winterm\test.ps1 -Suite Smoke -Configuration Release -Platform x64 ``` -## 2. Set release identity - -For a local development package, the v0.5 manifest uses version `0.5.0.0` and publisher `CN=winTerm Development`. - -For a public release: - -1. Update the package version according to the chosen release channel. -2. Replace the manifest publisher with the real publisher identity. -3. Use a code-signing certificate with a subject that exactly matches that publisher. -4. Recheck package family, upgrade, uninstall, and side-by-side behavior. - -Do not commit a PFX, private key, password, certificate export, Store credential, or CI secret. +Do not tag, publish, force-push, or overwrite user work at this stage. -## 3. Build and test x64 +## 2. Run compiled and manual gates -Use a provisioned Windows 11 x64 machine and PowerShell 7: +Build and test x64 with PowerShell 7, Visual Studio, and Windows SDK 10.0.22621.0: ```powershell -.\scripts\winterm\build.ps1 -Configuration Debug -Platform x64 -IncludeTests -.\scripts\winterm\test.ps1 -Suite Relevant -Configuration Debug -Platform x64 .\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -IncludeTests .\scripts\winterm\test.ps1 -Suite Relevant -Configuration Release -Platform x64 +.\scripts\winterm\package.ps1 -Platform x64 ``` -Run `-Suite Full` for release candidates when runner time permits. Record all failures, skips, warnings, and test binaries used. +Then validate a production-signed package on a clean Windows 11 x64 machine: install, launch, PowerShell, CMD, tabs, panes, Workspace save/restore, restart, upgrade from 0.6, uninstall, reinstall, `winterm.exe`, Windows Terminal coexistence, and continued ownership of `wt.exe` by Windows Terminal. -## 4. Create and inspect the package +Complete Narrator, keyboard, contrast, scaling, privacy, diagnostics, performance, and soak evidence. ARM64 is published only after separate native evidence. -```powershell -.\scripts\winterm\package.ps1 -Platform x64 -.\scripts\winterm\verify-branding.ps1 -PackageOutput -``` +## 3. Configure protected signing -The wrapper intentionally generates an unsigned MSIX. It reports the artifact under `src/cascadia/CascadiaPackage/AppPackages`, validates the embedded identity and alias, and reports signing status. +The `winterm-stable-release` GitHub environment owns: -Sign only in a controlled release environment. A typical Windows SDK operation is: +- `WINTERM_SIGNING_PFX_BASE64` secret; +- `WINTERM_SIGNING_PFX_PASSWORD` secret; +- `WINTERM_PACKAGE_PUBLISHER` protected variable; +- `WINTERM_TIMESTAMP_URL` protected variable; +- required reviewers. -```powershell -signtool.exe sign /fd SHA256 /f -``` +The workflow validates certificate purpose, subject, expiry, private key, timestamp, package Publisher, package identity, alias, and signature. It deletes the ephemeral PFX. Never echo or commit signing material. -Supply passwords through an approved secret mechanism, not a command committed to this repository. Verify the result: +## 4. Merge, tag, and prepare Draft -```powershell -Get-AuthenticodeSignature -``` +After every pre-tag gate is reviewed: -## 5. Install and perform acceptance +1. merge the release PR to `main`; +2. record the exact merge commit; +3. create annotated tag `v1.0.0` on that commit; +4. push only the tag; +5. let `.github/workflows/release.yml` build from the clean tag checkout. -Trust only the intended development or release certificate, then install the signed package: +The prepare mode creates a Draft, uploads an allowlist, generates Attestations, re-downloads every asset, verifies hashes, package identity, architecture, alias, Publisher, signature when present, and Attestation. It never uses `--clobber`. -```powershell -Add-AppxPackage -Path -``` +If production signing is unavailable, the Draft is explicitly blocked and remains unsigned. Do not promote it. + +## 5. Publish deliberately + +Run workflow dispatch from the `v1.0.0` tag in `publish` mode. Supply the exact commit that passed clean install, upgrade, uninstall, alias, and coexistence validation. The protected environment reviewers confirm all manual evidence. -If MSBuild emits an `Add-AppDevPackage.ps1` helper, inspect its certificate and script before running it. The package script never installs a certificate automatically. +The workflow re-downloads the Draft, requires a valid trusted signature and timestamp, verifies checksums and Attestation, then sets Draft false, Prerelease false, and Latest. It re-downloads the public assets into a new directory and verifies hashes again. -Complete every manual item in `docs/v0.2-acceptance.md`, including side-by-side installation with Microsoft Windows Terminal, aliases, first-launch state, profiles, tabs, panes, settings, Theme import and export, app-private fonts, command palette, clipboard, CJK input, ANSI colors, emoji, Powerline, upgrade, and uninstall behavior. +The phase remains open until a clean Windows 11 machine installs and launches the publicly downloaded package. If that smoke test fails, do not replace the `v1.0.0` asset; record the incident and prepare `v1.0.1`. -## 6. Publish deliberately +## 6. Post-release metadata -- Publish only reviewed, signed artifacts and their checksums. -- State architecture, version, upstream baseline, publisher, signing status, and known limitations. -- Keep CI free of Store publishing and real signing credentials. -- Do not overwrite an existing release or automatically publish to a Store. -- Tag and release only after the acceptance checklist is complete. +Only after the public URL and asset hash exist: -ARM64 must not be advertised until its build, tests, package, install, and manual acceptance have been run independently. +- generate the stable update manifest with `generate-stable-update-manifest.ps1`; +- run the WinGet workflow and `winget validate`; +- update the README to link `/releases/latest`; +- record Release ID, URL, workflow run, assets, hashes, signatures, installation results, and remaining issues. diff --git a/docs/releases/1.0.0.md b/docs/releases/1.0.0.md new file mode 100644 index 000000000..32c7e78bb --- /dev/null +++ b/docs/releases/1.0.0.md @@ -0,0 +1,115 @@ +# winTerm 1.0.0 + +## Highlights + +winTerm is an independent open-source terminal application based on Microsoft Windows Terminal. Version 1.0.0 freezes the public settings and schema contracts, isolates the `Kaname.winTerm` package and `winterm.exe` alias, and adds a gated stable-release supply chain. + +The candidate includes inherited PowerShell, Windows PowerShell, CMD, and WSL profile foundations; conservative Linux-style Safe Compatibility for local shells; built-in and open-source Themes; app-private Cascadia programming fonts; ANSI color and Emoji support; multiple windows, tabs, and panes; Workspace Restore and Named Workspaces; paste protection; and model foundations for edge and corner Docking with Layout Undo and Redo. + +Runtime Docking and cross-process Pane transfer are disabled. The release does not claim complete Bash compatibility, universal Shell support, perfect Emoji layout, zero data-loss risk, or support for every Windows version. + +## Download + +Most users should download `winTerm-1.0.0-x64.msix`. + +ARM64 and a universal bundle are not part of the current release plan because native ARM64 install and launch validation has not passed. Do not download similarly named files from third-party sites. + +## Installation + +Install only the package attached to the official `v1.0.0` GitHub Release after all of these checks succeed: + +1. verify the package hash against `SHA256SUMS.txt`; +2. verify a valid trusted signature whose subject matches the package Publisher; +3. confirm the Release is public, Latest, and is neither Draft nor Prerelease; +4. install by opening the MSIX or by running `Add-AppxPackage` from a normal supported Windows 11 environment. + +Do not install an unsigned Draft package or import a development certificate for a Stable installation. Visual Studio, Git, Developer Mode, registry edits, and manual file copying are not installation requirements. + +## Upgrade + +Back up important local data before upgrading. The intended upgrade path preserves settings, imported Themes, Workspaces, last-session state, font settings, compatibility settings, shortcuts, update preferences, and privacy preferences. A public Stable Release is blocked until upgrade from the latest 0.6 candidate is validated on a clean test machine. + +## Supported Platforms + +Windows 11 x64 is the intended fully supported platform after the clean-machine gate passes. Windows 10 is unsupported for the 1.0 Stable commitment. ARM64 remains Disabled until a published ARM64 artifact has its own native validation evidence. + +## Supported Shells + +PowerShell 7, Windows PowerShell 5.1, and Command Prompt have source-validated profile and integration foundations. WSL is discovered dynamically when installed. Packaged runtime results are tracked in `docs/compatibility-matrix.md`; “Not tested” never means supported. + +## Linux Compatibility + +Safe Compatibility is a small conservative command layer for local PowerShell and CMD sessions. It is off for WSL, Git Bash, SSH, and REPL contexts. Real executables take precedence. winTerm does not translate arbitrary Linux commands. + +## Themes and Fonts + +Theme and font assets are pinned by SHA-256 and include their licenses. Fonts remain app-private and are not installed globally. Theme import is data-only and rejects command, executable, environment, and remote-include fields. + +## Workspace Restore + +Workspace Schema version 2 stores layout metadata, not terminal output or command history. Newer unsupported Workspace files are preserved, corrupt files must not block startup, and migration must be deterministic, idempotent, and backed up. + +## Visual Docking + +The Docking model includes edge and corner targets, empty slots, transactional rollback, ownership tokens, keyboard command descriptors, and Layout Undo and Redo. Runtime Docking is Disabled until live Shell ownership, rollback, accessibility, multi-window, and soak validation pass. No Shell is restarted to imitate live transfer. + +## Keyboard and Accessibility + +Static keyboard and accessible-label checks exist. Narrator, High Contrast, scaling, pseudo-localization, and end-to-end keyboard-only validation must pass before Stable publication. Runtime Docking remains disabled, so no inaccessible Docking UI is presented as Stable. + +## Privacy + +winTerm does not collect command text, terminal output, clipboard content, Workspace contents, working directories, or usage analytics. Crash upload is off and opt-in. Update requests are disabled until an explicit consent path exists. Diagnostic bundles are user-generated and exclude raw sensitive content by default. + +## Known Issues + +### P2 + +- Complex Emoji ZWJ sequences and ambiguous-width glyphs can occupy unexpected cells. +- Binary plist Theme import is unsupported. + +### P3 + +- Broader WSL distribution and Git Bash compatibility coverage is planned after 1.0. +- A first-run update-consent experience is deferred; Update Check is Disabled. + +### Experimental limitations + +- Workspace Restore, Named Workspaces, Themes, fonts, Shell integration, and diagnostics retain Experimental status until packaged runtime evidence is recorded. +- Runtime Docking, cross-process Pane transfer, Update Check, Git Bash integration, and ARM64 are Disabled. + +## Verification + +Verify the downloaded hash: + +```powershell +$expected = (Get-Content .\SHA256SUMS.txt | Where-Object { $_ -match 'winTerm-1.0.0-x64.msix$' }).Split(' ')[0] +$actual = (Get-FileHash .\winTerm-1.0.0-x64.msix -Algorithm SHA256).Hash.ToLowerInvariant() +if ($actual -cne $expected) { throw 'SHA-256 mismatch.' } +``` + +Verify the signature: + +```powershell +Get-AuthenticodeSignature .\winTerm-1.0.0-x64.msix | Format-List Status,StatusMessage,SignerCertificate,TimeStamperCertificate +``` + +Verify GitHub build provenance when the Release includes an Attestation: + +```powershell +gh attestation verify .\winTerm-1.0.0-x64.msix -R HelloThisWorld/winTerm +``` + +Attestation verifies source and integrity; it is not proof that the application has no vulnerabilities. + +## Uninstall + +Uninstall winTerm from Windows Settings. This removes the application and `winterm.exe` registration. It must not remove Windows Terminal, `wt.exe`, WSL, global fonts, PowerShell profiles, CMD AutoRun settings, or unrelated data. See `docs/user/uninstall.md` for local-data behavior. + +## Feedback and Security + +Use the winTerm GitHub Issue templates for bugs and compatibility reports. Do not place vulnerabilities, credentials, private terminal output, memory dumps, or unredacted Workspace files in public Issues. Follow `SECURITY.md` for private reporting. + +## Credits + +winTerm is based on the Microsoft Terminal open-source codebase and retains its MIT license, copyright notices, and third-party notices. winTerm is not affiliated with or endorsed by Microsoft. diff --git a/docs/roadmap-post-1.0.md b/docs/roadmap-post-1.0.md new file mode 100644 index 000000000..1d3cf189a --- /dev/null +++ b/docs/roadmap-post-1.0.md @@ -0,0 +1,14 @@ +# Post-1.0 roadmap + +The v1.0 branch is feature-frozen. New feature requests are recorded here and are not implemented as part of the Stable release work. + +## Deferred candidates + +- explicit first-run update-consent UI and stable update-channel integration; +- native ARM64 build, install, launch, accessibility, and performance validation; +- live runtime Docking after rollback, ownership, keyboard, and Narrator validation; +- broader WSL and Git Bash compatibility evidence; +- improved complex Emoji width diagnostics; +- binary plist Theme import. + +The following remain out of scope: AI command generation, natural-language shell, terminal brokers, process persistence after exit, session reattachment, plugin SDK or marketplace, cloud Workspace synchronization, accounts, collaboration, SFTP, SSH credential management, online Theme or font marketplaces, command replay, and full scrollback persistence. diff --git a/docs/schema-support-policy.md b/docs/schema-support-policy.md new file mode 100644 index 000000000..8bf993e39 --- /dev/null +++ b/docs/schema-support-policy.md @@ -0,0 +1,20 @@ +# winTerm schema support policy + +| Contract | Stable version | Read policy | Write policy | +| --- | --- | --- | --- | +| Workspace | 2 | Read version 2; migrate supported older fixtures; preserve newer unsupported files. | Write version 2 atomically. | +| Docking Model | 1 | Read version 1 only inside a supported Workspace. | Write version 1. | +| Shell Protocol | 1 | Accept only validated version 1 payloads within size and field limits. | Emit version 1. | +| Theme | 1 | Read validated version 1 themes and supported data-only imports. | Write version 1. | +| Update Manifest | 1 | Accept HTTPS stable-channel metadata only after consent and identity, version, and hash validation. | Generated only after a real public Release exists. | + +## Failure behavior + +- Corrupted settings or Workspace data must not block startup. +- A migration must create a backup before replacement. +- Running the same migration again must not alter the result. +- Newer unsupported schema files must not be rewritten. +- Unknown fields are preserved only when doing so is demonstrably safe. +- Imported data may not add commands, executables, environment variables, remote includes, or external URLs. + +Application version 1.0.0 does not change any schema version. diff --git a/docs/security-review-v1.0.md b/docs/security-review-v1.0.md new file mode 100644 index 000000000..3c60e6bf9 --- /dev/null +++ b/docs/security-review-v1.0.md @@ -0,0 +1,23 @@ +# winTerm 1.0 security review + +Review date: 2026-07-18. This review distinguishes source validation from runtime evidence. No open GitHub issue is labelled Critical or High, but missing runtime and signing evidence prevents a passing Stable decision. + +| Area | Result | Evidence and remaining work | +| --- | --- | --- | +| Shell integration | Partial | Module loading, process-local behavior, executable precedence, local-only mappings, protocol size limits, and no policy bypass pass source tests. Packaged PowerShell 7, Windows PowerShell, CMD, WSL, SSH, and REPL boundaries remain unverified. | +| Import | Partial | Theme and Workspace inputs reject command fields, executables, remote includes, invalid paths, oversized files, malformed JSON, and excessive structures in fixtures. Live dialog and XML parser behavior require packaged tests. | +| Clipboard | Partial | Paste analysis is non-executing and source tests cover multiline and suspicious input. Packaged right-click and bracketed-paste behavior remains unverified. | +| Workspace | Partial | Command history, terminal output, credentials, and raw Workspace JSON are excluded from diagnostics; atomic persistence, backups, and sanitization have source coverage. Runtime recovery and migration need provisioned tests. | +| Docking | Partial | Opaque expiring tokens, single use, ownership, rollback, and cross-process rejection have model coverage. Runtime Docking remains disabled, so no live-session claim is made. | +| Updates | Disabled | winTerm performs no update request without explicit consent. Manifest, identity, version, hash, signature, and downgrade enforcement must pass before enabling the feature. | +| Telemetry and crash reporting | Passed (source) | winTerm-branded UI, settings, connection, control, and host binaries use distinct ETW provider identities and do not register Microsoft telemetry or fallback crash reporting. Runtime process inspection remains part of the clean-machine gate. | +| Diagnostics | Partial | Path, token, email, and connection redaction markers pass static validation; bundles are user-generated. End-to-end temporary-file and bundle inspection remains open. | +| Release supply chain | Partial | Exact-tag workflow, allowlisted assets, immutable Action pins, checksums, SBOM, Attestation, Draft re-download, and no-clobber rules are implemented. Production signing and clean-machine validation are unavailable. | + +## Severity decision + +- Known Critical issues: 0 reported. +- Known High issues: 0 reported. +- Security gate: **Not available**, because production signature, package installation, live Shell, and end-to-end diagnostics evidence are missing. + +Any confirmed command execution through import, secret or clipboard exposure, package identity collision, forged Docking ownership, or release secret leakage blocks publication. diff --git a/docs/stable-defaults.md b/docs/stable-defaults.md new file mode 100644 index 000000000..fa84d88cb --- /dev/null +++ b/docs/stable-defaults.md @@ -0,0 +1,21 @@ +# winTerm 1.0 default behavior + +This table records the current candidate behavior. It does not turn an unverified capability into Stable status. + +| Setting or behavior | Default | Notes | +| --- | --- | --- | +| Theme | Inherited terminal default | `winTerm Midnight` is bundled but is not claimed as the runtime default without packaged validation. | +| Font | Cascadia family | Bundled Cascadia Code and Cascadia Mono are app-private. No Nerd Font is bundled. | +| Linux Safe Compatibility | On for opted-in local PowerShell and CMD integration | Off for WSL, Git Bash, SSH, and REPL contexts. Real executables take precedence. | +| Restore last session | On in the winTerm Workspace settings model | Packaged restore remains Experimental. | +| Crash recovery | On with a user prompt | Automatic silent recovery is off. | +| Right-click copy/paste | Inherited terminal behavior | Packaged interaction validation remains open. | +| Multiline and suspicious paste warning | On where the winTerm paste analyzer is integrated | Clipboard contents are never logged. | +| Update check | Off | No request is sent until an explicit consent path is implemented. | +| Crash upload | Off | Future upload remains opt-in. | +| Usage telemetry | Off | No command, terminal, clipboard, Workspace, directory, or usage analytics are collected. | +| Edge and corner Docking | Off at runtime | The model defaults exist, but the runtime adapter is Disabled. | +| Layout Undo | Off at runtime | Model history exists; runtime Docking is Disabled. | +| Cross-process Pane transfer | Off | Rejected; Shells are never restarted to imitate transfer. | + +First launch does not upload data, modify a PowerShell profile, modify CMD AutoRun, change execution policy, install global fonts, download Themes, or enable crash upload. diff --git a/docs/user/accessibility.md b/docs/user/accessibility.md index 8a63c822a..01947f879 100644 --- a/docs/user/accessibility.md +++ b/docs/user/accessibility.md @@ -1,3 +1,3 @@ # Accessibility -Core flows must have a keyboard path and accessible labels. The v0.6 manual audit for Narrator, high contrast, scaling, and pseudo-localization remains open; see the [accessibility audit](../accessibility-audit.md) before relying on an unverified path. +Core flows must have a keyboard path and accessible labels. The v1.0 manual audit for Narrator, high contrast, scaling, and pseudo-localization remains open; see the [accessibility audit](../accessibility-audit-v1.0.md) before relying on an unverified path. diff --git a/docs/user/installation.md b/docs/user/installation.md index e1f4e38c4..38529adbf 100644 --- a/docs/user/installation.md +++ b/docs/user/installation.md @@ -1,3 +1,15 @@ # Install winTerm -Download only the official release MSIX and its `SHA256SUMS.txt`. Verify the hash before installation. The current development package is unsigned; do not treat it as a signed public release. winTerm uses its own `Kaname.winTerm` identity and `winterm.exe` alias and does not replace Windows Terminal or `wt.exe`. +There is no approved public Stable installer while `docs/release-checklist-v1.0.md` is blocked. Do not install an Actions artifact, unsigned Draft package, or package that asks you to import a development certificate as winTerm Stable. + +After a public `v1.0.0` Release exists: + +1. download `winTerm-1.0.0-x64.msix` and `SHA256SUMS.txt` from the official Release; +2. verify SHA-256; +3. run `Get-AuthenticodeSignature` and require `Status: Valid`, a trusted timestamp, and a signer matching the package Publisher; +4. open the MSIX or run `Add-AppxPackage`; +5. launch winTerm from Start or `winterm.exe`. + +Installation must not require Visual Studio, Windows SDK, Git, PowerShell build modules, Developer Mode, manual copying, registry edits, execution-policy changes, global font installation, PowerShell profile changes, or CMD AutoRun changes. + +winTerm uses `Kaname.winTerm` and `winterm.exe`. It does not replace Microsoft Windows Terminal or claim `wt.exe`. diff --git a/docs/user/uninstall.md b/docs/user/uninstall.md index 25988da12..d64c762dd 100644 --- a/docs/user/uninstall.md +++ b/docs/user/uninstall.md @@ -1,3 +1,14 @@ -# Uninstall winTerm +# Uninstall and reset winTerm -Uninstall winTerm through Windows Apps settings. This removes the application package but follows MSIX platform behavior for local app data. Delete only winTerm-specific local data if you choose a full reset; never remove Windows Terminal data, PowerShell profiles, WSL distributions, shell history, or unrelated user folders. +Uninstall winTerm through Windows Apps settings. This removes winTerm application binaries, its Start entry, and `winterm.exe` registration according to MSIX behavior. + +Uninstall must leave these items untouched: + +- Microsoft Windows Terminal and `wt.exe`; +- WSL distributions; +- global system fonts; +- PowerShell profiles and execution policy; +- CMD AutoRun settings; +- terminal history and unrelated application data. + +Local settings, named Workspaces, recovery snapshots, imported Themes, diagnostics, and logs are winTerm-owned data. A full **Settings → Reset → Delete all winTerm local data** operation must list those categories, require a second confirmation, and delete only winTerm’s package-local or `%LOCALAPPDATA%\winTerm` data. Until that reviewed UI is verified in a packaged build, users should not manually delete folders based on guessed paths. diff --git a/docs/v1.0-progress.md b/docs/v1.0-progress.md new file mode 100644 index 000000000..d44f34406 --- /dev/null +++ b/docs/v1.0-progress.md @@ -0,0 +1,24 @@ +# winTerm 1.0 progress + +Updated: 2026-07-18 + +| Field | Value | +| --- | --- | +| Completed phases | Baseline and GitHub inspection; scope freeze; repository-side version, privacy, release workflow, policy, and documentation preparation; available local validation | +| Current phase | Draft pull request and full GitHub Windows CI, followed by external signing and clean-machine gates | +| Current branch | `codex/release-1.0.0` | +| Baseline commit | `f43fc36721d7ef23a09328602eb1640b136b883c` (`origin/main`, v0.6 squash merge) | +| Current release-preparation commit | The pushed draft-PR head; it is not the final release commit until all gates pass | +| Release commit candidate | Not selected | +| Microsoft Terminal upstream | `release-1.25@1cea42d433253d95c4487a3037db48197b5e72f4` | +| Files changed | Version and About metadata; telemetry isolation; signing, checksum, SBOM, package, WinGet, update-manifest, and Release workflows; policies, reviews, checklists, and community files | +| Tests run | Baseline and final v1.0 Smoke suites; Shell compatibility; rendering fixture; Workspace model, restore, recovery, and import; Docking model, transactions, transfer, history, presentation, UI source validation, and benchmarks; artifact, checksum, WinGet, and update-manifest generator fixtures | +| Tests passed | Every available static, fixture, script syntax, privacy, workflow-security, checksum-tamper, generator, and non-runtime benchmark check completed successfully | +| Tests failed | No current local script failure. An unset `LASTEXITCODE` compatibility-test defect and two release-generator fixture defects were found, fixed, and passed on rerun. Compiled and packaged tests were not available. | +| Open P0 issues | 0 known; connected GitHub repository returned no open Issues | +| Open P1 issues | Production signing unavailable; clean install/upgrade/uninstall and delete-all-data UI verification unavailable; packaged runtime, accessibility, and performance evidence unavailable | +| Signing status | No production certificate with private key found; no local `signtool.exe`; protected CI configuration unknown | +| x64 package status | v1.0 not built; local Visual Studio, PowerShell 7, Windows SDK, and MakeAppx are unavailable | +| ARM64 package status | Disabled; no native build/install/launch evidence | +| Release status | No tag, Draft Release, public Release, or Release URL | +| Next exact action | Push the release-preparation branch and run both GitHub validation and full Windows build/package CI. Provision protected production signing and a clean Windows 11 x64 machine before selecting or pushing `v1.0.0`. | diff --git a/docs/workspace-schema.md b/docs/workspace-schema.md index bb48ca795..e01754c60 100644 --- a/docs/workspace-schema.md +++ b/docs/workspace-schema.md @@ -1,6 +1,6 @@ # Workspace schema -`WorkspaceSchemaVersion` is `2`, `DockingModelVersion` is `1`, and both are independent from application version `0.5.0`. A document is UTF-8 JSON with a 5 MB maximum size. The strict parser rejects comments, duplicate keys, special floating-point values, trailing data, excessive strings, and excessive nesting. +`WorkspaceSchemaVersion` is `2`, `DockingModelVersion` is `1`, and both remain independent from application version `1.0.0`. A document is UTF-8 JSON with a 5 MB maximum size. The strict parser rejects comments, duplicate keys, special floating-point values, trailing data, excessive strings, and excessive nesting. The top-level object contains `schemaVersion`, `id`, `name`, `description`, `createdAt`, `updatedAt`, `source`, `applicationVersion`, `protocolVersion`, `startupBehavior`, `activeWindowId`, and `windows`. Optional metadata includes tags, default state, last-opened time, capture reason, and recovery generation. IDs are globally unique and are not user-visible file names. diff --git a/packaging/winget/README.md b/packaging/winget/README.md index 21585475b..b2826528e 100644 --- a/packaging/winget/README.md +++ b/packaging/winget/README.md @@ -1,5 +1,16 @@ -# WinGet Public Beta manifest plan +# WinGet manifest preparation -The Public Beta package identifier is reserved as `Kaname.winTerm.Beta`. A WinGet manifest is not submitted or claimed valid until the draft GitHub Release has immutable x64 MSIX URLs and SHA-256 values. +The Stable package identifier is `Kaname.winTerm`. -Generate installer, default-locale, and version manifests from `release-metadata.json`, validate them with `winget validate`, and retain the final validation output with the release report. Do not publish a template with invented URLs or hashes. +No 1.0.0 manifest is committed before the public GitHub Release exists. The installer URL and SHA-256 must come from the actually published `winTerm-1.0.0-x64.msix`; placeholders and Actions artifact URLs are forbidden. + +After publication, `.github/workflows/winget.yml`: + +1. confirms `v1.0.0` is public and not a Prerelease; +2. downloads the x64 installer from the Release; +3. computes its real SHA-256; +4. generates installer, default-locale, and version manifests; +5. runs `winget validate`; +6. uploads the validated set as a workflow artifact for manual review. + +This does not claim that an official `microsoft/winget-pkgs` pull request exists or has been approved. Record a real PR URL only after a separate reviewed submission succeeds. diff --git a/scripts/winterm/benchmark-workspace.ps1 b/scripts/winterm/benchmark-workspace.ps1 index 8d56d0922..3fd6cd6e5 100644 --- a/scripts/winterm/benchmark-workspace.ps1 +++ b/scripts/winterm/benchmark-workspace.ps1 @@ -59,7 +59,7 @@ function New-BenchmarkWorkspace createdAt = '2026-07-15T00:00:00Z' updatedAt = '2026-07-15T00:00:00Z' source = 'runtime' - applicationVersion = '0.6.0-beta.1' + applicationVersion = '1.0.0' protocolVersion = 1 dockingModelVersion = 1 activeWindowId = 'window-0' diff --git a/scripts/winterm/create-msixbundle.ps1 b/scripts/winterm/create-msixbundle.ps1 new file mode 100644 index 000000000..f362c1ae6 --- /dev/null +++ b/scripts/winterm/create-msixbundle.ps1 @@ -0,0 +1,80 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$X64PackagePath, + + [Parameter(Mandatory)] + [string]$Arm64PackagePath, + + [Parameter(Mandatory)] + [string]$OutputPath +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$stagingDirectory = $null + +try +{ + $makeAppx = Get-Command makeappx.exe -ErrorAction SilentlyContinue + if ($null -eq $makeAppx) + { + throw 'makeappx.exe was not found.' + } + + $x64 = Get-Item -LiteralPath (Resolve-Path -LiteralPath $X64PackagePath).Path + $arm64 = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Arm64PackagePath).Path + foreach ($package in @($x64, $arm64)) + { + if ($package.Extension -ne '.msix' -or ($package.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) + { + throw "Invalid MSIX bundle input '$($package.Name)'." + } + } + + $resolvedOutput = if ([IO.Path]::IsPathRooted($OutputPath)) + { + [IO.Path]::GetFullPath($OutputPath) + } + else + { + [IO.Path]::GetFullPath((Join-Path (Get-Location) $OutputPath)) + } + if (Test-Path -LiteralPath $resolvedOutput) + { + throw "Bundle output already exists: $resolvedOutput" + } + $outputParent = Split-Path -Parent $resolvedOutput + if (-not (Test-Path -LiteralPath $outputParent)) + { + New-Item -ItemType Directory -Path $outputParent | Out-Null + } + + $stagingDirectory = Join-Path ([IO.Path]::GetTempPath()) ("winterm-bundle-{0}" -f [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $stagingDirectory | Out-Null + Copy-Item -LiteralPath $x64.FullName -Destination (Join-Path $stagingDirectory 'winTerm-1.0.0-x64.msix') + Copy-Item -LiteralPath $arm64.FullName -Destination (Join-Path $stagingDirectory 'winTerm-1.0.0-arm64.msix') + + & $makeAppx.Source bundle /d $stagingDirectory /p $resolvedOutput /o + if ($LASTEXITCODE -ne 0) + { + throw "makeappx.exe failed with exit code $LASTEXITCODE." + } + Write-Host "Created MSIX bundle: $resolvedOutput" -ForegroundColor Green +} +catch +{ + Write-Error "MSIX bundle generation failed: $($_.Exception.Message)" + exit 1 +} +finally +{ + if ($null -ne $stagingDirectory -and (Test-Path -LiteralPath $stagingDirectory)) + { + Remove-Item -LiteralPath $stagingDirectory -Recurse -Force + } +} diff --git a/scripts/winterm/generate-build-metadata.ps1 b/scripts/winterm/generate-build-metadata.ps1 new file mode 100644 index 000000000..33438ab26 --- /dev/null +++ b/scripts/winterm/generate-build-metadata.ps1 @@ -0,0 +1,48 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{40}$')] + [string]$CommitSha, + + [Parameter(Mandatory)] + [ValidatePattern('^\d{4}-\d{2}-\d{2}T')] + [string]$BuildTimestamp, + + [Parameter()] + [ValidatePattern('^[0-9]+$')] + [string]$WorkflowRunId = '0' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +$outputPath = Join-Path $repositoryRoot 'src\winterm\Branding\ReleaseMetadata.generated.h' + +try +{ + $parsedTimestamp = [DateTimeOffset]::Parse( + $BuildTimestamp, + [Globalization.CultureInfo]::InvariantCulture, + [Globalization.DateTimeStyles]::AssumeUniversal) + $normalizedTimestamp = $parsedTimestamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + + $content = @( + '// Generated by scripts/winterm/generate-build-metadata.ps1. Do not commit.' + '#pragma once' + '' + "#define WINTERM_BUILD_COMMIT_SHA L`"$($CommitSha.ToLowerInvariant())`"" + "#define WINTERM_BUILD_TIMESTAMP L`"$normalizedTimestamp`"" + "#define WINTERM_BUILD_WORKFLOW_RUN_ID L`"$WorkflowRunId`"" + ) + [System.IO.File]::WriteAllLines($outputPath, $content, [System.Text.UTF8Encoding]::new($false)) + Write-Host "Generated release build metadata: $outputPath" -ForegroundColor Green +} +catch +{ + Write-Error "Build metadata generation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/generate-checksums.ps1 b/scripts/winterm/generate-checksums.ps1 new file mode 100644 index 000000000..e9a6ecf6d --- /dev/null +++ b/scripts/winterm/generate-checksums.ps1 @@ -0,0 +1,49 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$Directory, + + [Parameter()] + [string]$OutputFile = 'SHA256SUMS.txt' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +try +{ + $root = (Resolve-Path -LiteralPath $Directory).Path + $outputPath = Join-Path $root $OutputFile + $files = @(Get-ChildItem -LiteralPath $root -File | + Where-Object { $_.Name -ne $OutputFile } | + Sort-Object Name) + + if ($files.Count -eq 0) + { + throw "No files were found in '$root'." + } + + $lines = foreach ($file in $files) + { + if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) + { + throw "Release file '$($file.Name)' is a reparse point." + } + if ($file.Length -le 0) + { + throw "Release file '$($file.Name)' is empty." + } + "$((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()) $($file.Name)" + } + + [System.IO.File]::WriteAllLines($outputPath, $lines, [System.Text.Encoding]::ASCII) + Write-Host "Generated SHA-256 checksums: $outputPath" -ForegroundColor Green +} +catch +{ + Write-Error "Checksum generation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/generate-release-artifacts.ps1 b/scripts/winterm/generate-release-artifacts.ps1 index a17501add..93c0da879 100644 --- a/scripts/winterm/generate-release-artifacts.ps1 +++ b/scripts/winterm/generate-release-artifacts.ps1 @@ -4,135 +4,303 @@ [CmdletBinding()] param( [Parameter(Mandatory)] - [ValidatePattern('^0\.6\.0-beta\.[0-9]+$')] + [ValidateSet('1.0.0')] [string]$Version, [Parameter(Mandatory)] - [string]$ArtifactDirectory, + [string]$X64PackagePath, + + [Parameter()] + [string]$Arm64PackagePath, + + [Parameter()] + [string]$BundlePath, + + [Parameter(Mandatory)] + [string]$SymbolsDirectory, [Parameter(Mandatory)] - [ValidatePattern('^[0-9a-fA-F]{7,64}$')] + [string]$OutputDirectory, + + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{40}$')] [string]$CommitSha, + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{40}$')] + [string]$UpstreamRevision, + + [Parameter(Mandatory)] + [ValidatePattern('^\d{4}-\d{2}-\d{2}T')] + [string]$BuildTimestamp, + + [Parameter()] + [ValidatePattern('^[0-9]+$')] + [string]$WorkflowRunId = '0', + [Parameter()] - [ValidateSet('x64', 'ARM64')] - [string[]]$RequiredArchitecture = @('x64') + [ValidateSet('valid-production-signature', 'unsigned-draft')] + [string]$SigningStatus = 'unsigned-draft' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest -function Get-ReleasePackage +function Copy-ReleaseFile { param( [Parameter(Mandatory)] - [string]$Architecture, + [string]$Source, [Parameter(Mandatory)] - [string]$Directory + [string]$TargetName ) - $package = Get-ChildItem -LiteralPath $Directory -Recurse -File -Filter '*.msix' | - Where-Object { $_.Name -match [regex]::Escape($Architecture) } | - Sort-Object LastWriteTimeUtc -Descending | - Select-Object -First 1 - if ($null -eq $package) + $sourceItem = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Source).Path + if (($sourceItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) + { + throw "Release input '$($sourceItem.Name)' is a reparse point." + } + if ($sourceItem.Length -le 0) { - throw "No $Architecture MSIX package was found under '$Directory'." + throw "Release input '$($sourceItem.Name)' is empty." + } + + $target = Join-Path $script:outputRoot $TargetName + Copy-Item -LiteralPath $sourceItem.FullName -Destination $target -ErrorAction Stop + return Get-Item -LiteralPath $target +} + +function New-SbomComponent +{ + param( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$VersionInfo, + + [Parameter(Mandatory)] + [string]$Type, + + [Parameter()] + [string]$License = 'NOASSERTION', + + [Parameter()] + [string]$Revision + ) + + return [ordered]@{ + name = $Name + version = $VersionInfo + type = $Type + license = $License + revision = $Revision } - return $package +} + +function Write-JsonWithoutBom +{ + param( + [Parameter(Mandatory)] + [object]$Value, + + [Parameter(Mandatory)] + [string]$Path + ) + + $json = $Value | ConvertTo-Json -Depth 20 + [IO.File]::WriteAllText($Path, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) } $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path -$inputDirectory = (Resolve-Path -LiteralPath $ArtifactDirectory).Path -$outputDirectory = Join-Path $inputDirectory "winTerm-$Version-release" -if (Test-Path -LiteralPath $outputDirectory) +$outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) +{ + [IO.Path]::GetFullPath($OutputDirectory) +} +else { - throw "Release output directory already exists: $outputDirectory" + [IO.Path]::GetFullPath((Join-Path (Get-Location) $OutputDirectory)) } -New-Item -ItemType Directory -Path $outputDirectory | Out-Null +$symbolsStaging = $null try { + $timestamp = [DateTimeOffset]::Parse( + $BuildTimestamp, + [Globalization.CultureInfo]::InvariantCulture, + [Globalization.DateTimeStyles]::AssumeUniversal).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $commit = $CommitSha.ToLowerInvariant() + $upstream = $UpstreamRevision.ToLowerInvariant() + + if (Test-Path -LiteralPath $outputRoot) + { + throw "Release output directory already exists: $outputRoot" + } + New-Item -ItemType Directory -Path $outputRoot | Out-Null + $releaseFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new() - foreach ($architecture in $RequiredArchitecture) + [void]$releaseFiles.Add((Copy-ReleaseFile -Source $X64PackagePath -TargetName "winTerm-$Version-x64.msix")) + + $hasArm64 = -not [string]::IsNullOrWhiteSpace($Arm64PackagePath) + if ($hasArm64) { - $sourcePackage = Get-ReleasePackage -Architecture $architecture -Directory $inputDirectory - $targetPackage = Join-Path $outputDirectory "winTerm-$Version-$($architecture.ToLowerInvariant()).msix" - Copy-Item -LiteralPath $sourcePackage.FullName -Destination $targetPackage -ErrorAction Stop - [void]$releaseFiles.Add((Get-Item -LiteralPath $targetPackage)) + if ([string]::IsNullOrWhiteSpace($BundlePath)) + { + throw 'An MSIX bundle is required when an ARM64 package is included.' + } + [void]$releaseFiles.Add((Copy-ReleaseFile -Source $Arm64PackagePath -TargetName "winTerm-$Version-arm64.msix")) + [void]$releaseFiles.Add((Copy-ReleaseFile -Source $BundlePath -TargetName "winTerm-$Version.msixbundle")) } + elseif (-not [string]::IsNullOrWhiteSpace($BundlePath)) + { + throw 'A bundle must not be included without a validated ARM64 package.' + } + + $notices = Copy-ReleaseFile -Source (Join-Path $repositoryRoot 'THIRD_PARTY_NOTICES.md') -TargetName 'THIRD_PARTY_NOTICES.md' + [void]$releaseFiles.Add($notices) + $releaseNotes = Copy-ReleaseFile -Source (Join-Path $repositoryRoot 'docs\releases\1.0.0.md') -TargetName "winTerm-$Version-release-notes.md" + [void]$releaseFiles.Add($releaseNotes) - foreach ($requiredDocument in @('LICENSE', 'NOTICE.md', 'THIRD_PARTY_NOTICES.md')) + $symbolsRoot = (Resolve-Path -LiteralPath $SymbolsDirectory).Path + $pdbFiles = @(Get-ChildItem -LiteralPath $symbolsRoot -Recurse -File -Filter '*.pdb') + if ($pdbFiles.Count -eq 0) { - $source = Join-Path $repositoryRoot $requiredDocument - Copy-Item -LiteralPath $source -Destination (Join-Path $outputDirectory $requiredDocument) -ErrorAction Stop - [void]$releaseFiles.Add((Get-Item -LiteralPath (Join-Path $outputDirectory $requiredDocument))) + throw "No PDB files were found under '$symbolsRoot'." } - $releaseNotes = Join-Path $repositoryRoot "docs\releases\$Version.md" - if (-not (Test-Path -LiteralPath $releaseNotes -PathType Leaf)) + $symbolsStaging = Join-Path ([IO.Path]::GetTempPath()) ("winterm-symbols-{0}" -f [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $symbolsStaging | Out-Null + foreach ($pdb in $pdbFiles) { - throw "Release notes are missing: $releaseNotes" + $relative = $pdb.FullName.Substring($symbolsRoot.Length).TrimStart('\', '/') + $target = Join-Path $symbolsStaging $relative + $targetDirectory = Split-Path -Parent $target + if (-not (Test-Path -LiteralPath $targetDirectory)) + { + New-Item -ItemType Directory -Path $targetDirectory | Out-Null + } + Copy-Item -LiteralPath $pdb.FullName -Destination $target } - Copy-Item -LiteralPath $releaseNotes -Destination (Join-Path $outputDirectory 'release-notes.md') -ErrorAction Stop - [void]$releaseFiles.Add((Get-Item -LiteralPath (Join-Path $outputDirectory 'release-notes.md'))) + $symbolsPath = Join-Path $outputRoot "winTerm-$Version-symbols.zip" + Compress-Archive -Path (Join-Path $symbolsStaging '*') -DestinationPath $symbolsPath -CompressionLevel Optimal + [void]$releaseFiles.Add((Get-Item -LiteralPath $symbolsPath)) + + $components = [System.Collections.Generic.List[object]]::new() + [void]$components.Add((New-SbomComponent -Name 'winTerm' -VersionInfo $Version -Type 'application' -License 'MIT' -Revision $commit)) + [void]$components.Add((New-SbomComponent -Name 'Microsoft Terminal upstream source' -VersionInfo 'release-1.25' -Type 'framework' -License 'MIT' -Revision $upstream)) + [void]$components.Add((New-SbomComponent -Name 'winTerm PowerShell Module' -VersionInfo '1.0.0' -Type 'library' -License 'MIT' -Revision $commit)) + [void]$components.Add((New-SbomComponent -Name 'winterm-shim' -VersionInfo '1.0.0' -Type 'application' -License 'MIT' -Revision $commit)) + [void]$components.Add((New-SbomComponent -Name 'winTerm MSIX installer' -VersionInfo '1.0.0.0' -Type 'installer' -License 'MIT' -Revision $commit)) - $sbom = [ordered]@{ + $themeManifest = Get-Content -LiteralPath (Join-Path $repositoryRoot 'assets\winterm\themes\manifest.json') -Raw | ConvertFrom-Json + foreach ($theme in $themeManifest.themes) + { + [void]$components.Add((New-SbomComponent -Name "Theme: $($theme.name)" -VersionInfo ([string]$theme.sourceRevision) -Type 'data' -License ([string]$theme.license) -Revision ([string]$theme.sourceRevision))) + } + + $fontManifest = Get-Content -LiteralPath (Join-Path $repositoryRoot 'assets\winterm\fonts\manifest.json') -Raw | ConvertFrom-Json + foreach ($font in $fontManifest.fonts) + { + [void]$components.Add((New-SbomComponent -Name "Font: $($font.fullName)" -VersionInfo ([string]$font.version) -Type 'file' -License ([string]$font.license) -Revision ([string]$font.sourceRevision))) + } + + $vcpkg = Get-Content -LiteralPath (Join-Path $repositoryRoot 'vcpkg.json') -Raw | ConvertFrom-Json + foreach ($dependency in $vcpkg.overrides) + { + [void]$components.Add((New-SbomComponent -Name ([string]$dependency.name) -VersionInfo ([string]$dependency.version) -Type 'library')) + } + + [xml]$nuget = Get-Content -LiteralPath (Join-Path $repositoryRoot 'dep\nuget\packages.config') -Raw + foreach ($package in $nuget.packages.package) + { + if ($package.GetAttribute('developmentDependency') -ne 'true') + { + [void]$components.Add((New-SbomComponent -Name ([string]$package.id) -VersionInfo ([string]$package.version) -Type 'library')) + } + } + + $spdxPackages = @($components | ForEach-Object { + $safeId = ($_.name -replace '[^A-Za-z0-9.-]', '-').Trim('-') + [ordered]@{ + SPDXID = "SPDXRef-$safeId" + name = $_.name + versionInfo = $_.version + downloadLocation = 'NOASSERTION' + filesAnalyzed = $false + licenseConcluded = $_.license + licenseDeclared = $_.license + supplier = 'NOASSERTION' + sourceInfo = if ([string]::IsNullOrWhiteSpace($_.revision)) { 'NOASSERTION' } else { "Revision $($_.revision)" } + } + }) + $spdx = [ordered]@{ SPDXID = 'SPDXRef-DOCUMENT' spdxVersion = 'SPDX-2.3' + dataLicense = 'CC0-1.0' name = "winTerm-$Version" - documentNamespace = "https://github.com/HelloThisWorld/winTerm/releases/$Version/$CommitSha" + documentNamespace = "https://github.com/HelloThisWorld/winTerm/releases/tag/v$Version/sbom/$commit" creationInfo = [ordered]@{ - created = [DateTime]::UtcNow.ToString('o') + created = $timestamp creators = @('Tool: winTerm release-artifact generator') } - packages = @( - [ordered]@{ - SPDXID = 'SPDXRef-winTerm' - name = 'winTerm' - versionInfo = $Version - downloadLocation = 'NOASSERTION' - licenseConcluded = 'MIT' - licenseDeclared = 'MIT' - supplier = 'NOASSERTION' - externalRefs = @([ordered]@{ referenceCategory = 'PACKAGE-MANAGER'; referenceType = 'purl'; referenceLocator = 'pkg:github/HelloThisWorld/winTerm' }) - } - ) + documentDescribes = @('SPDXRef-winTerm') + packages = $spdxPackages relationships = @() } - $sbomPath = Join-Path $outputDirectory 'SBOM.spdx.json' - $sbom | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $sbomPath -Encoding utf8NoBOM - [void]$releaseFiles.Add((Get-Item -LiteralPath $sbomPath)) + $spdxPath = Join-Path $outputRoot 'SBOM.spdx.json' + Write-JsonWithoutBom -Value $spdx -Path $spdxPath + [void]$releaseFiles.Add((Get-Item -LiteralPath $spdxPath)) + $seed = [Text.Encoding]::UTF8.GetBytes("winTerm:${Version}:$commit") + $hash = [Security.Cryptography.SHA256]::Create().ComputeHash($seed) + $uuidBytes = [byte[]]$hash[0..15] + $serial = [guid]::new($uuidBytes) + $cycloneComponents = @($components | ForEach-Object { + [ordered]@{ + type = if ($_.type -in @('application', 'framework', 'library', 'file')) { $_.type } else { 'application' } + 'bom-ref' = "component:$($_.name):$($_.version)" + name = $_.name + version = $_.version + licenses = if ($_.license -eq 'NOASSERTION') { @() } else { @([ordered]@{ license = [ordered]@{ id = $_.license } }) } + properties = if ([string]::IsNullOrWhiteSpace($_.revision)) { @() } else { @([ordered]@{ name = 'winterm:sourceRevision'; value = $_.revision }) } + } + }) $cycloneDx = [ordered]@{ bomFormat = 'CycloneDX' - specVersion = '1.5' - serialNumber = "urn:uuid:$([guid]::NewGuid())" + specVersion = '1.6' + serialNumber = "urn:uuid:$serial" version = 1 metadata = [ordered]@{ - timestamp = [DateTime]::UtcNow.ToString('o') - component = [ordered]@{ type = 'application'; name = 'winTerm'; version = $Version } + timestamp = $timestamp + component = [ordered]@{ type = 'application'; 'bom-ref' = 'component:winTerm:1.0.0'; name = 'winTerm'; version = $Version } + properties = @( + [ordered]@{ name = 'winterm:commitSha'; value = $commit }, + [ordered]@{ name = 'winterm:microsoftTerminalUpstreamRevision'; value = $upstream } + ) } - components = @() + components = $cycloneComponents } - $cycloneDxPath = Join-Path $outputDirectory 'SBOM.cyclonedx.json' - $cycloneDx | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $cycloneDxPath -Encoding utf8NoBOM + $cycloneDxPath = Join-Path $outputRoot 'SBOM.cyclonedx.json' + Write-JsonWithoutBom -Value $cycloneDx -Path $cycloneDxPath [void]$releaseFiles.Add((Get-Item -LiteralPath $cycloneDxPath)) - $hashLines = foreach ($file in $releaseFiles | Sort-Object Name) - { - "$((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()) $($file.Name)" - } - $hashLines | Set-Content -LiteralPath (Join-Path $outputDirectory 'SHA256SUMS.txt') -Encoding ascii - $metadata = [ordered]@{ schemaVersion = 1 version = $Version - packageVersion = '0.6.0.0' - channel = 'beta' - commitSha = $CommitSha.ToLowerInvariant() - generatedAt = [DateTime]::UtcNow.ToString('o') - signing = 'unsigned-development-package' + packageVersion = '1.0.0.0' + channel = 'stable' + tag = "v$Version" + commitSha = $commit + buildTimestamp = $timestamp + architecture = @('x64') + $(if ($hasArm64) { @('arm64') } else { @() }) + microsoftTerminalUpstreamRevision = $upstream + workflowRunId = $WorkflowRunId + workspaceSchemaVersion = 2 + dockingModelVersion = 1 + shellProtocolVersion = 1 + themeSchemaVersion = 1 + updateManifestSchemaVersion = 1 + signing = $SigningStatus artifacts = @($releaseFiles | Sort-Object Name | ForEach-Object { [ordered]@{ fileName = $_.Name @@ -141,11 +309,31 @@ try } }) } - $metadata | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $outputDirectory 'release-metadata.json') -Encoding utf8NoBOM - Write-Host "Prepared release artifacts in $outputDirectory" -ForegroundColor Green + $metadataPath = Join-Path $outputRoot 'release-metadata.json' + Write-JsonWithoutBom -Value $metadata -Path $metadataPath + + & (Join-Path $PSScriptRoot 'generate-checksums.ps1') -Directory $outputRoot + if (-not $?) + { + throw 'Checksum generation failed.' + } + & (Join-Path $PSScriptRoot 'verify-checksums.ps1') -Directory $outputRoot + if (-not $?) + { + throw 'Checksum verification failed.' + } + + Write-Host "Prepared winTerm $Version release artifacts in $outputRoot" -ForegroundColor Green } catch { Write-Error "Release artifact generation failed: $($_.Exception.Message)" exit 1 } +finally +{ + if ($null -ne $symbolsStaging -and (Test-Path -LiteralPath $symbolsStaging)) + { + Remove-Item -LiteralPath $symbolsStaging -Recurse -Force + } +} diff --git a/scripts/winterm/generate-stable-update-manifest.ps1 b/scripts/winterm/generate-stable-update-manifest.ps1 new file mode 100644 index 000000000..9a1d90157 --- /dev/null +++ b/scripts/winterm/generate-stable-update-manifest.ps1 @@ -0,0 +1,73 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^https://github\.com/HelloThisWorld/winTerm/releases/tag/v1\.0\.0$')] + [string]$ReleaseUrl, + + [Parameter(Mandatory)] + [ValidatePattern('^\d{4}-\d{2}-\d{2}T')] + [string]$PublishedAt, + + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{64}$')] + [string]$X64Sha256, + + [Parameter(Mandatory)] + [string]$OutputPath +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +try +{ + $timestamp = [DateTimeOffset]::Parse( + $PublishedAt, + [Globalization.CultureInfo]::InvariantCulture, + [Globalization.DateTimeStyles]::AssumeUniversal).ToUniversalTime().ToString('o') + $output = if ([IO.Path]::IsPathRooted($OutputPath)) + { + [IO.Path]::GetFullPath($OutputPath) + } + else + { + [IO.Path]::GetFullPath((Join-Path (Get-Location) $OutputPath)) + } + if (Test-Path -LiteralPath $output) + { + throw "Stable update manifest already exists: $output" + } + $parent = Split-Path -Parent $output + if (-not (Test-Path -LiteralPath $parent)) + { + New-Item -ItemType Directory -Path $parent | Out-Null + } + + $manifest = [ordered]@{ + schemaVersion = 1 + channel = 'stable' + version = '1.0.0' + tag = 'v1.0.0' + publishedAt = $timestamp + releaseUrl = $ReleaseUrl + artifacts = @( + [ordered]@{ + architecture = 'x64' + type = 'msix' + fileName = 'winTerm-1.0.0-x64.msix' + sha256 = $X64Sha256.ToLowerInvariant() + } + ) + } + $json = $manifest | ConvertTo-Json -Depth 8 + [IO.File]::WriteAllText($output, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) + Write-Host "Generated stable update manifest: $output" -ForegroundColor Green +} +catch +{ + Write-Error "Stable update manifest generation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/generate-winget-manifests.ps1 b/scripts/winterm/generate-winget-manifests.ps1 new file mode 100644 index 000000000..44b99206e --- /dev/null +++ b/scripts/winterm/generate-winget-manifests.ps1 @@ -0,0 +1,107 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^https://github\.com/HelloThisWorld/winTerm/releases/download/v1\.0\.0/winTerm-1\.0\.0-x64\.msix$')] + [string]$InstallerUrl, + + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{64}$')] + [string]$InstallerSha256, + + [Parameter(Mandatory)] + [string]$OutputDirectory +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Write-Utf8File +{ + param( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter(Mandatory)] + [string[]]$Lines + ) + + [IO.File]::WriteAllLines($Path, $Lines, [Text.UTF8Encoding]::new($false)) +} + +try +{ + $output = if ([IO.Path]::IsPathRooted($OutputDirectory)) + { + [IO.Path]::GetFullPath($OutputDirectory) + } + else + { + [IO.Path]::GetFullPath((Join-Path (Get-Location) $OutputDirectory)) + } + if (Test-Path -LiteralPath $output) + { + throw "WinGet output directory already exists: $output" + } + New-Item -ItemType Directory -Path $output | Out-Null + $sha = $InstallerSha256.ToUpperInvariant() + + Write-Utf8File -Path (Join-Path $output 'Kaname.winTerm.yaml') -Lines @( + '# Generated only from the public winTerm v1.0.0 GitHub Release.' + 'PackageIdentifier: Kaname.winTerm' + 'PackageVersion: 1.0.0' + 'DefaultLocale: en-US' + 'ManifestType: version' + 'ManifestVersion: 1.6.0' + ) + + Write-Utf8File -Path (Join-Path $output 'Kaname.winTerm.installer.yaml') -Lines @( + '# Generated only from the public winTerm v1.0.0 GitHub Release.' + 'PackageIdentifier: Kaname.winTerm' + 'PackageVersion: 1.0.0' + 'InstallerType: msix' + 'Scope: user' + 'InstallModes:' + '- interactive' + '- silent' + 'UpgradeBehavior: install' + 'Installers:' + '- Architecture: x64' + " InstallerUrl: $InstallerUrl" + " InstallerSha256: $sha" + 'ManifestType: installer' + 'ManifestVersion: 1.6.0' + ) + + Write-Utf8File -Path (Join-Path $output 'Kaname.winTerm.locale.en-US.yaml') -Lines @( + '# Generated only from the public winTerm v1.0.0 GitHub Release.' + 'PackageIdentifier: Kaname.winTerm' + 'PackageVersion: 1.0.0' + 'PackageLocale: en-US' + 'Publisher: winTerm contributors' + 'PublisherUrl: https://github.com/HelloThisWorld/winTerm' + 'PublisherSupportUrl: https://github.com/HelloThisWorld/winTerm/issues' + 'PackageName: winTerm' + 'PackageUrl: https://github.com/HelloThisWorld/winTerm' + 'License: MIT' + 'LicenseUrl: https://github.com/HelloThisWorld/winTerm/blob/v1.0.0/LICENSE' + 'ShortDescription: Independent open-source terminal application based on Microsoft Windows Terminal.' + 'Moniker: winterm' + 'Tags:' + '- command-line' + '- console' + '- terminal' + '- powershell' + 'ManifestType: defaultLocale' + 'ManifestVersion: 1.6.0' + ) + + Write-Host "Generated WinGet manifests in $output" -ForegroundColor Green +} +catch +{ + Write-Error "WinGet manifest generation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/package-shell-assets.ps1 b/scripts/winterm/package-shell-assets.ps1 index 6f85ce9b8..d2ee068ab 100644 --- a/scripts/winterm/package-shell-assets.ps1 +++ b/scripts/winterm/package-shell-assets.ps1 @@ -32,7 +32,7 @@ foreach ($relativePath in $sourceAssets) } $version = Get-Content -LiteralPath (Join-Path $repositoryRoot 'shell\shared\version.json') -Raw | ConvertFrom-Json -if ($version.moduleVersion -ne '0.3.0' -or $version.protocolVersion -ne 1) +if ($version.moduleVersion -ne '1.0.0' -or $version.applicationVersion -ne '1.0.0' -or $version.protocolVersion -ne 1) { throw 'The winTerm Shell asset version metadata is invalid.' } diff --git a/scripts/winterm/package.ps1 b/scripts/winterm/package.ps1 index 180c89bcb..7b131e98f 100644 --- a/scripts/winterm/package.ps1 +++ b/scripts/winterm/package.ps1 @@ -8,7 +8,11 @@ param( [string]$Platform = 'x64', [Parameter()] - [switch]$SkipBuild + [switch]$SkipBuild, + + [Parameter()] + [ValidatePattern('^CN=')] + [string]$ExpectedPublisher = 'CN=winTerm Development' ) $ErrorActionPreference = 'Stop' @@ -18,7 +22,10 @@ function Assert-WinTermManifest { param( [Parameter(Mandatory)] - [string]$Path + [string]$Path, + + [Parameter(Mandatory)] + [string]$Publisher ) [xml]$manifest = Get-Content -LiteralPath $Path -Raw @@ -31,9 +38,9 @@ function Assert-WinTermManifest { throw "Package '$Path' does not use the Kaname.winTerm identity." } - if ($identity.Publisher -match 'Microsoft') + if ($identity.Publisher -match 'Microsoft' -or $identity.Publisher -cne $Publisher) { - throw "Package '$Path' uses a forbidden Microsoft publisher identity." + throw "Package '$Path' does not use the expected non-Microsoft publisher identity." } $aliases = @($manifest.SelectNodes('//uap3:AppExecutionAlias/*', $namespaceManager) | ForEach-Object { $_.Alias }) @@ -41,9 +48,9 @@ function Assert-WinTermManifest { throw "Package '$Path' must claim winterm.exe and must not claim wt.exe." } - if ($identity.Version -ne '0.6.0.0') + if ($identity.Version -ne '1.0.0.0') { - throw "Package '$Path' must use the winTerm v0.6 package version." + throw "Package '$Path' must use the winTerm 1.0.0.0 package version." } } @@ -132,7 +139,7 @@ $temporaryDirectory = $null try { - Assert-WinTermManifest -Path $sourceManifest + Assert-WinTermManifest -Path $sourceManifest -Publisher $ExpectedPublisher if (-not $SkipBuild) { @@ -172,7 +179,7 @@ try throw "makeappx.exe failed to unpack '$($artifact.FullName)' with exit code $LASTEXITCODE." } - Assert-WinTermManifest -Path (Join-Path $temporaryDirectory 'AppxManifest.xml') + Assert-WinTermManifest -Path (Join-Path $temporaryDirectory 'AppxManifest.xml') -Publisher $ExpectedPublisher Assert-AppearancePayload -Path $temporaryDirectory -RepositoryRoot $repositoryRoot & (Join-Path $PSScriptRoot 'package-shell-assets.ps1') -PackageRoot $temporaryDirectory if (-not $?) diff --git a/scripts/winterm/set-package-publisher.ps1 b/scripts/winterm/set-package-publisher.ps1 new file mode 100644 index 000000000..fd3bec175 --- /dev/null +++ b/scripts/winterm/set-package-publisher.ps1 @@ -0,0 +1,52 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^CN=')] + [string]$Publisher +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +$manifestPath = Join-Path $repositoryRoot 'src\cascadia\CascadiaPackage\Package-winTerm.appxmanifest' + +try +{ + if ($Publisher -match '(?i)Microsoft' -or $Publisher -eq 'CN=winTerm Development') + { + throw 'A stable package publisher must be a production winTerm identity and must not use Microsoft or the development placeholder.' + } + + [xml]$manifest = Get-Content -LiteralPath $manifestPath -Raw + $namespace = [System.Xml.XmlNamespaceManager]::new($manifest.NameTable) + $namespace.AddNamespace('f', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10') + $identity = $manifest.SelectSingleNode('/f:Package/f:Identity', $namespace) + if ($null -eq $identity -or $identity.Name -ne 'Kaname.winTerm' -or $identity.Version -ne '1.0.0.0') + { + throw 'The winTerm package identity or version is not the expected stable value.' + } + + $identity.SetAttribute('Publisher', $Publisher) + $settings = [System.Xml.XmlWriterSettings]::new() + $settings.Indent = $true + $settings.Encoding = [System.Text.UTF8Encoding]::new($false) + $writer = [System.Xml.XmlWriter]::Create($manifestPath, $settings) + try + { + $manifest.Save($writer) + } + finally + { + $writer.Dispose() + } + Write-Host 'Prepared the package manifest with the protected production publisher.' -ForegroundColor Green +} +catch +{ + Write-Error "Package publisher preparation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/sign-release-artifacts.ps1 b/scripts/winterm/sign-release-artifacts.ps1 new file mode 100644 index 000000000..2702a7024 --- /dev/null +++ b/scripts/winterm/sign-release-artifacts.ps1 @@ -0,0 +1,118 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string[]]$Path, + + [Parameter(Mandatory)] + [string]$CertificatePath, + + [Parameter(Mandatory)] + [string]$CertificatePassword, + + [Parameter(Mandatory)] + [ValidatePattern('^CN=')] + [string]$ExpectedPublisher, + + [Parameter(Mandatory)] + [ValidatePattern('^https?://')] + [string]$TimestampUrl +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$certificate = $null + +try +{ + $certificateFile = (Resolve-Path -LiteralPath $CertificatePath).Path + $flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet + $certificate = [Security.Cryptography.X509Certificates.X509Certificate2]::new( + $certificateFile, + $CertificatePassword, + $flags) + + if (-not $certificate.HasPrivateKey) + { + throw 'The signing certificate does not contain a private key.' + } + if ($certificate.Subject -cne $ExpectedPublisher) + { + throw 'The signing certificate subject does not match the protected package publisher.' + } + if ($certificate.NotAfter.ToUniversalTime() -le [DateTime]::UtcNow.AddDays(7)) + { + throw 'The signing certificate is expired or expires within seven days.' + } + $ekuExtension = @($certificate.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.37' }) + if ($ekuExtension.Count -ne 1) + { + throw 'The signing certificate does not contain one enhanced key usage extension.' + } + $enhancedKeyUsage = [Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]$ekuExtension[0] + $codeSigningEku = @($enhancedKeyUsage.EnhancedKeyUsages | ForEach-Object { $_.Value }) + if ($codeSigningEku -notcontains '1.3.6.1.5.5.7.3.3') + { + throw 'The certificate is not valid for code signing.' + } + + $signTool = Get-Command signtool.exe -ErrorAction SilentlyContinue + if ($null -eq $signTool) + { + throw 'signtool.exe was not found.' + } + + foreach ($artifactPath in $Path) + { + $artifact = Get-Item -LiteralPath (Resolve-Path -LiteralPath $artifactPath).Path + if ($artifact.Extension -notin @('.msix', '.msixbundle')) + { + throw "Unsupported signing target '$($artifact.Name)'." + } + if (($artifact.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) + { + throw "Signing target '$($artifact.Name)' is a reparse point." + } + + & $signTool.Source sign /fd SHA256 /td SHA256 /tr $TimestampUrl /f $certificateFile /p $CertificatePassword $artifact.FullName + if ($LASTEXITCODE -ne 0) + { + throw "signtool.exe failed for '$($artifact.Name)' with exit code $LASTEXITCODE." + } + & $signTool.Source verify /pa /all /v $artifact.FullName + if ($LASTEXITCODE -ne 0) + { + throw "signtool.exe trust verification failed for '$($artifact.Name)' with exit code $LASTEXITCODE." + } + + $signature = Get-AuthenticodeSignature -LiteralPath $artifact.FullName + if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate) + { + throw "Signature validation failed for '$($artifact.Name)': $($signature.Status)." + } + if ($signature.SignerCertificate.Subject -cne $ExpectedPublisher) + { + throw "The signer subject for '$($artifact.Name)' does not match the package publisher." + } + if ($null -eq $signature.TimeStamperCertificate) + { + throw "The signature for '$($artifact.Name)' does not contain a trusted timestamp." + } + Write-Host "PASS: signed and timestamped $($artifact.Name)" -ForegroundColor Green + } +} +catch +{ + Write-Error "Release signing failed: $($_.Exception.Message)" + exit 1 +} +finally +{ + if ($null -ne $certificate) + { + $certificate.Dispose() + } +} diff --git a/scripts/winterm/test-compatibility.ps1 b/scripts/winterm/test-compatibility.ps1 index cfa1394a2..ac53f7285 100644 --- a/scripts/winterm/test-compatibility.ps1 +++ b/scripts/winterm/test-compatibility.ps1 @@ -8,7 +8,7 @@ $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest & (Join-Path $PSScriptRoot 'test-shell-integration.ps1') -Shell WindowsPowerShell -if ($LASTEXITCODE -ne 0) +if (-not $?) { - exit $LASTEXITCODE + throw 'Windows PowerShell compatibility validation failed.' } diff --git a/scripts/winterm/test-privacy.ps1 b/scripts/winterm/test-privacy.ps1 new file mode 100644 index 000000000..bb6b9fbd3 --- /dev/null +++ b/scripts/winterm/test-privacy.ps1 @@ -0,0 +1,97 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Assert-Condition +{ + param( + [Parameter(Mandatory)] + [bool]$Condition, + + [Parameter(Mandatory)] + [string]$Message + ) + + if (-not $Condition) + { + throw $Message + } + Write-Host "PASS: $Message" -ForegroundColor Green +} + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path + +try +{ + $privacy = Get-Content -LiteralPath (Join-Path $repositoryRoot 'PRIVACY.md') -Raw + foreach ($statement in @( + 'does not collect or upload command text', + 'terminal output', + 'clipboard content', + 'Workspace contents', + 'Crash-report upload is off by default', + 'Diagnostic bundles are created only when the user requests one' + )) + { + Assert-Condition ($privacy.Contains($statement)) "Privacy policy covers '$statement'" + } + + $aboutSource = Get-Content -LiteralPath (Join-Path $repositoryRoot 'src\cascadia\TerminalApp\AboutDialog.cpp') -Raw + Assert-Condition ($aboutSource -match '(?s)#if defined\(WT_BRANDING_WINTERM\).*?co_return;.*?#else') 'winTerm update checks do not run without an explicit opt-in path' + + $providerSources = @( + 'src\cascadia\TerminalApp\init.cpp', + 'src\cascadia\TerminalConnection\init.cpp', + 'src\cascadia\TerminalControl\init.cpp', + 'src\cascadia\TerminalSettingsEditor\init.cpp', + 'src\cascadia\TerminalSettingsModel\init.cpp', + 'src\cascadia\WindowsTerminal\main.cpp' + ) + foreach ($relativePath in $providerSources) + { + $providerSource = Get-Content -LiteralPath (Join-Path $repositoryRoot $relativePath) -Raw + Assert-Condition ($providerSource -match '(?s)#if defined\(WT_BRANDING_WINTERM\).*?"winTerm\..*?#else.*?TraceLoggingOptionMicrosoftTelemetry\(\).*?#endif') "$relativePath separates the winTerm provider from Microsoft telemetry" + Assert-Condition ($providerSource -match '(?s)#if !defined\(WT_BRANDING_WINTERM\).*?TraceLoggingRegister') "$relativePath does not register usage telemetry for winTerm" + } + + $diagnosticSource = Get-Content -LiteralPath (Join-Path $repositoryRoot 'src\winterm\Diagnostics\Redaction.cpp') -Raw + foreach ($redaction in @('', '', '', '', '', '', '')) + { + Assert-Condition ($diagnosticSource.Contains($redaction)) "Diagnostic redaction contains $redaction" + } + + $workspaceDiagnostics = Get-Content -LiteralPath (Join-Path $repositoryRoot 'src\winterm\Workspaces\Diagnostics\WorkspaceDiagnostics.cpp') -Raw + Assert-Condition (-not ($workspaceDiagnostics -match '(?i)terminalOutput|clipboard(Content|Text)|command(Line|Text)|workspaceJson')) 'Workspace diagnostics exclude commands, terminal output, clipboard content, and Workspace JSON' + + $dockingDiagnostics = Get-Content -LiteralPath (Join-Path $repositoryRoot 'src\winterm\Docking\Diagnostics\DockingDiagnostics.cpp') -Raw + Assert-Condition (-not ($dockingDiagnostics -match '(?i)terminalOutput|clipboard(Content|Text)|command(Line|Text)')) 'Docking diagnostics exclude commands, terminal output, and clipboard content' + + $loggingCandidates = @(Get-ChildItem -LiteralPath (Join-Path $repositoryRoot 'src\winterm') -Recurse -File -Include '*.cpp', '*.h') + foreach ($file in $loggingCandidates) + { + $content = Get-Content -LiteralPath $file.FullName -Raw + if ($content -match '(?i)(TraceLoggingWrite|OutputDebugString|WriteEvent).*(command(Line|Text)|clipboard(Content|Text)|terminalOutput)') + { + throw "Potential sensitive logging call found in '$($file.FullName)'." + } + } + Write-Host 'PASS: no sensitive winTerm logging call was detected' -ForegroundColor Green + + & (Join-Path $PSScriptRoot 'test-diagnostics.ps1') + if (-not $?) + { + throw 'Diagnostic redaction validation failed.' + } + + Write-Host 'winTerm privacy validation passed.' -ForegroundColor Green +} +catch +{ + Write-Error "Privacy validation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/test-release-workflow.ps1 b/scripts/winterm/test-release-workflow.ps1 new file mode 100644 index 000000000..be6bf108d --- /dev/null +++ b/scripts/winterm/test-release-workflow.ps1 @@ -0,0 +1,71 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path + +try +{ + $workflowPath = Join-Path $repositoryRoot '.github\workflows\release.yml' + $workflow = Get-Content -LiteralPath $workflowPath -Raw + + foreach ($required in @( + "push:", + "- 'v1.0.0'", + 'workflow_dispatch:', + 'environment: winterm-stable-release', + 'permissions:', + 'contents: write', + 'id-token: write', + 'attestations: write', + 'gh release create', + '--draft', + 'verify-release-assets.ps1', + 'verify-checksums.ps1', + 'gh release download', + '--draft=false', + '--prerelease=false', + '--latest' + )) + { + if (-not $workflow.Contains($required)) + { + throw "Release workflow is missing required boundary '$required'." + } + } + + if ($workflow -match '(?m)^\s*pull_request_target\s*:' -or + $workflow -match '(?m)^\s*permissions:\s*write-all\s*$' -or + $workflow.Contains('--clobber')) + { + throw 'Release workflow contains a forbidden trigger, permission, or asset replacement option.' + } + + foreach ($line in $workflow -split "`r?`n") + { + if ($line -match '^\s*uses:\s*(?[^@\s]+)@(?[^\s#]+)') + { + if ($Matches.reference -notmatch '^[0-9a-f]{40}$') + { + throw "GitHub Action '$($Matches.action)' is not pinned to an immutable commit SHA." + } + } + } + + if ($workflow -match '(?m)^\s*path:\s+.*\*\*' -or $workflow -match '(?m)^\s*path:\s+.*\/\*\s*$') + { + throw 'Release workflow must not upload broad workspace globs.' + } + + Write-Host 'PASS: stable release workflow security and publication boundaries.' -ForegroundColor Green +} +catch +{ + Write-Error "Release workflow validation failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/test.ps1 b/scripts/winterm/test.ps1 index fb994b93a..275de8910 100644 --- a/scripts/winterm/test.ps1 +++ b/scripts/winterm/test.ps1 @@ -101,7 +101,7 @@ function Test-ShellExperienceFoundations } $manifest = Import-PowerShellDataFile -LiteralPath $moduleManifest - if ($manifest.ModuleVersion -ne '0.3.0' -or $manifest.PowerShellVersion -ne '5.1') + if ($manifest.ModuleVersion -ne '1.0.0' -or $manifest.PowerShellVersion -ne '5.1') { throw 'The winTerm PowerShell module manifest does not declare the supported version boundary.' } @@ -175,6 +175,18 @@ try Write-Host "Running winTerm $Suite tests ($Configuration, $Platform)..." Test-PowerShellSyntax -Directory $PSScriptRoot + & (Join-Path $PSScriptRoot 'verify-version.ps1') + if (-not $?) + { + throw 'Version consistency validation failed.' + } + + & (Join-Path $PSScriptRoot 'test-release-workflow.ps1') + if (-not $?) + { + throw 'Release workflow validation failed.' + } + & (Join-Path $PSScriptRoot 'validate-assets.ps1') if (-not $?) { @@ -203,6 +215,12 @@ try throw 'Diagnostic redaction source validation failed.' } + & (Join-Path $PSScriptRoot 'test-privacy.ps1') + if (-not $?) + { + throw 'Privacy validation failed.' + } + if ($Suite -eq 'Smoke') { Write-Host 'SKIP: Compiled unit tests are not part of the Smoke suite.' -ForegroundColor Yellow diff --git a/scripts/winterm/verify-branding.ps1 b/scripts/winterm/verify-branding.ps1 index ee6e44803..1c32b95ca 100644 --- a/scripts/winterm/verify-branding.ps1 +++ b/scripts/winterm/verify-branding.ps1 @@ -4,7 +4,11 @@ [CmdletBinding()] param( [Parameter()] - [string]$PackageOutput + [string]$PackageOutput, + + [Parameter()] + [ValidatePattern('^CN=')] + [string]$ExpectedPublisher = 'CN=winTerm Development' ) $ErrorActionPreference = 'Stop' @@ -72,8 +76,8 @@ function Test-Manifest Test-Requirement -Condition ($null -ne $identity -and $identity.Name -eq 'Kaname.winTerm') -Message "$Path uses package identity Kaname.winTerm" Test-Requirement -Condition ($null -ne $identity -and $identity.Name -notmatch '^Microsoft\.') -Message "$Path does not use a Microsoft package name" - Test-Requirement -Condition ($null -ne $identity -and $identity.Publisher -eq 'CN=winTerm Development') -Message "$Path uses the documented development publisher placeholder" - Test-Requirement -Condition ($null -ne $identity -and $identity.Version -eq '0.6.0.0') -Message "$Path uses package version 0.6.0.0" + Test-Requirement -Condition ($null -ne $identity -and $identity.Publisher -ceq $ExpectedPublisher) -Message "$Path uses the expected non-Microsoft publisher" + Test-Requirement -Condition ($null -ne $identity -and $identity.Version -eq '1.0.0.0') -Message "$Path uses package version 1.0.0.0" Test-Requirement -Condition ($null -ne $properties -and $properties.DisplayName -eq 'winTerm') -Message "$Path package display name is winTerm" Test-Requirement -Condition ($null -ne $application -and $application.Id -eq 'winTerm') -Message "$Path application ID is winTerm" Test-Requirement -Condition ($null -ne $visualElements -and $visualElements.DisplayName -eq 'winTerm') -Message "$Path application display name is winTerm" diff --git a/scripts/winterm/verify-checksums.ps1 b/scripts/winterm/verify-checksums.ps1 new file mode 100644 index 000000000..42ea350b4 --- /dev/null +++ b/scripts/winterm/verify-checksums.ps1 @@ -0,0 +1,70 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$Directory, + + [Parameter()] + [string]$ChecksumFile = 'SHA256SUMS.txt' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +try +{ + $root = (Resolve-Path -LiteralPath $Directory).Path + $checksumPath = Join-Path $root $ChecksumFile + if (-not (Test-Path -LiteralPath $checksumPath -PathType Leaf)) + { + throw "Checksum file is missing: $checksumPath" + } + + $seen = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + $lineNumber = 0 + foreach ($line in Get-Content -LiteralPath $checksumPath) + { + $lineNumber++ + if ($line -notmatch '^(?[0-9a-f]{64}) (?[^\\/]+)$') + { + throw "Invalid checksum syntax at line $lineNumber." + } + + $name = $Matches.name + if ($name -eq $ChecksumFile -or -not $seen.Add($name)) + { + throw "Duplicate or recursive checksum entry '$name'." + } + + $path = Join-Path $root $name + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) + { + throw "Checksummed file is missing: $name" + } + $item = Get-Item -LiteralPath $path + if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) + { + throw "Checksummed file '$name' is a reparse point." + } + + $actual = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actual -cne $Matches.hash) + { + throw "SHA-256 mismatch for '$name'." + } + Write-Host "PASS: $name" -ForegroundColor Green + } + + if ($seen.Count -eq 0) + { + throw 'Checksum file contains no entries.' + } + Write-Host "Verified $($seen.Count) SHA-256 checksum(s)." -ForegroundColor Green +} +catch +{ + Write-Error "Checksum verification failed: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/winterm/verify-release-assets.ps1 b/scripts/winterm/verify-release-assets.ps1 new file mode 100644 index 000000000..8dbb9ce61 --- /dev/null +++ b/scripts/winterm/verify-release-assets.ps1 @@ -0,0 +1,286 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$Directory, + + [Parameter()] + [ValidateSet('x64', 'arm64')] + [string[]]$Architecture = @('x64'), + + [Parameter()] + [switch]$RequireSigned, + + [Parameter()] + [string]$ExpectedPublisher +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Assert-Condition +{ + param( + [Parameter(Mandatory)] + [bool]$Condition, + + [Parameter(Mandatory)] + [string]$Message + ) + + if (-not $Condition) + { + throw $Message + } + Write-Host "PASS: $Message" -ForegroundColor Green +} + +function Test-TrustedSignature +{ + param( + [Parameter(Mandatory)] + [System.IO.FileInfo]$Artifact + ) + + $signature = Get-AuthenticodeSignature -LiteralPath $Artifact.FullName + Assert-Condition ($signature.Status -eq 'Valid') "$($Artifact.Name) has a valid trusted signature" + Assert-Condition ($null -ne $signature.SignerCertificate) "$($Artifact.Name) contains a signer certificate" + Assert-Condition ($null -ne $signature.TimeStamperCertificate) "$($Artifact.Name) signature is timestamped" + if (-not [string]::IsNullOrWhiteSpace($ExpectedPublisher)) + { + Assert-Condition ($signature.SignerCertificate.Subject -ceq $ExpectedPublisher) "$($Artifact.Name) signer matches the protected publisher" + } + + $signTool = Get-Command signtool.exe -ErrorAction SilentlyContinue + if ($null -eq $signTool) + { + throw 'signtool.exe is required for trusted signature verification.' + } + & $signTool.Source verify /pa /all /v $Artifact.FullName + if ($LASTEXITCODE -ne 0) + { + throw "signtool.exe trust verification failed for '$($Artifact.Name)' with exit code $LASTEXITCODE." + } + Write-Host "PASS: signtool.exe verified $($Artifact.Name)" -ForegroundColor Green +} + +function Test-MsixPackage +{ + param( + [Parameter(Mandatory)] + [System.IO.FileInfo]$Package, + + [Parameter(Mandatory)] + [string]$ExpectedArchitecture, + + [Parameter(Mandatory)] + [string]$TemporaryRoot + ) + + $makeAppx = Get-Command makeappx.exe -ErrorAction SilentlyContinue + if ($null -eq $makeAppx) + { + throw 'makeappx.exe is required for release package verification.' + } + + $unpackDirectory = Join-Path $TemporaryRoot $ExpectedArchitecture + New-Item -ItemType Directory -Path $unpackDirectory | Out-Null + & $makeAppx.Source unpack /p $Package.FullName /d $unpackDirectory /o + if ($LASTEXITCODE -ne 0) + { + throw "makeappx.exe failed to unpack '$($Package.Name)'." + } + + [xml]$manifest = Get-Content -LiteralPath (Join-Path $unpackDirectory 'AppxManifest.xml') -Raw + $namespace = [System.Xml.XmlNamespaceManager]::new($manifest.NameTable) + $namespace.AddNamespace('f', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10') + $namespace.AddNamespace('uap3', 'http://schemas.microsoft.com/appx/manifest/uap/windows10/3') + $namespace.AddNamespace('desktop', 'http://schemas.microsoft.com/appx/manifest/desktop/windows10') + $identity = $manifest.SelectSingleNode('/f:Package/f:Identity', $namespace) + $aliases = @($manifest.SelectNodes('//uap3:AppExecutionAlias/desktop:ExecutionAlias', $namespace) | ForEach-Object { $_.Alias }) + + Assert-Condition ($identity.Name -eq 'Kaname.winTerm') "$($Package.Name) uses package identity Kaname.winTerm" + Assert-Condition ($identity.Version -eq '1.0.0.0') "$($Package.Name) contains package version 1.0.0.0" + Assert-Condition ($identity.ProcessorArchitecture.ToLowerInvariant() -eq $ExpectedArchitecture) "$($Package.Name) contains architecture $ExpectedArchitecture" + Assert-Condition ($aliases -contains 'winterm.exe') "$($Package.Name) registers winterm.exe" + Assert-Condition ($aliases -notcontains 'wt.exe') "$($Package.Name) does not register wt.exe" + Assert-Condition (-not (Test-Path -LiteralPath (Join-Path $unpackDirectory 'wt.exe'))) "$($Package.Name) does not contain wt.exe" + + if (-not [string]::IsNullOrWhiteSpace($ExpectedPublisher)) + { + Assert-Condition ($identity.Publisher -ceq $ExpectedPublisher) "$($Package.Name) publisher matches the protected publisher" + } + + if ($RequireSigned) + { + Test-TrustedSignature -Artifact $Package + } + else + { + $signature = Get-AuthenticodeSignature -LiteralPath $Package.FullName + Write-Host "INFO: $($Package.Name) signature status is $($signature.Status)." + } +} + +function Test-MsixBundle +{ + param( + [Parameter(Mandatory)] + [System.IO.FileInfo]$Bundle, + + [Parameter(Mandatory)] + [string]$TemporaryRoot + ) + + $makeAppx = Get-Command makeappx.exe -ErrorAction SilentlyContinue + if ($null -eq $makeAppx) + { + throw 'makeappx.exe is required for release bundle verification.' + } + + $unpackDirectory = Join-Path $TemporaryRoot 'bundle' + New-Item -ItemType Directory -Path $unpackDirectory | Out-Null + & $makeAppx.Source unpack /p $Bundle.FullName /d $unpackDirectory /o + if ($LASTEXITCODE -ne 0) + { + throw "makeappx.exe failed to unpack '$($Bundle.Name)'." + } + + [xml]$manifest = Get-Content -LiteralPath (Join-Path $unpackDirectory 'AppxMetadata\AppxBundleManifest.xml') -Raw + $namespace = [System.Xml.XmlNamespaceManager]::new($manifest.NameTable) + $namespace.AddNamespace('b', 'http://schemas.microsoft.com/appx/2013/bundle') + $identity = $manifest.SelectSingleNode('/b:Bundle/b:Identity', $namespace) + $packages = @($manifest.SelectNodes('/b:Bundle/b:Packages/b:Package', $namespace)) + $architectures = @($packages | ForEach-Object { $_.Architecture.ToLowerInvariant() }) + + Assert-Condition ($identity.Name -eq 'Kaname.winTerm') "$($Bundle.Name) uses bundle identity Kaname.winTerm" + Assert-Condition ($identity.Version -eq '1.0.0.0') "$($Bundle.Name) contains bundle version 1.0.0.0" + if (-not [string]::IsNullOrWhiteSpace($ExpectedPublisher)) + { + Assert-Condition ($identity.Publisher -ceq $ExpectedPublisher) "$($Bundle.Name) publisher matches the protected publisher" + } + Assert-Condition ($packages.Count -eq 2) "$($Bundle.Name) contains exactly two application packages" + Assert-Condition (((($architectures | Sort-Object) -join ',') -ceq 'arm64,x64')) "$($Bundle.Name) contains exactly x64 and ARM64" + foreach ($package in $packages) + { + Assert-Condition (Test-Path -LiteralPath (Join-Path $unpackDirectory $package.FileName) -PathType Leaf) "$($Bundle.Name) contains $($package.FileName)" + } + + if ($RequireSigned) + { + Test-TrustedSignature -Artifact $Bundle + } + else + { + $signature = Get-AuthenticodeSignature -LiteralPath $Bundle.FullName + Write-Host "INFO: $($Bundle.Name) signature status is $($signature.Status)." + } +} + +$temporaryDirectory = $null + +try +{ + $root = (Resolve-Path -LiteralPath $Directory).Path + if ($RequireSigned -and [string]::IsNullOrWhiteSpace($ExpectedPublisher)) + { + throw 'ExpectedPublisher is required when RequireSigned is enabled.' + } + $required = [System.Collections.Generic.List[string]]::new() + foreach ($name in @( + 'SHA256SUMS.txt', + 'THIRD_PARTY_NOTICES.md', + 'SBOM.spdx.json', + 'SBOM.cyclonedx.json', + 'release-metadata.json', + 'winTerm-1.0.0-release-notes.md', + 'winTerm-1.0.0-symbols.zip' + )) + { + [void]$required.Add($name) + } + foreach ($arch in $Architecture) + { + [void]$required.Add("winTerm-1.0.0-$arch.msix") + } + if ($Architecture.Count -eq 2) + { + [void]$required.Add('winTerm-1.0.0.msixbundle') + } + + $allowed = [System.Collections.Generic.HashSet[string]]::new($required, [StringComparer]::OrdinalIgnoreCase) + foreach ($item in Get-ChildItem -LiteralPath $root -Force) + { + Assert-Condition (-not $item.PSIsContainer) "Release entry '$($item.Name)' is a file" + Assert-Condition (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0) "Release entry '$($item.Name)' is not a reparse point" + Assert-Condition ($allowed.Contains($item.Name)) "Release entry '$($item.Name)' is allowlisted" + Assert-Condition ($item.Length -gt 0) "Release entry '$($item.Name)' is not empty" + } + foreach ($name in $required) + { + Assert-Condition (Test-Path -LiteralPath (Join-Path $root $name) -PathType Leaf) "Required release asset exists: $name" + } + + & (Join-Path $PSScriptRoot 'verify-checksums.ps1') -Directory $root + if (-not $?) + { + throw 'Release checksum verification failed.' + } + + $checksumNames = @(Get-Content -LiteralPath (Join-Path $root 'SHA256SUMS.txt') | ForEach-Object { + if ($_ -notmatch '^[0-9a-f]{64} (?[^\\/]+)$') + { + throw 'SHA256SUMS.txt contains invalid syntax.' + } + $Matches.name + }) + $expectedChecksumNames = @($required | Where-Object { $_ -ne 'SHA256SUMS.txt' } | Sort-Object) + Assert-Condition (((($checksumNames | Sort-Object) -join "`n") -ceq (($expectedChecksumNames | Sort-Object) -join "`n"))) 'SHA256SUMS.txt covers every release asset except itself' + + $releaseNotes = Get-Content -LiteralPath (Join-Path $root 'winTerm-1.0.0-release-notes.md') -Raw + Assert-Condition ($releaseNotes.Contains('# winTerm 1.0.0')) 'Release notes contain the stable release title' + + $metadata = Get-Content -LiteralPath (Join-Path $root 'release-metadata.json') -Raw | ConvertFrom-Json + Assert-Condition ($metadata.version -eq '1.0.0') 'Release metadata version is 1.0.0' + Assert-Condition ($metadata.packageVersion -eq '1.0.0.0') 'Release metadata package version is 1.0.0.0' + Assert-Condition ($metadata.channel -eq 'stable') 'Release metadata channel is stable' + Assert-Condition ($metadata.commitSha -match '^[0-9a-f]{40}$') 'Release metadata contains a full commit SHA' + Assert-Condition ($metadata.microsoftTerminalUpstreamRevision -match '^[0-9a-f]{40}$') 'Release metadata contains the upstream revision' + + foreach ($sbomName in @('SBOM.spdx.json', 'SBOM.cyclonedx.json')) + { + $sbomText = Get-Content -LiteralPath (Join-Path $root $sbomName) -Raw + $null = $sbomText | ConvertFrom-Json + Assert-Condition ($sbomText -notmatch '[A-Za-z]:\\\\Users\\\\') "$sbomName contains no Windows user path" + Assert-Condition ($sbomText -notmatch '(?i)(certificatePassword|github_token|BEGIN PRIVATE KEY)') "$sbomName contains no signing or repository secret" + } + + $temporaryDirectory = Join-Path ([IO.Path]::GetTempPath()) ("winterm-release-verify-{0}" -f [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $temporaryDirectory | Out-Null + foreach ($arch in $Architecture) + { + $package = Get-Item -LiteralPath (Join-Path $root "winTerm-1.0.0-$arch.msix") + Test-MsixPackage -Package $package -ExpectedArchitecture $arch -TemporaryRoot $temporaryDirectory + } + if ($Architecture.Count -eq 2) + { + $bundle = Get-Item -LiteralPath (Join-Path $root 'winTerm-1.0.0.msixbundle') + Test-MsixBundle -Bundle $bundle -TemporaryRoot $temporaryDirectory + } + + Write-Host 'winTerm release asset verification passed.' -ForegroundColor Green +} +catch +{ + Write-Error "Release asset verification failed: $($_.Exception.Message)" + exit 1 +} +finally +{ + if ($null -ne $temporaryDirectory -and (Test-Path -LiteralPath $temporaryDirectory)) + { + Remove-Item -LiteralPath $temporaryDirectory -Recurse -Force + } +} diff --git a/scripts/winterm/verify-version.ps1 b/scripts/winterm/verify-version.ps1 new file mode 100644 index 000000000..7f2993f2a --- /dev/null +++ b/scripts/winterm/verify-version.ps1 @@ -0,0 +1,122 @@ +# Copyright (c) winTerm contributors. +# Licensed under the MIT license. + +[CmdletBinding()] +param( + [Parameter()] + [switch]$RequireTag +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +function Assert-Condition +{ + param( + [Parameter(Mandatory)] + [bool]$Condition, + + [Parameter(Mandatory)] + [string]$Message + ) + + if (-not $Condition) + { + throw $Message + } + Write-Host "PASS: $Message" -ForegroundColor Green +} + +function Get-Text +{ + param( + [Parameter(Mandatory)] + [string]$RelativePath + ) + + $path = Join-Path $script:repositoryRoot $RelativePath + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) + { + throw "Versioned file is missing: $RelativePath" + } + return Get-Content -LiteralPath $path -Raw +} + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path + +try +{ + $versionPath = Join-Path $repositoryRoot 'src\winterm\Branding\version.json' + $version = Get-Content -LiteralPath $versionPath -Raw | ConvertFrom-Json + + Assert-Condition ($version.applicationVersion -eq '1.0.0') 'Application version is 1.0.0' + Assert-Condition ($version.packageVersion -eq '1.0.0.0') 'Package version is 1.0.0.0' + Assert-Condition ($version.moduleVersion -eq '1.0.0') 'PowerShell module version is 1.0.0' + Assert-Condition ($version.channel -eq 'stable') 'Release channel is stable' + Assert-Condition ($version.tag -eq 'v1.0.0') 'Release tag is v1.0.0' + Assert-Condition ($version.workspaceSchemaVersion -eq 2) 'Workspace Schema version remains 2' + Assert-Condition ($version.dockingModelVersion -eq 1) 'Docking Model version remains 1' + Assert-Condition ($version.shellProtocolVersion -eq 1) 'Shell Protocol version remains 1' + Assert-Condition ($version.themeSchemaVersion -eq 1) 'Theme Schema version remains 1' + Assert-Condition ($version.updateManifestSchemaVersion -eq 1) 'Update Manifest Schema version remains 1' + Assert-Condition ($version.microsoftTerminalUpstreamRevision -match '^[0-9a-f]{40}$') 'Microsoft Terminal upstream revision is immutable' + + [xml]$manifest = Get-Text 'src\cascadia\CascadiaPackage\Package-winTerm.appxmanifest' + $manifestNamespace = [System.Xml.XmlNamespaceManager]::new($manifest.NameTable) + $manifestNamespace.AddNamespace('f', 'http://schemas.microsoft.com/appx/manifest/foundation/windows10') + $identity = $manifest.SelectSingleNode('/f:Package/f:Identity', $manifestNamespace) + Assert-Condition ($identity.Version -eq $version.packageVersion) 'MSIX manifest version matches release metadata' + + [xml]$customProps = Get-Text 'custom.props' + $propsNamespace = [System.Xml.XmlNamespaceManager]::new($customProps.NameTable) + $propsNamespace.AddNamespace('m', 'http://schemas.microsoft.com/developer/msbuild/2003') + $major = $customProps.SelectSingleNode('//m:VersionMajor', $propsNamespace).'#text' + $minor = $customProps.SelectSingleNode('//m:VersionMinor', $propsNamespace).'#text' + Assert-Condition ("$major.$minor" -eq '1.0') 'Executable metadata major and minor versions match 1.0' + + $moduleManifest = Import-PowerShellDataFile -LiteralPath (Join-Path $repositoryRoot 'shell\powershell\winTerm.Shell\winTerm.Shell.psd1') + Assert-Condition ($moduleManifest.ModuleVersion.ToString() -eq $version.moduleVersion) 'PowerShell manifest version matches release metadata' + Assert-Condition ((Get-Text 'shell\powershell\winTerm.Shell\winTerm.Shell.psm1').Contains("`$script:WinTermModuleVersion = '1.0.0'")) 'PowerShell module runtime version matches release metadata' + + $shellVersion = Get-Text 'shell\shared\version.json' | ConvertFrom-Json + Assert-Condition ($shellVersion.applicationVersion -eq $version.applicationVersion) 'Shell asset application version matches release metadata' + Assert-Condition ($shellVersion.moduleVersion -eq $version.moduleVersion) 'Shell asset module version matches release metadata' + Assert-Condition ($shellVersion.protocolVersion -eq $version.shellProtocolVersion) 'Shell asset protocol version matches release metadata' + + $releaseHeader = Get-Text 'src\winterm\Branding\ReleaseMetadata.h' + Assert-Condition ($releaseHeader.Contains('ApplicationVersion{ L"1.0.0" }')) 'About metadata application version is 1.0.0' + Assert-Condition ($releaseHeader.Contains('ReleaseChannel{ L"Stable" }')) 'About metadata channel is Stable' + Assert-Condition ($releaseHeader.Contains($version.microsoftTerminalUpstreamRevision)) 'About metadata contains the Microsoft Terminal upstream revision' + Assert-Condition ($releaseHeader.Contains('WorkspaceSchemaVersion{ 2 }')) 'About metadata contains Workspace Schema version 2' + Assert-Condition ($releaseHeader.Contains('DockingModelVersion{ 1 }')) 'About metadata contains Docking Model version 1' + Assert-Condition ($releaseHeader.Contains('ShellProtocolVersion{ 1 }')) 'About metadata contains Shell Protocol version 1' + Assert-Condition ($releaseHeader.Contains('ThemeSchemaVersion{ 1 }')) 'About metadata contains Theme Schema version 1' + + Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('WorkspaceSchemaVersion{ 2 }')) 'Workspace model remains at Schema version 2' + Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('DockingModelVersion{ 1 }')) 'Workspace model remains at Docking version 1' + Assert-Condition ((Get-Text 'src\winterm\Shell\Protocol\ShellIntegrationProtocol.h').Contains('ShellProtocolVersion{ 1 }')) 'Shell protocol remains at version 1' + Assert-Condition ((Get-Text 'src\winterm\Appearance\Themes\ThemeDescriptor.h').Contains('CurrentThemeSchemaVersion{ 1 }')) 'Theme Schema remains at version 1' + Assert-Condition ((Get-Text 'src\winterm\Workspaces\Persistence\WorkspaceSerializer.cpp').Contains('"1.0.0"')) 'Workspace application-version fallback is 1.0.0' + + $releaseWorkflow = Get-Text '.github\workflows\release.yml' + Assert-Condition ($releaseWorkflow.Contains('v1.0.0')) 'Release workflow targets v1.0.0' + Assert-Condition ($releaseWorkflow.Contains('winTerm 1.0.0')) 'Release workflow title is winTerm 1.0.0' + Assert-Condition ($releaseWorkflow.Contains('winTerm-1.0.0-x64.msix')) 'Release workflow uses the required x64 artifact name' + + $releaseNotes = Get-Text 'docs\releases\1.0.0.md' + Assert-Condition ($releaseNotes.Contains('# winTerm 1.0.0')) 'Release notes title is winTerm 1.0.0' + Assert-Condition ((Get-Text 'CHANGELOG.md').Contains('## 1.0.0')) 'Changelog contains 1.0.0' + + if ($RequireTag) + { + $tag = (& git describe --tags --exact-match 2>$null).Trim() + Assert-Condition ($LASTEXITCODE -eq 0 -and $tag -eq $version.tag) 'Checked-out commit is exactly tagged v1.0.0' + } + + Write-Host 'winTerm version consistency verification passed.' -ForegroundColor Green +} +catch +{ + Write-Error "Version consistency verification failed: $($_.Exception.Message)" + exit 1 +} diff --git a/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 b/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 index faf4a3e2b..1270a4a52 100644 --- a/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 +++ b/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'winTerm.Shell.psm1' - ModuleVersion = '0.3.0' + ModuleVersion = '1.0.0' GUID = 'f65cd8f4-5d25-4a2a-a0d4-58df1ab3dc5a' Author = 'winTerm contributors' CompanyName = 'winTerm' diff --git a/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 b/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 index 2568c1922..e00fe15f8 100644 --- a/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 +++ b/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 @@ -3,7 +3,7 @@ Set-StrictMode -Version Latest -$script:WinTermModuleVersion = '0.3.0' +$script:WinTermModuleVersion = '1.0.0' $script:WinTermProtocolVersion = 1 $script:WinTermIntegrationEnabled = $false $script:WinTermPromptWrapped = $false diff --git a/shell/shared/version.json b/shell/shared/version.json index 383993343..ba28443a1 100644 --- a/shell/shared/version.json +++ b/shell/shared/version.json @@ -1,5 +1,5 @@ { - "applicationVersion": "0.5.0-alpha", - "moduleVersion": "0.3.0", + "applicationVersion": "1.0.0", + "moduleVersion": "1.0.0", "protocolVersion": 1 } diff --git a/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest b/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest index a01440720..db1228002 100644 --- a/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest @@ -18,7 +18,7 @@ + Version="1.0.0.0" /> winTerm diff --git a/src/cascadia/TerminalApp/AboutDialog.cpp b/src/cascadia/TerminalApp/AboutDialog.cpp index 96da48de0..7b026facc 100644 --- a/src/cascadia/TerminalApp/AboutDialog.cpp +++ b/src/cascadia/TerminalApp/AboutDialog.cpp @@ -10,6 +10,7 @@ #include "../../types/inc/utils.hpp" #include "Utils.h" +#include "../../winterm/Branding/ReleaseMetadata.h" using namespace winrt; using namespace winrt::Microsoft::Terminal::Settings::Model; @@ -38,7 +39,87 @@ namespace winrt::TerminalApp::implementation winrt::hstring AboutDialog::ApplicationVersion() { +#if defined(WT_BRANDING_WINTERM) + return winrt::hstring{ winTerm::Branding::ApplicationVersion }; +#else return CascadiaSettings::ApplicationVersion(); +#endif + } + + winrt::hstring AboutDialog::ReleaseDetails() + { +#if defined(WT_BRANDING_WINTERM) +#if defined(_M_ARM64) + constexpr std::wstring_view architecture{ L"ARM64" }; +#elif defined(_M_X64) + constexpr std::wstring_view architecture{ L"x64" }; +#else + constexpr std::wstring_view architecture{ L"unknown" }; +#endif + const auto details = wil::str_printf( + L"Channel: %.*s\n" + L"Architecture: %.*s\n" + L"Commit SHA: %.*s\n" + L"Build timestamp: %.*s\n" + L"Microsoft Terminal upstream revision: %.*s\n" + L"Workspace Schema version: %u\n" + L"Docking Model version: %u\n" + L"Shell Protocol version: %u\n" + L"Theme Schema version: %u", + static_cast(winTerm::Branding::ReleaseChannel.size()), + winTerm::Branding::ReleaseChannel.data(), + static_cast(architecture.size()), + architecture.data(), + static_cast(winTerm::Branding::CommitSha.size()), + winTerm::Branding::CommitSha.data(), + static_cast(winTerm::Branding::BuildTimestamp.size()), + winTerm::Branding::BuildTimestamp.data(), + static_cast(winTerm::Branding::MicrosoftTerminalUpstreamRevision.size()), + winTerm::Branding::MicrosoftTerminalUpstreamRevision.data(), + winTerm::Branding::WorkspaceSchemaVersion, + winTerm::Branding::DockingModelVersion, + winTerm::Branding::ShellProtocolVersion, + winTerm::Branding::ThemeSchemaVersion); + return winrt::hstring{ details }; +#else + return {}; +#endif + } + + Windows::Foundation::Uri AboutDialog::SourceCodeUri() + { +#if defined(WT_BRANDING_WINTERM) + return Windows::Foundation::Uri{ L"https://github.com/HelloThisWorld/winTerm" }; +#else + return Windows::Foundation::Uri{ L"https://go.microsoft.com/fwlink/?linkid=2203152" }; +#endif + } + + Windows::Foundation::Uri AboutDialog::DocumentationUri() + { +#if defined(WT_BRANDING_WINTERM) + return Windows::Foundation::Uri{ L"https://github.com/HelloThisWorld/winTerm/tree/main/docs" }; +#else + return Windows::Foundation::Uri{ L"https://go.microsoft.com/fwlink/?linkid=2125416" }; +#endif + } + + Windows::Foundation::Uri AboutDialog::ReleaseNotesUri() + { +#if defined(WT_BRANDING_WINTERM) + return Windows::Foundation::Uri{ L"https://github.com/HelloThisWorld/winTerm/releases" }; +#else + return Windows::Foundation::Uri{ L"https://go.microsoft.com/fwlink/?linkid=2125417" }; +#endif + } + + Windows::Foundation::Uri AboutDialog::PrivacyPolicyUri() + { +#if defined(WT_BRANDING_WINTERM) + return Windows::Foundation::Uri{ L"https://github.com/HelloThisWorld/winTerm/blob/main/PRIVACY.md" }; +#else + return Windows::Foundation::Uri{ L"https://go.microsoft.com/fwlink/?linkid=2125418" }; +#endif } void AboutDialog::_SendFeedbackOnClick(const IInspectable& /*sender*/, const Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs& /*eventArgs*/) @@ -65,6 +146,10 @@ namespace winrt::TerminalApp::implementation safe_void_coroutine AboutDialog::_queueUpdateCheck() { +#if defined(WT_BRANDING_WINTERM) + // Stable winTerm does not contact an update service without explicit consent. + co_return; +#else auto strongThis = get_strong(); auto now{ std::chrono::system_clock::now() }; if (now - _lastUpdateCheck < std::chrono::days{ 1 }) @@ -151,5 +236,6 @@ namespace winrt::TerminalApp::implementation co_await wil::resume_foreground(strongThis->Dispatcher()); CheckingForUpdates(false); +#endif } } diff --git a/src/cascadia/TerminalApp/AboutDialog.h b/src/cascadia/TerminalApp/AboutDialog.h index 0c8c174b4..5126782bc 100644 --- a/src/cascadia/TerminalApp/AboutDialog.h +++ b/src/cascadia/TerminalApp/AboutDialog.h @@ -14,6 +14,11 @@ namespace winrt::TerminalApp::implementation winrt::hstring ApplicationDisplayName(); winrt::hstring ApplicationVersion(); + winrt::hstring ReleaseDetails(); + Windows::Foundation::Uri SourceCodeUri(); + Windows::Foundation::Uri DocumentationUri(); + Windows::Foundation::Uri ReleaseNotesUri(); + Windows::Foundation::Uri PrivacyPolicyUri(); til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(bool, UpdatesAvailable, PropertyChanged.raise, false); diff --git a/src/cascadia/TerminalApp/AboutDialog.idl b/src/cascadia/TerminalApp/AboutDialog.idl index 28b48530d..b1e3b11d8 100644 --- a/src/cascadia/TerminalApp/AboutDialog.idl +++ b/src/cascadia/TerminalApp/AboutDialog.idl @@ -9,6 +9,11 @@ namespace TerminalApp String ApplicationDisplayName { get; }; String ApplicationVersion { get; }; + String ReleaseDetails { get; }; + Windows.Foundation.Uri SourceCodeUri { get; }; + Windows.Foundation.Uri DocumentationUri { get; }; + Windows.Foundation.Uri ReleaseNotesUri { get; }; + Windows.Foundation.Uri PrivacyPolicyUri { get; }; Boolean CheckingForUpdates { get; }; Boolean UpdatesAvailable { get; }; diff --git a/src/cascadia/TerminalApp/AboutDialog.xaml b/src/cascadia/TerminalApp/AboutDialog.xaml index ff075efb9..c7084014a 100644 --- a/src/cascadia/TerminalApp/AboutDialog.xaml +++ b/src/cascadia/TerminalApp/AboutDialog.xaml @@ -23,9 +23,12 @@ - - - + + + + - - - - - + + + + + diff --git a/src/cascadia/TerminalApp/init.cpp b/src/cascadia/TerminalApp/init.cpp index 38ecf7f87..95f90bc7f 100644 --- a/src/cascadia/TerminalApp/init.cpp +++ b/src/cascadia/TerminalApp/init.cpp @@ -4,29 +4,43 @@ #include "pch.h" #include -// Note: Generate GUID using TlgGuid.exe tool -TRACELOGGING_DEFINE_PROVIDER( - g_hTerminalAppProvider, - "Microsoft.Windows.Terminal.App", - // {24a1622f-7da7-5c77-3303-d850bd1ab2ed} - (0x24a1622f, 0x7da7, 0x5c77, 0x33, 0x03, 0xd8, 0x50, 0xbd, 0x1a, 0xb2, 0xed), - TraceLoggingOptionMicrosoftTelemetry()); +// winTerm deliberately leaves its provider unregistered. The distinct name and +// identifier prevent the binary from presenting itself as a Microsoft product. +#if defined(WT_BRANDING_WINTERM) +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalAppProvider, + "winTerm.App", + // {21379562-c011-418c-9ae0-67a61f4b16c9} + (0x21379562, 0xc011, 0x418c, 0x9a, 0xe0, 0x67, 0xa6, 0x1f, 0x4b, 0x16, 0xc9)); +#else +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalAppProvider, + "Microsoft.Windows.Terminal.App", + // {24a1622f-7da7-5c77-3303-d850bd1ab2ed} + (0x24a1622f, 0x7da7, 0x5c77, 0x33, 0x03, 0xd8, 0x50, 0xbd, 0x1a, 0xb2, 0xed), + TraceLoggingOptionMicrosoftTelemetry()); +#endif BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { switch (reason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hInstDll); - TraceLoggingRegister(g_hTerminalAppProvider); - Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalAppProvider); - break; - case DLL_PROCESS_DETACH: - if (g_hTerminalAppProvider) - { - TraceLoggingUnregister(g_hTerminalAppProvider); - } - break; + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hTerminalAppProvider); + Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalAppProvider); +#endif + break; + case DLL_PROCESS_DETACH: +#if !defined(WT_BRANDING_WINTERM) + if (g_hTerminalAppProvider) + { + TraceLoggingUnregister(g_hTerminalAppProvider); + } +#endif + break; } return TRUE; diff --git a/src/cascadia/TerminalConnection/init.cpp b/src/cascadia/TerminalConnection/init.cpp index fe3d3c314..814ab90ba 100644 --- a/src/cascadia/TerminalConnection/init.cpp +++ b/src/cascadia/TerminalConnection/init.cpp @@ -4,31 +4,44 @@ #include "pch.h" #include -// Note: Generate GUID using TlgGuid.exe tool -#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr. -TRACELOGGING_DEFINE_PROVIDER( +#if defined(WT_BRANDING_WINTERM) +#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr. +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalConnectionProvider, + "winTerm.Connection", + // {8bebe71b-1ac1-4ff4-a9e2-f5dccae0918e} + (0x8bebe71b, 0x1ac1, 0x4ff4, 0xa9, 0xe2, 0xf5, 0xdc, 0xca, 0xe0, 0x91, 0x8e)); +#else +// Note: Generate GUID using TlgGuid.exe tool +#pragma warning(suppress : 26477) // One of the macros uses 0/NULL. We don't have control to make it nullptr. +TRACELOGGING_DEFINE_PROVIDER( g_hTerminalConnectionProvider, "Microsoft.Windows.Terminal.Connection", - // {e912fe7b-eeb6-52a5-c628-abe388e5f792} - (0xe912fe7b, 0xeeb6, 0x52a5, 0xc6, 0x28, 0xab, 0xe3, 0x88, 0xe5, 0xf7, 0x92), - TraceLoggingOptionMicrosoftTelemetry()); + // {e912fe7b-eeb6-52a5-c628-abe388e5f792} + (0xe912fe7b, 0xeeb6, 0x52a5, 0xc6, 0x28, 0xab, 0xe3, 0x88, 0xe5, 0xf7, 0x92), + TraceLoggingOptionMicrosoftTelemetry()); +#endif #pragma warning(suppress : 26440) // Not interested in changing the specification of DllMain to make it noexcept given it's an interface to the OS. BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { switch (reason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hInstDll); - TraceLoggingRegister(g_hTerminalConnectionProvider); - Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalConnectionProvider); - break; - case DLL_PROCESS_DETACH: - if (g_hTerminalConnectionProvider) - { - TraceLoggingUnregister(g_hTerminalConnectionProvider); - } - break; + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hTerminalConnectionProvider); + Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalConnectionProvider); +#endif + break; + case DLL_PROCESS_DETACH: +#if !defined(WT_BRANDING_WINTERM) + if (g_hTerminalConnectionProvider) + { + TraceLoggingUnregister(g_hTerminalConnectionProvider); + } +#endif + break; default: break; } diff --git a/src/cascadia/TerminalControl/init.cpp b/src/cascadia/TerminalControl/init.cpp index e37ceed6f..4bc542fb9 100644 --- a/src/cascadia/TerminalControl/init.cpp +++ b/src/cascadia/TerminalControl/init.cpp @@ -7,30 +7,42 @@ // For g_hCTerminalCoreProvider #include "../../cascadia/TerminalCore/tracing.hpp" -// Note: Generate GUID using TlgGuid.exe tool -TRACELOGGING_DEFINE_PROVIDER( - g_hTerminalControlProvider, - "Microsoft.Windows.Terminal.Control", - // {28c82e50-57af-5a86-c25b-e39cd990032b} - (0x28c82e50, 0x57af, 0x5a86, 0xc2, 0x5b, 0xe3, 0x9c, 0xd9, 0x90, 0x03, 0x2b), - TraceLoggingOptionMicrosoftTelemetry()); +#if defined(WT_BRANDING_WINTERM) +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalControlProvider, + "winTerm.Control", + // {18965a29-3324-4078-a5e8-348aee483049} + (0x18965a29, 0x3324, 0x4078, 0xa5, 0xe8, 0x34, 0x8a, 0xee, 0x48, 0x30, 0x49)); +#else +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalControlProvider, + "Microsoft.Windows.Terminal.Control", + // {28c82e50-57af-5a86-c25b-e39cd990032b} + (0x28c82e50, 0x57af, 0x5a86, 0xc2, 0x5b, 0xe3, 0x9c, 0xd9, 0x90, 0x03, 0x2b), + TraceLoggingOptionMicrosoftTelemetry()); +#endif BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { switch (reason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hInstDll); - TraceLoggingRegister(g_hTerminalControlProvider); - TraceLoggingRegister(g_hCTerminalCoreProvider); - Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalControlProvider); - break; - case DLL_PROCESS_DETACH: - if (g_hTerminalControlProvider) - { - TraceLoggingUnregister(g_hTerminalControlProvider); - } - break; + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hTerminalControlProvider); + TraceLoggingRegister(g_hCTerminalCoreProvider); + Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalControlProvider); +#endif + break; + case DLL_PROCESS_DETACH: +#if !defined(WT_BRANDING_WINTERM) + if (g_hTerminalControlProvider) + { + TraceLoggingUnregister(g_hTerminalControlProvider); + } +#endif + break; } return TRUE; diff --git a/src/cascadia/TerminalSettingsEditor/init.cpp b/src/cascadia/TerminalSettingsEditor/init.cpp index bf25f63dc..81de2d3be 100644 --- a/src/cascadia/TerminalSettingsEditor/init.cpp +++ b/src/cascadia/TerminalSettingsEditor/init.cpp @@ -5,29 +5,41 @@ #include #include -// Note: Generate GUID using TlgGuid.exe tool -TRACELOGGING_DEFINE_PROVIDER( - g_hTerminalSettingsEditorProvider, - "Microsoft.Windows.Terminal.Settings.Editor", - // {1b16317d-b594-51f8-c552-5d50572b5efc} - (0x1b16317d, 0xb594, 0x51f8, 0xc5, 0x52, 0x5d, 0x50, 0x57, 0x2b, 0x5e, 0xfc), - TraceLoggingOptionMicrosoftTelemetry()); +#if defined(WT_BRANDING_WINTERM) +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalSettingsEditorProvider, + "winTerm.Settings.Editor", + // {5bc90042-5aa3-40a3-b340-a4d3facb06ae} + (0x5bc90042, 0x5aa3, 0x40a3, 0xb3, 0x40, 0xa4, 0xd3, 0xfa, 0xcb, 0x06, 0xae)); +#else +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalSettingsEditorProvider, + "Microsoft.Windows.Terminal.Settings.Editor", + // {1b16317d-b594-51f8-c552-5d50572b5efc} + (0x1b16317d, 0xb594, 0x51f8, 0xc5, 0x52, 0x5d, 0x50, 0x57, 0x2b, 0x5e, 0xfc), + TraceLoggingOptionMicrosoftTelemetry()); +#endif BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { switch (reason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hInstDll); - TraceLoggingRegister(g_hTerminalSettingsEditorProvider); - Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalSettingsEditorProvider); - break; - case DLL_PROCESS_DETACH: - if (g_hTerminalSettingsEditorProvider) - { - TraceLoggingUnregister(g_hTerminalSettingsEditorProvider); - } - break; + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hTerminalSettingsEditorProvider); + Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalSettingsEditorProvider); +#endif + break; + case DLL_PROCESS_DETACH: +#if !defined(WT_BRANDING_WINTERM) + if (g_hTerminalSettingsEditorProvider) + { + TraceLoggingUnregister(g_hTerminalSettingsEditorProvider); + } +#endif + break; } return TRUE; diff --git a/src/cascadia/TerminalSettingsModel/init.cpp b/src/cascadia/TerminalSettingsModel/init.cpp index 8c6ab3b74..8fef3b862 100644 --- a/src/cascadia/TerminalSettingsModel/init.cpp +++ b/src/cascadia/TerminalSettingsModel/init.cpp @@ -4,29 +4,41 @@ #include "pch.h" #include -// Note: Generate GUID using TlgGuid.exe tool -TRACELOGGING_DEFINE_PROVIDER( - g_hSettingsModelProvider, - "Microsoft.Windows.Terminal.Setting.Model", - // {be579944-4d33-5202-e5d6-a7a57f1935cb} - (0xbe579944, 0x4d33, 0x5202, 0xe5, 0xd6, 0xa7, 0xa5, 0x7f, 0x19, 0x35, 0xcb), - TraceLoggingOptionMicrosoftTelemetry()); +#if defined(WT_BRANDING_WINTERM) +TRACELOGGING_DEFINE_PROVIDER( + g_hSettingsModelProvider, + "winTerm.Settings.Model", + // {b4bf582b-5ede-46cb-9d69-c5649e3d6e13} + (0xb4bf582b, 0x5ede, 0x46cb, 0x9d, 0x69, 0xc5, 0x64, 0x9e, 0x3d, 0x6e, 0x13)); +#else +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hSettingsModelProvider, + "Microsoft.Windows.Terminal.Setting.Model", + // {be579944-4d33-5202-e5d6-a7a57f1935cb} + (0xbe579944, 0x4d33, 0x5202, 0xe5, 0xd6, 0xa7, 0xa5, 0x7f, 0x19, 0x35, 0xcb), + TraceLoggingOptionMicrosoftTelemetry()); +#endif BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) { switch (reason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hInstDll); - TraceLoggingRegister(g_hSettingsModelProvider); - Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hSettingsModelProvider); - break; - case DLL_PROCESS_DETACH: - if (g_hSettingsModelProvider) - { - TraceLoggingUnregister(g_hSettingsModelProvider); - } - break; + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hSettingsModelProvider); + Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hSettingsModelProvider); +#endif + break; + case DLL_PROCESS_DETACH: +#if !defined(WT_BRANDING_WINTERM) + if (g_hSettingsModelProvider) + { + TraceLoggingUnregister(g_hSettingsModelProvider); + } +#endif + break; } return TRUE; diff --git a/src/cascadia/WindowsTerminal/main.cpp b/src/cascadia/WindowsTerminal/main.cpp index b15f7c32c..bdc9a3b7c 100644 --- a/src/cascadia/WindowsTerminal/main.cpp +++ b/src/cascadia/WindowsTerminal/main.cpp @@ -13,14 +13,22 @@ using namespace winrt::Windows::UI::Composition; using namespace winrt::Windows::UI::Xaml::Hosting; using namespace winrt::Windows::Foundation::Numerics; -// Note: Generate GUID using TlgGuid.exe tool - seriously, it won't work if you -// just generate an arbitrary GUID -TRACELOGGING_DEFINE_PROVIDER( - g_hWindowsTerminalProvider, - "Microsoft.Windows.Terminal.Win32Host", - // {56c06166-2e2e-5f4d-7ff3-74f4b78c87d6} - (0x56c06166, 0x2e2e, 0x5f4d, 0x7f, 0xf3, 0x74, 0xf4, 0xb7, 0x8c, 0x87, 0xd6), - TraceLoggingOptionMicrosoftTelemetry()); +#if defined(WT_BRANDING_WINTERM) +TRACELOGGING_DEFINE_PROVIDER( + g_hWindowsTerminalProvider, + "winTerm.Win32Host", + // {a303edd2-dea1-42be-9b6d-158f42246d54} + (0xa303edd2, 0xdea1, 0x42be, 0x9b, 0x6d, 0x15, 0x8f, 0x42, 0x24, 0x6d, 0x54)); +#else +// Note: Generate GUID using TlgGuid.exe tool - seriously, it won't work if you +// just generate an arbitrary GUID +TRACELOGGING_DEFINE_PROVIDER( + g_hWindowsTerminalProvider, + "Microsoft.Windows.Terminal.Win32Host", + // {56c06166-2e2e-5f4d-7ff3-74f4b78c87d6} + (0x56c06166, 0x2e2e, 0x5f4d, 0x7f, 0xf3, 0x74, 0xf4, 0xb7, 0x8c, 0x87, 0xd6), + TraceLoggingOptionMicrosoftTelemetry()); +#endif // !! BODGY !! // Manually use the resources from TerminalApp as our resources. @@ -82,19 +90,21 @@ static void EnsureNativeArchitecture() } } -int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int nCmdShow) -{ - TraceLoggingRegister(g_hWindowsTerminalProvider); - ::Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hWindowsTerminalProvider); +int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int nCmdShow) +{ +#if !defined(WT_BRANDING_WINTERM) + TraceLoggingRegister(g_hWindowsTerminalProvider); + ::Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hWindowsTerminalProvider); TraceLoggingWrite( g_hWindowsTerminalProvider, "ExeCreated", TraceLoggingDescription("Event emitted when the terminal process is started"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - // If Terminal is spawned by a shortcut that requests that it run in a new process group + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); +#endif + + // If Terminal is spawned by a shortcut that requests that it run in a new process group // while attached to a console session, that request is nonsense. That request will, however, // cause WT to start with Ctrl-C disabled. This wouldn't matter, because it's a Windows-subsystem // application. Unfortunately, that state is heritable. In short, if you start WT using cmd in diff --git a/src/winterm/Branding/ReleaseMetadata.h b/src/winterm/Branding/ReleaseMetadata.h new file mode 100644 index 000000000..c16a4b66b --- /dev/null +++ b/src/winterm/Branding/ReleaseMetadata.h @@ -0,0 +1,38 @@ +// Copyright (c) winTerm contributors. +// Licensed under the MIT license. + +#pragma once + +#include +#include + +#if __has_include("ReleaseMetadata.generated.h") +#include "ReleaseMetadata.generated.h" +#endif + +#ifndef WINTERM_BUILD_COMMIT_SHA +#define WINTERM_BUILD_COMMIT_SHA L"development" +#endif + +#ifndef WINTERM_BUILD_TIMESTAMP +#define WINTERM_BUILD_TIMESTAMP L"not available" +#endif + +#ifndef WINTERM_BUILD_WORKFLOW_RUN_ID +#define WINTERM_BUILD_WORKFLOW_RUN_ID L"local" +#endif + +namespace winTerm::Branding +{ + inline constexpr std::wstring_view ApplicationVersion{ L"1.0.0" }; + inline constexpr std::wstring_view ReleaseChannel{ L"Stable" }; + inline constexpr std::wstring_view CommitSha{ WINTERM_BUILD_COMMIT_SHA }; + inline constexpr std::wstring_view BuildTimestamp{ WINTERM_BUILD_TIMESTAMP }; + inline constexpr std::wstring_view WorkflowRunId{ WINTERM_BUILD_WORKFLOW_RUN_ID }; + inline constexpr std::wstring_view MicrosoftTerminalUpstreamRevision{ L"1cea42d433253d95c4487a3037db48197b5e72f4" }; + inline constexpr uint32_t WorkspaceSchemaVersion{ 2 }; + inline constexpr uint32_t DockingModelVersion{ 1 }; + inline constexpr uint32_t ShellProtocolVersion{ 1 }; + inline constexpr uint32_t ThemeSchemaVersion{ 1 }; + inline constexpr uint32_t UpdateManifestSchemaVersion{ 1 }; +} diff --git a/src/winterm/Branding/version.json b/src/winterm/Branding/version.json new file mode 100644 index 000000000..447208f99 --- /dev/null +++ b/src/winterm/Branding/version.json @@ -0,0 +1,13 @@ +{ + "applicationVersion": "1.0.0", + "packageVersion": "1.0.0.0", + "moduleVersion": "1.0.0", + "channel": "stable", + "tag": "v1.0.0", + "workspaceSchemaVersion": 2, + "dockingModelVersion": 1, + "shellProtocolVersion": 1, + "themeSchemaVersion": 1, + "updateManifestSchemaVersion": 1, + "microsoftTerminalUpstreamRevision": "1cea42d433253d95c4487a3037db48197b5e72f4" +} diff --git a/src/winterm/Workspaces/Model/WorkspaceDescriptor.h b/src/winterm/Workspaces/Model/WorkspaceDescriptor.h index 9fa3012e1..b0bbca73b 100644 --- a/src/winterm/Workspaces/Model/WorkspaceDescriptor.h +++ b/src/winterm/Workspaces/Model/WorkspaceDescriptor.h @@ -219,7 +219,7 @@ namespace winTerm::Workspaces std::string createdAt; std::string updatedAt; WorkspaceSource source{ WorkspaceSource::User }; - std::string applicationVersion{ "0.5.0-alpha" }; + std::string applicationVersion{ "1.0.0" }; uint32_t protocolVersion{ 1 }; uint32_t dockingModelVersion{ DockingModelVersion }; WorkspaceStartupBehavior startupBehavior; diff --git a/src/winterm/Workspaces/Persistence/WorkspaceMigration.cpp b/src/winterm/Workspaces/Persistence/WorkspaceMigration.cpp index 0e281cf6d..cfcd781b4 100644 --- a/src/winterm/Workspaces/Persistence/WorkspaceMigration.cpp +++ b/src/winterm/Workspaces/Persistence/WorkspaceMigration.cpp @@ -36,7 +36,7 @@ WorkspaceMigrationResult WorkspaceMigration::Migrate(const Json::Value& document result.document.removeMember("workspaceId"); } if (result.document["source"].isNull()) result.document["source"] = "user"; - if (result.document["applicationVersion"].isNull()) result.document["applicationVersion"] = "0.5.0-alpha"; + if (result.document["applicationVersion"].isNull()) result.document["applicationVersion"] = "1.0.0"; if (result.document["protocolVersion"].isNull()) result.document["protocolVersion"] = 1; if (result.document["description"].isNull()) result.document["description"] = ""; if (result.document["startupBehavior"].isNull()) diff --git a/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp b/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp index cc2d5f3e1..0d66b20f7 100644 --- a/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp +++ b/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp @@ -618,7 +618,7 @@ WorkspaceDescriptor WorkspaceSerializer::FromJson(const Json::Value& json, const throw std::runtime_error("The workspace source is not supported."); } workspace.source = *source; - workspace.applicationVersion = StringOrDefault(json, "applicationVersion", "0.6.0-beta.1"); + workspace.applicationVersion = StringOrDefault(json, "applicationVersion", "1.0.0"); workspace.protocolVersion = UIntOrDefault(json, "protocolVersion", 1); workspace.dockingModelVersion = UIntOrDefault(json, "dockingModelVersion", DockingModelVersion); if (const auto& startup = json["startupBehavior"]; !startup.isNull())