@arkebcacy/beacon-cli-temp 0.1.5 → 0.1.6
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/dist/cs/labels/Label.d.ts +8 -0
- package/dist/cs/labels/Label.d.ts.map +1 -0
- package/dist/cs/labels/Label.js +7 -0
- package/dist/cs/labels/Label.js.map +1 -0
- package/dist/cs/labels/getAllLabels.d.ts +2 -1
- package/dist/cs/labels/getAllLabels.d.ts.map +1 -1
- package/dist/cs/labels/getAllLabels.js +24 -4
- package/dist/cs/labels/getAllLabels.js.map +1 -1
- package/dist/dto/labels/NormalizedLabels.d.ts +13 -0
- package/dist/dto/labels/NormalizedLabels.d.ts.map +1 -0
- package/dist/dto/labels/NormalizedLabels.js +25 -0
- package/dist/dto/labels/NormalizedLabels.js.map +1 -0
- package/dist/dto/labels/flatten.d.ts +4 -0
- package/dist/dto/labels/flatten.d.ts.map +1 -0
- package/dist/dto/labels/flatten.js +18 -0
- package/dist/dto/labels/flatten.js.map +1 -0
- package/dist/dto/labels/organize.d.ts +4 -0
- package/dist/dto/labels/organize.d.ts.map +1 -0
- package/dist/dto/labels/organize.js +40 -0
- package/dist/dto/labels/organize.js.map +1 -0
- package/dist/schema/labels/toContentstack.d.ts.map +1 -1
- package/dist/schema/labels/toContentstack.js +33 -10
- package/dist/schema/labels/toContentstack.js.map +1 -1
- package/dist/schema/labels/toFilesystem.d.ts.map +1 -1
- package/dist/schema/labels/toFilesystem.js +5 -6
- package/dist/schema/labels/toFilesystem.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/cs/labels/Label.ts +16 -0
- package/src/cs/labels/getAllLabels.ts +30 -5
- package/src/dto/labels/NormalizedLabels.ts +47 -0
- package/src/dto/labels/flatten.test.ts +118 -0
- package/src/dto/labels/flatten.ts +27 -0
- package/src/dto/labels/organize.test.ts +131 -0
- package/src/dto/labels/organize.ts +57 -0
- package/src/schema/labels/toContentstack.ts +33 -11
- package/src/schema/labels/toFilesystem.ts +6 -7
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type OmitIndex from '../../util/OmitIndex.js';
|
|
2
|
+
import type { Item } from '../Types.js';
|
|
3
|
+
import { isItem } from '../Types.js';
|
|
4
|
+
|
|
5
|
+
export default interface Label extends OmitIndex<Item> {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly parent_uid: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isLabel(o: unknown): o is Label {
|
|
11
|
+
return (
|
|
12
|
+
isItem(o) &&
|
|
13
|
+
typeof o.name === 'string' &&
|
|
14
|
+
(o.parent_uid === null || typeof o.parent_uid === 'string')
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
import ContentstackError from '../api/ContentstackError.js';
|
|
2
2
|
import type Client from '../api/Client.js';
|
|
3
3
|
import isRecord from '#cli/util/isRecord.js';
|
|
4
|
+
import type Label from './Label.js';
|
|
5
|
+
import { isLabel } from './Label.js';
|
|
4
6
|
|
|
5
|
-
export default async function getAllLabels(client: Client): Promise<
|
|
7
|
+
export default async function getAllLabels(client: Client): Promise<Label[]> {
|
|
6
8
|
const res = (await client.GET('/v3/labels')) as unknown;
|
|
7
9
|
const data = (res as { data?: unknown } | undefined)?.data;
|
|
8
10
|
const error = (res as { error?: unknown } | undefined)?.error;
|
|
9
11
|
const msg = `Failed to fetch labels`;
|
|
10
12
|
ContentstackError.throwIfError(error, msg);
|
|
13
|
+
|
|
14
|
+
let rawLabels: unknown[] = [];
|
|
11
15
|
if (isRecord(data) && Array.isArray(data.labels)) {
|
|
12
|
-
|
|
16
|
+
rawLabels = data.labels;
|
|
17
|
+
} else if (Array.isArray(data)) {
|
|
18
|
+
rawLabels = data;
|
|
13
19
|
}
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
|
|
21
|
+
// Transform raw labels to ensure they have parent_uid (null if not present)
|
|
22
|
+
const labels: Label[] = [];
|
|
23
|
+
for (const raw of rawLabels) {
|
|
24
|
+
if (
|
|
25
|
+
isRecord(raw) &&
|
|
26
|
+
typeof raw.uid === 'string' &&
|
|
27
|
+
typeof raw.name === 'string'
|
|
28
|
+
) {
|
|
29
|
+
// Preserve all fields from the remote label
|
|
30
|
+
const label: Label = {
|
|
31
|
+
...raw,
|
|
32
|
+
name: raw.name,
|
|
33
|
+
parent_uid: typeof raw.parent_uid === 'string' ? raw.parent_uid : null,
|
|
34
|
+
uid: raw.uid,
|
|
35
|
+
};
|
|
36
|
+
if (isLabel(label)) {
|
|
37
|
+
labels.push(label);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
16
40
|
}
|
|
17
|
-
|
|
41
|
+
|
|
42
|
+
return labels;
|
|
18
43
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import isRecord from '#cli/util/isRecord.js';
|
|
2
|
+
import type Label from '#cli/cs/labels/Label.js';
|
|
3
|
+
import { isItem } from '#cli/cs/Types.js';
|
|
4
|
+
|
|
5
|
+
export default interface NormalizedLabels {
|
|
6
|
+
readonly labels: readonly LabelTreeNode[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LabelTreeNode {
|
|
10
|
+
// Allow any additional fields from the label
|
|
11
|
+
readonly [key: string]: unknown;
|
|
12
|
+
readonly uid: Label['uid'];
|
|
13
|
+
readonly name: Label['name'];
|
|
14
|
+
readonly children?: readonly LabelTreeNode[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function key() {
|
|
18
|
+
return 'labels';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isNormalizedLabels(
|
|
22
|
+
value: unknown,
|
|
23
|
+
): value is NormalizedLabels & Record<string, unknown> {
|
|
24
|
+
if (!isRecord(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Array.isArray(value.labels) && value.labels.every(isLabelTreeNode);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isLabelTreeNode(value: unknown): value is LabelTreeNode {
|
|
32
|
+
if (!isItem(value)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof value.name !== 'string') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!('children' in value)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { children } = value;
|
|
45
|
+
|
|
46
|
+
return Array.isArray(children) && children.every(isLabelTreeNode);
|
|
47
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import flatten from './flatten.js';
|
|
3
|
+
import type { LabelTreeNode } from './NormalizedLabels.js';
|
|
4
|
+
|
|
5
|
+
describe('flatten', () => {
|
|
6
|
+
it('flattens a simple hierarchy', () => {
|
|
7
|
+
const tree: LabelTreeNode[] = [
|
|
8
|
+
{
|
|
9
|
+
children: [
|
|
10
|
+
{ name: 'Child 1', uid: 'child1' },
|
|
11
|
+
{ name: 'Child 2', uid: 'child2' },
|
|
12
|
+
],
|
|
13
|
+
name: 'Parent',
|
|
14
|
+
uid: 'parent',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const result = flatten(tree);
|
|
19
|
+
|
|
20
|
+
expect(result).toEqual([
|
|
21
|
+
{ name: 'Parent', parent_uid: null, uid: 'parent' },
|
|
22
|
+
{ name: 'Child 1', parent_uid: 'parent', uid: 'child1' },
|
|
23
|
+
{ name: 'Child 2', parent_uid: 'parent', uid: 'child2' },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('flattens nested hierarchy', () => {
|
|
28
|
+
const tree: LabelTreeNode[] = [
|
|
29
|
+
{
|
|
30
|
+
children: [
|
|
31
|
+
{ name: 'Calculator', uid: 'calculator' },
|
|
32
|
+
{ name: 'Data', uid: 'data' },
|
|
33
|
+
{ name: 'Page', uid: 'page' },
|
|
34
|
+
],
|
|
35
|
+
name: 'Component',
|
|
36
|
+
uid: 'component',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const result = flatten(tree);
|
|
41
|
+
|
|
42
|
+
expect(result).toEqual([
|
|
43
|
+
{ name: 'Component', parent_uid: null, uid: 'component' },
|
|
44
|
+
{ name: 'Calculator', parent_uid: 'component', uid: 'calculator' },
|
|
45
|
+
{ name: 'Data', parent_uid: 'component', uid: 'data' },
|
|
46
|
+
{ name: 'Page', parent_uid: 'component', uid: 'page' },
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles deeply nested hierarchy', () => {
|
|
51
|
+
const tree: LabelTreeNode[] = [
|
|
52
|
+
{
|
|
53
|
+
children: [
|
|
54
|
+
{
|
|
55
|
+
children: [
|
|
56
|
+
{
|
|
57
|
+
children: [{ name: 'Level 3', uid: 'level3' }],
|
|
58
|
+
name: 'Level 2',
|
|
59
|
+
uid: 'level2',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
name: 'Level 1',
|
|
63
|
+
uid: 'level1',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
name: 'Root',
|
|
67
|
+
uid: 'root',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const result = flatten(tree);
|
|
72
|
+
|
|
73
|
+
expect(result).toEqual([
|
|
74
|
+
{ name: 'Root', parent_uid: null, uid: 'root' },
|
|
75
|
+
{ name: 'Level 1', parent_uid: 'root', uid: 'level1' },
|
|
76
|
+
{ name: 'Level 2', parent_uid: 'level1', uid: 'level2' },
|
|
77
|
+
{ name: 'Level 3', parent_uid: 'level2', uid: 'level3' },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('handles multiple top-level labels', () => {
|
|
82
|
+
const tree: LabelTreeNode[] = [
|
|
83
|
+
{ name: 'Label 1', uid: 'label1' },
|
|
84
|
+
{
|
|
85
|
+
children: [{ name: 'Child', uid: 'child' }],
|
|
86
|
+
name: 'Label 2',
|
|
87
|
+
uid: 'label2',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const result = flatten(tree);
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual([
|
|
94
|
+
{ name: 'Label 1', parent_uid: null, uid: 'label1' },
|
|
95
|
+
{ name: 'Label 2', parent_uid: null, uid: 'label2' },
|
|
96
|
+
{ name: 'Child', parent_uid: 'label2', uid: 'child' },
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles empty array', () => {
|
|
101
|
+
const result = flatten([]);
|
|
102
|
+
expect(result).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles labels without children', () => {
|
|
106
|
+
const tree: LabelTreeNode[] = [
|
|
107
|
+
{ name: 'Label 1', uid: 'label1' },
|
|
108
|
+
{ name: 'Label 2', uid: 'label2' },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const result = flatten(tree);
|
|
112
|
+
|
|
113
|
+
expect(result).toEqual([
|
|
114
|
+
{ name: 'Label 1', parent_uid: null, uid: 'label1' },
|
|
115
|
+
{ name: 'Label 2', parent_uid: null, uid: 'label2' },
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type Label from '#cli/cs/labels/Label.js';
|
|
2
|
+
import type { LabelTreeNode } from './NormalizedLabels.js';
|
|
3
|
+
|
|
4
|
+
export default function flatten(
|
|
5
|
+
labels: readonly LabelTreeNode[],
|
|
6
|
+
): readonly Label[] {
|
|
7
|
+
const result: Label[] = [];
|
|
8
|
+
|
|
9
|
+
function traverse(nodes: readonly LabelTreeNode[], parentUid: string | null) {
|
|
10
|
+
for (const node of nodes) {
|
|
11
|
+
const { children, ...label } = node;
|
|
12
|
+
|
|
13
|
+
result.push({
|
|
14
|
+
...label,
|
|
15
|
+
parent_uid: parentUid,
|
|
16
|
+
} as Label);
|
|
17
|
+
|
|
18
|
+
if (children) {
|
|
19
|
+
traverse(children, node.uid);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
traverse(labels, null);
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import organize from './organize.js';
|
|
3
|
+
import type Label from '#cli/cs/labels/Label.js';
|
|
4
|
+
|
|
5
|
+
describe('organize', () => {
|
|
6
|
+
it('organizes flat labels into hierarchy', () => {
|
|
7
|
+
const labels: Label[] = [
|
|
8
|
+
{ name: 'Parent', parent_uid: null, uid: 'parent' },
|
|
9
|
+
{ name: 'Child 1', parent_uid: 'parent', uid: 'child1' },
|
|
10
|
+
{ name: 'Child 2', parent_uid: 'parent', uid: 'child2' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const result = organize(labels);
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual([
|
|
16
|
+
{
|
|
17
|
+
children: [
|
|
18
|
+
{ name: 'Child 1', uid: 'child1' },
|
|
19
|
+
{ name: 'Child 2', uid: 'child2' },
|
|
20
|
+
],
|
|
21
|
+
name: 'Parent',
|
|
22
|
+
uid: 'parent',
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('organizes component label structure from screenshot', () => {
|
|
28
|
+
const labels: Label[] = [
|
|
29
|
+
{ name: 'Component', parent_uid: null, uid: 'component' },
|
|
30
|
+
{ name: 'Calculator', parent_uid: 'component', uid: 'calculator' },
|
|
31
|
+
{ name: 'Data', parent_uid: 'component', uid: 'data' },
|
|
32
|
+
{ name: 'Page', parent_uid: 'component', uid: 'page' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const result = organize(labels);
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual([
|
|
38
|
+
{
|
|
39
|
+
children: [
|
|
40
|
+
{ name: 'Calculator', uid: 'calculator' },
|
|
41
|
+
{ name: 'Data', uid: 'data' },
|
|
42
|
+
{ name: 'Page', uid: 'page' },
|
|
43
|
+
],
|
|
44
|
+
name: 'Component',
|
|
45
|
+
uid: 'component',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles deeply nested labels', () => {
|
|
51
|
+
const labels: Label[] = [
|
|
52
|
+
{ name: 'Root', parent_uid: null, uid: 'root' },
|
|
53
|
+
{ name: 'Level 1', parent_uid: 'root', uid: 'level1' },
|
|
54
|
+
{ name: 'Level 2', parent_uid: 'level1', uid: 'level2' },
|
|
55
|
+
{ name: 'Level 3', parent_uid: 'level2', uid: 'level3' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const result = organize(labels);
|
|
59
|
+
|
|
60
|
+
expect(result).toEqual([
|
|
61
|
+
{
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
children: [
|
|
65
|
+
{
|
|
66
|
+
children: [{ name: 'Level 3', uid: 'level3' }],
|
|
67
|
+
name: 'Level 2',
|
|
68
|
+
uid: 'level2',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
name: 'Level 1',
|
|
72
|
+
uid: 'level1',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
name: 'Root',
|
|
76
|
+
uid: 'root',
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('handles multiple top-level labels', () => {
|
|
82
|
+
const labels: Label[] = [
|
|
83
|
+
{ name: 'Label 1', parent_uid: null, uid: 'label1' },
|
|
84
|
+
{ name: 'Label 2', parent_uid: null, uid: 'label2' },
|
|
85
|
+
{ name: 'Child', parent_uid: 'label2', uid: 'child' },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const result = organize(labels);
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual([
|
|
91
|
+
{ name: 'Label 1', uid: 'label1' },
|
|
92
|
+
{
|
|
93
|
+
children: [{ name: 'Child', uid: 'child' }],
|
|
94
|
+
name: 'Label 2',
|
|
95
|
+
uid: 'label2',
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles empty array', () => {
|
|
101
|
+
const result = organize([]);
|
|
102
|
+
expect(result).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('throws error for orphaned label', () => {
|
|
106
|
+
const labels: Label[] = [
|
|
107
|
+
{ name: 'Child', parent_uid: 'nonexistent', uid: 'child' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
expect(() => organize(labels)).toThrow(
|
|
111
|
+
'Orphaned label child with parent nonexistent',
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('preserves order of siblings', () => {
|
|
116
|
+
const labels: Label[] = [
|
|
117
|
+
{ name: 'Parent', parent_uid: null, uid: 'parent' },
|
|
118
|
+
{ name: 'Child 3', parent_uid: 'parent', uid: 'child3' },
|
|
119
|
+
{ name: 'Child 1', parent_uid: 'parent', uid: 'child1' },
|
|
120
|
+
{ name: 'Child 2', parent_uid: 'parent', uid: 'child2' },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const result = organize(labels);
|
|
124
|
+
|
|
125
|
+
expect(result[0]?.children).toEqual([
|
|
126
|
+
{ name: 'Child 3', uid: 'child3' },
|
|
127
|
+
{ name: 'Child 1', uid: 'child1' },
|
|
128
|
+
{ name: 'Child 2', uid: 'child2' },
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type Label from '#cli/cs/labels/Label.js';
|
|
2
|
+
import type { LabelTreeNode } from './NormalizedLabels.js';
|
|
3
|
+
|
|
4
|
+
// Labels have a tree structure defined by uid/parent_uid.
|
|
5
|
+
//
|
|
6
|
+
// The API returns a flat array that includes uid/parent_uid, but
|
|
7
|
+
// leaves it up to the client to reconstruct the tree.
|
|
8
|
+
//
|
|
9
|
+
// We organize labels into a tree structure for serialization.
|
|
10
|
+
//
|
|
11
|
+
// The sort order amongst siblings is maintained equal to the order
|
|
12
|
+
// as it appears in the array.
|
|
13
|
+
export default function organize(
|
|
14
|
+
labels: readonly Label[],
|
|
15
|
+
): readonly LabelTreeNode[] {
|
|
16
|
+
const byUid = new Map<string, MutableNode>();
|
|
17
|
+
const topLevel: MutableNode[] = [];
|
|
18
|
+
|
|
19
|
+
for (const label of labels) {
|
|
20
|
+
const { parent_uid, ...labelWithoutParent } = label;
|
|
21
|
+
// Preserve all fields except parent_uid (which is represented by the tree structure)
|
|
22
|
+
byUid.set(label.uid, {
|
|
23
|
+
...labelWithoutParent,
|
|
24
|
+
name: label.name,
|
|
25
|
+
uid: label.uid,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const { uid, parent_uid } of labels) {
|
|
30
|
+
const label = byUid.get(uid);
|
|
31
|
+
if (!label) {
|
|
32
|
+
throw new Error(`Label ${uid} not found`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!parent_uid) {
|
|
36
|
+
topLevel.push(label);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parent = byUid.get(parent_uid);
|
|
41
|
+
if (!parent) {
|
|
42
|
+
const msg = `Orphaned label ${uid} with parent ${parent_uid}`;
|
|
43
|
+
throw new Error(msg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
(parent.children ??= []).push(label);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return topLevel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface MutableNode {
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
readonly uid: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
children?: MutableNode[];
|
|
57
|
+
}
|
|
@@ -5,6 +5,9 @@ import type Ctx from '../ctx/Ctx.js';
|
|
|
5
5
|
import { MutableTransferResults } from '../xfer/TransferResults.js';
|
|
6
6
|
import getUi from '../lib/SchemaUi.js';
|
|
7
7
|
import isRecord from '#cli/util/isRecord.js';
|
|
8
|
+
import flatten from '#cli/dto/labels/flatten.js';
|
|
9
|
+
import { isNormalizedLabels } from '#cli/dto/labels/NormalizedLabels.js';
|
|
10
|
+
import type Label from '#cli/cs/labels/Label.js';
|
|
8
11
|
|
|
9
12
|
export default async function toContentstack(ctx: Ctx) {
|
|
10
13
|
const directory = schemaDirectory();
|
|
@@ -20,18 +23,24 @@ export default async function toContentstack(ctx: Ctx) {
|
|
|
20
23
|
return new MutableTransferResults();
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// Support both hierarchical and flat label structures
|
|
27
|
+
let flatLabels: Label[] = [];
|
|
28
|
+
if (isNormalizedLabels(data)) {
|
|
29
|
+
// Hierarchical structure - flatten it first
|
|
30
|
+
flatLabels = [...flatten(data.labels)];
|
|
31
|
+
} else if (isRecord(data) && Array.isArray(data.labels)) {
|
|
32
|
+
// Legacy flat array format
|
|
33
|
+
flatLabels = data.labels as Label[];
|
|
34
|
+
} else if (Array.isArray(data)) {
|
|
35
|
+
// Direct array format
|
|
36
|
+
flatLabels = data as Label[];
|
|
37
|
+
}
|
|
29
38
|
|
|
30
|
-
ui.info(`Labels: read ${
|
|
39
|
+
ui.info(`Labels: read ${flatLabels.length} label(s) from ${path}`);
|
|
31
40
|
|
|
32
41
|
const results = new MutableTransferResults();
|
|
33
42
|
|
|
34
|
-
for (const labelRaw of
|
|
43
|
+
for (const labelRaw of flatLabels) {
|
|
35
44
|
if (!isRecord(labelRaw)) continue;
|
|
36
45
|
// keep per-label logic in helper to reduce complexity of this function
|
|
37
46
|
|
|
@@ -49,6 +58,9 @@ function canonicalize(value: unknown): unknown {
|
|
|
49
58
|
for (const key of Object.keys(obj).sort()) {
|
|
50
59
|
if (key === 'uid' || key === 'created_at' || key === 'updated_at')
|
|
51
60
|
continue;
|
|
61
|
+
// Normalize parent_uid: skip if null or undefined
|
|
62
|
+
if (key === 'parent_uid' && (obj[key] === null || obj[key] === undefined))
|
|
63
|
+
continue;
|
|
52
64
|
out[key] = canonicalize(obj[key]);
|
|
53
65
|
}
|
|
54
66
|
return out;
|
|
@@ -70,7 +82,7 @@ async function updateIfNecessary(
|
|
|
70
82
|
if (!isRecord(remoteData) || !isRecord(remoteData.label)) {
|
|
71
83
|
// If we can't parse remote data, err on the side of updating
|
|
72
84
|
const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
|
|
73
|
-
body: { label: localLabel },
|
|
85
|
+
body: { label: prepareLabel(localLabel) },
|
|
74
86
|
params: { path: { label_uid: uid } },
|
|
75
87
|
})) as unknown;
|
|
76
88
|
const putError = (res as { error?: unknown } | undefined)?.error;
|
|
@@ -92,7 +104,7 @@ async function updateIfNecessary(
|
|
|
92
104
|
if (!shouldUpdate) return;
|
|
93
105
|
|
|
94
106
|
const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
|
|
95
|
-
body: { label: localLabel },
|
|
107
|
+
body: { label: prepareLabel(localLabel) },
|
|
96
108
|
params: { path: { label_uid: uid } },
|
|
97
109
|
})) as unknown;
|
|
98
110
|
const putError = (res as { error?: unknown } | undefined)?.error;
|
|
@@ -106,7 +118,7 @@ async function createLabel(
|
|
|
106
118
|
results: MutableTransferResults,
|
|
107
119
|
) {
|
|
108
120
|
const res = (await ctx.cs.client.POST('/v3/labels', {
|
|
109
|
-
body: { label: localLabel },
|
|
121
|
+
body: { label: prepareLabel(localLabel) },
|
|
110
122
|
})) as unknown;
|
|
111
123
|
const postError = (res as { error?: unknown } | undefined)?.error;
|
|
112
124
|
ContentstackError.throwIfError(postError, `Failed to create label`);
|
|
@@ -123,6 +135,16 @@ async function createLabel(
|
|
|
123
135
|
results.created.add(createdUid ?? '<created>');
|
|
124
136
|
}
|
|
125
137
|
|
|
138
|
+
// Prepare label for API by removing parent_uid if null
|
|
139
|
+
function prepareLabel(label: Record<string, unknown>): Record<string, unknown> {
|
|
140
|
+
const { parent_uid, ...rest } = label;
|
|
141
|
+
// Only include parent_uid if it's a non-null string
|
|
142
|
+
if (typeof parent_uid === 'string') {
|
|
143
|
+
return { ...rest, parent_uid };
|
|
144
|
+
}
|
|
145
|
+
return rest;
|
|
146
|
+
}
|
|
147
|
+
|
|
126
148
|
async function handleLabel(
|
|
127
149
|
ctx: Ctx,
|
|
128
150
|
labelRaw: Record<string, unknown>,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import getAllLabels from '#cli/cs/labels/getAllLabels.js';
|
|
2
2
|
import writeYaml from '#cli/fs/writeYaml.js';
|
|
3
|
+
import organize from '#cli/dto/labels/organize.js';
|
|
3
4
|
import schemaDirectory from '../content-types/schemaDirectory.js';
|
|
4
5
|
import { MutableTransferResults } from '../xfer/TransferResults.js';
|
|
5
6
|
import createProgressBar from '../lib/createProgressBar.js';
|
|
@@ -9,16 +10,14 @@ export default async function toFilesystem(ctx: Ctx) {
|
|
|
9
10
|
const directory = schemaDirectory();
|
|
10
11
|
const bar = createProgressBar('Labels', 1, 0);
|
|
11
12
|
|
|
12
|
-
// Fetch labels and
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
await writeYaml(`${directory}/labels.yaml`, { labels });
|
|
13
|
+
// Fetch labels and organize them into a hierarchical structure
|
|
14
|
+
const flatLabels = await getAllLabels(ctx.cs.client);
|
|
15
|
+
const hierarchicalLabels = organize(flatLabels);
|
|
16
|
+
|
|
17
|
+
await writeYaml(`${directory}/labels.yaml`, { labels: hierarchicalLabels });
|
|
17
18
|
|
|
18
19
|
const result = new MutableTransferResults();
|
|
19
20
|
result.created.add('labels.yaml');
|
|
20
21
|
bar.increment();
|
|
21
22
|
return result;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
//# sourceMappingURL=toFilesystem.js.map
|