@sanity/cli 8.7.0 → 8.9.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 +396 -0
- package/dist/actions/api/distillApiRoutes.js +1 -1
- package/dist/actions/api/distillApiRoutes.js.map +1 -1
- package/dist/actions/api/resolveEndpoint.js +1 -1
- package/dist/actions/api/resolveEndpoint.js.map +1 -1
- package/dist/actions/backup/archiveDir.js +7 -3
- package/dist/actions/backup/archiveDir.js.map +1 -1
- package/dist/actions/context/resolveOrganizationId.js +17 -0
- package/dist/actions/context/resolveOrganizationId.js.map +1 -0
- package/dist/actions/context/waitForJob.js +24 -0
- package/dist/actions/context/waitForJob.js.map +1 -0
- package/dist/actions/context/watchJob.js +32 -0
- package/dist/actions/context/watchJob.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +7 -6
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +7 -6
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/exec/execScript.js +6 -18
- package/dist/actions/exec/execScript.js.map +1 -1
- package/dist/actions/exec/runScript.worker.js +21 -0
- package/dist/actions/exec/runScript.worker.js.map +1 -0
- package/dist/actions/init/initAction.js +1 -1
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/media/importMedia.js +4 -2
- package/dist/actions/media/importMedia.js.map +1 -1
- package/dist/actions/typegen/parseTypegenConfig.js +21 -0
- package/dist/actions/typegen/parseTypegenConfig.js.map +1 -0
- package/dist/commands/backups/download.js +8 -17
- package/dist/commands/backups/download.js.map +1 -1
- package/dist/commands/context/build.js +111 -0
- package/dist/commands/context/build.js.map +1 -0
- package/dist/commands/context/create.js +106 -0
- package/dist/commands/context/create.js.map +1 -0
- package/dist/commands/context/delete.js +68 -0
- package/dist/commands/context/delete.js.map +1 -0
- package/dist/commands/context/get.js +95 -0
- package/dist/commands/context/get.js.map +1 -0
- package/dist/commands/context/imports/create.js +270 -0
- package/dist/commands/context/imports/create.js.map +1 -0
- package/dist/commands/context/imports/delete.js +73 -0
- package/dist/commands/context/imports/delete.js.map +1 -0
- package/dist/commands/context/imports/download.js +65 -0
- package/dist/commands/context/imports/download.js.map +1 -0
- package/dist/commands/context/imports/get.js +93 -0
- package/dist/commands/context/imports/get.js.map +1 -0
- package/dist/commands/context/imports/list.js +105 -0
- package/dist/commands/context/imports/list.js.map +1 -0
- package/dist/commands/context/jobs/get.js +90 -0
- package/dist/commands/context/jobs/get.js.map +1 -0
- package/dist/commands/context/list.js +109 -0
- package/dist/commands/context/list.js.map +1 -0
- package/dist/commands/context/refresh.js +46 -0
- package/dist/commands/context/refresh.js.map +1 -0
- package/dist/commands/context/update.js +127 -0
- package/dist/commands/context/update.js.map +1 -0
- package/dist/commands/datasets/alias/delete.js +4 -9
- package/dist/commands/datasets/alias/delete.js.map +1 -1
- package/dist/commands/datasets/alias/unlink.js +4 -9
- package/dist/commands/datasets/alias/unlink.js.map +1 -1
- package/dist/commands/datasets/delete.js +4 -9
- package/dist/commands/datasets/delete.js.map +1 -1
- package/dist/commands/graphql/undeploy.js +4 -9
- package/dist/commands/graphql/undeploy.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/media/create-aspect.js +5 -6
- package/dist/commands/media/create-aspect.js.map +1 -1
- package/dist/commands/media/delete-aspect.js +7 -23
- package/dist/commands/media/delete-aspect.js.map +1 -1
- package/dist/commands/media/deploy-aspect.js +4 -9
- package/dist/commands/media/deploy-aspect.js.map +1 -1
- package/dist/commands/media/export.js +3 -7
- package/dist/commands/media/export.js.map +1 -1
- package/dist/commands/media/import.js +3 -7
- package/dist/commands/media/import.js.map +1 -1
- package/dist/commands/organizations/create.js +4 -9
- package/dist/commands/organizations/create.js.map +1 -1
- package/dist/commands/schemas/delete.js +3 -8
- package/dist/commands/schemas/delete.js.map +1 -1
- package/dist/commands/tokens/delete.js +4 -7
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/typegen/generate.js +4 -3
- package/dist/commands/typegen/generate.js.map +1 -1
- package/dist/commands/users/invite.js +8 -18
- package/dist/commands/users/invite.js.map +1 -1
- package/dist/exports/index.js +1 -1
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +18 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/generated/apiRoutes.js +11 -9
- package/dist/generated/apiRoutes.js.map +1 -1
- package/dist/prompts/promptForOrganization.js +19 -0
- package/dist/prompts/promptForOrganization.js.map +1 -0
- package/dist/server/vite/plugin-typegen.js +3 -2
- package/dist/server/vite/plugin-typegen.js.map +1 -1
- package/dist/services/context.js +132 -0
- package/dist/services/context.js.map +1 -0
- package/dist/util/sharedFlags.js +24 -0
- package/dist/util/sharedFlags.js.map +1 -1
- package/oclif.config.js +1 -0
- package/oclif.manifest.json +1516 -718
- package/package.json +18 -23
|
@@ -10,7 +10,7 @@ import gunzipMaybe from 'gunzip-maybe';
|
|
|
10
10
|
// @ts-expect-error `peek-stream` module currently untyped
|
|
11
11
|
import peek from 'peek-stream';
|
|
12
12
|
import { catchError, EMPTY, filter, from, map, mergeMap, mergeWith, of, switchMap, tap, zip } from 'rxjs';
|
|
13
|
-
import
|
|
13
|
+
import { x as extractTar } from 'tar';
|
|
14
14
|
import { glob } from 'tinyglobby';
|
|
15
15
|
import { isTar } from '../../util/isTar.js';
|
|
16
16
|
import { buildNdjsonIndex } from './buildNdjsonIndex.js';
|
|
@@ -93,7 +93,9 @@ export function importer(options) {
|
|
|
93
93
|
newline: false
|
|
94
94
|
}, (data, swap)=>{
|
|
95
95
|
if (isTar(data)) {
|
|
96
|
-
return swap(null,
|
|
96
|
+
return swap(null, extractTar({
|
|
97
|
+
cwd: outputPath
|
|
98
|
+
}));
|
|
97
99
|
}
|
|
98
100
|
return swap(null);
|
|
99
101
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/media/importMedia.ts"],"sourcesContent":["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'\nimport {styleText} from 'node:util'\n\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type SanityClient} from '@sanity/client'\nimport {type FileAsset, type ImageAsset, type SanityDocument} from '@sanity/types'\nimport gunzipMaybe from 'gunzip-maybe'\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 switchMap,\n tap,\n zip,\n} from 'rxjs'\nimport tar from 'tar-fs'\nimport {glob} from 'tinyglobby'\n\nimport {isTar} from '../../util/isTar.js'\nimport {buildNdjsonIndex} from './buildNdjsonIndex.js'\nimport {importMediaDebug} from './importMediaDebug.js'\n\nconst DEFAULT_CONCURRENCY = 6\n\ninterface MediaLibraryUploadResult {\n asset: SanityDocument & {\n _type: 'sanity.asset'\n aspects: unknown\n assetType: FileAsset['_type'] | ImageAsset['_type']\n }\n assetInstance: FileAsset | ImageAsset\n}\n\ninterface MediaLibraryUploadResponse {\n body: MediaLibraryUploadResult\n type: 'response'\n}\n\nexport interface State {\n /**\n * The last asset processed.\n */\n asset: AssetWithAspects\n /**\n * The count of input files.\n */\n fileCount: number\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 isExistingAsset: boolean\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}\n\nexport type AssetWithAspects<Asset extends ResolvedAsset = ResolvedAsset> = Asset & {\n aspects: unknown | undefined\n}\n\ninterface Options {\n client: SanityClient\n replaceAspects: boolean\n sourcePath: string\n spinner: ReturnType<typeof spinner>\n}\n\ninterface Context extends Options {\n aspectsIndex: Map<string, unknown>\n workingPath: string\n}\n\nexport function importer(options: Options): Observable<State> {\n return resolveSource(options).pipe(\n mergeMap(({aspectsNdjsonPath, files, images, workingPath}) => {\n const fileCount = files.length + images.length\n\n if (fileCount === 0) {\n throw new Error('No assets to import')\n }\n\n // Stream the ndjson file once and build an index to avoid creating\n // multiple read streams (which causes file descriptor leaks)\n const aspectsIndexPromise = buildNdjsonIndex(\n createReadStream(aspectsNdjsonPath),\n 'filename',\n 'aspects',\n )\n\n return from(aspectsIndexPromise).pipe(\n mergeMap((aspectsIndex) => {\n const context: Context = {\n ...options,\n aspectsIndex,\n workingPath,\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({sourcePath}: Pick<Context, 'sourcePath'>): Observable<{\n aspectsNdjsonPath: string\n files: string[]\n images: 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 absolute: true,\n cwd: importSourcePath,\n deep: 2,\n }),\n ).pipe(\n map(([aspectsNdjsonPath]) => ({\n aspectsNdjsonPath,\n importSourcePath,\n workingPath:\n aspectsNdjsonPath === undefined ? importSourcePath : path.dirname(aspectsNdjsonPath),\n })),\n )\n }),\n tap(({aspectsNdjsonPath, importSourcePath}) => {\n if (aspectsNdjsonPath === undefined) {\n throw new Error(\n `No ${styleText('bold', 'data.ndjson')} file found in import source ${styleText('bold', importSourcePath)}`,\n )\n }\n importMediaDebug(`[Found NDJSON file] ${aspectsNdjsonPath}`)\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 aspectsNdjsonPath,\n files,\n images,\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({maxBuffer: 300, newline: false}, (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: 'file' | 'image'\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 hash,\n type: ['sanity', `${type}Asset`].join('.'),\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: 'file' | 'image', asset: string],\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset\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) =>\n importMediaDebug(`[Asset ${asset}] Checking for ${type} asset with hash ${hash}`),\n ),\n fetchAssetsByHash({client, type}),\n map<\n [string, string[]],\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset\n >(([hash, assetIds]) => {\n if (assetIds.length === 0) {\n return [type, asset, hash]\n }\n\n return {\n assetIds,\n isExistingAsset: true,\n originalFilename: asset,\n sha1Hash: hash,\n }\n }),\n )\n })\n}\n\n/**\n * Find the first matching entry in the cached aspect data and attach it to the asset object.\n *\n * @internal\n */\nfunction resolveAspectData({\n aspectsIndex,\n}: Context): OperatorFunction<ResolvedAsset, AssetWithAspects> {\n return map((resolvedAsset) => ({\n ...resolvedAsset,\n aspects: aspectsIndex.get(resolvedAsset.originalFilename),\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 {aspects, assetIds, isExistingAsset} = asset\n\n if (isExistingAsset && !replaceAspects) {\n importMediaDebug(`[Asset ${asset.originalFilename}] Skipping replacement of existing aspects`)\n return of(asset)\n }\n\n if (aspects === undefined) {\n importMediaDebug(`[Asset ${asset.originalFilename}] No aspects to import`)\n return of(asset)\n }\n\n const transaction = client.observable.transaction()\n for (const assetId of assetIds) {\n transaction.patch(assetId, {set: {aspects}})\n }\n\n importMediaDebug(\n `[Asset ${asset.originalFilename}] Setting aspects on asset documents ${JSON.stringify(assetIds)}`,\n )\n\n return transaction\n .commit({\n tag: 'asset.setAspects',\n visibility: 'async',\n })\n .pipe(map(() => asset))\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction uploadAsset({\n client,\n workingPath,\n}: Context): OperatorFunction<\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset,\n ResolvedAsset\n> {\n return mergeMap((maybeResolvedAsset) => {\n if ('assetIds' in maybeResolvedAsset) {\n importMediaDebug(\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 importMediaDebug(`[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 importMediaDebug(\n `[Asset ${asset}] Cannot overwrite existing ${type} asset with hash ${hash}`,\n )\n return EMPTY\n }\n return EMPTY\n }),\n filter((response) => response.type === 'response'),\n tap(() => importMediaDebug(`[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 isExistingAsset: false,\n originalFilename: asset,\n sha1Hash: hash,\n })),\n )\n }, DEFAULT_CONCURRENCY)\n}\n"],"names":["createHash","createReadStream","fs","mkdtemp","tmpdir","path","text","pipeline","styleText","gunzipMaybe","peek","catchError","EMPTY","filter","from","map","mergeMap","mergeWith","of","switchMap","tap","zip","tar","glob","isTar","buildNdjsonIndex","importMediaDebug","DEFAULT_CONCURRENCY","importer","options","resolveSource","pipe","aspectsNdjsonPath","files","images","workingPath","fileCount","length","Error","aspectsIndexPromise","aspectsIndex","context","file","fetchExistingAssets","uploadAsset","resolveAspectData","setAspects","asset","sourcePath","stat","stats","isDirectory","join","tempPath","untarMaybe","importSourcePath","absolute","cwd","deep","undefined","dirname","Promise","all","outputPath","maxBuffer","newline","data","swap","extract","fetchAssetsByHash","client","type","hash","observable","fetch","tag","assetIds","createSha1Hash","sha1hash","setEncoding","isExistingAsset","originalFilename","sha1Hash","resolvedAsset","aspects","get","replaceAspects","transaction","assetId","patch","set","JSON","stringify","commit","visibility","maybeResolvedAsset","assets","upload","error","statusCode","response","body","result","_id"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/media/importMedia.ts"],"sourcesContent":["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'\nimport {styleText} from 'node:util'\n\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type SanityClient} from '@sanity/client'\nimport {type FileAsset, type ImageAsset, type SanityDocument} from '@sanity/types'\nimport gunzipMaybe from 'gunzip-maybe'\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 switchMap,\n tap,\n zip,\n} from 'rxjs'\nimport {x as extractTar} from 'tar'\nimport {glob} from 'tinyglobby'\n\nimport {isTar} from '../../util/isTar.js'\nimport {buildNdjsonIndex} from './buildNdjsonIndex.js'\nimport {importMediaDebug} from './importMediaDebug.js'\n\nconst DEFAULT_CONCURRENCY = 6\n\ninterface MediaLibraryUploadResult {\n asset: SanityDocument & {\n _type: 'sanity.asset'\n aspects: unknown\n assetType: FileAsset['_type'] | ImageAsset['_type']\n }\n assetInstance: FileAsset | ImageAsset\n}\n\ninterface MediaLibraryUploadResponse {\n body: MediaLibraryUploadResult\n type: 'response'\n}\n\nexport interface State {\n /**\n * The last asset processed.\n */\n asset: AssetWithAspects\n /**\n * The count of input files.\n */\n fileCount: number\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 isExistingAsset: boolean\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}\n\nexport type AssetWithAspects<Asset extends ResolvedAsset = ResolvedAsset> = Asset & {\n aspects: unknown | undefined\n}\n\ninterface Options {\n client: SanityClient\n replaceAspects: boolean\n sourcePath: string\n spinner: ReturnType<typeof spinner>\n}\n\ninterface Context extends Options {\n aspectsIndex: Map<string, unknown>\n workingPath: string\n}\n\nexport function importer(options: Options): Observable<State> {\n return resolveSource(options).pipe(\n mergeMap(({aspectsNdjsonPath, files, images, workingPath}) => {\n const fileCount = files.length + images.length\n\n if (fileCount === 0) {\n throw new Error('No assets to import')\n }\n\n // Stream the ndjson file once and build an index to avoid creating\n // multiple read streams (which causes file descriptor leaks)\n const aspectsIndexPromise = buildNdjsonIndex(\n createReadStream(aspectsNdjsonPath),\n 'filename',\n 'aspects',\n )\n\n return from(aspectsIndexPromise).pipe(\n mergeMap((aspectsIndex) => {\n const context: Context = {\n ...options,\n aspectsIndex,\n workingPath,\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({sourcePath}: Pick<Context, 'sourcePath'>): Observable<{\n aspectsNdjsonPath: string\n files: string[]\n images: 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 absolute: true,\n cwd: importSourcePath,\n deep: 2,\n }),\n ).pipe(\n map(([aspectsNdjsonPath]) => ({\n aspectsNdjsonPath,\n importSourcePath,\n workingPath:\n aspectsNdjsonPath === undefined ? importSourcePath : path.dirname(aspectsNdjsonPath),\n })),\n )\n }),\n tap(({aspectsNdjsonPath, importSourcePath}) => {\n if (aspectsNdjsonPath === undefined) {\n throw new Error(\n `No ${styleText('bold', 'data.ndjson')} file found in import source ${styleText('bold', importSourcePath)}`,\n )\n }\n importMediaDebug(`[Found NDJSON file] ${aspectsNdjsonPath}`)\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 aspectsNdjsonPath,\n files,\n images,\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({maxBuffer: 300, newline: false}, (data, swap) => {\n if (isTar(data)) {\n return swap(null, extractTar({cwd: 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: 'file' | 'image'\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 hash,\n type: ['sanity', `${type}Asset`].join('.'),\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: 'file' | 'image', asset: string],\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset\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) =>\n importMediaDebug(`[Asset ${asset}] Checking for ${type} asset with hash ${hash}`),\n ),\n fetchAssetsByHash({client, type}),\n map<\n [string, string[]],\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset\n >(([hash, assetIds]) => {\n if (assetIds.length === 0) {\n return [type, asset, hash]\n }\n\n return {\n assetIds,\n isExistingAsset: true,\n originalFilename: asset,\n sha1Hash: hash,\n }\n }),\n )\n })\n}\n\n/**\n * Find the first matching entry in the cached aspect data and attach it to the asset object.\n *\n * @internal\n */\nfunction resolveAspectData({\n aspectsIndex,\n}: Context): OperatorFunction<ResolvedAsset, AssetWithAspects> {\n return map((resolvedAsset) => ({\n ...resolvedAsset,\n aspects: aspectsIndex.get(resolvedAsset.originalFilename),\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 {aspects, assetIds, isExistingAsset} = asset\n\n if (isExistingAsset && !replaceAspects) {\n importMediaDebug(`[Asset ${asset.originalFilename}] Skipping replacement of existing aspects`)\n return of(asset)\n }\n\n if (aspects === undefined) {\n importMediaDebug(`[Asset ${asset.originalFilename}] No aspects to import`)\n return of(asset)\n }\n\n const transaction = client.observable.transaction()\n for (const assetId of assetIds) {\n transaction.patch(assetId, {set: {aspects}})\n }\n\n importMediaDebug(\n `[Asset ${asset.originalFilename}] Setting aspects on asset documents ${JSON.stringify(assetIds)}`,\n )\n\n return transaction\n .commit({\n tag: 'asset.setAspects',\n visibility: 'async',\n })\n .pipe(map(() => asset))\n }, DEFAULT_CONCURRENCY)\n}\n\nfunction uploadAsset({\n client,\n workingPath,\n}: Context): OperatorFunction<\n [type: 'file' | 'image', asset: string, hash: string] | ResolvedAsset,\n ResolvedAsset\n> {\n return mergeMap((maybeResolvedAsset) => {\n if ('assetIds' in maybeResolvedAsset) {\n importMediaDebug(\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 importMediaDebug(`[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 importMediaDebug(\n `[Asset ${asset}] Cannot overwrite existing ${type} asset with hash ${hash}`,\n )\n return EMPTY\n }\n return EMPTY\n }),\n filter((response) => response.type === 'response'),\n tap(() => importMediaDebug(`[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 isExistingAsset: false,\n originalFilename: asset,\n sha1Hash: hash,\n })),\n )\n }, DEFAULT_CONCURRENCY)\n}\n"],"names":["createHash","createReadStream","fs","mkdtemp","tmpdir","path","text","pipeline","styleText","gunzipMaybe","peek","catchError","EMPTY","filter","from","map","mergeMap","mergeWith","of","switchMap","tap","zip","x","extractTar","glob","isTar","buildNdjsonIndex","importMediaDebug","DEFAULT_CONCURRENCY","importer","options","resolveSource","pipe","aspectsNdjsonPath","files","images","workingPath","fileCount","length","Error","aspectsIndexPromise","aspectsIndex","context","file","fetchExistingAssets","uploadAsset","resolveAspectData","setAspects","asset","sourcePath","stat","stats","isDirectory","join","tempPath","untarMaybe","importSourcePath","absolute","cwd","deep","undefined","dirname","Promise","all","outputPath","maxBuffer","newline","data","swap","fetchAssetsByHash","client","type","hash","observable","fetch","tag","assetIds","createSha1Hash","sha1hash","setEncoding","isExistingAsset","originalFilename","sha1Hash","resolvedAsset","aspects","get","replaceAspects","transaction","assetId","patch","set","JSON","stringify","commit","visibility","maybeResolvedAsset","assets","upload","error","statusCode","response","body","result","_id"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { configDefinition } from '@sanity/codegen';
|
|
2
|
+
import { isValiError, parse } from 'valibot';
|
|
3
|
+
/**
|
|
4
|
+
* Applies `@sanity/codegen` schema defaults to a partial typegen config.
|
|
5
|
+
*
|
|
6
|
+
* `@sanity/codegen` 8.1.0 moved `configDefinition` from Zod to Valibot, so the
|
|
7
|
+
* schema is no longer callable as `configDefinition.parse()`.
|
|
8
|
+
*/ export function parseTypegenConfig(input = {}) {
|
|
9
|
+
try {
|
|
10
|
+
return parse(configDefinition, input);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (isValiError(error)) {
|
|
13
|
+
throw new TypeError(`Error in typegen config\n ${error.issues.map((issue)=>issue.message).join('\n')}`, {
|
|
14
|
+
cause: error
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=parseTypegenConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/typegen/parseTypegenConfig.ts"],"sourcesContent":["import {configDefinition, type TypeGenConfig} from '@sanity/codegen'\nimport {isValiError, parse} from 'valibot'\n\n/**\n * Applies `@sanity/codegen` schema defaults to a partial typegen config.\n *\n * `@sanity/codegen` 8.1.0 moved `configDefinition` from Zod to Valibot, so the\n * schema is no longer callable as `configDefinition.parse()`.\n */\nexport function parseTypegenConfig(input: unknown = {}): TypeGenConfig {\n try {\n return parse(configDefinition, input)\n } catch (error) {\n if (isValiError(error)) {\n throw new TypeError(\n `Error in typegen config\\n ${error.issues.map((issue) => issue.message).join('\\n')}`,\n {cause: error},\n )\n }\n throw error\n }\n}\n"],"names":["configDefinition","isValiError","parse","parseTypegenConfig","input","error","TypeError","issues","map","issue","message","join","cause"],"mappings":"AAAA,SAAQA,gBAAgB,QAA2B,kBAAiB;AACpE,SAAQC,WAAW,EAAEC,KAAK,QAAO,UAAS;AAE1C;;;;;CAKC,GACD,OAAO,SAASC,mBAAmBC,QAAiB,CAAC,CAAC;IACpD,IAAI;QACF,OAAOF,MAAMF,kBAAkBI;IACjC,EAAE,OAAOC,OAAO;QACd,IAAIJ,YAAYI,QAAQ;YACtB,MAAM,IAAIC,UACR,CAAC,0BAA0B,EAAED,MAAME,MAAM,CAACC,GAAG,CAAC,CAACC,QAAUA,MAAMC,OAAO,EAAEC,IAAI,CAAC,OAAO,EACpF;gBAACC,OAAOP;YAAK;QAEjB;QACA,MAAMA;IACR;AACF"}
|
|
@@ -6,6 +6,7 @@ import { finished } from 'node:stream/promises';
|
|
|
6
6
|
import { styleText } from 'node:util';
|
|
7
7
|
import { Args, Flags } from '@oclif/core';
|
|
8
8
|
import { exitCodes, SanityCommand } from '@sanity/cli-core';
|
|
9
|
+
import { requiredWhenUnattended } from '@sanity/cli-core/flags';
|
|
9
10
|
import { boxen, confirm, input, select } from '@sanity/cli-core/ux';
|
|
10
11
|
import pMap from 'p-map';
|
|
11
12
|
import prettyMs from 'pretty-ms';
|
|
@@ -22,7 +23,6 @@ import { promptForDataset } from '../../prompts/promptForDataset.js';
|
|
|
22
23
|
import { promptForProject } from '../../prompts/promptForProject.js';
|
|
23
24
|
import { listBackups } from '../../services/backup.js';
|
|
24
25
|
import { listDatasets } from '../../services/datasets.js';
|
|
25
|
-
import { formatCliErrorMessages } from '../../util/formatCliErrorMessages.js';
|
|
26
26
|
import { humanFileSize } from '../../util/humanFileSize.js';
|
|
27
27
|
import { isPathDirName } from '../../util/isPathDirName.js';
|
|
28
28
|
import { getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
@@ -59,9 +59,9 @@ export class DownloadBackupCommand extends SanityCommand {
|
|
|
59
59
|
description: 'Project ID to download backup from',
|
|
60
60
|
semantics: 'override'
|
|
61
61
|
}),
|
|
62
|
-
'backup-id': Flags.string({
|
|
62
|
+
'backup-id': requiredWhenUnattended(Flags.string({
|
|
63
63
|
description: 'The backup ID to download'
|
|
64
|
-
}),
|
|
64
|
+
})),
|
|
65
65
|
concurrency: Flags.integer({
|
|
66
66
|
default: DEFAULT_DOWNLOAD_CONCURRENCY,
|
|
67
67
|
description: `Concurrent number of backup item downloads (max: ${MAX_DOWNLOAD_CONCURRENCY})`
|
|
@@ -78,21 +78,12 @@ export class DownloadBackupCommand extends SanityCommand {
|
|
|
78
78
|
'backup:download'
|
|
79
79
|
];
|
|
80
80
|
async run() {
|
|
81
|
-
const { args
|
|
81
|
+
const { args } = await this.parse(DownloadBackupCommand);
|
|
82
82
|
let { dataset } = args;
|
|
83
|
-
if (this.isUnattended()) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
if (!flags['backup-id']) {
|
|
89
|
-
errors.push('Backup ID is required in unattended mode. Pass it with `--backup-id <id>`.');
|
|
90
|
-
}
|
|
91
|
-
if (errors.length > 0) {
|
|
92
|
-
this.error(formatCliErrorMessages(errors), {
|
|
93
|
-
exit: exitCodes.USAGE_ERROR
|
|
94
|
-
});
|
|
95
|
-
}
|
|
83
|
+
if (this.isUnattended() && !dataset) {
|
|
84
|
+
this.error('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.', {
|
|
85
|
+
exit: exitCodes.USAGE_ERROR
|
|
86
|
+
});
|
|
96
87
|
}
|
|
97
88
|
const projectId = await this.getProjectId({
|
|
98
89
|
fallback: ()=>promptForProject({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/backups/download.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {access, mkdir, mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {finished} from 'node:stream/promises'\nimport {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {boxen, confirm, input, select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\nimport pMap from 'p-map'\nimport prettyMs from 'pretty-ms'\n\nimport {archiveDir} from '../../actions/backup/archiveDir.js'\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {backupDownloadDebug} from '../../actions/backup/backupDownloadDebug.js'\nimport {cleanupTmpDir} from '../../actions/backup/cleanupTmpDir.js'\nimport {downloadAsset} from '../../actions/backup/downloadAsset.js'\nimport {downloadDocument} from '../../actions/backup/downloadDocument.js'\nimport {type File, PaginatedGetBackupStream} from '../../actions/backup/fetchNextBackupPage.js'\nimport {newProgress} from '../../actions/backup/progressSpinner.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {promptForDataset} from '../../prompts/promptForDataset.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type BackupItem, listBackups} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {formatCliErrorMessages} from '../../util/formatCliErrorMessages.js'\nimport {humanFileSize} from '../../util/humanFileSize.js'\nimport {isPathDirName} from '../../util/isPathDirName.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst DEFAULT_DOWNLOAD_CONCURRENCY = 10\nconst MAX_DOWNLOAD_CONCURRENCY = 24\n\ninterface DownloadBackupOptions {\n backupId: string\n concurrency: number\n datasetName: string\n outDir: string\n outFileName: string\n overwrite: boolean\n projectId: string\n}\n\nexport class DownloadBackupCommand extends SanityCommand<typeof DownloadBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to download backup from',\n required: false,\n }),\n }\n\n static override description = 'Download a dataset backup to a local file'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively download a backup',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-1',\n description: 'Download a specific backup for the production dataset',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-2 --out /path/to/file',\n description: 'Download backup to a specific file',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-3 --out /path/to/file --overwrite',\n description: 'Download backup and overwrite existing file',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to download backup from',\n semantics: 'override',\n }),\n 'backup-id': Flags.string({\n description: 'The backup ID to download',\n }),\n concurrency: Flags.integer({\n default: DEFAULT_DOWNLOAD_CONCURRENCY,\n description: `Concurrent number of backup item downloads (max: ${MAX_DOWNLOAD_CONCURRENCY})`,\n }),\n out: Flags.string({\n description: 'The file or directory path the backup should download to',\n }),\n overwrite: Flags.boolean({\n default: false,\n description: 'Allows overwriting of existing backup file',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:download']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(DownloadBackupCommand)\n let {dataset} = args\n\n if (this.isUnattended()) {\n const errors: string[] = []\n\n if (!dataset) {\n errors.push('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.')\n }\n if (!flags['backup-id']) {\n errors.push('Backup ID is required in unattended mode. Pass it with `--backup-id <id>`.')\n }\n\n if (errors.length > 0) {\n this.error(formatCliErrorMessages(errors), {exit: exitCodes.USAGE_ERROR})\n }\n }\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.datasets'}],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (datasets.length === 0) {\n this.error('No datasets found in this project.', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (dataset) {\n assertDatasetExists(datasets, dataset, this.output)\n } else {\n dataset = await promptForDataset({allowCreation: false, datasets})\n }\n\n const opts = await this.prepareBackupOptions(projectId, dataset)\n const outFilePath = path.join(opts.outDir, opts.outFileName)\n\n this.log(\n boxen(\n `Downloading backup for:\n\n${styleText('bold', 'projectId')}: ${styleText('cyan', opts.projectId)}\n${styleText('bold', 'dataset')}: ${styleText('cyan', opts.datasetName)}\n${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`,\n {\n borderColor: 'cyan',\n borderStyle: 'round',\n padding: 1,\n },\n ),\n )\n this.log('')\n this.log(`Downloading backup to \"${styleText('cyan', outFilePath)}\"`)\n\n const start = Date.now()\n const progressSpinner = newProgress('Setting up backup environment...')\n\n // Create a unique temporary directory to store files before bundling them into the archive at outputPath.\n // Temporary directories are normally deleted at the end of backup process, any unexpected exit may leave them\n // behind, hence it is important to create a unique directory for each attempt.\n const tmpOutDir = await mkdtemp(path.join(tmpdir(), `sanity-backup-`))\n\n // Create required directories if they don't exist.\n for (const dir of [\n opts.outDir,\n path.join(tmpOutDir, 'images'),\n path.join(tmpOutDir, 'files'),\n ]) {\n await mkdir(dir, {recursive: true})\n }\n\n backupDownloadDebug('Writing to temporary directory %s', tmpOutDir)\n const tmpOutDocumentsFile = path.join(tmpOutDir, 'data.ndjson')\n\n const docOutStream = createWriteStream(tmpOutDocumentsFile)\n\n try {\n const backupFileStream = new PaginatedGetBackupStream(\n opts.projectId,\n opts.datasetName,\n opts.backupId,\n )\n\n const files: File[] = []\n let i = 0\n for await (const file of backupFileStream) {\n files.push(file)\n i++\n progressSpinner.set({\n current: i,\n step: `Reading backup files...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n }\n\n let totalItemsDownloaded = 0\n await pMap(\n files,\n async (file: File) => {\n if (file.type === 'file' || file.type === 'image') {\n await downloadAsset(file.url, file.name, file.type, tmpOutDir)\n } else {\n const doc = await downloadDocument(file.url)\n docOutStream.write(`${doc}\\n`)\n }\n\n totalItemsDownloaded += 1\n progressSpinner.set({\n current: totalItemsDownloaded,\n step: `Downloading documents and assets...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n },\n {concurrency: opts.concurrency},\n )\n } catch (error) {\n progressSpinner.fail()\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Downloading dataset backup failed: ${message}`, error)\n this.error(`Downloading dataset backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n docOutStream.end()\n await finished(docOutStream)\n\n progressSpinner.set({step: `Archiving files into a tarball...`, update: true})\n try {\n await archiveDir(tmpOutDir, outFilePath, (processedBytes: number) => {\n progressSpinner.update({\n step: `Archiving files into a tarball, ${humanFileSize(processedBytes)} bytes written...`,\n })\n })\n } catch (err) {\n progressSpinner.fail()\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Archiving backup failed: ${message}`, err)\n this.error(`Archiving backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n progressSpinner.set({\n step: `Cleaning up temporary files at ${styleText('cyan', `${tmpOutDir}`)}`,\n })\n await cleanupTmpDir(tmpOutDir)\n\n progressSpinner.set({\n step: `Backup download complete [${prettyMs(Date.now() - start)}]`,\n })\n progressSpinner.succeed()\n }\n\n private async getOutputPath(defaultOutFileName: string): Promise<string> {\n if (this.flags.out !== undefined) {\n // Rewrite the output path to an absolute path, if it is not already.\n return path.resolve(this.flags.out)\n }\n\n const workDir = process.cwd()\n const defaultPath = path.join(workDir, defaultOutFileName)\n if (this.isUnattended()) {\n return defaultPath\n }\n\n const inputResult = await input({\n default: defaultPath,\n message: 'Output path:',\n })\n return path.resolve(inputResult)\n }\n\n private async prepareBackupOptions(\n projectId: string,\n datasetName: string,\n ): Promise<DownloadBackupOptions> {\n const err = validateDatasetName(datasetName)\n if (err) {\n this.error(err, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupId = String(\n this.flags['backup-id'] || (await this.promptForBackupId(projectId, datasetName)),\n )\n\n if (\n 'concurrency' in this.flags &&\n (this.flags.concurrency < 1 || this.flags.concurrency > MAX_DOWNLOAD_CONCURRENCY)\n ) {\n this.error(`\\`--concurrency\\` must be between 1 and ${MAX_DOWNLOAD_CONCURRENCY}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const defaultOutFileName = `${datasetName}-backup-${backupId}.tar.gz`\n let out = await this.getOutputPath(defaultOutFileName)\n\n // If path is a directory name, then add a default file name to the path.\n if (isPathDirName(out)) {\n out = path.join(out, defaultOutFileName)\n }\n\n const exists = await access(out).then(\n () => true,\n () => false,\n )\n // If the file already exists, ask for confirmation if it should be overwritten.\n if (!this.flags.overwrite && exists) {\n if (this.isUnattended()) {\n this.error(`File \"${out}\" already exists. Pass \\`--overwrite\\` to replace it.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n const shouldOverwrite = await confirm({\n default: false,\n message: `File \"${out}\" already exists, would you like to overwrite it?`,\n })\n\n // If the user does not want to overwrite the file, cancel the operation.\n if (!shouldOverwrite) {\n this.error('Operation cancelled.', {exit: exitCodes.USER_ABORT})\n }\n }\n\n return {\n backupId,\n concurrency: this.flags.concurrency || DEFAULT_DOWNLOAD_CONCURRENCY,\n datasetName,\n outDir: path.dirname(out),\n outFileName: path.basename(out),\n overwrite: this.flags.overwrite,\n projectId,\n }\n }\n\n private async promptForBackupId(projectId: string, datasetName: string): Promise<string> {\n const maxBackupIdsShown = 100\n\n try {\n const response = await listBackups({\n datasetName,\n limit: maxBackupIdsShown,\n projectId,\n })\n\n if (!response?.backups?.length) {\n this.error('No backups found', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupIdChoices = response.backups.map((backup: BackupItem) => ({\n name: backup.id,\n value: backup.id,\n }))\n\n const hint =\n backupIdChoices.length === maxBackupIdsShown\n ? ` (only last ${maxBackupIdsShown} shown)`\n : ''\n\n return select({\n choices: backupIdChoices,\n message: `Select backup ID to use${hint}`,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Failed to fetch backups for dataset ${datasetName}: ${message}`, err)\n this.error(`Failed to fetch backups for dataset ${datasetName}: ${message}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["createWriteStream","access","mkdir","mkdtemp","tmpdir","path","finished","styleText","Args","Flags","exitCodes","SanityCommand","boxen","confirm","input","select","pMap","prettyMs","archiveDir","assertDatasetExists","backupDownloadDebug","cleanupTmpDir","downloadAsset","downloadDocument","PaginatedGetBackupStream","newProgress","validateDatasetName","promptForDataset","promptForProject","listBackups","listDatasets","formatCliErrorMessages","humanFileSize","isPathDirName","getProjectIdFlag","DEFAULT_DOWNLOAD_CONCURRENCY","MAX_DOWNLOAD_CONCURRENCY","DownloadBackupCommand","args","dataset","string","description","required","examples","command","flags","semantics","concurrency","integer","default","out","overwrite","boolean","hiddenAliases","run","parse","isUnattended","errors","push","length","error","exit","USAGE_ERROR","projectId","getProjectId","fallback","requiredPermissions","grant","permission","datasets","message","Error","String","RUNTIME_ERROR","output","allowCreation","opts","prepareBackupOptions","outFilePath","join","outDir","outFileName","log","datasetName","backupId","borderColor","borderStyle","padding","start","Date","now","progressSpinner","tmpOutDir","dir","recursive","tmpOutDocumentsFile","docOutStream","backupFileStream","files","i","file","set","current","step","total","totalFiles","update","totalItemsDownloaded","type","url","name","doc","write","fail","end","processedBytes","err","succeed","getOutputPath","defaultOutFileName","undefined","resolve","workDir","process","cwd","defaultPath","inputResult","promptForBackupId","exists","then","shouldOverwrite","USER_ABORT","dirname","basename","maxBackupIdsShown","response","limit","backups","backupIdChoices","map","backup","id","value","hint","choices"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/backups/download.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {access, mkdir, mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {finished} from 'node:stream/promises'\nimport {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {boxen, confirm, input, select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\nimport pMap from 'p-map'\nimport prettyMs from 'pretty-ms'\n\nimport {archiveDir} from '../../actions/backup/archiveDir.js'\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {backupDownloadDebug} from '../../actions/backup/backupDownloadDebug.js'\nimport {cleanupTmpDir} from '../../actions/backup/cleanupTmpDir.js'\nimport {downloadAsset} from '../../actions/backup/downloadAsset.js'\nimport {downloadDocument} from '../../actions/backup/downloadDocument.js'\nimport {type File, PaginatedGetBackupStream} from '../../actions/backup/fetchNextBackupPage.js'\nimport {newProgress} from '../../actions/backup/progressSpinner.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {promptForDataset} from '../../prompts/promptForDataset.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type BackupItem, listBackups} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {humanFileSize} from '../../util/humanFileSize.js'\nimport {isPathDirName} from '../../util/isPathDirName.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst DEFAULT_DOWNLOAD_CONCURRENCY = 10\nconst MAX_DOWNLOAD_CONCURRENCY = 24\n\ninterface DownloadBackupOptions {\n backupId: string\n concurrency: number\n datasetName: string\n outDir: string\n outFileName: string\n overwrite: boolean\n projectId: string\n}\n\nexport class DownloadBackupCommand extends SanityCommand<typeof DownloadBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to download backup from',\n required: false,\n }),\n }\n\n static override description = 'Download a dataset backup to a local file'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively download a backup',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-1',\n description: 'Download a specific backup for the production dataset',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-2 --out /path/to/file',\n description: 'Download backup to a specific file',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-3 --out /path/to/file --overwrite',\n description: 'Download backup and overwrite existing file',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to download backup from',\n semantics: 'override',\n }),\n 'backup-id': requiredWhenUnattended(\n Flags.string({\n description: 'The backup ID to download',\n }),\n ),\n concurrency: Flags.integer({\n default: DEFAULT_DOWNLOAD_CONCURRENCY,\n description: `Concurrent number of backup item downloads (max: ${MAX_DOWNLOAD_CONCURRENCY})`,\n }),\n out: Flags.string({\n description: 'The file or directory path the backup should download to',\n }),\n overwrite: Flags.boolean({\n default: false,\n description: 'Allows overwriting of existing backup file',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:download']\n\n public async run(): Promise<void> {\n const {args} = await this.parse(DownloadBackupCommand)\n let {dataset} = args\n\n if (this.isUnattended() && !dataset) {\n this.error('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.datasets'}],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (datasets.length === 0) {\n this.error('No datasets found in this project.', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (dataset) {\n assertDatasetExists(datasets, dataset, this.output)\n } else {\n dataset = await promptForDataset({allowCreation: false, datasets})\n }\n\n const opts = await this.prepareBackupOptions(projectId, dataset)\n const outFilePath = path.join(opts.outDir, opts.outFileName)\n\n this.log(\n boxen(\n `Downloading backup for:\n\n${styleText('bold', 'projectId')}: ${styleText('cyan', opts.projectId)}\n${styleText('bold', 'dataset')}: ${styleText('cyan', opts.datasetName)}\n${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`,\n {\n borderColor: 'cyan',\n borderStyle: 'round',\n padding: 1,\n },\n ),\n )\n this.log('')\n this.log(`Downloading backup to \"${styleText('cyan', outFilePath)}\"`)\n\n const start = Date.now()\n const progressSpinner = newProgress('Setting up backup environment...')\n\n // Create a unique temporary directory to store files before bundling them into the archive at outputPath.\n // Temporary directories are normally deleted at the end of backup process, any unexpected exit may leave them\n // behind, hence it is important to create a unique directory for each attempt.\n const tmpOutDir = await mkdtemp(path.join(tmpdir(), `sanity-backup-`))\n\n // Create required directories if they don't exist.\n for (const dir of [\n opts.outDir,\n path.join(tmpOutDir, 'images'),\n path.join(tmpOutDir, 'files'),\n ]) {\n await mkdir(dir, {recursive: true})\n }\n\n backupDownloadDebug('Writing to temporary directory %s', tmpOutDir)\n const tmpOutDocumentsFile = path.join(tmpOutDir, 'data.ndjson')\n\n const docOutStream = createWriteStream(tmpOutDocumentsFile)\n\n try {\n const backupFileStream = new PaginatedGetBackupStream(\n opts.projectId,\n opts.datasetName,\n opts.backupId,\n )\n\n const files: File[] = []\n let i = 0\n for await (const file of backupFileStream) {\n files.push(file)\n i++\n progressSpinner.set({\n current: i,\n step: `Reading backup files...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n }\n\n let totalItemsDownloaded = 0\n await pMap(\n files,\n async (file: File) => {\n if (file.type === 'file' || file.type === 'image') {\n await downloadAsset(file.url, file.name, file.type, tmpOutDir)\n } else {\n const doc = await downloadDocument(file.url)\n docOutStream.write(`${doc}\\n`)\n }\n\n totalItemsDownloaded += 1\n progressSpinner.set({\n current: totalItemsDownloaded,\n step: `Downloading documents and assets...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n },\n {concurrency: opts.concurrency},\n )\n } catch (error) {\n progressSpinner.fail()\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Downloading dataset backup failed: ${message}`, error)\n this.error(`Downloading dataset backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n docOutStream.end()\n await finished(docOutStream)\n\n progressSpinner.set({step: `Archiving files into a tarball...`, update: true})\n try {\n await archiveDir(tmpOutDir, outFilePath, (processedBytes: number) => {\n progressSpinner.update({\n step: `Archiving files into a tarball, ${humanFileSize(processedBytes)} bytes written...`,\n })\n })\n } catch (err) {\n progressSpinner.fail()\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Archiving backup failed: ${message}`, err)\n this.error(`Archiving backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n progressSpinner.set({\n step: `Cleaning up temporary files at ${styleText('cyan', `${tmpOutDir}`)}`,\n })\n await cleanupTmpDir(tmpOutDir)\n\n progressSpinner.set({\n step: `Backup download complete [${prettyMs(Date.now() - start)}]`,\n })\n progressSpinner.succeed()\n }\n\n private async getOutputPath(defaultOutFileName: string): Promise<string> {\n if (this.flags.out !== undefined) {\n // Rewrite the output path to an absolute path, if it is not already.\n return path.resolve(this.flags.out)\n }\n\n const workDir = process.cwd()\n const defaultPath = path.join(workDir, defaultOutFileName)\n if (this.isUnattended()) {\n return defaultPath\n }\n\n const inputResult = await input({\n default: defaultPath,\n message: 'Output path:',\n })\n return path.resolve(inputResult)\n }\n\n private async prepareBackupOptions(\n projectId: string,\n datasetName: string,\n ): Promise<DownloadBackupOptions> {\n const err = validateDatasetName(datasetName)\n if (err) {\n this.error(err, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupId = String(\n this.flags['backup-id'] || (await this.promptForBackupId(projectId, datasetName)),\n )\n\n if (\n 'concurrency' in this.flags &&\n (this.flags.concurrency < 1 || this.flags.concurrency > MAX_DOWNLOAD_CONCURRENCY)\n ) {\n this.error(`\\`--concurrency\\` must be between 1 and ${MAX_DOWNLOAD_CONCURRENCY}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const defaultOutFileName = `${datasetName}-backup-${backupId}.tar.gz`\n let out = await this.getOutputPath(defaultOutFileName)\n\n // If path is a directory name, then add a default file name to the path.\n if (isPathDirName(out)) {\n out = path.join(out, defaultOutFileName)\n }\n\n const exists = await access(out).then(\n () => true,\n () => false,\n )\n // If the file already exists, ask for confirmation if it should be overwritten.\n if (!this.flags.overwrite && exists) {\n if (this.isUnattended()) {\n this.error(`File \"${out}\" already exists. Pass \\`--overwrite\\` to replace it.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n const shouldOverwrite = await confirm({\n default: false,\n message: `File \"${out}\" already exists, would you like to overwrite it?`,\n })\n\n // If the user does not want to overwrite the file, cancel the operation.\n if (!shouldOverwrite) {\n this.error('Operation cancelled.', {exit: exitCodes.USER_ABORT})\n }\n }\n\n return {\n backupId,\n concurrency: this.flags.concurrency || DEFAULT_DOWNLOAD_CONCURRENCY,\n datasetName,\n outDir: path.dirname(out),\n outFileName: path.basename(out),\n overwrite: this.flags.overwrite,\n projectId,\n }\n }\n\n private async promptForBackupId(projectId: string, datasetName: string): Promise<string> {\n const maxBackupIdsShown = 100\n\n try {\n const response = await listBackups({\n datasetName,\n limit: maxBackupIdsShown,\n projectId,\n })\n\n if (!response?.backups?.length) {\n this.error('No backups found', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupIdChoices = response.backups.map((backup: BackupItem) => ({\n name: backup.id,\n value: backup.id,\n }))\n\n const hint =\n backupIdChoices.length === maxBackupIdsShown\n ? ` (only last ${maxBackupIdsShown} shown)`\n : ''\n\n return select({\n choices: backupIdChoices,\n message: `Select backup ID to use${hint}`,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Failed to fetch backups for dataset ${datasetName}: ${message}`, err)\n this.error(`Failed to fetch backups for dataset ${datasetName}: ${message}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["createWriteStream","access","mkdir","mkdtemp","tmpdir","path","finished","styleText","Args","Flags","exitCodes","SanityCommand","requiredWhenUnattended","boxen","confirm","input","select","pMap","prettyMs","archiveDir","assertDatasetExists","backupDownloadDebug","cleanupTmpDir","downloadAsset","downloadDocument","PaginatedGetBackupStream","newProgress","validateDatasetName","promptForDataset","promptForProject","listBackups","listDatasets","humanFileSize","isPathDirName","getProjectIdFlag","DEFAULT_DOWNLOAD_CONCURRENCY","MAX_DOWNLOAD_CONCURRENCY","DownloadBackupCommand","args","dataset","string","description","required","examples","command","flags","semantics","concurrency","integer","default","out","overwrite","boolean","hiddenAliases","run","parse","isUnattended","error","exit","USAGE_ERROR","projectId","getProjectId","fallback","requiredPermissions","grant","permission","datasets","message","Error","String","RUNTIME_ERROR","length","output","allowCreation","opts","prepareBackupOptions","outFilePath","join","outDir","outFileName","log","datasetName","backupId","borderColor","borderStyle","padding","start","Date","now","progressSpinner","tmpOutDir","dir","recursive","tmpOutDocumentsFile","docOutStream","backupFileStream","files","i","file","push","set","current","step","total","totalFiles","update","totalItemsDownloaded","type","url","name","doc","write","fail","end","processedBytes","err","succeed","getOutputPath","defaultOutFileName","undefined","resolve","workDir","process","cwd","defaultPath","inputResult","promptForBackupId","exists","then","shouldOverwrite","USER_ABORT","dirname","basename","maxBackupIdsShown","response","limit","backups","backupIdChoices","map","backup","id","value","hint","choices"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
5
|
+
import { isHttpError } from '@sanity/client';
|
|
6
|
+
import { watchJob } from '../../actions/context/watchJob.js';
|
|
7
|
+
import { formatKeyValue } from '../../actions/debug/output.js';
|
|
8
|
+
import { buildKnowledgeBase, cancelKnowledgeBaseBuild } from '../../services/context.js';
|
|
9
|
+
const buildContextDebug = subdebug('context:build');
|
|
10
|
+
export class BuildKnowledgeBaseCommand extends SanityCommand {
|
|
11
|
+
static args = {
|
|
12
|
+
knowledgeBaseId: Args.string({
|
|
13
|
+
description: 'Knowledge base ID',
|
|
14
|
+
required: true
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
static description = 'Build a knowledge base from its imported content';
|
|
18
|
+
static examples = [
|
|
19
|
+
{
|
|
20
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123',
|
|
21
|
+
description: 'Start a build and return the job ID'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123 --watch',
|
|
25
|
+
description: 'Start a build and wait for it to finish (non-zero exit on failure)'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
command: '<%= config.bin %> <%= command.id %> kb-abc123 --cancel',
|
|
29
|
+
description: 'Cancel the running build, if any'
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
static flags = {
|
|
33
|
+
cancel: Flags.boolean({
|
|
34
|
+
default: false,
|
|
35
|
+
description: 'Cancel the running build instead of starting one',
|
|
36
|
+
exclusive: [
|
|
37
|
+
'watch'
|
|
38
|
+
]
|
|
39
|
+
}),
|
|
40
|
+
watch: Flags.boolean({
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Wait for the build to finish, exiting non-zero if it fails'
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { knowledgeBaseId } = this.args;
|
|
47
|
+
const { cancel, watch } = this.flags;
|
|
48
|
+
if (cancel) {
|
|
49
|
+
await this.cancelBuild(knowledgeBaseId);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let jobId;
|
|
53
|
+
const spin = spinner('Starting build').start();
|
|
54
|
+
try {
|
|
55
|
+
const accepted = await buildKnowledgeBase(knowledgeBaseId);
|
|
56
|
+
jobId = accepted.jobId;
|
|
57
|
+
spin.succeed('Build started');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
spin.fail();
|
|
60
|
+
buildContextDebug('Error starting build', error);
|
|
61
|
+
if (isHttpError(error) && error.statusCode === 404) {
|
|
62
|
+
this.error(`Knowledge base "${knowledgeBaseId}" not found`, {
|
|
63
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
this.error(`Failed to start build: ${getErrorMessage(error)}`, {
|
|
67
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
this.log(formatKeyValue('Job ID', jobId));
|
|
71
|
+
this.log(`Track it with: sanity context jobs get ${knowledgeBaseId} ${jobId}`);
|
|
72
|
+
if (!watch) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let job;
|
|
76
|
+
try {
|
|
77
|
+
job = await watchJob(knowledgeBaseId, jobId, {
|
|
78
|
+
label: 'Building knowledge base',
|
|
79
|
+
successText: 'Build succeeded'
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
buildContextDebug('Error watching build job', error);
|
|
83
|
+
this.error(`Failed to watch build job: ${getErrorMessage(error)}`, {
|
|
84
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (job.status !== 'succeeded') {
|
|
88
|
+
this.error(`Build ${job.status}${job.error ? `: ${job.error}` : ''}`, {
|
|
89
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async cancelBuild(knowledgeBaseId) {
|
|
94
|
+
try {
|
|
95
|
+
const { cancelled } = await cancelKnowledgeBaseBuild(knowledgeBaseId);
|
|
96
|
+
this.log(cancelled ? 'Build cancelled' : 'No running build to cancel');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
buildContextDebug('Error cancelling build', error);
|
|
99
|
+
if (isHttpError(error) && error.statusCode === 404) {
|
|
100
|
+
this.error(`Knowledge base "${knowledgeBaseId}" not found`, {
|
|
101
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
this.error(`Failed to cancel build: ${getErrorMessage(error)}`, {
|
|
105
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/context/build.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {isHttpError} from '@sanity/client'\n\nimport {watchJob} from '../../actions/context/watchJob.js'\nimport {formatKeyValue} from '../../actions/debug/output.js'\nimport {buildKnowledgeBase, cancelKnowledgeBaseBuild} from '../../services/context.js'\n\nconst buildContextDebug = subdebug('context:build')\n\nexport class BuildKnowledgeBaseCommand extends SanityCommand<typeof BuildKnowledgeBaseCommand> {\n static override args = {\n knowledgeBaseId: Args.string({\n description: 'Knowledge base ID',\n required: true,\n }),\n }\n\n static override description = 'Build a knowledge base from its imported content'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123',\n description: 'Start a build and return the job ID',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 --watch',\n description: 'Start a build and wait for it to finish (non-zero exit on failure)',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 --cancel',\n description: 'Cancel the running build, if any',\n },\n ]\n\n static override flags = {\n cancel: Flags.boolean({\n default: false,\n description: 'Cancel the running build instead of starting one',\n exclusive: ['watch'],\n }),\n watch: Flags.boolean({\n default: false,\n description: 'Wait for the build to finish, exiting non-zero if it fails',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {knowledgeBaseId} = this.args\n const {cancel, watch} = this.flags\n\n if (cancel) {\n await this.cancelBuild(knowledgeBaseId)\n return\n }\n\n let jobId: string\n const spin = spinner('Starting build').start()\n try {\n const accepted = await buildKnowledgeBase(knowledgeBaseId)\n jobId = accepted.jobId\n spin.succeed('Build started')\n } catch (error) {\n spin.fail()\n buildContextDebug('Error starting build', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Knowledge base \"${knowledgeBaseId}\" not found`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to start build: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n this.log(formatKeyValue('Job ID', jobId))\n this.log(`Track it with: sanity context jobs get ${knowledgeBaseId} ${jobId}`)\n\n if (!watch) {\n return\n }\n\n let job\n try {\n job = await watchJob(knowledgeBaseId, jobId, {\n label: 'Building knowledge base',\n successText: 'Build succeeded',\n })\n } catch (error) {\n buildContextDebug('Error watching build job', error)\n this.error(`Failed to watch build job: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (job.status !== 'succeeded') {\n this.error(`Build ${job.status}${job.error ? `: ${job.error}` : ''}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n\n private async cancelBuild(knowledgeBaseId: string): Promise<void> {\n try {\n const {cancelled} = await cancelKnowledgeBaseBuild(knowledgeBaseId)\n this.log(cancelled ? 'Build cancelled' : 'No running build to cancel')\n } catch (error) {\n buildContextDebug('Error cancelling build', error)\n if (isHttpError(error) && error.statusCode === 404) {\n this.error(`Knowledge base \"${knowledgeBaseId}\" not found`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n this.error(`Failed to cancel build: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","spinner","isHttpError","watchJob","formatKeyValue","buildKnowledgeBase","cancelKnowledgeBaseBuild","buildContextDebug","BuildKnowledgeBaseCommand","args","knowledgeBaseId","string","description","required","examples","command","flags","cancel","boolean","default","exclusive","watch","run","cancelBuild","jobId","spin","start","accepted","succeed","error","fail","statusCode","exit","RUNTIME_ERROR","log","job","label","successText","status","cancelled"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
|
+
import { requiredWhenUnattended } from '@sanity/cli-core/flags';
|
|
5
|
+
import { input, spinner } from '@sanity/cli-core/ux';
|
|
6
|
+
import { MissingOrganizationError, resolveOrganizationId } from '../../actions/context/resolveOrganizationId.js';
|
|
7
|
+
import { formatKeyValue } from '../../actions/debug/output.js';
|
|
8
|
+
import { createKnowledgeBase } from '../../services/context.js';
|
|
9
|
+
import { getOrganizationFlag } from '../../util/sharedFlags.js';
|
|
10
|
+
import { defineCommandTelemetry } from '../../util/telemetry/commandTelemetry.js';
|
|
11
|
+
const createContextDebug = subdebug('context:create');
|
|
12
|
+
const flags = {
|
|
13
|
+
...getOrganizationFlag({
|
|
14
|
+
description: 'Organization to create the knowledge base in',
|
|
15
|
+
semantics: 'override'
|
|
16
|
+
}),
|
|
17
|
+
description: requiredWhenUnattended(Flags.string({
|
|
18
|
+
description: 'Knowledge base description'
|
|
19
|
+
})),
|
|
20
|
+
title: requiredWhenUnattended(Flags.string({
|
|
21
|
+
description: 'Knowledge base title'
|
|
22
|
+
}))
|
|
23
|
+
};
|
|
24
|
+
export class CreateKnowledgeBaseCommand extends SanityCommand {
|
|
25
|
+
static description = 'Create a knowledge base';
|
|
26
|
+
static examples = [
|
|
27
|
+
{
|
|
28
|
+
command: '<%= config.bin %> <%= command.id %>',
|
|
29
|
+
description: 'Interactively create a knowledge base'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
command: '<%= config.bin %> <%= command.id %> --organization org-abc123 --title "Support docs" --description "Product docs and troubleshooting guides"',
|
|
33
|
+
description: 'Create a knowledge base in a specific organization'
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
static flags = flags;
|
|
37
|
+
static telemetry = defineCommandTelemetry(flags, {
|
|
38
|
+
redact: [
|
|
39
|
+
'title',
|
|
40
|
+
'description'
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
async run() {
|
|
44
|
+
const { description: descriptionFlag, organization, title: titleFlag } = this.flags;
|
|
45
|
+
if (titleFlag !== undefined && titleFlag.trim() === '') {
|
|
46
|
+
this.error('Title cannot be empty', {
|
|
47
|
+
exit: exitCodes.USAGE_ERROR
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (descriptionFlag !== undefined && descriptionFlag.trim() === '') {
|
|
51
|
+
this.error('Description cannot be empty', {
|
|
52
|
+
exit: exitCodes.USAGE_ERROR
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let organizationId;
|
|
56
|
+
try {
|
|
57
|
+
organizationId = await resolveOrganizationId({
|
|
58
|
+
configuredOrganizationId: (await this.tryGetCliConfig()).app?.organizationId,
|
|
59
|
+
flagOrganizationId: organization,
|
|
60
|
+
unattended: this.isUnattended()
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
createContextDebug('Error resolving organization', error);
|
|
64
|
+
this.error(getErrorMessage(error), {
|
|
65
|
+
exit: error instanceof MissingOrganizationError ? exitCodes.USAGE_ERROR : exitCodes.RUNTIME_ERROR
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const title = titleFlag?.trim() || await promptForText('Knowledge base title:');
|
|
69
|
+
const description = descriptionFlag?.trim() || await promptForText('Knowledge base description:');
|
|
70
|
+
const spin = spinner('Creating knowledge base').start();
|
|
71
|
+
try {
|
|
72
|
+
const knowledgeBase = await createKnowledgeBase({
|
|
73
|
+
description,
|
|
74
|
+
organizationId,
|
|
75
|
+
title
|
|
76
|
+
});
|
|
77
|
+
spin.succeed('Knowledge base created');
|
|
78
|
+
const padTo = 12 // "Organization" is the longest key
|
|
79
|
+
;
|
|
80
|
+
this.log(formatKeyValue('ID', knowledgeBase.publicId, {
|
|
81
|
+
padTo
|
|
82
|
+
}));
|
|
83
|
+
this.log(formatKeyValue('Title', knowledgeBase.title, {
|
|
84
|
+
padTo
|
|
85
|
+
}));
|
|
86
|
+
this.log(formatKeyValue('Organization', knowledgeBase.organizationId, {
|
|
87
|
+
padTo
|
|
88
|
+
}));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
spin.fail();
|
|
91
|
+
createContextDebug('Error creating knowledge base', error);
|
|
92
|
+
this.error(`Failed to create knowledge base: ${getErrorMessage(error)}`, {
|
|
93
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function promptForText(message) {
|
|
99
|
+
const value = await input({
|
|
100
|
+
message,
|
|
101
|
+
validate: (input_)=>input_.trim().length > 0 || 'Value cannot be empty'
|
|
102
|
+
});
|
|
103
|
+
return value.trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/context/create.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {input, spinner} from '@sanity/cli-core/ux'\n\nimport {\n MissingOrganizationError,\n resolveOrganizationId,\n} from '../../actions/context/resolveOrganizationId.js'\nimport {formatKeyValue} from '../../actions/debug/output.js'\nimport {createKnowledgeBase} from '../../services/context.js'\nimport {getOrganizationFlag} from '../../util/sharedFlags.js'\nimport {defineCommandTelemetry} from '../../util/telemetry/commandTelemetry.js'\n\nconst createContextDebug = subdebug('context:create')\n\nconst flags = {\n ...getOrganizationFlag({\n description: 'Organization to create the knowledge base in',\n semantics: 'override',\n }),\n description: requiredWhenUnattended(\n Flags.string({\n description: 'Knowledge base description',\n }),\n ),\n title: requiredWhenUnattended(\n Flags.string({\n description: 'Knowledge base title',\n }),\n ),\n} satisfies FlagInput\n\nexport class CreateKnowledgeBaseCommand extends SanityCommand<typeof CreateKnowledgeBaseCommand> {\n static override description = 'Create a knowledge base'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively create a knowledge base',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> --organization org-abc123 --title \"Support docs\" --description \"Product docs and troubleshooting guides\"',\n description: 'Create a knowledge base in a specific organization',\n },\n ]\n\n static override flags = flags\n\n static telemetry = defineCommandTelemetry(flags, {redact: ['title', 'description']})\n\n public async run(): Promise<void> {\n const {description: descriptionFlag, organization, title: titleFlag} = this.flags\n\n if (titleFlag !== undefined && titleFlag.trim() === '') {\n this.error('Title cannot be empty', {exit: exitCodes.USAGE_ERROR})\n }\n if (descriptionFlag !== undefined && descriptionFlag.trim() === '') {\n this.error('Description cannot be empty', {exit: exitCodes.USAGE_ERROR})\n }\n\n let organizationId: string\n try {\n organizationId = await resolveOrganizationId({\n configuredOrganizationId: (await this.tryGetCliConfig()).app?.organizationId,\n flagOrganizationId: organization,\n unattended: this.isUnattended(),\n })\n } catch (error) {\n createContextDebug('Error resolving organization', error)\n this.error(getErrorMessage(error), {\n exit:\n error instanceof MissingOrganizationError\n ? exitCodes.USAGE_ERROR\n : exitCodes.RUNTIME_ERROR,\n })\n }\n\n const title = titleFlag?.trim() || (await promptForText('Knowledge base title:'))\n const description =\n descriptionFlag?.trim() || (await promptForText('Knowledge base description:'))\n\n const spin = spinner('Creating knowledge base').start()\n try {\n const knowledgeBase = await createKnowledgeBase({description, organizationId, title})\n spin.succeed('Knowledge base created')\n const padTo = 12 // \"Organization\" is the longest key\n this.log(formatKeyValue('ID', knowledgeBase.publicId, {padTo}))\n this.log(formatKeyValue('Title', knowledgeBase.title, {padTo}))\n this.log(formatKeyValue('Organization', knowledgeBase.organizationId, {padTo}))\n } catch (error) {\n spin.fail()\n createContextDebug('Error creating knowledge base', error)\n this.error(`Failed to create knowledge base: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n\nasync function promptForText(message: string): Promise<string> {\n const value = await input({\n message,\n validate: (input_) => input_.trim().length > 0 || 'Value cannot be empty',\n })\n return value.trim()\n}\n"],"names":["Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","requiredWhenUnattended","input","spinner","MissingOrganizationError","resolveOrganizationId","formatKeyValue","createKnowledgeBase","getOrganizationFlag","defineCommandTelemetry","createContextDebug","flags","description","semantics","string","title","CreateKnowledgeBaseCommand","examples","command","telemetry","redact","run","descriptionFlag","organization","titleFlag","undefined","trim","error","exit","USAGE_ERROR","organizationId","configuredOrganizationId","tryGetCliConfig","app","flagOrganizationId","unattended","isUnattended","RUNTIME_ERROR","promptForText","spin","start","knowledgeBase","succeed","padTo","log","publicId","fail","message","value","validate","input_","length"],"mappings":"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"}
|