Skip to content

Commit 01fc41b

Browse files
Adds command spo brandcenter colors list. Closes #7162
1 parent 0848f68 commit 01fc41b

6 files changed

Lines changed: 451 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import Global from '../../_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# spo brandcenter colors list
6+
7+
Lists the brand center colors
8+
9+
## Usage
10+
11+
```sh
12+
m365 spo brandcenter colors list [options]
13+
```
14+
15+
## Options
16+
17+
<Global />
18+
19+
## Permissions
20+
21+
<Tabs>
22+
<TabItem value="Delegated">
23+
24+
| Resource | Permissions |
25+
|------------|---------------|
26+
| SharePoint | AllSites.Read |
27+
28+
</TabItem>
29+
<TabItem value="Application">
30+
31+
| Resource | Permissions |
32+
|------------|----------------|
33+
| SharePoint | Sites.Read.All |
34+
35+
</TabItem>
36+
</Tabs>
37+
38+
## Remarks
39+
40+
If the brand colors list has not been created yet in the Brand Center, the command will return an empty array.
41+
42+
## Examples
43+
44+
List all brand center colors
45+
46+
```sh
47+
m365 spo brandcenter colors list
48+
```
49+
50+
## Response
51+
52+
<Tabs>
53+
<TabItem value="JSON">
54+
55+
```json
56+
[
57+
{
58+
"Title": "Primary",
59+
"ColorCode": "#0078D4",
60+
"IsVisible": true
61+
},
62+
{
63+
"Title": "Secondary",
64+
"ColorCode": "#FF4500",
65+
"IsVisible": false
66+
}
67+
]
68+
```
69+
70+
</TabItem>
71+
<TabItem value="Text">
72+
73+
```txt
74+
Title ColorCode IsVisible
75+
--------- --------- ---------
76+
Primary #0078D4 true
77+
Secondary #FF4500 false
78+
```
79+
80+
</TabItem>
81+
<TabItem value="CSV">
82+
83+
```csv
84+
Title,ColorCode,IsVisible
85+
Primary,#0078D4,true
86+
Secondary,#FF4500,false
87+
```
88+
89+
</TabItem>
90+
<TabItem value="Markdown">
91+
92+
```md
93+
# spo brandcenter colors list
94+
95+
Date: 4/4/2026
96+
97+
## Primary
98+
99+
Property | Value
100+
---------|-------
101+
Title | Primary
102+
ColorCode | #0078D4
103+
IsVisible | true
104+
105+
## Secondary
106+
107+
Property | Value
108+
---------|-------
109+
Title | Secondary
110+
ColorCode | #FF4500
111+
IsVisible | false
112+
```
113+
114+
</TabItem>
115+
</Tabs>

docs/src/config/sidebars.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,11 @@ const sidebars: SidebarsConfig = {
24412441
},
24422442
{
24432443
brandcenter: [
2444+
{
2445+
type: 'doc',
2446+
label: 'brandcenter colors list',
2447+
id: 'cmd/spo/brandcenter/brandcenter-colors-list'
2448+
},
24442449
{
24452450
type: 'doc',
24462451
label: 'brandcenter settings list',

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const dictionary = [
3535
'center',
3636
'checklist',
3737
'client',
38+
'colors',
3839
'comm',
3940
'command',
4041
'community',

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default {
2020
APPLICATIONCUSTOMIZER_SET: `${prefix} applicationcustomizer set`,
2121
APPPAGE_ADD: `${prefix} apppage add`,
2222
APPPAGE_SET: `${prefix} apppage set`,
23+
BRANDCENTER_COLORS_LIST: `${prefix} brandcenter colors list`,
2324
BRANDCENTER_SETTINGS_LIST: `${prefix} brandcenter settings list`,
2425
CDN_GET: `${prefix} cdn get`,
2526
CDN_ORIGIN_ADD: `${prefix} cdn origin add`,
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import auth from '../../../../Auth.js';
4+
import { cli } from '../../../../cli/cli.js';
5+
import { CommandInfo } from '../../../../cli/CommandInfo.js';
6+
import { Logger } from '../../../../cli/Logger.js';
7+
import { CommandError } from '../../../../Command.js';
8+
import request from '../../../../request.js';
9+
import { telemetry } from '../../../../telemetry.js';
10+
import { odata } from '../../../../utils/odata.js';
11+
import { pid } from '../../../../utils/pid.js';
12+
import { session } from '../../../../utils/session.js';
13+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
14+
import { z } from 'zod';
15+
import commands from '../../commands.js';
16+
import command from './brandcenter-colors-list.js';
17+
18+
describe(commands.BRANDCENTER_COLORS_LIST, () => {
19+
let log: any[];
20+
let logger: Logger;
21+
let loggerLogSpy: sinon.SinonSpy;
22+
let commandInfo: CommandInfo;
23+
let commandOptionsSchema: z.ZodTypeAny;
24+
25+
const configurationResponseWithColors = {
26+
"BrandColorsListId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
27+
"BrandColorsListUrl": {
28+
"DecodedUrl": "https://contoso.sharepoint.com/sites/BrandGuide/_catalogs/brandcolors"
29+
},
30+
"BrandFontLibraryId": "23af51de-856c-4d00-aa11-0d03af0e46e3",
31+
"BrandFontLibraryUrl": {
32+
"DecodedUrl": "https://contoso.sharepoint.com/sites/BrandGuide/Fonts"
33+
},
34+
"IsBrandCenterSiteFeatureEnabled": true,
35+
"IsPublicCdnEnabled": true,
36+
"SiteId": "52b46e48-9c0c-40cb-a955-13eb6c717ff3",
37+
"SiteUrl": "https://contoso.sharepoint.com/sites/BrandGuide"
38+
};
39+
40+
const configurationResponseWithoutColors = {
41+
"BrandColorsListId": "00000000-0000-0000-0000-000000000000",
42+
"BrandColorsListUrl": null,
43+
"BrandFontLibraryId": "23af51de-856c-4d00-aa11-0d03af0e46e3",
44+
"BrandFontLibraryUrl": {
45+
"DecodedUrl": "https://contoso.sharepoint.com/sites/BrandGuide/Fonts"
46+
},
47+
"IsBrandCenterSiteFeatureEnabled": true,
48+
"IsPublicCdnEnabled": true,
49+
"SiteId": "52b46e48-9c0c-40cb-a955-13eb6c717ff3",
50+
"SiteUrl": "https://contoso.sharepoint.com/sites/BrandGuide"
51+
};
52+
53+
const brandColorsListItems = [
54+
{
55+
"OData__SPColorTitle": "Primary",
56+
"OData__SPColorCode": "#0078D4",
57+
"OData__SPColorVisible": true
58+
},
59+
{
60+
"OData__SPColorTitle": "Secondary",
61+
"OData__SPColorCode": "#FF4500",
62+
"OData__SPColorVisible": false
63+
}
64+
];
65+
66+
const mappedBrandColors = [
67+
{
68+
"Title": "Primary",
69+
"ColorCode": "#0078D4",
70+
"IsVisible": true
71+
},
72+
{
73+
"Title": "Secondary",
74+
"ColorCode": "#FF4500",
75+
"IsVisible": false
76+
}
77+
];
78+
79+
before(() => {
80+
sinon.stub(auth, 'restoreAuth').resolves();
81+
sinon.stub(telemetry, 'trackEvent').resolves();
82+
sinon.stub(pid, 'getProcessName').returns('');
83+
sinon.stub(session, 'getId').returns('');
84+
85+
auth.connection.active = true;
86+
auth.connection.spoUrl = 'https://contoso.sharepoint.com';
87+
commandInfo = cli.getCommandInfo(command);
88+
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
89+
});
90+
91+
beforeEach(() => {
92+
log = [];
93+
logger = {
94+
log: async (msg: string) => {
95+
log.push(msg);
96+
},
97+
logRaw: async (msg: string) => {
98+
log.push(msg);
99+
},
100+
logToStderr: async (msg: string) => {
101+
log.push(msg);
102+
}
103+
};
104+
loggerLogSpy = sinon.spy(logger, 'log');
105+
});
106+
107+
afterEach(() => {
108+
sinonUtil.restore([
109+
request.get,
110+
odata.getAllItems
111+
]);
112+
});
113+
114+
after(() => {
115+
sinon.restore();
116+
auth.connection.active = false;
117+
auth.connection.spoUrl = undefined;
118+
});
119+
120+
it('has correct name', () => {
121+
assert.strictEqual(command.name, commands.BRANDCENTER_COLORS_LIST);
122+
});
123+
124+
it('has a description', () => {
125+
assert.notStrictEqual(command.description, null);
126+
});
127+
128+
it('defines correct default properties', () => {
129+
assert.deepStrictEqual(command.defaultProperties(), ['ColorCode', 'Title', 'IsVisible']);
130+
});
131+
132+
it('passes validation with no options', () => {
133+
const actual = commandOptionsSchema.safeParse({});
134+
assert.strictEqual(actual.success, true);
135+
});
136+
137+
it('fails validation with unknown options', () => {
138+
const actual = commandOptionsSchema.safeParse({ option: "value" });
139+
assert.strictEqual(actual.success, false);
140+
});
141+
142+
it('returns empty array when brand colors list does not exist', async () => {
143+
sinon.stub(request, 'get').callsFake(async (opts) => {
144+
if (opts.url === 'https://contoso.sharepoint.com/_api/Brandcenter/Configuration') {
145+
return configurationResponseWithoutColors;
146+
}
147+
148+
throw 'Invalid request';
149+
});
150+
151+
await command.action(logger, { options: {} });
152+
assert(loggerLogSpy.calledOnceWithExactly([]));
153+
});
154+
155+
it('returns empty array when brand colors list does not exist (verbose)', async () => {
156+
sinon.stub(request, 'get').callsFake(async (opts) => {
157+
if (opts.url === 'https://contoso.sharepoint.com/_api/Brandcenter/Configuration') {
158+
return configurationResponseWithoutColors;
159+
}
160+
161+
throw 'Invalid request';
162+
});
163+
164+
await command.action(logger, { options: { verbose: true } });
165+
assert(loggerLogSpy.calledOnceWithExactly([]));
166+
});
167+
168+
it('successfully lists brand center colors', async () => {
169+
sinon.stub(request, 'get').callsFake(async (opts) => {
170+
if (opts.url === 'https://contoso.sharepoint.com/_api/Brandcenter/Configuration') {
171+
return configurationResponseWithColors;
172+
}
173+
174+
throw 'Invalid request';
175+
});
176+
177+
sinon.stub(odata, 'getAllItems').callsFake(async (url) => {
178+
if (url === `https://contoso.sharepoint.com/sites/BrandGuide/_api/web/lists(guid'a1b2c3d4-e5f6-7890-abcd-ef1234567890')/items?$select=OData__SPColorTitle,OData__SPColorCode,OData__SPColorVisible`) {
179+
return brandColorsListItems;
180+
}
181+
182+
throw 'Invalid request';
183+
});
184+
185+
await command.action(logger, { options: {} });
186+
assert(loggerLogSpy.calledOnceWithExactly(mappedBrandColors));
187+
});
188+
189+
it('successfully lists brand center colors (verbose)', async () => {
190+
sinon.stub(request, 'get').callsFake(async (opts) => {
191+
if (opts.url === 'https://contoso.sharepoint.com/_api/Brandcenter/Configuration') {
192+
return configurationResponseWithColors;
193+
}
194+
195+
throw 'Invalid request';
196+
});
197+
198+
sinon.stub(odata, 'getAllItems').callsFake(async (url) => {
199+
if (url === `https://contoso.sharepoint.com/sites/BrandGuide/_api/web/lists(guid'a1b2c3d4-e5f6-7890-abcd-ef1234567890')/items?$select=OData__SPColorTitle,OData__SPColorCode,OData__SPColorVisible`) {
200+
return brandColorsListItems;
201+
}
202+
203+
throw 'Invalid request';
204+
});
205+
206+
await command.action(logger, { options: { verbose: true } });
207+
assert(loggerLogSpy.calledOnceWithExactly(mappedBrandColors));
208+
});
209+
210+
it('correctly handles error when retrieving configuration', async () => {
211+
sinon.stub(request, 'get').rejects({
212+
"error": {
213+
"code": "accessDenied",
214+
"message": "Access denied"
215+
}
216+
});
217+
218+
await assert.rejects(command.action(logger, { options: {} }),
219+
new CommandError('Access denied'));
220+
});
221+
222+
it('correctly handles error when retrieving list items', async () => {
223+
sinon.stub(request, 'get').callsFake(async (opts) => {
224+
if (opts.url === 'https://contoso.sharepoint.com/_api/Brandcenter/Configuration') {
225+
return configurationResponseWithColors;
226+
}
227+
228+
throw 'Invalid request';
229+
});
230+
231+
sinon.stub(odata, 'getAllItems').rejects({
232+
"error": {
233+
"code": "itemNotFound",
234+
"message": "The specified list was not found"
235+
}
236+
});
237+
238+
await assert.rejects(command.action(logger, { options: {} }),
239+
new CommandError('The specified list was not found'));
240+
});
241+
});

0 commit comments

Comments
 (0)