@krx3d/tizentube2 1.16.30 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,305 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Build and release the TizenTube Standalone app (standalone/)
3
+ #
4
+ # WHAT THIS DOES
5
+ # ---------------
6
+ # Builds the userscript + regular DIAL service (needed by the standalone
7
+ # service), then the standalone service itself, then packages and signs
8
+ # standalone/ into a .wgt using Samsung's own Tizen Studio CLI, and publishes
9
+ # it as a GitHub Release.
10
+ #
11
+ # Signing note: this used to sign via tizenjs (an unofficial community
12
+ # packaging tool), which produced a .wgt that installed fine as an *update*
13
+ # to an already-trusted app but was rejected with "invalid certificate
14
+ # chain" on a brand-new app id's *first* install. Comparing against three
15
+ # sibling projects (TizenBrew, TizenBrewInstaller, TizenYouTube) that all
16
+ # install fine as fresh apps: the one thing they share and this build didn't
17
+ # is that they're all packaged with Samsung's own official Tizen Studio CLI
18
+ # (`tizen build-web` + `tizen package`), not tizenjs. TizenYouTube in
19
+ # particular proved this isn't about certificate freshness — it uses the
20
+ # exact same (long-expired) Samsung public sample distributor certificate
21
+ # tizenjs was also falling back to, and still installs fine, because it's
22
+ # packaged with the official tool. So this workflow now matches that same
23
+ # working recipe: install Tizen Studio, configure a certificate profile
24
+ # (author cert from secrets + Samsung's public sample distributor cert,
25
+ # same as TizenYouTube/TizenBrew use), and package with `tizen package`
26
+ # instead of tizenjs.
27
+ #
28
+ # This is entirely separate from build-publish-cleanup.yml (which builds and
29
+ # npm-publishes the regular mod on push to main) — different trigger, no
30
+ # shared state, so it doesn't affect that workflow.
31
+ #
32
+ # WHEN IT RUNS
33
+ # ------------
34
+ # - Automatically, right after "Build, publish and cleanup old npm versions"
35
+ # (build-publish-cleanup.yml) finishes successfully on main — i.e. once the
36
+ # userscript's new version has actually been bumped and published to npm.
37
+ # This keeps the standalone app's version in lockstep with the userscript:
38
+ # it reads the version build-publish-cleanup.yml just bumped in package.json
39
+ # and writes that same number into standalone/config.xml before packaging,
40
+ # rather than tracking its own separate version.
41
+ # - On pushing a tag matching v*.*.* (e.g. v1.0.0)
42
+ # - Manually via "Run workflow" (workflow_dispatch)
43
+ #
44
+ # PREREQUISITES (REQUIRED) — Tizen author certificate
45
+ # -----------------------------------------------------
46
+ # The build needs a Tizen author certificate to sign the .wgt.
47
+ #
48
+ # 1) Install Tizen Studio (or just the Certificate Manager) from
49
+ # https://developer.tizen.org/development/tizen-studio/download
50
+ #
51
+ # 2) Open Certificate Manager (Tizen Studio → Tools → Certificate Manager,
52
+ # or run `certificate-manager` directly from the Tizen Studio tools dir).
53
+ #
54
+ # 3) Author tab → "+" → "Create a new author certificate". Fill in a name,
55
+ # organization/email, and a password — remember this password. This
56
+ # produces an author.p12 file (Certificate Manager will tell you where
57
+ # it saved it).
58
+ #
59
+ # 4) Base64-encode the .p12 with NO line wrapping:
60
+ # Linux/macOS: base64 -w0 author.p12
61
+ # Windows (PowerShell):
62
+ # [Convert]::ToBase64String([IO.File]::ReadAllBytes("author.p12"))
63
+ #
64
+ # 5) Add two repository secrets (GitHub repo → Settings → Secrets and
65
+ # variables → Actions → New repository secret):
66
+ # TIZEN_AUTHOR_KEY = the base64 string from step 4
67
+ # TIZEN_AUTHOR_KEY_PW = the certificate password from step 3
68
+ #
69
+ # (The distributor certificate needs no setup — it's Samsung's own public
70
+ # sample certificate, bundled with Tizen Studio and installed automatically
71
+ # by this workflow, same as TizenBrew/TizenYouTube use.)
72
+ #
73
+ # HOW TO TRIGGER A BUILD
74
+ # -----------------------
75
+ # git tag v1.0.0 && git push origin v1.0.0
76
+ # (or use "Run workflow" in the Actions tab for an ad-hoc build)
77
+ # -----------------------------------------------------------------------------
78
+
79
+ name: Build TizenTube Standalone and Release
80
+
81
+ on:
82
+ workflow_run:
83
+ workflows: ['Build, publish and cleanup old npm versions']
84
+ types: [completed]
85
+ push:
86
+ tags:
87
+ - 'v*.*.*'
88
+ workflow_dispatch:
89
+ inputs:
90
+ release_version:
91
+ description: 'Release version/tag name (e.g. v1.0.0)'
92
+ required: false
93
+
94
+ env:
95
+ TIZEN_STUDIO_VER: 5.6
96
+
97
+ jobs:
98
+ build:
99
+ runs-on: ubuntu-latest
100
+ # Only proceed on a successful npm-publish run that was itself triggered by
101
+ # a push to main (build-publish-cleanup.yml's job is skipped entirely for
102
+ # pull_request runs, which can still report an overall "success" — checking
103
+ # .workflow_run.event too avoids firing off of those). Tag pushes and
104
+ # manual dispatch always proceed.
105
+ if: >
106
+ github.event_name != 'workflow_run' ||
107
+ (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
108
+ permissions:
109
+ contents: write
110
+ steps:
111
+ # No explicit ref: default checkout behavior is already correct for every
112
+ # trigger — workflow_run checks out the default branch's current tip,
113
+ # push (tag) checks out the tag, workflow_dispatch checks out the branch
114
+ # it was run from. Pinning to github.event.workflow_run.head_sha (as an
115
+ # earlier version of this workflow did) is wrong: that's the commit that
116
+ # *triggered* build-publish-cleanup.yml, not the version-bump commit it
117
+ # produces partway through its own run — checking that out meant this
118
+ # workflow read package.json *before* the bump, one version behind.
119
+ - name: Clone repo with full history and tags
120
+ uses: actions/checkout@v5
121
+ with:
122
+ fetch-depth: 0
123
+ fetch-tags: true
124
+
125
+ - name: Record the commit this build is actually based on
126
+ run: echo "RELEASE_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
127
+
128
+ - name: Setup Node.js
129
+ uses: actions/setup-node@v6
130
+ with:
131
+ node-version: '24'
132
+
133
+ - name: Determine release version (sync with userscript's package.json)
134
+ run: |
135
+ set -euo pipefail
136
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.release_version }}" ]; then
137
+ RELEASE_VERSION="${{ github.event.inputs.release_version }}"
138
+ elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
139
+ RELEASE_VERSION="${GITHUB_REF#refs/tags/}"
140
+ elif [ -f package.json ]; then
141
+ PKG_VER=$(node -p "require('./package.json').version")
142
+ RELEASE_VERSION="${PKG_VER:+v$PKG_VER}"
143
+ fi
144
+
145
+ if [ -z "${RELEASE_VERSION:-}" ]; then
146
+ CFG_VER=$(sed -n 's/.*<widget[^>]*version="\([^"]*\)".*/\1/p' standalone/config.xml | head -n1)
147
+ RELEASE_VERSION="${CFG_VER:+v$CFG_VER}"
148
+ RELEASE_VERSION="${RELEASE_VERSION:-v0.0.0-${GITHUB_SHA:0:7}}"
149
+ fi
150
+ echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
151
+
152
+ - name: Sync standalone/config.xml version with the userscript's version
153
+ run: |
154
+ set -euo pipefail
155
+ NEW_VER="${RELEASE_VERSION#v}"
156
+ CUR_VER=$(sed -n 's/.*<widget[^>]*version="\([^"]*\)".*/\1/p' standalone/config.xml | head -n1)
157
+ if [ -n "$NEW_VER" ] && [ "$NEW_VER" != "$CUR_VER" ]; then
158
+ sed -i "0,/<widget /s/version=\"${CUR_VER}\"/version=\"${NEW_VER}\"/" standalone/config.xml
159
+ echo "standalone/config.xml version: ${CUR_VER} -> ${NEW_VER}"
160
+ else
161
+ echo "standalone/config.xml version already ${CUR_VER}, nothing to sync"
162
+ fi
163
+
164
+ - name: Determine app metadata for release notes
165
+ run: |
166
+ set -euo pipefail
167
+ APP_ID=$(sed -n 's/.*<tizen:application[^>]*id="\([^"]*\)".*/\1/p' standalone/config.xml | head -n1)
168
+ APP_NAME=$(echo "$APP_ID" | sed -n 's/^[^.]*\.\(.*\)$/\1/p')
169
+ echo "APP_ID=${APP_ID:-unknown}" >> $GITHUB_ENV
170
+ echo "APP_NAME=${APP_NAME:-unknown}" >> $GITHUB_ENV
171
+
172
+ # ----------------------------
173
+ # Build the regular mod + DIAL service — the standalone service wraps
174
+ # dist/service.js, so it must exist before the standalone service builds.
175
+ # ----------------------------
176
+ - name: Build mods (userScript.js)
177
+ working-directory: mods
178
+ run: |
179
+ set -euo pipefail
180
+ npm ci
181
+ npx rollup -c
182
+
183
+ - name: Build service (service.js)
184
+ working-directory: service
185
+ run: |
186
+ set -euo pipefail
187
+ npm ci
188
+ npx rollup -c
189
+
190
+ # ----------------------------
191
+ # Build the standalone service
192
+ # ----------------------------
193
+ - name: Build standalone service
194
+ working-directory: standalone/service
195
+ run: |
196
+ set -euo pipefail
197
+ npm install
198
+ npm install -g @vercel/ncc
199
+ npm run build
200
+ rm -rf node_modules transpiled
201
+
202
+ # ----------------------------
203
+ # Sign and package the standalone app with Tizen Studio
204
+ # ----------------------------
205
+ - name: Download Tizen Studio CLI
206
+ run: |
207
+ set -euo pipefail
208
+ curl -fL -o tizen-installer "https://download.tizen.org/sdk/Installer/tizen-studio_${TIZEN_STUDIO_VER}/web-cli_Tizen_Studio_${TIZEN_STUDIO_VER}_ubuntu-64.bin"
209
+ chmod +x tizen-installer
210
+
211
+ - name: Install Tizen Studio CLI
212
+ run: |
213
+ set -euo pipefail
214
+ ./tizen-installer --accept-license "${GITHUB_WORKSPACE}/tizen-studio"
215
+ rm ./tizen-installer
216
+ echo "${GITHUB_WORKSPACE}/tizen-studio/tools/ide/bin" >> $GITHUB_PATH
217
+
218
+ - name: Configure certificate profile
219
+ env:
220
+ AUTHOR_P12_B64: ${{ secrets.TIZEN_AUTHOR_KEY }}
221
+ AUTHOR_P12_PASSWORD: ${{ secrets.TIZEN_AUTHOR_KEY_PW }}
222
+ run: |
223
+ set -euo pipefail
224
+ mkdir -p "${GITHUB_WORKSPACE}/tizen-data/keystore/author"
225
+ mkdir -p "${GITHUB_WORKSPACE}/tizen-data/profile"
226
+ echo "$AUTHOR_P12_B64" | base64 -d > "${GITHUB_WORKSPACE}/tizen-data/keystore/author/author.p12"
227
+
228
+ echo "Author cert encryption before conversion:"
229
+ openssl pkcs12 -info -in "${GITHUB_WORKSPACE}/tizen-data/keystore/author/author.p12" -passin env:AUTHOR_P12_PASSWORD -noout 2>&1 | grep -i "pbe\|cipher\|mac" || true
230
+
231
+ # Tizen Studio's bundled Java signing tool is old and can fail to read a
232
+ # PKCS#12 file encrypted with the modern AES-256 cipher OpenSSL 3.x (the
233
+ # default everywhere since ~2021) uses by default — throwing a generic,
234
+ # misleading "Invalid password" even when the password is correct. Both
235
+ # node-forge (what tizenjs used) and modern OpenSSL read AES-256 p12s
236
+ # fine, so this wouldn't have surfaced until switching to Tizen Studio's
237
+ # own tooling. Re-encrypt with the older, broadly-compatible cipher
238
+ # in-place — same password, same cert/key contents, just an older wrapper.
239
+ openssl pkcs12 -legacy -in "${GITHUB_WORKSPACE}/tizen-data/keystore/author/author.p12" -out /tmp/author-intermediate.pem -nodes -passin env:AUTHOR_P12_PASSWORD
240
+ openssl pkcs12 -export -legacy -in /tmp/author-intermediate.pem -passout env:AUTHOR_P12_PASSWORD -out "${GITHUB_WORKSPACE}/tizen-data/keystore/author/author.p12" -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1
241
+ rm -f /tmp/author-intermediate.pem
242
+
243
+ echo "Author cert encryption after conversion:"
244
+ openssl pkcs12 -info -in "${GITHUB_WORKSPACE}/tizen-data/keystore/author/author.p12" -passin env:AUTHOR_P12_PASSWORD -noout 2>&1 | grep -i "pbe\|cipher\|mac" || true
245
+
246
+ cp .github/assets/profiles.xml "${GITHUB_WORKSPACE}/tizen-data/profile/profiles.xml"
247
+ python3 -c "from pathlib import Path; import os; from xml.sax.saxutils import escape; p=Path(os.environ['GITHUB_WORKSPACE'])/'tizen-data/profile/profiles.xml'; t=p.read_text(); t=t.replace('\$GITHUB_WORKSPACE', os.environ['GITHUB_WORKSPACE']).replace('\$KEY_PW', escape(os.environ['AUTHOR_P12_PASSWORD'], {'\"':'&quot;'})); p.write_text(t)"
248
+
249
+ tizen cli-config "profiles.path=${GITHUB_WORKSPACE}/tizen-data/profile/profiles.xml"
250
+
251
+ - name: Build web app
252
+ working-directory: standalone
253
+ run: >
254
+ tizen build-web
255
+ -e "service/index.js"
256
+ -e "service/injector.js"
257
+ -e "service/bootstrap.js"
258
+ -e "service/build-service.js"
259
+ -e "service/package.json"
260
+ -e "service/package-lock.json"
261
+ -e "service/node_modules"
262
+ -e "release"
263
+
264
+ - name: Package WGT
265
+ working-directory: standalone
266
+ run: |
267
+ set -euo pipefail
268
+ tizen package -t wgt -s TizenTubeStandalone -- ./
269
+ mkdir -p release
270
+ cp *.wgt release/TizenTube.wgt
271
+
272
+ - name: Show Tizen CLI log on failure
273
+ if: failure()
274
+ run: |
275
+ LOG_PATH="${GITHUB_WORKSPACE}/tizen-studio-data/cli/logs/cli.log"
276
+ if [ -f "$LOG_PATH" ]; then
277
+ echo "--- tizen cli.log ---"
278
+ cat "$LOG_PATH"
279
+ else
280
+ echo "No cli.log found at $LOG_PATH"
281
+ fi
282
+
283
+ - name: Upload TizenTube Standalone artifact
284
+ uses: actions/upload-artifact@v6
285
+ with:
286
+ name: TizenTube-Standalone-${{ env.RELEASE_SHA }}.wgt
287
+ path: standalone/release/TizenTube.wgt
288
+
289
+ # Changelog is built from the PRs merged since the previous published
290
+ # release; the build/TBI_METADATA header stays first so the TizenBrew
291
+ # Installer still finds it.
292
+ - name: Build release notes
293
+ env:
294
+ GH_TOKEN: ${{ github.token }}
295
+ run: |
296
+ bash .github/scripts/build-release-notes.sh "${{ env.RELEASE_SHA }}" "${{ env.RELEASE_VERSION }}" "${{ env.APP_ID }}" "${{ env.APP_NAME }}" "$RUNNER_TEMP/release-notes.md"
297
+
298
+ - name: Release TizenTube Standalone
299
+ uses: softprops/action-gh-release@v1
300
+ with:
301
+ tag_name: ${{ env.RELEASE_VERSION }}
302
+ target_commitish: ${{ env.RELEASE_SHA }}
303
+ body_path: ${{ runner.temp }}/release-notes.md
304
+ files: |
305
+ standalone/release/*
@@ -0,0 +1,58 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: write
23
+ pull-requests: write
24
+ issues: write
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v6
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Run Claude Code
34
+ id: claude
35
+ uses: anthropics/claude-code-action@v1
36
+ with:
37
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
38
+
39
+ # Optional: Customize the trigger phrase (default: @claude)
40
+ # trigger_phrase: "/claude"
41
+
42
+ # Optional: Trigger when specific user is assigned to an issue
43
+ # assignee_trigger: "claude-bot"
44
+
45
+ # Optional: Configure Claude's behavior with CLI arguments
46
+ # claude_args: |
47
+ # --model claude-opus-4-1-20250805
48
+ # --max-turns 10
49
+ # --allowedTools "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
50
+ # --system-prompt "Follow our coding standards. Ensure all new code has tests. Use TypeScript for new files."
51
+
52
+ # Optional: Advanced settings configuration
53
+ # settings: |
54
+ # {
55
+ # "env": {
56
+ # "NODE_ENV": "test"
57
+ # }
58
+ # }
@@ -0,0 +1,44 @@
1
+ name: "CodeQL"
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+ schedule:
9
+ - cron: '23 3 * * 1'
10
+
11
+ jobs:
12
+ analyze:
13
+ name: Analyze (${{ matrix.language }})
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ actions: read
17
+ contents: read
18
+ security-events: write
19
+
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ language: ["javascript-typescript"]
24
+
25
+ steps:
26
+ - name: Checkout repository
27
+ uses: actions/checkout@v6
28
+
29
+ - name: Initialize CodeQL
30
+ uses: github/codeql-action/init@v4
31
+ with:
32
+ languages: ${{ matrix.language }}
33
+ queries: security-extended,security-and-quality
34
+ config: |
35
+ paths-ignore:
36
+ - dist/**
37
+
38
+ - name: Autobuild
39
+ uses: github/codeql-action/autobuild@v4
40
+
41
+ - name: Perform CodeQL Analysis
42
+ uses: github/codeql-action/analyze@v4
43
+ with:
44
+ category: "/language:${{matrix.language}}"
@@ -0,0 +1,165 @@
1
+ # Watch reisxd/TizenTube for new commits and open a PR that carries them.
2
+ #
3
+ # This fork has rewritten large parts of mods/ (adblock.js, hideWatched.js,
4
+ # settings.js in particular), so most upstream commits touching those files
5
+ # WILL conflict. The workflow does not pretend otherwise: it always produces a
6
+ # branch that merges cleanly into main, and when a file conflicted it keeps the
7
+ # fork's version and says so in the PR body, so the upstream change can be
8
+ # hand-ported afterwards.
9
+ name: Upstream sync
10
+
11
+ on:
12
+ schedule:
13
+ # 04:20 UTC daily — outside the usual release window.
14
+ - cron: '20 4 * * *'
15
+ workflow_dispatch:
16
+
17
+ concurrency:
18
+ group: upstream-sync
19
+ cancel-in-progress: false
20
+
21
+ env:
22
+ UPSTREAM_REPO: https://github.com/reisxd/TizenTube.git
23
+ UPSTREAM_BRANCH: main
24
+ UPSTREAM_BRANCH_REF: upstream/main
25
+ SYNC_BRANCH: sync/upstream
26
+
27
+ jobs:
28
+ sync:
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: write
32
+ pull-requests: write
33
+ steps:
34
+ - name: Clone repo with full history
35
+ uses: actions/checkout@v5
36
+ with:
37
+ fetch-depth: 0
38
+
39
+ - name: Configure git identity
40
+ run: |
41
+ git config user.name 'github-actions[bot]'
42
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
43
+
44
+ - name: Fetch upstream
45
+ run: |
46
+ set -euo pipefail
47
+ git remote add upstream "$UPSTREAM_REPO" 2>/dev/null || git remote set-url upstream "$UPSTREAM_REPO"
48
+ git fetch upstream "$UPSTREAM_BRANCH" --no-tags
49
+
50
+ - name: Check for new upstream commits
51
+ id: check
52
+ run: |
53
+ set -euo pipefail
54
+ COUNT=$(git rev-list --count "origin/main..upstream/${UPSTREAM_BRANCH}")
55
+ echo "count=$COUNT" >> "$GITHUB_OUTPUT"
56
+ echo "Upstream commits not in main: $COUNT"
57
+
58
+ - name: Merge upstream, keeping the fork's side of any conflict
59
+ id: merge
60
+ if: steps.check.outputs.count != '0'
61
+ run: |
62
+ set -euo pipefail
63
+ BASE_SHA=$(git rev-parse origin/main)
64
+ git checkout -B "$SYNC_BRANCH" origin/main
65
+
66
+ # --no-commit so the CI-exclusion below lands in the same commit
67
+ # whether or not the merge conflicted, rather than needing a second.
68
+ CONFLICTS=""
69
+ git merge --no-commit --no-ff "$UPSTREAM_BRANCH_REF" || true
70
+
71
+ CONFLICTS=$(git diff --name-only --diff-filter=U || true)
72
+ if [ -n "$CONFLICTS" ]; then
73
+ # Keep this fork's version of every conflicted file. The point of
74
+ # the PR is to surface the change for review, not to guess at a
75
+ # resolution — committing conflict markers would only produce a
76
+ # branch that cannot build.
77
+ echo "Conflicted files kept at fork version:"
78
+ echo "$CONFLICTS"
79
+ while IFS= read -r f; do
80
+ [ -z "$f" ] && continue
81
+ git checkout --ours -- "$f" 2>/dev/null || git rm -q -- "$f"
82
+ git add -- "$f" 2>/dev/null || true
83
+ done <<< "$CONFLICTS"
84
+ fi
85
+
86
+ # CI is never synced from upstream, for two reasons. This fork runs
87
+ # its own workflows (npm publish, standalone .wgt build, this sync)
88
+ # which have no upstream counterpart, so importing theirs would be
89
+ # actively wrong. And GITHUB_TOKEN *cannot* push changes under
90
+ # .github/workflows at all — there is no "workflows" permission in
91
+ # the permissions: block, that scope exists only for PATs and GitHub
92
+ # Apps, so a push carrying one is rejected outright:
93
+ # refusing to allow a GitHub App to create or update workflow
94
+ # `.github/workflows/...` without `workflows` permission
95
+ # Restoring the directory to exactly origin/main covers both the
96
+ # modified and the newly-added case.
97
+ if git rev-parse --verify -q "${BASE_SHA}:.github/workflows" >/dev/null; then
98
+ git rm -rf --quiet --ignore-unmatch .github/workflows >/dev/null 2>&1 || true
99
+ git checkout "$BASE_SHA" -- .github/workflows
100
+ git add -A .github/workflows
101
+ echo "Restored .github/workflows to the fork's version"
102
+ fi
103
+
104
+ # A clean merge leaves everything staged but uncommitted because of
105
+ # --no-commit; a conflicted one is staged by the loop above.
106
+ git commit --no-edit || echo "Nothing to commit"
107
+
108
+ {
109
+ echo 'conflicts<<TT_EOF'
110
+ echo "$CONFLICTS"
111
+ echo 'TT_EOF'
112
+ } >> "$GITHUB_OUTPUT"
113
+
114
+ if git diff --quiet origin/main -- .; then
115
+ echo "changed=false" >> "$GITHUB_OUTPUT"
116
+ else
117
+ echo "changed=true" >> "$GITHUB_OUTPUT"
118
+ git push --force-with-lease origin "$SYNC_BRANCH"
119
+ fi
120
+
121
+ - name: Open or update the sync PR
122
+ if: steps.check.outputs.count != '0' && steps.merge.outputs.changed == 'true'
123
+ env:
124
+ GH_TOKEN: ${{ github.token }}
125
+ CONFLICTS: ${{ steps.merge.outputs.conflicts }}
126
+ NEW_COMMITS: ${{ steps.check.outputs.count }}
127
+ run: |
128
+ set -euo pipefail
129
+
130
+ {
131
+ echo "Automated sync with [reisxd/TizenTube](https://github.com/reisxd/TizenTube) — **${NEW_COMMITS}** new upstream commit(s)."
132
+ echo
133
+
134
+ if [ -n "${CONFLICTS//[[:space:]]/}" ]; then
135
+ echo "## :warning: Needs hand-porting"
136
+ echo
137
+ echo "These files conflicted and were **kept at this fork's version**, so the upstream changes to them are *not* in this PR. Merging as-is is safe but incomplete — port them by hand before or after merging:"
138
+ echo
139
+ while IFS= read -r f; do
140
+ [ -z "$f" ] && continue
141
+ echo "- \`$f\`"
142
+ done <<< "$CONFLICTS"
143
+ echo
144
+ else
145
+ echo "Merged cleanly — no conflicts."
146
+ echo
147
+ fi
148
+
149
+ echo "## Upstream commits"
150
+ echo
151
+ git log --reverse --no-merges --pretty=format:'- [`%h`](https://github.com/reisxd/TizenTube/commit/%H) %s' "origin/main..upstream/${UPSTREAM_BRANCH}"
152
+ echo
153
+ echo
154
+ echo "_Opened automatically by \`.github/workflows/upstream-sync.yml\`._"
155
+ } > /tmp/pr-body.md
156
+
157
+ EXISTING=$(gh pr list --head "$SYNC_BRANCH" --state open --json number --jq '.[0].number // empty')
158
+ TITLE="Sync with upstream (${NEW_COMMITS} new commit(s))"
159
+
160
+ if [ -n "$EXISTING" ]; then
161
+ gh pr edit "$EXISTING" --title "$TITLE" --body-file /tmp/pr-body.md
162
+ echo "Updated PR #$EXISTING"
163
+ else
164
+ gh pr create --base main --head "$SYNC_BRANCH" --title "$TITLE" --body-file /tmp/pr-body.md
165
+ fi