sf-plugin-cms 0.1.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.
- package/README.md +143 -25
- package/lib/{commands/cms/command-base.d.ts → command-base.d.ts} +2 -1
- package/lib/{commands/cms/command-base.js → command-base.js} +3 -0
- package/lib/command-base.js.map +1 -0
- package/lib/commands/cms/export/workspace.d.ts +15 -7
- package/lib/commands/cms/export/workspace.js +114 -12
- package/lib/commands/cms/export/workspace.js.map +1 -1
- package/lib/commands/cms/get/channel.d.ts +1 -1
- package/lib/commands/cms/get/channel.js +1 -1
- package/lib/commands/cms/get/channel.js.map +1 -1
- package/lib/commands/cms/get/content.d.ts +1 -1
- package/lib/commands/cms/get/content.js +1 -1
- package/lib/commands/cms/get/content.js.map +1 -1
- package/lib/commands/cms/get/variant.d.ts +1 -1
- package/lib/commands/cms/get/variant.js +1 -1
- package/lib/commands/cms/get/variant.js.map +1 -1
- package/lib/commands/cms/get/workspace.d.ts +1 -1
- package/lib/commands/cms/get/workspace.js +1 -1
- package/lib/commands/cms/get/workspace.js.map +1 -1
- package/lib/commands/cms/import/workspace.d.ts +10 -6
- package/lib/commands/cms/import/workspace.js +127 -35
- package/lib/commands/cms/import/workspace.js.map +1 -1
- package/lib/commands/cms/info.d.ts +11 -10
- package/lib/commands/cms/info.js +123 -8
- package/lib/commands/cms/info.js.map +1 -1
- package/lib/commands/cms/list/channel.d.ts +1 -1
- package/lib/commands/cms/list/channel.js +1 -1
- package/lib/commands/cms/list/channel.js.map +1 -1
- package/lib/commands/cms/list/workspace.d.ts +1 -1
- package/lib/commands/cms/list/workspace.js +1 -1
- package/lib/commands/cms/list/workspace.js.map +1 -1
- package/lib/contracts/info.d.ts +43 -0
- package/lib/contracts/info.js +69 -0
- package/lib/contracts/info.js.map +1 -0
- package/lib/contracts/shared.d.ts +81 -0
- package/lib/contracts/shared.js +218 -0
- package/lib/contracts/shared.js.map +1 -0
- package/lib/contracts/workspace-export.d.ts +91 -0
- package/lib/contracts/workspace-export.js +324 -0
- package/lib/contracts/workspace-export.js.map +1 -0
- package/lib/contracts/workspace-import.d.ts +46 -0
- package/lib/contracts/workspace-import.js +108 -0
- package/lib/contracts/workspace-import.js.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/services/bulk-export-workspaces.d.ts +25 -0
- package/lib/services/bulk-export-workspaces.js +272 -0
- package/lib/services/bulk-export-workspaces.js.map +1 -0
- package/lib/services/export-references.d.ts +21 -0
- package/lib/services/export-references.js +150 -0
- package/lib/services/export-references.js.map +1 -0
- package/lib/services/export-workspace.d.ts +12 -22
- package/lib/services/export-workspace.js +91 -2
- package/lib/services/export-workspace.js.map +1 -1
- package/lib/services/import-workspace.d.ts +7 -3
- package/lib/services/import-workspace.js +224 -96
- package/lib/services/import-workspace.js.map +1 -1
- package/lib/services/resolve-workspace.d.ts +20 -0
- package/lib/services/resolve-workspace.js +86 -0
- package/lib/services/resolve-workspace.js.map +1 -0
- package/messages/cms.export.workspace.md +31 -5
- package/messages/cms.import.workspace.md +17 -7
- package/messages/cms.info.md +21 -0
- package/package.json +3 -2
- 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":"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"}
|
|
@@ -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,29 +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
|
-
export declare function
|
|
37
|
-
export
|
|
25
|
+
export declare function safeWorkspaceDirectoryName(workspaceName: string): string;
|
|
26
|
+
export declare function defaultWorkspaceDestination(workspaceName: string): string;
|
|
27
|
+
export declare function exportWorkspace(connection: RequestConnection, workspaceId: string, destination: string, options?: ExportWorkspaceOptions): Promise<ExportWorkspaceResult>;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
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,31 @@ 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
|
+
}
|
|
48
|
+
const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
|
|
49
|
+
export function safeWorkspaceDirectoryName(workspaceName) {
|
|
50
|
+
const replaced = [...workspaceName]
|
|
51
|
+
.map((character) => {
|
|
52
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
53
|
+
return codePoint <= 31 || codePoint === 127 || /[\\/<>:"|?*]/u.test(character)
|
|
54
|
+
? '_'
|
|
55
|
+
: character;
|
|
56
|
+
})
|
|
57
|
+
.join('');
|
|
58
|
+
const safeName = replaced.replaceAll(/[. ]+$/gu, '');
|
|
59
|
+
if (safeName.length === 0 ||
|
|
60
|
+
safeName === '.' ||
|
|
61
|
+
safeName === '..' ||
|
|
62
|
+
WINDOWS_RESERVED_NAME.test(safeName)) {
|
|
63
|
+
throw new Error(`Workspace name ${workspaceName} cannot be used as a portable directory name. Pass --output-dir explicitly.`);
|
|
64
|
+
}
|
|
65
|
+
return safeName;
|
|
66
|
+
}
|
|
67
|
+
export function defaultWorkspaceDestination(workspaceName) {
|
|
68
|
+
return path.join('.', 'cms', safeWorkspaceDirectoryName(workspaceName));
|
|
69
|
+
}
|
|
42
70
|
async function exists(path) {
|
|
43
71
|
try {
|
|
44
72
|
await stat(path);
|
|
@@ -59,6 +87,34 @@ async function writeExclusive(path, value) {
|
|
|
59
87
|
await handle.close();
|
|
60
88
|
}
|
|
61
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
|
+
}
|
|
62
118
|
export async function exportWorkspace(connection, workspaceId, destination, options = {}) {
|
|
63
119
|
if (!nonemptyString(workspaceId))
|
|
64
120
|
throw new TypeError('workspaceId must be a nonempty string');
|
|
@@ -175,6 +231,22 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
|
|
|
175
231
|
file: `items/${variantId}.json`,
|
|
176
232
|
variantId,
|
|
177
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
|
+
});
|
|
178
250
|
const manifest = {
|
|
179
251
|
schemaVersion: 1,
|
|
180
252
|
mode: 'experimental-best-effort',
|
|
@@ -193,8 +265,23 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
|
|
|
193
265
|
rejectedVariantIds: [...rejected].toSorted(),
|
|
194
266
|
failedVariantIds: failedIds.toSorted(),
|
|
195
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,
|
|
196
281
|
};
|
|
282
|
+
assertWorkspaceExportManifest(manifest);
|
|
197
283
|
const parent = path.dirname(destination);
|
|
284
|
+
await mkdir(parent, { recursive: true });
|
|
198
285
|
const temporary = path.join(parent, `.${path.basename(destination)}.tmp-${process.pid}-${Date.now()}`);
|
|
199
286
|
try {
|
|
200
287
|
await mkdir(temporary);
|
|
@@ -202,6 +289,7 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
|
|
|
202
289
|
for (const entry of entries) {
|
|
203
290
|
await writeExclusive(path.join(temporary, entry.file), details.get(entry.variantId));
|
|
204
291
|
}
|
|
292
|
+
await assertRegularPackageFiles(temporary, new Set(manifest.items.map(({ path }) => path)));
|
|
205
293
|
await writeExclusive(path.join(temporary, 'manifest.json'), manifest);
|
|
206
294
|
await rename(temporary, destination);
|
|
207
295
|
}
|
|
@@ -209,6 +297,7 @@ export async function exportWorkspace(connection, workspaceId, destination, opti
|
|
|
209
297
|
await rm(temporary, { force: true, recursive: true });
|
|
210
298
|
throw error;
|
|
211
299
|
}
|
|
212
|
-
|
|
300
|
+
const manifestSha256 = sha256(await readFile(path.join(destination, 'manifest.json')));
|
|
301
|
+
return { destination, manifest, manifestSha256 };
|
|
213
302
|
}
|
|
214
303
|
//# sourceMappingURL=export-workspace.js.map
|