acumatica-cli 0.15.3__tar.gz → 0.16.0__tar.gz
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.
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/PKG-INFO +57 -108
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/README.md +56 -107
- acumatica_cli-0.16.0/pyproject.toml +109 -0
- acumatica_cli-0.15.3/pyproject.toml → acumatica_cli-0.16.0/pyproject.toml.orig +1 -1
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/cli.py +161 -20
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/config.py +66 -25
- acumatica_cli-0.16.0/src/acumatica_cli/inventory.py +548 -0
- acumatica_cli-0.16.0/src/acumatica_cli/reconcile.py +760 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/seed.py +3 -3
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/target.py +12 -18
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/README.md +2 -1
- acumatica_cli-0.16.0/src/acumatica_cli/templates/env +16 -0
- acumatica_cli-0.16.0/src/acumatica_cli/templates/target +7 -0
- acumatica_cli-0.15.3/src/acumatica_cli/templates/env +0 -17
- acumatica_cli-0.15.3/src/acumatica_cli/templates/target +0 -6
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/client.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/extract.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/firstlogin.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/seed_catalog.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/state.py +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/gitignore +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
- {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/tenant.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: acumatica-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data
|
|
5
5
|
Author: Konstantin Borovik
|
|
6
6
|
Author-email: Konstantin Borovik <kb@lab5.ca>
|
|
@@ -39,7 +39,7 @@ uv tool install acumatica-cli
|
|
|
39
39
|
|
|
40
40
|
acu config init --host erp.example.com my-erp
|
|
41
41
|
cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
|
|
42
|
-
#
|
|
42
|
+
# Default API pin = target.yaml default_api
|
|
43
43
|
# start from a brand-new empty tenant
|
|
44
44
|
|
|
45
45
|
acu config check # read-only preflight (incl. target.yaml)
|
|
@@ -49,18 +49,17 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
|
|
|
49
49
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
50
50
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
51
51
|
acu --tenant DEV state # capture state/ trial-balance
|
|
52
|
-
|
|
53
|
-
acu --tenant DEV run scenario/
|
|
52
|
+
acu --tenant DEV run scenario/ # replay transaction scenarios
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
57
56
|
See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
|
|
58
57
|
|
|
59
|
-
**Hosted Acumatica (no SSH):** the tenant already exists;
|
|
58
|
+
**Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
|
|
60
59
|
|
|
61
60
|
```sh
|
|
62
61
|
acu config init --host customer.acumatica.com my-erp
|
|
63
|
-
cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; ACU_SSH=
|
|
62
|
+
cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; add ACU_SSH=
|
|
64
63
|
acu config check # REST preflight; ssh probe is skipped
|
|
65
64
|
acu --tenant DEV bootstrap # publish AcuBootstrap via REST only
|
|
66
65
|
acu --tenant DEV apply config/
|
|
@@ -89,6 +88,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
89
88
|
│ capture derived state into state/ (not seed)
|
|
90
89
|
├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
|
|
91
90
|
│ inverse of apply into config/{bootstrap,baseline,setup,master}/
|
|
91
|
+
├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
|
|
92
|
+
│ offline snapshot artifact → inventory/ (not seed)
|
|
93
|
+
├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
|
|
94
|
+
│ inventory/ + optional config/ → findings/ only
|
|
92
95
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
93
96
|
│
|
|
94
97
|
└── config configuration ops
|
|
@@ -102,9 +105,28 @@ A path like `config/` expands nested seed dirs in that fixed order.
|
|
|
102
105
|
`run` without FILES defaults to `scenario/`.
|
|
103
106
|
`state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
104
107
|
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
108
|
+
`inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
|
|
109
|
+
`reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
|
|
105
110
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
106
111
|
Run `acu <command> --help` for details on any command.
|
|
107
112
|
|
|
113
|
+
### Dual readers, one writer
|
|
114
|
+
|
|
115
|
+
Two read paths, one mutator (V35).
|
|
116
|
+
Do not confuse them with each other or with `state`:
|
|
117
|
+
|
|
118
|
+
| Command | Plane | Input | Writes | Role |
|
|
119
|
+
| ------- | ----- | ----- | ------ | ---- |
|
|
120
|
+
| `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
|
|
121
|
+
| `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
|
|
122
|
+
| `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
|
|
123
|
+
| `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
|
|
124
|
+
| `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
|
|
125
|
+
|
|
126
|
+
`inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
|
|
127
|
+
Binary `.adb` snapshots are rejected (XML only).
|
|
128
|
+
See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
|
|
129
|
+
|
|
108
130
|
## The data repo
|
|
109
131
|
|
|
110
132
|
Your configuration lives in its own git repo.
|
|
@@ -120,13 +142,14 @@ There is no `--flavor`.
|
|
|
120
142
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
121
143
|
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
122
144
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
145
|
+
| `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
|
|
146
|
+
| `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
|
|
123
147
|
| `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
|
|
124
148
|
| `.env` | where to apply and who signs in, every key an `ACU_*` variable |
|
|
125
149
|
|
|
126
150
|
Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
|
|
127
151
|
|
|
128
152
|
Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
|
|
129
|
-
The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
|
|
130
153
|
Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
|
|
131
154
|
|
|
132
155
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
@@ -141,38 +164,19 @@ git diff config/ # review extract delta before commit
|
|
|
141
164
|
acu --tenant DEV apply config/ # replay extracted seed
|
|
142
165
|
acu --tenant DEV diff config/ # expect exit 0
|
|
143
166
|
```
|
|
144
|
-
|
|
145
|
-
**Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`.
|
|
146
|
-
Paths are always `config/…`.
|
|
147
|
-
There is no `--layout`.
|
|
148
|
-
Move any root-layout extract output under `config/`, or re-extract into a modern data repo.
|
|
149
|
-
Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
|
|
150
|
-
|
|
151
|
-
Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
|
|
152
|
-
`acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
|
|
153
|
-
`once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
|
|
154
|
-
|
|
155
|
-
`acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review.
|
|
156
|
-
It is not `extract` (config seed) and not `diff` (desired vs actual config).
|
|
157
|
-
Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`).
|
|
158
|
-
After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
|
|
159
|
-
|
|
160
|
-
**Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias).
|
|
161
|
-
Bare defaults are `config/views/` (views) and `state/` (observations).
|
|
162
|
-
Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
|
|
163
|
-
|
|
164
167
|
### Seed `endpoint:` symbols
|
|
165
168
|
|
|
166
169
|
Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:` line.
|
|
167
170
|
|
|
168
171
|
| Value | Resolves to |
|
|
169
172
|
| ----- | ----------- |
|
|
170
|
-
| omitted | `Default/<
|
|
173
|
+
| omitted | `Default/<api_version>` for Default-only entities |
|
|
171
174
|
| `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
|
|
172
|
-
| `default` | `Default/<
|
|
173
|
-
| `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores
|
|
175
|
+
| `default` | `Default/<api_version>` — tracks the resolved API version |
|
|
176
|
+
| `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
|
|
174
177
|
|
|
175
|
-
|
|
178
|
+
`api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
|
|
179
|
+
Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
|
|
176
180
|
|
|
177
181
|
## Installation
|
|
178
182
|
|
|
@@ -193,41 +197,45 @@ Verify with `acu --version`.
|
|
|
193
197
|
|
|
194
198
|
## Configuration
|
|
195
199
|
|
|
196
|
-
Everything lives in one `.env` file: *where* to apply and *who* signs in
|
|
197
|
-
|
|
200
|
+
Everything lives in one `.env` file: *where* to apply and *who* signs in
|
|
201
|
+
(`ACU_*` vars only).
|
|
202
|
+
The Default contract API pin is **not** in `.env` — it
|
|
203
|
+
lives in committed `target.yaml` (`default_api`).
|
|
198
204
|
|
|
199
205
|
```sh
|
|
200
206
|
ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
|
|
201
207
|
ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
|
|
202
|
-
ACU_SSH
|
|
203
|
-
|
|
208
|
+
# ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
|
|
209
|
+
# ACU_SSH= # hosted opt-out (blank key)
|
|
204
210
|
ACU_USER=admin # optional, defaults to admin
|
|
205
211
|
ACU_PASSWORD=... # required for live commands
|
|
206
212
|
```
|
|
207
213
|
|
|
208
|
-
|
|
209
|
-
|
|
214
|
+
There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
|
|
215
|
+
Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
|
|
216
|
+
`Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
|
|
210
217
|
|
|
211
|
-
The committed `target.yaml` next to `.env` declares the verified matrix (what, not where)
|
|
218
|
+
The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
|
|
212
219
|
|
|
213
220
|
```yaml
|
|
214
|
-
erp: "26.101.0225" # claimed product line/build
|
|
215
|
-
default_api: "25.200.001" #
|
|
221
|
+
erp: "26.101.0225" # claimed product line/build
|
|
222
|
+
default_api: "25.200.001" # sources Instance.api_version when --api-version absent
|
|
216
223
|
```
|
|
217
224
|
|
|
218
|
-
When `target.yaml` is present,
|
|
219
|
-
`acu config check` reports
|
|
220
|
-
|
|
225
|
+
When `target.yaml` is present, live commands resolve `api_version` from
|
|
226
|
+
`default_api` (source-merge). `acu config check` reports
|
|
227
|
+
`ok target (api_version from default_api=…; erp=… claimed)`.
|
|
228
|
+
Missing `target.yaml` only warns on check unless you pass `--strict` (then
|
|
229
|
+
the code default `25.200.001` is used).
|
|
221
230
|
|
|
222
231
|
Worth knowing:
|
|
223
232
|
|
|
224
233
|
- The file is found by walking up from the current directory, so any subdirectory of the data repo works.
|
|
225
234
|
- Without a `.env`, global flags plus the process environment supply the full configuration.
|
|
226
|
-
- `ACU_SSH` is
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
- `acu config show` prints the
|
|
230
|
-
- When `target.yaml` is present, `config show` also comments `erp` / `default_api`.
|
|
235
|
+
- When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
|
|
236
|
+
A **present blank** `ACU_SSH=` is the hosted opt-out.
|
|
237
|
+
Only `acu tenant` requires a non-empty value post-default.
|
|
238
|
+
- `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
|
|
231
239
|
- Redirect it to turn resolved state into a working config: `acu config show > .env`.
|
|
232
240
|
|
|
233
241
|
Verify before touching anything live:
|
|
@@ -238,65 +246,6 @@ acu config check --strict # missing target.yaml becomes fail
|
|
|
238
246
|
acu apply --dry-run # show what would be written, write nothing
|
|
239
247
|
```
|
|
240
248
|
|
|
241
|
-
## Control and Data Planes
|
|
242
|
-
|
|
243
|
-
`acu` talks to an instance over two independent channels:
|
|
244
|
-
|
|
245
|
-
- **Control plane (SSH):** `acu tenant` runs `ac.exe -cm:CompanyConfig` and `sqlcmd` on the Windows guest — see [`docs/ac-exe.md`](docs/ac-exe.md).
|
|
246
|
-
- **Data plane (REST):** `acu bootstrap`, `apply`, `diff`, `run`, `extract`, and `schema` use the contract-based API and CustomizationApi — see [`docs/rest-api.md`](docs/rest-api.md).
|
|
247
|
-
|
|
248
|
-
If you never touch `acu tenant`, you never need SSH.
|
|
249
|
-
On a hosted instance, publish AcuBootstrap with `acu bootstrap`, then seed with `apply`.
|
|
250
|
-
`acu bootstrap --export PATH` writes the package zip for manual import on the Customization Projects screen (SM204505) when you cannot call the API.
|
|
251
|
-
|
|
252
|
-
## SSH setup (control plane)
|
|
253
|
-
|
|
254
|
-
`acu tenant` runs commands on the Windows guest through plain `ssh`.
|
|
255
|
-
Two things about this setup are not obvious, and both are hard requirements.
|
|
256
|
-
|
|
257
|
-
**1.
|
|
258
|
-
The default SSH shell on the Windows guest must be PowerShell.**
|
|
259
|
-
`acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default.
|
|
260
|
-
Switch it once, in an elevated PowerShell on the guest:
|
|
261
|
-
|
|
262
|
-
```powershell
|
|
263
|
-
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
|
|
264
|
-
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
|
|
265
|
-
-PropertyType String -Force
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
**2.
|
|
269
|
-
Authentication must be key-based and non-interactive.**
|
|
270
|
-
`acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
|
|
271
|
-
Because the default user is `Administrator` (an administrators-group member), Windows OpenSSH reads the key from the *machine-wide* file `C:\ProgramData\ssh\administrators_authorized_keys` — **not** from `~\.ssh\authorized_keys` like on Linux.
|
|
272
|
-
On the guest:
|
|
273
|
-
|
|
274
|
-
Install + start the server (once):
|
|
275
|
-
|
|
276
|
-
```powershell
|
|
277
|
-
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
|
278
|
-
Set-Service sshd -StartupType Automatic
|
|
279
|
-
Start-Service sshd
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Authorize your public key for administrators:
|
|
283
|
-
|
|
284
|
-
```powershell
|
|
285
|
-
Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
|
|
289
|
-
|
|
290
|
-
```powershell
|
|
291
|
-
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
|
|
295
|
-
|
|
296
|
-
```sh
|
|
297
|
-
ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
|
|
298
|
-
```
|
|
299
|
-
|
|
300
249
|
## Development
|
|
301
250
|
|
|
302
251
|
Requires **GNU Make at least 3.82** — the Makefile uses `.ONESHELL`.
|
|
@@ -328,7 +277,7 @@ GitHub Actions re-runs the check on the tag, then publishes the GitHub release a
|
|
|
328
277
|
|
|
329
278
|
`gmake e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
|
|
330
279
|
|
|
331
|
-
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `
|
|
280
|
+
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
|
|
332
281
|
`gmake e2e` refuses to start without it.
|
|
333
282
|
|
|
334
283
|
The tier is self-contained.
|
|
@@ -21,7 +21,7 @@ uv tool install acumatica-cli
|
|
|
21
21
|
|
|
22
22
|
acu config init --host erp.example.com my-erp
|
|
23
23
|
cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
|
|
24
|
-
#
|
|
24
|
+
# Default API pin = target.yaml default_api
|
|
25
25
|
# start from a brand-new empty tenant
|
|
26
26
|
|
|
27
27
|
acu config check # read-only preflight (incl. target.yaml)
|
|
@@ -31,18 +31,17 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
|
|
|
31
31
|
acu --tenant DEV run scenario/ # once capital → buy → build → sell
|
|
32
32
|
acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
|
|
33
33
|
acu --tenant DEV state # capture state/ trial-balance
|
|
34
|
-
|
|
35
|
-
acu --tenant DEV run scenario/
|
|
34
|
+
acu --tenant DEV run scenario/ # replay transaction scenarios
|
|
36
35
|
```
|
|
37
36
|
|
|
38
37
|
Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
|
|
39
38
|
See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
|
|
40
39
|
|
|
41
|
-
**Hosted Acumatica (no SSH):** the tenant already exists;
|
|
40
|
+
**Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
|
|
42
41
|
|
|
43
42
|
```sh
|
|
44
43
|
acu config init --host customer.acumatica.com my-erp
|
|
45
|
-
cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; ACU_SSH=
|
|
44
|
+
cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; add ACU_SSH=
|
|
46
45
|
acu config check # REST preflight; ssh probe is skipped
|
|
47
46
|
acu --tenant DEV bootstrap # publish AcuBootstrap via REST only
|
|
48
47
|
acu --tenant DEV apply config/
|
|
@@ -71,6 +70,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
|
|
|
71
70
|
│ capture derived state into state/ (not seed)
|
|
72
71
|
├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
|
|
73
72
|
│ inverse of apply into config/{bootstrap,baseline,setup,master}/
|
|
73
|
+
├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
|
|
74
|
+
│ offline snapshot artifact → inventory/ (not seed)
|
|
75
|
+
├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
|
|
76
|
+
│ inventory/ + optional config/ → findings/ only
|
|
74
77
|
├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
|
|
75
78
|
│
|
|
76
79
|
└── config configuration ops
|
|
@@ -84,9 +87,28 @@ A path like `config/` expands nested seed dirs in that fixed order.
|
|
|
84
87
|
`run` without FILES defaults to `scenario/`.
|
|
85
88
|
`state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
|
|
86
89
|
`extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
|
|
90
|
+
`inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
|
|
91
|
+
`reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
|
|
87
92
|
`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
|
|
88
93
|
Run `acu <command> --help` for details on any command.
|
|
89
94
|
|
|
95
|
+
### Dual readers, one writer
|
|
96
|
+
|
|
97
|
+
Two read paths, one mutator (V35).
|
|
98
|
+
Do not confuse them with each other or with `state`:
|
|
99
|
+
|
|
100
|
+
| Command | Plane | Input | Writes | Role |
|
|
101
|
+
| ------- | ----- | ----- | ------ | ---- |
|
|
102
|
+
| `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
|
|
103
|
+
| `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
|
|
104
|
+
| `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
|
|
105
|
+
| `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
|
|
106
|
+
| `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
|
|
107
|
+
|
|
108
|
+
`inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
|
|
109
|
+
Binary `.adb` snapshots are rejected (XML only).
|
|
110
|
+
See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
|
|
111
|
+
|
|
90
112
|
## The data repo
|
|
91
113
|
|
|
92
114
|
Your configuration lives in its own git repo.
|
|
@@ -102,13 +124,14 @@ There is no `--flavor`.
|
|
|
102
124
|
| `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
|
|
103
125
|
| `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
|
|
104
126
|
| `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
|
|
127
|
+
| `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
|
|
128
|
+
| `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
|
|
105
129
|
| `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
|
|
106
130
|
| `.env` | where to apply and who signs in, every key an `ACU_*` variable |
|
|
107
131
|
|
|
108
132
|
Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
|
|
109
133
|
|
|
110
134
|
Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
|
|
111
|
-
The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
|
|
112
135
|
Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
|
|
113
136
|
|
|
114
137
|
Seed YAML is state: `apply` upserts it, `diff` proves it.
|
|
@@ -123,38 +146,19 @@ git diff config/ # review extract delta before commit
|
|
|
123
146
|
acu --tenant DEV apply config/ # replay extracted seed
|
|
124
147
|
acu --tenant DEV diff config/ # expect exit 0
|
|
125
148
|
```
|
|
126
|
-
|
|
127
|
-
**Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`.
|
|
128
|
-
Paths are always `config/…`.
|
|
129
|
-
There is no `--layout`.
|
|
130
|
-
Move any root-layout extract output under `config/`, or re-extract into a modern data repo.
|
|
131
|
-
Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
|
|
132
|
-
|
|
133
|
-
Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
|
|
134
|
-
`acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
|
|
135
|
-
`once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
|
|
136
|
-
|
|
137
|
-
`acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review.
|
|
138
|
-
It is not `extract` (config seed) and not `diff` (desired vs actual config).
|
|
139
|
-
Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`).
|
|
140
|
-
After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
|
|
141
|
-
|
|
142
|
-
**Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias).
|
|
143
|
-
Bare defaults are `config/views/` (views) and `state/` (observations).
|
|
144
|
-
Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
|
|
145
|
-
|
|
146
149
|
### Seed `endpoint:` symbols
|
|
147
150
|
|
|
148
151
|
Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:` line.
|
|
149
152
|
|
|
150
153
|
| Value | Resolves to |
|
|
151
154
|
| ----- | ----------- |
|
|
152
|
-
| omitted | `Default/<
|
|
155
|
+
| omitted | `Default/<api_version>` for Default-only entities |
|
|
153
156
|
| `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
|
|
154
|
-
| `default` | `Default/<
|
|
155
|
-
| `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores
|
|
157
|
+
| `default` | `Default/<api_version>` — tracks the resolved API version |
|
|
158
|
+
| `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
|
|
156
159
|
|
|
157
|
-
|
|
160
|
+
`api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
|
|
161
|
+
Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
|
|
158
162
|
|
|
159
163
|
## Installation
|
|
160
164
|
|
|
@@ -175,41 +179,45 @@ Verify with `acu --version`.
|
|
|
175
179
|
|
|
176
180
|
## Configuration
|
|
177
181
|
|
|
178
|
-
Everything lives in one `.env` file: *where* to apply and *who* signs in
|
|
179
|
-
|
|
182
|
+
Everything lives in one `.env` file: *where* to apply and *who* signs in
|
|
183
|
+
(`ACU_*` vars only).
|
|
184
|
+
The Default contract API pin is **not** in `.env` — it
|
|
185
|
+
lives in committed `target.yaml` (`default_api`).
|
|
180
186
|
|
|
181
187
|
```sh
|
|
182
188
|
ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
|
|
183
189
|
ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
|
|
184
|
-
ACU_SSH
|
|
185
|
-
|
|
190
|
+
# ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
|
|
191
|
+
# ACU_SSH= # hosted opt-out (blank key)
|
|
186
192
|
ACU_USER=admin # optional, defaults to admin
|
|
187
193
|
ACU_PASSWORD=... # required for live commands
|
|
188
194
|
```
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
|
|
197
|
+
Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
|
|
198
|
+
`Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
|
|
192
199
|
|
|
193
|
-
The committed `target.yaml` next to `.env` declares the verified matrix (what, not where)
|
|
200
|
+
The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
|
|
194
201
|
|
|
195
202
|
```yaml
|
|
196
|
-
erp: "26.101.0225" # claimed product line/build
|
|
197
|
-
default_api: "25.200.001" #
|
|
203
|
+
erp: "26.101.0225" # claimed product line/build
|
|
204
|
+
default_api: "25.200.001" # sources Instance.api_version when --api-version absent
|
|
198
205
|
```
|
|
199
206
|
|
|
200
|
-
When `target.yaml` is present,
|
|
201
|
-
`acu config check` reports
|
|
202
|
-
|
|
207
|
+
When `target.yaml` is present, live commands resolve `api_version` from
|
|
208
|
+
`default_api` (source-merge). `acu config check` reports
|
|
209
|
+
`ok target (api_version from default_api=…; erp=… claimed)`.
|
|
210
|
+
Missing `target.yaml` only warns on check unless you pass `--strict` (then
|
|
211
|
+
the code default `25.200.001` is used).
|
|
203
212
|
|
|
204
213
|
Worth knowing:
|
|
205
214
|
|
|
206
215
|
- The file is found by walking up from the current directory, so any subdirectory of the data repo works.
|
|
207
216
|
- Without a `.env`, global flags plus the process environment supply the full configuration.
|
|
208
|
-
- `ACU_SSH` is
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
- `acu config show` prints the
|
|
212
|
-
- When `target.yaml` is present, `config show` also comments `erp` / `default_api`.
|
|
217
|
+
- When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
|
|
218
|
+
A **present blank** `ACU_SSH=` is the hosted opt-out.
|
|
219
|
+
Only `acu tenant` requires a non-empty value post-default.
|
|
220
|
+
- `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
|
|
213
221
|
- Redirect it to turn resolved state into a working config: `acu config show > .env`.
|
|
214
222
|
|
|
215
223
|
Verify before touching anything live:
|
|
@@ -220,65 +228,6 @@ acu config check --strict # missing target.yaml becomes fail
|
|
|
220
228
|
acu apply --dry-run # show what would be written, write nothing
|
|
221
229
|
```
|
|
222
230
|
|
|
223
|
-
## Control and Data Planes
|
|
224
|
-
|
|
225
|
-
`acu` talks to an instance over two independent channels:
|
|
226
|
-
|
|
227
|
-
- **Control plane (SSH):** `acu tenant` runs `ac.exe -cm:CompanyConfig` and `sqlcmd` on the Windows guest — see [`docs/ac-exe.md`](docs/ac-exe.md).
|
|
228
|
-
- **Data plane (REST):** `acu bootstrap`, `apply`, `diff`, `run`, `extract`, and `schema` use the contract-based API and CustomizationApi — see [`docs/rest-api.md`](docs/rest-api.md).
|
|
229
|
-
|
|
230
|
-
If you never touch `acu tenant`, you never need SSH.
|
|
231
|
-
On a hosted instance, publish AcuBootstrap with `acu bootstrap`, then seed with `apply`.
|
|
232
|
-
`acu bootstrap --export PATH` writes the package zip for manual import on the Customization Projects screen (SM204505) when you cannot call the API.
|
|
233
|
-
|
|
234
|
-
## SSH setup (control plane)
|
|
235
|
-
|
|
236
|
-
`acu tenant` runs commands on the Windows guest through plain `ssh`.
|
|
237
|
-
Two things about this setup are not obvious, and both are hard requirements.
|
|
238
|
-
|
|
239
|
-
**1.
|
|
240
|
-
The default SSH shell on the Windows guest must be PowerShell.**
|
|
241
|
-
`acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default.
|
|
242
|
-
Switch it once, in an elevated PowerShell on the guest:
|
|
243
|
-
|
|
244
|
-
```powershell
|
|
245
|
-
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
|
|
246
|
-
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
|
|
247
|
-
-PropertyType String -Force
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**2.
|
|
251
|
-
Authentication must be key-based and non-interactive.**
|
|
252
|
-
`acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
|
|
253
|
-
Because the default user is `Administrator` (an administrators-group member), Windows OpenSSH reads the key from the *machine-wide* file `C:\ProgramData\ssh\administrators_authorized_keys` — **not** from `~\.ssh\authorized_keys` like on Linux.
|
|
254
|
-
On the guest:
|
|
255
|
-
|
|
256
|
-
Install + start the server (once):
|
|
257
|
-
|
|
258
|
-
```powershell
|
|
259
|
-
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
|
260
|
-
Set-Service sshd -StartupType Automatic
|
|
261
|
-
Start-Service sshd
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Authorize your public key for administrators:
|
|
265
|
-
|
|
266
|
-
```powershell
|
|
267
|
-
Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
|
|
271
|
-
|
|
272
|
-
```powershell
|
|
273
|
-
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
|
|
277
|
-
|
|
278
|
-
```sh
|
|
279
|
-
ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
|
|
280
|
-
```
|
|
281
|
-
|
|
282
231
|
## Development
|
|
283
232
|
|
|
284
233
|
Requires **GNU Make at least 3.82** — the Makefile uses `.ONESHELL`.
|
|
@@ -310,7 +259,7 @@ GitHub Actions re-runs the check on the tag, then publishes the GitHub release a
|
|
|
310
259
|
|
|
311
260
|
`gmake e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
|
|
312
261
|
|
|
313
|
-
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `
|
|
262
|
+
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
|
|
314
263
|
`gmake e2e` refuses to start without it.
|
|
315
264
|
|
|
316
265
|
The tier is self-contained.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "acumatica-cli"
|
|
3
|
+
version = "0.16.0"
|
|
4
|
+
description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
|
|
5
|
+
license = "PolyForm-Noncommercial-1.0.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"click>=8.1",
|
|
10
|
+
"httpx>=0.27",
|
|
11
|
+
"pydantic>=2.7",
|
|
12
|
+
"pydantic-settings>=2.5",
|
|
13
|
+
"pyyaml>=6.0",
|
|
14
|
+
"rich>=13",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[project.authors]]
|
|
18
|
+
name = "Konstantin Borovik"
|
|
19
|
+
email = "kb@lab5.ca"
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://lab5.ca"
|
|
23
|
+
Repository = "https://github.com/kborovik/acumatica-cli"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
acu = "acumatica_cli.cli:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["uv_build>=0.11,<0.12"]
|
|
30
|
+
build-backend = "uv_build"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"basedpyright>=1.37.0",
|
|
35
|
+
"pytest>=8",
|
|
36
|
+
"ruff>=0.13.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
addopts = '-q -m "not e2e"'
|
|
42
|
+
markers = ["e2e: live end-to-end tests against a real Acumatica instance (make e2e)"]
|
|
43
|
+
|
|
44
|
+
[tool.basedpyright]
|
|
45
|
+
typeCheckingMode = "strict"
|
|
46
|
+
pythonVersion = "3.14"
|
|
47
|
+
include = [
|
|
48
|
+
"src/acumatica_cli/",
|
|
49
|
+
"tests/",
|
|
50
|
+
]
|
|
51
|
+
reportMissingTypeStubs = false
|
|
52
|
+
reportUnknownVariableType = false
|
|
53
|
+
reportUnknownMemberType = false
|
|
54
|
+
reportUnknownArgumentType = false
|
|
55
|
+
reportUnknownParameterType = false
|
|
56
|
+
reportUnknownLambdaType = false
|
|
57
|
+
reportUnusedFunction = "warning"
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
target-version = "py314"
|
|
61
|
+
src = ["src"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.format]
|
|
64
|
+
docstring-code-format = true
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
fixable = ["ALL"]
|
|
68
|
+
select = [
|
|
69
|
+
"B",
|
|
70
|
+
"C4",
|
|
71
|
+
"C90",
|
|
72
|
+
"COM",
|
|
73
|
+
"D",
|
|
74
|
+
"E",
|
|
75
|
+
"F",
|
|
76
|
+
"I",
|
|
77
|
+
"N",
|
|
78
|
+
"PL",
|
|
79
|
+
"PT",
|
|
80
|
+
"RUF",
|
|
81
|
+
"SIM",
|
|
82
|
+
"T20",
|
|
83
|
+
"TID",
|
|
84
|
+
"UP",
|
|
85
|
+
"W",
|
|
86
|
+
]
|
|
87
|
+
ignore = [
|
|
88
|
+
"PLR2004",
|
|
89
|
+
"PLC0415",
|
|
90
|
+
"D100",
|
|
91
|
+
"D104",
|
|
92
|
+
"D105",
|
|
93
|
+
"D107",
|
|
94
|
+
"COM812",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint.pydocstyle]
|
|
98
|
+
convention = "google"
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.isort]
|
|
101
|
+
known-first-party = ["acumatica_cli"]
|
|
102
|
+
|
|
103
|
+
[tool.ruff.lint.per-file-ignores]
|
|
104
|
+
"src/acumatica_cli/cli.py" = ["PLR0913"]
|
|
105
|
+
"tests/*" = [
|
|
106
|
+
"D101",
|
|
107
|
+
"D102",
|
|
108
|
+
"D103",
|
|
109
|
+
]
|