comfortable_media_surfer 3.1.6 → 3.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +2 -0
- data/.github/workflows/claude.yml +99 -1
- data/.github/workflows/coveralls.yml +4 -3
- data/.github/workflows/rubyonrails.yml +34 -5
- data/CHANGELOG.md +27 -5
- data/CONTRIBUTING.md +5 -1
- data/Gemfile +6 -5
- data/README.md +4 -4
- data/Rakefile +201 -0
- data/app/assets/javascripts/comfy/admin/cms/application.js +3 -0
- data/app/assets/javascripts/comfy/admin/cms/base.js +1 -0
- data/app/assets/javascripts/comfy/admin/cms/file_link.js +34 -11
- data/app/assets/javascripts/comfy/admin/cms/file_upload.js +37 -28
- data/app/assets/javascripts/comfy/admin/cms/files_modal.js +76 -26
- data/app/controllers/comfy/admin/cms/categories_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/files_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/layouts_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/pages_controller.rb +33 -4
- data/app/controllers/comfy/admin/cms/revisions/base_controller.rb +2 -2
- data/app/controllers/comfy/admin/cms/revisions/page_controller.rb +5 -5
- data/app/controllers/comfy/admin/cms/revisions/translation_controller.rb +5 -5
- data/app/controllers/comfy/admin/cms/sites_controller.rb +3 -3
- data/app/controllers/comfy/admin/cms/snippets_controller.rb +1 -1
- data/app/controllers/comfy/admin/cms/translations_controller.rb +15 -3
- data/app/controllers/comfy/cms/base_controller.rb +16 -7
- data/app/controllers/comfy/cms/content_controller.rb +4 -4
- data/app/controllers/concerns/comfy/reorder_action.rb +3 -4
- data/app/models/comfy/cms/categorization.rb +9 -0
- data/app/models/comfy/cms/category.rb +1 -1
- data/app/models/comfy/cms/fragment.rb +7 -0
- data/app/models/comfy/cms/layout.rb +7 -2
- data/app/models/comfy/cms/page.rb +13 -4
- data/app/models/comfy/cms/site.rb +3 -3
- data/app/models/comfy/cms/translation.rb +5 -0
- data/app/models/concerns/comfy/cms/with_categories.rb +1 -1
- data/app/models/concerns/comfy/cms/with_fragments.rb +1 -1
- data/app/views/comfy/admin/cms/pages/edit.html.haml +5 -1
- data/config/database.yml +1 -1
- data/config/initializers/json.rb +2 -0
- data/config/locales/ar.yml +6 -0
- data/config/locales/ca.yml +6 -0
- data/config/locales/cs.yml +6 -0
- data/config/locales/da.yml +6 -0
- data/config/locales/de.yml +6 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/es.yml +6 -0
- data/config/locales/fi.yml +6 -0
- data/config/locales/fr.yml +6 -0
- data/config/locales/gr.yml +6 -0
- data/config/locales/hr.yml +6 -0
- data/config/locales/it.yml +6 -0
- data/config/locales/ja.yml +6 -0
- data/config/locales/nb.yml +6 -0
- data/config/locales/nl.yml +6 -0
- data/config/locales/pl.yml +6 -0
- data/config/locales/pt-BR.yml +6 -0
- data/config/locales/ru.yml +6 -0
- data/config/locales/sk.yml +6 -0
- data/config/locales/sv.yml +6 -0
- data/config/locales/tr.yml +6 -0
- data/config/locales/uk.yml +6 -0
- data/config/locales/zh-CN.yml +6 -0
- data/config/locales/zh-TW.yml +6 -0
- data/config/storage.yml +1 -1
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/8.1.gemfile +1 -0
- data/lib/comfortable_media_surfer/access_control/admin_authentication.rb +2 -2
- data/lib/comfortable_media_surfer/content/tags/date.rb +1 -1
- data/lib/comfortable_media_surfer/content/tags/datetime.rb +1 -1
- data/lib/comfortable_media_surfer/extensions/has_revisions.rb +2 -2
- data/lib/comfortable_media_surfer/routing.rb +2 -0
- data/lib/comfortable_media_surfer/seeds/page/importer.rb +40 -12
- data/lib/comfortable_media_surfer/seeds.rb +1 -3
- data/lib/comfortable_media_surfer/version.rb +1 -1
- metadata +2 -3
- data/gemfiles/7.1.gemfile +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f97502706b77eb2d52d4e8bf2ee3bde3bbe30d73523fa969526752610749738
|
|
4
|
+
data.tar.gz: bc9b28a3c19372688e334f6b259325b9d6054913f52d42d255b2d9f8afa0172b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5711fa8354ad641f8667f70554aae551740325a650e86a61216bb68776e90b23cf741ba2760c6727c81da0d34ca5db04725ed7e7aa0add8141f429bd3b1a1559
|
|
7
|
+
data.tar.gz: 81087fabb487b7c0fd1455415051bc2be6fa053ebe50ada16936be258081d4841814e0fadaa744d4f6fbd670dda928db03213e9ea822a273ace8ad07d3be10af
|
|
@@ -22,6 +22,8 @@ jobs:
|
|
|
22
22
|
- name: Run Claude Code Review
|
|
23
23
|
id: claude-review
|
|
24
24
|
uses: anthropics/claude-code-action@v1
|
|
25
|
+
env:
|
|
26
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
25
27
|
with:
|
|
26
28
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
27
29
|
prompt: |
|
|
@@ -11,19 +11,112 @@ on:
|
|
|
11
11
|
types: [submitted]
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
|
-
|
|
14
|
+
authorize_claude_actor:
|
|
15
15
|
if: |
|
|
16
16
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
17
|
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
18
|
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
19
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
outputs:
|
|
24
|
+
authorized: ${{ steps.permission-gate.outputs.authorized }}
|
|
25
|
+
steps:
|
|
26
|
+
- name: Verify actor can run Claude
|
|
27
|
+
id: permission-gate
|
|
28
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
|
29
|
+
with:
|
|
30
|
+
script: |
|
|
31
|
+
const owner = context.repo.owner;
|
|
32
|
+
const repo = context.repo.repo;
|
|
33
|
+
const permissionByLogin = new Map();
|
|
34
|
+
|
|
35
|
+
function claudeRequester() {
|
|
36
|
+
if (
|
|
37
|
+
context.eventName === 'issue_comment' ||
|
|
38
|
+
context.eventName === 'pull_request_review_comment'
|
|
39
|
+
) {
|
|
40
|
+
return context.payload.comment?.user?.login;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (context.eventName === 'pull_request_review') {
|
|
44
|
+
return context.payload.review?.user?.login;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (context.eventName === 'issues') {
|
|
48
|
+
return context.payload.issue?.user?.login;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function hasWriteAccessFor(username) {
|
|
55
|
+
if (!username) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (permissionByLogin.has(username)) {
|
|
60
|
+
return permissionByLogin.get(username);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let hasWriteAccess = false;
|
|
64
|
+
try {
|
|
65
|
+
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
66
|
+
owner,
|
|
67
|
+
repo,
|
|
68
|
+
username
|
|
69
|
+
});
|
|
70
|
+
hasWriteAccess = ['admin', 'write'].includes(permission.permission);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
core.warning(
|
|
73
|
+
`Unable to verify ${username} write permission for Claude; ` +
|
|
74
|
+
`treating the Claude request as untrusted: ${error.message || error}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
permissionByLogin.set(username, hasWriteAccess);
|
|
79
|
+
return hasWriteAccess;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const actor = context.actor;
|
|
83
|
+
const requester = claudeRequester();
|
|
84
|
+
|
|
85
|
+
if (!requester) {
|
|
86
|
+
core.setOutput('authorized', 'false');
|
|
87
|
+
core.setFailed('Unable to run Claude because the requester could not be determined.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!(await hasWriteAccessFor(actor))) {
|
|
92
|
+
core.setOutput('authorized', 'false');
|
|
93
|
+
core.setFailed(`Unable to run Claude because ${actor} does not have write/admin repository permission.`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!(await hasWriteAccessFor(requester))) {
|
|
98
|
+
core.setOutput('authorized', 'false');
|
|
99
|
+
core.setFailed(
|
|
100
|
+
`Unable to run Claude because requester ${requester} does not have write/admin repository permission.`
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
core.setOutput('authorized', 'true');
|
|
106
|
+
core.info(`Authorized ${actor} and Claude requester ${requester} to run Claude.`);
|
|
107
|
+
|
|
108
|
+
claude:
|
|
109
|
+
needs: authorize_claude_actor
|
|
110
|
+
if: needs.authorize_claude_actor.outputs.authorized == 'true'
|
|
111
|
+
runs-on: ubuntu-latest
|
|
21
112
|
permissions:
|
|
22
113
|
contents: read
|
|
23
114
|
pull-requests: read
|
|
24
115
|
issues: read
|
|
25
116
|
id-token: write
|
|
26
117
|
actions: read # Required for Claude to read CI results on PRs
|
|
118
|
+
checks: read
|
|
119
|
+
statuses: read
|
|
27
120
|
steps:
|
|
28
121
|
- name: Checkout repository
|
|
29
122
|
uses: actions/checkout@v4
|
|
@@ -33,12 +126,17 @@ jobs:
|
|
|
33
126
|
- name: Run Claude Code
|
|
34
127
|
id: claude
|
|
35
128
|
uses: anthropics/claude-code-action@v1
|
|
129
|
+
env:
|
|
130
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
36
131
|
with:
|
|
132
|
+
github_token: ${{ github.token }}
|
|
37
133
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
134
|
|
|
39
135
|
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
136
|
additional_permissions: |
|
|
41
137
|
actions: read
|
|
138
|
+
checks: read
|
|
139
|
+
statuses: read
|
|
42
140
|
|
|
43
141
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
44
142
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
@@ -3,6 +3,7 @@ on:
|
|
|
3
3
|
push:
|
|
4
4
|
branches:
|
|
5
5
|
- main
|
|
6
|
+
- master
|
|
6
7
|
pull_request:
|
|
7
8
|
|
|
8
9
|
jobs:
|
|
@@ -10,9 +11,9 @@ jobs:
|
|
|
10
11
|
strategy:
|
|
11
12
|
matrix:
|
|
12
13
|
ruby-version:
|
|
13
|
-
- "3.
|
|
14
|
+
- "3.4"
|
|
14
15
|
rails-version:
|
|
15
|
-
- "
|
|
16
|
+
- "8.1"
|
|
16
17
|
continue-on-error: [true]
|
|
17
18
|
name: ${{ format('Coverage (Ruby {0}, Rails {1})', matrix.ruby-version, matrix.rails-version) }}
|
|
18
19
|
runs-on: ubuntu-latest
|
|
@@ -49,4 +50,4 @@ jobs:
|
|
|
49
50
|
bin/bundle exec rails db:drop
|
|
50
51
|
bin/bundle exec rails db:create
|
|
51
52
|
bin/bundle exec rails db:migrate
|
|
52
|
-
bin/bundle exec
|
|
53
|
+
bin/bundle exec rake test:serial
|
|
@@ -3,6 +3,7 @@ on:
|
|
|
3
3
|
push:
|
|
4
4
|
branches:
|
|
5
5
|
- main
|
|
6
|
+
- master
|
|
6
7
|
pull_request:
|
|
7
8
|
|
|
8
9
|
jobs:
|
|
@@ -10,19 +11,15 @@ jobs:
|
|
|
10
11
|
strategy:
|
|
11
12
|
matrix:
|
|
12
13
|
ruby-version:
|
|
13
|
-
- "3.2"
|
|
14
14
|
- "3.3"
|
|
15
15
|
- "3.4"
|
|
16
16
|
- "4.0"
|
|
17
17
|
rails-version:
|
|
18
|
-
- "7.1"
|
|
19
18
|
- "7.2"
|
|
20
19
|
- "8.0"
|
|
21
20
|
- "8.1"
|
|
22
|
-
continue-on-error: [true]
|
|
23
21
|
name: ${{ format('Tests (Ruby {0}, Rails {1})', matrix.ruby-version, matrix.rails-version) }}
|
|
24
22
|
runs-on: ubuntu-latest
|
|
25
|
-
continue-on-error: ${{ matrix.continue-on-error }}
|
|
26
23
|
env:
|
|
27
24
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.rails-version }}.gemfile
|
|
28
25
|
RAILS_ENV: test
|
|
@@ -54,7 +51,7 @@ jobs:
|
|
|
54
51
|
bin/bundle exec rails db:drop
|
|
55
52
|
bin/bundle exec rails db:create
|
|
56
53
|
bin/bundle exec rails db:migrate
|
|
57
|
-
bin/bundle exec
|
|
54
|
+
bin/bundle exec rake test
|
|
58
55
|
|
|
59
56
|
- name: Lint the code
|
|
60
57
|
run: bin/bundle exec rubocop --parallel
|
|
@@ -63,3 +60,35 @@ jobs:
|
|
|
63
60
|
run: |
|
|
64
61
|
bin/bundle exec brakeman -q -w3
|
|
65
62
|
bin/bundle exec bundler-audit --update --gemfile-lock gemfiles/${{ matrix.rails-version }}.gemfile.lock
|
|
63
|
+
|
|
64
|
+
system_tests:
|
|
65
|
+
name: System tests (Ruby 4.0, Rails 8.1)
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
env:
|
|
68
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/8.1.gemfile
|
|
69
|
+
RAILS_ENV: test
|
|
70
|
+
SKIP_COV: true
|
|
71
|
+
steps:
|
|
72
|
+
- name: Checkout source
|
|
73
|
+
uses: actions/checkout@v4
|
|
74
|
+
|
|
75
|
+
- name: Install Node.js
|
|
76
|
+
uses: actions/setup-node@v4
|
|
77
|
+
with:
|
|
78
|
+
cache: npm
|
|
79
|
+
- run: npm ci
|
|
80
|
+
|
|
81
|
+
- name: Install Ruby 4.0
|
|
82
|
+
uses: ruby/setup-ruby@v1
|
|
83
|
+
with:
|
|
84
|
+
ruby-version: "4.0"
|
|
85
|
+
bundler-cache: true
|
|
86
|
+
|
|
87
|
+
- name: Compile assets
|
|
88
|
+
run: npm run build && npm run build:css
|
|
89
|
+
|
|
90
|
+
- name: Prepare database
|
|
91
|
+
run: bin/bundle exec rails db:prepare
|
|
92
|
+
|
|
93
|
+
- name: Run system tests
|
|
94
|
+
run: bin/bundle exec rake test:system
|
data/CHANGELOG.md
CHANGED
|
@@ -12,17 +12,37 @@ For all changes prior to the inception of this project, see the [Release History
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [v3.1.7] - 2026-02-20
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fix wrong number of arguments for `to_s` in date tags from [PR 58](https://github.com/shakacode/comfortable-media-surfer/pull/58)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Update rubocop requirement from ~> 1.81.1 to ~> 1.84.0
|
|
24
|
+
|
|
25
|
+
## [v3.1.6] - 2026-02-19
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Claude code workflow
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- problem creating root/home page from [P5 45](https://github.com/shakacode/comfortable-media-surfer/pull/45)
|
|
34
|
+
|
|
15
35
|
## [v3.1.5] - 2026-01-08
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
### Added
|
|
18
38
|
|
|
19
39
|
- Rails 8.1 compatibility
|
|
20
40
|
|
|
21
|
-
|
|
41
|
+
### Fixed
|
|
22
42
|
|
|
23
|
-
- Strip possible spaces around exclude page slugs, from [PR](https://github.com/shakacode/comfortable-media-surfer/pull/42)
|
|
43
|
+
- Strip possible spaces around exclude page slugs, from [PR 42](https://github.com/shakacode/comfortable-media-surfer/pull/42)
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
### Changed
|
|
26
46
|
|
|
27
47
|
- gem updates for propshaft, brakeman, rubocop, haml-rails
|
|
28
48
|
|
|
@@ -115,7 +135,9 @@ First release of `comfortable_media_surfer`. This new gem is a revival of [Comfo
|
|
|
115
135
|
|
|
116
136
|
- Rebranded **ComfortableMexicanSofa** as **ComfortableMediaSurfer** in order to publish new gem (database table names and schema have not changed).
|
|
117
137
|
|
|
118
|
-
[Unreleased]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.
|
|
138
|
+
[Unreleased]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.7...master
|
|
139
|
+
[v3.1.7]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.6...v3.1.7
|
|
140
|
+
[v3.1.6]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.5...v3.1.6
|
|
119
141
|
[v3.1.5]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.4...v3.1.5
|
|
120
142
|
[v3.1.4]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.3...v3.1.4
|
|
121
143
|
[v3.1.3]: https://github.com/shakacode/comfortable-media-surfer/compare/v3.1.2...v3.1.3
|
data/CONTRIBUTING.md
CHANGED
|
@@ -13,7 +13,11 @@ Fork the project. Optionally, create a branch you want to work on.
|
|
|
13
13
|
created for you. Just run `bundle exec rake db:migrate`
|
|
14
14
|
- Prepare the environment by running `rails comfy:compile_assets`, and
|
|
15
15
|
`rake db:test:prepare`
|
|
16
|
-
- Make sure that existing tests are passing by running `bundle exec rake test
|
|
16
|
+
- Make sure that existing tests are passing by running `bundle exec rake test`.
|
|
17
|
+
This uses six isolated workers by default, merges their coverage, and records
|
|
18
|
+
per-file runtimes in `tmp/parallel_runtime_test.log` for future balancing.
|
|
19
|
+
Set `PARALLEL_WORKERS` to tune the worker count, or run
|
|
20
|
+
`bundle exec rake test:serial` for a serial coverage run.
|
|
17
21
|
- There are system tests that can be run with `bundle exec rake test:system`.
|
|
18
22
|
You need to have `chromedriver` installed for that.
|
|
19
23
|
- You should be able to start the app via `bin/rails s` and navigate to http://localhost:3000/admin
|
data/Gemfile
CHANGED
|
@@ -9,9 +9,9 @@ gem 'rails', '~> 8.1.0'
|
|
|
9
9
|
|
|
10
10
|
group :development, :test do
|
|
11
11
|
gem 'autoprefixer-rails', '~> 10.4.21.0'
|
|
12
|
-
gem 'byebug', '~>
|
|
12
|
+
gem 'byebug', '~> 13.0.0', platforms: %i[mri mingw x64_mingw]
|
|
13
13
|
gem 'gem-release'
|
|
14
|
-
gem 'image_processing', '>= 1.12.
|
|
14
|
+
gem 'image_processing', '>= 1.12.2'
|
|
15
15
|
gem 'propshaft', '~> 1.3.1'
|
|
16
16
|
gem 'sqlite3', '>= 2.1'
|
|
17
17
|
# gem 'mysql2', '~> 0.5'
|
|
@@ -19,12 +19,12 @@ group :development, :test do
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
group :development do
|
|
22
|
-
gem 'listen', '~> 3.
|
|
22
|
+
gem 'listen', '~> 3.10.0'
|
|
23
23
|
gem 'web-console', '~> 4.2'
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
group :test do
|
|
27
|
-
gem 'brakeman'
|
|
27
|
+
gem 'brakeman'
|
|
28
28
|
gem 'bundler-audit', '~> 0.9.1'
|
|
29
29
|
gem 'coveralls_reborn', '~> 0.29.0', require: false
|
|
30
30
|
gem 'cuprite', '>= 0.15'
|
|
@@ -34,9 +34,10 @@ group :test do
|
|
|
34
34
|
gem 'minitest-reporters', '>= 1.6.1'
|
|
35
35
|
gem 'mocha', '>= 2.3.0', require: false
|
|
36
36
|
gem 'ostruct'
|
|
37
|
+
gem 'parallel_tests', '~> 5.7', require: false
|
|
37
38
|
gem 'puma'
|
|
38
39
|
gem 'rails-controller-testing', '~> 1.0.5'
|
|
39
|
-
gem 'rubocop',
|
|
40
|
+
gem 'rubocop', require: false
|
|
40
41
|
gem 'rubocop-minitest'
|
|
41
42
|
gem 'rubocop-rails'
|
|
42
43
|
gem 'simplecov', '~> 0.22.0', require: false
|
data/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
[](https://github.com/shakacode/comfortable-media-surfer/actions/workflows/rubyonrails.yml)
|
|
2
|
-
[](https://coveralls.io/github/shakacode/comfortable-media-surfer?branch=master)
|
|
3
3
|
[](http://rubygems.org/gems/comfortable_media_surfer)
|
|
4
4
|
[](http://rubygems.org/gems/comfortable_media_surfer)
|
|
5
5
|
[](https://github.com/shakacode/comfortable-media-surfer/releases)
|
|
6
6
|
|
|
7
7
|
# ComfortableMediaSurfer
|
|
8
8
|
|
|
9
|
-
ComfortableMediaSurfer is a powerful Ruby 7.
|
|
9
|
+
ComfortableMediaSurfer is a powerful Ruby 7.2+ CMS (Content Management System) Engine, picking up where [ComfortableMexicanSofa](https://github.com/comfy/comfortable-mexican-sofa) left off.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- Simple drop-in integration with Rails 7.
|
|
13
|
+
- Simple drop-in integration with Rails 7.2+ apps with minimal configuration
|
|
14
14
|
* The CMS keeps clear from the rest of your application
|
|
15
15
|
* Powerful page templating capability using [Content Tags](https://github.com/shakacode/comfortable-media-surfer/wiki/Docs:-Content-Tags)
|
|
16
16
|
* [Multiple Sites](https://github.com/shakacode/comfortable-media-surfer/wiki/Docs:-Sites) from a single installation
|
|
@@ -27,7 +27,7 @@ ComfortableMediaSurfer is a powerful Ruby 7.0+ CMS (Content Management System) E
|
|
|
27
27
|
|
|
28
28
|
## Compatibility
|
|
29
29
|
|
|
30
|
-
On Ruby 3.x, Rails 7.x
|
|
30
|
+
On Ruby 3.2+, 4.x, Rails 7.2+, 8.x
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
33
33
|
|
data/Rakefile
CHANGED
|
@@ -1,5 +1,206 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'config/application'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require 'parallel_tests/tasks'
|
|
6
|
+
require 'rake/testtask'
|
|
7
|
+
require 'stringio'
|
|
4
8
|
|
|
5
9
|
Rails.application.load_tasks
|
|
10
|
+
|
|
11
|
+
module ComfortableMediaSurferTestOutput
|
|
12
|
+
SUMMARY = %r{(?<assertions>\d+) assertions, (?<errors>\d+) errors, (?<failures>\d+) failures, (?<skips>\d+) skips?, (?<tests>\d+) tests}
|
|
13
|
+
COLORS = { green: 32, yellow: 33, red: 31, cyan: 36 }.freeze
|
|
14
|
+
|
|
15
|
+
class Progress
|
|
16
|
+
def initialize(io)
|
|
17
|
+
@io = io
|
|
18
|
+
@line = +''
|
|
19
|
+
@pending = +''
|
|
20
|
+
@awaiting_blank = false
|
|
21
|
+
@expecting_progress = false
|
|
22
|
+
@printing_progress = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def feed(chunk)
|
|
26
|
+
chunk.each_char { |character| consume(character) }
|
|
27
|
+
flush
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def finish
|
|
31
|
+
flush
|
|
32
|
+
@io.puts if @printing_progress
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def consume(character)
|
|
38
|
+
return if character == "\r"
|
|
39
|
+
return finish_line if character == "\n"
|
|
40
|
+
|
|
41
|
+
if @expecting_progress && character.match?(%r{[.EFS]})
|
|
42
|
+
@printing_progress = true
|
|
43
|
+
@pending << character
|
|
44
|
+
elsif @printing_progress
|
|
45
|
+
flush
|
|
46
|
+
@io.puts
|
|
47
|
+
@printing_progress = false
|
|
48
|
+
@expecting_progress = false
|
|
49
|
+
@line = +character
|
|
50
|
+
else
|
|
51
|
+
@expecting_progress = false if @expecting_progress
|
|
52
|
+
@line << character
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def finish_line
|
|
57
|
+
flush
|
|
58
|
+
if @printing_progress
|
|
59
|
+
@io.puts
|
|
60
|
+
@expecting_progress = false
|
|
61
|
+
elsif @line.start_with?('# Running tests')
|
|
62
|
+
@awaiting_blank = true
|
|
63
|
+
elsif @awaiting_blank && @line.empty?
|
|
64
|
+
@expecting_progress = true
|
|
65
|
+
@awaiting_blank = false
|
|
66
|
+
end
|
|
67
|
+
@line.clear
|
|
68
|
+
@printing_progress = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def flush
|
|
72
|
+
return if @pending.empty?
|
|
73
|
+
|
|
74
|
+
@io.print ComfortableMediaSurferTestOutput.progress_color(@pending)
|
|
75
|
+
@io.flush
|
|
76
|
+
@pending.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
module_function
|
|
81
|
+
|
|
82
|
+
def capture(env, command)
|
|
83
|
+
output = +''
|
|
84
|
+
errors = +''
|
|
85
|
+
status = nil
|
|
86
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
87
|
+
progress = Progress.new($stdout)
|
|
88
|
+
|
|
89
|
+
Open3.popen3(env, *command) do |stdin, stdout, stderr, wait_thread|
|
|
90
|
+
stdin.close
|
|
91
|
+
error_reader = Thread.new { stderr.read }
|
|
92
|
+
loop do
|
|
93
|
+
chunk = stdout.readpartial(4096)
|
|
94
|
+
output << chunk
|
|
95
|
+
progress.feed(chunk)
|
|
96
|
+
end
|
|
97
|
+
rescue EOFError
|
|
98
|
+
errors = error_reader.value
|
|
99
|
+
status = wait_thread.value
|
|
100
|
+
ensure
|
|
101
|
+
progress.finish
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
105
|
+
[output, errors, status, elapsed]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def color(text, name)
|
|
109
|
+
return text unless $stdout.tty? && !ENV.key?('NO_COLOR')
|
|
110
|
+
|
|
111
|
+
"\e[#{COLORS.fetch(name)}m#{text}\e[0m"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def progress_color(text)
|
|
115
|
+
return text unless $stdout.tty? && !ENV.key?('NO_COLOR')
|
|
116
|
+
|
|
117
|
+
highlighted = text.gsub('S', "\e[33mS\e[32m")
|
|
118
|
+
.gsub(%r{[EF]}) { |character| "\e[31m#{character}\e[32m" }
|
|
119
|
+
"\e[32m#{highlighted}\e[0m"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def summary(output)
|
|
123
|
+
counts = output.match(SUMMARY)
|
|
124
|
+
raise 'Could not read the parallel test summary' unless counts
|
|
125
|
+
|
|
126
|
+
"#{counts[:tests]} tests, #{counts[:assertions]} assertions, " \
|
|
127
|
+
"#{counts[:failures]} failures, #{counts[:errors]} errors, " \
|
|
128
|
+
"#{counts[:skips]} #{counts[:skips] == '1' ? 'skip' : 'skips'}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def run_parallel
|
|
132
|
+
workers = ENV.fetch('PARALLEL_WORKERS', '6')
|
|
133
|
+
skip_coverage = ENV.fetch('SKIP_COV', nil)
|
|
134
|
+
FileUtils.rm_rf('coverage')
|
|
135
|
+
FileUtils.mkdir_p('tmp')
|
|
136
|
+
Rake::Task['parallel:prepare'].invoke(workers)
|
|
137
|
+
|
|
138
|
+
env = {
|
|
139
|
+
'PARALLEL_COVERAGE' => skip_coverage ? nil : 'true',
|
|
140
|
+
'RECORD_RUNTIME' => 'true',
|
|
141
|
+
'SKIP_COV' => skip_coverage,
|
|
142
|
+
'NO_COLOR' => '1',
|
|
143
|
+
'TMPDIR' => ENV.fetch('COMFY_TEST_TMPDIR', '/tmp')
|
|
144
|
+
}
|
|
145
|
+
command = [
|
|
146
|
+
'bundle', 'exec', 'parallel_test', 'test',
|
|
147
|
+
'--type', 'test',
|
|
148
|
+
'--exclude-pattern', 'test/system/',
|
|
149
|
+
'-n', workers
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
puts color("Running tests (#{workers} workers)…", :cyan)
|
|
153
|
+
stdout, stderr, status, elapsed = capture(env, command)
|
|
154
|
+
unless status.success?
|
|
155
|
+
$stdout.write(stdout)
|
|
156
|
+
$stderr.write(stderr)
|
|
157
|
+
raise "Parallel tests failed with status #{status.exitstatus}"
|
|
158
|
+
end
|
|
159
|
+
$stderr.write(stderr) unless stderr.empty?
|
|
160
|
+
puts color(summary(stdout), :green)
|
|
161
|
+
puts color(format('Completed in %.1f seconds', elapsed), :cyan)
|
|
162
|
+
|
|
163
|
+
report_coverage unless skip_coverage
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def report_coverage
|
|
167
|
+
require 'simplecov'
|
|
168
|
+
SimpleCov.coverage_dir File.expand_path('coverage', __dir__)
|
|
169
|
+
result = SimpleCov::ResultMerger.merged_result
|
|
170
|
+
quietly { SimpleCov::Formatter::HTMLFormatter.new.format(result) }
|
|
171
|
+
coverage = format('Coverage: %<percent>.2f%% (%<covered>d/%<total>d) — coverage/index.html',
|
|
172
|
+
percent: result.covered_percent, covered: result.covered_lines, total: result.total_lines)
|
|
173
|
+
puts color(coverage, :green)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def quietly
|
|
177
|
+
previous = $stdout
|
|
178
|
+
$stdout = StringIO.new
|
|
179
|
+
yield
|
|
180
|
+
ensure
|
|
181
|
+
$stdout = previous
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
namespace :test do
|
|
186
|
+
task :prepare_serial do
|
|
187
|
+
FileUtils.rm_rf('coverage')
|
|
188
|
+
ENV['TMPDIR'] = ENV.fetch('COMFY_TEST_TMPDIR', '/tmp')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
desc 'Run tests serially with coverage'
|
|
192
|
+
Rake::TestTask.new(:serial) do |task|
|
|
193
|
+
task.libs << 'test' << 'lib'
|
|
194
|
+
task.test_files = Rake::FileList['test/**/*_test.rb'].exclude('test/system/**/*_test.rb')
|
|
195
|
+
task.warning = false
|
|
196
|
+
end
|
|
197
|
+
Rake::Task['test:serial'].enhance(['test:prepare_serial'])
|
|
198
|
+
|
|
199
|
+
desc 'Run tests in isolated processes'
|
|
200
|
+
task(:parallel) { ComfortableMediaSurferTestOutput.run_parallel }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
Rake::Task[:test].clear
|
|
204
|
+
|
|
205
|
+
desc 'Run tests in parallel (default: 6 workers)'
|
|
206
|
+
task test: 'test:parallel'
|