contentful-management 3.12.1 → 3.13.0
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.
- checksums.yaml +4 -4
- data/.bito/guidelines/domain-invariants.txt +37 -0
- data/.bito/guidelines/repo-truth-and-boundaries.txt +10 -0
- data/.bito/guidelines/review-posture.txt +10 -0
- data/.bito.yaml +17 -0
- data/.devcontainer/Dockerfile +4 -0
- data/.devcontainer/devcontainer.json +24 -0
- data/.github/workflows/ci.yml +31 -0
- data/AGENTS.md +55 -0
- data/ARCHITECTURE.md +163 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +144 -0
- data/README.md +17 -2
- data/catalog-info.yaml +1 -0
- data/docs/ADRs/2015-01-01-factory-mixin-architecture.md +31 -0
- data/docs/ADRs/2015-01-01-vcr-cassettes-for-testing.md +26 -0
- data/docs/ADRs/2018-01-01-dynamic-entry-cache.md +28 -0
- data/docs/ADRs/2021-01-01-ci-migration-to-github-actions.md +27 -0
- data/docs/ADRs/README.md +10 -0
- data/docs/specs/README.md +5 -0
- data/lib/contentful/management/client.rb +12 -0
- data/lib/contentful/management/client_organization_periodic_usage_methods_factory.rb +4 -0
- data/lib/contentful/management/client_space_periodic_usage_methods_factory.rb +4 -0
- data/lib/contentful/management/organization_periodic_usage.rb +7 -0
- data/lib/contentful/management/space_periodic_usage.rb +7 -0
- data/lib/contentful/management/version.rb +1 -1
- data/renovate.json +4 -0
- metadata +18 -3
- data/.circleci/config.yml +0 -23
- data/.github/workflows/codeql.yml +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7efabeaaf8740255f7d6147c7c6d974e696f41264717e3f0db5ac3f6cfada421
|
|
4
|
+
data.tar.gz: cc61b0d7a48aaf6236b90f09e01b7439b5db14ab771283d6e984d39881769c47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e09e71e0aa2560c86e87ce960cda5575e5c1306955237a6c4fe0e90c43ad3be0bb7f8429cdb3384e9d838e19a454039921d71d0c28f17bfc90f1ddc3ea77f9d
|
|
7
|
+
data.tar.gz: 5779bde9485676668ad19614f0f4975abdb07e2457e3cdeed5ed7a1ab149944eee091e7d945eca168cce0ed57de221989dafe1bc75f11d14240bd7a53ca8b47d
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Critical invariants for the contentful-management.rb SDK:
|
|
2
|
+
|
|
3
|
+
RAISE_ERRORS DEFAULT
|
|
4
|
+
- The client defaults to raise_errors: false. Errors are returned as Contentful::Management::Error objects, not raised. Code that calls the SDK must check the response type — `response.is_a?(Contentful::Management::Error)` — unless raise_errors: true is explicitly set. Missing this check is the most common source of silent failures.
|
|
5
|
+
|
|
6
|
+
VERSIONING
|
|
7
|
+
- Every mutating operation (update, publish, unpublish, archive, unarchive, destroy) passes sys[:version] in the request. Stale versions result in HTTP 409 Conflict. Always re-fetch before retrying a mutating operation on a version conflict.
|
|
8
|
+
|
|
9
|
+
FACTORY NAMING CONVENTION
|
|
10
|
+
- The associated_class method in ClientAssociationMethodsFactory derives the resource class from the factory's class name by stripping "Client" and "MethodsFactory" from the name. ClientEntryMethodsFactory → Contentful::Management::Entry. This is convention magic — breaking it by renaming a factory without renaming the resource class will silently fail at runtime, not at load time.
|
|
11
|
+
|
|
12
|
+
RESOURCE BUILDER COMPLETENESS
|
|
13
|
+
- Every new resource type must be registered in ResourceBuilder::DEFAULT_RESOURCE_MAPPING. Missing entries cause UnparsableResource errors at runtime when the API returns that type.
|
|
14
|
+
|
|
15
|
+
THREAD SAFETY
|
|
16
|
+
- The client stores itself in Thread.current[:client] at initialization. Use one Client instance per thread. Sharing a client across threads produces undefined behavior.
|
|
17
|
+
|
|
18
|
+
DYNAMIC ENTRY CACHE
|
|
19
|
+
- If dynamic_entries is configured, the client fetches content type schemas at init and caches DynamicEntry subclasses. Schema changes after init are not automatically detected — refresh with client.update_dynamic_entry_cache_for_environment!(env) or reinitialize the client.
|
|
20
|
+
|
|
21
|
+
SCOPE BOUNDARIES
|
|
22
|
+
- Taxonomy resources (TaxonomyConcept, TaxonomyConceptScheme) are organization-scoped. Use organization_id, not space_id or environment_id.
|
|
23
|
+
- Environment-scoped resources (entries, assets, content types, locales, tags, UI extensions) require both space_id and environment_id.
|
|
24
|
+
- Space-scoped resources (webhooks, API keys, roles, uploads) take space_id only.
|
|
25
|
+
|
|
26
|
+
TESTING
|
|
27
|
+
- All tests use VCR cassettes for HTTP mocking. Never add tests that make live API calls. Never fabricate cassette YAML manually — always record against the real API with a valid management token.
|
|
28
|
+
- Test files mirror source files: spec/lib/contentful/management/entry_spec.rb tests lib/contentful/management/entry.rb.
|
|
29
|
+
|
|
30
|
+
DEFAULT BRANCH
|
|
31
|
+
- The default branch is master, not main. PRs and releases target master.
|
|
32
|
+
|
|
33
|
+
BUNDLER VERSION
|
|
34
|
+
- Bundler is pinned at 2.3.26. Always invoke bundle commands as: bundle _2.3.26_ <command>.
|
|
35
|
+
|
|
36
|
+
FROZEN STRING LITERALS
|
|
37
|
+
- All lib/ source files must start with # frozen_string_literal: true.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Use the repository's written documentation as review context and check whether the change matches the documented intent.
|
|
2
|
+
|
|
3
|
+
- Start from README.md, ARCHITECTURE.md, AGENTS.md, CONTRIBUTING.md, and docs/ADRs/ for architectural context.
|
|
4
|
+
- Check whether code, tests, and documentation all tell the same story. Flag mismatches between implementation and the documented architecture or ADRs.
|
|
5
|
+
- Treat AGENTS.md as the authoritative guide for sharp edges and invariants. If a change violates an invariant documented there, flag it.
|
|
6
|
+
- If CI or another required check already enforces a merge rule, do not ask for duplicate PR template sections or manual checklists.
|
|
7
|
+
- Ask for an ADR update when a change is architecture-significant: adding a new resource type, changing the HTTP client (`http` gem), modifying error handling behavior, changing how the DynamicEntry cache is populated or used, or adding a new scoping dimension (e.g., organization-scoped vs space/environment-scoped).
|
|
8
|
+
- Distinguish the public API surface (Client methods, resource classes) from internal helpers. Public API changes require extra scrutiny and documentation updates.
|
|
9
|
+
VCR cassettes in `spec/fixtures/vcr_cassettes/` are the source of truth for expected API response shapes. If a change modifies response parsing, check whether the relevant cassettes are updated or flagged for re-recording.
|
|
10
|
+
- The `raise_errors` configuration defaults to `false`. If a PR changes error handling, verify the behavior is consistent for both `raise_errors: true` and `raise_errors: false` configurations.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Review this pull request like the tech lead of the contentful-management.rb project — the official Ruby SDK for the Contentful Management API.
|
|
2
|
+
|
|
3
|
+
- Prefer a few high-signal findings to a long list of minor or style-only comments.
|
|
4
|
+
- Prefer behavior, contract, runtime, and documentation issues over process-only suggestions. Do not ask for duplicate PR template sections, checklists, or manual validation acknowledgements when CI or required checks already enforce that policy.
|
|
5
|
+
- Pay close attention to: API contract correctness (does this match the CMA API spec?), version management (do all mutating operations pass sys[:version]?), error handling (are new error codes handled?), and the `raise_errors: false` default behavior (do callers check for error objects?).
|
|
6
|
+
- Watch for the DynamicEntry cache: if content type schemas are modified, flag whether the cache refresh path is documented or handled.
|
|
7
|
+
- Keep feedback actionable: explain why it matters, how it would surface in practice, and the clearest next step.
|
|
8
|
+
- If a concern is only a risk or assumption rather than a confirmed bug, say that clearly.
|
|
9
|
+
- If you find no issues, say so explicitly and call out any residual uncertainty.
|
|
10
|
+
- New resource types require three coordinated changes: a Resource class, a Factory module, and a ResourceBuilder mapping. Flag if any of the three is missing.
|
data/.bito.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
suggestion_mode: comprehensive
|
|
2
|
+
post_description: true
|
|
3
|
+
post_changelist: true
|
|
4
|
+
exclude_files: 'Gemfile.lock'
|
|
5
|
+
exclude_draft_pr: false
|
|
6
|
+
secret_scanner_feedback: true
|
|
7
|
+
linters_feedback: true
|
|
8
|
+
repo_level_guidelines_enabled: true
|
|
9
|
+
sequence_diagram_enabled: true
|
|
10
|
+
custom_guidelines:
|
|
11
|
+
general:
|
|
12
|
+
- name: 'Review Posture'
|
|
13
|
+
path: './.bito/guidelines/review-posture.txt'
|
|
14
|
+
- name: 'Repo Truth And Alignment'
|
|
15
|
+
path: './.bito/guidelines/repo-truth-and-boundaries.txt'
|
|
16
|
+
- name: 'Domain Invariants'
|
|
17
|
+
path: './.bito/guidelines/domain-invariants.txt'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contentful-management.rb",
|
|
3
|
+
"build": {
|
|
4
|
+
"dockerfile": "Dockerfile",
|
|
5
|
+
"context": "..",
|
|
6
|
+
"args": {
|
|
7
|
+
"RUBY_VERSION": "${localEnv:RUBY_VERSION:3.4}"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"containerEnv": {
|
|
11
|
+
"BUNDLE_APP_CONFIG": "/home/vscode/.bundle",
|
|
12
|
+
"BUNDLE_PATH": "/home/vscode/.bundle/vendor"
|
|
13
|
+
},
|
|
14
|
+
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
|
15
|
+
"remoteUser": "vscode",
|
|
16
|
+
"postCreateCommand": "bundle _2.3.26_ install",
|
|
17
|
+
"customizations": {
|
|
18
|
+
"vscode": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"Shopify.ruby-lsp"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test-and-lint:
|
|
14
|
+
name: Test and lint (Ruby ${{ matrix.ruby-version }})
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
ruby-version: ["3.2", "3.3", "3.4"]
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v5
|
|
22
|
+
|
|
23
|
+
- name: Install devcontainer CLI
|
|
24
|
+
run: npm install -g @devcontainers/cli@0
|
|
25
|
+
|
|
26
|
+
- name: Run tests and lint checks in dev container
|
|
27
|
+
env:
|
|
28
|
+
RUBY_VERSION: ${{ matrix.ruby-version }}
|
|
29
|
+
run: |
|
|
30
|
+
devcontainer up --workspace-folder .
|
|
31
|
+
devcontainer exec --workspace-folder . bash -lc "bundle _2.3.26_ exec rake rspec_rubocop"
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Agent Guide
|
|
2
|
+
|
|
3
|
+
<!-- Generated by seed-golden-context | Last updated: 2026-05-05 -->
|
|
4
|
+
|
|
5
|
+
Read this file first. It tells you where to find context in this repo.
|
|
6
|
+
|
|
7
|
+
## Quick Reference
|
|
8
|
+
|
|
9
|
+
| What you need | Where to look |
|
|
10
|
+
|---|---|
|
|
11
|
+
| What this repo does | [README.md](./README.md) |
|
|
12
|
+
| How this repo is structured | [ARCHITECTURE.md](./ARCHITECTURE.md) |
|
|
13
|
+
| How to build/test/run | [CONTRIBUTING.md](./CONTRIBUTING.md) |
|
|
14
|
+
| Why decisions were made | [docs/ADRs/](./docs/ADRs/) |
|
|
15
|
+
| PR review rules | [.bito/guidelines/](./.bito/guidelines/) |
|
|
16
|
+
| Active specs/work | [docs/specs/](./docs/specs/) |
|
|
17
|
+
| Release checklist | [RELEASE.md](./RELEASE.md) |
|
|
18
|
+
|
|
19
|
+
## Sharp Edges & Invariants
|
|
20
|
+
|
|
21
|
+
- **`raise_errors` defaults to `false`.** The client returns `Contentful::Management::Error` objects instead of raising. Always check `response.is_a?(Contentful::Management::Error)` — or use `raise_errors: true` to get exceptions. Silent errors are the most common source of subtle bugs.
|
|
22
|
+
- **Every mutating operation passes `sys[:version]`.** The CMA uses optimistic concurrency. Stale versions get HTTP 409 `Conflict`. Re-fetch the resource to get the latest version before retrying.
|
|
23
|
+
- **The client stores itself in `Thread.current[:client]`.** Use a separate `Client` instance per thread. Do not share a single client across threads.
|
|
24
|
+
- **`associated_class` derives the resource class from the factory name.** The factory `ClientEntryMethodsFactory` resolves to `Contentful::Management::Entry` via naming convention. Renaming a factory without renaming the resource class will silently break the resolution.
|
|
25
|
+
- **VCR cassettes must not be fabricated.** Record against the real CMA API. Fake cassettes produce tests that pass but don't validate real API behavior.
|
|
26
|
+
- **Adding a new CMA resource requires three changes:** a new resource class module in `lib/contentful/management/`, a new factory module (`client_<resource>_methods_factory.rb`), and an entry in `ResourceBuilder::DEFAULT_RESOURCE_MAPPING`.
|
|
27
|
+
- **DynamicEntry cache can go stale.** If content type schemas change after client init, call `client.update_dynamic_entry_cache_for_environment!(env)` or reinitialize the client.
|
|
28
|
+
- **Taxonomy resources are organization-scoped**, not space/environment-scoped. `TaxonomyConcept` and `TaxonomyConceptScheme` use `organization_id`.
|
|
29
|
+
- **The default branch is `master`, not `main`.**
|
|
30
|
+
- **Bundler version is pinned at 2.3.26.** Always invoke as `bundle _2.3.26_ <command>` to match the devcontainer and CI.
|
|
31
|
+
|
|
32
|
+
## Key Conventions
|
|
33
|
+
|
|
34
|
+
- **Commit format:** Type-prefixed with optional ticket ID (`chore:`, `feat:`, `fix:`, `docs:`)
|
|
35
|
+
- **Branch strategy:** `master` + feature branches; PRs target `master`
|
|
36
|
+
- **Test location:** `spec/lib/contentful/management/<resource>_spec.rb` mirroring `lib/contentful/management/`
|
|
37
|
+
- **HTTP mocking:** VCR cassettes in `spec/fixtures/vcr_cassettes/` — no live API calls in tests
|
|
38
|
+
- **Frozen string literals:** All `lib/` files begin with `# frozen_string_literal: true`
|
|
39
|
+
- **YARD docs:** All public methods documented; `@private` on internal methods
|
|
40
|
+
|
|
41
|
+
## Integration Points
|
|
42
|
+
|
|
43
|
+
**Upstream (this repo consumes):**
|
|
44
|
+
- Contentful Management API (`api.contentful.com`) — all CMA operations over HTTPS
|
|
45
|
+
- Contentful Upload API (`upload.contentful.com`) — binary asset uploads
|
|
46
|
+
|
|
47
|
+
**Downstream (consumes this repo):**
|
|
48
|
+
- Any Ruby application or script doing programmatic Contentful management
|
|
49
|
+
|
|
50
|
+
## Build & Quality
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Full verification loop (inside devcontainer or local Ruby env with Bundler 2.3.26)
|
|
54
|
+
bundle _2.3.26_ install && bundle exec rake rspec_rubocop
|
|
55
|
+
```
|
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
<!-- Generated by seed-golden-context | Last updated: 2026-05-05 -->
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`contentful-management.rb` is the official Ruby client for the [Contentful Management API (CMA)](https://www.contentful.com/developers/docs/references/content-management-api/). It wraps every CMA endpoint in a Rubyish interface — managing spaces, environments, content types, entries, assets, webhooks, roles, API keys, taxonomy concepts, and more. The library handles authentication, serialization, rate-limiting with retry, and optionally caches content type schemas into `DynamicEntry` classes so callers get typed field accessors on entries.
|
|
8
|
+
|
|
9
|
+
## System Context
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph TD
|
|
13
|
+
Developer["Developer / Integration Script"]
|
|
14
|
+
Gem["contentful-management.rb (this repo)"]
|
|
15
|
+
CMA["Contentful Management API\napi.contentful.com"]
|
|
16
|
+
UploadAPI["Contentful Upload API\nupload.contentful.com"]
|
|
17
|
+
RubyGems["RubyGems.org\ncontentful-management gem"]
|
|
18
|
+
|
|
19
|
+
Developer -- "gem 'contentful-management'" --> RubyGems
|
|
20
|
+
RubyGems -- "installs" --> Gem
|
|
21
|
+
Developer -- "Contentful::Management::Client.new('token')..." --> Gem
|
|
22
|
+
Gem -- "HTTPS/REST" --> CMA
|
|
23
|
+
Gem -- "HTTPS/REST (binary uploads)" --> UploadAPI
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Upstream (consumes):**
|
|
27
|
+
- Contentful Management API (`api.(eu.)contentful.com`) — all read/write operations
|
|
28
|
+
- Contentful Upload API (`upload.(eu.)contentful.com`) — binary asset uploads
|
|
29
|
+
|
|
30
|
+
**Downstream (consumes this repo):**
|
|
31
|
+
- Any Ruby script, app, or integration requiring programmatic Contentful content management
|
|
32
|
+
|
|
33
|
+
## Internal Structure
|
|
34
|
+
|
|
35
|
+
| Path | Purpose |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `lib/contentful/management/client.rb` | Entry point. `Client` class holds config, exposes all `*MethodsFactory` accessors, handles HTTP execution, rate-limit retry, and dynamic entry cache management. |
|
|
38
|
+
| `lib/contentful/management/client_*_methods_factory.rb` | One factory module per resource type (e.g., `ClientEntryMethodsFactory`). Provide `all`, `find`, `create` scoped to `space_id` / `environment_id`. Inherit from `ClientAssociationMethodsFactory`. |
|
|
39
|
+
| `lib/contentful/management/client_association_methods_factory.rb` | Base factory mixin — generic `all`, `find`, `create`, `new`. Uses `associated_class` (inferred from the factory's own class name) to dispatch to the resource class. |
|
|
40
|
+
| `lib/contentful/management/resource.rb` | Base `Resource` module. Handles `sys` block, property coercions, YARD-documented CRUD lifecycle (`update`, `destroy`, `reload`). Mixed into every resource class. |
|
|
41
|
+
| `lib/contentful/management/resource/publisher.rb` | `Publisher` mixin — `publish`, `unpublish`, `published?`, `updated?`. Included by `Entry`, `Asset`, `ContentType`. |
|
|
42
|
+
| `lib/contentful/management/resource/archiver.rb` | `Archiver` mixin — `archive`, `unarchive`, `archived?`. Included by `Entry`, `Asset`. |
|
|
43
|
+
| `lib/contentful/management/resource/system_properties.rb` | `SystemProperties` mixin — maps `sys` block to Ruby accessors (`id`, `type`, `version`, `space`, `environment`, etc.). |
|
|
44
|
+
| `lib/contentful/management/resource_builder.rb` | Parses API JSON into typed Ruby objects. Uses `DEFAULT_RESOURCE_MAPPING` (`sys.type` → class). Handles `DynamicEntry` lookup and link resolution within response payloads. |
|
|
45
|
+
| `lib/contentful/management/resource_requester.rb` | Makes HTTP calls via `Client#execute_request`. Handles `get`, `post`, `put`, `delete`, `publish`, `unpublish`, `archive`, `unarchive` verbs. |
|
|
46
|
+
| `lib/contentful/management/request.rb` | Encapsulates a single HTTP request — URL, query, headers, body. |
|
|
47
|
+
| `lib/contentful/management/response.rb` | Wraps an HTTP response — parses status, extracts error message, surfaces raw body. |
|
|
48
|
+
| `lib/contentful/management/dynamic_entry.rb` | `DynamicEntry` factory — creates per-content-type `Entry` subclasses with typed field accessors generated from the content type schema. Cached on `Client#dynamic_entry_cache`. |
|
|
49
|
+
| `lib/contentful/management/<resource>.rb` | One file per CMA resource type: `entry.rb`, `asset.rb`, `space.rb`, `environment.rb`, `content_type.rb`, etc. |
|
|
50
|
+
| `lib/contentful/management/error.rb` | Error class hierarchy: `Error` → `BadRequest`, `Unauthorized`, `AccessDenied`, `NotFound`, `Conflict`, `UnprocessableEntity`, `RateLimitExceeded`, `ServerError`, etc. |
|
|
51
|
+
| `lib/contentful/management/support.rb` | Utility helpers — URL helpers, camelCase/snake_case conversion. |
|
|
52
|
+
| `spec/` | RSpec test suite, mirroring `lib/` structure. HTTP mocked with VCR cassettes in `spec/fixtures/vcr_cassettes/`. |
|
|
53
|
+
| `examples/` | Usage examples including custom class mapping and resource mapping. |
|
|
54
|
+
| `lib/contentful/management/version.rb` | Contains `VERSION` constant — bump for every release |
|
|
55
|
+
| `spec/fixtures/vcr_cassettes/` | VCR cassette YAML files. Do not fabricate or hand-edit cassettes — re-record against the real API if API response shapes change |
|
|
56
|
+
| `.rubocop_todo.yml` | Auto-generated RuboCop todo list — regenerate with `bundle exec rubocop --auto-gen-config`, do not hand-edit |
|
|
57
|
+
|
|
58
|
+
## Data Flow
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Developer code
|
|
62
|
+
│
|
|
63
|
+
▼
|
|
64
|
+
Client.new('token').entries('space_id', 'env_id') # returns ClientEntryMethodsFactory
|
|
65
|
+
│
|
|
66
|
+
▼
|
|
67
|
+
ClientEntryMethodsFactory#all(params) # calls Entry.all(client, space_id, env_id, params)
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
ResourceRequester#get → Client#execute_request # builds Request, fires HTTP via `http` gem
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
HTTP 200 JSON response
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
Response.new(raw_response, request) # wraps and parses status
|
|
77
|
+
│
|
|
78
|
+
▼
|
|
79
|
+
ResourceBuilder#run # dispatches via DEFAULT_RESOURCE_MAPPING
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
Array of Entry objects (or DynamicEntry subclass if in cache)
|
|
83
|
+
with .publish, .update, .destroy, .sys, .fields, etc.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Dynamic entry flow:**
|
|
87
|
+
If `dynamic_entries: { 'space_id' => 'env_id' }` is passed at `Client` init, the client fetches all content types for that environment and builds `DynamicEntry` subclasses with per-field Ruby method accessors. These are cached in `Client#dynamic_entry_cache` and used automatically by `ResourceBuilder` when deserializing entries.
|
|
88
|
+
|
|
89
|
+
**Rate-limit retry flow:**
|
|
90
|
+
`Client#execute_request` rescues `RateLimitExceeded` (HTTP 429), reads `x-contentful-ratelimit-reset` header, sleeps with jitter (1.0–1.2× reset time), and retries up to `max_rate_limit_retries` times (default: 1).
|
|
91
|
+
|
|
92
|
+
**Thread safety note:**
|
|
93
|
+
The client stores itself in `Thread.current[:client]` at initialization. Multiple threads should use separate client instances.
|
|
94
|
+
|
|
95
|
+
## Domain Concepts
|
|
96
|
+
|
|
97
|
+
| Concept | Description |
|
|
98
|
+
|---|---|
|
|
99
|
+
| **Space** | Top-level organizational unit — contains environments, API keys, webhooks, roles. |
|
|
100
|
+
| **Environment** | A named branch within a space (e.g., `master`, `staging`). ContentTypes, Entries, Assets, and Locales are environment-scoped. |
|
|
101
|
+
| **Content Type** | Schema definition. Defines fields and types. Must be published before entries reference it. |
|
|
102
|
+
| **Entry** | A single content item. Lifecycle: Draft → Published → Archived. `Entry` has typed field accessors when DynamicEntry is used. |
|
|
103
|
+
| **DynamicEntry** | Auto-generated `Entry` subclass with named field methods (e.g., `entry.title` instead of `entry.fields[:title]`). Built from content type schema at client init. |
|
|
104
|
+
| **Asset** | Binary file (image, video, PDF). Two-step lifecycle: create → process → publish. |
|
|
105
|
+
| **Snapshot** | Immutable historical copy of an entry or content type at a specific version. |
|
|
106
|
+
| **Tag** | Metadata tag for organizational filtering on entries/assets. |
|
|
107
|
+
| **Taxonomy Concept / Concept Scheme** | Hierarchical controlled vocabulary (SKOS-based). Organization-scoped, not space/environment-scoped. Added in v3.12.0. |
|
|
108
|
+
| **sys** | Every resource has a `sys` hash: `id`, `type`, `version`, `createdAt`, `updatedAt`, `publishedAt`, `archivedAt`, `space`, `environment`. |
|
|
109
|
+
|
|
110
|
+
**Version management:** All mutating operations (update, publish, archive, delete) pass `sys[:version]` in the request header. Mismatch returns HTTP 409 `Conflict`.
|
|
111
|
+
|
|
112
|
+
## Key Dependencies
|
|
113
|
+
|
|
114
|
+
| Dependency | Why |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `http` (~> 5.0) | HTTP client for all CMA API calls. Replaces earlier versions of the gem; bumped through major versions as the project matured. |
|
|
117
|
+
| `multi_json` (~> 1.15) | JSON parsing with automatic adapter selection (yajl, oj, json gem). Provides consistent JSON behavior across Ruby environments. |
|
|
118
|
+
| `json` (>= 1.8, < 3.0) | JSON gem as a direct dependency for environments where `multi_json` falls through. |
|
|
119
|
+
| `rspec` (~> 3) | Test framework. |
|
|
120
|
+
| `vcr` (~> 6.2.0) | Records/replays HTTP interactions as cassette fixtures for deterministic offline tests. |
|
|
121
|
+
| `webmock` | HTTP request stubbing used alongside VCR. |
|
|
122
|
+
| `rubocop` (~> 1.56.2) | Linter. Config in `.rubocop.yml` + `.rubocop_todo.yml`. |
|
|
123
|
+
| `simplecov` | Test coverage reporting. |
|
|
124
|
+
| `guard` + `guard-rspec` + `guard-rubocop` | File-watcher dev loop (run tests and lint on file changes). Config in `Guardfile`. |
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
All configuration is passed as a Hash to `Client.new('token', configuration)`. No environment variables are read by the library.
|
|
129
|
+
|
|
130
|
+
| Parameter | Purpose | Default |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `api_url` | CMA base URL | `api.contentful.com` |
|
|
133
|
+
| `uploads_url` | Upload API base URL | `upload.contentful.com` |
|
|
134
|
+
| `api_version` | CMA API version | `'1'` |
|
|
135
|
+
| `secure` | Use HTTPS | `true` |
|
|
136
|
+
| `default_locale` | Default locale for fields | `'en-US'` |
|
|
137
|
+
| `gzip_encoded` | Accept gzip responses | `false` |
|
|
138
|
+
| `logger` | `::Logger` instance or `false` | `false` |
|
|
139
|
+
| `log_level` | Logger level | `Logger::INFO` |
|
|
140
|
+
| `raise_errors` | Raise errors vs return error objects | `false` |
|
|
141
|
+
| `dynamic_entries` | Hash of `space_id => environment_id` to pre-cache DynamicEntries at init | `{}` |
|
|
142
|
+
| `disable_content_type_caching` | Skip DynamicEntry cache population | `false` |
|
|
143
|
+
| `proxy_host` / `proxy_port` / `proxy_username` / `proxy_password` | HTTP proxy config | `nil` |
|
|
144
|
+
| `max_rate_limit_retries` | Max 429 retries before raising | `1` |
|
|
145
|
+
| `max_rate_limit_wait` | Max seconds to wait for rate limit reset | `60` |
|
|
146
|
+
| `application_name` / `application_version` | Injected into `X-Contentful-User-Agent` | `nil` |
|
|
147
|
+
| `integration_name` / `integration_version` | Integration metadata for user-agent | `nil` |
|
|
148
|
+
|
|
149
|
+
**Notable difference from other SDKs:** `raise_errors` defaults to `false` — errors are returned as `Contentful::Management::Error` objects rather than raised, unless explicitly set.
|
|
150
|
+
|
|
151
|
+
## Operational Knowledge
|
|
152
|
+
|
|
153
|
+
### Failure Modes
|
|
154
|
+
|
|
155
|
+
| Failure | Symptom | Resolution |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `Conflict` (HTTP 409) | Raised on update/publish/delete | Re-fetch the resource to get the latest `sys[:version]` |
|
|
158
|
+
| `RateLimitExceeded` (HTTP 429) | Retries exhausted | Increase `max_rate_limit_retries` or `max_rate_limit_wait` |
|
|
159
|
+
| `Unauthorized` (HTTP 401) | Access token invalid/expired | Rotate the management token |
|
|
160
|
+
| `AccessDenied` (HTTP 403) | Token lacks permission | Check space/org permissions for the token |
|
|
161
|
+
| `NotFound` (HTTP 404) | Wrong space/env/resource ID | Verify IDs |
|
|
162
|
+
| Error objects returned silently | `raise_errors: false` (default) — errors returned, not raised | Set `raise_errors: true` or always check `response.is_a?(Contentful::Management::Error)` |
|
|
163
|
+
| Stale DynamicEntry cache | Content type fields changed after client init | Call `client.update_dynamic_entry_cache_for_environment!(env)` or reinitialize the client |
|
data/CHANGELOG.md
CHANGED
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
<!-- Generated by seed-golden-context | Last updated: 2026-05-05 -->
|
|
4
|
+
|
|
5
|
+
Thanks for helping improve `contentful-management.rb`.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
| Tool | Version | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Ruby | 3.0+ (3.2–3.4 tested in CI) | See `.devcontainer/Dockerfile` — default is 3.4 |
|
|
12
|
+
| Bundler | 2.3.26 | Pinned version; install with `gem install bundler:2.3.26` |
|
|
13
|
+
| Docker | Any recent | Required for the dev container workflow |
|
|
14
|
+
| Dev Container CLI | Latest | `npm install -g @devcontainers/cli` — for terminal devcontainer |
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
The recommended path is the **dev container** — it pins the Ruby version and installs bundler/gems automatically.
|
|
19
|
+
|
|
20
|
+
### Option A: Dev Container (recommended)
|
|
21
|
+
|
|
22
|
+
**VS Code:**
|
|
23
|
+
Open the repository in VS Code, install the Dev Containers extension, then run `Dev Containers: Reopen in Container`. The container runs `bundle _2.3.26_ install` on creation.
|
|
24
|
+
|
|
25
|
+
**Terminal:**
|
|
26
|
+
```bash
|
|
27
|
+
# Install dev container CLI
|
|
28
|
+
npm install -g @devcontainers/cli
|
|
29
|
+
|
|
30
|
+
# Start container and enter it
|
|
31
|
+
devcontainer up --workspace-folder .
|
|
32
|
+
devcontainer exec --workspace-folder . bash
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Option B: Local Ruby environment
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Clone
|
|
39
|
+
git clone git@github.com:contentful/contentful-management.rb.git
|
|
40
|
+
cd contentful-management.rb
|
|
41
|
+
|
|
42
|
+
# Install dependencies (using pinned Bundler version)
|
|
43
|
+
gem install bundler:2.3.26
|
|
44
|
+
bundle _2.3.26_ install # source: .devcontainer/devcontainer.json → postCreateCommand
|
|
45
|
+
|
|
46
|
+
# Verify
|
|
47
|
+
bundle exec rake rspec_rubocop # source: Rakefile → task :rspec_rubocop
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Development Workflow
|
|
51
|
+
|
|
52
|
+
All commands run via `bundle exec rake`. Tasks are defined in `Rakefile`.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run full test suite + RuboCop (the CI command)
|
|
56
|
+
bundle exec rake rspec_rubocop # source: Rakefile → task :rspec_rubocop
|
|
57
|
+
|
|
58
|
+
# Run RSpec tests only
|
|
59
|
+
bundle exec rake spec # source: Rakefile → task :spec (default)
|
|
60
|
+
|
|
61
|
+
# Run RuboCop linter only
|
|
62
|
+
bundle exec rake rubocop # source: Rakefile → task :rubocop
|
|
63
|
+
|
|
64
|
+
# Run tests, rubocop, and reek (full CI suite)
|
|
65
|
+
bundle exec rake ci # source: Rakefile → task :ci
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Guard (file-watch dev loop)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bundle exec guard # source: Gemfile → guard; config in Guardfile
|
|
72
|
+
```
|
|
73
|
+
Guard watches for file changes and automatically re-runs the relevant specs and RuboCop checks.
|
|
74
|
+
|
|
75
|
+
## Testing
|
|
76
|
+
|
|
77
|
+
- **Framework:** RSpec (`rspec`, `rspec-its`)
|
|
78
|
+
- **HTTP mocking:** VCR cassettes (`vcr ~> 6.2.0`) + WebMock — cassettes stored in `spec/fixtures/vcr_cassettes/`
|
|
79
|
+
- **Location:** `spec/lib/contentful/management/` — one `*_spec.rb` per source module
|
|
80
|
+
- **Run all:** `bundle exec rake spec` — source: `Rakefile → task :spec`
|
|
81
|
+
- **Order:** Random (configured in `.rspec`)
|
|
82
|
+
- **Coverage:** `simplecov` — runs automatically with the test suite
|
|
83
|
+
|
|
84
|
+
**Adding tests for a new resource:** Follow the existing pattern — create `spec/lib/contentful/management/<resource>_spec.rb`. Add VCR fixture cassettes to `spec/fixtures/vcr_cassettes/`. **Do not make live API calls in tests** — record cassettes against the real CMA API once, then replay.
|
|
85
|
+
|
|
86
|
+
**VCR token redaction:** The VCR config redacts CMA tokens from cassettes so cassettes are safe to commit. Do not manually add live tokens to cassette files.
|
|
87
|
+
|
|
88
|
+
## Code Style & Conventions
|
|
89
|
+
|
|
90
|
+
- **Linter:** RuboCop with config in `.rubocop.yml` + `.rubocop_todo.yml`
|
|
91
|
+
- Line length max: 135 (`Metrics/LineLength`)
|
|
92
|
+
- Class/module length max: 350
|
|
93
|
+
- Spec files, examples, Gemfile, Rakefile, and Guardfile are excluded from linting
|
|
94
|
+
- **Frozen string literals:** All source files start with `# frozen_string_literal: true`
|
|
95
|
+
- **YARD docs:** All public methods have YARD documentation; `@private` marks internal methods
|
|
96
|
+
- **Error handling:** By default the client returns error objects (`raise_errors: false`). Callers should check `response.is_a?(Contentful::Management::Error)` or initialize with `raise_errors: true`
|
|
97
|
+
|
|
98
|
+
## Commit Convention
|
|
99
|
+
|
|
100
|
+
follow [Conventional Commits](https://www.conventionalcommits.org/):
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
type(scope): description [TICKET-ID]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Valid types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `ci`, `build`
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
```
|
|
110
|
+
feat: add taxonomy concept endpoints
|
|
111
|
+
fix: preserve fields default value on update
|
|
112
|
+
chore: route CI alerts to sdk-bots channel
|
|
113
|
+
docs: add agent section to README
|
|
114
|
+
build(deps): update requests dependencies [DX-886]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Branch Strategy
|
|
118
|
+
|
|
119
|
+
- `master` — production branch
|
|
120
|
+
- Feature/fix branches — create from `master`, e.g., `feat/taxonomy-endpoints`
|
|
121
|
+
|
|
122
|
+
## Release Process
|
|
123
|
+
|
|
124
|
+
From `RELEASE.md`:
|
|
125
|
+
1. Ensure tests are green: `bundle exec rake rspec_rubocop`
|
|
126
|
+
2. Update `CHANGELOG.md`
|
|
127
|
+
3. Bump version in `lib/contentful/management/version.rb`
|
|
128
|
+
4. `bundle exec rake release` — source: `Rakefile` (Bundler standard gem task); builds gem and pushes to RubyGems
|
|
129
|
+
|
|
130
|
+
## Pull Requests
|
|
131
|
+
|
|
132
|
+
1. Fork and create a branch from `master`
|
|
133
|
+
2. Run checks inside the dev container: `bundle exec rake rspec_rubocop`
|
|
134
|
+
3. Open a PR with a short summary of the change
|
|
135
|
+
|
|
136
|
+
## CI/CD
|
|
137
|
+
|
|
138
|
+
| Job | Trigger | What it does |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `Test and lint (Ruby 3.2)` | Push/PR to `master` | `bundle exec rake rspec_rubocop` inside devcontainer |
|
|
141
|
+
| `Test and lint (Ruby 3.3)` | Push/PR to `master` | Same |
|
|
142
|
+
| `Test and lint (Ruby 3.4)` | Push/PR to `master` | Same — default Ruby version in devcontainer |
|
|
143
|
+
|
|
144
|
+
Source: `.github/workflows/ci.yml`
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Contentful::Management
|
|
2
|
-
[](http://badge.fury.io/rb/contentful-management) [](http://badge.fury.io/rb/contentful-management) [](https://github.com/contentful/contentful-management.rb/actions/workflows/ci.yml)
|
|
3
3
|
|
|
4
4
|
Ruby client for the Contentful Content Management API (CMA).
|
|
5
5
|
|
|
@@ -1375,8 +1375,23 @@ is blocking per execution thread.
|
|
|
1375
1375
|
|
|
1376
1376
|
## Contributing
|
|
1377
1377
|
|
|
1378
|
-
|
|
1378
|
+
For a reproducible local setup, open this repository in its included dev container. The container installs the project dependencies automatically when it is created.
|
|
1379
|
+
|
|
1380
|
+
After the container is ready, run:
|
|
1381
|
+
|
|
1382
|
+
```bash
|
|
1383
|
+
bundle exec rake rspec_rubocop
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contributor workflow.
|
|
1387
|
+
|
|
1388
|
+
1. Fork it ( https://github.com/[my-github-username]/contentful-management.rb/fork )
|
|
1379
1389
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
1380
1390
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
1381
1391
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
1382
1392
|
5. Create a new Pull Request
|
|
1393
|
+
|
|
1394
|
+
## For AI Agents
|
|
1395
|
+
|
|
1396
|
+
<!-- Generated by seed-golden-context | Last updated: 2026-05-05 -->
|
|
1397
|
+
If you are an AI coding agent working in this repository, read [AGENTS.md](./AGENTS.md) first. It tells you where to find architectural context, development setup, decision records, and repo-specific rules.
|
data/catalog-info.yaml
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Factory Mixin Architecture
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The Contentful Management API exposes a large, hierarchical resource model: Organizations → Spaces → Environments → ContentTypes/Entries/Assets, each with CRUD operations and lifecycle verbs (publish, archive). A naive approach of directly implementing HTTP calls in every resource class would lead to massive duplication. The library needed to:
|
|
10
|
+
|
|
11
|
+
1. Provide scoped access (factory per resource type holds `space_id`, `environment_id`)
|
|
12
|
+
2. Share generic CRUD across all resource types with minimal repetition
|
|
13
|
+
3. Stay extensible: adding a new CMA resource type should require minimal boilerplate
|
|
14
|
+
4. Remain idiomatic Ruby
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Two patterns were established at initial commit (January 2015):
|
|
19
|
+
|
|
20
|
+
**Factory mixin pattern:** A `ClientAssociationMethodsFactory` module provides generic `all`, `find`, `create` methods. Per-resource factories (e.g., `ClientEntryMethodsFactory`) include this module and hold scope identifiers. The `associated_class` method derives the resource class from the factory's own class name via naming convention, eliminating the need to declare it explicitly.
|
|
21
|
+
|
|
22
|
+
**Resource mixin pattern:** A `Resource` module + a set of capability mixins (`Publisher`, `Archiver`, `SystemProperties`, `EnvironmentAware`, `Fields`, etc.) compose resource behavior. Each resource class (`Entry`, `Asset`, etc.) includes only the mixins relevant to it.
|
|
23
|
+
|
|
24
|
+
This maps cleanly to Ruby's module/mixin system and allows fine-grained capability assignment.
|
|
25
|
+
|
|
26
|
+
## Consequences
|
|
27
|
+
|
|
28
|
+
- New resources require: one resource class module + one client factory module (optionally space/environment factory modules) + an entry in `ResourceBuilder::DEFAULT_RESOURCE_MAPPING`
|
|
29
|
+
- The `associated_class` naming-convention magic is clever but brittle — the factory class name must match the resource class name precisely (e.g., `ClientEntryMethodsFactory` → `Contentful::Management::Entry`)
|
|
30
|
+
- Factory instances are lightweight scope containers; the `Client` is the singleton holding config and HTTP state
|
|
31
|
+
- Context not found for why this pattern was chosen over a simpler flat API or a registry approach — likely an inherited convention from sibling SDKs
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# VCR Cassettes for Testing
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The SDK makes real HTTP calls to `api.contentful.com`. Testing against the live API requires valid credentials, creates content side-effects, and produces flaky, slow tests. Options:
|
|
10
|
+
|
|
11
|
+
1. **Live API calls** — needs real credentials, slow, side-effects
|
|
12
|
+
2. **Manual stubs** (WebMock alone) — verbose, must hand-craft response bodies
|
|
13
|
+
3. **VCR cassettes** — record real interactions once, replay deterministically offline
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
`vcr` (with `webmock` as the HTTP adapter) was adopted from the initial commit. Cassette YAML files live in `spec/fixtures/vcr_cassettes/`. Tests record against the real CMA API once; subsequent runs replay from cassettes.
|
|
18
|
+
|
|
19
|
+
An additional security measure was added in commit `1a975bc` (v3.6.0): VCR is configured to redact CMA tokens from cassettes before they are committed, preventing accidental credential exposure.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
- Tests are fast, deterministic, run offline — CI needs no API credentials
|
|
24
|
+
- When the CMA changes a response format, cassettes for that resource must be re-recorded
|
|
25
|
+
- New endpoint tests require a one-time recording step with a valid management token
|
|
26
|
+
- Cassettes can go stale silently if API response shapes change without re-recording
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# DynamicEntry Cache for Typed Field Access
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The CMA API returns entry fields as a generic hash (`fields: { title: { 'en-US': 'Hello' } }`). Without schema knowledge, callers must access fields with hash syntax. But content type schemas are available via the API, and many callers want typed, named accessors on entries (e.g., `entry.title` instead of `entry.fields[:title]['en-US']`).
|
|
10
|
+
|
|
11
|
+
Two approaches:
|
|
12
|
+
1. **Always use hash access** — simple but verbose, no IDE completion
|
|
13
|
+
2. **Pre-fetch schemas at client init and generate per-content-type classes** — convenient but requires an API call at startup and adds a caching layer
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
`DynamicEntry` was introduced to support optional typed field access. At `Client` init, if `dynamic_entries: { space_id => env_id }` is specified, the client fetches all content types for each environment and generates `DynamicEntry` subclasses via `DynamicEntry.create(content_type, client)`. These are cached in `Client#dynamic_entry_cache` (a plain Ruby hash). `ResourceBuilder` checks this cache when deserializing entries and returns the typed subclass instead of the generic `Entry`.
|
|
18
|
+
|
|
19
|
+
Content type caching can be disabled with `disable_content_type_caching: true`. The cache can be refreshed at any time via `client.update_dynamic_entry_cache_for_environment!(env)`.
|
|
20
|
+
|
|
21
|
+
Source: commit archaeology — `DynamicEntry` was introduced in 2014 (commit `0e71be1`); the `disable_content_type_caching` option was added later in 2017 (commit `92faf6c`).
|
|
22
|
+
|
|
23
|
+
## Consequences
|
|
24
|
+
|
|
25
|
+
- Callers using `dynamic_entries` get named field accessors and better IDE support
|
|
26
|
+
- Startup time increases by one `content_types.all` API call per configured environment
|
|
27
|
+
- If content type schemas change after client init, the cache becomes stale — callers must refresh or reinitialize the client
|
|
28
|
+
- Cache is stored per client instance, not globally; thread safety requires separate client instances per thread
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CI/CD Migration: Travis CI → CircleCI → GitHub Actions + Devcontainers
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The repo has undergone two CI vendor migrations:
|
|
10
|
+
|
|
11
|
+
1. **Travis CI → CircleCI** (commit `6b3b482`, ~2021): Travis CI moved toward a paid model for open-source projects. CircleCI was the Contentful team standard at the time.
|
|
12
|
+
|
|
13
|
+
2. **CircleCI → GitHub Actions + devcontainers** (commit `8940acd`, DX-822, March 2026): CircleCI caused 401 errors for forked-repo PRs, preventing external contributors from running CI. The DX team migrated all SDK repos to GitHub Actions simultaneously. The devcontainer workflow was introduced to ensure local development and CI use identical environments.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
All CI now runs via `.github/workflows/ci.yml`. The workflow uses the devcontainer Dockerfile (`ARG RUBY_VERSION=3.4` default) to run `bundle _2.3.26_ exec rake rspec_rubocop` across Ruby 3.2, 3.3, and 3.4. The CI matrix uses the same container that developers use locally, eliminating "works on my machine" divergence.
|
|
18
|
+
|
|
19
|
+
Bundler is pinned at `2.3.26` in the devcontainer Dockerfile (`gem install bundler:2.3.26`) and all `bundle` invocations use `bundle _2.3.26_`.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
- Fork PRs can run CI without CircleCI credentials — unblocks external contributors
|
|
24
|
+
- Local dev and CI use identical environments (same Dockerfile, same Bundler pin)
|
|
25
|
+
- External contributors need Docker to use the devcontainer locally
|
|
26
|
+
- CircleCI config was deleted with no rollback path
|
|
27
|
+
- Source: DX-822, commit `8940acd` (2026-03-31)
|
data/docs/ADRs/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
<!-- Generated by seed-golden-context | Last updated: 2026-05-05 -->
|
|
4
|
+
|
|
5
|
+
| ADR | Date | Status | Title |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| [001](./2015-01-01-factory-mixin-architecture.md) | 2015-01-01 | Accepted | Factory Mixin Architecture |
|
|
8
|
+
| [002](./2015-01-01-vcr-cassettes-for-testing.md) | 2015-01-01 | Accepted | VCR Cassettes for Testing |
|
|
9
|
+
| [003](./2018-01-01-dynamic-entry-cache.md) | 2018-01-01 | Accepted | DynamicEntry Cache for Typed Field Access |
|
|
10
|
+
| [004](./2021-01-01-ci-migration-to-github-actions.md) | 2021-01-01 | Accepted | CI/CD Migration: Travis CI → CircleCI → GitHub Actions + Devcontainers |
|
|
@@ -141,16 +141,28 @@ module Contentful
|
|
|
141
141
|
# Allows listing all usage periods for organization grouped by organization.
|
|
142
142
|
# @see _ README for details.
|
|
143
143
|
#
|
|
144
|
+
# @deprecated The `GET /organizations/:organization_id/organization_periodic_usages`
|
|
145
|
+
# endpoint is deprecated in favor of the new Usage API. It will be removed on
|
|
146
|
+
# 2027-02-28, after which requests will return 410 Gone.
|
|
144
147
|
# @return [Contentful::Management::ClientOrganizationPeriodicUsageMethodsFactory]
|
|
145
148
|
def organization_periodic_usages(organization_id)
|
|
149
|
+
warn '[DEPRECATION] `Client#organization_periodic_usages` calls the legacy ' \
|
|
150
|
+
'organization_periodic_usages endpoint, which is deprecated and will be ' \
|
|
151
|
+
'removed on 2027-02-28. Migrate to the new Usage API.'
|
|
146
152
|
ClientOrganizationPeriodicUsageMethodsFactory.new(self, organization_id)
|
|
147
153
|
end
|
|
148
154
|
|
|
149
155
|
# Allows listing all usage periods for organization grouped by space.
|
|
150
156
|
# @see _ README for details.
|
|
151
157
|
#
|
|
158
|
+
# @deprecated The `GET /organizations/:organization_id/space_periodic_usages`
|
|
159
|
+
# endpoint is deprecated in favor of the new Usage API. It will be removed on
|
|
160
|
+
# 2027-02-28, after which requests will return 410 Gone.
|
|
152
161
|
# @return [Contentful::Management::ClientSpacePeriodicUsageMethodsFactory]
|
|
153
162
|
def space_periodic_usages(organization_id)
|
|
163
|
+
warn '[DEPRECATION] `Client#space_periodic_usages` calls the legacy ' \
|
|
164
|
+
'space_periodic_usages endpoint, which is deprecated and will be ' \
|
|
165
|
+
'removed on 2027-02-28. Migrate to the new Usage API.'
|
|
154
166
|
ClientSpacePeriodicUsageMethodsFactory.new(self, organization_id)
|
|
155
167
|
end
|
|
156
168
|
|
|
@@ -6,6 +6,8 @@ module Contentful
|
|
|
6
6
|
module Management
|
|
7
7
|
# Wrapper for Organization Periodic Usages for usage from within Client
|
|
8
8
|
# @private
|
|
9
|
+
# @deprecated Wraps the legacy organization_periodic_usages endpoint, which is
|
|
10
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
9
11
|
class ClientOrganizationPeriodicUsageMethodsFactory
|
|
10
12
|
include Contentful::Management::ClientAssociationMethodsFactory
|
|
11
13
|
|
|
@@ -14,6 +16,8 @@ module Contentful
|
|
|
14
16
|
@organization_id = organization_id
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# @deprecated Calls the legacy organization_periodic_usages endpoint, which is
|
|
20
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
17
21
|
def all(params = {})
|
|
18
22
|
@resource_requester.all(
|
|
19
23
|
{
|
|
@@ -6,6 +6,8 @@ module Contentful
|
|
|
6
6
|
module Management
|
|
7
7
|
# Wrapper for Space Periodic Usages for usage from within Client
|
|
8
8
|
# @private
|
|
9
|
+
# @deprecated Wraps the legacy space_periodic_usages endpoint, which is
|
|
10
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
9
11
|
class ClientSpacePeriodicUsageMethodsFactory
|
|
10
12
|
include Contentful::Management::ClientAssociationMethodsFactory
|
|
11
13
|
|
|
@@ -14,6 +16,8 @@ module Contentful
|
|
|
14
16
|
@organization_id = organization_id
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# @deprecated Calls the legacy space_periodic_usages endpoint, which is
|
|
20
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
17
21
|
def all(params = {})
|
|
18
22
|
@resource_requester.all(
|
|
19
23
|
{
|
|
@@ -6,6 +6,8 @@ module Contentful
|
|
|
6
6
|
module Management
|
|
7
7
|
# Resource class for OrganizationPeriodicUsage.
|
|
8
8
|
# @see _ https://www.contentful.com/developers/docs/references/content-management-api/#/reference/usage/organization-usage/get-organization-usage/console/curl
|
|
9
|
+
# @deprecated Backed by the legacy organization_periodic_usages endpoint, which is
|
|
10
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
9
11
|
class OrganizationPeriodicUsage
|
|
10
12
|
include Contentful::Management::Resource
|
|
11
13
|
include Contentful::Management::Resource::Refresher
|
|
@@ -30,8 +32,13 @@ module Contentful
|
|
|
30
32
|
# @param [String] organization_id
|
|
31
33
|
# @param [Hash] params
|
|
32
34
|
#
|
|
35
|
+
# @deprecated The legacy organization_periodic_usages endpoint is deprecated in
|
|
36
|
+
# favor of the new Usage API and will be removed on 2027-02-28.
|
|
33
37
|
# @return [Contentful::Management::Array<Contentful::Management::OrganizationPeriodicUsage>]
|
|
34
38
|
def self.all(client, organization_id, params = {})
|
|
39
|
+
warn '[DEPRECATION] `OrganizationPeriodicUsage.all` calls the legacy ' \
|
|
40
|
+
'organization_periodic_usages endpoint, which is deprecated and will be ' \
|
|
41
|
+
'removed on 2027-02-28. Migrate to the new Usage API.'
|
|
35
42
|
ClientOrganizationPeriodicUsageMethodsFactory.new(client, organization_id).all(params)
|
|
36
43
|
end
|
|
37
44
|
|
|
@@ -6,6 +6,8 @@ module Contentful
|
|
|
6
6
|
module Management
|
|
7
7
|
# Resource class for SpacePeriodicUsage.
|
|
8
8
|
# @see _ https://www.contentful.com/developers/docs/references/content-management-api/#/reference/usage/space-usage/get-space-usage/console/curl
|
|
9
|
+
# @deprecated Backed by the legacy space_periodic_usages endpoint, which is
|
|
10
|
+
# deprecated in favor of the new Usage API and will be removed on 2027-02-28.
|
|
9
11
|
class SpacePeriodicUsage
|
|
10
12
|
include Contentful::Management::Resource
|
|
11
13
|
include Contentful::Management::Resource::Refresher
|
|
@@ -30,8 +32,13 @@ module Contentful
|
|
|
30
32
|
# @param [String] organization_id
|
|
31
33
|
# @param [Hash] params
|
|
32
34
|
#
|
|
35
|
+
# @deprecated The legacy space_periodic_usages endpoint is deprecated in favor
|
|
36
|
+
# of the new Usage API and will be removed on 2027-02-28.
|
|
33
37
|
# @return [Contentful::Management::Array<Contentful::Management::SpacePeriodicUsage>]
|
|
34
38
|
def self.all(client, organization_id, params = {})
|
|
39
|
+
warn '[DEPRECATION] `SpacePeriodicUsage.all` calls the legacy ' \
|
|
40
|
+
'space_periodic_usages endpoint, which is deprecated and will be ' \
|
|
41
|
+
'removed on 2027-02-28. Migrate to the new Usage API.'
|
|
35
42
|
ClientSpacePeriodicUsageMethodsFactory.new(client, organization_id).all(params)
|
|
36
43
|
end
|
|
37
44
|
|
data/renovate.json
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: contentful-management
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Piotr Protas
|
|
@@ -279,16 +279,24 @@ executables:
|
|
|
279
279
|
extensions: []
|
|
280
280
|
extra_rdoc_files: []
|
|
281
281
|
files:
|
|
282
|
-
- ".
|
|
282
|
+
- ".bito.yaml"
|
|
283
|
+
- ".bito/guidelines/domain-invariants.txt"
|
|
284
|
+
- ".bito/guidelines/repo-truth-and-boundaries.txt"
|
|
285
|
+
- ".bito/guidelines/review-posture.txt"
|
|
286
|
+
- ".devcontainer/Dockerfile"
|
|
287
|
+
- ".devcontainer/devcontainer.json"
|
|
283
288
|
- ".github/CODEOWNERS"
|
|
284
|
-
- ".github/workflows/
|
|
289
|
+
- ".github/workflows/ci.yml"
|
|
285
290
|
- ".gitignore"
|
|
286
291
|
- ".reek"
|
|
287
292
|
- ".rspec"
|
|
288
293
|
- ".rubocop.yml"
|
|
289
294
|
- ".rubocop_todo.yml"
|
|
290
295
|
- ".yardopts"
|
|
296
|
+
- AGENTS.md
|
|
297
|
+
- ARCHITECTURE.md
|
|
291
298
|
- CHANGELOG.md
|
|
299
|
+
- CONTRIBUTING.md
|
|
292
300
|
- Gemfile
|
|
293
301
|
- Guardfile
|
|
294
302
|
- LICENSE.txt
|
|
@@ -298,6 +306,12 @@ files:
|
|
|
298
306
|
- bin/cma-console
|
|
299
307
|
- catalog-info.yaml
|
|
300
308
|
- contentful-management.gemspec
|
|
309
|
+
- docs/ADRs/2015-01-01-factory-mixin-architecture.md
|
|
310
|
+
- docs/ADRs/2015-01-01-vcr-cassettes-for-testing.md
|
|
311
|
+
- docs/ADRs/2018-01-01-dynamic-entry-cache.md
|
|
312
|
+
- docs/ADRs/2021-01-01-ci-migration-to-github-actions.md
|
|
313
|
+
- docs/ADRs/README.md
|
|
314
|
+
- docs/specs/README.md
|
|
301
315
|
- examples/blog.rb
|
|
302
316
|
- examples/content_types.rb
|
|
303
317
|
- examples/create_space.rb
|
|
@@ -406,6 +420,7 @@ files:
|
|
|
406
420
|
- lib/contentful/management/webhook_health.rb
|
|
407
421
|
- lib/contentful/management/webhook_webhook_call_methods_factory.rb
|
|
408
422
|
- lib/contentful/management/webhook_webhook_health_methods_factory.rb
|
|
423
|
+
- renovate.json
|
|
409
424
|
- spec/fixtures/json_responses/400_details_errors_object.json
|
|
410
425
|
- spec/fixtures/json_responses/400_details_errors_string.json
|
|
411
426
|
- spec/fixtures/json_responses/400_details_string.json
|
data/.circleci/config.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
version: 2.1
|
|
2
|
-
|
|
3
|
-
jobs:
|
|
4
|
-
test_and_lint:
|
|
5
|
-
parameters:
|
|
6
|
-
ruby-version:
|
|
7
|
-
type: string
|
|
8
|
-
docker:
|
|
9
|
-
- image: cimg/ruby:<< parameters.ruby-version >>
|
|
10
|
-
steps:
|
|
11
|
-
- checkout
|
|
12
|
-
- run: gem install bundler:2.3.26
|
|
13
|
-
- run: bundle install
|
|
14
|
-
- run: bundle exec rake rspec_rubocop
|
|
15
|
-
|
|
16
|
-
workflows:
|
|
17
|
-
version: 2
|
|
18
|
-
workflow:
|
|
19
|
-
jobs:
|
|
20
|
-
- test_and_lint:
|
|
21
|
-
matrix:
|
|
22
|
-
parameters:
|
|
23
|
-
ruby-version: ["3.2", "3.3", "3.4"]
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "CodeQL Scan for GitHub Actions Workflows"
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
push:
|
|
6
|
-
branches: [master]
|
|
7
|
-
paths: [".github/workflows/**"]
|
|
8
|
-
pull_request:
|
|
9
|
-
branches: [master]
|
|
10
|
-
paths: [".github/workflows/**"]
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
analyze:
|
|
14
|
-
name: Analyze GitHub Actions workflows
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
permissions:
|
|
17
|
-
actions: read
|
|
18
|
-
contents: read
|
|
19
|
-
security-events: write
|
|
20
|
-
|
|
21
|
-
steps:
|
|
22
|
-
- uses: actions/checkout@v4
|
|
23
|
-
|
|
24
|
-
- name: Initialize CodeQL
|
|
25
|
-
uses: github/codeql-action/init@v3
|
|
26
|
-
with:
|
|
27
|
-
languages: actions
|
|
28
|
-
|
|
29
|
-
- name: Run CodeQL Analysis
|
|
30
|
-
uses: github/codeql-action/analyze@v3
|
|
31
|
-
with:
|
|
32
|
-
category: actions
|