Skip to content

Commit 9bd3f91

Browse files
author
naman-contentstack
committed
revert: asset localisation
1 parent 53caf2b commit 9bd3f91

4 files changed

Lines changed: 32 additions & 129 deletions

File tree

.talismanrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ fileignoreconfig:
2323
checksum: 22708ea1e27a48a5741426a8e17e5d8b243864d877066861bc275d82393002eb
2424
- filename: packages/contentstack-asset-management/src/export/assets.ts
2525
checksum: b169481a31393a9036fbe4d41429bfee3d0f321629f01a72089469ddf5e8826d
26+
- filename: packages/contentstack-asset-management/src/export/assets.ts
27+
checksum: 0a4e04bc91f65cb695a4ca0415dc042b64e6563f0b4a3b718cd9a0ac0d1d7fab
2628
version: '1.0'

packages/contentstack-asset-management/src/export/assets.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,9 @@ import { withRetry, RetryableHttpError, isRetryableStatus, parseRetryAfterMs } f
1313
import type { CustomPromiseHandler } from '../utils/cs-assets-api-adapter';
1414
import { PROCESS_NAMES, PROCESS_STATUS } from '../constants/index';
1515

16-
// `locale` is part of the storage key so multi-locale variants of the same uid are kept
17-
// as distinct records (each locale has its own binary) instead of collapsing to one.
18-
const ASSET_META_KEYS = ['uid', 'url', 'filename', 'file_name', 'parent_uid', 'locale'];
19-
20-
type AssetRecord = {
21-
uid?: string;
22-
_uid?: string;
23-
url?: string;
24-
filename?: string;
25-
file_name?: string;
26-
locale?: string;
27-
};
16+
const ASSET_META_KEYS = ['uid', 'url', 'filename', 'file_name', 'parent_uid'];
17+
18+
type AssetRecord = { uid?: string; _uid?: string; url?: string; filename?: string; file_name?: string };
2819

2920
/** Per-space export counts surfaced to the summary (assets = downloaded binaries; folders = entities). */
3021
export type SpaceExportCounts = { assets: number; folders: number };
@@ -56,9 +47,7 @@ export default class ExportAssets extends CSAssetsExportAdapter {
5647
const onPage = (items: unknown[]) => {
5748
if (items.length === 0) return;
5849
if (!fsWriter) fsWriter = this.createChunkedJsonWriter(assetsDir, 'assets.json', 'assets', ASSET_META_KEYS);
59-
// Composite key (uid + locale) keeps each localized variant — a plain uid key would let the
60-
// last locale overwrite the rest, silently dropping their binaries.
61-
fsWriter.writeIntoFile(items as Record<string, string>[], { mapKeyVal: true, keyName: ['uid', 'locale'] });
50+
fsWriter.writeIntoFile(items as Record<string, string>[], { mapKeyVal: true });
6251
totalStreamed += items.length;
6352
for (const asset of items as AssetRecord[]) if (this.isDownloadable(asset)) downloadableCount += 1;
6453
};
@@ -138,7 +127,7 @@ export default class ExportAssets extends CSAssetsExportAdapter {
138127
const label = rec.file_name ?? rec.filename ?? rec.uid ?? 'asset';
139128
this.tick(false, `asset: ${label}`, 'Asset chunk unreadable');
140129
log.error(
141-
`Asset ${rec.uid ?? '<unknown>'} (locale ${rec.locale ?? 'n/a'}) not downloaded — chunk unreadable for space ${spaceUid}`,
130+
`Asset ${rec.uid ?? '<unknown>'} not downloaded — chunk unreadable for space ${spaceUid}`,
142131
this.exportContext.context,
143132
);
144133
}
@@ -179,8 +168,7 @@ export default class ExportAssets extends CSAssetsExportAdapter {
179168
const body = response.body;
180169
if (!body) throw new Error('No response body');
181170
const nodeStream = Readable.fromWeb(body as Parameters<typeof Readable.fromWeb>[0]);
182-
// Locale-scoped path keeps each localized variant's binary distinct under the same uid.
183-
const assetFolderPath = asset.locale ? pResolve(filesDir, uid, asset.locale) : pResolve(filesDir, uid);
171+
const assetFolderPath = pResolve(filesDir, uid);
184172
await mkdir(assetFolderPath, { recursive: true });
185173
const filePath = pResolve(assetFolderPath, filename);
186174
await writeStreamToFile(nodeStream, filePath);

packages/contentstack-asset-management/src/import/assets.ts

Lines changed: 18 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ type AssetRecord = {
2424
parent_uid?: string;
2525
title?: string;
2626
description?: string;
27-
locale?: string;
2827
};
2928

3029
type UploadJob = {
@@ -195,10 +194,6 @@ export default class ImportAssets extends CSAssetsImportAdapter {
195194
let uploadFail = 0;
196195
let missingFiles = 0;
197196

198-
// Master uid (old → new). The first locale seen for a uid creates the asset; later locales of the
199-
// same uid are localized onto that new uid so multi-locale assets keep one asset with per-locale files.
200-
const uidToNewUid = new Map<string, string>();
201-
202197
await forEachChunkRecordsFromFs<AssetRecord>(
203198
assetFs,
204199
{
@@ -219,10 +214,7 @@ export default class ImportAssets extends CSAssetsImportAdapter {
219214
for (const asset of assetChunk) {
220215
const oldUid = asset.uid;
221216
const filename = asset.filename ?? asset.file_name ?? 'asset';
222-
// Locale-scoped path mirrors the export layout (files/<uid>/<locale>/<filename>).
223-
const filePath = asset.locale
224-
? pResolve(assetsDir, 'files', oldUid, asset.locale, filename)
225-
: pResolve(assetsDir, 'files', oldUid, filename);
217+
const filePath = pResolve(assetsDir, 'files', oldUid, filename);
226218

227219
if (!existsSync(filePath)) {
228220
missingFiles += 1;
@@ -243,62 +235,22 @@ export default class ImportAssets extends CSAssetsImportAdapter {
243235
this.importContext.context,
244236
);
245237

246-
// Group by old uid so a uid's locales upload as master-then-localize (sequential within a
247-
// group); different uids still upload concurrently.
248-
const jobsByUid = new Map<string, UploadJob[]>();
249-
for (const job of uploadJobs) {
250-
const group = jobsByUid.get(job.oldUid);
251-
if (group) group.push(job);
252-
else jobsByUid.set(job.oldUid, [job]);
253-
}
254-
255-
await runInBatches([...jobsByUid.values()], this.uploadAssetsBatchConcurrency, async (group) => {
256-
// If the master row's create fails, its remaining locales can't be localized — skip them.
257-
let masterFailed = false;
258-
for (const { asset, filePath, mappedParentUid, oldUid } of group) {
238+
await runInBatches(
239+
uploadJobs,
240+
this.uploadAssetsBatchConcurrency,
241+
async ({ asset, filePath, mappedParentUid, oldUid }) => {
259242
const filename = asset.filename ?? asset.file_name ?? 'asset';
260-
const masterNewUid = uidToNewUid.get(oldUid);
261-
// Master (first row for this uid) failed earlier → can't localize onto it; skip its locales.
262-
if (!masterNewUid && masterFailed) {
263-
uploadFail += 1;
264-
this.tick(false, `asset: ${filename}`, 'Skipped: master asset upload failed');
265-
log.error(
266-
`Skipped locale ${asset.locale ?? 'n/a'} of asset ${oldUid} — master upload failed`,
267-
this.importContext.context,
268-
);
269-
continue;
270-
}
271243
try {
272-
if (!masterNewUid) {
273-
// First locale for this uid → create the asset.
274-
const { asset: created } = await this.uploadAsset(newSpaceUid, filePath, {
275-
title: asset.title ?? filename,
276-
description: asset.description,
277-
parent_uid: mappedParentUid,
278-
});
279-
uidToNewUid.set(oldUid, created.uid);
280-
uidMap[oldUid] = created.uid;
281-
if (asset.url && created.url) urlMap[asset.url] = created.url;
282-
this.tick(true, `asset: ${filename}`, null);
283-
uploadOk += 1;
284-
log.debug(`Uploaded asset ${oldUid}${created.uid} (${filePath})`, this.importContext.context);
285-
} else {
286-
// Additional locale → localize onto the asset created from the master row.
287-
const { asset: localized } = await this.localizeAsset(
288-
newSpaceUid,
289-
masterNewUid,
290-
asset.locale as string,
291-
filePath,
292-
{ title: asset.title ?? filename, description: asset.description, parent_uid: mappedParentUid },
293-
);
294-
if (asset.url && localized.url) urlMap[asset.url] = localized.url;
295-
this.tick(true, `asset: ${filename} (${asset.locale})`, null);
296-
uploadOk += 1;
297-
log.debug(
298-
`Localized asset ${oldUid}${masterNewUid} for locale ${asset.locale} (${filePath})`,
299-
this.importContext.context,
300-
);
301-
}
244+
const { asset: created } = await this.uploadAsset(newSpaceUid, filePath, {
245+
title: asset.title ?? filename,
246+
description: asset.description,
247+
parent_uid: mappedParentUid,
248+
});
249+
uidMap[oldUid] = created.uid;
250+
if (asset.url && created.url) urlMap[asset.url] = created.url;
251+
this.tick(true, `asset: ${filename}`, null);
252+
uploadOk += 1;
253+
log.debug(`Uploaded asset ${oldUid}${created.uid} (${filePath})`, this.importContext.context);
302254
} catch (e) {
303255
uploadFail += 1;
304256
this.tick(
@@ -307,19 +259,12 @@ export default class ImportAssets extends CSAssetsImportAdapter {
307259
(e as Error)?.message ?? PROCESS_STATUS[PROCESS_NAMES.AM_IMPORT_ASSETS].FAILED,
308260
);
309261
log.error(
310-
`${
311-
masterNewUid
312-
? `Failed to localize asset ${oldUid} for locale ${asset.locale}`
313-
: `Failed to upload asset ${oldUid}`
314-
}: ${(e as Error)?.message ?? String(e)}`,
262+
`Failed to upload asset ${oldUid}: ${(e as Error)?.message ?? String(e)}`,
315263
this.importContext.context,
316264
);
317-
// Master create failed → skip this uid's remaining locale rows instead of re-attempting
318-
// them as new masters (which would duplicate the asset with the wrong default locale).
319-
if (!masterNewUid) masterFailed = true;
320265
}
321-
}
322-
});
266+
},
267+
);
323268
},
324269
);
325270

packages/contentstack-asset-management/src/utils/cs-assets-api-adapter.ts

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -636,38 +636,33 @@ export class CSAssetsAdapter implements ICSAssetsAdapter {
636636
}
637637
}
638638

639-
private async postMultipart<T>(
640-
path: string,
641-
form: FormData,
642-
extraHeaders: Record<string, string> = {},
643-
method: 'POST' | 'PUT' = 'POST',
644-
): Promise<T> {
639+
private async postMultipart<T>(path: string, form: FormData, extraHeaders: Record<string, string> = {}): Promise<T> {
645640
const baseUrl = this.config.baseURL?.replace(/\/$/, '') ?? '';
646641
const headers = await this.getPostHeaders(extraHeaders);
647-
log.debug(`${method} (multipart) ${path}`, this.config.context);
642+
log.debug(`POST (multipart) ${path}`, this.config.context);
648643

649644
try {
650645
const response = await fetch(`${baseUrl}${path}`, {
651-
method,
646+
method: 'POST',
652647
headers,
653648
body: form,
654649
});
655650
if (!response.ok) {
656651
const text = await response.text().catch(() => '');
657652
const bodySnippet = this.formatResponseBodyForError(text);
658653
throw new Error(
659-
`CS Assets API multipart ${method} failed: status ${response.status} path ${path}${
654+
`CS Assets API multipart POST failed: status ${response.status} path ${path}${
660655
bodySnippet ? `\nResponse: ${bodySnippet}` : ''
661656
}`,
662657
);
663658
}
664659
return response.json() as Promise<T>;
665660
} catch (error) {
666-
if (error instanceof Error && error.message.includes('CS Assets API multipart')) {
661+
if (error instanceof Error && error.message.includes('CS Assets API multipart POST failed')) {
667662
throw error;
668663
}
669664
throw new Error(
670-
`CS Assets API multipart ${method} failed: path ${path} - ${error instanceof Error ? error.message : String(error)}`,
665+
`CS Assets API multipart POST failed: path ${path} - ${error instanceof Error ? error.message : String(error)}`,
671666
);
672667
}
673668
}
@@ -718,33 +713,6 @@ export class CSAssetsAdapter implements ICSAssetsAdapter {
718713
);
719714
}
720715

721-
/**
722-
* PUT /api/spaces/{spaceUid}/assets/{assetUid}?locale={locale} — uploads a localized binary onto an
723-
* existing asset so multi-locale assets keep a distinct file per locale (master locale created via
724-
* {@link uploadAsset}; each additional locale is localized onto the same asset uid). Locale travels
725-
* as a query param (per CS Assets v4 "update Asset"); the file rides in the multipart body.
726-
*/
727-
async localizeAsset(
728-
spaceUid: string,
729-
assetUid: string,
730-
locale: string,
731-
filePath: string,
732-
metadata: CreateAssetMetadata,
733-
): Promise<{ asset: { uid: string; url: string } }> {
734-
const filename = basename(filePath);
735-
const fileBuffer = await readFile(filePath);
736-
const blob = new Blob([fileBuffer]);
737-
const form = new FormData();
738-
form.append('file', blob, filename);
739-
if (metadata.title) form.append('title', metadata.title);
740-
if (metadata.description) form.append('description', metadata.description);
741-
return this.postMultipart<{ asset: { uid: string; url: string } }>(
742-
`/api/spaces/${encodeURIComponent(spaceUid)}/assets/${encodeURIComponent(assetUid)}?locale=${encodeURIComponent(locale)}`,
743-
form,
744-
{ space_key: spaceUid },
745-
'PUT',
746-
);
747-
}
748716

749717
/**
750718
* POST /api/fields — creates a shared field.

0 commit comments

Comments
 (0)