avo 4.1.0 → 4.1.2
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/Gemfile.lock +1 -1
- data/app/assets/builds/avo/application.css +30 -0
- data/lib/avo/base_action.rb +1 -1
- data/lib/avo/reloader.rb +9 -0
- data/lib/avo/skills/avo-actions/SKILL.md +255 -0
- data/lib/avo/skills/avo-admin-config/SKILL.md +163 -0
- data/lib/avo/skills/avo-associations/SKILL.md +168 -0
- data/lib/avo/skills/avo-authentication/SKILL.md +193 -0
- data/lib/avo/skills/avo-aware/SKILL.md +74 -0
- data/lib/avo/skills/avo-branding-appearance/SKILL.md +270 -0
- data/lib/avo/skills/avo-controllers/SKILL.md +236 -0
- data/lib/avo/skills/avo-custom-fields/SKILL.md +197 -0
- data/lib/avo/skills/avo-custom-ui/SKILL.md +460 -0
- data/lib/avo/skills/avo-engine-internals/SKILL.md +245 -0
- data/lib/avo/skills/avo-fields/SKILL.md +219 -0
- data/lib/avo/skills/avo-filters/SKILL.md +196 -0
- data/lib/avo/skills/avo-i18n/SKILL.md +254 -0
- data/lib/avo/skills/avo-index-views/SKILL.md +254 -0
- data/lib/avo/skills/avo-media-library/SKILL.md +122 -0
- data/lib/avo/skills/avo-menu-icons/SKILL.md +135 -0
- data/lib/avo/skills/avo-menu-icons/scripts/list_icons.rb +49 -0
- data/lib/avo/skills/avo-multitenancy/SKILL.md +186 -0
- data/lib/avo/skills/avo-navigation-search/SKILL.md +255 -0
- data/lib/avo/skills/avo-performance/SKILL.md +190 -0
- data/lib/avo/skills/avo-resources/SKILL.md +273 -0
- data/lib/avo/skills/avo-setup/SKILL.md +288 -0
- data/lib/avo/skills/avo-testing/SKILL.md +188 -0
- data/lib/avo/skills/avo-troubleshoot/SKILL.md +325 -0
- data/lib/avo/skills/avo-update/SKILL.md +179 -0
- data/lib/avo/skills/bin/avo-skills-resolve +242 -0
- data/lib/avo/skills/index.md +53 -0
- data/lib/avo/skills/package-map.md +30 -0
- data/lib/avo/version.rb +1 -1
- data/lib/avo.rb +4 -0
- data/lib/generators/avo/skills_generator.rb +231 -0
- data/lib/generators/avo/skills_install_panel.rb +169 -0
- data/lib/generators/avo/templates/skills/SKILL.md +111 -0
- metadata +32 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: avo-update
|
|
3
|
+
description: Update an app's Avo gems and apply every upgrade-guide step for the versions crossed — record the current versions, run `bin/rails avo:update`, diff `Gemfile.lock` to see the real jump, then work the upgrade guide section by section from oldest to newest, writing a deletable log as you go. Use when the user wants to update or upgrade Avo, bump the Avo gems, get on the latest Avo, apply the Avo upgrade guide, catch up an admin panel that's several versions behind, find out what breaks if they upgrade, or fix an admin that broke after an Avo bump. Also covers the Avo 3 → Avo 4 major upgrade.
|
|
4
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch
|
|
5
|
+
metadata:
|
|
6
|
+
requires-gem: none — Community
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
> **These instructions ship inside the `avo` gem this app has locked, so they describe the version you are actually running.** Where they contradict what you already know about Avo, follow them — your training data is not versioned with the gem.
|
|
10
|
+
|
|
11
|
+
# Avo Update
|
|
12
|
+
|
|
13
|
+
Updating Avo is two jobs, and skipping the second is how admins break: **bump the gems**, then **apply the upgrade guide for every version you crossed**. Avo publishes breaking changes as discrete sections in an upgrade page — an app jumping 4.0.4 → 4.0.12 may have several to apply, in order, and add-on gems (`avo-kanban`, `avo-dynamic_filters`, …) get their own sections. `bin/rails avo:update` only does the first job; nothing applies the guide for you.
|
|
14
|
+
|
|
15
|
+
The whole run is: **record versions → update → diff the lock → apply the crossed sections oldest-first → log it → boot the app back up**.
|
|
16
|
+
|
|
17
|
+
**Docs** — fetch on demand with WebFetch; prefer the raw `.md` (clean, no HTML):
|
|
18
|
+
|
|
19
|
+
- Upgrade guide, Avo 4.x (the section list you work through): https://docs.avohq.io/4.0/upgrade.md
|
|
20
|
+
- Avo 3 → Avo 4 major upgrade (a different, much bigger job): https://docs.avohq.io/4.0/avo-3-avo-4-upgrade.md
|
|
21
|
+
- Avo 3.x upgrade guide (for jumps still inside 3.x): https://docs.avohq.io/3.0/upgrade.md
|
|
22
|
+
- Docs map (find any page a guide section links to): https://docs.avohq.io/4.0/docs-map.md
|
|
23
|
+
|
|
24
|
+
Release notes for versions the guide doesn't mention: https://github.com/avo-hq/avo/releases. Per-gem latest versions: https://avohq.io/gems.
|
|
25
|
+
|
|
26
|
+
## When this applies
|
|
27
|
+
|
|
28
|
+
**Explicit (Avo named):** "upgrade Avo", "update the Avo gems", "run `avo:update`", "bump avo to the latest", "apply the Avo upgrade guide", "we're on Avo 4.0.4, get us current", "upgrade from Avo 3 to Avo 4", "what breaks if I upgrade Avo?", "the admin broke after I bumped Avo".
|
|
29
|
+
|
|
30
|
+
**Implicit (no mention of Avo):** "update the admin panel gems", "we're several versions behind on the admin", "get the backoffice onto the latest release", "`bundle update avo` broke the admin", "the admin panel stopped rendering after last week's bundle update".
|
|
31
|
+
|
|
32
|
+
**Not this skill:** first-time install, gem-server token, mounting, license key → **avo-setup**. A broken admin with no recent version change → **avo-troubleshoot**. Locale casing after an upgrade → apply the guide section here, then **avo-i18n** for the wider i18n picture.
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
### 1. Get a safe starting point
|
|
37
|
+
|
|
38
|
+
Non-negotiable before touching the lockfile — the whole method depends on `git diff` telling the truth:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git status --porcelain # must be empty; if not, stop and ask
|
|
42
|
+
git checkout -b avo-update
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run the test suite now to capture a baseline. **If it's already red, stop and tell the user** — otherwise you can't tell your changes from theirs.
|
|
46
|
+
|
|
47
|
+
### 2. Record the current versions
|
|
48
|
+
|
|
49
|
+
This is the "before" half of the jump. Read every Avo gem, not just core — add-ons have their own upgrade sections and their own version lines:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
grep -E "^ {4}avo(-[a-z_]+)? \(" Gemfile.lock
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Write these down in the log file (step 6) **before** running the update — after the bundle they're gone unless you kept them.
|
|
56
|
+
|
|
57
|
+
Also note the Gemfile constraints, which cap what the update can do:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
grep -n "avo" Gemfile
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Run the update
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bin/rails avo:update # or: bundle exec rails avo:update
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The task resolves Avo core plus every registered Avo plugin to its real gem name and runs `bundle update --conservative <those gems>` — `--conservative` so it doesn't drag unrelated shared dependencies along. Read its output: it echoes the exact `bundle update` command it ran.
|
|
70
|
+
|
|
71
|
+
If the task isn't available (older Avo, or the app won't boot), fall back to naming the gems yourself:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle update --conservative avo avo-dashboards avo-menu # …every avo* gem in the lock
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 4. Diff the lock to find the real jump
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git diff Gemfile.lock | grep -E "^[-+] {4}avo"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Now you have `before → after` per gem. **This is the input to everything downstream** — a `4.0.4 → 4.0.12` jump means every guide section between those two applies, and the same is true separately for each add-on.
|
|
84
|
+
|
|
85
|
+
If a gem didn't move as far as expected, the Gemfile constraint from step 2 is capping it (see Gotchas). Fix the constraint and re-run rather than assuming there was nothing to update.
|
|
86
|
+
|
|
87
|
+
### 5. Apply the crossed guide sections, oldest first
|
|
88
|
+
|
|
89
|
+
Fetch the upgrade guide for the major version you're on (4.x → `upgrade.md`; a jump still inside 3.x → the 3.0 page; **3.x → 4.x → stop and use the Avo 3 → Avo 4 guide instead**, that's a much bigger, chapter-by-chapter job with its own procedure).
|
|
90
|
+
|
|
91
|
+
Guide sections are headed by version (`## Upgrade to 3.22.0`, `## Upgrade from 3.16.2 to 3.16.3`) or by the change itself when a version only shipped one. Some are per-add-on (`## Upgrade to avo-kanban 0.1.18`). A version section may hold **several named changes**, each with its own "Action required" note — work them all, not just the first. A `## Unreleased — …` section at the very top describes changes that have landed on `main` but aren't in a released gem yet; skip it unless the app tracks Avo from git. Newest is at the top — **read up, then apply down.**
|
|
92
|
+
|
|
93
|
+
For each section between your before and after versions:
|
|
94
|
+
|
|
95
|
+
1. **Inventory first.** Grep for the API the section touches *before* changing anything. Most sections won't apply to a given app.
|
|
96
|
+
2. Mark it **APPLIES / NOT USED / NEEDS REVIEW** in the log. Never apply a change for an API the app doesn't use.
|
|
97
|
+
3. If it applies, make the edit, then boot the app and re-run the tests.
|
|
98
|
+
4. Commit per section, with the version in the message.
|
|
99
|
+
|
|
100
|
+
Sections often link a deeper page (i18n, appearance, actions) — fetch and follow it rather than guessing the new API.
|
|
101
|
+
|
|
102
|
+
Watch for **silent behavior changes**: a section that changes a *default* rather than an API name passes every test and still changes what users see. Flag each one to the user explicitly instead of just ticking it off.
|
|
103
|
+
|
|
104
|
+
If a version in your range has no guide section, check the GitHub releases for it — the guide only documents changes that need action, so silence is usually (but not always) fine.
|
|
105
|
+
|
|
106
|
+
### 6. Keep a log as you go
|
|
107
|
+
|
|
108
|
+
Write it incrementally, not at the end — if the run stops halfway, the log is the handoff. Put it at the repo root as `avo-update-<from>-to-<to>.md`:
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+
# Avo update log — 4.0.4 → 4.0.12
|
|
112
|
+
|
|
113
|
+
Scratch notes from an assisted Avo update on 2026-07-24. Not part of the app —
|
|
114
|
+
**safe to delete once the upgrade is reviewed and merged.**
|
|
115
|
+
|
|
116
|
+
## Versions
|
|
117
|
+
|
|
118
|
+
| Gem | Before | After |
|
|
119
|
+
| ------------ | ------- | ------- |
|
|
120
|
+
| avo | 4.0.4 | 4.0.12 |
|
|
121
|
+
| avo-kanban | 0.1.17 | 0.1.18 |
|
|
122
|
+
|
|
123
|
+
## Guide sections
|
|
124
|
+
|
|
125
|
+
| Section | Status | What changed |
|
|
126
|
+
| ------------------------------------------ | ----------- | ------------ |
|
|
127
|
+
| Resource and field translations verbatim | APPLIED | Capitalized 14 entries in `config/locales/avo.pt-BR.yml` |
|
|
128
|
+
| avo-kanban 0.1.18 | NOT USED | App has no kanban boards |
|
|
129
|
+
| … | NEEDS REVIEW | … |
|
|
130
|
+
|
|
131
|
+
## Manual verification needed
|
|
132
|
+
|
|
133
|
+
- Things the tests can't catch — icons, avatars/covers, custom CSS on renamed
|
|
134
|
+
variables, anything visual.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The "safe to delete" line goes in the log itself — the user will find this file later and needs to know it isn't app code.
|
|
138
|
+
|
|
139
|
+
### 7. Start the app back up
|
|
140
|
+
|
|
141
|
+
Check the project's own instructions for how — in this order: `AGENTS.md`, `CLAUDE.md`, `.claude/CLAUDE.md`, `README.md`, `Procfile.dev` / `bin/dev`. If the project documents a dev-server command, run it (restarting it if you stopped it in step 1) and confirm the admin boots.
|
|
142
|
+
|
|
143
|
+
**If no command is documented, don't guess** — say so and ask. A wrong `rails s` in a Procfile/overmind project just fights the real process.
|
|
144
|
+
|
|
145
|
+
## Key commands
|
|
146
|
+
|
|
147
|
+
| Command | Does |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `grep -E "^ {4}avo(-[a-z_]+)? \(" Gemfile.lock` | Current version of every Avo gem — run **before** updating |
|
|
150
|
+
| `bin/rails avo:update` | `bundle update --conservative` across Avo core + every registered plugin |
|
|
151
|
+
| `git diff Gemfile.lock \| grep -E "^[-+] {4}avo"` | The actual before → after jump per gem |
|
|
152
|
+
| `bundle update --conservative avo …` | Manual fallback when the rake task isn't available |
|
|
153
|
+
| `bundle outdated \| grep avo` | What's available above the current Gemfile constraints |
|
|
154
|
+
| `rake avo:build-assets` | Recompile assets — only for GitHub-sourced installs (**avo-setup**) |
|
|
155
|
+
|
|
156
|
+
## Gotchas
|
|
157
|
+
|
|
158
|
+
- **`avo:update` is bounded by the Gemfile.** It runs `bundle update`, so a pin like `gem "avo", "~> 4.0.4"` or `"= 4.0.4"` caps the jump — the task reports success and nothing moves. Check the constraint in step 2, and loosen it deliberately (with the user) rather than silently.
|
|
159
|
+
- **The rake task doesn't fail on a failed bundle.** It shells out with `system` and ignores the exit status, so a `403 Forbidden` on `packager.dev` or a resolution conflict prints an error while the task still exits 0. **Read the output and confirm with the lock diff** — never trust the exit code alone.
|
|
160
|
+
- **It only updates plugins that are actually loaded.** The gem list comes from the registered plugin manager, so an Avo gem in an optional/skipped bundler group won't be updated. Cross-check the lock diff against the full gem list from step 2.
|
|
161
|
+
- **Paid gems need the gem-server token to update, same as to install.** In a sandboxed or restricted-egress environment a correct token still 403s because `packager.dev` is blocked. See **avo-setup**.
|
|
162
|
+
- **The update alone is not the upgrade.** Bumping the gems and skipping the guide is the single most common way an admin breaks after an "upgrade" — and the breakage often surfaces days later, in a view nobody opened.
|
|
163
|
+
- **Apply sections oldest → newest.** They're written as a chain; applying 4.0.12's change before 4.0.7's can leave you editing code the earlier section was about to rename.
|
|
164
|
+
- **Silent default flips pass tests.** Sections that change a default (authorization strictness, confirmation modals, expanded filters) break nothing visible in CI and change runtime behavior. Call these out individually.
|
|
165
|
+
- **Add-on gems version independently.** `avo-kanban 0.1.17 → 0.1.18` has its own section even when Avo core barely moved. Work the lock diff, not just the core version.
|
|
166
|
+
- **Assets after upgrade.** A GitHub-sourced install ships no precompiled assets — re-run `rake avo:build-assets` or the admin renders unstyled (**avo-setup**).
|
|
167
|
+
- **Don't invent a migration.** If a version's change isn't in the guide or the release notes, stop and ask rather than guessing at the new API.
|
|
168
|
+
|
|
169
|
+
## Report
|
|
170
|
+
|
|
171
|
+
When done, tell the user:
|
|
172
|
+
|
|
173
|
+
- The **jump per gem** (`avo 4.0.4 → 4.0.12`, plus each add-on), taken from the lock diff.
|
|
174
|
+
- **Which guide sections applied**, which were skipped as not-used, and which need their judgement — and the files you edited for each.
|
|
175
|
+
- Any **silent behavior change** you crossed, named explicitly, since tests won't surface it.
|
|
176
|
+
- **Test status**: baseline vs. now, and whether the app boots.
|
|
177
|
+
- **Where the log is** and that it's safe to delete after review.
|
|
178
|
+
- Whether the app was **restarted** and how — or that no start command was documented, so you didn't guess.
|
|
179
|
+
- Follow-ups: anything needing manual visual checks (icons, avatars/covers, custom CSS), plus **avo-troubleshoot** if something's still broken and **avo-setup** if a paid gem wouldn't bundle.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Avo skills loader — resolves the skills that ship inside this app's locked Avo
|
|
4
|
+
# gems, proves each resolved gem is the version the lock names, and prints the
|
|
5
|
+
# combined index.
|
|
6
|
+
#
|
|
7
|
+
# Gemfile.lock is the inventory and the version oracle. `bundle show` is only one
|
|
8
|
+
# path resolver among several: it exits non-zero on an unrelated Ruby-version
|
|
9
|
+
# mismatch, and `gem which` happily returns a stale gem from another version, so
|
|
10
|
+
# neither is trusted without the version assertion below.
|
|
11
|
+
#
|
|
12
|
+
# Exit 0 prints verified absolute paths and index content on stdout. Any failure
|
|
13
|
+
# exits non-zero with a stable token on stderr. There is no soft-failure mode:
|
|
14
|
+
# the caller must stop rather than fall back on prior knowledge of Avo.
|
|
15
|
+
#
|
|
16
|
+
# Usage: avo-skills-resolve
|
|
17
|
+
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
fail() { printf 'avo-skills: error %s: %s\n' "$1" "$2" >&2; exit 1; }
|
|
22
|
+
warn() { printf 'avo-skills: warning %s: %s\n' "$1" "$2" >&2; }
|
|
23
|
+
|
|
24
|
+
# --- 1. Locate Gemfile.lock -------------------------------------------------
|
|
25
|
+
|
|
26
|
+
find_lock() {
|
|
27
|
+
local dir="$PWD"
|
|
28
|
+
while [ "$dir" != "/" ]; do
|
|
29
|
+
[ -f "$dir/Gemfile.lock" ] && { printf '%s\n' "$dir/Gemfile.lock"; return 0; }
|
|
30
|
+
dir="$(dirname "$dir")"
|
|
31
|
+
done
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
LOCK="$(find_lock)" || fail not_an_app \
|
|
36
|
+
"no Gemfile.lock found in $PWD or any parent directory. This does not look like a Ruby app; do not guess an Avo version."
|
|
37
|
+
APP_ROOT="$(dirname "$LOCK")"
|
|
38
|
+
|
|
39
|
+
# --- 2. Parse the lock ------------------------------------------------------
|
|
40
|
+
#
|
|
41
|
+
# Top-level specs are indented exactly four spaces; their dependencies get six,
|
|
42
|
+
# so the anchored pattern below never mistakes a dependency for an installed gem.
|
|
43
|
+
# The section (GEM/PATH/GIT) is carried through because a path- or git-sourced
|
|
44
|
+
# gem legitimately has no version to assert against.
|
|
45
|
+
|
|
46
|
+
LOCK_ENTRIES="$(awk '
|
|
47
|
+
/^(GEM|PATH|GIT)$/ { section = $1; next }
|
|
48
|
+
/^[A-Z]/ { section = "" ; next }
|
|
49
|
+
section == "" { next }
|
|
50
|
+
/^ [^ ]+ \([^)]+\)$/ {
|
|
51
|
+
name = $1
|
|
52
|
+
gsub(/[()]/, "", $2)
|
|
53
|
+
print name "|" $2 "|" section
|
|
54
|
+
}
|
|
55
|
+
' "$LOCK")"
|
|
56
|
+
|
|
57
|
+
avo_line="$(printf '%s\n' "$LOCK_ENTRIES" | grep -m1 '^avo|' || true)"
|
|
58
|
+
[ -n "$avo_line" ] && [ -z "${avo_line##avo|*}" ] || fail avo_not_locked \
|
|
59
|
+
"the avo gem is not in $LOCK. This app does not use Avo, or its bundle is not installed."
|
|
60
|
+
|
|
61
|
+
# --- 3. Validate every parsed name and version (never trust the lock) -------
|
|
62
|
+
|
|
63
|
+
valid_name() { [[ "$1" =~ ^[a-z0-9_]+(-[a-z0-9_]+)*$ ]]; }
|
|
64
|
+
valid_version() { [[ "$1" =~ ^[0-9]+(\.[0-9a-zA-Z]+)*$ ]]; }
|
|
65
|
+
|
|
66
|
+
AVO_GEMS=""
|
|
67
|
+
while IFS='|' read -r name version section; do
|
|
68
|
+
[ -z "$name" ] && continue
|
|
69
|
+
case "$name" in avo|avo-*) ;; *) continue ;; esac
|
|
70
|
+
valid_name "$name" || fail malformed_lock \
|
|
71
|
+
"gem name '$name' in $LOCK is not a valid gem name. Refusing to build a path from it."
|
|
72
|
+
valid_version "$version" || fail malformed_lock \
|
|
73
|
+
"version '$version' for gem '$name' in $LOCK is not a valid version string."
|
|
74
|
+
AVO_GEMS+="$name|$version|$section"$'\n'
|
|
75
|
+
done <<< "$LOCK_ENTRIES"
|
|
76
|
+
|
|
77
|
+
lock_version_of() { printf '%s\n' "$AVO_GEMS" | awk -F'|' -v n="$1" '$1 == n { print $2; exit }'; }
|
|
78
|
+
lock_section_of() { printf '%s\n' "$AVO_GEMS" | awk -F'|' -v n="$1" '$1 == n { print $3; exit }'; }
|
|
79
|
+
|
|
80
|
+
# --- 4. Resolve a gem's path ------------------------------------------------
|
|
81
|
+
#
|
|
82
|
+
# Bundler subprocess stderr is discarded rather than inherited: bundler prints
|
|
83
|
+
# configuration on some error paths, and this app's bundle may carry a private
|
|
84
|
+
# gem-server credential that must never reach an agent transcript.
|
|
85
|
+
|
|
86
|
+
resolve_path() {
|
|
87
|
+
local gem="$1" version="$2" candidate
|
|
88
|
+
|
|
89
|
+
# BUNDLE_GEMFILE wins over the working directory, so an agent invoked from
|
|
90
|
+
# inside any `bundle exec` context would otherwise resolve that outer bundle's
|
|
91
|
+
# Avo instead of this app's. Clear bundler's ambient state and resolve from the
|
|
92
|
+
# app root. (The version assertion below would catch the mismatch, but failing
|
|
93
|
+
# loudly on a question we can simply answer correctly is worse.)
|
|
94
|
+
candidate="$(cd "$APP_ROOT" && env -u BUNDLE_GEMFILE -u BUNDLE_BIN_PATH -u RUBYOPT bundle show "$gem" 2>/dev/null)"
|
|
95
|
+
if [ -n "$candidate" ] && [ -d "$candidate" ]; then
|
|
96
|
+
printf '%s\n' "$candidate"; return 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# `gempath` (plural) rather than `gemdir`: a bundle installed under a
|
|
100
|
+
# different Ruby or a custom GEM_HOME still shows up here.
|
|
101
|
+
# Same reason as above: bundler's RUBYOPT shim makes `gem env` report the
|
|
102
|
+
# active bundle's paths rather than the machine's real gem homes.
|
|
103
|
+
local gempaths
|
|
104
|
+
gempaths="$(env -u BUNDLE_GEMFILE -u BUNDLE_BIN_PATH -u RUBYOPT gem env gempath 2>/dev/null)"
|
|
105
|
+
if [ -n "$gempaths" ]; then
|
|
106
|
+
local IFS=:
|
|
107
|
+
for dir in $gempaths; do
|
|
108
|
+
[ -d "$dir/gems/$gem-$version" ] && { printf '%s\n' "$dir/gems/$gem-$version"; return 0; }
|
|
109
|
+
done
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
candidate="$(find "$APP_ROOT/vendor/bundle" -maxdepth 4 -type d -name "$gem-$version" 2>/dev/null | head -1)"
|
|
113
|
+
if [ -n "$candidate" ] && [ -d "$candidate" ]; then
|
|
114
|
+
printf '%s\n' "$candidate"; return 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
return 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Core keeps VERSION at lib/avo/version.rb; a feature gem keeps it at
|
|
121
|
+
# lib/avo/<gem name minus the avo- prefix>/version.rb.
|
|
122
|
+
version_file_for() {
|
|
123
|
+
local gem="$1" path="$2"
|
|
124
|
+
if [ "$gem" = "avo" ]; then
|
|
125
|
+
printf '%s\n' "$path/lib/avo/version.rb"
|
|
126
|
+
else
|
|
127
|
+
printf '%s\n' "$path/lib/avo/${gem#avo-}/version.rb"
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Both quote styles are in use across the gem family — avo-audit_logging
|
|
132
|
+
# declares VERSION with single quotes — so matching only double quotes would
|
|
133
|
+
# fail the version assertion on a gem that is perfectly fine.
|
|
134
|
+
installed_version_of() {
|
|
135
|
+
local file="$1"
|
|
136
|
+
[ -f "$file" ] || return 1
|
|
137
|
+
sed -n "s/.*VERSION *= *['\"]\([^'\"]*\)['\"].*/\1/p" "$file" | head -1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Resolves, asserts, and echoes the gem's path. A path- or git-sourced gem is a
|
|
141
|
+
# working tree, so its version legitimately differs from the lock — report the
|
|
142
|
+
# source instead of failing.
|
|
143
|
+
resolve_and_verify() {
|
|
144
|
+
local gem="$1" strict="$2" path locked installed section
|
|
145
|
+
locked="$(lock_version_of "$gem")"
|
|
146
|
+
section="$(lock_section_of "$gem")"
|
|
147
|
+
|
|
148
|
+
path="$(resolve_path "$gem" "$locked")" || {
|
|
149
|
+
[ "$strict" = "strict" ] && fail gem_not_on_disk \
|
|
150
|
+
"$gem $locked is in $LOCK but no matching gem is installed on disk. Run 'bundle install'. If the app pins a Ruby version this shell is not running, switch to it first — the gems live under that Ruby. Do not guess a path."
|
|
151
|
+
return 1
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if [ "$section" != "GEM" ]; then
|
|
155
|
+
printf '%s\n' "$path"; return 0
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
installed="$(installed_version_of "$(version_file_for "$gem" "$path")")" || {
|
|
159
|
+
[ "$strict" = "strict" ] && fail version_mismatch \
|
|
160
|
+
"cannot read a VERSION constant for $gem at $path. Refusing to use an unverified path."
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if [ "$installed" != "$locked" ]; then
|
|
165
|
+
[ "$strict" = "strict" ] && fail version_mismatch \
|
|
166
|
+
"$LOCK says $gem $locked but the gem resolved at $path is $installed. Refusing to load skills that do not match this app's locked version."
|
|
167
|
+
return 1
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
printf '%s\n' "$path"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# --- 5. Resolve avo itself, then read its package map -----------------------
|
|
174
|
+
|
|
175
|
+
# `fail` inside a command substitution exits only that subshell, so the caller
|
|
176
|
+
# must propagate the status — otherwise a hard failure would print its error and
|
|
177
|
+
# then carry on resolving, which is the one thing this script must never do.
|
|
178
|
+
AVO_PATH="$(resolve_and_verify avo strict)" || exit 1
|
|
179
|
+
AVO_VERSION="$(lock_version_of avo)"
|
|
180
|
+
AVO_SECTION="$(lock_section_of avo)"
|
|
181
|
+
SKILLS_ROOT="$AVO_PATH/lib/avo/skills"
|
|
182
|
+
PACKAGE_MAP="$SKILLS_ROOT/package-map.md"
|
|
183
|
+
|
|
184
|
+
# Reachable because the deprecated avo-hq/skills channel hands the pointer to
|
|
185
|
+
# everyone, including apps whose Avo predates gem-shipped skills. No minimum
|
|
186
|
+
# version is named: this file is copied into the app and cannot be updated when
|
|
187
|
+
# a release number turns out differently than planned.
|
|
188
|
+
[ -f "$SKILLS_ROOT/index.md" ] || fail skills_not_shipped \
|
|
189
|
+
"avo $AVO_VERSION does not ship agent skills. Run 'bin/rails avo:update' to get a version that does, then re-run 'rails g avo:skills'. Until then use the documentation at https://docs.avohq.io — do not answer from prior knowledge of Avo."
|
|
190
|
+
|
|
191
|
+
# The map is the trust boundary as well as the miss-path lookup: 'avo-' on
|
|
192
|
+
# rubygems.org is an unreserved namespace, and loaded skill text becomes
|
|
193
|
+
# instructions, so a gem name alone never earns that authority.
|
|
194
|
+
allowlisted() {
|
|
195
|
+
[ -f "$PACKAGE_MAP" ] || return 1
|
|
196
|
+
grep -qE "^\| \`$1\` \|" "$PACKAGE_MAP"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# --- 6. Emit ----------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
printf '# Avo skills for this app\n\n'
|
|
202
|
+
printf 'avo %s' "$AVO_VERSION"
|
|
203
|
+
[ "$AVO_SECTION" != "GEM" ] && printf ' (%s source — working tree, version not asserted)' "$(printf '%s' "$AVO_SECTION" | tr 'A-Z' 'a-z')"
|
|
204
|
+
printf '\n%s\n\n' "$SKILLS_ROOT"
|
|
205
|
+
|
|
206
|
+
cat "$SKILLS_ROOT/index.md"
|
|
207
|
+
printf '\n'
|
|
208
|
+
|
|
209
|
+
printf '\n## Installed packages with their own skills\n\n'
|
|
210
|
+
found_pkg=0
|
|
211
|
+
while IFS='|' read -r name version section; do
|
|
212
|
+
[ -z "$name" ] || [ "$name" = "avo" ] && continue
|
|
213
|
+
allowlisted "$name" || continue
|
|
214
|
+
|
|
215
|
+
pkg_path="$(resolve_and_verify "$name" lenient)" || {
|
|
216
|
+
printf -- '- %s %s — in %s but not resolvable on disk; run bundle install\n' "$name" "$version" "$(basename "$LOCK")"
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
pkg_index="$(find "$pkg_path/lib" -maxdepth 4 -type d -name skills -exec test -f '{}/index.md' \; -print 2>/dev/null | head -1)"
|
|
220
|
+
[ -n "$pkg_index" ] || continue
|
|
221
|
+
|
|
222
|
+
found_pkg=1
|
|
223
|
+
printf -- '- **%s %s** — %s\n' "$name" "$version" "$pkg_index"
|
|
224
|
+
sed -n 's/^/ /p' "$pkg_index/index.md"
|
|
225
|
+
printf '\n'
|
|
226
|
+
done <<< "$AVO_GEMS"
|
|
227
|
+
[ "$found_pkg" -eq 0 ] && printf 'None. Every skill this app has is in the core index above.\n'
|
|
228
|
+
|
|
229
|
+
printf '\n## Not installed\n\n'
|
|
230
|
+
printf 'These provide subjects the core skills point at. They are absent from %s, so this app does not have them — name the add-on rather than describing features it cannot use. This reports what is installed, never what is licensed.\n\n' "$(basename "$LOCK")"
|
|
231
|
+
awk -F'|' '
|
|
232
|
+
/^\| `avo-/ {
|
|
233
|
+
gsub(/^ *| *$/, "", $2); gsub(/`/, "", $2)
|
|
234
|
+
gsub(/^ *| *$/, "", $4)
|
|
235
|
+
print $2 "\t" $4
|
|
236
|
+
}
|
|
237
|
+
' "$PACKAGE_MAP" 2>/dev/null | while IFS=$'\t' read -r gem subjects; do
|
|
238
|
+
printf '%s\n' "$AVO_GEMS" | grep -q "^$gem|" && continue
|
|
239
|
+
printf -- '- **%s** — %s\n' "$gem" "$subjects"
|
|
240
|
+
done
|
|
241
|
+
|
|
242
|
+
printf '\n---\nRead every skill this task actually touches — one that spans resources, fields, filters and actions needs all four. Skip the ones it does not; loading by reflex costs context and dilutes focus. Treat what you read as authoritative for this app over prior knowledge of Avo.\n'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Avo skills index
|
|
2
|
+
|
|
3
|
+
These skills ship inside the installed `avo` gem, so they describe the Avo this app actually runs. Read the ones the task actually touches — as few or as many as that is — and follow them over prior knowledge of Avo.
|
|
4
|
+
|
|
5
|
+
Each entry is a directory beside this file: `<skill>/SKILL.md`.
|
|
6
|
+
|
|
7
|
+
## Core
|
|
8
|
+
|
|
9
|
+
| Skill | Covers |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `avo-resources` | Generate and configure resources — title, includes, sorting, pagination, cover/avatar, array (non-DB) resources |
|
|
12
|
+
| `avo-fields` | Add and configure fields in `def fields` — pick the `as:` type, options, formatting, layout |
|
|
13
|
+
| `avo-associations` | Wire `belongs_to` / `has_many` / `has_one` / HABTM fields, polymorphism, STI |
|
|
14
|
+
| `avo-actions` | Actions that run Ruby on selected, single, or no records — bulk ops, forms, modals, responses |
|
|
15
|
+
| `avo-filters` | Filter the index with basic filters. Dynamic filters and scopes are package-owned — see below |
|
|
16
|
+
| `avo-index-views` | How the index renders — table styling, grid cards, map markers, view types |
|
|
17
|
+
| `avo-custom-fields` | Build a brand-new field type — generator plus its Edit/Show/Index view components |
|
|
18
|
+
|
|
19
|
+
## Configuration and operations
|
|
20
|
+
|
|
21
|
+
| Skill | Covers |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `avo-setup` | Install Avo, mount it, authenticate the private gem server, set the license key |
|
|
24
|
+
| `avo-update` | Bump the Avo gems and apply every upgrade-guide step for the versions crossed |
|
|
25
|
+
| `avo-authentication` | Tell Avo who the current user is, gate access, wire roles / profile / sign-out |
|
|
26
|
+
| `avo-admin-config` | Global initializer knobs — app name, per-page, container width, density, home path |
|
|
27
|
+
| `avo-performance` | Caching and stale-row fixes to make the admin fast |
|
|
28
|
+
| `avo-testing` | Unblock the license check in the test suite and use Avo's test helpers |
|
|
29
|
+
| `avo-multitenancy` | Scope the admin per tenant — route- or session-based, with an account switcher |
|
|
30
|
+
|
|
31
|
+
## Customization
|
|
32
|
+
|
|
33
|
+
| Skill | Covers |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `avo-branding-appearance` | Logo, favicon, color scheme, palettes, CSS re-skin, icons |
|
|
36
|
+
| `avo-menu-icons` | Pick the right Tabler icon and apply it to resources. The menu DSL is `avo-menu`'s own skill |
|
|
37
|
+
| `avo-navigation-search` | Per-resource search, breadcrumbs, keyboard shortcuts, the auto-generated sidebar |
|
|
38
|
+
| `avo-custom-ui` | Custom pages, embedded panels, dynamic/nested forms, ejected views, Stimulus, Tailwind |
|
|
39
|
+
| `avo-i18n` | Translate and localize the admin — labels, locale switching, RTL |
|
|
40
|
+
| `avo-controllers` | Override per-resource CRUD controller hooks and safely extend Avo's ApplicationController |
|
|
41
|
+
| `avo-engine-internals` | `main_app`/`avo` helpers, `Avo::Current`, `ExecutionContext`, reserved names |
|
|
42
|
+
| `avo-media-library` | Central asset browser and a picker inside rich-text editors |
|
|
43
|
+
|
|
44
|
+
## Cross-cutting
|
|
45
|
+
|
|
46
|
+
| Skill | Covers |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `avo-aware` | Map a Rails change onto the admin surface it affects, and route the edit — the loader decides when this applies |
|
|
49
|
+
| `avo-troubleshoot` | Diagnose a broken or misbehaving Avo app, organized by symptom |
|
|
50
|
+
|
|
51
|
+
## Package-owned skills
|
|
52
|
+
|
|
53
|
+
Features that live in their own gem ship their own skill. The loader lists the ones this app has installed and names the ones it does not, so nothing here points at a path that is absent. See `package-map.md` for the full map of gem to subject.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Package map
|
|
2
|
+
|
|
3
|
+
Every Avo gem that owns skills or is referenced by a core skill's pointer block.
|
|
4
|
+
|
|
5
|
+
This file does two jobs for the skills loader:
|
|
6
|
+
|
|
7
|
+
1. **Allowlist.** The loader reads a gem's skills index only when that gem is listed here. The `avo-` prefix on rubygems.org is unreserved, and loaded skill text becomes instructions the agent follows, so a gem name alone never grants that authority.
|
|
8
|
+
2. **Miss-path map.** When a task's subject belongs to a gem this app does not have, the loader names the gem and what it provides instead of pointing at a path that is not there.
|
|
9
|
+
|
|
10
|
+
The loader reports what is **installed**, never what is **licensed** — license state needs a runtime call the loader cannot make. A gem listed here and present in `Gemfile.lock` is treated as available.
|
|
11
|
+
|
|
12
|
+
## Ships its own skills
|
|
13
|
+
|
|
14
|
+
| Gem | Skill | Subjects | More |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| `avo-advanced_search` | `avo-advanced-search` | Cmd+K global search across resources; searchable association pickers | https://avohq.io/addons/global-search |
|
|
17
|
+
| `avo-api` | `avo-rest-api` | JSON REST API over every resource, token auth, per-token permission matrix | https://avohq.io/addons/api |
|
|
18
|
+
| `avo-audit_logging` | `avo-audit-logging` | Who changed and viewed what — timeline, diffs, revert | https://avohq.io/pricing |
|
|
19
|
+
| `avo-authorization` | `avo-authorization` | Pundit policies for resources, actions, associations, files | https://avohq.io/addons/authorization |
|
|
20
|
+
| `avo-collaboration` | `avo-collaboration` | Comments, reactions, automatic change-log on a record | https://avohq.io/addons/collaboration |
|
|
21
|
+
| `avo-custom_controls` | `avo-custom-controls` | Take over the show/edit/index/row button bars | https://avohq.io/addons/custom-controls |
|
|
22
|
+
| `avo-dashboards` | `avo-dashboards-cards` | Dashboards and the six card types — metrics, charts, tables, lists | https://avohq.io/addons/dashboards |
|
|
23
|
+
| `avo-dynamic_filters` | `avo-dynamic-filters` | Filter the index by any column from a filter bar, Ransack authorization | https://avohq.io/addons/dynamic-filters |
|
|
24
|
+
| `avo-forms` | `avo-forms-and-pages` | Model-agnostic forms and sidebar page hierarchies | https://avohq.io/addons/forms |
|
|
25
|
+
| `avo-http_resource` | `avo-http-resource` | Back a resource with an external HTTP API instead of Active Record | https://avohq.io/addons/http-resource |
|
|
26
|
+
| `avo-kanban` | `avo-kanban` | Database-backed drag-and-drop boards across resources | https://avohq.io/addons/kanban |
|
|
27
|
+
| `avo-menu` | `avo-menu` | The sidebar menu editor DSL — sections, groups, links, dashboards, visibility, sub-items, profile and header menus | https://avohq.io/addons/menu-editor |
|
|
28
|
+
| `avo-notifications` | `avo-notifications` | In-app notifications — bell dropdown, levels, action buttons, realtime | https://avohq.io/addons/notifications |
|
|
29
|
+
| `avo-record_reordering` | `avo-record-reordering` | Persistent up/down and drag-and-drop record ordering | https://avohq.io/addons/record-reordering |
|
|
30
|
+
| `avo-scopes` | `avo-scopes` | Scope tabs on the index — default view, counts, per-scope columns | https://avohq.io/addons/scopes |
|
data/lib/avo/version.rb
CHANGED
data/lib/avo.rb
CHANGED
|
@@ -19,6 +19,10 @@ loader.inflector.inflect(
|
|
|
19
19
|
"railtie_dsl" => "RailtieDSL"
|
|
20
20
|
)
|
|
21
21
|
loader.ignore("#{__dir__}/generators")
|
|
22
|
+
# Shipped agent skills. Markdown alone is invisible to Zeitwerk, but a skill that
|
|
23
|
+
# bundles a script makes its hyphenated directory eligible, and Zeitwerk then
|
|
24
|
+
# raises trying to constantize e.g. `avo-menu-icons`.
|
|
25
|
+
loader.ignore("#{__dir__}/avo/skills")
|
|
22
26
|
loader.ignore("#{__dir__}/avo/engine_dsl.rb")
|
|
23
27
|
loader.ignore("#{__dir__}/avo/plugin_dsl.rb")
|
|
24
28
|
loader.ignore("#{__dir__}/avo/railtie_dsl.rb")
|