@cxtms/cx-schema 1.7.12 → 1.7.14
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.
- package/.claude/skills/cx-core/SKILL.md +4 -0
- package/.claude/skills/cx-core/ref-cli-auth.md +4 -7
- package/.claude/skills/cx-core/ref-graphql-query.md +320 -0
- package/.claude/skills/cx-module/SKILL.md +6 -9
- package/.claude/skills/cx-workflow/SKILL.md +6 -9
- package/dist/cli.js +202 -10
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,10 @@ Shared domain reference for CargoXplorer entities. Used by `cx-workflow` and `cx
|
|
|
12
12
|
|
|
13
13
|
For CLI authentication, PAT tokens, org management, and publishing: see [ref-cli-auth.md](ref-cli-auth.md)
|
|
14
14
|
|
|
15
|
+
## GraphQL Querying & Audit History
|
|
16
|
+
|
|
17
|
+
For running GraphQL queries via CLI, filter syntax (Lucene), pagination, audit change history, and field discovery: see [ref-graphql-query.md](ref-graphql-query.md)
|
|
18
|
+
|
|
15
19
|
## Feature File Layout
|
|
16
20
|
|
|
17
21
|
All modules and workflows are organized under feature directories:
|
|
@@ -103,14 +103,11 @@ npx cxtms app install --branch develop
|
|
|
103
103
|
# Install but skip modules that have unpublished local changes
|
|
104
104
|
npx cxtms app install --skip-changed
|
|
105
105
|
|
|
106
|
-
# Publish server changes to git (creates a PR)
|
|
107
|
-
npx cxtms app publish
|
|
108
|
-
|
|
109
|
-
# Publish with a custom commit message
|
|
110
|
-
npx cxtms app publish --message "Add new shipping module"
|
|
106
|
+
# Publish server changes to git (creates a PR) — message is required
|
|
107
|
+
npx cxtms app publish -m "Add new shipping module"
|
|
111
108
|
|
|
112
109
|
# Force publish all modules and workflows (not just changed ones)
|
|
113
|
-
npx cxtms app publish --force
|
|
110
|
+
npx cxtms app publish -m "Full republish" --force
|
|
114
111
|
|
|
115
112
|
# List installed app manifests on the server
|
|
116
113
|
npx cxtms app list
|
|
@@ -118,6 +115,6 @@ npx cxtms app list
|
|
|
118
115
|
|
|
119
116
|
**`app install`** reads `repository` and `branch` from `app.yaml`, downloads the repo on the server side, and installs/updates all modules and workflows. Use `--force` to reinstall even if the version hasn't changed. Use `--skip-changed` to preserve modules with unpublished changes.
|
|
120
117
|
|
|
121
|
-
**`app publish`** takes the current server state and publishes it to git by creating a PR. The server increments the version, creates a publish branch, commits all module/workflow YAML files, and opens a pull request to the target branch.
|
|
118
|
+
**`app publish`** takes the current server state and publishes it to git by creating a PR. Requires a `-m` message describing the changes (like a git commit message). The server increments the version, creates a publish branch, commits all module/workflow YAML files, and opens a pull request to the target branch.
|
|
122
119
|
|
|
123
120
|
**`app list`** shows all installed app manifests with their version, status flags (disabled, unpublished changes, update available), and repository info.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# GraphQL Query Reference
|
|
2
|
+
|
|
3
|
+
## CLI Query Command
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx cxtms query '<graphql-query>' # Inline query
|
|
7
|
+
npx cxtms query my-query.graphql # From file
|
|
8
|
+
npx cxtms query '<query>' --vars '{"key":"v"}' # With variables
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires active login (`npx cxtms login`). Uses the active organization.
|
|
12
|
+
|
|
13
|
+
## Query Arguments
|
|
14
|
+
|
|
15
|
+
All root entity queries follow this pattern:
|
|
16
|
+
|
|
17
|
+
```graphql
|
|
18
|
+
orders(
|
|
19
|
+
organizationId: Int! # Required — active org ID
|
|
20
|
+
filter: String # Lucene query syntax (see below)
|
|
21
|
+
search: String # Full-text search across key fields
|
|
22
|
+
orderBy: String # Comma-separated sort fields
|
|
23
|
+
take: Int # Page size (alias: limit)
|
|
24
|
+
skip: Int # Offset (alias: offset)
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Entity-specific extras:
|
|
29
|
+
- `orders` — `includeDraft: Boolean` (default false)
|
|
30
|
+
|
|
31
|
+
Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, and others.
|
|
32
|
+
|
|
33
|
+
## Filter Syntax (Lucene Query)
|
|
34
|
+
|
|
35
|
+
Filters use **Lucene Query syntax** (not OData, not NCalc).
|
|
36
|
+
|
|
37
|
+
### Basic field matching
|
|
38
|
+
```
|
|
39
|
+
filter: "orderId:196596"
|
|
40
|
+
filter: "orderType:ParcelShipment"
|
|
41
|
+
filter: "status:Active"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Wildcards
|
|
45
|
+
```
|
|
46
|
+
filter: "name:*pending*" # Contains
|
|
47
|
+
filter: "code:ABC*" # Starts with
|
|
48
|
+
filter: "status:*Active" # Ends with
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Logical operators
|
|
52
|
+
```
|
|
53
|
+
filter: "countryCode:US AND name:Test"
|
|
54
|
+
filter: "status:Active OR status:Pending"
|
|
55
|
+
filter: "NOT customValues.po:123"
|
|
56
|
+
filter: "orderType:ParcelShipment AND (status:Active OR status:Pending)"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Nested paths (dot notation)
|
|
60
|
+
```
|
|
61
|
+
filter: "orderEntities.entityType:Consignee"
|
|
62
|
+
filter: "orderEntities.contactId:7128"
|
|
63
|
+
filter: "jobs.orders.orderNumber:16*"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Range queries
|
|
67
|
+
```
|
|
68
|
+
filter: "lastModified:[\"2026-01-01\" TO \"2026-03-15\"]"
|
|
69
|
+
filter: "lastModified:[\"2026-01-01\" TO *]" # >= date
|
|
70
|
+
filter: "amount:[100 TO 500]"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### NULL checks
|
|
74
|
+
```
|
|
75
|
+
filter: "customValues.po:NULL"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### CustomValues (JSONB fields)
|
|
79
|
+
```
|
|
80
|
+
filter: "customValues.fieldName:value"
|
|
81
|
+
filter: "customValues.fieldName:\"exact match\""
|
|
82
|
+
filter: "customValues.fieldName:NULL"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Filtered collections (bracket notation)
|
|
86
|
+
```
|
|
87
|
+
filter: "children[category:CatA].name:test"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## OrderBy Syntax
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
orderBy: "orderNumber" # Ascending
|
|
94
|
+
orderBy: "-orderNumber" # Descending
|
|
95
|
+
orderBy: "-created,orderNumber" # Multi-field
|
|
96
|
+
orderBy: "customValues.fieldName" # Custom field sort
|
|
97
|
+
orderBy: "orderNumber~ToInt32" # Type conversion during sort
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Pagination
|
|
101
|
+
|
|
102
|
+
```graphql
|
|
103
|
+
{
|
|
104
|
+
orders(organizationId: 1, take: 50, skip: 0) {
|
|
105
|
+
items { orderId orderNumber }
|
|
106
|
+
totalCount
|
|
107
|
+
pageInfo { hasNextPage hasPreviousPage }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Audit Change History
|
|
113
|
+
|
|
114
|
+
### Entity-level: `changeHistory` computed field
|
|
115
|
+
|
|
116
|
+
Available on: **Order**, **Contact**, **Commodity**, **AccountingTransaction**
|
|
117
|
+
|
|
118
|
+
```graphql
|
|
119
|
+
{
|
|
120
|
+
orders(organizationId: 1, filter: "orderId:196596", take: 1) {
|
|
121
|
+
items {
|
|
122
|
+
orderId
|
|
123
|
+
changeHistory(maxResults: 20) {
|
|
124
|
+
entityName
|
|
125
|
+
hasMoreRecords
|
|
126
|
+
continuationToken
|
|
127
|
+
changes {
|
|
128
|
+
state # "Added" | "Modified" | "Deleted"
|
|
129
|
+
userId
|
|
130
|
+
timestamp
|
|
131
|
+
user {
|
|
132
|
+
fullName
|
|
133
|
+
email
|
|
134
|
+
}
|
|
135
|
+
changedFields {
|
|
136
|
+
fieldName
|
|
137
|
+
originalValue
|
|
138
|
+
currentValue
|
|
139
|
+
fieldType
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Arguments: `startDate: DateTime`, `endDate: DateTime`, `maxResults: Int` (default 10)
|
|
149
|
+
|
|
150
|
+
### Root-level: `auditChanges` query
|
|
151
|
+
|
|
152
|
+
Query audit changes directly without fetching the entity first:
|
|
153
|
+
|
|
154
|
+
```graphql
|
|
155
|
+
{
|
|
156
|
+
auditChanges(
|
|
157
|
+
organizationId: 1
|
|
158
|
+
filter: "entityName:Order AND primaryKey:196596"
|
|
159
|
+
take: 20
|
|
160
|
+
) {
|
|
161
|
+
items {
|
|
162
|
+
state
|
|
163
|
+
userId
|
|
164
|
+
timestamp
|
|
165
|
+
user { fullName email }
|
|
166
|
+
changedFields {
|
|
167
|
+
fieldName
|
|
168
|
+
originalValue
|
|
169
|
+
currentValue
|
|
170
|
+
fieldType
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Arguments: `organizationId: Int!`, `filter: String`, `search: String`, `take: Int`, `skip: Int`, `orderBy: String`
|
|
178
|
+
|
|
179
|
+
Filter fields: `entityName`, `primaryKey`, `userId`, `state`
|
|
180
|
+
|
|
181
|
+
### Root-level: `auditChangeSummaries` query
|
|
182
|
+
|
|
183
|
+
High-level summary of changes (grouped by change event):
|
|
184
|
+
|
|
185
|
+
```graphql
|
|
186
|
+
{
|
|
187
|
+
auditChangeSummaries(
|
|
188
|
+
organizationId: 1
|
|
189
|
+
startDate: "2026-03-01T00:00:00Z"
|
|
190
|
+
endDate: "2026-03-15T23:59:59Z"
|
|
191
|
+
maxResults: 50
|
|
192
|
+
) {
|
|
193
|
+
items {
|
|
194
|
+
changeId
|
|
195
|
+
userId
|
|
196
|
+
timestamp
|
|
197
|
+
changePaths
|
|
198
|
+
organizationId
|
|
199
|
+
}
|
|
200
|
+
continuationToken
|
|
201
|
+
hasMoreRecords
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Root-level: `auditChange` — single change detail
|
|
207
|
+
|
|
208
|
+
```graphql
|
|
209
|
+
{
|
|
210
|
+
auditChange(filePath: "<s3-file-path>") {
|
|
211
|
+
entityName
|
|
212
|
+
state
|
|
213
|
+
primaryKey
|
|
214
|
+
userId
|
|
215
|
+
timestamp
|
|
216
|
+
originalValues
|
|
217
|
+
currentValues
|
|
218
|
+
fields {
|
|
219
|
+
fieldName
|
|
220
|
+
originalValue
|
|
221
|
+
currentValue
|
|
222
|
+
fieldType
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## GraphQL Type Reference
|
|
229
|
+
|
|
230
|
+
### EntityAuditHistoryLightResult
|
|
231
|
+
| Field | Type |
|
|
232
|
+
|-------|------|
|
|
233
|
+
| `entityName` | `String` |
|
|
234
|
+
| `primaryKey` | `String` |
|
|
235
|
+
| `organizationId` | `Int` |
|
|
236
|
+
| `changes` | `[AuditChangeEntry!]!` |
|
|
237
|
+
| `continuationToken` | `String` |
|
|
238
|
+
| `hasMoreRecords` | `Boolean!` |
|
|
239
|
+
|
|
240
|
+
### AuditChangeEntry
|
|
241
|
+
| Field | Type | Notes |
|
|
242
|
+
|-------|------|-------|
|
|
243
|
+
| `state` | `String` | "Added", "Modified", "Deleted" |
|
|
244
|
+
| `userId` | `String` | |
|
|
245
|
+
| `timestamp` | `DateTime!` | |
|
|
246
|
+
| `user` | `AuditUser` | Lazy-loaded resolver |
|
|
247
|
+
| `changedFields` | `[AuditChangeField!]!` | Lazy-loaded resolver |
|
|
248
|
+
|
|
249
|
+
### AuditChangeField
|
|
250
|
+
| Field | Type |
|
|
251
|
+
|-------|------|
|
|
252
|
+
| `fieldName` | `String!` |
|
|
253
|
+
| `originalValue` | `Any` |
|
|
254
|
+
| `currentValue` | `Any` |
|
|
255
|
+
| `fieldType` | `String!` |
|
|
256
|
+
|
|
257
|
+
### AuditUser
|
|
258
|
+
| Field | Type |
|
|
259
|
+
|-------|------|
|
|
260
|
+
| `id` | `String` |
|
|
261
|
+
| `firstName` | `String` |
|
|
262
|
+
| `lastName` | `String` |
|
|
263
|
+
| `fullName` | `String` |
|
|
264
|
+
| `userName` | `String` |
|
|
265
|
+
| `email` | `String` |
|
|
266
|
+
|
|
267
|
+
## Discovering Fields
|
|
268
|
+
|
|
269
|
+
**Always discover field names before building a query.** Do not guess field names — use the tools below.
|
|
270
|
+
|
|
271
|
+
### CLI: `cxtms gql` — schema exploration commands
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# List all query root fields (what you can query)
|
|
275
|
+
npx cxtms gql queries
|
|
276
|
+
npx cxtms gql queries --filter order
|
|
277
|
+
|
|
278
|
+
# List all mutation root fields
|
|
279
|
+
npx cxtms gql mutations
|
|
280
|
+
npx cxtms gql mutations --filter order
|
|
281
|
+
|
|
282
|
+
# List all types (filter by name)
|
|
283
|
+
npx cxtms gql types --filter audit
|
|
284
|
+
npx cxtms gql types --filter order
|
|
285
|
+
|
|
286
|
+
# Inspect a specific type — shows fields, arguments, input fields, enum values
|
|
287
|
+
npx cxtms gql type OrderGqlDto
|
|
288
|
+
npx cxtms gql type AuditChangeEntry
|
|
289
|
+
npx cxtms gql type EntityAuditHistoryLightResult
|
|
290
|
+
npx cxtms gql type CreateOrderInput
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### TMS MCP: `graphql_schema` / `graphql_type` — equivalent MCP tools
|
|
294
|
+
|
|
295
|
+
When TMS MCP is available, the same discovery is available via MCP tools:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
graphql_schema(section: "queries", filter: "order")
|
|
299
|
+
graphql_schema(section: "types", filter: "audit")
|
|
300
|
+
graphql_type(typeName: "OrderGqlDto")
|
|
301
|
+
graphql_execute(query: "{ orders(organizationId: 1, take: 1) { items { orderId } } }")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Workflow: discover → query
|
|
305
|
+
|
|
306
|
+
1. **Find the type** — `cxtms gql types --filter order` to find type names
|
|
307
|
+
2. **Inspect the type** — `cxtms gql type OrderGqlDto` to see all fields and their types
|
|
308
|
+
3. **Check query args** — `cxtms gql queries --filter order` to see arguments
|
|
309
|
+
4. **Build and run** — `cxtms query '<graphql-query>'`
|
|
310
|
+
|
|
311
|
+
### Fallback: error-driven discovery
|
|
312
|
+
|
|
313
|
+
GraphQL error messages reveal valid field/type names:
|
|
314
|
+
- "The field `foo` does not exist on the type `BarType`" — tells you the type name
|
|
315
|
+
- "The argument `bar` does not exist" — tells you valid argument patterns
|
|
316
|
+
|
|
317
|
+
### Other sources
|
|
318
|
+
|
|
319
|
+
- `npx cxtms schema <name>` — shows workflow/module JSON schema fields (not GraphQL)
|
|
320
|
+
- Entity reference files (`ref-entity-*.md`) — document common fields, computed properties, and enums
|
|
@@ -416,21 +416,18 @@ Deploy, undeploy, and publish commands are listed in the CLI section at the top
|
|
|
416
416
|
Use `app publish` to push modified modules and workflows from the CX server to a GitHub repository. This creates a branch and pull request — it does NOT push directly to the target branch.
|
|
417
417
|
|
|
418
418
|
```bash
|
|
419
|
-
# Publish all unpublished changes to GitHub (creates a PR)
|
|
420
|
-
npx cxtms app publish
|
|
419
|
+
# Publish all unpublished changes to GitHub (creates a PR) — message is required
|
|
420
|
+
npx cxtms app publish -m "Add warehouse locations module"
|
|
421
421
|
|
|
422
422
|
# Publish specific modules and/or workflows by YAML file
|
|
423
|
-
npx cxtms app publish modules/my-module.yaml
|
|
424
|
-
npx cxtms app publish modules/a.yaml workflows/b.yaml
|
|
425
|
-
|
|
426
|
-
# Publish with a custom commit message
|
|
427
|
-
npx cxtms app publish --message "Add warehouse locations module"
|
|
423
|
+
npx cxtms app publish -m "Fix country module" modules/my-module.yaml
|
|
424
|
+
npx cxtms app publish -m "Update billing" modules/a.yaml workflows/b.yaml
|
|
428
425
|
|
|
429
426
|
# Force publish all modules and workflows (not just unpublished ones)
|
|
430
|
-
npx cxtms app publish --force
|
|
427
|
+
npx cxtms app publish -m "Full republish" --force
|
|
431
428
|
|
|
432
429
|
# Publish with explicit org
|
|
433
|
-
npx cxtms app publish --org 42
|
|
430
|
+
npx cxtms app publish -m "Add warehouse locations module" --org 42
|
|
434
431
|
```
|
|
435
432
|
|
|
436
433
|
**What `app publish` does:**
|
|
@@ -327,21 +327,18 @@ Deploy, undeploy, and publish commands are listed in the CLI section at the top
|
|
|
327
327
|
Use `app publish` to push modified workflows and modules from the CX server to a GitHub repository. This creates a branch and pull request — it does NOT push directly to the target branch.
|
|
328
328
|
|
|
329
329
|
```bash
|
|
330
|
-
# Publish all unpublished changes to GitHub (creates a PR)
|
|
331
|
-
npx cxtms app publish
|
|
330
|
+
# Publish all unpublished changes to GitHub (creates a PR) — message is required
|
|
331
|
+
npx cxtms app publish -m "Add order notification workflow"
|
|
332
332
|
|
|
333
333
|
# Publish specific workflows and/or modules by YAML file
|
|
334
|
-
npx cxtms app publish workflows/my-workflow.yaml
|
|
335
|
-
npx cxtms app publish workflows/a.yaml modules/b.yaml
|
|
336
|
-
|
|
337
|
-
# Publish with a custom commit message
|
|
338
|
-
npx cxtms app publish --message "Add order notification workflow"
|
|
334
|
+
npx cxtms app publish -m "Fix tracking workflow" workflows/my-workflow.yaml
|
|
335
|
+
npx cxtms app publish -m "Update shipping" workflows/a.yaml modules/b.yaml
|
|
339
336
|
|
|
340
337
|
# Force publish all workflows and modules (not just unpublished ones)
|
|
341
|
-
npx cxtms app publish --force
|
|
338
|
+
npx cxtms app publish -m "Full republish" --force
|
|
342
339
|
|
|
343
340
|
# Publish with explicit org
|
|
344
|
-
npx cxtms app publish --org 42
|
|
341
|
+
npx cxtms app publish -m "Add order notification workflow" --org 42
|
|
345
342
|
```
|
|
346
343
|
|
|
347
344
|
**What `app publish` does:**
|
package/dist/cli.js
CHANGED
|
@@ -119,6 +119,7 @@ ${chalk_1.default.bold.yellow('COMMANDS:')}
|
|
|
119
119
|
${chalk_1.default.green('publish')} Publish all modules and workflows to a CX server
|
|
120
120
|
${chalk_1.default.green('app')} Manage app manifests (install/upgrade from git, publish to git, list)
|
|
121
121
|
${chalk_1.default.green('query')} Run a GraphQL query against the CX server
|
|
122
|
+
${chalk_1.default.green('gql')} Explore GraphQL schema (types, queries, mutations)
|
|
122
123
|
${chalk_1.default.green('schema')} Show JSON schema for a component or task
|
|
123
124
|
${chalk_1.default.green('example')} Show example YAML for a component or task
|
|
124
125
|
${chalk_1.default.green('list')} List available schemas (modules, workflows, tasks)
|
|
@@ -148,7 +149,7 @@ ${chalk_1.default.bold.yellow('OPTIONS:')}
|
|
|
148
149
|
${chalk_1.default.green('--output <file>')} Save workflow log to file (or -o)
|
|
149
150
|
${chalk_1.default.green('--console')} Print workflow log to stdout
|
|
150
151
|
${chalk_1.default.green('--json')} Download JSON log instead of text
|
|
151
|
-
${chalk_1.default.green('-m, --message <msg>')} Commit message for app publish
|
|
152
|
+
${chalk_1.default.green('-m, --message <msg>')} Commit message for app publish (required)
|
|
152
153
|
${chalk_1.default.green('-b, --branch <branch>')} Branch override for app install/publish
|
|
153
154
|
${chalk_1.default.green('--force')} Force install (even if same version) or publish all
|
|
154
155
|
${chalk_1.default.green('--skip-changed')} Skip modules with unpublished changes during install
|
|
@@ -310,18 +311,15 @@ ${chalk_1.default.bold.yellow('APP COMMANDS:')}
|
|
|
310
311
|
${chalk_1.default.cyan(`${PROGRAM_NAME} app upgrade`)}
|
|
311
312
|
${chalk_1.default.cyan(`${PROGRAM_NAME} app upgrade --force`)}
|
|
312
313
|
|
|
313
|
-
${chalk_1.default.gray('# Publish server changes to git (creates a PR)')}
|
|
314
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish`)}
|
|
315
|
-
|
|
316
|
-
${chalk_1.default.gray('# Publish with a custom commit message')}
|
|
317
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish --message "Add new shipping module"`)}
|
|
314
|
+
${chalk_1.default.gray('# Publish server changes to git (creates a PR) — message is required')}
|
|
315
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Add new shipping module"`)}
|
|
318
316
|
|
|
319
317
|
${chalk_1.default.gray('# Publish specific workflows and/or modules by YAML file')}
|
|
320
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish workflows/my-workflow.yaml`)}
|
|
321
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish workflows/a.yaml modules/b.yaml`)}
|
|
318
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Fix order workflow" workflows/my-workflow.yaml`)}
|
|
319
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Update billing" workflows/a.yaml modules/b.yaml`)}
|
|
322
320
|
|
|
323
321
|
${chalk_1.default.gray('# Force publish all modules and workflows')}
|
|
324
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish --force`)}
|
|
322
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Full republish" --force`)}
|
|
325
323
|
|
|
326
324
|
${chalk_1.default.gray('# List installed app manifests on the server')}
|
|
327
325
|
${chalk_1.default.cyan(`${PROGRAM_NAME} app list`)}
|
|
@@ -336,6 +334,20 @@ ${chalk_1.default.bold.yellow('QUERY COMMANDS:')}
|
|
|
336
334
|
${chalk_1.default.gray('# Pass variables as JSON')}
|
|
337
335
|
${chalk_1.default.cyan(`${PROGRAM_NAME} query my-query.graphql --vars '{"id": 42}'`)}
|
|
338
336
|
|
|
337
|
+
${chalk_1.default.bold.yellow('GRAPHQL SCHEMA EXPLORATION:')}
|
|
338
|
+
${chalk_1.default.gray('# List all queries, mutations, and types')}
|
|
339
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql queries`)}
|
|
340
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql mutations`)}
|
|
341
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql types`)}
|
|
342
|
+
|
|
343
|
+
${chalk_1.default.gray('# Filter by name')}
|
|
344
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql types --filter audit`)}
|
|
345
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql queries --filter order`)}
|
|
346
|
+
|
|
347
|
+
${chalk_1.default.gray('# Inspect a specific type')}
|
|
348
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql type OrderGqlDto`)}
|
|
349
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} gql type AuditChangeEntry`)}
|
|
350
|
+
|
|
339
351
|
${chalk_1.default.bold.yellow('VALIDATION TYPES:')}
|
|
340
352
|
${chalk_1.default.bold('module')} - CargoXplorer UI module definitions (components, routes, entities)
|
|
341
353
|
${chalk_1.default.bold('workflow')} - CargoXplorer workflow definitions (activities, tasks, triggers)
|
|
@@ -3017,6 +3029,12 @@ async function runAppPublish(orgOverride, message, branch, force, targetFiles) {
|
|
|
3017
3029
|
const domain = session.domain;
|
|
3018
3030
|
const token = session.access_token;
|
|
3019
3031
|
const orgId = await resolveOrgId(domain, token, orgOverride);
|
|
3032
|
+
if (!message) {
|
|
3033
|
+
console.error(chalk_1.default.red('Error: --message (-m) is required for app publish'));
|
|
3034
|
+
console.error(chalk_1.default.gray('Describe what changed, similar to a git commit message.'));
|
|
3035
|
+
console.error(chalk_1.default.gray(`Example: ${PROGRAM_NAME} app publish -m "Add new shipping module"`));
|
|
3036
|
+
process.exit(2);
|
|
3037
|
+
}
|
|
3020
3038
|
const appYaml = readAppYaml();
|
|
3021
3039
|
const appManifestId = appYaml.id;
|
|
3022
3040
|
if (!appManifestId) {
|
|
@@ -3188,6 +3206,169 @@ async function runQuery(queryArg, variables) {
|
|
|
3188
3206
|
console.log(JSON.stringify(data, null, 2));
|
|
3189
3207
|
}
|
|
3190
3208
|
// ============================================================================
|
|
3209
|
+
// GQL Schema Exploration Command
|
|
3210
|
+
// ============================================================================
|
|
3211
|
+
async function runGql(sub, filter) {
|
|
3212
|
+
if (!sub) {
|
|
3213
|
+
console.error(chalk_1.default.red('Error: subcommand required'));
|
|
3214
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql <queries|mutations|types|type> [name] [--filter <text>]`));
|
|
3215
|
+
process.exit(2);
|
|
3216
|
+
}
|
|
3217
|
+
const session = resolveSession();
|
|
3218
|
+
if (sub === 'type') {
|
|
3219
|
+
if (!filter) {
|
|
3220
|
+
console.error(chalk_1.default.red('Error: type name required'));
|
|
3221
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql type <TypeName>`));
|
|
3222
|
+
process.exit(2);
|
|
3223
|
+
}
|
|
3224
|
+
await runGqlType(session, filter);
|
|
3225
|
+
}
|
|
3226
|
+
else if (sub === 'queries') {
|
|
3227
|
+
await runGqlRootFields(session, 'queryType', filter);
|
|
3228
|
+
}
|
|
3229
|
+
else if (sub === 'mutations') {
|
|
3230
|
+
await runGqlRootFields(session, 'mutationType', filter);
|
|
3231
|
+
}
|
|
3232
|
+
else if (sub === 'types') {
|
|
3233
|
+
await runGqlTypes(session, filter);
|
|
3234
|
+
}
|
|
3235
|
+
else {
|
|
3236
|
+
console.error(chalk_1.default.red(`Unknown gql subcommand: ${sub}`));
|
|
3237
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql <queries|mutations|types|type> [--filter <text>]`));
|
|
3238
|
+
process.exit(2);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
function formatGqlType(t) {
|
|
3242
|
+
if (!t)
|
|
3243
|
+
return 'unknown';
|
|
3244
|
+
if (t.kind === 'NON_NULL')
|
|
3245
|
+
return `${formatGqlType(t.ofType)}!`;
|
|
3246
|
+
if (t.kind === 'LIST')
|
|
3247
|
+
return `[${formatGqlType(t.ofType)}]`;
|
|
3248
|
+
return t.name || 'unknown';
|
|
3249
|
+
}
|
|
3250
|
+
async function runGqlType(session, typeName) {
|
|
3251
|
+
const query = `{
|
|
3252
|
+
__type(name: "${typeName}") {
|
|
3253
|
+
name kind description
|
|
3254
|
+
fields { name description type { name kind ofType { name kind ofType { name kind ofType { name kind } } } } args { name type { name kind ofType { name kind ofType { name kind } } } defaultValue } }
|
|
3255
|
+
inputFields { name type { name kind ofType { name kind ofType { name kind } } } defaultValue }
|
|
3256
|
+
enumValues { name description }
|
|
3257
|
+
}
|
|
3258
|
+
}`;
|
|
3259
|
+
const data = await graphqlRequest(session.domain, session.access_token, query, {});
|
|
3260
|
+
const type = data.__type;
|
|
3261
|
+
if (!type) {
|
|
3262
|
+
console.error(chalk_1.default.red(`Type "${typeName}" not found`));
|
|
3263
|
+
process.exit(1);
|
|
3264
|
+
}
|
|
3265
|
+
console.log(chalk_1.default.bold.cyan(`${type.name}`) + chalk_1.default.gray(` (${type.kind})`));
|
|
3266
|
+
if (type.description)
|
|
3267
|
+
console.log(chalk_1.default.gray(type.description));
|
|
3268
|
+
console.log('');
|
|
3269
|
+
if (type.fields && type.fields.length > 0) {
|
|
3270
|
+
console.log(chalk_1.default.bold.yellow('Fields:'));
|
|
3271
|
+
for (const f of type.fields) {
|
|
3272
|
+
const typeStr = formatGqlType(f.type);
|
|
3273
|
+
let line = ` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(typeStr)}`;
|
|
3274
|
+
if (f.args && f.args.length > 0) {
|
|
3275
|
+
const argsStr = f.args.map((a) => {
|
|
3276
|
+
const argType = formatGqlType(a.type);
|
|
3277
|
+
return a.defaultValue ? `${a.name}: ${argType} = ${a.defaultValue}` : `${a.name}: ${argType}`;
|
|
3278
|
+
}).join(', ');
|
|
3279
|
+
line += chalk_1.default.gray(` (${argsStr})`);
|
|
3280
|
+
}
|
|
3281
|
+
if (f.description)
|
|
3282
|
+
line += chalk_1.default.gray(` — ${f.description}`);
|
|
3283
|
+
console.log(line);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
if (type.inputFields && type.inputFields.length > 0) {
|
|
3287
|
+
console.log(chalk_1.default.bold.yellow('Input Fields:'));
|
|
3288
|
+
for (const f of type.inputFields) {
|
|
3289
|
+
const typeStr = formatGqlType(f.type);
|
|
3290
|
+
let line = ` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(typeStr)}`;
|
|
3291
|
+
if (f.defaultValue)
|
|
3292
|
+
line += chalk_1.default.gray(` = ${f.defaultValue}`);
|
|
3293
|
+
console.log(line);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
if (type.enumValues && type.enumValues.length > 0) {
|
|
3297
|
+
console.log(chalk_1.default.bold.yellow('Enum Values:'));
|
|
3298
|
+
for (const v of type.enumValues) {
|
|
3299
|
+
let line = ` ${chalk_1.default.green(v.name)}`;
|
|
3300
|
+
if (v.description)
|
|
3301
|
+
line += chalk_1.default.gray(` — ${v.description}`);
|
|
3302
|
+
console.log(line);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
async function runGqlRootFields(session, rootType, filter) {
|
|
3307
|
+
const query = `{
|
|
3308
|
+
__schema {
|
|
3309
|
+
${rootType} {
|
|
3310
|
+
fields { name description args { name type { name kind ofType { name kind ofType { name kind } } } defaultValue } type { name kind ofType { name kind ofType { name kind } } } }
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
}`;
|
|
3314
|
+
const data = await graphqlRequest(session.domain, session.access_token, query, {});
|
|
3315
|
+
const fields = data.__schema?.[rootType]?.fields || [];
|
|
3316
|
+
const filtered = filter
|
|
3317
|
+
? fields.filter((f) => f.name.toLowerCase().includes(filter.toLowerCase()))
|
|
3318
|
+
: fields;
|
|
3319
|
+
const label = rootType === 'queryType' ? 'Queries' : 'Mutations';
|
|
3320
|
+
console.log(chalk_1.default.bold.yellow(`${label}${filter ? ` (filter: "${filter}")` : ''}:`));
|
|
3321
|
+
console.log('');
|
|
3322
|
+
for (const f of filtered) {
|
|
3323
|
+
const returnType = formatGqlType(f.type);
|
|
3324
|
+
console.log(` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(returnType)}`);
|
|
3325
|
+
if (f.description)
|
|
3326
|
+
console.log(` ${chalk_1.default.gray(f.description)}`);
|
|
3327
|
+
if (f.args && f.args.length > 0) {
|
|
3328
|
+
for (const a of f.args) {
|
|
3329
|
+
const argType = formatGqlType(a.type);
|
|
3330
|
+
const def = a.defaultValue ? chalk_1.default.gray(` = ${a.defaultValue}`) : '';
|
|
3331
|
+
console.log(` ${chalk_1.default.white(a.name)}: ${chalk_1.default.cyan(argType)}${def}`);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
console.log('');
|
|
3335
|
+
}
|
|
3336
|
+
console.log(chalk_1.default.gray(`${filtered.length} ${label.toLowerCase()} found`));
|
|
3337
|
+
}
|
|
3338
|
+
async function runGqlTypes(session, filter) {
|
|
3339
|
+
const query = `{
|
|
3340
|
+
__schema {
|
|
3341
|
+
types { name kind description }
|
|
3342
|
+
}
|
|
3343
|
+
}`;
|
|
3344
|
+
const data = await graphqlRequest(session.domain, session.access_token, query, {});
|
|
3345
|
+
const types = (data.__schema?.types || [])
|
|
3346
|
+
.filter((t) => !t.name.startsWith('__'))
|
|
3347
|
+
.filter((t) => !filter || t.name.toLowerCase().includes(filter.toLowerCase()));
|
|
3348
|
+
const grouped = {};
|
|
3349
|
+
for (const t of types) {
|
|
3350
|
+
const kind = t.kind || 'OTHER';
|
|
3351
|
+
if (!grouped[kind])
|
|
3352
|
+
grouped[kind] = [];
|
|
3353
|
+
grouped[kind].push(t);
|
|
3354
|
+
}
|
|
3355
|
+
const kindOrder = ['OBJECT', 'INPUT_OBJECT', 'ENUM', 'INTERFACE', 'UNION', 'SCALAR'];
|
|
3356
|
+
for (const kind of kindOrder) {
|
|
3357
|
+
const items = grouped[kind];
|
|
3358
|
+
if (!items || items.length === 0)
|
|
3359
|
+
continue;
|
|
3360
|
+
console.log(chalk_1.default.bold.yellow(`${kind} (${items.length}):`));
|
|
3361
|
+
for (const t of items.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3362
|
+
let line = ` ${chalk_1.default.green(t.name)}`;
|
|
3363
|
+
if (t.description)
|
|
3364
|
+
line += chalk_1.default.gray(` — ${t.description}`);
|
|
3365
|
+
console.log(line);
|
|
3366
|
+
}
|
|
3367
|
+
console.log('');
|
|
3368
|
+
}
|
|
3369
|
+
console.log(chalk_1.default.gray(`${types.length} types found${filter ? ` matching "${filter}"` : ''}`));
|
|
3370
|
+
}
|
|
3371
|
+
// ============================================================================
|
|
3191
3372
|
// Extract Command
|
|
3192
3373
|
// ============================================================================
|
|
3193
3374
|
function runExtract(sourceFile, componentName, targetFile, copy) {
|
|
@@ -3398,7 +3579,7 @@ function parseArgs(args) {
|
|
|
3398
3579
|
reportFormat: 'json'
|
|
3399
3580
|
};
|
|
3400
3581
|
// Check for commands
|
|
3401
|
-
const commands = ['validate', 'schema', 'example', 'list', 'help', 'version', 'report', 'init', 'create', 'extract', 'sync-schemas', 'install-skills', 'update', 'setup-claude', 'login', 'logout', 'pat', 'appmodule', 'orgs', 'workflow', 'publish', 'query', 'app'];
|
|
3582
|
+
const commands = ['validate', 'schema', 'example', 'list', 'help', 'version', 'report', 'init', 'create', 'extract', 'sync-schemas', 'install-skills', 'update', 'setup-claude', 'login', 'logout', 'pat', 'appmodule', 'orgs', 'workflow', 'publish', 'query', 'gql', 'app'];
|
|
3402
3583
|
if (args.length > 0 && commands.includes(args[0])) {
|
|
3403
3584
|
command = args[0];
|
|
3404
3585
|
args = args.slice(1);
|
|
@@ -3509,6 +3690,9 @@ function parseArgs(args) {
|
|
|
3509
3690
|
options.file = [];
|
|
3510
3691
|
options.file.push(args[++i]);
|
|
3511
3692
|
}
|
|
3693
|
+
else if (arg === '--filter') {
|
|
3694
|
+
options.filter = args[++i];
|
|
3695
|
+
}
|
|
3512
3696
|
else if (arg === '--force') {
|
|
3513
3697
|
options.force = true;
|
|
3514
3698
|
}
|
|
@@ -4435,6 +4619,14 @@ async function main() {
|
|
|
4435
4619
|
}
|
|
4436
4620
|
process.exit(0);
|
|
4437
4621
|
}
|
|
4622
|
+
// Handle gql command (no schemas needed)
|
|
4623
|
+
if (command === 'gql') {
|
|
4624
|
+
const sub = files[0];
|
|
4625
|
+
// For 'gql type <name>', the type name is in files[1] — use it as filter
|
|
4626
|
+
const filterArg = sub === 'type' ? (files[1] || options.filter) : options.filter;
|
|
4627
|
+
await runGql(sub, filterArg);
|
|
4628
|
+
process.exit(0);
|
|
4629
|
+
}
|
|
4438
4630
|
// Handle query command (no schemas needed)
|
|
4439
4631
|
if (command === 'query') {
|
|
4440
4632
|
await runQuery(files[0], options.vars);
|