biscuit-rails 0.1.4 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccf326f39e2cdccc7cb9991e0f9ad738b97ff3d1ca1837ea0793907e1cf07e72
4
- data.tar.gz: f94e5fec99ef45102ac05d9523d68b1db95c2a61a13a331553419de7feb2dc46
3
+ metadata.gz: 1eacb49f37fb2acc7796bd6574367af55d9428d4b3d83c69420a12f6c2856359
4
+ data.tar.gz: 22d3e98312498def4e3c0420448b1698a0fa123891dfe5e32237e90494760115
5
5
  SHA512:
6
- metadata.gz: 37b281be185c6e46f19ada084760ec585ff2e144dd15c532a5d94188610809488fbafe6d7fcfb64881bcc87f54b5ee22876ac7f7809079197cbee408e999ca61
7
- data.tar.gz: 32d9e8d34b824c355a190041c58d5fc6b33ad436ae9100b4babec40fb8585ca2031d0cc683d369590ca9fea6f9afe9972650a74a474448a06332df2def4e710e
6
+ metadata.gz: 88015405e5c6ef0cc3cdbd927008e59fa0717660c645aca958ce88bb7ebef09dc994f23a316c0b68eb129765b72847bb52cd7fef7c8e96f1b8d4e12658faea50
7
+ data.tar.gz: 57993714ad02872a2e6b84b489db819a52ce6f9c6fb427fd9a08434ac2e9494e400810ba48e416e908a5640859b193f361d8a17a3476be4fff6d0e6584eef9c5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.1] - 2026-04-02
9
+
10
+ ### Fixed
11
+
12
+ - `biscuit-install` skill: add Sprockets manifest step — when not using Propshaft,
13
+ `biscuit/biscuit.css` and `biscuit/biscuit_controller.js` must be linked in
14
+ `app/assets/config/manifest.js` or they 404 in test and production
15
+ - `biscuit-install` skill: fix integration test POST syntax — `body:` is not a valid
16
+ Rails integration test keyword; replaced with `params: { ... }.to_json` and explicit
17
+ `Content-Type`/`Accept` headers
18
+ - `biscuit-install` skill: explain why the Stimulus controller must be registered
19
+ manually (`eagerLoadControllersFrom` only discovers controllers in
20
+ `app/javascript/controllers/`, not gem-provided ones)
21
+
22
+ ---
23
+
24
+ ## [0.2.0] - 2026-03-31
25
+
26
+ ### Added
27
+
28
+ - `rails generate biscuit:install` generator — installs a Claude Code skill
29
+ into `.claude/skills/biscuit-install/` for AI-assisted setup
30
+ - `biscuit-install` Claude Code skill — guides developers through compatibility
31
+ checks, engine mounting, Stimulus registration, initializer configuration,
32
+ cookie and tracking script audit, integration tests, and optional commit
33
+ - README section documenting the AI-assisted setup workflow
34
+
35
+ ---
36
+
8
37
  ## [0.1.4] - 2026-03-29
9
38
 
10
39
  ### Fixed
data/README.md CHANGED
@@ -21,7 +21,39 @@ default).
21
21
 
22
22
  ---
23
23
 
24
- ## Installation
24
+ ## AI-assisted setup (Claude Code)
25
+
26
+ If you use [Claude Code](https://claude.ai/code), the quickest way to install
27
+ and configure biscuit is via the built-in setup skill.
28
+
29
+ After adding the gem to your Gemfile and running `bundle install`, run the
30
+ generator to install the skill:
31
+
32
+ ```sh
33
+ rails generate biscuit:install
34
+ ```
35
+
36
+ Then open Claude Code in your project and run:
37
+
38
+ ```
39
+ /biscuit-install
40
+ ```
41
+
42
+ The skill will:
43
+
44
+ - Check your app is compatible (Ruby, Rails, Propshaft, Stimulus)
45
+ - Mount the engine and register the Stimulus controller
46
+ - Ask about banner position, cookie categories, and other preferences
47
+ - Generate `config/initializers/biscuit.rb` from your answers
48
+ - Scan your codebase for existing cookies and third-party tracking scripts
49
+ (Google Analytics, GTM, Meta Pixel, etc.) and help you wrap them with
50
+ `biscuit_allowed?` guards
51
+ - Add integration tests and run them
52
+ - Optionally commit everything
53
+
54
+ ---
55
+
56
+ ## Manual installation
25
57
 
26
58
  Add to your `Gemfile`:
27
59
 
@@ -1,3 +1,3 @@
1
1
  module Biscuit
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -0,0 +1,26 @@
1
+ require "rails/generators"
2
+
3
+ module Biscuit
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Installs the biscuit-rails Claude Code setup skill into .claude/skills/biscuit-install/"
9
+
10
+ def copy_claude_skill
11
+ empty_directory ".claude/skills/biscuit-install"
12
+ copy_file "SKILL.md", ".claude/skills/biscuit-install/SKILL.md"
13
+
14
+ say ""
15
+ say " Claude Code skill installed at .claude/skills/biscuit-install/SKILL.md", :green
16
+ say ""
17
+ say " Open Claude Code in this project and run:", :cyan
18
+ say " /biscuit-install", :cyan
19
+ say ""
20
+ say " The skill will check compatibility, configure the gem, audit your existing", :cyan
21
+ say " cookies and tracking scripts, add tests, and optionally commit.", :cyan
22
+ say ""
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,275 @@
1
+ ---
2
+ name: biscuit-install
3
+ description: Install and configure the biscuit-rails GDPR cookie consent gem. Checks compatibility, installs the gem, configures categories and position, audits existing cookies and tracking scripts, adds integration tests, and optionally commits.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # biscuit-install
8
+
9
+ Complete installation and setup of biscuit-rails in this Rails app. Work through each step in order, confirming with the user before making any changes.
10
+
11
+ ---
12
+
13
+ ## Step 1 — Compatibility check
14
+
15
+ Read `Gemfile`, `Gemfile.lock`, and `.ruby-version` (if present) to check:
16
+
17
+ - **Ruby >= 3.2** — stop with a clear error if not met
18
+ - **Rails >= 8.0** — check `Gemfile.lock` for the rails version; stop if not met
19
+ - **Asset pipeline** — check Gemfile for `propshaft`. If absent, warn that `stylesheet_link_tag "biscuit/biscuit"` may need adjustment
20
+ - **JS setup** — check for `importmap-rails` vs `jsbundling-rails`/`vite_rails`. Note the difference for Step 5
21
+ - **Stimulus** — check for `stimulus-rails` or `hotwire-rails`. Warn if absent — Stimulus must be installed for the banner to work
22
+ - **Conflicts** — check for other cookie consent gems (`eu_cookie_law`, `cookieconsent`, `cookie_law`). Warn if found
23
+
24
+ Report findings before proceeding.
25
+
26
+ ---
27
+
28
+ ## Step 2 — Add gem to Gemfile
29
+
30
+ Check if `biscuit-rails` already appears in the Gemfile. If not:
31
+
32
+ - Add `gem "biscuit-rails"` to the Gemfile
33
+ - Run `bundle install`
34
+
35
+ If already present, skip this step.
36
+
37
+ ---
38
+
39
+ ## Step 3 — Mount the engine
40
+
41
+ Check `config/routes.rb` for an existing `mount Biscuit::Engine` line. If absent, add it inside the `routes.draw` block:
42
+
43
+ ```ruby
44
+ mount Biscuit::Engine, at: "/biscuit"
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Step 4 — Configure the initializer
50
+
51
+ Ask the user the following questions before writing anything:
52
+
53
+ 1. **Banner position** — top or bottom? (default: `bottom`)
54
+ 2. **Cookie categories** — which categories beyond `necessary` are needed? Options: `analytics`, `marketing`, `preferences`, or custom names. (default: `analytics` and `marketing`)
55
+ 3. **Reload on consent** — should the page reload after the user saves consent, so conditionally-loaded scripts activate immediately? (default: `false`)
56
+ 4. **Privacy policy URL** — where does your privacy policy live? (default: `"/privacy"`)
57
+ 5. **Cookie lifetime** — how many days should consent last? (default: `365`)
58
+
59
+ Then create `config/initializers/biscuit.rb` based on their answers. Example output:
60
+
61
+ ```ruby
62
+ Biscuit.configure do |config|
63
+ config.position = :bottom
64
+ config.privacy_policy_url = "/privacy"
65
+ config.cookie_expires_days = 365
66
+
67
+ config.categories = {
68
+ necessary: { required: true },
69
+ analytics: { required: false },
70
+ marketing: { required: false }
71
+ }
72
+ end
73
+ ```
74
+
75
+ If `config/initializers/biscuit.rb` already exists, show the current content and ask before overwriting.
76
+
77
+ If custom category names are used, remind the user to add matching i18n keys to their locale files — for example:
78
+
79
+ ```yaml
80
+ en:
81
+ biscuit:
82
+ categories:
83
+ my_category:
84
+ name: "My Category"
85
+ description: "Used for..."
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Step 5 — Register the Stimulus controller
91
+
92
+ ### If using importmap
93
+
94
+ Check `app/javascript/controllers/index.js`. If `biscuit/biscuit_controller` is not already imported, add:
95
+
96
+ ```javascript
97
+ import BiscuitController from "biscuit/biscuit_controller"
98
+ application.register("biscuit", BiscuitController)
99
+ ```
100
+
101
+ Ensure `application` is already imported at the top of that file (it will be in the standard Rails 8 scaffold).
102
+
103
+ The explicit import is required because Rails' `eagerLoadControllersFrom` only auto-discovers controllers under `app/javascript/controllers/` — it cannot find controllers provided by gems, so they must always be registered manually.
104
+
105
+ ### If using esbuild / jsbundling
106
+
107
+ The same import and register lines apply, but inform the user that `@hotwired/stimulus` must be marked as external in their esbuild config so it is not bundled twice:
108
+
109
+ ```js
110
+ // esbuild.config.js
111
+ external: ["@hotwired/stimulus"]
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Step 6 — Add stylesheet and banner to layout
117
+
118
+ Check `app/views/layouts/application.html.erb`:
119
+
120
+ - Add `<%= stylesheet_link_tag "biscuit/biscuit" %>` inside `<head>` if not already present
121
+ - Add `<%= biscuit_banner %>` as the first child of `<body>` if not already present
122
+
123
+ If the layout file doesn't exist at that path, ask the user which layout file to modify.
124
+
125
+ If the app uses `reload_on_consent: true` (from Step 4), use:
126
+
127
+ ```erb
128
+ <%= biscuit_banner(reload_on_consent: true) %>
129
+ ```
130
+
131
+ ### Sprockets manifest (if not using Propshaft)
132
+
133
+ If the app uses Sprockets (no `propshaft` in Gemfile), check whether `app/assets/config/manifest.js` exists. If it does, append these two lines if they are not already present — otherwise the stylesheet and JS file will 404 in test and production:
134
+
135
+ ```js
136
+ //= link biscuit/biscuit.css
137
+ //= link biscuit/biscuit_controller.js
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Step 7 — Cookie and tracking script audit
143
+
144
+ Scan the codebase for existing cookie and tracking usage that should be gated behind consent. Report all findings before making any changes — let the user decide what to wrap.
145
+
146
+ ### Server-side cookies (Ruby)
147
+
148
+ Search `app/` for:
149
+ - `cookies[`, `cookies.permanent[`, `cookies.signed[`, `cookies.encrypted[`
150
+
151
+ For each result, determine whether it is a functional/necessary cookie (session, CSRF, etc.) or a tracking cookie. Non-necessary cookies should be wrapped in the relevant controller:
152
+
153
+ ```ruby
154
+ if Biscuit::Consent.new(cookies).allowed?(:analytics)
155
+ cookies[:my_tracking_cookie] = { value: "...", expires: 1.year }
156
+ end
157
+ ```
158
+
159
+ ### Client-side storage (JavaScript)
160
+
161
+ Search `app/assets/` and `app/javascript/` for:
162
+ - `document.cookie`
163
+ - `localStorage.setItem`
164
+ - `sessionStorage.setItem`
165
+
166
+ Non-necessary writes should check the `biscuit_consent` cookie before setting.
167
+
168
+ ### Third-party scripts (layout/views)
169
+
170
+ Search `app/views/layouts/` and `app/views/` for known analytics and marketing patterns:
171
+
172
+ | Pattern | Category |
173
+ |---|---|
174
+ | `gtag(`, `googletagmanager.com`, `_gaq`, `ga(` | analytics |
175
+ | `GTM-` | analytics |
176
+ | `fbq(`, `connect.facebook.net` | marketing |
177
+ | `intercomSettings`, `widget.intercom.io` | marketing |
178
+ | `hj(`, `static.hotjar.com` | analytics |
179
+ | `hs-script-loader` | marketing |
180
+ | `_linkedin_data_partner_id` | marketing |
181
+
182
+ For each match, show the file, line number, and suggested wrapping:
183
+
184
+ ```erb
185
+ <% if biscuit_allowed?(:analytics) %>
186
+ <!-- existing script -->
187
+ <% end %>
188
+ ```
189
+
190
+ Ask the user to confirm each wrapping before applying it.
191
+
192
+ ---
193
+
194
+ ## Step 8 — Add integration tests
195
+
196
+ Check whether the app uses Minitest (`test/`) or RSpec (`spec/`).
197
+
198
+ ### Minitest
199
+
200
+ If `test/integration/biscuit_consent_test.rb` does not exist, create it:
201
+
202
+ ```ruby
203
+ require "test_helper"
204
+
205
+ class BiscuitConsentTest < ActionDispatch::IntegrationTest
206
+ test "banner is shown on first visit" do
207
+ get "/"
208
+ assert_response :success
209
+ assert_select "[data-controller='biscuit']"
210
+ end
211
+
212
+ test "biscuit_allowed? returns false before consent" do
213
+ get "/"
214
+ assert_equal false, Biscuit::Consent.new(cookies).allowed?(:analytics)
215
+ end
216
+
217
+ test "biscuit_allowed? for necessary always returns true" do
218
+ get "/"
219
+ assert_equal true, Biscuit::Consent.new(cookies).allowed?(:necessary)
220
+ end
221
+
222
+ test "accept all sets consent cookie" do
223
+ post "/biscuit/consent",
224
+ params: { categories: { analytics: true, marketing: true } }.to_json,
225
+ headers: { "Content-Type" => "application/json", "Accept" => "application/json" }
226
+ assert_response :success
227
+ assert Biscuit::Consent.new(cookies).given?
228
+ assert Biscuit::Consent.new(cookies).allowed?(:analytics)
229
+ end
230
+
231
+ test "reject all sets non-required categories to false" do
232
+ post "/biscuit/consent",
233
+ params: { categories: { analytics: false, marketing: false } }.to_json,
234
+ headers: { "Content-Type" => "application/json", "Accept" => "application/json" }
235
+ assert_response :success
236
+ assert Biscuit::Consent.new(cookies).given?
237
+ assert_equal false, Biscuit::Consent.new(cookies).allowed?(:analytics)
238
+ end
239
+ end
240
+ ```
241
+
242
+ Adjust category names to match the categories configured in Step 4.
243
+
244
+ ### RSpec
245
+
246
+ If `spec/requests/biscuit_consent_spec.rb` does not exist, create an equivalent using RSpec/Rails request spec syntax.
247
+
248
+ Run the new tests and confirm they pass before continuing.
249
+
250
+ ---
251
+
252
+ ## Step 9 — Optional commit
253
+
254
+ Ask the user: "Would you like to commit these changes?"
255
+
256
+ If yes, stage only the files that were created or modified during this setup and commit:
257
+
258
+ ```
259
+ git add config/routes.rb config/initializers/biscuit.rb \
260
+ app/views/layouts/application.html.erb \
261
+ app/javascript/controllers/index.js \
262
+ test/integration/biscuit_consent_test.rb
263
+ git commit -m "Install biscuit-rails cookie consent"
264
+ ```
265
+
266
+ Do not use `git add -A` — only stage biscuit-related files.
267
+
268
+ ---
269
+
270
+ ## Summary
271
+
272
+ After all steps complete, print a summary of:
273
+ - What was installed and configured
274
+ - Which tracking scripts were wrapped (if any)
275
+ - Any manual steps remaining (custom i18n keys, esbuild config, layouts other than `application.html.erb`)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: biscuit-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gareth James
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-29 00:00:00.000000000 Z
10
+ date: 2026-04-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -51,6 +51,8 @@ files:
51
51
  - lib/biscuit/engine.rb
52
52
  - lib/biscuit/rails.rb
53
53
  - lib/biscuit/version.rb
54
+ - lib/generators/biscuit/install/install_generator.rb
55
+ - lib/generators/biscuit/install/templates/SKILL.md
54
56
  homepage: https://github.com/garethfr/biscuit-rails
55
57
  licenses:
56
58
  - MIT