@sanity/cli 8.7.0 → 8.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +396 -0
  2. package/dist/actions/api/distillApiRoutes.js +1 -1
  3. package/dist/actions/api/distillApiRoutes.js.map +1 -1
  4. package/dist/actions/api/resolveEndpoint.js +1 -1
  5. package/dist/actions/api/resolveEndpoint.js.map +1 -1
  6. package/dist/actions/backup/archiveDir.js +7 -3
  7. package/dist/actions/backup/archiveDir.js.map +1 -1
  8. package/dist/actions/context/resolveOrganizationId.js +17 -0
  9. package/dist/actions/context/resolveOrganizationId.js.map +1 -0
  10. package/dist/actions/context/waitForJob.js +24 -0
  11. package/dist/actions/context/waitForJob.js.map +1 -0
  12. package/dist/actions/context/watchJob.js +32 -0
  13. package/dist/actions/context/watchJob.js.map +1 -0
  14. package/dist/actions/deploy/deployApp.js +6 -6
  15. package/dist/actions/deploy/deployApp.js.map +1 -1
  16. package/dist/actions/deploy/deployStudio.js +6 -6
  17. package/dist/actions/deploy/deployStudio.js.map +1 -1
  18. package/dist/actions/exec/execScript.js +6 -18
  19. package/dist/actions/exec/execScript.js.map +1 -1
  20. package/dist/actions/exec/runScript.worker.js +21 -0
  21. package/dist/actions/exec/runScript.worker.js.map +1 -0
  22. package/dist/actions/init/initAction.js +1 -1
  23. package/dist/actions/init/initAction.js.map +1 -1
  24. package/dist/actions/media/importMedia.js +4 -2
  25. package/dist/actions/media/importMedia.js.map +1 -1
  26. package/dist/commands/backups/download.js +8 -17
  27. package/dist/commands/backups/download.js.map +1 -1
  28. package/dist/commands/context/build.js +111 -0
  29. package/dist/commands/context/build.js.map +1 -0
  30. package/dist/commands/context/create.js +106 -0
  31. package/dist/commands/context/create.js.map +1 -0
  32. package/dist/commands/context/delete.js +68 -0
  33. package/dist/commands/context/delete.js.map +1 -0
  34. package/dist/commands/context/get.js +95 -0
  35. package/dist/commands/context/get.js.map +1 -0
  36. package/dist/commands/context/imports/create.js +270 -0
  37. package/dist/commands/context/imports/create.js.map +1 -0
  38. package/dist/commands/context/imports/delete.js +73 -0
  39. package/dist/commands/context/imports/delete.js.map +1 -0
  40. package/dist/commands/context/imports/download.js +65 -0
  41. package/dist/commands/context/imports/download.js.map +1 -0
  42. package/dist/commands/context/imports/get.js +93 -0
  43. package/dist/commands/context/imports/get.js.map +1 -0
  44. package/dist/commands/context/imports/list.js +105 -0
  45. package/dist/commands/context/imports/list.js.map +1 -0
  46. package/dist/commands/context/jobs/get.js +90 -0
  47. package/dist/commands/context/jobs/get.js.map +1 -0
  48. package/dist/commands/context/list.js +109 -0
  49. package/dist/commands/context/list.js.map +1 -0
  50. package/dist/commands/context/refresh.js +46 -0
  51. package/dist/commands/context/refresh.js.map +1 -0
  52. package/dist/commands/context/update.js +127 -0
  53. package/dist/commands/context/update.js.map +1 -0
  54. package/dist/commands/datasets/alias/delete.js +4 -9
  55. package/dist/commands/datasets/alias/delete.js.map +1 -1
  56. package/dist/commands/datasets/alias/unlink.js +4 -9
  57. package/dist/commands/datasets/alias/unlink.js.map +1 -1
  58. package/dist/commands/datasets/delete.js +4 -9
  59. package/dist/commands/datasets/delete.js.map +1 -1
  60. package/dist/commands/graphql/undeploy.js +4 -9
  61. package/dist/commands/graphql/undeploy.js.map +1 -1
  62. package/dist/commands/init.js +1 -1
  63. package/dist/commands/init.js.map +1 -1
  64. package/dist/commands/media/create-aspect.js +5 -6
  65. package/dist/commands/media/create-aspect.js.map +1 -1
  66. package/dist/commands/media/delete-aspect.js +7 -23
  67. package/dist/commands/media/delete-aspect.js.map +1 -1
  68. package/dist/commands/media/deploy-aspect.js +4 -9
  69. package/dist/commands/media/deploy-aspect.js.map +1 -1
  70. package/dist/commands/media/export.js +3 -7
  71. package/dist/commands/media/export.js.map +1 -1
  72. package/dist/commands/media/import.js +3 -7
  73. package/dist/commands/media/import.js.map +1 -1
  74. package/dist/commands/organizations/create.js +4 -9
  75. package/dist/commands/organizations/create.js.map +1 -1
  76. package/dist/commands/schemas/delete.js +3 -8
  77. package/dist/commands/schemas/delete.js.map +1 -1
  78. package/dist/commands/tokens/delete.js +4 -7
  79. package/dist/commands/tokens/delete.js.map +1 -1
  80. package/dist/commands/users/invite.js +8 -18
  81. package/dist/commands/users/invite.js.map +1 -1
  82. package/dist/exports/index.js +1 -1
  83. package/dist/exports/index.js.map +1 -1
  84. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +18 -0
  85. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  86. package/dist/generated/apiRoutes.js +11 -9
  87. package/dist/generated/apiRoutes.js.map +1 -1
  88. package/dist/prompts/promptForOrganization.js +19 -0
  89. package/dist/prompts/promptForOrganization.js.map +1 -0
  90. package/dist/services/context.js +132 -0
  91. package/dist/services/context.js.map +1 -0
  92. package/dist/util/sharedFlags.js +24 -0
  93. package/dist/util/sharedFlags.js.map +1 -1
  94. package/oclif.config.js +1 -0
  95. package/oclif.manifest.json +1931 -1133
  96. package/package.json +16 -22
@@ -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 tar from 'tar-fs';
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, tar.extract(outputPath));
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":"AAAA,SAAQA,UAAU,QAAO,cAAa;AACtC,SAAQC,gBAAgB,QAAO,UAAS;AACxC,OAAOC,MAAKC,OAAO,QAAO,mBAAkB;AAC5C,SAAQC,MAAM,QAAO,UAAS;AAC9B,OAAOC,UAAU,YAAW;AAC5B,SAAQC,IAAI,QAAO,wBAAuB;AAC1C,SAAQC,QAAQ,QAAO,uBAAsB;AAC7C,SAAQC,SAAS,QAAO,YAAW;AAKnC,OAAOC,iBAAiB,eAAc;AACtC,0DAA0D;AAC1D,OAAOC,UAAU,cAAa;AAC9B,SACEC,UAAU,EACVC,KAAK,EACLC,MAAM,EACNC,IAAI,EACJC,GAAG,EACHC,QAAQ,EACRC,SAAS,EAETC,EAAE,EAEFC,SAAS,EACTC,GAAG,EACHC,GAAG,QACE,OAAM;AACb,OAAOC,SAAS,SAAQ;AACxB,SAAQC,IAAI,QAAO,aAAY;AAE/B,SAAQC,KAAK,QAAO,sBAAqB;AACzC,SAAQC,gBAAgB,QAAO,wBAAuB;AACtD,SAAQC,gBAAgB,QAAO,wBAAuB;AAEtD,MAAMC,sBAAsB;AA4D5B,OAAO,SAASC,SAASC,OAAgB;IACvC,OAAOC,cAAcD,SAASE,IAAI,CAChCf,SAAS,CAAC,EAACgB,iBAAiB,EAAEC,KAAK,EAAEC,MAAM,EAAEC,WAAW,EAAC;QACvD,MAAMC,YAAYH,MAAMI,MAAM,GAAGH,OAAOG,MAAM;QAE9C,IAAID,cAAc,GAAG;YACnB,MAAM,IAAIE,MAAM;QAClB;QAEA,mEAAmE;QACnE,6DAA6D;QAC7D,MAAMC,sBAAsBd,iBAC1BxB,iBAAiB+B,oBACjB,YACA;QAGF,OAAOlB,KAAKyB,qBAAqBR,IAAI,CACnCf,SAAS,CAACwB;YACR,MAAMC,UAAmB;gBACvB,GAAGZ,OAAO;gBACVW;gBACAL;YACF;YAEA,OAAOrB,KAAKmB,OAAOF,IAAI,CACrBZ,UAAU,CAACuB,OAASrB,IAAIH,GAAW,SAASA,GAAGwB,SAC/CzB,UAAUH,KAAKoB,QAAQH,IAAI,CAACZ,UAAU,CAACuB,OAASrB,IAAIH,GAAY,UAAUA,GAAGwB,WAC7EC,oBAAoBF,UACpBG,YAAYH,UACZI,kBAAkBJ,UAClBK,WAAWL,UACX1B,IAAI,CAACgC,QAAW,CAAA;oBACdA;oBACAX;gBACF,CAAA;QAEJ;IAEJ;AAEJ;AAEA;;CAEC,GACD,OAAO,SAASN,cAAc,EAACkB,UAAU,EAA8B;IAMrE,OAAOlC,KAAKZ,GAAG+C,IAAI,CAACD,aAAajB,IAAI,CACnCZ,UAAU,CAAC+B;QACT,OAAOA,MAAMC,WAAW,KACpBjC,GAAG8B,cACHlC,KAAKX,QAAQE,KAAK+C,IAAI,CAAChD,UAAU,iCAAiC2B,IAAI,CACpEZ,UAAU,CAACkC;YACT,OAAOvC,KACLP,SAASN,iBAAiB+C,aAAavC,eAAe6C,WAAWD,YACjEtB,IAAI,CAAChB,IAAI,IAAMsC;QACnB;IAER,IACAlC,UAAU,CAACoC;QACT,OAAOzC,KACLS,KAAK;YAAC;SAAiB,EAAE;YACvBiC,UAAU;YACVC,KAAKF;YACLG,MAAM;QACR,IACA3B,IAAI,CACJhB,IAAI,CAAC,CAACiB,kBAAkB,GAAM,CAAA;gBAC5BA;gBACAuB;gBACApB,aACEH,sBAAsB2B,YAAYJ,mBAAmBlD,KAAKuD,OAAO,CAAC5B;YACtE,CAAA;IAEJ,IACAZ,IAAI,CAAC,EAACY,iBAAiB,EAAEuB,gBAAgB,EAAC;QACxC,IAAIvB,sBAAsB2B,WAAW;YACnC,MAAM,IAAIrB,MACR,CAAC,GAAG,EAAE9B,UAAU,QAAQ,eAAe,6BAA6B,EAAEA,UAAU,QAAQ+C,mBAAmB;QAE/G;QACA7B,iBAAiB,CAAC,oBAAoB,EAAEM,mBAAmB;IAC7D,IACAb,UAAU,CAAC,EAACa,iBAAiB,EAAEG,WAAW,EAAC;QACzC,OAAOrB,KACL+C,QAAQC,GAAG,CAAC;YACVvC,KAAK;gBAAC;aAAU,EAAE;gBAChBkC,KAAKtB;YACP;YACAZ,KAAK;gBAAC;aAAW,EAAE;gBACjBkC,KAAKtB;YACP;SACD,GACDJ,IAAI,CACJhB,IAAI,CAAC,CAACkB,OAAOC,OAAO,GAAM,CAAA;gBACxBF;gBACAC;gBACAC;gBACAC;YACF,CAAA;IAEJ;AAEJ;AAEA;;;;CAIC,GACD,SAASmB,WAAWS,UAAkB;IACpC,0DAA0D;IAC1D,OAAOrD,KAAK;QAACsD,WAAW;QAAKC,SAAS;IAAK,GAAG,CAACC,MAAMC;QACnD,IAAI3C,MAAM0C,OAAO;YACf,OAAOC,KAAK,MAAM7C,IAAI8C,OAAO,CAACL;QAChC;QAEA,OAAOI,KAAK;IACd;AACF;AAEA;;;;;CAKC,GACD,SAASE,kBAAkB,EACzBC,MAAM,EACNC,IAAI,EAIL;IACC,OAAOpD,UAAU,CAACqD,OAChBF,OAAOG,UAAU,CACdC,KAAK,CACJ,CAAC;;;;;;aAMI,CAAC,EACN;YACEF;YACAD,MAAM;gBAAC;gBAAU,GAAGA,KAAK,KAAK,CAAC;aAAC,CAACnB,IAAI,CAAC;QACxC,GACA;YACEuB,KAAK;QACP,GAED5C,IAAI,CAACZ,UAAU,CAACyD,WAAavD,IAAIH,GAAGsD,OAAOtD,GAAG0D;AAErD;AAEA,SAASjC,oBAAoB,EAC3B2B,MAAM,EACNnC,WAAW,EACH;IAIR,OAAOnB,SAAS,CAAC,CAACuD,MAAMxB,MAAM;QAC5B,MAAM8B,iBAAiB7E,WAAW;QAElC,MAAM8E,WAAWxE,KACfL,iBAAiBI,KAAK+C,IAAI,CAACjB,aAAaY,QAAQhB,IAAI,CAAC8C,gBAAgBE,WAAW,CAAC;QAGnF,OAAOjE,KAAKgE,UAAU/C,IAAI,CACxBX,IAAI,CAACoD,OACH9C,iBAAiB,CAAC,OAAO,EAAEqB,MAAM,eAAe,EAAEwB,KAAK,iBAAiB,EAAEC,MAAM,IAElFH,kBAAkB;YAACC;YAAQC;QAAI,IAC/BxD,IAGE,CAAC,CAACyD,MAAMI,SAAS;YACjB,IAAIA,SAASvC,MAAM,KAAK,GAAG;gBACzB,OAAO;oBAACkC;oBAAMxB;oBAAOyB;iBAAK;YAC5B;YAEA,OAAO;gBACLI;gBACAI,iBAAiB;gBACjBC,kBAAkBlC;gBAClBmC,UAAUV;YACZ;QACF;IAEJ;AACF;AAEA;;;;CAIC,GACD,SAAS3B,kBAAkB,EACzBL,YAAY,EACJ;IACR,OAAOzB,IAAI,CAACoE,gBAAmB,CAAA;YAC7B,GAAGA,aAAa;YAChBC,SAAS5C,aAAa6C,GAAG,CAACF,cAAcF,gBAAgB;QAC1D,CAAA;AACF;AAEA,sDAAsD;AACtD,OAAO,SAASnC,WAAW,EACzBwB,MAAM,EACNgB,cAAc,EAC6B;IAI3C,OAAOtE,SAAS,CAAC+B;QACf,MAAM,EAACqC,OAAO,EAAER,QAAQ,EAAEI,eAAe,EAAC,GAAGjC;QAE7C,IAAIiC,mBAAmB,CAACM,gBAAgB;YACtC5D,iBAAiB,CAAC,OAAO,EAAEqB,MAAMkC,gBAAgB,CAAC,0CAA0C,CAAC;YAC7F,OAAO/D,GAAG6B;QACZ;QAEA,IAAIqC,YAAYzB,WAAW;YACzBjC,iBAAiB,CAAC,OAAO,EAAEqB,MAAMkC,gBAAgB,CAAC,sBAAsB,CAAC;YACzE,OAAO/D,GAAG6B;QACZ;QAEA,MAAMwC,cAAcjB,OAAOG,UAAU,CAACc,WAAW;QACjD,KAAK,MAAMC,WAAWZ,SAAU;YAC9BW,YAAYE,KAAK,CAACD,SAAS;gBAACE,KAAK;oBAACN;gBAAO;YAAC;QAC5C;QAEA1D,iBACE,CAAC,OAAO,EAAEqB,MAAMkC,gBAAgB,CAAC,qCAAqC,EAAEU,KAAKC,SAAS,CAAChB,WAAW;QAGpG,OAAOW,YACJM,MAAM,CAAC;YACNlB,KAAK;YACLmB,YAAY;QACd,GACC/D,IAAI,CAAChB,IAAI,IAAMgC;IACpB,GAAGpB;AACL;AAEA,SAASiB,YAAY,EACnB0B,MAAM,EACNnC,WAAW,EACH;IAIR,OAAOnB,SAAS,CAAC+E;QACf,IAAI,cAAcA,oBAAoB;YACpCrE,iBACE,CAAC,OAAO,EAAEqE,mBAAmBd,gBAAgB,CAAC,8CAA8C,EAAEc,mBAAmBb,QAAQ,EAAE;YAE7H,OAAOhE,GAAG6E;QACZ;QAEA,MAAM,CAACxB,MAAMxB,OAAOyB,KAAK,GAAGuB;QAC5BrE,iBAAiB,CAAC,OAAO,EAAEqB,MAAM,qBAAqB,CAAC;QAEvD,OAAOuB,OAAOG,UAAU,CAACuB,MAAM,CAC5BC,MAAM,CAAC1B,MAAMtE,iBAAiBI,KAAK+C,IAAI,CAACjB,aAAaY,SAAS;YAC7D4B,KAAK;QACP,GACC5C,IAAI,CACHpB,WAAW,CAACuF;YACV,oGAAoG;YACpG,EAAE;YACF,qBAAqB;YACrB,8EAA8E;YAC9E,uFAAuF;YACvF,IAAIA,MAAMC,UAAU,KAAK,KAAK;gBAC5BzE,iBACE,CAAC,OAAO,EAAEqB,MAAM,4BAA4B,EAAEwB,KAAK,iBAAiB,EAAEC,MAAM;gBAE9E,OAAO5D;YACT;YACA,OAAOA;QACT,IACAC,OAAO,CAACuF,WAAaA,SAAS7B,IAAI,KAAK,aACvCnD,IAAI,IAAMM,iBAAiB,CAAC,OAAO,EAAEqB,MAAM,8BAA8B,CAAC,IAC1E,sLAAsL;QACtLhC,IAAI,CAACqF,WAAa,AAACA,SAAmDC,IAAI,GAC1EtF,IAA6C,CAACuF,SAAY,CAAA;gBACxD1B,UAAU;oBAAC0B,OAAOvD,KAAK,CAACwD,GAAG;iBAAC;gBAC5BvB,iBAAiB;gBACjBC,kBAAkBlC;gBAClBmC,UAAUV;YACZ,CAAA;IAEN,GAAG7C;AACL"}
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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"}
@@ -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, flags } = await this.parse(DownloadBackupCommand);
81
+ const { args } = await this.parse(DownloadBackupCommand);
82
82
  let { dataset } = args;
83
- if (this.isUnattended()) {
84
- const errors = [];
85
- if (!dataset) {
86
- errors.push('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.');
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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"}
@@ -0,0 +1,68 @@
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 { confirm } from '@sanity/cli-core/ux';
5
+ import { isHttpError } from '@sanity/client';
6
+ import { deleteKnowledgeBase } from '../../services/context.js';
7
+ const deleteContextDebug = subdebug('context:delete');
8
+ export class DeleteKnowledgeBaseCommand extends SanityCommand {
9
+ static args = {
10
+ knowledgeBaseId: Args.string({
11
+ description: 'Knowledge base ID',
12
+ required: true
13
+ })
14
+ };
15
+ static description = 'Delete a knowledge base and its generated content';
16
+ static examples = [
17
+ {
18
+ command: '<%= config.bin %> <%= command.id %> kb-abc123',
19
+ description: 'Delete a knowledge base after confirmation'
20
+ },
21
+ {
22
+ command: '<%= config.bin %> <%= command.id %> kb-abc123 --yes',
23
+ description: 'Delete a knowledge base without confirmation prompt'
24
+ }
25
+ ];
26
+ static flags = {
27
+ yes: Flags.boolean({
28
+ char: 'y',
29
+ default: false,
30
+ description: 'Skip confirmation prompt (unattended mode)'
31
+ })
32
+ };
33
+ async run() {
34
+ const { knowledgeBaseId } = this.args;
35
+ const { yes: skipConfirmation } = this.flags;
36
+ if (this.isUnattended() && !skipConfirmation) {
37
+ this.error('Deletion requires confirmation. Pass `--yes` to delete the knowledge base.', {
38
+ exit: exitCodes.USAGE_ERROR
39
+ });
40
+ }
41
+ if (!skipConfirmation) {
42
+ const confirmed = await confirm({
43
+ default: false,
44
+ message: `Delete knowledge base "${knowledgeBaseId}"? This permanently deletes its generated content.`
45
+ });
46
+ if (!confirmed) {
47
+ this.log('Knowledge base not deleted');
48
+ this.exit(exitCodes.USER_ABORT);
49
+ }
50
+ }
51
+ try {
52
+ await deleteKnowledgeBase(knowledgeBaseId);
53
+ this.log('Knowledge base deleted');
54
+ } catch (error) {
55
+ deleteContextDebug('Error deleting knowledge base', error);
56
+ if (isHttpError(error) && error.statusCode === 404) {
57
+ this.error(`Knowledge base "${knowledgeBaseId}" not found`, {
58
+ exit: exitCodes.RUNTIME_ERROR
59
+ });
60
+ }
61
+ this.error(`Failed to delete knowledge base: ${getErrorMessage(error)}`, {
62
+ exit: exitCodes.RUNTIME_ERROR
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ //# sourceMappingURL=delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/context/delete.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 {confirm} from '@sanity/cli-core/ux'\nimport {isHttpError} from '@sanity/client'\n\nimport {deleteKnowledgeBase} from '../../services/context.js'\n\nconst deleteContextDebug = subdebug('context:delete')\n\nexport class DeleteKnowledgeBaseCommand extends SanityCommand<typeof DeleteKnowledgeBaseCommand> {\n static override args = {\n knowledgeBaseId: Args.string({\n description: 'Knowledge base ID',\n required: true,\n }),\n }\n\n static override description = 'Delete a knowledge base and its generated content'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123',\n description: 'Delete a knowledge base after confirmation',\n },\n {\n command: '<%= config.bin %> <%= command.id %> kb-abc123 --yes',\n description: 'Delete a knowledge base without confirmation prompt',\n },\n ]\n\n static override flags = {\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skip confirmation prompt (unattended mode)',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {knowledgeBaseId} = this.args\n const {yes: skipConfirmation} = this.flags\n\n if (this.isUnattended() && !skipConfirmation) {\n this.error('Deletion requires confirmation. Pass `--yes` to delete the knowledge base.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n if (!skipConfirmation) {\n const confirmed = await confirm({\n default: false,\n message: `Delete knowledge base \"${knowledgeBaseId}\"? This permanently deletes its generated content.`,\n })\n\n if (!confirmed) {\n this.log('Knowledge base not deleted')\n this.exit(exitCodes.USER_ABORT)\n }\n }\n\n try {\n await deleteKnowledgeBase(knowledgeBaseId)\n this.log('Knowledge base deleted')\n } catch (error) {\n deleteContextDebug('Error deleting knowledge base', 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 delete knowledge base: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","confirm","isHttpError","deleteKnowledgeBase","deleteContextDebug","DeleteKnowledgeBaseCommand","args","knowledgeBaseId","string","description","required","examples","command","flags","yes","boolean","char","default","run","skipConfirmation","isUnattended","error","exit","USAGE_ERROR","confirmed","message","log","USER_ABORT","statusCode","RUNTIME_ERROR"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,KAAK,QAAO,cAAa;AAEvC,SAAQC,SAAS,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACnE,SAAQC,eAAe,QAAO,0BAAyB;AACvD,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,WAAW,QAAO,iBAAgB;AAE1C,SAAQC,mBAAmB,QAAO,4BAA2B;AAE7D,MAAMC,qBAAqBL,SAAS;AAEpC,OAAO,MAAMM,mCAAmCP;IAC9C,OAAgBQ,OAAO;QACrBC,iBAAiBZ,KAAKa,MAAM,CAAC;YAC3BC,aAAa;YACbC,UAAU;QACZ;IACF,EAAC;IAED,OAAgBD,cAAc,oDAAmD;IAEjF,OAAgBE,WAAW;QACzB;YACEC,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;KACD,CAAA;IAED,OAAgBI,QAAQ;QACtBC,KAAKlB,MAAMmB,OAAO,CAAC;YACjBC,MAAM;YACNC,SAAS;YACTR,aAAa;QACf;IACF,EAAqB;IAErB,MAAaS,MAAqB;QAChC,MAAM,EAACX,eAAe,EAAC,GAAG,IAAI,CAACD,IAAI;QACnC,MAAM,EAACQ,KAAKK,gBAAgB,EAAC,GAAG,IAAI,CAACN,KAAK;QAE1C,IAAI,IAAI,CAACO,YAAY,MAAM,CAACD,kBAAkB;YAC5C,IAAI,CAACE,KAAK,CAAC,8EAA8E;gBACvFC,MAAMzB,UAAU0B,WAAW;YAC7B;QACF;QAEA,IAAI,CAACJ,kBAAkB;YACrB,MAAMK,YAAY,MAAMvB,QAAQ;gBAC9BgB,SAAS;gBACTQ,SAAS,CAAC,uBAAuB,EAAElB,gBAAgB,kDAAkD,CAAC;YACxG;YAEA,IAAI,CAACiB,WAAW;gBACd,IAAI,CAACE,GAAG,CAAC;gBACT,IAAI,CAACJ,IAAI,CAACzB,UAAU8B,UAAU;YAChC;QACF;QAEA,IAAI;YACF,MAAMxB,oBAAoBI;YAC1B,IAAI,CAACmB,GAAG,CAAC;QACX,EAAE,OAAOL,OAAO;YACdjB,mBAAmB,iCAAiCiB;YACpD,IAAInB,YAAYmB,UAAUA,MAAMO,UAAU,KAAK,KAAK;gBAClD,IAAI,CAACP,KAAK,CAAC,CAAC,gBAAgB,EAAEd,gBAAgB,WAAW,CAAC,EAAE;oBAC1De,MAAMzB,UAAUgC,aAAa;gBAC/B;YACF;YACA,IAAI,CAACR,KAAK,CAAC,CAAC,iCAAiC,EAAErB,gBAAgBqB,QAAQ,EAAE;gBACvEC,MAAMzB,UAAUgC,aAAa;YAC/B;QACF;IACF;AACF"}