@sanity/cli 7.17.0 → 8.0.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/README.md +81 -6
- package/dist/SanityHelp.js +1 -35
- package/dist/SanityHelp.js.map +1 -1
- package/dist/actions/assets/assetFileError.js +9 -0
- package/dist/actions/assets/assetFileError.js.map +1 -0
- package/dist/actions/assets/uploadAssetFromFile.js +40 -0
- package/dist/actions/assets/uploadAssetFromFile.js.map +1 -0
- package/dist/actions/assets/uploadAssetWithProgress.js +76 -0
- package/dist/actions/assets/uploadAssetWithProgress.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +39 -44
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +21 -13
- package/dist/actions/deploy/deployChecks.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js +8 -7
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +55 -35
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deploymentPlan.js +17 -29
- package/dist/actions/deploy/deploymentPlan.js.map +1 -1
- package/dist/actions/deploy/resolveDeployTarget.js +2 -8
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js +2 -8
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +7 -1
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/init/bootstrapRemoteTemplate.js +2 -2
- package/dist/actions/init/bootstrapRemoteTemplate.js.map +1 -1
- package/dist/actions/schema/listSchemas.js +1 -1
- package/dist/actions/schema/listSchemas.js.map +1 -1
- package/dist/actions/tokens/constants.js +2 -2
- package/dist/actions/tokens/constants.js.map +1 -1
- package/dist/actions/tokens/expiry.js +35 -0
- package/dist/actions/tokens/expiry.js.map +1 -0
- package/dist/actions/tokens/types.js.map +1 -1
- package/dist/actions/tokens/validateRole.js +5 -2
- package/dist/actions/tokens/validateRole.js.map +1 -1
- package/dist/actions/typegen/count.js +6 -0
- package/dist/actions/typegen/count.js.map +1 -0
- package/dist/actions/typegen/formatPath.js +5 -0
- package/dist/actions/typegen/formatPath.js.map +1 -0
- package/dist/actions/typegen/percent.js +8 -0
- package/dist/actions/typegen/percent.js.map +1 -0
- package/dist/actions/typegen/renderTypegenProgress.js +88 -0
- package/dist/actions/typegen/renderTypegenProgress.js.map +1 -0
- package/dist/actions/undeploy/adapters.js +6 -9
- package/dist/actions/undeploy/adapters.js.map +1 -1
- package/dist/actions/undeploy/runUndeploy.js +12 -4
- package/dist/actions/undeploy/runUndeploy.js.map +1 -1
- package/dist/actions/undeploy/undeployPlan.js +6 -14
- package/dist/actions/undeploy/undeployPlan.js.map +1 -1
- package/dist/commands/assets/upload.js +126 -0
- package/dist/commands/assets/upload.js.map +1 -0
- package/dist/commands/backups/list.js +1 -1
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/datasets/copy.js +1 -1
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/login.js +2 -8
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/migrations/list.js +1 -1
- package/dist/commands/migrations/list.js.map +1 -1
- package/dist/commands/migrations/run.js +67 -3
- package/dist/commands/migrations/run.js.map +1 -1
- package/dist/commands/new.js +56 -4
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/organizations/list.js +1 -1
- package/dist/commands/organizations/list.js.map +1 -1
- package/dist/commands/projects/list.js +39 -12
- package/dist/commands/projects/list.js.map +1 -1
- package/dist/commands/projects/unclaimed.js +39 -33
- package/dist/commands/projects/unclaimed.js.map +1 -1
- package/dist/commands/tokens/create.js +92 -7
- package/dist/commands/tokens/create.js.map +1 -1
- package/dist/commands/tokens/delete.js +2 -2
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/tokens/list.js +12 -11
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/commands/tokens/rotate.js +95 -0
- package/dist/commands/tokens/rotate.js.map +1 -0
- package/dist/commands/typegen/generate.js +222 -0
- package/dist/commands/typegen/generate.js.map +1 -0
- package/dist/commands/users/list.js +8 -8
- package/dist/commands/users/list.js.map +1 -1
- package/dist/exports/_internal.d.ts +29 -0
- package/dist/exports/_internal.js +1 -0
- package/dist/exports/_internal.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +29 -14
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/exports/invokeSanityCli/help.js +4 -0
- package/dist/exports/invokeSanityCli/help.js.map +1 -1
- package/dist/exports/invokeSanityCli/index.js +2 -0
- package/dist/exports/invokeSanityCli/index.js.map +1 -1
- package/dist/hooks/commandNotFound/topicAliases.js +13 -40
- package/dist/hooks/commandNotFound/topicAliases.js.map +1 -1
- package/dist/hooks/prerun/setupTelemetry.js +3 -2
- package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
- package/dist/server/vite/plugin-typegen.js +4 -16
- package/dist/server/vite/plugin-typegen.js.map +1 -1
- package/dist/services/assets.js +46 -0
- package/dist/services/assets.js.map +1 -0
- package/dist/services/newInstructions.js +48 -0
- package/dist/services/newInstructions.js.map +1 -0
- package/dist/services/tokens.js +81 -31
- package/dist/services/tokens.js.map +1 -1
- package/dist/topicAliases.js +43 -0
- package/dist/topicAliases.js.map +1 -1
- package/dist/util/parseArguments.js +7 -2
- package/dist/util/parseArguments.js.map +1 -1
- package/dist/util/readTokenFromStdin.js +17 -0
- package/dist/util/readTokenFromStdin.js.map +1 -0
- package/dist/util/responsiveTable.js +97 -0
- package/dist/util/responsiveTable.js.map +1 -0
- package/dist/util/telemetry/commandTelemetry.js +34 -0
- package/dist/util/telemetry/commandTelemetry.js.map +1 -0
- package/dist/util/unclaimedProjects.js +10 -0
- package/dist/util/unclaimedProjects.js.map +1 -1
- package/oclif.config.js +2 -4
- package/oclif.manifest.json +224 -2
- package/package.json +12 -10
- package/templates/shopify/components/inputs/ShopifyMetafield.tsx +20 -0
- package/templates/shopify/docs/features.md +24 -0
- package/templates/shopify/schemaTypes/index.ts +2 -0
- package/templates/shopify/schemaTypes/objects/shopify/shopifyCollectionType.ts +18 -1
- package/templates/shopify/schemaTypes/objects/shopify/shopifyMetafieldType.ts +47 -0
- package/templates/shopify/schemaTypes/objects/shopify/shopifyProductType.ts +17 -0
- package/templates/shopify/utils/formatMetafieldValue.ts +30 -0
- package/templates/shopify-online-storefront/components/inputs/ShopifyMetafield.tsx +22 -0
- package/templates/shopify-online-storefront/schemaTypes/index.ts +4 -0
- package/templates/shopify-online-storefront/schemaTypes/objects/shop.ts +16 -0
- package/templates/shopify-online-storefront/schemaTypes/objects/shopifyCollection.ts +19 -0
- package/templates/shopify-online-storefront/schemaTypes/objects/shopifyMetafield.ts +53 -0
- package/templates/shopify-online-storefront/schemaTypes/objects/shopifyProduct.ts +19 -0
- package/templates/shopify-online-storefront/utils/formatMetafieldValue.ts +30 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { subdebug } from '@sanity/cli-core/debug';
|
|
2
|
+
import { SANITY_NEW_URL } from '../util/mintProjectConstants.js';
|
|
3
|
+
const debug = subdebug('new:instructions');
|
|
4
|
+
const INSTRUCTIONS_TIMEOUT = 10_000;
|
|
5
|
+
/**
|
|
6
|
+
* Prerendered markdown route on sanity.new. Preferred over content negotiation on `/`, which
|
|
7
|
+
* depends on request headers and can fall through to the HTML page.
|
|
8
|
+
*/ export const INSTRUCTIONS_URL = `${SANITY_NEW_URL}/llms.txt`;
|
|
9
|
+
const HTML_PATTERN = /^\s*(?:<!doctype html|<html[\s>])/iu;
|
|
10
|
+
export class InstructionsUnavailableError extends Error {
|
|
11
|
+
constructor(reason){
|
|
12
|
+
super(`Could not fetch the setup instructions from ${INSTRUCTIONS_URL}: ${reason}`);
|
|
13
|
+
this.name = 'InstructionsUnavailableError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fetches the agent setup instructions from sanity.new, which is the single source of truth for
|
|
18
|
+
* them. Deliberately not bundled with the CLI: a stale local copy is worse than a clear failure.
|
|
19
|
+
*/ export async function fetchNewInstructions() {
|
|
20
|
+
debug('fetching instructions from %s', INSTRUCTIONS_URL);
|
|
21
|
+
let response;
|
|
22
|
+
try {
|
|
23
|
+
response = await fetch(INSTRUCTIONS_URL, {
|
|
24
|
+
headers: {
|
|
25
|
+
Accept: 'text/markdown'
|
|
26
|
+
},
|
|
27
|
+
method: 'GET',
|
|
28
|
+
signal: AbortSignal.timeout(INSTRUCTIONS_TIMEOUT)
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const reason = err instanceof Error && err.name === 'TimeoutError' ? `the request timed out after ${INSTRUCTIONS_TIMEOUT / 1000} seconds` : `${err instanceof Error ? err.message : err}`;
|
|
32
|
+
throw new InstructionsUnavailableError(reason);
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new InstructionsUnavailableError(`the server responded with HTTP ${response.status}`);
|
|
36
|
+
}
|
|
37
|
+
const body = (await response.text()).trim();
|
|
38
|
+
if (!body) {
|
|
39
|
+
throw new InstructionsUnavailableError('the response was empty');
|
|
40
|
+
}
|
|
41
|
+
if (HTML_PATTERN.test(body)) {
|
|
42
|
+
throw new InstructionsUnavailableError('the response was HTML rather than markdown');
|
|
43
|
+
}
|
|
44
|
+
debug('fetched %d characters', body.length);
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=newInstructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/services/newInstructions.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core/debug'\n\nimport {SANITY_NEW_URL} from '../util/mintProjectConstants.js'\n\nconst debug = subdebug('new:instructions')\n\nconst INSTRUCTIONS_TIMEOUT = 10_000\n\n/**\n * Prerendered markdown route on sanity.new. Preferred over content negotiation on `/`, which\n * depends on request headers and can fall through to the HTML page.\n */\nexport const INSTRUCTIONS_URL = `${SANITY_NEW_URL}/llms.txt`\n\nconst HTML_PATTERN = /^\\s*(?:<!doctype html|<html[\\s>])/iu\n\nexport class InstructionsUnavailableError extends Error {\n constructor(reason: string) {\n super(`Could not fetch the setup instructions from ${INSTRUCTIONS_URL}: ${reason}`)\n this.name = 'InstructionsUnavailableError'\n }\n}\n\n/**\n * Fetches the agent setup instructions from sanity.new, which is the single source of truth for\n * them. Deliberately not bundled with the CLI: a stale local copy is worse than a clear failure.\n */\nexport async function fetchNewInstructions(): Promise<string> {\n debug('fetching instructions from %s', INSTRUCTIONS_URL)\n\n let response: Response\n try {\n response = await fetch(INSTRUCTIONS_URL, {\n headers: {Accept: 'text/markdown'},\n method: 'GET',\n signal: AbortSignal.timeout(INSTRUCTIONS_TIMEOUT),\n })\n } catch (err) {\n const reason =\n err instanceof Error && err.name === 'TimeoutError'\n ? `the request timed out after ${INSTRUCTIONS_TIMEOUT / 1000} seconds`\n : `${err instanceof Error ? err.message : err}`\n throw new InstructionsUnavailableError(reason)\n }\n\n if (!response.ok) {\n throw new InstructionsUnavailableError(`the server responded with HTTP ${response.status}`)\n }\n\n const body = (await response.text()).trim()\n\n if (!body) {\n throw new InstructionsUnavailableError('the response was empty')\n }\n\n if (HTML_PATTERN.test(body)) {\n throw new InstructionsUnavailableError('the response was HTML rather than markdown')\n }\n\n debug('fetched %d characters', body.length)\n return body\n}\n"],"names":["subdebug","SANITY_NEW_URL","debug","INSTRUCTIONS_TIMEOUT","INSTRUCTIONS_URL","HTML_PATTERN","InstructionsUnavailableError","Error","reason","name","fetchNewInstructions","response","fetch","headers","Accept","method","signal","AbortSignal","timeout","err","message","ok","status","body","text","trim","test","length"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,yBAAwB;AAE/C,SAAQC,cAAc,QAAO,kCAAiC;AAE9D,MAAMC,QAAQF,SAAS;AAEvB,MAAMG,uBAAuB;AAE7B;;;CAGC,GACD,OAAO,MAAMC,mBAAmB,GAAGH,eAAe,SAAS,CAAC,CAAA;AAE5D,MAAMI,eAAe;AAErB,OAAO,MAAMC,qCAAqCC;IAChD,YAAYC,MAAc,CAAE;QAC1B,KAAK,CAAC,CAAC,4CAA4C,EAAEJ,iBAAiB,EAAE,EAAEI,QAAQ;QAClF,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA;;;CAGC,GACD,OAAO,eAAeC;IACpBR,MAAM,iCAAiCE;IAEvC,IAAIO;IACJ,IAAI;QACFA,WAAW,MAAMC,MAAMR,kBAAkB;YACvCS,SAAS;gBAACC,QAAQ;YAAe;YACjCC,QAAQ;YACRC,QAAQC,YAAYC,OAAO,CAACf;QAC9B;IACF,EAAE,OAAOgB,KAAK;QACZ,MAAMX,SACJW,eAAeZ,SAASY,IAAIV,IAAI,KAAK,iBACjC,CAAC,4BAA4B,EAAEN,uBAAuB,KAAK,QAAQ,CAAC,GACpE,GAAGgB,eAAeZ,QAAQY,IAAIC,OAAO,GAAGD,KAAK;QACnD,MAAM,IAAIb,6BAA6BE;IACzC;IAEA,IAAI,CAACG,SAASU,EAAE,EAAE;QAChB,MAAM,IAAIf,6BAA6B,CAAC,+BAA+B,EAAEK,SAASW,MAAM,EAAE;IAC5F;IAEA,MAAMC,OAAO,AAAC,CAAA,MAAMZ,SAASa,IAAI,EAAC,EAAGC,IAAI;IAEzC,IAAI,CAACF,MAAM;QACT,MAAM,IAAIjB,6BAA6B;IACzC;IAEA,IAAID,aAAaqB,IAAI,CAACH,OAAO;QAC3B,MAAM,IAAIjB,6BAA6B;IACzC;IAEAJ,MAAM,yBAAyBqB,KAAKI,MAAM;IAC1C,OAAOJ;AACT"}
|
package/dist/services/tokens.js
CHANGED
|
@@ -1,58 +1,113 @@
|
|
|
1
1
|
import { getGlobalCliClient } from '@sanity/cli-core';
|
|
2
|
-
|
|
2
|
+
import { TOKENS_API_VERSION } from '../actions/tokens/constants.js';
|
|
3
|
+
function getClient(token) {
|
|
4
|
+
return getGlobalCliClient({
|
|
5
|
+
apiVersion: TOKENS_API_VERSION,
|
|
6
|
+
requireUser: true,
|
|
7
|
+
...token === undefined ? {} : {
|
|
8
|
+
token
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async function fetchAllPages(client, uri) {
|
|
13
|
+
const items = [];
|
|
14
|
+
let cursor = null;
|
|
15
|
+
do {
|
|
16
|
+
const response = await client.request({
|
|
17
|
+
query: cursor === null ? {} : {
|
|
18
|
+
nextCursor: cursor
|
|
19
|
+
},
|
|
20
|
+
uri
|
|
21
|
+
});
|
|
22
|
+
items.push(...response.data);
|
|
23
|
+
cursor = response.nextCursor;
|
|
24
|
+
}while (cursor !== null)
|
|
25
|
+
return items;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the membership granting a robot access to the given project
|
|
29
|
+
* @param robot - The robot to inspect
|
|
30
|
+
* @param projectId - The project ID
|
|
31
|
+
* @returns The project membership, if any
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/ export function getProjectMembership(robot, projectId) {
|
|
35
|
+
return robot.memberships.find((membership)=>membership.resourceType === 'project' && membership.resourceId === projectId);
|
|
36
|
+
}
|
|
3
37
|
/**
|
|
4
38
|
* Add a token to a project
|
|
5
39
|
* @param options - The options for adding a token to a project
|
|
6
|
-
* @returns A promise that resolves to the token
|
|
40
|
+
* @returns A promise that resolves to the created robot, including its secret token
|
|
7
41
|
*
|
|
8
42
|
* @internal
|
|
9
43
|
*/ export async function createToken(options) {
|
|
10
|
-
const { label, projectId, roleName } = options;
|
|
11
|
-
const client = await
|
|
12
|
-
apiVersion: TOKENS_API_VERSION,
|
|
13
|
-
requireUser: true
|
|
14
|
-
});
|
|
44
|
+
const { expiresAt, label, projectId, roleName, sendNotification } = options;
|
|
45
|
+
const client = await getClient();
|
|
15
46
|
return client.request({
|
|
16
47
|
body: {
|
|
17
48
|
label,
|
|
18
|
-
|
|
49
|
+
memberships: [
|
|
50
|
+
{
|
|
51
|
+
resourceId: projectId,
|
|
52
|
+
resourceType: 'project',
|
|
53
|
+
roleNames: [
|
|
54
|
+
roleName
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
...expiresAt === undefined ? {} : {
|
|
59
|
+
expiresAt
|
|
60
|
+
}
|
|
19
61
|
},
|
|
20
62
|
method: 'POST',
|
|
21
|
-
|
|
63
|
+
query: sendNotification === false ? {
|
|
64
|
+
sendNotification: 'false'
|
|
65
|
+
} : {},
|
|
66
|
+
uri: `/access/project/${projectId}/robots`
|
|
22
67
|
});
|
|
23
68
|
}
|
|
24
69
|
/**
|
|
25
70
|
* Delete a token from a project
|
|
26
|
-
* @param options - The options for deleting a token from a project
|
|
71
|
+
* @param options - The options for deleting a token from a project; `tokenId`
|
|
72
|
+
* is the id reported by `getTokens`/`createToken`
|
|
27
73
|
* @returns A promise that resolves when the token is deleted
|
|
28
74
|
*
|
|
29
75
|
* @internal
|
|
30
76
|
*/ export async function deleteToken(options) {
|
|
31
77
|
const { projectId, tokenId } = options;
|
|
32
|
-
const client = await
|
|
33
|
-
apiVersion: TOKENS_API_VERSION,
|
|
34
|
-
requireUser: true
|
|
35
|
-
});
|
|
78
|
+
const client = await getClient();
|
|
36
79
|
return client.request({
|
|
37
80
|
method: 'DELETE',
|
|
38
|
-
uri: `/
|
|
81
|
+
uri: `/access/project/${projectId}/robots/${tokenId}`
|
|
39
82
|
});
|
|
40
83
|
}
|
|
41
84
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
85
|
+
* Rotate a robot token, replacing its secret while preserving the robot,
|
|
86
|
+
* its roles, and the token's expiry. The request authenticates as the robot
|
|
87
|
+
* itself; the previous secret is revoked immediately.
|
|
88
|
+
* @param token - The current robot token secret
|
|
89
|
+
* @returns A promise that resolves to the robot with its new secret token
|
|
45
90
|
*
|
|
46
91
|
* @internal
|
|
47
|
-
*/ export async function
|
|
48
|
-
const client = await
|
|
49
|
-
apiVersion: TOKENS_API_VERSION,
|
|
50
|
-
requireUser: true
|
|
51
|
-
});
|
|
92
|
+
*/ export async function rotateToken(token) {
|
|
93
|
+
const client = await getClient(token);
|
|
52
94
|
return client.request({
|
|
53
|
-
|
|
95
|
+
method: 'POST',
|
|
96
|
+
uri: '/access/robots/me/rotate'
|
|
54
97
|
});
|
|
55
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Get all tokens for a project. Tokens managed by an organization are
|
|
101
|
+
* excluded: they cannot be managed at the project scope.
|
|
102
|
+
* @param projectId - The project ID
|
|
103
|
+
* @returns A promise that resolves to an array of robots
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/ export async function getTokens(projectId) {
|
|
107
|
+
const client = await getClient();
|
|
108
|
+
const robots = await fetchAllPages(client, `/access/project/${projectId}/robots`);
|
|
109
|
+
return robots.filter((robot)=>robot.managedBy?.resourceType !== 'organization');
|
|
110
|
+
}
|
|
56
111
|
/**
|
|
57
112
|
* Get all roles for a project
|
|
58
113
|
* @param projectId - The project ID
|
|
@@ -60,13 +115,8 @@ const TOKENS_API_VERSION = 'v2025-08-18';
|
|
|
60
115
|
*
|
|
61
116
|
* @internal
|
|
62
117
|
*/ export async function getTokenRoles(projectId) {
|
|
63
|
-
const client = await
|
|
64
|
-
|
|
65
|
-
requireUser: true
|
|
66
|
-
});
|
|
67
|
-
return client.request({
|
|
68
|
-
uri: `/projects/${projectId}/roles`
|
|
69
|
-
});
|
|
118
|
+
const client = await getClient();
|
|
119
|
+
return fetchAllPages(client, `/access/project/${projectId}/roles`);
|
|
70
120
|
}
|
|
71
121
|
|
|
72
122
|
//# sourceMappingURL=tokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\n\nimport {
|
|
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, uri: 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 uri,\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 roleName: 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, roleName, sendNotification} = options\n\n const client = await getClient()\n\n return client.request<RobotWithToken>({\n body: {\n label,\n memberships: [{resourceId: projectId, resourceType: 'project', roleNames: [roleName]}],\n ...(expiresAt === undefined ? {} : {expiresAt}),\n },\n method: 'POST',\n query: sendNotification === false ? {sendNotification: 'false'} : {},\n uri: `/access/project/${projectId}/robots`,\n })\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 uri: `/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 uri: '/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","uri","items","cursor","response","request","query","nextCursor","push","data","getProjectMembership","robot","projectId","memberships","find","membership","resourceType","resourceId","createToken","options","expiresAt","label","roleName","sendNotification","body","roleNames","method","deleteToken","tokenId","rotateToken","getTokens","robots","filter","managedBy","getTokenRoles"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AAGnD,SAAQC,kBAAkB,QAAO,iCAAgC;AAajE,SAASC,UAAUC,KAAc;IAC/B,OAAOH,mBAAmB;QACxBI,YAAYH;QACZI,aAAa;QACb,GAAIF,UAAUG,YAAY,CAAC,IAAI;YAACH;QAAK,CAAC;IACxC;AACF;AAEA,eAAeI,cAAiBC,MAAoB,EAAEC,GAAW;IAC/D,MAAMC,QAAa,EAAE;IACrB,IAAIC,SAAwB;IAE5B,GAAG;QACD,MAAMC,WAAiC,MAAMJ,OAAOK,OAAO,CAAuB;YAChFC,OAAOH,WAAW,OAAO,CAAC,IAAI;gBAACI,YAAYJ;YAAM;YACjDF;QACF;QACAC,MAAMM,IAAI,IAAIJ,SAASK,IAAI;QAC3BN,SAASC,SAASG,UAAU;IAC9B,QAASJ,WAAW,KAAK;IAEzB,OAAOD;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASQ,qBAAqBC,KAAY,EAAEC,SAAiB;IAClE,OAAOD,MAAME,WAAW,CAACC,IAAI,CAC3B,CAACC,aAAeA,WAAWC,YAAY,KAAK,aAAaD,WAAWE,UAAU,KAAKL;AAEvF;AAWA;;;;;;CAMC,GACD,OAAO,eAAeM,YAAYC,OAA2B;IAC3D,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAET,SAAS,EAAEU,QAAQ,EAAEC,gBAAgB,EAAC,GAAGJ;IAElE,MAAMnB,SAAS,MAAMN;IAErB,OAAOM,OAAOK,OAAO,CAAiB;QACpCmB,MAAM;YACJH;YACAR,aAAa;gBAAC;oBAACI,YAAYL;oBAAWI,cAAc;oBAAWS,WAAW;wBAACH;qBAAS;gBAAA;aAAE;YACtF,GAAIF,cAActB,YAAY,CAAC,IAAI;gBAACsB;YAAS,CAAC;QAChD;QACAM,QAAQ;QACRpB,OAAOiB,qBAAqB,QAAQ;YAACA,kBAAkB;QAAO,IAAI,CAAC;QACnEtB,KAAK,CAAC,gBAAgB,EAAEW,UAAU,OAAO,CAAC;IAC5C;AACF;AAOA;;;;;;;CAOC,GACD,OAAO,eAAee,YAAYR,OAA2B;IAC3D,MAAM,EAACP,SAAS,EAAEgB,OAAO,EAAC,GAAGT;IAE7B,MAAMnB,SAAS,MAAMN;IAErB,OAAOM,OAAOK,OAAO,CAAC;QACpBqB,QAAQ;QACRzB,KAAK,CAAC,gBAAgB,EAAEW,UAAU,QAAQ,EAAEgB,SAAS;IACvD;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,YAAYlC,KAAa;IAC7C,MAAMK,SAAS,MAAMN,UAAUC;IAE/B,OAAOK,OAAOK,OAAO,CAAiB;QACpCqB,QAAQ;QACRzB,KAAK;IACP;AACF;AAEA;;;;;;;CAOC,GACD,OAAO,eAAe6B,UAAUlB,SAAiB;IAC/C,MAAMZ,SAAS,MAAMN;IAErB,MAAMqC,SAAS,MAAMhC,cAAqBC,QAAQ,CAAC,gBAAgB,EAAEY,UAAU,OAAO,CAAC;IAEvF,OAAOmB,OAAOC,MAAM,CAAC,CAACrB,QAAUA,MAAMsB,SAAS,EAAEjB,iBAAiB;AACpE;AAEA;;;;;;CAMC,GACD,OAAO,eAAekB,cAActB,SAAiB;IACnD,MAAMZ,SAAS,MAAMN;IAErB,OAAOK,cAAoBC,QAAQ,CAAC,gBAAgB,EAAEY,UAAU,MAAM,CAAC;AACzE"}
|
package/dist/topicAliases.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Maps canonical topic names to their aliases. Used by:
|
|
5
5
|
* - The command_not_found hook (runtime alias resolution)
|
|
6
|
+
* - Programmatic CLI invocation
|
|
7
|
+
* - Help rendering
|
|
6
8
|
* - The check-topic-aliases script (build-time validation)
|
|
7
9
|
*
|
|
8
10
|
* Every topic with aliases must be listed here. The key is the canonical
|
|
@@ -16,6 +18,9 @@
|
|
|
16
18
|
* While the config technically supports any direction, keeping it consistent
|
|
17
19
|
* (plural to singular) makes the CLI predictable for users.
|
|
18
20
|
*/ export const topicAliases = {
|
|
21
|
+
assets: [
|
|
22
|
+
'asset'
|
|
23
|
+
],
|
|
19
24
|
backups: [
|
|
20
25
|
'backup'
|
|
21
26
|
],
|
|
@@ -57,5 +62,43 @@
|
|
|
57
62
|
'user'
|
|
58
63
|
]
|
|
59
64
|
};
|
|
65
|
+
const canonicalTopicByAlias = new Map(Object.entries(topicAliases).flatMap(([canonical, aliases])=>aliases.map((alias)=>[
|
|
66
|
+
alias,
|
|
67
|
+
canonical
|
|
68
|
+
])));
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the topic portion of a command ID from an alias to its canonical name.
|
|
71
|
+
* Handles both bare topics (`hook`) and colon-separated command IDs (`hook:list`).
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/ export function resolveTopicAlias(id) {
|
|
75
|
+
const separatorIndex = id.indexOf(':');
|
|
76
|
+
const topic = separatorIndex === -1 ? id : id.slice(0, separatorIndex);
|
|
77
|
+
const canonical = canonicalTopicByAlias.get(topic);
|
|
78
|
+
if (!canonical) return undefined;
|
|
79
|
+
const suffix = separatorIndex === -1 ? '' : id.slice(separatorIndex);
|
|
80
|
+
return `${canonical}${suffix}`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the first positional topic in argv, preserving flags and arguments.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/ export function resolveTopicAliasInArgv(argv) {
|
|
87
|
+
for(let i = 0; i < argv.length; i++){
|
|
88
|
+
const arg = argv[i];
|
|
89
|
+
if (arg === '--') break;
|
|
90
|
+
if (arg.startsWith('-')) continue;
|
|
91
|
+
const resolvedTopic = resolveTopicAlias(arg);
|
|
92
|
+
if (resolvedTopic) {
|
|
93
|
+
const resolved = [
|
|
94
|
+
...argv
|
|
95
|
+
];
|
|
96
|
+
resolved[i] = resolvedTopic;
|
|
97
|
+
return resolved;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return argv;
|
|
102
|
+
}
|
|
60
103
|
|
|
61
104
|
//# sourceMappingURL=topicAliases.js.map
|
package/dist/topicAliases.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/topicAliases.ts"],"sourcesContent":["/**\n * Topic Alias Configuration\n *\n * Maps canonical topic names to their aliases. Used by:\n * - The command_not_found hook (runtime alias resolution)\n * - The check-topic-aliases script (build-time validation)\n *\n * Every topic with aliases must be listed here. The key is the canonical\n * topic name (the actual directory under commands/), and values are\n * alternative names that should resolve to it.\n *\n * Convention: prefer plural canonical names with singular aliases.\n * datasets: ['dataset'] - \"sanity dataset create\" works\n * cors: [] - no alias needed (no natural singular/plural pair)\n *\n * While the config technically supports any direction, keeping it consistent\n * (plural to singular) makes the CLI predictable for users.\n */\nexport const topicAliases: Record<string, string[]> = {\n backups: ['backup'],\n blueprints: ['blueprint'],\n datasets: ['dataset'],\n documents: ['document'],\n functions: ['function'],\n hooks: ['hook'],\n migrations: ['migration'],\n organizations: ['organization', 'organisations', 'organisation', 'org', 'orgs'],\n projects: ['project'],\n schemas: ['schema'],\n tokens: ['token'],\n users: ['user'],\n}\n"],"names":["topicAliases","backups","blueprints","datasets","documents","functions","hooks","migrations","organizations","projects","schemas","tokens","users"],"mappings":"AAAA
|
|
1
|
+
{"version":3,"sources":["../src/topicAliases.ts"],"sourcesContent":["/**\n * Topic Alias Configuration\n *\n * Maps canonical topic names to their aliases. Used by:\n * - The command_not_found hook (runtime alias resolution)\n * - Programmatic CLI invocation\n * - Help rendering\n * - The check-topic-aliases script (build-time validation)\n *\n * Every topic with aliases must be listed here. The key is the canonical\n * topic name (the actual directory under commands/), and values are\n * alternative names that should resolve to it.\n *\n * Convention: prefer plural canonical names with singular aliases.\n * datasets: ['dataset'] - \"sanity dataset create\" works\n * cors: [] - no alias needed (no natural singular/plural pair)\n *\n * While the config technically supports any direction, keeping it consistent\n * (plural to singular) makes the CLI predictable for users.\n */\nexport const topicAliases: Record<string, string[]> = {\n assets: ['asset'],\n backups: ['backup'],\n blueprints: ['blueprint'],\n datasets: ['dataset'],\n documents: ['document'],\n functions: ['function'],\n hooks: ['hook'],\n migrations: ['migration'],\n organizations: ['organization', 'organisations', 'organisation', 'org', 'orgs'],\n projects: ['project'],\n schemas: ['schema'],\n tokens: ['token'],\n users: ['user'],\n}\n\nconst canonicalTopicByAlias = new Map(\n Object.entries(topicAliases).flatMap(([canonical, aliases]) =>\n aliases.map((alias) => [alias, canonical]),\n ),\n)\n\n/**\n * Resolve the topic portion of a command ID from an alias to its canonical name.\n * Handles both bare topics (`hook`) and colon-separated command IDs (`hook:list`).\n *\n * @internal\n */\nexport function resolveTopicAlias(id: string): string | undefined {\n const separatorIndex = id.indexOf(':')\n const topic = separatorIndex === -1 ? id : id.slice(0, separatorIndex)\n const canonical = canonicalTopicByAlias.get(topic)\n if (!canonical) return undefined\n\n const suffix = separatorIndex === -1 ? '' : id.slice(separatorIndex)\n return `${canonical}${suffix}`\n}\n\n/**\n * Resolve the first positional topic in argv, preserving flags and arguments.\n *\n * @internal\n */\nexport function resolveTopicAliasInArgv(argv: string[]): string[] {\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') break\n if (arg.startsWith('-')) continue\n\n const resolvedTopic = resolveTopicAlias(arg)\n if (resolvedTopic) {\n const resolved = [...argv]\n resolved[i] = resolvedTopic\n return resolved\n }\n break\n }\n return argv\n}\n"],"names":["topicAliases","assets","backups","blueprints","datasets","documents","functions","hooks","migrations","organizations","projects","schemas","tokens","users","canonicalTopicByAlias","Map","Object","entries","flatMap","canonical","aliases","map","alias","resolveTopicAlias","id","separatorIndex","indexOf","topic","slice","get","undefined","suffix","resolveTopicAliasInArgv","argv","i","length","arg","startsWith","resolvedTopic","resolved"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMA,eAAyC;IACpDC,QAAQ;QAAC;KAAQ;IACjBC,SAAS;QAAC;KAAS;IACnBC,YAAY;QAAC;KAAY;IACzBC,UAAU;QAAC;KAAU;IACrBC,WAAW;QAAC;KAAW;IACvBC,WAAW;QAAC;KAAW;IACvBC,OAAO;QAAC;KAAO;IACfC,YAAY;QAAC;KAAY;IACzBC,eAAe;QAAC;QAAgB;QAAiB;QAAgB;QAAO;KAAO;IAC/EC,UAAU;QAAC;KAAU;IACrBC,SAAS;QAAC;KAAS;IACnBC,QAAQ;QAAC;KAAQ;IACjBC,OAAO;QAAC;KAAO;AACjB,EAAC;AAED,MAAMC,wBAAwB,IAAIC,IAChCC,OAAOC,OAAO,CAACjB,cAAckB,OAAO,CAAC,CAAC,CAACC,WAAWC,QAAQ,GACxDA,QAAQC,GAAG,CAAC,CAACC,QAAU;YAACA;YAAOH;SAAU;AAI7C;;;;;CAKC,GACD,OAAO,SAASI,kBAAkBC,EAAU;IAC1C,MAAMC,iBAAiBD,GAAGE,OAAO,CAAC;IAClC,MAAMC,QAAQF,mBAAmB,CAAC,IAAID,KAAKA,GAAGI,KAAK,CAAC,GAAGH;IACvD,MAAMN,YAAYL,sBAAsBe,GAAG,CAACF;IAC5C,IAAI,CAACR,WAAW,OAAOW;IAEvB,MAAMC,SAASN,mBAAmB,CAAC,IAAI,KAAKD,GAAGI,KAAK,CAACH;IACrD,OAAO,GAAGN,YAAYY,QAAQ;AAChC;AAEA;;;;CAIC,GACD,OAAO,SAASC,wBAAwBC,IAAc;IACpD,IAAK,IAAIC,IAAI,GAAGA,IAAID,KAAKE,MAAM,EAAED,IAAK;QACpC,MAAME,MAAMH,IAAI,CAACC,EAAE;QACnB,IAAIE,QAAQ,MAAM;QAClB,IAAIA,IAAIC,UAAU,CAAC,MAAM;QAEzB,MAAMC,gBAAgBf,kBAAkBa;QACxC,IAAIE,eAAe;YACjB,MAAMC,WAAW;mBAAIN;aAAK;YAC1BM,QAAQ,CAACL,EAAE,GAAGI;YACd,OAAOC;QACT;QACA;IACF;IACA,OAAON;AACT"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import minimist from 'minimist';
|
|
2
|
+
import { redactTelemetryArguments } from './telemetry/commandTelemetry.js';
|
|
3
|
+
function collectOptionArguments(argv, commandTelemetry) {
|
|
4
|
+
return redactTelemetryArguments(argv, commandTelemetry).filter((argument)=>argument.startsWith('-'));
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* Parse the arguments from the command line
|
|
4
8
|
*
|
|
5
9
|
* @param argv - The arguments from the command line
|
|
10
|
+
* @param commandTelemetry - Resolved telemetry behavior for the command
|
|
6
11
|
* @returns The parsed arguments
|
|
7
|
-
*/ export function parseArguments(argv = process.argv) {
|
|
12
|
+
*/ export function parseArguments(argv = process.argv, commandTelemetry) {
|
|
8
13
|
const args = argv.slice(2);
|
|
9
14
|
const { '--': extraArguments, _, version, ...extOptions } = minimist(args, {
|
|
10
15
|
'--': true,
|
|
@@ -18,7 +23,7 @@ import minimist from 'minimist';
|
|
|
18
23
|
const [groupOrCommand, ...argsWithoutOptions] = _;
|
|
19
24
|
const finalExtraArguments = [
|
|
20
25
|
...extraArguments || [],
|
|
21
|
-
...argv
|
|
26
|
+
...collectOptionArguments(argv, commandTelemetry)
|
|
22
27
|
];
|
|
23
28
|
// oclif allows to run `sanity help` or `sanity help <command>`
|
|
24
29
|
// It does not fire the hooks on `--help` so this is okay to track
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/parseArguments.ts"],"sourcesContent":["import minimist from 'minimist'\n\ninterface ParsedArguments<F = Record<string, string>> {\n /**\n * Command arguments, eg any arguments after `sanity <command>` (no flags)\n */\n argsWithoutOptions: string[]\n\n // Raw, forwarded arguments, for commands that want to be more explicit about parsing\n argv: string[]\n\n /**\n * Options mostly relevant for the core CLI runner\n */\n coreOptions: {\n debug: boolean\n\n help: boolean\n\n version: boolean\n }\n\n /**\n * Command flags, without the core options (help, debug, version etc)\n */\n extOptions: F\n\n /**\n * Arguments after the ended argument list (--)\n */\n extraArguments: string[]\n\n /**\n * Group or command name, eg `dataset` (`sanity dataset`) or `import` (`sanity dataset import`)\n */\n groupOrCommand: string\n}\n\n/**\n * Parse the arguments from the command line\n *\n * @param argv - The arguments from the command line\n * @returns The parsed arguments\n */\nexport function parseArguments(argv = process.argv): ParsedArguments {\n const args = argv.slice(2)\n
|
|
1
|
+
{"version":3,"sources":["../../src/util/parseArguments.ts"],"sourcesContent":["import minimist from 'minimist'\n\nimport {type CommandTelemetry, redactTelemetryArguments} from './telemetry/commandTelemetry.js'\n\ninterface ParsedArguments<F = Record<string, string>> {\n /**\n * Command arguments, eg any arguments after `sanity <command>` (no flags)\n */\n argsWithoutOptions: string[]\n\n // Raw, forwarded arguments, for commands that want to be more explicit about parsing\n argv: string[]\n\n /**\n * Options mostly relevant for the core CLI runner\n */\n coreOptions: {\n debug: boolean\n\n help: boolean\n\n version: boolean\n }\n\n /**\n * Command flags, without the core options (help, debug, version etc)\n */\n extOptions: F\n\n /**\n * Arguments after the ended argument list (--)\n */\n extraArguments: string[]\n\n /**\n * Group or command name, eg `dataset` (`sanity dataset`) or `import` (`sanity dataset import`)\n */\n groupOrCommand: string\n}\n\nfunction collectOptionArguments(argv: string[], commandTelemetry?: CommandTelemetry): string[] {\n return redactTelemetryArguments(argv, commandTelemetry).filter((argument) =>\n argument.startsWith('-'),\n )\n}\n\n/**\n * Parse the arguments from the command line\n *\n * @param argv - The arguments from the command line\n * @param commandTelemetry - Resolved telemetry behavior for the command\n * @returns The parsed arguments\n */\nexport function parseArguments(\n argv = process.argv,\n commandTelemetry?: CommandTelemetry,\n): ParsedArguments {\n const args = argv.slice(2)\n const {\n '--': extraArguments,\n _,\n version,\n ...extOptions\n } = minimist(args, {\n '--': true,\n boolean: ['version'],\n string: ['_'],\n })\n\n const [groupOrCommand, ...argsWithoutOptions] = _\n\n const finalExtraArguments = [\n ...(extraArguments || []),\n ...collectOptionArguments(argv, commandTelemetry),\n ]\n\n // oclif allows to run `sanity help` or `sanity help <command>`\n // It does not fire the hooks on `--help` so this is okay to track\n const hasHelp = args.includes('help')\n\n // We only have global debug via env var\n const hasDebug = !!process.env.DEBUG\n\n return {\n groupOrCommand,\n\n argsWithoutOptions,\n argv,\n extOptions,\n extraArguments: finalExtraArguments,\n\n coreOptions: {\n debug: hasDebug,\n help: hasHelp,\n version,\n },\n }\n}\n"],"names":["minimist","redactTelemetryArguments","collectOptionArguments","argv","commandTelemetry","filter","argument","startsWith","parseArguments","process","args","slice","extraArguments","_","version","extOptions","boolean","string","groupOrCommand","argsWithoutOptions","finalExtraArguments","hasHelp","includes","hasDebug","env","DEBUG","coreOptions","debug","help"],"mappings":"AAAA,OAAOA,cAAc,WAAU;AAE/B,SAA+BC,wBAAwB,QAAO,kCAAiC;AAsC/F,SAASC,uBAAuBC,IAAc,EAAEC,gBAAmC;IACjF,OAAOH,yBAAyBE,MAAMC,kBAAkBC,MAAM,CAAC,CAACC,WAC9DA,SAASC,UAAU,CAAC;AAExB;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,eACdL,OAAOM,QAAQN,IAAI,EACnBC,gBAAmC;IAEnC,MAAMM,OAAOP,KAAKQ,KAAK,CAAC;IACxB,MAAM,EACJ,MAAMC,cAAc,EACpBC,CAAC,EACDC,OAAO,EACP,GAAGC,YACJ,GAAGf,SAASU,MAAM;QACjB,MAAM;QACNM,SAAS;YAAC;SAAU;QACpBC,QAAQ;YAAC;SAAI;IACf;IAEA,MAAM,CAACC,gBAAgB,GAAGC,mBAAmB,GAAGN;IAEhD,MAAMO,sBAAsB;WACtBR,kBAAkB,EAAE;WACrBV,uBAAuBC,MAAMC;KACjC;IAED,+DAA+D;IAC/D,kEAAkE;IAClE,MAAMiB,UAAUX,KAAKY,QAAQ,CAAC;IAE9B,wCAAwC;IACxC,MAAMC,WAAW,CAAC,CAACd,QAAQe,GAAG,CAACC,KAAK;IAEpC,OAAO;QACLP;QAEAC;QACAhB;QACAY;QACAH,gBAAgBQ;QAEhBM,aAAa;YACXC,OAAOJ;YACPK,MAAMP;YACNP;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { text } from 'node:stream/consumers';
|
|
2
|
+
/**
|
|
3
|
+
* Read a secret token from standard input. Reading from stdin keeps the
|
|
4
|
+
* secret out of shell history, process lists, and terminal logs.
|
|
5
|
+
* @param usageHint - Error message shown when stdin is a TTY (i.e. nothing
|
|
6
|
+
* was piped in)
|
|
7
|
+
* @returns The piped input, trimmed
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/ export async function readTokenFromStdin(usageHint) {
|
|
11
|
+
if (process.stdin.isTTY) {
|
|
12
|
+
throw new Error(usageHint);
|
|
13
|
+
}
|
|
14
|
+
return (await text(process.stdin)).trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=readTokenFromStdin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/readTokenFromStdin.ts"],"sourcesContent":["import {text} from 'node:stream/consumers'\n\n/**\n * Read a secret token from standard input. Reading from stdin keeps the\n * secret out of shell history, process lists, and terminal logs.\n * @param usageHint - Error message shown when stdin is a TTY (i.e. nothing\n * was piped in)\n * @returns The piped input, trimmed\n *\n * @internal\n */\nexport async function readTokenFromStdin(usageHint: string): Promise<string> {\n if (process.stdin.isTTY) {\n throw new Error(usageHint)\n }\n\n return (await text(process.stdin)).trim()\n}\n"],"names":["text","readTokenFromStdin","usageHint","process","stdin","isTTY","Error","trim"],"mappings":"AAAA,SAAQA,IAAI,QAAO,wBAAuB;AAE1C;;;;;;;;CAQC,GACD,OAAO,eAAeC,mBAAmBC,SAAiB;IACxD,IAAIC,QAAQC,KAAK,CAACC,KAAK,EAAE;QACvB,MAAM,IAAIC,MAAMJ;IAClB;IAEA,OAAO,AAAC,CAAA,MAAMF,KAAKG,QAAQC,KAAK,CAAA,EAAGG,IAAI;AACzC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Table as ConsoleTable } from 'console-table-printer';
|
|
2
|
+
import stringWidth from 'string-width';
|
|
3
|
+
import wrapAnsi from 'wrap-ansi';
|
|
4
|
+
const cellPaddingWidth = 2;
|
|
5
|
+
const borderCharacterWidth = 1;
|
|
6
|
+
function cellText(value) {
|
|
7
|
+
return value === undefined || value === null ? '' : String(value);
|
|
8
|
+
}
|
|
9
|
+
function maxLineWidth(value) {
|
|
10
|
+
return Math.max(...value.split('\n').map((line)=>stringWidth(line)));
|
|
11
|
+
}
|
|
12
|
+
export class Table {
|
|
13
|
+
#options;
|
|
14
|
+
#rows = [];
|
|
15
|
+
constructor(options){
|
|
16
|
+
this.#options = options;
|
|
17
|
+
}
|
|
18
|
+
addRow(cells, options) {
|
|
19
|
+
this.#rows.push({
|
|
20
|
+
cells,
|
|
21
|
+
options
|
|
22
|
+
});
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
addRows(rows, options) {
|
|
26
|
+
for (const row of rows)this.addRow(row, options);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
render() {
|
|
30
|
+
const columns = this.#options.columns ?? [];
|
|
31
|
+
const columnTitles = columns.map(({ name, title = name })=>title);
|
|
32
|
+
const minimumColumnWidths = columns.map(({ minLen = 1 })=>Math.max(minLen, 1));
|
|
33
|
+
const columnWidths = columns.map(({ name }, index)=>Math.max(stringWidth(columnTitles[index]), ...this.#rows.map(({ cells })=>maxLineWidth(cellText(cells[name])))));
|
|
34
|
+
const borderWidth = borderCharacterWidth + columns.length * (cellPaddingWidth + borderCharacterWidth);
|
|
35
|
+
const minimumContentWidth = minimumColumnWidths.reduce((total, width)=>total + width, 0);
|
|
36
|
+
const availableContentWidth = Math.max((process.stdout.columns || Infinity) - borderWidth, minimumContentWidth);
|
|
37
|
+
let excessWidth = columnWidths.reduce((total, width)=>total + width, 0) - availableContentWidth;
|
|
38
|
+
while(excessWidth > 0){
|
|
39
|
+
const widestColumnIndex = columnWidths.map((width, index)=>({
|
|
40
|
+
index,
|
|
41
|
+
width: width - minimumColumnWidths[index]
|
|
42
|
+
})).toSorted((left, right)=>right.width - left.width)[0].index;
|
|
43
|
+
columnWidths[widestColumnIndex] -= 1;
|
|
44
|
+
excessWidth -= 1;
|
|
45
|
+
}
|
|
46
|
+
const tableWidth = borderWidth + columnWidths.reduce((total, width)=>total + width, 0);
|
|
47
|
+
const renderedColumns = columns.map((column, index)=>({
|
|
48
|
+
...column,
|
|
49
|
+
maxLen: columnWidths[index],
|
|
50
|
+
title: wrapAnsi(columnTitles[index], columnWidths[index], {
|
|
51
|
+
hard: true
|
|
52
|
+
})
|
|
53
|
+
}));
|
|
54
|
+
const table = new ConsoleTable({
|
|
55
|
+
rowSeparator: true,
|
|
56
|
+
...this.#options,
|
|
57
|
+
columns: renderedColumns,
|
|
58
|
+
title: undefined
|
|
59
|
+
});
|
|
60
|
+
for (const { cells, options } of this.#rows){
|
|
61
|
+
const wrappedCells = {
|
|
62
|
+
...cells
|
|
63
|
+
};
|
|
64
|
+
for (const [index, { name }] of columns.entries()){
|
|
65
|
+
wrappedCells[name] = wrapAnsi(cellText(cells[name]), columnWidths[index], {
|
|
66
|
+
hard: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
table.addRow(wrappedCells, options);
|
|
70
|
+
}
|
|
71
|
+
const renderedTable = table.render();
|
|
72
|
+
if (!this.#options.title) return renderedTable;
|
|
73
|
+
// Use empty tables to preserve the printer's title styling without invoking row callbacks again.
|
|
74
|
+
const titleTableOptions = {
|
|
75
|
+
...this.#options,
|
|
76
|
+
columns: renderedColumns,
|
|
77
|
+
rows: undefined
|
|
78
|
+
};
|
|
79
|
+
const renderedTitleTable = new ConsoleTable({
|
|
80
|
+
...titleTableOptions,
|
|
81
|
+
title: this.#options.title
|
|
82
|
+
}).render();
|
|
83
|
+
const renderedEmptyTable = new ConsoleTable({
|
|
84
|
+
...titleTableOptions,
|
|
85
|
+
title: undefined
|
|
86
|
+
}).render();
|
|
87
|
+
const emptyTableSuffix = `\n${renderedEmptyTable}`;
|
|
88
|
+
const renderedTitle = renderedTitleTable.slice(0, -emptyTableSuffix.length);
|
|
89
|
+
const wrappedTitle = wrapAnsi(renderedTitle, tableWidth, {
|
|
90
|
+
hard: true,
|
|
91
|
+
trim: false
|
|
92
|
+
});
|
|
93
|
+
return `${wrappedTitle}\n${renderedTable}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=responsiveTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/responsiveTable.ts"],"sourcesContent":["import {Table as ConsoleTable} from 'console-table-printer'\nimport stringWidth from 'string-width'\nimport wrapAnsi from 'wrap-ansi'\n\ntype ConsoleTableOptions = Exclude<\n ConstructorParameters<typeof ConsoleTable>[0],\n string[] | undefined\n>\ntype ConsoleTableRow = Parameters<ConsoleTable['addRow']>[0]\ntype ConsoleTableRowOptions = Parameters<ConsoleTable['addRow']>[1]\n\ntype StoredRow = {\n cells: ConsoleTableRow\n options?: ConsoleTableRowOptions\n}\n\nconst cellPaddingWidth = 2\nconst borderCharacterWidth = 1\n\nfunction cellText(value: ConsoleTableRow[string]): string {\n return value === undefined || value === null ? '' : String(value)\n}\n\nfunction maxLineWidth(value: string): number {\n return Math.max(...value.split('\\n').map((line) => stringWidth(line)))\n}\n\nexport class Table {\n readonly #options: ConsoleTableOptions\n readonly #rows: StoredRow[] = []\n\n constructor(options: ConsoleTableOptions) {\n this.#options = options\n }\n\n addRow(cells: ConsoleTableRow, options?: ConsoleTableRowOptions): this {\n this.#rows.push({cells, options})\n return this\n }\n\n addRows(rows: ConsoleTableRow[], options?: ConsoleTableRowOptions): this {\n for (const row of rows) this.addRow(row, options)\n return this\n }\n\n render(): string {\n const columns = this.#options.columns ?? []\n const columnTitles = columns.map(({name, title = name}) => title)\n const minimumColumnWidths = columns.map(({minLen = 1}) => Math.max(minLen, 1))\n const columnWidths = columns.map(({name}, index) =>\n Math.max(\n stringWidth(columnTitles[index]),\n ...this.#rows.map(({cells}) => maxLineWidth(cellText(cells[name]))),\n ),\n )\n const borderWidth =\n borderCharacterWidth + columns.length * (cellPaddingWidth + borderCharacterWidth)\n const minimumContentWidth = minimumColumnWidths.reduce((total, width) => total + width, 0)\n const availableContentWidth = Math.max(\n (process.stdout.columns || Infinity) - borderWidth,\n minimumContentWidth,\n )\n\n let excessWidth =\n columnWidths.reduce((total, width) => total + width, 0) - availableContentWidth\n while (excessWidth > 0) {\n const widestColumnIndex = columnWidths\n .map((width, index) => ({index, width: width - minimumColumnWidths[index]}))\n .toSorted((left, right) => right.width - left.width)[0].index\n columnWidths[widestColumnIndex] -= 1\n excessWidth -= 1\n }\n\n const tableWidth = borderWidth + columnWidths.reduce((total, width) => total + width, 0)\n const renderedColumns = columns.map((column, index) => ({\n ...column,\n maxLen: columnWidths[index],\n title: wrapAnsi(columnTitles[index], columnWidths[index], {hard: true}),\n }))\n const table = new ConsoleTable({\n rowSeparator: true,\n ...this.#options,\n columns: renderedColumns,\n title: undefined,\n })\n\n for (const {cells, options} of this.#rows) {\n const wrappedCells = {...cells}\n for (const [index, {name}] of columns.entries()) {\n wrappedCells[name] = wrapAnsi(cellText(cells[name]), columnWidths[index], {hard: true})\n }\n table.addRow(wrappedCells, options)\n }\n\n const renderedTable = table.render()\n if (!this.#options.title) return renderedTable\n\n // Use empty tables to preserve the printer's title styling without invoking row callbacks again.\n const titleTableOptions = {\n ...this.#options,\n columns: renderedColumns,\n rows: undefined,\n }\n const renderedTitleTable = new ConsoleTable({\n ...titleTableOptions,\n title: this.#options.title,\n }).render()\n const renderedEmptyTable = new ConsoleTable({...titleTableOptions, title: undefined}).render()\n const emptyTableSuffix = `\\n${renderedEmptyTable}`\n const renderedTitle = renderedTitleTable.slice(0, -emptyTableSuffix.length)\n const wrappedTitle = wrapAnsi(renderedTitle, tableWidth, {hard: true, trim: false})\n return `${wrappedTitle}\\n${renderedTable}`\n }\n}\n"],"names":["Table","ConsoleTable","stringWidth","wrapAnsi","cellPaddingWidth","borderCharacterWidth","cellText","value","undefined","String","maxLineWidth","Math","max","split","map","line","options","addRow","cells","push","addRows","rows","row","render","columns","columnTitles","name","title","minimumColumnWidths","minLen","columnWidths","index","borderWidth","length","minimumContentWidth","reduce","total","width","availableContentWidth","process","stdout","Infinity","excessWidth","widestColumnIndex","toSorted","left","right","tableWidth","renderedColumns","column","maxLen","hard","table","rowSeparator","wrappedCells","entries","renderedTable","titleTableOptions","renderedTitleTable","renderedEmptyTable","emptyTableSuffix","renderedTitle","slice","wrappedTitle","trim"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** @internal */ export function defineCommandTelemetry(flags, config) {
|
|
2
|
+
return {
|
|
3
|
+
redactedFlags: (config.redact ?? []).map((name)=>{
|
|
4
|
+
const flag = flags[name];
|
|
5
|
+
const aliases = flag?.aliases ?? [];
|
|
6
|
+
return {
|
|
7
|
+
canonical: `--${name}`,
|
|
8
|
+
flags: [
|
|
9
|
+
`--${name}`,
|
|
10
|
+
...aliases.map((alias)=>`${alias.length === 1 ? '-' : '--'}${alias}`),
|
|
11
|
+
...flag?.char ? [
|
|
12
|
+
`-${flag.char}`
|
|
13
|
+
] : [],
|
|
14
|
+
...(flag?.charAliases ?? []).map((alias)=>`-${alias}`)
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function redactTelemetryArguments(arguments_, commandTelemetry) {
|
|
21
|
+
let forwarding = false;
|
|
22
|
+
return arguments_.map((argument)=>{
|
|
23
|
+
if (argument === '--') {
|
|
24
|
+
forwarding = true;
|
|
25
|
+
return argument;
|
|
26
|
+
}
|
|
27
|
+
if (forwarding) return argument;
|
|
28
|
+
const isShortOption = argument.startsWith('-') && !argument.startsWith('--');
|
|
29
|
+
const redactedFlag = commandTelemetry?.redactedFlags.find(({ flags })=>flags.some((syntax)=>syntax.startsWith('--') ? argument === syntax || argument.startsWith(`${syntax}=`) : isShortOption && argument.startsWith(syntax)));
|
|
30
|
+
return redactedFlag?.canonical ?? argument;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=commandTelemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/util/telemetry/commandTelemetry.ts"],"sourcesContent":["import {type Interfaces} from '@oclif/core'\n\ninterface RedactedFlag {\n canonical: string\n flags: readonly string[]\n}\n\n/** @internal */\nexport interface CommandTelemetry {\n redactedFlags: readonly RedactedFlag[]\n}\n\ntype OptionFlagName<Flags extends Interfaces.FlagInput> = string &\n {\n [Name in keyof Flags]: Flags[Name] extends {type: 'option'} ? Name : never\n }[keyof Flags]\n\ntype CommandTelemetryConfig<Flags extends Interfaces.FlagInput> = {\n redact?: readonly OptionFlagName<Flags>[]\n}\n\n/** @internal */\nexport function defineCommandTelemetry<const Flags extends Interfaces.FlagInput>(\n flags: Flags,\n config: CommandTelemetryConfig<Flags>,\n): CommandTelemetry {\n return {\n redactedFlags: (config.redact ?? []).map((name) => {\n const flag = flags[name]\n const aliases = flag?.aliases ?? []\n\n return {\n canonical: `--${name}`,\n flags: [\n `--${name}`,\n ...aliases.map((alias) => `${alias.length === 1 ? '-' : '--'}${alias}`),\n ...(flag?.char ? [`-${flag.char}`] : []),\n ...(flag?.charAliases ?? []).map((alias) => `-${alias}`),\n ],\n }\n }),\n }\n}\n\nexport function redactTelemetryArguments(\n arguments_: string[],\n commandTelemetry?: CommandTelemetry,\n): string[] {\n let forwarding = false\n\n return arguments_.map((argument) => {\n if (argument === '--') {\n forwarding = true\n return argument\n }\n\n if (forwarding) return argument\n\n const isShortOption = argument.startsWith('-') && !argument.startsWith('--')\n const redactedFlag = commandTelemetry?.redactedFlags.find(({flags}) =>\n flags.some((syntax) =>\n syntax.startsWith('--')\n ? argument === syntax || argument.startsWith(`${syntax}=`)\n : isShortOption && argument.startsWith(syntax),\n ),\n )\n\n return redactedFlag?.canonical ?? argument\n })\n}\n"],"names":["defineCommandTelemetry","flags","config","redactedFlags","redact","map","name","flag","aliases","canonical","alias","length","char","charAliases","redactTelemetryArguments","arguments_","commandTelemetry","forwarding","argument","isShortOption","startsWith","redactedFlag","find","some","syntax"],"mappings":"AAqBA,cAAc,GACd,OAAO,SAASA,uBACdC,KAAY,EACZC,MAAqC;IAErC,OAAO;QACLC,eAAe,AAACD,CAAAA,OAAOE,MAAM,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YACxC,MAAMC,OAAON,KAAK,CAACK,KAAK;YACxB,MAAME,UAAUD,MAAMC,WAAW,EAAE;YAEnC,OAAO;gBACLC,WAAW,CAAC,EAAE,EAAEH,MAAM;gBACtBL,OAAO;oBACL,CAAC,EAAE,EAAEK,MAAM;uBACRE,QAAQH,GAAG,CAAC,CAACK,QAAU,GAAGA,MAAMC,MAAM,KAAK,IAAI,MAAM,OAAOD,OAAO;uBAClEH,MAAMK,OAAO;wBAAC,CAAC,CAAC,EAAEL,KAAKK,IAAI,EAAE;qBAAC,GAAG,EAAE;uBACpC,AAACL,CAAAA,MAAMM,eAAe,EAAE,AAAD,EAAGR,GAAG,CAAC,CAACK,QAAU,CAAC,CAAC,EAAEA,OAAO;iBACxD;YACH;QACF;IACF;AACF;AAEA,OAAO,SAASI,yBACdC,UAAoB,EACpBC,gBAAmC;IAEnC,IAAIC,aAAa;IAEjB,OAAOF,WAAWV,GAAG,CAAC,CAACa;QACrB,IAAIA,aAAa,MAAM;YACrBD,aAAa;YACb,OAAOC;QACT;QAEA,IAAID,YAAY,OAAOC;QAEvB,MAAMC,gBAAgBD,SAASE,UAAU,CAAC,QAAQ,CAACF,SAASE,UAAU,CAAC;QACvE,MAAMC,eAAeL,kBAAkBb,cAAcmB,KAAK,CAAC,EAACrB,KAAK,EAAC,GAChEA,MAAMsB,IAAI,CAAC,CAACC,SACVA,OAAOJ,UAAU,CAAC,QACdF,aAAaM,UAAUN,SAASE,UAAU,CAAC,GAAGI,OAAO,CAAC,CAAC,IACvDL,iBAAiBD,SAASE,UAAU,CAACI;QAI7C,OAAOH,cAAcZ,aAAaS;IACpC;AACF"}
|
|
@@ -71,6 +71,16 @@ function parseRecord(projectId, value) {
|
|
|
71
71
|
}
|
|
72
72
|
return parsed;
|
|
73
73
|
}
|
|
74
|
+
export function hasLocalUnclaimedProject(projectId) {
|
|
75
|
+
if (!projectId) return false;
|
|
76
|
+
try {
|
|
77
|
+
const stored = getUserConfig().get(UNCLAIMED_PROJECTS_CONFIG_KEY);
|
|
78
|
+
if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return false;
|
|
79
|
+
return Object.hasOwn(stored, projectId);
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
74
84
|
/**
|
|
75
85
|
* Read and validate the passive local recovery registry without changing it.
|
|
76
86
|
*/ export function readUnclaimedProjects() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/unclaimedProjects.ts"],"sourcesContent":["import {getUserConfig} from '@sanity/cli-core/config'\nimport {subdebug} from '@sanity/cli-core/debug'\n\nimport {type MintedProject} from '../services/mintProject.js'\n\nconst debug = subdebug('projects:unclaimed:registry')\n\nexport const UNCLAIMED_PROJECTS_CONFIG_KEY = 'unclaimedProjects'\n\nexport interface UnclaimedProjectRecord {\n claimToken: string\n claimUrl: string\n dataset: string\n expiresAt: string\n mintedAt: string\n projectId: string\n token: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\n/**\n * Record a mint response in the passive, project-ID-keyed user-config registry.\n *\n * This storage is deliberately not connected to CLI credential resolution.\n */\nexport function recordUnclaimedProject(\n minted: MintedProject,\n mintedAt: string = new Date().toISOString(),\n): boolean {\n try {\n const config = getUserConfig()\n const records = asRecord(config.get(UNCLAIMED_PROJECTS_CONFIG_KEY))\n const record: UnclaimedProjectRecord = {\n claimToken: minted.claimToken,\n claimUrl: minted.claimUrl,\n dataset: minted.datasetName,\n expiresAt: minted.expiresAt,\n mintedAt,\n projectId: minted.resourceId,\n token: minted.token,\n }\n\n config.set(UNCLAIMED_PROJECTS_CONFIG_KEY, {\n ...records,\n [minted.resourceId]: record,\n })\n return true\n } catch (error) {\n debug(\n 'failed to record unclaimed project: %s',\n error instanceof Error ? error.message : `${error}`,\n )\n return false\n }\n}\n\nfunction requireString(\n value: Record<string, unknown>,\n key: keyof UnclaimedProjectRecord,\n projectId: string,\n): string {\n const field = value[key]\n if (typeof field !== 'string' || field.length === 0) {\n throw new Error(\n `Local recovery record for project \"${projectId}\" is malformed: \"${key}\" is missing or invalid.`,\n )\n }\n return field\n}\n\nfunction parseRecord(projectId: string, value: unknown): UnclaimedProjectRecord {\n const record = asRecord(value)\n const parsed: UnclaimedProjectRecord = {\n claimToken: requireString(record, 'claimToken', projectId),\n claimUrl: requireString(record, 'claimUrl', projectId),\n dataset: requireString(record, 'dataset', projectId),\n expiresAt: requireString(record, 'expiresAt', projectId),\n mintedAt: requireString(record, 'mintedAt', projectId),\n projectId: requireString(record, 'projectId', projectId),\n token: requireString(record, 'token', projectId),\n }\n\n if (parsed.projectId !== projectId) {\n throw new Error(\n `Local recovery record for project \"${projectId}\" is malformed: its project ID does not match.`,\n )\n }\n let claimUrlValid = false\n try {\n const protocol = new URL(parsed.claimUrl).protocol\n claimUrlValid = protocol === 'https:' || protocol === 'http:'\n } catch {\n // The actionable error is reported below.\n }\n if (!claimUrlValid) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"claimUrl\" is not a valid URL.`,\n )\n }\n if (!Number.isFinite(Date.parse(parsed.mintedAt))) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"mintedAt\" is not a valid date.`,\n )\n }\n if (!Number.isFinite(Date.parse(parsed.expiresAt))) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"expiresAt\" is not a valid date.`,\n )\n }\n\n return parsed\n}\n\n/**\n * Read and validate the passive local recovery registry without changing it.\n */\nexport function readUnclaimedProjects(): UnclaimedProjectRecord[] {\n const stored = getUserConfig().get(UNCLAIMED_PROJECTS_CONFIG_KEY)\n if (stored === undefined) return []\n if (!stored || typeof stored !== 'object' || Array.isArray(stored)) {\n throw new Error('The local unclaimed-project registry is malformed.')\n }\n\n return Object.entries(stored)\n .map(([projectId, record]) => parseRecord(projectId, record))\n .toSorted((left, right) => Date.parse(left.expiresAt) - Date.parse(right.expiresAt))\n}\n\n/**\n * Remove a record only when its claim token still matches the record that was checked.\n */\nexport function removeUnclaimedProject(projectId: string, expectedClaimToken: string): boolean {\n try {\n const config = getUserConfig()\n const records = asRecord(config.get(UNCLAIMED_PROJECTS_CONFIG_KEY))\n const current = asRecord(records[projectId])\n if (current.claimToken !== expectedClaimToken) return false\n\n const {[projectId]: _, ...remaining} = records\n if (Object.keys(remaining).length === 0) {\n config.delete(UNCLAIMED_PROJECTS_CONFIG_KEY)\n } else {\n config.set(UNCLAIMED_PROJECTS_CONFIG_KEY, remaining)\n }\n return true\n } catch (error) {\n debug(\n 'failed to remove unclaimed project: %s',\n error instanceof Error ? error.message : `${error}`,\n )\n return false\n }\n}\n"],"names":["getUserConfig","subdebug","debug","UNCLAIMED_PROJECTS_CONFIG_KEY","asRecord","value","Array","isArray","recordUnclaimedProject","minted","mintedAt","Date","toISOString","config","records","get","record","claimToken","claimUrl","dataset","datasetName","expiresAt","projectId","resourceId","token","set","error","Error","message","requireString","key","field","length","parseRecord","parsed","claimUrlValid","protocol","URL","TypeError","Number","isFinite","parse","
|
|
1
|
+
{"version":3,"sources":["../../src/util/unclaimedProjects.ts"],"sourcesContent":["import {getUserConfig} from '@sanity/cli-core/config'\nimport {subdebug} from '@sanity/cli-core/debug'\n\nimport {type MintedProject} from '../services/mintProject.js'\n\nconst debug = subdebug('projects:unclaimed:registry')\n\nexport const UNCLAIMED_PROJECTS_CONFIG_KEY = 'unclaimedProjects'\n\nexport interface UnclaimedProjectRecord {\n claimToken: string\n claimUrl: string\n dataset: string\n expiresAt: string\n mintedAt: string\n projectId: string\n token: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n}\n\n/**\n * Record a mint response in the passive, project-ID-keyed user-config registry.\n *\n * This storage is deliberately not connected to CLI credential resolution.\n */\nexport function recordUnclaimedProject(\n minted: MintedProject,\n mintedAt: string = new Date().toISOString(),\n): boolean {\n try {\n const config = getUserConfig()\n const records = asRecord(config.get(UNCLAIMED_PROJECTS_CONFIG_KEY))\n const record: UnclaimedProjectRecord = {\n claimToken: minted.claimToken,\n claimUrl: minted.claimUrl,\n dataset: minted.datasetName,\n expiresAt: minted.expiresAt,\n mintedAt,\n projectId: minted.resourceId,\n token: minted.token,\n }\n\n config.set(UNCLAIMED_PROJECTS_CONFIG_KEY, {\n ...records,\n [minted.resourceId]: record,\n })\n return true\n } catch (error) {\n debug(\n 'failed to record unclaimed project: %s',\n error instanceof Error ? error.message : `${error}`,\n )\n return false\n }\n}\n\nfunction requireString(\n value: Record<string, unknown>,\n key: keyof UnclaimedProjectRecord,\n projectId: string,\n): string {\n const field = value[key]\n if (typeof field !== 'string' || field.length === 0) {\n throw new Error(\n `Local recovery record for project \"${projectId}\" is malformed: \"${key}\" is missing or invalid.`,\n )\n }\n return field\n}\n\nfunction parseRecord(projectId: string, value: unknown): UnclaimedProjectRecord {\n const record = asRecord(value)\n const parsed: UnclaimedProjectRecord = {\n claimToken: requireString(record, 'claimToken', projectId),\n claimUrl: requireString(record, 'claimUrl', projectId),\n dataset: requireString(record, 'dataset', projectId),\n expiresAt: requireString(record, 'expiresAt', projectId),\n mintedAt: requireString(record, 'mintedAt', projectId),\n projectId: requireString(record, 'projectId', projectId),\n token: requireString(record, 'token', projectId),\n }\n\n if (parsed.projectId !== projectId) {\n throw new Error(\n `Local recovery record for project \"${projectId}\" is malformed: its project ID does not match.`,\n )\n }\n let claimUrlValid = false\n try {\n const protocol = new URL(parsed.claimUrl).protocol\n claimUrlValid = protocol === 'https:' || protocol === 'http:'\n } catch {\n // The actionable error is reported below.\n }\n if (!claimUrlValid) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"claimUrl\" is not a valid URL.`,\n )\n }\n if (!Number.isFinite(Date.parse(parsed.mintedAt))) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"mintedAt\" is not a valid date.`,\n )\n }\n if (!Number.isFinite(Date.parse(parsed.expiresAt))) {\n throw new TypeError(\n `Local recovery record for project \"${projectId}\" is malformed: \"expiresAt\" is not a valid date.`,\n )\n }\n\n return parsed\n}\n\nexport function hasLocalUnclaimedProject(projectId: string): boolean {\n if (!projectId) return false\n try {\n const stored = getUserConfig().get(UNCLAIMED_PROJECTS_CONFIG_KEY)\n if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return false\n return Object.hasOwn(stored, projectId)\n } catch {\n return false\n }\n}\n\n/**\n * Read and validate the passive local recovery registry without changing it.\n */\nexport function readUnclaimedProjects(): UnclaimedProjectRecord[] {\n const stored = getUserConfig().get(UNCLAIMED_PROJECTS_CONFIG_KEY)\n if (stored === undefined) return []\n if (!stored || typeof stored !== 'object' || Array.isArray(stored)) {\n throw new Error('The local unclaimed-project registry is malformed.')\n }\n\n return Object.entries(stored)\n .map(([projectId, record]) => parseRecord(projectId, record))\n .toSorted((left, right) => Date.parse(left.expiresAt) - Date.parse(right.expiresAt))\n}\n\n/**\n * Remove a record only when its claim token still matches the record that was checked.\n */\nexport function removeUnclaimedProject(projectId: string, expectedClaimToken: string): boolean {\n try {\n const config = getUserConfig()\n const records = asRecord(config.get(UNCLAIMED_PROJECTS_CONFIG_KEY))\n const current = asRecord(records[projectId])\n if (current.claimToken !== expectedClaimToken) return false\n\n const {[projectId]: _, ...remaining} = records\n if (Object.keys(remaining).length === 0) {\n config.delete(UNCLAIMED_PROJECTS_CONFIG_KEY)\n } else {\n config.set(UNCLAIMED_PROJECTS_CONFIG_KEY, remaining)\n }\n return true\n } catch (error) {\n debug(\n 'failed to remove unclaimed project: %s',\n error instanceof Error ? error.message : `${error}`,\n )\n return false\n }\n}\n"],"names":["getUserConfig","subdebug","debug","UNCLAIMED_PROJECTS_CONFIG_KEY","asRecord","value","Array","isArray","recordUnclaimedProject","minted","mintedAt","Date","toISOString","config","records","get","record","claimToken","claimUrl","dataset","datasetName","expiresAt","projectId","resourceId","token","set","error","Error","message","requireString","key","field","length","parseRecord","parsed","claimUrlValid","protocol","URL","TypeError","Number","isFinite","parse","hasLocalUnclaimedProject","stored","Object","hasOwn","readUnclaimedProjects","undefined","entries","map","toSorted","left","right","removeUnclaimedProject","expectedClaimToken","current","_","remaining","keys","delete"],"mappings":"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"}
|