sf-plugin-cms 0.2.0 → 0.3.0

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 (64) hide show
  1. package/README.md +116 -13
  2. package/lib/{commands/cms/command-base.d.ts → command-base.d.ts} +2 -1
  3. package/lib/{commands/cms/command-base.js → command-base.js} +3 -0
  4. package/lib/command-base.js.map +1 -0
  5. package/lib/commands/cms/export/workspace.d.ts +12 -5
  6. package/lib/commands/cms/export/workspace.js +102 -11
  7. package/lib/commands/cms/export/workspace.js.map +1 -1
  8. package/lib/commands/cms/get/channel.d.ts +1 -1
  9. package/lib/commands/cms/get/channel.js +1 -1
  10. package/lib/commands/cms/get/channel.js.map +1 -1
  11. package/lib/commands/cms/get/content.d.ts +1 -1
  12. package/lib/commands/cms/get/content.js +1 -1
  13. package/lib/commands/cms/get/content.js.map +1 -1
  14. package/lib/commands/cms/get/variant.d.ts +1 -1
  15. package/lib/commands/cms/get/variant.js +1 -1
  16. package/lib/commands/cms/get/variant.js.map +1 -1
  17. package/lib/commands/cms/get/workspace.d.ts +1 -1
  18. package/lib/commands/cms/get/workspace.js +1 -1
  19. package/lib/commands/cms/get/workspace.js.map +1 -1
  20. package/lib/commands/cms/import/workspace.d.ts +7 -4
  21. package/lib/commands/cms/import/workspace.js +121 -35
  22. package/lib/commands/cms/import/workspace.js.map +1 -1
  23. package/lib/commands/cms/info.d.ts +11 -10
  24. package/lib/commands/cms/info.js +123 -8
  25. package/lib/commands/cms/info.js.map +1 -1
  26. package/lib/commands/cms/list/channel.d.ts +1 -1
  27. package/lib/commands/cms/list/channel.js +1 -1
  28. package/lib/commands/cms/list/channel.js.map +1 -1
  29. package/lib/commands/cms/list/workspace.d.ts +1 -1
  30. package/lib/commands/cms/list/workspace.js +1 -1
  31. package/lib/commands/cms/list/workspace.js.map +1 -1
  32. package/lib/contracts/info.d.ts +43 -0
  33. package/lib/contracts/info.js +69 -0
  34. package/lib/contracts/info.js.map +1 -0
  35. package/lib/contracts/shared.d.ts +81 -0
  36. package/lib/contracts/shared.js +218 -0
  37. package/lib/contracts/shared.js.map +1 -0
  38. package/lib/contracts/workspace-export.d.ts +91 -0
  39. package/lib/contracts/workspace-export.js +324 -0
  40. package/lib/contracts/workspace-export.js.map +1 -0
  41. package/lib/contracts/workspace-import.d.ts +46 -0
  42. package/lib/contracts/workspace-import.js +108 -0
  43. package/lib/contracts/workspace-import.js.map +1 -0
  44. package/lib/index.d.ts +1 -1
  45. package/lib/services/bulk-export-workspaces.d.ts +25 -0
  46. package/lib/services/bulk-export-workspaces.js +272 -0
  47. package/lib/services/bulk-export-workspaces.js.map +1 -0
  48. package/lib/services/export-references.d.ts +21 -0
  49. package/lib/services/export-references.js +150 -0
  50. package/lib/services/export-references.js.map +1 -0
  51. package/lib/services/export-workspace.d.ts +10 -22
  52. package/lib/services/export-workspace.js +68 -2
  53. package/lib/services/export-workspace.js.map +1 -1
  54. package/lib/services/import-workspace.d.ts +7 -3
  55. package/lib/services/import-workspace.js +224 -96
  56. package/lib/services/import-workspace.js.map +1 -1
  57. package/lib/services/resolve-workspace.d.ts +5 -0
  58. package/lib/services/resolve-workspace.js +56 -47
  59. package/lib/services/resolve-workspace.js.map +1 -1
  60. package/messages/cms.export.workspace.md +22 -4
  61. package/messages/cms.import.workspace.md +8 -2
  62. package/messages/cms.info.md +21 -0
  63. package/package.json +3 -2
  64. package/lib/commands/cms/command-base.js.map +0 -1
@@ -0,0 +1,272 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { assertCmsEnvelope, MAX_DIAGNOSTICS_PER_KIND, sanitizeDiagnostics, } from '../contracts/shared.js';
5
+ import { assertWorkspaceExportSetResult, WORKSPACE_EXPORT_MANIFEST_CONTRACT, WORKSPACE_EXPORT_SET_CONTRACT, } from '../contracts/workspace-export.js';
6
+ import { redactSecrets } from '../transport/redact-secrets.js';
7
+ import { exportWorkspace, safeWorkspaceDirectoryName } from './export-workspace.js';
8
+ import { getWorkspace } from './read.js';
9
+ import { enumerateWorkspaces } from './resolve-workspace.js';
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function nonemptyString(value) {
14
+ return typeof value === 'string' && value.length > 0;
15
+ }
16
+ function folded(value) {
17
+ return value.normalize('NFKC').toLocaleLowerCase('en-US');
18
+ }
19
+ function actualWorkspaceType(workspace) {
20
+ const spaceType = workspace.spaceType;
21
+ if (typeof spaceType === 'string')
22
+ return normalizeWorkspaceType(spaceType);
23
+ if (isRecord(spaceType) && typeof spaceType.apiName === 'string') {
24
+ return normalizeWorkspaceType(spaceType.apiName);
25
+ }
26
+ return normalizeWorkspaceType();
27
+ }
28
+ export function normalizeWorkspaceType(value) {
29
+ if (typeof value === 'string') {
30
+ const normalized = value.toLocaleLowerCase('en-US');
31
+ if (normalized === 'marketing')
32
+ return 'Marketing';
33
+ if (normalized === 'content')
34
+ return 'Content';
35
+ }
36
+ throw new Error('Workspace type must be Marketing or Content.');
37
+ }
38
+ async function exists(candidate) {
39
+ try {
40
+ await stat(candidate);
41
+ return true;
42
+ }
43
+ catch (error) {
44
+ if (isRecord(error) && error.code === 'ENOENT')
45
+ return false;
46
+ throw error;
47
+ }
48
+ }
49
+ async function assertParentCompatible(parent) {
50
+ let cursor = path.resolve(parent);
51
+ for (;;) {
52
+ try {
53
+ const information = await stat(cursor);
54
+ if (!information.isDirectory()) {
55
+ throw new Error(`Bulk output parent is not a directory: ${cursor}`);
56
+ }
57
+ return;
58
+ }
59
+ catch (error) {
60
+ if (!isRecord(error) || error.code !== 'ENOENT')
61
+ throw error;
62
+ }
63
+ const next = path.dirname(cursor);
64
+ if (next === cursor)
65
+ throw new Error(`Bulk output parent is not usable: ${parent}`);
66
+ cursor = next;
67
+ }
68
+ }
69
+ function normalizedError(error) {
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ return (redactSecrets(message)
72
+ .replaceAll(/[\r\n]+/gu, ' ')
73
+ .trim() || 'Workspace export failed.');
74
+ }
75
+ function portableOutputPath(value) {
76
+ const portable = value.split(path.sep).join('/');
77
+ if (/^[A-Za-z]:/u.test(portable) || portable.startsWith('/'))
78
+ return path.basename(value);
79
+ return portable.startsWith('./') ? portable : `./${portable}`;
80
+ }
81
+ function relativeArtifactPath(outputDirectory, destination) {
82
+ return path.relative(outputDirectory, destination).split(path.sep).join('/');
83
+ }
84
+ function hashExportSet(result) {
85
+ return `export-set:${createHash('sha256').update(JSON.stringify(result)).digest('hex')}`;
86
+ }
87
+ export async function preflightBulkWorkspaceExport(connection, outputDirectory, workspaceType, options = {}) {
88
+ const listed = await enumerateWorkspaces(connection, options);
89
+ const canonical = [];
90
+ const canonicalNames = new Map();
91
+ for (const summary of listed) {
92
+ const workspace = await getWorkspace(connection, summary.id, options);
93
+ if (workspace.id !== summary.id || !nonemptyString(workspace.name)) {
94
+ throw new Error(`Canonical workspace ${summary.id} returned a malformed or mismatched ID/name.`);
95
+ }
96
+ const nameKey = folded(workspace.name);
97
+ const existingNameId = canonicalNames.get(nameKey);
98
+ if (existingNameId !== undefined && existingNameId !== summary.id) {
99
+ throw new Error(`Workspace names for IDs ${existingNameId} and ${summary.id} differ only by case or Unicode normalization and are ambiguous.`);
100
+ }
101
+ canonicalNames.set(nameKey, summary.id);
102
+ const type = actualWorkspaceType(workspace);
103
+ if (workspaceType !== undefined && type !== workspaceType)
104
+ continue;
105
+ canonical.push({
106
+ id: summary.id,
107
+ name: workspace.name,
108
+ type,
109
+ destination: path.join(outputDirectory, safeWorkspaceDirectoryName(workspace.name)),
110
+ });
111
+ }
112
+ canonical.sort((left, right) => left.id.localeCompare(right.id, 'en-US'));
113
+ await assertParentCompatible(outputDirectory);
114
+ const destinations = new Map();
115
+ for (const workspace of canonical) {
116
+ if (await exists(workspace.destination)) {
117
+ throw new Error(`Export destination already exists: ${workspace.destination}`);
118
+ }
119
+ const destinationKey = folded(path.resolve(workspace.destination));
120
+ const existing = destinations.get(destinationKey);
121
+ if (existing !== undefined) {
122
+ throw new Error(`Workspace destinations collide after sanitization: ${existing.id} (${existing.name}) and ${workspace.id} (${workspace.name}).`);
123
+ }
124
+ destinations.set(destinationKey, workspace);
125
+ }
126
+ return { discoveredCount: listed.length, selected: canonical };
127
+ }
128
+ export async function exportAllWorkspaces(connection, outputDirectory, workspaceType, options = {}) {
129
+ const generatedAt = options.generatedAt ?? new Date().toISOString();
130
+ const pluginVersion = options.pluginVersion ?? '0.3.0';
131
+ const sourceOrgId = options.sourceOrgId ?? 'unknown-org';
132
+ const preflight = await preflightBulkWorkspaceExport(connection, outputDirectory, workspaceType, options);
133
+ if (preflight.selected.length > 0)
134
+ await mkdir(outputDirectory, { recursive: true });
135
+ const workspaces = [];
136
+ const externalReferenceCorrelations = [];
137
+ for (const workspace of preflight.selected) {
138
+ const artifactPath = relativeArtifactPath(outputDirectory, workspace.destination);
139
+ try {
140
+ const exported = await exportWorkspace(connection, workspace.id, workspace.destination, {
141
+ ...options,
142
+ generatedAt,
143
+ pluginVersion,
144
+ sourceOrgId,
145
+ });
146
+ const partial = exported.manifest.completeness === 'partial';
147
+ for (const reference of exported.manifest.externalReferences) {
148
+ if (reference.resolution !== 'included' || reference.kind !== 'cms.content')
149
+ continue;
150
+ externalReferenceCorrelations.push({
151
+ sourceWorkspaceId: workspace.id,
152
+ sourceReference: reference.portableKey.value,
153
+ referenceKind: reference.kind,
154
+ referenceId: reference.referenceId,
155
+ packageManifestSha256: exported.manifestSha256,
156
+ });
157
+ }
158
+ const warnings = partial
159
+ ? exported.manifest.warnings.map(({ code, message }) => ({ code, message }))
160
+ : [];
161
+ workspaces.push({
162
+ source: {
163
+ kind: 'cms.workspace',
164
+ sourceId: workspace.id,
165
+ name: workspace.name,
166
+ workspaceType: workspace.type,
167
+ },
168
+ status: partial ? 'partial' : 'success',
169
+ artifact: {
170
+ path: artifactPath,
171
+ manifestPath: `${artifactPath}/manifest.json`,
172
+ manifestContract: WORKSPACE_EXPORT_MANIFEST_CONTRACT,
173
+ manifestContractVersion: '1.0.0',
174
+ manifestSha256: exported.manifestSha256,
175
+ },
176
+ diagnostics: { warnings, errors: [] },
177
+ });
178
+ }
179
+ catch (error) {
180
+ workspaces.push({
181
+ source: {
182
+ kind: 'cms.workspace',
183
+ sourceId: workspace.id,
184
+ name: workspace.name,
185
+ workspaceType: workspace.type,
186
+ },
187
+ status: 'failed',
188
+ artifact: null,
189
+ diagnostics: {
190
+ warnings: [],
191
+ errors: [{ code: 'WORKSPACE_EXPORT_FAILED', message: normalizedError(error) }],
192
+ },
193
+ });
194
+ }
195
+ }
196
+ externalReferenceCorrelations.sort((left, right) => [
197
+ left.sourceWorkspaceId,
198
+ left.sourceReference,
199
+ left.referenceKind,
200
+ left.referenceId,
201
+ left.packageManifestSha256,
202
+ ]
203
+ .join('\u0000')
204
+ .localeCompare([
205
+ right.sourceWorkspaceId,
206
+ right.sourceReference,
207
+ right.referenceKind,
208
+ right.referenceId,
209
+ right.packageManifestSha256,
210
+ ].join('\u0000')));
211
+ const result = {
212
+ workspaceType,
213
+ outputDirectory: portableOutputPath(outputDirectory),
214
+ selection: {
215
+ mode: 'all',
216
+ discoveredCount: preflight.discoveredCount,
217
+ selectedCount: workspaces.length,
218
+ },
219
+ summary: {
220
+ succeededCount: workspaces.filter(({ status }) => status === 'success').length,
221
+ partialCount: workspaces.filter(({ status }) => status === 'partial').length,
222
+ failedCount: workspaces.filter(({ status }) => status === 'failed').length,
223
+ },
224
+ externalReferenceCorrelations,
225
+ workspaces,
226
+ };
227
+ let status = 'success';
228
+ if (result.summary.failedCount === workspaces.length && workspaces.length > 0)
229
+ status = 'failed';
230
+ else if (result.summary.failedCount > 0 || result.summary.partialCount > 0)
231
+ status = 'partial';
232
+ const errors = aggregateDiagnostics(workspaces, 'errors');
233
+ const warnings = aggregateDiagnostics(workspaces, 'warnings');
234
+ const envelope = {
235
+ contract: WORKSPACE_EXPORT_SET_CONTRACT,
236
+ contractVersion: '1.0.0',
237
+ status,
238
+ metadata: {
239
+ operation: 'workspace.export.bulk',
240
+ plugin: { name: 'sf-plugin-cms', version: pluginVersion },
241
+ apiVersion: options.apiVersion ?? '67.0',
242
+ },
243
+ diagnostics: { warnings, errors },
244
+ provenance: {
245
+ producer: 'sf-plugin-cms',
246
+ sourceOrgId,
247
+ pluginVersion,
248
+ exportSetId: hashExportSet(result),
249
+ command: 'sf cms export workspace',
250
+ generatedAt,
251
+ },
252
+ result,
253
+ };
254
+ assertCmsEnvelope(envelope, assertWorkspaceExportSetResult);
255
+ return envelope;
256
+ }
257
+ function aggregateDiagnostics(workspaces, kind) {
258
+ const diagnostics = workspaces.flatMap(({ source, diagnostics: workspaceDiagnostics }) => workspaceDiagnostics[kind].map((diagnostic) => ({ ...diagnostic, scope: source.sourceId })));
259
+ if (diagnostics.length <= MAX_DIAGNOSTICS_PER_KIND) {
260
+ return sanitizeDiagnostics(diagnostics, `diagnostics.${kind}`);
261
+ }
262
+ const omitted = diagnostics.length - MAX_DIAGNOSTICS_PER_KIND + 1;
263
+ return sanitizeDiagnostics([
264
+ ...diagnostics.slice(0, MAX_DIAGNOSTICS_PER_KIND - 1),
265
+ {
266
+ code: 'DIAGNOSTICS_TRUNCATED',
267
+ message: `${omitted} additional ${kind} omitted from aggregate diagnostics; per-workspace diagnostics remain complete.`,
268
+ retryable: false,
269
+ },
270
+ ], `diagnostics.${kind}`);
271
+ }
272
+ //# sourceMappingURL=bulk-export-workspaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-export-workspaces.js","sourceRoot":"","sources":["../../src/services/bulk-export-workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,GAIpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,8BAA8B,EAC9B,kCAAkC,EAClC,6BAA6B,GAI9B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAkB,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAoB7D,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,mBAAmB,CAAC,SAAoB;IAC/C,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;IACtC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC5E,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACjE,OAAO,sBAAsB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,sBAAsB,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAe;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,UAAU,KAAK,WAAW;YAAE,OAAO,WAAW,CAAC;QACnD,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;IACjD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAClE,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,SAAiB;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC7D,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,MAAc;IAClD,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,EAAE,CAAC,CAAC;YACtE,CAAC;YACD,OAAO;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;QAC/D,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,IAAI,KAAK,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,EAAE,CAAC,CAAC;QACpF,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CACL,aAAa,CAAC,OAAO,CAAC;SACnB,UAAU,CAAC,WAAW,EAAE,GAAG,CAAC;SAC5B,IAAI,EAAE,IAAI,0BAA0B,CACxC,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjD,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1F,OAAO,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,oBAAoB,CAAC,eAAuB,EAAE,WAAmB;IACxE,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,aAAa,CAAC,MAAgC;IACrD,OAAO,cAAc,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAA6B,EAC7B,eAAuB,EACvB,aAAsC,EACtC,UAA8B,EAAE;IAEhC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEjD,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACtE,IAAI,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,MAAM,IAAI,KAAK,CACb,uBAAuB,OAAO,CAAC,EAAE,8CAA8C,CAChF,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,OAAO,CAAC,EAAE,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CACb,2BAA2B,cAAc,QAAQ,OAAO,CAAC,EAAE,kEAAkE,CAC9H,CAAC;QACJ,CAAC;QACD,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,aAAa,KAAK,SAAS,IAAI,IAAI,KAAK,aAAa;YAAE,SAAS;QACpE,SAAS,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,0BAA0B,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;SACpF,CAAC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1E,MAAM,sBAAsB,CAAC,eAAe,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC1D,KAAK,MAAM,SAAS,IAAI,SAAS,EAAE,CAAC;QAClC,IAAI,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,sCAAsC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;QACnE,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,sDAAsD,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,IAAI,SAAS,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,IAAI,IAAI,CAChI,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAA6B,EAC7B,eAAuB,EACvB,aAAqC,EACrC,UAAsC,EAAE;IAExC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,aAAa,CAAC;IACzD,MAAM,SAAS,GAAG,MAAM,4BAA4B,CAClD,UAAU,EACV,eAAe,EACf,aAAa,EACb,OAAO,CACR,CAAC;IACF,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAErF,MAAM,UAAU,GAA8B,EAAE,CAAC;IACjD,MAAM,6BAA6B,GAAmC,EAAE,CAAC;IACzE,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,YAAY,GAAG,oBAAoB,CAAC,eAAe,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QAClF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,WAAW,EAAE;gBACtF,GAAG,OAAO;gBACV,WAAW;gBACX,aAAa;gBACb,WAAW;aACZ,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,CAAC;YAC7D,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;gBAC7D,IAAI,SAAS,CAAC,UAAU,KAAK,UAAU,IAAI,SAAS,CAAC,IAAI,KAAK,aAAa;oBAAE,SAAS;gBACtF,6BAA6B,CAAC,IAAI,CAAC;oBACjC,iBAAiB,EAAE,SAAS,CAAC,EAAE;oBAC/B,eAAe,EAAE,SAAS,CAAC,WAAW,CAAC,KAAK;oBAC5C,aAAa,EAAE,SAAS,CAAC,IAAI;oBAC7B,WAAW,EAAE,SAAS,CAAC,WAAW;oBAClC,qBAAqB,EAAE,QAAQ,CAAC,cAAc;iBAC/C,CAAC,CAAC;YACL,CAAC;YACD,MAAM,QAAQ,GAAoB,OAAO;gBACvC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC5E,CAAC,CAAC,EAAE,CAAC;YACP,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE;oBACN,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,SAAS,CAAC,EAAE;oBACtB,IAAI,EAAE,SAAS,CAAC,IAAI;oBACpB,aAAa,EAAE,SAAS,CAAC,IAAI;iBAC9B;gBACD,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBACvC,QAAQ,EAAE;oBACR,IAAI,EAAE,YAAY;oBAClB,YAAY,EAAE,GAAG,YAAY,gBAAgB;oBAC7C,gBAAgB,EAAE,kCAAkC;oBACpD,uBAAuB,EAAE,OAAO;oBAChC,cAAc,EAAE,QAAQ,CAAC,cAAc;iBACxC;gBACD,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;aACtC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE;oBACN,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,SAAS,CAAC,EAAE;oBACtB,IAAI,EAAE,SAAS,CAAC,IAAI;oBACpB,aAAa,EAAE,SAAS,CAAC,IAAI;iBAC9B;gBACD,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE;oBACX,QAAQ,EAAE,EAAE;oBACZ,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;iBAC/E;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,6BAA6B,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACjD;QACE,IAAI,CAAC,iBAAiB;QACtB,IAAI,CAAC,eAAe;QACpB,IAAI,CAAC,aAAa;QAClB,IAAI,CAAC,WAAW;QAChB,IAAI,CAAC,qBAAqB;KAC3B;SACE,IAAI,CAAC,QAAQ,CAAC;SACd,aAAa,CACZ;QACE,KAAK,CAAC,iBAAiB;QACvB,KAAK,CAAC,eAAe;QACrB,KAAK,CAAC,aAAa;QACnB,KAAK,CAAC,WAAW;QACjB,KAAK,CAAC,qBAAqB;KAC5B,CAAC,IAAI,CAAC,QAAQ,CAAC,CACjB,CACJ,CAAC;IACF,MAAM,MAAM,GAA6B;QACvC,aAAa;QACb,eAAe,EAAE,kBAAkB,CAAC,eAAe,CAAC;QACpD,SAAS,EAAE;YACT,IAAI,EAAE,KAAK;YACX,eAAe,EAAE,SAAS,CAAC,eAAe;YAC1C,aAAa,EAAE,UAAU,CAAC,MAAM;SACjC;QACD,OAAO,EAAE;YACP,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;YAC9E,YAAY,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;YAC5E,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM;SAC3E;QACD,6BAA6B;QAC7B,UAAU;KACX,CAAC;IACF,IAAI,MAAM,GAAc,SAAS,CAAC;IAClC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,GAAG,QAAQ,CAAC;SAC5F,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,GAAG,CAAC;QAAE,MAAM,GAAG,SAAS,CAAC;IAC/F,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,oBAAoB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAE9D,MAAM,QAAQ,GAA0C;QACtD,QAAQ,EAAE,6BAA6B;QACvC,eAAe,EAAE,OAAO;QACxB,MAAM;QACN,QAAQ,EAAE;YACR,SAAS,EAAE,uBAAuB;YAClC,MAAM,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE;YACzD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,MAAM;SACzC;QACD,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjC,UAAU,EAAE;YACV,QAAQ,EAAE,eAAe;YACzB,WAAW;YACX,aAAa;YACb,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC;YAClC,OAAO,EAAE,yBAAyB;YAClC,WAAW;SACZ;QACD,MAAM;KACP,CAAC;IACF,iBAAiB,CAAC,QAAQ,EAAE,8BAA8B,CAAC,CAAC;IAC5D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,oBAAoB,CAC3B,UAA8C,EAC9C,IAA2B;IAE3B,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,EAAE,EAAE,CACvF,oBAAoB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAC5F,CAAC;IACF,IAAI,WAAW,CAAC,MAAM,IAAI,wBAAwB,EAAE,CAAC;QACnD,OAAO,mBAAmB,CAAC,WAAW,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,GAAG,wBAAwB,GAAG,CAAC,CAAC;IAClE,OAAO,mBAAmB,CACxB;QACE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,GAAG,CAAC,CAAC;QACrD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,GAAG,OAAO,eAAe,IAAI,iFAAiF;YACvH,SAAS,EAAE,KAAK;SACjB;KACF,EACD,eAAe,IAAI,EAAE,CACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { OpaqueCmsReference } from '../contracts/shared.js';
2
+ import type { CmsRecord } from './read.js';
3
+ export type ExportReferenceWarning = {
4
+ code: 'REFERENCE_UNRESOLVED' | 'REFERENCE_UNSUPPORTED';
5
+ message: string;
6
+ variantIds: string[];
7
+ };
8
+ export type ExportReferenceInventory = {
9
+ dependencies: string[];
10
+ externalReferences: OpaqueCmsReference[];
11
+ itemReferenceIds: ReadonlyMap<string, string>;
12
+ warnings: ExportReferenceWarning[];
13
+ };
14
+ /**
15
+ * Inventories only the CMS content identity fields retained in exported variant payloads.
16
+ * Unknown payload fields are deliberately not interpreted as relationships.
17
+ * @param {string} workspaceId - Canonical source CMS workspace identifier.
18
+ * @param {ReadonlyMap<string, CmsRecord>} details - Exported variants keyed by variant ID.
19
+ * @returns {ExportReferenceInventory} Deterministic included and unresolved reference evidence.
20
+ */
21
+ export declare function inventoryExportReferences(workspaceId: string, details: ReadonlyMap<string, CmsRecord>): ExportReferenceInventory;
@@ -0,0 +1,150 @@
1
+ import { createHash } from 'node:crypto';
2
+ function nonemptyString(value) {
3
+ return typeof value === 'string' && value.length > 0;
4
+ }
5
+ function referenceId(workspaceId, kind, sourceId) {
6
+ const identity = JSON.stringify({ kind, sourceId, workspaceId });
7
+ return `ref:${createHash('sha256').update(identity).digest('hex')}`;
8
+ }
9
+ function containsCmsContentBodyReference(value) {
10
+ if (Array.isArray(value))
11
+ return value.some((item_) => containsCmsContentBodyReference(item_));
12
+ if (typeof value !== 'object' || value === null)
13
+ return false;
14
+ const record = value;
15
+ if (typeof record.ref === 'object' &&
16
+ record.ref !== null &&
17
+ nonemptyString(record.ref.contentKey)) {
18
+ return true;
19
+ }
20
+ return Object.values(record).some((item_) => containsCmsContentBodyReference(item_));
21
+ }
22
+ function compareReferences(left, right) {
23
+ return [left.kind, left.referenceId, left.portableKey.value]
24
+ .join('\u0000')
25
+ .localeCompare([right.kind, right.referenceId, right.portableKey.value].join('\u0000'));
26
+ }
27
+ /**
28
+ * Inventories only the CMS content identity fields retained in exported variant payloads.
29
+ * Unknown payload fields are deliberately not interpreted as relationships.
30
+ * @param {string} workspaceId - Canonical source CMS workspace identifier.
31
+ * @param {ReadonlyMap<string, CmsRecord>} details - Exported variants keyed by variant ID.
32
+ * @returns {ExportReferenceInventory} Deterministic included and unresolved reference evidence.
33
+ */
34
+ export function inventoryExportReferences(workspaceId, details) {
35
+ const candidates = [];
36
+ const externalReferences = [];
37
+ const itemReferenceIds = new Map();
38
+ const warnings = [];
39
+ const includedById = new Map();
40
+ for (const [variantId, detail] of details) {
41
+ if ('references' in detail ||
42
+ 'referencesList' in detail ||
43
+ containsCmsContentBodyReference(detail.contentBody)) {
44
+ const id = referenceId(workspaceId, 'cms.relationship', variantId);
45
+ externalReferences.push({
46
+ referenceId: id,
47
+ owner: 'cms',
48
+ kind: 'cms.relationship',
49
+ source: { workspaceId, sourceId: variantId },
50
+ portableKey: { scheme: 'cms-opaque-v1', value: variantId },
51
+ required: true,
52
+ resolution: 'unsupported',
53
+ });
54
+ warnings.push({
55
+ code: 'REFERENCE_UNSUPPORTED',
56
+ message: 'The exported CMS payload contains a relationship class without an evidenced portable resolver.',
57
+ variantIds: [variantId],
58
+ });
59
+ }
60
+ if (!nonemptyString(detail.contentKey))
61
+ continue;
62
+ candidates.push({
63
+ contentId: nonemptyString(detail.contentId) ? detail.contentId : undefined,
64
+ contentKey: detail.contentKey,
65
+ variantId,
66
+ });
67
+ }
68
+ const idsByKey = new Map();
69
+ const keysById = new Map();
70
+ const incompleteKeys = new Set();
71
+ for (const candidate of candidates) {
72
+ if (candidate.contentId === undefined) {
73
+ incompleteKeys.add(candidate.contentKey);
74
+ continue;
75
+ }
76
+ const ids = idsByKey.get(candidate.contentKey) ?? new Set();
77
+ ids.add(candidate.contentId);
78
+ idsByKey.set(candidate.contentKey, ids);
79
+ const keys = keysById.get(candidate.contentId) ?? new Set();
80
+ keys.add(candidate.contentKey);
81
+ keysById.set(candidate.contentId, keys);
82
+ }
83
+ for (const candidate of candidates.toSorted((left, right) => left.variantId.localeCompare(right.variantId))) {
84
+ const conflicting = candidate.contentId !== undefined &&
85
+ ((idsByKey.get(candidate.contentKey)?.size ?? 0) > 1 ||
86
+ incompleteKeys.has(candidate.contentKey) ||
87
+ (keysById.get(candidate.contentId)?.size ?? 0) > 1);
88
+ if (conflicting) {
89
+ const id = referenceId(workspaceId, 'cms.unknown', candidate.variantId);
90
+ externalReferences.push({
91
+ referenceId: id,
92
+ owner: 'cms',
93
+ kind: 'cms.unknown',
94
+ source: { workspaceId, sourceId: candidate.variantId },
95
+ portableKey: { scheme: 'cms-opaque-v1', value: candidate.contentKey },
96
+ required: true,
97
+ resolution: 'unsupported',
98
+ });
99
+ warnings.push({
100
+ code: 'REFERENCE_UNSUPPORTED',
101
+ message: 'A CMS content reference has conflicting retained identity evidence.',
102
+ variantIds: [candidate.variantId],
103
+ });
104
+ continue;
105
+ }
106
+ if (candidate.contentId === undefined) {
107
+ const id = referenceId(workspaceId, 'cms.unknown', candidate.variantId);
108
+ externalReferences.push({
109
+ referenceId: id,
110
+ owner: 'cms',
111
+ kind: 'cms.unknown',
112
+ source: { workspaceId, sourceId: candidate.variantId },
113
+ portableKey: { scheme: 'cms-opaque-v1', value: candidate.contentKey },
114
+ required: true,
115
+ resolution: 'unresolved',
116
+ });
117
+ warnings.push({
118
+ code: 'REFERENCE_UNRESOLVED',
119
+ message: 'A CMS content reference lacks retained canonical content identity.',
120
+ variantIds: [candidate.variantId],
121
+ });
122
+ continue;
123
+ }
124
+ const id = referenceId(workspaceId, 'cms.content', candidate.contentId);
125
+ itemReferenceIds.set(candidate.variantId, id);
126
+ if (!includedById.has(candidate.contentId)) {
127
+ includedById.set(candidate.contentId, {
128
+ referenceId: id,
129
+ owner: 'cms',
130
+ kind: 'cms.content',
131
+ source: { workspaceId, sourceId: candidate.contentId },
132
+ portableKey: { scheme: 'cms-opaque-v1', value: candidate.contentKey },
133
+ required: true,
134
+ resolution: 'included',
135
+ });
136
+ }
137
+ }
138
+ externalReferences.push(...includedById.values());
139
+ externalReferences.sort(compareReferences);
140
+ warnings.sort((left, right) => [left.code, left.variantIds.join('\u0000')]
141
+ .join('\u0000')
142
+ .localeCompare([right.code, right.variantIds.join('\u0000')].join('\u0000')));
143
+ return {
144
+ dependencies: [],
145
+ externalReferences,
146
+ itemReferenceIds,
147
+ warnings,
148
+ };
149
+ }
150
+ //# sourceMappingURL=export-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-references.js","sourceRoot":"","sources":["../../src/services/export-references.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,9 @@
1
1
  import type { Connection } from '@salesforce/core';
2
+ import { type WorkspaceExportManifest } from '../contracts/workspace-export.js';
3
+ export type { WorkspaceExportManifest } from '../contracts/workspace-export.js';
2
4
  import { type JsonRequestOptions } from '../transport/json-request.js';
3
5
  export type WorkspaceExportWarning = {
4
- code: 'COUNT_MISMATCH' | 'DETAIL_FAILED' | 'DUPLICATE_VARIANTS' | 'OWNERSHIP_MISMATCH' | 'PREMATURE_EMPTY_PAGE' | 'UNSUPPORTED_WILDCARD';
6
+ code: 'COUNT_MISMATCH' | 'DETAIL_FAILED' | 'DUPLICATE_VARIANTS' | 'OWNERSHIP_MISMATCH' | 'PREMATURE_EMPTY_PAGE' | 'REFERENCE_UNRESOLVED' | 'REFERENCE_UNSUPPORTED' | 'UNSUPPORTED_WILDCARD';
5
7
  message: string;
6
8
  variantIds?: string[];
7
9
  };
@@ -9,31 +11,17 @@ export type WorkspaceExportEntry = {
9
11
  file: string;
10
12
  variantId: string;
11
13
  };
12
- export type WorkspaceExportManifest = {
13
- schemaVersion: 1;
14
- mode: 'experimental-best-effort';
15
- workspaceId: string;
16
- search: {
17
- contentSpaceOrFolderIds: [string];
18
- languages: ['All'];
19
- pageSize: 250;
20
- queryTerm: '*';
21
- };
22
- expectedCount: number;
23
- foundCount: number;
24
- exportedCount: number;
25
- pagesRequested: number;
26
- entries: WorkspaceExportEntry[];
27
- rejectedVariantIds: string[];
28
- failedVariantIds: string[];
29
- warnings: WorkspaceExportWarning[];
30
- };
31
14
  export type ExportWorkspaceResult = {
32
15
  destination: string;
33
16
  manifest: WorkspaceExportManifest;
17
+ manifestSha256: string;
18
+ };
19
+ export type ExportWorkspaceOptions = JsonRequestOptions & {
20
+ sourceOrgId?: string;
21
+ pluginVersion?: string;
22
+ generatedAt?: string;
34
23
  };
35
24
  type RequestConnection = Pick<Connection, 'request'>;
36
25
  export declare function safeWorkspaceDirectoryName(workspaceName: string): string;
37
26
  export declare function defaultWorkspaceDestination(workspaceName: string): string;
38
- export declare function exportWorkspace(connection: RequestConnection, workspaceId: string, destination: string, options?: JsonRequestOptions): Promise<ExportWorkspaceResult>;
39
- export {};
27
+ export declare function exportWorkspace(connection: RequestConnection, workspaceId: string, destination: string, options?: ExportWorkspaceOptions): Promise<ExportWorkspaceResult>;
@@ -1,6 +1,9 @@
1
- import { mkdir, open, rename, rm, stat } from 'node:fs/promises';
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat, mkdir, open, readdir, readFile, rename, rm, stat } from 'node:fs/promises';
2
3
  import path from 'node:path';
4
+ import { assertWorkspaceExportManifest, WORKSPACE_EXPORT_MANIFEST_CONTRACT, } from '../contracts/workspace-export.js';
3
5
  import { getSelectedOperation, requestJson, } from '../transport/json-request.js';
6
+ import { inventoryExportReferences } from './export-references.js';
4
7
  import { getVariant } from './read.js';
5
8
  const PAGE_SIZE = 250;
6
9
  const ABSOLUTE_PAGE_CAP = 1000;
@@ -39,6 +42,9 @@ function detailWorkspaceId(detail) {
39
42
  function jsonBytes(value) {
40
43
  return `${JSON.stringify(value, null, 2)}\n`;
41
44
  }
45
+ function sha256(bytes) {
46
+ return createHash('sha256').update(bytes).digest('hex');
47
+ }
42
48
  const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
43
49
  export function safeWorkspaceDirectoryName(workspaceName) {
44
50
  const replaced = [...workspaceName]
@@ -81,6 +87,34 @@ async function writeExclusive(path, value) {
81
87
  await handle.close();
82
88
  }
83
89
  }
90
+ async function assertRegularPackageFiles(root, expectedPaths) {
91
+ const found = new Set();
92
+ const visit = async (directory) => {
93
+ const entries = await readdir(directory, { withFileTypes: true });
94
+ for (const entry of entries) {
95
+ const absolute = path.join(directory, entry.name);
96
+ const relative = path.relative(root, absolute).split(path.sep).join('/');
97
+ const information = await lstat(absolute);
98
+ if (information.isSymbolicLink())
99
+ throw new Error(`Package entry is a symlink: ${relative}`);
100
+ if (information.isDirectory()) {
101
+ await visit(absolute);
102
+ }
103
+ else if (information.isFile()) {
104
+ if (relative !== 'manifest.json')
105
+ found.add(relative);
106
+ }
107
+ else {
108
+ throw new Error(`Package entry is not a regular file or directory: ${relative}`);
109
+ }
110
+ }
111
+ };
112
+ await visit(root);
113
+ if (found.size !== expectedPaths.size ||
114
+ [...found].some((itemPath) => !expectedPaths.has(itemPath))) {
115
+ throw new Error('Package regular files do not exactly match manifest items.');
116
+ }
117
+ }
84
118
  export async function exportWorkspace(connection, workspaceId, destination, options = {}) {
85
119
  if (!nonemptyString(workspaceId))
86
120
  throw new TypeError('workspaceId must be a nonempty string');
@@ -197,6 +231,22 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
197
231
  file: `items/${variantId}.json`,
198
232
  variantId,
199
233
  }));
234
+ const referenceInventory = inventoryExportReferences(workspaceId, details);
235
+ warnings.push(...referenceInventory.warnings);
236
+ const completeness = warnings.some(({ code }) => code !== 'UNSUPPORTED_WILDCARD') ||
237
+ rejected.size > 0 ||
238
+ failedIds.length > 0
239
+ ? 'partial'
240
+ : 'complete';
241
+ const manifestItems = entries.map((entry) => {
242
+ const referenceId = referenceInventory.itemReferenceIds.get(entry.variantId);
243
+ return {
244
+ path: entry.file,
245
+ sha256: sha256(jsonBytes(details.get(entry.variantId))),
246
+ kind: 'cms.content',
247
+ ...(referenceId === undefined ? {} : { referenceId }),
248
+ };
249
+ });
200
250
  const manifest = {
201
251
  schemaVersion: 1,
202
252
  mode: 'experimental-best-effort',
@@ -215,7 +265,21 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
215
265
  rejectedVariantIds: [...rejected].toSorted(),
216
266
  failedVariantIds: failedIds.toSorted(),
217
267
  warnings,
268
+ contract: WORKSPACE_EXPORT_MANIFEST_CONTRACT,
269
+ contractVersion: '1.0.0',
270
+ provenance: {
271
+ producer: 'sf-plugin-cms',
272
+ sourceOrgId: options.sourceOrgId ?? 'unknown-org',
273
+ sourceWorkspaceId: workspaceId,
274
+ pluginVersion: options.pluginVersion ?? '0.3.0',
275
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
276
+ },
277
+ completeness,
278
+ dependencies: referenceInventory.dependencies,
279
+ externalReferences: referenceInventory.externalReferences,
280
+ items: manifestItems,
218
281
  };
282
+ assertWorkspaceExportManifest(manifest);
219
283
  const parent = path.dirname(destination);
220
284
  await mkdir(parent, { recursive: true });
221
285
  const temporary = path.join(parent, `.${path.basename(destination)}.tmp-${process.pid}-${Date.now()}`);
@@ -225,6 +289,7 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
225
289
  for (const entry of entries) {
226
290
  await writeExclusive(path.join(temporary, entry.file), details.get(entry.variantId));
227
291
  }
292
+ await assertRegularPackageFiles(temporary, new Set(manifest.items.map(({ path }) => path)));
228
293
  await writeExclusive(path.join(temporary, 'manifest.json'), manifest);
229
294
  await rename(temporary, destination);
230
295
  }
@@ -232,6 +297,7 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
232
297
  await rm(temporary, { force: true, recursive: true });
233
298
  throw error;
234
299
  }
235
- return { destination, manifest };
300
+ const manifestSha256 = sha256(await readFile(path.join(destination, 'manifest.json')));
301
+ return { destination, manifest, manifestSha256 };
236
302
  }
237
303
  //# sourceMappingURL=export-workspace.js.map