sanity 5.3.0-next.14 → 5.3.0-next.15
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.
|
@@ -14,18 +14,19 @@ import { glob } from "tinyglobby";
|
|
|
14
14
|
import { debug as debug$1 } from "./_internal.js";
|
|
15
15
|
import { determineTargetMediaLibrary, MINIMUM_API_VERSION } from "./determineTargetMediaLibrary.js";
|
|
16
16
|
import readline from "node:readline";
|
|
17
|
-
async function
|
|
17
|
+
async function readNdjsonFile(ndjson) {
|
|
18
18
|
const lines = readline.createInterface({
|
|
19
19
|
input: ndjson
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return;
|
|
20
|
+
}), entries = [];
|
|
21
|
+
try {
|
|
22
|
+
for await (const line of lines) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
trimmed && entries.push(JSON.parse(trimmed));
|
|
26
25
|
}
|
|
26
|
+
} finally {
|
|
27
|
+
lines.close(), ndjson.destroy();
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
return entries;
|
|
29
30
|
}
|
|
30
31
|
const debug = debug$1.extend("importMedia"), DEFAULT_CONCURRENCY = 6, importAssetsAction = async (args, context) => {
|
|
31
32
|
const {
|
|
@@ -69,15 +70,18 @@ function importer(options) {
|
|
|
69
70
|
const fileCount = files.length + images.length;
|
|
70
71
|
if (fileCount === 0)
|
|
71
72
|
throw new Error("No assets to import");
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const aspectsDataPromise = aspectsNdjsonPath ? readNdjsonFile(createReadStream(aspectsNdjsonPath)) : Promise.resolve([]);
|
|
74
|
+
return from(aspectsDataPromise).pipe(mergeMap((aspectsData) => {
|
|
75
|
+
const context = {
|
|
76
|
+
...options,
|
|
77
|
+
workingPath,
|
|
78
|
+
aspectsData
|
|
79
|
+
};
|
|
80
|
+
return from(files).pipe(switchMap((file) => zip(of("file"), of(file))), mergeWith(from(images).pipe(switchMap((file) => zip(of("image"), of(file))))), fetchExistingAssets(context), uploadAsset(context), resolveAspectData(context), setAspects(context), map((asset) => ({
|
|
81
|
+
asset,
|
|
82
|
+
fileCount
|
|
83
|
+
})));
|
|
84
|
+
}));
|
|
81
85
|
}));
|
|
82
86
|
}
|
|
83
87
|
function resolveSource({
|
|
@@ -152,17 +156,19 @@ function fetchExistingAssets({
|
|
|
152
156
|
});
|
|
153
157
|
}
|
|
154
158
|
function resolveAspectData({
|
|
155
|
-
|
|
159
|
+
aspectsData
|
|
156
160
|
}) {
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
return map((resolvedAsset) => {
|
|
162
|
+
if (!aspectsData || aspectsData.length === 0)
|
|
163
|
+
return {
|
|
164
|
+
...resolvedAsset,
|
|
165
|
+
aspects: void 0
|
|
166
|
+
};
|
|
167
|
+
const aspectsFromImport = aspectsData.find((entry) => entry.filename === resolvedAsset.originalFilename);
|
|
168
|
+
return {
|
|
160
169
|
...resolvedAsset,
|
|
161
170
|
aspects: aspectsFromImport?.aspects
|
|
162
|
-
}
|
|
163
|
-
...resolvedAsset,
|
|
164
|
-
aspects: void 0
|
|
165
|
-
});
|
|
171
|
+
};
|
|
166
172
|
});
|
|
167
173
|
}
|
|
168
174
|
function setAspects({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"importAssetsAction.js","sources":["../../src/_internal/cli/actions/media/lib/findNdjsonEntry.ts","../../src/_internal/cli/actions/media/importAssetsAction.ts"],"sourcesContent":["import readline from 'node:readline'\nimport {Readable} from 'node:stream'\n\n/**\n * Find the first matching entry in the provided NDJSON stream.\n *\n * @internal\n */\nexport async function* findNdjsonEntry<Type>(\n ndjson: Readable,\n matcher: (line: Type) => boolean,\n): AsyncGenerator<Type | undefined> {\n const lines = readline.createInterface({\n input: ndjson,\n })\n\n for await (const line of lines) {\n const parsed = JSON.parse(line.trim())\n if (matcher(parsed)) {\n yield parsed\n lines.close()\n return\n }\n }\n\n yield undefined\n}\n","import {createHash} from 'node:crypto'\nimport {createReadStream, type ReadStream} from 'node:fs'\nimport fs, {mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {text} from 'node:stream/consumers'\nimport {pipeline} from 'node:stream/promises'\n\nimport {\n type CliCommandAction,\n type CliCommandContext,\n type CliOutputter,\n type SanityClient,\n} from '@sanity/cli'\nimport {type FileAsset, type ImageAsset, type SanityDocument} from '@sanity/types'\nimport {type Chalk} from 'chalk'\nimport gunzipMaybe from 'gunzip-maybe'\nimport isTar from 'is-tar'\n// @ts-expect-error `peek-stream` module currently untyped\nimport peek from 'peek-stream'\nimport {\n catchError,\n EMPTY,\n filter,\n from,\n map,\n mergeMap,\n mergeWith,\n type Observable,\n of,\n type OperatorFunction,\n pipe,\n scan,\n switchMap,\n tap,\n zip,\n} from 'rxjs'\nimport tar from 'tar-fs'\nimport {glob} from 'tinyglobby'\n\nimport {debug as baseDebug} from '../../debug'\nimport {MINIMUM_API_VERSION} from './constants'\nimport {determineTargetMediaLibrary} from './lib/determineTargetMediaLibrary'\nimport {findNdjsonEntry} from './lib/findNdjsonEntry'\n\ninterface ImportAssetsFlags {\n 'media-library-id'?: string\n 'replace-aspects'?: boolean\n}\n\nconst debug = baseDebug.extend('importMedia')\n\nconst DEFAULT_CONCURRENCY = 6\n\ninterface MediaLibraryUploadResult {\n asset: SanityDocument & {\n _type: 'sanity.asset'\n assetType: ImageAsset['_type'] | FileAsset['_type']\n aspects: unknown\n }\n assetInstance: ImageAsset | FileAsset\n}\n\ninterface MediaLibraryUploadResponse {\n type: 'response'\n body: MediaLibraryUploadResult\n}\n\ninterface ResolvedAsset {\n /**\n * The ids of the `sanity.asset` documents that currently refer to the asset.\n *\n * These documents contain aspects, and reference an asset instance document.\n */\n assetIds: string[]\n /**\n * The original filename of the asset as it appears in the import source.\n *\n * Note: Currently includes `images/` or `files/` prefix.\n */\n originalFilename: string\n sha1Hash: string\n isExistingAsset: boolean\n}\n\n/**\n * @internal\n */\nexport type AssetWithAspects<Asset extends ResolvedAsset = ResolvedAsset> = Asset & {\n aspects: unknown | undefined\n}\n\ninterface State {\n /**\n * The count of input files.\n */\n fileCount: number\n /**\n * The last asset processed.\n */\n asset: AssetWithAspects\n}\n\ninterface Options {\n sourcePath: string\n client: SanityClient\n replaceAspects: boolean\n chalk: Chalk\n spinner: ReturnType<CliCommandContext['output']['spinner']>\n output: CliOutputter\n}\n\ninterface Context extends Options {\n workingPath: string\n ndjson: () => ReadStream | null\n}\n\n// TODO: Order assets lexicographically before processing, allow resumable import\n// TODO: Granularly report upload progress of each asset (especially useful for large assets).\nconst importAssetsAction: CliCommandAction<ImportAssetsFlags> = async (args, context) => {\n const {output, apiClient, chalk} = context\n const [importSourcePath] = args.argsWithoutOptions\n const replaceAspects = args.extOptions['replace-aspects'] ?? false\n\n const mediaLibraryId =\n args.extOptions['media-library-id'] ?? (await determineTargetMediaLibrary(context))\n\n const client = apiClient().withConfig({\n 'apiVersion': MINIMUM_API_VERSION,\n 'requestTagPrefix': 'sanity.mediaLibraryCli.import',\n '~experimental_resource': {\n type: 'media-library',\n id: mediaLibraryId,\n },\n 'perspective': 'drafts',\n })\n\n output.print()\n output.print(`Importing to media library: ${chalk.bold(mediaLibraryId)}`)\n output.print(`Importing from path: ${chalk.bold(importSourcePath)}`)\n output.print()\n\n const spinner = output.spinner('Beginning import…').start()\n\n importer({\n client,\n sourcePath: importSourcePath,\n replaceAspects,\n chalk,\n spinner,\n output,\n })\n .pipe(\n reportResult({\n chalk,\n spinner,\n }),\n )\n .subscribe({\n error: (error) => {\n spinner.stop()\n output.error(error)\n },\n })\n}\n\nexport default importAssetsAction\n\nexport function importer(options: Options): Observable<State> {\n return resolveSource(options).pipe(\n mergeMap(({files, images, aspectsNdjsonPath, workingPath}) => {\n const fileCount = files.length + images.length\n\n if (fileCount === 0) {\n throw new Error('No assets to import')\n }\n\n const context: Context = {\n ...options,\n workingPath,\n ndjson: () => (aspectsNdjsonPath ? createReadStream(aspectsNdjsonPath) : null),\n }\n\n return from(files).pipe(\n switchMap((file) => zip(of<'file'>('file'), of(file))),\n mergeWith(from(images).pipe(switchMap((file) => zip(of<'image'>('image'), of(file))))),\n fetchExistingAssets(context),\n uploadAsset(context),\n resolveAspectData(context),\n setAspects(context),\n map((asset) => ({\n asset,\n fileCount,\n })),\n )\n }),\n )\n}\n\n/**\n * @internal\n */\nexport function resolveSource({\n sourcePath,\n chalk,\n}: Pick<Context, 'sourcePath' | 'chalk'>): Observable<{\n files: string[]\n images: string[]\n aspectsNdjsonPath: string\n workingPath: string\n}> {\n return from(fs.stat(sourcePath)).pipe(\n switchMap((stats) => {\n return stats.isDirectory()\n ? of(sourcePath)\n : from(mkdtemp(path.join(tmpdir(), 'sanity-media-library-import'))).pipe(\n switchMap((tempPath) => {\n return from(\n pipeline(createReadStream(sourcePath), gunzipMaybe(), untarMaybe(tempPath)),\n ).pipe(map(() => tempPath))\n }),\n )\n }),\n switchMap((importSourcePath) => {\n return from(\n glob(['**/data.ndjson'], {\n cwd: importSourcePath,\n deep: 2,\n absolute: true,\n }),\n ).pipe(\n map(([aspectsNdjsonPath]) => ({\n aspectsNdjsonPath,\n importSourcePath,\n workingPath:\n typeof aspectsNdjsonPath === 'undefined'\n ? importSourcePath\n : path.dirname(aspectsNdjsonPath),\n })),\n )\n }),\n tap(({aspectsNdjsonPath, importSourcePath}) => {\n if (typeof aspectsNdjsonPath === 'undefined') {\n debug(\n `[No data.ndjson file] No predefined aspect data will be imported from ${importSourcePath}`,\n )\n } else {\n debug(`[Found NDJSON file] ${aspectsNdjsonPath}`)\n }\n }),\n switchMap(({aspectsNdjsonPath, workingPath}) => {\n return from(\n Promise.all([\n glob(['files/*'], {\n cwd: workingPath,\n }),\n glob(['images/*'], {\n cwd: workingPath,\n }),\n ]),\n ).pipe(\n map(([files, images]) => ({\n files,\n images,\n aspectsNdjsonPath,\n workingPath,\n })),\n )\n }),\n )\n}\n\n/**\n * Untar the stream if its contents appear to be tarred.\n *\n * @internal\n */\nfunction untarMaybe(outputPath: string) {\n // @ts-expect-error `peek-stream` module currently untyped\n return peek({newline: false, maxBuffer: 300}, (data, swap) => {\n if (isTar(data)) {\n return swap(null, tar.extract(outputPath))\n }\n\n return swap(null)\n })\n}\n\n/**\n * Fetch the ids of all asset documents that reference the input asset.\n * The input asset is identified by its SHA-1 hash.\n *\n * @internal\n */\nfunction fetchAssetsByHash({\n client,\n type,\n}: {\n client: SanityClient\n type: 'image' | 'file'\n}): OperatorFunction<string, [hash: string, assetIds: string[]]> {\n return switchMap((hash) =>\n client.observable\n .fetch<string[]>(\n `*[\n _type == \"sanity.asset\" &&\n currentVersion._ref == *[\n _type == $type &&\n sha1hash == $hash\n ][0]._id\n ]._id`,\n {\n type: ['sanity', `${type}Asset`].join('.'),\n hash,\n },\n {\n tag: 'asset.getId',\n },\n )\n .pipe(switchMap((assetIds) => zip(of(hash), of(assetIds)))),\n )\n}\n\nfunction fetchExistingAssets({\n client,\n workingPath,\n}: Context): OperatorFunction<\n [type: 'image' | 'file', asset: string],\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string]\n> {\n return mergeMap(([type, asset]) => {\n const createSha1Hash = createHash('sha1')\n\n const sha1hash = text(\n createReadStream(path.join(workingPath, asset)).pipe(createSha1Hash).setEncoding('hex'),\n )\n\n return from(sha1hash).pipe(\n tap((hash) => debug(`[Asset ${asset}] Checking for ${type} asset with hash ${hash}`)),\n fetchAssetsByHash({client, type}),\n map<\n [string, string[]],\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string]\n >(([hash, assetIds]) => {\n if (assetIds.length === 0) {\n return [type, asset, hash]\n }\n\n return {\n originalFilename: asset,\n sha1Hash: hash,\n assetIds,\n isExistingAsset: true,\n }\n }),\n )\n })\n}\n\n/**\n * Find the first matching entry in the provided NDJSON stream and attach it to the asset object.\n *\n * @internal\n */\nfunction resolveAspectData({ndjson}: Context): OperatorFunction<ResolvedAsset, AssetWithAspects> {\n return mergeMap((resolvedAsset) => {\n const ndjsonStream = ndjson()\n\n // If no ndjson file exists, return asset with undefined aspects\n if (!ndjsonStream) {\n return of({\n ...resolvedAsset,\n aspects: undefined,\n })\n }\n\n return from(\n findNdjsonEntry<{aspects: unknown}>(\n ndjsonStream,\n (line) =>\n typeof line === 'object' &&\n line !== null &&\n 'filename' in line &&\n line.filename === resolvedAsset.originalFilename,\n ),\n ).pipe(\n map((aspectsFromImport) => ({\n ...resolvedAsset,\n aspects: aspectsFromImport?.aspects,\n })),\n )\n })\n}\n\n// TODO: Batch mutations to reduce HTTP request count.\nexport function setAspects({\n client,\n replaceAspects,\n}: Pick<Context, 'client' | 'replaceAspects'>): OperatorFunction<\n AssetWithAspects,\n AssetWithAspects\n> {\n return mergeMap((asset) => {\n const {assetIds, isExistingAsset, aspects} = asset\n\n if (isExistingAsset && !replaceAspects) {\n debug(`[Asset ${asset.originalFilename}] Skipping replacement of existing aspects`)\n return of(asset)\n }\n\n if (typeof aspects === 'undefined') {\n debug(`[Asset ${asset.originalFilename}] No aspects to import`)\n return of(asset)\n }\n\n const transaction = assetIds.reduce(\n (previous, assetId) => previous.patch(assetId, {set: {aspects}}),\n client.observable.transaction(),\n )\n\n debug(\n `[Asset ${asset.originalFilename}] Setting aspects on asset documents ${JSON.stringify(assetIds)}`,\n )\n\n return transaction\n .commit({\n visibility: 'async',\n tag: 'asset.setAspects',\n })\n .pipe(map(() => asset))\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction uploadAsset({\n workingPath,\n client,\n}: Context): OperatorFunction<\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string],\n ResolvedAsset\n> {\n return mergeMap((maybeResolvedAsset) => {\n if ('assetIds' in maybeResolvedAsset) {\n debug(\n `[Asset ${maybeResolvedAsset.originalFilename}] Skipping upload of existing asset with hash ${maybeResolvedAsset.sha1Hash}`,\n )\n return of(maybeResolvedAsset)\n }\n\n const [type, asset, hash] = maybeResolvedAsset\n debug(`[Asset ${asset}] Uploading new asset`)\n\n return client.observable.assets\n .upload(type, createReadStream(path.join(workingPath, asset)), {\n tag: 'asset.upload',\n })\n .pipe(\n catchError((error) => {\n // An asset matching the hash was not found during previous steps, but appears to exist upon upload.\n //\n // This may occur if:\n // - The asset was uploaded by another client since the check was performed.\n // - The asset instance document exists, but is not referenced by any asset document.\n if (error.statusCode === 409) {\n debug(`[Asset ${asset}] Cannot overwrite existing ${type} asset with hash ${hash}`)\n return EMPTY\n }\n return EMPTY\n }),\n filter((response) => response.type === 'response'),\n tap(() => debug(`[Asset ${asset}] Finished uploading new asset`)),\n // TODO: The `client.assets.upload` method should return `MediaLibraryUploadResponse` when operating on Media Library resources. When that occurs, this type assertion can be removed.\n map((response) => (response as unknown as MediaLibraryUploadResponse).body),\n map<MediaLibraryUploadResult, ResolvedAsset>((result) => ({\n assetIds: [result.asset._id],\n originalFilename: asset,\n sha1Hash: hash,\n isExistingAsset: false,\n })),\n )\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction reportResult({\n chalk,\n spinner,\n}: Pick<Context, 'chalk' | 'spinner'>): OperatorFunction<State, [number, State | undefined]> {\n let previousState: State | undefined\n\n return pipe(\n scan<State, [number, State | undefined]>(\n (processedAssetsCount, state) => [processedAssetsCount[0] + 1, state],\n [0, undefined],\n ),\n tap({\n next: ([processedAssetsCount, state]) => {\n previousState = state\n spinner.text = `${processedAssetsCount} of ${state?.fileCount} assets imported ${chalk.dim(state?.asset.originalFilename)}`\n },\n complete: () => spinner.succeed(`Imported ${previousState?.fileCount} assets`),\n }),\n )\n}\n"],"names":["findNdjsonEntry","ndjson","matcher","lines","readline","createInterface","input","line","parsed","JSON","parse","trim","close","undefined","debug","baseDebug","extend","DEFAULT_CONCURRENCY","importAssetsAction","args","context","output","apiClient","chalk","importSourcePath","argsWithoutOptions","replaceAspects","extOptions","mediaLibraryId","determineTargetMediaLibrary","client","withConfig","MINIMUM_API_VERSION","type","id","print","bold","spinner","start","importer","sourcePath","pipe","reportResult","subscribe","error","stop","options","resolveSource","mergeMap","files","images","aspectsNdjsonPath","workingPath","fileCount","length","Error","createReadStream","from","switchMap","file","zip","of","mergeWith","fetchExistingAssets","uploadAsset","resolveAspectData","setAspects","map","asset","fs","stat","stats","isDirectory","mkdtemp","path","join","tmpdir","tempPath","pipeline","gunzipMaybe","untarMaybe","glob","cwd","deep","absolute","dirname","tap","Promise","all","outputPath","peek","newline","maxBuffer","data","swap","isTar","tar","extract","fetchAssetsByHash","hash","observable","fetch","tag","assetIds","createSha1Hash","createHash","sha1hash","text","setEncoding","originalFilename","sha1Hash","isExistingAsset","resolvedAsset","ndjsonStream","filename","aspectsFromImport","aspects","transaction","reduce","previous","assetId","patch","set","stringify","commit","visibility","maybeResolvedAsset","assets","upload","catchError","statusCode","EMPTY","filter","response","body","result","_id","previousState","scan","processedAssetsCount","state","next","dim","complete","succeed"],"mappings":";;;;;;;;;;;;;;;;AAQA,gBAAuBA,gBACrBC,QACAC,SACkC;AAClC,QAAMC,QAAQC,SAASC,gBAAgB;AAAA,IACrCC,OAAOL;AAAAA,EAAAA,CACR;AAED,mBAAiBM,QAAQJ,OAAO;AAC9B,UAAMK,SAASC,KAAKC,MAAMH,KAAKI,MAAM;AACrC,QAAIT,QAAQM,MAAM,GAAG;AACnB,YAAMA,QACNL,MAAMS,MAAAA;AACN;AAAA,IACF;AAAA,EACF;AAEA,QAAMC;AACR;ACwBA,MAAMC,QAAQC,QAAUC,OAAO,aAAa,GAEtCC,sBAAsB,GAmEtBC,qBAA0D,OAAOC,MAAMC,YAAY;AACvF,QAAM;AAAA,IAACC;AAAAA,IAAQC;AAAAA,IAAWC;AAAAA,EAAAA,IAASH,SAC7B,CAACI,gBAAgB,IAAIL,KAAKM,oBAC1BC,iBAAiBP,KAAKQ,WAAW,iBAAiB,KAAK,IAEvDC,iBACJT,KAAKQ,WAAW,kBAAkB,KAAM,MAAME,4BAA4BT,OAAO,GAE7EU,SAASR,UAAAA,EAAYS,WAAW;AAAA,IACpC,YAAcC;AAAAA,IACd,kBAAoB;AAAA,IACpB,0BAA0B;AAAA,MACxBC,MAAM;AAAA,MACNC,IAAIN;AAAAA,IAAAA;AAAAA,IAEN,aAAe;AAAA,EAAA,CAChB;AAEDP,SAAOc,SACPd,OAAOc,MAAM,+BAA+BZ,MAAMa,KAAKR,cAAc,CAAC,EAAE,GACxEP,OAAOc,MAAM,wBAAwBZ,MAAMa,KAAKZ,gBAAgB,CAAC,EAAE,GACnEH,OAAOc,MAAAA;AAEP,QAAME,UAAUhB,OAAOgB,QAAQ,wBAAmB,EAAEC,MAAAA;AAEpDC,WAAS;AAAA,IACPT;AAAAA,IACAU,YAAYhB;AAAAA,IACZE;AAAAA,IACAH;AAAAA,IACAc;AAAAA,IACAhB;AAAAA,EAAAA,CACD,EACEoB,KACCC,aAAa;AAAA,IACXnB;AAAAA,IACAc;AAAAA,EAAAA,CACD,CACH,EACCM,UAAU;AAAA,IACTC,OAAQA,CAAAA,UAAU;AAChBP,cAAQQ,KAAAA,GACRxB,OAAOuB,MAAMA,KAAK;AAAA,IACpB;AAAA,EAAA,CACD;AACL;AAIO,SAASL,SAASO,SAAqC;AAC5D,SAAOC,cAAcD,OAAO,EAAEL,KAC5BO,SAAS,CAAC;AAAA,IAACC;AAAAA,IAAOC;AAAAA,IAAQC;AAAAA,IAAmBC;AAAAA,EAAAA,MAAiB;AAC5D,UAAMC,YAAYJ,MAAMK,SAASJ,OAAOI;AAExC,QAAID,cAAc;AAChB,YAAM,IAAIE,MAAM,qBAAqB;AAGvC,UAAMnC,UAAmB;AAAA,MACvB,GAAG0B;AAAAA,MACHM;AAAAA,MACAnD,QAAQA,MAAOkD,oBAAoBK,iBAAiBL,iBAAiB,IAAI;AAAA,IAAA;AAG3E,WAAOM,KAAKR,KAAK,EAAER,KACjBiB,UAAWC,CAAAA,SAASC,IAAIC,GAAW,MAAM,GAAGA,GAAGF,IAAI,CAAC,CAAC,GACrDG,UAAUL,KAAKP,MAAM,EAAET,KAAKiB,UAAWC,CAAAA,SAASC,IAAIC,GAAY,OAAO,GAAGA,GAAGF,IAAI,CAAC,CAAC,CAAC,CAAC,GACrFI,oBAAoB3C,OAAO,GAC3B4C,YAAY5C,OAAO,GACnB6C,kBAAkB7C,OAAO,GACzB8C,WAAW9C,OAAO,GAClB+C,IAAKC,CAAAA,WAAW;AAAA,MACdA;AAAAA,MACAf;AAAAA,IAAAA,EACA,CACJ;AAAA,EACF,CAAC,CACH;AACF;AAKO,SAASN,cAAc;AAAA,EAC5BP;AAAAA,EACAjB;AACqC,GAKpC;AACD,SAAOkC,KAAKY,GAAGC,KAAK9B,UAAU,CAAC,EAAEC,KAC/BiB,UAAWa,CAAAA,UACFA,MAAMC,YAAAA,IACTX,GAAGrB,UAAU,IACbiB,KAAKgB,QAAQC,KAAKC,KAAKC,UAAU,6BAA6B,CAAC,CAAC,EAAEnC,KAChEiB,UAAWmB,cACFpB,KACLqB,SAAStB,iBAAiBhB,UAAU,GAAGuC,YAAAA,GAAeC,WAAWH,QAAQ,CAAC,CAC5E,EAAEpC,KAAK0B,IAAI,MAAMU,QAAQ,CAAC,CAC3B,CACH,CACL,GACDnB,UAAWlC,CAAAA,qBACFiC,KACLwB,KAAK,CAAC,gBAAgB,GAAG;AAAA,IACvBC,KAAK1D;AAAAA,IACL2D,MAAM;AAAA,IACNC,UAAU;AAAA,EAAA,CACX,CACH,EAAE3C,KACA0B,IAAI,CAAC,CAAChB,iBAAiB,OAAO;AAAA,IAC5BA;AAAAA,IACA3B;AAAAA,IACA4B,aACE,OAAOD,oBAAsB,MACzB3B,mBACAkD,KAAKW,QAAQlC,iBAAiB;AAAA,EAAA,EACpC,CACJ,CACD,GACDmC,IAAI,CAAC;AAAA,IAACnC;AAAAA,IAAmB3B;AAAAA,EAAAA,MAAsB;AAE3CV,UADE,OAAOqC,oBAAsB,MAE7B,yEAAyE3B,gBAAgB,KAGrF,uBAAuB2B,iBAAiB,EAF9C;AAAA,EAIJ,CAAC,GACDO,UAAU,CAAC;AAAA,IAACP;AAAAA,IAAmBC;AAAAA,EAAAA,MACtBK,KACL8B,QAAQC,IAAI,CACVP,KAAK,CAAC,SAAS,GAAG;AAAA,IAChBC,KAAK9B;AAAAA,EAAAA,CACN,GACD6B,KAAK,CAAC,UAAU,GAAG;AAAA,IACjBC,KAAK9B;AAAAA,EAAAA,CACN,CAAC,CACH,CACH,EAAEX,KACA0B,IAAI,CAAC,CAAClB,OAAOC,MAAM,OAAO;AAAA,IACxBD;AAAAA,IACAC;AAAAA,IACAC;AAAAA,IACAC;AAAAA,EAAAA,EACA,CACJ,CACD,CACH;AACF;AAOA,SAAS4B,WAAWS,YAAoB;AAEtC,SAAOC,KAAK;AAAA,IAACC,SAAS;AAAA,IAAOC,WAAW;AAAA,EAAA,GAAM,CAACC,MAAMC,SAC/CC,MAAMF,IAAI,IACLC,KAAK,MAAME,IAAIC,QAAQR,UAAU,CAAC,IAGpCK,KAAK,IAAI,CACjB;AACH;AAQA,SAASI,kBAAkB;AAAA,EACzBpE;AAAAA,EACAG;AAIF,GAAiE;AAC/D,SAAOyB,UAAWyC,CAAAA,SAChBrE,OAAOsE,WACJC,MACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAOA;AAAA,IACEpE,MAAM,CAAC,UAAU,GAAGA,IAAI,OAAO,EAAE0C,KAAK,GAAG;AAAA,IACzCwB;AAAAA,EAAAA,GAEF;AAAA,IACEG,KAAK;AAAA,EAAA,CAET,EACC7D,KAAKiB,UAAW6C,cAAa3C,IAAIC,GAAGsC,IAAI,GAAGtC,GAAG0C,QAAQ,CAAC,CAAC,CAAC,CAC9D;AACF;AAEA,SAASxC,oBAAoB;AAAA,EAC3BjC;AAAAA,EACAsB;AACO,GAGP;AACA,SAAOJ,SAAS,CAAC,CAACf,MAAMmC,KAAK,MAAM;AACjC,UAAMoC,iBAAiBC,WAAW,MAAM,GAElCC,WAAWC,KACfnD,iBAAiBkB,KAAKC,KAAKvB,aAAagB,KAAK,CAAC,EAAE3B,KAAK+D,cAAc,EAAEI,YAAY,KAAK,CACxF;AAEA,WAAOnD,KAAKiD,QAAQ,EAAEjE,KACpB6C,IAAKa,UAASrF,MAAM,UAAUsD,KAAK,kBAAkBnC,IAAI,oBAAoBkE,IAAI,EAAE,CAAC,GACpFD,kBAAkB;AAAA,MAACpE;AAAAA,MAAQG;AAAAA,IAAAA,CAAK,GAChCkC,IAGE,CAAC,CAACgC,MAAMI,QAAQ,MACZA,SAASjD,WAAW,IACf,CAACrB,MAAMmC,OAAO+B,IAAI,IAGpB;AAAA,MACLU,kBAAkBzC;AAAAA,MAClB0C,UAAUX;AAAAA,MACVI;AAAAA,MACAQ,iBAAiB;AAAA,IAAA,CAEpB,CACH;AAAA,EACF,CAAC;AACH;AAOA,SAAS9C,kBAAkB;AAAA,EAAChE;AAAe,GAAsD;AAC/F,SAAO+C,SAAUgE,CAAAA,kBAAkB;AACjC,UAAMC,eAAehH,OAAAA;AAGrB,WAAKgH,eAOExD,KACLzD,gBACEiH,cACC1G,UACC,OAAOA,QAAS,YAChBA,SAAS,QACT,cAAcA,QACdA,KAAK2G,aAAaF,cAAcH,gBACpC,CACF,EAAEpE,KACA0B,IAAKgD,CAAAA,uBAAuB;AAAA,MAC1B,GAAGH;AAAAA,MACHI,SAASD,mBAAmBC;AAAAA,IAAAA,EAC5B,CACJ,IApBSvD,GAAG;AAAA,MACR,GAAGmD;AAAAA,MACHI,SAASvG;AAAAA,IAAAA,CACV;AAAA,EAkBL,CAAC;AACH;AAGO,SAASqD,WAAW;AAAA,EACzBpC;AAAAA,EACAJ;AAC0C,GAG1C;AACA,SAAOsB,SAAUoB,CAAAA,UAAU;AACzB,UAAM;AAAA,MAACmC;AAAAA,MAAUQ;AAAAA,MAAiBK;AAAAA,IAAAA,IAAWhD;AAE7C,QAAI2C,mBAAmB,CAACrF;AACtBZ,aAAAA,MAAM,UAAUsD,MAAMyC,gBAAgB,4CAA4C,GAC3EhD,GAAGO,KAAK;AAGjB,QAAI,OAAOgD,UAAY;AACrBtG,aAAAA,MAAM,UAAUsD,MAAMyC,gBAAgB,wBAAwB,GACvDhD,GAAGO,KAAK;AAGjB,UAAMiD,cAAcd,SAASe,OAC3B,CAACC,UAAUC,YAAYD,SAASE,MAAMD,SAAS;AAAA,MAACE,KAAK;AAAA,QAACN;AAAAA,MAAAA;AAAAA,IAAO,CAAE,GAC/DtF,OAAOsE,WAAWiB,aACpB;AAEAvG,WAAAA,MACE,UAAUsD,MAAMyC,gBAAgB,wCAAwCpG,KAAKkH,UAAUpB,QAAQ,CAAC,EAClG,GAEOc,YACJO,OAAO;AAAA,MACNC,YAAY;AAAA,MACZvB,KAAK;AAAA,IAAA,CACN,EACA7D,KAAK0B,IAAI,MAAMC,KAAK,CAAC;AAAA,EAC1B,GAAGnD,mBAAmB;AACxB;AAEA,SAAS+C,YAAY;AAAA,EACnBZ;AAAAA,EACAtB;AACO,GAGP;AACA,SAAOkB,SAAU8E,CAAAA,uBAAuB;AACtC,QAAI,cAAcA;AAChBhH,aAAAA,MACE,UAAUgH,mBAAmBjB,gBAAgB,iDAAiDiB,mBAAmBhB,QAAQ,EAC3H,GACOjD,GAAGiE,kBAAkB;AAG9B,UAAM,CAAC7F,MAAMmC,OAAO+B,IAAI,IAAI2B;AAC5BhH,WAAAA,MAAM,UAAUsD,KAAK,uBAAuB,GAErCtC,OAAOsE,WAAW2B,OACtBC,OAAO/F,MAAMuB,iBAAiBkB,KAAKC,KAAKvB,aAAagB,KAAK,CAAC,GAAG;AAAA,MAC7DkC,KAAK;AAAA,IAAA,CACN,EACA7D;AAAAA,MACCwF,WAAYrF,CAAAA,UAMNA,MAAMsF,eAAe,OACvBpH,MAAM,UAAUsD,KAAK,+BAA+BnC,IAAI,oBAAoBkE,IAAI,EAAE,GAC3EgC,SAEFA,KACR;AAAA,MACDC,OAAQC,CAAAA,aAAaA,SAASpG,SAAS,UAAU;AAAA,MACjDqD,IAAI,MAAMxE,MAAM,UAAUsD,KAAK,gCAAgC,CAAC;AAAA;AAAA,MAEhED,IAAKkE,CAAAA,aAAcA,SAAmDC,IAAI;AAAA,MAC1EnE,IAA8CoE,CAAAA,YAAY;AAAA,QACxDhC,UAAU,CAACgC,OAAOnE,MAAMoE,GAAG;AAAA,QAC3B3B,kBAAkBzC;AAAAA,QAClB0C,UAAUX;AAAAA,QACVY,iBAAiB;AAAA,MAAA,EACjB;AAAA,IAAA;AAAA,EAER,GAAG9F,mBAAmB;AACxB;AAEA,SAASyB,aAAa;AAAA,EACpBnB;AAAAA,EACAc;AACkC,GAAyD;AAC3F,MAAIoG;AAEJ,SAAOhG,KACLiG,KACE,CAACC,sBAAsBC,UAAU,CAACD,qBAAqB,CAAC,IAAI,GAAGC,KAAK,GACpE,CAAC,GAAG/H,MAAS,CACf,GACAyE,IAAI;AAAA,IACFuD,MAAMA,CAAC,CAACF,sBAAsBC,KAAK,MAAM;AACvCH,sBAAgBG,OAChBvG,QAAQsE,OAAO,GAAGgC,oBAAoB,OAAOC,OAAOvF,SAAS,oBAAoB9B,MAAMuH,IAAIF,OAAOxE,MAAMyC,gBAAgB,CAAC;AAAA,IAC3H;AAAA,IACAkC,UAAUA,MAAM1G,QAAQ2G,QAAQ,YAAYP,eAAepF,SAAS,SAAS;AAAA,EAAA,CAC9E,CACH;AACF;"}
|
|
1
|
+
{"version":3,"file":"importAssetsAction.js","sources":["../../src/_internal/cli/actions/media/lib/findNdjsonEntry.ts","../../src/_internal/cli/actions/media/importAssetsAction.ts"],"sourcesContent":["import readline from 'node:readline'\nimport {Readable} from 'node:stream'\n\n/**\n * Find the first matching entry in the provided NDJSON stream.\n *\n * @internal\n */\nexport async function* findNdjsonEntry<Type>(\n ndjson: Readable,\n matcher: (line: Type) => boolean,\n): AsyncGenerator<Type | undefined> {\n const lines = readline.createInterface({\n input: ndjson,\n })\n\n try {\n for await (const line of lines) {\n const parsed = JSON.parse(line.trim())\n if (matcher(parsed)) {\n yield parsed\n return\n }\n }\n\n yield undefined\n } finally {\n lines.close()\n // Explicitly destroy the underlying stream to prevent file descriptor leaks\n ndjson.destroy()\n }\n}\n\n/**\n * Read and parse all entries from an NDJSON stream.\n *\n * @internal\n */\nexport async function readNdjsonFile<Type>(ndjson: Readable): Promise<Type[]> {\n const lines = readline.createInterface({\n input: ndjson,\n })\n\n const entries: Type[] = []\n\n try {\n for await (const line of lines) {\n const trimmed = line.trim()\n if (trimmed) {\n entries.push(JSON.parse(trimmed))\n }\n }\n } finally {\n lines.close()\n // Explicitly destroy the underlying stream to prevent file descriptor leaks\n ndjson.destroy()\n }\n\n return entries\n}\n","import {createHash} from 'node:crypto'\nimport {createReadStream} from 'node:fs'\nimport fs, {mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {text} from 'node:stream/consumers'\nimport {pipeline} from 'node:stream/promises'\n\nimport {\n type CliCommandAction,\n type CliCommandContext,\n type CliOutputter,\n type SanityClient,\n} from '@sanity/cli'\nimport {type FileAsset, type ImageAsset, type SanityDocument} from '@sanity/types'\nimport {type Chalk} from 'chalk'\nimport gunzipMaybe from 'gunzip-maybe'\nimport isTar from 'is-tar'\n// @ts-expect-error `peek-stream` module currently untyped\nimport peek from 'peek-stream'\nimport {\n catchError,\n EMPTY,\n filter,\n from,\n map,\n mergeMap,\n mergeWith,\n type Observable,\n of,\n type OperatorFunction,\n pipe,\n scan,\n switchMap,\n tap,\n zip,\n} from 'rxjs'\nimport tar from 'tar-fs'\nimport {glob} from 'tinyglobby'\n\nimport {debug as baseDebug} from '../../debug'\nimport {MINIMUM_API_VERSION} from './constants'\nimport {determineTargetMediaLibrary} from './lib/determineTargetMediaLibrary'\nimport {readNdjsonFile} from './lib/findNdjsonEntry'\n\ninterface ImportAssetsFlags {\n 'media-library-id'?: string\n 'replace-aspects'?: boolean\n}\n\nconst debug = baseDebug.extend('importMedia')\n\nconst DEFAULT_CONCURRENCY = 6\n\ninterface MediaLibraryUploadResult {\n asset: SanityDocument & {\n _type: 'sanity.asset'\n assetType: ImageAsset['_type'] | FileAsset['_type']\n aspects: unknown\n }\n assetInstance: ImageAsset | FileAsset\n}\n\ninterface MediaLibraryUploadResponse {\n type: 'response'\n body: MediaLibraryUploadResult\n}\n\ninterface ResolvedAsset {\n /**\n * The ids of the `sanity.asset` documents that currently refer to the asset.\n *\n * These documents contain aspects, and reference an asset instance document.\n */\n assetIds: string[]\n /**\n * The original filename of the asset as it appears in the import source.\n *\n * Note: Currently includes `images/` or `files/` prefix.\n */\n originalFilename: string\n sha1Hash: string\n isExistingAsset: boolean\n}\n\n/**\n * @internal\n */\nexport type AssetWithAspects<Asset extends ResolvedAsset = ResolvedAsset> = Asset & {\n aspects: unknown | undefined\n}\n\ninterface State {\n /**\n * The count of input files.\n */\n fileCount: number\n /**\n * The last asset processed.\n */\n asset: AssetWithAspects\n}\n\ninterface Options {\n sourcePath: string\n client: SanityClient\n replaceAspects: boolean\n chalk: Chalk\n spinner: ReturnType<CliCommandContext['output']['spinner']>\n output: CliOutputter\n}\n\ninterface AspectDataEntry {\n filename: string\n aspects?: unknown\n}\n\ninterface Context extends Options {\n workingPath: string\n aspectsData: AspectDataEntry[]\n}\n\n// TODO: Order assets lexicographically before processing, allow resumable import\n// TODO: Granularly report upload progress of each asset (especially useful for large assets).\nconst importAssetsAction: CliCommandAction<ImportAssetsFlags> = async (args, context) => {\n const {output, apiClient, chalk} = context\n const [importSourcePath] = args.argsWithoutOptions\n const replaceAspects = args.extOptions['replace-aspects'] ?? false\n\n const mediaLibraryId =\n args.extOptions['media-library-id'] ?? (await determineTargetMediaLibrary(context))\n\n const client = apiClient().withConfig({\n 'apiVersion': MINIMUM_API_VERSION,\n 'requestTagPrefix': 'sanity.mediaLibraryCli.import',\n '~experimental_resource': {\n type: 'media-library',\n id: mediaLibraryId,\n },\n 'perspective': 'drafts',\n })\n\n output.print()\n output.print(`Importing to media library: ${chalk.bold(mediaLibraryId)}`)\n output.print(`Importing from path: ${chalk.bold(importSourcePath)}`)\n output.print()\n\n const spinner = output.spinner('Beginning import…').start()\n\n importer({\n client,\n sourcePath: importSourcePath,\n replaceAspects,\n chalk,\n spinner,\n output,\n })\n .pipe(\n reportResult({\n chalk,\n spinner,\n }),\n )\n .subscribe({\n error: (error) => {\n spinner.stop()\n output.error(error)\n },\n })\n}\n\nexport default importAssetsAction\n\nexport function importer(options: Options): Observable<State> {\n return resolveSource(options).pipe(\n mergeMap(({files, images, aspectsNdjsonPath, workingPath}) => {\n const fileCount = files.length + images.length\n\n if (fileCount === 0) {\n throw new Error('No assets to import')\n }\n\n // Read the ndjson file once upfront and cache the data to avoid creating\n // multiple read streams (which causes file descriptor leaks)\n const aspectsDataPromise = aspectsNdjsonPath\n ? readNdjsonFile<AspectDataEntry>(createReadStream(aspectsNdjsonPath))\n : Promise.resolve([])\n\n return from(aspectsDataPromise).pipe(\n mergeMap((aspectsData) => {\n const context: Context = {\n ...options,\n workingPath,\n aspectsData,\n }\n\n return from(files).pipe(\n switchMap((file) => zip(of<'file'>('file'), of(file))),\n mergeWith(from(images).pipe(switchMap((file) => zip(of<'image'>('image'), of(file))))),\n fetchExistingAssets(context),\n uploadAsset(context),\n resolveAspectData(context),\n setAspects(context),\n map((asset) => ({\n asset,\n fileCount,\n })),\n )\n }),\n )\n }),\n )\n}\n\n/**\n * @internal\n */\nexport function resolveSource({\n sourcePath,\n chalk,\n}: Pick<Context, 'sourcePath' | 'chalk'>): Observable<{\n files: string[]\n images: string[]\n aspectsNdjsonPath: string\n workingPath: string\n}> {\n return from(fs.stat(sourcePath)).pipe(\n switchMap((stats) => {\n return stats.isDirectory()\n ? of(sourcePath)\n : from(mkdtemp(path.join(tmpdir(), 'sanity-media-library-import'))).pipe(\n switchMap((tempPath) => {\n return from(\n pipeline(createReadStream(sourcePath), gunzipMaybe(), untarMaybe(tempPath)),\n ).pipe(map(() => tempPath))\n }),\n )\n }),\n switchMap((importSourcePath) => {\n return from(\n glob(['**/data.ndjson'], {\n cwd: importSourcePath,\n deep: 2,\n absolute: true,\n }),\n ).pipe(\n map(([aspectsNdjsonPath]) => ({\n aspectsNdjsonPath,\n importSourcePath,\n workingPath:\n typeof aspectsNdjsonPath === 'undefined'\n ? importSourcePath\n : path.dirname(aspectsNdjsonPath),\n })),\n )\n }),\n tap(({aspectsNdjsonPath, importSourcePath}) => {\n if (typeof aspectsNdjsonPath === 'undefined') {\n debug(\n `[No data.ndjson file] No predefined aspect data will be imported from ${importSourcePath}`,\n )\n } else {\n debug(`[Found NDJSON file] ${aspectsNdjsonPath}`)\n }\n }),\n switchMap(({aspectsNdjsonPath, workingPath}) => {\n return from(\n Promise.all([\n glob(['files/*'], {\n cwd: workingPath,\n }),\n glob(['images/*'], {\n cwd: workingPath,\n }),\n ]),\n ).pipe(\n map(([files, images]) => ({\n files,\n images,\n aspectsNdjsonPath,\n workingPath,\n })),\n )\n }),\n )\n}\n\n/**\n * Untar the stream if its contents appear to be tarred.\n *\n * @internal\n */\nfunction untarMaybe(outputPath: string) {\n // @ts-expect-error `peek-stream` module currently untyped\n return peek({newline: false, maxBuffer: 300}, (data, swap) => {\n if (isTar(data)) {\n return swap(null, tar.extract(outputPath))\n }\n\n return swap(null)\n })\n}\n\n/**\n * Fetch the ids of all asset documents that reference the input asset.\n * The input asset is identified by its SHA-1 hash.\n *\n * @internal\n */\nfunction fetchAssetsByHash({\n client,\n type,\n}: {\n client: SanityClient\n type: 'image' | 'file'\n}): OperatorFunction<string, [hash: string, assetIds: string[]]> {\n return switchMap((hash) =>\n client.observable\n .fetch<string[]>(\n `*[\n _type == \"sanity.asset\" &&\n currentVersion._ref == *[\n _type == $type &&\n sha1hash == $hash\n ][0]._id\n ]._id`,\n {\n type: ['sanity', `${type}Asset`].join('.'),\n hash,\n },\n {\n tag: 'asset.getId',\n },\n )\n .pipe(switchMap((assetIds) => zip(of(hash), of(assetIds)))),\n )\n}\n\nfunction fetchExistingAssets({\n client,\n workingPath,\n}: Context): OperatorFunction<\n [type: 'image' | 'file', asset: string],\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string]\n> {\n return mergeMap(([type, asset]) => {\n const createSha1Hash = createHash('sha1')\n\n const sha1hash = text(\n createReadStream(path.join(workingPath, asset)).pipe(createSha1Hash).setEncoding('hex'),\n )\n\n return from(sha1hash).pipe(\n tap((hash) => debug(`[Asset ${asset}] Checking for ${type} asset with hash ${hash}`)),\n fetchAssetsByHash({client, type}),\n map<\n [string, string[]],\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string]\n >(([hash, assetIds]) => {\n if (assetIds.length === 0) {\n return [type, asset, hash]\n }\n\n return {\n originalFilename: asset,\n sha1Hash: hash,\n assetIds,\n isExistingAsset: true,\n }\n }),\n )\n })\n}\n\n/**\n * Find the matching entry in the cached aspect data and attach it to the asset object.\n *\n * @internal\n */\nfunction resolveAspectData({\n aspectsData,\n}: Context): OperatorFunction<ResolvedAsset, AssetWithAspects> {\n return map((resolvedAsset) => {\n // If no aspects data exists, return asset with undefined aspects\n if (!aspectsData || aspectsData.length === 0) {\n return {\n ...resolvedAsset,\n aspects: undefined,\n }\n }\n\n // Find matching aspect data from the cached data\n const aspectsFromImport = aspectsData.find(\n (entry) => entry.filename === resolvedAsset.originalFilename,\n )\n\n return {\n ...resolvedAsset,\n aspects: aspectsFromImport?.aspects,\n }\n })\n}\n\n// TODO: Batch mutations to reduce HTTP request count.\nexport function setAspects({\n client,\n replaceAspects,\n}: Pick<Context, 'client' | 'replaceAspects'>): OperatorFunction<\n AssetWithAspects,\n AssetWithAspects\n> {\n return mergeMap((asset) => {\n const {assetIds, isExistingAsset, aspects} = asset\n\n if (isExistingAsset && !replaceAspects) {\n debug(`[Asset ${asset.originalFilename}] Skipping replacement of existing aspects`)\n return of(asset)\n }\n\n if (typeof aspects === 'undefined') {\n debug(`[Asset ${asset.originalFilename}] No aspects to import`)\n return of(asset)\n }\n\n const transaction = assetIds.reduce(\n (previous, assetId) => previous.patch(assetId, {set: {aspects}}),\n client.observable.transaction(),\n )\n\n debug(\n `[Asset ${asset.originalFilename}] Setting aspects on asset documents ${JSON.stringify(assetIds)}`,\n )\n\n return transaction\n .commit({\n visibility: 'async',\n tag: 'asset.setAspects',\n })\n .pipe(map(() => asset))\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction uploadAsset({\n workingPath,\n client,\n}: Context): OperatorFunction<\n ResolvedAsset | [type: 'image' | 'file', asset: string, hash: string],\n ResolvedAsset\n> {\n return mergeMap((maybeResolvedAsset) => {\n if ('assetIds' in maybeResolvedAsset) {\n debug(\n `[Asset ${maybeResolvedAsset.originalFilename}] Skipping upload of existing asset with hash ${maybeResolvedAsset.sha1Hash}`,\n )\n return of(maybeResolvedAsset)\n }\n\n const [type, asset, hash] = maybeResolvedAsset\n debug(`[Asset ${asset}] Uploading new asset`)\n\n return client.observable.assets\n .upload(type, createReadStream(path.join(workingPath, asset)), {\n tag: 'asset.upload',\n })\n .pipe(\n catchError((error) => {\n // An asset matching the hash was not found during previous steps, but appears to exist upon upload.\n //\n // This may occur if:\n // - The asset was uploaded by another client since the check was performed.\n // - The asset instance document exists, but is not referenced by any asset document.\n if (error.statusCode === 409) {\n debug(`[Asset ${asset}] Cannot overwrite existing ${type} asset with hash ${hash}`)\n return EMPTY\n }\n return EMPTY\n }),\n filter((response) => response.type === 'response'),\n tap(() => debug(`[Asset ${asset}] Finished uploading new asset`)),\n // TODO: The `client.assets.upload` method should return `MediaLibraryUploadResponse` when operating on Media Library resources. When that occurs, this type assertion can be removed.\n map((response) => (response as unknown as MediaLibraryUploadResponse).body),\n map<MediaLibraryUploadResult, ResolvedAsset>((result) => ({\n assetIds: [result.asset._id],\n originalFilename: asset,\n sha1Hash: hash,\n isExistingAsset: false,\n })),\n )\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction reportResult({\n chalk,\n spinner,\n}: Pick<Context, 'chalk' | 'spinner'>): OperatorFunction<State, [number, State | undefined]> {\n let previousState: State | undefined\n\n return pipe(\n scan<State, [number, State | undefined]>(\n (processedAssetsCount, state) => [processedAssetsCount[0] + 1, state],\n [0, undefined],\n ),\n tap({\n next: ([processedAssetsCount, state]) => {\n previousState = state\n spinner.text = `${processedAssetsCount} of ${state?.fileCount} assets imported ${chalk.dim(state?.asset.originalFilename)}`\n },\n complete: () => spinner.succeed(`Imported ${previousState?.fileCount} assets`),\n }),\n )\n}\n"],"names":["readNdjsonFile","ndjson","lines","readline","createInterface","input","entries","line","trimmed","trim","push","JSON","parse","close","destroy","debug","baseDebug","extend","DEFAULT_CONCURRENCY","importAssetsAction","args","context","output","apiClient","chalk","importSourcePath","argsWithoutOptions","replaceAspects","extOptions","mediaLibraryId","determineTargetMediaLibrary","client","withConfig","MINIMUM_API_VERSION","type","id","print","bold","spinner","start","importer","sourcePath","pipe","reportResult","subscribe","error","stop","options","resolveSource","mergeMap","files","images","aspectsNdjsonPath","workingPath","fileCount","length","Error","aspectsDataPromise","createReadStream","Promise","resolve","from","aspectsData","switchMap","file","zip","of","mergeWith","fetchExistingAssets","uploadAsset","resolveAspectData","setAspects","map","asset","fs","stat","stats","isDirectory","mkdtemp","path","join","tmpdir","tempPath","pipeline","gunzipMaybe","untarMaybe","glob","cwd","deep","absolute","dirname","tap","all","outputPath","peek","newline","maxBuffer","data","swap","isTar","tar","extract","fetchAssetsByHash","hash","observable","fetch","tag","assetIds","createSha1Hash","createHash","sha1hash","text","setEncoding","originalFilename","sha1Hash","isExistingAsset","resolvedAsset","aspects","undefined","aspectsFromImport","find","entry","filename","transaction","reduce","previous","assetId","patch","set","stringify","commit","visibility","maybeResolvedAsset","assets","upload","catchError","statusCode","EMPTY","filter","response","body","result","_id","previousState","scan","processedAssetsCount","state","next","dim","complete","succeed"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ try {
|
|
|
7
7
|
try {
|
|
8
8
|
buildVersion = buildVersion || // This is replaced by `@sanity/pkg-utils` at build time
|
|
9
9
|
// and must always be references by its full static name, e.g. no optional chaining, no `if (process && process.env)` etc.
|
|
10
|
-
"5.3.0-next.
|
|
10
|
+
"5.3.0-next.15+fd350a7a71";
|
|
11
11
|
} catch {
|
|
12
12
|
}
|
|
13
13
|
const SANITY_VERSION = buildVersion || `${version}-dev`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity",
|
|
3
|
-
"version": "5.3.0-next.
|
|
3
|
+
"version": "5.3.0-next.15+fd350a7a71",
|
|
4
4
|
"description": "Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -250,12 +250,12 @@
|
|
|
250
250
|
"which": "^5.0.0",
|
|
251
251
|
"xstate": "^5.25.0",
|
|
252
252
|
"yargs": "^17.7.2",
|
|
253
|
-
"@sanity/
|
|
254
|
-
"@sanity/
|
|
255
|
-
"@sanity/
|
|
256
|
-
"@sanity/
|
|
257
|
-
"@sanity/
|
|
258
|
-
"@sanity/
|
|
253
|
+
"@sanity/diff": "5.3.0-next.15+fd350a7a71",
|
|
254
|
+
"@sanity/cli": "5.3.0-next.15+fd350a7a71",
|
|
255
|
+
"@sanity/schema": "5.3.0-next.15+fd350a7a71",
|
|
256
|
+
"@sanity/types": "5.2.0",
|
|
257
|
+
"@sanity/mutator": "5.3.0-next.15+fd350a7a71",
|
|
258
|
+
"@sanity/util": "5.3.0-next.15+fd350a7a71"
|
|
259
259
|
},
|
|
260
260
|
"devDependencies": {
|
|
261
261
|
"@playwright/experimental-ct-react": "1.56.1",
|
|
@@ -301,13 +301,13 @@
|
|
|
301
301
|
"swr": "2.2.5",
|
|
302
302
|
"vitest": "^3.2.4",
|
|
303
303
|
"vitest-package-exports": "^1.1.1",
|
|
304
|
-
"@repo/dev-aliases": "5.3.0-next.
|
|
305
|
-
"@repo/
|
|
306
|
-
"@repo/
|
|
307
|
-
"@repo/
|
|
308
|
-
"@repo/
|
|
309
|
-
"@
|
|
310
|
-
"@
|
|
304
|
+
"@repo/dev-aliases": "5.3.0-next.15+fd350a7a71",
|
|
305
|
+
"@repo/package.bundle": "5.3.0-next.15+fd350a7a71",
|
|
306
|
+
"@repo/eslint-config": "5.3.0-next.15+fd350a7a71",
|
|
307
|
+
"@repo/package.config": "5.3.0-next.15+fd350a7a71",
|
|
308
|
+
"@repo/tsconfig": "5.3.0-next.15+fd350a7a71",
|
|
309
|
+
"@sanity/codegen": "5.3.0-next.15+fd350a7a71",
|
|
310
|
+
"@repo/test-config": "5.3.0-next.15+fd350a7a71"
|
|
311
311
|
},
|
|
312
312
|
"peerDependencies": {
|
|
313
313
|
"react": "^19.2.2",
|