Skip to content

Commit 205f368

Browse files
Merge pull request #249 from contentstack/cl-7761
fix(variables): split a variable at the first separator and raise when a stack lookup cannot finish
2 parents a39900d + 90c096f commit 205f368

6 files changed

Lines changed: 52 additions & 20 deletions

File tree

‎.talismanrc‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fileignoreconfig:
1010
- filename: src/transport/rest-client.test.ts
1111
checksum: 3b5751449c61f7a1d71b978ed03b9472f714e12dd1679f691bbd4dd8f84be923
1212
- filename: src/transport/cma-client.ts
13-
checksum: 87d8c6f8a41ea520ecc5fb4b6f71467d44af95f55d1facec1de4778fe22677b3
13+
checksum: 0e144de22c757e8d3efd31c892d6de56796a49ce2c18d0e41ba16eff8dc3d24e
1414
- filename: src/transport/auth-strategy.test.ts
1515
checksum: a4f3e3ea7db10dbf82abbac0207d2231ee740916f476fcda836a2268505295ef
1616
- filename: src/projects/types.ts
@@ -233,7 +233,7 @@ fileignoreconfig:
233233
- "a project name passed on argv to the project resolver"
234234
- "expect\\(Object.keys\\(DEPENDENCIES\\)\\).toEqual\\(\\['project', 'env'\\]\\)"
235235
- filename: AGENTS.md
236-
checksum: 66b94ec2bcb34947bcda15e6364dceee7edb50bfe851e26e6c737425315e81da
236+
checksum: 4766a4bf28eab95d90878b825979cef70c83a79a24fb9450a52d617ce0933f78
237237
allowed_patterns:
238238
- "Do not add an exemption to a guard to make a new file pass"
239239
- "passes its own error wording to `client.request\\(req, MESSAGES\\)`"
@@ -303,7 +303,7 @@ fileignoreconfig:
303303
- filename: src/functions/function.logger.test.ts
304304
checksum: 840b2b2b7f938e0862881871e8d568112631c58e12cf6c8a5be32e7552b8cf9e
305305
- filename: src/transport/cma-client.test.ts
306-
checksum: 9eb4958cd7fa07822954d14c5860c5ca18f0880e4390716ebfa9c826add49311
306+
checksum: 471361febb8e08a50af8cf7e8f81824adc7a31af6231d78baf0e125823f43686
307307
- filename: src/core/catalog.test.ts
308308
checksum: bf8c203a8efc40a75f5407ceb29a639ea9a6e1fa081d02260def2d7e0fab958a
309309
- filename: test/credential-guard.setup.ts
@@ -373,9 +373,9 @@ fileignoreconfig:
373373
- filename: src/variables/variable.page.test.ts
374374
checksum: 9a4e63c0dbfd35b499149a98ce5642787097e9491746b4b874b2eda88f1d2b50
375375
- filename: src/variables/variable.parse.test.ts
376-
checksum: 01441ffca77438dbd29420ad38fbfee160dfa535434e4a3eca1c57c47eeebaa0
376+
checksum: d620d89c4b5cafb80dcf696f430c8fa4e696dfc744f814bcdd74324243010460
377377
- filename: src/variables/variable.parse.ts
378-
checksum: 9dbb9e0c8715e319aa70a4b6a49ae255a3a89207081f6bff0f4c21ea91eed7e7
378+
checksum: 87be9f454ce0ffb48ff5729ccd3c1b105ce9f1030b32278c3359555b983c2142
379379
- filename: src/variables/variable.presenter.test.ts
380380
checksum: 62e8b0eff16ed4408d929092ce6808aeabea001a6cc15780d46c4ac4226eec3d
381381
- filename: src/variables/variable.presenter.ts

‎AGENTS.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,10 @@ under `--data-dir`, always printing the file it chose, and fails with exit 2 whe
343343
exit 2 naming `--dotenv -` for two or more. The API refuses an empty value, so `KEY=` is a usage error naming
344344
the key, and an empty value inside a dotenv file is skipped with its key reported. No value reaches stdout, a
345345
prompt message, a confirmation or an error: `list` masks through `redactedColumn`, and a failed `PATCH` is worded from
346-
`ENVIRONMENT_ERROR_MESSAGES`, never from the API's own text.
346+
`ENVIRONMENT_ERROR_MESSAGES`. A code with no entry there falls back to the API's own message, which the
347+
service words from keys at most, never from a value (CL-7179). The stack and delivery-token lookups behind
348+
`--from-stack` walk the Management API 100 per page and raise `CmaScanLimitError` after `MAX_PAGES` pages rather
349+
than return a truncated list.
347350

348351
`projects:create` and `env:create` take `--var` and `--dotenv` and reach the same resolver through
349352
`EnvironmentBuilder`, which asks last - after Contentstack Authentication, where V1 asked - with no auto-detection.

‎src/transport/cma-client.test.ts‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('createCmaSession', () => {
209209
]);
210210
});
211211

212-
it('gives up after a bounded number of pages when the server keeps answering', async () => {
212+
it('raises after a bounded number of pages when the server keeps answering, rather than dropping the rest', async () => {
213213
let calls = 0;
214214
sdkClient.mockReset();
215215
sdkClient.mockResolvedValue({
@@ -224,10 +224,22 @@ describe('createCmaSession', () => {
224224
});
225225
const session = createCmaSession({ cma: 'https://api.contentstack.io', analyticsInfo: 'cli/2.0.0' });
226226

227-
const { items } = await session.fetchStacks('org7');
228-
227+
await expect(session.fetchStacks('org7')).rejects.toThrow(
228+
'Stopped after fetching 100 pages of stacks without reaching the end of the list.',
229+
);
229230
expect(calls).toBe(100);
230-
expect(items).toHaveLength(100);
231+
});
232+
233+
it('names delivery tokens when it is their list that cannot be finished', async () => {
234+
sdkClient.mockReset();
235+
sdkClient.mockResolvedValue({
236+
stack: () => ({ deliveryToken: () => ({ query: () => ({ find: async () => ({ items: [{ token: 'same' }] }) }) }) }),
237+
});
238+
const session = createCmaSession({ cma: 'https://api.contentstack.io', analyticsInfo: 'cli/2.0.0' });
239+
240+
await expect(session.fetchDeliveryTokens('blt1')).rejects.toThrow(
241+
'Stopped after fetching 100 pages of delivery tokens without reaching the end of the list.',
242+
);
231243
});
232244

233245
it('treats a reply with no items as an empty list', async () => {

‎src/transport/cma-client.ts‎

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { ContentstackClient } from '@contentstack/cli-utilities';
2+
3+
import { UsageError } from '../core/errors';
24
import {
35
configHandler,
46
managementSDKClient,
@@ -15,20 +17,30 @@ export interface CmaCollection {
1517
const PAGE_SIZE = 100;
1618
const MAX_PAGES = 100;
1719

18-
async function allPages(fetchPage: (skip: number) => Promise<CmaCollection>): Promise<CmaCollection> {
20+
export class CmaScanLimitError extends UsageError {
21+
constructor(subject: string) {
22+
super(`Stopped after fetching ${MAX_PAGES} pages of ${subject} without reaching the end of the list.`);
23+
this.name = 'CmaScanLimitError';
24+
}
25+
}
26+
27+
async function allPages(
28+
fetchPage: (skip: number) => Promise<CmaCollection>,
29+
subject: string,
30+
): Promise<CmaCollection> {
1931
const items: unknown[] = [];
2032

2133
for (let page = 0; page < MAX_PAGES; page += 1) {
2234
const batch = (await fetchPage(items.length)).items ?? [];
2335

2436
if (batch.length === 0) {
25-
break;
37+
return { items };
2638
}
2739

2840
items.push(...batch);
2941
}
3042

31-
return { items };
43+
throw new CmaScanLimitError(subject);
3244
}
3345

3446
export interface CmaSession {
@@ -71,12 +83,16 @@ export function createCmaSession(options: CmaSessionOptions): CmaSession {
7183
fetchOrganizations: async (query) => (await open()).organization().fetchAll(query),
7284
fetchOrganization: async (uid) => (await open()).organization(uid).fetch(),
7385
fetchStacks: (organizationUid) =>
74-
allPages(async (skip) =>
75-
(await open()).stack().query({ organization_uid: organizationUid, limit: PAGE_SIZE, skip }).find(),
86+
allPages(
87+
async (skip) =>
88+
(await open()).stack().query({ organization_uid: organizationUid, limit: PAGE_SIZE, skip }).find(),
89+
'stacks',
7690
),
7791
fetchDeliveryTokens: (apiKey) =>
78-
allPages(async (skip) =>
79-
(await open()).stack({ api_key: apiKey }).deliveryToken().query({ limit: PAGE_SIZE, skip }).find(),
92+
allPages(
93+
async (skip) =>
94+
(await open()).stack({ api_key: apiKey }).deliveryToken().query({ limit: PAGE_SIZE, skip }).find(),
95+
'delivery tokens',
8096
),
8197
scopedOrganizationUid: () => {
8298
const uid: unknown = configHandler.get(OAUTH_ORG_UID_CONFIG_FIELD);

‎src/variables/variable.parse.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ describe('variable parse', () => {
1818
expect(parseAssignment('API_KEY')).toEqual({ key: 'API_KEY', value: undefined });
1919
});
2020

21-
it('accepts a colon as the separator only when no equals sign is present', () => {
21+
it('splits on whichever of the equals sign and the colon comes first, so the value may hold the other', () => {
2222
expect(parseAssignment('API_URL:value')).toEqual({ key: 'API_URL', value: 'value' });
2323
expect(parseAssignment('API_URL=https://x.test:8080')).toEqual({ key: 'API_URL', value: 'https://x.test:8080' });
24+
expect(parseAssignment('API_URL:https://x.test?a=b')).toEqual({ key: 'API_URL', value: 'https://x.test?a=b' });
2425
});
2526

2627
it('trims the key but not the value', () => {

‎src/variables/variable.parse.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ export interface Assignment {
66
}
77

88
function separatorIndex(token: string): number {
9-
const equals = token.indexOf('=');
9+
const separators = [token.indexOf('='), token.indexOf(':')].filter((index) => index !== -1);
1010

11-
return equals === -1 ? token.indexOf(':') : equals;
11+
return separators.length === 0 ? -1 : Math.min(...separators);
1212
}
1313

1414
export function parseAssignment(token: string): Assignment {

0 commit comments

Comments
 (0)