@arkebcacy/beacon-cli-temp 0.1.6 → 0.1.7

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 (59) hide show
  1. package/dist/cs/labels/Label.d.ts +1 -1
  2. package/dist/cs/labels/Label.d.ts.map +1 -1
  3. package/dist/cs/labels/Label.js +1 -3
  4. package/dist/cs/labels/Label.js.map +1 -1
  5. package/dist/cs/labels/getAllLabels.js +1 -1
  6. package/dist/cs/labels/getAllLabels.js.map +1 -1
  7. package/dist/cs/locales/addLocale.d.ts +11 -0
  8. package/dist/cs/locales/addLocale.d.ts.map +1 -0
  9. package/dist/cs/locales/addLocale.js +36 -0
  10. package/dist/cs/locales/addLocale.js.map +1 -0
  11. package/dist/cs/locales/ensureLocaleExists.d.ts +11 -0
  12. package/dist/cs/locales/ensureLocaleExists.d.ts.map +1 -0
  13. package/dist/cs/locales/ensureLocaleExists.js +38 -0
  14. package/dist/cs/locales/ensureLocaleExists.js.map +1 -0
  15. package/dist/cs/locales/getLocales.d.ts +12 -0
  16. package/dist/cs/locales/getLocales.d.ts.map +1 -0
  17. package/dist/cs/locales/getLocales.js +23 -0
  18. package/dist/cs/locales/getLocales.js.map +1 -0
  19. package/dist/dto/labels/flatten.js +1 -1
  20. package/dist/dto/labels/flatten.js.map +1 -1
  21. package/dist/dto/labels/organize.d.ts.map +1 -1
  22. package/dist/dto/labels/organize.js +15 -14
  23. package/dist/dto/labels/organize.js.map +1 -1
  24. package/dist/schema/entries/toContentstack.d.ts.map +1 -1
  25. package/dist/schema/entries/toContentstack.js +30 -5
  26. package/dist/schema/entries/toContentstack.js.map +1 -1
  27. package/dist/schema/entries/toFilesystem.js +23 -10
  28. package/dist/schema/entries/toFilesystem.js.map +1 -1
  29. package/dist/schema/labels/lib/labelHelpers.d.ts +3 -0
  30. package/dist/schema/labels/lib/labelHelpers.d.ts.map +1 -0
  31. package/dist/schema/labels/lib/labelHelpers.js +34 -0
  32. package/dist/schema/labels/lib/labelHelpers.js.map +1 -0
  33. package/dist/schema/labels/lib/labelOperations.d.ts +6 -0
  34. package/dist/schema/labels/lib/labelOperations.d.ts.map +1 -0
  35. package/dist/schema/labels/lib/labelOperations.js +57 -0
  36. package/dist/schema/labels/lib/labelOperations.js.map +1 -0
  37. package/dist/schema/labels/toContentstack.d.ts.map +1 -1
  38. package/dist/schema/labels/toContentstack.js +142 -105
  39. package/dist/schema/labels/toContentstack.js.map +1 -1
  40. package/dist/schema/labels/toFilesystem.d.ts.map +1 -1
  41. package/dist/schema/labels/toFilesystem.js +6 -3
  42. package/dist/schema/labels/toFilesystem.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +1 -1
  45. package/src/cs/labels/Label.ts +2 -6
  46. package/src/cs/labels/getAllLabels.ts +1 -1
  47. package/src/cs/locales/addLocale.ts +59 -0
  48. package/src/cs/locales/ensureLocaleExists.ts +51 -0
  49. package/src/cs/locales/getLocales.ts +38 -0
  50. package/src/dto/labels/flatten.test.ts +16 -16
  51. package/src/dto/labels/flatten.ts +1 -1
  52. package/src/dto/labels/organize.test.ts +19 -19
  53. package/src/dto/labels/organize.ts +15 -14
  54. package/src/schema/entries/toContentstack.ts +75 -6
  55. package/src/schema/entries/toFilesystem.ts +29 -14
  56. package/src/schema/labels/lib/labelHelpers.ts +36 -0
  57. package/src/schema/labels/lib/labelOperations.ts +74 -0
  58. package/src/schema/labels/toContentstack.ts +204 -109
  59. package/src/schema/labels/toFilesystem.ts +10 -3
@@ -0,0 +1,74 @@
1
+ import ContentstackError from '#cli/cs/api/ContentstackError.js';
2
+ import isRecord from '#cli/util/isRecord.js';
3
+ import type Ctx from '../../ctx/Ctx.js';
4
+ import type { MutableTransferResults } from '../../xfer/TransferResults.js';
5
+ import type Label from '#cli/cs/labels/Label.js';
6
+ import { canonicalize, prepareLabel } from './labelHelpers.js';
7
+
8
+ export async function updateIfNecessary(
9
+ ctx: Ctx,
10
+ uid: string,
11
+ localLabel: Record<string, unknown>,
12
+ remoteLabel: Label | undefined,
13
+ results: MutableTransferResults,
14
+ ) {
15
+ if (!remoteLabel) {
16
+ // If we don't have remote data, err on the side of updating
17
+ const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
18
+ body: { label: prepareLabel(localLabel) },
19
+ params: { path: { label_uid: uid } },
20
+ })) as unknown;
21
+ const putError = (res as { error?: unknown } | undefined)?.error;
22
+ ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
23
+ results.updated.add(uid);
24
+ return;
25
+ }
26
+
27
+ let shouldUpdate = true;
28
+ try {
29
+ shouldUpdate =
30
+ JSON.stringify(canonicalize(localLabel)) !==
31
+ JSON.stringify(canonicalize(remoteLabel));
32
+ } catch {
33
+ shouldUpdate = true;
34
+ }
35
+
36
+ if (!shouldUpdate) return;
37
+
38
+ const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
39
+ body: { label: prepareLabel(localLabel) },
40
+ params: { path: { label_uid: uid } },
41
+ })) as unknown;
42
+ const putError = (res as { error?: unknown } | undefined)?.error;
43
+ ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
44
+ results.updated.add(uid);
45
+ }
46
+
47
+ export async function createLabel(
48
+ ctx: Ctx,
49
+ localLabel: Record<string, unknown>,
50
+ results: MutableTransferResults,
51
+ ): Promise<string | null> {
52
+ const res = (await ctx.cs.client.POST('/v3/labels', {
53
+ body: { label: prepareLabel(localLabel) },
54
+ })) as unknown;
55
+
56
+ const postError = (res as { error?: unknown } | undefined)?.error;
57
+ const labelName = typeof localLabel.name === 'string' ? localLabel.name : '';
58
+ ContentstackError.throwIfError(
59
+ postError,
60
+ `Failed to create label: ${labelName}`,
61
+ );
62
+ const postData = (res as { data?: unknown } | undefined)?.data;
63
+ let createdUid: string | null = null;
64
+ if (isRecord(postData)) {
65
+ const pd = postData;
66
+ if (isRecord(pd.label)) {
67
+ const labelObj = pd.label;
68
+ if (typeof labelObj.uid === 'string') createdUid = labelObj.uid;
69
+ }
70
+ if (createdUid === null && typeof pd.uid === 'string') createdUid = pd.uid;
71
+ }
72
+ results.created.add(createdUid ?? '<created>');
73
+ return createdUid;
74
+ }
@@ -1,6 +1,4 @@
1
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
2
  import type Ctx from '../ctx/Ctx.js';
5
3
  import { MutableTransferResults } from '../xfer/TransferResults.js';
6
4
  import getUi from '../lib/SchemaUi.js';
@@ -8,154 +6,251 @@ import isRecord from '#cli/util/isRecord.js';
8
6
  import flatten from '#cli/dto/labels/flatten.js';
9
7
  import { isNormalizedLabels } from '#cli/dto/labels/NormalizedLabels.js';
10
8
  import type Label from '#cli/cs/labels/Label.js';
9
+ import getAllLabels from '#cli/cs/labels/getAllLabels.js';
10
+ import { createLabel, updateIfNecessary } from './lib/labelOperations.js';
11
+ import { resolve } from 'node:path';
11
12
 
12
- export default async function toContentstack(ctx: Ctx) {
13
- const directory = schemaDirectory();
14
- const path = `${directory}/labels.yaml`;
13
+ /**
14
+ * Sort labels to ensure parents are processed before children.
15
+ * Labels without parents come first, then labels are sorted by dependency depth.
16
+ */
17
+ function sortLabelsByDependency(labels: Label[]): Label[] {
18
+ const labelsByUid = new Map<string, Label>();
19
+ const sorted: Label[] = [];
20
+ const processed = new Set<string>();
15
21
 
16
- const ui = getUi();
22
+ // Build a map for quick lookup
23
+ for (const label of labels) {
24
+ if (isRecord(label) && typeof label.uid === 'string') {
25
+ labelsByUid.set(label.uid, label as Label);
26
+ }
27
+ }
17
28
 
18
- let data: unknown;
19
- try {
20
- data = await readYaml(path);
21
- } catch {
22
- ui.info(`Labels: no file at ${path}, skipping`);
23
- return new MutableTransferResults();
29
+ // Recursive function to add a label and its dependencies
30
+ function addLabel(label: Label) {
31
+ const uid = typeof label.uid === 'string' ? label.uid : '';
32
+ if (!uid || processed.has(uid)) return;
33
+
34
+ // First, process parent if it exists in our label set
35
+ const parentArray = label.parent;
36
+ if (Array.isArray(parentArray) && parentArray.length > 0) {
37
+ // Use destructuring to satisfy eslint prefer-destructuring rule
38
+ const [parentValue] = parentArray as unknown as readonly [
39
+ string,
40
+ ...string[],
41
+ ];
42
+ if (labelsByUid.has(parentValue) && !processed.has(parentValue)) {
43
+ const parentLabel = labelsByUid.get(parentValue);
44
+ if (parentLabel) addLabel(parentLabel);
45
+ }
46
+ }
47
+
48
+ // Now add this label
49
+ sorted.push(label);
50
+ processed.add(uid);
24
51
  }
25
52
 
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[];
53
+ // Process all labels
54
+ for (const label of labels) {
55
+ if (isRecord(label)) {
56
+ addLabel(label as Label);
57
+ }
37
58
  }
38
59
 
60
+ return sorted;
61
+ }
62
+
63
+ export default async function toContentstack(ctx: Ctx) {
64
+ const ui = getUi();
65
+ const path = resolve(ui.options.schema.schemaPath, 'labels.yaml');
66
+
67
+ const data = await loadLabelsData(path, ui);
68
+ if (!data) {
69
+ return new MutableTransferResults();
70
+ }
71
+
72
+ const flatLabels = extractFlatLabels(data);
39
73
  ui.info(`Labels: read ${flatLabels.length} label(s) from ${path}`);
40
74
 
75
+ const {
76
+ localUidToName,
77
+ nameToRemoteUid,
78
+ remoteLabelsByName,
79
+ remoteLabelsByUid,
80
+ } = await prepareLabelMappings(ctx, flatLabels);
81
+
82
+ const sortedLabels = sortLabelsByDependency(flatLabels);
41
83
  const results = new MutableTransferResults();
42
84
 
43
- for (const labelRaw of flatLabels) {
85
+ for (const labelRaw of sortedLabels) {
44
86
  if (!isRecord(labelRaw)) continue;
45
- // keep per-label logic in helper to reduce complexity of this function
46
87
 
47
- await handleLabel(ctx, labelRaw, results);
88
+ await handleLabel(
89
+ ctx,
90
+ labelRaw,
91
+ remoteLabelsByUid,
92
+ remoteLabelsByName,
93
+ nameToRemoteUid,
94
+ localUidToName,
95
+ results,
96
+ );
48
97
  }
49
98
 
50
99
  return results;
51
100
  }
52
101
 
53
- function canonicalize(value: unknown): unknown {
54
- if (Array.isArray(value)) return value.map(canonicalize);
55
- if (isRecord(value)) {
56
- const obj = value;
57
- const out: Record<string, unknown> = {};
58
- for (const key of Object.keys(obj).sort()) {
59
- if (key === 'uid' || key === 'created_at' || key === 'updated_at')
60
- continue;
61
- // Normalize parent_uid: skip if null or undefined
62
- if (key === 'parent_uid' && (obj[key] === null || obj[key] === undefined))
63
- continue;
64
- out[key] = canonicalize(obj[key]);
65
- }
66
- return out;
102
+ async function loadLabelsData(
103
+ path: string,
104
+ ui: ReturnType<typeof getUi>,
105
+ ): Promise<unknown> {
106
+ try {
107
+ return await readYaml(path);
108
+ } catch {
109
+ ui.info(`Labels: no file at ${path}, skipping`);
110
+ return undefined;
67
111
  }
68
- return value;
69
112
  }
70
113
 
71
- async function updateIfNecessary(
72
- ctx: Ctx,
73
- uid: string,
74
- localLabel: Record<string, unknown>,
75
- results: MutableTransferResults,
76
- ) {
77
- const remoteRes = (await ctx.cs.client.GET('/v3/labels/{label_uid}', {
78
- params: { path: { label_uid: uid } },
79
- })) as unknown;
80
- const remoteData = (remoteRes as { data?: unknown } | undefined)?.data;
81
-
82
- if (!isRecord(remoteData) || !isRecord(remoteData.label)) {
83
- // If we can't parse remote data, err on the side of updating
84
- const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
85
- body: { label: prepareLabel(localLabel) },
86
- params: { path: { label_uid: uid } },
87
- })) as unknown;
88
- const putError = (res as { error?: unknown } | undefined)?.error;
89
- ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
90
- results.updated.add(uid);
91
- return;
114
+ function extractFlatLabels(data: unknown): Label[] {
115
+ if (isNormalizedLabels(data)) {
116
+ interface LabelWithChildren {
117
+ children?: unknown;
118
+ }
119
+ const hasChildren = (data.labels as unknown as LabelWithChildren[]).some(
120
+ (l) => 'children' in l && l.children !== undefined,
121
+ );
122
+
123
+ if (hasChildren) {
124
+ return [...flatten(data.labels)];
125
+ }
126
+ return [...data.labels] as unknown as Label[];
92
127
  }
93
128
 
94
- const remoteLabel = remoteData.label;
95
- let shouldUpdate = true;
96
- try {
97
- shouldUpdate =
98
- JSON.stringify(canonicalize(localLabel)) !==
99
- JSON.stringify(canonicalize(remoteLabel));
100
- } catch {
101
- shouldUpdate = true;
129
+ if (isRecord(data) && Array.isArray(data.labels)) {
130
+ return data.labels as Label[];
102
131
  }
103
132
 
104
- if (!shouldUpdate) return;
133
+ if (Array.isArray(data)) {
134
+ return data as Label[];
135
+ }
105
136
 
106
- const res = (await ctx.cs.client.PUT('/v3/labels/{label_uid}', {
107
- body: { label: prepareLabel(localLabel) },
108
- params: { path: { label_uid: uid } },
109
- })) as unknown;
110
- const putError = (res as { error?: unknown } | undefined)?.error;
111
- ContentstackError.throwIfError(putError, `Failed to update label: ${uid}`);
112
- results.updated.add(uid);
137
+ return [];
113
138
  }
114
139
 
115
- async function createLabel(
116
- ctx: Ctx,
117
- localLabel: Record<string, unknown>,
118
- results: MutableTransferResults,
119
- ) {
120
- const res = (await ctx.cs.client.POST('/v3/labels', {
121
- body: { label: prepareLabel(localLabel) },
122
- })) as unknown;
123
- const postError = (res as { error?: unknown } | undefined)?.error;
124
- ContentstackError.throwIfError(postError, `Failed to create label`);
125
- const postData = (res as { data?: unknown } | undefined)?.data;
126
- let createdUid: string | null = null;
127
- if (isRecord(postData)) {
128
- const pd = postData;
129
- if (isRecord(pd.label)) {
130
- const labelObj = pd.label;
131
- if (typeof labelObj.uid === 'string') createdUid = labelObj.uid;
132
- }
133
- if (createdUid === null && typeof pd.uid === 'string') createdUid = pd.uid;
140
+ async function prepareLabelMappings(ctx: Ctx, flatLabels: Label[]) {
141
+ const remoteLabels = await getAllLabels(ctx.cs.client);
142
+ const remoteLabelsByUid = new Map<string, Label>();
143
+ const remoteLabelsByName = new Map<string, Label>();
144
+
145
+ for (const label of remoteLabels) {
146
+ remoteLabelsByUid.set(label.uid, label);
147
+ remoteLabelsByName.set(label.name, label);
148
+ }
149
+
150
+ const nameToRemoteUid = new Map<string, string>();
151
+ for (const [name, label] of remoteLabelsByName) {
152
+ nameToRemoteUid.set(name, label.uid);
134
153
  }
135
- results.created.add(createdUid ?? '<created>');
136
- }
137
154
 
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 };
155
+ const localUidToName = new Map<string, string>();
156
+ for (const label of flatLabels) {
157
+ if (
158
+ isRecord(label) &&
159
+ typeof label.uid === 'string' &&
160
+ typeof label.name === 'string'
161
+ ) {
162
+ localUidToName.set(label.uid, label.name);
163
+ }
144
164
  }
145
- return rest;
165
+
166
+ return {
167
+ localUidToName,
168
+ nameToRemoteUid,
169
+ remoteLabelsByName,
170
+ remoteLabelsByUid,
171
+ };
146
172
  }
147
173
 
148
174
  async function handleLabel(
149
175
  ctx: Ctx,
150
176
  labelRaw: Record<string, unknown>,
177
+ remoteLabelsByUid: Map<string, Label>,
178
+ remoteLabelsByName: Map<string, Label>,
179
+ nameToRemoteUid: Map<string, string>,
180
+ localUidToName: Map<string, string>,
151
181
  results: MutableTransferResults,
152
182
  ) {
153
183
  const uid = typeof labelRaw.uid === 'string' ? labelRaw.uid : '';
184
+ const name = typeof labelRaw.name === 'string' ? labelRaw.name : '';
185
+
186
+ const preparedLabel = translateParentReference(
187
+ labelRaw,
188
+ localUidToName,
189
+ nameToRemoteUid,
190
+ );
154
191
 
155
- if (uid.length) {
156
- await updateIfNecessary(ctx, uid, labelRaw, results);
192
+ // First try to find by UID (exact match from same stack)
193
+ if (uid.length > 0 && remoteLabelsByUid.has(uid)) {
194
+ const remoteLabel = remoteLabelsByUid.get(uid);
195
+ await updateIfNecessary(ctx, uid, preparedLabel, remoteLabel, results);
157
196
  return;
158
197
  }
159
198
 
160
- await createLabel(ctx, labelRaw, results);
199
+ // Then try to find by name (for cross-stack scenarios)
200
+ if (name.length > 0 && remoteLabelsByName.has(name)) {
201
+ const remoteLabel = remoteLabelsByName.get(name);
202
+ if (!remoteLabel) {
203
+ return;
204
+ }
205
+ await updateIfNecessary(
206
+ ctx,
207
+ remoteLabel.uid,
208
+ preparedLabel,
209
+ remoteLabel,
210
+ results,
211
+ );
212
+ nameToRemoteUid.set(name, remoteLabel.uid);
213
+ return;
214
+ }
215
+
216
+ // Label doesn't exist remotely, create it
217
+ const createdUid = await createLabel(ctx, preparedLabel, results);
218
+
219
+ if (createdUid && name) {
220
+ nameToRemoteUid.set(name, createdUid);
221
+ }
222
+ }
223
+
224
+ function translateParentReference(
225
+ labelRaw: Record<string, unknown>,
226
+ localUidToName: Map<string, string>,
227
+ nameToRemoteUid: Map<string, string>,
228
+ ): Record<string, unknown> {
229
+ const parentField = labelRaw.parent;
230
+ if (!Array.isArray(parentField) || parentField.length === 0) {
231
+ return { ...labelRaw };
232
+ }
233
+
234
+ // TypeScript knows parentField is an array, but we need to type it properly
235
+ const [firstElement] = parentField as unknown[];
236
+ if (typeof firstElement !== 'string') {
237
+ return { ...labelRaw };
238
+ }
239
+ const firstParent: string = firstElement;
240
+
241
+ const parentName = localUidToName.get(firstParent);
242
+
243
+ if (!parentName || !nameToRemoteUid.has(parentName)) {
244
+ return { ...labelRaw };
245
+ }
246
+
247
+ const remoteParentUid = nameToRemoteUid.get(parentName);
248
+ if (!remoteParentUid) {
249
+ return { ...labelRaw };
250
+ }
251
+
252
+ return {
253
+ ...labelRaw,
254
+ parent: [remoteParentUid],
255
+ };
161
256
  }
@@ -1,20 +1,27 @@
1
1
  import getAllLabels from '#cli/cs/labels/getAllLabels.js';
2
2
  import writeYaml from '#cli/fs/writeYaml.js';
3
3
  import organize from '#cli/dto/labels/organize.js';
4
- import schemaDirectory from '../content-types/schemaDirectory.js';
5
4
  import { MutableTransferResults } from '../xfer/TransferResults.js';
6
5
  import createProgressBar from '../lib/createProgressBar.js';
7
6
  import type Ctx from '../ctx/Ctx.js';
7
+ import getUi from '../lib/SchemaUi.js';
8
+ import { resolve } from 'node:path';
8
9
 
9
10
  export default async function toFilesystem(ctx: Ctx) {
10
- const directory = schemaDirectory();
11
+ const ui = getUi();
12
+ const {
13
+ options: {
14
+ schema: { schemaPath },
15
+ },
16
+ } = ui;
17
+ const labelsPath = resolve(schemaPath, 'labels.yaml');
11
18
  const bar = createProgressBar('Labels', 1, 0);
12
19
 
13
20
  // Fetch labels and organize them into a hierarchical structure
14
21
  const flatLabels = await getAllLabels(ctx.cs.client);
15
22
  const hierarchicalLabels = organize(flatLabels);
16
23
 
17
- await writeYaml(`${directory}/labels.yaml`, { labels: hierarchicalLabels });
24
+ await writeYaml(labelsPath, { labels: hierarchicalLabels });
18
25
 
19
26
  const result = new MutableTransferResults();
20
27
  result.created.add('labels.yaml');