Skip to content

BEX-470 refactor(config): normalize app-config.json keys to snake_case with legacy migration - #104

Merged
piyushsarin-sib merged 6 commits into
features_set-dp-functionfrom
fix/app-config-snake-case-keys
Sep 4, 2026
Merged

BEX-470 refactor(config): normalize app-config.json keys to snake_case with legacy migration#104
piyushsarin-sib merged 6 commits into
features_set-dp-functionfrom
fix/app-config-snake-case-keys

Conversation

@piyushsarin-sib

@piyushsarin-sib piyushsarin-sib commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Normalizes every key in app-config.json to snake_case, matching the wire contract. The file was meant to be snake_case throughout, but several keys were written in camelCase.

Before After
appId app_id
appName app_name
logoUri logo_uri
appType app_type
auth.redirectUris auth.redirect_uris

version, distribution_type, auth.scopes, ui_app and brevo_function are unchanged. Nothing sent to or received from the Brevo API changes — this is only the local file contract.

Jira: BEX-470. Base branch: features_set-dp-function (PR #100) — this PR carries only the snake_case change on top of it.

Backward compatibility guarantee

  • Every command still reads the old camelCase keys (and the even older auth.redirectUrls). The folding happens in one place, readProjectConfigAt in src/lib/config.ts, via two alias tables (LEGACY_KEY_ALIASES, LEGACY_AUTH_KEY_ALIASES) — no per-command checks.
  • Mixed files: when both spellings of a key are present with different values, the snake_case one wins and a one-line notice goes to stderr (never stdout, so --json stays a single parseable document). Identical values are folded silently. The notice fires once per process.
  • ProjectConfig is the file shape. The interface fields were renamed rather than translated at a boundary, so the compiler found every read site and writeProjectConfig emits snake_case by construction.

Migration on write

  • brevo app create, brevo app upload, brevo app scaffold and brevo app start now write snake_case keys only; the camelCase copies are dropped.
  • brevo app upload (the "already up to date" path) and brevo app scaffold (the no-drift path) previously wrote nothing, so an in-sync legacy file would never have been migrated. Both now call migrateProjectConfigKeys() there — a pure key rewrite, values untouched — and print a one-line note in human mode. One run of either command is enough to migrate a project.
  • No separate migrate command.

Nothing breaks functionally

  • app upload's local-vs-server diff compares normalized values, so a rename alone is not a change.
  • app install's drift check, app start's port/redirect resolution, app status/delete/withdraw/init's linked-app lookup all read through the same normalizer and were covered by the compiler-driven rename.
  • --json output: the only place that echoed config key names was brevo app scaffold's diffs[].field, which now reports app_name / redirect_uris / logo_uri. Every other command's --json keys (appId, appName, logoUri, …) are unchanged — those are output contracts, not config keys. Documented in the changeset.
  • upload's raw-file guard accepts either app_id or appId.

Users with custom code, and agents

  • Whenever a legacy file is rewritten — on the upload no-op path, after an upload push, on either scaffold path — the CLI prints one line naming the new keys and saying: If your own scripts read this file, update them to the new key names.
  • agent-context/SKILL.md (Hard rules) and agent-context/AGENTS.md (Conventions) carry an explicit migration rule: keys are snake_case, the CLI still reads camelCase but rewrites on every write, tell the user to switch custom code, never write camelCase into a file, and --json output keys are a separate unchanged contract.

Tests

  • config.test.ts: new legacy camelCase keys suite — camelCase-only, snake_case-only, mixed (snake wins + single stderr warning), write-back migration for OAuth / UI / Function apps, hasLegacyProjectConfigKeys for every legacy key, migrateProjectConfigKeys idempotency and no-op cases. The app_type suite was reworked and a legacy-shaped fixture is kept deliberately.
  • upload.test.ts / scaffold.test.ts: migration on the no-op paths, silence when nothing migrated, --json stays one document, legacy appId accepted by the raw guard.
  • All existing fixtures moved to snake_case; command option objects and --json assertions left as they were.

Docs

agent-context/SKILL.md, agent-context/AGENTS.md, README.md, CLAUDE.md, the scaffold templates (app-config.json.tmpl, README.md.tmpl, CLAUDE.md.tmpl, AGENTS.md.tmpl), and the smoke scripts (which now accept both spellings so against=published runs keep passing).

Changeset

Folded into the branch's single pending changeset, .changeset/function-app-type-registry.md (minor), per the one-changeset-per-branch rule; its appType mention now reads app_type.

Checks

  • yarn lint
  • yarn test:ci ✅ (70 suites, 1587 tests)
  • yarn build
  • yarn format:check

🤖 Generated with Claude Code

piyushsarin-sib and others added 5 commits September 4, 2026 15:44
…egacy migration

Rename the camelCase keys in app-config.json to snake_case, matching the
wire contract: appId → app_id, appName → app_name, logoUri → logo_uri,
appType → app_type, auth.redirectUris → auth.redirect_uris. The
ProjectConfig interface now IS the file shape, so every read site was
found by the compiler.

Backward compatibility: readProjectConfigAt folds the legacy camelCase
keys (and the older auth.redirectUrls) through one alias table, drops
them from the returned object, and warns once on stderr when a file
carries both spellings with different values.

Migration on write: create/upload/scaffold/start now emit snake_case
only. upload and scaffold also rewrite an in-sync legacy file on their
no-op paths via migrateProjectConfigKeys(), so one run migrates a
project regardless of drift. Values are never changed.

Also: template, scaffold docs, agent-context docs, README, smoke scripts
(accept both spellings so published-build runs still pass), tests for
camelCase-only / snake_case-only / mixed reads and the write-back
migration, and a changeset.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…igration note

Print the snake_case migration notice whenever a legacy app-config.json is
rewritten — after an upload push and after a consented scaffold refresh,
not only on the no-op paths — and word it so users know to update their
own scripts that read the file. Add an explicit migration rule to
agent-context/SKILL.md and AGENTS.md so agents steer custom code to the
new key names and never write the camelCase names.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pin the ordering `assertAppTypeAgrees` documents: a config that is BOTH
mislabelled and structurally broken must report the structural problem, so a
partner is never sent off to fix a label only to hit the real refusal on the
next upload. Asserted positively (the entry-named `.label:` error) plus an
explicit check that the message does not mention `app_type`, rather than via
`rejects.not.toThrow`, which can pass for the wrong reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`app upload` now reads `app_type` to check it agrees with the blocks, so three
docs that said the CLI never reads it were wrong as written.

- CLAUDE.md: read for validation, never for detection; the pre-flight ordering
  and why it is last; and the wire decision, which was nowhere stated — the
  field is file-only because `UploadAppPayload` and the create body are closed
  structs built key by key, so nothing strips it and bo-be needs no change.
- The scaffolded AGENTS.md template said "the CLI never reads it". It now says
  what the field is for and that a hand-edited block needs the label updated.
- agent-context/SKILL.md and AGENTS.md documented the key but not its meaning;
  both now carry the same paragraph, kept in sync per the repo rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…changeset

CLAUDE.md asks for one changeset file per branch, appended to rather than
multiplied. Merges the separate snake_case file into the existing one and adds
the app_type agreement check. Bump level stays `minor` — both entries already
warranted it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@piyushsarin-sib
piyushsarin-sib changed the base branch from main to features_set-dp-function September 4, 2026 10:23
@piyushsarin-sib piyushsarin-sib changed the title refactor(config): normalize app-config.json keys to snake_case with legacy migration BEX-470 refactor(config): normalize app-config.json keys to snake_case with legacy migration Sep 4, 2026
…e-keys

Resolve conflicts from the base's appType → app_type revert (1d90504,
dae10cf): keep this branch's ProjectConfig doc block, changeset text,
AGENTS template wording and test fixtures, keep the base's three
appType-migration tests, and drop the base's one-off appType fold in
readProjectConfigAt — LEGACY_KEY_ALIASES already covers it with the
other camelCase keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@piyushsarin-sib
piyushsarin-sib merged commit be7d9cf into features_set-dp-function Sep 4, 2026
2 checks passed
@piyushsarin-sib
piyushsarin-sib deleted the fix/app-config-snake-case-keys branch September 4, 2026 10:39
piyushsarin-sib added a commit that referenced this pull request Sep 4, 2026
* feat: add Brevo Function commands with smoke tests

Add `brevo function list` and `brevo function get <id>` commands for
managing Brevo Functions, plus the "Brevo Function" app type choice
in `brevo app create`.

- New service (src/services/function.ts) with list, draft list, and get
- New command handlers (src/commands/function/list.ts, get.ts)
- Register functionCommandGroup in definitions.ts and bin/index.ts
- Add Function commands section to root help screen
- Add brevo_function template flag for app-config.json rendering
- Add Brevo Function app type to interactive create prompt (private only)
- Unit tests for service, list command, and get command (23 tests)
- Command registration tests in definitions.test.ts (4 tests)
- Help formatting tests updated for functionCommandGroup
- New smoke suite (scripts/smoke/function.ts) exercising list, list
  --draft, get, and get-not-found against a real account — opt-in via
  `yarn smoke --suite=function`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: gate Brevo Function behind __BREVO_PREVIEW__ and add fn alias

- Add `brevo-function-type` to FEATURE_STAGE as a preview feature
- Move function group definition to preview-definitions.ts so esbuild
  can eliminate it from published builds
- Gate the Brevo Function choice in `app create` behind __BREVO_PREVIEW__
- Gate the Function commands section in help.ts behind __BREVO_PREVIEW__
- Add `aliases` support to SubcommandGroupDefinition so `brevo fn list`
  and `brevo fn get` work as shortcuts
- Update tests for the conditional export and gated app-type prompt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add brevo function activate, deactivate and delete commands

Add three management commands to the Brevo Function group:
- `brevo function activate <id>` — PATCH with is_active: true
- `brevo function deactivate <id>` — PATCH with is_active: false
- `brevo function delete <id>` — DELETE with --force to skip confirmation

All three are gated behind __BREVO_PREVIEW__ alongside the existing
list and get commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add brevo fn init command with AI generation, template flow, and SSE error handling

- Add `brevo fn init` command with two creation flows: AI generation and global templates
- Implement SSE streaming for AI code generation and iteration with colored progress stages
- Add preview execution (fetch contacts + execute + results table) after generation and iteration
- Auto-derive `attribute_id` from function name in SCREAMING_SNAKE_CASE
- Add graceful error handling for SSE stream termination and API failures
- Add shared function app selector (`select-function.ts`) used by fn subcommands
- Update all fn subcommands to use shared app selector
- Set `source: 'cli'` on all dp-functions API calls
- Wrap SSE reader.read() errors into ApiError for clean failure messages
- Add comprehensive tests for init command and SSE stream module

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use draft_id instead of code for post-iterate execute preview

The backend updates the draft in the database during iterate (PATCH
/generate/stream) in all auth modes, so referencing the draft by ID
is consistent with the initial preview and avoids sending the full
code payload to the execute endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show friendly message when Brevo Functions is not enabled (403)

The dp-functions backend returns 403 with code `feature_not_enabled`
when the account lacks the dp-functions entitlement. Map this to a
user-friendly message in both the REST client (ApiClient) and the
SSE stream handler so all fn subcommands display:

  "Brevo Functions is not enabled for this account."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address SonarCloud issues — reduce complexity, fix nested ternaries

- sse-stream.ts: extract performSSEFetch, handleSSEErrorResponse, readChunk,
  processSSELine, flushSSEState helpers to reduce cognitive complexity from 49
  to ~11; replace nested ternary with extractErrorMessage function
- init.ts: extract executePreview, saveGeneratedFunction, mergeGenerateResult,
  updateSpinnerFromEvent, accumulateResult helpers to reduce cognitive complexity
  of processGenerateStream (38→~8) and aiGenerationFlow (40→~11); combine
  consecutive push calls into single push with multiple args
- list.ts: extract inner template literals to variables to eliminate nesting
- templates/index.ts: replace nested ternary with if/else in resolveTemplateFlags
- create.ts: replace nested ternary with if/else in buildCreatePayload; extract
  cacheCreatedAppCredentials to reduce createCommand complexity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address remaining SonarCloud warnings

- Parameterize similar tests in sse-stream.test.ts and definitions.test.ts (S5976)
- Add formatCellValue helper for safe object stringification in init.ts (S6551)
- Replace boolean statusBadge function with ACTIVE_BADGE/INACTIVE_BADGE constants in list.ts (S2301)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: fix last S6551 warning in formatCellValue (init.ts)

Use explicit type checks (string, number, boolean) instead of
String(value) on unknown, so SonarCloud can verify no object
reaches the default toString path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: hide attribute_id from predefined template preview table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove Attribute ID line from template preview output

The Attribute ID was shown both as a header line and could appear in the
results table. Remove the explicit header line so the preview only shows
the description and the data table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR #69 review comments

- Use shared functionService from container instead of local instance (init.ts)
- Wire STAGE_LABELS to lang file keys instead of hardcoded strings
- Rethrow ApiError/CliError in generate and iterate catch blocks
- Guard iterate request and preview against missing draftId
- Match duplicate-name detection on HTTP 409 status code, not message copy
- Union columns across all rows in printResultsTable
- Document AbortSignal.timeout as a deadline in sse-stream.ts
- Evaluate statusBadge at call time to respect TTY/NO_COLOR (list.ts)
- Change --id [id] to --id <id> in all fn subcommands
- Remove orphaned lang keys (stage labels, success messages, file-written)
- Hoist distribution_type out of template branches (app-config.json.tmpl)
- Remove unused src/__tests__/tsconfig.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve SonarCloud issues — cognitive complexity and boolean param

- init.ts: extract `runIterateRound` and `tryPreview` from
  `aiGenerationFlow` to reduce cognitive complexity from 21 to
  within the 15 threshold (typescript:S3776).
- list.ts: replace `statusBadge(isActive)` boolean-param function
  with two named functions `activeBadge()` / `inactiveBadge()`
  (typescript:S2301).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add `brevo fn deploy` command for deploying draft functions

Add a standalone deploy command that lets users deploy draft Brevo
Functions independently of the `brevo fn init` AI generation flow.

- New `brevo function deploy` command with `--id` and `--json` flags
- Interactive draft picker when `--id` is omitted
- Preview with sample contacts before deployment
- Name prompt with duplicate-name (409) retry loop
- Stop flow with "Unable to deploy function" when preview returns errors
- Extract shared preview-table utilities from init.ts into preview-table.ts
- 11 test cases covering success, JSON, picker, errors, and edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve SonarCloud issues in deploy.ts — cognitive complexity and boolean param

- Extract `deployJsonMode`, `deployInteractive`, and `tryPreview` from
  the main handler to reduce cognitive complexity (typescript:S3776).
- Replace `assertDraftSelectionAllowed(jsonMode?)` boolean-param function
  with `assertInteractiveTerminal()` that only checks TTY (typescript:S2301).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace backtracking regex with lastIndexOf in deriveNameFromDescription

Sonar flagged `/\s+\S*$/` as super-linear due to backtracking.
Use `lastIndexOf(' ', 50)` instead — same word-boundary cut, O(n).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: link deployed function to app after creation

Add app-linking step to `function deploy` and `function init` flows.
After a function is created, it is linked to the selected app via
POST /v3/app-store/app-functions. Adds --app-id flag to deploy for
non-interactive use, and an interactive app picker when omitted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR #85 review — shared module, JSON safety, tests

- Extract selectFunctionApp/tryLinkFunctionToApp into select-app.ts,
  used by both deploy.ts and init.ts (review point 3)
- Fix --json output corruption: link runs silent, spinner uses
  { silent: true }, no logInfo to stdout (point 1)
- Add linked/app_id to JSON payload so callers can detect link
  status (point 2)
- Move app picker after preview+confirm to avoid wasted round-trips
  on early failure (point 4)
- Remove appId! non-null assertion — narrowed via control flow (point 5)
- Log link error detail behind isDebug via logDebug (point 6)
- Add 5 new tests: --app-id path, --json without --app-id, link
  failure in JSON mode, no-apps throw, link failure warning (point 7)
- Fix template literal + concatenation mix in app name display (point 8)
- Add changeset for new --app-id flag (point 8)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared deploy helpers to reduce code duplication

Move isDuplicateNameError, executePreview, tryPreview, and the
name-confirm-deploy loop into deploy-helpers.ts. Both deploy.ts
and init.ts now delegate to these shared functions, parameterized
by message constants. This addresses the SonarCloud duplication
quality gate (11.2% → well below 3%).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove changeset file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve SonarCloud duplication and nested template literal

- Generalize executePreview to accept templateArgs (draft_id or
  template_id) so init.ts templateFlow uses the shared function
  instead of an inline duplicate.
- Fix nested template literal in select-app.ts (S4624).
- Extract test setup into setupHappyPath() helper to reduce
  duplicated mock boilerplate across 16 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: remove preview gate from brevo function commands (GA)

Move the `brevo function` surface out of the `__BREVO_PREVIEW__` build
gate so it ships in every published build. This follows the same
pattern as the UI-apps GA transition (BEX-290).

Changes:
- Flip FEATURE_STAGE['brevo-function-type'] from 'preview' to 'ga'
- Move function group definition from preview-definitions.ts to
  definitions.ts (always-defined, no longer conditional)
- Add function handler imports directly in definitions.ts
- Remove __BREVO_PREVIEW__ guard from bin/index.ts, help.ts, create.ts
- Move all FUNCTION_* strings from preview-messages.ts to en.ts
- Add GA markers (listFunctionCommand, initFunctionCommand,
  deployFunctionCommand) to scripts/build.mjs
- Update tests to reflect the GA state

Verified: yarn test (1513 pass), yarn build (public), PREVIEW=1 yarn
build (preview), yarn lint, yarn format:check — all clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add function suite to smoke test live surfaces

Include `function` in the smoke test's default and pinned suites so the
GA'd Brevo Function commands are exercised on every publish and pre-merge
lane — matching the CLAUDE.md requirement that a GA'd feature joins the
smoke test in the same PR.

- scripts/smoke-test.ts: add `function` to DEFAULT_SUITES
- smoke.yml: update workflow_call/dispatch defaults and options
- smoke-post-merge.yml: widen the pinned suite to `private,ui,function`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: expand function smoke suite with mutation steps, init flow, and dedicated account

Wire BREVO_TEST_API_KEY_FUNCTION secret and split function suite into its
own CI job so it runs against a dedicated dp-functions account. Add
activate/deactivate cycle, not-found error probes, deploy+cleanup, and
pty-driven function init (template path) with automatic cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve SonarCloud duplication in function smoke not-found probes

Extract shared assertNotFound() helper from the four identical
not-found error probe functions, bringing new duplicated lines
density well under the 3% quality gate threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve SonarCloud duplication in function action commands

Extract shared function state-change pattern (ID resolution, spinner,
404 handling, JSON/card output) into buildFunctionActionCommand helper.
Activate and deactivate become thin config wrappers; delete delegates
execution while keeping its confirmation prompt. Shared logic is tested
once in function-action.test.ts, individual tests verify wiring only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reduce SonarCloud duplication across function commands and smoke tests

Extract resolveFunctionId and withNotFoundHandling helpers for get.ts,
deduplicate smoke test cleanup with deleteAndAssert and toggle helpers,
and remove unused import flagged by code-quality bot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add app_type field to app-config.json template

Add explicit `app_type` field ('oauth', 'ui', or 'function') to the
scaffolded app-config.json so the app type is immediately visible
without relying solely on the presence of discriminator blocks.

Addresses PR #100 review comment by @piyush.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: address PR #100 review comments

- Add `function` to app-type registry with positive `brevo_function`
  detection on both config and record paths
- Add `function` row to capability matrix (no OAuth flow, no redirect
  URIs, no account-install)
- Add `brevo_function` field to `OAuthApp` type and `AppRecordLike`
- Add `brevo function deploy` to help section
- Replace IIFE with `resolveFunctionId` in `executeFunctionAction`
- Add pagination to `fetchFunctionList` for >50 functions
- Wire `ensureFresh` into `SSEStreamDeps` for token refresh before
  SSE connections
- Expose `runEnsureFresh()` on `ApiClient`, plumb through container
- Fix comment in templates/index.ts (`brevo_function` is mutually
  exclusive with oauth, not orthogonal)
- Add explanatory comments in init.ts, tsconfig.eslint.json, help.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add backward compatibility and smoke tests for app_type field

- Config backward compat: legacy configs without app_type parse
  correctly (field is undefined, not an error)
- Config round-trip: app_type survives writeProjectConfig, and legacy
  configs without it stay absent after write-back
- Wire isolation: app_type never leaks into the upload payload
  (UploadAppPayload has no app_type field)
- Template rendering: brevo_function branch renders app_type correctly,
  all three app types produce the right value
- Capability matrix: function type has no OAuth/UI capabilities, only
  review-lifecycle on public distribution
- Record resolution: brevo_function record resolves to function type;
  blockless records still resolve to UI (not function)
- Recoverability: function apps are always recoverable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add changeset for function app-type registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: rename app_type to appType in config for camelCase consistency

Aligns with the existing camelCase convention used by other config
fields (appId, appName, logoUri). The field is local metadata only
and never sent to the server, so this is a config-shape change with
no wire impact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update stale doc comments for appType and add app_type migration

- Fix doc comment in config.ts that said "there is no separate appType key"
- Fix comment in en.ts that said "there is no app-type field"
- Fix AGENTS.md.tmpl that said "There is no appType key"
- Add legacy app_type → appType migration in readProjectConfigAt
- Add 3 migration tests (read migration, write-back cleanup, precedence)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: revert appType back to app_type in config

Rename the informational app-type field from camelCase `appType` back to
snake_case `app_type` to match the wire-mirrored key convention used by
other platform-facing fields (`distribution_type`, `ui_app`,
`brevo_function`). The migration logic now migrates the legacy `appType`
key to `app_type` on read and drops it on write-back.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* BEX-470 refactor(config): normalize app-config.json keys to snake_case with legacy migration (#104)

* refactor(config): normalize app-config.json keys to snake_case with legacy migration

Rename the camelCase keys in app-config.json to snake_case, matching the
wire contract: appId → app_id, appName → app_name, logoUri → logo_uri,
appType → app_type, auth.redirectUris → auth.redirect_uris. The
ProjectConfig interface now IS the file shape, so every read site was
found by the compiler.

Backward compatibility: readProjectConfigAt folds the legacy camelCase
keys (and the older auth.redirectUrls) through one alias table, drops
them from the returned object, and warns once on stderr when a file
carries both spellings with different values.

Migration on write: create/upload/scaffold/start now emit snake_case
only. upload and scaffold also rewrite an in-sync legacy file on their
no-op paths via migrateProjectConfigKeys(), so one run migrates a
project regardless of drift. Values are never changed.

Also: template, scaffold docs, agent-context docs, README, smoke scripts
(accept both spellings so published-build runs still pass), tests for
camelCase-only / snake_case-only / mixed reads and the write-back
migration, and a changeset.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(config): announce key migration on every rewrite and add agent migration note

Print the snake_case migration notice whenever a legacy app-config.json is
rewritten — after an upload push and after a consented scaffold refresh,
not only on the no-op paths — and word it so users know to update their
own scripts that read the file. Add an explicit migration rule to
agent-context/SKILL.md and AGENTS.md so agents steer custom code to the
new key names and never write the camelCase names.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(upload): assert the block error pre-empts the app_type label check

Pin the ordering `assertAppTypeAgrees` documents: a config that is BOTH
mislabelled and structurally broken must report the structural problem, so a
partner is never sent off to fix a label only to hit the real refusal on the
next upload. Asserted positively (the entry-named `.label:` error) plus an
explicit check that the message does not mention `app_type`, rather than via
`rejects.not.toThrow`, which can pass for the wrong reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: describe app_type as a validated label, not a discriminator

`app upload` now reads `app_type` to check it agrees with the blocks, so three
docs that said the CLI never reads it were wrong as written.

- CLAUDE.md: read for validation, never for detection; the pre-flight ordering
  and why it is last; and the wire decision, which was nowhere stated — the
  field is file-only because `UploadAppPayload` and the create body are closed
  structs built key by key, so nothing strips it and bo-be needs no change.
- The scaffolded AGENTS.md template said "the CLI never reads it". It now says
  what the field is for and that a hand-edited block needs the label updated.
- agent-context/SKILL.md and AGENTS.md documented the key but not its meaning;
  both now carry the same paragraph, kept in sync per the repo rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(changeset): fold the snake_case entry into the branch's single changeset

CLAUDE.md asks for one changeset file per branch, appended to rather than
multiplied. Merges the separate snake_case file into the existing one and adds
the app_type agreement check. Bump level stays `minor` — both entries already
warranted it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* fix: address PR #100 review comments for brevo function

- Add Brevo Functions documentation to SKILL.md and AGENTS.md (was
  missing entirely — agents had no knowledge of the function commands)
- Fix `brevo app upload` for Function apps: skip redirect_uris check,
  send `brevo_function: {}` on the wire, omit `auth` block, handle
  write-back and diff rendering correctly for the third app type
- Paginate `fetchDraftFunctionList` (was capped at 50, causing silent
  truncation and false not-found errors in `brevo fn deploy --id`)
- Fix `fetchFunctionList` pagination loop: use `has_more` + empty-page
  guard instead of `total` comparison to prevent infinite loops
- Refactor `executeFunctionAction` to delegate to `withNotFoundHandling`
  instead of re-implementing the 404 catch block (Sonar duplication)
- Move hardcoded strings (`Description:`, `Name:`, `Untitled Function`)
  to `src/lang/en.ts`
- Remove unused `functionService` mock from function-action.test.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* BEX-471 feat(json): emit snake_case twins for every camelCase --json key (transition step) (#106)

feat(json): emit snake_case twins for every camelCase --json key (transition step)

Machine-readable output had two conventions: pass-through commands
(app list, app submit, function *) emit the wire's snake_case, while
CLI-built objects (create, upload, credentials, install, delete, whoami,
the error envelope) emit camelCase. app-config.json settled on
snake_case in BEX-470, and scripts should be able to read one spelling
everywhere.

Renaming outright would break every `jq .appId` in a pipeline with
nothing the CLI could migrate, so this is the deprecation step:
jsonOutput() now adds a snake_case twin after each camelCase key at the
top level of every document (per element for arrays) and inside the
error envelope. redirectUri / redirectUris alias to the wire name
redirect_uris. Nested objects are left untouched — they are wire records
or user data. Nothing is removed; camelCase goes away in the next major.

Agent docs and the changeset carry the deprecation note; the seven tests
that pinned exact camelCase shapes now pin both spellings.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Piyush Sarin <piyush.sarin@brevo.com>
Co-authored-by: Piyush Sarin <84779634+piyushsarin-sib@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants