@sanity/cli 8.18.0 → 8.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/init/bootstrapRemoteTemplate.js +6 -2
- package/dist/actions/init/bootstrapRemoteTemplate.js.map +1 -1
- package/dist/actions/tokens/grants.js +192 -0
- package/dist/actions/tokens/grants.js.map +1 -0
- package/dist/commands/tokens/create.js +223 -83
- package/dist/commands/tokens/create.js.map +1 -1
- package/dist/generated/apiRoutes.js +2 -0
- package/dist/generated/apiRoutes.js.map +1 -1
- package/dist/prompts/promptForGrants.js +64 -0
- package/dist/prompts/promptForGrants.js.map +1 -0
- package/dist/services/tokens.js +36 -22
- package/dist/services/tokens.js.map +1 -1
- package/oclif.manifest.json +636 -619
- package/package.json +3 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { roleChecklist } from '@sanity/cli-core/ux';
|
|
2
|
+
import { DEFAULT_ROLE, ORG_PERMISSIONS, projectGrantKey } from '../actions/tokens/grants.js';
|
|
3
|
+
import { getTokenRoles } from '../services/tokens.js';
|
|
4
|
+
const defaultRole = {
|
|
5
|
+
title: DEFAULT_ROLE.title,
|
|
6
|
+
value: DEFAULT_ROLE.name
|
|
7
|
+
};
|
|
8
|
+
function toOrgItem(permission) {
|
|
9
|
+
const item = {
|
|
10
|
+
name: permission.title,
|
|
11
|
+
value: permission.key
|
|
12
|
+
};
|
|
13
|
+
if ('roleName' in permission) return item;
|
|
14
|
+
return {
|
|
15
|
+
...item,
|
|
16
|
+
role: defaultRole.value,
|
|
17
|
+
roles: permission.roles.map(({ key, title })=>({
|
|
18
|
+
title,
|
|
19
|
+
value: key
|
|
20
|
+
}))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function toProjectItem(project, preselectedProjectId) {
|
|
24
|
+
return {
|
|
25
|
+
checked: project.id === preselectedProjectId,
|
|
26
|
+
loadRoles: async ()=>{
|
|
27
|
+
const roles = await getTokenRoles(project.id);
|
|
28
|
+
return roles.filter((role)=>role.appliesToRobots).map(({ name, title })=>({
|
|
29
|
+
title,
|
|
30
|
+
value: name
|
|
31
|
+
}));
|
|
32
|
+
},
|
|
33
|
+
name: project.displayName,
|
|
34
|
+
role: defaultRole.value,
|
|
35
|
+
roles: [
|
|
36
|
+
defaultRole
|
|
37
|
+
],
|
|
38
|
+
suffix: `(${project.id})`,
|
|
39
|
+
value: projectGrantKey(project.id)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function promptForGrants({ preselectedProjectId, projects }) {
|
|
43
|
+
const answers = await roleChecklist({
|
|
44
|
+
message: 'What should this token access?',
|
|
45
|
+
sections: [
|
|
46
|
+
{
|
|
47
|
+
items: ORG_PERMISSIONS.map((permission)=>toOrgItem(permission)),
|
|
48
|
+
title: 'Organization'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
items: projects.map((project)=>toProjectItem(project, preselectedProjectId)),
|
|
52
|
+
title: 'Projects'
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
return answers.map(({ role, value })=>({
|
|
57
|
+
key: value,
|
|
58
|
+
roles: role ? [
|
|
59
|
+
role
|
|
60
|
+
] : []
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=promptForGrants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/prompts/promptForGrants.ts"],"sourcesContent":["import {type ChecklistItem, roleChecklist} from '@sanity/cli-core/ux'\n\nimport {\n DEFAULT_ROLE,\n type Grant,\n ORG_PERMISSIONS,\n type OrgPermission,\n projectGrantKey,\n} from '../actions/tokens/grants.js'\nimport {getTokenRoles} from '../services/tokens.js'\n\ntype Project = {displayName: string; id: string}\n\nconst defaultRole = {title: DEFAULT_ROLE.title, value: DEFAULT_ROLE.name}\n\nfunction toOrgItem(permission: OrgPermission): ChecklistItem {\n const item = {name: permission.title, value: permission.key}\n if ('roleName' in permission) return item\n\n return {\n ...item,\n role: defaultRole.value,\n roles: permission.roles.map(({key, title}) => ({title, value: key})),\n }\n}\n\nfunction toProjectItem(project: Project, preselectedProjectId: string | undefined): ChecklistItem {\n return {\n checked: project.id === preselectedProjectId,\n loadRoles: async () => {\n const roles = await getTokenRoles(project.id)\n return roles\n .filter((role) => role.appliesToRobots)\n .map(({name, title}) => ({title, value: name}))\n },\n name: project.displayName,\n role: defaultRole.value,\n roles: [defaultRole],\n suffix: `(${project.id})`,\n value: projectGrantKey(project.id),\n }\n}\n\nexport async function promptForGrants({\n preselectedProjectId,\n projects,\n}: {\n preselectedProjectId: string | undefined\n projects: Project[]\n}): Promise<Grant[]> {\n const answers = await roleChecklist({\n message: 'What should this token access?',\n sections: [\n {\n items: ORG_PERMISSIONS.map((permission) => toOrgItem(permission)),\n title: 'Organization',\n },\n {\n items: projects.map((project) => toProjectItem(project, preselectedProjectId)),\n title: 'Projects',\n },\n ],\n })\n\n return answers.map(({role, value}) => ({\n key: value,\n roles: role ? [role] : [],\n }))\n}\n"],"names":["roleChecklist","DEFAULT_ROLE","ORG_PERMISSIONS","projectGrantKey","getTokenRoles","defaultRole","title","value","name","toOrgItem","permission","item","key","role","roles","map","toProjectItem","project","preselectedProjectId","checked","id","loadRoles","filter","appliesToRobots","displayName","suffix","promptForGrants","projects","answers","message","sections","items"],"mappings":"AAAA,SAA4BA,aAAa,QAAO,sBAAqB;AAErE,SACEC,YAAY,EAEZC,eAAe,EAEfC,eAAe,QACV,8BAA6B;AACpC,SAAQC,aAAa,QAAO,wBAAuB;AAInD,MAAMC,cAAc;IAACC,OAAOL,aAAaK,KAAK;IAAEC,OAAON,aAAaO,IAAI;AAAA;AAExE,SAASC,UAAUC,UAAyB;IAC1C,MAAMC,OAAO;QAACH,MAAME,WAAWJ,KAAK;QAAEC,OAAOG,WAAWE,GAAG;IAAA;IAC3D,IAAI,cAAcF,YAAY,OAAOC;IAErC,OAAO;QACL,GAAGA,IAAI;QACPE,MAAMR,YAAYE,KAAK;QACvBO,OAAOJ,WAAWI,KAAK,CAACC,GAAG,CAAC,CAAC,EAACH,GAAG,EAAEN,KAAK,EAAC,GAAM,CAAA;gBAACA;gBAAOC,OAAOK;YAAG,CAAA;IACnE;AACF;AAEA,SAASI,cAAcC,OAAgB,EAAEC,oBAAwC;IAC/E,OAAO;QACLC,SAASF,QAAQG,EAAE,KAAKF;QACxBG,WAAW;YACT,MAAMP,QAAQ,MAAMV,cAAca,QAAQG,EAAE;YAC5C,OAAON,MACJQ,MAAM,CAAC,CAACT,OAASA,KAAKU,eAAe,EACrCR,GAAG,CAAC,CAAC,EAACP,IAAI,EAAEF,KAAK,EAAC,GAAM,CAAA;oBAACA;oBAAOC,OAAOC;gBAAI,CAAA;QAChD;QACAA,MAAMS,QAAQO,WAAW;QACzBX,MAAMR,YAAYE,KAAK;QACvBO,OAAO;YAACT;SAAY;QACpBoB,QAAQ,CAAC,CAAC,EAAER,QAAQG,EAAE,CAAC,CAAC,CAAC;QACzBb,OAAOJ,gBAAgBc,QAAQG,EAAE;IACnC;AACF;AAEA,OAAO,eAAeM,gBAAgB,EACpCR,oBAAoB,EACpBS,QAAQ,EAIT;IACC,MAAMC,UAAU,MAAM5B,cAAc;QAClC6B,SAAS;QACTC,UAAU;YACR;gBACEC,OAAO7B,gBAAgBa,GAAG,CAAC,CAACL,aAAeD,UAAUC;gBACrDJ,OAAO;YACT;YACA;gBACEyB,OAAOJ,SAASZ,GAAG,CAAC,CAACE,UAAYD,cAAcC,SAASC;gBACxDZ,OAAO;YACT;SACD;IACH;IAEA,OAAOsB,QAAQb,GAAG,CAAC,CAAC,EAACF,IAAI,EAAEN,KAAK,EAAC,GAAM,CAAA;YACrCK,KAAKL;YACLO,OAAOD,OAAO;gBAACA;aAAK,GAAG,EAAE;QAC3B,CAAA;AACF"}
|
package/dist/services/tokens.js
CHANGED
|
@@ -34,38 +34,52 @@ async function fetchAllPages(client, url) {
|
|
|
34
34
|
*/ export function getProjectMembership(robot, projectId) {
|
|
35
35
|
return robot.memberships.find((membership)=>membership.resourceType === 'project' && membership.resourceId === projectId);
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
* Add a token to a project
|
|
39
|
-
* @param options - The options for adding a token to a project
|
|
40
|
-
* @returns A promise that resolves to the created robot, including its secret token
|
|
41
|
-
*
|
|
42
|
-
* @internal
|
|
43
|
-
*/ export async function createToken(options) {
|
|
44
|
-
const { expiresAt, label, projectId, roleName, sendNotification } = options;
|
|
37
|
+
async function postRobot(url, { expiresAt, ...body }, query = {}) {
|
|
45
38
|
const client = await getClient();
|
|
46
39
|
return client.request({
|
|
47
40
|
body: {
|
|
48
|
-
|
|
49
|
-
memberships: [
|
|
50
|
-
{
|
|
51
|
-
resourceId: projectId,
|
|
52
|
-
resourceType: 'project',
|
|
53
|
-
roleNames: [
|
|
54
|
-
roleName
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
],
|
|
41
|
+
...body,
|
|
58
42
|
...expiresAt === undefined ? {} : {
|
|
59
43
|
expiresAt
|
|
60
44
|
}
|
|
61
45
|
},
|
|
62
46
|
method: 'POST',
|
|
63
|
-
query
|
|
64
|
-
|
|
65
|
-
} : {},
|
|
66
|
-
url: `/access/project/${projectId}/robots`
|
|
47
|
+
query,
|
|
48
|
+
url
|
|
67
49
|
});
|
|
68
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Add a token to a project
|
|
53
|
+
* @param options - The options for adding a token to a project
|
|
54
|
+
* @returns A promise that resolves to the created robot, including its secret token
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/ export async function createToken(options) {
|
|
58
|
+
const { expiresAt, label, projectId, roleNames, sendNotification } = options;
|
|
59
|
+
return postRobot(`/access/project/${projectId}/robots`, {
|
|
60
|
+
expiresAt,
|
|
61
|
+
label,
|
|
62
|
+
memberships: [
|
|
63
|
+
{
|
|
64
|
+
resourceId: projectId,
|
|
65
|
+
resourceType: 'project',
|
|
66
|
+
roleNames
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}, sendNotification === false ? {
|
|
70
|
+
sendNotification: 'false'
|
|
71
|
+
} : {});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create an organization token, which can hold organization permissions and roles on several projects
|
|
75
|
+
* @param options - The options for the organization token
|
|
76
|
+
* @returns A promise that resolves to the created robot, including its secret token
|
|
77
|
+
*
|
|
78
|
+
* @internal
|
|
79
|
+
*/ export async function createOrganizationToken(options) {
|
|
80
|
+
const { organizationId, ...body } = options;
|
|
81
|
+
return postRobot(`/access/organization/${organizationId}/robots`, body);
|
|
82
|
+
}
|
|
69
83
|
/**
|
|
70
84
|
* Delete a token from a project
|
|
71
85
|
* @param options - The options for deleting a token from a project; `tokenId`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(client: SanityClient, url: string): Promise<T[]> {\n const items: T[] = []\n let cursor: string | null = null\n\n do {\n const response: PaginatedResponse<T> = await client.request<PaginatedResponse<T>>({\n query: cursor === null ? {} : {nextCursor: cursor},\n url,\n })\n items.push(...response.data)\n cursor = response.nextCursor\n } while (cursor !== null)\n\n return items\n}\n\n/**\n * Get the membership granting a robot access to the given project\n * @param robot - The robot to inspect\n * @param projectId - The project ID\n * @returns The project membership, if any\n *\n * @internal\n */\nexport function getProjectMembership(robot: Robot, projectId: string): Membership | undefined {\n return robot.memberships.find(\n (membership) => membership.resourceType === 'project' && membership.resourceId === projectId,\n )\n}\n\ninterface CreateTokenOptions {\n label: string\n projectId: string\n
|
|
1
|
+
{"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(client: SanityClient, url: string): Promise<T[]> {\n const items: T[] = []\n let cursor: string | null = null\n\n do {\n const response: PaginatedResponse<T> = await client.request<PaginatedResponse<T>>({\n query: cursor === null ? {} : {nextCursor: cursor},\n url,\n })\n items.push(...response.data)\n cursor = response.nextCursor\n } while (cursor !== null)\n\n return items\n}\n\n/**\n * Get the membership granting a robot access to the given project\n * @param robot - The robot to inspect\n * @param projectId - The project ID\n * @returns The project membership, if any\n *\n * @internal\n */\nexport function getProjectMembership(robot: Robot, projectId: string): Membership | undefined {\n return robot.memberships.find(\n (membership) => membership.resourceType === 'project' && membership.resourceId === projectId,\n )\n}\n\ninterface RobotBody {\n label: string\n memberships: Membership[]\n\n expiresAt?: string\n}\n\nasync function postRobot(\n url: string,\n {expiresAt, ...body}: RobotBody,\n query: Record<string, string> = {},\n): Promise<RobotWithToken> {\n const client = await getClient()\n\n return client.request<RobotWithToken>({\n body: {...body, ...(expiresAt === undefined ? {} : {expiresAt})},\n method: 'POST',\n query,\n url,\n })\n}\n\ninterface CreateTokenOptions {\n label: string\n projectId: string\n roleNames: string[]\n\n expiresAt?: string\n sendNotification?: boolean\n}\n\n/**\n * Add a token to a project\n * @param options - The options for adding a token to a project\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createToken(options: CreateTokenOptions): Promise<RobotWithToken> {\n const {expiresAt, label, projectId, roleNames, sendNotification} = options\n\n return postRobot(\n `/access/project/${projectId}/robots`,\n {\n expiresAt,\n label,\n memberships: [{resourceId: projectId, resourceType: 'project', roleNames}],\n },\n sendNotification === false ? {sendNotification: 'false'} : {},\n )\n}\n\ninterface CreateOrganizationTokenOptions extends RobotBody {\n organizationId: string\n}\n\n/**\n * Create an organization token, which can hold organization permissions and roles on several projects\n * @param options - The options for the organization token\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createOrganizationToken(\n options: CreateOrganizationTokenOptions,\n): Promise<RobotWithToken> {\n const {organizationId, ...body} = options\n\n return postRobot(`/access/organization/${organizationId}/robots`, body)\n}\n\ninterface DeleteTokenOptions {\n projectId: string\n tokenId: string\n}\n\n/**\n * Delete a token from a project\n * @param options - The options for deleting a token from a project; `tokenId`\n * is the id reported by `getTokens`/`createToken`\n * @returns A promise that resolves when the token is deleted\n *\n * @internal\n */\nexport async function deleteToken(options: DeleteTokenOptions): Promise<void> {\n const {projectId, tokenId} = options\n\n const client = await getClient()\n\n return client.request({\n method: 'DELETE',\n url: `/access/project/${projectId}/robots/${tokenId}`,\n })\n}\n\n/**\n * Rotate a robot token, replacing its secret while preserving the robot,\n * its roles, and the token's expiry. The request authenticates as the robot\n * itself; the previous secret is revoked immediately.\n * @param token - The current robot token secret\n * @returns A promise that resolves to the robot with its new secret token\n *\n * @internal\n */\nexport async function rotateToken(token: string): Promise<RobotWithToken> {\n const client = await getClient(token)\n\n return client.request<RobotWithToken>({\n method: 'POST',\n url: '/access/robots/me/rotate',\n })\n}\n\n/**\n * Get all tokens for a project. Tokens managed by an organization are\n * excluded: they cannot be managed at the project scope.\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of robots\n *\n * @internal\n */\nexport async function getTokens(projectId: string): Promise<Robot[]> {\n const client = await getClient()\n\n const robots = await fetchAllPages<Robot>(client, `/access/project/${projectId}/robots`)\n\n return robots.filter((robot) => robot.managedBy?.resourceType !== 'organization')\n}\n\n/**\n * Get all roles for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of project roles\n *\n * @internal\n */\nexport async function getTokenRoles(projectId: string): Promise<Role[]> {\n const client = await getClient()\n\n return fetchAllPages<Role>(client, `/access/project/${projectId}/roles`)\n}\n"],"names":["getGlobalCliClient","TOKENS_API_VERSION","getClient","token","apiVersion","requireUser","undefined","fetchAllPages","client","url","items","cursor","response","request","query","nextCursor","push","data","getProjectMembership","robot","projectId","memberships","find","membership","resourceType","resourceId","postRobot","expiresAt","body","method","createToken","options","label","roleNames","sendNotification","createOrganizationToken","organizationId","deleteToken","tokenId","rotateToken","getTokens","robots","filter","managedBy","getTokenRoles"],"mappings":"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"}
|