trek 2.0.1 → 2.1.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/.prettierignore +2 -0
- data/.rubocop.yml +11 -0
- data/AGENTS.md +5 -2
- data/CHANGELOG.md +34 -1
- data/Gemfile.lock +1 -1
- data/Makefile +13 -0
- data/app/components/trek/entries/item_component/item_component.css +1 -0
- data/app/components/trek/form/content_editor_component/content_editor_component.js +2 -0
- data/app/components/trek/form/content_editor_component.rb +4 -2
- data/app/components/trek/form/fieldset_component/fieldset_component.css +4 -3
- data/app/components/trek/form/group_component/group_component.css +5 -2
- data/app/components/trek/menu_component.rb +15 -2
- data/app/controllers/concerns/trek/model.rb +13 -6
- data/app/controllers/trek/resource_controller.rb +9 -1
- data/app/helpers/trek/i18n_helper.rb +1 -1
- data/docs/.vitepress/config.mjs +2 -1
- data/docs/deploy.md +57 -0
- data/docs/getting-started.md +1 -0
- data/docs/reference/controllers.md +23 -0
- data/docs/reference/generators/install.md +2 -0
- data/docs/reference/generators/scaffold.md +12 -0
- data/lib/generators/trek/install/config_generator.rb +9 -1
- data/lib/generators/trek/install/postmark_generator.rb +2 -0
- data/lib/generators/trek/install/prosopite_generator.rb +56 -0
- data/lib/generators/trek/install/scalingo_generator.rb +19 -7
- data/lib/generators/trek/install/yarn_generator.rb +1 -1
- data/lib/generators/trek/install_generator.rb +1 -0
- data/lib/generators/trek/scaffold_generator.rb +37 -5
- data/lib/generators/trek/templates/controllers/admin/scaffold_controller.rb.tt +2 -2
- data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +4 -3
- data/lib/generators/trek/templates/spec/requests/admin/n_plus_one_spec.rb +28 -0
- data/lib/generators/trek/templates/views/admin/scaffold/show.html.slim.tt +2 -2
- data/lib/generators/trek/upgrade/v2_generator.rb +31 -0
- data/lib/trek/version.rb +1 -1
- data/package.json +1 -1
- data/vendor/tom_select/css/tom-select.custom.css +0 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz: '
|
|
3
|
+
metadata.gz: 5a2b7ab45c51c036cab94f7ab2f6119b84cfccf501d1133cd4f390e320d40aa6
|
|
4
|
+
data.tar.gz: '0803283b1a6283983d174c9797d5ea753f97ffc1ce458e0285ab2ac681257ecf'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10aea34299dba23456b3f687c6e450996d5473e8d34d9a12200373c2e3f394025e6e5fc2aab4d2dfe00628133113171354780ab3123c6628dc5480e75ed87987
|
|
7
|
+
data.tar.gz: 966a6004dc2aaa1b7ff21c4db7c87d00e3eb4054ea3dbaec780c62823b0d297f8736be2d93216dc438820375aa3487063882325fd55cb8f612ec831a9179c830
|
data/.prettierignore
ADDED
data/.rubocop.yml
CHANGED
|
@@ -33,6 +33,17 @@ Style/StringLiteralsInInterpolation:
|
|
|
33
33
|
Layout/LineLength:
|
|
34
34
|
Max: 120
|
|
35
35
|
|
|
36
|
+
# rubocop-rails-omakase disables both so it can indent methods under
|
|
37
|
+
# `private`; we use the normal style (methods at the class-body level,
|
|
38
|
+
# `private` included) and want `rubocop --autocorrect` to fix indentation,
|
|
39
|
+
# generated files included.
|
|
40
|
+
Layout/IndentationWidth:
|
|
41
|
+
Enabled: true
|
|
42
|
+
|
|
43
|
+
Layout/IndentationConsistency:
|
|
44
|
+
Enabled: true
|
|
45
|
+
EnforcedStyle: normal
|
|
46
|
+
|
|
36
47
|
Gemspec/RequiredRubyVersion:
|
|
37
48
|
Enabled: false
|
|
38
49
|
|
data/AGENTS.md
CHANGED
|
@@ -21,11 +21,13 @@ bin/setup # bundle install + yarn install (dev setup)
|
|
|
21
21
|
bin/console # IRB with trek loaded
|
|
22
22
|
rake # default: spec + rubocop
|
|
23
23
|
rake spec # run tests (or: bundle exec rspec)
|
|
24
|
+
make lint # all linters below
|
|
25
|
+
make fix # rubocop/eslint/stylelint/prettier autocorrect
|
|
24
26
|
make release # bump version (version.rb + package.json), stamp CHANGELOG, tag, push gem + npm
|
|
25
27
|
yarn docs:dev # VitePress docs locally
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
Linters (also run automatically via lefthook on pre-commit):
|
|
30
|
+
Linters (`make lint`; also run automatically via lefthook on pre-commit):
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
33
|
bundle exec rubocop
|
|
@@ -51,5 +53,6 @@ Security: `bundle exec brakeman` · `bundle exec bundle-audit check --update`
|
|
|
51
53
|
- **Slim** linted via slim_lint; **JS/CSS** via ESLint + Stylelint + Prettier 2.8.8
|
|
52
54
|
- **i18n**: keep English + French locales in sync (`config/locales/trek.*.yml`); DeepL auto-translation via `DEEPL_AUTH_KEY`
|
|
53
55
|
- **Versioning**: version lives in both `lib/trek/version.rb` and `package.json` — keep in sync via `make release`
|
|
54
|
-
- **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry.** When the change requires action in the host app on upgrade,
|
|
56
|
+
- **CHANGELOG.md**: Keep-a-Changelog format. **Every MR must add an `[Unreleased]` entry.** When the change requires action in the host app on upgrade, end the entry with a bold `**Upgrade note:**` label followed by what to do. Use the same label when the change lets host apps remove or simplify existing patches or custom implementations (e.g. "**Upgrade note:** you can now drop your custom …") — never as an unlabelled trailing sentence.
|
|
57
|
+
- **Public code**: Trek ships publicly (gem, npm, docs), so nothing committed may mention a client or project name, nor a client's domain models or business logic — not in code, comments, specs, templates, docs, CHANGELOG or commit messages. Use neutral examples (`Page`, `Shop::Product`, an engine's `Blog::Post`). Client context belongs in GitLab issues and MR descriptions only.
|
|
55
58
|
- **CI**: GitLab CI (`.gitlab-ci.yml`), stages build → lint → security → test → deploy; docs deploy to Netlify; deps via Renovate
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.1.0] - 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Docs: a [Deploying to Scalingo](https://trek.etamin.studio/deploy) guide covering the trek-buildpack setup, the environment to configure, and how to migrate an app from the stock buildpacks.
|
|
8
|
+
- `trek:install:prosopite` generator (wired into `trek:install`) sets up [Prosopite](https://github.com/charkost/prosopite) N+1 query detection (#211): adds `prosopite` and `pg_query` to the `development`/`test` Gemfile group and a `config/initializers/prosopite.rb` that mounts the Rack middleware — so every request is scanned, Trek's engine controllers included — raising `Prosopite::NPlusOneQueriesError` in test and logging in development. It also generates `spec/requests/admin/n_plus_one_spec.rb`, a smoke request spec rendering the dashboard, pages index and fragments index as an admin (extend it with the app's other admin routes). Documented under [Controllers › N+1 detection](https://trek.etamin.studio/reference/controllers#n-1-detection) with the `Prosopite.pause` escape hatch. **Upgrade note:** existing apps can run `rails g trek:install:prosopite`; apps that already wired Prosopite by hand can keep their setup (the generator skips the gems when `prosopite` is already in the Gemfile and won't overwrite an existing initializer).
|
|
9
|
+
- `make lint` and `make fix` targets in Trek's own Makefile, mirroring the ones generated for host apps (RuboCop, slim-lint, ESLint, Stylelint, Prettier — the `fix` variant autocorrects all but slim-lint). A `.prettierignore` excludes the gitignored esbuild output in `app/assets/builds/`, which made the Prettier check fail on a local checkout that had run `yarn build`.
|
|
10
|
+
- `Trek::MenuComponent` items accept an `active:` option for menu entries whose pages live under other controllers: a `String` is matched as a `controller_path` prefix, a `Regexp` against the whole path, and a boolean forces (or clears) the state — e.g. `s.with_item(text: …, href: [:admin, Shop], active: "admin/shop")` keeps _Shop_ highlighted while editing its products. Without it, the item stays active when its href matches the current controller, as before.
|
|
11
|
+
- `content_editor` accepts an `autofocus:` option (default `false`): when true, the editor grabs focus on mount with the cursor at the end of the content — useful when the editor is the primary field of a side panel.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- The shared `.rubocop.yml` (Trek's own, and the one `trek:install:rubocop` copies into host apps) re-enables `Layout/IndentationWidth` and `Layout/IndentationConsistency` with `EnforcedStyle: normal`. rubocop-rails-omakase disables both to allow its house style of indenting methods under `private`; Trek's style keeps methods at the class-body level, and a plain `rubocop --autocorrect` now fixes indentation — in generated files too. **Upgrade note:** copy the two cop blocks from the template (`rails g trek:install:rubocop` will prompt to overwrite) into your app's `.rubocop.yml`, then run `bundle exec rubocop --autocorrect` once; methods indented under `private` and mis-indented generated files get fixed.
|
|
16
|
+
- `trek:install:scalingo` now targets the dedicated [trek-buildpack](https://github.com/etaminstudio/trek-buildpack) (#193) instead of the stock apt + ruby buildpack pair: it no longer generates `.buildpacks` (and removes an existing one) and prints the `env-set` commands to run (`BUILDPACK_URL` pinned to the buildpack's release tag, and the `DATABASE_URL=$SCALINGO_POSTGRESQL_URL` mapping). The buildpack pins Ruby/Node/Yarn from `.ruby-version`, the `.node-version` that `rails new` already generates, and `package.json`'s `packageManager`, runs `yarn install` once, and ships a slimmed image (~40% smaller, measured deploys down from ~3min to ~1min35) — see the buildpack README for details. **Upgrade note:** on each Scalingo app, set `BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack#v0.1.0`, delete `.buildpacks`, make sure `.node-version` exists (apps generated with `rails new -j esbuild` have it), and check that `config/database.yml` reads `DATABASE_URL` in production (mapped with `scalingo env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'`).
|
|
17
|
+
- `Trek::Form::FieldsetComponent`: the fieldset title (`.c-trek--form--fieldset-title`) now shares the form group label style (`--slate-12`, semibold, `1.2rem` bottom margin) so a titled fieldset reads like a labelled group; the `section` modifier only adds its larger font size on top. Hosts that restyled the plain fieldset title themselves can drop that override.
|
|
18
|
+
- `trek:install:yarn` now installs **Yarn 4.18.0** (was 4.17.0). **Upgrade note:** existing apps can upgrade with `yarn set version 4.18.0` followed by `yarn install`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `trek:scaffold` now references the model top-level qualified (`::Shop::Product`) in the generated controller's `model` and in the `Admin::Dashboard::MODELS_IN_MENU` entry: once the scaffold creates the `Admin::Shop` namespace, a bare `Shop` under `module Admin` resolves to that module instead of the model — the scaffolded controller raised `NameError`, and previously generated code referencing a `Shop` model silently got the module. The generator also warns when a namespace segment matches an existing model file that `rails g model` would replace with a namespace module. (#201) **Upgrade note:** if a scaffold introduced a namespace named like an existing model, qualify that model as `::Shop` in the existing `Admin::…Controller#model` and `MODELS_IN_MENU` entries.
|
|
23
|
+
- `Trek::Entries::ItemComponent`: the details list (`.c-trek--entries--item-details`) now wraps (`flex-wrap: wrap`) instead of overflowing or squeezing its items when an entry has many details or a narrow container.
|
|
24
|
+
- `trek:install:postmark` is idempotent again: re-running it on an app where it had already run appended a second `config.action_mailer.delivery_method = :postmark` / `postmark_settings` block to `config/environments/production.rb` (the gem, credentials and mailer steps were already guarded). The config injection is now skipped when the file already sets `delivery_method = :postmark`. (#210)
|
|
25
|
+
- Namespaced models (e.g. `Shop::Product`, routed under `namespace :shop`) now work out of the box with `Trek::ResourceController` (#212): `object_params` requires `model_name.param_key` (`shop_product`) instead of the demodulized element, so create/update no longer 400; `model_element_route` no longer prefixes the namespace when the route key already carries it (`shop_product`), so the delete button and list item links build `admin_shop_product_path` instead of the double-namespaced `admin_shop_shop_product_path` — models from isolated engines with relative naming (e.g. an engine's `Blog::Post`, route key `post`) keep the `[:blog, :post]` form; the controller defines `implicit_authorization_target` (`@object || model`) so `authorized(params)` and `Trek::Reorderable#reorder`'s bare `authorize!` find the policy; and `i18n_model_gender` reads `activerecord.models.<i18n_key>.gender` (`shop/product`), so `i18n_resource_label` picks the declared gender instead of the neutral fallback. **Upgrade note:** hosts that worked around this with a controller concern overriding `implicit_authorization_target`/`object_params`/`model_element_route`, or a prepend on `Trek::I18nHelper#i18n_model_gender`, can drop them.
|
|
26
|
+
- `Trek::Form::GroupComponent` now styles `input[type="time"]` like `input[type="date"]` (field frame, 16rem max width, lighter picker colour); `time_field`s used to render as bare browser inputs.
|
|
27
|
+
- Removed the dangling `sourceMappingURL` comment from the vendored `tom_select/css/tom-select.custom.css`: the referenced `.map` file is not shipped, so every host-app esbuild build (including `assets:precompile` on deploy) logged a `missing-source-map` debug warning. (#209)
|
|
28
|
+
- `trek:scaffold` now re-indents the generated controller and policy: for a namespaced model the nested `module` wrappers were emitted without indenting the class body, tripping the host app's linters. The generator runs a targeted `rubocop --autocorrect --only Layout/IndentationWidth,Layout/IndentationConsistency` on both files (forced with `--only` since rubocop-rails-omakase leaves those cops disabled). (#199)
|
|
29
|
+
- The `config/initializers/host.rb` generated by `trek:install:config` no longer references `ActionMailer::Base` during the initializer phase: doing so fires Action Mailer's load hook before the app's autoloader can resolve app classes, so an app configuring a custom `config.action_mailer.delivery_job` (e.g. `"MailDeliveryJob"`) crashed with a `NameError` on every production boot — breaking `assets:precompile` on deploy. The assignment is now deferred with `ActiveSupport.on_load(:action_mailer)`. (#208) **Upgrade note:** apply the same change to your app's `config/initializers/host.rb` — replace the `ActionMailer::Base.default_url_options = … if Rails.env.production?` line with the deferred block (see the generator template).
|
|
30
|
+
- `trek:scaffold` now generates the right controller class for a namespaced model (e.g. `rails g trek:scaffold Shop::Product`): the template used the still-namespaced `controller_class_name` inside the per-namespace `module` wrappers, defining `Admin::Shop::Shop::ProductsController` while the generated route expected `Admin::Shop::ProductsController` — every request 404ed with an uninitialized constant. The class name is now demodulized, like the policy template already did. (#200)
|
|
31
|
+
- The `show.html.slim` generated by `trek:scaffold` no longer ends with a trailing blank line: the attribute loop emitted its separator after every item instead of between items, so slim-lint failed in host apps with `TrailingBlankLines: Multiple empty lines in the end of file`. (#205)
|
|
32
|
+
- `trek:upgrade:v2` now upgrades Yarn to 4.18.0 first (applying the 0.1.26 upgrade note: `yarn set version` + dropping the `interactive-tools` plugin) when the app still runs Yarn 3. Previously the generator wrote `npmPreapprovedPackages` — a Yarn 4-only setting — unconditionally, which under Yarn 3 is a hard error that breaks every yarn command, including the generator's own `yarn add` step.
|
|
33
|
+
- Clarified the 0.1.26 changelog upgrade note about the Yarn minimal-age-gate exemption: it only applied to the pre-2.0.0 private GitLab distribution — since 2.0.0 the (Yarn 4-only) `npmPreapprovedPackages` entry, set up automatically by `trek:install` and `trek:upgrade:v2`, replaces it.
|
|
34
|
+
- Fixed whitespace in `model.en.yml.tt` which caused prettier to fail.
|
|
35
|
+
|
|
3
36
|
## [2.0.1] - 2026-08-18
|
|
4
37
|
|
|
5
38
|
### Fixed
|
|
@@ -41,7 +74,7 @@
|
|
|
41
74
|
### Fixed
|
|
42
75
|
|
|
43
76
|
- Added the missing French translations that fell back to English: `admin.dialog_component.{title,text}` (the confirmation dialog shown on every destroy action) and `admin.{pages,fragments}.{show.intro,reorder.success,search.alert}`. Also added the English `admin.section`/`admin.sections` flash messages that previously existed only in French. (#194)
|
|
44
|
-
- `trek:install:yarn` now exempts the `@etaminstudio` scope from Yarn's minimal-age gate (`npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in the generated `.yarnrc.yml`), so a freshly published `@etaminstudio/trek` release installs without being quarantined; the gate still applies to public packages. **Upgrade note:** existing apps can add `npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in their `.yarnrc.yml`.
|
|
77
|
+
- `trek:install:yarn` now exempts the `@etaminstudio` scope from Yarn's minimal-age gate (`npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in the generated `.yarnrc.yml`), so a freshly published `@etaminstudio/trek` release installs without being quarantined; the gate still applies to public packages. **Upgrade note:** existing apps can add `npmMinimalAgeGate: 0` under `npmScopes.etaminstudio` in their `.yarnrc.yml`. _(Only relevant to the private GitLab distribution used before 2.0.0: the `@etaminstudio` scope is retired since then, so this scope exemption does nothing for the public unscoped `trek` package. From 2.0.0 the equivalent is `trek` listed under `npmPreapprovedPackages` — a Yarn 4-only setting, hence the Yarn upgrade above — which `trek:install` generates and `trek:upgrade:v2` adds during migration, so no manual step is needed.)_
|
|
45
78
|
- `trek:install:locales` no longer generates an invalid `config/initializers/locales.rb` (`I18n.available_locales = [:]` / `I18n.default_locale = :`) when passed no or blank locales. A blank option/env now falls back to the default (`en, fr`), blank entries are dropped, and the list can never end up empty. (#169)
|
|
46
79
|
|
|
47
80
|
## [0.1.25] - 2026-07-01
|
data/Gemfile.lock
CHANGED
data/Makefile
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
.PHONY: *
|
|
2
2
|
|
|
3
|
+
lint:
|
|
4
|
+
bundle exec rubocop
|
|
5
|
+
bundle exec slim-lint "app/**/*.slim"
|
|
6
|
+
yarn eslint "app/**/*.js"
|
|
7
|
+
yarn stylelint "app/**/*.css"
|
|
8
|
+
yarn prettier --check "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
|
|
9
|
+
|
|
10
|
+
fix:
|
|
11
|
+
bundle exec rubocop --autocorrect
|
|
12
|
+
yarn eslint "app/**/*.js" --fix
|
|
13
|
+
yarn stylelint "app/**/*.css" --fix
|
|
14
|
+
yarn prettier --write "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
|
|
15
|
+
|
|
3
16
|
release:
|
|
4
17
|
@read -p "New version (current: $$(sed -n 's/.*VERSION = "\(.*\)"/\1/p' lib/trek/version.rb)): " version; \
|
|
5
18
|
if [ -z "$$version" ]; then echo "Version cannot be empty"; exit 1; fi; \
|
|
@@ -23,6 +23,7 @@ export class Controller extends BaseController {
|
|
|
23
23
|
];
|
|
24
24
|
static values = {
|
|
25
25
|
placeholder: String,
|
|
26
|
+
autofocus: Boolean,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
connect() {
|
|
@@ -55,6 +56,7 @@ export class Controller extends BaseController {
|
|
|
55
56
|
PromptsBlock,
|
|
56
57
|
],
|
|
57
58
|
content: this.contentJson,
|
|
59
|
+
autofocus: this.autofocusValue ? "end" : false,
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
this.editor.on("update", () => this.updateContent());
|
|
@@ -8,7 +8,7 @@ module Trek
|
|
|
8
8
|
include Turbo::FramesHelper
|
|
9
9
|
include ViewComponent::Translatable
|
|
10
10
|
|
|
11
|
-
attr_reader :nodes, :blocks, :floating
|
|
11
|
+
attr_reader :nodes, :blocks, :floating, :autofocus
|
|
12
12
|
|
|
13
13
|
def initialize(form, object_name, method_name, options = {})
|
|
14
14
|
@nodes = options.delete(:nodes)
|
|
@@ -17,6 +17,7 @@ module Trek
|
|
|
17
17
|
@blocks = true if @blocks.nil?
|
|
18
18
|
@floating = options.delete(:floating)
|
|
19
19
|
@floating = true if @floating.nil?
|
|
20
|
+
@autofocus = options.delete(:autofocus) || false
|
|
20
21
|
|
|
21
22
|
super
|
|
22
23
|
end
|
|
@@ -29,7 +30,8 @@ module Trek
|
|
|
29
30
|
}.merge(
|
|
30
31
|
stimulus_class_hash("active", class_for("is-active")),
|
|
31
32
|
stimulus_class_hash("empty", class_for("is-empty")),
|
|
32
|
-
stimulus_value_hash("placeholder", t(".placeholder"))
|
|
33
|
+
stimulus_value_hash("placeholder", t(".placeholder")),
|
|
34
|
+
stimulus_value_hash("autofocus", autofocus)
|
|
33
35
|
)
|
|
34
36
|
end
|
|
35
37
|
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.title {
|
|
13
|
+
color: var(--slate-12);
|
|
14
|
+
display: block;
|
|
15
|
+
font-weight: var(--semibold);
|
|
16
|
+
margin-bottom: 1.2rem;
|
|
13
17
|
margin-left: -1rem;
|
|
14
18
|
padding: 0 1rem;
|
|
15
19
|
}
|
|
@@ -35,8 +39,5 @@
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
.root.section .title {
|
|
38
|
-
color: var(--slate-12);
|
|
39
42
|
font-size: 1.2em;
|
|
40
|
-
font-weight: var(--semibold);
|
|
41
|
-
margin-bottom: 1.2rem;
|
|
42
43
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
.root input[type="text"],
|
|
33
33
|
.root input[type="url"],
|
|
34
34
|
.root input[type="date"],
|
|
35
|
+
.root input[type="time"],
|
|
35
36
|
.root select,
|
|
36
37
|
.root textarea {
|
|
37
38
|
background-color: var(--slate-2);
|
|
@@ -55,7 +56,8 @@
|
|
|
55
56
|
|
|
56
57
|
.root input[type="number"],
|
|
57
58
|
.root input[name*="color"],
|
|
58
|
-
.root input[type="date"]
|
|
59
|
+
.root input[type="date"],
|
|
60
|
+
.root input[type="time"] {
|
|
59
61
|
max-width: 16rem;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -63,7 +65,8 @@
|
|
|
63
65
|
max-width: 19rem;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
.root input[type="date"]
|
|
68
|
+
.root input[type="date"],
|
|
69
|
+
.root input[type="time"] {
|
|
67
70
|
color: var(--slate-11);
|
|
68
71
|
font-weight: var(--light);
|
|
69
72
|
}
|
|
@@ -57,6 +57,11 @@ module Trek
|
|
|
57
57
|
option :href
|
|
58
58
|
option :text
|
|
59
59
|
option :allowed_to, default: -> { true }
|
|
60
|
+
# Overrides the active state for a section whose sub-resources live under other
|
|
61
|
+
# controllers: a String is a controller_path prefix ("admin/shop"), a Regexp is matched
|
|
62
|
+
# against it, a boolean forces the state. By default the item is active when its href
|
|
63
|
+
# matches the current controller.
|
|
64
|
+
option :active, optional: true
|
|
60
65
|
|
|
61
66
|
def call
|
|
62
67
|
tag.li(
|
|
@@ -79,10 +84,18 @@ module Trek
|
|
|
79
84
|
private
|
|
80
85
|
|
|
81
86
|
def active?
|
|
82
|
-
|
|
87
|
+
case active
|
|
88
|
+
when nil then href_path == controller_path
|
|
89
|
+
when String then controller_path.start_with?(active)
|
|
90
|
+
when Regexp then active.match?(controller_path)
|
|
91
|
+
else active
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def href_path
|
|
96
|
+
Array.wrap(href).map do |part|
|
|
83
97
|
part.respond_to?(:model_name) ? part.model_name.collection : part
|
|
84
98
|
end.join("/").sub("root", "dashboards")
|
|
85
|
-
href_path == controller_path
|
|
86
99
|
end
|
|
87
100
|
end
|
|
88
101
|
end
|
|
@@ -21,7 +21,7 @@ module Trek
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Returns model collection as an array of symbols for route building.
|
|
24
|
-
# For namespaced models like
|
|
24
|
+
# For namespaced models like Shop::Product, returns [:shop, :products].
|
|
25
25
|
# For non-namespaced models like Page, returns [:pages].
|
|
26
26
|
def model_collection_route
|
|
27
27
|
model_name.collection.split("/").map(&:to_sym)
|
|
@@ -32,13 +32,20 @@ module Trek
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Returns model element as an array of symbols for route building.
|
|
35
|
-
# For namespaced models like Missive::List, returns [:missive, :list].
|
|
36
35
|
# For non-namespaced models like Page, returns [:page].
|
|
37
|
-
#
|
|
38
|
-
#
|
|
36
|
+
# For namespaced models, it depends on the route key: callers build member
|
|
37
|
+
# routes as `[:admin, *model_element_route[0...-1], record]`, and the
|
|
38
|
+
# record's route key must not carry the namespace twice.
|
|
39
|
+
# - Shop::Product (plain app namespace, route key `shop_product`)
|
|
40
|
+
# returns [:shop_product] — a [:shop, :product] prefix would yield
|
|
41
|
+
# `admin_shop_shop_product_path`.
|
|
42
|
+
# - Blog::Post from an isolated engine (`isolate_namespace` → relative
|
|
43
|
+
# model naming, route key `post`) returns [:blog, :post], since the
|
|
44
|
+
# namespace segment is needed for `admin_blog_post_path`.
|
|
39
45
|
def model_element_route
|
|
40
|
-
|
|
41
|
-
(
|
|
46
|
+
element = model_name.singular_route_key
|
|
47
|
+
namespace = element == model_name.element ? model_name.collection.split("/")[0...-1] : []
|
|
48
|
+
(namespace + [element]).map(&:to_sym)
|
|
42
49
|
end
|
|
43
50
|
|
|
44
51
|
def model_intro(action: nil)
|
|
@@ -79,8 +79,16 @@ module Trek
|
|
|
79
79
|
authorize! @object
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# The form posts under model_name.param_key, which keeps the namespace
|
|
83
|
+
# (shop_product for Shop::Product) unlike model_element (product).
|
|
82
84
|
def object_params
|
|
83
|
-
authorized(params.require(
|
|
85
|
+
authorized(params.require(model_name.param_key))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# action_policy infers the target from controller_name.classify, which
|
|
89
|
+
# doesn't exist for a namespaced model (Product for Shop::Product).
|
|
90
|
+
def implicit_authorization_target
|
|
91
|
+
@object || model
|
|
84
92
|
end
|
|
85
93
|
end
|
|
86
94
|
end
|
|
@@ -11,7 +11,7 @@ module Trek
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def i18n_model_gender(model_name)
|
|
14
|
-
t("activerecord.models.#{model_name.
|
|
14
|
+
t("activerecord.models.#{model_name.i18n_key}.gender", default: "n")
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def boolean_collection(key: "boolean_labels")
|
data/docs/.vitepress/config.mjs
CHANGED
data/docs/deploy.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Deploying to Scalingo
|
|
2
|
+
|
|
3
|
+
Trek apps deploy to [Scalingo](https://scalingo.com) with the dedicated [trek-buildpack](https://github.com/etaminstudio/trek-buildpack), a single buildpack that replaces the usual `apt` + `ruby` buildpack pair and knows how a Trek app is built.
|
|
4
|
+
|
|
5
|
+
## What the buildpack does
|
|
6
|
+
|
|
7
|
+
- installs the APT packages listed in `Aptfile` (image processing libraries, jemalloc), skipping `apt-get update` and package resolution entirely when the `Aptfile` is unchanged
|
|
8
|
+
- installs Ruby with [rv](https://rv.dev) — precompiled, in seconds — using the version pinned in `.ruby-version`
|
|
9
|
+
- installs Node.js pinned by `.node-version` (generated by `rails new`), and the exact Yarn release pinned by `package.json`'s `packageManager` field — no unpinned defaults
|
|
10
|
+
- runs `yarn install` exactly once, with a persistent cache, before `assets:precompile`
|
|
11
|
+
- slims the shipped image: `node_modules`, Yarn caches and Node.js itself are dropped after the build, roughly halving the image size of a typical Trek app — faster deploys, restarts and scaling
|
|
12
|
+
|
|
13
|
+
Compared with the stock buildpacks, deploys typically go from around 3 minutes to about 1 minute 30 with a warm cache.
|
|
14
|
+
|
|
15
|
+
## Set up the app
|
|
16
|
+
|
|
17
|
+
Generate the Scalingo files (an `Aptfile` and a `Procfile` with the web process and post-deploy migrations):
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
rails g trek:install:scalingo
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then configure the Scalingo app's environment, pinning the buildpack to a release tag:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack#v0.1.0
|
|
27
|
+
scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'
|
|
28
|
+
scalingo --app <app> env-set RAILS_MASTER_KEY=$(cat config/master.key)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Make sure `config/database.yml` reads `DATABASE_URL` in production — the section `rails new` generates targets a local socket with a password variable instead:
|
|
32
|
+
|
|
33
|
+
```yml
|
|
34
|
+
production:
|
|
35
|
+
primary: &primary_production
|
|
36
|
+
<<: *default
|
|
37
|
+
url: <%= ENV["DATABASE_URL"] %>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Provision a PostgreSQL addon (`scalingo --app <app> addons-add postgresql <plan>`), push, and the app boots with `RAILS_ENV`, `RAILS_LOG_TO_STDOUT` and `RAILS_SERVE_STATIC_FILES` already set by the buildpack.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Variable | Effect |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `TREK_BUILDPACK_RUNTIME_NODE=1` | Ship Node.js in the image (only if the app runs JavaScript at runtime, e.g. execjs) |
|
|
47
|
+
| `TREK_BUILDPACK_PRUNE_SOURCEMAPS=1` | Also remove `.map` files from the compiled assets |
|
|
48
|
+
|
|
49
|
+
## Migrating an existing app
|
|
50
|
+
|
|
51
|
+
Apps deployed with the stock `.buildpacks` (apt + ruby) migrate in three steps:
|
|
52
|
+
|
|
53
|
+
1. delete `.buildpacks` (re-running `rails g trek:install:scalingo` does it) and check that `.node-version` exists — apps generated with `rails new -j esbuild` have it
|
|
54
|
+
2. set `BUILDPACK_URL` as above, and map `DATABASE_URL` if the app doesn't read it yet
|
|
55
|
+
3. deploy — the first build fills the caches, the next ones benefit from them
|
|
56
|
+
|
|
57
|
+
The buildpack's [README](https://github.com/etaminstudio/trek-buildpack#readme) details the cache layout and the `Aptfile` syntax.
|
data/docs/getting-started.md
CHANGED
|
@@ -132,3 +132,4 @@ Next steps:
|
|
|
132
132
|
|
|
133
133
|
- [Customize Trek](/customize) — branding, favicon, components
|
|
134
134
|
- [Reference manual](/reference/) — generators, models, components and more
|
|
135
|
+
- [Deploying to Scalingo](/deploy) — `trek:install:scalingo`, a few environment variables, and the dedicated trek-buildpack handles the rest
|
|
@@ -93,3 +93,26 @@ end
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
Like `Trek::Filters`, forgetting to call `apply_scopes!` raises `Trek::Scopes::NotAppliedError` outside production.
|
|
96
|
+
|
|
97
|
+
## N+1 detection
|
|
98
|
+
|
|
99
|
+
`trek:install:prosopite` (part of `trek:install`) sets up [Prosopite](https://github.com/charkost/prosopite)
|
|
100
|
+
in development and test: `config/initializers/prosopite.rb` mounts its Rack middleware, so **every request** —
|
|
101
|
+
including Trek's own engine controllers — is scanned. A detected N+1 raises `Prosopite::NPlusOneQueriesError`
|
|
102
|
+
in test (so request specs fail) and is logged in development.
|
|
103
|
+
|
|
104
|
+
The generator also adds `spec/requests/admin/n_plus_one_spec.rb`, a smoke request spec that renders the
|
|
105
|
+
dashboard, the pages index and the fragments index as an admin — extend its route table with the app's other
|
|
106
|
+
admin pages (scaffolded indexes and edit pages in particular), with a few records created beforehand, since an
|
|
107
|
+
empty list can't reveal an N+1.
|
|
108
|
+
|
|
109
|
+
Legitimate repeated queries (e.g. one `COUNT` per row) can be exempted with the escape hatch:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
Prosopite.pause do
|
|
113
|
+
scopes.index_with { |scope| relation.public_send(scope).count }
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Trek's own concerns already do this where needed (`Trek::Scopes#scope_counts`). Guard with
|
|
118
|
+
`if defined?(Prosopite)` in code that must also run in production, where the gem isn't loaded.
|
|
@@ -32,7 +32,9 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
32
32
|
| Tooling | esbuild, PostCSS, EditorConfig, Procfiles, Makefile, health check endpoint |
|
|
33
33
|
| Linting | Standard, ESLint, Stylelint, Prettier, Slim Lint, Lefthook git hooks |
|
|
34
34
|
| Security | Brakeman, bundler-audit, ruby_audit |
|
|
35
|
+
| N+1 detection | Prosopite (development/test), scanning every request — see [Controllers › N+1 detection](/reference/controllers#n-1-detection) |
|
|
35
36
|
| CI | GitLab CI configuration |
|
|
37
|
+
| Deployment | Scalingo files (`trek:install:scalingo`, run on demand) — see [Deploying to Scalingo](/deploy) |
|
|
36
38
|
|
|
37
39
|
## ENV variables read by the installer
|
|
38
40
|
|
|
@@ -18,6 +18,18 @@ Akin to Rails', Trek's scaffold generates, with a single command:
|
|
|
18
18
|
|
|
19
19
|
Standard Rails attribute types are supported (`string`, `text`, `integer`, `float`, `decimal`, `boolean`, `date`, `datetime`, `references`…) and mapped to the matching [form helpers](/reference/forms): `text` renders the content editor, `boolean` a switch box, `references` a collection select, and so on.
|
|
20
20
|
|
|
21
|
+
## Namespaced models
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
rails g trek:scaffold Shop::Product name price:decimal
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The controller and policy are nested accordingly (`Admin::Shop::ProductsController`, `Admin::Shop::ProductPolicy`), the routes go under `namespace :shop`, and everything else follows the namespace.
|
|
28
|
+
|
|
29
|
+
The generated code references the model as `::Shop::Product`, top-level qualified, and the dashboard menu entry is `::Shop::Product` too. This is deliberate: once `Admin::Shop` exists, a bare `Shop` written anywhere under `module Admin` resolves to that namespace instead of the `Shop` model. Existing admin code that references such a model — an `Admin::ShopsController#model` returning `Shop`, or a `Shop` entry in `Admin::Dashboard::MODELS_IN_MENU` — must be updated to `::Shop` once a scaffold introduces a namespace with the same name (the menu would otherwise silently list the module).
|
|
30
|
+
|
|
31
|
+
Also note that `rails g model Shop::Product` (which the scaffold runs) writes `app/models/shop.rb` as a `module Shop` namespace file; if a `Shop` model already lives there, the generator warns and Rails asks before overwriting it — move the model or answer `n`.
|
|
32
|
+
|
|
21
33
|
## Opt-in concerns
|
|
22
34
|
|
|
23
35
|
The generated model ships with commented-out hooks for Trek's most useful [concerns](/reference/), ready to be enabled when needed:
|
|
@@ -25,7 +25,15 @@ module Trek
|
|
|
25
25
|
|
|
26
26
|
# Mailers run outside a request, so in production they need the same host for
|
|
27
27
|
# link helpers. Development and test keep the host from their environment files.
|
|
28
|
-
ActionMailer::Base
|
|
28
|
+
# Deferred with on_load: referencing ActionMailer::Base here would load it
|
|
29
|
+
# before the app's autoloader can resolve app classes, and its load hook
|
|
30
|
+
# constantizes config.action_mailer.delivery_job — NameError at boot when
|
|
31
|
+
# the app configures a custom delivery job.
|
|
32
|
+
if Rails.env.production?
|
|
33
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
34
|
+
self.default_url_options = Rails.application.routes.default_url_options
|
|
35
|
+
end
|
|
36
|
+
end
|
|
29
37
|
RUBY
|
|
30
38
|
end
|
|
31
39
|
end
|
|
@@ -28,6 +28,8 @@ module Trek
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def inject_postmark_to_config
|
|
31
|
+
return if File.read("config/environments/production.rb").include?("delivery_method = :postmark")
|
|
32
|
+
|
|
31
33
|
inject_into_file "config/environments/production.rb",
|
|
32
34
|
config_injection,
|
|
33
35
|
after: %r{# Specify outgoing SMTP server\. Remember to add smtp/\* credentials via (?:bin/)?rails credentials:edit\.\n}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require "rails/generators/actions"
|
|
2
|
+
require "generators/trek/helpers"
|
|
3
|
+
|
|
4
|
+
module Trek
|
|
5
|
+
module Generators
|
|
6
|
+
module Install
|
|
7
|
+
class ProsopiteGenerator < Rails::Generators::Base
|
|
8
|
+
include Trek::Generators::Helpers
|
|
9
|
+
|
|
10
|
+
desc "Install Prosopite (N+1 query detection)."
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("../templates", __dir__)
|
|
13
|
+
|
|
14
|
+
def add_gems
|
|
15
|
+
return if in_gemfile?("prosopite")
|
|
16
|
+
|
|
17
|
+
gem "prosopite", group: [:development, :test],
|
|
18
|
+
comment: "N+1 query detection (raises in test, logs in development) [https://github.com/charkost/prosopite]"
|
|
19
|
+
gem "pg_query", group: [:development, :test]
|
|
20
|
+
bundle_install
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create_initializer
|
|
24
|
+
return if File.exist?("config/initializers/prosopite.rb")
|
|
25
|
+
|
|
26
|
+
create_file "config/initializers/prosopite.rb" do
|
|
27
|
+
<<~RUBY
|
|
28
|
+
# N+1 query detection with Prosopite, in development and test only (the gem is not loaded in production).
|
|
29
|
+
# Every request is scanned by the Rack middleware, which also covers Trek's engine controllers.
|
|
30
|
+
# Detected N+1s raise Prosopite::NPlusOneQueriesError in test and are logged in development.
|
|
31
|
+
# Legitimate repeated queries can be wrapped in `Prosopite.pause { … }`.
|
|
32
|
+
if defined?(Prosopite)
|
|
33
|
+
require "prosopite/middleware/rack"
|
|
34
|
+
|
|
35
|
+
Rails.application.configure do
|
|
36
|
+
config.middleware.use(Prosopite::Middleware::Rack)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Rails.application.config.after_initialize do
|
|
40
|
+
Prosopite.rails_logger = true
|
|
41
|
+
Prosopite.raise = Rails.env.test?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
RUBY
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_smoke_spec
|
|
49
|
+
return if File.exist?("spec/requests/admin/n_plus_one_spec.rb")
|
|
50
|
+
|
|
51
|
+
copy_file "spec/requests/admin/n_plus_one_spec.rb", "spec/requests/admin/n_plus_one_spec.rb"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -5,17 +5,16 @@ module Trek
|
|
|
5
5
|
module Generators
|
|
6
6
|
module Install
|
|
7
7
|
class ScalingoGenerator < Rails::Generators::Base
|
|
8
|
+
BUILDPACK_URL = "https://github.com/etaminstudio/trek-buildpack#v0.1.0".freeze
|
|
9
|
+
|
|
8
10
|
include Trek::Generators::Helpers
|
|
9
11
|
|
|
10
12
|
desc "Create Scalingo files."
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
https://github.com/Scalingo/ruby-buildpack.git
|
|
17
|
-
TEXT
|
|
18
|
-
end
|
|
14
|
+
# The trek-buildpack replaces the apt + ruby buildpack combination. It
|
|
15
|
+
# pins Node from the .node-version that `rails new` already generates.
|
|
16
|
+
def remove_buildpacks
|
|
17
|
+
remove_file ".buildpacks"
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def create_aptfile
|
|
@@ -37,6 +36,19 @@ module Trek
|
|
|
37
36
|
TEXT
|
|
38
37
|
end
|
|
39
38
|
end
|
|
39
|
+
|
|
40
|
+
def final_instructions
|
|
41
|
+
say ""
|
|
42
|
+
say "Configure the Scalingo app environment:"
|
|
43
|
+
say ""
|
|
44
|
+
say " scalingo --app <app> env-set BUILDPACK_URL=#{BUILDPACK_URL}"
|
|
45
|
+
say " scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'"
|
|
46
|
+
say ""
|
|
47
|
+
say "and make sure config/database.yml reads DATABASE_URL in production."
|
|
48
|
+
say ""
|
|
49
|
+
say "See https://trek.etamin.studio/deploy for details."
|
|
50
|
+
say ""
|
|
51
|
+
end
|
|
40
52
|
end
|
|
41
53
|
end
|
|
42
54
|
end
|
|
@@ -25,6 +25,7 @@ module Trek
|
|
|
25
25
|
generate "trek:install:postcss"
|
|
26
26
|
generate "trek:install:prettier"
|
|
27
27
|
generate "trek:install:procfiles"
|
|
28
|
+
generate "trek:install:prosopite"
|
|
28
29
|
generate "trek:install:rubocop"
|
|
29
30
|
generate "trek:install:slim_lint"
|
|
30
31
|
generate "trek:install:stylelint"
|
|
@@ -15,6 +15,18 @@ module Trek
|
|
|
15
15
|
|
|
16
16
|
source_root File.expand_path("templates", __dir__)
|
|
17
17
|
|
|
18
|
+
# `rails g model Shop::Product` writes app/models/shop.rb as a `module Shop`
|
|
19
|
+
# namespace file; if a `Shop` model already lives there, it gets replaced.
|
|
20
|
+
def warn_about_shadowed_models
|
|
21
|
+
model_namespaces.each do |ns|
|
|
22
|
+
path = "app/models/#{ns}.rb"
|
|
23
|
+
next unless File.exist?(path) && File.read(path).match?(/^class #{ns.camelize}\b/)
|
|
24
|
+
|
|
25
|
+
say_status :warning, "#{path} defines the #{ns.camelize} model, which the #{ns.camelize} namespace " \
|
|
26
|
+
"would replace — answer 'n' below to keep it, or move the model first", :red
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
18
30
|
def create_model
|
|
19
31
|
generate :model, class_name, attributes.map { |a| "#{a.name}:#{a.type}" }
|
|
20
32
|
end
|
|
@@ -32,7 +44,8 @@ module Trek
|
|
|
32
44
|
|
|
33
45
|
def create_controller
|
|
34
46
|
template "controllers/admin/scaffold_controller.rb",
|
|
35
|
-
|
|
47
|
+
controller_file_path
|
|
48
|
+
reindent controller_file_path
|
|
36
49
|
end
|
|
37
50
|
|
|
38
51
|
def create_views
|
|
@@ -42,9 +55,11 @@ module Trek
|
|
|
42
55
|
end
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# Top-level qualified: once an Admin::Shop namespace exists (scaffolded
|
|
59
|
+
# controller/policy), a bare `Shop` under `module Admin` resolves to it.
|
|
45
60
|
def add_models_to_menu
|
|
46
61
|
inject_into_file dashboard_file_path,
|
|
47
|
-
"\n
|
|
62
|
+
"\n::#{class_name},",
|
|
48
63
|
after: "MODELS_IN_MENU = ["
|
|
49
64
|
run "rubocop --autocorrect #{dashboard_file_path}", abort_on_failure: true
|
|
50
65
|
end
|
|
@@ -67,7 +82,8 @@ module Trek
|
|
|
67
82
|
|
|
68
83
|
def add_policy
|
|
69
84
|
template "policies/admin/scaffold_policy.rb",
|
|
70
|
-
|
|
85
|
+
policy_file_path
|
|
86
|
+
reindent policy_file_path
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
def add_default_locale_files
|
|
@@ -114,6 +130,22 @@ module Trek
|
|
|
114
130
|
"app/models/admin/dashboard.rb"
|
|
115
131
|
end
|
|
116
132
|
|
|
133
|
+
# The templates wrap the class in one `module` per namespace segment
|
|
134
|
+
# without re-indenting the body; the indentation cops are forced with
|
|
135
|
+
# --only because rubocop-rails-omakase leaves them disabled.
|
|
136
|
+
def reindent(path)
|
|
137
|
+
run "rubocop --autocorrect --only Layout/IndentationWidth,Layout/IndentationConsistency #{path}",
|
|
138
|
+
abort_on_failure: true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def controller_file_path
|
|
142
|
+
"app/controllers/admin/#{namespaced_plural}_controller.rb"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def policy_file_path
|
|
146
|
+
"app/policies/admin/#{file_path}_policy.rb"
|
|
147
|
+
end
|
|
148
|
+
|
|
117
149
|
def model_injection
|
|
118
150
|
<<~RUBY
|
|
119
151
|
# TODO: uncomment to format some attributes, or remove if not needed
|
|
@@ -160,12 +192,12 @@ module Trek
|
|
|
160
192
|
"config/locales/admin/#{file_path}.#{locale}.yml"
|
|
161
193
|
end
|
|
162
194
|
|
|
163
|
-
# Returns namespace segments from file_path (e.g., ["
|
|
195
|
+
# Returns namespace segments from file_path (e.g., ["shop"] for "shop/product")
|
|
164
196
|
def model_namespaces
|
|
165
197
|
file_path.split("/")[0...-1]
|
|
166
198
|
end
|
|
167
199
|
|
|
168
|
-
# Returns the pluralized path preserving namespaces (e.g., "
|
|
200
|
+
# Returns the pluralized path preserving namespaces (e.g., "shop/products" for "shop/product")
|
|
169
201
|
def namespaced_plural
|
|
170
202
|
parts = file_path.split("/")
|
|
171
203
|
parts[-1] = parts[-1].pluralize
|
|
@@ -2,13 +2,13 @@ module Admin
|
|
|
2
2
|
<% model_namespaces.each do |ns| -%>
|
|
3
3
|
module <%= ns.camelize %>
|
|
4
4
|
<% end -%>
|
|
5
|
-
class <%= controller_class_name %>Controller < Trek::ResourceController
|
|
5
|
+
class <%= controller_class_name.demodulize %>Controller < Trek::ResourceController
|
|
6
6
|
# include Trek::Pagination
|
|
7
7
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def model
|
|
11
|
-
|
|
11
|
+
::<%= class_name %>
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
<% model_namespaces.each do |_ns| -%>
|
|
@@ -2,10 +2,11 @@ en:
|
|
|
2
2
|
activerecord:
|
|
3
3
|
models:
|
|
4
4
|
<%= file_path %>:
|
|
5
|
-
gender:
|
|
5
|
+
gender: "n"
|
|
6
6
|
one: <%= human_name %>
|
|
7
7
|
other: <%= plural_name.humanize %>
|
|
8
8
|
attributes:
|
|
9
|
-
<%= file_path
|
|
9
|
+
<%= file_path %>:
|
|
10
|
+
<%- attributes.each do |attr| -%>
|
|
10
11
|
<%= attr.name %>: <%= attr.name.humanize -%>
|
|
11
|
-
|
|
12
|
+
<%- end -%>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
# Smoke test for Prosopite (config/initializers/prosopite.rb): each admin page below is rendered
|
|
4
|
+
# as an admin, and any N+1 query raises Prosopite::NPlusOneQueriesError.
|
|
5
|
+
#
|
|
6
|
+
# TODO: add the other admin routes (index and edit pages of scaffolded models in particular), and
|
|
7
|
+
# create a few records first so the per-row queries have something to loop over — an empty list
|
|
8
|
+
# can't reveal an N+1.
|
|
9
|
+
RSpec.describe "Admin pages N+1 queries", type: :request do
|
|
10
|
+
let(:password) { "secret-password-123" }
|
|
11
|
+
let(:admin) { User.create!(email: "admin@example.com", password:, role: "admin") }
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
post admin_user_session_path, params: { email: admin.email, password: }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
"dashboard" => -> { admin_root_path },
|
|
19
|
+
"pages index" => -> { admin_pages_path },
|
|
20
|
+
"fragments index" => -> { admin_fragments_path }
|
|
21
|
+
}.each do |page, path|
|
|
22
|
+
it "renders the #{page} without N+1 queries" do
|
|
23
|
+
get instance_exec(&path)
|
|
24
|
+
|
|
25
|
+
expect(response).to have_http_status(:ok)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
= render Trek::TabsComponent.new do |c|
|
|
4
4
|
- c.with_item(id: :content, label: t("admin.content"))
|
|
5
5
|
= render Trek::PropertiesComponent.new(model:) do |p|
|
|
6
|
-
<% attributes.
|
|
6
|
+
<% attributes.each_with_index do |attribute, i| -%>
|
|
7
|
+
<%= "\n" unless i.zero? -%>
|
|
7
8
|
= p.with_item :<%= attribute.column_name %>
|
|
8
9
|
= @object.<%= attribute.column_name %>
|
|
9
|
-
|
|
10
10
|
<% end -%>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "rails/generators/actions"
|
|
2
2
|
require "generators/trek/helpers"
|
|
3
|
+
require "generators/trek/install/yarn_generator"
|
|
3
4
|
require "yaml"
|
|
4
5
|
|
|
5
6
|
module Trek
|
|
@@ -66,10 +67,28 @@ module Trek
|
|
|
66
67
|
"and the etaminstudio scope manually.", :yellow
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
def upgrade_yarn
|
|
71
|
+
return if yarn_major_version >= 4
|
|
72
|
+
|
|
73
|
+
# The npmPreapprovedPackages setting below requires Yarn 4 (Yarn 3
|
|
74
|
+
# hard-errors on it), so apply the 0.1.26 upgrade note as part of the
|
|
75
|
+
# migration: bump Yarn and drop the interactive-tools plugin, whose
|
|
76
|
+
# upgrade-interactive command is built into Yarn 4.
|
|
77
|
+
if File.exist?(".yarnrc.yml") && File.read(".yarnrc.yml").include?("plugin-interactive-tools")
|
|
78
|
+
run "yarn plugin remove @yarnpkg/plugin-interactive-tools"
|
|
79
|
+
end
|
|
80
|
+
run "yarn set version #{Install::YarnGenerator::YARN_VERSION}", abort_on_failure: true
|
|
81
|
+
@yarn_version = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
69
84
|
def preapprove_npm_package
|
|
70
85
|
return unless File.exist?(".yarnrc.yml")
|
|
71
86
|
return if File.read(".yarnrc.yml").include?("npmPreapprovedPackages")
|
|
72
87
|
|
|
88
|
+
# Defensive: never write the (Yarn 4-only) setting under Yarn 3,
|
|
89
|
+
# where it is a hard error that breaks every yarn command.
|
|
90
|
+
return if yarn_major_version < 4
|
|
91
|
+
|
|
73
92
|
append_to_file ".yarnrc.yml", <<~YAML
|
|
74
93
|
|
|
75
94
|
# Trek is our own package — exempt it from Yarn's package gates
|
|
@@ -98,6 +117,18 @@ module Trek
|
|
|
98
117
|
* Revoke this project's Trek deploy token on GitLab.
|
|
99
118
|
SUMMARY
|
|
100
119
|
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def yarn_version
|
|
124
|
+
@yarn_version ||= `yarn --version`.strip
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
@yarn_version = "0"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def yarn_major_version
|
|
130
|
+
yarn_version.to_i
|
|
131
|
+
end
|
|
101
132
|
end
|
|
102
133
|
end
|
|
103
134
|
end
|
data/lib/trek/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trek
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mohamed Bengrich
|
|
@@ -434,6 +434,7 @@ files:
|
|
|
434
434
|
- ".eslintrc.json"
|
|
435
435
|
- ".node-version"
|
|
436
436
|
- ".npmignore"
|
|
437
|
+
- ".prettierignore"
|
|
437
438
|
- ".rspec"
|
|
438
439
|
- ".rubocop.yml"
|
|
439
440
|
- ".slim-lint.yml"
|
|
@@ -737,6 +738,7 @@ files:
|
|
|
737
738
|
- docs/.vitepress/theme/index.js
|
|
738
739
|
- docs/.vitepress/theme/style.css
|
|
739
740
|
- docs/customize.md
|
|
741
|
+
- docs/deploy.md
|
|
740
742
|
- docs/getting-started.md
|
|
741
743
|
- docs/index.md
|
|
742
744
|
- docs/public/logo.svg
|
|
@@ -821,6 +823,7 @@ files:
|
|
|
821
823
|
- lib/generators/trek/install/postmark_generator.rb
|
|
822
824
|
- lib/generators/trek/install/prettier_generator.rb
|
|
823
825
|
- lib/generators/trek/install/procfiles_generator.rb
|
|
826
|
+
- lib/generators/trek/install/prosopite_generator.rb
|
|
824
827
|
- lib/generators/trek/install/readme_generator.rb
|
|
825
828
|
- lib/generators/trek/install/rubocop_generator.rb
|
|
826
829
|
- lib/generators/trek/install/ruby_generator.rb
|
|
@@ -902,6 +905,7 @@ files:
|
|
|
902
905
|
- lib/generators/trek/templates/policies/page.rb
|
|
903
906
|
- lib/generators/trek/templates/policies/preview.rb
|
|
904
907
|
- lib/generators/trek/templates/policies/user.rb
|
|
908
|
+
- lib/generators/trek/templates/spec/requests/admin/n_plus_one_spec.rb
|
|
905
909
|
- lib/generators/trek/templates/views/admin/dashboards/index.html.slim
|
|
906
910
|
- lib/generators/trek/templates/views/admin/fragments/_form.html.slim
|
|
907
911
|
- lib/generators/trek/templates/views/admin/fragments/_fragment.json.jb
|