Skip to main content

Free, open-source linter for Acumatica customization projects. The full pipeline is AcuOps.

Project description

acumatica-lint

PyPI License: Apache 2.0 Python 3.10+

Free, open-source linter for Acumatica customization projects. Catches publish-time failure modes — silent NullReferenceExceptions, dropped DAC extensions, orphan SiteMap rows, GI catalog exclusions, schema mutations without IF NOT EXISTS guards — before they restart your production app pool.

Want this run automatically on every PR, plus sandbox dry-run before prod, snapshot/rollback with integrity verification, Codex AI review, Slack telemetry, and deploy-window enforcement? → AcuOps

Install

pip install acumatica-lint

Python 3.10+. One dependency (pyyaml for the ISV co-publish allowlist parser). Works on macOS, Linux, and Windows.

Use

Point it at a project.xml:

acumatica-lint Customization/MyPackage/project.xml

With strict mode (additional warnings for best-practice violations):

acumatica-lint --strict Customization/MyPackage/project.xml

ISV authors — enforce a class-name prefix on all <Graph> declarations:

acumatica-lint --isv-prefix AO Customization/MyISV/project.xml

Debug-only DLL-source plugin scan (skips XML/Graph/semantic checks):

acumatica-lint --dll-source-only Customization/MyPackage/project.xml

Exit codes

  • 0 — passed (with or without warnings)
  • 1 — failed (one or more hard-fail errors)
  • 2 — usage error (bad CLI args)

What it catches

acumatica-lint encodes ~60 failure-mode rules from real Acumatica production incidents Studio B has resolved. A non-exhaustive list:

  • DAC fields without matching <Sql> ALTER TABLE — auto-column creation has been broken on cloud Acumatica since early 2026. [PXDBx] fields without a corresponding <Sql> ALTER will silently fail at publish.
  • Phantom DAC references in <GITable Name> — silently aborts GI processing without an error in the publish log.
  • GI Creation Contract — every new OData-exposed GI requires <RolesInGraph Rolename="*" Accessrights="4" ApplicationName="/" /> AND a matching entry in the package's CustomizationPlugin allowlist. Miss either and the GI silently catalog-excludes, returning HTTP 404 from /OData/{tenant}/$metadata.
  • SiteMap orphans (ParentID="00000000-...") — invisible in SM205010 (Access Rights by Role), ungrantable, OData 404.
  • Modern GI format requirements (24.208+) — GITable.Name must be fully-qualified DAC name; <GIWhere Condition> must use Acumatica's vocabulary ("E ", "NE", "GE", etc., NOT "EQ"); <GIWhere> attribute name is Operation= NOT Operator=; MUIScreen.SubcategoryID must point at tenant-real subcategory, not auto-created system-default.
  • <Sql> re-execution warnings<Sql> scripts are tracked by Name; bumping to _v2 is silently skipped. For logic that must survive every publish, use CustomizationPlugin.PXDatabase.Execute().
  • DAC extension fields without <EntityEndpoint> mappingPUT returns HTTP 200 + echoes value + silent-drops at commit. Three places must match: SQL column, DAC attribute, inline <EntityEndpoint> block.
  • C# CustomizationPlugin static checks — banned APIs (WebConfigurationManager, HttpContext); SQL against GI* tables in UpdateDatabase(); SQL against INUnit; INSERT/UPDATE/SELECT column refs on Acumatica system tables outside the allowlist.
  • DLL source reference auditing — gap-closer for compiled-DLL plugins: every plugin check runs on every .cs under src/<DllName>/ when project.xml references <File AppRelativePath="Bin\<DllName>.dll" />.
  • ISV prefix enforcement — when invoked with --isv-prefix, validates that every <Graph ClassName=> follows your ISV convention.
  • Inline <EntityEndpoint> block requirements — must be a top-level child of <Customization> in project.xml; standalone EntityEndpoint_*.xml at zip root is NOT processed.

Every hard-fail check ships an escape-hatch comment marker for audited exceptions:

<!-- REVIEWED: gi-sql-safe       — suppress validate_gi_sql -->
<!-- REVIEWED: inunit-sql-safe   — suppress validate_inunit_sql -->
<!-- REVIEWED: schema-safe       — suppress validate_plugin_sql_column_refs -->

What this linter is NOT

This linter is the deterministic check engine. It runs once, against one project, returns errors and warnings, exits.

It does not:

  • Run continuously on every PR
  • Push your customization to a sandbox tenant and run an end-to-end test suite before allowing the prod publish
  • Take a snapshot of your current customization state with integrity verification before each publish (catching merge=true partial-publish corruption)
  • Roll back automatically when a publish fails midway
  • Review the C# code in CustomizationPlugin.UpdateDatabase() with AI to flag non-obvious correctness issues a static rule can't catch
  • Post :rotating_light: Slack notifications when a publish fails
  • Enforce deploy windows (no schema changes during business hours)
  • Coordinate co-publishes with ISV packages without inflating the publishBegin page count past Acumatica's ThreadAbortException limit
  • Issue + revoke per-VAR license keys with grace periods

All of that is AcuOps — the managed pipeline. $800/mo per Acumatica instance.

Founding partners

AcuOps is selecting 7–10 founding partners locked at $500/mo for the life of subscription in exchange for a published case study + reference availability. We're picking partners across VAR, ISV, in-house mid-market, and enterprise deployment patterns. → Apply

Contributing

Pull requests welcome. New rules should:

  1. Reference the originating production incident (AAR date, PR number, or both) in a comment
  2. Ship with both a positive example (project.xml snippet that triggers the rule) and a negative example
  3. Include an escape-hatch comment marker if the rule may have legitimate exceptions

Coding style is enforced by ruff. Run pip install -e ".[dev]" then ruff check src/.

License

Apache 2.0 — see LICENSE.

The Apache 2.0 patent grant protects all contributors from patent litigation over contributed code. This matters when the codebase encodes hard-won failure-mode knowledge from real production incidents.

Built by Studio B

Studio B (studiob.ai) runs Acumatica deployments at scale across its portfolio. acumatica-lint is the static-check engine extracted from the AcuOps pipeline — the same code paths Studio B uses against its own production deployments.

The full pipeline → acuops.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

acumatica_lint-0.1.0.tar.gz (71.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

acumatica_lint-0.1.0-py3-none-any.whl (70.2 kB view details)

Uploaded Python 3

File details

Details for the file acumatica_lint-0.1.0.tar.gz.

File metadata

  • Download URL: acumatica_lint-0.1.0.tar.gz
  • Upload date:
  • Size: 71.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for acumatica_lint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b680c5bd64812d36d5719a53cbf6951dd575eed43edd8fe44294fac545880b58
MD5 5c0e53f947f1a081f0abfddc2717e223
BLAKE2b-256 5b6100eeac9899f5fd6857c68affa04d51062342af4df0e8e7210517f4eee55f

See more details on using hashes here.

File details

Details for the file acumatica_lint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: acumatica_lint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 70.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for acumatica_lint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6df4474734615ff00793949efeb8c6818054a6e631e8af367a616566f450f2b7
MD5 5b4bda68db027b3de884ddf05c0be3ef
BLAKE2b-256 df80df19ee92c4f52040f0d2871f3c0e04efe4252eecb6a4f0d1aa7084376a7a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page