Skip to content

Commit c8b95b4

Browse files
committed
Merge remote-tracking branch 'origin/main' into pr-28851-merge-fix
# Conflicts: # apps/comments-ui/package.json
2 parents f0309bf + 2506a68 commit c8b95b4

295 files changed

Lines changed: 5350 additions & 4190 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/hooks/pre-commit.bash

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -99,72 +99,3 @@ if [[ -n "$MOD_SUBMODULES" ]]; then
9999
else
100100
echo "No submodules in commit, continuing..."
101101
fi
102-
103-
##
104-
## 3) Suggest shipping a new version of @tryghost/activitypub when changes are detected
105-
## The intent is to ship smaller changes more frequently to production
106-
##
107-
108-
increment_version() {
109-
local package_json_path=$1
110-
local version_type=$2
111-
112-
local current_version
113-
current_version=$(grep '"version":' "$package_json_path" | awk -F '"' '{print $4}')
114-
115-
IFS='.' read -r major minor patch <<< "$current_version"
116-
117-
case "$version_type" in
118-
major) ((major++)); minor=0; patch=0 ;;
119-
minor) ((minor++)); patch=0 ;;
120-
patch) ((patch++)) ;;
121-
*) echo "Invalid version type"; exit 1 ;;
122-
esac
123-
124-
new_version="$major.$minor.$patch"
125-
126-
# Update package.json with new version
127-
if [[ "$OSTYPE" == "darwin"* ]]; then
128-
# macOS
129-
sed -i '' -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"$new_version\"/" "$package_json_path"
130-
else
131-
# Linux and others
132-
sed -i -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+\"/\"version\": \"$new_version\"/" "$package_json_path"
133-
fi
134-
135-
echo "Updated version to $new_version in $package_json_path"
136-
}
137-
138-
AP_BUMP_NEEDED=false
139-
MODIFIED_FILES=$(git diff --cached --name-only)
140-
141-
for FILE in $MODIFIED_FILES; do
142-
if [[ "$FILE" == apps/activitypub/* ]]; then
143-
AP_BUMP_NEEDED=true
144-
break
145-
fi
146-
done
147-
148-
if [[ "$AP_BUMP_NEEDED" == true ]]; then
149-
echo -e "\nYou have made changes to @tryghost/activitypub."
150-
echo -e "Would you like to ship a new version? (yes)"
151-
read -r new_version </dev/tty
152-
153-
if [[ -z "$new_version" || "$new_version" == "yes" || "$new_version" == "y" ]]; then
154-
echo -e "Is that a patch, minor or major? (patch)"
155-
read -r version_type </dev/tty
156-
157-
# Default to patch
158-
if [[ -z "$version_type" ]]; then
159-
version_type="patch"
160-
fi
161-
162-
if [[ "$version_type" != "patch" && "$version_type" != "minor" && "$version_type" != "major" ]]; then
163-
echo -e "${red}Invalid input. Skipping version bump.${no_color}"
164-
else
165-
echo "Bumping version ($version_type)..."
166-
increment_version "apps/activitypub/package.json" "$version_type"
167-
git add apps/activitypub/package.json
168-
fi
169-
fi
170-
fi

.github/workflows/ci.yml

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ jobs:
550550
if [ "${{ matrix.env.DB }}" = "sqlite3" ]; then
551551
pnpm nx run ghost:test:ci:e2e
552552
else
553-
pnpm nx run ghost:test:ci:e2e:no-coverage
553+
pnpm nx run ghost:test:e2e
554554
fi
555555
556556
- name: Start MinIO for integration tests
@@ -577,7 +577,7 @@ jobs:
577577
if [ "${{ matrix.env.DB }}" = "sqlite3" ]; then
578578
pnpm nx run ghost:test:ci:integration
579579
else
580-
pnpm nx run ghost:test:ci:integration:no-coverage
580+
pnpm nx run ghost:test:integration
581581
fi
582582
583583
- name: Check for unexpected file changes
@@ -672,7 +672,7 @@ jobs:
672672
echo "database__connection__password=root" >> $GITHUB_ENV
673673
674674
- name: Legacy tests
675-
run: pnpm nx run ghost:test:ci:legacy
675+
run: pnpm nx run ghost:test:legacy
676676

677677
- name: Check for unexpected file changes
678678
run: |
@@ -1583,8 +1583,8 @@ jobs:
15831583
job_build_e2e_public_apps,
15841584
job_build_e2e_image,
15851585
job_e2e_tests,
1586-
build_packages,
1587-
publish_packages
1586+
build_public_apps,
1587+
publish_public_apps
15881588
]
15891589
if: always()
15901590
runs-on: ubuntu-latest
@@ -1601,34 +1601,17 @@ jobs:
16011601
# Runs on PRs and pushes. Intentionally has no `id-token: write` — PR-controlled
16021602
# code (nx build) must never execute in a job that can mint an npm OIDC token.
16031603
# The privileged publish flow lives in publish_packages below, gated to main pushes.
1604-
build_packages:
1604+
build_public_apps:
16051605
needs: [
16061606
job_setup,
16071607
job_lint,
16081608
job_unit-tests
16091609
]
1610-
name: Build ${{ matrix.package_name }}
1610+
name: Build Public Apps
16111611
runs-on: ubuntu-latest
16121612
if: always() && github.repository == 'TryGhost/Ghost' && needs.job_setup.result == 'success' && needs.job_lint.result == 'success' && needs.job_unit-tests.result == 'success' && needs.job_setup.outputs.run_e2e == 'true'
16131613
permissions:
16141614
contents: read
1615-
strategy:
1616-
matrix:
1617-
include:
1618-
- package_name: '@tryghost/activitypub'
1619-
package_path: 'apps/activitypub'
1620-
- package_name: '@tryghost/portal'
1621-
package_path: 'apps/portal'
1622-
- package_name: '@tryghost/sodo-search'
1623-
package_path: 'apps/sodo-search'
1624-
- package_name: '@tryghost/comments-ui'
1625-
package_path: 'apps/comments-ui'
1626-
- package_name: '@tryghost/signup-form'
1627-
package_path: 'apps/signup-form'
1628-
- package_name: '@tryghost/announcement-bar'
1629-
package_path: 'apps/announcement-bar'
1630-
- package_name: '@tryghost/admin-toolbar'
1631-
package_path: 'apps/admin-toolbar'
16321615
steps:
16331616
- name: Checkout code
16341617
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -1644,17 +1627,17 @@ jobs:
16441627
run: pnpm install --frozen-lockfile
16451628

16461629
- name: Build the package
1647-
run: pnpm nx build ${{ matrix.package_name }}
1630+
run: pnpm nx run-many --target=build --projects='tag:public-app'
16481631

16491632
# Publishes @tryghost/* public apps to npm via OIDC trusted publishing.
16501633
# Runs only on push-to-main — never on pull_request — so the `id-token: write`
16511634
# permission is never exposed to PR-controlled code (ref: ONC-1677).
1652-
publish_packages:
1635+
publish_public_apps:
16531636
needs: [
16541637
job_setup,
16551638
job_lint,
16561639
job_unit-tests,
1657-
build_packages
1640+
build_public_apps
16581641
]
16591642
name: Publish ${{ matrix.package_name }}
16601643
runs-on: ubuntu-latest
@@ -1665,15 +1648,12 @@ jobs:
16651648
&& needs.job_setup.result == 'success'
16661649
&& needs.job_lint.result == 'success'
16671650
&& needs.job_unit-tests.result == 'success'
1668-
&& needs.build_packages.result == 'success'
1651+
&& needs.build_public_apps.result == 'success'
16691652
permissions:
16701653
id-token: write
16711654
strategy:
16721655
matrix:
16731656
include:
1674-
- package_name: '@tryghost/activitypub'
1675-
package_path: 'apps/activitypub'
1676-
cdn_paths: 'https://cdn.jsdelivr.net/ghost/activitypub@CURRENT_MAJOR/dist/activitypub.js'
16771657
- package_name: '@tryghost/portal'
16781658
package_path: 'apps/portal'
16791659
cdn_paths: 'https://cdn.jsdelivr.net/ghost/portal@~CURRENT_MINOR/umd/portal.min.js'
@@ -1765,12 +1745,6 @@ jobs:
17651745
if: steps.version_check.outputs.version_changed == 'true'
17661746
run: echo "${{ steps.cdn_paths.outputs.cdn_paths }}"
17671747

1768-
- name: Wait before purging jsDelivr cache
1769-
if: steps.version_check.outputs.version_changed == 'true' && matrix.package_name == '@tryghost/activitypub'
1770-
run: |
1771-
echo "Purging jsDelivr cache immediately after publishing a new version on NPM is unreliable. Waiting 1 minute before purging cache..."
1772-
sleep 60
1773-
17741748
- name: Purge jsDelivr cache
17751749
if: steps.version_check.outputs.version_changed == 'true'
17761750
uses: gacts/purge-jsdelivr-cache@8d92aea944f1a3e8ad70505379e1a8ac72d56b73 # v1

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ Caddyfile
144144
# Admin
145145
/ghost/admin/dist
146146

147+
# Admin Toolbar
148+
/apps/admin-toolbar/umd
149+
147150
# Comments-UI
148151
/apps/comments-ui/umd
149152
/apps/comments-ui/playwright-report

AGENTS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,25 @@ Public-facing apps (`comments-ui`, `signup-form`, `sodo-search`, `portal`, `anno
254254
### Commit Messages
255255
When the user asks you to create a commit or draft a commit message, load and follow the `commit` skill from `.agents/skills/commit`.
256256

257+
### ESLint Config
258+
Source of truth: [eslint.shared.mjs](eslint.shared.mjs) at the repo root. Two factories cover most workspaces — `reactAppConfig` (every `apps/*` workspace) and `nodeLibConfig` (Node libs in `ghost/`). Each factory has full JSDoc with `@example`s; hover the call site in your editor.
259+
260+
Minimal example for a new admin React app (`apps/new-feature/eslint.config.js`):
261+
262+
```js
263+
import {reactAppConfig} from '../../eslint.shared.mjs';
264+
export default await reactAppConfig({
265+
tailwindCssPath: `${import.meta.dirname}/../admin/src/index.css`,
266+
shadeRestricted: true
267+
});
268+
```
269+
270+
Conventions:
271+
- **Rules are `'error'` or `'off'` — never `'warn'`.** Warnings get ignored and pollute output. Applies to every workspace covered by the factories above + the standalones; `e2e/` has its own setup (see [e2e/CLAUDE.md](e2e/CLAUDE.md)) and currently still uses warn-level Playwright rules — a separate cleanup.
272+
- **Params prefixed `legacy*`** (`legacyTailwindV3ConfigPath`, `legacyJsTsSplit`) are escape hatches for migrations that haven't shipped yet. Intentional and visible — PRs to remove them are scoped.
273+
- **Standalone configs** (`ghost/core`, `ghost/admin`, `apps/admin`, `apps/admin-toolbar`) exist because their rule sets genuinely don't fit a factory — read the file directly. They import shared atoms (`correctnessRules`, `nodeLibRules`, `localFilenamesPlugin`, `strictLinterOptions`) where applicable.
274+
- **Plugin deps**: workspaces that use Tailwind must list `tailwindcss` as a (dev)Dependency themselves; other eslint plugins are root devDeps because the factory imports them dynamically.
275+
257276
### When Working on Admin UI
258277
- **New features:** Build in React (`apps/admin-x-*` or `apps/posts`)
259278
- **Use:** `admin-x-framework` for API hooks (`useBrowse`, `useEdit`, etc.)

apps/activitypub/eslint.config.js

Lines changed: 6 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,7 @@
1-
import js from '@eslint/js';
2-
import globals from 'globals';
3-
import ghostPlugin from 'eslint-plugin-ghost';
4-
import reactPlugin from 'eslint-plugin-react';
5-
import reactHooksPlugin from 'eslint-plugin-react-hooks';
6-
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
7-
import tailwindcssPlugin from 'eslint-plugin-tailwindcss';
8-
import tseslint from 'typescript-eslint';
1+
import {reactAppConfig} from '../../eslint.shared.mjs';
92

10-
import {
11-
correctnessRules,
12-
mochaRulesOff,
13-
reactDefaultsOff,
14-
reactStrictRules,
15-
shadeLayeredImportsRule,
16-
sortImportsRule,
17-
tailwindRulesV4,
18-
tsUnusedVarsRule
19-
} from '../../eslint.shared.mjs';
20-
21-
const tailwindCssConfig = `${import.meta.dirname}/../admin/src/index.css`;
22-
23-
const reactFlat = reactPlugin.configs.flat.recommended;
24-
25-
export default tseslint.config(
26-
{
27-
ignores: ['dist/**/*']
28-
},
29-
{
30-
files: ['src/**/*.{js,ts,cjs,tsx}'],
31-
extends: [...tseslint.configs.recommended],
32-
languageOptions: {
33-
...reactFlat.languageOptions,
34-
ecmaVersion: 2022,
35-
sourceType: 'module',
36-
globals: {
37-
...globals.browser,
38-
...globals.node
39-
}
40-
},
41-
plugins: {
42-
...reactFlat.plugins,
43-
ghost: ghostPlugin,
44-
'react-hooks': reactHooksPlugin,
45-
'react-refresh': reactRefreshPlugin,
46-
tailwindcss: tailwindcssPlugin
47-
},
48-
settings: {
49-
react: {version: 'detect'},
50-
tailwindcss: {config: tailwindCssConfig}
51-
},
52-
rules: {
53-
...js.configs.recommended.rules,
54-
...reactFlat.rules,
55-
...reactHooksPlugin.configs.recommended.rules,
56-
...correctnessRules,
57-
...tsUnusedVarsRule,
58-
...reactDefaultsOff,
59-
...reactStrictRules,
60-
...sortImportsRule,
61-
...shadeLayeredImportsRule,
62-
...tailwindRulesV4,
63-
'no-undef': 'off',
64-
'no-redeclare': 'off',
65-
'no-unexpected-multiline': 'off',
66-
'no-shadow': 'off',
67-
'@typescript-eslint/no-shadow': 'error',
68-
'react-refresh/only-export-components': 'off',
69-
'@typescript-eslint/no-inferrable-types': 'off',
70-
'@typescript-eslint/no-non-null-assertion': 'off',
71-
'@typescript-eslint/no-empty-function': 'off'
72-
}
73-
},
74-
{
75-
files: ['test/**/*.{js,ts,cjs,tsx}'],
76-
extends: [...tseslint.configs.recommended],
77-
languageOptions: {
78-
ecmaVersion: 2022,
79-
sourceType: 'module',
80-
globals: {
81-
...globals.browser,
82-
...globals.node,
83-
...globals.vitest,
84-
vi: 'readonly'
85-
}
86-
},
87-
plugins: {
88-
ghost: ghostPlugin
89-
},
90-
rules: {
91-
...correctnessRules,
92-
...tsUnusedVarsRule,
93-
...mochaRulesOff(ghostPlugin),
94-
'no-undef': 'off',
95-
'@typescript-eslint/no-inferrable-types': 'off'
96-
}
97-
}
98-
);
3+
export default await reactAppConfig({
4+
tailwindCssPath: `${import.meta.dirname}/../admin/src/index.css`,
5+
shadeRestricted: true,
6+
sortImports: true
7+
});

apps/activitypub/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@tryghost/activitypub",
33
"type": "module",
4-
"version": "3.1.46",
4+
"version": "3.1.52",
55
"license": "MIT",
66
"repository": {
77
"type": "git",
@@ -84,7 +84,10 @@
8484
"test:acceptance"
8585
]
8686
}
87-
}
87+
},
88+
"tags": [
89+
"public-app"
90+
]
8891
},
8992
"dependencies": {
9093
"@hookform/resolvers": "5.4.0",

apps/activitypub/src/views/preferences/components/bluesky-sharing.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ const BlueskySharing: React.FC = () => {
6060
setHandleConfirmed(true);
6161
}
6262
});
63-
// eslint-disable-next-line react-hooks/exhaustive-deps
6463
}, []); // Empty deps - mutations are stable in practice
6564

6665
useEffect(() => {
@@ -107,7 +106,6 @@ const BlueskySharing: React.FC = () => {
107106
}, CONFIRMATION_INTERVAL);
108107

109108
return () => clearInterval(confirmHandleInterval);
110-
// eslint-disable-next-line react-hooks/exhaustive-deps
111109
}, [account?.blueskyEnabled, account?.blueskyHandleConfirmed, confirmHandle]); // disableBlueskyMutation is stable
112110

113111
if (isLoadingAccount) {

apps/activitypub/src/views/profile/components/profile-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ const ProfilePage:React.FC<ProfilePageProps> = ({
279279
</>
280280
}
281281
{customFields?.map((attachment: {name: string, value: string}) => (
282-
<span className='mt-3 line-clamp-1 flex flex-col text-[1.5rem]'>
282+
<span key={attachment.name} className='mt-3 line-clamp-1 flex flex-col text-[1.5rem]'>
283283
<span className={`text-xs font-semibold`}>{attachment.name}</span>
284284
<span dangerouslySetInnerHTML={{__html: sanitizeHtml(attachment.value)}} className='ap-profile-content truncate'/>
285285
</span>

0 commit comments

Comments
 (0)