@chordcommerce/analytics 1.21.1 → 1.21.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,15 +168,28 @@ This package uses [semantic-release](https://github.com/semantic-release/semanti
168
168
 
169
169
  ### How it works
170
170
 
171
+ The repo has a single CI workflow (`.github/workflows/ci.yml`) with two jobs:
172
+
173
+ 1. **`test`** — Runs lint, prettier, build, and tests for all packages. Triggers on every push to `main`/`beta` and on every pull request.
174
+ 2. **`release`** — Runs only on `main` and `beta` branches, after `test` passes. Runs `semantic-release` to determine if a new version should be published.
175
+
176
+ The release flow:
177
+
171
178
  1. Merge a PR into `main` (stable) or `beta` (pre-release).
172
- 2. The **Test** GitHub Actions workflow runs.
173
- 3. On success, the **Semantic Release** workflow runs automatically.
174
- 4. `semantic-release` analyzes commit messages since the last release and determines the version bump.
175
- 5. The package is published to npm and GitHub Packages, CHANGELOG.md is updated, and a Git tag is created.
179
+ 2. The `test` job runs. If it passes, the `release` job starts.
180
+ 3. `semantic-release` analyzes commit messages since the last release tag.
181
+ 4. If releasable commits exist, the package is published to npm and GitHub Packages, CHANGELOG.md is updated, and a Git tag is created.
182
+ 5. If no releasable commits exist, `semantic-release` exits cleanly and nothing is published.
176
183
 
177
- ### Commit message conventions
184
+ ### When a release happens (and when it doesn't)
178
185
 
179
- This repo enforces [Conventional Commits](https://www.conventionalcommits.org/) via commitlint. The commit message prefix determines the version bump:
186
+ A release is published **only** when all three conditions are met:
187
+
188
+ 1. A push lands on `main` or `beta` (via merged PR or direct push)
189
+ 2. Tests pass
190
+ 3. There are new commits since the last release tag with a releasable prefix
191
+
192
+ **Releasable commit prefixes** (these trigger a version bump):
180
193
 
181
194
  | Prefix | Version Bump | Example |
182
195
  | -------------------------- | --------------------- | --------------------------------------------------------- |
@@ -184,19 +197,35 @@ This repo enforces [Conventional Commits](https://www.conventionalcommits.org/)
184
197
  | `feat:` | Minor (1.0.0 → 1.1.0) | `feat: add trackOrderCompleted method` |
185
198
  | `BREAKING CHANGE:` in body | Major (1.0.0 → 2.0.0) | Commit body contains `BREAKING CHANGE: remove legacy API` |
186
199
 
187
- Other prefixes (`chore:`, `ci:`, `docs:`, `refactor:`, `test:`, etc.) do **not** trigger a release.
200
+ **Non-releasable prefixes** (these do NOT trigger a release):
201
+
202
+ `chore:`, `ci:`, `docs:`, `refactor:`, `test:`, `style:`, `build:`, `perf:`
203
+
204
+ If a merge to `main` or `beta` contains only non-releasable commits, the release job runs but `semantic-release` determines no release is needed and exits without publishing.
188
205
 
189
206
  ### Monorepo scoping
190
207
 
191
- This repo uses [semantic-release-monorepo](https://github.com/pmowrer/semantic-release-monorepo) to scope releases to individual packages. Only commits that touch files within a package's directory (e.g., `packages/core/`) are considered when determining the next version. Commits that only modify root-level files like `.github/workflows/`, `.nvmrc`, or other packages will not trigger a release for this package.
208
+ This repo uses [semantic-release-monorepo](https://github.com/pmowrer/semantic-release-monorepo) to scope releases to individual packages. Only commits that touch files within a package's directory (e.g., `packages/core/`) are considered when determining the next version. A `feat:` commit that only modifies `.github/workflows/` or another package's directory will **not** trigger a release for `@chordcommerce/analytics`.
192
209
 
193
210
  ### Pre-releases
194
211
 
195
- Merging to `beta` creates pre-release versions (e.g., `1.21.0-beta.1`). Use this for testing before promoting to `main`.
212
+ Merging to `beta` creates pre-release versions (e.g., `1.22.0-beta.1`). Use this for testing before promoting to `main`. Only `main` and `beta` are currently configured for automatic releases in the CI workflow. Additional prerelease branches (e.g., `alpha`) can be enabled by adding them to both the workflow's branch filter in `ci.yml` and the semantic-release branch config.
213
+
214
+ ### Beta release workflow
215
+
216
+ 1. Create a feature branch off `beta` and do your work.
217
+ 2. Create a PR targeting the `beta` branch and merge it.
218
+ 3. The CI workflow runs tests, then `semantic-release` publishes a beta version (e.g., `1.22.0-beta.1`).
219
+ 4. Install with: `npm install @chordcommerce/analytics@beta`
220
+
221
+ ### Promoting beta to production
222
+
223
+ 1. Create a PR from `beta` → `main` and merge it.
224
+ 2. The CI workflow runs tests, then `semantic-release` publishes a stable version on `main`.
196
225
 
197
226
  ### Dry run
198
227
 
199
- You can preview what a release would do without publishing by manually triggering the Semantic Release workflow in GitHub Actions with `dryRun` set to `true`.
228
+ You can preview what a release would do without publishing by manually triggering the CI workflow in GitHub Actions with `dryRun` set to `true`.
200
229
 
201
230
  ## Support
202
231
 
@@ -80,15 +80,9 @@ var addCdpSnippet = function (options) { return __awaiter(void 0, void 0, void 0
80
80
  _a.sent();
81
81
  initialConsent = null;
82
82
  if (!options.consent) return [3 /*break*/, 3];
83
- if (options.debug && options.enableLogging) {
84
- console.log('[Chord Analytics DEBUG]: Setting up consent management');
85
- }
86
83
  return [4 /*yield*/, (0, index_js_1.addCdpConsent)(options).init()];
87
84
  case 2:
88
85
  initialConsent = _a.sent();
89
- if (options.debug && options.enableLogging) {
90
- console.log('[Chord Analytics DEBUG]: Consent management setup complete');
91
- }
92
86
  _a.label = 3;
93
87
  case 3:
94
88
  if (options.debug && options.enableLogging) {
@@ -152,7 +152,7 @@ var addCdpConsent = function (options) {
152
152
  * or null if consent setup failed or was blocked.
153
153
  */
154
154
  var init = function () { return __awaiter(void 0, void 0, void 0, function () {
155
- var canBeTracked, result, error_1;
155
+ var canBeTracked, error_1;
156
156
  return __generator(this, function (_a) {
157
157
  switch (_a.label) {
158
158
  case 0:
@@ -181,18 +181,13 @@ var addCdpConsent = function (options) {
181
181
  _a.trys.push([2, 4, , 5]);
182
182
  return [4 /*yield*/, setupConsentManagement()];
183
183
  case 3:
184
- result = _a.sent();
185
- if (options.debug && options.enableLogging) {
186
- console.log('[Chord Analytics DEBUG]: setupConsentManagement() returned:', JSON.stringify(result));
187
- }
188
- return [2 /*return*/, result];
184
+ _a.sent();
185
+ return [3 /*break*/, 5];
189
186
  case 4:
190
187
  error_1 = _a.sent();
191
- if (options.debug && options.enableLogging) {
192
- console.error('[Chord Analytics DEBUG]: Error setting up consent management:', error_1);
193
- }
194
- return [2 /*return*/, null];
195
- case 5: return [2 /*return*/];
188
+ console.error('Error setting up consent management:', error_1);
189
+ return [3 /*break*/, 5];
190
+ case 5: return [2 /*return*/, null];
196
191
  }
197
192
  });
198
193
  }); };
@@ -71,6 +71,16 @@ export declare const getOneTrustActiveGroups: () => string | undefined;
71
71
  * getNormalizedActiveGroupIds() // ["C0001", "C0003"]
72
72
  */
73
73
  export declare const getNormalizedActiveGroupIds: () => string[];
74
+ /**
75
+ * Check if OnetrustActiveGroups is populated with actual consent categories.
76
+ *
77
+ * OneTrust sets window.OnetrustActiveGroups after the API object is available,
78
+ * so this guards against a race condition where the API exists but consent
79
+ * categories have not yet been populated.
80
+ *
81
+ * @returns true if OnetrustActiveGroups is a non-empty string with at least one group ID
82
+ */
83
+ export declare const hasActiveGroups: () => boolean;
74
84
  /**
75
85
  * Get all configured consent groups from OneTrust.
76
86
  *
@@ -8,7 +8,7 @@
8
8
  * Modeled after analytics-next's onetrust-api.ts
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.coerceConsentModel = exports.getNormalizedCategories = exports.getAllGroups = exports.getNormalizedActiveGroupIds = exports.getOneTrustActiveGroups = exports.getOneTrustGlobal = exports.OtConsentModel = void 0;
11
+ exports.coerceConsentModel = exports.getNormalizedCategories = exports.getAllGroups = exports.hasActiveGroups = exports.getNormalizedActiveGroupIds = exports.getOneTrustActiveGroups = exports.getOneTrustGlobal = exports.OtConsentModel = void 0;
12
12
  /**
13
13
  * OneTrust consent model types
14
14
  */
@@ -70,6 +70,19 @@ var getNormalizedActiveGroupIds = function () {
70
70
  return groups.trim().split(',').filter(Boolean);
71
71
  };
72
72
  exports.getNormalizedActiveGroupIds = getNormalizedActiveGroupIds;
73
+ /**
74
+ * Check if OnetrustActiveGroups is populated with actual consent categories.
75
+ *
76
+ * OneTrust sets window.OnetrustActiveGroups after the API object is available,
77
+ * so this guards against a race condition where the API exists but consent
78
+ * categories have not yet been populated.
79
+ *
80
+ * @returns true if OnetrustActiveGroups is a non-empty string with at least one group ID
81
+ */
82
+ var hasActiveGroups = function () {
83
+ return (0, exports.getNormalizedActiveGroupIds)().length > 0;
84
+ };
85
+ exports.hasActiveGroups = hasActiveGroups;
73
86
  /**
74
87
  * Get all configured consent groups from OneTrust.
75
88
  *
@@ -63,7 +63,7 @@ var onetrustConsentAdapter = function (options) {
63
63
  },
64
64
  userCanBeTracked: userCanBeTracked,
65
65
  getCategories: function () { return (0, onetrust_api_js_1.getNormalizedCategories)(); },
66
- isConsentReady: function () { return !!(0, onetrust_api_js_1.getOneTrustGlobal)(); },
66
+ isConsentReady: function () { return !!(0, onetrust_api_js_1.getOneTrustGlobal)() && (0, onetrust_api_js_1.hasActiveGroups)(); },
67
67
  getConsentModel: function () {
68
68
  var api = (0, onetrust_api_js_1.getOneTrustGlobal)();
69
69
  if (!api)
@@ -44,13 +44,7 @@ export const addCdpSnippet = (options) => __awaiter(void 0, void 0, void 0, func
44
44
  yield cdpReadyPromise;
45
45
  let initialConsent = null;
46
46
  if (options.consent) {
47
- if (options.debug && options.enableLogging) {
48
- console.log('[Chord Analytics DEBUG]: Setting up consent management');
49
- }
50
47
  initialConsent = yield addCdpConsent(options).init();
51
- if (options.debug && options.enableLogging) {
52
- console.log('[Chord Analytics DEBUG]: Consent management setup complete');
53
- }
54
48
  }
55
49
  if (options.debug && options.enableLogging) {
56
50
  console.log('[Chord Analytics DEBUG]: CDP snippet added, initialConsent:', JSON.stringify(initialConsent));
@@ -121,19 +121,14 @@ export const addCdpConsent = (options) => {
121
121
  return null;
122
122
  }
123
123
  }
124
+ // Set up consent management and wait for it to complete
124
125
  try {
125
- const result = yield setupConsentManagement();
126
- if (options.debug && options.enableLogging) {
127
- console.log('[Chord Analytics DEBUG]: setupConsentManagement() returned:', JSON.stringify(result));
128
- }
129
- return result;
126
+ yield setupConsentManagement();
130
127
  }
131
128
  catch (error) {
132
- if (options.debug && options.enableLogging) {
133
- console.error('[Chord Analytics DEBUG]: Error setting up consent management:', error);
134
- }
135
- return null;
129
+ console.error('Error setting up consent management:', error);
136
130
  }
131
+ return null;
137
132
  });
138
133
  const getConsentConfig = () => {
139
134
  if (!(adapter === null || adapter === void 0 ? void 0 : adapter.getCategories) || !(adapter === null || adapter === void 0 ? void 0 : adapter.isConsentReady)) {
@@ -71,6 +71,16 @@ export declare const getOneTrustActiveGroups: () => string | undefined;
71
71
  * getNormalizedActiveGroupIds() // ["C0001", "C0003"]
72
72
  */
73
73
  export declare const getNormalizedActiveGroupIds: () => string[];
74
+ /**
75
+ * Check if OnetrustActiveGroups is populated with actual consent categories.
76
+ *
77
+ * OneTrust sets window.OnetrustActiveGroups after the API object is available,
78
+ * so this guards against a race condition where the API exists but consent
79
+ * categories have not yet been populated.
80
+ *
81
+ * @returns true if OnetrustActiveGroups is a non-empty string with at least one group ID
82
+ */
83
+ export declare const hasActiveGroups: () => boolean;
74
84
  /**
75
85
  * Get all configured consent groups from OneTrust.
76
86
  *
@@ -64,6 +64,18 @@ export const getNormalizedActiveGroupIds = () => {
64
64
  return [];
65
65
  return groups.trim().split(',').filter(Boolean);
66
66
  };
67
+ /**
68
+ * Check if OnetrustActiveGroups is populated with actual consent categories.
69
+ *
70
+ * OneTrust sets window.OnetrustActiveGroups after the API object is available,
71
+ * so this guards against a race condition where the API exists but consent
72
+ * categories have not yet been populated.
73
+ *
74
+ * @returns true if OnetrustActiveGroups is a non-empty string with at least one group ID
75
+ */
76
+ export const hasActiveGroups = () => {
77
+ return getNormalizedActiveGroupIds().length > 0;
78
+ };
67
79
  /**
68
80
  * Get all configured consent groups from OneTrust.
69
81
  *
@@ -1,4 +1,4 @@
1
- import { getOneTrustGlobal, getNormalizedCategories, coerceConsentModel, } from './onetrust-api.js';
1
+ import { getOneTrustGlobal, getNormalizedCategories, coerceConsentModel, hasActiveGroups, } from './onetrust-api.js';
2
2
  /**
3
3
  * Returns whether the user can currently be tracked.
4
4
  * Uses duck typing to check if OneTrust is fully loaded.
@@ -60,7 +60,7 @@ export const onetrustConsentAdapter = (options) => {
60
60
  },
61
61
  userCanBeTracked,
62
62
  getCategories: () => getNormalizedCategories(),
63
- isConsentReady: () => !!getOneTrustGlobal(),
63
+ isConsentReady: () => !!getOneTrustGlobal() && hasActiveGroups(),
64
64
  getConsentModel: () => {
65
65
  const api = getOneTrustGlobal();
66
66
  if (!api)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chordcommerce/analytics",
3
3
  "description": "Chord Commerce event tracking",
4
- "version": "1.21.1",
4
+ "version": "1.21.2-beta.1",
5
5
  "author": "Chord Commerce <help@chord.co> (https://www.chord.co)",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",