Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ This repo is a small .NET global tool (PackageId: `solrevdev.ytx`) that extracts

- Entry points:
- `src/Ytx/Program.cs` — single-file console app; contains the main flow: input parsing (args or JSON stdin), YoutubeExplode calls, caption selection, transcript normalization, and JSON output.
- `src/Ytx/Ytx.csproj` — packaging details (PackAsTool, ToolCommandName=`ytx`), TargetFrameworks (`net8.0;net9.0`), and Version (auto-bumped by CI).
- `src/Ytx/Ytx.csproj` — packaging details (PackAsTool, ToolCommandName=`ytx`), TargetFrameworks (`net10.0`, plus `net11.0` when the SDK is 11 or later), and Version (auto-bumped by CI).

- What to change and where:
- Feature or bugfix touching runtime behavior: edit `Program.cs` and add small, well-scoped tests under `tests/` when applicable.
- Packaging/version bumps: update `<Version>` in `src/Ytx/Ytx.csproj` if needed locally — CI auto-increments on `master` pushes.

- Build / run / package (explicit commands example):
- SDK: `global.json` asks for 10.0.100 and rolls forward to the latest major. SDK 11 builds both targets; SDK 10 alone builds net10.0 only. CI installs both SDKs, so releases ship both.
- Restore: `dotnet restore src/Ytx`
- Build: `dotnet build src/Ytx -c Release`
- Run (manual test): `dotnet run --project src/Ytx --framework net8.0 "<YOUTUBE_URL>"`
- Run (manual test): `dotnet run --project src/Ytx --framework net10.0 "<YOUTUBE_URL>"`
- Pack: `dotnet pack src/Ytx -c Release` (packages to `./nupkg` per csproj)
- Local tool install for verification: `dotnet tool install -g solrevdev.ytx --add-source ./nupkg`

Expand All @@ -35,7 +36,7 @@ This repo is a small .NET global tool (PackageId: `solrevdev.ytx`) that extracts

- External dependencies & compatibility:
- Uses `YoutubeExplode` (pinned to 6.5.4). Avoid upgrading without testing caption extraction end-to-end.
- Targets .NET 8 and 9; keep `Nullable` and `ImplicitUsings` enabled to match project style.
- Targets .NET 10 (LTS baseline) and 11; keep `Nullable` and `ImplicitUsings` enabled to match project style.

- Small fixes that are preferred:
- Add unit tests for any non-trivial parsing/formatting changes.
Expand Down
42 changes: 38 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ jobs:
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
11.0.x

- name: Restore
run: |
Expand All @@ -68,6 +67,24 @@ jobs:
- name: Pack
run: dotnet pack "$PROJECT_DIR" --configuration Release --no-build

- name: Check package targets
shell: bash
run: |
set -euo pipefail
# The targets depend on the SDK that built them; fail rather than
# ship a package missing one.
PACKAGES=("$NUPKG_DIR"/solrevdev.ytx.*.nupkg)
# unzip -l reads a second path as a member name, so insist on one package.
if [[ ${#PACKAGES[@]} -ne 1 || ! -f "${PACKAGES[0]}" ]]; then
echo "Expected one package in $NUPKG_DIR, found: ${PACKAGES[*]}" >&2
exit 1
fi
LISTING=$(unzip -l "${PACKAGES[0]}")
for tfm in net10.0 net11.0; do
grep -q "tools/$tfm/any/Ytx.dll" <<<"$LISTING" \
|| { echo "Package is missing tools/$tfm" >&2; exit 1; }
done

publish:
if: github.event_name != 'pull_request'
permissions:
Expand All @@ -84,9 +101,8 @@ jobs:
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
11.0.x

- name: Determine and bump version
id: bump
Expand Down Expand Up @@ -166,6 +182,24 @@ jobs:
- name: Pack
run: dotnet pack "$PROJECT_DIR" --configuration Release --no-build

- name: Check package targets
shell: bash
run: |
set -euo pipefail
# The targets depend on the SDK that built them; fail rather than
# ship a package missing one.
PACKAGES=("$NUPKG_DIR"/solrevdev.ytx.*.nupkg)
# unzip -l reads a second path as a member name, so insist on one package.
if [[ ${#PACKAGES[@]} -ne 1 || ! -f "${PACKAGES[0]}" ]]; then
echo "Expected one package in $NUPKG_DIR, found: ${PACKAGES[*]}" >&2
exit 1
fi
LISTING=$(unzip -l "${PACKAGES[0]}")
for tfm in net10.0 net11.0; do
grep -q "tools/$tfm/any/Ytx.dll" <<<"$LISTING" \
|| { echo "Package is missing tools/$tfm" >&2; exit 1; }
done

- name: Verify package
id: package
shell: bash
Expand Down
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to coding agents such as Codex and Claude Code when

## Project Overview

This is `ytx`, a .NET Global Tool that extracts YouTube video metadata and transcripts as structured JSON. It's packaged as `solrevdev.ytx` on NuGet and targets .NET 8.0, 9.0, and 10.0.
This is `ytx`, a .NET Global Tool that extracts YouTube video metadata and transcripts as structured JSON. It's packaged as `solrevdev.ytx` on NuGet and targets .NET 10.0 and 11.0.

## Core Architecture

Expand All @@ -24,10 +24,11 @@ This is `ytx`, a .NET Global Tool that extracts YouTube video metadata and trans
## Development Commands

```bash
# Build and test locally
# Build and test locally. global.json rolls forward to the latest SDK major.
# SDK 11 builds net10.0 and net11.0; SDK 10 alone builds net10.0 only.
dotnet restore src/Ytx
dotnet build src/Ytx -c Release
dotnet run --project src/Ytx --framework net8.0 "YOUTUBE_URL"
dotnet run --project src/Ytx --framework net10.0 "YOUTUBE_URL"

# Package for local testing
dotnet pack src/Ytx -c Release
Expand All @@ -40,7 +41,7 @@ dotnet tool install -g solrevdev.ytx --add-source ./nupkg
## CI/CD Integration

**GitHub Actions Workflow** (`.github/workflows/publish.yml`):
- Pull requests that change the project, tests or workflow run read-only restore, build, test (net8.0, net9.0, net10.0) and pack validation
- Pull requests that change the project, tests or workflow run read-only restore, build, test (net10.0, net11.0) and pack validation
- Pushes to `master` automatically use a patch bump; manual dispatch can select patch, minor, or major
- Builds and packs before publishing the exact package to NuGet with `--skip-duplicate`
- Only after NuGet publication succeeds, atomically pushes the version commit and tag
Expand All @@ -66,7 +67,7 @@ dotnet tool install -g solrevdev.ytx --add-source ./nupkg
## Key Dependencies

- `YoutubeExplode` 6.6.2 - Core YouTube data extraction
- .NET 8.0/9.0/10.0 target frameworks with nullable reference types enabled
- .NET 10.0/11.0 target frameworks with nullable reference types enabled. net10.0 (LTS) is the baseline; net11.0 is added only when the SDK is 11 or later (a `NETCoreSdkVersion` condition in both csproj files). `global.json` pins SDK 10.0.100 with `rollForward: latestMajor`. CI installs SDKs 10 and 11 so the package ships both targets
- System.Text.Json for serialization
- System.Text.RegularExpressions for caption text normalization

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ All notable changes to ytx are listed here. The format follows

## [Unreleased]

### Breaking

- ytx now targets .NET 10 and 11 only. The net8.0 and net9.0 builds are gone because .NET 8 and 9 left support on 10 November 2026. A machine with only .NET 8 or 9 can no longer install new releases. It needs the .NET 10 or 11 runtime. Older ytx versions stay on NuGet for those machines.

### Changed

- Tests and CI run on .NET 10 and 11.

### Fixed

- The tool rolls forward to a newer .NET major, so it keeps running when only a later runtime, such as .NET 11, is installed.
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A .NET global tool that extracts YouTube video metadata and transcripts as JSON.
- 🌍 **Captions-aware** — Prefers human-written captions in your language, reports which track it used
- 📝 **Markdown transcript** — Human-readable format with timestamped links, split into sections by chapter
- 📑 **Chapters** — Read from the video description, using YouTube's own rules
- 🚀 **Cross-platform** — Works on macOS, Windows, Linux (.NET 8/9/10)
- 🚀 **Cross-platform** — Works on macOS, Windows, Linux (.NET 10/11)
- ⚡ **Fast & lightweight** — No dependencies beyond .NET and YoutubeExplode

## Quick Start
Expand Down Expand Up @@ -159,6 +159,14 @@ When `captionStatus` is not `ok`, the caption fields are `null` and both transcr

## Development

### SDK policy

.NET 10 is the LTS and the baseline. `global.json` asks for SDK 10.0.100 and rolls forward to the latest installed major, so a machine with SDKs 10 and 11 builds with 11.

- With SDK 11 (alone or with 10), the project builds and tests `net10.0` and `net11.0`.
- With SDK 10 only, it builds and tests `net10.0` only. That is fine for local work, but release packages must come from SDK 11.
- CI installs SDKs 10 and 11, so the published package holds both targets.

### Local Build & Test

```bash
Expand All @@ -172,7 +180,8 @@ dotnet restore src/Ytx
# Build
dotnet build src/Ytx -c Release

# Run the unit tests (on .NET 8, 9 and 10; add -f net8.0 to use one runtime)
# Run the unit tests (net10.0 and net11.0 with SDK 11, net10.0 only with SDK 10;
# add -f net10.0 to use one runtime)
dotnet test tests/Ytx.Tests -c Release

# Test locally (choose an installed target framework)
Expand Down Expand Up @@ -248,7 +257,7 @@ The GitHub Actions workflow uses NuGet Trusted Publishing and does not need a st
## Dependencies

- [YoutubeExplode](https://github.com/Tyrrrz/YoutubeExplode) — YouTube video data extraction
- .NET 8.0+ SDK (for development)
- .NET 10 SDK or later for development. SDK 11 builds both targets; see [SDK policy](#sdk-policy).

## License

Expand Down
1 change: 1 addition & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "sdk": { "version": "10.0.100", "rollForward": "latestMajor", "allowPrerelease": false } }
4 changes: 3 additions & 1 deletion src/Ytx/Ytx.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<!-- The package must ship both targets; CI installs SDKs 10 and 11 so it does, while SDK 10 alone builds net10.0 only. -->
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '11.0'))">net10.0;net11.0</TargetFrameworks>
<!-- A tool installs its highest TFM; without this it dies when that runtime is removed, as SDK 11's cask upgrade does. -->
<RollForward>Major</RollForward>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
4 changes: 3 additions & 1 deletion tests/Ytx.Tests/Ytx.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<!-- Match the tool: CI installs SDKs 10 and 11 so tests run on both targets, while SDK 10 alone tests net10.0 only. -->
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '11.0'))">net10.0;net11.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
Loading