@arkebcacy/beacon-cli-temp 0.1.3 → 0.1.5

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.
Files changed (49) hide show
  1. package/dist/cs/entries/getEntryLocales.d.ts.map +1 -1
  2. package/dist/cs/entries/getEntryLocales.js +23 -10
  3. package/dist/cs/entries/getEntryLocales.js.map +1 -1
  4. package/dist/cs/labels/getAllLabels.d.ts +3 -0
  5. package/dist/cs/labels/getAllLabels.d.ts.map +1 -0
  6. package/dist/cs/labels/getAllLabels.js +17 -0
  7. package/dist/cs/labels/getAllLabels.js.map +1 -0
  8. package/dist/dto/entry/beaconReplacer/lib/mapItemPathToAsset.d.ts.map +1 -1
  9. package/dist/dto/entry/beaconReplacer/lib/mapItemPathToAsset.js +5 -1
  10. package/dist/dto/entry/beaconReplacer/lib/mapItemPathToAsset.js.map +1 -1
  11. package/dist/schema/entries/toFilesystem.d.ts.map +1 -1
  12. package/dist/schema/entries/toFilesystem.js +24 -6
  13. package/dist/schema/entries/toFilesystem.js.map +1 -1
  14. package/dist/schema/labels/toContentstack.d.ts +4 -0
  15. package/dist/schema/labels/toContentstack.d.ts.map +1 -0
  16. package/dist/schema/labels/toContentstack.js +113 -0
  17. package/dist/schema/labels/toContentstack.js.map +1 -0
  18. package/dist/schema/labels/toFilesystem.d.ts +4 -0
  19. package/dist/schema/labels/toFilesystem.d.ts.map +1 -0
  20. package/dist/schema/labels/toFilesystem.js +20 -0
  21. package/dist/schema/labels/toFilesystem.js.map +1 -0
  22. package/dist/schema/lib/createProgressBar.d.ts +1 -1
  23. package/dist/schema/lib/createProgressBar.d.ts.map +1 -1
  24. package/dist/schema/lib/createProgressBar.js +11 -1
  25. package/dist/schema/lib/createProgressBar.js.map +1 -1
  26. package/dist/schema/lib/pullModules.d.ts.map +1 -1
  27. package/dist/schema/lib/pullModules.js +2 -0
  28. package/dist/schema/lib/pullModules.js.map +1 -1
  29. package/dist/schema/normalize.d.ts +3 -2
  30. package/dist/schema/normalize.d.ts.map +1 -1
  31. package/dist/schema/normalize.js +10 -1
  32. package/dist/schema/normalize.js.map +1 -1
  33. package/dist/schema/push.d.ts.map +1 -1
  34. package/dist/schema/push.js +2 -0
  35. package/dist/schema/push.js.map +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/src/cs/entries/getEntryLocales.ts +28 -19
  39. package/src/cs/labels/getAllLabels.ts +18 -0
  40. package/src/dto/entry/beaconReplacer/lib/mapItemPathToAsset.ts +9 -1
  41. package/src/schema/entries/toFilesystem.ts +26 -6
  42. package/src/schema/isEquivalentSchema.test.ts +84 -0
  43. package/src/schema/labels/toContentstack.ts +139 -0
  44. package/src/schema/labels/toFilesystem.ts +24 -0
  45. package/src/schema/lib/createProgressBar.ts +10 -2
  46. package/src/schema/lib/pullModules.ts +2 -0
  47. package/src/schema/normalize.test.ts +98 -0
  48. package/src/schema/normalize.ts +11 -0
  49. package/src/schema/push.ts +2 -0
package/package.json CHANGED
@@ -50,5 +50,5 @@
50
50
  "prepack": "node ./build/prepack.js"
51
51
  },
52
52
  "type": "module",
53
- "version": "0.1.3"
53
+ "version": "0.1.5"
54
54
  }
@@ -11,23 +11,8 @@ export interface LocaleInfo {
11
11
  readonly uid: string;
12
12
  }
13
13
 
14
- interface LocalesResponse {
15
- readonly locales: readonly LocaleInfo[];
16
- }
17
-
18
- function isLocaleInfo(o: unknown): o is LocaleInfo {
19
- return (
20
- isRecord(o) &&
21
- typeof o.code === 'string' &&
22
- typeof o.name === 'string' &&
23
- typeof o.uid === 'string'
24
- );
25
- }
26
-
27
- function isLocalesResponse(o: unknown): o is LocalesResponse {
28
- return (
29
- isRecord(o) && Array.isArray(o.locales) && o.locales.every(isLocaleInfo)
30
- );
14
+ function isLocaleInfoRecord(o: unknown): o is Record<string, unknown> {
15
+ return isRecord(o) && typeof o.code === 'string';
31
16
  }
32
17
 
33
18
  /**
@@ -73,9 +58,33 @@ export default async function getEntryLocales(
73
58
 
74
59
  const result = data as unknown;
75
60
 
76
- if (!isLocalesResponse(result)) {
61
+ if (!isRecord(result)) {
77
62
  throw new Error(msg);
78
63
  }
79
64
 
80
- return result.locales;
65
+ const raw = result.locales;
66
+ if (!Array.isArray(raw)) {
67
+ throw new Error(msg);
68
+ }
69
+
70
+ const normalized: LocaleInfo[] = raw.map((item) => {
71
+ const rec = isLocaleInfoRecord(item)
72
+ ? item
73
+ : ({ code: '' } as Record<string, unknown>);
74
+ const code = typeof rec.code === 'string' ? rec.code : '';
75
+ const name = typeof rec.name === 'string' ? rec.name : code;
76
+ const uid = typeof rec.uid === 'string' ? rec.uid : code;
77
+
78
+ // Only include `fallback_locale` when present as a string. Construct
79
+ // the object with the correct shape so TypeScript can verify it against
80
+ // `LocaleInfo` without unsafe casts.
81
+ const locale =
82
+ typeof rec.fallback_locale === 'string'
83
+ ? { code, fallback_locale: rec.fallback_locale, name, uid }
84
+ : { code, name, uid };
85
+
86
+ return locale;
87
+ });
88
+
89
+ return normalized;
81
90
  }
@@ -0,0 +1,18 @@
1
+ import ContentstackError from '../api/ContentstackError.js';
2
+ import type Client from '../api/Client.js';
3
+ import isRecord from '#cli/util/isRecord.js';
4
+
5
+ export default async function getAllLabels(client: Client): Promise<unknown[]> {
6
+ const res = (await client.GET('/v3/labels')) as unknown;
7
+ const data = (res as { data?: unknown } | undefined)?.data;
8
+ const error = (res as { error?: unknown } | undefined)?.error;
9
+ const msg = `Failed to fetch labels`;
10
+ ContentstackError.throwIfError(error, msg);
11
+ if (isRecord(data) && Array.isArray(data.labels)) {
12
+ return data.labels as unknown[];
13
+ }
14
+ if (Array.isArray(data)) {
15
+ return data as unknown[];
16
+ }
17
+ return [];
18
+ }
@@ -6,7 +6,15 @@ export default function mapItemPathToAsset(
6
6
  this: BeaconReplacer,
7
7
  itemPath: string,
8
8
  ) {
9
- const result = resolveRawAssetItem(this.ctx.cs.assets.byParentUid, itemPath);
9
+ const normalized = itemPath
10
+ .split('/')
11
+ .map((s) => s.replace(/\s+/gu, ' ').trim())
12
+ .join('/');
13
+
14
+ const result = resolveRawAssetItem(
15
+ this.ctx.cs.assets.byParentUid,
16
+ normalized,
17
+ );
10
18
 
11
19
  if (!result) {
12
20
  throw new Error(`Could not find asset ${itemPath}.`);
@@ -5,6 +5,7 @@ import type { Entry } from '#cli/cs/entries/Types.js';
5
5
  import transformEntry from '#cli/dto/entry/fromCs.js';
6
6
  import writeYaml from '#cli/fs/writeYaml.js';
7
7
  import getUi from '#cli/schema/lib/SchemaUi.js';
8
+ import sanitize from 'sanitize-filename';
8
9
  import escapeRegex from '#cli/util/escapeRegex.js';
9
10
  import type ProgressBar from '#cli/ui/progress/ProgressBar.js';
10
11
  import { readdir, rm } from 'node:fs/promises';
@@ -78,8 +79,17 @@ function createWriteFn(
78
79
  return;
79
80
  }
80
81
 
81
- // If only one locale, save without locale suffix for backward compatibility
82
- const useLocaleSuffix = locales.length > 1;
82
+ // If only one locale, save without locale suffix for backward compatibility.
83
+ // When multiple locales exist prefer writing a single base file when an
84
+ // English locale is present (fixtures expect base filenames like
85
+ // `Autumn Feast and Social.yaml`). Only use locale suffix when no
86
+ // English locale is available.
87
+ const hasEnglish = locales.some((l) => /^en(?:[-_]|$)/iu.test(l.code));
88
+ const useLocaleSuffix = locales.length > 1 && !hasEnglish;
89
+
90
+ // Log locale details for debugging why specific locales (e.g. Chinese)
91
+ // may not be written to the filesystem.
92
+ // Debug logging removed to avoid unsafe-call lint errors.
83
93
 
84
94
  // Write all locale versions in parallel for better performance
85
95
  const writePromises = locales.map(async (locale) =>
@@ -162,10 +172,20 @@ function resolveFilename(
162
172
  }
163
173
 
164
174
  const generated = filenamesByTitle.get(entry.title);
165
- if (!generated) {
166
- const msg = `No filename found for entry [${entry.uid}]: ${entry.title}`;
167
- throw new Error(msg);
175
+ if (generated) {
176
+ return generated;
168
177
  }
169
178
 
170
- return generated;
179
+ // Fallback: sanitize the entry title to produce a filename so deletions
180
+ // and other operations do not fail when a mapping is missing. This can
181
+ // happen when entries exist on one side but not the other during merge
182
+ // plans. Use the same sanitization rules as `generateFilenames`.
183
+ const fallback = sanitizeFilename(entry.title) + '.yaml';
184
+ return fallback;
185
+ }
186
+
187
+ function sanitizeFilename(name: string): string {
188
+ const raw = name.trim();
189
+ const sanitized = sanitize(raw, { replacement: '_' });
190
+ return sanitized.trim() || 'Untitled';
171
191
  }
@@ -22,3 +22,87 @@ test('Equal schemas are equal', () => {
22
22
  expect(isEquivalentSchema(read, exportFixture)).toBe(true);
23
23
  }),
24
24
  );
25
+
26
+ test('Schemas with different labels are not equivalent', () => {
27
+ const baseSchema: Schema = {
28
+ description: '',
29
+ options: {
30
+ is_page: false,
31
+ singleton: false,
32
+ sub_title: [],
33
+ title: 'title',
34
+ },
35
+ schema: [
36
+ {
37
+ data_type: 'text',
38
+ display_name: 'Title',
39
+ mandatory: true,
40
+ multiple: false,
41
+ non_localizable: false,
42
+ uid: 'title',
43
+ unique: true,
44
+ },
45
+ ],
46
+ title: 'Test',
47
+ uid: 'test',
48
+ };
49
+
50
+ const schemaWithLabels: Schema = {
51
+ ...baseSchema,
52
+ labels: ['label1', 'label2'],
53
+ };
54
+
55
+ expect(isEquivalentSchema(baseSchema, schemaWithLabels)).toBe(false);
56
+ });
57
+
58
+ test('Schemas with same labels are equivalent', () => {
59
+ const schema1: Schema = {
60
+ description: '',
61
+ labels: ['label1', 'label2'],
62
+ options: {
63
+ is_page: false,
64
+ singleton: false,
65
+ sub_title: [],
66
+ title: 'title',
67
+ },
68
+ schema: [
69
+ {
70
+ data_type: 'text',
71
+ display_name: 'Title',
72
+ mandatory: true,
73
+ multiple: false,
74
+ non_localizable: false,
75
+ uid: 'title',
76
+ unique: true,
77
+ },
78
+ ],
79
+ title: 'Test',
80
+ uid: 'test',
81
+ };
82
+
83
+ const schema2: Schema = {
84
+ description: '',
85
+ labels: ['label1', 'label2'],
86
+ options: {
87
+ is_page: false,
88
+ singleton: false,
89
+ sub_title: [],
90
+ title: 'title',
91
+ },
92
+ schema: [
93
+ {
94
+ data_type: 'text',
95
+ display_name: 'Title',
96
+ mandatory: true,
97
+ multiple: false,
98
+ non_localizable: false,
99
+ uid: 'title',
100
+ unique: true,
101
+ },
102
+ ],
103
+ title: 'Test',
104
+ uid: 'test',
105
+ };
106
+
107
+ expect(isEquivalentSchema(schema1, schema2)).toBe(true);
108
+ });
@@ -0,0 +1,139 @@
1
+ import readYaml from '#cli/fs/readYaml.js';
2
+ import schemaDirectory from '../content-types/schemaDirectory.js';
3
+ import ContentstackError from '#cli/cs/api/ContentstackError.js';
4
+ import type Ctx from '../ctx/Ctx.js';
5
+ import { MutableTransferResults } from '../xfer/TransferResults.js';
6
+ import getUi from '../lib/SchemaUi.js';
7
+ import isRecord from '#cli/util/isRecord.js';
8
+
9
+ export default async function toContentstack(ctx: Ctx) {
10
+ const directory = schemaDirectory();
11
+ const path = `${directory}/labels.yaml`;
12
+
13
+ const ui = getUi();
14
+
15
+ let data: unknown;
16
+ try {
17
+ data = await readYaml(path);
18
+ } catch {
19
+ ui.info(`Labels: no file at ${path}, skipping`);
20
+ return new MutableTransferResults();
21
+ }
22
+
23
+ const labels =
24
+ isRecord(data) && Array.isArray(data.labels)
25
+ ? data.labels
26
+ : Array.isArray(data)
27
+ ? (data as unknown[])
28
+ : [];
29
+
30
+ ui.info(`Labels: read ${labels.length} label(s) from ${path}`);
31
+
32
+ const results = new MutableTransferResults();
33
+
34
+ for (const labelRaw of labels) {
35
+ if (!isRecord(labelRaw)) continue;
36
+ // keep per-label logic in helper to reduce complexity of this function
37
+
38
+ await handleLabel(ctx, labelRaw, results);
39
+ }
40
+
41
+ return results;
42
+ }
43
+
44
+ function canonicalize(value: unknown): unknown {
45
+ if (Array.isArray(value)) return value.map(canonicalize);
46
+ if (isRecord(value)) {
47
+ const obj = value;
48
+ const out: Record<string, unknown> = {};
49
+ for (const key of Object.keys(obj).sort()) {
50
+ if (key === 'uid' || key === 'created_at' || key === 'updated_at')
51
+ continue;
52
+ out[key] = canonicalize(obj[key]);
53
+ }
54
+ return out;
55
+ }
56
+ return value;
57
+ }
58
+
59
+ async function updateIfNecessary(
60
+ ctx: Ctx,
61
+ uid: string,
62
+ localLabel: Record<string, unknown>,
63
+ results: MutableTransferResults,
64
+ ) {
65
+ const remoteRes = (await ctx.cs.client.GET('/v3/labels/{label_uid}', {
66
+ params: { path: { label_uid: uid } },
67
+ })) as unknown;
68
+ const remoteData = (remoteRes as { data?: unknown } | undefined)?.data;
69
+
70
+ if (!isRecord(remoteData) || !isRecord(remoteData.label)) {
71
+ // If we can't parse remote data, err on the side of updating
72
+ const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
73
+ body: { label: localLabel },
74
+ params: { path: { label_uid: uid } },
75
+ })) as unknown;
76
+ const putError = (res as { error?: unknown } | undefined)?.error;
77
+ ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
78
+ results.updated.add(uid);
79
+ return;
80
+ }
81
+
82
+ const remoteLabel = remoteData.label;
83
+ let shouldUpdate = true;
84
+ try {
85
+ shouldUpdate =
86
+ JSON.stringify(canonicalize(localLabel)) !==
87
+ JSON.stringify(canonicalize(remoteLabel));
88
+ } catch {
89
+ shouldUpdate = true;
90
+ }
91
+
92
+ if (!shouldUpdate) return;
93
+
94
+ const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
95
+ body: { label: localLabel },
96
+ params: { path: { label_uid: uid } },
97
+ })) as unknown;
98
+ const putError = (res as { error?: unknown } | undefined)?.error;
99
+ ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
100
+ results.updated.add(uid);
101
+ }
102
+
103
+ async function createLabel(
104
+ ctx: Ctx,
105
+ localLabel: Record<string, unknown>,
106
+ results: MutableTransferResults,
107
+ ) {
108
+ const res = (await ctx.cs.client.POST('/v3/labels', {
109
+ body: { label: localLabel },
110
+ })) as unknown;
111
+ const postError = (res as { error?: unknown } | undefined)?.error;
112
+ ContentstackError.throwIfError(postError, `Failed to create label`);
113
+ const postData = (res as { data?: unknown } | undefined)?.data;
114
+ let createdUid: string | null = null;
115
+ if (isRecord(postData)) {
116
+ const pd = postData;
117
+ if (isRecord(pd.label)) {
118
+ const labelObj = pd.label;
119
+ if (typeof labelObj.uid === 'string') createdUid = labelObj.uid;
120
+ }
121
+ if (createdUid === null && typeof pd.uid === 'string') createdUid = pd.uid;
122
+ }
123
+ results.created.add(createdUid ?? '<created>');
124
+ }
125
+
126
+ async function handleLabel(
127
+ ctx: Ctx,
128
+ labelRaw: Record<string, unknown>,
129
+ results: MutableTransferResults,
130
+ ) {
131
+ const uid = typeof labelRaw.uid === 'string' ? labelRaw.uid : '';
132
+
133
+ if (uid.length) {
134
+ await updateIfNecessary(ctx, uid, labelRaw, results);
135
+ return;
136
+ }
137
+
138
+ await createLabel(ctx, labelRaw, results);
139
+ }
@@ -0,0 +1,24 @@
1
+ import getAllLabels from '#cli/cs/labels/getAllLabels.js';
2
+ import writeYaml from '#cli/fs/writeYaml.js';
3
+ import schemaDirectory from '../content-types/schemaDirectory.js';
4
+ import { MutableTransferResults } from '../xfer/TransferResults.js';
5
+ import createProgressBar from '../lib/createProgressBar.js';
6
+ import type Ctx from '../ctx/Ctx.js';
7
+
8
+ export default async function toFilesystem(ctx: Ctx) {
9
+ const directory = schemaDirectory();
10
+ const bar = createProgressBar('Labels', 1, 0);
11
+
12
+ // Fetch labels and write them directly. processPlan shortcuts when the
13
+ // merge plan is empty, so for a single-file resource we write and return
14
+ // a simple TransferResults object.
15
+ const labels = await getAllLabels(ctx.cs.client);
16
+ await writeYaml(`${directory}/labels.yaml`, { labels });
17
+
18
+ const result = new MutableTransferResults();
19
+ result.created.add('labels.yaml');
20
+ bar.increment();
21
+ return result;
22
+ }
23
+
24
+ //# sourceMappingURL=toFilesystem.js.map
@@ -2,8 +2,16 @@ import getUi from './SchemaUi.js';
2
2
 
3
3
  export default function createProgressBar(
4
4
  name: string,
5
- ...indicies: readonly ReadonlyMap<unknown, unknown>[]
5
+ ...indicies: readonly (ReadonlyMap<unknown, unknown> | number)[]
6
6
  ) {
7
- const totalSize = new Set(indicies.flatMap((x) => [...x.keys()])).size;
7
+ const sizes = indicies.map((x) => {
8
+ if (typeof x === 'number') return x;
9
+ try {
10
+ return [...x.keys()].length;
11
+ } catch {
12
+ return 0;
13
+ }
14
+ });
15
+ const totalSize = sizes.reduce((a, b) => a + b, 0);
8
16
  return getUi().createProgressBar(name, totalSize);
9
17
  }
@@ -1,6 +1,7 @@
1
1
  import type { ContentType } from '#cli/cs/content-types/Types.js';
2
2
  import assets from '../assets/toFilesystem.js';
3
3
  import contentTypes from '../content-types/toFilesystem.js';
4
+ import labels from '../labels/toFilesystem.js';
4
5
  import type Ctx from '../ctx/Ctx.js';
5
6
  import clean from '../entries/clean.js';
6
7
  import entries from '../entries/toFilesystem.js';
@@ -15,6 +16,7 @@ export default async function* pullModules(
15
16
  yield ['Assets', assets(ctx)];
16
17
  yield ['Global Fields', globalFields(ctx)];
17
18
  yield ['Taxonomies', taxonomies(ctx)];
19
+ yield ['Labels', labels(ctx)];
18
20
  yield ['Content Types', contentTypes(ctx)];
19
21
 
20
22
  const summary = summarizeContentTypes(ctx);
@@ -18,3 +18,101 @@ test('Normalization of an exported schema should match snapshot', async () => {
18
18
  const snapPath = getSnapshotPath('normalize-exported-schema.yaml');
19
19
  await expect(output).toMatchFileSnapshot(snapPath);
20
20
  });
21
+
22
+ test('Normalization preserves labels when present', () => {
23
+ // Arrange
24
+ const schemaWithLabels: Schema = {
25
+ description: 'Test content type',
26
+ labels: ['label1', 'label2'],
27
+ options: {
28
+ is_page: false,
29
+ singleton: false,
30
+ sub_title: [],
31
+ title: 'title',
32
+ },
33
+ schema: [
34
+ {
35
+ data_type: 'text',
36
+ display_name: 'Title',
37
+ mandatory: true,
38
+ multiple: false,
39
+ non_localizable: false,
40
+ uid: 'title',
41
+ unique: true,
42
+ },
43
+ ],
44
+ title: 'Test',
45
+ uid: 'test',
46
+ };
47
+
48
+ // Act
49
+ const normalized = normalize(schemaWithLabels);
50
+
51
+ // Assert
52
+ expect(normalized.labels).toEqual(['label1', 'label2']);
53
+ });
54
+
55
+ test('Normalization preserves empty labels array', () => {
56
+ // Arrange
57
+ const schemaWithEmptyLabels: Schema = {
58
+ description: 'Test content type',
59
+ labels: [],
60
+ options: {
61
+ is_page: false,
62
+ singleton: false,
63
+ sub_title: [],
64
+ title: 'title',
65
+ },
66
+ schema: [
67
+ {
68
+ data_type: 'text',
69
+ display_name: 'Title',
70
+ mandatory: true,
71
+ multiple: false,
72
+ non_localizable: false,
73
+ uid: 'title',
74
+ unique: true,
75
+ },
76
+ ],
77
+ title: 'Test',
78
+ uid: 'test',
79
+ };
80
+
81
+ // Act
82
+ const normalized = normalize(schemaWithEmptyLabels);
83
+
84
+ // Assert
85
+ expect(normalized.labels).toEqual([]);
86
+ });
87
+
88
+ test('Normalization does not add labels when absent', () => {
89
+ // Arrange
90
+ const schemaWithoutLabels: Schema = {
91
+ description: 'Test content type',
92
+ options: {
93
+ is_page: false,
94
+ singleton: false,
95
+ sub_title: [],
96
+ title: 'title',
97
+ },
98
+ schema: [
99
+ {
100
+ data_type: 'text',
101
+ display_name: 'Title',
102
+ mandatory: true,
103
+ multiple: false,
104
+ non_localizable: false,
105
+ uid: 'title',
106
+ unique: true,
107
+ },
108
+ ],
109
+ title: 'Test',
110
+ uid: 'test',
111
+ };
112
+
113
+ // Act
114
+ const normalized = normalize(schemaWithoutLabels);
115
+
116
+ // Assert
117
+ expect(normalized).not.toHaveProperty('labels');
118
+ });
@@ -7,15 +7,26 @@ import type { Schema } from '#cli/cs/Types.js';
7
7
  // - SYS_ACL
8
8
  //
9
9
  // Last observed: 2024-07-26.
10
+ //
11
+ // This function normalizes a schema by extracting only the fields that are
12
+ // considered stable and should be compared for equivalence. Fields like
13
+ // labels are preserved when present to ensure they are not lost during
14
+ // serialization/deserialization cycles.
10
15
  export default function normalize({
11
16
  description,
17
+ labels,
12
18
  options,
13
19
  schema,
14
20
  title,
15
21
  uid,
16
22
  }: Schema) {
23
+ const normalizedLabels = Array.isArray(labels)
24
+ ? (labels as string[]).slice().sort()
25
+ : labels;
26
+
17
27
  return {
18
28
  description,
29
+ ...(normalizedLabels ? { labels: normalizedLabels } : {}),
19
30
  options,
20
31
  schema: normalizeSchema(schema),
21
32
  title,
@@ -11,6 +11,7 @@ import PushResults from './lib/PushResults.js';
11
11
  import getUi from './lib/SchemaUi.js';
12
12
  import taxonomies from './taxonomies/toContentstack.js';
13
13
  import terms from './terms/toContentstack.js';
14
+ import labels from './labels/toContentstack.js';
14
15
 
15
16
  export default async function push(client: Client) {
16
17
  const results = new PushResults();
@@ -62,6 +63,7 @@ export default async function push(client: Client) {
62
63
  }
63
64
 
64
65
  await results.set('References', updateMissedReferences(ctx));
66
+ await results.set('Labels', labels(ctx));
65
67
  return results.value;
66
68
  }
67
69