@sanity/cli 8.0.2 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +4 -4
  2. package/dist/SanityHelp.js.map +1 -1
  3. package/dist/actions/backup/assertDatasetExist.js +2 -1
  4. package/dist/actions/backup/assertDatasetExist.js.map +1 -1
  5. package/dist/actions/build/buildStudio.js +3 -2
  6. package/dist/actions/build/buildStudio.js.map +1 -1
  7. package/dist/actions/build/eventListenerFactory.js +7 -6
  8. package/dist/actions/build/eventListenerFactory.js.map +1 -1
  9. package/dist/actions/build/shouldAutoUpdate.js +2 -1
  10. package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
  11. package/dist/actions/cors/filterAndValidateOrigin.js +3 -2
  12. package/dist/actions/cors/filterAndValidateOrigin.js.map +1 -1
  13. package/dist/actions/deploy/createUserApplication.js +2 -1
  14. package/dist/actions/deploy/createUserApplication.js.map +1 -1
  15. package/dist/actions/deploy/deployRunner.js +3 -2
  16. package/dist/actions/deploy/deployRunner.js.map +1 -1
  17. package/dist/actions/deploy/deployStudio.js +3 -3
  18. package/dist/actions/deploy/deployStudio.js.map +1 -1
  19. package/dist/actions/deploy/findUserApplication.js +8 -7
  20. package/dist/actions/deploy/findUserApplication.js.map +1 -1
  21. package/dist/actions/dev/servers/startAppDevServer.js +2 -1
  22. package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
  23. package/dist/actions/dev/servers/startStudioDevServer.js +3 -3
  24. package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
  25. package/dist/actions/documents/validate.js +5 -2
  26. package/dist/actions/documents/validate.js.map +1 -1
  27. package/dist/actions/init/env/createOrAppendEnvVars.js +2 -1
  28. package/dist/actions/init/env/createOrAppendEnvVars.js.map +1 -1
  29. package/dist/actions/init/env/parseAndUpdateEnvVars.js +2 -2
  30. package/dist/actions/init/env/parseAndUpdateEnvVars.js.map +1 -1
  31. package/dist/actions/init/sdkAppDependencies.js +2 -2
  32. package/dist/actions/init/sdkAppDependencies.js.map +1 -1
  33. package/dist/actions/init/studioDependencies.js +2 -2
  34. package/dist/actions/init/studioDependencies.js.map +1 -1
  35. package/dist/actions/scaffold/scaffoldProject.js +11 -6
  36. package/dist/actions/scaffold/scaffoldProject.js.map +1 -1
  37. package/dist/actions/schema/extractSchema.js +2 -2
  38. package/dist/actions/schema/extractSchema.js.map +1 -1
  39. package/dist/actions/schema/extractSchemaWatcher.js +2 -1
  40. package/dist/actions/schema/extractSchemaWatcher.js.map +1 -1
  41. package/dist/actions/undeploy/runUndeploy.js +2 -2
  42. package/dist/actions/undeploy/runUndeploy.js.map +1 -1
  43. package/dist/commands/backups/list.js +5 -8
  44. package/dist/commands/backups/list.js.map +1 -1
  45. package/dist/commands/datasets/copy.js +5 -7
  46. package/dist/commands/datasets/copy.js.map +1 -1
  47. package/dist/commands/docs/search.js +2 -2
  48. package/dist/commands/docs/search.js.map +1 -1
  49. package/dist/commands/mcp/configure.js +2 -2
  50. package/dist/commands/mcp/configure.js.map +1 -1
  51. package/dist/commands/new.js +1 -2
  52. package/dist/commands/new.js.map +1 -1
  53. package/dist/commands/typegen/generate.js +6 -6
  54. package/dist/commands/typegen/generate.js.map +1 -1
  55. package/dist/services/assets.js +1 -1
  56. package/dist/services/assets.js.map +1 -1
  57. package/dist/services/mintProject.js +27 -3
  58. package/dist/services/mintProject.js.map +1 -1
  59. package/dist/util/appId.js +2 -1
  60. package/dist/util/appId.js.map +1 -1
  61. package/dist/util/dates.js +125 -0
  62. package/dist/util/dates.js.map +1 -0
  63. package/dist/util/getCliVersion.js +1 -11
  64. package/dist/util/getCliVersion.js.map +1 -1
  65. package/oclif.manifest.json +2 -2
  66. package/package.json +5 -8
@@ -4,6 +4,29 @@ import { isStaging } from '@sanity/cli-core/util';
4
4
  import { TERMS_OF_SERVICE_FALLBACK_NOTICE, TERMS_OF_SERVICE_FALLBACK_URL } from '../util/mintProjectConstants.js';
5
5
  const debug = subdebug('new:provision');
6
6
  /** Provision API version for minting unclaimed projects. */ export const PROVISION_API_VERSION = 'v2026-06-23';
7
+ /**
8
+ * Request tag identifying the caller to the provisioning funnel, using the same `?tag=` convention
9
+ * as every other Sanity API request. `sanity new` is deliberately usable without an account, so
10
+ * most mints have no user to attribute — this is what tells reporting who made them, and is how
11
+ * synthetic callers are kept out of mint-to-claim conversion.
12
+ */ export const MINT_REQUEST_TAG = 'sanity.cli';
13
+ /**
14
+ * Overrides {@link MINT_REQUEST_TAG}. Internal plumbing for the scheduled smoke test, which mints
15
+ * against production several times an hour and never claims; it sets `sanity.cli.smoketest` so
16
+ * those mints can be excluded from reporting.
17
+ */ const MINT_TAG_ENV_VAR = 'SANITY_CLI_MINT_TAG';
18
+ /** Mirrors `@sanity/client`'s `requestTag` rule, which is what the API accepts. */ const TAG_PATTERN = /^[a-z0-9._-]{1,75}$/i;
19
+ function getRequestTag() {
20
+ const override = process.env[MINT_TAG_ENV_VAR];
21
+ if (!override) return MINT_REQUEST_TAG;
22
+ if (!TAG_PATTERN.test(override)) {
23
+ // A malformed override would be dropped by the API anyway; fall back rather than mint
24
+ // untagged, so the request is still attributable.
25
+ debug('ignoring malformed %s value %j', MINT_TAG_ENV_VAR, override);
26
+ return MINT_REQUEST_TAG;
27
+ }
28
+ return override;
29
+ }
7
30
  const request = createRequester({
8
31
  httpErrors: false
9
32
  });
@@ -58,8 +81,9 @@ function parseProvisionResponse(body) {
58
81
  if (!displayName || displayName.length > 80) {
59
82
  throw new Error('Project name must be 1-80 characters.');
60
83
  }
61
- const url = `${getProvisionApiBase()}/${PROVISION_API_VERSION}/provision`;
62
- debug('minting unclaimed project at %s', url);
84
+ const url = new URL(`${PROVISION_API_VERSION}/provision`, getProvisionApiBase());
85
+ url.searchParams.set('tag', getRequestTag());
86
+ debug('minting unclaimed project at %s', url.toString());
63
87
  const response = await request({
64
88
  body: JSON.stringify({
65
89
  displayName,
@@ -69,7 +93,7 @@ function parseProvisionResponse(body) {
69
93
  'Content-Type': 'application/json'
70
94
  },
71
95
  method: 'POST',
72
- url
96
+ url: url.toString()
73
97
  });
74
98
  if (response.status === 404) {
75
99
  throw new Error('Creating projects without an account is currently unavailable. Try again later, or run `sanity login` and `sanity init`.');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/mintProject.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core/debug'\nimport {createRequester} from '@sanity/cli-core/request'\nimport {isStaging} from '@sanity/cli-core/util'\n\nimport {\n TERMS_OF_SERVICE_FALLBACK_NOTICE,\n TERMS_OF_SERVICE_FALLBACK_URL,\n} from '../util/mintProjectConstants.js'\n\nconst debug = subdebug('new:provision')\n\n/** Provision API version for minting unclaimed projects. */\nexport const PROVISION_API_VERSION = 'v2026-06-23'\n\nconst request = createRequester({httpErrors: false})\n\nexport interface MintedProject {\n apiHost: string\n claimApiUrl: string\n claimToken: string\n claimUrl: string\n datasetName: string\n expiresAt: string\n resourceId: string\n /** Terms of Service accepted by using the project. Falls back to the bundled constants. */\n termsNotice: string\n termsUrl: string\n token: string\n}\n\nfunction getProvisionApiBase(): string {\n const override = process.env.SANITY_API_HOST\n if (override) return override.replace(/\\/$/u, '')\n return isStaging() ? 'https://api.sanity.work' : 'https://api.sanity.io'\n}\n\nfunction parseJsonBody(body: unknown): unknown {\n if (typeof body !== 'string') return body\n\n try {\n return JSON.parse(body)\n } catch {\n return undefined\n }\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction parseProvisionResponse(body: unknown): MintedProject {\n const data = parseJsonBody(body)\n const response =\n data && typeof data === 'object' && !Array.isArray(data)\n ? (data as Record<string, unknown>)\n : {}\n const links =\n response.links && typeof response.links === 'object' && !Array.isArray(response.links)\n ? (response.links as Record<string, unknown>)\n : {}\n const terms =\n response.terms && typeof response.terms === 'object' && !Array.isArray(response.terms)\n ? (response.terms as Record<string, unknown>)\n : {}\n\n const minted = {\n apiHost: getString(response.apiHost),\n claimApiUrl: getString(links.claimApiUrl),\n claimToken: getString(response.claimToken),\n claimUrl: getString(links.claimUrl),\n datasetName: getString(response.datasetName),\n expiresAt: getString(response.expiresAt),\n resourceId: getString(response.resourceId),\n token: getString(response.token),\n }\n const missing = Object.entries(minted)\n .filter(([, value]) => value === undefined)\n .map(([key]) => key)\n\n if (response.resourceType !== 'project') {\n missing.push('resourceType')\n }\n if (missing.length > 0) {\n throw new Error(`Project creation response is missing or invalid: ${missing.join(', ')}`)\n }\n\n return {\n ...(minted as Omit<MintedProject, 'termsNotice' | 'termsUrl'>),\n termsNotice: getString(terms.notice) ?? TERMS_OF_SERVICE_FALLBACK_NOTICE,\n termsUrl: getString(terms.url) ?? TERMS_OF_SERVICE_FALLBACK_URL,\n }\n}\n\n/**\n * Mint an unclaimed Sanity project through the public provision endpoint.\n */\nexport async function mintUnclaimedProject(options: {displayName: string}): Promise<MintedProject> {\n const displayName = options.displayName.trim()\n if (!displayName || displayName.length > 80) {\n throw new Error('Project name must be 1-80 characters.')\n }\n\n const url = `${getProvisionApiBase()}/${PROVISION_API_VERSION}/provision`\n debug('minting unclaimed project at %s', url)\n\n const response = await request({\n body: JSON.stringify({displayName, resourceType: 'project'}),\n headers: {'Content-Type': 'application/json'},\n method: 'POST',\n url,\n })\n\n if (response.status === 404) {\n throw new Error(\n 'Creating projects without an account is currently unavailable. Try again later, or run `sanity login` and `sanity init`.',\n )\n }\n if (response.status === 429) {\n throw new Error('Project creation rate limit reached for this machine. Try again later.')\n }\n if (response.status < 200 || response.status >= 300) {\n throw new Error(`Project creation failed (HTTP ${response.status}). Try again later.`)\n }\n\n return parseProvisionResponse(response.text())\n}\n"],"names":["subdebug","createRequester","isStaging","TERMS_OF_SERVICE_FALLBACK_NOTICE","TERMS_OF_SERVICE_FALLBACK_URL","debug","PROVISION_API_VERSION","request","httpErrors","getProvisionApiBase","override","process","env","SANITY_API_HOST","replace","parseJsonBody","body","JSON","parse","undefined","getString","value","length","parseProvisionResponse","data","response","Array","isArray","links","terms","minted","apiHost","claimApiUrl","claimToken","claimUrl","datasetName","expiresAt","resourceId","token","missing","Object","entries","filter","map","key","resourceType","push","Error","join","termsNotice","notice","termsUrl","url","mintUnclaimedProject","options","displayName","trim","stringify","headers","method","status","text"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/mintProject.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core/debug'\nimport {createRequester} from '@sanity/cli-core/request'\nimport {isStaging} from '@sanity/cli-core/util'\n\nimport {\n TERMS_OF_SERVICE_FALLBACK_NOTICE,\n TERMS_OF_SERVICE_FALLBACK_URL,\n} from '../util/mintProjectConstants.js'\n\nconst debug = subdebug('new:provision')\n\n/** Provision API version for minting unclaimed projects. */\nexport const PROVISION_API_VERSION = 'v2026-06-23'\n\n/**\n * Request tag identifying the caller to the provisioning funnel, using the same `?tag=` convention\n * as every other Sanity API request. `sanity new` is deliberately usable without an account, so\n * most mints have no user to attribute — this is what tells reporting who made them, and is how\n * synthetic callers are kept out of mint-to-claim conversion.\n */\nexport const MINT_REQUEST_TAG = 'sanity.cli'\n\n/**\n * Overrides {@link MINT_REQUEST_TAG}. Internal plumbing for the scheduled smoke test, which mints\n * against production several times an hour and never claims; it sets `sanity.cli.smoketest` so\n * those mints can be excluded from reporting.\n */\nconst MINT_TAG_ENV_VAR = 'SANITY_CLI_MINT_TAG'\n\n/** Mirrors `@sanity/client`'s `requestTag` rule, which is what the API accepts. */\nconst TAG_PATTERN = /^[a-z0-9._-]{1,75}$/i\n\nfunction getRequestTag(): string {\n const override = process.env[MINT_TAG_ENV_VAR]\n if (!override) return MINT_REQUEST_TAG\n if (!TAG_PATTERN.test(override)) {\n // A malformed override would be dropped by the API anyway; fall back rather than mint\n // untagged, so the request is still attributable.\n debug('ignoring malformed %s value %j', MINT_TAG_ENV_VAR, override)\n return MINT_REQUEST_TAG\n }\n return override\n}\n\nconst request = createRequester({httpErrors: false})\n\nexport interface MintedProject {\n apiHost: string\n claimApiUrl: string\n claimToken: string\n claimUrl: string\n datasetName: string\n expiresAt: string\n resourceId: string\n /** Terms of Service accepted by using the project. Falls back to the bundled constants. */\n termsNotice: string\n termsUrl: string\n token: string\n}\n\nfunction getProvisionApiBase(): string {\n const override = process.env.SANITY_API_HOST\n if (override) return override.replace(/\\/$/u, '')\n return isStaging() ? 'https://api.sanity.work' : 'https://api.sanity.io'\n}\n\nfunction parseJsonBody(body: unknown): unknown {\n if (typeof body !== 'string') return body\n\n try {\n return JSON.parse(body)\n } catch {\n return undefined\n }\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction parseProvisionResponse(body: unknown): MintedProject {\n const data = parseJsonBody(body)\n const response =\n data && typeof data === 'object' && !Array.isArray(data)\n ? (data as Record<string, unknown>)\n : {}\n const links =\n response.links && typeof response.links === 'object' && !Array.isArray(response.links)\n ? (response.links as Record<string, unknown>)\n : {}\n const terms =\n response.terms && typeof response.terms === 'object' && !Array.isArray(response.terms)\n ? (response.terms as Record<string, unknown>)\n : {}\n\n const minted = {\n apiHost: getString(response.apiHost),\n claimApiUrl: getString(links.claimApiUrl),\n claimToken: getString(response.claimToken),\n claimUrl: getString(links.claimUrl),\n datasetName: getString(response.datasetName),\n expiresAt: getString(response.expiresAt),\n resourceId: getString(response.resourceId),\n token: getString(response.token),\n }\n const missing = Object.entries(minted)\n .filter(([, value]) => value === undefined)\n .map(([key]) => key)\n\n if (response.resourceType !== 'project') {\n missing.push('resourceType')\n }\n if (missing.length > 0) {\n throw new Error(`Project creation response is missing or invalid: ${missing.join(', ')}`)\n }\n\n return {\n ...(minted as Omit<MintedProject, 'termsNotice' | 'termsUrl'>),\n termsNotice: getString(terms.notice) ?? TERMS_OF_SERVICE_FALLBACK_NOTICE,\n termsUrl: getString(terms.url) ?? TERMS_OF_SERVICE_FALLBACK_URL,\n }\n}\n\n/**\n * Mint an unclaimed Sanity project through the public provision endpoint.\n */\nexport async function mintUnclaimedProject(options: {displayName: string}): Promise<MintedProject> {\n const displayName = options.displayName.trim()\n if (!displayName || displayName.length > 80) {\n throw new Error('Project name must be 1-80 characters.')\n }\n\n const url = new URL(`${PROVISION_API_VERSION}/provision`, getProvisionApiBase())\n url.searchParams.set('tag', getRequestTag())\n debug('minting unclaimed project at %s', url.toString())\n\n const response = await request({\n body: JSON.stringify({displayName, resourceType: 'project'}),\n headers: {'Content-Type': 'application/json'},\n method: 'POST',\n url: url.toString(),\n })\n\n if (response.status === 404) {\n throw new Error(\n 'Creating projects without an account is currently unavailable. Try again later, or run `sanity login` and `sanity init`.',\n )\n }\n if (response.status === 429) {\n throw new Error('Project creation rate limit reached for this machine. Try again later.')\n }\n if (response.status < 200 || response.status >= 300) {\n throw new Error(`Project creation failed (HTTP ${response.status}). Try again later.`)\n }\n\n return parseProvisionResponse(response.text())\n}\n"],"names":["subdebug","createRequester","isStaging","TERMS_OF_SERVICE_FALLBACK_NOTICE","TERMS_OF_SERVICE_FALLBACK_URL","debug","PROVISION_API_VERSION","MINT_REQUEST_TAG","MINT_TAG_ENV_VAR","TAG_PATTERN","getRequestTag","override","process","env","test","request","httpErrors","getProvisionApiBase","SANITY_API_HOST","replace","parseJsonBody","body","JSON","parse","undefined","getString","value","length","parseProvisionResponse","data","response","Array","isArray","links","terms","minted","apiHost","claimApiUrl","claimToken","claimUrl","datasetName","expiresAt","resourceId","token","missing","Object","entries","filter","map","key","resourceType","push","Error","join","termsNotice","notice","termsUrl","url","mintUnclaimedProject","options","displayName","trim","URL","searchParams","set","toString","stringify","headers","method","status","text"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { styleText } from 'node:util';
2
+ import { exitCodes } from '@sanity/cli-core';
2
3
  function getDeploymentAppId(cliConfig) {
3
4
  const id = cliConfig?.deployment?.appId;
4
5
  return id;
@@ -33,7 +34,7 @@ function hasDeprecatedAppId(cliConfig) {
33
34
  output.error(`${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}
34
35
 
35
36
  Please remove app.id from your sanity.cli.js or sanity.cli.ts file.`, {
36
- exit: 1
37
+ exit: exitCodes.RUNTIME_ERROR
37
38
  });
38
39
  }
39
40
  // Just warn if only the old app ID config is found
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/appId.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\n\ninterface Options {\n cliConfig: CliConfig\n output: Output\n}\n\nfunction getDeploymentAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.deployment?.appId\n return id\n}\n\nfunction getDeprecatedAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.app?.id\n return id\n}\n\nfunction hasNewAppId(cliConfig: CliConfig) {\n return Boolean(getDeploymentAppId(cliConfig))\n}\n\nfunction hasDeprecatedAppId(cliConfig: CliConfig) {\n return Boolean(getDeprecatedAppId(cliConfig))\n}\n\n/** The one app-id configuration problem to surface, if any. */\nexport type AppIdIssue = 'conflicting-config' | 'deprecated-config'\n\n/**\n * Decides which app-id problem a config has: both the deprecated `app.id` and\n * `deployment.appId` (a conflict), only the deprecated one, or neither. Shared\n * so the real deploy and the dry-run check reach the same verdict.\n * @internal\n */\nexport function resolveAppIdIssue(cliConfig: CliConfig): AppIdIssue | null {\n if (!hasDeprecatedAppId(cliConfig)) return null\n return hasNewAppId(cliConfig) ? 'conflicting-config' : 'deprecated-config'\n}\n\n/**\n * Checks if an SDK app uses the deprecated app.id config & throws a warning if so.\n * @remarks Throws an error if an app uses both deployment.appId and app.id\n * @internal\n */\nexport function checkForDeprecatedAppId({cliConfig, output}: Options): void {\n const issue = resolveAppIdIssue(cliConfig)\n\n // Both configs set: a real deploy can't pick one, so stop here\n if (issue === 'conflicting-config') {\n output.error(\n `${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}\n\nPlease remove app.id from your sanity.cli.js or sanity.cli.ts file.`,\n {\n exit: 1,\n },\n )\n }\n\n // Just warn if only the old app ID config is found\n if (issue === 'deprecated-config') {\n output.warn(\n `${styleText('bold', 'The `app.id` config has moved to `deployment.appId`.')}\n\nPlease update \\`sanity.cli.ts\\` or \\`sanity.cli.js\\` and move:\n${styleText('red', `app: {id: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\nto\n${styleText('green', `deployment: {appId: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\n`,\n )\n }\n}\n\n/**\n * Get an application's ID\n * @remarks Favors the current implementation (deployment.appId) but will fall back to the deprecated app.id\n * @internal\n */\nexport function getAppId(cliConfig: CliConfig): string | undefined {\n const hasNew = hasNewAppId(cliConfig)\n const hasOld = hasDeprecatedAppId(cliConfig)\n\n if (hasNew) {\n return getDeploymentAppId(cliConfig)\n }\n\n if (hasOld) {\n return getDeprecatedAppId(cliConfig)\n }\n\n return undefined\n}\n"],"names":["styleText","getDeploymentAppId","cliConfig","id","deployment","appId","getDeprecatedAppId","app","hasNewAppId","Boolean","hasDeprecatedAppId","resolveAppIdIssue","checkForDeprecatedAppId","output","issue","error","exit","warn","getAppId","hasNew","hasOld","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AASnC,SAASC,mBAAmBC,SAAoB;IAC9C,MAAMC,KAAKD,WAAWE,YAAYC;IAClC,OAAOF;AACT;AAEA,SAASG,mBAAmBJ,SAAoB;IAC9C,MAAMC,KAAKD,WAAWK,KAAKJ;IAC3B,OAAOA;AACT;AAEA,SAASK,YAAYN,SAAoB;IACvC,OAAOO,QAAQR,mBAAmBC;AACpC;AAEA,SAASQ,mBAAmBR,SAAoB;IAC9C,OAAOO,QAAQH,mBAAmBJ;AACpC;AAKA;;;;;CAKC,GACD,OAAO,SAASS,kBAAkBT,SAAoB;IACpD,IAAI,CAACQ,mBAAmBR,YAAY,OAAO;IAC3C,OAAOM,YAAYN,aAAa,uBAAuB;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASU,wBAAwB,EAACV,SAAS,EAAEW,MAAM,EAAU;IAClE,MAAMC,QAAQH,kBAAkBT;IAEhC,+DAA+D;IAC/D,IAAIY,UAAU,sBAAsB;QAClCD,OAAOE,KAAK,CACV,GAAGf,UAAU,QAAQ,0FAA0F;;mEAElD,CAAC,EAC9D;YACEgB,MAAM;QACR;IAEJ;IAEA,mDAAmD;IACnD,IAAIF,UAAU,qBAAqB;QACjCD,OAAOI,IAAI,CACT,GAAGjB,UAAU,QAAQ,wDAAwD;;;AAGnF,EAAEA,UAAU,OAAO,CAAC,WAAW,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;;AAE1E,EAAEF,UAAU,SAAS,CAAC,qBAAqB,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;AACtF,CAAC;IAEC;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASgB,SAAShB,SAAoB;IAC3C,MAAMiB,SAASX,YAAYN;IAC3B,MAAMkB,SAASV,mBAAmBR;IAElC,IAAIiB,QAAQ;QACV,OAAOlB,mBAAmBC;IAC5B;IAEA,IAAIkB,QAAQ;QACV,OAAOd,mBAAmBJ;IAC5B;IAEA,OAAOmB;AACT"}
1
+ {"version":3,"sources":["../../src/util/appId.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, exitCodes, type Output} from '@sanity/cli-core'\n\ninterface Options {\n cliConfig: CliConfig\n output: Output\n}\n\nfunction getDeploymentAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.deployment?.appId\n return id\n}\n\nfunction getDeprecatedAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.app?.id\n return id\n}\n\nfunction hasNewAppId(cliConfig: CliConfig) {\n return Boolean(getDeploymentAppId(cliConfig))\n}\n\nfunction hasDeprecatedAppId(cliConfig: CliConfig) {\n return Boolean(getDeprecatedAppId(cliConfig))\n}\n\n/** The one app-id configuration problem to surface, if any. */\nexport type AppIdIssue = 'conflicting-config' | 'deprecated-config'\n\n/**\n * Decides which app-id problem a config has: both the deprecated `app.id` and\n * `deployment.appId` (a conflict), only the deprecated one, or neither. Shared\n * so the real deploy and the dry-run check reach the same verdict.\n * @internal\n */\nexport function resolveAppIdIssue(cliConfig: CliConfig): AppIdIssue | null {\n if (!hasDeprecatedAppId(cliConfig)) return null\n return hasNewAppId(cliConfig) ? 'conflicting-config' : 'deprecated-config'\n}\n\n/**\n * Checks if an SDK app uses the deprecated app.id config & throws a warning if so.\n * @remarks Throws an error if an app uses both deployment.appId and app.id\n * @internal\n */\nexport function checkForDeprecatedAppId({cliConfig, output}: Options): void {\n const issue = resolveAppIdIssue(cliConfig)\n\n // Both configs set: a real deploy can't pick one, so stop here\n if (issue === 'conflicting-config') {\n output.error(\n `${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}\n\nPlease remove app.id from your sanity.cli.js or sanity.cli.ts file.`,\n {\n exit: exitCodes.RUNTIME_ERROR,\n },\n )\n }\n\n // Just warn if only the old app ID config is found\n if (issue === 'deprecated-config') {\n output.warn(\n `${styleText('bold', 'The `app.id` config has moved to `deployment.appId`.')}\n\nPlease update \\`sanity.cli.ts\\` or \\`sanity.cli.js\\` and move:\n${styleText('red', `app: {id: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\nto\n${styleText('green', `deployment: {appId: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\n`,\n )\n }\n}\n\n/**\n * Get an application's ID\n * @remarks Favors the current implementation (deployment.appId) but will fall back to the deprecated app.id\n * @internal\n */\nexport function getAppId(cliConfig: CliConfig): string | undefined {\n const hasNew = hasNewAppId(cliConfig)\n const hasOld = hasDeprecatedAppId(cliConfig)\n\n if (hasNew) {\n return getDeploymentAppId(cliConfig)\n }\n\n if (hasOld) {\n return getDeprecatedAppId(cliConfig)\n }\n\n return undefined\n}\n"],"names":["styleText","exitCodes","getDeploymentAppId","cliConfig","id","deployment","appId","getDeprecatedAppId","app","hasNewAppId","Boolean","hasDeprecatedAppId","resolveAppIdIssue","checkForDeprecatedAppId","output","issue","error","exit","RUNTIME_ERROR","warn","getAppId","hasNew","hasOld","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAwBC,SAAS,QAAoB,mBAAkB;AAOvE,SAASC,mBAAmBC,SAAoB;IAC9C,MAAMC,KAAKD,WAAWE,YAAYC;IAClC,OAAOF;AACT;AAEA,SAASG,mBAAmBJ,SAAoB;IAC9C,MAAMC,KAAKD,WAAWK,KAAKJ;IAC3B,OAAOA;AACT;AAEA,SAASK,YAAYN,SAAoB;IACvC,OAAOO,QAAQR,mBAAmBC;AACpC;AAEA,SAASQ,mBAAmBR,SAAoB;IAC9C,OAAOO,QAAQH,mBAAmBJ;AACpC;AAKA;;;;;CAKC,GACD,OAAO,SAASS,kBAAkBT,SAAoB;IACpD,IAAI,CAACQ,mBAAmBR,YAAY,OAAO;IAC3C,OAAOM,YAAYN,aAAa,uBAAuB;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASU,wBAAwB,EAACV,SAAS,EAAEW,MAAM,EAAU;IAClE,MAAMC,QAAQH,kBAAkBT;IAEhC,+DAA+D;IAC/D,IAAIY,UAAU,sBAAsB;QAClCD,OAAOE,KAAK,CACV,GAAGhB,UAAU,QAAQ,0FAA0F;;mEAElD,CAAC,EAC9D;YACEiB,MAAMhB,UAAUiB,aAAa;QAC/B;IAEJ;IAEA,mDAAmD;IACnD,IAAIH,UAAU,qBAAqB;QACjCD,OAAOK,IAAI,CACT,GAAGnB,UAAU,QAAQ,wDAAwD;;;AAGnF,EAAEA,UAAU,OAAO,CAAC,WAAW,EAAEO,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;;AAE1E,EAAEH,UAAU,SAAS,CAAC,qBAAqB,EAAEO,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;AACtF,CAAC;IAEC;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASiB,SAASjB,SAAoB;IAC3C,MAAMkB,SAASZ,YAAYN;IAC3B,MAAMmB,SAASX,mBAAmBR;IAElC,IAAIkB,QAAQ;QACV,OAAOnB,mBAAmBC;IAC5B;IAEA,IAAImB,QAAQ;QACV,OAAOf,mBAAmBJ;IAC5B;IAEA,OAAOoB;AACT"}
@@ -0,0 +1,125 @@
1
+ import { pluralize } from './pluralize.js';
2
+ const MS_IN = {
3
+ day: 86_400_000,
4
+ hour: 3_600_000,
5
+ minute: 60_000,
6
+ month: 2_592_000_000,
7
+ second: 1000,
8
+ week: 604_800_000,
9
+ year: 31_536_000_000
10
+ };
11
+ /** Largest unit first, so the first unit the duration fills is the one we report. */ const UNITS = [
12
+ {
13
+ ms: MS_IN.year,
14
+ unit: 'year'
15
+ },
16
+ {
17
+ ms: MS_IN.month,
18
+ unit: 'month'
19
+ },
20
+ {
21
+ ms: MS_IN.week,
22
+ unit: 'week'
23
+ },
24
+ {
25
+ ms: MS_IN.day,
26
+ unit: 'day'
27
+ },
28
+ {
29
+ ms: MS_IN.hour,
30
+ unit: 'hour'
31
+ },
32
+ {
33
+ ms: MS_IN.minute,
34
+ unit: 'minute'
35
+ },
36
+ {
37
+ ms: MS_IN.second,
38
+ unit: 'second'
39
+ }
40
+ ];
41
+ const relativeTime = new Intl.RelativeTimeFormat('en', {
42
+ numeric: 'always'
43
+ });
44
+ const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
45
+ function pad(value, length = 2) {
46
+ return String(value).padStart(length, '0');
47
+ }
48
+ /**
49
+ * Returns the coarsest unit the elapsed time fills and the rounded number of those
50
+ * units, or `undefined` below a second.
51
+ *
52
+ * Rounding can land the value on the boundary of the next unit up, so we promote
53
+ * rather than report `60 minutes` where `1 hour` is meant. Units aren't all whole
54
+ * multiples of each other, so the boundary is the rounded count we'd display, which
55
+ * is why 25+ days reads as `1 month` rather than `4 weeks`.
56
+ */ function selectUnit(elapsed) {
57
+ let index = UNITS.findIndex(({ ms })=>elapsed >= ms);
58
+ if (index === -1) return undefined;
59
+ while(index > 0 && Math.round(elapsed / UNITS[index].ms) >= Math.round(UNITS[index - 1].ms / UNITS[index].ms)){
60
+ index -= 1;
61
+ }
62
+ const { ms, unit } = UNITS[index];
63
+ return {
64
+ unit,
65
+ value: Math.round(elapsed / ms)
66
+ };
67
+ }
68
+ /**
69
+ * Formats a timestamp as `YYYY-MM-DD HH:mm:ss` in the local time zone. Unparseable
70
+ * timestamps are returned as-is, so a bad value from the API doesn't fail the command.
71
+ *
72
+ * @param timestamp - An ISO 8601 timestamp
73
+ * @internal
74
+ */ export function formatDateTime(timestamp) {
75
+ const date = new Date(timestamp);
76
+ if (!Number.isFinite(date.getTime())) return timestamp;
77
+ const day = `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
78
+ const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
79
+ return `${day} ${time}`;
80
+ }
81
+ /**
82
+ * Formats a duration in milliseconds as an approximate, human readable length of
83
+ * time, eg `less than a minute`, `5 minutes` or `2 days`.
84
+ *
85
+ * @param ms - The duration in milliseconds
86
+ * @internal
87
+ */ export function formatDuration(ms) {
88
+ if (!Number.isFinite(ms)) return '';
89
+ const selected = selectUnit(Math.abs(ms));
90
+ if (!selected || selected.unit === 'second') return 'less than a minute';
91
+ return `${selected.value} ${pluralize(selected.unit, selected.value)}`;
92
+ }
93
+ /**
94
+ * Formats a date relative to now, eg `2 days ago` or `in 3 hours`.
95
+ *
96
+ * @param date - The date to describe
97
+ * @param now - The reference point to compare against, defaults to the current time
98
+ * @internal
99
+ */ export function formatTimeAgo(date, now = Date.now()) {
100
+ const delta = date.getTime() - now;
101
+ if (!Number.isFinite(delta)) return '';
102
+ const selected = selectUnit(Math.abs(delta));
103
+ if (!selected) return 'just now';
104
+ return relativeTime.format(delta < 0 ? -selected.value : selected.value, selected.unit);
105
+ }
106
+ /**
107
+ * Parses a `YYYY-MM-DD` date into a `Date` at local midnight. Returns `undefined`
108
+ * for anything that isn't a real calendar date in that exact format.
109
+ *
110
+ * @param value - The date string to parse
111
+ * @internal
112
+ */ export function parseDateOnly(value) {
113
+ const match = DATE_ONLY_PATTERN.exec(value);
114
+ if (!match) return undefined;
115
+ const year = Number(match[1]);
116
+ const month = Number(match[2]);
117
+ const day = Number(match[3]);
118
+ const date = new Date(year, month - 1, day);
119
+ // `new Date(2024, 1, 31)` silently rolls over into March, so reject anything
120
+ // that didn't survive the round trip
121
+ const isRoundTrip = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
122
+ return isRoundTrip ? date : undefined;
123
+ }
124
+
125
+ //# sourceMappingURL=dates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/dates.ts"],"sourcesContent":["import {pluralize} from './pluralize.js'\n\nconst MS_IN = {\n day: 86_400_000,\n hour: 3_600_000,\n minute: 60_000,\n month: 2_592_000_000, // 30 days\n second: 1000,\n week: 604_800_000,\n year: 31_536_000_000, // 365 days\n} as const\n\n/** Largest unit first, so the first unit the duration fills is the one we report. */\nconst UNITS: {ms: number; unit: Intl.RelativeTimeFormatUnit}[] = [\n {ms: MS_IN.year, unit: 'year'},\n {ms: MS_IN.month, unit: 'month'},\n {ms: MS_IN.week, unit: 'week'},\n {ms: MS_IN.day, unit: 'day'},\n {ms: MS_IN.hour, unit: 'hour'},\n {ms: MS_IN.minute, unit: 'minute'},\n {ms: MS_IN.second, unit: 'second'},\n]\n\nconst relativeTime = new Intl.RelativeTimeFormat('en', {numeric: 'always'})\n\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\nfunction pad(value: number, length = 2): string {\n return String(value).padStart(length, '0')\n}\n\n/**\n * Returns the coarsest unit the elapsed time fills and the rounded number of those\n * units, or `undefined` below a second.\n *\n * Rounding can land the value on the boundary of the next unit up, so we promote\n * rather than report `60 minutes` where `1 hour` is meant. Units aren't all whole\n * multiples of each other, so the boundary is the rounded count we'd display, which\n * is why 25+ days reads as `1 month` rather than `4 weeks`.\n */\nfunction selectUnit(\n elapsed: number,\n): {unit: Intl.RelativeTimeFormatUnit; value: number} | undefined {\n let index = UNITS.findIndex(({ms}) => elapsed >= ms)\n if (index === -1) return undefined\n\n while (\n index > 0 &&\n Math.round(elapsed / UNITS[index].ms) >= Math.round(UNITS[index - 1].ms / UNITS[index].ms)\n ) {\n index -= 1\n }\n\n const {ms, unit} = UNITS[index]\n return {unit, value: Math.round(elapsed / ms)}\n}\n\n/**\n * Formats a timestamp as `YYYY-MM-DD HH:mm:ss` in the local time zone. Unparseable\n * timestamps are returned as-is, so a bad value from the API doesn't fail the command.\n *\n * @param timestamp - An ISO 8601 timestamp\n * @internal\n */\nexport function formatDateTime(timestamp: string): string {\n const date = new Date(timestamp)\n if (!Number.isFinite(date.getTime())) return timestamp\n\n const day = `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n return `${day} ${time}`\n}\n\n/**\n * Formats a duration in milliseconds as an approximate, human readable length of\n * time, eg `less than a minute`, `5 minutes` or `2 days`.\n *\n * @param ms - The duration in milliseconds\n * @internal\n */\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms)) return ''\n\n const selected = selectUnit(Math.abs(ms))\n if (!selected || selected.unit === 'second') return 'less than a minute'\n\n return `${selected.value} ${pluralize(selected.unit, selected.value)}`\n}\n\n/**\n * Formats a date relative to now, eg `2 days ago` or `in 3 hours`.\n *\n * @param date - The date to describe\n * @param now - The reference point to compare against, defaults to the current time\n * @internal\n */\nexport function formatTimeAgo(date: Date, now: number = Date.now()): string {\n const delta = date.getTime() - now\n if (!Number.isFinite(delta)) return ''\n\n const selected = selectUnit(Math.abs(delta))\n if (!selected) return 'just now'\n\n return relativeTime.format(delta < 0 ? -selected.value : selected.value, selected.unit)\n}\n\n/**\n * Parses a `YYYY-MM-DD` date into a `Date` at local midnight. Returns `undefined`\n * for anything that isn't a real calendar date in that exact format.\n *\n * @param value - The date string to parse\n * @internal\n */\nexport function parseDateOnly(value: string): Date | undefined {\n const match = DATE_ONLY_PATTERN.exec(value)\n if (!match) return undefined\n\n const year = Number(match[1])\n const month = Number(match[2])\n const day = Number(match[3])\n const date = new Date(year, month - 1, day)\n\n // `new Date(2024, 1, 31)` silently rolls over into March, so reject anything\n // that didn't survive the round trip\n const isRoundTrip =\n date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day\n\n return isRoundTrip ? date : undefined\n}\n"],"names":["pluralize","MS_IN","day","hour","minute","month","second","week","year","UNITS","ms","unit","relativeTime","Intl","RelativeTimeFormat","numeric","DATE_ONLY_PATTERN","pad","value","length","String","padStart","selectUnit","elapsed","index","findIndex","undefined","Math","round","formatDateTime","timestamp","date","Date","Number","isFinite","getTime","getFullYear","getMonth","getDate","time","getHours","getMinutes","getSeconds","formatDuration","selected","abs","formatTimeAgo","now","delta","format","parseDateOnly","match","exec","isRoundTrip"],"mappings":"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"}
@@ -1,24 +1,14 @@
1
1
  import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
2
  import { readPackageJson } from '@sanity/cli-core';
4
- import { packageDirectory } from 'package-directory';
5
3
  /**
6
4
  * Get the version of the `@sanity/cli` package.
7
5
  *
8
6
  * @internal
9
7
  * @returns The version of the `@sanity/cli` package.
10
8
  */ export async function getCliVersion() {
11
- // using the meta.url will resolve to the code running from the cli
12
- // this will find the package.json in cli package.
13
- const cliPath = await packageDirectory({
14
- cwd: fileURLToPath(import.meta.url)
15
- });
16
- if (!cliPath) {
17
- throw new Error('Unable to resolve root of @sanity/cli module');
18
- }
19
9
  let pkg;
20
10
  try {
21
- pkg = await readPackageJson(path.join(cliPath, 'package.json'));
11
+ pkg = await readPackageJson(path.join(import.meta.dirname, '..', '..', 'package.json'));
22
12
  } catch (err) {
23
13
  throw new Error(`Unable to read @sanity/cli/package.json: ${err.message}`, {
24
14
  cause: err
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/getCliVersion.ts"],"sourcesContent":["import path from 'node:path'\nimport {fileURLToPath} from 'node:url'\n\nimport {type PackageJson, readPackageJson} from '@sanity/cli-core'\nimport {packageDirectory} from 'package-directory'\n\n/**\n * Get the version of the `@sanity/cli` package.\n *\n * @internal\n * @returns The version of the `@sanity/cli` package.\n */\nexport async function getCliVersion(): Promise<string> {\n // using the meta.url will resolve to the code running from the cli\n // this will find the package.json in cli package.\n const cliPath = await packageDirectory({cwd: fileURLToPath(import.meta.url)})\n if (!cliPath) {\n throw new Error('Unable to resolve root of @sanity/cli module')\n }\n\n let pkg: PackageJson | undefined\n try {\n pkg = await readPackageJson(path.join(cliPath, 'package.json'))\n } catch (err) {\n throw new Error(`Unable to read @sanity/cli/package.json: ${(err as Error).message}`, {\n cause: err,\n })\n }\n\n return pkg.version\n}\n"],"names":["path","fileURLToPath","readPackageJson","packageDirectory","getCliVersion","cliPath","cwd","url","Error","pkg","join","err","message","cause","version"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAC5B,SAAQC,aAAa,QAAO,WAAU;AAEtC,SAA0BC,eAAe,QAAO,mBAAkB;AAClE,SAAQC,gBAAgB,QAAO,oBAAmB;AAElD;;;;;CAKC,GACD,OAAO,eAAeC;IACpB,mEAAmE;IACnE,kDAAkD;IAClD,MAAMC,UAAU,MAAMF,iBAAiB;QAACG,KAAKL,cAAc,YAAYM,GAAG;IAAC;IAC3E,IAAI,CAACF,SAAS;QACZ,MAAM,IAAIG,MAAM;IAClB;IAEA,IAAIC;IACJ,IAAI;QACFA,MAAM,MAAMP,gBAAgBF,KAAKU,IAAI,CAACL,SAAS;IACjD,EAAE,OAAOM,KAAK;QACZ,MAAM,IAAIH,MAAM,CAAC,yCAAyC,EAAE,AAACG,IAAcC,OAAO,EAAE,EAAE;YACpFC,OAAOF;QACT;IACF;IAEA,OAAOF,IAAIK,OAAO;AACpB"}
1
+ {"version":3,"sources":["../../src/util/getCliVersion.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {type PackageJson, readPackageJson} from '@sanity/cli-core'\n\n/**\n * Get the version of the `@sanity/cli` package.\n *\n * @internal\n * @returns The version of the `@sanity/cli` package.\n */\nexport async function getCliVersion(): Promise<string> {\n let pkg: PackageJson | undefined\n try {\n pkg = await readPackageJson(path.join(import.meta.dirname, '..', '..', 'package.json'))\n } catch (err) {\n throw new Error(`Unable to read @sanity/cli/package.json: ${(err as Error).message}`, {\n cause: err,\n })\n }\n\n return pkg.version\n}\n"],"names":["path","readPackageJson","getCliVersion","pkg","join","dirname","err","Error","message","cause","version"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAE5B,SAA0BC,eAAe,QAAO,mBAAkB;AAElE;;;;;CAKC,GACD,OAAO,eAAeC;IACpB,IAAIC;IACJ,IAAI;QACFA,MAAM,MAAMF,gBAAgBD,KAAKI,IAAI,CAAC,YAAYC,OAAO,EAAE,MAAM,MAAM;IACzE,EAAE,OAAOC,KAAK;QACZ,MAAM,IAAIC,MAAM,CAAC,yCAAyC,EAAE,AAACD,IAAcE,OAAO,EAAE,EAAE;YACpFC,OAAOH;QACT;IACF;IAEA,OAAOH,IAAIO,OAAO;AACpB"}
@@ -1150,7 +1150,7 @@
1150
1150
  "required": false
1151
1151
  }
1152
1152
  },
1153
- "description": "Sets up two folders here: ./sanity, a Studio where you write and edit your\ncontent, and ./web, a Next.js website that reads it. Both are already\nconnected to your new project, so you can start them straight away. Use\n--no-scaffold if you just want the project and nothing else.\n\nThe project is real and works immediately, but it is only yours for 72 hours.\nClaim it with a Sanity account before the deadline and everything you have\nbuilt stays exactly as it is. Claiming is free and takes about a minute. Miss\nthe deadline and the project and its content are deleted.\n\nTwo things to keep private: the claim link, because anyone who opens it\nbecomes the owner, and the access token saved in ./sanity/.env.local and\n./web/.env.local, because it can read and change everything in the project.\nKeep both env files out of git, and never put the token in code that runs in\nthe browser.\n\nRun this command with --instructions for the full agent setup guide.",
1153
+ "description": "Sets up two folders here: ./sanity, a Studio where you write and edit your\ncontent, and ./web, a Next.js website that reads it. Both are already\nconnected to your new project, so you can start them straight away. Use\n--no-scaffold if you just want the project and nothing else.\n\nThe project is real and works immediately, but it is only yours for 72 hours.\nClaim it with a Sanity account before the deadline and everything you have\nbuilt stays exactly as it is. Claiming is free and takes about a minute. Miss\nthe deadline and the project and its content are deleted.\n\nTwo things to keep private: the claim link, because anyone who opens it\nbecomes the owner, and the access token saved in ./sanity/.env.local, because\nit can read and change everything in the project. ./web/.env.local has only\nthe project ID and dataset. Keep both env files out of git, and never put the\ntoken in code that runs in the browser.\n\nRun this command with --instructions for the full agent setup guide.",
1154
1154
  "examples": [
1155
1155
  {
1156
1156
  "command": "<%= config.bin %> <%= command.id %>",
@@ -6161,5 +6161,5 @@
6161
6161
  ]
6162
6162
  }
6163
6163
  },
6164
- "version": "8.0.2"
6164
+ "version": "8.1.0"
6165
6165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "8.0.2",
3
+ "version": "8.1.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -79,8 +79,6 @@
79
79
  "@vercel/frameworks": "3.29.0",
80
80
  "chokidar": "^5.0.0",
81
81
  "console-table-printer": "^2.15.0",
82
- "date-fns": "^4.4.0",
83
- "dotenv": "^17.4.2",
84
82
  "eventsource": "^4.1.0",
85
83
  "execa": "^9.6.0",
86
84
  "form-data": "^4.0.5",
@@ -99,7 +97,6 @@
99
97
  "oneline": "^2.0.0",
100
98
  "open": "^11.0.0",
101
99
  "p-map": "^7.0.3",
102
- "package-directory": "^8.2.0",
103
100
  "peek-stream": "^1.1.3",
104
101
  "picomatch": "^4.0.4",
105
102
  "pluralize-esm": "^9.0.5",
@@ -126,8 +123,8 @@
126
123
  "yaml": "^2.9.0",
127
124
  "zod": "^4.4.3",
128
125
  "@sanity/cli-build": "^5.2.1",
129
- "@sanity/cli-core": "^3.1.0",
130
- "@sanity/workbench-cli": "^2.0.1"
126
+ "@sanity/workbench-cli": "^2.0.2",
127
+ "@sanity/cli-core": "^3.1.0"
131
128
  },
132
129
  "devDependencies": {
133
130
  "@eslint/compat": "^2.1.0",
@@ -162,8 +159,8 @@
162
159
  "vitest": "^4.1.10",
163
160
  "@repo/package.config": "0.0.1",
164
161
  "@repo/tsconfig": "3.70.0",
165
- "@sanity/eslint-config-cli": "1.1.3",
166
- "@sanity/cli-test": "11.0.0"
162
+ "@sanity/cli-test": "11.0.0",
163
+ "@sanity/eslint-config-cli": "1.1.3"
167
164
  },
168
165
  "peerDependencies": {
169
166
  "babel-plugin-react-compiler": "*"