sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
155 lines (138 loc) • 5.32 kB
text/typescript
import {type CliCommandContext} from '@sanity/cli'
import chalk from 'chalk'
import uniq from 'lodash/uniq'
import {isDefined} from '../../../manifest/manifestTypeHelpers'
import {type SchemaStoreActionResult, type SchemaStoreContext} from './schemaStoreTypes'
import {createManifestExtractor, ensureManifestExtractSatisfied} from './utils/mainfestExtractor'
import {createManifestReader} from './utils/manifestReader'
import {createSchemaApiClient} from './utils/schemaApiClient'
import {getDatasetsOutString, getStringList} from './utils/schemaStoreOutStrings'
import {
filterLogReadProjectIdMismatch,
parseDeleteSchemasConfig,
type SchemaStoreCommonFlags,
} from './utils/schemaStoreValidation'
export interface DeleteSchemaFlags extends SchemaStoreCommonFlags {
ids?: string
dataset?: string
}
interface DeleteResult {
dataset: string
schemaId: string
deleted: boolean
}
class DeleteIdError extends Error {
public id: string
public dataset: string
constructor(id: string, dataset: string, options?: ErrorOptions) {
super((options?.cause as {message?: string})?.message, options)
this.name = 'DeleteIdError'
this.id = id
this.dataset = dataset
}
}
export default function deleteSchemasActionForCommand(
flags: DeleteSchemaFlags,
context: CliCommandContext,
): Promise<SchemaStoreActionResult> {
return deleteSchemaAction(flags, {
...context,
manifestExtractor: createManifestExtractor(context),
})
}
/**
* Deletes all stored schemas matching --ids in workspace datasets.
*
* Workspaces are determined by on-disk manifest file – not directly from sanity.config.
* All schema store actions require a manifest to exist, so we regenerate it by default.
* Manifest generation can be optionally disabled with --no-manifest-extract.
* In this case the command uses and existing file or throws when missing.
*/
export async function deleteSchemaAction(
flags: DeleteSchemaFlags,
context: SchemaStoreContext,
): Promise<SchemaStoreActionResult> {
const {ids, dataset, extractManifest, manifestDir, verbose} = parseDeleteSchemasConfig(
flags,
context,
)
const {output, apiClient, jsonReader, manifestExtractor} = context
// prettier-ignore
if (!(await ensureManifestExtractSatisfied({schemaRequired: true, extractManifest, manifestDir, manifestExtractor, output,}))) {
return 'failure'
}
const {client, projectId} = createSchemaApiClient(apiClient)
const manifest = await createManifestReader({manifestDir, output, jsonReader}).getManifest()
const workspaces = manifest.workspaces
.filter((workspace) => !dataset || workspace.dataset === dataset)
.filter((workspace) => filterLogReadProjectIdMismatch(workspace, projectId, output))
const datasets = uniq(workspaces.map((w) => w.dataset))
const results = await Promise.allSettled(
datasets.flatMap((targetDataset: string) => {
return ids.map(async ({schemaId}): Promise<DeleteResult> => {
try {
const deletedSchema = await client.withConfig({dataset: targetDataset}).delete(schemaId)
return {dataset: targetDataset, schemaId, deleted: deletedSchema.results.length}
} catch (err) {
throw new DeleteIdError(schemaId, targetDataset, {cause: err})
}
})
}),
)
const deletedIds = results
.filter((r): r is PromiseFulfilledResult<DeleteResult> => r.status === 'fulfilled')
.filter((r) => r.value.deleted)
.map((r) => r.value)
const notFound = uniq(
results
.filter((r): r is PromiseFulfilledResult<DeleteResult> => r.status === 'fulfilled')
.filter((r) => !r.value.deleted)
.filter((r) => !deletedIds.map(({schemaId}) => schemaId).includes(r.value.schemaId))
.map((r) => r.value.schemaId),
)
const deleteFailureIds = uniq(
results
.filter((r) => r.status === 'rejected')
.map((result) => {
const error = result.reason
if (error instanceof DeleteIdError) {
output.error(
chalk.red(
`Failed to delete schema "${error.id}" in dataset "${error.dataset}":\n${error.message}`,
),
)
if (verbose) output.error(error)
return error.id
}
//hubris inc: given the try-catch wrapping the full promise "this should never happen"
throw error
}),
)
const success = deletedIds.length === ids.length
if (success) {
output.success(`Successfully deleted ${deletedIds.length}/${ids.length} schemas`)
} else {
output.error(
[
`Deleted ${deletedIds.length}/${ids.length} schemas.`,
deletedIds.length
? `Successfully deleted ids:\n${deletedIds
.map(
({schemaId, dataset: targetDataset}) =>
`- "${schemaId}" (in ${getDatasetsOutString([targetDataset])})`,
)
.join('\n')}`
: undefined,
notFound.length
? `Ids not found in ${getDatasetsOutString(datasets)}:\n${getStringList(notFound)}`
: undefined,
...(deleteFailureIds.length
? [`Failed to delete ids:\n${getStringList(deleteFailureIds)}`, 'Check logs for errors.']
: []),
]
.filter(isDefined)
.join('\n'),
)
}
return success ? 'success' : 'failure'
}