Skip to content

Latest commit

 

History

History
44 lines (33 loc) · 5.97 KB

File metadata and controls

44 lines (33 loc) · 5.97 KB

Noridoc: nori-shopify-cli

Path: @/

Overview

  • Agent-oriented CLI (nori-shopify) for the Shopify Admin GraphQL API: shop info, orders, customers, products, inventory, discounts, ShopifyQL analytics, and a raw GraphQL passthrough escape hatch.
  • All output is pretty-printed JSON; all parameters are flags; no colors, no interactivity. Help text and error messages always print the source directory so an agent can read the implementation when confused.
  • The behavioral spec lives in @/APPLICATION_SPEC.md — it is the deeper reference for command-to-GraphQL mappings, required access scopes, and error-layer semantics.

How it fits into the larger codebase

  • Independent git repo inside the tilework-tech workspace, built so AI agents in Nori sessions can read and manage arbitrary Shopify stores. Intended for npm publication as nori-shopify-cli (bin nori-shopify); not yet published.
  • Structurally mirrors the sibling nori-luma-cli repo, which served as the template: same dependency-injection program factory, Output abstraction, agentic help/error conventions, and mock-service test harness.
  • Talks to exactly one external system: the Shopify Admin GraphQL endpoint at https://{shop}.myshopify.com/admin/api/{version}/graphql.json, authenticated with a custom-app Admin token (shpat_...) via the X-Shopify-Access-Token header.
  • Configuration comes entirely from environment variables (SHOPIFY_SHOP, SHOPIFY_ADMIN_TOKEN, optional SHOPIFY_API_VERSION, default pinned in @/src/config.ts). There is no config file or stored state.

Core Implementation

src/index.ts (entry, only try/catch)
   └─ loadConfig (src/config.ts)          ← env vars
   └─ createShopifyService (src/services/shopify.ts)  ← fetch → Shopify Admin GraphQL
   └─ createProgram(shopify, out) (src/program.ts)    ← commander wiring
         └─ src/commands/*.ts             ← one file per command group
  • @/src/index.ts is the entry point. --help, --version, and bare invocation are detected before config loading and run with a placeholder config, so help works without credentials. The top-level try/catch is the only error boundary: it prints String(err) to stderr and sets exit code 1. All intermediate layers let errors bubble.
  • Dependency injection: createProgram(shopify, out) in @/src/program.ts takes the ShopifyService interface and an Output interface (write/error/setExitCode, defined in @/src/output.ts). Tests drive the real commander program with a mock service and capture output without spawning processes.
  • configureCommandOutput in @/src/program.ts recursively configures every command and subcommand: colors disabled, writeOut/writeErr routed through Output, errors append "Look at the source at {sourceDir}", help appends "Source: {sourceDir}", plus showHelpAfterError and showSuggestionAfterError. This is the agentic-CLI pattern — failures always tell the agent where the source lives.
  • @/src/services/shopify.ts holds both the ShopifyService interface (with all result and param types) and createShopifyService. It normalizes SHOPIFY_SHOP (bare subdomain, domain, or https URL all accepted) to a myshopify.com endpoint and POSTs GraphQL documents with embedded field-selection constants.
  • Command files in @/src/commands/ (e.g. orders, products, graphql) translate flags to service params and JSON.stringify the result. Shared flag parsers (parseIntStrict, parseJSON, etc.) live in @/src/parse.ts; toGid in @/src/gid.ts wraps bare numeric ids into full GIDs (123gid://shopify/Order/123) before the service call, while full GIDs pass through unchanged.
  • Wire-shape mapping: GraphQL connection sub-lists (order lineItems, product variants, inventory levels) come back as {nodes: [...]} and are flattened to plain arrays before output. discountNodes returns a union of code/automatic discount types via inline fragments; these are flattened to a uniform DiscountNode whose discountType is the GraphQL __typename.
  • Validation that is business logic rather than wire format lives in the command layer: discounts create-basic-code in @/src/commands/discounts.ts enforces exactly-one-of --percentage/--amount, requires --currency with --amount, and converts --percentage 10 to the fractional 0.1 the API expects.

Things to Know

  • Shopify returns most errors as HTTP 200. The service checks three layers in @/src/services/shopify.ts: HTTP non-2xx status; top-level errors[] in the JSON body (messages joined, extensions.code like THROTTLED appended); and mutation userErrors[] (mapped to field.path: message and thrown). Null single-object lookups (e.g. order(id) returning null) are converted to "not found" errors rather than printing null.
  • The API version is pinned via SHOPIFY_API_VERSION (default 2026-04 in @/src/config.ts). Shopify versions quarterly; field deprecations are version-dependent. Customer.email is deprecated, so the CLI reads defaultEmailAddress/defaultPhoneNumber instead.
  • Scope-dependent behavior surfaces as API errors, not CLI errors: orders older than 60 days need the read_all_orders scope, and analytics query (the shopifyqlQuery field) needs read_reports. ShopifyQL parse errors come back in-band in the parseErrors field with exit code 0 — they are data, not thrown errors.
  • Test architecture in @/tests/: createMockShopifyService in @/tests/helpers.ts is a stateful in-memory implementation (Maps plus last*Params recording) and runCommand runs the real commander program with exitOverride, so command tests assert exact stdout/stderr/exit codes. @/tests/services/shopify.test.ts stubs global fetch to assert exact wire shapes (endpoint URL, headers, GraphQL variables). @/tests/cli-startup.test.ts spawns the CLI via tsx to verify help works without credentials.
  • The graphql command (@/src/commands/graphql.ts) is a deliberate escape hatch: any Admin API operation not covered by the typed commands can be run raw, returning the unwrapped data payload.

Created and maintained by Nori.