@sanity/cli 6.3.0 → 6.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -442
- package/dist/actions/build/decorateIndexWithStagingScript.js +16 -0
- package/dist/actions/build/decorateIndexWithStagingScript.js.map +1 -0
- package/dist/actions/build/writeSanityRuntime.js +3 -2
- package/dist/actions/build/writeSanityRuntime.js.map +1 -1
- package/dist/actions/dataset/create.js +4 -0
- package/dist/actions/dataset/create.js.map +1 -1
- package/dist/actions/deploy/findUserApplicationForApp.js +1 -0
- package/dist/actions/deploy/findUserApplicationForApp.js.map +1 -1
- package/dist/actions/deploy/types.js +1 -1
- package/dist/actions/deploy/types.js.map +1 -1
- package/dist/actions/init/templates/nextjs/index.js +1 -2
- package/dist/actions/init/templates/nextjs/index.js.map +1 -1
- package/dist/actions/manifest/types.js +1 -1
- package/dist/actions/manifest/types.js.map +1 -1
- package/dist/actions/schema/types.js +3 -3
- package/dist/actions/schema/types.js.map +1 -1
- package/dist/actions/users/validateEmail.js +2 -2
- package/dist/actions/users/validateEmail.js.map +1 -1
- package/dist/commands/backups/disable.js +1 -1
- package/dist/commands/backups/disable.js.map +1 -1
- package/dist/commands/backups/download.js +1 -1
- package/dist/commands/backups/download.js.map +1 -1
- package/dist/commands/backups/enable.js +1 -1
- package/dist/commands/backups/enable.js.map +1 -1
- package/dist/commands/backups/list.js +1 -1
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/build.js +1 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cors/add.js +1 -1
- package/dist/commands/cors/add.js.map +1 -1
- package/dist/commands/cors/delete.js +1 -1
- package/dist/commands/cors/delete.js.map +1 -1
- package/dist/commands/cors/list.js +2 -2
- package/dist/commands/cors/list.js.map +1 -1
- package/dist/commands/datasets/alias/create.js +1 -1
- package/dist/commands/datasets/alias/create.js.map +1 -1
- package/dist/commands/datasets/alias/delete.js +1 -1
- package/dist/commands/datasets/alias/delete.js.map +1 -1
- package/dist/commands/datasets/alias/link.js +1 -1
- package/dist/commands/datasets/alias/link.js.map +1 -1
- package/dist/commands/datasets/alias/unlink.js +1 -1
- package/dist/commands/datasets/alias/unlink.js.map +1 -1
- package/dist/commands/datasets/copy.js +1 -1
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/datasets/create.js +1 -1
- package/dist/commands/datasets/create.js.map +1 -1
- package/dist/commands/datasets/delete.js +1 -1
- package/dist/commands/datasets/delete.js.map +1 -1
- package/dist/commands/datasets/embeddings/enable.js +11 -0
- package/dist/commands/datasets/embeddings/enable.js.map +1 -1
- package/dist/commands/datasets/export.js +2 -2
- package/dist/commands/datasets/export.js.map +1 -1
- package/dist/commands/datasets/list.js +2 -2
- package/dist/commands/datasets/list.js.map +1 -1
- package/dist/commands/debug.js +1 -1
- package/dist/commands/debug.js.map +1 -1
- package/dist/commands/deploy.js +3 -3
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +5 -5
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docs/browse.js +1 -1
- package/dist/commands/docs/browse.js.map +1 -1
- package/dist/commands/documents/delete.js +1 -1
- package/dist/commands/documents/delete.js.map +1 -1
- package/dist/commands/exec.js +2 -2
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/graphql/deploy.js +2 -2
- package/dist/commands/graphql/deploy.js.map +1 -1
- package/dist/commands/graphql/list.js +2 -2
- package/dist/commands/graphql/list.js.map +1 -1
- package/dist/commands/hooks/create.js +2 -2
- package/dist/commands/hooks/create.js.map +1 -1
- package/dist/commands/hooks/delete.js +5 -5
- package/dist/commands/hooks/delete.js.map +1 -1
- package/dist/commands/hooks/list.js +3 -3
- package/dist/commands/hooks/list.js.map +1 -1
- package/dist/commands/hooks/logs.js +5 -5
- package/dist/commands/hooks/logs.js.map +1 -1
- package/dist/commands/init.js +30 -12
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.js +1 -1
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/learn.js +1 -1
- package/dist/commands/learn.js.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/manage.js +1 -1
- package/dist/commands/manage.js.map +1 -1
- package/dist/commands/manifest/extract.js +2 -2
- package/dist/commands/manifest/extract.js.map +1 -1
- package/dist/commands/media/delete-aspect.js +1 -1
- package/dist/commands/media/delete-aspect.js.map +1 -1
- package/dist/commands/media/export.js +1 -1
- package/dist/commands/media/export.js.map +1 -1
- package/dist/commands/preview.js +3 -3
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/projects/list.js +4 -2
- package/dist/commands/projects/list.js.map +1 -1
- package/dist/commands/schemas/deploy.js +3 -4
- package/dist/commands/schemas/deploy.js.map +1 -1
- package/dist/commands/schemas/extract.js +3 -3
- package/dist/commands/schemas/extract.js.map +1 -1
- package/dist/commands/schemas/list.js +4 -5
- package/dist/commands/schemas/list.js.map +1 -1
- package/dist/commands/telemetry/disable.js +2 -2
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js +2 -2
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js +2 -2
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/commands/tokens/add.js +1 -1
- package/dist/commands/tokens/add.js.map +1 -1
- package/dist/commands/tokens/delete.js +1 -1
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/tokens/list.js +2 -2
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/commands/users/list.js +1 -1
- package/dist/commands/users/list.js.map +1 -1
- package/dist/commands/versions.js +1 -1
- package/dist/commands/versions.js.map +1 -1
- package/dist/server/vite/plugin-sanity-build-entries.js +3 -2
- package/dist/server/vite/plugin-sanity-build-entries.js.map +1 -1
- package/dist/util/telemetry/createTelemetryStore.js +27 -12
- package/dist/util/telemetry/createTelemetryStore.js.map +1 -1
- package/dist/util/validateProjection.js +121 -0
- package/dist/util/validateProjection.js.map +1 -0
- package/oclif.manifest.json +317 -315
- package/package.json +27 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/add.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {input, select} from '@sanity/cli-core/ux'\n\nimport {validateRole} from '../../actions/tokens/validateRole.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {createToken, getTokenRoles} from '../../services/tokens.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst tokensAddDebug = subdebug('tokens:add')\n\nexport class AddTokenCommand extends SanityCommand<typeof AddTokenCommand> {\n static override args = {\n label: Args.string({\n description: 'Label for the new token',\n required: false,\n }),\n }\n\n static override description = 'Create a new API token for
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/add.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {input, select} from '@sanity/cli-core/ux'\n\nimport {validateRole} from '../../actions/tokens/validateRole.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {createToken, getTokenRoles} from '../../services/tokens.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst tokensAddDebug = subdebug('tokens:add')\n\nexport class AddTokenCommand extends SanityCommand<typeof AddTokenCommand> {\n static override args = {\n label: Args.string({\n description: 'Label for the new token',\n required: false,\n }),\n }\n\n static override description = 'Create a new API token for the project'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> \"My API Token\"',\n description: 'Create a token with a label',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My API Token\" --role=editor',\n description: 'Create a token with editor role',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"CI Token\" --role=editor --yes',\n description: 'Create a token in unattended mode',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"API Token\" --json',\n description: 'Output token information as JSON',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My Token\" --project-id abc123 --role=editor',\n description: 'Create a token for a specific project',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({description: 'Project ID to add token to', semantics: 'override'}),\n json: Flags.boolean({\n default: false,\n description: 'Output as JSON',\n }),\n role: Flags.string({\n description: 'Role to assign to the token',\n helpValue: 'viewer',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skip prompts and use defaults (unattended mode)',\n }),\n }\n\n static override hiddenAliases: string[] = ['token:add']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(AddTokenCommand)\n const {label: givenLabel} = args\n const {json, role} = flags\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [\n {grant: 'read', permission: 'sanity.project.roles'},\n {grant: 'create', permission: 'sanity.project.tokens'},\n ],\n }),\n })\n\n try {\n const label = givenLabel || (await this.promptForLabel())\n const roleName = await (role ? validateRole(role, projectId) : this.promptForRole(projectId))\n\n tokensAddDebug(`Creating token for project ${projectId}`, {label, roleName})\n const token = await createToken({\n label,\n projectId,\n roleName,\n })\n\n if (json) {\n this.log(JSON.stringify(token, null, 2))\n return\n }\n\n this.log('Token created successfully!')\n this.log(`Label: ${token.label}`)\n this.log(`ID: ${token.id}`)\n this.log(`Role: ${token.roles.map((r) => r.title).join(', ')}`)\n this.log(`Token: ${token.key}`)\n this.log('')\n this.log('Copy the token above – this is your only chance to do so!')\n } catch (error) {\n const err = error as Error\n\n tokensAddDebug(`Error creating token for project ${projectId}`, err)\n this.error(`Token creation failed:\\n${err.message}`, {exit: 1})\n }\n }\n\n private async promptForLabel(): Promise<string> {\n if (this.isUnattended()) {\n this.error(\n 'Token label is required in non-interactive mode. Provide a label as an argument.',\n {\n exit: 1,\n },\n )\n }\n\n const label = await input({\n message: 'Token label:',\n validate: (value) => {\n if (!value || !value.trim()) {\n return 'Label cannot be empty'\n }\n return true\n },\n })\n\n return label\n }\n\n private async promptForRole(projectId: string): Promise<string> {\n if (this.isUnattended()) {\n return 'viewer' // Default role for unattended mode\n }\n\n const roles = await getTokenRoles(projectId)\n const robotRoles = roles.filter((role) => role.appliesToRobots)\n\n tokensAddDebug('Robot roles', {robotRoles})\n\n if (robotRoles.length === 0) {\n this.error('No roles available for tokens', {exit: 1})\n }\n\n const selectedRoleName = await select({\n choices: robotRoles.map((role) => ({\n name: `${role.title} (${role.name})`,\n short: role.title,\n value: role.name,\n })),\n default: 'viewer',\n message: 'Select role for the token:',\n })\n\n return selectedRoleName\n }\n}\n"],"names":["Args","Flags","SanityCommand","subdebug","input","select","validateRole","promptForProject","createToken","getTokenRoles","getProjectIdFlag","tokensAddDebug","AddTokenCommand","args","label","string","description","required","examples","command","flags","semantics","json","boolean","default","role","helpValue","yes","char","hiddenAliases","run","parse","givenLabel","projectId","getProjectId","fallback","requiredPermissions","grant","permission","promptForLabel","roleName","promptForRole","token","log","JSON","stringify","id","roles","map","r","title","join","key","error","err","message","exit","isUnattended","validate","value","trim","robotRoles","filter","appliesToRobots","length","selectedRoleName","choices","name","short"],"mappings":"AAAA,SAAQA,IAAI,EAAEC,KAAK,QAAO,cAAa;AACvC,SAAQC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,KAAK,EAAEC,MAAM,QAAO,sBAAqB;AAEjD,SAAQC,YAAY,QAAO,uCAAsC;AACjE,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,WAAW,EAAEC,aAAa,QAAO,2BAA0B;AACnE,SAAQC,gBAAgB,QAAO,4BAA2B;AAE1D,MAAMC,iBAAiBR,SAAS;AAEhC,OAAO,MAAMS,wBAAwBV;IACnC,OAAgBW,OAAO;QACrBC,OAAOd,KAAKe,MAAM,CAAC;YACjBC,aAAa;YACbC,UAAU;QACZ;IACF,EAAC;IAED,OAAgBD,cAAc,yCAAwC;IAEtE,OAAgBE,WAAW;QACzB;YACEC,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;QACA;YACEG,SAAS;YACTH,aAAa;QACf;KACD,CAAA;IAED,OAAgBI,QAAQ;QACtB,GAAGV,iBAAiB;YAACM,aAAa;YAA8BK,WAAW;QAAU,EAAE;QACvFC,MAAMrB,MAAMsB,OAAO,CAAC;YAClBC,SAAS;YACTR,aAAa;QACf;QACAS,MAAMxB,MAAMc,MAAM,CAAC;YACjBC,aAAa;YACbU,WAAW;QACb;QACAC,KAAK1B,MAAMsB,OAAO,CAAC;YACjBK,MAAM;YACNJ,SAAS;YACTR,aAAa;QACf;IACF,EAAC;IAED,OAAgBa,gBAA0B;QAAC;KAAY,CAAA;IAEvD,MAAaC,MAAqB;QAChC,MAAM,EAACjB,IAAI,EAAEO,KAAK,EAAC,GAAG,MAAM,IAAI,CAACW,KAAK,CAACnB;QACvC,MAAM,EAACE,OAAOkB,UAAU,EAAC,GAAGnB;QAC5B,MAAM,EAACS,IAAI,EAAEG,IAAI,EAAC,GAAGL;QAErB,MAAMa,YAAY,MAAM,IAAI,CAACC,YAAY,CAAC;YACxCC,UAAU,IACR5B,iBAAiB;oBACf6B,qBAAqB;wBACnB;4BAACC,OAAO;4BAAQC,YAAY;wBAAsB;wBAClD;4BAACD,OAAO;4BAAUC,YAAY;wBAAuB;qBACtD;gBACH;QACJ;QAEA,IAAI;YACF,MAAMxB,QAAQkB,cAAe,MAAM,IAAI,CAACO,cAAc;YACtD,MAAMC,WAAW,MAAOf,CAAAA,OAAOnB,aAAamB,MAAMQ,aAAa,IAAI,CAACQ,aAAa,CAACR,UAAS;YAE3FtB,eAAe,CAAC,2BAA2B,EAAEsB,WAAW,EAAE;gBAACnB;gBAAO0B;YAAQ;YAC1E,MAAME,QAAQ,MAAMlC,YAAY;gBAC9BM;gBACAmB;gBACAO;YACF;YAEA,IAAIlB,MAAM;gBACR,IAAI,CAACqB,GAAG,CAACC,KAAKC,SAAS,CAACH,OAAO,MAAM;gBACrC;YACF;YAEA,IAAI,CAACC,GAAG,CAAC;YACT,IAAI,CAACA,GAAG,CAAC,CAAC,OAAO,EAAED,MAAM5B,KAAK,EAAE;YAChC,IAAI,CAAC6B,GAAG,CAAC,CAAC,IAAI,EAAED,MAAMI,EAAE,EAAE;YAC1B,IAAI,CAACH,GAAG,CAAC,CAAC,MAAM,EAAED,MAAMK,KAAK,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,KAAK,EAAEC,IAAI,CAAC,OAAO;YAC9D,IAAI,CAACR,GAAG,CAAC,CAAC,OAAO,EAAED,MAAMU,GAAG,EAAE;YAC9B,IAAI,CAACT,GAAG,CAAC;YACT,IAAI,CAACA,GAAG,CAAC;QACX,EAAE,OAAOU,OAAO;YACd,MAAMC,MAAMD;YAEZ1C,eAAe,CAAC,iCAAiC,EAAEsB,WAAW,EAAEqB;YAChE,IAAI,CAACD,KAAK,CAAC,CAAC,wBAAwB,EAAEC,IAAIC,OAAO,EAAE,EAAE;gBAACC,MAAM;YAAC;QAC/D;IACF;IAEA,MAAcjB,iBAAkC;QAC9C,IAAI,IAAI,CAACkB,YAAY,IAAI;YACvB,IAAI,CAACJ,KAAK,CACR,oFACA;gBACEG,MAAM;YACR;QAEJ;QAEA,MAAM1C,QAAQ,MAAMV,MAAM;YACxBmD,SAAS;YACTG,UAAU,CAACC;gBACT,IAAI,CAACA,SAAS,CAACA,MAAMC,IAAI,IAAI;oBAC3B,OAAO;gBACT;gBACA,OAAO;YACT;QACF;QAEA,OAAO9C;IACT;IAEA,MAAc2B,cAAcR,SAAiB,EAAmB;QAC9D,IAAI,IAAI,CAACwB,YAAY,IAAI;YACvB,OAAO,SAAS,mCAAmC;;QACrD;QAEA,MAAMV,QAAQ,MAAMtC,cAAcwB;QAClC,MAAM4B,aAAad,MAAMe,MAAM,CAAC,CAACrC,OAASA,KAAKsC,eAAe;QAE9DpD,eAAe,eAAe;YAACkD;QAAU;QAEzC,IAAIA,WAAWG,MAAM,KAAK,GAAG;YAC3B,IAAI,CAACX,KAAK,CAAC,iCAAiC;gBAACG,MAAM;YAAC;QACtD;QAEA,MAAMS,mBAAmB,MAAM5D,OAAO;YACpC6D,SAASL,WAAWb,GAAG,CAAC,CAACvB,OAAU,CAAA;oBACjC0C,MAAM,GAAG1C,KAAKyB,KAAK,CAAC,EAAE,EAAEzB,KAAK0C,IAAI,CAAC,CAAC,CAAC;oBACpCC,OAAO3C,KAAKyB,KAAK;oBACjBS,OAAOlC,KAAK0C,IAAI;gBAClB,CAAA;YACA3C,SAAS;YACT+B,SAAS;QACX;QAEA,OAAOU;IACT;AACF"}
|
|
@@ -13,7 +13,7 @@ export class DeleteTokensCommand extends SanityCommand {
|
|
|
13
13
|
required: false
|
|
14
14
|
})
|
|
15
15
|
};
|
|
16
|
-
static description = 'Delete an API token from
|
|
16
|
+
static description = 'Delete an API token from the project';
|
|
17
17
|
static examples = [
|
|
18
18
|
{
|
|
19
19
|
command: '<%= config.bin %> <%= command.id %>',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/delete.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {confirm, select} from '@sanity/cli-core/ux'\nimport {ClientError} from '@sanity/client'\n\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {deleteToken, getTokens} from '../../services/tokens.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst deleteTokenDebug = subdebug('tokens:delete')\n\nexport class DeleteTokensCommand extends SanityCommand<typeof DeleteTokensCommand> {\n static override args = {\n tokenId: Args.string({\n description: 'Token ID to delete (will prompt if not provided)',\n required: false,\n }),\n }\n\n static override description = 'Delete an API token from
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/delete.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {confirm, select} from '@sanity/cli-core/ux'\nimport {ClientError} from '@sanity/client'\n\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {deleteToken, getTokens} from '../../services/tokens.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst deleteTokenDebug = subdebug('tokens:delete')\n\nexport class DeleteTokensCommand extends SanityCommand<typeof DeleteTokensCommand> {\n static override args = {\n tokenId: Args.string({\n description: 'Token ID to delete (will prompt if not provided)',\n required: false,\n }),\n }\n\n static override description = 'Delete an API token from the project'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively select and delete a token',\n },\n {\n command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB',\n description: 'Delete a specific token by ID',\n },\n {\n command: '<%= config.bin %> <%= command.id %> silJ2lFmK6dONB --yes',\n description: 'Delete a specific token without confirmation prompt',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'Delete a token from a specific project',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to delete token from',\n semantics: 'override',\n }),\n yes: Flags.boolean({\n aliases: ['y'],\n description: 'Skip confirmation prompt (unattended mode)',\n required: false,\n }),\n }\n\n static override hiddenAliases: string[] = ['token:delete']\n\n private projectId!: string\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(DeleteTokensCommand)\n\n const unattended = flags.yes\n const {tokenId: givenTokenId} = args\n\n if (unattended && !givenTokenId) {\n this.error(\n 'Token ID is required in non-interactive mode. Provide a token ID as an argument.',\n {exit: 1},\n )\n }\n\n // Ensure we have project context\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'delete', permission: 'sanity.project.tokens'}],\n }),\n })\n\n this.projectId = projectId\n\n let tokenId: string | undefined\n\n try {\n tokenId = givenTokenId || (await this.getTokenIdFromList())\n\n if (!unattended) {\n const confirmed = await confirm({\n default: false,\n message: `Are you sure you want to delete the token with ID \"${tokenId}\"?`,\n })\n\n if (!confirmed) {\n this.error('Operation cancelled', {exit: 1})\n }\n }\n\n await deleteToken({\n projectId: this.projectId,\n tokenId,\n })\n\n this.log('Token deleted successfully')\n } catch (error) {\n if (error instanceof ClientError && error.response.statusCode === 404) {\n this.error(`Token with ID \"${tokenId}\" not found`, {exit: 1})\n }\n\n const err = error as Error\n deleteTokenDebug(`Error deleting token`, err)\n this.error(`Token deletion failed:\\n${err.message}`, {exit: 1})\n }\n }\n\n private async getTokenIdFromList() {\n const tokens = await getTokens(this.projectId)\n\n if (tokens.length === 0) {\n this.error('No tokens found', {exit: 1})\n }\n\n const choices = tokens.map((token) => ({\n name: `${token.label} (${(token.roles || []).map((r) => r.title).join(', ')})`,\n value: token.id,\n }))\n\n return select({\n choices,\n message: 'Select token to delete:',\n })\n }\n}\n"],"names":["Args","Flags","SanityCommand","subdebug","confirm","select","ClientError","promptForProject","deleteToken","getTokens","getProjectIdFlag","deleteTokenDebug","DeleteTokensCommand","args","tokenId","string","description","required","examples","command","flags","semantics","yes","boolean","aliases","hiddenAliases","projectId","run","parse","unattended","givenTokenId","error","exit","getProjectId","fallback","requiredPermissions","grant","permission","getTokenIdFromList","confirmed","default","message","log","response","statusCode","err","tokens","length","choices","map","token","name","label","roles","r","title","join","value","id"],"mappings":"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"}
|
|
@@ -7,11 +7,11 @@ import { getErrorMessage } from '../../util/getErrorMessage.js';
|
|
|
7
7
|
import { getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
8
8
|
const listTokenDebug = subdebug('tokens:list');
|
|
9
9
|
export class TokensListCommand extends SanityCommand {
|
|
10
|
-
static description = 'List API tokens for the
|
|
10
|
+
static description = 'List API tokens for the project';
|
|
11
11
|
static examples = [
|
|
12
12
|
{
|
|
13
13
|
command: '<%= config.bin %> <%= command.id %>',
|
|
14
|
-
description: 'List tokens for the
|
|
14
|
+
description: 'List tokens for the project'
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
command: '<%= config.bin %> <%= command.id %> --json',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {Table} from 'console-table-printer'\n\nimport {type Token} from '../../actions/tokens/types.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getTokens} from '../../services/tokens.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst listTokenDebug = subdebug('tokens:list')\n\nexport class TokensListCommand extends SanityCommand<typeof TokensListCommand> {\n static override description = 'List API tokens for the
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/tokens/list.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand, subdebug} from '@sanity/cli-core'\nimport {Table} from 'console-table-printer'\n\nimport {type Token} from '../../actions/tokens/types.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getTokens} from '../../services/tokens.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst listTokenDebug = subdebug('tokens:list')\n\nexport class TokensListCommand extends SanityCommand<typeof TokensListCommand> {\n static override description = 'List API tokens for the project'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List tokens for the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json',\n description: 'List tokens in JSON format',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'List tokens for a specific project',\n },\n ]\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to list tokens for',\n semantics: 'override',\n }),\n json: Flags.boolean({\n default: false,\n description: 'Output tokens in JSON format',\n }),\n }\n\n static override hiddenAliases: string[] = ['token:list']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(TokensListCommand)\n const {json} = flags\n const outputJson = json ?? false\n\n // Ensure we have project context\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.tokens'}],\n }),\n })\n\n let tokens: Token[]\n try {\n tokens = await getTokens(projectId)\n } catch (error) {\n const message = getErrorMessage(error)\n listTokenDebug(`Error fetching tokens for project ${projectId}`, error)\n this.error(`Token list retrieval failed:\\n${message}`, {exit: 1})\n }\n\n if (outputJson) {\n this.log(JSON.stringify(tokens, null, 2))\n return\n }\n\n if (tokens.length === 0) {\n this.log('No API tokens found for this project.')\n return\n }\n\n const table = new Table({\n columns: [\n {alignment: 'left', maxLen: 40, name: 'label', title: 'Label'},\n {alignment: 'left', maxLen: 20, name: 'id', title: 'Token ID'},\n {alignment: 'left', maxLen: 30, name: 'roles', title: 'Roles'},\n ],\n title: `Found ${tokens.length} API tokens`,\n })\n\n for (const token of tokens) {\n const roles = token.roles?.map((role) => role.title).join(', ') || 'No roles'\n const truncatedLabel =\n token.label.length > 37 ? `${token.label.slice(0, 37)}...` : token.label\n const truncatedRoles = roles.length > 27 ? `${roles.slice(0, 27)}...` : roles\n\n table.addRow({\n id: token.id,\n label: truncatedLabel,\n roles: truncatedRoles,\n })\n }\n\n table.printTable()\n }\n}\n"],"names":["Flags","SanityCommand","subdebug","Table","promptForProject","getTokens","getErrorMessage","getProjectIdFlag","listTokenDebug","TokensListCommand","description","examples","command","flags","semantics","json","boolean","default","hiddenAliases","run","parse","outputJson","projectId","getProjectId","fallback","requiredPermissions","grant","permission","tokens","error","message","exit","log","JSON","stringify","length","table","columns","alignment","maxLen","name","title","token","roles","map","role","join","truncatedLabel","label","slice","truncatedRoles","addRow","id","printTable"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,KAAK,QAAO,wBAAuB;AAG3C,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,SAAS,QAAO,2BAA0B;AAClD,SAAQC,eAAe,QAAO,gCAA+B;AAC7D,SAAQC,gBAAgB,QAAO,4BAA2B;AAE1D,MAAMC,iBAAiBN,SAAS;AAEhC,OAAO,MAAMO,0BAA0BR;IACrC,OAAgBS,cAAc,kCAAiC;IAC/D,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IACD,OAAgBG,QAAQ;QACtB,GAAGN,iBAAiB;YAClBG,aAAa;YACbI,WAAW;QACb,EAAE;QACFC,MAAMf,MAAMgB,OAAO,CAAC;YAClBC,SAAS;YACTP,aAAa;QACf;IACF,EAAC;IAED,OAAgBQ,gBAA0B;QAAC;KAAa,CAAA;IAExD,MAAaC,MAAqB;QAChC,MAAM,EAACN,KAAK,EAAC,GAAG,MAAM,IAAI,CAACO,KAAK,CAACX;QACjC,MAAM,EAACM,IAAI,EAAC,GAAGF;QACf,MAAMQ,aAAaN,QAAQ;QAE3B,iCAAiC;QACjC,MAAMO,YAAY,MAAM,IAAI,CAACC,YAAY,CAAC;YACxCC,UAAU,IACRpB,iBAAiB;oBACfqB,qBAAqB;wBAAC;4BAACC,OAAO;4BAAQC,YAAY;wBAAuB;qBAAE;gBAC7E;QACJ;QAEA,IAAIC;QACJ,IAAI;YACFA,SAAS,MAAMvB,UAAUiB;QAC3B,EAAE,OAAOO,OAAO;YACd,MAAMC,UAAUxB,gBAAgBuB;YAChCrB,eAAe,CAAC,kCAAkC,EAAEc,WAAW,EAAEO;YACjE,IAAI,CAACA,KAAK,CAAC,CAAC,8BAA8B,EAAEC,SAAS,EAAE;gBAACC,MAAM;YAAC;QACjE;QAEA,IAAIV,YAAY;YACd,IAAI,CAACW,GAAG,CAACC,KAAKC,SAAS,CAACN,QAAQ,MAAM;YACtC;QACF;QAEA,IAAIA,OAAOO,MAAM,KAAK,GAAG;YACvB,IAAI,CAACH,GAAG,CAAC;YACT;QACF;QAEA,MAAMI,QAAQ,IAAIjC,MAAM;YACtBkC,SAAS;gBACP;oBAACC,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAASC,OAAO;gBAAO;gBAC7D;oBAACH,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAAMC,OAAO;gBAAU;gBAC7D;oBAACH,WAAW;oBAAQC,QAAQ;oBAAIC,MAAM;oBAASC,OAAO;gBAAO;aAC9D;YACDA,OAAO,CAAC,MAAM,EAAEb,OAAOO,MAAM,CAAC,WAAW,CAAC;QAC5C;QAEA,KAAK,MAAMO,SAASd,OAAQ;YAC1B,MAAMe,QAAQD,MAAMC,KAAK,EAAEC,IAAI,CAACC,OAASA,KAAKJ,KAAK,EAAEK,KAAK,SAAS;YACnE,MAAMC,iBACJL,MAAMM,KAAK,CAACb,MAAM,GAAG,KAAK,GAAGO,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,GAAGP,MAAMM,KAAK;YAC1E,MAAME,iBAAiBP,MAAMR,MAAM,GAAG,KAAK,GAAGQ,MAAMM,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,GAAGN;YAExEP,MAAMe,MAAM,CAAC;gBACXC,IAAIV,MAAMU,EAAE;gBACZJ,OAAOD;gBACPJ,OAAOO;YACT;QACF;QAEAd,MAAMiB,UAAU;IAClB;AACF"}
|
|
@@ -16,7 +16,7 @@ function dimText(value, isDim) {
|
|
|
16
16
|
return isDim ? styleText('dim', value) : value;
|
|
17
17
|
}
|
|
18
18
|
export class List extends SanityCommand {
|
|
19
|
-
static description = 'List
|
|
19
|
+
static description = 'List project members';
|
|
20
20
|
static examples = [
|
|
21
21
|
{
|
|
22
22
|
command: '<%= config.bin %> <%= command.id %>',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/users/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {Table} from 'console-table-printer'\nimport sortBy from 'lodash-es/sortBy.js'\n\nimport {getMembersForProject} from '../../actions/users/getMembersForProject.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst sortFields = ['id', 'name', 'role', 'date']\n\nfunction dimText(value: string, isDim: boolean): string {\n return isDim ? styleText('dim', value) : value\n}\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/users/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {Table} from 'console-table-printer'\nimport sortBy from 'lodash-es/sortBy.js'\n\nimport {getMembersForProject} from '../../actions/users/getMembersForProject.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst sortFields = ['id', 'name', 'role', 'date']\n\nfunction dimText(value: string, isDim: boolean): string {\n return isDim ? styleText('dim', value) : value\n}\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List project members'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List all users of the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-invitations --no-robots',\n description: 'List all users of the project, but exclude pending invitations and robots',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sort role',\n description: 'List all users, sorted by role',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'List users for a specific project',\n },\n ]\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to list users for',\n semantics: 'override',\n }),\n invitations: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Includes or excludes pending invitations',\n }),\n order: Flags.string({\n default: 'asc',\n description: 'Sort output ascending/descending',\n options: ['asc', 'desc'],\n }),\n robots: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Includes or excludes robots (token users)',\n }),\n sort: Flags.string({\n default: 'date',\n description: 'Sort users by specified column',\n options: ['id', 'name', 'role', 'date'],\n }),\n }\n\n static override hiddenAliases: string[] = ['user:list']\n\n public async run(): Promise<void> {\n const {invitations, order, robots, sort} = this.flags\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [\n {grant: 'read', permission: 'sanity.project'},\n {grant: 'read', permission: 'sanity.project.members'},\n ],\n }),\n })\n\n const members = await getMembersForProject({\n includeInvitations: invitations,\n includeRobots: robots,\n projectId,\n })\n\n const ordered = sortBy(\n members.map(({date, id, name, roles}) => [\n id,\n name,\n roles\n ?.map((role) => role.title)\n .join(', ')\n .trim() || '-',\n date,\n ]),\n [sortFields.indexOf(sort)],\n )\n\n const rows = order === 'asc' ? ordered : ordered.toReversed()\n\n const table = new Table({\n columns: [\n {alignment: 'left', maxLen: 30, name: 'id', title: 'ID'},\n {alignment: 'left', maxLen: 40, name: 'name', title: 'Name'},\n {alignment: 'left', maxLen: 30, name: 'roles', title: 'Roles'},\n {alignment: 'left', maxLen: 12, name: 'date', title: 'Date'},\n ],\n rowSeparator: true,\n })\n\n for (const [id, name, roles, date] of rows) {\n const isPending = id === '<pending>'\n table.addRow({\n date: dimText(date, isPending),\n id: dimText(id, isPending),\n name: dimText(name, isPending),\n roles: dimText(roles, isPending),\n })\n }\n\n table.printTable()\n }\n}\n"],"names":["styleText","Flags","SanityCommand","Table","sortBy","getMembersForProject","promptForProject","getProjectIdFlag","sortFields","dimText","value","isDim","List","description","examples","command","flags","semantics","invitations","boolean","allowNo","default","order","string","options","robots","sort","hiddenAliases","run","projectId","getProjectId","fallback","requiredPermissions","grant","permission","members","includeInvitations","includeRobots","ordered","map","date","id","name","roles","role","title","join","trim","indexOf","rows","toReversed","table","columns","alignment","maxLen","rowSeparator","isPending","addRow","printTable"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { findSanityModulesVersions } from '../actions/versions/findSanityModules
|
|
|
5
5
|
import { getDisplayName, getFormatters } from '../actions/versions/getFormatters.js';
|
|
6
6
|
import { versionsDebug } from '../actions/versions/versionsDebug.js';
|
|
7
7
|
export class Versions extends SanityCommand {
|
|
8
|
-
static description = '
|
|
8
|
+
static description = 'Show installed package versions';
|
|
9
9
|
static examples = [
|
|
10
10
|
'<%= config.bin %> <%= command.id %>'
|
|
11
11
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/versions.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {SanityCommand} from '@sanity/cli-core'\nimport padStart from 'lodash-es/padStart.js'\n\nimport {findSanityModulesVersions} from '../actions/versions/findSanityModulesVersions.js'\nimport {getDisplayName, getFormatters} from '../actions/versions/getFormatters.js'\nimport {versionsDebug} from '../actions/versions/versionsDebug.js'\n\nexport class Versions extends SanityCommand<typeof Versions> {\n static override description = '
|
|
1
|
+
{"version":3,"sources":["../../src/commands/versions.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {SanityCommand} from '@sanity/cli-core'\nimport padStart from 'lodash-es/padStart.js'\n\nimport {findSanityModulesVersions} from '../actions/versions/findSanityModulesVersions.js'\nimport {getDisplayName, getFormatters} from '../actions/versions/getFormatters.js'\nimport {versionsDebug} from '../actions/versions/versionsDebug.js'\n\nexport class Versions extends SanityCommand<typeof Versions> {\n static override description = 'Show installed package versions'\n static override examples = ['<%= config.bin %> <%= command.id %>']\n\n public async run(): Promise<void> {\n const root = (await this.getProjectRoot()).directory\n\n const versions = await findSanityModulesVersions({cwd: root})\n\n versionsDebug('resolved versions:', versions)\n\n const {formatName, versionLength} = getFormatters(versions)\n for (const mod of versions) {\n const version = padStart(mod.installed || '<missing>', versionLength)\n const latest =\n mod.installed === mod.latest\n ? styleText('green', '(up to date)')\n : `(latest: ${styleText('yellow', mod.latest)})`\n\n this.log(`${formatName(getDisplayName(mod))} ${version} ${latest}`)\n }\n }\n}\n"],"names":["styleText","SanityCommand","padStart","findSanityModulesVersions","getDisplayName","getFormatters","versionsDebug","Versions","description","examples","run","root","getProjectRoot","directory","versions","cwd","formatName","versionLength","mod","version","installed","latest","log"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,aAAa,QAAO,mBAAkB;AAC9C,OAAOC,cAAc,wBAAuB;AAE5C,SAAQC,yBAAyB,QAAO,mDAAkD;AAC1F,SAAQC,cAAc,EAAEC,aAAa,QAAO,uCAAsC;AAClF,SAAQC,aAAa,QAAO,uCAAsC;AAElE,OAAO,MAAMC,iBAAiBN;IAC5B,OAAgBO,cAAc,kCAAiC;IAC/D,OAAgBC,WAAW;QAAC;KAAsC,CAAA;IAElE,MAAaC,MAAqB;QAChC,MAAMC,OAAO,AAAC,CAAA,MAAM,IAAI,CAACC,cAAc,EAAC,EAAGC,SAAS;QAEpD,MAAMC,WAAW,MAAMX,0BAA0B;YAACY,KAAKJ;QAAI;QAE3DL,cAAc,sBAAsBQ;QAEpC,MAAM,EAACE,UAAU,EAAEC,aAAa,EAAC,GAAGZ,cAAcS;QAClD,KAAK,MAAMI,OAAOJ,SAAU;YAC1B,MAAMK,UAAUjB,SAASgB,IAAIE,SAAS,IAAI,aAAaH;YACvD,MAAMI,SACJH,IAAIE,SAAS,KAAKF,IAAIG,MAAM,GACxBrB,UAAU,SAAS,kBACnB,CAAC,SAAS,EAAEA,UAAU,UAAUkB,IAAIG,MAAM,EAAE,CAAC,CAAC;YAEpD,IAAI,CAACC,GAAG,CAAC,GAAGN,WAAWZ,eAAec,MAAM,CAAC,EAAEC,QAAQ,CAAC,EAAEE,QAAQ;QACpE;IACF;AACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decorateIndexWithBridgeScript } from '../../actions/build/decorateIndexWithBridgeScript.js';
|
|
2
|
+
import { decorateIndexWithStagingScript } from '../../actions/build/decorateIndexWithStagingScript.js';
|
|
2
3
|
import { renderDocument } from '../../actions/build/renderDocument.js';
|
|
3
4
|
const entryChunkId = '.sanity/runtime/app.js';
|
|
4
5
|
export function sanityBuildEntries(options) {
|
|
@@ -46,7 +47,7 @@ export function sanityBuildEntries(options) {
|
|
|
46
47
|
}
|
|
47
48
|
this.emitFile({
|
|
48
49
|
fileName: 'index.html',
|
|
49
|
-
source: decorateIndexWithBridgeScript(await renderDocument({
|
|
50
|
+
source: decorateIndexWithStagingScript(decorateIndexWithBridgeScript(await renderDocument({
|
|
50
51
|
importMap,
|
|
51
52
|
isApp,
|
|
52
53
|
props: {
|
|
@@ -55,7 +56,7 @@ export function sanityBuildEntries(options) {
|
|
|
55
56
|
entryPath
|
|
56
57
|
},
|
|
57
58
|
studioRootPath: cwd
|
|
58
|
-
})),
|
|
59
|
+
}))),
|
|
59
60
|
type: 'asset'
|
|
60
61
|
});
|
|
61
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/server/vite/plugin-sanity-build-entries.ts"],"sourcesContent":["import {type ChunkMetadata, type Plugin} from 'vite'\n\nimport {decorateIndexWithBridgeScript} from '../../actions/build/decorateIndexWithBridgeScript.js'\nimport {renderDocument} from '../../actions/build/renderDocument.js'\n\ninterface ViteOutputBundle {\n [fileName: string]: ViteRenderedAsset | ViteRenderedChunk\n}\n\ninterface ViteRenderedAsset {\n type: 'asset'\n}\n\ninterface ViteRenderedChunk {\n code: string\n facadeModuleId: string | null\n fileName: string\n imports: string[]\n isEntry: boolean\n name: string\n type: 'chunk'\n viteMetadata: ChunkMetadata\n}\n\nconst entryChunkId = '.sanity/runtime/app.js'\n\nexport function sanityBuildEntries(options: {\n basePath: string\n cwd: string\n importMap?: {imports?: Record<string, string>}\n isApp?: boolean\n}): Plugin {\n const {basePath, cwd, importMap, isApp} = options\n\n return {\n apply: 'build',\n name: 'sanity/server/build-entries',\n\n buildStart() {\n this.emitFile({\n id: entryChunkId,\n name: 'sanity',\n type: 'chunk',\n })\n },\n\n async generateBundle(_options, outputBundle) {\n const bundle = outputBundle as unknown as ViteOutputBundle\n const entryFile = Object.values(bundle).find(\n (file) =>\n file.type === 'chunk' &&\n file.name === 'sanity' &&\n file.facadeModuleId?.endsWith(entryChunkId),\n )\n\n if (!entryFile) {\n throw new Error(`Failed to find entry file in bundle (${entryChunkId})`)\n }\n\n if (entryFile.type !== 'chunk') {\n throw new Error('Entry file is not a chunk')\n }\n\n const entryFileName = entryFile.fileName\n const entryPath = [basePath.replace(/\\/+$/, ''), entryFileName].join('/')\n\n let css: string[] = []\n if (entryFile.viteMetadata?.importedCss) {\n // Check all the top-level imports of the entryPoint to see if they have\n // static CSS assets that need loading\n css = [...entryFile.viteMetadata.importedCss]\n for (const key of entryFile.imports) {\n // Traverse all CSS assets that isn't loaded by the runtime and\n // need <link> tags in the HTML template\n const entry = bundle[key]\n const importedCss =\n entry && entry.type === 'chunk' ? entry.viteMetadata.importedCss : undefined\n\n if (importedCss) {\n css.push(...importedCss)\n }\n }\n }\n\n this.emitFile({\n fileName: 'index.html',\n source: decorateIndexWithBridgeScript(\n
|
|
1
|
+
{"version":3,"sources":["../../../src/server/vite/plugin-sanity-build-entries.ts"],"sourcesContent":["import {type ChunkMetadata, type Plugin} from 'vite'\n\nimport {decorateIndexWithBridgeScript} from '../../actions/build/decorateIndexWithBridgeScript.js'\nimport {decorateIndexWithStagingScript} from '../../actions/build/decorateIndexWithStagingScript.js'\nimport {renderDocument} from '../../actions/build/renderDocument.js'\n\ninterface ViteOutputBundle {\n [fileName: string]: ViteRenderedAsset | ViteRenderedChunk\n}\n\ninterface ViteRenderedAsset {\n type: 'asset'\n}\n\ninterface ViteRenderedChunk {\n code: string\n facadeModuleId: string | null\n fileName: string\n imports: string[]\n isEntry: boolean\n name: string\n type: 'chunk'\n viteMetadata: ChunkMetadata\n}\n\nconst entryChunkId = '.sanity/runtime/app.js'\n\nexport function sanityBuildEntries(options: {\n basePath: string\n cwd: string\n importMap?: {imports?: Record<string, string>}\n isApp?: boolean\n}): Plugin {\n const {basePath, cwd, importMap, isApp} = options\n\n return {\n apply: 'build',\n name: 'sanity/server/build-entries',\n\n buildStart() {\n this.emitFile({\n id: entryChunkId,\n name: 'sanity',\n type: 'chunk',\n })\n },\n\n async generateBundle(_options, outputBundle) {\n const bundle = outputBundle as unknown as ViteOutputBundle\n const entryFile = Object.values(bundle).find(\n (file) =>\n file.type === 'chunk' &&\n file.name === 'sanity' &&\n file.facadeModuleId?.endsWith(entryChunkId),\n )\n\n if (!entryFile) {\n throw new Error(`Failed to find entry file in bundle (${entryChunkId})`)\n }\n\n if (entryFile.type !== 'chunk') {\n throw new Error('Entry file is not a chunk')\n }\n\n const entryFileName = entryFile.fileName\n const entryPath = [basePath.replace(/\\/+$/, ''), entryFileName].join('/')\n\n let css: string[] = []\n if (entryFile.viteMetadata?.importedCss) {\n // Check all the top-level imports of the entryPoint to see if they have\n // static CSS assets that need loading\n css = [...entryFile.viteMetadata.importedCss]\n for (const key of entryFile.imports) {\n // Traverse all CSS assets that isn't loaded by the runtime and\n // need <link> tags in the HTML template\n const entry = bundle[key]\n const importedCss =\n entry && entry.type === 'chunk' ? entry.viteMetadata.importedCss : undefined\n\n if (importedCss) {\n css.push(...importedCss)\n }\n }\n }\n\n this.emitFile({\n fileName: 'index.html',\n source: decorateIndexWithStagingScript(\n decorateIndexWithBridgeScript(\n await renderDocument({\n importMap,\n isApp,\n props: {\n basePath,\n css,\n entryPath,\n },\n studioRootPath: cwd,\n }),\n ),\n ),\n type: 'asset',\n })\n },\n }\n}\n"],"names":["decorateIndexWithBridgeScript","decorateIndexWithStagingScript","renderDocument","entryChunkId","sanityBuildEntries","options","basePath","cwd","importMap","isApp","apply","name","buildStart","emitFile","id","type","generateBundle","_options","outputBundle","bundle","entryFile","Object","values","find","file","facadeModuleId","endsWith","Error","entryFileName","fileName","entryPath","replace","join","css","viteMetadata","importedCss","key","imports","entry","undefined","push","source","props","studioRootPath"],"mappings":"AAEA,SAAQA,6BAA6B,QAAO,uDAAsD;AAClG,SAAQC,8BAA8B,QAAO,wDAAuD;AACpG,SAAQC,cAAc,QAAO,wCAAuC;AAqBpE,MAAMC,eAAe;AAErB,OAAO,SAASC,mBAAmBC,OAKlC;IACC,MAAM,EAACC,QAAQ,EAAEC,GAAG,EAAEC,SAAS,EAAEC,KAAK,EAAC,GAAGJ;IAE1C,OAAO;QACLK,OAAO;QACPC,MAAM;QAENC;YACE,IAAI,CAACC,QAAQ,CAAC;gBACZC,IAAIX;gBACJQ,MAAM;gBACNI,MAAM;YACR;QACF;QAEA,MAAMC,gBAAeC,QAAQ,EAAEC,YAAY;YACzC,MAAMC,SAASD;YACf,MAAME,YAAYC,OAAOC,MAAM,CAACH,QAAQI,IAAI,CAC1C,CAACC,OACCA,KAAKT,IAAI,KAAK,WACdS,KAAKb,IAAI,KAAK,YACda,KAAKC,cAAc,EAAEC,SAASvB;YAGlC,IAAI,CAACiB,WAAW;gBACd,MAAM,IAAIO,MAAM,CAAC,qCAAqC,EAAExB,aAAa,CAAC,CAAC;YACzE;YAEA,IAAIiB,UAAUL,IAAI,KAAK,SAAS;gBAC9B,MAAM,IAAIY,MAAM;YAClB;YAEA,MAAMC,gBAAgBR,UAAUS,QAAQ;YACxC,MAAMC,YAAY;gBAACxB,SAASyB,OAAO,CAAC,QAAQ;gBAAKH;aAAc,CAACI,IAAI,CAAC;YAErE,IAAIC,MAAgB,EAAE;YACtB,IAAIb,UAAUc,YAAY,EAAEC,aAAa;gBACvC,wEAAwE;gBACxE,sCAAsC;gBACtCF,MAAM;uBAAIb,UAAUc,YAAY,CAACC,WAAW;iBAAC;gBAC7C,KAAK,MAAMC,OAAOhB,UAAUiB,OAAO,CAAE;oBACnC,+DAA+D;oBAC/D,wCAAwC;oBACxC,MAAMC,QAAQnB,MAAM,CAACiB,IAAI;oBACzB,MAAMD,cACJG,SAASA,MAAMvB,IAAI,KAAK,UAAUuB,MAAMJ,YAAY,CAACC,WAAW,GAAGI;oBAErE,IAAIJ,aAAa;wBACfF,IAAIO,IAAI,IAAIL;oBACd;gBACF;YACF;YAEA,IAAI,CAACtB,QAAQ,CAAC;gBACZgB,UAAU;gBACVY,QAAQxC,+BACND,8BACE,MAAME,eAAe;oBACnBM;oBACAC;oBACAiC,OAAO;wBACLpC;wBACA2B;wBACAH;oBACF;oBACAa,gBAAgBpC;gBAClB;gBAGJQ,MAAM;YACR;QACF;IACF;AACF"}
|
|
@@ -21,6 +21,8 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
|
|
|
21
21
|
telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId);
|
|
22
22
|
let cachedConsent = null;
|
|
23
23
|
let filePath = null;
|
|
24
|
+
let initComplete = false;
|
|
25
|
+
const eventBuffer = [];
|
|
24
26
|
const initializeConsent = async ()=>{
|
|
25
27
|
if (cachedConsent) return;
|
|
26
28
|
try {
|
|
@@ -48,34 +50,37 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
|
|
|
48
50
|
filePath = null;
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
|
-
const
|
|
52
|
-
if (!cachedConsent || cachedConsent.status !== 'granted') {
|
|
53
|
-
if (cachedConsent) {
|
|
54
|
-
telemetryStoreDebug('Cached consent not granted (%s), skipping event: %s', cachedConsent.status, event.type);
|
|
55
|
-
} else {
|
|
56
|
-
telemetryStoreDebug('Consent not resolved, skipping event: %s', event.type);
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
53
|
+
const writeEvent = (event)=>{
|
|
60
54
|
if (!filePath) {
|
|
61
|
-
telemetryStoreDebug('
|
|
55
|
+
telemetryStoreDebug('No file path, skipping event: %s', event.type);
|
|
62
56
|
return;
|
|
63
57
|
}
|
|
64
58
|
telemetryStoreDebug('Emitting event: %s', event.type);
|
|
65
59
|
try {
|
|
66
|
-
const eventLine = JSON.stringify(event) + '\n';
|
|
67
60
|
// We use synchronous file writes to ensure telemetry events are captured even when
|
|
68
61
|
// the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).
|
|
69
62
|
// The performance impact is probably negligible and is worth the trade-off
|
|
70
63
|
// for 100% reliability. Async writes would be lost when the event loop
|
|
71
64
|
// shuts down during process exit.
|
|
72
|
-
appendFileSync(filePath,
|
|
65
|
+
appendFileSync(filePath, JSON.stringify(event) + '\n', 'utf8');
|
|
73
66
|
telemetryStoreDebug('Successfully wrote event to file: %s', filePath);
|
|
74
67
|
} catch (error) {
|
|
75
68
|
telemetryStoreDebug('Failed to write telemetry event: %o', error);
|
|
76
69
|
// Silent failure - don't break CLI functionality
|
|
77
70
|
}
|
|
78
71
|
};
|
|
72
|
+
const emit = (event)=>{
|
|
73
|
+
if (!initComplete) {
|
|
74
|
+
telemetryStoreDebug('Init pending, buffering event: %s', event.type);
|
|
75
|
+
eventBuffer.push(event);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!cachedConsent || cachedConsent.status !== 'granted') {
|
|
79
|
+
telemetryStoreDebug('Consent not granted (%s), skipping event: %s', cachedConsent?.status ?? 'unresolved', event.type);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
writeEvent(event);
|
|
83
|
+
};
|
|
79
84
|
const logger = createLogger(sessionId, emit);
|
|
80
85
|
// Initialize both consent and file path concurrently
|
|
81
86
|
Promise.allSettled([
|
|
@@ -88,6 +93,16 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
|
|
|
88
93
|
telemetryStoreDebug('Error initializing %s: %o', type, result.reason);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
96
|
+
initComplete = true;
|
|
97
|
+
if (cachedConsent?.status === 'granted' && filePath) {
|
|
98
|
+
telemetryStoreDebug('Flushing %d buffered event(s)', eventBuffer.length);
|
|
99
|
+
for (const event of eventBuffer){
|
|
100
|
+
writeEvent(event);
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
telemetryStoreDebug('Discarding %d buffered event(s), consent: %s', eventBuffer.length, cachedConsent?.status ?? 'unresolved');
|
|
104
|
+
}
|
|
105
|
+
eventBuffer.length = 0;
|
|
91
106
|
});
|
|
92
107
|
return logger;
|
|
93
108
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/util/telemetry/createTelemetryStore.ts"],"sourcesContent":["import {appendFileSync} from 'node:fs'\nimport {mkdir} from 'node:fs/promises'\nimport {dirname} from 'node:path'\n\nimport {\n type CLITelemetryStore,\n type ConsentInformation,\n type TelemetryUserProperties,\n} from '@sanity/cli-core'\nimport {type TelemetryEvent} from '@sanity/telemetry'\n\nimport {generateTelemetryFilePath} from './generateTelemetryFilePath.js'\nimport {createLogger} from './logger.js'\nimport {telemetryStoreDebug} from './telemetryStoreDebug.js'\n\n/**\n * FILE MANAGEMENT STRATEGY:\n *\n * The telemetry system uses a multi-file approach to handle concurrent CLI processes:\n *\n * 1. WRITING (per session):\n * - Each CLI session gets a unique file: telemetry-\\{hash\\}-\\{env\\}-\\{sessionId\\}.ndjson\n * - Prevents write conflicts when multiple CLI commands run simultaneously\n * - Events are written using an RxJS queue for ordered processing with retry logic\n *\n * 2. FLUSHING (aggregate all sessions):\n * - findTelemetryFiles() discovers ALL telemetry files for user/environment\n * - Events are collected from all session files and sent as a batch\n * - Files are deleted after successful transmission\n *\n * 3. CLEANUP (background maintenance):\n * - cleanupOldTelemetryFiles() removes stale files older than 7 days\n * - Prevents disk space accumulation from abandoned sessions\n */\n\ninterface CreateTelemetryStoreOptions {\n resolveConsent: () => Promise<ConsentInformation>\n}\n\n/**\n * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.\n *\n * Key optimizations:\n * - Consent resolved once at creation and cached (vs checking on every emit)\n * - File path generated and directory created once during initialization\n * - Synchronous file writes to ensure events are captured even during process exit\n *\n * @param sessionId - Unique session identifier for file isolation\n * @param options - Configuration options\n * @returns TelemetryStore instance compatible with the telemetry interface\n *\n * @internal\n */\nexport function createTelemetryStore(\n sessionId: string,\n options: CreateTelemetryStoreOptions,\n): CLITelemetryStore {\n telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId)\n\n let cachedConsent: ConsentInformation | null = null\n let filePath: string | null = null\n\n const initializeConsent = async () => {\n if (cachedConsent) return\n\n try {\n cachedConsent = await options.resolveConsent()\n telemetryStoreDebug('Cached consent status: %s', cachedConsent.status)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error)\n cachedConsent = {reason: 'fetchError', status: 'undetermined'}\n }\n }\n\n const initializeFilePath = async () => {\n if (filePath) return\n\n try {\n filePath = await generateTelemetryFilePath(sessionId)\n telemetryStoreDebug('Generated file path: %s', filePath)\n\n await mkdir(dirname(filePath), {recursive: true})\n telemetryStoreDebug('Created directory structure for: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize file path: %o', error)\n filePath = null\n }\n }\n\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/util/telemetry/createTelemetryStore.ts"],"sourcesContent":["import {appendFileSync} from 'node:fs'\nimport {mkdir} from 'node:fs/promises'\nimport {dirname} from 'node:path'\n\nimport {\n type CLITelemetryStore,\n type ConsentInformation,\n type TelemetryUserProperties,\n} from '@sanity/cli-core'\nimport {type TelemetryEvent} from '@sanity/telemetry'\n\nimport {generateTelemetryFilePath} from './generateTelemetryFilePath.js'\nimport {createLogger} from './logger.js'\nimport {telemetryStoreDebug} from './telemetryStoreDebug.js'\n\n/**\n * FILE MANAGEMENT STRATEGY:\n *\n * The telemetry system uses a multi-file approach to handle concurrent CLI processes:\n *\n * 1. WRITING (per session):\n * - Each CLI session gets a unique file: telemetry-\\{hash\\}-\\{env\\}-\\{sessionId\\}.ndjson\n * - Prevents write conflicts when multiple CLI commands run simultaneously\n * - Events are written using an RxJS queue for ordered processing with retry logic\n *\n * 2. FLUSHING (aggregate all sessions):\n * - findTelemetryFiles() discovers ALL telemetry files for user/environment\n * - Events are collected from all session files and sent as a batch\n * - Files are deleted after successful transmission\n *\n * 3. CLEANUP (background maintenance):\n * - cleanupOldTelemetryFiles() removes stale files older than 7 days\n * - Prevents disk space accumulation from abandoned sessions\n */\n\ninterface CreateTelemetryStoreOptions {\n resolveConsent: () => Promise<ConsentInformation>\n}\n\n/**\n * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.\n *\n * Key optimizations:\n * - Consent resolved once at creation and cached (vs checking on every emit)\n * - File path generated and directory created once during initialization\n * - Synchronous file writes to ensure events are captured even during process exit\n *\n * @param sessionId - Unique session identifier for file isolation\n * @param options - Configuration options\n * @returns TelemetryStore instance compatible with the telemetry interface\n *\n * @internal\n */\nexport function createTelemetryStore(\n sessionId: string,\n options: CreateTelemetryStoreOptions,\n): CLITelemetryStore {\n telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId)\n\n let cachedConsent: ConsentInformation | null = null\n let filePath: string | null = null\n let initComplete = false\n const eventBuffer: TelemetryEvent[] = []\n\n const initializeConsent = async () => {\n if (cachedConsent) return\n\n try {\n cachedConsent = await options.resolveConsent()\n telemetryStoreDebug('Cached consent status: %s', cachedConsent.status)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error)\n cachedConsent = {reason: 'fetchError', status: 'undetermined'}\n }\n }\n\n const initializeFilePath = async () => {\n if (filePath) return\n\n try {\n filePath = await generateTelemetryFilePath(sessionId)\n telemetryStoreDebug('Generated file path: %s', filePath)\n\n await mkdir(dirname(filePath), {recursive: true})\n telemetryStoreDebug('Created directory structure for: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize file path: %o', error)\n filePath = null\n }\n }\n\n const writeEvent = (event: TelemetryEvent) => {\n if (!filePath) {\n telemetryStoreDebug('No file path, skipping event: %s', event.type)\n return\n }\n\n telemetryStoreDebug('Emitting event: %s', event.type)\n\n try {\n // We use synchronous file writes to ensure telemetry events are captured even when\n // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).\n // The performance impact is probably negligible and is worth the trade-off\n // for 100% reliability. Async writes would be lost when the event loop\n // shuts down during process exit.\n appendFileSync(filePath, JSON.stringify(event) + '\\n', 'utf8')\n telemetryStoreDebug('Successfully wrote event to file: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to write telemetry event: %o', error)\n // Silent failure - don't break CLI functionality\n }\n }\n\n const emit = (event: TelemetryEvent) => {\n if (!initComplete) {\n telemetryStoreDebug('Init pending, buffering event: %s', event.type)\n eventBuffer.push(event)\n return\n }\n\n if (!cachedConsent || cachedConsent.status !== 'granted') {\n telemetryStoreDebug(\n 'Consent not granted (%s), skipping event: %s',\n cachedConsent?.status ?? 'unresolved',\n event.type,\n )\n return\n }\n\n writeEvent(event)\n }\n\n const logger = createLogger<TelemetryUserProperties>(sessionId, emit)\n\n // Initialize both consent and file path concurrently\n Promise.allSettled([initializeConsent(), initializeFilePath()]).then((results) => {\n for (const [index, result] of results.entries()) {\n if (result.status === 'rejected') {\n const type = index === 0 ? 'consent' : 'file path'\n telemetryStoreDebug('Error initializing %s: %o', type, result.reason)\n }\n }\n\n initComplete = true\n\n if (cachedConsent?.status === 'granted' && filePath) {\n telemetryStoreDebug('Flushing %d buffered event(s)', eventBuffer.length)\n for (const event of eventBuffer) {\n writeEvent(event)\n }\n } else {\n telemetryStoreDebug(\n 'Discarding %d buffered event(s), consent: %s',\n eventBuffer.length,\n cachedConsent?.status ?? 'unresolved',\n )\n }\n eventBuffer.length = 0\n })\n\n return logger\n}\n"],"names":["appendFileSync","mkdir","dirname","generateTelemetryFilePath","createLogger","telemetryStoreDebug","createTelemetryStore","sessionId","options","cachedConsent","filePath","initComplete","eventBuffer","initializeConsent","resolveConsent","status","error","reason","initializeFilePath","recursive","writeEvent","event","type","JSON","stringify","emit","push","logger","Promise","allSettled","then","results","index","result","entries","length"],"mappings":"AAAA,SAAQA,cAAc,QAAO,UAAS;AACtC,SAAQC,KAAK,QAAO,mBAAkB;AACtC,SAAQC,OAAO,QAAO,YAAW;AASjC,SAAQC,yBAAyB,QAAO,iCAAgC;AACxE,SAAQC,YAAY,QAAO,cAAa;AACxC,SAAQC,mBAAmB,QAAO,2BAA0B;AA0B5D;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,qBACdC,SAAiB,EACjBC,OAAoC;IAEpCH,oBAAoB,+CAA+CE;IAEnE,IAAIE,gBAA2C;IAC/C,IAAIC,WAA0B;IAC9B,IAAIC,eAAe;IACnB,MAAMC,cAAgC,EAAE;IAExC,MAAMC,oBAAoB;QACxB,IAAIJ,eAAe;QAEnB,IAAI;YACFA,gBAAgB,MAAMD,QAAQM,cAAc;YAC5CT,oBAAoB,6BAA6BI,cAAcM,MAAM;QACvE,EAAE,OAAOC,OAAO;YACdX,oBAAoB,8DAA8DW;YAClFP,gBAAgB;gBAACQ,QAAQ;gBAAcF,QAAQ;YAAc;QAC/D;IACF;IAEA,MAAMG,qBAAqB;QACzB,IAAIR,UAAU;QAEd,IAAI;YACFA,WAAW,MAAMP,0BAA0BI;YAC3CF,oBAAoB,2BAA2BK;YAE/C,MAAMT,MAAMC,QAAQQ,WAAW;gBAACS,WAAW;YAAI;YAC/Cd,oBAAoB,uCAAuCK;QAC7D,EAAE,OAAOM,OAAO;YACdX,oBAAoB,sCAAsCW;YAC1DN,WAAW;QACb;IACF;IAEA,MAAMU,aAAa,CAACC;QAClB,IAAI,CAACX,UAAU;YACbL,oBAAoB,oCAAoCgB,MAAMC,IAAI;YAClE;QACF;QAEAjB,oBAAoB,sBAAsBgB,MAAMC,IAAI;QAEpD,IAAI;YACF,mFAAmF;YACnF,iFAAiF;YACjF,2EAA2E;YAC3E,uEAAuE;YACvE,kCAAkC;YAClCtB,eAAeU,UAAUa,KAAKC,SAAS,CAACH,SAAS,MAAM;YACvDhB,oBAAoB,wCAAwCK;QAC9D,EAAE,OAAOM,OAAO;YACdX,oBAAoB,uCAAuCW;QAC3D,iDAAiD;QACnD;IACF;IAEA,MAAMS,OAAO,CAACJ;QACZ,IAAI,CAACV,cAAc;YACjBN,oBAAoB,qCAAqCgB,MAAMC,IAAI;YACnEV,YAAYc,IAAI,CAACL;YACjB;QACF;QAEA,IAAI,CAACZ,iBAAiBA,cAAcM,MAAM,KAAK,WAAW;YACxDV,oBACE,gDACAI,eAAeM,UAAU,cACzBM,MAAMC,IAAI;YAEZ;QACF;QAEAF,WAAWC;IACb;IAEA,MAAMM,SAASvB,aAAsCG,WAAWkB;IAEhE,qDAAqD;IACrDG,QAAQC,UAAU,CAAC;QAAChB;QAAqBK;KAAqB,EAAEY,IAAI,CAAC,CAACC;QACpE,KAAK,MAAM,CAACC,OAAOC,OAAO,IAAIF,QAAQG,OAAO,GAAI;YAC/C,IAAID,OAAOlB,MAAM,KAAK,YAAY;gBAChC,MAAMO,OAAOU,UAAU,IAAI,YAAY;gBACvC3B,oBAAoB,6BAA6BiB,MAAMW,OAAOhB,MAAM;YACtE;QACF;QAEAN,eAAe;QAEf,IAAIF,eAAeM,WAAW,aAAaL,UAAU;YACnDL,oBAAoB,iCAAiCO,YAAYuB,MAAM;YACvE,KAAK,MAAMd,SAAST,YAAa;gBAC/BQ,WAAWC;YACb;QACF,OAAO;YACLhB,oBACE,gDACAO,YAAYuB,MAAM,EAClB1B,eAAeM,UAAU;QAE7B;QACAH,YAAYuB,MAAM,GAAG;IACvB;IAEA,OAAOR;AACT"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { parse } from 'groq-js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates that a string is a syntactically valid GROQ object projection
|
|
4
|
+
* suitable for embeddings indexing.
|
|
5
|
+
*
|
|
6
|
+
* Rejects:
|
|
7
|
+
* - Invalid GROQ syntax
|
|
8
|
+
* - Non-object expressions (filters, function calls, literals, etc.)
|
|
9
|
+
* - Dereferences (`->`) -- the indexer cannot resolve cross-document references
|
|
10
|
+
* - Full queries (`*[...]`) -- dataset scans are not supported
|
|
11
|
+
* - `user::` namespace functions -- user-defined functions are not available
|
|
12
|
+
*/ export function validateProjection(projection) {
|
|
13
|
+
const tree = parse(projection);
|
|
14
|
+
if (tree.type !== 'Object') {
|
|
15
|
+
throw new Error(`Expected a GROQ projection (e.g. "{ title, body }"), but received a ${tree.type} expression`);
|
|
16
|
+
}
|
|
17
|
+
walk(tree);
|
|
18
|
+
}
|
|
19
|
+
function walk(node) {
|
|
20
|
+
if (!node) return;
|
|
21
|
+
switch(node.type){
|
|
22
|
+
case 'AccessAttribute':
|
|
23
|
+
case 'AccessElement':
|
|
24
|
+
case 'ArrayCoerce':
|
|
25
|
+
case 'Asc':
|
|
26
|
+
case 'Desc':
|
|
27
|
+
case 'Group':
|
|
28
|
+
case 'Neg':
|
|
29
|
+
case 'Not':
|
|
30
|
+
case 'Pos':
|
|
31
|
+
case 'Slice':
|
|
32
|
+
{
|
|
33
|
+
walk(node.base);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'And':
|
|
37
|
+
case 'OpCall':
|
|
38
|
+
case 'Or':
|
|
39
|
+
{
|
|
40
|
+
walk(node.left);
|
|
41
|
+
walk(node.right);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'Array':
|
|
45
|
+
{
|
|
46
|
+
for (const el of node.elements)walk(el.value);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case 'Everything':
|
|
50
|
+
{
|
|
51
|
+
throw new Error('Projection must not contain full queries (*[...])');
|
|
52
|
+
}
|
|
53
|
+
case 'Deref':
|
|
54
|
+
{
|
|
55
|
+
throw new Error('Projection must not contain dereferences (->)');
|
|
56
|
+
}
|
|
57
|
+
case 'FuncCall':
|
|
58
|
+
{
|
|
59
|
+
if (node.namespace === 'user') {
|
|
60
|
+
throw new Error('Projection must not contain user:: functions');
|
|
61
|
+
}
|
|
62
|
+
for (const arg of node.args)walk(arg);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'Filter':
|
|
66
|
+
case 'FlatMap':
|
|
67
|
+
case 'Map':
|
|
68
|
+
case 'Projection':
|
|
69
|
+
{
|
|
70
|
+
walk(node.base);
|
|
71
|
+
walk(node.expr);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'InRange':
|
|
75
|
+
{
|
|
76
|
+
walk(node.base);
|
|
77
|
+
walk(node.left);
|
|
78
|
+
walk(node.right);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'Object':
|
|
82
|
+
{
|
|
83
|
+
for (const attr of node.attributes){
|
|
84
|
+
switch(attr.type){
|
|
85
|
+
case 'ObjectAttributeValue':
|
|
86
|
+
case 'ObjectSplat':
|
|
87
|
+
{
|
|
88
|
+
walk(attr.value);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'ObjectConditionalSplat':
|
|
92
|
+
{
|
|
93
|
+
walk(attr.condition);
|
|
94
|
+
walk(attr.value);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'PipeFuncCall':
|
|
103
|
+
{
|
|
104
|
+
walk(node.base);
|
|
105
|
+
for (const arg of node.args)walk(arg);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'Select':
|
|
109
|
+
{
|
|
110
|
+
for (const alt of node.alternatives){
|
|
111
|
+
walk(alt.condition);
|
|
112
|
+
walk(alt.value);
|
|
113
|
+
}
|
|
114
|
+
walk(node.fallback);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=validateProjection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/validateProjection.ts"],"sourcesContent":["import {type ExprNode, parse} from 'groq-js'\n\n/**\n * Validates that a string is a syntactically valid GROQ object projection\n * suitable for embeddings indexing.\n *\n * Rejects:\n * - Invalid GROQ syntax\n * - Non-object expressions (filters, function calls, literals, etc.)\n * - Dereferences (`->`) -- the indexer cannot resolve cross-document references\n * - Full queries (`*[...]`) -- dataset scans are not supported\n * - `user::` namespace functions -- user-defined functions are not available\n */\nexport function validateProjection(projection: string): void {\n const tree = parse(projection)\n if (tree.type !== 'Object') {\n throw new Error(\n `Expected a GROQ projection (e.g. \"{ title, body }\"), but received a ${tree.type} expression`,\n )\n }\n walk(tree)\n}\n\nfunction walk(node: ExprNode | undefined): void {\n if (!node) return\n\n switch (node.type) {\n case 'AccessAttribute':\n case 'AccessElement':\n case 'ArrayCoerce':\n case 'Asc':\n case 'Desc':\n case 'Group':\n case 'Neg':\n case 'Not':\n case 'Pos':\n case 'Slice': {\n walk(node.base)\n break\n }\n case 'And':\n case 'OpCall':\n case 'Or': {\n walk(node.left)\n walk(node.right)\n break\n }\n case 'Array': {\n for (const el of node.elements) walk(el.value)\n break\n }\n case 'Everything': {\n throw new Error('Projection must not contain full queries (*[...])')\n }\n case 'Deref': {\n throw new Error('Projection must not contain dereferences (->)')\n }\n case 'FuncCall': {\n if (node.namespace === 'user') {\n throw new Error('Projection must not contain user:: functions')\n }\n for (const arg of node.args) walk(arg)\n break\n }\n case 'Filter':\n case 'FlatMap':\n case 'Map':\n case 'Projection': {\n walk(node.base)\n walk(node.expr)\n break\n }\n case 'InRange': {\n walk(node.base)\n walk(node.left)\n walk(node.right)\n break\n }\n case 'Object': {\n for (const attr of node.attributes) {\n switch (attr.type) {\n case 'ObjectAttributeValue':\n case 'ObjectSplat': {\n walk(attr.value)\n break\n }\n case 'ObjectConditionalSplat': {\n walk(attr.condition)\n walk(attr.value)\n break\n }\n default:\n }\n }\n break\n }\n case 'PipeFuncCall': {\n walk(node.base)\n for (const arg of node.args) walk(arg)\n break\n }\n case 'Select': {\n for (const alt of node.alternatives) {\n walk(alt.condition)\n walk(alt.value)\n }\n walk(node.fallback)\n break\n }\n default:\n }\n}\n"],"names":["parse","validateProjection","projection","tree","type","Error","walk","node","base","left","right","el","elements","value","namespace","arg","args","expr","attr","attributes","condition","alt","alternatives","fallback"],"mappings":"AAAA,SAAuBA,KAAK,QAAO,UAAS;AAE5C;;;;;;;;;;CAUC,GACD,OAAO,SAASC,mBAAmBC,UAAkB;IACnD,MAAMC,OAAOH,MAAME;IACnB,IAAIC,KAAKC,IAAI,KAAK,UAAU;QAC1B,MAAM,IAAIC,MACR,CAAC,oEAAoE,EAAEF,KAAKC,IAAI,CAAC,WAAW,CAAC;IAEjG;IACAE,KAAKH;AACP;AAEA,SAASG,KAAKC,IAA0B;IACtC,IAAI,CAACA,MAAM;IAEX,OAAQA,KAAKH,IAAI;QACf,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YAAS;gBACZE,KAAKC,KAAKC,IAAI;gBACd;YACF;QACA,KAAK;QACL,KAAK;QACL,KAAK;YAAM;gBACTF,KAAKC,KAAKE,IAAI;gBACdH,KAAKC,KAAKG,KAAK;gBACf;YACF;QACA,KAAK;YAAS;gBACZ,KAAK,MAAMC,MAAMJ,KAAKK,QAAQ,CAAEN,KAAKK,GAAGE,KAAK;gBAC7C;YACF;QACA,KAAK;YAAc;gBACjB,MAAM,IAAIR,MAAM;YAClB;QACA,KAAK;YAAS;gBACZ,MAAM,IAAIA,MAAM;YAClB;QACA,KAAK;YAAY;gBACf,IAAIE,KAAKO,SAAS,KAAK,QAAQ;oBAC7B,MAAM,IAAIT,MAAM;gBAClB;gBACA,KAAK,MAAMU,OAAOR,KAAKS,IAAI,CAAEV,KAAKS;gBAClC;YACF;QACA,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YAAc;gBACjBT,KAAKC,KAAKC,IAAI;gBACdF,KAAKC,KAAKU,IAAI;gBACd;YACF;QACA,KAAK;YAAW;gBACdX,KAAKC,KAAKC,IAAI;gBACdF,KAAKC,KAAKE,IAAI;gBACdH,KAAKC,KAAKG,KAAK;gBACf;YACF;QACA,KAAK;YAAU;gBACb,KAAK,MAAMQ,QAAQX,KAAKY,UAAU,CAAE;oBAClC,OAAQD,KAAKd,IAAI;wBACf,KAAK;wBACL,KAAK;4BAAe;gCAClBE,KAAKY,KAAKL,KAAK;gCACf;4BACF;wBACA,KAAK;4BAA0B;gCAC7BP,KAAKY,KAAKE,SAAS;gCACnBd,KAAKY,KAAKL,KAAK;gCACf;4BACF;wBACA;oBACF;gBACF;gBACA;YACF;QACA,KAAK;YAAgB;gBACnBP,KAAKC,KAAKC,IAAI;gBACd,KAAK,MAAMO,OAAOR,KAAKS,IAAI,CAAEV,KAAKS;gBAClC;YACF;QACA,KAAK;YAAU;gBACb,KAAK,MAAMM,OAAOd,KAAKe,YAAY,CAAE;oBACnChB,KAAKe,IAAID,SAAS;oBAClBd,KAAKe,IAAIR,KAAK;gBAChB;gBACAP,KAAKC,KAAKgB,QAAQ;gBAClB;YACF;QACA;IACF;AACF"}
|