Files
2026-09-15 13:06:45 -05:00

150 lines
6.8 KiB
Markdown

---
name: ignition-exchange-conformance
description: Check whether an Ignition project follows Inductive Automation's Exchange Resources Style Guide before publishing it to the Ignition Exchange — resource namespacing, Perspective/Vision/Python naming, session-property and client-tag prohibitions, database and logger conventions. Use when asked whether a project is Exchange-ready, when reviewing a project intended for the Exchange, or before packaging one for upload. Bundles a stdlib-only checker (exchange_lint.py) with 38 rules traceable to guide sections. Not for converting a project into that shape (use ignition-exchange-convert) or for packaging and upload (use ignition-exchange-publish).
---
# Ignition Exchange conformance
Tests an Ignition project against the **Exchange Resources Style Guide v1.1.0**.
Run `exchange_lint.py` (next to this file) rather than checking by eye — the rules
are numerous, mechanical, and easy to half-apply.
```bash
python3 skills/ignition-exchange-conformance/exchange_lint.py /path/to/Project
```
Stdlib only, Python 3.8+. Accepts a project folder, a folder containing several
projects (a gateway's `projects/` directory, an unpacked backup, a repo), or a
project export `.zip`. Infers the resource name from the namespace the project
already uses — no configuration.
Exit codes: `0` clean · `1` findings · `2` bad invocation.
## The one rule that is actually mandatory
The guide states exactly one `must`:
> Views, Styles, Scripts, etc. **must** all be contained within an appropriately
> named folder. This allows easy imports into existing projects, where those
> project resources won't overlap, interfere, or overwrite existing parts of a
> user's project.
Everything else — every naming convention — the guide explicitly calls optional:
> We encourage you to follow the naming conventions outlined here, however
> **these conventions are not required**.
So `exchange_lint.py` reports **FAIL** only for namespacing, the storage
prohibitions, and the project-name character set. Naming is **warn**. Don't
report warnings as blockers, and don't contort a project to silence them —
a consistent house style is defensible and the guide says so.
## Namespace roots — two spellings, both correct
Per the guide's Project Browser screenshots:
| Under `Exchange/<ResourceName>/` | Under `exchange/<resource-name>/` |
|---|---|
| Views, Windows, Templates | Style classes |
| Named queries, Reports | Project library (scripts) |
| SFCs, Transaction groups | WebDev sources |
| Alarm pipelines, Tags | Images |
Uppercase roots take Title Case or PascalCase names; lowercase roots take
kebab-case — **except the project library**, which is code-facing and uses
lowercase or camelCase (`exchange.resourceName.scriptName`), matching the
logger convention `exchange.resourceName.LoggerName`.
## The storage prohibitions
Both are FAILs because both break a clean import, and both are easy to miss:
- **Perspective session custom properties.** Importing merges them into the host
project's session props. Use view `params`/`custom` plus, for genuinely
non-reactive cross-session state, `system.util.getGlobals()['exchange']['resourceName']`.
- **Vision client tags.** Same reasoning.
## What the checker cannot tell you
A clean report is not "ready to publish". Four gaps, all of which have bitten:
1. **Content.** It reads names and structure, never label text. A visible string
like `"Acme Internal — do not distribute"` passes silently. Always sweep
user-visible strings separately:
```bash
python3 - <<'PY'
import json, glob, re
bad = re.compile(r"internal|confidential|do not|contest|demo only|todo|fixme", re.I)
for f in glob.glob("<project>/com.inductiveautomation.perspective/views/**/view.json", recursive=True):
v = json.load(open(f))
def walk(n):
yield n
for c in (n.get("children") or []): yield from walk(c)
for n in walk(v.get("root") or {}):
t = (n.get("props") or {}).get("text")
if isinstance(t, str) and bad.search(t): print(f, repr(t))
PY
```
2. **Referential integrity.** It does not check that embedded view paths, style
classes or script calls resolve. See [`ignition-exchange-convert`](../ignition-exchange-convert/SKILL.md).
3. **Runtime.** Nothing is executed or rendered. Load it on a gateway.
4. **The shipped package.** `E-UP-DOCS` checks the working directory, not the
export zip. See [`ignition-exchange-publish`](../ignition-exchange-publish/SKILL.md).
## Findings that need interpretation
**`E-STYLE-HIER` false positives.** It decides where a style class belongs by
finding which views reference it, reading only literal `props.style.classes`
strings. A class name built in a binding —
`'exchange/my-resource/priority/' + level.lower()` — is invisible, so a shared
class gets reported as single-use. Verify before acting; do not duplicate style
classes to silence it.
**`E-PAGE-ROOT` is not in the guide.** It is an added caution: a page mapped to
`/` takes over the root URL of any project the resource is imported into. Real
trade-off — a resource with no `/` page cannot be launched from the gateway's
Perspective project list, only from its full page URL
(`/data/perspective/client/<project>/<page>`). Flag the trade-off; let the user
decide.
**`E-PY-VAR` volume.** A project written in idiomatic snake_case produces
hundreds of warnings, since the guide wants camelCase for variables and
functions (deliberately diverging from PEP-8; `UPPER_SNAKE` constants are
exempt). Offer `--ignore E-PY-VAR,E-PY-FUNC` rather than a mass rename, unless
the user wants full conformance.
## Useful flags
| Flag | Effect |
|---|---|
| `--strict` | Warnings become failures |
| `--ignore E-PY-VAR,E-COMP-NAME` | Suppress rules |
| `-v` | Every occurrence (default collapses at 8 per rule) |
| `--json` | Machine-readable, for CI |
| `--list-rules` | All 38 rules with descriptions |
## Reporting results
The summary reads `N of 38 rules exercised`. Quote it. A project with no
database, tags or named queries legitimately leaves those rules unexercised, and
a narrow pass should never be presented as a broad one.
## Self-test
`test_exchange_lint.py` builds a conforming and a non-conforming project in a
temp dir, asserts the checker is silent on the first and fires the expected rule
id on the second, and asserts every declared rule is reachable in the source.
Run it after changing any rule.
```bash
python3 skills/ignition-exchange-conformance/test_exchange_lint.py
```
## Provenance
Rules derived from Exchange Resources Style Guide v1.1.0. Checker exercised
against four real Perspective projects and validated on Ignition **8.1.20** and
**8.3.7** gateways (2026-09-15). Where a rule is an inference rather than guide
text it says so in its description — currently only `E-PAGE-ROOT`.