@sanity/cli 8.22.1 → 8.24.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 (98) hide show
  1. package/README.md +313 -0
  2. package/dist/actions/codemods/dashboard.js +136 -8
  3. package/dist/actions/codemods/dashboard.js.map +1 -1
  4. package/dist/actions/codemods/types.js.map +1 -1
  5. package/dist/actions/context/watchJob.js +9 -3
  6. package/dist/actions/context/watchJob.js.map +1 -1
  7. package/dist/actions/deploy/blueprintManaged.js +59 -0
  8. package/dist/actions/deploy/blueprintManaged.js.map +1 -0
  9. package/dist/actions/deploy/deployApp.js +117 -1
  10. package/dist/actions/deploy/deployApp.js.map +1 -1
  11. package/dist/actions/deploy/deployRunner.js.map +1 -1
  12. package/dist/actions/deploy/deployStudio.js +6 -31
  13. package/dist/actions/deploy/deployStudio.js.map +1 -1
  14. package/dist/actions/exec/runScript.worker.js +6 -2
  15. package/dist/actions/exec/runScript.worker.js.map +1 -1
  16. package/dist/actions/init/bootstrapLocalTemplate.js +35 -11
  17. package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
  18. package/dist/actions/init/createMediaLibraryCliConfig.js +12 -0
  19. package/dist/actions/init/createMediaLibraryCliConfig.js.map +1 -0
  20. package/dist/actions/init/determineMediaLibraryTemplate.js +14 -0
  21. package/dist/actions/init/determineMediaLibraryTemplate.js.map +1 -0
  22. package/dist/actions/init/initAction.js +35 -6
  23. package/dist/actions/init/initAction.js.map +1 -1
  24. package/dist/actions/init/initApp.js +81 -6
  25. package/dist/actions/init/initApp.js.map +1 -1
  26. package/dist/actions/init/offerAspectPull.js +81 -0
  27. package/dist/actions/init/offerAspectPull.js.map +1 -0
  28. package/dist/actions/init/project/getOrCreateDataset.js +8 -5
  29. package/dist/actions/init/project/getOrCreateDataset.js.map +1 -1
  30. package/dist/actions/init/project/getOrCreateProject.js +7 -5
  31. package/dist/actions/init/project/getOrCreateProject.js.map +1 -1
  32. package/dist/actions/init/project/getProjectDetails.js +25 -2
  33. package/dist/actions/init/project/getProjectDetails.js.map +1 -1
  34. package/dist/actions/init/project/promptUserForOrganization.js +6 -4
  35. package/dist/actions/init/project/promptUserForOrganization.js.map +1 -1
  36. package/dist/actions/init/templates/index.js +3 -0
  37. package/dist/actions/init/templates/index.js.map +1 -1
  38. package/dist/actions/init/templates/mediaLibraryConfig.js +51 -0
  39. package/dist/actions/init/templates/mediaLibraryConfig.js.map +1 -0
  40. package/dist/actions/media/aspectName.js +8 -0
  41. package/dist/actions/media/aspectName.js.map +1 -0
  42. package/dist/actions/media/aspectSync/contract.js +163 -0
  43. package/dist/actions/media/aspectSync/contract.js.map +1 -0
  44. package/dist/actions/media/aspectSync/syncAspects.js +115 -0
  45. package/dist/actions/media/aspectSync/syncAspects.js.map +1 -0
  46. package/dist/actions/media/cliConfigFields.js +192 -0
  47. package/dist/actions/media/cliConfigFields.js.map +1 -0
  48. package/dist/actions/media/pullAspects.js +225 -0
  49. package/dist/actions/media/pullAspects.js.map +1 -0
  50. package/dist/actions/media/resolveMediaLibraryId.js +83 -0
  51. package/dist/actions/media/resolveMediaLibraryId.js.map +1 -0
  52. package/dist/commands/codemod.js +1 -0
  53. package/dist/commands/codemod.js.map +1 -1
  54. package/dist/commands/context/build.js +2 -2
  55. package/dist/commands/context/build.js.map +1 -1
  56. package/dist/commands/context/entries/get.js +83 -0
  57. package/dist/commands/context/entries/get.js.map +1 -0
  58. package/dist/commands/context/entries/list.js +82 -0
  59. package/dist/commands/context/entries/list.js.map +1 -0
  60. package/dist/commands/context/entries/rebuild.js +95 -0
  61. package/dist/commands/context/entries/rebuild.js.map +1 -0
  62. package/dist/commands/context/issues/apply.js +86 -0
  63. package/dist/commands/context/issues/apply.js.map +1 -0
  64. package/dist/commands/context/issues/dismiss.js +52 -0
  65. package/dist/commands/context/issues/dismiss.js.map +1 -0
  66. package/dist/commands/context/issues/get.js +105 -0
  67. package/dist/commands/context/issues/get.js.map +1 -0
  68. package/dist/commands/context/issues/list.js +128 -0
  69. package/dist/commands/context/issues/list.js.map +1 -0
  70. package/dist/commands/context/issues/reopen.js +53 -0
  71. package/dist/commands/context/issues/reopen.js.map +1 -0
  72. package/dist/commands/context/issues/resolve.js +137 -0
  73. package/dist/commands/context/issues/resolve.js.map +1 -0
  74. package/dist/commands/context/jobs/get.js +2 -2
  75. package/dist/commands/context/jobs/get.js.map +1 -1
  76. package/dist/commands/context/query.js +92 -0
  77. package/dist/commands/context/query.js.map +1 -0
  78. package/dist/commands/deploy.js +13 -0
  79. package/dist/commands/deploy.js.map +1 -1
  80. package/dist/commands/media/prune-aspect-data.js +170 -0
  81. package/dist/commands/media/prune-aspect-data.js.map +1 -0
  82. package/dist/commands/media/pull-aspects.js +309 -0
  83. package/dist/commands/media/pull-aspects.js.map +1 -0
  84. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +17 -0
  85. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  86. package/dist/services/context.js +96 -3
  87. package/dist/services/context.js.map +1 -1
  88. package/dist/services/mediaLibraries.js +175 -8
  89. package/dist/services/mediaLibraries.js.map +1 -1
  90. package/dist/util/searchChoices.js +52 -0
  91. package/dist/util/searchChoices.js.map +1 -0
  92. package/oclif.manifest.json +2134 -1445
  93. package/package.json +9 -8
  94. package/templates/media-library-config/README.md +52 -0
  95. package/templates/media-library-config/eslint.config.txt +52 -0
  96. package/templates/media-library-config/src/fields/caption.tsx +29 -0
  97. package/templates/media-library-config/src/fields/notes.tsx +54 -0
  98. package/templates/shopify/.gitignore +0 -26
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/actions/init/project/getProjectDetails.ts"],"sourcesContent":["import {type SanityOrgUser, subdebug, type TelemetryUserProperties} from '@sanity/cli-core'\nimport {type TelemetryTrace} from '@sanity/telemetry'\n\nimport {listOrganizations, type ProjectOrganization} from '../../../services/organizations.js'\nimport {type InitStepResult} from '../../../telemetry/init.telemetry.js'\nimport {InitError} from '../initError.js'\nimport {type InitContext} from '../types.js'\nimport {getOrCreateDataset} from './getOrCreateDataset.js'\nimport {getOrCreateProject} from './getOrCreateProject.js'\nimport {promptForAppTemplateSetup} from './promptForAppTemplateSetup.js'\nimport {promptUserForOrganization} from './promptUserForOrganization.js'\n\nconst debug = subdebug('init')\n\nexport async function getProjectDetails({\n coupon,\n dataset,\n datasetDefault,\n isAppTemplate,\n newProject,\n organization,\n output,\n planId,\n project,\n showDefaultConfigPrompt,\n trace,\n unattended,\n user,\n visibility,\n}: {\n coupon: string | undefined\n dataset: string | undefined\n datasetDefault: boolean\n isAppTemplate: boolean\n newProject: string | undefined\n organization: string | undefined\n output: InitContext['output']\n planId: string | undefined\n project: string | undefined\n showDefaultConfigPrompt: boolean\n trace: TelemetryTrace<TelemetryUserProperties, InitStepResult>\n unattended: boolean\n user: SanityOrgUser\n visibility: 'private' | 'public' | undefined\n}): Promise<{\n datasetName: string\n displayName: string\n isFirstProject: boolean\n organizationId?: string\n projectId: string\n schemaUrl?: string\n}> {\n if (isAppTemplate) {\n const hasProjectFlag = Boolean(project || newProject)\n let appOrganizationId: string | undefined = organization\n if (!appOrganizationId && !hasProjectFlag) {\n let organizations: ProjectOrganization[]\n try {\n organizations = await listOrganizations()\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n throw new InitError(`Failed to communicate with the Sanity API:\\n${message}`, 1)\n }\n appOrganizationId = await promptUserForOrganization({\n isAppTemplate: true,\n organizations,\n user,\n })\n }\n\n const {\n datasetName: appDatasetName,\n displayName: appDisplayName,\n organizationId: resolvedOrganizationId,\n projectId: appProjectId,\n } = await promptForAppTemplateSetup({\n coupon,\n dataset,\n datasetDefault,\n newProject,\n organization,\n organizationId: appOrganizationId,\n output,\n planId,\n project,\n trace,\n unattended,\n user,\n visibility,\n })\n\n return {\n datasetName: appDatasetName,\n displayName: appDisplayName,\n isFirstProject: false,\n organizationId: resolvedOrganizationId ?? appOrganizationId,\n projectId: appProjectId,\n }\n }\n\n debug('Prompting user to select or create a project')\n const projectResult = await getOrCreateProject({\n coupon,\n newProject,\n organization,\n planId,\n project,\n unattended,\n user,\n })\n debug(`Project with name ${projectResult.displayName} selected`)\n\n debug('Prompting user to select or create a dataset')\n const datasetResult = await getOrCreateDataset({\n dataset,\n defaultConfig: datasetDefault || undefined,\n displayName: projectResult.displayName,\n output,\n projectId: projectResult.projectId,\n showDefaultConfigPrompt,\n unattended,\n visibility,\n })\n debug(`Dataset with name ${datasetResult.datasetName} selected`)\n\n trace.log({\n datasetName: datasetResult.datasetName,\n selectedOption: datasetResult.userAction,\n step: 'createOrSelectDataset',\n visibility,\n })\n\n return {\n datasetName: datasetResult.datasetName,\n displayName: projectResult.displayName,\n isFirstProject: projectResult.isFirstProject,\n organizationId: projectResult.organizationId,\n projectId: projectResult.projectId,\n }\n}\n"],"names":["subdebug","listOrganizations","InitError","getOrCreateDataset","getOrCreateProject","promptForAppTemplateSetup","promptUserForOrganization","debug","getProjectDetails","coupon","dataset","datasetDefault","isAppTemplate","newProject","organization","output","planId","project","showDefaultConfigPrompt","trace","unattended","user","visibility","hasProjectFlag","Boolean","appOrganizationId","organizations","err","message","Error","String","datasetName","appDatasetName","displayName","appDisplayName","organizationId","resolvedOrganizationId","projectId","appProjectId","isFirstProject","projectResult","datasetResult","defaultConfig","undefined","log","selectedOption","userAction","step"],"mappings":"AAAA,SAA4BA,QAAQ,QAAqC,mBAAkB;AAG3F,SAAQC,iBAAiB,QAAiC,qCAAoC;AAE9F,SAAQC,SAAS,QAAO,kBAAiB;AAEzC,SAAQC,kBAAkB,QAAO,0BAAyB;AAC1D,SAAQC,kBAAkB,QAAO,0BAAyB;AAC1D,SAAQC,yBAAyB,QAAO,iCAAgC;AACxE,SAAQC,yBAAyB,QAAO,iCAAgC;AAExE,MAAMC,QAAQP,SAAS;AAEvB,OAAO,eAAeQ,kBAAkB,EACtCC,MAAM,EACNC,OAAO,EACPC,cAAc,EACdC,aAAa,EACbC,UAAU,EACVC,YAAY,EACZC,MAAM,EACNC,MAAM,EACNC,OAAO,EACPC,uBAAuB,EACvBC,KAAK,EACLC,UAAU,EACVC,IAAI,EACJC,UAAU,EAgBX;IAQC,IAAIV,eAAe;QACjB,MAAMW,iBAAiBC,QAAQP,WAAWJ;QAC1C,IAAIY,oBAAwCX;QAC5C,IAAI,CAACW,qBAAqB,CAACF,gBAAgB;YACzC,IAAIG;YACJ,IAAI;gBACFA,gBAAgB,MAAMzB;YACxB,EAAE,OAAO0B,KAAc;gBACrB,MAAMC,UAAUD,eAAeE,QAAQF,IAAIC,OAAO,GAAGE,OAAOH;gBAC5D,MAAM,IAAIzB,UAAU,CAAC,4CAA4C,EAAE0B,SAAS,EAAE;YAChF;YACAH,oBAAoB,MAAMnB,0BAA0B;gBAClDM,eAAe;gBACfc;gBACAL;YACF;QACF;QAEA,MAAM,EACJU,aAAaC,cAAc,EAC3BC,aAAaC,cAAc,EAC3BC,gBAAgBC,sBAAsB,EACtCC,WAAWC,YAAY,EACxB,GAAG,MAAMjC,0BAA0B;YAClCI;YACAC;YACAC;YACAE;YACAC;YACAqB,gBAAgBV;YAChBV;YACAC;YACAC;YACAE;YACAC;YACAC;YACAC;QACF;QAEA,OAAO;YACLS,aAAaC;YACbC,aAAaC;YACbK,gBAAgB;YAChBJ,gBAAgBC,0BAA0BX;YAC1CY,WAAWC;QACb;IACF;IAEA/B,MAAM;IACN,MAAMiC,gBAAgB,MAAMpC,mBAAmB;QAC7CK;QACAI;QACAC;QACAE;QACAC;QACAG;QACAC;IACF;IACAd,MAAM,CAAC,kBAAkB,EAAEiC,cAAcP,WAAW,CAAC,SAAS,CAAC;IAE/D1B,MAAM;IACN,MAAMkC,gBAAgB,MAAMtC,mBAAmB;QAC7CO;QACAgC,eAAe/B,kBAAkBgC;QACjCV,aAAaO,cAAcP,WAAW;QACtClB;QACAsB,WAAWG,cAAcH,SAAS;QAClCnB;QACAE;QACAE;IACF;IACAf,MAAM,CAAC,kBAAkB,EAAEkC,cAAcV,WAAW,CAAC,SAAS,CAAC;IAE/DZ,MAAMyB,GAAG,CAAC;QACRb,aAAaU,cAAcV,WAAW;QACtCc,gBAAgBJ,cAAcK,UAAU;QACxCC,MAAM;QACNzB;IACF;IAEA,OAAO;QACLS,aAAaU,cAAcV,WAAW;QACtCE,aAAaO,cAAcP,WAAW;QACtCM,gBAAgBC,cAAcD,cAAc;QAC5CJ,gBAAgBK,cAAcL,cAAc;QAC5CE,WAAWG,cAAcH,SAAS;IACpC;AACF"}
1
+ {"version":3,"sources":["../../../../src/actions/init/project/getProjectDetails.ts"],"sourcesContent":["import {type SanityOrgUser, subdebug, type TelemetryUserProperties} from '@sanity/cli-core'\nimport {type TelemetryTrace} from '@sanity/telemetry'\n\nimport {listOrganizations, type ProjectOrganization} from '../../../services/organizations.js'\nimport {type InitStepResult} from '../../../telemetry/init.telemetry.js'\nimport {InitError} from '../initError.js'\nimport {type InitContext} from '../types.js'\nimport {getOrCreateDataset} from './getOrCreateDataset.js'\nimport {getOrCreateProject} from './getOrCreateProject.js'\nimport {promptForAppTemplateSetup} from './promptForAppTemplateSetup.js'\nimport {promptUserForOrganization} from './promptUserForOrganization.js'\n\nconst debug = subdebug('init')\n\nexport async function getProjectDetails({\n coupon,\n dataset,\n datasetDefault,\n isAppTemplate,\n isMediaLibraryTemplate = false,\n newProject,\n organization,\n output,\n planId,\n project,\n showDefaultConfigPrompt,\n trace,\n unattended,\n user,\n visibility,\n}: {\n coupon: string | undefined\n dataset: string | undefined\n datasetDefault: boolean\n isAppTemplate: boolean\n isMediaLibraryTemplate?: boolean\n newProject: string | undefined\n organization: string | undefined\n output: InitContext['output']\n planId: string | undefined\n project: string | undefined\n showDefaultConfigPrompt: boolean\n trace: TelemetryTrace<TelemetryUserProperties, InitStepResult>\n unattended: boolean\n user: SanityOrgUser\n visibility: 'private' | 'public' | undefined\n}): Promise<{\n datasetName: string\n displayName: string\n isFirstProject: boolean\n organizationId?: string\n projectId: string\n schemaUrl?: string\n}> {\n if (isAppTemplate) {\n const hasProjectFlag = Boolean(project || newProject)\n let appOrganizationId: string | undefined = organization\n // A media-library config is organization-only, so the org is always\n // resolved here — project flags can't stand in for it.\n if (!appOrganizationId && (isMediaLibraryTemplate || !hasProjectFlag)) {\n let organizations: ProjectOrganization[]\n try {\n organizations = await listOrganizations()\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n throw new InitError(`Failed to communicate with the Sanity API:\\n${message}`, 1)\n }\n appOrganizationId = await promptUserForOrganization({\n isAppTemplate: true,\n organizations,\n user,\n })\n }\n\n // The media-library config scaffold records only `app.organizationId`, so\n // the project/dataset setup below would create or select resources the\n // template throws away — and picking a project from another organization\n // would silently override the chosen org. Skip it entirely.\n if (isMediaLibraryTemplate) {\n if (hasProjectFlag) {\n output.warn(\n 'Project flags are ignored for the media-library-config template — it only records the organization.',\n )\n }\n trace.log({datasetName: '', selectedOption: 'skip', step: 'createOrSelectDataset'})\n return {\n datasetName: '',\n displayName: '',\n isFirstProject: false,\n organizationId: appOrganizationId,\n projectId: '',\n }\n }\n\n const {\n datasetName: appDatasetName,\n displayName: appDisplayName,\n organizationId: resolvedOrganizationId,\n projectId: appProjectId,\n } = await promptForAppTemplateSetup({\n coupon,\n dataset,\n datasetDefault,\n newProject,\n organization,\n organizationId: appOrganizationId,\n output,\n planId,\n project,\n trace,\n unattended,\n user,\n visibility,\n })\n\n return {\n datasetName: appDatasetName,\n displayName: appDisplayName,\n isFirstProject: false,\n organizationId: resolvedOrganizationId ?? appOrganizationId,\n projectId: appProjectId,\n }\n }\n\n debug('Prompting user to select or create a project')\n const projectResult = await getOrCreateProject({\n coupon,\n newProject,\n organization,\n planId,\n project,\n unattended,\n user,\n })\n debug(`Project with name ${projectResult.displayName} selected`)\n\n debug('Prompting user to select or create a dataset')\n const datasetResult = await getOrCreateDataset({\n dataset,\n defaultConfig: datasetDefault || undefined,\n displayName: projectResult.displayName,\n output,\n projectId: projectResult.projectId,\n showDefaultConfigPrompt,\n unattended,\n visibility,\n })\n debug(`Dataset with name ${datasetResult.datasetName} selected`)\n\n trace.log({\n datasetName: datasetResult.datasetName,\n selectedOption: datasetResult.userAction,\n step: 'createOrSelectDataset',\n visibility,\n })\n\n return {\n datasetName: datasetResult.datasetName,\n displayName: projectResult.displayName,\n isFirstProject: projectResult.isFirstProject,\n organizationId: projectResult.organizationId,\n projectId: projectResult.projectId,\n }\n}\n"],"names":["subdebug","listOrganizations","InitError","getOrCreateDataset","getOrCreateProject","promptForAppTemplateSetup","promptUserForOrganization","debug","getProjectDetails","coupon","dataset","datasetDefault","isAppTemplate","isMediaLibraryTemplate","newProject","organization","output","planId","project","showDefaultConfigPrompt","trace","unattended","user","visibility","hasProjectFlag","Boolean","appOrganizationId","organizations","err","message","Error","String","warn","log","datasetName","selectedOption","step","displayName","isFirstProject","organizationId","projectId","appDatasetName","appDisplayName","resolvedOrganizationId","appProjectId","projectResult","datasetResult","defaultConfig","undefined","userAction"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { subdebug } from '@sanity/cli-core';
2
- import { select } from '@sanity/cli-core/ux';
2
+ import { search } from '@sanity/cli-core/ux';
3
+ import { searchChoices, searchTheme } from '../../../util/searchChoices.js';
3
4
  import { findOrganizationByUserName } from '../../organizations/findOrganizationByUserName.js';
4
5
  import { getOrganizationChoices } from '../../organizations/getOrganizationChoices.js';
5
6
  import { getOrganizationsWithAttachGrantInfo } from '../../organizations/getOrganizationsWithAttachGrantInfo.js';
@@ -23,10 +24,11 @@ export async function promptUserForOrganization({ isAppTemplate = false, organiz
23
24
  organizationChoices = getOrganizationChoices(withGrantInfo);
24
25
  defaultOrganizationId = withAttach.length === 1 ? withAttach[0].organization.id : findOrganizationByUserName(organizations, user);
25
26
  }
26
- const chosenOrg = await select({
27
- choices: organizationChoices,
27
+ const chosenOrg = await search({
28
28
  default: defaultOrganizationId || undefined,
29
- message: 'Select organization:'
29
+ message: 'Select organization:',
30
+ source: searchChoices(organizationChoices),
31
+ theme: searchTheme
30
32
  });
31
33
  if (chosenOrg === '-new-') {
32
34
  const newOrganization = await promptUserForNewOrganization(user);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/actions/init/project/promptUserForOrganization.ts"],"sourcesContent":["import {type SanityOrgUser, subdebug} from '@sanity/cli-core'\nimport {select} from '@sanity/cli-core/ux'\n\nimport {type ProjectOrganization} from '../../../services/organizations.js'\nimport {findOrganizationByUserName} from '../../organizations/findOrganizationByUserName.js'\nimport {getOrganizationChoices} from '../../organizations/getOrganizationChoices.js'\nimport {getOrganizationsWithAttachGrantInfo} from '../../organizations/getOrganizationsWithAttachGrantInfo.js'\nimport {type OrganizationChoices} from '../../organizations/types.js'\nimport {promptUserForNewOrganization} from './promptUserForNewOrganization.js'\n\nconst debug = subdebug('init')\n\nexport async function promptUserForOrganization({\n isAppTemplate = false,\n organizations,\n user,\n}: {\n isAppTemplate?: boolean\n organizations: ProjectOrganization[]\n user: SanityOrgUser\n}) {\n if (organizations.length === 0) {\n const newOrganization = await promptUserForNewOrganization(user)\n return newOrganization.id\n }\n\n let organizationChoices: OrganizationChoices\n let defaultOrganizationId: string | undefined\n\n if (isAppTemplate) {\n organizationChoices = getOrganizationChoices(organizations)\n defaultOrganizationId =\n organizations.length === 1\n ? organizations[0].id\n : findOrganizationByUserName(organizations, user)\n } else {\n debug(`User has ${organizations.length} organization(s), checking attach access`)\n const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations)\n const withAttach = withGrantInfo.filter(({hasAttachGrant}) => hasAttachGrant)\n\n debug('User has attach access to %d organizations.', withAttach.length)\n organizationChoices = getOrganizationChoices(withGrantInfo)\n defaultOrganizationId =\n withAttach.length === 1\n ? withAttach[0].organization.id\n : findOrganizationByUserName(organizations, user)\n }\n\n const chosenOrg = await select({\n choices: organizationChoices,\n default: defaultOrganizationId || undefined,\n message: 'Select organization:',\n })\n\n if (chosenOrg === '-new-') {\n const newOrganization = await promptUserForNewOrganization(user)\n return newOrganization.id\n }\n\n return chosenOrg || undefined\n}\n"],"names":["subdebug","select","findOrganizationByUserName","getOrganizationChoices","getOrganizationsWithAttachGrantInfo","promptUserForNewOrganization","debug","promptUserForOrganization","isAppTemplate","organizations","user","length","newOrganization","id","organizationChoices","defaultOrganizationId","withGrantInfo","withAttach","filter","hasAttachGrant","organization","chosenOrg","choices","default","undefined","message"],"mappings":"AAAA,SAA4BA,QAAQ,QAAO,mBAAkB;AAC7D,SAAQC,MAAM,QAAO,sBAAqB;AAG1C,SAAQC,0BAA0B,QAAO,oDAAmD;AAC5F,SAAQC,sBAAsB,QAAO,gDAA+C;AACpF,SAAQC,mCAAmC,QAAO,6DAA4D;AAE9G,SAAQC,4BAA4B,QAAO,oCAAmC;AAE9E,MAAMC,QAAQN,SAAS;AAEvB,OAAO,eAAeO,0BAA0B,EAC9CC,gBAAgB,KAAK,EACrBC,aAAa,EACbC,IAAI,EAKL;IACC,IAAID,cAAcE,MAAM,KAAK,GAAG;QAC9B,MAAMC,kBAAkB,MAAMP,6BAA6BK;QAC3D,OAAOE,gBAAgBC,EAAE;IAC3B;IAEA,IAAIC;IACJ,IAAIC;IAEJ,IAAIP,eAAe;QACjBM,sBAAsBX,uBAAuBM;QAC7CM,wBACEN,cAAcE,MAAM,KAAK,IACrBF,aAAa,CAAC,EAAE,CAACI,EAAE,GACnBX,2BAA2BO,eAAeC;IAClD,OAAO;QACLJ,MAAM,CAAC,SAAS,EAAEG,cAAcE,MAAM,CAAC,wCAAwC,CAAC;QAChF,MAAMK,gBAAgB,MAAMZ,oCAAoCK;QAChE,MAAMQ,aAAaD,cAAcE,MAAM,CAAC,CAAC,EAACC,cAAc,EAAC,GAAKA;QAE9Db,MAAM,+CAA+CW,WAAWN,MAAM;QACtEG,sBAAsBX,uBAAuBa;QAC7CD,wBACEE,WAAWN,MAAM,KAAK,IAClBM,UAAU,CAAC,EAAE,CAACG,YAAY,CAACP,EAAE,GAC7BX,2BAA2BO,eAAeC;IAClD;IAEA,MAAMW,YAAY,MAAMpB,OAAO;QAC7BqB,SAASR;QACTS,SAASR,yBAAyBS;QAClCC,SAAS;IACX;IAEA,IAAIJ,cAAc,SAAS;QACzB,MAAMT,kBAAkB,MAAMP,6BAA6BK;QAC3D,OAAOE,gBAAgBC,EAAE;IAC3B;IAEA,OAAOQ,aAAaG;AACtB"}
1
+ {"version":3,"sources":["../../../../src/actions/init/project/promptUserForOrganization.ts"],"sourcesContent":["import {type SanityOrgUser, subdebug} from '@sanity/cli-core'\nimport {search} from '@sanity/cli-core/ux'\n\nimport {type ProjectOrganization} from '../../../services/organizations.js'\nimport {searchChoices, searchTheme} from '../../../util/searchChoices.js'\nimport {findOrganizationByUserName} from '../../organizations/findOrganizationByUserName.js'\nimport {getOrganizationChoices} from '../../organizations/getOrganizationChoices.js'\nimport {getOrganizationsWithAttachGrantInfo} from '../../organizations/getOrganizationsWithAttachGrantInfo.js'\nimport {type OrganizationChoices} from '../../organizations/types.js'\nimport {promptUserForNewOrganization} from './promptUserForNewOrganization.js'\n\nconst debug = subdebug('init')\n\nexport async function promptUserForOrganization({\n isAppTemplate = false,\n organizations,\n user,\n}: {\n isAppTemplate?: boolean\n organizations: ProjectOrganization[]\n user: SanityOrgUser\n}) {\n if (organizations.length === 0) {\n const newOrganization = await promptUserForNewOrganization(user)\n return newOrganization.id\n }\n\n let organizationChoices: OrganizationChoices\n let defaultOrganizationId: string | undefined\n\n if (isAppTemplate) {\n organizationChoices = getOrganizationChoices(organizations)\n defaultOrganizationId =\n organizations.length === 1\n ? organizations[0].id\n : findOrganizationByUserName(organizations, user)\n } else {\n debug(`User has ${organizations.length} organization(s), checking attach access`)\n const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations)\n const withAttach = withGrantInfo.filter(({hasAttachGrant}) => hasAttachGrant)\n\n debug('User has attach access to %d organizations.', withAttach.length)\n organizationChoices = getOrganizationChoices(withGrantInfo)\n defaultOrganizationId =\n withAttach.length === 1\n ? withAttach[0].organization.id\n : findOrganizationByUserName(organizations, user)\n }\n\n const chosenOrg = await search({\n default: defaultOrganizationId || undefined,\n message: 'Select organization:',\n source: searchChoices(organizationChoices),\n theme: searchTheme,\n })\n\n if (chosenOrg === '-new-') {\n const newOrganization = await promptUserForNewOrganization(user)\n return newOrganization.id\n }\n\n return chosenOrg || undefined\n}\n"],"names":["subdebug","search","searchChoices","searchTheme","findOrganizationByUserName","getOrganizationChoices","getOrganizationsWithAttachGrantInfo","promptUserForNewOrganization","debug","promptUserForOrganization","isAppTemplate","organizations","user","length","newOrganization","id","organizationChoices","defaultOrganizationId","withGrantInfo","withAttach","filter","hasAttachGrant","organization","chosenOrg","default","undefined","message","source","theme"],"mappings":"AAAA,SAA4BA,QAAQ,QAAO,mBAAkB;AAC7D,SAAQC,MAAM,QAAO,sBAAqB;AAG1C,SAAQC,aAAa,EAAEC,WAAW,QAAO,iCAAgC;AACzE,SAAQC,0BAA0B,QAAO,oDAAmD;AAC5F,SAAQC,sBAAsB,QAAO,gDAA+C;AACpF,SAAQC,mCAAmC,QAAO,6DAA4D;AAE9G,SAAQC,4BAA4B,QAAO,oCAAmC;AAE9E,MAAMC,QAAQR,SAAS;AAEvB,OAAO,eAAeS,0BAA0B,EAC9CC,gBAAgB,KAAK,EACrBC,aAAa,EACbC,IAAI,EAKL;IACC,IAAID,cAAcE,MAAM,KAAK,GAAG;QAC9B,MAAMC,kBAAkB,MAAMP,6BAA6BK;QAC3D,OAAOE,gBAAgBC,EAAE;IAC3B;IAEA,IAAIC;IACJ,IAAIC;IAEJ,IAAIP,eAAe;QACjBM,sBAAsBX,uBAAuBM;QAC7CM,wBACEN,cAAcE,MAAM,KAAK,IACrBF,aAAa,CAAC,EAAE,CAACI,EAAE,GACnBX,2BAA2BO,eAAeC;IAClD,OAAO;QACLJ,MAAM,CAAC,SAAS,EAAEG,cAAcE,MAAM,CAAC,wCAAwC,CAAC;QAChF,MAAMK,gBAAgB,MAAMZ,oCAAoCK;QAChE,MAAMQ,aAAaD,cAAcE,MAAM,CAAC,CAAC,EAACC,cAAc,EAAC,GAAKA;QAE9Db,MAAM,+CAA+CW,WAAWN,MAAM;QACtEG,sBAAsBX,uBAAuBa;QAC7CD,wBACEE,WAAWN,MAAM,KAAK,IAClBM,UAAU,CAAC,EAAE,CAACG,YAAY,CAACP,EAAE,GAC7BX,2BAA2BO,eAAeC;IAClD;IAEA,MAAMW,YAAY,MAAMtB,OAAO;QAC7BuB,SAASP,yBAAyBQ;QAClCC,SAAS;QACTC,QAAQzB,cAAcc;QACtBY,OAAOzB;IACT;IAEA,IAAIoB,cAAc,SAAS;QACzB,MAAMT,kBAAkB,MAAMP,6BAA6BK;QAC3D,OAAOE,gBAAgBC,EAAE;IAC3B;IAEA,OAAOQ,aAAaE;AACtB"}
@@ -3,6 +3,7 @@ import appSanityUiTemplate from './appSanityUi.js';
3
3
  import blog from './blog.js';
4
4
  import clean from './clean.js';
5
5
  import getStartedTemplate from './getStarted.js';
6
+ import mediaLibraryConfig from './mediaLibraryConfig.js';
6
7
  import moviedb from './moviedb.js';
7
8
  import pageBuilder from './pageBuilder.js';
8
9
  import quickstart from './quickstart.js';
@@ -14,6 +15,8 @@ const templates = {
14
15
  blog,
15
16
  clean,
16
17
  'get-started': getStartedTemplate,
18
+ // Unlisted (not in `templateChoices`): `--template media-library-config` only.
19
+ 'media-library-config': mediaLibraryConfig,
17
20
  moviedb,
18
21
  'page-builder': pageBuilder,
19
22
  quickstart,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/actions/init/templates/index.ts"],"sourcesContent":["import {type ProjectTemplate} from '../types.js'\nimport appTemplate from './appQuickstart.js'\nimport appSanityUiTemplate from './appSanityUi.js'\nimport blog from './blog.js'\nimport clean from './clean.js'\nimport getStartedTemplate from './getStarted.js'\nimport moviedb from './moviedb.js'\nimport pageBuilder from './pageBuilder.js'\nimport quickstart from './quickstart.js'\nimport shopify from './shopify.js'\nimport shopifyOnline from './shopifyOnline.js'\n\nconst templates: Record<string, ProjectTemplate | undefined> = {\n 'app-quickstart': appTemplate,\n 'app-sanity-ui': appSanityUiTemplate,\n blog,\n clean,\n 'get-started': getStartedTemplate,\n moviedb,\n 'page-builder': pageBuilder,\n quickstart, // empty project that dynamically imports its own schema\n shopify,\n 'shopify-online-storefront': shopifyOnline,\n}\n\nexport default templates\n"],"names":["appTemplate","appSanityUiTemplate","blog","clean","getStartedTemplate","moviedb","pageBuilder","quickstart","shopify","shopifyOnline","templates"],"mappings":"AACA,OAAOA,iBAAiB,qBAAoB;AAC5C,OAAOC,yBAAyB,mBAAkB;AAClD,OAAOC,UAAU,YAAW;AAC5B,OAAOC,WAAW,aAAY;AAC9B,OAAOC,wBAAwB,kBAAiB;AAChD,OAAOC,aAAa,eAAc;AAClC,OAAOC,iBAAiB,mBAAkB;AAC1C,OAAOC,gBAAgB,kBAAiB;AACxC,OAAOC,aAAa,eAAc;AAClC,OAAOC,mBAAmB,qBAAoB;AAE9C,MAAMC,YAAyD;IAC7D,kBAAkBV;IAClB,iBAAiBC;IACjBC;IACAC;IACA,eAAeC;IACfC;IACA,gBAAgBC;IAChBC;IACAC;IACA,6BAA6BC;AAC/B;AAEA,eAAeC,UAAS"}
1
+ {"version":3,"sources":["../../../../src/actions/init/templates/index.ts"],"sourcesContent":["import {type ProjectTemplate} from '../types.js'\nimport appTemplate from './appQuickstart.js'\nimport appSanityUiTemplate from './appSanityUi.js'\nimport blog from './blog.js'\nimport clean from './clean.js'\nimport getStartedTemplate from './getStarted.js'\nimport mediaLibraryConfig from './mediaLibraryConfig.js'\nimport moviedb from './moviedb.js'\nimport pageBuilder from './pageBuilder.js'\nimport quickstart from './quickstart.js'\nimport shopify from './shopify.js'\nimport shopifyOnline from './shopifyOnline.js'\n\nconst templates: Record<string, ProjectTemplate | undefined> = {\n 'app-quickstart': appTemplate,\n 'app-sanity-ui': appSanityUiTemplate,\n blog,\n clean,\n 'get-started': getStartedTemplate,\n // Unlisted (not in `templateChoices`): `--template media-library-config` only.\n 'media-library-config': mediaLibraryConfig,\n moviedb,\n 'page-builder': pageBuilder,\n quickstart, // empty project that dynamically imports its own schema\n shopify,\n 'shopify-online-storefront': shopifyOnline,\n}\n\nexport default templates\n"],"names":["appTemplate","appSanityUiTemplate","blog","clean","getStartedTemplate","mediaLibraryConfig","moviedb","pageBuilder","quickstart","shopify","shopifyOnline","templates"],"mappings":"AACA,OAAOA,iBAAiB,qBAAoB;AAC5C,OAAOC,yBAAyB,mBAAkB;AAClD,OAAOC,UAAU,YAAW;AAC5B,OAAOC,WAAW,aAAY;AAC9B,OAAOC,wBAAwB,kBAAiB;AAChD,OAAOC,wBAAwB,0BAAyB;AACxD,OAAOC,aAAa,eAAc;AAClC,OAAOC,iBAAiB,mBAAkB;AAC1C,OAAOC,gBAAgB,kBAAiB;AACxC,OAAOC,aAAa,eAAc;AAClC,OAAOC,mBAAmB,qBAAoB;AAE9C,MAAMC,YAAyD;IAC7D,kBAAkBX;IAClB,iBAAiBC;IACjBC;IACAC;IACA,eAAeC;IACf,+EAA+E;IAC/E,wBAAwBC;IACxBC;IACA,gBAAgBC;IAChBC;IACAC;IACA,6BAA6BC;AAC/B;AAEA,eAAeC,UAAS"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Media Library config app: field (aspect) definitions for an organization's
3
+ * Media Library, bundled into a federated remote with island-rendered custom
4
+ * inputs. Unlisted — reachable only as `sanity init` with the template flag
5
+ * set to `media-library-config`, not offered in the template prompt.
6
+ *
7
+ * Brings its own dependency set (neither the SDK-app nor the studio base
8
+ * applies): field modules import `sanity` and `@sanity/ui` directly, and the
9
+ * island runtime the build bundles first shipped in `sanity` 6.20.0
10
+ * (`sanity/media-library-island-runtime`), which the range pins as the floor.
11
+ */ const mediaLibraryConfigTemplate = {
12
+ dependencies: {
13
+ // Matches the @sanity/ui major `sanity` itself depends on, so one copy
14
+ // serves both the field modules and the studio dependency tree.
15
+ '@sanity/ui': '^4.4.10',
16
+ // The react floor @sanity/ui 4 and sanity 6.20 peer-require.
17
+ react: '^19.2.2',
18
+ 'react-dom': '^19.2.2',
19
+ sanity: '^6.20.0',
20
+ // Peer of both `sanity` and `@sanity/ui`, imported by @sanity/ui at
21
+ // runtime and bundled into the island build — declared explicitly because
22
+ // installs without auto-installed peers would fail at runtime. (Sanity's
23
+ // own monorepos swap it for the `@sanity/css-in-js` fork via a pnpm
24
+ // override; a scaffolded project has no override, so it needs the real
25
+ // package.)
26
+ 'styled-components': '^6.1.18'
27
+ },
28
+ devDependencies: {
29
+ '@sanity/eslint-config-studio': '^7',
30
+ '@types/react': '^19',
31
+ '@types/react-dom': '^19',
32
+ eslint: '^10',
33
+ // typescript-eslint (via eslint-config-studio) only supports TypeScript up to 6.0.x
34
+ typescript: '~6.0',
35
+ // The scaffolded eslint.config.mjs imports it directly (plugin declaration
36
+ // for the restricted-imports override). Same range eslint-config-studio
37
+ // uses, so npm dedupes to one instance — a second copy would make the
38
+ // plugin redefinition conflict.
39
+ 'typescript-eslint': '^8'
40
+ },
41
+ scripts: {
42
+ build: 'sanity build',
43
+ deploy: 'sanity deploy',
44
+ dev: 'sanity dev'
45
+ },
46
+ type: 'module',
47
+ typescriptOnly: true
48
+ };
49
+ export default mediaLibraryConfigTemplate;
50
+
51
+ //# sourceMappingURL=mediaLibraryConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/actions/init/templates/mediaLibraryConfig.ts"],"sourcesContent":["import {type ProjectTemplate} from '../types.js'\n\n/**\n * Media Library config app: field (aspect) definitions for an organization's\n * Media Library, bundled into a federated remote with island-rendered custom\n * inputs. Unlisted — reachable only as `sanity init` with the template flag\n * set to `media-library-config`, not offered in the template prompt.\n *\n * Brings its own dependency set (neither the SDK-app nor the studio base\n * applies): field modules import `sanity` and `@sanity/ui` directly, and the\n * island runtime the build bundles first shipped in `sanity` 6.20.0\n * (`sanity/media-library-island-runtime`), which the range pins as the floor.\n */\nconst mediaLibraryConfigTemplate: ProjectTemplate = {\n dependencies: {\n // Matches the @sanity/ui major `sanity` itself depends on, so one copy\n // serves both the field modules and the studio dependency tree.\n '@sanity/ui': '^4.4.10',\n // The react floor @sanity/ui 4 and sanity 6.20 peer-require.\n react: '^19.2.2',\n 'react-dom': '^19.2.2',\n sanity: '^6.20.0',\n // Peer of both `sanity` and `@sanity/ui`, imported by @sanity/ui at\n // runtime and bundled into the island build — declared explicitly because\n // installs without auto-installed peers would fail at runtime. (Sanity's\n // own monorepos swap it for the `@sanity/css-in-js` fork via a pnpm\n // override; a scaffolded project has no override, so it needs the real\n // package.)\n 'styled-components': '^6.1.18',\n },\n devDependencies: {\n '@sanity/eslint-config-studio': '^7',\n '@types/react': '^19',\n '@types/react-dom': '^19',\n eslint: '^10',\n // typescript-eslint (via eslint-config-studio) only supports TypeScript up to 6.0.x\n typescript: '~6.0',\n // The scaffolded eslint.config.mjs imports it directly (plugin declaration\n // for the restricted-imports override). Same range eslint-config-studio\n // uses, so npm dedupes to one instance — a second copy would make the\n // plugin redefinition conflict.\n 'typescript-eslint': '^8',\n },\n scripts: {\n build: 'sanity build',\n deploy: 'sanity deploy',\n dev: 'sanity dev',\n },\n type: 'module',\n typescriptOnly: true,\n}\n\nexport default mediaLibraryConfigTemplate\n"],"names":["mediaLibraryConfigTemplate","dependencies","react","sanity","devDependencies","eslint","typescript","scripts","build","deploy","dev","type","typescriptOnly"],"mappings":"AAEA;;;;;;;;;;CAUC,GACD,MAAMA,6BAA8C;IAClDC,cAAc;QACZ,uEAAuE;QACvE,gEAAgE;QAChE,cAAc;QACd,6DAA6D;QAC7DC,OAAO;QACP,aAAa;QACbC,QAAQ;QACR,oEAAoE;QACpE,0EAA0E;QAC1E,yEAAyE;QACzE,oEAAoE;QACpE,uEAAuE;QACvE,YAAY;QACZ,qBAAqB;IACvB;IACAC,iBAAiB;QACf,gCAAgC;QAChC,gBAAgB;QAChB,oBAAoB;QACpBC,QAAQ;QACR,oFAAoF;QACpFC,YAAY;QACZ,2EAA2E;QAC3E,wEAAwE;QACxE,sEAAsE;QACtE,gCAAgC;QAChC,qBAAqB;IACvB;IACAC,SAAS;QACPC,OAAO;QACPC,QAAQ;QACRC,KAAK;IACP;IACAC,MAAM;IACNC,gBAAgB;AAClB;AAEA,eAAeZ,2BAA0B"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The shape an aspect/field name must have — the single source for every
3
+ * consumer. Names double as the stored `sanity.asset.aspect` document `_id`
4
+ * and as the `aspects.<name>` data key on asset documents, and get
5
+ * interpolated into GROQ by tooling, so the same rule guards all three.
6
+ */ export const VALID_ASPECT_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
7
+
8
+ //# sourceMappingURL=aspectName.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/media/aspectName.ts"],"sourcesContent":["/**\n * The shape an aspect/field name must have — the single source for every\n * consumer. Names double as the stored `sanity.asset.aspect` document `_id`\n * and as the `aspects.<name>` data key on asset documents, and get\n * interpolated into GROQ by tooling, so the same rule guards all three.\n */\nexport const VALID_ASPECT_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/\n"],"names":["VALID_ASPECT_NAME"],"mappings":"AAAA;;;;;CAKC,GACD,OAAO,MAAMA,oBAAoB,0BAAyB"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The Media Library configuration lifecycle — pure serialization and mirror
3
+ * semantics, vendored from huey's `@repo/media-library-config` (the package
4
+ * that proved the contract). Keep the two copies behaviorally identical until
5
+ * huey's standalone sync bin is retired in favor of `sanity deploy`.
6
+ *
7
+ * A media library's aspect fields are declared as *field modules* in a config
8
+ * app's `sanity.cli.ts` (`unstable_defineMediaLibrary({fields: [...]})`).
9
+ * Environments that evaluate the config app live (the Workbench) get the full
10
+ * definitions — custom inputs, validation callbacks, initial-value resolvers.
11
+ * Everything else (Studio plugin mode, standalone dashboard, Gradient) reads
12
+ * stored `sanity.asset.aspect` documents instead.
13
+ *
14
+ * This module owns the contract between those two worlds:
15
+ *
16
+ * - **Serialization** (`serializeField`): field modules are live JS, stored
17
+ * aspect documents are JSON. Literal values pass through; functions and
18
+ * component references are stripped deterministically, with every stripped
19
+ * path reported so the capability gap is explicit.
20
+ * - **Mirror semantics** (`computeMirrorDiff`, `buildSyncMutations`): the
21
+ * config app is the source of truth for the aspect namespace. A sync
22
+ * upserts every config field; stored aspect documents without a matching
23
+ * config field are *stale* and are deleted to complete the mirror. Deleting
24
+ * an aspect document removes the definition, not asset data — values remain
25
+ * on assets under `aspects.<name>` and reappear if the field is restored.
26
+ *
27
+ * This module is pure (no I/O); composition with the CLI's API services lives
28
+ * in `./syncAspects.ts`.
29
+ */ import { VALID_ASPECT_NAME } from '../aspectName.js';
30
+ /** The document type stored aspect definitions use. */ export const ASPECT_TYPE = 'sanity.asset.aspect';
31
+ /**
32
+ * Serialize a field module's live `defineField(...)` value into a stored
33
+ * aspect document.
34
+ *
35
+ * The `_id` convention is the field/aspect name, matching the legacy
36
+ * `sanity media deploy-aspect` command (its delete targets `id == name`).
37
+ */ export function serializeField(entry, definition) {
38
+ if (!VALID_ASPECT_NAME.test(entry.name)) {
39
+ throw new Error(`"${entry.name}" is not a valid aspect name (${VALID_ASPECT_NAME}) — it becomes the document _id and the aspects.<name> key on assets`);
40
+ }
41
+ if (!definition || typeof definition !== 'object' || typeof definition.name !== 'string' || typeof definition.type !== 'string') {
42
+ throw new Error(`Field "${entry.name}" must be a defineField(...) value (an object with name and type)`);
43
+ }
44
+ const definitionName = definition.name;
45
+ // A mismatch must not serialize: `entry.name` becomes the document _id
46
+ // (mirror upserts/deletes) while `definition.name` becomes the
47
+ // aspects.<name> data key on assets — a document carrying both would have
48
+ // its data under one name and its lifecycle under another.
49
+ if (definitionName !== entry.name) {
50
+ throw new Error(`Field "${entry.name}": its field module defines name "${definitionName}" — the names must match`);
51
+ }
52
+ const stripped = [];
53
+ const serialized = serializeValue(definition, 'definition', stripped);
54
+ return {
55
+ document: {
56
+ _id: entry.name,
57
+ _type: ASPECT_TYPE,
58
+ definition: serialized,
59
+ // Strict boolean check, mirroring the read-side guard in the Media
60
+ // Library app: `entry` is a runtime value from a config app, and a
61
+ // mistyped `public: 'false'` must not serialize into the stored doc
62
+ // where later Boolean(...) coercion would flip the aspect to public.
63
+ ...typeof entry.public === 'boolean' ? {
64
+ public: entry.public
65
+ } : {}
66
+ },
67
+ stripped
68
+ };
69
+ }
70
+ /**
71
+ * Deterministic strip rule: literal values pass through; functions (initial
72
+ * value resolvers, validation callbacks, input components) and other
73
+ * non-JSON values are dropped with their path recorded. The `components` key
74
+ * is dropped wholesale — it is UI-only and never meaningful in a stored doc.
75
+ *
76
+ * `ancestors` holds the objects on the current recursion path: revisiting one
77
+ * means a cycle, which is stripped like any other non-JSON value (instead of
78
+ * overflowing the stack). Shared acyclic references serialize at every site.
79
+ */ function serializeValue(value, atPath, stripped, ancestors = new Set()) {
80
+ if (value === null) return null;
81
+ const kind = typeof value;
82
+ if (kind === 'string' || kind === 'number' || kind === 'boolean') return value;
83
+ if (kind === 'function' || kind === 'symbol' || kind === 'bigint' || kind === 'undefined') {
84
+ stripped.push(atPath);
85
+ return undefined;
86
+ }
87
+ if (ancestors.has(value)) {
88
+ stripped.push(atPath);
89
+ return undefined;
90
+ }
91
+ if (Array.isArray(value)) {
92
+ ancestors.add(value);
93
+ const out = [];
94
+ for (const [index, item] of value.entries()){
95
+ const serialized = serializeValue(item, `${atPath}[${index}]`, stripped, ancestors);
96
+ if (serialized !== undefined) out.push(serialized);
97
+ }
98
+ ancestors.delete(value);
99
+ return out;
100
+ }
101
+ if (kind === 'object') {
102
+ // React elements / memo components etc. are tagged objects, not plain data
103
+ if (value.$$typeof) {
104
+ stripped.push(atPath);
105
+ return undefined;
106
+ }
107
+ // Dates, RegExps, Maps etc. would walk to `{}` (no enumerable own keys)
108
+ // without ever being reported — strip anything that isn't a plain object
109
+ // so the "every stripped path reported" contract holds.
110
+ const proto = Object.getPrototypeOf(value);
111
+ if (proto !== Object.prototype && proto !== null) {
112
+ stripped.push(atPath);
113
+ return undefined;
114
+ }
115
+ ancestors.add(value);
116
+ const out = {};
117
+ for (const [key, entryValue] of Object.entries(value)){
118
+ if (key === 'components') {
119
+ stripped.push(`${atPath}.components`);
120
+ continue;
121
+ }
122
+ const serialized = serializeValue(entryValue, `${atPath}.${key}`, stripped, ancestors);
123
+ if (serialized !== undefined) out[key] = serialized;
124
+ }
125
+ ancestors.delete(value);
126
+ return out;
127
+ }
128
+ stripped.push(atPath);
129
+ return undefined;
130
+ }
131
+ /**
132
+ * Mirror semantics: given the documents a config declares and the ids stored
133
+ * in the library, compute which stored documents are stale (present in the
134
+ * library, absent from the config) and must be deleted for the library to
135
+ * mirror the config.
136
+ */ export function computeMirrorDiff(configDocuments, storedIds) {
137
+ const configIds = new Set(configDocuments.map((doc)=>doc._id));
138
+ return {
139
+ staleIds: storedIds.filter((id)=>!configIds.has(id))
140
+ };
141
+ }
142
+ /**
143
+ * Build the mutation transaction for one sync: upsert every config document,
144
+ * delete every stale stored document. The delete shape matches the CLI's
145
+ * `deleteAspect` (`sanity media delete-aspect`).
146
+ */ export function buildSyncMutations(configDocuments, staleIds) {
147
+ return [
148
+ ...configDocuments.map((document)=>({
149
+ createOrReplace: document
150
+ })),
151
+ ...staleIds.map((id)=>({
152
+ delete: {
153
+ params: {
154
+ id,
155
+ type: ASPECT_TYPE
156
+ },
157
+ query: `*[_type == $type && _id == $id]`
158
+ }
159
+ }))
160
+ ];
161
+ }
162
+
163
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/actions/media/aspectSync/contract.ts"],"sourcesContent":["/**\n * The Media Library configuration lifecycle — pure serialization and mirror\n * semantics, vendored from huey's `@repo/media-library-config` (the package\n * that proved the contract). Keep the two copies behaviorally identical until\n * huey's standalone sync bin is retired in favor of `sanity deploy`.\n *\n * A media library's aspect fields are declared as *field modules* in a config\n * app's `sanity.cli.ts` (`unstable_defineMediaLibrary({fields: [...]})`).\n * Environments that evaluate the config app live (the Workbench) get the full\n * definitions — custom inputs, validation callbacks, initial-value resolvers.\n * Everything else (Studio plugin mode, standalone dashboard, Gradient) reads\n * stored `sanity.asset.aspect` documents instead.\n *\n * This module owns the contract between those two worlds:\n *\n * - **Serialization** (`serializeField`): field modules are live JS, stored\n * aspect documents are JSON. Literal values pass through; functions and\n * component references are stripped deterministically, with every stripped\n * path reported so the capability gap is explicit.\n * - **Mirror semantics** (`computeMirrorDiff`, `buildSyncMutations`): the\n * config app is the source of truth for the aspect namespace. A sync\n * upserts every config field; stored aspect documents without a matching\n * config field are *stale* and are deleted to complete the mirror. Deleting\n * an aspect document removes the definition, not asset data — values remain\n * on assets under `aspects.<name>` and reappear if the field is restored.\n *\n * This module is pure (no I/O); composition with the CLI's API services lives\n * in `./syncAspects.ts`.\n */\n\nimport {VALID_ASPECT_NAME} from '../aspectName.js'\n\n/** The document type stored aspect definitions use. */\nexport const ASPECT_TYPE = 'sanity.asset.aspect'\n\n/**\n * A field entry as declared in a media-library config app's `sanity.cli.ts`.\n */\nexport interface FieldEntry {\n name: string\n src: string\n\n public?: boolean\n title?: string\n}\n\n/**\n * The `sanity.asset.aspect` *write payload* produced by a sync.\n *\n * This is deliberately not `MediaLibraryAssetAspectDocument` from\n * `@sanity/types` — that is the read-side type: it carries required\n * server-assigned `_rev`/`_createdAt`/`_updatedAt` (which a to-be-written\n * document does not have) and types `definition` as a full `FieldDefinition`,\n * which the serializer's stripped-JSON output is only guaranteed to be after\n * schema validation. Read-side documents can be passed to\n * {@link computeMirrorDiff} (it only needs ids), but upserts via\n * {@link buildSyncMutations} require this write shape — echoing read-side\n * server metadata back into a `createOrReplace` would be a bug.\n */\nexport interface AspectDocument {\n _id: string\n _type: typeof ASPECT_TYPE\n definition: Record<string, unknown>\n\n public?: boolean\n}\n\n/**\n * Result of serializing a live field definition into a stored document.\n */\nexport interface SerializedField {\n document: AspectDocument\n /** Dot-paths of values dropped because they are not JSON-serializable. */\n stripped: string[]\n}\n\n/** One mutation in a sync transaction (see {@link buildSyncMutations}). */\nexport type SyncMutation =\n | {createOrReplace: AspectDocument}\n | {delete: {params: {id: string; type: string}; query: string}}\n\n/**\n * Serialize a field module's live `defineField(...)` value into a stored\n * aspect document.\n *\n * The `_id` convention is the field/aspect name, matching the legacy\n * `sanity media deploy-aspect` command (its delete targets `id == name`).\n */\nexport function serializeField(entry: FieldEntry, definition: unknown): SerializedField {\n if (!VALID_ASPECT_NAME.test(entry.name)) {\n throw new Error(\n `\"${entry.name}\" is not a valid aspect name (${VALID_ASPECT_NAME}) — it becomes the document _id and the aspects.<name> key on assets`,\n )\n }\n if (\n !definition ||\n typeof definition !== 'object' ||\n typeof (definition as {name?: unknown}).name !== 'string' ||\n typeof (definition as {type?: unknown}).type !== 'string'\n ) {\n throw new Error(\n `Field \"${entry.name}\" must be a defineField(...) value (an object with name and type)`,\n )\n }\n\n const definitionName = (definition as {name: string}).name\n // A mismatch must not serialize: `entry.name` becomes the document _id\n // (mirror upserts/deletes) while `definition.name` becomes the\n // aspects.<name> data key on assets — a document carrying both would have\n // its data under one name and its lifecycle under another.\n if (definitionName !== entry.name) {\n throw new Error(\n `Field \"${entry.name}\": its field module defines name \"${definitionName}\" — the names must match`,\n )\n }\n\n const stripped: string[] = []\n const serialized = serializeValue(definition, 'definition', stripped) as Record<string, unknown>\n\n return {\n document: {\n _id: entry.name,\n _type: ASPECT_TYPE,\n definition: serialized,\n // Strict boolean check, mirroring the read-side guard in the Media\n // Library app: `entry` is a runtime value from a config app, and a\n // mistyped `public: 'false'` must not serialize into the stored doc\n // where later Boolean(...) coercion would flip the aspect to public.\n ...(typeof entry.public === 'boolean' ? {public: entry.public} : {}),\n },\n stripped,\n }\n}\n\n/**\n * Deterministic strip rule: literal values pass through; functions (initial\n * value resolvers, validation callbacks, input components) and other\n * non-JSON values are dropped with their path recorded. The `components` key\n * is dropped wholesale — it is UI-only and never meaningful in a stored doc.\n *\n * `ancestors` holds the objects on the current recursion path: revisiting one\n * means a cycle, which is stripped like any other non-JSON value (instead of\n * overflowing the stack). Shared acyclic references serialize at every site.\n */\nfunction serializeValue(\n value: unknown,\n atPath: string,\n stripped: string[],\n ancestors: Set<object> = new Set(),\n): unknown {\n if (value === null) return null\n const kind = typeof value\n if (kind === 'string' || kind === 'number' || kind === 'boolean') return value\n if (kind === 'function' || kind === 'symbol' || kind === 'bigint' || kind === 'undefined') {\n stripped.push(atPath)\n return undefined\n }\n if (ancestors.has(value as object)) {\n stripped.push(atPath)\n return undefined\n }\n if (Array.isArray(value)) {\n ancestors.add(value)\n const out: unknown[] = []\n for (const [index, item] of value.entries()) {\n const serialized = serializeValue(item, `${atPath}[${index}]`, stripped, ancestors)\n if (serialized !== undefined) out.push(serialized)\n }\n ancestors.delete(value)\n return out\n }\n if (kind === 'object') {\n // React elements / memo components etc. are tagged objects, not plain data\n if ((value as {$$typeof?: unknown}).$$typeof) {\n stripped.push(atPath)\n return undefined\n }\n // Dates, RegExps, Maps etc. would walk to `{}` (no enumerable own keys)\n // without ever being reported — strip anything that isn't a plain object\n // so the \"every stripped path reported\" contract holds.\n const proto = Object.getPrototypeOf(value) as object | null\n if (proto !== Object.prototype && proto !== null) {\n stripped.push(atPath)\n return undefined\n }\n ancestors.add(value as object)\n const out: Record<string, unknown> = {}\n for (const [key, entryValue] of Object.entries(value as Record<string, unknown>)) {\n if (key === 'components') {\n stripped.push(`${atPath}.components`)\n continue\n }\n const serialized = serializeValue(entryValue, `${atPath}.${key}`, stripped, ancestors)\n if (serialized !== undefined) out[key] = serialized\n }\n ancestors.delete(value as object)\n return out\n }\n stripped.push(atPath)\n return undefined\n}\n\n/**\n * Mirror semantics: given the documents a config declares and the ids stored\n * in the library, compute which stored documents are stale (present in the\n * library, absent from the config) and must be deleted for the library to\n * mirror the config.\n */\nexport function computeMirrorDiff(\n configDocuments: readonly {_id: string}[],\n storedIds: readonly string[],\n): {staleIds: string[]} {\n const configIds = new Set(configDocuments.map((doc) => doc._id))\n return {staleIds: storedIds.filter((id) => !configIds.has(id))}\n}\n\n/**\n * Build the mutation transaction for one sync: upsert every config document,\n * delete every stale stored document. The delete shape matches the CLI's\n * `deleteAspect` (`sanity media delete-aspect`).\n */\nexport function buildSyncMutations(\n configDocuments: readonly AspectDocument[],\n staleIds: readonly string[],\n): SyncMutation[] {\n return [\n ...configDocuments.map((document) => ({createOrReplace: document})),\n ...staleIds.map((id) => ({\n delete: {\n params: {id, type: ASPECT_TYPE},\n query: `*[_type == $type && _id == $id]`,\n },\n })),\n ]\n}\n"],"names":["VALID_ASPECT_NAME","ASPECT_TYPE","serializeField","entry","definition","test","name","Error","type","definitionName","stripped","serialized","serializeValue","document","_id","_type","public","value","atPath","ancestors","Set","kind","push","undefined","has","Array","isArray","add","out","index","item","entries","delete","$$typeof","proto","Object","getPrototypeOf","prototype","key","entryValue","computeMirrorDiff","configDocuments","storedIds","configIds","map","doc","staleIds","filter","id","buildSyncMutations","createOrReplace","params","query"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Composition of the pure aspect-sync contract (`./contract.ts`) with the
3
+ * CLI's Media Library API services, for `sanity deploy` of a media-library
4
+ * config app.
5
+ *
6
+ * Split into a read-only **plan** and a mutating **apply** so the deploy flow
7
+ * can report the plan — including the deletion list — in both dry-run and
8
+ * real mode before anything is written:
9
+ *
10
+ * - {@link planAspectSync}: evaluate + serialize + validate every config
11
+ * field module, resolve the target media library, and diff against the
12
+ * stored aspect ids. No mutations.
13
+ * - {@link applyAspectSync}: apply the plan's upserts and deletes as one
14
+ * mutation transaction.
15
+ */ import path from 'node:path';
16
+ import { exitCodes, importModule } from '@sanity/cli-core';
17
+ import { validateMediaLibraryAssetAspect } from '@sanity/schema/_internal';
18
+ import { getStoredAspectIds, mutateAspects } from '../../../services/mediaLibraries.js';
19
+ import { resolveMediaLibrary } from '../resolveMediaLibraryId.js';
20
+ import { buildSyncMutations, computeMirrorDiff, serializeField } from './contract.js';
21
+ /**
22
+ * Build the sync plan: evaluate, serialize, and schema-validate every field
23
+ * module before touching the network, then resolve the target library and
24
+ * diff against its stored aspect ids. Read-only.
25
+ */ export async function planAspectSync(options) {
26
+ const { fields, mediaLibraryId: explicitMediaLibraryId, organizationId, workDir } = options;
27
+ // Evaluate + serialize + validate every field module before touching the
28
+ // network, collecting all failures so one broken module doesn't hide the next.
29
+ const documents = [];
30
+ const strippedByField = {};
31
+ const failures = [];
32
+ for (const entry of fields){
33
+ try {
34
+ // jsx: fields may be .tsx modules (custom input components) — the
35
+ // template ships one — and jiti does not parse JSX by default.
36
+ const definition = await importModule(path.resolve(workDir, entry.src), {
37
+ jsx: true
38
+ });
39
+ const { document, stripped } = serializeField(entry, definition);
40
+ const [valid, validationErrors] = validateMediaLibraryAssetAspect(document.definition);
41
+ if (!valid) {
42
+ const messages = validationErrors.flat().map((problem)=>problem.message);
43
+ failures.push(`${entry.name}: schema validation failed: ${messages.join('; ')}`);
44
+ continue;
45
+ }
46
+ documents.push(document);
47
+ if (stripped.length > 0) strippedByField[entry.name] = stripped;
48
+ } catch (error) {
49
+ failures.push(`${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ }
52
+ if (failures.length > 0) {
53
+ return {
54
+ failure: {
55
+ exitCode: exitCodes.USAGE_ERROR,
56
+ message: `${failures.length === 1 ? 'A config field' : `${failures.length} config fields`} cannot sync to stored aspect documents:\n${failures.map((failure)=>` - ${failure}`).join('\n')}`,
57
+ solution: 'Fix the field modules listed above, then deploy again'
58
+ }
59
+ };
60
+ }
61
+ const library = await resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId);
62
+ if ('failure' in library) return library;
63
+ const storedIds = await getStoredAspectIds(library.mediaLibraryId);
64
+ const { staleIds } = computeMirrorDiff(documents, storedIds);
65
+ return {
66
+ plan: {
67
+ documents,
68
+ mediaLibraryId: library.mediaLibraryId,
69
+ staleIds,
70
+ strippedByField
71
+ }
72
+ };
73
+ }
74
+ /**
75
+ * Apply the plan as one mutation transaction: upsert every config document,
76
+ * delete every stale stored document. Definitions only — asset data under
77
+ * `aspects.<name>` is never touched.
78
+ */ export async function applyAspectSync(plan) {
79
+ await mutateAspects({
80
+ mediaLibraryId: plan.mediaLibraryId,
81
+ mutations: buildSyncMutations(plan.documents, plan.staleIds)
82
+ });
83
+ }
84
+ /**
85
+ * Shares the one resolution rule (`resolveMediaLibrary`) with the media
86
+ * commands, mapped to deploy-check failures. Ambiguity always fails here —
87
+ * deploy can run unattended (CI), so it never prompts, and a sync that
88
+ * guesses the target would mirror the config into the wrong library. Misuse
89
+ * (a bad flag value, a missing required flag) carries the usage exit code;
90
+ * a missing library is an environment problem and keeps the default.
91
+ */ async function resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId) {
92
+ const resolution = await resolveMediaLibrary({
93
+ mediaLibraryIdFlag: explicitMediaLibraryId,
94
+ organizationId
95
+ });
96
+ if (resolution.type === 'resolved') {
97
+ return {
98
+ mediaLibraryId: resolution.mediaLibraryId
99
+ };
100
+ }
101
+ // Deploy-specific wording over the shared resolution's generic remedies:
102
+ // the install matters *before deploying the config*, and deploy never
103
+ // prompts, so the ambiguity message says the sync needs an explicit target.
104
+ return {
105
+ failure: {
106
+ ...resolution.type === 'none-active' ? {} : {
107
+ exitCode: exitCodes.USAGE_ERROR
108
+ },
109
+ message: resolution.type === 'ambiguous' ? `${resolution.message} — the sync needs an explicit target` : resolution.message,
110
+ solution: resolution.type === 'none-active' ? 'Install the Media Library for the organization before deploying its config' : resolution.solution
111
+ }
112
+ };
113
+ }
114
+
115
+ //# sourceMappingURL=syncAspects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/actions/media/aspectSync/syncAspects.ts"],"sourcesContent":["/**\n * Composition of the pure aspect-sync contract (`./contract.ts`) with the\n * CLI's Media Library API services, for `sanity deploy` of a media-library\n * config app.\n *\n * Split into a read-only **plan** and a mutating **apply** so the deploy flow\n * can report the plan — including the deletion list — in both dry-run and\n * real mode before anything is written:\n *\n * - {@link planAspectSync}: evaluate + serialize + validate every config\n * field module, resolve the target media library, and diff against the\n * stored aspect ids. No mutations.\n * - {@link applyAspectSync}: apply the plan's upserts and deletes as one\n * mutation transaction.\n */\n\nimport path from 'node:path'\n\nimport {exitCodes, importModule} from '@sanity/cli-core'\nimport {validateMediaLibraryAssetAspect} from '@sanity/schema/_internal'\n\nimport {getStoredAspectIds, mutateAspects} from '../../../services/mediaLibraries.js'\nimport {resolveMediaLibrary} from '../resolveMediaLibraryId.js'\nimport {\n type AspectDocument,\n buildSyncMutations,\n computeMirrorDiff,\n type FieldEntry,\n serializeField,\n} from './contract.js'\n\n/** The read-only sync plan `sanity deploy` reports before mutating. */\nexport interface AspectSyncPlan {\n /** The documents the sync upserts, one per config field. */\n documents: AspectDocument[]\n mediaLibraryId: string\n /** Stored aspect ids with no matching config field — deleted by the sync. */\n staleIds: string[]\n /** Per-field dot-paths stripped during serialization, for fields that had any. */\n strippedByField: Record<string, string[]>\n}\n\ninterface AspectSyncFailure {\n message: string\n\n /** Exit code a real deploy uses for this failure; defaults to 1. */\n exitCode?: number\n solution?: string\n}\n\nexport type AspectSyncPlanResult = {failure: AspectSyncFailure} | {plan: AspectSyncPlan}\n\n/**\n * Build the sync plan: evaluate, serialize, and schema-validate every field\n * module before touching the network, then resolve the target library and\n * diff against its stored aspect ids. Read-only.\n */\nexport async function planAspectSync(options: {\n fields: readonly FieldEntry[]\n mediaLibraryId?: string\n organizationId: string\n workDir: string\n}): Promise<AspectSyncPlanResult> {\n const {fields, mediaLibraryId: explicitMediaLibraryId, organizationId, workDir} = options\n\n // Evaluate + serialize + validate every field module before touching the\n // network, collecting all failures so one broken module doesn't hide the next.\n const documents: AspectDocument[] = []\n const strippedByField: Record<string, string[]> = {}\n const failures: string[] = []\n\n for (const entry of fields) {\n try {\n // jsx: fields may be .tsx modules (custom input components) — the\n // template ships one — and jiti does not parse JSX by default.\n const definition = await importModule(path.resolve(workDir, entry.src), {jsx: true})\n const {document, stripped} = serializeField(entry, definition)\n\n const [valid, validationErrors] = validateMediaLibraryAssetAspect(document.definition)\n if (!valid) {\n const messages = validationErrors.flat().map((problem) => problem.message)\n failures.push(`${entry.name}: schema validation failed: ${messages.join('; ')}`)\n continue\n }\n\n documents.push(document)\n if (stripped.length > 0) strippedByField[entry.name] = stripped\n } catch (error) {\n failures.push(`${entry.name}: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n if (failures.length > 0) {\n return {\n failure: {\n exitCode: exitCodes.USAGE_ERROR,\n message: `${failures.length === 1 ? 'A config field' : `${failures.length} config fields`} cannot sync to stored aspect documents:\\n${failures.map((failure) => ` - ${failure}`).join('\\n')}`,\n solution: 'Fix the field modules listed above, then deploy again',\n },\n }\n }\n\n const library = await resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId)\n if ('failure' in library) return library\n\n const storedIds = await getStoredAspectIds(library.mediaLibraryId)\n const {staleIds} = computeMirrorDiff(documents, storedIds)\n\n return {plan: {documents, mediaLibraryId: library.mediaLibraryId, staleIds, strippedByField}}\n}\n\n/**\n * Apply the plan as one mutation transaction: upsert every config document,\n * delete every stale stored document. Definitions only — asset data under\n * `aspects.<name>` is never touched.\n */\nexport async function applyAspectSync(plan: AspectSyncPlan): Promise<void> {\n await mutateAspects({\n mediaLibraryId: plan.mediaLibraryId,\n mutations: buildSyncMutations(plan.documents, plan.staleIds),\n })\n}\n\n/**\n * Shares the one resolution rule (`resolveMediaLibrary`) with the media\n * commands, mapped to deploy-check failures. Ambiguity always fails here —\n * deploy can run unattended (CI), so it never prompts, and a sync that\n * guesses the target would mirror the config into the wrong library. Misuse\n * (a bad flag value, a missing required flag) carries the usage exit code;\n * a missing library is an environment problem and keeps the default.\n */\nasync function resolveTargetMediaLibrary(\n organizationId: string,\n explicitMediaLibraryId: string | undefined,\n): Promise<{failure: AspectSyncFailure} | {mediaLibraryId: string}> {\n const resolution = await resolveMediaLibrary({\n mediaLibraryIdFlag: explicitMediaLibraryId,\n organizationId,\n })\n if (resolution.type === 'resolved') {\n return {mediaLibraryId: resolution.mediaLibraryId}\n }\n // Deploy-specific wording over the shared resolution's generic remedies:\n // the install matters *before deploying the config*, and deploy never\n // prompts, so the ambiguity message says the sync needs an explicit target.\n return {\n failure: {\n ...(resolution.type === 'none-active' ? {} : {exitCode: exitCodes.USAGE_ERROR}),\n message:\n resolution.type === 'ambiguous'\n ? `${resolution.message} — the sync needs an explicit target`\n : resolution.message,\n solution:\n resolution.type === 'none-active'\n ? 'Install the Media Library for the organization before deploying its config'\n : resolution.solution,\n },\n }\n}\n"],"names":["path","exitCodes","importModule","validateMediaLibraryAssetAspect","getStoredAspectIds","mutateAspects","resolveMediaLibrary","buildSyncMutations","computeMirrorDiff","serializeField","planAspectSync","options","fields","mediaLibraryId","explicitMediaLibraryId","organizationId","workDir","documents","strippedByField","failures","entry","definition","resolve","src","jsx","document","stripped","valid","validationErrors","messages","flat","map","problem","message","push","name","join","length","error","Error","String","failure","exitCode","USAGE_ERROR","solution","library","resolveTargetMediaLibrary","storedIds","staleIds","plan","applyAspectSync","mutations","resolution","mediaLibraryIdFlag","type"],"mappings":"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"}